diff --git a/website/src/components/AdminArea.tsx b/website/src/components/AdminArea.tsx new file mode 100644 index 0000000000..8e6d1229cd --- /dev/null +++ b/website/src/components/AdminArea.tsx @@ -0,0 +1,19 @@ +import { useRouter } from "next/router"; +import { useSession } from "next-auth/react"; +import { ReactNode, useEffect } from "react"; + +export const AdminArea = ({ children }: { children: ReactNode }) => { + const router = useRouter(); + const { data: session, status } = useSession(); + + useEffect(() => { + if (status === "loading") { + return; + } + if (session?.user.role === "admin") { + return; + } + router.push("/"); + }, [router, session, status]); + return
{status === "loading" ? "loading..." : children}
; +}; diff --git a/website/src/components/Layout.tsx b/website/src/components/Layout.tsx index 1b5bf43066..2f89ae47a4 100644 --- a/website/src/components/Layout.tsx +++ b/website/src/components/Layout.tsx @@ -1,7 +1,7 @@ // https://nextjs.org/docs/basic-features/layouts import { Box, Grid } from "@chakra-ui/react"; -import { Activity, BarChart2, Layout, MessageSquare, Users } from "lucide-react"; +import { Activity, BarChart2, Layout, MessageSquare, Settings, Users } from "lucide-react"; import type { NextPage } from "next"; import { Header } from "src/components/Header"; @@ -37,19 +37,16 @@ export const getDashboardLayout = (page: React.ReactElement) => ( { label: "Dashboard", pathname: "/dashboard", - desc: "Dashboard Home", icon: Layout, }, { label: "Messages", pathname: "/messages", - desc: "Messages Dashboard", icon: MessageSquare, }, { label: "Leaderboard", pathname: "/leaderboard", - desc: "User Leaderboard", icon: BarChart2, }, ]} @@ -72,15 +69,18 @@ export const getAdminLayout = (page: React.ReactElement) => ( { label: "Users", pathname: "/admin", - desc: "Users Dashboard", icon: Users, }, { label: "Status", pathname: "/admin/status", - desc: "Status Dashboard", icon: Activity, }, + { + label: "Parameters", + pathname: "/admin/parameters", + icon: Settings, + }, ]} > {page} diff --git a/website/src/components/SideMenu.tsx b/website/src/components/SideMenu.tsx index ee7dba4db8..ecd27bec49 100644 --- a/website/src/components/SideMenu.tsx +++ b/website/src/components/SideMenu.tsx @@ -6,7 +6,6 @@ import { useRouter } from "next/router"; export interface MenuButtonOption { label: string; pathname: string; - desc: string; icon: LucideIcon; } diff --git a/website/src/lib/oasst_api_client.ts b/website/src/lib/oasst_api_client.ts index 51e691b350..8cd2ae76e3 100644 --- a/website/src/lib/oasst_api_client.ts +++ b/website/src/lib/oasst_api_client.ts @@ -29,6 +29,15 @@ export class OasstApiClient { }; } } + + fetch_full_settings() { + return this.get>("/api/v1/admin/backend_settings/full"); + } + + fetch_public_settings() { + return this.get>("/api/v1/admin/backend_settings/public"); + } + // TODO return a strongly typed Task? // This method is used to store a task in RegisteredTask.task. // This is a raw Json type, so we can't use it to strongly type the task. diff --git a/website/src/pages/_app.tsx b/website/src/pages/_app.tsx index adadfe56cc..5b2421eab3 100644 --- a/website/src/pages/_app.tsx +++ b/website/src/pages/_app.tsx @@ -2,8 +2,9 @@ import "../styles/globals.css"; import "focus-visible"; import type { AppProps } from "next/app"; +import Head from "next/head"; import { SessionProvider } from "next-auth/react"; -import { appWithTranslation } from "next-i18next"; +import { appWithTranslation, useTranslation } from "next-i18next"; import { FlagsProvider } from "react-feature-flags"; import { getDefaultLayout, NextPageWithLayout } from "src/components/Layout"; import flags from "src/flags"; @@ -24,15 +25,21 @@ const swrConfig: SWRConfiguration = { function MyApp({ Component, pageProps: { session, cookies, ...pageProps } }: AppPropsWithLayout) { const getLayout = Component.getLayout ?? getDefaultLayout; const page = getLayout(); + const { t } = useTranslation(); return ( - - - - {page} - - - + <> + + + + + + + {page} + + + + ); } export { getServerSideProps }; diff --git a/website/src/pages/admin/index.tsx b/website/src/pages/admin/index.tsx index ede9f59cbc..a4b7750818 100644 --- a/website/src/pages/admin/index.tsx +++ b/website/src/pages/admin/index.tsx @@ -1,7 +1,5 @@ import Head from "next/head"; -import { useRouter } from "next/router"; -import { useSession } from "next-auth/react"; -import { useEffect } from "react"; +import { AdminArea } from "src/components/AdminArea"; import { getAdminLayout } from "src/components/Layout"; import { UserTable } from "src/components/UserTable"; export { getDefaultStaticProps as getStaticProps } from "src/lib/default_static_props"; @@ -11,33 +9,14 @@ export { getDefaultStaticProps as getStaticProps } from "src/lib/default_static_ * admins the ability to manage their access rights. */ const AdminIndex = () => { - const router = useRouter(); - const { data: session, status } = useSession(); - - // Check when the user session is loaded and re-route if the user is not an - // admin. This follows the suggestion by NextJS for handling private pages: - // https://nextjs.org/docs/api-reference/next/router#usage - // - // All admin pages should use the same check and routing steps. - useEffect(() => { - if (status === "loading") { - return; - } - if (session?.user?.role === "admin") { - return; - } - router.push("/"); - }, [router, session, status]); return ( <> Open Assistant - -
{status === "loading" ? "loading..." : }
+ + + ); }; diff --git a/website/src/pages/admin/parameters.tsx b/website/src/pages/admin/parameters.tsx new file mode 100644 index 0000000000..ac33a04b79 --- /dev/null +++ b/website/src/pages/admin/parameters.tsx @@ -0,0 +1,36 @@ +import { Card, CardBody, CircularProgress } from "@chakra-ui/react"; +import Head from "next/head"; +import { AdminArea } from "src/components/AdminArea"; +import { getAdminLayout } from "src/components/Layout"; +import { get } from "src/lib/api"; +import useSWRImmutable from "swr/immutable"; +export { getDefaultStaticProps as getStaticProps } from "src/lib/default_static_props"; + +export default function Parameters() { + const { data, isLoading, error } = useSWRImmutable("/api/admin/parameters", get); + + return ( + <> + + Parameters - Open Assistant + + + + + {isLoading && } + {error && "Unable to load data"} + {data && ( + + +
{JSON.stringify(data, null, 2)}
+
+
+ )} +
+
+
+ + ); +} + +Parameters.getLayout = getAdminLayout; diff --git a/website/src/pages/admin/status/index.tsx b/website/src/pages/admin/status/index.tsx index 5430512254..9218b7faef 100644 --- a/website/src/pages/admin/status/index.tsx +++ b/website/src/pages/admin/status/index.tsx @@ -13,12 +13,9 @@ import { Th, Thead, Tr, - useColorMode, } from "@chakra-ui/react"; import Head from "next/head"; -import { useRouter } from "next/router"; -import { useSession } from "next-auth/react"; -import { useEffect } from "react"; +import { AdminArea } from "src/components/AdminArea"; import { getAdminLayout } from "src/components/Layout"; import { get } from "src/lib/api"; import useSWRImmutable from "swr/immutable"; @@ -30,31 +27,7 @@ export { getDefaultStaticProps as getStaticProps } from "src/lib/default_static_ */ const StatusIndex = () => { - const router = useRouter(); - const { data: session, status } = useSession(); - - const { colorMode } = useColorMode(); - const dataBackgroundColor = colorMode === "light" ? "gray.100" : "gray.800"; - // Check when the user session is loaded and re-route if the user is not an - // admin. This follows the suggestion by NextJS for handling private pages: - // https://nextjs.org/docs/api-reference/next/router#usage - // - // All admin pages should use the same check and routing steps. - useEffect(() => { - if (status === "loading") { - return; - } - if (session?.user?.role === "admin") { - return; - } - router.push("/"); - }, [router, session, status]); - - const { - data: dataStatus, - error: errorStatus, - isLoading: isLoadingStatus, - } = useSWRImmutable("/api/admin/status", get); + const { data: dataStatus, error: errorStatus } = useSWRImmutable("/api/admin/status", get); const { tasksAvailability, stats, treeManager } = dataStatus || {}; @@ -62,110 +35,114 @@ const StatusIndex = () => { <> Status - Open Assistant - - - - - - /api/v1/tasks/availability - - - {tasksAvailability?.status === "fulfilled" ? ( -
{JSON.stringify(tasksAvailability.value, null, 2)}
- ) : tasksAvailability?.status === "rejected" ? ( -
{JSON.stringify(tasksAvailability.reason, null, 2)}
- ) : errorStatus ? ( -
{JSON.stringify(errorStatus, null, 2)}
- ) : ( - - )} -
-
-
+ + + + + + /api/v1/tasks/availability + + + + {tasksAvailability?.status === "fulfilled" ? ( +
{JSON.stringify(tasksAvailability.value, null, 2)}
+ ) : tasksAvailability?.status === "rejected" ? ( +
{JSON.stringify(tasksAvailability.reason, null, 2)}
+ ) : errorStatus ? ( +
{JSON.stringify(errorStatus, null, 2)}
+ ) : ( + + )} +
+
+
+
+ + + + /api/v1/stats/ + + + + {stats?.status === "fulfilled" ? ( +
{JSON.stringify(stats.value, null, 2)}
+ ) : stats?.status === "rejected" ? ( +
{JSON.stringify(stats.reason, null, 2)}
+ ) : errorStatus ? ( +
{JSON.stringify(errorStatus, null, 2)}
+ ) : ( + + )} +
+
+
+
+
+
- /api/v1/stats/ + /api/v1/stats/tree_manager - - {stats?.status === "fulfilled" ? ( -
{JSON.stringify(stats.value, null, 2)}
- ) : stats?.status === "rejected" ? ( -
{JSON.stringify(stats.reason, null, 2)}
- ) : errorStatus ? ( -
{JSON.stringify(errorStatus, null, 2)}
- ) : ( - - )} -
-
-
-
-
- - - - /api/v1/stats/tree_manager - - {treeManager?.status === "fulfilled" ? ( - - - state_counts - - -
{JSON.stringify(treeManager.value.state_counts, null, 2)}
-
- -
+ {treeManager?.status === "fulfilled" ? ( + - message_counts + state_counts - - Tree Manager - - - - - - - - - - - - - {treeManager.value.message_counts.map( - ({ message_tree_id, state, depth, oldest, youngest, count, goal_tree_size }) => ( - - - - - - - - - - ) - )} - -
Message Tree IDStateDepthOldestYoungestCountGoal Tree Size
{message_tree_id}{state}{depth}{oldest}{youngest}{count}{goal_tree_size}
-
-
- ) : treeManager?.status === "rejected" ? ( -
{JSON.stringify(treeManager.reason, null, 2)}
- ) : errorStatus ? ( -
{JSON.stringify(errorStatus, null, 2)}
- ) : ( - - )} -
-
+ + +
{JSON.stringify(treeManager.value.state_counts, null, 2)}
+
+
+ +
+ + message_counts + + + Tree Manager + + + + + + + + + + + + + {treeManager.value.message_counts.map( + ({ message_tree_id, state, depth, oldest, youngest, count, goal_tree_size }) => ( + + + + + + + + + + ) + )} + +
Message Tree IDStateDepthOldestYoungestCountGoal Tree Size
{message_tree_id}{state}{depth}{oldest}{youngest}{count}{goal_tree_size}
+
+ + ) : treeManager?.status === "rejected" ? ( +
{JSON.stringify(treeManager.reason, null, 2)}
+ ) : errorStatus ? ( +
{JSON.stringify(errorStatus, null, 2)}
+ ) : ( + + )} + + + ); }; diff --git a/website/src/pages/api/admin/parameters.ts b/website/src/pages/api/admin/parameters.ts new file mode 100644 index 0000000000..73e0432939 --- /dev/null +++ b/website/src/pages/api/admin/parameters.ts @@ -0,0 +1,16 @@ +import { withRole } from "src/lib/auth"; +import { createApiClient } from "src/lib/oasst_client_factory"; + +export default withRole("admin", async (_, res, token) => { + const client = await createApiClient(token); + + try { + const fullSettings = await client.fetch_full_settings(); + + return res.json(fullSettings); + } catch { + const publicSettings = await client.fetch_public_settings(); + + return res.json(publicSettings); + } +}); diff --git a/website/src/pages/dashboard.tsx b/website/src/pages/dashboard.tsx index e9fe3a1895..b34a89a367 100644 --- a/website/src/pages/dashboard.tsx +++ b/website/src/pages/dashboard.tsx @@ -41,7 +41,7 @@ const Dashboard = () => { <> {`${t("dashboard")} - ${t("common:title")}`} - + diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx index 6ac7dd3808..e53142e3d3 100644 --- a/website/src/pages/index.tsx +++ b/website/src/pages/index.tsx @@ -24,7 +24,6 @@ const Home = () => { <> {t("title")} - diff --git a/website/src/styles/Theme/components/Card.ts b/website/src/styles/Theme/components/Card.ts index 8cd66031e1..c60261a467 100644 --- a/website/src/styles/Theme/components/Card.ts +++ b/website/src/styles/Theme/components/Card.ts @@ -17,11 +17,21 @@ export const cardTheme = defineMultiStyleConfig({ footer: {}, }; }), - variants: { - elevated: definePartsStyle({ + sizes: { + md: definePartsStyle({ container: { borderRadius: "xl", }, }), }, + variants: { + json: definePartsStyle(({ colorMode }) => { + const isLightMode = colorMode === "light"; + return { + container: { + backgroundColor: isLightMode ? "gray.100" : "gray.800", + }, + }; + }), + }, });