replace ai-costs placeholder with real API billing dashboard
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 0s
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 0s
This commit is contained in:
parent
1a55fce7e4
commit
49e1b10f67
@ -10,6 +10,7 @@ import PageLoading from './components/PageLoading'
|
|||||||
import AdminLayout from './layouts/AdminLayout'
|
import AdminLayout from './layouts/AdminLayout'
|
||||||
|
|
||||||
const Login = lazy(() => import('./pages/Login'))
|
const Login = lazy(() => import('./pages/Login'))
|
||||||
|
const BillingPage = lazy(() => import('./pages/Billing'))
|
||||||
const GiteaEmbed = lazy(() => import('./pages/GiteaEmbed'))
|
const GiteaEmbed = lazy(() => import('./pages/GiteaEmbed'))
|
||||||
const ServersPage = lazy(() => import("./pages/Servers"))
|
const ServersPage = lazy(() => import("./pages/Servers"))
|
||||||
const AuditLogPage = lazy(() => import("./pages/AuditLog"))
|
const AuditLogPage = lazy(() => import("./pages/AuditLog"))
|
||||||
@ -72,7 +73,6 @@ function App() {
|
|||||||
<Route path="knowledge/bases" element={<Placeholder title="知识库列表" />} />
|
<Route path="knowledge/bases" element={<Placeholder title="知识库列表" />} />
|
||||||
<Route path="knowledge/sources" element={<Placeholder title="知识源列表" />} />
|
<Route path="knowledge/sources" element={<Placeholder title="知识源列表" />} />
|
||||||
<Route path="imports" element={<Placeholder title="文档导入" />} />
|
<Route path="imports" element={<Placeholder title="文档导入" />} />
|
||||||
<Route path="ai-costs" element={<Placeholder title="AI 调用与成本" />} />
|
|
||||||
<Route path="files" element={<Placeholder title="文件与 COS" />} />
|
<Route path="files" element={<Placeholder title="文件与 COS" />} />
|
||||||
<Route
|
<Route
|
||||||
path="settings"
|
path="settings"
|
||||||
@ -82,6 +82,10 @@ function App() {
|
|||||||
</PermissionGuard>
|
</PermissionGuard>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="billing"
|
||||||
|
element={<PermissionGuard requiredRole="SUPER_ADMIN"><Suspense fallback={<PageLoading />}><BillingPage /></Suspense></PermissionGuard>}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="git"
|
path="git"
|
||||||
element={
|
element={
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import type React from 'react'
|
import type React from 'react'
|
||||||
import { CodeOutlined, CloudServerOutlined, RobotOutlined, DashboardOutlined,
|
import { DollarOutlined, CodeOutlined, CloudServerOutlined, RobotOutlined, DashboardOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
BookOutlined,
|
BookOutlined,
|
||||||
ImportOutlined,
|
ImportOutlined,
|
||||||
DollarOutlined,
|
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
FileOutlined,
|
FileOutlined,
|
||||||
CloudOutlined,
|
|
||||||
SafetyOutlined,
|
SafetyOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import type { AdminRole } from '@/types/admin'
|
import type { AdminRole } from '@/types/admin'
|
||||||
@ -44,9 +42,9 @@ export const adminMenuItems: AdminMenuItem[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ path: '/imports', name: '文档导入', icon: <ImportOutlined /> },
|
{ path: '/imports', name: '文档导入', icon: <ImportOutlined /> },
|
||||||
{ path: '/ai-costs', name: 'AI 调用与成本', icon: <CloudOutlined /> },
|
|
||||||
{ path: '/files', name: '文件与 COS', icon: <FileOutlined /> },
|
{ path: '/files', name: '文件与 COS', icon: <FileOutlined /> },
|
||||||
{ path: '/settings', name: '系统配置', icon: <SettingOutlined />, requiredRole: 'ADMIN' },
|
{ path: '/settings', name: '系统配置', icon: <SettingOutlined />, requiredRole: 'ADMIN' },
|
||||||
|
{ path: '/billing', name: 'API 用量', icon: <DollarOutlined />, requiredRole: 'SUPER_ADMIN' },
|
||||||
{ path: '/git', name: '代码仓库', icon: <CodeOutlined /> },
|
{ path: '/git', name: '代码仓库', icon: <CodeOutlined /> },
|
||||||
{ path: '/servers', name: '服务器运维', icon: <CloudServerOutlined />, requiredRole: 'SUPER_ADMIN' },
|
{ path: '/servers', name: '服务器运维', icon: <CloudServerOutlined />, requiredRole: 'SUPER_ADMIN' },
|
||||||
{ path: '/audit', name: '审计日志', icon: <SafetyOutlined />, requiredRole: 'ADMIN' },
|
{ path: '/audit', name: '审计日志', icon: <SafetyOutlined />, requiredRole: 'ADMIN' },
|
||||||
|
|||||||
@ -18,9 +18,9 @@ const breadcrumbMap: Record<string, string> = {
|
|||||||
'/knowledge/bases': '知识库列表',
|
'/knowledge/bases': '知识库列表',
|
||||||
'/knowledge/sources': '知识源列表',
|
'/knowledge/sources': '知识源列表',
|
||||||
'/imports': '文档导入',
|
'/imports': '文档导入',
|
||||||
'/ai-costs': 'AI 调用与成本',
|
|
||||||
'/files': '文件与 COS',
|
'/files': '文件与 COS',
|
||||||
'/settings': '系统配置',
|
'/settings': '系统配置',
|
||||||
|
'/billing': 'API 用量',
|
||||||
'/git': '代码仓库',
|
'/git': '代码仓库',
|
||||||
'/servers': '服务器运维',
|
'/servers': '服务器运维',
|
||||||
'/audit': '审计日志',
|
'/audit': '审计日志',
|
||||||
|
|||||||
30
src/pages/Billing.tsx
Normal file
30
src/pages/Billing.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import { Card, Row, Col, Statistic, Button, Tag, Space, Typography, App } from 'antd'
|
||||||
|
import { DollarOutlined, ReloadOutlined, LinkOutlined } from '@ant-design/icons'
|
||||||
|
import { getBilling, type BillingInfo } from '@/services/billing-api'
|
||||||
|
const { Text } = Typography
|
||||||
|
function BillingCard({ p }: { p: BillingInfo }) {
|
||||||
|
const color = p.status === 'ok' ? (parseFloat(p.balance) < 10 ? '#faad14' : '#52c41a') : '#999'
|
||||||
|
return (
|
||||||
|
<Card title={<Space><DollarOutlined />{p.name}<Tag color={p.status === 'ok' ? 'green' : 'default'}>{p.status === 'ok' ? '正常' : '未知'}</Tag></Space>}
|
||||||
|
extra={<Button type="link" size="small" icon={<LinkOutlined />} href={p.consoleUrl} target="_blank">控制台</Button>}>
|
||||||
|
<Statistic title="余额" value={p.balance} valueStyle={{ color, fontSize: 28 }} suffix={<Text type="secondary" style={{ fontSize: 14 }}>{p.currency}</Text>} />
|
||||||
|
<div style={{ marginTop: 8 }}><Text type="secondary" style={{ fontSize: 12 }}>模型: {p.model}</Text><br /><Text type="secondary" style={{ fontSize: 12 }}>{p.note}</Text></div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
function BillingContent() {
|
||||||
|
const qc = useQueryClient(); const [refreshing, setRefreshing] = useState(false)
|
||||||
|
const { data } = useQuery({ queryKey: ['billing'], queryFn: getBilling, staleTime: 60_000 })
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||||
|
<Typography.Title level={5} style={{ margin: 0 }}><DollarOutlined /> API 用量</Typography.Title>
|
||||||
|
<Button icon={<ReloadOutlined spin={refreshing} />} onClick={async () => { setRefreshing(true); await qc.invalidateQueries({ queryKey: ['billing'] }); setTimeout(() => setRefreshing(false), 800) }} loading={refreshing}>刷新</Button>
|
||||||
|
</div>
|
||||||
|
<Row gutter={[16, 16]}>{(data?.providers || []).map(p => <Col xs={24} sm={12} lg={6} key={p.name}><BillingCard p={p} /></Col>)}</Row>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default function BillingPage() { return <App><BillingContent /></App> }
|
||||||
5
src/services/billing-api.ts
Normal file
5
src/services/billing-api.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { api } from './http-client'
|
||||||
|
export interface BillingInfo {
|
||||||
|
name: string; model: string; balance: string; currency: string; status: 'ok' | 'unknown'; consoleUrl: string; note: string
|
||||||
|
}
|
||||||
|
export function getBilling(): Promise<{ providers: BillingInfo[] }> { return api.get('/admin-api/billing') }
|
||||||
Loading…
x
Reference in New Issue
Block a user