diff --git a/src/App.js b/src/App.js index d607194..0694485 100644 --- a/src/App.js +++ b/src/App.js @@ -11,7 +11,6 @@ import Login from './pages/Login'; import { get, setSession, setSettings } from './util/Util'; import { PrivateRoute } from './structures/PrivateRoute'; import { UnauthedRoute } from './structures/UnauthedRoute'; -import Users from './pages/Users'; import Admin from './pages/Admin'; import TitledPage from './components/TitledPage'; import Register from './pages/Register'; @@ -45,8 +44,12 @@ function App() { { to: '/applications', label: 'Applications', relative: true } ] }, - { to: '/users', label: 'Users' }, - { to: '/admin', label: 'Admin' } + { + to: '/admin', label: 'Admin', items: [ + { to: '/users', label: 'Users', relative: true }, + { to: '/roles', label: 'Roles', relative: true } + ] + } ]; if (loading) return null; @@ -79,12 +82,6 @@ function App() { } /> - - - - - } /> - diff --git a/src/components/PageControls.js b/src/components/PageControls.js new file mode 100644 index 0000000..9d29ab3 --- /dev/null +++ b/src/components/PageControls.js @@ -0,0 +1,19 @@ +import React from "react"; +import { useNavigate } from "react-router"; + +export const PageButtons = ({setPage, page, length}) => { + return
+ +

Page: {page}

+ +
; +}; + +export const BackButton = () => { + const navigate = useNavigate(); + return ; +}; \ No newline at end of file diff --git a/src/components/PermissionsView.js b/src/components/PermissionsView.js new file mode 100644 index 0000000..6851a28 --- /dev/null +++ b/src/components/PermissionsView.js @@ -0,0 +1,64 @@ +import React, { useRef } from "react"; + +export const Permission = ({ name, value, chain, updatePerms }) => { + const inputRef = useRef(); + const onChange = () => { + const val = inputRef.current.value; + updatePerms(chain, val); + }; + + return
  • + + +
  • ; +}; + +export const PermissionGroup = ({ name, value, chain, updatePerms }) => { + const elements = []; + + for (const [perm, val] of Object.entries(value)) { + const props = { key: perm, name: perm, value: val, updatePerms, chain: `${chain}:${perm}` }; + if(typeof val ==='object') elements.push(); + else elements.push(); + } + return
  • +
    + Group: {name} +
    +
      + {elements} +
    +
  • ; +}; + +export const Permissions = ({ perms, updatePerms }) => { + + const elements = []; + const keys = Object.keys(perms); + for (const perm of keys) { + const props = { key: perm, name: perm, value: perms[perm], chain: perm, updatePerms }; + let Elem = null; + switch (typeof perms[perm]) { + case 'number': + Elem = Permission; + break; + case 'object': + Elem = PermissionGroup; + break; + default: + // eslint-disable-next-line react/display-name + Elem = () => { + return

    Uknown permission structure

    ; + }; + break; + } + elements.push(); + } + + return
    +

    Permissions

    +
      + {elements} +
    +
    ; +}; \ No newline at end of file diff --git a/src/components/RateLimitsView.js b/src/components/RateLimitsView.js new file mode 100644 index 0000000..9666690 --- /dev/null +++ b/src/components/RateLimitsView.js @@ -0,0 +1,35 @@ +import React from "react"; + +const RateLimit = ({route, limit}) => { + + return
    +

    {route}

    + + + + + + +
    ; +}; + +export const RateLimits = ({ rateLimits }) => { + + const routes = Object.keys(rateLimits); + const elements = routes.map((route, index) => { + return
  • + +
  • ; + }); + + return
    +

    Rate Limits

    +
      + {elements} +
    + + + {JSON.stringify(rateLimits, null, 4)} + +
    ; +}; \ No newline at end of file diff --git a/src/css/pages/Users.css b/src/css/pages/Users.css index e3fba41..8935c89 100644 --- a/src/css/pages/Users.css +++ b/src/css/pages/Users.css @@ -53,13 +53,13 @@ li input { .tree input{ width: 60px !important; } -.userActionButtons{ +.actionButtons{ display:flex; flex-direction: row; gap: 4px; margin-bottom: 1em; } -.userActionButtons .button+.button{ +.actionButtons .button+.button{ margin-left: 0; } .registerCodeWrapper{ diff --git a/src/pages/Admin.js b/src/pages/Admin.js index 483e806..f788043 100644 --- a/src/pages/Admin.js +++ b/src/pages/Admin.js @@ -1,9 +1,13 @@ import React, { useRef, useState } from "react"; +import { Route, Routes } from "react-router"; import FileSelector from "../components/FileSelector"; import '../css/pages/Admin.css'; +import ErrorBoundary from "../util/ErrorBoundary"; +import Roles from "./admin/Roles"; +import Users from "./admin/Users"; -const Admin = () => { +const Main = () => { const [file, setFile] = useState(null); const submit = (event) => { @@ -31,4 +35,15 @@ const Admin = () => { ; }; +const Admin = () => { + + return + + } /> + } /> + } /> + + ; +}; + export default Admin; \ No newline at end of file diff --git a/src/pages/admin/Roles.js b/src/pages/admin/Roles.js new file mode 100644 index 0000000..654bcad --- /dev/null +++ b/src/pages/admin/Roles.js @@ -0,0 +1,124 @@ +import React, { useEffect, useState } from "react"; +import { Route, Routes, useNavigate, useParams } from "react-router"; +import { BackButton, PageButtons } from "../../components/PageControls"; +import { Permissions } from "../../components/PermissionsView"; +import { RateLimits } from "../../components/RateLimitsView"; +import { Table, TableListEntry } from "../../components/Table"; +import ErrorBoundary from "../../util/ErrorBoundary"; +import { get, patch } from "../../util/Util"; + +const Role = ({ role }) => { + + const perms = { ...role.permissions }; + const updatePerms = (chain, value) => { + value = parseInt(value); + if (isNaN(value)) return; + + let selected = perms; + const keys = chain.split(':'); + for (const key of keys) { + if (key === keys[keys.length - 1]) selected[key] = value; + else selected = selected[key]; + } + }; + + const commitPerms = async () => { + await patch(`/api/roles/${role.id}`, perms); + }; + + return
    +
    +
    + +
    + +

    Role {role.id}

    +
    + +
    + {role.disabled ? : } + +
    + +

    Created: {new Date(role.createdTimestamp).toDateString()}

    +

    Disabled: {role.disabled.toString()}

    + + + + +
    + +
    + + +
    +
    + +
    + +
    + +
    ; +}; + +const Roles = () => { + + const [error, setError] = useState(null); + const [roles, setRoles] = useState([]); + const [page, setPage] = useState(1); + const [loading, setLoading] = useState(true); + + useEffect(() => { + (async () => { + const result = await get('/api/roles', { page }); + if (result.success) { + setError(null); + setRoles(result.data); + } else { + setError(result.message); + } + setLoading(false); + })(); + }, [page]); + + const RoleWrapper = () => { + const { id } = useParams(); + const role = roles.find(r => r.id === id); + if(!role) return

    Unknown role

    ; + return ; + }; + + const navigate = useNavigate(); + const RoleListWrapper = () => { + return
    +
    +

    All Roles

    + + {roles.map(role => { + navigate(role.id); + }} + key={role.id} + item={role} + itemKeys={['name', 'id']} + />)} +
    + + + +
    +
    ; + }; + + return
    + {error &&

    {error}

    } + + + } /> + }/> + + +
    ; +}; + +export default Roles; \ No newline at end of file diff --git a/src/pages/Users.js b/src/pages/admin/Users.js similarity index 70% rename from src/pages/Users.js rename to src/pages/admin/Users.js index 77ae4a6..364d4df 100644 --- a/src/pages/Users.js +++ b/src/pages/admin/Users.js @@ -1,72 +1,11 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useState } from "react"; import { Route, Routes, useNavigate, useParams } from "react-router"; -import ErrorBoundary from "../util/ErrorBoundary"; -import { get, post } from '../util/Util'; -import '../css/pages/Users.css'; -import { Table, TableListEntry } from "../components/Table"; - -const Permission = ({ name, value, chain, updatePerms }) => { - const inputRef = useRef(); - const onChange = () => { - const val = inputRef.current.value; - updatePerms(chain, val); - }; - - return
  • - - -
  • ; -}; - -const PermissionGroup = ({ name, value, chain, updatePerms }) => { - const elements = []; - - for (const [perm, val] of Object.entries(value)) { - const props = { key: perm, name: perm, value: val, updatePerms, chain: `${chain}:${perm}` }; - if(typeof val ==='object') elements.push(); - else elements.push(); - } - return
  • -
    - Group: {name} -
    -
      - {elements} -
    -
  • ; -}; - -const Permissions = ({ perms, updatePerms }) => { - - const elements = []; - const keys = Object.keys(perms); - for (const perm of keys) { - const props = { key: perm, name: perm, value: perms[perm], chain: perm, updatePerms }; - let Elem = null; - switch (typeof perms[perm]) { - case 'number': - Elem = Permission; - break; - case 'object': - Elem = PermissionGroup; - break; - default: - // eslint-disable-next-line react/display-name - Elem = () => { - return

    Uknown permission structure

    ; - }; - break; - } - elements.push(); - } - - return
    -

    Permissions

    -
      - {elements} -
    -
    ; -}; +import ErrorBoundary from "../../util/ErrorBoundary"; +import { get, post } from '../../util/Util'; +import '../../css/pages/Users.css'; +import { Table, TableListEntry } from "../../components/Table"; +import { BackButton, PageButtons } from "../../components/PageControls"; +import { Permissions } from "../../components/PermissionsView"; const ApplicationList = ({ apps }) => { @@ -99,8 +38,7 @@ const Application = ({ app }) => { // TODO: Groups, description, notes const User = ({ user }) => { - - const navigate = useNavigate(); + const [apps, updateApps] = useState([]); const perms = {...user.permissions}; @@ -135,16 +73,13 @@ const User = ({ user }) => { }; return
    -
    - +

    User {user.id}

    -
    +
    {user.disabled ? : }
    @@ -251,20 +186,14 @@ const Users = () => { {users.map(user => { - navigate(`/users/${user.id}`); + navigate(user.id); }} key={user.id} item={user} itemKeys={['name', 'id']} />)}
    -
    - -

    Page: {page}

    - -
    +
    diff --git a/src/util/Util.js b/src/util/Util.js index f50b512..b470bca 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -78,6 +78,26 @@ export const post = async (url, body, opts = {}) => { return parseResponse(response); }; +export const patch = async (url, body, opts = {}) => { + const options = { + method: 'patch', + body + }; + + if (opts.headers !== null) { + options.headers = { + 'Content-Type': 'application/json', + ...opts.headers || {} + }; + } + + if (options.headers && options.headers['Content-Type'] === 'application/json' && body) options.body = JSON.stringify(body); + + const response = await fetch(url, options); + return parseResponse(response); + +}; + export const get = async (url, params) => { if (params) url += '?' + Object.entries(params) .map(([key, val]) => `${key}=${val}`)