diff --git a/src/Router.tsx b/src/Router.tsx index c3cc5b18a..56d1bebe4 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -1,7 +1,6 @@ import React, { Suspense, lazy } from "react"; import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import Layout from "@/layouts/Layout"; -import RankList from "./pages/RankList"; const Problem = lazy(() => import("@/pages/problem/Problem")); const AdminProblemList = lazy(() => import("@/pages/admin/ProblemList")); @@ -10,6 +9,7 @@ const AdminUserList = lazy(() => import("@/pages/admin/UserList")); const ProblemList = lazy(() => import("@/pages/problem/ProblemList")); const JudgeList = lazy(() => import("@/pages//judge/JudgeList")); const Judge = lazy(() => import("@/pages/judge/Judge")); +const RankList = lazy(() => import("@/pages/RankList")); const Login = lazy(() => import("@/pages/Login")); const Router: React.FC = () => { @@ -24,11 +24,11 @@ const Router: React.FC = () => { } /> } /> + } /> } /> } /> - } /> } /> {/* Admin */} diff --git a/src/apis/judge.ts b/src/apis/judge.ts index f64a8e468..b0ce31b2a 100644 --- a/src/apis/judge.ts +++ b/src/apis/judge.ts @@ -19,9 +19,14 @@ export async function postJudge(slug: string, code: string, language: string) { return res.data; } -export async function getJudgeList(limit?: number, offset?: number) { +export async function getJudgeList( + limit?: number, + offset?: number, + selfOnly?: boolean, +) { limit = limit || 10; offset = offset || 0; + selfOnly = selfOnly || false; let res = await axiosClient.get<{ total: number; @@ -30,6 +35,7 @@ export async function getJudgeList(limit?: number, offset?: number) { params: { limit, offset, + self_only: selfOnly, }, }); if (res.status !== 200) { diff --git a/src/apis/user.ts b/src/apis/user.ts index e32512407..127f28ec3 100644 --- a/src/apis/user.ts +++ b/src/apis/user.ts @@ -25,3 +25,50 @@ export async function getUserInfoList( } return res.data; } + +export async function deleteUser(account: string) { + let res = await axiosClient.delete(`/api/v1/user/${account}`); + if (res.status !== 200) { + throw Error("failed to delete user"); + } +} + +export async function grantUserRole( + account: string, + role: string, + domain?: string, +) { + if (!domain) domain = "system"; + + let body = { + role, + domain, + }; + let data = JSON.stringify(body); + + let res = await axiosClient.post(`/api/v1/user/${account}/role`, data); + if (res.status !== 200) { + throw Error("failed to grant user role"); + } +} + +export async function revokeUserRole( + account: string, + role: string, + domain?: string, +) { + if (!domain) domain = "system"; + + let body = { + role, + domain, + }; + let data = JSON.stringify(body); + + let res = await axiosClient.delete(`/api/v1/user/${account}/role`, { + data, + }); + if (res.status !== 200) { + throw Error("failed to revoke user role"); + } +} diff --git a/src/components/display/JudgeTable.tsx b/src/components/display/JudgeTable.tsx index 45131fa54..80232164f 100644 --- a/src/components/display/JudgeTable.tsx +++ b/src/components/display/JudgeTable.tsx @@ -7,6 +7,8 @@ import BrandCPPIcon from "@/components/display/icons/tabler/BrandCPPIcon"; import BrandPythonIcon from "@/components/display/icons/tabler/BrandPythonIcon"; import { shortenString } from "@/utils/string"; import UserAvatar from "./UserAvatar"; +import { useSelector } from "react-redux"; +import { userInfoSelector } from "@/store/selectors"; export interface JudgeTableProps { data: JudgeServiceModel.JudgeInfo[]; @@ -17,6 +19,7 @@ export interface JudgeTableProps { const JudgeTable: React.FC = (props) => { const { t } = useTranslation(); const navigate = useNavigate(); + const userInfo = useSelector(userInfoSelector); return (
@@ -37,10 +40,19 @@ const JudgeTable: React.FC = (props) => { key={idx} className={joinClasses( props.data.length > 1 ? "border-base-content/10" : "border-0", - props.enableRouting && "hover cursor-pointer", + ((props.enableRouting && userInfo?.roles?.includes("admin")) || + userInfo?.roles?.includes("super") || + userInfo?.account === judge.user?.account) && + "hover cursor-pointer", )} onClick={() => { - if (props.enableRouting) navigate(judge.UID); + if ( + (props.enableRouting && userInfo?.roles?.includes("admin")) || + userInfo?.roles?.includes("super") || + userInfo?.account === judge.user?.account + ) { + navigate(`/judges/${judge.UID}`); + } }} > {shortenString(judge.UID, 8, false)} diff --git a/src/components/display/ProblemTable.tsx b/src/components/display/ProblemTable.tsx index 39480dd49..dbe76b5d7 100644 --- a/src/components/display/ProblemTable.tsx +++ b/src/components/display/ProblemTable.tsx @@ -101,11 +101,11 @@ const ProblemTable: React.FC = (props) => { const ProblemTags: React.FC<{ tags: { name: string }[] }> = (props) => { return ( -
+
{props.tags.map((tag) => (
{tag.name}
diff --git a/src/components/display/UserTable.tsx b/src/components/display/UserTable.tsx index d7ae626a9..fac434198 100644 --- a/src/components/display/UserTable.tsx +++ b/src/components/display/UserTable.tsx @@ -6,6 +6,9 @@ import ConfirmDialog from "../control/ConfirmDialog"; import TrashIcon from "./icons/tabler/TrashIcon"; import { joinClasses } from "@/utils/common"; import UserCogIcon from "./icons/tabler/UserCogIcon"; +import * as UserService from "@/apis/user"; +import { useSelector } from "react-redux"; +import { userInfoSelector } from "@/store/selectors"; export interface UserTableProps { data: UserServiceModel.UserInfo[]; @@ -15,6 +18,9 @@ export interface UserTableProps { const UserTable: React.FC = (props) => { const { t } = useTranslation(); + const [deletingAccount, setDeletingAccount] = React.useState( + null, + ); return ( <> @@ -47,7 +53,12 @@ const UserTable: React.FC = (props) => { {props.showActions && ( - {}} /> + { + setDeletingAccount(userInfo.account); + }} + /> )} @@ -60,7 +71,15 @@ const UserTable: React.FC = (props) => { title="Confirm" message="Are you sure to delete this user?" onClickConfirm={() => { - window.location.reload(); + if (deletingAccount) { + UserService.deleteUser(deletingAccount) + .then(() => { + window.location.reload(); + }) + .catch((err) => { + console.error(err); + }); + } }} /> @@ -92,24 +111,50 @@ interface ActionsProps { } const UserActions: React.FC = (props) => { + const userInfo = useSelector(userInfoSelector); + const onClickDelete = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); props.onClickDelete(); const modal = document.getElementById( - "delete_confirm_modal", + "user_delete_confirm_modal", ) as HTMLDialogElement; modal?.showModal(); }; return (
- ) : ( <> @@ -95,11 +97,10 @@ const Login: React.FC = () => {
- Password Login - + {t("Password Login")} + {" "} - {" "} - (Internal) + {`(${t("Internal")})`}
@@ -107,7 +108,7 @@ const Login: React.FC = () => { <> - OAuth Login + {t("OAuth Login")} )} diff --git a/src/pages/RankList.tsx b/src/pages/RankList.tsx index 8bab78303..bcb95a950 100644 --- a/src/pages/RankList.tsx +++ b/src/pages/RankList.tsx @@ -7,8 +7,8 @@ const RankList: React.FC = () => { return (
-
-
+
+
diff --git a/src/pages/admin/ProblemList.tsx b/src/pages/admin/ProblemList.tsx index f14e410bd..b1016d2e2 100644 --- a/src/pages/admin/ProblemList.tsx +++ b/src/pages/admin/ProblemList.tsx @@ -29,7 +29,7 @@ const ProblemList: React.FC = () => { return (
-
+
{ setSearchingTitle(t); diff --git a/src/pages/admin/UserList.tsx b/src/pages/admin/UserList.tsx index 99cd75bcb..bbf598133 100644 --- a/src/pages/admin/UserList.tsx +++ b/src/pages/admin/UserList.tsx @@ -14,8 +14,7 @@ const UserList: React.FC = () => { useEffect(() => { setPagenation(countPerPage, page * countPerPage); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [countPerPage, page]); + }, [countPerPage, page, setPagenation]); return (
diff --git a/src/pages/judge/JudgeList.tsx b/src/pages/judge/JudgeList.tsx index 018dc8361..2228a893d 100644 --- a/src/pages/judge/JudgeList.tsx +++ b/src/pages/judge/JudgeList.tsx @@ -7,8 +7,14 @@ import React, { useEffect } from "react"; const countPerPageSelections = [10, 25, 50]; const JudgeList: React.FC = () => { - const { getJudgeList, refreshJudgeList, getPageCount, setPagenation } = - useJudgeList(); + const { + getJudgeList, + refreshJudgeList, + getPageCount, + setPagenation, + getSelfOnly, + setSelfOnly, + } = useJudgeList(); const { t } = useTranslation(); // useEvent(); const refreshSeconds = 5; @@ -33,15 +39,19 @@ const JudgeList: React.FC = () => { useEffect(() => { setPagenation(countPerPage, page * countPerPage); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [countPerPage, page]); + }, [countPerPage, page, setPagenation]); return (