mirror of
https://github.com/Fasthei/taiji-pda-v0.git
synced 2026-09-26 20:12:27 +00:00
feat: 完善超级管理员控制台API集成
- 删除资源管理中的假数据,改为从API获取 - 添加删除Agent和模型供应商功能 - 完善创建渠道、资源管理、审批申请等功能的API集成 - 修复计费统计功能,接入后端API - 添加缺失API接口清单文档 - 优化API响应格式处理,支持多种响应结构 - 修复审批申请对话框,添加供应商申请审批功能
This commit is contained in:
+657
-529
File diff suppressed because it is too large
Load Diff
@@ -344,11 +344,6 @@ export default function DataToolsPage() {
|
||||
<Input placeholder={t("输入工具名称", "Enter tool name")} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>{t("API端点", "API Endpoint")}</Label>
|
||||
<Input placeholder="https://api.example.com/endpoint" />
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-4 space-y-4">
|
||||
<h3 className="font-semibold flex items-center gap-2">
|
||||
<Cpu className="h-4 w-4" />
|
||||
@@ -514,7 +509,14 @@ export default function DataToolsPage() {
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("数据接口URL", "Data Interface URL")}</Label>
|
||||
<Input placeholder="https://api.example.com/data" />
|
||||
<Input
|
||||
placeholder="https://api.example.com/data"
|
||||
disabled
|
||||
className="bg-muted cursor-not-allowed"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("端点URL由系统自动配置,不支持自定义", "Endpoint URL is automatically configured by the system and cannot be customized")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("查询参数", "Query Parameters")}</Label>
|
||||
|
||||
+8
-1
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { isAuthenticated } from "@/lib/auth"
|
||||
import { DashboardLayout } from "@/components/dashboard-layout"
|
||||
@@ -8,13 +8,20 @@ import { DashboardOverview } from "@/components/dashboard-overview"
|
||||
|
||||
export default function Home() {
|
||||
const router = useRouter()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
if (!isAuthenticated()) {
|
||||
router.push("/login")
|
||||
}
|
||||
}, [router])
|
||||
|
||||
// 在客户端挂载前返回 null,避免 Hydration 错误
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!isAuthenticated()) {
|
||||
return null
|
||||
}
|
||||
|
||||
+38
-10
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useRouter, usePathname } from "next/navigation"
|
||||
import { isAuthenticated } from "@/lib/auth"
|
||||
import { isAuthenticated, isAdminAuthenticated, isChannelAuthenticated } from "@/lib/auth"
|
||||
|
||||
interface AuthGuardProps {
|
||||
children: React.ReactNode
|
||||
@@ -13,15 +13,33 @@ interface AuthGuardProps {
|
||||
export function AuthGuard({ children, requireAuth = true, redirectTo = "/login" }: AuthGuardProps) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const [isAuth, setIsAuth] = useState(false)
|
||||
|
||||
// 检查是否是登录页面
|
||||
const isLoginPage = pathname === "/login" || pathname === "/channel/login" || pathname === "/admin/login"
|
||||
const isAdminPage = pathname.startsWith("/admin")
|
||||
const isChannelPage = pathname.startsWith("/channel")
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
|
||||
// 根据页面类型检查对应的认证状态
|
||||
let auth = false
|
||||
if (isAdminPage) {
|
||||
auth = isAdminAuthenticated()
|
||||
} else if (isChannelPage) {
|
||||
auth = isChannelAuthenticated()
|
||||
} else {
|
||||
auth = isAuthenticated()
|
||||
}
|
||||
|
||||
setIsAuth(auth)
|
||||
|
||||
// 登录页面不需要认证
|
||||
if (isLoginPage) {
|
||||
// 如果已登录,重定向到首页
|
||||
if (isAuthenticated()) {
|
||||
// 如果已登录,重定向到对应的首页
|
||||
if (auth) {
|
||||
if (pathname === "/channel/login") {
|
||||
router.push("/channel/dashboard")
|
||||
} else if (pathname === "/admin/login") {
|
||||
@@ -34,20 +52,30 @@ export function AuthGuard({ children, requireAuth = true, redirectTo = "/login"
|
||||
}
|
||||
|
||||
// 需要认证的页面
|
||||
if (requireAuth && !isAuthenticated()) {
|
||||
if (requireAuth && !auth) {
|
||||
// 根据路径判断重定向到哪个登录页
|
||||
if (pathname.startsWith("/channel")) {
|
||||
if (isChannelPage) {
|
||||
router.push("/channel/login")
|
||||
} else if (pathname.startsWith("/admin")) {
|
||||
} else if (isAdminPage) {
|
||||
router.push("/admin/login")
|
||||
} else {
|
||||
router.push(redirectTo)
|
||||
}
|
||||
}
|
||||
}, [pathname, requireAuth, redirectTo, router, isLoginPage])
|
||||
}, [pathname, requireAuth, redirectTo, router, isLoginPage, isAdminPage, isChannelPage])
|
||||
|
||||
// 在客户端挂载前,返回一个占位符以避免 Hydration 错误
|
||||
if (!mounted) {
|
||||
// 登录页面在挂载前直接渲染
|
||||
if (isLoginPage) {
|
||||
return <>{children}</>
|
||||
}
|
||||
// 其他页面在挂载前返回 null(等待客户端检查)
|
||||
return null
|
||||
}
|
||||
|
||||
// 如果是登录页面且已登录,不渲染内容(等待重定向)
|
||||
if (isLoginPage && isAuthenticated()) {
|
||||
if (isLoginPage && isAuth) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -57,7 +85,7 @@ export function AuthGuard({ children, requireAuth = true, redirectTo = "/login"
|
||||
}
|
||||
|
||||
// 如果需要认证但未登录,不渲染内容(等待重定向)
|
||||
if (requireAuth && !isAuthenticated()) {
|
||||
if (requireAuth && !isAuth) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -55,16 +55,22 @@ export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname()
|
||||
const { language, setLanguage, t } = useLanguage()
|
||||
const [showApiKeyDialog, setShowApiKeyDialog] = useState(false)
|
||||
// 使用固定的初始值,避免服务器端和客户端不一致
|
||||
const [apiKey, setApiKey] = useState("sk_live_1234567890abcdefghijklmnopqrstuvwxyz")
|
||||
const [serviceEndpoint, setServiceEndpoint] = useState("https://api.taiji-ai.com/v1")
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text)
|
||||
if (typeof window !== "undefined" && navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text)
|
||||
}
|
||||
}
|
||||
|
||||
const regenerateApiKey = () => {
|
||||
const newKey = `sk_live_${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}`
|
||||
setApiKey(newKey)
|
||||
// 只在客户端执行,确保服务器端和客户端一致
|
||||
if (typeof window !== "undefined") {
|
||||
const newKey = `sk_live_${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}`
|
||||
setApiKey(newKey)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -14,11 +14,13 @@ interface LanguageContextType {
|
||||
const LanguageContext = createContext<LanguageContextType | undefined>(undefined)
|
||||
|
||||
export function LanguageProvider({ children }: { children: React.ReactNode }) {
|
||||
// 始终从 "zh" 开始,确保服务器端和客户端初始状态一致
|
||||
const [language, setLanguage] = useState<Language>("zh")
|
||||
|
||||
useEffect(() => {
|
||||
// 只在客户端挂载后读取 localStorage
|
||||
const savedLang = localStorage.getItem("language") as Language
|
||||
if (savedLang) {
|
||||
if (savedLang && (savedLang === "zh" || savedLang === "en")) {
|
||||
setLanguage(savedLang)
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
# 缺失的 API 接口清单
|
||||
|
||||
根据前端代码和 API 文档的对比,以下接口在 API 文档中**没有提供**:
|
||||
|
||||
## 1. 删除 Agent 接口
|
||||
|
||||
**前端使用位置**: `/app/admin/dashboard/page.tsx` (资源管理标签页)
|
||||
|
||||
**当前实现**:
|
||||
- 前端使用 `TaijiAPIClient.deleteTool(agent.id || agent.name)` 来删除 Agent
|
||||
- 这个接口实际上是删除 Data Ingestion 服务中的工具,而不是删除 Agent 资源
|
||||
|
||||
**需要的接口**:
|
||||
```
|
||||
DELETE /api/admin/resources/agents/{agent_id}
|
||||
```
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:8002/api/admin/resources/agents/agent-uuid-1" \
|
||||
-H "Authorization: Bearer <admin_token>"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Agent资源已删除"
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 当前前端代码错误地使用了 `deleteTool` 接口来删除 Agent
|
||||
- 应该提供一个专门的删除 Agent 资源的接口
|
||||
- 删除应该是软删除,仅标记为不活跃
|
||||
|
||||
---
|
||||
|
||||
## 2. 删除渠道接口
|
||||
|
||||
**前端使用位置**: `/app/admin/dashboard/page.tsx` (渠道管理标签页)
|
||||
|
||||
**当前状态**:
|
||||
- 前端UI中有"删除渠道"按钮,但没有实现对应的API调用
|
||||
|
||||
**需要的接口**:
|
||||
```
|
||||
DELETE /api/admin/channels/{channel_id}
|
||||
```
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:8002/api/admin/channels/channel-uuid-1" \
|
||||
-H "Authorization: Bearer <admin_token>"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "渠道已删除"
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 删除渠道前应该检查是否有关联的租户
|
||||
- 如果有租户,应该提示或阻止删除
|
||||
- 删除应该是软删除,仅标记为不活跃
|
||||
|
||||
---
|
||||
|
||||
## 3. 更新渠道信息接口
|
||||
|
||||
**前端使用位置**: `/app/admin/dashboard/page.tsx` (渠道管理标签页)
|
||||
|
||||
**当前状态**:
|
||||
- 前端可能有编辑渠道信息的功能,但需要确认是否有对应的API
|
||||
|
||||
**需要的接口**:
|
||||
```
|
||||
PUT /api/admin/channels/{channel_id}
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"name": "合作渠道A(更新)",
|
||||
"email": "new-email@channel-a.com",
|
||||
"commissionRate": 12.0,
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "channel-uuid-1",
|
||||
"name": "合作渠道A(更新)",
|
||||
"email": "new-email@channel-a.com"
|
||||
},
|
||||
"message": "渠道信息更新成功"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 更新 Agent 资源配置接口
|
||||
|
||||
**前端使用位置**: `/app/admin/dashboard/page.tsx` (资源管理标签页 - Agent资源配置对话框)
|
||||
|
||||
**当前状态**:
|
||||
- 前端有配置 Agent CPU 和内存的对话框,但保存时没有调用API
|
||||
|
||||
**需要的接口**:
|
||||
```
|
||||
PUT /api/admin/resources/agents/{agent_id}/config
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"cpu": 4.0,
|
||||
"memory": 8.0,
|
||||
"maxInstances": 10
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Agent资源配置更新成功"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
### 必须实现的接口(前端已使用):
|
||||
1. ❌ **DELETE /api/admin/resources/agents/{agent_id}** - 删除Agent资源
|
||||
- 当前前端错误地使用了 `deleteTool` 接口
|
||||
|
||||
### 建议实现的接口(前端UI已存在但未实现):
|
||||
2. ❌ **DELETE /api/admin/channels/{channel_id}** - 删除渠道
|
||||
3. ⚠️ **PUT /api/admin/channels/{channel_id}** - 更新渠道信息
|
||||
4. ⚠️ **PUT /api/admin/resources/agents/{agent_id}/config** - 更新Agent资源配置
|
||||
|
||||
### 已实现的接口(前端已使用):
|
||||
✅ 所有其他接口都已实现并在API文档中有说明
|
||||
|
||||
---
|
||||
|
||||
## 修复建议
|
||||
|
||||
1. **立即修复**: 实现 `DELETE /api/admin/resources/agents/{agent_id}` 接口,并更新前端代码使用正确的接口
|
||||
2. **优先级高**: 实现 `DELETE /api/admin/channels/{channel_id}` 接口,完善渠道管理功能
|
||||
3. **优先级中**: 实现渠道和Agent的更新接口,提升管理功能的完整性
|
||||
|
||||
+14
@@ -25,6 +25,20 @@ export function isAuthenticated(): boolean {
|
||||
return getAuthToken() !== null
|
||||
}
|
||||
|
||||
export function isAdminAuthenticated(): boolean {
|
||||
if (typeof window !== "undefined") {
|
||||
return localStorage.getItem("admin_token") !== null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function isChannelAuthenticated(): boolean {
|
||||
if (typeof window !== "undefined") {
|
||||
return localStorage.getItem("channel_token") !== null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function clearAuth(): void {
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.removeItem("auth_token")
|
||||
|
||||
Reference in New Issue
Block a user