Show ENS records

This commit is contained in:
Anthony Laibe 2018-08-14 10:23:48 +01:00 committed by Pascal Precht
parent b6156e7632
commit 0f7a30f530
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
15 changed files with 357 additions and 102 deletions

View File

@ -142,7 +142,7 @@ export const ensRecord = {
export const ENS_RECORDS = createRequestTypes('ENS_RECORDS'); export const ENS_RECORDS = createRequestTypes('ENS_RECORDS');
export const ensRecords = { export const ensRecords = {
post: (name, address) => action(ENSENS_RECORDS_RECORD[REQUEST], {name, address}), post: (name, address) => action(ENS_RECORDS[REQUEST], {name, address}),
success: (_record, payload) => action(ENS_RECORDS[SUCCESS], {ensRecords: [{name: payload.name, address: payload.address}]}), success: (_record, payload) => action(ENS_RECORDS[SUCCESS], {ensRecords: [{name: payload.name, address: payload.address}]}),
failure: (error) => action(ENS_RECORDS[FAILURE], {error}) failure: (error) => action(ENS_RECORDS[FAILURE], {error})
}; };

View File

@ -84,7 +84,7 @@ export function fetchContractProfile(payload) {
return get(`/profiler/${payload.contractName}`); return get(`/profiler/${payload.contractName}`);
} }
export function fetchENSRecord(payload) { export function fetchEnsRecord(payload) {
if (payload.name) { if (payload.name) {
return get('/ens/resolve', {params: payload}); return get('/ens/resolve', {params: payload});
} else { } else {
@ -92,7 +92,7 @@ export function fetchENSRecord(payload) {
} }
} }
export function postENSRecord(payload) { export function postEnsRecord(payload) {
return post('/ens/register', payload); return post('/ens/register', payload);
} }

View File

@ -0,0 +1,44 @@
import React, {Component} from 'react';
import {
Button,
Form
} from "tabler-react";
import PropTypes from 'prop-types';
class EnsLookup extends Component {
constructor(props) {
super(props);
this.state = {
address: ''
};
}
handleChange(e) {
this.setState({address: e.target.value});
}
handleLookup() {
this.props.lookup(this.state.address);
}
render(){
return (
<React.Fragment>
<h3>Lookup</h3>
<Form.FieldSet>
<Form.Group>
<Form.Input placeholder="Enter an address" onChange={e => this.handleChange(e)}/>
</Form.Group>
<Button color="primary" onClick={() => this.handleLookup()}>Lookup</Button>
</Form.FieldSet>
</React.Fragment>
);
}
}
EnsLookup.propTypes = {
lookup: PropTypes.func
};
export default EnsLookup;

View File

@ -0,0 +1,41 @@
import React from 'react';
import {
Page,
Grid,
Card,
Table
} from "tabler-react";
import PropTypes from 'prop-types';
const EnsRecords = ({ensRecords}) => (
<Page.Content title="Records">
<Grid.Row>
<Grid.Col>
<Card>
<Table
responsive
className="card-table table-vcenter text-nowrap"
headerItems={[
{content: "Name"},
{content: "Address"}
]}
bodyItems={
ensRecords.map((ensRecord) => {
return ([
{content: ensRecord.name},
{content: ensRecord.address}
]);
})
}
/>
</Card>
</Grid.Col>
</Grid.Row>
</Page.Content>
);
EnsRecords.propTypes = {
ensRecords: PropTypes.arrayOf(PropTypes.object)
};
export default EnsRecords;

View File

@ -0,0 +1,56 @@
import React, {Component} from 'react';
import {
Button,
Form, Grid
} from "tabler-react";
import PropTypes from 'prop-types';
class EnsRegister extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
address: ''
};
}
handleChange(e, name) {
this.setState({
[name]: e.target.value
});
}
handleRegister() {
this.props.register(this.state.name, this.state.address);
}
render(){
return (
<React.Fragment>
<h3>Register</h3>
<Form.FieldSet>
<Grid.Row>
<Grid.Col md={6}>
<Form.Group>
<Form.Input placeholder="Enter a name" onChange={e => this.handleChange(e, "name")}/>
</Form.Group>
</Grid.Col>
<Grid.Col md={6}>
<Form.Group>
<Form.Input placeholder="Enter an address" onChange={e => this.handleChange(e, "address")}/>
</Form.Group>
</Grid.Col>
</Grid.Row>
<Button color="primary" onClick={() => this.handleRegister()}>Register</Button>
</Form.FieldSet>
</React.Fragment>
);
}
}
EnsRegister.propTypes = {
register: PropTypes.func
};
export default EnsRegister;

