aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp/src/admin/UserAdmin.tsx
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2020-05-30 23:41:31 +0800
committerGitHub <noreply@github.com>2020-05-30 23:41:31 +0800
commitc8d71a3b5e7aef4fcf75bad44b7be90b45aaaf0b (patch)
treef76ecd77c99c4136d1ab4771b91a92f1e5ad4a35 /Timeline/ClientApp/src/admin/UserAdmin.tsx
parent7e393559d2883a37b1be0c82cccc06bc97c3d102 (diff)
parent83de798e74323e96e81b8196b04e23ed2bd4efbf (diff)
downloadtimeline-c8d71a3b5e7aef4fcf75bad44b7be90b45aaaf0b.tar.gz
timeline-c8d71a3b5e7aef4fcf75bad44b7be90b45aaaf0b.tar.bz2
timeline-c8d71a3b5e7aef4fcf75bad44b7be90b45aaaf0b.zip
Merge pull request #72 from crupest/merge-frontend
Merge frontend repo into this repo.
Diffstat (limited to 'Timeline/ClientApp/src/admin/UserAdmin.tsx')
-rw-r--r--Timeline/ClientApp/src/admin/UserAdmin.tsx458
1 files changed, 458 insertions, 0 deletions
diff --git a/Timeline/ClientApp/src/admin/UserAdmin.tsx b/Timeline/ClientApp/src/admin/UserAdmin.tsx
new file mode 100644
index 00000000..afef79c9
--- /dev/null
+++ b/Timeline/ClientApp/src/admin/UserAdmin.tsx
@@ -0,0 +1,458 @@
+import React, { useState, useEffect } from 'react';
+import {
+ ListGroupItem,
+ Row,
+ Col,
+ UncontrolledDropdown,
+ DropdownToggle,
+ DropdownMenu,
+ DropdownItem,
+ Spinner,
+ Button,
+} from 'reactstrap';
+import axios from 'axios';
+
+import OperationDialog from '../common/OperationDialog';
+
+import { User, UserWithToken } from '../data/user';
+import { apiBaseUrl } from '../config';
+
+async function fetchUserList(_token: string): Promise<User[]> {
+ const res = await axios.get(`${apiBaseUrl}/users`);
+ return res.data;
+}
+
+interface CreateUserInfo {
+ username: string;
+ password: string;
+ administrator: boolean;
+}
+
+async function createUser(user: CreateUserInfo, token: string): Promise<User> {
+ const res = await axios.post<User>(
+ `${apiBaseUrl}/userop/createuser?token=${token}`,
+ user
+ );
+ return res.data;
+}
+
+function deleteUser(username: string, token: string): Promise<void> {
+ return axios.delete(`${apiBaseUrl}/users/${username}?token=${token}`);
+}
+
+function changeUsername(
+ oldUsername: string,
+ newUsername: string,
+ token: string
+): Promise<void> {
+ return axios.patch(`${apiBaseUrl}/users/${oldUsername}?token=${token}`, {
+ username: newUsername,
+ });
+}
+
+function changePassword(
+ username: string,
+ newPassword: string,
+ token: string
+): Promise<void> {
+ return axios.patch(`${apiBaseUrl}/users/${username}?token=${token}`, {
+ password: newPassword,
+ });
+}
+
+function changePermission(
+ username: string,
+ newPermission: boolean,
+ token: string
+): Promise<void> {
+ return axios.patch(`${apiBaseUrl}/users/${username}?token=${token}`, {
+ administrator: newPermission,
+ });
+}
+
+const kChangeUsername = 'changeusername';
+const kChangePassword = 'changepassword';
+const kChangePermission = 'changepermission';
+const kDelete = 'delete';
+
+type TChangeUsername = typeof kChangeUsername;
+type TChangePassword = typeof kChangePassword;
+type TChangePermission = typeof kChangePermission;
+type TDelete = typeof kDelete;
+
+type ContextMenuItem =
+ | TChangeUsername
+ | TChangePassword
+ | TChangePermission
+ | TDelete;
+
+interface UserCardProps {
+ onContextMenu: (item: ContextMenuItem) => void;
+ user: User;
+}
+
+const UserItem: React.FC<UserCardProps> = (props) => {
+ const user = props.user;
+
+ const createClickCallback = (item: ContextMenuItem): (() => void) => {
+ return () => {
+ props.onContextMenu(item);
+ };
+ };
+
+ return (
+ <ListGroupItem className="container">
+ <Row className="align-items-center">
+ <Col>
+ <p className="mb-0 text-primary">{user.username}</p>
+ <small
+ className={user.administrator ? 'text-danger' : 'text-secondary'}
+ >
+ {user.administrator ? 'administrator' : 'user'}
+ </small>
+ </Col>
+ <Col className="col-auto">
+ <UncontrolledDropdown>
+ <DropdownToggle color="warning" className="text-light" caret>
+ Manage
+ </DropdownToggle>
+ <DropdownMenu>
+ <DropdownItem onClick={createClickCallback(kChangeUsername)}>
+ Change Username
+ </DropdownItem>
+ <DropdownItem onClick={createClickCallback(kChangePassword)}>
+ Change Password
+ </DropdownItem>
+ <DropdownItem onClick={createClickCallback(kChangePermission)}>
+ Change Permission
+ </DropdownItem>
+ <DropdownItem
+ className="text-danger"
+ onClick={createClickCallback(kDelete)}
+ >
+ Delete
+ </DropdownItem>
+ </DropdownMenu>
+ </UncontrolledDropdown>
+ </Col>
+ </Row>
+ </ListGroupItem>
+ );
+};
+
+interface DialogProps {
+ open: boolean;
+ close: () => void;
+}
+
+interface CreateUserDialogProps extends DialogProps {
+ process: (user: CreateUserInfo) => Promise<void>;
+}
+
+const CreateUserDialog: React.FC<CreateUserDialogProps> = (props) => {
+ return (
+ <OperationDialog
+ title="Create"
+ titleColor="create"
+ inputPrompt="You are creating a new user."
+ inputScheme={[
+ { type: 'text', label: 'Username' },
+ { type: 'text', label: 'Password' },
+ { type: 'bool', label: 'Administrator' },
+ ]}
+ onProcess={([username, password, administrator]) =>
+ props.process({
+ username: username as string,
+ password: password as string,
+ administrator: administrator as boolean,
+ })
+ }
+ close={props.close}
+ open={props.open}
+ />
+ );
+};
+
+const UsernameLabel: React.FC = (props) => {
+ return <span style={{ color: 'blue' }}>{props.children}</span>;
+};
+
+interface UserDeleteDialogProps extends DialogProps {
+ username: string;
+ process: () => Promise<void>;
+}
+
+const UserDeleteDialog: React.FC<UserDeleteDialogProps> = (props) => {
+ return (
+ <OperationDialog
+ open={props.open}
+ close={props.close}
+ title="Dangerous"
+ titleColor="dangerous"
+ inputPrompt={() => (
+ <>
+ {'You are deleting user '}
+ <UsernameLabel>{props.username}</UsernameLabel>
+ {' !'}
+ </>
+ )}
+ onProcess={props.process}
+ />
+ );
+};
+
+interface UserModifyDialogProps<T> extends DialogProps {
+ username: string;
+ process: (value: T) => Promise<void>;
+}
+
+const UserChangeUsernameDialog: React.FC<UserModifyDialogProps<string>> = (
+ props
+) => {
+ return (
+ <OperationDialog
+ open={props.open}
+ close={props.close}
+ title="Caution"
+ titleColor="dangerous"
+ inputPrompt={() => (
+ <>
+ {'You are change the username of user '}
+ <UsernameLabel>{props.username}</UsernameLabel>
+ {' !'}
+ </>
+ )}
+ inputScheme={[{ type: 'text', label: 'New Username' }]}
+ onProcess={([newUsername]) => {
+ return props.process(newUsername as string);
+ }}
+ />
+ );
+};
+
+const UserChangePasswordDialog: React.FC<UserModifyDialogProps<string>> = (
+ props
+) => {
+ return (
+ <OperationDialog
+ open={props.open}
+ close={props.close}
+ title="Caution"
+ titleColor="dangerous"
+ inputPrompt={() => (
+ <>
+ {'You are change the password of user '}
+ <UsernameLabel>{props.username}</UsernameLabel>
+ {' !'}
+ </>
+ )}
+ inputScheme={[{ type: 'text', label: 'New Password' }]}
+ onProcess={([newPassword]) => {
+ return props.process(newPassword as string);
+ }}
+ />
+ );
+};
+
+interface UserChangePermissionDialogProps extends DialogProps {
+ username: string;
+ newPermission: boolean;
+ process: () => Promise<void>;
+}
+
+const UserChangePermissionDialog: React.FC<UserChangePermissionDialogProps> = (
+ props
+) => {
+ return (
+ <OperationDialog
+ open={props.open}
+ close={props.close}
+ title="Caution"
+ titleColor="dangerous"
+ inputPrompt={() => (
+ <>
+ {'You are change user '}
+ <UsernameLabel>{props.username}</UsernameLabel>
+ {' to '}
+ <span style={{ color: 'orange' }}>
+ {props.newPermission ? 'administrator' : 'normal user'}
+ </span>
+ {' !'}
+ </>
+ )}
+ onProcess={props.process}
+ />
+ );
+};
+
+interface UserAdminProps {
+ user: UserWithToken;
+}
+
+const UserAdmin: React.FC<UserAdminProps> = (props) => {
+ type DialogInfo =
+ | null
+ | {
+ type: 'create';
+ }
+ | { type: 'delete'; username: string }
+ | {
+ type: TChangeUsername;
+ username: string;
+ }
+ | {
+ type: TChangePassword;
+ username: string;
+ }
+ | {
+ type: TChangePermission;
+ username: string;
+ newPermission: boolean;
+ };
+
+ const [users, setUsers] = useState<User[] | null>(null);
+ const [dialog, setDialog] = useState<DialogInfo>(null);
+
+ const token = props.user.token;
+
+ useEffect(() => {
+ let subscribe = true;
+ fetchUserList(props.user.token).then((us) => {
+ if (subscribe) {
+ setUsers(us);
+ }
+ });
+ return () => {
+ subscribe = false;
+ };
+ }, [props.user]);
+
+ let dialogNode: React.ReactNode;
+ if (dialog)
+ switch (dialog.type) {
+ case 'create':
+ dialogNode = (
+ <CreateUserDialog
+ open
+ close={() => setDialog(null)}
+ process={async (user) => {
+ const u = await createUser(user, token);
+ setUsers((oldUsers) => [...oldUsers!, u]);
+ }}
+ />
+ );
+ break;
+ case 'delete':
+ dialogNode = (
+ <UserDeleteDialog
+ open
+ close={() => setDialog(null)}
+ username={dialog.username}
+ process={async () => {
+ await deleteUser(dialog.username, token);
+ setUsers((oldUsers) =>
+ oldUsers!.filter((u) => u.username !== dialog.username)
+ );
+ }}
+ />
+ );
+ break;
+ case kChangeUsername:
+ dialogNode = (
+ <UserChangeUsernameDialog
+ open
+ close={() => setDialog(null)}
+ username={dialog.username}
+ process={async (newUsername) => {
+ await changeUsername(dialog.username, newUsername, token);
+ setUsers((oldUsers) => {
+ const users = oldUsers!.slice();
+ users.find(
+ (u) => u.username === dialog.username
+ )!.username = newUsername;
+ return users;
+ });
+ }}
+ />
+ );
+ break;
+ case kChangePassword:
+ dialogNode = (
+ <UserChangePasswordDialog
+ open
+ close={() => setDialog(null)}
+ username={dialog.username}
+ process={async (newPassword) => {
+ await changePassword(dialog.username, newPassword, token);
+ }}
+ />
+ );
+ break;
+ case kChangePermission: {
+ const newPermission = dialog.newPermission;
+ dialogNode = (
+ <UserChangePermissionDialog
+ open
+ close={() => setDialog(null)}
+ username={dialog.username}
+ newPermission={newPermission}
+ process={async () => {
+ await changePermission(dialog.username, newPermission, token);
+ setUsers((oldUsers) => {
+ const users = oldUsers!.slice();
+ users.find(
+ (u) => u.username === dialog.username
+ )!.administrator = newPermission;
+ return users;
+ });
+ }}
+ />
+ );
+ break;
+ }
+ }
+
+ if (users) {
+ const userComponents = users.map((user) => {
+ return (
+ <UserItem
+ key={user.username}
+ user={user}
+ onContextMenu={(item) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const dialogInfo: any = {
+ type: item,
+ username: user.username,
+ };
+ if (item === 'changepermission') {
+ dialogInfo.newPermission = !user.administrator;
+ }
+ setDialog(dialogInfo);
+ }}
+ />
+ );
+ });
+
+ return (
+ <>
+ <Button
+ color="success"
+ onClick={() =>
+ setDialog({
+ type: 'create',
+ })
+ }
+ className="align-self-end"
+ >
+ Create User
+ </Button>
+ {userComponents}
+ {dialogNode}
+ </>
+ );
+ } else {
+ return <Spinner />;
+ }
+};
+
+export default UserAdmin;