update api

This commit is contained in:
Zhang Minghan 2023-11-07 09:04:15 +08:00
parent 610464c493
commit b8076e12a9
14 changed files with 186 additions and 35 deletions

53
app/src/admin/api.ts Normal file
View File

@ -0,0 +1,53 @@
import {
BillingChartResponse,
ErrorChartResponse,
InfoResponse,
ModelChartResponse,
RequestChartResponse,
} from "@/admin/types.ts";
import axios from "axios";
export async function getAdminInfo(): Promise<InfoResponse> {
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<ModelChartResponse> {
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<RequestChartResponse> {
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<BillingChartResponse> {
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<ErrorChartResponse> {
const response = await axios.get("/admin/analytics/error");
if (response.status !== 200) {
return { date: [], value: [] };
}
return response.data as ErrorChartResponse;
}

View File

@ -1,3 +1,9 @@
export type InfoResponse = {
billing_today: number;
billing_month: number;
subscription_count: number;
};
export type ModelChartResponse = {
date: string[];
value: {

View File

@ -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<boolean>(getMemory("theme") === "dark");
themeEvent.bind((theme: string) => setDark(theme === "dark"));
const [model, setModel] = useState<ModelChartResponse>({
date: [], value: [],
date: [],
value: [],
});
const [request, setRequest] = useState<RequestChartResponse>({
date: [], value: [],
date: [],
value: [],
});
const [billing, setBilling] = useState<BillingChartResponse>({
date: [], value: [],
date: [],
value: [],
});
const [error, setError] = useState<ErrorChartResponse>({
date: [], value: [],
date: [],
value: [],
});
useEffectAsync(async () => {
setModel(await getModelChart());
setRequest(await getRequestChart());
setBilling(await getBillingChart());
setError(await getErrorChart());
}, []);
return (
<div className={`chart-boxes`}>
<div className={`chart-box`}>
<ModelChart labels={model.date} datasets={model.value} dark={dark} />
</div>
<div className={`chart-box`}>
<RequestChart labels={request.date} datasets={request.value} dark={dark} />
<RequestChart
labels={request.date}
datasets={request.value}
dark={dark}
/>
</div>
<div className={`chart-box`}>
<BillingChart labels={billing.date} datasets={billing.value} dark={dark} />
<BillingChart
labels={billing.date}
datasets={billing.value}
dark={dark}
/>
</div>
<div className={`chart-box`}>
<ErrorChart labels={error.date} datasets={error.value} dark={dark} />

View File

@ -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<InfoResponse>({
billing_today: 0,
billing_month: 0,
subscription_count: 0,
});
useEffectAsync(async () => {
setForm(await getAdminInfo());
}, []);
return (
<div className={`info-boxes`}>
<div className={`info-box`}>
<div className={`box-wrapper`}>
<div className={`box-title`}>{t("admin.billing-today")}</div>
<div className={`box-value money`}>{form.today}</div>
<div className={`box-value money`}>{form.billing_today}</div>
</div>
<div className={`box-icon`}>
<CircleDollarSign />
@ -25,7 +32,7 @@ function InfoBox() {
<div className={`info-box`}>
<div className={`box-wrapper`}>
<div className={`box-title`}>{t("admin.billing-month")}</div>
<div className={`box-value money`}>{form.month}</div>
<div className={`box-value money`}>{form.billing_month}</div>
</div>
<div className={`box-icon`}>
<Wallet />
@ -36,7 +43,7 @@ function InfoBox() {
<div className={`box-wrapper`}>
<div className={`box-title`}>{t("admin.subscription-users")}</div>
<div className={`box-value`}>
{form.users}
{form.subscription_count}
<span className={`box-subvalue`}>{t("admin.seat")}</span>
</div>
</div>

View File

@ -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={<LayoutDashboard />}
path={"/"}
/>
<MenuItem title={t("admin.users")} icon={<Users />} path={"/users"} />
<MenuItem
title={t("admin.settings")}
icon={<Settings />}
path={"/config"}
path={"/settings"}
/>
</div>
);

View File

@ -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)",

View File

@ -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 = {

View File

@ -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)",

View File

@ -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": "Доход за месяц",

View File

@ -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([
</Suspense>
),
},
{
id: "admin-users",
path: "users",
element: (
<Suspense>
<Users />
</Suspense>
),
},
{
id: "admin-settings",
path: "settings",
element: (
<Suspense>
<Settings />
</Suspense>
),
},
],
ErrorBoundary: NotFound,
},

View File

@ -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 (
<div className={`admin-page`}>
<MenuBar />

View File

@ -0,0 +1,5 @@
function Settings() {
return <></>;
}
export default Settings;

View File

@ -0,0 +1,5 @@
function Users() {
return <></>;
}
export default Users;

View File

@ -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;