View File

@ -0,0 +1,44 @@
import React, {Component} from 'react';
import {
Button,
Form
} from "tabler-react";
import PropTypes from 'prop-types';
class EnsResolve extends Component {
constructor(props) {
super(props);
this.state = {
name: ''
};
}
handleChange(e) {
this.setState({name: e.target.value});
}
handleResolve() {
this.props.resolve(this.state.name);
}
render(){
return (
<React.Fragment>
<h3>Resolve</h3>
<Form.FieldSet>
<Form.Group>
<Form.Input placeholder="Enter a name" onChange={e => this.handleChange(e)}/>
</Form.Group>
<Button color="primary" onClick={() => this.handleResolve()}>Resolve</Button>
</Form.FieldSet>
</React.Fragment>
);
}
}
EnsResolve.propTypes = {
resolve: PropTypes.func
};
export default EnsResolve;

View File

@ -13,7 +13,7 @@ import BlockContainer from '../containers/BlockContainer';
import TransactionsContainer from '../containers/TransactionsContainer'; import TransactionsContainer from '../containers/TransactionsContainer';
import TransactionContainer from '../containers/TransactionContainer'; import TransactionContainer from '../containers/TransactionContainer';
import CommunicationContainer from '../containers/CommunicationContainer'; import CommunicationContainer from '../containers/CommunicationContainer';
import ENSContainer from '../containers/ENSContainer'; import EnsContainer from '../containers/EnsContainer';
const groupItems = [ const groupItems = [
{to: "/embark/explorer/accounts", icon: "users", value: "Accounts"}, {to: "/embark/explorer/accounts", icon: "users", value: "Accounts"},
@ -53,7 +53,7 @@ const ExplorerLayout = () => (
<Route exact path="/embark/explorer/transactions" component={TransactionsContainer} /> <Route exact path="/embark/explorer/transactions" component={TransactionsContainer} />
<Route exact path="/embark/explorer/transactions/:hash" component={TransactionContainer} /> <Route exact path="/embark/explorer/transactions/:hash" component={TransactionContainer} />
<Route exact path="/embark/explorer/communication" component={CommunicationContainer} /> <Route exact path="/embark/explorer/communication" component={CommunicationContainer} />
<Route exact path="/embark/explorer/ens" component={ENSContainer} /> <Route exact path="/embark/explorer/ens" component={EnsContainer} />
</Switch> </Switch>
</Grid.Col> </Grid.Col>
</Grid.Row> </Grid.Row>

View File

@ -0,0 +1,35 @@
import PropTypes from "prop-types";
import React from 'react';
import {Link} from "react-router-dom";
import {Grid, Card} from 'tabler-react';
const Version = ({version}) => (
<Grid.Col sm={6} lg={3}>
<Card className="p-3">
<div className="d-flex align-items-center">
<span className="stamp stamp-md mr-3 bg-info">
<i className="fe fa-cube"></i>
</span>
<div>
<h4 className="text-capitalize m-0">{version.name}: {version.value}</h4>
</div>
</div>
</Card>
</Grid.Col>
);
Version.propTypes = {
version: PropTypes.object
};
const Versions = ({versions}) => (
<Grid.Row cards>
{versions.map((version) => <Version key={version.name} version={version} />)}
</Grid.Row>
);
Versions.propTypes = {
versions: PropTypes.arrayOf(PropTypes.object)
};
export default Versions;

View File

@ -1,10 +1,10 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import React, {Component} from 'react'; import React, {Component} from 'react';
import connect from "react-redux/es/connect/connect"; import connect from "react-redux/es/connect/connect";
import {Alert, Loader, Page} from 'tabler-react'; import {Alert, Page} from 'tabler-react';
import {messageSend, messageListen} from "../actions"; import {messageSend, messageListen} from "../actions";
import Communication from "../components/Communication"; import Communication from "../components/Communication";
import {getMessages, getMessageChannels} from "../reducers/selectors"; import {getMessages, getMessageChannels, isOldWeb3, isWeb3Enabled} from "../reducers/selectors";
class CommunicationContainer extends Component { class CommunicationContainer extends Component {
sendMessage(topic, message) { sendMessage(topic, message) {
@ -15,24 +15,29 @@ class CommunicationContainer extends Component {
this.props.messageListen(channel); this.props.messageListen(channel);
} }
render() { web3DisabledWarning() {
let isEnabledMessage = ''; return <Alert type="warning">The node you are using does not support Whisper</Alert>
if (this.props.messageVersion === undefined || this.props.messageVersion === null) {
isEnabledMessage =
<Alert bsStyle="secondary "><Loader/> Checking Whisper support, please wait</Alert>;
} else if (!this.props.messageVersion) {
isEnabledMessage = <Alert type="warning">The node you are using does not support Whisper</Alert>;
} else if (this.props.messageVersion === -1) {
isEnabledMessage = <Alert type="warning">The node uses an unsupported version of Whisper</Alert>;
} }
return ( web3Enabled() {
<Page.Content title="Communication explorer"> return this.props.isOldWeb3 ? this.web3DisabledWarning() : this.showCommunication();
{isEnabledMessage} }
<Communication listenToMessages={(channel) => this.listenToChannel(channel)}
web3OldWarning() {
return <Alert type="warning">The node uses an unsupported version of Whisper</Alert>;
}
showCommunication() {
return <Communication listenToMessages={(channel) => this.listenToChannel(channel)}
sendMessage={(channel, message) => this.sendMessage(channel, message)} sendMessage={(channel, message) => this.sendMessage(channel, message)}
channels={this.props.messages} channels={this.props.messages}
subscriptions={this.props.messageChannels}/> subscriptions={this.props.messageChannels}/>;
}
render() {
return (
<Page.Content title="Communication explorer">
{this.props.isWeb3Enabled ? this.web3Enabled() : this.web3DisabledWarning()}
</Page.Content> </Page.Content>
); );
} }
@ -41,7 +46,8 @@ class CommunicationContainer extends Component {
CommunicationContainer.propTypes = { CommunicationContainer.propTypes = {
messageSend: PropTypes.func, messageSend: PropTypes.func,
messageListen: PropTypes.func, messageListen: PropTypes.func,
isWhisperEnabled: PropTypes.bool, isOldWeb3: PropTypes.bool,
isWeb3Enabled: PropTypes.bool,
messages: PropTypes.object, messages: PropTypes.object,
messageChannels: PropTypes.array messageChannels: PropTypes.array
}; };
@ -50,7 +56,8 @@ function mapStateToProps(state) {
return { return {
messages: getMessages(state), messages: getMessages(state),
messageChannels: getMessageChannels(state), messageChannels: getMessageChannels(state),
isWhisperEnabled: isWhisperEnabled(state) isOldWeb3: isOldWeb3(state),
isWeb3Enabled: isWeb3Enabled(state)
}; };
} }

View File

@ -1,67 +0,0 @@
import PropTypes from "prop-types";
import React, {Component} from 'react';
import connect from "react-redux/es/connect/connect";
import {Alert, Loader, Page} from 'tabler-react';
import {messageSend, messageListen, messageVersion} from "../actions";
import Communication from "../components/Communication";
import Loading from "../components/Loading";
import {getMessages, getMessageChannels} from "../reducers/selectors";
class CommunicationContainer extends Component {
sendMessage(topic, message) {
this.props.messageSend({topic, message});
}
listenToChannel(channel) {
this.props.messageListen(channel);
}
render() {
let isEnabledMessage = '';
if (this.props.messageVersion === undefined || this.props.messageVersion === null) {
isEnabledMessage =
<Alert bsStyle="secondary "><Loader/> Checking Whisper support, please wait</Alert>;
} else if (!this.props.messageVersion) {
isEnabledMessage = <Alert type="warning">The node you are using does not support Whisper</Alert>;
} else if (this.props.messageVersion === -1) {
isEnabledMessage = <Alert type="warning">The node uses an unsupported version of Whisper</Alert>;
}
if (!this.props.messages) {
return <Loading/>;
}
return (
<Page.Content title="Communication explorer">
{isEnabledMessage}
<Communication listenToMessages={(channel) => this.listenToChannel(channel)}
sendMessage={(channel, message) => this.sendMessage(channel, message)}
channels={this.props.messages}
subscriptions={this.props.messageChannels}/>
</Page.Content>
);
}
}
CommunicationContainer.propTypes = {
messageSend: PropTypes.func,
messageListen: PropTypes.func,
messageVersion: PropTypes.number,
messages: PropTypes.object,
messageChannels: PropTypes.array
};
function mapStateToProps(state) {
return {
messages: getMessages(state),
messageChannels: getMessageChannels(state)
};
}
export default connect(
mapStateToProps,
{
messageSend: messageSend.request,
messageListen: messageListen.request
}
)(CommunicationContainer);

View File

@ -0,0 +1,64 @@
import PropTypes from "prop-types";
import React, {Component} from 'react';
import connect from "react-redux/es/connect/connect";
import {Alert, Page} from "tabler-react";
import {ensRecord, ensRecords} from "../actions";
import DataWrapper from "../components/DataWrapper";
import EnsRegister from "../components/EnsRegister";
import EnsLookup from "../components/EnsLookup";
import EnsResolve from "../components/EnsResolve";
import EnsRecords from "../components/EnsRecords";
import {getEnsRecords, isEnsEnabled} from "../reducers/selectors";
class EnsContainer extends Component {
showEns() {
return (
<React.Fragment>
<EnsLookup lookup={this.props.lookup}/>
<EnsResolve resolve={this.props.resolve}/>
<EnsRegister register={this.props.register}/>
<DataWrapper shouldRender={this.props.ensRecords.length > 0} {...this.props} render={({ensRecords}) => (
<EnsRecords ensRecords={ensRecords} />
)} />
</React.Fragment>
);
}
showWarning() {
return <Alert type="warning">Please enable Ens in Embark first</Alert>;
}
render() {
return (
<Page.Content title="Ens">
{this.props.isEnsEnabled ? this.showEns() : this.showWarning()}
</Page.Content>
);
}
}
EnsContainer.propTypes = {
ensRecords: PropTypes.array,
resolve: PropTypes.func,
lookup: PropTypes.func,
register: PropTypes.func,
isEnsEnabled: PropTypes.bool
};
function mapStateToProps(state) {
return {
ensRecords: getEnsRecords(state),
isEnsEnabled: isEnsEnabled(state)
};
}
export default connect(
mapStateToProps,
{
resolve: ensRecord.resolve,
lookup: ensRecord.lookup,
register: ensRecords.post
}
)(EnsContainer);

View File

@ -6,8 +6,9 @@ import {Page} from "tabler-react";
import {commands as commandsAction} from "../actions"; import {commands as commandsAction} from "../actions";
import DataWrapper from "../components/DataWrapper"; import DataWrapper from "../components/DataWrapper";
import Processes from '../components/Processes'; import Processes from '../components/Processes';
import Versions from '../components/Versions';
import Console from '../components/Console'; import Console from '../components/Console';
import {getProcesses, getCommands} from "../reducers/selectors"; import {getProcesses, getCommands, getVersions} from "../reducers/selectors";
class HomeContainer extends Component { class HomeContainer extends Component {
render() { render() {
@ -17,6 +18,9 @@ class HomeContainer extends Component {
<DataWrapper shouldRender={this.props.processes.length > 0 } {...this.props} render={({processes}) => ( <DataWrapper shouldRender={this.props.processes.length > 0 } {...this.props} render={({processes}) => (
<Processes processes={processes} /> <Processes processes={processes} />
)} /> )} />
<DataWrapper shouldRender={this.props.versions.length > 0 } {...this.props} render={({versions}) => (
<Versions versions={versions} />
)} />
<Console postCommand={this.props.postCommand} commands={this.props.commands} /> <Console postCommand={this.props.postCommand} commands={this.props.commands} />
</React.Fragment> </React.Fragment>
); );
@ -25,12 +29,16 @@ class HomeContainer extends Component {
HomeContainer.propTypes = { HomeContainer.propTypes = {
processes: PropTypes.arrayOf(PropTypes.object), processes: PropTypes.arrayOf(PropTypes.object),
versions: PropTypes.arrayOf(PropTypes.object),
postCommand: PropTypes.func, postCommand: PropTypes.func,
commands: PropTypes.arrayOf(PropTypes.object) commands: PropTypes.arrayOf(PropTypes.object),
error: PropTypes.string,
loading: PropTypes.bool
}; };
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
versions: getVersions(state),
processes: getProcesses(state), processes: getProcesses(state),
commands: getCommands(state), commands: getCommands(state),
error: state.errorMessage, error: state.errorMessage,

View File

@ -2,6 +2,7 @@ import {combineReducers} from 'redux';
import {REQUEST} from "../actions"; import {REQUEST} from "../actions";
const BN_FACTOR = 10000; const BN_FACTOR = 10000;
const voidAddress = '0x0000000000000000000000000000000000000000';
const entitiesDefaultState = { const entitiesDefaultState = {
accounts: [], accounts: [],
@ -56,6 +57,11 @@ const filtrer = {
return index === self.findIndex((t) => ( return index === self.findIndex((t) => (
t.blockNumber === tx.blockNumber && t.transactionIndex === tx.transactionIndex t.blockNumber === tx.blockNumber && t.transactionIndex === tx.transactionIndex
)); ));
},
ensRecords: function(record, index, self) {
return record.name && record.address && record.address !== voidAddress && index === self.findIndex((r) => (
r.address=== record.address && r.name === record.name
));
} }
}; };

View File

@ -62,8 +62,17 @@ export function getContractProfile(state, contractName) {
return state.entities.contractProfiles.find((contractProfile => contractProfile.name === contractName)); return state.entities.contractProfiles.find((contractProfile => contractProfile.name === contractName));
} }
export function getMessageVersion(state) { export function getVersions(state) {
return state.entities.messageVersion; return state.entities.versions;
}
export function isWeb3Enabled(state) {
return Boolean(state.entities.versions.find((version) => version.name === 'web3'));
}
export function isOldWeb3(state) {
const web3 = state.entities.versions.find((version) => version.name === 'web3');
return web3 && parseInt(web3[0], 10) === 0;
} }
export function getMessageChannels(state) { export function getMessageChannels(state) {
@ -81,6 +90,14 @@ export function getMessages(state) {
return messages; return messages;
} }
export function getFiddle(state){ export function getFiddle(state) {
return state.entities.fiddle; return state.entities.fiddle;
} }
export function getEnsRecords(state) {
return state.entities.ensRecords;
}
export function isEnsEnabled(state) {
return Boolean(state.entities.plugins.find((plugin) => plugin.name === 'ens'));
}

View File

@ -33,8 +33,8 @@ export const fetchContract = doRequest.bind(null, contract, api.fetchContract);
export const fetchContractProfile = doRequest.bind(null, contractProfile, api.fetchContractProfile); export const fetchContractProfile = doRequest.bind(null, contractProfile, api.fetchContractProfile);
export const fetchFiddle = doRequest.bind(null, fiddle, api.fetchFiddle); export const fetchFiddle = doRequest.bind(null, fiddle, api.fetchFiddle);
export const sendMessage = doRequest.bind(null, messageSend, api.sendMessage); export const sendMessage = doRequest.bind(null, messageSend, api.sendMessage);
export const fetchENSRecord = doRequest.bind(null, ensRecord, api.fetchENSRecord); export const fetchEnsRecord = doRequest.bind(null, ensRecord, api.fetchEnsRecord);
export const postENSRecord = doRequest.bind(null, ensRecords, api.postENSRecord); export const postEnsRecord = doRequest.bind(null, ensRecords, api.postEnsRecord);
export function *watchFetchTransaction() { export function *watchFetchTransaction() {
yield takeEvery(actions.TRANSACTION[actions.REQUEST], fetchTransaction); yield takeEvery(actions.TRANSACTION[actions.REQUEST], fetchTransaction);
@ -100,12 +100,12 @@ export function *watchSendMessage() {
yield takeEvery(actions.MESSAGE_SEND[actions.REQUEST], sendMessage); yield takeEvery(actions.MESSAGE_SEND[actions.REQUEST], sendMessage);
} }
export function *watchFetchENSRecord() { export function *watchFetchEnsRecord() {
yield takeEvery(actions.ENS_RECORD[actions.REQUEST], fetchENSRecord); yield takeEvery(actions.ENS_RECORD[actions.REQUEST], fetchEnsRecord);
} }
export function *watchPostENSRecords() { export function *watchPostEnsRecords() {
yield takeEvery(actions.ENS_RECORDS[actions.REQUEST], postENSRecord); yield takeEvery(actions.ENS_RECORDS[actions.REQUEST], postEnsRecord);
} }
function createChannel(socket) { function createChannel(socket) {