From b8076e12a9ddad2231bc2e918ac23b57eafa79bf Mon Sep 17 00:00:00 2001 From: Zhang Minghan Date: Tue, 7 Nov 2023 09:04:15 +0800 Subject: [PATCH] update api --- app/src/admin/api.ts | 53 +++++++++++++++++++ app/src/admin/types.ts | 6 +++ app/src/components/admin/ChartBox.tsx | 51 +++++++++++++----- app/src/components/admin/InfoBox.tsx | 21 +++++--- app/src/components/admin/MenuBar.tsx | 5 +- .../admin/assemblies/BillingChart.tsx | 6 +-- .../admin/assemblies/ErrorChart.tsx | 4 +- .../admin/assemblies/RequestChart.tsx | 6 +-- app/src/i18n.ts | 3 ++ app/src/router.tsx | 20 +++++++ app/src/routes/Admin.tsx | 11 ++++ app/src/routes/admin/Settings.tsx | 5 ++ app/src/routes/admin/Users.tsx | 5 ++ app/src/store/auth.ts | 25 ++++++--- 14 files changed, 186 insertions(+), 35 deletions(-) create mode 100644 app/src/admin/api.ts create mode 100644 app/src/routes/admin/Settings.tsx create mode 100644 app/src/routes/admin/Users.tsx diff --git a/app/src/admin/api.ts b/app/src/admin/api.ts new file mode 100644 index 0000000..a1817ce --- /dev/null +++ b/app/src/admin/api.ts @@ -0,0 +1,53 @@ +import { + BillingChartResponse, + ErrorChartResponse, + InfoResponse, + ModelChartResponse, + RequestChartResponse, +} from "@/admin/types.ts"; +import axios from "axios"; + +export async function getAdminInfo(): Promise { + const response = await axios.get("/admin/analytics/info"); + if (response.status !== 200) { + return { subscription_count: 0, billing_today: 0, billing_month: 0 }; + } + + return response.data as InfoResponse; +} + +export async function getModelChart(): Promise { + const response = await axios.get("/admin/analytics/model"); + if (response.status !== 200) { + return { date: [], value: [] }; + } + + return response.data as ModelChartResponse; +} + +export async function getRequestChart(): Promise { + const response = await axios.get("/admin/analytics/request"); + if (response.status !== 200) { + return { date: [], value: [] }; + } + + return response.data as RequestChartResponse; +} + +export async function getBillingChart(): Promise { + const response = await axios.get("/admin/analytics/billing"); + if (response.status !== 200) { + return { date: [], value: [] }; + } + + return response.data as BillingChartResponse; +} + +export async function getErrorChart(): Promise { + const response = await axios.get("/admin/analytics/error"); + if (response.status !== 200) { + return { date: [], value: [] }; + } + + return response.data as ErrorChartResponse; +} diff --git a/app/src/admin/types.ts b/app/src/admin/types.ts index cb9f6cd..3363e4f 100644 --- a/app/src/admin/types.ts +++ b/app/src/admin/types.ts @@ -1,3 +1,9 @@ +export type InfoResponse = { + billing_today: number; + billing_month: number; + subscription_count: number; +}; + export type ModelChartResponse = { date: string[]; value: { diff --git a/app/src/components/admin/ChartBox.tsx b/app/src/components/admin/ChartBox.tsx index c3e0ae4..8a5dca8 100644 --- a/app/src/components/admin/ChartBox.tsx +++ b/app/src/components/admin/ChartBox.tsx @@ -1,8 +1,13 @@ import ModelChart from "@/components/admin/assemblies/ModelChart.tsx"; import { useEffect, useState } from "react"; -import {BillingChartResponse, ErrorChartResponse, ModelChartResponse, RequestChartResponse} from "@/admin/types.ts"; +import { + BillingChartResponse, + ErrorChartResponse, + ModelChartResponse, + RequestChartResponse, +} from "@/admin/types.ts"; -import {ArcElement, Chart, Filler, LineElement, PointElement} from "chart.js"; +import { ArcElement, Chart, Filler, LineElement, PointElement } from "chart.js"; import { CategoryScale, LinearScale, @@ -18,6 +23,13 @@ import { themeEvent } from "@/events/theme.ts"; import RequestChart from "@/components/admin/assemblies/RequestChart.tsx"; import BillingChart from "@/components/admin/assemblies/BillingChart.tsx"; import ErrorChart from "@/components/admin/assemblies/ErrorChart.tsx"; +import { useEffectAsync } from "@/utils/hook.ts"; +import { + getBillingChart, + getErrorChart, + getModelChart, + getRequestChart, +} from "@/admin/api.ts"; Chart.register( CategoryScale, @@ -61,39 +73,54 @@ function ChartBox() { }; }, [open]); - useEffect(() => { - - }, []); - const [dark, setDark] = useState(getMemory("theme") === "dark"); themeEvent.bind((theme: string) => setDark(theme === "dark")); const [model, setModel] = useState({ - date: [], value: [], + date: [], + value: [], }); const [request, setRequest] = useState({ - date: [], value: [], + date: [], + value: [], }); const [billing, setBilling] = useState({ - date: [], value: [], + date: [], + value: [], }); const [error, setError] = useState({ - date: [], value: [], + date: [], + value: [], }); + useEffectAsync(async () => { + setModel(await getModelChart()); + setRequest(await getRequestChart()); + setBilling(await getBillingChart()); + setError(await getErrorChart()); + }, []); + return (
- +
- +
diff --git a/app/src/components/admin/InfoBox.tsx b/app/src/components/admin/InfoBox.tsx index 7d28a6f..5c10cfd 100644 --- a/app/src/components/admin/InfoBox.tsx +++ b/app/src/components/admin/InfoBox.tsx @@ -1,21 +1,28 @@ import { useTranslation } from "react-i18next"; import { useState } from "react"; import { CircleDollarSign, Users2, Wallet } from "lucide-react"; +import { useEffectAsync } from "@/utils/hook.ts"; +import { getAdminInfo } from "@/admin/api.ts"; +import { InfoResponse } from "@/admin/types.ts"; function InfoBox() { const { t } = useTranslation(); - const [form, setForm] = useState({ - today: 0, - month: 0, - users: 0, + const [form, setForm] = useState({ + billing_today: 0, + billing_month: 0, + subscription_count: 0, }); + useEffectAsync(async () => { + setForm(await getAdminInfo()); + }, []); + return (
{t("admin.billing-today")}
-
{form.today}
+
{form.billing_today}
@@ -25,7 +32,7 @@ function InfoBox() {
{t("admin.billing-month")}
-
{form.month}
+
{form.billing_month}
@@ -36,7 +43,7 @@ function InfoBox() {
{t("admin.subscription-users")}
- {form.users} + {form.subscription_count} {t("admin.seat")}
diff --git a/app/src/components/admin/MenuBar.tsx b/app/src/components/admin/MenuBar.tsx index 439efb0..df8362e 100644 --- a/app/src/components/admin/MenuBar.tsx +++ b/app/src/components/admin/MenuBar.tsx @@ -1,7 +1,7 @@ import { useSelector } from "react-redux"; import { selectMenu } from "@/store/menu.ts"; import React, { useMemo } from "react"; -import { LayoutDashboard, Settings } from "lucide-react"; +import { LayoutDashboard, Settings, Users } from "lucide-react"; import router from "@/router.tsx"; import { useLocation } from "react-router-dom"; import { useTranslation } from "react-i18next"; @@ -42,10 +42,11 @@ function MenuBar() { icon={} path={"/"} /> + } path={"/users"} /> } - path={"/config"} + path={"/settings"} />
); diff --git a/app/src/components/admin/assemblies/BillingChart.tsx b/app/src/components/admin/assemblies/BillingChart.tsx index 1cb2a82..c6364e0 100644 --- a/app/src/components/admin/assemblies/BillingChart.tsx +++ b/app/src/components/admin/assemblies/BillingChart.tsx @@ -1,5 +1,5 @@ -import {useTranslation} from "react-i18next"; -import {useMemo} from "react"; +import { useTranslation } from "react-i18next"; +import { useMemo } from "react"; import { Line } from "react-chartjs-2"; type BillingChartProps = { @@ -14,7 +14,7 @@ function BillingChart({ labels, datasets, dark }: BillingChartProps) { labels, datasets: [ { - label: 'CNY', + label: "CNY", fill: true, data: datasets, backgroundColor: "rgba(255,205,111,0.78)", diff --git a/app/src/components/admin/assemblies/ErrorChart.tsx b/app/src/components/admin/assemblies/ErrorChart.tsx index e5393a8..3700dce 100644 --- a/app/src/components/admin/assemblies/ErrorChart.tsx +++ b/app/src/components/admin/assemblies/ErrorChart.tsx @@ -1,5 +1,5 @@ -import {useTranslation} from "react-i18next"; -import {useMemo} from "react"; +import { useTranslation } from "react-i18next"; +import { useMemo } from "react"; import { Line } from "react-chartjs-2"; type ErrorChartProps = { diff --git a/app/src/components/admin/assemblies/RequestChart.tsx b/app/src/components/admin/assemblies/RequestChart.tsx index d4da2a0..1a747b8 100644 --- a/app/src/components/admin/assemblies/RequestChart.tsx +++ b/app/src/components/admin/assemblies/RequestChart.tsx @@ -1,5 +1,5 @@ -import {useTranslation} from "react-i18next"; -import {useMemo} from "react"; +import { useTranslation } from "react-i18next"; +import { useMemo } from "react"; import { Line } from "react-chartjs-2"; type RequestChartProps = { @@ -14,7 +14,7 @@ function RequestChart({ labels, datasets, dark }: RequestChartProps) { labels, datasets: [ { - label: t('admin.requests'), + label: t("admin.requests"), fill: true, data: datasets, borderColor: "rgba(109,179,255,1)", diff --git a/app/src/i18n.ts b/app/src/i18n.ts index 8008428..dccd21d 100644 --- a/app/src/i18n.ts +++ b/app/src/i18n.ts @@ -258,6 +258,7 @@ const resources = { }, admin: { dashboard: "Dashboard", + users: "User Management", settings: "Settings", "billing-today": "Billing Today", "billing-month": "Billing Month", @@ -509,6 +510,7 @@ const resources = { }, admin: { dashboard: "仪表盘", + users: "用户管理", settings: "设置", "billing-today": "今日入账", "billing-month": "本月入账", @@ -776,6 +778,7 @@ const resources = { }, admin: { dashboard: "Панель управления", + users: "Управление пользователями", settings: "Настройки", "billing-today": "Сегодняшний доход", "billing-month": "Доход за месяц", diff --git a/app/src/router.tsx b/app/src/router.tsx index 5a492a2..9ffbbac 100644 --- a/app/src/router.tsx +++ b/app/src/router.tsx @@ -10,6 +10,8 @@ const Article = lazy(() => import("@/routes/Article.tsx")); const Admin = lazy(() => import("@/routes/Admin.tsx")); const Dashboard = lazy(() => import("@/routes/admin/DashBoard.tsx")); +const Settings = lazy(() => import("@/routes/admin/Settings.tsx")); +const Users = lazy(() => import("@/routes/admin/Users.tsx")); const router = createBrowserRouter([ { @@ -72,6 +74,24 @@ const router = createBrowserRouter([ ), }, + { + id: "admin-users", + path: "users", + element: ( + + + + ), + }, + { + id: "admin-settings", + path: "settings", + element: ( + + + + ), + }, ], ErrorBoundary: NotFound, }, diff --git a/app/src/routes/Admin.tsx b/app/src/routes/Admin.tsx index 6795db0..7a85813 100644 --- a/app/src/routes/Admin.tsx +++ b/app/src/routes/Admin.tsx @@ -1,8 +1,19 @@ import "@/assets/admin/all.less"; import MenuBar from "@/components/admin/MenuBar.tsx"; import { Outlet } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { selectAdmin, selectInit } from "@/store/auth.ts"; +import { useEffect } from "react"; +import router from "@/router.tsx"; function Admin() { + const init = useSelector(selectInit); + const admin = useSelector(selectAdmin); + + useEffect(() => { + if (init && !admin) router.navigate("/"); + }, []); + return (
diff --git a/app/src/routes/admin/Settings.tsx b/app/src/routes/admin/Settings.tsx new file mode 100644 index 0000000..9d60730 --- /dev/null +++ b/app/src/routes/admin/Settings.tsx @@ -0,0 +1,5 @@ +function Settings() { + return <>; +} + +export default Settings; diff --git a/app/src/routes/admin/Users.tsx b/app/src/routes/admin/Users.tsx new file mode 100644 index 0000000..61a6ee5 --- /dev/null +++ b/app/src/routes/admin/Users.tsx @@ -0,0 +1,5 @@ +function Users() { + return <>; +} + +export default Users; diff --git a/app/src/store/auth.ts b/app/src/store/auth.ts index 2791ae3..4639316 100644 --- a/app/src/store/auth.ts +++ b/app/src/store/auth.ts @@ -1,7 +1,7 @@ import { createSlice } from "@reduxjs/toolkit"; import axios from "axios"; import { tokenField } from "@/conf.ts"; -import { AppDispatch } from "./index.ts"; +import { AppDispatch, RootState } from "./index.ts"; import { forgetMemory, setMemory } from "@/utils/memory.ts"; export const authSlice = createSlice({ @@ -10,6 +10,7 @@ export const authSlice = createSlice({ token: "", init: false, authenticated: false, + admin: false, username: "", }, reducers: { @@ -28,6 +29,9 @@ export const authSlice = createSlice({ setInit: (state, action) => { state.init = action.payload as boolean; }, + setAdmin: (state, action) => { + state.admin = action.payload as boolean; + }, logout: (state) => { state.token = ""; state.authenticated = false; @@ -60,6 +64,7 @@ export function validateToken( dispatch(setAuthenticated(res.data.status)); dispatch(setUsername(res.data.user)); dispatch(setInit(true)); + dispatch(setAdmin(res.data.admin)); hook && hook(); }) .catch((err) => { @@ -68,10 +73,18 @@ export function validateToken( }); } -export const selectAuthenticated = (state: any) => state.auth.authenticated; -export const selectUsername = (state: any) => state.auth.username; -export const selectInit = (state: any) => state.auth.init; +export const selectAuthenticated = (state: RootState) => + state.auth.authenticated; +export const selectUsername = (state: RootState) => state.auth.username; +export const selectInit = (state: RootState) => state.auth.init; +export const selectAdmin = (state: RootState) => state.auth.admin; -export const { setToken, setAuthenticated, setUsername, logout, setInit } = - authSlice.actions; +export const { + setToken, + setAuthenticated, + setUsername, + logout, + setInit, + setAdmin, +} = authSlice.actions; export default authSlice.reducer;