Merge pull request #239 from filecoin-project/@textile/staging

@textile/staging - The Filecoin Branch
This commit is contained in:
CAKE 2020-09-17 14:15:05 -07:00 committed by GitHub
commit d30d1be565
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 649 additions and 365 deletions

View File

@ -33,15 +33,7 @@ const constructFilesTreeForNavigation = (library) => {
return {
...library[0],
name: `Data`,
children: [
{
id: "V1_NAVIGATION_ARCHIVE",
decorator: "ARCHIVE",
name: "Archive",
pageTitle: "Archive on Filecoin",
children: null,
},
],
children: [],
};
};
@ -126,14 +118,35 @@ export const generate = ({ library = [], slates = [] }) => [
decorator: "SETTINGS_DEVELOPER",
name: "API",
pageTitle: "Developer API",
children: [],
},
{
id: "V1_NAVIGATION_ARCHIVE",
decorator: "FILECOIN",
name: "Filecoin Testnet",
pageTitle: "Archive on Filecoin",
children: [
{
id: "V1_NAVIGATION_NETWORK",
decorator: "NETWORK",
name: "Filecoin Network",
name: "Network API",
pageTitle: "The Filecoin Network",
children: null,
},
{
id: "V1_NAVIGATION_WALLET",
decorator: "WALLET",
name: "Wallet",
pageTitle: "Your wallet and addresses",
children: [],
},
{
id: "V1_NAVIGATION_FILECOIN_SETTINGS",
decorator: "SETTINGS",
name: "Deal settings",
pageTitle: "Deal Settings.",
children: null,
},
],
},
{

View File

@ -68,8 +68,13 @@ export const getCIDFromIPFS = (url) => {
return cid;
};
export const formatAsFilecoinConversion = (number) => {
number = number / Math.pow(10, 18);
return `${formatAsFilecoin(number)}`;
};
export const formatAsFilecoin = (number) => {
return `${number} FIL`;
return `${formatNumber(number)} FIL`;
};
export const pluralize = (text, count) => {
@ -200,7 +205,10 @@ export const createSlug = (text, base = "untitled") => {
return base;
}
text = text.toString().toLowerCase().trim();
text = text
.toString()
.toLowerCase()
.trim();
const sets = [
{ to: "a", from: "[ÀÁÂÃÅÆĀĂĄẠẢẤẦẨẪẬẮẰẲẴẶ]" },

View File

@ -285,37 +285,32 @@ export const ExpandArrow = (props) => (
</svg>
);
// export const Wallet = (props) => (
// <svg
// viewBox="0 0 24 24"
// xmlns="http://www.w3.org/2000/svg"
// height={props.height}
// style={props.style}
// >
// <g
// fill="none"
// stroke="currentColor"
// strokeLinecap="round"
// strokeLinejoin="round"
// >
// <rect height="21" rx="1.5" width="22.5" x=".75" y=".75" />
// <rect height="15" rx="1.5" width="15" x="5.25" y="3.75" />
// <path d="m3.75 21.75v1.5" />
// <path d="m20.25 21.75v1.5" />
// <path d="m3.75 8.25h3" />
// <path d="m3.75 14.25h3" />
// <circle cx="12.75" cy="11.25" r="2.7" />
// <path d="m12.75 8.55v-1.8" />
// <path d="m12.75 15.75v-1.8" />
// <path d="m15.45 11.25h1.8" />
// <path d="m8.25 11.25h1.8" />
// <path d="m9.986 14.432 1.075-1.075" />
// <path d="m9.986 8.068 1.075 1.075" />
// <path d="m15.514 14.432-1.075-1.075" />
// <path d="m15.514 8.068-1.075 1.075" />
// </g>
// </svg>
// );
export const OldWallet = (props) => (
<svg
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
height={props.height}
style={props.style}
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
>
<path d="m22.5 20.25v1.227l.00000001.00000013c.0413973.931766-.675662 1.7229-1.607 1.773h-8.036-.00000003c-.931338-.0501042-1.6484-.841234-1.607-1.773v-6.2l-.00000001-.00000016c-.0436626-.933293.674031-1.72691 1.607-1.777h8.036-.00000007c.931338.0501042 1.6484.841234 1.607 1.773v.977" />
<path d="m21.04 13.507-1.282-2.565.00000003.00000007c-.19778-.395089-.678395-.555039-1.07348-.357259-.00017232.00008626-.00034461.00017259-.00051687.00025898l-5.827 2.915" />
<path d="m22.45 20.25h-.00000003c.441828.00000002.8-.358172.8-.8v-2.4c0-.441828-.358172-.8-.8-.8h-2-.00000009c-1.10457.00000005-2 .895431-2 2 .00000005 1.10457.895431 2 2 2z" />
<path d="m5.737 21.347-.00000013-.00000008c-5.16103-3.46042-6.53965-10.4495-3.07924-15.6105 3.46042-5.16103 10.4495-6.53965 15.6105-3.07924 2.14401 1.43754 3.7285 3.5686 4.48772 6.03575.0486667.161333.0943333.322333.137.483" />
<path d="m7.981 19.4-.00000183-.00000581c-1.20611-3.82323-1.51577-7.87283-.904998-11.835l-.00000004.00000035c.268804-2.2946 1.0232-4.50564 2.213-6.486" />
<path d="m.77 11.25h10.48" />
<path d="m2.48 6h19.04" />
<path d="m1.69 16.5h5.55" />
<path d="m14.711 1.079.00000073.00000119c1.00053 1.62525 1.6795 3.42757 2 5.309.118.58.213333 1.159.286 1.737" />
</g>
</svg>
);
export const NavigationArrow = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>

View File

@ -87,7 +87,7 @@ const SCENES = {
LOCAL_DATA: <SceneLocalData />,
NETWORK: <SceneSentinel />,
DIRECTORY: <SceneDirectory />,
ARCHIVE: <SceneArchive />,
FILECOIN: <SceneArchive />,
};
export default class ApplicationPage extends React.Component {

View File

@ -12,15 +12,16 @@ const IconMap = {
NETWORK: <SVG.Activity height="20px" />,
DIRECTORY: <SVG.Directory height="20px" />,
FOLDER: <SVG.Folder height="20px" />,
WALLET: <SVG.Wallet height="20px" />,
WALLET: <SVG.OldWallet height="20px" />,
DEALS: <SVG.Deals height="20px" />,
SLATES: <SVG.Layers height="20px" />,
SLATE: <SVG.Slate height="20px" />,
LOCAL_DATA: <SVG.HardDrive height="20px" />,
PROFILE_PAGE: <SVG.ProfileUser height="20px" />,
SETTINGS_DEVELOPER: <SVG.Tool height="20px" />,
SETTINGS: <SVG.Layers height="20px" />,
DIRECTORY: <SVG.Directory height="20px" />,
ARCHIVE: <SVG.Layers height="20px" />,
FILECOIN: <SVG.Wallet height="20px" />,
};
const STYLES_NAVIGATION = css`

View File

@ -5,8 +5,7 @@ import * as System from "~/components/system";
import { css } from "@emotion/react";
// NOTE(jim): 10 GB
const MAX_IN_BYTES = 10737418240;
const MAX_IN_BYTES = 10737418240 * 50;
const STYLES_CONTAINER = css`
border-radius: 4px;

View File

@ -11,6 +11,7 @@ const STYLES_SECTION = css`
border-radius: 4px;
font-weight: 400;
margin-top: 24px;
white-space: pre-wrap;
:first-child {
margin-top: 0px;

View File

@ -9,17 +9,14 @@ import { css } from "@emotion/react";
import { dispatchCustomEvent } from "~/common/custom-events";
export default class SidebarFilecoinArchive extends React.Component {
state = { response: "" };
async componentDidMount() {}
_handleMakeDeal = async () => {
const response = await Actions.archive();
console.log(response);
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: { message: "Deal archiving is still under development" },
},
});
this.setState({ response });
};
_handleSubmit = async (e) => {
@ -29,7 +26,8 @@ export default class SidebarFilecoinArchive extends React.Component {
this.props.onSidebarLoading(true);
await this._handleMakeDeal();
await this.props.onSubmit({});
await this.props.onRehydrate();
this.props.onSidebarLoading(false);
};
_handleCancel = () => {
@ -41,8 +39,6 @@ export default class SidebarFilecoinArchive extends React.Component {
};
render() {
console.log(this.props);
return (
<React.Fragment>
<System.P
@ -66,6 +62,10 @@ export default class SidebarFilecoinArchive extends React.Component {
>
Make storage deal
</System.ButtonPrimary>
<div style={{ whiteSpace: "pre-wrap", marginTop: 48 }}>
{JSON.stringify(this.state.response, null, 2)}
</div>
</React.Fragment>
);
}

View File

@ -6,4 +6,4 @@ export const GITHUB_URL = "https://github.com/filecoin-project/slate";
export const ANALYTICS_URL = "https://slate-stats-dev.azurewebsites.net/";
// NOTE(jim): 1 GB from Ignacio
export const TEXTILE_ACCOUNT_BYTE_LIMIT = 1073741824;
export const TEXTILE_ACCOUNT_BYTE_LIMIT = 1073741824 * 50;

View File

@ -1,3 +1,9 @@
// TODO(jim): The claim is that we can remove this
// and the package.json depdencies at some later time.
import { grpc } from "@improbable-eng/grpc-web";
import { WebsocketTransport } from "@textile/grpc-transport";
grpc.setDefaultTransport(WebsocketTransport());
import * as Utilities from "~/node_common/utilities";
import * as Data from "~/node_common/data";
import * as Constants from "~/node_common/constants";
@ -73,11 +79,6 @@ export const getById = async ({ id }) => {
return {
...Serializers.user(user),
type: "VIEWER",
// NOTE(jim): The only safe viewer fields to expose.
settings: {
deals_auto_approve: user.data.settings_deals_auto_approve,
},
library: user.data.library,
// NOTE(jim): Remaining data.
@ -94,3 +95,77 @@ export const getById = async ({ id }) => {
pendingTrusted: r4.serializedPendingTrusted,
};
};
export const getTextileById = async ({ id }) => {
const user = await Data.getUserById({
id,
});
if (!user) {
return null;
}
if (user.error) {
return null;
}
let info = {};
let status = {};
let errors = [];
let jobs = [];
const {
buckets,
bucketKey,
bucketName,
bucketRoot,
} = await Utilities.getBucketAPIFromUserToken(user.data.tokens.api);
const {
power,
powerInfo,
powerHealth,
} = await Utilities.getPowergateAPIFromUserToken(user.data.tokens.api);
try {
buckets.archiveWatch(bucketRoot.root.key, (job) => {
if (!job) {
return;
}
job.id = job.id ? job.id : "UNDEFINED";
jobs.push(job);
});
} catch (e) {
errors.push({ decorator: "JOB", message: e.message, code: e.code });
}
try {
info = await buckets.archiveInfo(bucketRoot.root.key);
} catch (e) {
errors.push({ decorator: "INFO", message: e.message, code: e.code });
}
try {
status = await buckets.archiveStatus(bucketRoot.root.key);
} catch (e) {
errors.push({ decorator: "STATUS", message: e.message, code: e.code });
}
console.log(jobs);
return {
type: "VIEWER_FILECOIN",
settings: {
deals_auto_approve: user.data.settings_deals_auto_approve,
},
powerInfo,
powerHealth,
archive: {
info,
status,
errors,
jobs,
},
};
};

View File

@ -5,7 +5,7 @@ import * as Constants from "~/node_common/constants";
import JWT from "jsonwebtoken";
import BCrypt from "bcrypt";
import { Buckets, PrivateKey } from "@textile/hub";
import { Buckets, PrivateKey, Pow } from "@textile/hub";
const BUCKET_NAME = "data";
@ -79,6 +79,21 @@ export const parseAuthHeader = (value) => {
return matches && { scheme: matches[1], value: matches[2] };
};
// NOTE(jim): Requires @textile/hub
export const getPowergateAPIFromUserToken = async (token) => {
const identity = await PrivateKey.fromString(token);
const power = await Pow.withKeyInfo(TEXTILE_KEY_INFO);
await power.getToken(identity);
const { info } = await power.info();
const health = await power.health();
return {
power,
powerHealth: health,
powerInfo: info,
};
};
// NOTE(jim): Requires @textile/hub
export const getBucketAPIFromUserToken = async (token) => {
const identity = await PrivateKey.fromString(token);

View File

@ -43,9 +43,11 @@
"@emotion/css": "11.0.0-next.12",
"@emotion/react": "11.0.0-next.12",
"@emotion/server": "11.0.0-next.12",
"@improbable-eng/grpc-web": "^0.13.0",
"@react-hook/window-size": "^3.0.7",
"@slack/webhook": "^5.0.3",
"@textile/hub": "^0.6.2",
"@textile/grpc-transport": "0.0.3",
"@textile/hub": "^0.7.1",
"babel-plugin-module-resolver": "^4.0.0",
"bcrypt": "^5.0.0",
"body-parser": "^1.19.0",

View File

@ -34,16 +34,20 @@ export default async (req, res) => {
bucketRoot,
} = await Utilities.getBucketAPIFromUserToken(user.data.tokens.api);
console.log(bucketRoot.root);
// bucketRoot.root.key
// bucketRoot.root.path
const response = await buckets.archive(bucketRoot.root.key);
console.log(response);
let response = {};
let error = {};
try {
response = await buckets.archive(bucketRoot.root.key);
} catch (e) {
error.message = e.message;
error.code = e.code;
}
return res.status(200).send({
decorator: "SERVER_BUCKET_ARCHIVE_DEAL",
data: {},
data: { response, error },
});
};

27
pages/api/network.js Normal file
View File

@ -0,0 +1,27 @@
import * as ViewerManager from "~/node_common/managers/viewer";
import * as Utilities from "~/node_common/utilities";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
return res
.status(500)
.send({ decorator: "SERVER_FILECOIN_NETWORK_FAILURE", error: true });
}
const data = await ViewerManager.getTextileById({ id });
if (!data) {
return res
.status(500)
.send({
decorator: "SERVER_FILECOIN_NETWORK_ERROR",
error: true,
data: null,
});
}
return res
.status(200)
.send({ decorator: "SERVER_FILECOIN_NETWORK", success: true, data });
};

View File

@ -49,8 +49,26 @@ export default async (req, res) => {
}
}
// TODO(jim): Do not expose how many times you are salting
// in OSS, add a random value as an environment variable.
// TODO(jim): POWERGATE
// Doesn't actually work yet.
if (req.body.type === "SET_DEFAULT_STORAGE_CONFIG") {
const {
power,
powerInfo,
powerHealth,
} = await Utilities.getPowergateAPIFromUserToken(user.data.tokens.api);
let data;
try {
data = await power.ffs.setDefaultStorageConfig(req.body.config);
} catch (e) {
console.log(e);
return res
.status(500)
.send({ decorator: "SERVER_USER_UPDATE_SETTINGS_CONFIG", error: true });
}
}
if (req.body.type == "CHANGE_PASSWORD") {
if (!Validations.password(req.body.password)) {
return res

View File

@ -1,46 +1,127 @@
import * as React from "react";
import * as System from "~/components/system";
import * as Constants from "~/common/constants";
import { css } from "@emotion/react";
import { LoaderSpinner } from "~/components/system/components/Loaders";
import Section from "~/components/core/Section";
import ScenePage from "~/components/core/ScenePage";
import ScenePageHeader from "~/components/core/ScenePageHeader";
const STYLES_LABEL = css`
font-family: ${Constants.font.semiBold};
font-size: 16px;
margin-bottom: 16px;
`;
export default class SceneArchive extends React.Component {
state = {};
async componentDidMount() {
let networkViewer;
try {
const response = await fetch("/api/network");
const json = await response.json();
networkViewer = json.data;
} catch (e) {}
this.setState({
networkViewer,
});
}
render() {
return (
<ScenePage>
<ScenePageHeader title="Archive">
Slate provides a way to archive your data onto the Filecoin network.
Once your archive is sealed on the Filecoin network, it will be shown
here.
<ScenePageHeader title="Filecoin Testnet">
Filecoin is currently in Testnet phase. You can use this tab to test
and verify Filecoin deals with Testnet FIL.
</ScenePageHeader>
<Section
title="Archives"
onAction={this.props.onAction}
buttons={[
{
name: "Archive",
type: "SIDEBAR",
value: "SIDEBAR_FILECOIN_ARCHIVE",
},
]}
>
<System.Table
data={{
columns: [
{this.state.networkViewer ? (
<React.Fragment>
<Section
title="Filecoin Testnet trusted miners"
style={{ marginTop: 48 }}
>
<System.Table
data={{
columns: [
{
key: "miner",
name: "Miner ID",
width: "100%",
},
],
rows: this.state.networkViewer.powerInfo.defaultStorageConfig.cold.filecoin.trustedMinersList.map(
(miner) => {
return {
miner,
};
}
),
}}
/>
</Section>
<Section
title="Filecoin Testnet archive deals"
onAction={this.props.onAction}
buttons={[
{
key: "job",
name: "Job ID",
width: "100%",
name: "Make Filecoin Testnet deal",
type: "SIDEBAR",
value: "SIDEBAR_FILECOIN_ARCHIVE",
},
],
rows: [],
}}
/>
</Section>
]}
>
<div style={{ padding: 24 }}>
<div css={STYLES_LABEL}>Info</div>
{JSON.stringify(this.state.networkViewer.archive.info, null, 2)}
<div css={STYLES_LABEL} style={{ marginTop: 24 }}>
Status
</div>
{JSON.stringify(
this.state.networkViewer.archive.status,
null,
2
)}
<div css={STYLES_LABEL} style={{ marginTop: 24 }}>
Errors
</div>
{JSON.stringify(
this.state.networkViewer.archive.errors,
null,
2
)}
</div>
</Section>
<Section title="Job history" style={{ marginTop: 48 }}>
<System.Table
data={{
columns: [
{
key: "job",
name: "Job Message",
width: "100%",
},
],
rows: this.state.networkViewer.archive.jobs.map((job) => {
return {
job: job.msg,
};
}),
}}
/>
</Section>
</React.Fragment>
) : (
<LoaderSpinner style={{ marginTop: 48, height: 32, width: 32 }} />
)}
</ScenePage>
);
}

View File

@ -26,12 +26,13 @@ export default class SceneSentinel extends React.Component {
render() {
return (
<ScenePage>
<ScenePageHeader title="Filecoin Network">
Slate provides access to live data on the Filecoin Network through
Sentinel. Each of these API endpoints can be used programatically.
<ScenePageHeader title="Filecoin Testnet API">
Slate provides access to live data on the Filecoin Testnet Network
through Sentinel. Each of these API endpoints can be used
programatically.
</ScenePageHeader>
<Section title="Filecoin network API routes">
<Section title="Filecoin Testnet API routes">
<System.Table
data={{
columns: [

View File

@ -3,8 +3,11 @@ import * as Actions from "~/common/actions";
import * as System from "~/components/system";
import { css } from "@emotion/react";
import { LoaderSpinner } from "~/components/system/components/Loaders";
import Section from "~/components/core/Section";
import ScenePage from "~/components/core/ScenePage";
import ScenePageHeader from "~/components/core/ScenePageHeader";
const STYLES_GROUP = css`
display: flex;
@ -36,19 +39,52 @@ const STYLES_RIGHT = css`
flex-shrink: 0;
`;
const createState = (config) => {
return {
settings_hot_enabled: config.hot.enabled,
settings_hot_allow_unfreeze: config.hot.allowUnfreeze,
settings_hot_ipfs_add_timeout: config.hot.ipfs.addTimeout,
settings_cold_enabled: config.cold.enabled,
settings_cold_default_address: config.cold.filecoin.addr,
settings_cold_default_duration: config.cold.filecoin.dealMinDuration,
settings_cold_default_replication_factor: config.cold.filecoin.repFactor,
settings_cold_default_excluded_miners:
config.cold.filecoin.excludedMinersList,
settings_cold_default_trusted_miners:
config.cold.filecoin.trustedMinersList,
settings_cold_default_max_price: config.cold.filecoin.maxPrice,
settings_cold_default_auto_renew: config.cold.filecoin.renew.enabled,
settings_cold_default_auto_renew_max_price:
config.cold.filecoin.renew.threshold,
};
};
export default class SceneSettings extends React.Component {
state = { ...this.props.viewer };
state = {};
async componentDidMount() {
let networkViewer;
try {
const response = await fetch("/api/network");
const json = await response.json();
networkViewer = json.data;
} catch (e) {}
this.setState({
networkViewer,
...createState(networkViewer.powerInfo.defaultStorageConfig),
});
}
_deferredSave = null;
_handleSave = async () => {
this.setState({ loading: true });
return alert(`Changing settings is currently disabled.`);
await Actions.updateViewer({
type: "SET_DEFAULT_STORAGE_CONFIG",
data: {
settings_deals_auto_approve: this.state.settings_deals_auto_approve,
},
config: {
hot: {
enabled: this.state.settings_hot_enabled,
@ -88,179 +124,175 @@ export default class SceneSettings extends React.Component {
render() {
return (
<ScenePage>
<System.H1>Filecoin settings</System.H1>
<ScenePageHeader title="Filecoin Testnet settings (read-only)">
Filecoin is currently in Testnet phase. You can use this page to view
the default settings. Once your deal settings can be configured, you
can edit your settings whenever you like.
</ScenePageHeader>
<System.H2 style={{ marginTop: 48 }}>Storage defaults</System.H2>
{this.state.networkViewer ? (
<React.Fragment>
<Section title="Trusted miners" style={{ marginTop: 48 }}>
<System.Table
data={{
columns: [
{
key: "miner",
name: "Miner ID",
width: "100%",
},
],
rows: this.state.settings_cold_default_trusted_miners.map(
(miner) => {
return {
miner,
};
}
),
}}
/>
</Section>
<div css={STYLES_GROUP} style={{ marginTop: 32 }}>
<div css={STYLES_LEFT}>
<System.DescriptionGroup
label="Automatically approve deals"
tooltip="When this is enabled you will skip the confirmation step, but if you do not have enough Filecoin you will receive a warning."
description="Enable this if every storage deal should be automatically approved to skip confirmation."
/>
</div>
<div css={STYLES_RIGHT}>
<System.Toggle
name="settings_deals_auto_approve"
onChange={this._handleChange}
active={this.state.settings_deals_auto_approve}
/>
</div>
</div>
<div css={STYLES_GROUP} style={{ marginTop: 48 }}>
<div css={STYLES_LEFT}>
<System.DescriptionGroup
label="Enable cold storage"
tooltip="Placeholder"
description="By enabling cold storage, every time you make a deal your data will be stored on the Filecoin Network."
/>
</div>
<div css={STYLES_RIGHT}>
<System.Toggle
name="settings_cold_enabled"
onChange={this._handleChange}
active={this.state.settings_cold_enabled}
/>
</div>
</div>
<div style={{ marginTop: 24 }}>
<System.ButtonPrimary
loading={this.state.loading}
onClick={this._handleSave}
>
Save
</System.ButtonPrimary>
</div>
{this.state.settings_cold_enabled ? (
<div css={STYLES_SUBGROUP}>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Default Filecoin address (Read only)"
description="Default Filecoin address."
name="settings_cold_default_duration"
readOnly
type="text"
value={this.state.settings_cold_default_address}
onChange={this._handleChange}
/>
<div css={STYLES_GROUP} style={{ marginTop: 48 }}>
<div css={STYLES_LEFT}>
<System.DescriptionGroup
label="Enable cold storage"
tooltip="Placeholder"
description="By enabling cold storage, every time you make a deal your data will be stored on the Filecoin Network."
/>
</div>
<div css={STYLES_RIGHT}>
<System.Toggle
name="settings_cold_enabled"
onChange={this._handleChange}
active={this.state.settings_cold_enabled}
/>
</div>
</div>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Default Filecoin deal duration"
description="Current deal duration is in epochs but should change to months/weeks/days."
name="settings_cold_default_duration"
type="number"
value={this.state.settings_cold_default_duration}
placeholder="Type in epochs (~25 seconds)"
onChange={this._handleChange}
/>
{this.state.settings_cold_enabled ? (
<div css={STYLES_SUBGROUP}>
<System.SelectMenu
containerStyle={{ marginTop: 24 }}
label="Default Filecoin address"
description="Default Filecoin address settings description."
tooltip="Placeholder."
name="settings_cold_default_address"
value={this.state.settings_cold_default_address}
category="address"
onChange={this._handleChange}
options={this.state.addresses}
/>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Default Filecoin replication factor"
description=""
name="settings_cold_default_replication_factor"
value={this.state.settings_cold_default_replication_factor}
placeholder="Type in amount of miners"
onChange={this._handleChange}
/>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Default Filecoin deal duration"
description="Default Filecoin deal duration settings description. Current deal duration is in epochs but should change to months/weeks/days."
tooltip="Placeholder."
name="settings_cold_default_duration"
type="number"
value={this.state.settings_cold_default_duration}
placeholder="Type in epochs (~25 seconds)"
onChange={this._handleChange}
/>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Max Filecoin price."
description="Set the maximum Filecoin price you're willing to pay."
name="settings_cold_default_max_price"
value={this.state.settings_cold_default_max_price}
placeholder="Type in amount of Filecoin"
onChange={this._handleChange}
/>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Default Filecoin replication factor"
description="Default Filecoin replication factor settings description."
tooltip="Placeholder."
name="settings_cold_default_replication_factor"
value={this.state.settings_cold_default_replication_factor}
placeholder="Type in amount of miners"
onChange={this._handleChange}
/>
<System.CheckBox
style={{ marginTop: 48 }}
name="settings_cold_default_auto_renew"
value={this.state.settings_cold_default_auto_renew}
onChange={this._handleChange}
>
Enable auto renew for Filecoin Network deals.
</System.CheckBox>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Max Filecoin price."
description="Set the maximum Filecoin price you're willing to pay."
tooltip="Placeholder."
name="settings_cold_default_max_price"
value={this.state.settings_cold_default_max_price}
placeholder="Type in amount of Filecoin"
onChange={this._handleChange}
/>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Max Filecoin deal auto renew price."
description="Set the maximum Filecoin price you're willing to pay for auto renew."
name="settings_cold_default_auto_renew_max_price"
value={this.state.settings_cold_default_auto_renew_max_price}
placeholder="Type in amount of Filecoin"
onChange={this._handleChange}
/>
</div>
) : null}
<div style={{ marginTop: 32 }}>
<System.ButtonPrimary
loading={this.state.loading}
onClick={this._handleSave}
>
Save
</System.ButtonPrimary>
</div>
<System.CheckBox
style={{ marginTop: 48 }}
name="settings_cold_default_auto_renew"
value={this.state.settings_cold_default_auto_renew}
onChange={this._handleChange}
>
Enable auto renew for Filecoin Network deals.
</System.CheckBox>
<div css={STYLES_GROUP} style={{ marginTop: 32 }}>
<div css={STYLES_LEFT}>
<System.DescriptionGroup
label="Enable hot storage"
description="By enabling hot storage, every time you make a deal your data will be stored on IPFS."
/>
</div>
<div css={STYLES_RIGHT}>
<System.Toggle
name="settings_hot_enabled"
onChange={this._handleChange}
active={this.state.settings_hot_enabled}
/>
</div>
</div>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Max Filecoin deal auto renew price."
description="Set the maximum Filecoin price you're willing to pay for auto renew."
tooltip="Placeholder."
name="settings_cold_default_auto_renew_max_price"
value={this.state.settings_cold_default_auto_renew_max_price}
placeholder="Type in amount of Filecoin"
onChange={this._handleChange}
/>
</div>
) : null}
<div style={{ marginTop: 32 }}>
<System.ButtonPrimary
loading={this.state.loading}
onClick={this._handleSave}
>
Save
</System.ButtonPrimary>
</div>
{this.state.settings_hot_enabled ? (
<div css={STYLES_SUBGROUP}>
<System.CheckBox
style={{ marginTop: 48 }}
name="settings_hot_allow_unfreeze"
value={this.state.settings_hot_allow_unfreeze}
onChange={this._handleChange}
>
IPFS allow unfreeze setting description.
</System.CheckBox>
<div css={STYLES_GROUP} style={{ marginTop: 32 }}>
<div css={STYLES_LEFT}>
<System.DescriptionGroup
label="Enable hot storage"
tooltip="Placeholder"
description="By enabling hot storage, every time you make a deal your data will be stored on IPFS."
/>
</div>
<div css={STYLES_RIGHT}>
<System.Toggle
name="settings_hot_enabled"
onChange={this._handleChange}
active={this.state.settings_hot_enabled}
/>
</div>
</div>
{this.state.settings_hot_enabled ? (
<div css={STYLES_SUBGROUP}>
<System.CheckBox
style={{ marginTop: 48 }}
name="settings_hot_allow_unfreeze"
value={this.state.settings_hot_allow_unfreeze}
onChange={this._handleChange}
>
IPFS allow unfreeze setting description.
</System.CheckBox>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Add timeout"
description="Add IPFS timeout setting description."
tooltip="Placeholder."
name="settings_hot_ipfs_add_timeout"
value={this.state.settings_hot_ipfs_add_timeout}
placeholder="Type in seconds"
onChange={this._handleChange}
/>
</div>
) : null}
<div style={{ marginTop: 32 }}>
<System.ButtonPrimary
loading={this.state.loading}
onClick={this._handleSave}
>
Save
</System.ButtonPrimary>
</div>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Add timeout"
description="Add IPFS timeout setting description."
name="settings_hot_ipfs_add_timeout"
value={this.state.settings_hot_ipfs_add_timeout}
placeholder="Type in seconds"
onChange={this._handleChange}
/>
</div>
) : null}
<div style={{ marginTop: 32 }}>
<System.ButtonPrimary
loading={this.state.loading}
onClick={this._handleSave}
>
Save
</System.ButtonPrimary>
</div>
</React.Fragment>
) : (
<LoaderSpinner style={{ marginTop: 48, height: 32, width: 32 }} />
)}
</ScenePage>
);
}

View File

@ -6,6 +6,7 @@ import * as System from "~/components/system";
import { css } from "@emotion/react";
import { dispatchCustomEvent } from "~/common/custom-events";
import { LoaderSpinner } from "~/components/system/components/Loaders";
import Section from "~/components/core/Section";
import ScenePage from "~/components/core/ScenePage";
@ -85,6 +86,22 @@ const STYLES_ITEM_GROUP = css`
`;
export default class SceneWallet extends React.Component {
state = {};
async componentDidMount() {
let networkViewer;
try {
const response = await fetch("/api/network");
const json = await response.json();
networkViewer = json.data;
} catch (e) {}
console.log(networkViewer);
this.setState({
networkViewer,
});
}
state = { table_transaction: null, visible: false };
_handleChange = (e) => {
@ -109,133 +126,128 @@ export default class SceneWallet extends React.Component {
};
render() {
let addresses = {};
let lastAddress;
// TODO(jim): Temporary because of read only Filecoin Addresses
const { networkViewer } = this.state;
this.props.viewer.addresses.forEach((a) => {
addresses[a.address] = a;
lastAddress = a.address;
});
const addressMap = {};
const addresses = [];
let selected = null;
const currentAddress = this.props.selected.address
? addresses[this.props.selected.address]
: addresses[lastAddress];
if (networkViewer) {
networkViewer.powerInfo.balancesList.forEach((a) => {
addressMap[a.addr.addr] = { ...a.addr, balance: a.balance };
addresses.push({ ...a.addr, balance: a.balance });
});
// TODO(jim):
// Capture this state.
if (!currentAddress) {
return null;
}
if (addresses.length) {
selected = addresses[0];
}
let transactions = [];
if (currentAddress.transactions) {
transactions = [...currentAddress.transactions];
let transactions = [];
if (selected.transactions) {
transactions = [...selected.transactions];
}
}
return (
<ScenePage>
<ScenePageHeader title="Wallet [WIP]">
This scene is currently a work in progress.
<ScenePageHeader title="Filecoin Testnet wallet">
This is your testnet wallet address. It is prefilled by Textile to
help test the Filecoin Testnet.
</ScenePageHeader>
<Section
onAction={this.props.onAction}
onNavigateTo={this.props.onNavigateTo}
title="Addresses"
style={{ maxWidth: `568px`, minWidth: "auto" }}
buttons={[
{networkViewer ? (
<Section
onAction={this.props.onAction}
onNavigateTo={this.props.onNavigateTo}
title="Your Filecoin Testnet address"
style={{ maxWidth: `568px`, minWidth: "auto" }}
buttons={
[
/*
{
name: "Create a new address",
type: "SIDEBAR",
value: "SIDEBAR_CREATE_WALLET_ADDRESS",
},
]}
>
<div css={STYLES_GROUP}>
*/
]
}
>
{/* <div css={STYLES_GROUP}>
<System.SelectMenu
label="Select your address"
name="address"
value={this.props.selected.address}
value={selected.addr}
category="address"
onChange={this._handleWalletChange}
options={this.props.viewer.addresses}
options={addresses}
/>
</div>
</div> */}
<div css={STYLES_ROW} style={{ marginTop: 24 }}>
<div css={STYLES_TEXT}>
<div>
<div css={STYLES_FOCUS}>
{this.state.visible ? (
currentAddress.address
) : (
<span css={STYLES_FOCUS_EMPAHSIS}>Hidden</span>
)}
</div>
<div css={STYLES_SUBTEXT}>Filecoin address</div>
</div>
<div style={{ marginTop: 24 }}>
<div css={STYLES_FOCUS}>
{currentAddress.name}{" "}
{this.props.viewer.settings_cold_default_address ===
currentAddress.address ? (
<strong css={STYLES_FOCUS_EMPAHSIS}>(Primary)</strong>
) : null}
</div>
<div css={STYLES_SUBTEXT}>Filecoin address alias</div>
</div>
<div css={STYLES_ITEM_GROUP}>
<div css={STYLES_ITEM}>
<div css={STYLES_ROW} style={{ marginTop: 24 }}>
<div css={STYLES_TEXT}>
<div>
<div css={STYLES_FOCUS}>
{Strings.formatNumber(currentAddress.balance)}
{this.state.visible ? (
selected.addr
) : (
<span css={STYLES_FOCUS_EMPAHSIS}>Hidden</span>
)}
</div>
<div css={STYLES_SUBTEXT}>Filecoin</div>
<div css={STYLES_SUBTEXT}>Filecoin address</div>
</div>
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>{currentAddress.type}</div>
<div css={STYLES_SUBTEXT}>Address type</div>
<div style={{ marginTop: 24 }}>
<div css={STYLES_FOCUS}>
{selected.name}{" "}
{networkViewer.settings_cold_default_address ===
selected.addr ? (
<strong css={STYLES_FOCUS_EMPAHSIS}>(Primary)</strong>
) : null}
</div>
<div css={STYLES_SUBTEXT}>Filecoin address alias</div>
</div>
<div css={STYLES_ITEM_GROUP}>
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>
{Strings.formatAsFilecoinConversion(selected.balance)}
</div>
<div css={STYLES_SUBTEXT}>Filecoin</div>
</div>
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>{selected.type}</div>
<div css={STYLES_SUBTEXT}>Address type</div>
</div>
</div>
</div>
<div style={{ marginTop: 24 }}>
<System.ButtonPrimary
onClick={() =>
this.props.onAction({
name: "Send Filecoin",
type: "SIDEBAR",
value: "SIDEBAR_WALLET_SEND_FUNDS",
})
}
<div css={STYLES_ACTIONS}>
<span
css={STYLES_CIRCLE_BUTTON}
onClick={this._handleMakeAddressVisible}
style={{
marginRight: 16,
backgroundColor: this.state.visible
? null
: Constants.system.brand,
}}
>
Send Filecoin
</System.ButtonPrimary>
<SVG.Privacy height="16px" />
</span>
<span
css={STYLES_CIRCLE_BUTTON}
onClick={() => this._handleCopy(selected.address)}
>
<SVG.CopyAndPaste height="16px" />
</span>
</div>
</div>
<div css={STYLES_ACTIONS}>
<span
css={STYLES_CIRCLE_BUTTON}
onClick={this._handleMakeAddressVisible}
style={{
marginRight: 16,
backgroundColor: this.state.visible
? null
: Constants.system.brand,
}}
>
<SVG.Privacy height="16px" />
</span>
<span
css={STYLES_CIRCLE_BUTTON}
onClick={() => this._handleCopy(currentAddress.address)}
>
<SVG.CopyAndPaste height="16px" />
</span>
</div>
</div>
</Section>
</Section>
) : (
<LoaderSpinner style={{ marginTop: 48, height: 32, width: 32 }} />
)}
</ScenePage>
);
}