mirror of
https://github.com/open5gs/open5gs.git
synced 2026-04-28 03:19:31 +00:00
add Initial Account DB and REST API
This commit is contained in:
parent
c2de3c5596
commit
ec3530524a
15 changed files with 816 additions and 9 deletions
|
|
@ -16,4 +16,11 @@ restify.serve(router, Profile, {
|
|||
version: ''
|
||||
});
|
||||
|
||||
const Account = require('../models/account');
|
||||
restify.serve(router, Account, {
|
||||
prefix: '',
|
||||
version: '',
|
||||
idProperty: 'username'
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
126
webui/src/components/Account/Edit.js
Normal file
126
webui/src/components/Account/Edit.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import withWidth, { SMALL } from 'helpers/with-width';
|
||||
import { Form } from 'components';
|
||||
|
||||
const schema = {
|
||||
"title": "",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"title": "Username",
|
||||
"required": true,
|
||||
},
|
||||
"roles": {
|
||||
"type" : "array",
|
||||
"title" : "",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"title": "Role",
|
||||
"enum": [ "user", "admin" ],
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const uiSchema = {
|
||||
"username" : {
|
||||
classNames: "col-xs-12",
|
||||
},
|
||||
"roles" : {
|
||||
"ui:options": {
|
||||
"addable": false,
|
||||
"orderable": false,
|
||||
"removable": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Edit extends Component {
|
||||
static propTypes = {
|
||||
visible: PropTypes.bool,
|
||||
action: PropTypes.string,
|
||||
formData: PropTypes.object,
|
||||
isLoading: PropTypes.bool,
|
||||
validate: PropTypes.func,
|
||||
onHide: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
onError: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = this.getStateFromProps(props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState(this.getStateFromProps(nextProps));
|
||||
}
|
||||
|
||||
getStateFromProps(props) {
|
||||
const {
|
||||
action,
|
||||
width,
|
||||
formData,
|
||||
} = props;
|
||||
|
||||
let state = {
|
||||
schema,
|
||||
uiSchema,
|
||||
formData,
|
||||
};
|
||||
|
||||
if (action === 'update') {
|
||||
state.uiSchema = Object.assign(state.uiSchema, {
|
||||
"username": {
|
||||
"ui:disabled": true
|
||||
}
|
||||
});
|
||||
} else if (width !== SMALL) {
|
||||
state.uiSchema = Object.assign(state.uiSchema, {
|
||||
"username": {
|
||||
"ui:autofocus": true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
visible,
|
||||
action,
|
||||
isLoading,
|
||||
validate,
|
||||
onHide,
|
||||
onSubmit,
|
||||
onError
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
formData
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<Form
|
||||
visible={visible}
|
||||
title={(action === 'update') ? 'Edit Account' : 'Create Account'}
|
||||
width="480px"
|
||||
height="240px"
|
||||
schema={this.state.schema}
|
||||
uiSchema={this.state.uiSchema}
|
||||
formData={formData}
|
||||
isLoading={isLoading}
|
||||
validate={validate}
|
||||
onHide={onHide}
|
||||
onSubmit={onSubmit}
|
||||
onError={onError}/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withWidth()(Edit);
|
||||
180
webui/src/components/Account/Item.js
Normal file
180
webui/src/components/Account/Item.js
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import oc from 'open-color';
|
||||
import { media } from 'helpers/style-utils';
|
||||
|
||||
import EditIcon from 'react-icons/lib/md/edit';
|
||||
import DeleteIcon from 'react-icons/lib/md/delete';
|
||||
|
||||
import { Tooltip, Spinner } from 'components';
|
||||
|
||||
const Card = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 0.5rem;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
${p => p.disabled && 'opacity: 0.5; cursor: not-allowed; pointer-events: none;'};
|
||||
|
||||
.actions {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 8rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: ${oc.gray[1]};
|
||||
|
||||
.actions {
|
||||
${p => p.disabled ? 'opacity: 0;' : 'opacity: 1;'};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CircleButton = styled.div`
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 1px;
|
||||
|
||||
color: ${oc.gray[6]};
|
||||
|
||||
border-radius: 1rem;
|
||||
font-size: 1.5rem;
|
||||
|
||||
&:hover {
|
||||
color: ${oc.indigo[6]};
|
||||
}
|
||||
|
||||
&.delete {
|
||||
&:hover {
|
||||
color: ${oc.pink[6]};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Account = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex:1;
|
||||
line-height: 2.5rem;
|
||||
margin : 0 2rem;
|
||||
|
||||
.username {
|
||||
font-size: 1.25rem;
|
||||
color: ${oc.gray[8]};
|
||||
width: 320px;
|
||||
}
|
||||
.role {
|
||||
font-size: 1.1rem;
|
||||
color: ${oc.gray[6]};
|
||||
width: 240px;
|
||||
}
|
||||
`;
|
||||
|
||||
const SpinnerWrapper = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 4rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const propTypes = {
|
||||
account: PropTypes.shape({
|
||||
username: PropTypes.string
|
||||
}),
|
||||
onEdit: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
}
|
||||
|
||||
class Item extends Component {
|
||||
static propTypes = {
|
||||
account: PropTypes.shape({
|
||||
username: PropTypes.string
|
||||
}),
|
||||
onEdit: PropTypes.func,
|
||||
onDelete: PropTypes.func
|
||||
}
|
||||
|
||||
handleEdit = e => {
|
||||
e.stopPropagation();
|
||||
|
||||
const {
|
||||
account,
|
||||
onEdit,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
username
|
||||
} = account;
|
||||
|
||||
onEdit(username)
|
||||
}
|
||||
|
||||
handleDelete = e => {
|
||||
e.stopPropagation();
|
||||
|
||||
const {
|
||||
account,
|
||||
onDelete
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
username
|
||||
} = account;
|
||||
|
||||
onDelete(username)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
handleEdit,
|
||||
handleDelete
|
||||
} = this;
|
||||
|
||||
const {
|
||||
disabled,
|
||||
account,
|
||||
onEdit,
|
||||
onDelete
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
username,
|
||||
roles
|
||||
} = account;
|
||||
|
||||
return (
|
||||
<Card disabled={disabled} onClick={handleEdit}>
|
||||
<Account>
|
||||
<div className='username'>{username}</div>
|
||||
<div className='role'>{roles[0]}</div>
|
||||
</Account>
|
||||
<div className="actions">
|
||||
<Tooltip content='Delete' width="60px">
|
||||
<CircleButton className="delete" onClick={handleDelete}><DeleteIcon/></CircleButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{disabled && <SpinnerWrapper><Spinner sm/></SpinnerWrapper>}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Item;
|
||||
47
webui/src/components/Account/List.js
Normal file
47
webui/src/components/Account/List.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import oc from 'open-color';
|
||||
import { media } from 'helpers/style-utils';
|
||||
|
||||
import { Layout, Blank } from 'components';
|
||||
import Item from './Item';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: block;
|
||||
margin: 2rem;
|
||||
|
||||
${media.mobile`
|
||||
margin: 0.5rem 0.25rem;
|
||||
`}
|
||||
`
|
||||
|
||||
const propTypes = {
|
||||
accounts: PropTypes.arrayOf(PropTypes.object),
|
||||
onEdit: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
search: PropTypes.string
|
||||
}
|
||||
|
||||
const List = ({ accounts, deletedId, onEdit, onDelete, search }) => {
|
||||
const accountList = accounts
|
||||
.filter(s => s.username.indexOf(search) !== -1)
|
||||
.map(account =>
|
||||
<Item
|
||||
key={account.username}
|
||||
account={account}
|
||||
disabled={deletedId === account.username}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete} />
|
||||
);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
{accountList}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
List.propTypes = propTypes;
|
||||
|
||||
export default List;
|
||||
7
webui/src/components/Account/index.js
Normal file
7
webui/src/components/Account/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import List from './List';
|
||||
import Edit from './Edit';
|
||||
|
||||
export {
|
||||
List,
|
||||
Edit
|
||||
};
|
||||
|
|
@ -88,6 +88,10 @@ const Sidebar = ({ isOpen, width, selectedView, onSelectView }) => (
|
|||
<Icon><ProfileIcon/></Icon>
|
||||
<Title>Profile</Title>
|
||||
</Item>
|
||||
<Item name="account" selected={selectedView} onSelect={onSelectView}>
|
||||
<Icon><ProfileIcon/></Icon>
|
||||
<Title>Account</Title>
|
||||
</Item>
|
||||
</Menu>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ const Card = styled.div`
|
|||
display: flex;
|
||||
padding : 0.5rem;
|
||||
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
|
||||
${p => p.disabled && 'opacity: 0.5; cursor: not-allowed; pointer-events: none;'}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import styled from 'styled-components';
|
||||
import oc from 'open-color';
|
||||
import { media, transitions } from 'helpers/style-utils';
|
||||
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
|
||||
import { media } from 'helpers/style-utils';
|
||||
|
||||
import { Layout, Blank } from 'components';
|
||||
import Item from './Item';
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const Wrapper = styled.div`
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
postion: relative;
|
||||
width: 1050px;
|
||||
width: ${p => p.width || `1050px`};
|
||||
|
||||
${media.mobile`
|
||||
width: calc(100vw - 2rem);
|
||||
|
|
@ -39,7 +39,7 @@ const Body = styled.div`
|
|||
padding: 2rem;
|
||||
font-size: 14px;
|
||||
|
||||
height: 500px;
|
||||
height: ${p => p.height || `500px`};
|
||||
${media.mobile`
|
||||
height: calc(100vh - 16rem);
|
||||
`}
|
||||
|
|
@ -242,11 +242,11 @@ class Form extends Component {
|
|||
visible={visible}
|
||||
onOutside={handleOutside}
|
||||
disableOnClickOutside={this.state.confirm}>
|
||||
<Wrapper id='nprogress-base-form'>
|
||||
<Wrapper id='nprogress-base-form' width={this.props.width}>
|
||||
<Header>
|
||||
{title}
|
||||
</Header>
|
||||
<Body>
|
||||
<Body height={this.props.height}>
|
||||
{isLoading && <Spinner/>}
|
||||
{!isLoading &&
|
||||
<JsonSchemaForm
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import Confirm from './Shared/Confirm';
|
|||
|
||||
import * as Subscriber from './Subscriber';
|
||||
import * as Profile from './Profile';
|
||||
import * as Account from './Account';
|
||||
|
||||
export {
|
||||
Layout,
|
||||
|
|
@ -39,5 +40,6 @@ export {
|
|||
Confirm,
|
||||
|
||||
Subscriber,
|
||||
Profile
|
||||
Profile,
|
||||
Account
|
||||
}
|
||||
|
|
|
|||
216
webui/src/containers/Account/Collection.js
Normal file
216
webui/src/containers/Account/Collection.js
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
import { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { MODEL, fetchAccounts, deleteAccount } from 'modules/crud/account';
|
||||
import { clearActionStatus } from 'modules/crud/actions';
|
||||
import { select, selectActionStatus } from 'modules/crud/selectors';
|
||||
import * as Notification from 'modules/notification/actions';
|
||||
|
||||
import {
|
||||
Layout,
|
||||
Account,
|
||||
Spinner,
|
||||
FloatingButton,
|
||||
Blank,
|
||||
Dimmed,
|
||||
Confirm
|
||||
} from 'components';
|
||||
|
||||
import Document from './Document';
|
||||
|
||||
class Collection extends Component {
|
||||
state = {
|
||||
search: '',
|
||||
document: {
|
||||
action: '',
|
||||
visible: false,
|
||||
dimmed: false
|
||||
},
|
||||
confirm: {
|
||||
visible: false,
|
||||
username: ''
|
||||
},
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
const { accounts, dispatch } = this.props
|
||||
|
||||
if (accounts.needsFetch) {
|
||||
dispatch(accounts.fetch)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { accounts, status } = nextProps
|
||||
const { dispatch } = this.props
|
||||
|
||||
if (accounts.needsFetch) {
|
||||
dispatch(accounts.fetch)
|
||||
}
|
||||
|
||||
if (status.response) {
|
||||
dispatch(Notification.success({
|
||||
title: 'Account',
|
||||
message: `${status.id} has been deleted`
|
||||
}));
|
||||
dispatch(clearActionStatus(MODEL, 'delete'));
|
||||
}
|
||||
|
||||
if (status.error) {
|
||||
let title = 'Unknown Code';
|
||||
let message = 'Unknown Error';
|
||||
if (response.data && response.data.name && response.data.message) {
|
||||
title = response.data.name;
|
||||
message = response.data.message;
|
||||
} else {
|
||||
title = response.status;
|
||||
message = response.statusText;
|
||||
}
|
||||
|
||||
dispatch(Notification.error({
|
||||
title,
|
||||
message,
|
||||
autoDismiss: 0,
|
||||
action: {
|
||||
label: 'Dismiss'
|
||||
}
|
||||
}));
|
||||
dispatch(clearActionStatus(MODEL, 'delete'));
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchChange = (e) => {
|
||||
this.setState({
|
||||
search: e.target.value
|
||||
});
|
||||
}
|
||||
|
||||
handleSearchClear = (e) => {
|
||||
this.setState({
|
||||
search: ''
|
||||
});
|
||||
}
|
||||
|
||||
documentHandler = {
|
||||
show: (action, payload) => {
|
||||
this.setState({
|
||||
document: {
|
||||
action,
|
||||
visible: true,
|
||||
dimmed: true,
|
||||
...payload
|
||||
}
|
||||
})
|
||||
},
|
||||
hide: () => {
|
||||
this.setState({
|
||||
document: {
|
||||
action: '',
|
||||
visible: false,
|
||||
dimmed: false
|
||||
},
|
||||
})
|
||||
},
|
||||
actions: {
|
||||
create: () => {
|
||||
this.documentHandler.show('create');
|
||||
},
|
||||
update: (username) => {
|
||||
this.documentHandler.show('update', { username });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
confirmHandler = {
|
||||
show: (username) => {
|
||||
this.setState({
|
||||
confirm: {
|
||||
visible: true,
|
||||
username,
|
||||
}
|
||||
})
|
||||
},
|
||||
hide: () => {
|
||||
this.setState({
|
||||
confirm: {
|
||||
...this.state.confirm,
|
||||
visible: false
|
||||
}
|
||||
})
|
||||
},
|
||||
actions : {
|
||||
delete: () => {
|
||||
const { dispatch } = this.props
|
||||
|
||||
if (this.state.confirm.visible === true) {
|
||||
this.confirmHandler.hide();
|
||||
this.documentHandler.hide();
|
||||
|
||||
dispatch(deleteAccount(this.state.confirm.username));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
handleSearchChange,
|
||||
handleSearchClear,
|
||||
documentHandler,
|
||||
confirmHandler
|
||||
} = this;
|
||||
|
||||
const {
|
||||
search,
|
||||
document
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
accounts,
|
||||
status
|
||||
} = this.props
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
data
|
||||
} = accounts;
|
||||
|
||||
return (
|
||||
<Layout.Content>
|
||||
<Account.List
|
||||
accounts={data}
|
||||
deletedId={status.id}
|
||||
onEdit={documentHandler.actions.update}
|
||||
onDelete={confirmHandler.show}
|
||||
search={search}
|
||||
/>
|
||||
{isLoading && <Spinner md />}
|
||||
<FloatingButton onClick={documentHandler.actions.create}/>
|
||||
<Document
|
||||
{ ...document }
|
||||
onEdit={documentHandler.actions.update}
|
||||
onDelete={confirmHandler.show}
|
||||
onHide={documentHandler.hide} />
|
||||
<Dimmed visible={document.dimmed} />
|
||||
<Confirm
|
||||
visible={this.state.confirm.visible}
|
||||
message="Delete this account?"
|
||||
onOutside={confirmHandler.hide}
|
||||
buttons={[
|
||||
{ text: "CANCEL", action: confirmHandler.hide, info:true },
|
||||
{ text: "DELETE", action: confirmHandler.actions.delete, danger:true }
|
||||
]}/>
|
||||
</Layout.Content>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Collection = connect(
|
||||
(state) => ({
|
||||
accounts: select(fetchAccounts(), state.crud),
|
||||
status: selectActionStatus(MODEL, state.crud, 'delete')
|
||||
})
|
||||
)(Collection);
|
||||
|
||||
export default Collection;
|
||||
180
webui/src/containers/Account/Document.js
Normal file
180
webui/src/containers/Account/Document.js
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import NProgress from 'nprogress';
|
||||
|
||||
import { MODEL, fetchAccounts, fetchAccount, createAccount, updateAccount } from 'modules/crud/account';
|
||||
import { clearActionStatus } from 'modules/crud/actions';
|
||||
import { select, selectActionStatus } from 'modules/crud/selectors';
|
||||
import * as Notification from 'modules/notification/actions';
|
||||
|
||||
import { Account } from 'components';
|
||||
|
||||
import traverse from 'traverse';
|
||||
|
||||
const formData = {
|
||||
"roles": [ "user" ]
|
||||
}
|
||||
|
||||
class Document extends Component {
|
||||
static propTypes = {
|
||||
action: PropTypes.string,
|
||||
visible: PropTypes.bool,
|
||||
onHide: PropTypes.func
|
||||
}
|
||||
|
||||
state = {
|
||||
formData
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const { account, dispatch } = this.props
|
||||
|
||||
if (account.needsFetch) {
|
||||
dispatch(account.fetch)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { account, status } = nextProps
|
||||
const { dispatch, action, onHide } = this.props
|
||||
|
||||
if (account.needsFetch) {
|
||||
dispatch(account.fetch)
|
||||
}
|
||||
|
||||
if (account.data) {
|
||||
this.setState({ formData: account.data })
|
||||
} else {
|
||||
this.setState({ formData });
|
||||
}
|
||||
|
||||
if (status.response) {
|
||||
NProgress.configure({
|
||||
parent: 'body',
|
||||
trickleSpeed: 5
|
||||
});
|
||||
NProgress.done(true);
|
||||
|
||||
const message = action === 'create' ? "New account created" : `${status.id} account updated`;
|
||||
|
||||
dispatch(Notification.success({
|
||||
title: 'Account',
|
||||
message
|
||||
}));
|
||||
|
||||
dispatch(clearActionStatus(MODEL, action));
|
||||
onHide();
|
||||
}
|
||||
|
||||
if (status.error) {
|
||||
NProgress.configure({
|
||||
parent: 'body',
|
||||
trickleSpeed: 5
|
||||
});
|
||||
NProgress.done(true);
|
||||
|
||||
const response = ((status || {}).error || {}).response || {};
|
||||
|
||||
let title = 'Unknown Code';
|
||||
let message = 'Unknown Error';
|
||||
if (response.data && response.data.name && response.data.message) {
|
||||
title = response.data.name;
|
||||
message = response.data.message;
|
||||
} else {
|
||||
title = response.status;
|
||||
message = response.statusText;
|
||||
}
|
||||
|
||||
dispatch(Notification.error({
|
||||
title,
|
||||
message,
|
||||
autoDismiss: 0,
|
||||
action: {
|
||||
label: 'Dismiss',
|
||||
callback: () => onHide()
|
||||
}
|
||||
}));
|
||||
dispatch(clearActionStatus(MODEL, action));
|
||||
}
|
||||
}
|
||||
|
||||
validate = (formData, errors) => {
|
||||
const { accounts, action, status } = this.props;
|
||||
const { username } = formData;
|
||||
|
||||
if (action === 'create' && accounts && accounts.data &&
|
||||
accounts.data.filter(account => account.username === username).length > 0) {
|
||||
errors.username.addError(`'${username}' is duplicated`);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
handleSubmit = (formData) => {
|
||||
const { dispatch, action } = this.props;
|
||||
|
||||
NProgress.configure({
|
||||
parent: '#nprogress-base-form',
|
||||
trickleSpeed: 5
|
||||
});
|
||||
NProgress.start();
|
||||
|
||||
if (action === 'create') {
|
||||
dispatch(createAccount({}, formData));
|
||||
} else if (action === 'update') {
|
||||
dispatch(updateAccount(formData.username, {}, formData));
|
||||
} else {
|
||||
throw new Error(`Action type '${action}' is invalid.`);
|
||||
}
|
||||
}
|
||||
|
||||
handleError = errors => {
|
||||
const { dispatch } = this.props;
|
||||
errors.map(error =>
|
||||
dispatch(Notification.error({
|
||||
title: 'Validation Error',
|
||||
message: error.stack
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
validate,
|
||||
handleSubmit,
|
||||
handleError
|
||||
} = this;
|
||||
|
||||
const {
|
||||
visible,
|
||||
action,
|
||||
status,
|
||||
account,
|
||||
onHide
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<Account.Edit
|
||||
visible={visible}
|
||||
action={action}
|
||||
formData={this.state.formData}
|
||||
isLoading={account.isLoading && !status.pending}
|
||||
validate={validate}
|
||||
onHide={onHide}
|
||||
onSubmit={handleSubmit}
|
||||
onError={handleError} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Document = connect(
|
||||
(state, props) => ({
|
||||
accounts: select(fetchAccounts(), state.crud),
|
||||
account: select(fetchAccount(props.username), state.crud),
|
||||
status: selectActionStatus(MODEL, state.crud, props.action)
|
||||
})
|
||||
)(Document);
|
||||
|
||||
export default Document;
|
||||
7
webui/src/containers/Account/index.js
Normal file
7
webui/src/containers/Account/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Collection from './Collection';
|
||||
import Document from './Document';
|
||||
|
||||
export {
|
||||
Collection,
|
||||
Document
|
||||
};
|
||||
|
|
@ -10,7 +10,7 @@ import { Layout } from 'components';
|
|||
import Notification from 'containers/Notification';
|
||||
import * as Subscriber from 'containers/Subscriber';
|
||||
import * as Profile from 'containers/Profile';
|
||||
|
||||
import * as Account from 'containers/Account';
|
||||
|
||||
class App extends Component {
|
||||
static propTypes = {
|
||||
|
|
@ -49,6 +49,9 @@ class App extends Component {
|
|||
<Layout.Container visible={view === "profile"}>
|
||||
<Profile.Collection/>
|
||||
</Layout.Container>
|
||||
<Layout.Container visible={view === "account"}>
|
||||
<Account.Collection/>
|
||||
</Layout.Container>
|
||||
<Notification/>
|
||||
</Layout>
|
||||
)
|
||||
|
|
|
|||
30
webui/src/modules/crud/account.js
Normal file
30
webui/src/modules/crud/account.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import {
|
||||
fetchCollection,
|
||||
fetchDocument,
|
||||
createDocument,
|
||||
updateDocument,
|
||||
deleteDocument
|
||||
} from './actions'
|
||||
|
||||
export const MODEL = 'accounts';
|
||||
export const URL = '/Account';
|
||||
|
||||
export const fetchAccounts = (params = {}) => {
|
||||
return fetchCollection(MODEL, URL, params, { idProperty: 'username' });
|
||||
}
|
||||
|
||||
export const fetchAccount = (username, params = {}) => {
|
||||
return fetchDocument(MODEL, username, `${URL}/${username}`, params, { idProperty: 'username' });
|
||||
}
|
||||
|
||||
export const createAccount = (params = {}, data = {}) => {
|
||||
return createDocument(MODEL, URL, params, data, { idProperty: 'username' });
|
||||
}
|
||||
|
||||
export const updateAccount = (username, params = {}, data = {}) => {
|
||||
return updateDocument(MODEL, username, `${URL}/${username}`, params, data, { idProperty: 'username' });
|
||||
}
|
||||
|
||||
export const deleteAccount = (username, params = {}) => {
|
||||
return deleteDocument(MODEL, username, `${URL}/${username}`, params, { idProperty: 'username' });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue