"backend/internal/vscode:/vscode.git/clone" did not exist on "ca3e9336e11c2db3e09fc6881ec1b3c6ea698874"
Commit 11d063e3 authored by IanShaw027's avatar IanShaw027
Browse files

feat(前端API): 实现运维监控 API 客户端

- 新增 ops API 客户端(ops.ts)
- 扩展 settings API 支持 ops 配置
- 更新 admin API 索引导出 ops 模块
- 扩展 API 客户端支持 WebSocket 连接
parent e8464580
...@@ -16,6 +16,7 @@ import usageAPI from './usage' ...@@ -16,6 +16,7 @@ import usageAPI from './usage'
import geminiAPI from './gemini' import geminiAPI from './gemini'
import antigravityAPI from './antigravity' import antigravityAPI from './antigravity'
import userAttributesAPI from './userAttributes' import userAttributesAPI from './userAttributes'
import opsAPI from './ops'
/** /**
* Unified admin API object for convenient access * Unified admin API object for convenient access
...@@ -33,7 +34,8 @@ export const adminAPI = { ...@@ -33,7 +34,8 @@ export const adminAPI = {
usage: usageAPI, usage: usageAPI,
gemini: geminiAPI, gemini: geminiAPI,
antigravity: antigravityAPI, antigravity: antigravityAPI,
userAttributes: userAttributesAPI userAttributes: userAttributesAPI,
ops: opsAPI
} }
export { export {
...@@ -49,7 +51,8 @@ export { ...@@ -49,7 +51,8 @@ export {
usageAPI, usageAPI,
geminiAPI, geminiAPI,
antigravityAPI, antigravityAPI,
userAttributesAPI userAttributesAPI,
opsAPI
} }
export default adminAPI export default adminAPI
This diff is collapsed.
...@@ -34,9 +34,22 @@ export interface SystemSettings { ...@@ -34,9 +34,22 @@ export interface SystemSettings {
turnstile_enabled: boolean turnstile_enabled: boolean
turnstile_site_key: string turnstile_site_key: string
turnstile_secret_key_configured: boolean turnstile_secret_key_configured: boolean
// Model fallback configuration
enable_model_fallback: boolean
fallback_model_anthropic: string
fallback_model_openai: string
fallback_model_gemini: string
fallback_model_antigravity: string
// Identity patch configuration (Claude -> Gemini) // Identity patch configuration (Claude -> Gemini)
enable_identity_patch: boolean enable_identity_patch: boolean
identity_patch_prompt: string identity_patch_prompt: string
// Ops Monitoring (vNext)
ops_monitoring_enabled: boolean
ops_realtime_monitoring_enabled: boolean
ops_query_mode_default: 'auto' | 'raw' | 'preagg' | string
} }
export interface UpdateSettingsRequest { export interface UpdateSettingsRequest {
...@@ -60,8 +73,16 @@ export interface UpdateSettingsRequest { ...@@ -60,8 +73,16 @@ export interface UpdateSettingsRequest {
turnstile_enabled?: boolean turnstile_enabled?: boolean
turnstile_site_key?: string turnstile_site_key?: string
turnstile_secret_key?: string turnstile_secret_key?: string
enable_model_fallback?: boolean
fallback_model_anthropic?: string
fallback_model_openai?: string
fallback_model_gemini?: string
fallback_model_antigravity?: string
enable_identity_patch?: boolean enable_identity_patch?: boolean
identity_patch_prompt?: string identity_patch_prompt?: string
ops_monitoring_enabled?: boolean
ops_realtime_monitoring_enabled?: boolean
ops_query_mode_default?: 'auto' | 'raw' | 'preagg' | string
} }
/** /**
......
...@@ -80,9 +80,45 @@ apiClient.interceptors.response.use( ...@@ -80,9 +80,45 @@ apiClient.interceptors.response.use(
return response return response
}, },
(error: AxiosError<ApiResponse<unknown>>) => { (error: AxiosError<ApiResponse<unknown>>) => {
// Request cancellation: keep the original axios cancellation error so callers can ignore it.
// Otherwise we'd misclassify it as a generic "network error".
if (error.code === 'ERR_CANCELED' || axios.isCancel(error)) {
return Promise.reject(error)
}
// Handle common errors // Handle common errors
if (error.response) { if (error.response) {
const { status, data } = error.response const { status, data } = error.response
const url = String(error.config?.url || '')
// Validate `data` shape to avoid HTML error pages breaking our error handling.
const apiData = (typeof data === 'object' && data !== null ? data : {}) as Record<string, any>
// Ops monitoring disabled: treat as feature-flagged 404, and proactively redirect away
// from ops pages to avoid broken UI states.
if (status === 404 && apiData.message === 'Ops monitoring is disabled') {
try {
localStorage.setItem('ops_monitoring_enabled_cached', 'false')
} catch {
// ignore localStorage failures
}
try {
window.dispatchEvent(new CustomEvent('ops-monitoring-disabled'))
} catch {
// ignore event failures
}
if (window.location.pathname.startsWith('/admin/ops')) {
window.location.href = '/admin/settings'
}
return Promise.reject({
status,
code: 'OPS_DISABLED',
message: apiData.message || error.message,
url
})
}
// 401: Unauthorized - clear token and redirect to login // 401: Unauthorized - clear token and redirect to login
if (status === 401) { if (status === 401) {
...@@ -113,8 +149,8 @@ apiClient.interceptors.response.use( ...@@ -113,8 +149,8 @@ apiClient.interceptors.response.use(
// Return structured error // Return structured error
return Promise.reject({ return Promise.reject({
status, status,
code: data?.code, code: apiData.code,
message: data?.message || error.message message: apiData.message || apiData.detail || error.message
}) })
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment