diff options
author | crupest <crupest@outlook.com> | 2021-06-15 14:14:28 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-06-15 14:14:28 +0800 |
commit | 47587812b809fee2a95c76266d9d0e42fc4ac1ca (patch) | |
tree | bfaa7320c838e21edf88b5a037263f89a8012222 /FrontEnd/src/app/views/admin | |
parent | da26373c7fc13cded47428b27638b349b0432437 (diff) | |
download | timeline-47587812b809fee2a95c76266d9d0e42fc4ac1ca.tar.gz timeline-47587812b809fee2a95c76266d9d0e42fc4ac1ca.tar.bz2 timeline-47587812b809fee2a95c76266d9d0e42fc4ac1ca.zip |
...
Diffstat (limited to 'FrontEnd/src/app/views/admin')
-rw-r--r-- | FrontEnd/src/app/views/admin/Admin.tsx | 48 | ||||
-rw-r--r-- | FrontEnd/src/app/views/admin/AdminNav.tsx | 44 | ||||
-rw-r--r-- | FrontEnd/src/app/views/admin/MoreAdmin.tsx | 13 | ||||
-rw-r--r-- | FrontEnd/src/app/views/admin/UserAdmin.tsx | 396 | ||||
-rw-r--r-- | FrontEnd/src/app/views/admin/admin.sass | 22 |
5 files changed, 0 insertions, 523 deletions
diff --git a/FrontEnd/src/app/views/admin/Admin.tsx b/FrontEnd/src/app/views/admin/Admin.tsx deleted file mode 100644 index 0b6d1f05..00000000 --- a/FrontEnd/src/app/views/admin/Admin.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { Fragment } from "react"; -import { Redirect, Route, Switch, useRouteMatch, match } from "react-router"; -import { Container } from "react-bootstrap"; -import { useTranslation } from "react-i18next"; - -import { AuthUser } from "@/services/user"; - -import AdminNav from "./AdminNav"; -import UserAdmin from "./UserAdmin"; -import MoreAdmin from "./MoreAdmin"; - -interface AdminProps { - user: AuthUser; -} - -const Admin: React.FC<AdminProps> = ({ user }) => { - useTranslation("admin"); - - const match = useRouteMatch(); - - return ( - <Fragment> - <Switch> - <Redirect from={match.path} to={`${match.path}/users`} exact /> - <Route path={`${match.path}/:name`}> - {(p) => { - const match = p.match as match<{ name: string }>; - const name = match.params["name"]; - return ( - <Container> - <AdminNav /> - {(() => { - if (name === "users") { - return <UserAdmin user={user} />; - } else if (name === "more") { - return <MoreAdmin user={user} />; - } - })()} - </Container> - ); - }} - </Route> - </Switch> - </Fragment> - ); -}; - -export default Admin; diff --git a/FrontEnd/src/app/views/admin/AdminNav.tsx b/FrontEnd/src/app/views/admin/AdminNav.tsx deleted file mode 100644 index 47e2138f..00000000 --- a/FrontEnd/src/app/views/admin/AdminNav.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from "react"; -import { Nav } from "react-bootstrap"; -import { useTranslation } from "react-i18next"; -import { useHistory, useRouteMatch } from "react-router"; - -const AdminNav: React.FC = () => { - const match = useRouteMatch<{ name: string }>(); - const history = useHistory(); - - const { t } = useTranslation(); - - const name = match.params.name; - - function toggle(newTab: string): void { - history.push(`/admin/${newTab}`); - } - - return ( - <Nav variant="tabs" className="my-2"> - <Nav.Item> - <Nav.Link - active={name === "users"} - onClick={() => { - toggle("users"); - }} - > - {t("admin:nav.users")} - </Nav.Link> - </Nav.Item> - <Nav.Item> - <Nav.Link - active={name === "more"} - onClick={() => { - toggle("more"); - }} - > - {t("admin:nav.more")} - </Nav.Link> - </Nav.Item> - </Nav> - ); -}; - -export default AdminNav; diff --git a/FrontEnd/src/app/views/admin/MoreAdmin.tsx b/FrontEnd/src/app/views/admin/MoreAdmin.tsx deleted file mode 100644 index 042789a0..00000000 --- a/FrontEnd/src/app/views/admin/MoreAdmin.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; - -import { AuthUser } from "@/services/user"; - -export interface MoreAdminProps { - user: AuthUser; -} - -const MoreAdmin: React.FC<MoreAdminProps> = () => { - return <>More...</>; -}; - -export default MoreAdmin; diff --git a/FrontEnd/src/app/views/admin/UserAdmin.tsx b/FrontEnd/src/app/views/admin/UserAdmin.tsx deleted file mode 100644 index 558d3aee..00000000 --- a/FrontEnd/src/app/views/admin/UserAdmin.tsx +++ /dev/null @@ -1,396 +0,0 @@ -import React, { useState, useEffect } from "react"; -import classnames from "classnames"; -import { ListGroup, Row, Col, Spinner, Button } from "react-bootstrap"; - -import OperationDialog, { - OperationDialogBoolInput, -} from "../common/OperationDialog"; - -import { AuthUser } from "@/services/user"; -import { - getHttpUserClient, - HttpUser, - kUserPermissionList, - UserPermission, -} from "@/http/user"; -import { Trans, useTranslation } from "react-i18next"; - -interface DialogProps<TData = undefined, TReturn = undefined> { - open: boolean; - close: () => void; - data: TData; - onSuccess: (data: TReturn) => void; -} - -const CreateUserDialog: React.FC<DialogProps<undefined, HttpUser>> = ({ - open, - close, - onSuccess, -}) => { - return ( - <OperationDialog - title="admin:user.dialog.create.title" - themeColor="success" - inputPrompt="admin:user.dialog.create.prompt" - inputScheme={ - [ - { type: "text", label: "admin:user.username" }, - { type: "text", label: "admin:user.password" }, - ] as const - } - onProcess={([username, password]) => - getHttpUserClient().post({ - username, - password, - }) - } - close={close} - open={open} - onSuccessAndClose={onSuccess} - /> - ); -}; - -const UsernameLabel: React.FC = (props) => { - return <span style={{ color: "blue" }}>{props.children}</span>; -}; - -const UserDeleteDialog: React.FC<DialogProps<{ username: string }, unknown>> = - ({ open, close, data: { username }, onSuccess }) => { - return ( - <OperationDialog - open={open} - close={close} - title="admin:user.dialog.delete.title" - themeColor="danger" - inputPrompt={() => ( - <Trans i18nKey="admin:user.dialog.delete.prompt"> - 0<UsernameLabel>{username}</UsernameLabel>2 - </Trans> - )} - onProcess={() => getHttpUserClient().delete(username)} - onSuccessAndClose={onSuccess} - /> - ); - }; - -const UserModifyDialog: React.FC< - DialogProps< - { - oldUser: HttpUser; - }, - HttpUser - > -> = ({ open, close, data: { oldUser }, onSuccess }) => { - return ( - <OperationDialog - open={open} - close={close} - title="admin:user.dialog.modify.title" - themeColor="danger" - inputPrompt={() => ( - <Trans i18nKey="admin:user.dialog.modify.prompt"> - 0<UsernameLabel>{oldUser.username}</UsernameLabel>2 - </Trans> - )} - inputScheme={ - [ - { - type: "text", - label: "admin:user.username", - initValue: oldUser.username, - }, - { type: "text", label: "admin:user.password" }, - { - type: "text", - label: "admin:user.nickname", - initValue: oldUser.nickname, - }, - ] as const - } - onProcess={([username, password, nickname]) => - getHttpUserClient().patch(oldUser.username, { - username: username !== oldUser.username ? username : undefined, - password: password !== "" ? password : undefined, - nickname: nickname !== oldUser.nickname ? nickname : undefined, - }) - } - onSuccessAndClose={onSuccess} - /> - ); -}; - -const UserPermissionModifyDialog: React.FC< - DialogProps< - { - username: string; - permissions: UserPermission[]; - }, - UserPermission[] - > -> = ({ open, close, data: { username, permissions }, onSuccess }) => { - const oldPermissionBoolList: boolean[] = kUserPermissionList.map( - (permission) => permissions.includes(permission) - ); - - return ( - <OperationDialog - open={open} - close={close} - title="admin:user.dialog.modifyPermissions.title" - themeColor="danger" - inputPrompt={() => ( - <Trans i18nKey="admin:user.dialog.modifyPermissions.prompt"> - 0<UsernameLabel>{username}</UsernameLabel>2 - </Trans> - )} - inputScheme={kUserPermissionList.map<OperationDialogBoolInput>( - (permission, index) => ({ - type: "bool", - label: permission, - initValue: oldPermissionBoolList[index], - }) - )} - onProcess={async (newPermissionBoolList): Promise<boolean[]> => { - for (let index = 0; index < kUserPermissionList.length; index++) { - const oldValue = oldPermissionBoolList[index]; - const newValue = newPermissionBoolList[index]; - const permission = kUserPermissionList[index]; - if (oldValue === newValue) continue; - if (newValue) { - await getHttpUserClient().putUserPermission(username, permission); - } else { - await getHttpUserClient().deleteUserPermission( - username, - permission - ); - } - } - return newPermissionBoolList; - }} - onSuccessAndClose={(newPermissionBoolList: boolean[]) => { - const permissions: UserPermission[] = []; - for (let index = 0; index < kUserPermissionList.length; index++) { - if (newPermissionBoolList[index]) { - permissions.push(kUserPermissionList[index]); - } - } - onSuccess(permissions); - }} - /> - ); -}; - -const kModify = "modify"; -const kModifyPermission = "permission"; -const kDelete = "delete"; - -type TModify = typeof kModify; -type TModifyPermission = typeof kModifyPermission; -type TDelete = typeof kDelete; - -type ContextMenuItem = TModify | TModifyPermission | TDelete; - -interface UserItemProps { - on: { [key in ContextMenuItem]: () => void }; - user: HttpUser; -} - -const UserItem: React.FC<UserItemProps> = ({ user, on }) => { - const { t } = useTranslation(); - - const [editMaskVisible, setEditMaskVisible] = React.useState<boolean>(false); - - return ( - <ListGroup.Item className="admin-user-item"> - <i - className="bi-pencil-square float-end icon-button text-warning" - onClick={() => setEditMaskVisible(true)} - /> - <h4 className="text-primary">{user.username}</h4> - <div className="text-secondary"> - {t("admin:user.nickname")} - {user.nickname} - </div> - <div className="text-secondary"> - {t("admin:user.uniqueId")} - {user.uniqueId} - </div> - <div className="text-secondary"> - {t("admin:user.permissions")} - {user.permissions.map((permission) => { - return ( - <span key={permission} className="text-danger"> - {permission}{" "} - </span> - ); - })} - </div> - <div - className={classnames("edit-mask", !editMaskVisible && "d-none")} - onClick={() => setEditMaskVisible(false)} - > - <button className="text-button primary" onClick={on[kModify]}> - {t("admin:user.modify")} - </button> - <button className="text-button primary" onClick={on[kModifyPermission]}> - {t("admin:user.modifyPermissions")} - </button> - <button className="text-button danger" onClick={on[kDelete]}> - {t("admin:user.delete")} - </button> - </div> - </ListGroup.Item> - ); -}; - -interface UserAdminProps { - user: AuthUser; -} - -const UserAdmin: React.FC<UserAdminProps> = () => { - const { t } = useTranslation(); - - type DialogInfo = - | null - | { - type: "create"; - } - | { - type: TModify; - user: HttpUser; - } - | { - type: TModifyPermission; - username: string; - permissions: UserPermission[]; - } - | { type: TDelete; username: string }; - - const [users, setUsers] = useState<HttpUser[] | null>(null); - const [dialog, setDialog] = useState<DialogInfo>(null); - const [usersVersion, setUsersVersion] = useState<number>(0); - const updateUsers = (): void => { - setUsersVersion(usersVersion + 1); - }; - - useEffect(() => { - let subscribe = true; - void getHttpUserClient() - .list() - .then((us) => { - if (subscribe) { - setUsers(us); - } - }); - return () => { - subscribe = false; - }; - }, [usersVersion]); - - let dialogNode: React.ReactNode; - if (dialog) { - switch (dialog.type) { - case "create": - dialogNode = ( - <CreateUserDialog - open - close={() => setDialog(null)} - data={undefined} - onSuccess={updateUsers} - /> - ); - break; - case kDelete: - dialogNode = ( - <UserDeleteDialog - open - close={() => setDialog(null)} - data={{ username: dialog.username }} - onSuccess={updateUsers} - /> - ); - break; - case kModify: - dialogNode = ( - <UserModifyDialog - open - close={() => setDialog(null)} - data={{ oldUser: dialog.user }} - onSuccess={updateUsers} - /> - ); - break; - case kModifyPermission: - dialogNode = ( - <UserPermissionModifyDialog - open - close={() => setDialog(null)} - data={{ - username: dialog.username, - permissions: dialog.permissions, - }} - onSuccess={updateUsers} - /> - ); - break; - } - } - - if (users) { - const userComponents = users.map((user) => { - return ( - <UserItem - key={user.username} - user={user} - on={{ - modify: () => { - setDialog({ - type: "modify", - user, - }); - }, - permission: () => { - setDialog({ - type: kModifyPermission, - username: user.username, - permissions: user.permissions, - }); - }, - delete: () => { - setDialog({ - type: "delete", - username: user.username, - }); - }, - }} - /> - ); - }); - - return ( - <> - <Row className="justify-content-end my-2"> - <Col xs="auto"> - <Button - variant="outline-success" - onClick={() => - setDialog({ - type: "create", - }) - } - > - {t("admin:create")} - </Button> - </Col> - </Row> - {userComponents} - {dialogNode} - </> - ); - } else { - return <Spinner animation="border" />; - } -}; - -export default UserAdmin; diff --git a/FrontEnd/src/app/views/admin/admin.sass b/FrontEnd/src/app/views/admin/admin.sass deleted file mode 100644 index 1ce010f8..00000000 --- a/FrontEnd/src/app/views/admin/admin.sass +++ /dev/null @@ -1,22 +0,0 @@ -.admin-user-item
- position: relative
-
- .edit-mask
- position: absolute
- top: 0
- left: 0
- bottom: 0
- right: 0
-
- background: #ffffffc5
- position: absolute
-
- display: flex
- justify-content: center
- align-items: center
-
- @include media-breakpoint-down(xs)
- flex-direction: column
-
- button
- margin: 0.5em 2em
|