mirror of
https://github.com/coaidev/coai.git
synced 2025-05-20 05:20:15 +09:00
update api
This commit is contained in:
parent
610464c493
commit
b8076e12a9
53
app/src/admin/api.ts
Normal file
53
app/src/admin/api.ts
Normal 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;
|
||||
}
|
@ -1,3 +1,9 @@
|
||||
export type InfoResponse = {
|
||||
billing_today: number;
|
||||
billing_month: number;
|
||||
subscription_count: number;
|
||||
};
|
||||
|
||||
export type ModelChartResponse = {
|
||||
date: string[];
|
||||
value: {
|
||||
|
@ -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} />
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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)",
|
||||
|
@ -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 = {
|
||||
|
@ -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)",
|
||||
|
@ -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": "Доход за месяц",
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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 />
|
||||
|
5
app/src/routes/admin/Settings.tsx
Normal file
5
app/src/routes/admin/Settings.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
function Settings() {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default Settings;
|
5
app/src/routes/admin/Users.tsx
Normal file
5
app/src/routes/admin/Users.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
function Users() {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export default Users;
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user