Add user management in settings
This commit is contained in:
parent
e0f6d8fa0b
commit
ab278a5a99
|
@ -56,7 +56,6 @@ const schema = makeAugmentedSchema({
|
||||||
hasRole: true,
|
hasRole: true,
|
||||||
},
|
},
|
||||||
mutation: false,
|
mutation: false,
|
||||||
query: true,
|
|
||||||
query: {
|
query: {
|
||||||
exclude: [
|
exclude: [
|
||||||
'ScannerResult',
|
'ScannerResult',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import { registerUser } from './users'
|
import { registerUser, authorizeUser } from './users'
|
||||||
|
|
||||||
async function initialSetup(driver) {
|
async function initialSetup(driver) {
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
|
@ -59,10 +59,12 @@ const Mutation = {
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
const token = (await authorizeUser(root, args, ctx, info)).token
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
status: 'Initial setup successful',
|
status: 'Initial setup successful',
|
||||||
token: userResult.token,
|
token,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,9 +84,30 @@ const Mutation = {
|
||||||
token: token,
|
token: token,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async updateUser(root, args, ctx, info) {
|
||||||
|
if (args.rootPath) {
|
||||||
|
if (!(await fs.exists(args.rootPath))) {
|
||||||
|
throw Error('New root path not found in server filesystem')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return neo4jgraphql(root, args, ctx, info)
|
||||||
|
},
|
||||||
|
async createUser(root, args, ctx, info) {
|
||||||
|
if (args.rootPath) {
|
||||||
|
if (!(await fs.exists(args.rootPath))) {
|
||||||
|
throw Error('Root path not found in server filesystem')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args.id = uuid()
|
||||||
|
|
||||||
|
return neo4jgraphql(root, args, ctx, info)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registerUser = Mutation.registerUser
|
export const registerUser = Mutation.registerUser
|
||||||
|
export const authorizeUser = Mutation.authorizeUser
|
||||||
|
|
||||||
const Query = {
|
const Query = {
|
||||||
myUser(root, args, ctx, info) {
|
myUser(root, args, ctx, info) {
|
||||||
|
|
|
@ -106,6 +106,12 @@ type Mutation {
|
||||||
password: String!
|
password: String!
|
||||||
rootPath: String!
|
rootPath: String!
|
||||||
): AuthorizeResult @neo4j_ignore
|
): AuthorizeResult @neo4j_ignore
|
||||||
|
|
||||||
|
updateUser(id: ID!, username: String, rootPath: String, admin: Boolean): User
|
||||||
|
@hasRole(roles: [admin])
|
||||||
|
createUser(id: ID, username: String, rootPath: String, admin: Boolean): User
|
||||||
|
@hasRole(roles: [admin])
|
||||||
|
deleteUser(id: ID!): User @hasRole(roles: [admin])
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { Mutation } from 'react-apollo'
|
||||||
|
import { Table, Button, Input, Checkbox } from 'semantic-ui-react'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
const createUserMutation = gql`
|
||||||
|
mutation createUser($username: String, $rootPath: String, $admin: Boolean) {
|
||||||
|
createUser(username: $username, rootPath: $rootPath, admin: $admin) {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
rootPath
|
||||||
|
admin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
username: '',
|
||||||
|
rootPath: '',
|
||||||
|
admin: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddUserRow = ({ setShow, show, onUserAdded }) => {
|
||||||
|
const [state, setState] = useState(initialState)
|
||||||
|
|
||||||
|
function updateInput(event, key) {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
[key]: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Mutation
|
||||||
|
mutation={createUserMutation}
|
||||||
|
onCompleted={data => {
|
||||||
|
onUserAdded()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(createUser, { loading, data }) => (
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell>
|
||||||
|
<Input
|
||||||
|
placeholder="Username"
|
||||||
|
value={state.username}
|
||||||
|
onChange={e => updateInput(e, 'username')}
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Input
|
||||||
|
placeholder="/path/to/photos"
|
||||||
|
value={state.rootPath}
|
||||||
|
onChange={e => updateInput(e, 'rootPath')}
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Checkbox
|
||||||
|
toggle
|
||||||
|
checked={state.admin}
|
||||||
|
onChange={(e, data) => {
|
||||||
|
console.log(data)
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
admin: data.checked,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Button.Group>
|
||||||
|
<Button negative onClick={e => setShow(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
positive
|
||||||
|
onClick={() => {
|
||||||
|
createUser({
|
||||||
|
variables: {
|
||||||
|
username: state.username,
|
||||||
|
rootPath: state.rootPath,
|
||||||
|
admin: state.admin,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
setState(initialState)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add User
|
||||||
|
</Button>
|
||||||
|
</Button.Group>
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
)}
|
||||||
|
</Mutation>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddUserRow
|
|
@ -4,6 +4,7 @@ import Layout from '../../Layout'
|
||||||
import { Button, Icon } from 'semantic-ui-react'
|
import { Button, Icon } from 'semantic-ui-react'
|
||||||
import { Mutation } from 'react-apollo'
|
import { Mutation } from 'react-apollo'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
import UsersTable from './UsersTable'
|
||||||
|
|
||||||
const scanMutation = gql`
|
const scanMutation = gql`
|
||||||
mutation scanAllMutation {
|
mutation scanAllMutation {
|
||||||
|
@ -18,7 +19,7 @@ const SettingsPage = () => (
|
||||||
<Layout>
|
<Layout>
|
||||||
<h1>Settings</h1>
|
<h1>Settings</h1>
|
||||||
<Mutation mutation={scanMutation}>
|
<Mutation mutation={scanMutation}>
|
||||||
{(scan, { data }) => (
|
{(scan, { data, called }) => (
|
||||||
<>
|
<>
|
||||||
<h2>Scanner</h2>
|
<h2>Scanner</h2>
|
||||||
<Button
|
<Button
|
||||||
|
@ -27,15 +28,15 @@ const SettingsPage = () => (
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
scan()
|
scan()
|
||||||
}}
|
}}
|
||||||
disabled={data && data.scanAll && data.scanAll.success}
|
disabled={called}
|
||||||
>
|
>
|
||||||
<Icon name="sync" />
|
<Icon name="sync" />
|
||||||
Scan All
|
Scan All
|
||||||
</Button>
|
</Button>
|
||||||
<p>Scan for new images for all users</p>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Mutation>
|
</Mutation>
|
||||||
|
<UsersTable />
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { Mutation } from 'react-apollo'
|
||||||
|
import { Table, Icon, Button, Input, Checkbox, Modal } from 'semantic-ui-react'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
|
const updateUserMutation = gql`
|
||||||
|
mutation updateUser(
|
||||||
|
$id: ID!
|
||||||
|
$username: String
|
||||||
|
$rootPath: String
|
||||||
|
$admin: Boolean
|
||||||
|
) {
|
||||||
|
updateUser(
|
||||||
|
id: $id
|
||||||
|
username: $username
|
||||||
|
rootPath: $rootPath
|
||||||
|
admin: $admin
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
rootPath
|
||||||
|
admin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const deleteUserMutation = gql`
|
||||||
|
mutation deleteUser($id: ID!) {
|
||||||
|
deleteUser(id: $id) {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const UserRow = ({ user, refetchUsers }) => {
|
||||||
|
const [state, setState] = useState({
|
||||||
|
...user,
|
||||||
|
editing: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [showComfirmDelete, setConfirmDelete] = useState(false)
|
||||||
|
|
||||||
|
function updateInput(event, key) {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
[key]: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.editing) {
|
||||||
|
return (
|
||||||
|
<Mutation
|
||||||
|
mutation={updateUserMutation}
|
||||||
|
onCompleted={data => {
|
||||||
|
setState({
|
||||||
|
...data.updateUser,
|
||||||
|
editing: false,
|
||||||
|
})
|
||||||
|
refetchUsers()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(updateUser, { loading, data }) => (
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell>
|
||||||
|
<Input
|
||||||
|
placeholder={user.username}
|
||||||
|
value={state.username}
|
||||||
|
onChange={e => updateInput(e, 'username')}
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Input
|
||||||
|
placeholder={user.rootPath}
|
||||||
|
value={state.rootPath}
|
||||||
|
onChange={e => updateInput(e, 'rootPath')}
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Checkbox
|
||||||
|
toggle
|
||||||
|
checked={state.admin}
|
||||||
|
onChange={(e, data) => {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
admin: data.checked,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Button.Group>
|
||||||
|
<Button
|
||||||
|
negative
|
||||||
|
onClick={e =>
|
||||||
|
setState({
|
||||||
|
...state.oldState,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
positive
|
||||||
|
onClick={() =>
|
||||||
|
updateUser({
|
||||||
|
variables: {
|
||||||
|
id: user.id,
|
||||||
|
username: state.username,
|
||||||
|
rootPath: state.rootPath,
|
||||||
|
admin: state.admin,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Button.Group>
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
)}
|
||||||
|
</Mutation>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Mutation
|
||||||
|
mutation={deleteUserMutation}
|
||||||
|
onCompleted={() => {
|
||||||
|
refetchUsers()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(deleteUser, { loading, data }) => (
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell>{user.username}</Table.Cell>
|
||||||
|
<Table.Cell>{user.rootPath}</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{user.admin ? <Icon name="checkmark" size="large" /> : null}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Button.Group>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setState({ ...state, editing: true, oldState: state })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name="edit" />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
negative
|
||||||
|
onClick={() => {
|
||||||
|
setConfirmDelete(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name="delete" />
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Modal open={showComfirmDelete}>
|
||||||
|
<Modal.Header>Delete user</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<p>
|
||||||
|
{`Are you sure, you want to delete `}
|
||||||
|
<b>{user.username}</b>?
|
||||||
|
</p>
|
||||||
|
<p>{`This action cannot be undone`}</p>
|
||||||
|
</Modal.Content>
|
||||||
|
<Modal.Actions>
|
||||||
|
<Button onClick={() => setConfirmDelete(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
negative
|
||||||
|
onClick={() => {
|
||||||
|
setConfirmDelete(false)
|
||||||
|
deleteUser({
|
||||||
|
variables: {
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete {user.username}
|
||||||
|
</Button>
|
||||||
|
</Modal.Actions>
|
||||||
|
</Modal>
|
||||||
|
</Button.Group>
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
)}
|
||||||
|
</Mutation>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserRow
|
|
@ -0,0 +1,80 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
import { Table, Loader, Button } from 'semantic-ui-react'
|
||||||
|
import { Query } from 'react-apollo'
|
||||||
|
import gql from 'graphql-tag'
|
||||||
|
import UserRow from './UserRow'
|
||||||
|
import AddUserRow from './AddUserRow'
|
||||||
|
|
||||||
|
const usersQuery = gql`
|
||||||
|
query settingsUsersQuery {
|
||||||
|
User {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
rootPath
|
||||||
|
admin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const UsersTable = () => {
|
||||||
|
const [showAddUser, setShowAddUser] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Query query={usersQuery}>
|
||||||
|
{({ loading, error, data, refetch }) => {
|
||||||
|
let userRows = []
|
||||||
|
if (data && data.User) {
|
||||||
|
userRows = data.User.map(user => (
|
||||||
|
<UserRow user={user} refetchUsers={refetch} key={user.id} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: 24 }}>
|
||||||
|
<h2>Users</h2>
|
||||||
|
<Loader active={loading} />
|
||||||
|
<Table celled>
|
||||||
|
<Table.Header>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.HeaderCell>Username</Table.HeaderCell>
|
||||||
|
<Table.HeaderCell>Photo path</Table.HeaderCell>
|
||||||
|
<Table.HeaderCell>Admin</Table.HeaderCell>
|
||||||
|
<Table.HeaderCell>Action</Table.HeaderCell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
|
||||||
|
<Table.Body>
|
||||||
|
{userRows}
|
||||||
|
<AddUserRow
|
||||||
|
show={showAddUser}
|
||||||
|
setShow={setShowAddUser}
|
||||||
|
onUserAdded={() => {
|
||||||
|
setShowAddUser(false)
|
||||||
|
refetch()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Table.Body>
|
||||||
|
|
||||||
|
<Table.Footer>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.HeaderCell colSpan="4">
|
||||||
|
<Button
|
||||||
|
positive
|
||||||
|
floated="right"
|
||||||
|
onClick={e => setShowAddUser(true)}
|
||||||
|
>
|
||||||
|
New user
|
||||||
|
</Button>
|
||||||
|
</Table.HeaderCell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Footer>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Query>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UsersTable
|
Loading…
Reference in New Issue