mateclaw/mateclaw-ui/src/api/index.ts

560 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import axios from 'axios'
import { handleAuthFailure, updateTokenFromHeader } from '@/utils/auth'
// Axios 实例
export const http = axios.create({
baseURL: '/api/v1',
timeout: 30000,
})
// 请求拦截器:注入 Token + Workspace ID
http.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
const workspaceId = localStorage.getItem('mc-workspace-id')
if (workspaceId) {
config.headers['X-Workspace-Id'] = workspaceId
}
return config
})
// 响应拦截器:适配后端 R<T> { code, msg, data } 格式
http.interceptors.response.use(
(res) => {
// 滑动窗口续期:后端在 Token 接近过期时通过响应头下发新 Token
updateTokenFromHeader(res.headers)
const data = res.data
// 后端统一响应格式 R<T>: { code: number, msg: string, data: T }
if (data && typeof data === 'object' && 'code' in data) {
if (data.code === 200) return data
if (data.code === 401 || data.code === 403) {
handleAuthFailure()
return Promise.reject(new Error(data.msg || 'Unauthorized'))
}
return Promise.reject(new Error(data.msg || 'Request failed'))
}
return data
},
(err) => {
if (err.response?.status === 401 || err.response?.status === 403) {
handleAuthFailure()
}
return Promise.reject(err.response?.data?.msg || err.message)
}
)
// ==================== 受保护文件访问 ====================
/**
* 使用 JWT 认证 fetch 受保护文件,返回 Blob。
* 用于 <img> 和 <a download> 无法自动携带 Authorization header 的场景。
* 使用原生 fetch不走 axios避免 baseURL 拼接和 R<T> 拦截器干扰。
*/
export async function fetchAuthenticatedBlob(fileUrl: string): Promise<Blob> {
const token = localStorage.getItem('token')
const headers: Record<string, string> = {}
if (token) headers.Authorization = `Bearer ${token}`
const response = await fetch(fileUrl, { headers })
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`)
return response.blob()
}
// ==================== Auth ====================
export const authApi = {
login: (data: { username: string; password: string }) =>
http.post('/auth/login', data),
listUsers: () => http.get('/auth/users'),
createUser: (data: any) => http.post('/auth/users', data),
changePassword: (id: number, oldPassword: string, newPassword: string) =>
http.put(`/auth/users/${id}/password`, null, { params: { oldPassword, newPassword } }),
}
// ==================== Agent ====================
export const agentApi = {
list: () => http.get('/agents'),
get: (id: string | number) => http.get(`/agents/${id}`),
create: (data: any) => http.post('/agents', data),
update: (id: string | number, data: any) => http.put(`/agents/${id}`, data),
delete: (id: string | number) => http.delete(`/agents/${id}`),
chat: (id: string | number, data: any) => http.post(`/agents/${id}/chat`, data),
execute: (id: string | number, data: any) => http.post(`/agents/${id}/execute`, data),
getState: (id: string | number) => http.get(`/agents/${id}/state`),
}
// ==================== Templates ====================
export const templateApi = {
list: () => http.get('/templates'),
apply: (id: string) => http.post(`/templates/${id}/apply`),
}
// ==================== Chat ====================
export const chatApi = {
uploadFile: async (conversationId: string, file: File) => {
const formData = new FormData()
formData.append('file', file)
formData.append('conversationId', conversationId)
return http.post('/chat/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
},
stream: (data: any, signal?: AbortSignal) => {
const headers: Record<string, string> = {
Accept: 'text/event-stream',
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
}
const token = localStorage.getItem('token')
if (token) {
headers.Authorization = `Bearer ${token}`
}
return fetch('/api/v1/chat/stream', {
method: 'POST',
headers,
body: JSON.stringify(data),
signal,
})
},
stop: (conversationId: string) =>
http.post<{ stopped: boolean }>(`/chat/${conversationId}/stop`),
approve: (conversationId: string, data: { pendingId: string; decision: string }) =>
http.post(`/chat/${conversationId}/approve`, data),
getPendingApprovals: (conversationId: string) =>
http.get(`/chat/${conversationId}/pending-approvals`),
}
// ==================== Conversation ====================
export const conversationApi = {
list: () => http.get('/conversations'),
listMessages: (conversationId: string, params?: { beforeId?: number; limit?: number }) =>
http.get(`/conversations/${conversationId}/messages`, { params }),
getStatus: (conversationId: string) =>
http.get(`/conversations/${conversationId}/status`),
delete: (conversationId: string) =>
http.delete(`/conversations/${conversationId}`),
clearMessages: (conversationId: string) =>
http.delete(`/conversations/${conversationId}/messages`),
rename: (conversationId: string, title: string) =>
http.put(`/conversations/${conversationId}/title`, { title }),
}
// ==================== Skill ====================
export const skillApi = {
/** RFC-042 §2.1 — paginated skill listing with search/type/enabled/scanStatus filters */
page: (params: {
page?: number
size?: number
keyword?: string
skillType?: string
enabled?: boolean
/** 'PASSED' / 'FAILED' — filters by security_scan_status (RFC-042 §2.3.5) */
scanStatus?: string
} = {}) => http.get('/skills', { params }),
/** Tab count aggregate — returns { all, builtin, mcp, dynamic } */
counts: () => http.get('/skills/counts'),
/** RFC-042 §2.3.4 — manually rescan a single skill's security */
rescan: (id: string | number) => http.post(`/skills/${id}/rescan`),
listEnabled: () => http.get('/skills/enabled'),
get: (id: string | number) => http.get(`/skills/${id}`),
create: (data: any) => http.post('/skills', data),
update: (id: string | number, data: any) => http.put(`/skills/${id}`, data),
delete: (id: string | number) => http.delete(`/skills/${id}`),
toggle: (id: string | number, enabled: boolean) =>
http.put(`/skills/${id}/toggle?enabled=${enabled}`),
getActiveSkills: () => http.get('/skills/runtime/active'),
getRuntimeStatus: () => http.get('/skills/runtime/status'),
refreshRuntime: () => http.post('/skills/runtime/refresh'),
exportWorkspace: (id: string | number) => http.post(`/skills/${id}/export-workspace`),
getWorkspaceInfo: (id: string | number) => http.get(`/skills/${id}/workspace`),
}
// ==================== Skill Install ====================
export const skillInstallApi = {
searchHub: (q: string, limit = 20) =>
http.get('/skills/install/hub/search', { params: { q, limit } }),
startInstall: (data: { bundleUrl: string; version?: string; enable?: boolean; targetName?: string; overwrite?: boolean }) =>
http.post('/skills/install/start', data),
getStatus: (taskId: string) =>
http.get(`/skills/install/status/${taskId}`),
cancelInstall: (taskId: string) =>
http.post(`/skills/install/cancel/${taskId}`),
uninstall: (skillName: string) =>
http.delete(`/skills/install/${skillName}`),
uploadZip: (file: File, options?: { enable?: boolean; overwrite?: boolean; targetName?: string }) => {
const formData = new FormData()
formData.append('file', file)
return http.post('/skills/install/upload', formData, {
params: options,
headers: { 'Content-Type': 'multipart/form-data' },
})
},
}
// ==================== Datasource ====================
export const datasourceApi = {
list: () => http.get('/datasources'),
get: (id: string | number) => http.get(`/datasources/${id}`),
create: (data: any) => http.post('/datasources', data),
update: (id: string | number, data: any) => http.put(`/datasources/${id}`, data),
delete: (id: string | number) => http.delete(`/datasources/${id}`),
toggle: (id: string | number, enabled: boolean) =>
http.put(`/datasources/${id}/toggle?enabled=${enabled}`),
test: (id: string | number) => http.post(`/datasources/${id}/test`),
}
// ==================== Tool ====================
export const toolApi = {
list: () => http.get('/tools'),
listEnabled: () => http.get('/tools/enabled'),
get: (id: string | number) => http.get(`/tools/${id}`),
create: (data: any) => http.post('/tools', data),
update: (id: string | number, data: any) => http.put(`/tools/${id}`, data),
delete: (id: string | number) => http.delete(`/tools/${id}`),
toggle: (id: string | number, enabled: boolean) =>
http.put(`/tools/${id}/toggle?enabled=${enabled}`),
}
// ==================== Channel ====================
export const channelApi = {
list: () => http.get('/channels'),
get: (id: string | number) => http.get(`/channels/${id}`),
create: (data: any) => http.post('/channels', data),
update: (id: string | number, data: any) => http.put(`/channels/${id}`, data),
delete: (id: string | number) => http.delete(`/channels/${id}`),
toggle: (id: string | number, enabled: boolean) =>
http.put(`/channels/${id}/toggle?enabled=${enabled}`),
status: () => http.get('/channels/status'),
// 微信 iLink Bot QR 码登录
weixinQrcode: () => http.get('/channels/webhook/weixin/qrcode'),
weixinQrcodeStatus: (qrcode: string) =>
http.get(`/channels/webhook/weixin/qrcode/status?qrcode=${encodeURIComponent(qrcode)}`),
}
// ==================== MCP Server ====================
export const mcpApi = {
list: () => http.get('/mcp/servers'),
get: (id: string | number) => http.get(`/mcp/servers/${id}`),
create: (data: any) => http.post('/mcp/servers', data),
update: (id: string | number, data: any) => http.put(`/mcp/servers/${id}`, data),
delete: (id: string | number) => http.delete(`/mcp/servers/${id}`),
toggle: (id: string | number, enabled: boolean) =>
http.put(`/mcp/servers/${id}/toggle?enabled=${enabled}`),
test: (id: string | number) => http.post(`/mcp/servers/${id}/test`),
refresh: () => http.post('/mcp/servers/refresh'),
}
// ==================== Plan ====================
export const planApi = {
listByAgent: (agentId: string) => http.get(`/plans?agentId=${agentId}`),
get: (id: string | number) => http.get(`/plans/${id}`),
}
// ==================== Model ====================
export const modelApi = {
listProviders: () => http.get('/models'),
listEnabled: () => http.get('/models/enabled'),
get: (id: string | number) => http.get(`/models/${id}`),
getDefault: () => http.get('/models/default'),
create: (data: any) => http.post('/models', data),
update: (id: string | number, data: any) => http.put(`/models/${id}`, data),
delete: (id: string | number) => http.delete(`/models/${id}`),
setDefault: (id: string | number) => http.post(`/models/${id}/default`),
updateProviderConfig: (providerId: string, data: any) =>
http.put(`/models/${providerId}/config`, data),
createCustomProvider: (data: any) => http.post('/models/custom-providers', data),
deleteCustomProvider: (providerId: string) =>
http.delete(`/models/custom-providers/${providerId}`),
addProviderModel: (providerId: string, data: any) =>
http.post(`/models/${providerId}/models`, data),
removeProviderModel: (providerId: string, modelId: string) =>
http.delete(`/models/${providerId}/models/${encodeURIComponent(modelId)}`),
getActive: () => http.get('/models/active'),
setActive: (data: { providerId: string; model: string }) =>
http.put('/models/active', data),
// 模型发现与连接测试
discoverModels: (providerId: string) =>
http.post(`/models/${providerId}/discover`),
applyDiscoveredModels: (providerId: string, modelIds: string[]) =>
http.post(`/models/${providerId}/discover/apply`, { modelIds }),
testConnection: (providerId: string) =>
http.post(`/models/${providerId}/test-connection`),
testModel: (providerId: string, modelId: string) =>
http.post(`/models/${providerId}/models/${encodeURIComponent(modelId)}/test`),
// ==================== Embedding Model (RFC Embedding UI) ====================
listByType: (modelType: 'chat' | 'embedding') =>
http.get('/models/by-type', { params: { modelType } }),
testEmbedding: (modelId: string | number) =>
http.post(`/models/embedding/${modelId}/test`),
getDefaultEmbedding: () => http.get('/models/embedding/default'),
setDefaultEmbedding: (modelId: string | number | '') =>
http.post('/models/embedding/default', { modelId }),
}
// ==================== Provider Pool (RFC-009 Phase 4) ====================
export interface ProviderPoolEntry {
providerId: string
providerName: string
inPool: boolean
removalSource: string | null
removalMessage: string | null
removedAtMs: number | null
inCooldown: boolean
cooldownRemainingMs: number
consecutiveFailures: number
}
export interface ReprobeResult {
providerId: string
success: boolean
latencyMs: number
errorMessage: string | null
inPool: boolean
}
export const providerPoolApi = {
snapshot: () => http.get<ProviderPoolEntry[]>('/llm/provider-pool'),
reprobe: (providerId: string) =>
http.post<ReprobeResult>(`/llm/provider-pool/${encodeURIComponent(providerId)}/reprobe`),
}
// ==================== OAuth ====================
export const oauthApi = {
authorize: () => http.get('/oauth/openai/authorize'),
status: () => http.get('/oauth/openai/status'),
refresh: () => http.post('/oauth/openai/refresh'),
revoke: () => http.delete('/oauth/openai/revoke'),
}
// RFC-062: Claude Code OAuth piggybacks on the user's local Claude Code
// install — no in-app authorize/revoke flow yet (PR-4). Until then the UI
// can only check status + force a re-detect from disk.
export const claudeCodeOAuthApi = {
status: () => http.get('/oauth/anthropic/status'),
reload: () => http.post('/oauth/anthropic/reload'),
}
// ==================== Setup ====================
export const setupApi = {
onboardingStatus: () => http.get('/setup/onboarding-status'),
}
// ==================== Settings ====================
export const settingsApi = {
get: () => http.get('/settings'),
update: (data: any) => http.put('/settings', data),
getLanguage: () => http.get('/settings/language'),
updateLanguage: (language: string) => http.put('/settings/language', { language }),
}
// ==================== Workspace ====================
const encodeFilePath = (filename: string) =>
filename.split('/').map(encodeURIComponent).join('/')
export const agentContextApi = {
listFiles: (agentId: string | number) =>
http.get(`/agents/${agentId}/workspace/files`),
getFile: (agentId: string | number, filename: string) =>
http.get(`/agents/${agentId}/workspace/files/${encodeFilePath(filename)}`),
saveFile: (agentId: string | number, filename: string, content: string) =>
http.put(`/agents/${agentId}/workspace/files/${encodeFilePath(filename)}`, { content }),
deleteFile: (agentId: string | number, filename: string) =>
http.delete(`/agents/${agentId}/workspace/files/${encodeFilePath(filename)}`),
getPromptFiles: (agentId: string | number) =>
http.get(`/agents/${agentId}/workspace/prompt-files`),
setPromptFiles: (agentId: string | number, files: string[]) =>
http.put(`/agents/${agentId}/workspace/prompt-files`, { files }),
}
// ==================== Security ====================
export const securityApi = {
getGuardConfig: () => http.get('/security/guard/config'),
updateGuardConfig: (data: any) => http.put('/security/guard/config', data),
getFileGuardConfig: () => http.get('/security/guard/config/file-guard'),
updateFileGuardConfig: (data: any) => http.put('/security/guard/config/file-guard', data),
listRules: (params?: any) => http.get('/security/guard/rules', { params }),
listBuiltinRules: (params?: any) => http.get('/security/guard/rules/builtin', { params }),
createRule: (data: any) => http.post('/security/guard/rules', data),
updateRule: (ruleId: string, data: any) => http.put(`/security/guard/rules/${ruleId}`, data),
toggleRule: (ruleId: string, enabled: boolean) =>
http.put(`/security/guard/rules/${ruleId}/toggle?enabled=${enabled}`),
deleteRule: (ruleId: string) => http.delete(`/security/guard/rules/${ruleId}`),
listAuditLogs: (params?: any) => http.get('/security/audit/logs', { params }),
getAuditStats: () => http.get('/security/audit/stats'),
listApprovals: (params?: any) => http.get('/security/approvals', { params }),
}
// ==================== Token Usage ====================
export const tokenUsageApi = {
getSummary: (params?: { startDate?: string; endDate?: string; modelName?: string; providerId?: string }) =>
http.get('/token-usage', { params }),
}
// ==================== CronJob ====================
export const cronJobApi = {
list: () => http.get('/cron-jobs'),
get: (id: string | number) => http.get(`/cron-jobs/${id}`),
create: (data: any) => http.post('/cron-jobs', data),
update: (id: string | number, data: any) => http.put(`/cron-jobs/${id}`, data),
delete: (id: string | number) => http.delete(`/cron-jobs/${id}`),
toggle: (id: string | number, enabled: boolean) =>
http.put(`/cron-jobs/${id}/toggle`, null, { params: { enabled } }),
runNow: (id: string | number) => http.post(`/cron-jobs/${id}/run`),
}
// ==================== Wiki Knowledge Base ====================
export const wikiApi = {
// Knowledge Base
listKBs: () => http.get('/wiki/knowledge-bases'),
getKB: (id: number) => http.get(`/wiki/knowledge-bases/${id}`),
listKBsByAgent: (agentId: number) => http.get(`/wiki/knowledge-bases/agent/${agentId}`),
createKB: (data: { name: string; description?: string; agentId?: number }) =>
http.post('/wiki/knowledge-bases', data),
updateKB: (id: number, data: { name?: string; description?: string; agentId?: number; embeddingModelId?: string | number | null }) =>
http.put(`/wiki/knowledge-bases/${id}`, data),
deleteKB: (id: number) => http.delete(`/wiki/knowledge-bases/${id}`),
getConfig: (id: number) => http.get(`/wiki/knowledge-bases/${id}/config`),
updateConfig: (id: number, content: string) =>
http.put(`/wiki/knowledge-bases/${id}/config`, { content }),
// Directory Scan
setSourceDirectory: (id: number, path: string) =>
http.put(`/wiki/knowledge-bases/${id}/source-directory`, { path }),
scanDirectory: (id: number) => http.post(`/wiki/knowledge-bases/${id}/scan`),
// Raw Materials
listRaw: (kbId: number) => http.get(`/wiki/knowledge-bases/${kbId}/raw`),
addRawText: (kbId: number, data: { title: string; content: string }) =>
http.post(`/wiki/knowledge-bases/${kbId}/raw/text`, data),
uploadRaw: (kbId: number, formData: FormData, onProgress?: (pct: number) => void) =>
http.post(`/wiki/knowledge-bases/${kbId}/raw/upload`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: onProgress
? (e) => { if (e.total) onProgress(Math.round((e.loaded / e.total) * 100)) }
: undefined,
}),
deleteRaw: (kbId: number, rawId: number) =>
http.delete(`/wiki/knowledge-bases/${kbId}/raw/${rawId}`),
reprocessRaw: (kbId: number, rawId: number) =>
http.post(`/wiki/knowledge-bases/${kbId}/raw/${rawId}/reprocess`),
downloadRaw: (kbId: number, rawId: number) =>
http.get<Blob>(`/wiki/knowledge-bases/${kbId}/raw/${rawId}/download`, {
responseType: 'blob',
}),
// Wiki Pages
listPages: (kbId: number, rawId?: number) =>
http.get(`/wiki/knowledge-bases/${kbId}/pages`, rawId != null ? { params: { rawId } } : undefined),
getPage: (kbId: number, slug: string) =>
http.get(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}`),
updatePage: (kbId: number, slug: string, content: string) =>
http.put(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}`, { content }),
deletePage: (kbId: number, slug: string) =>
http.delete(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}`),
batchDeletePages: (kbId: number, slugs: string[]) =>
http.delete(`/wiki/knowledge-bases/${kbId}/pages/batch`, { data: slugs }),
getBacklinks: (kbId: number, slug: string) =>
http.get(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}/backlinks`),
// RFC-051 PR-7: archived pages
listArchivedPages: (kbId: number) =>
http.get(`/wiki/knowledge-bases/${kbId}/pages/archived`),
archivePage: (kbId: number, slug: string) =>
http.post(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}/archive`),
unarchivePage: (kbId: number, slug: string) =>
http.post(`/wiki/knowledge-bases/${kbId}/pages/${encodeURIComponent(slug)}/unarchive`),
// Processing
processKB: (kbId: number) => http.post(`/wiki/knowledge-bases/${kbId}/process`),
getProcessingStatus: (kbId: number) => http.get(`/wiki/knowledge-bases/${kbId}/processing-status`),
// RFC-029: Relations
getRelatedPages: (kbId: number, slug: string, topK = 5) =>
http.get(`/wiki/kb/${kbId}/pages/${encodeURIComponent(slug)}/related`, { params: { topK } }),
explainRelation: (kbId: number, slugA: string, slugB: string) =>
http.get(`/wiki/kb/${kbId}/pages/${encodeURIComponent(slugA)}/relation/${encodeURIComponent(slugB)}`),
getPageCitations: (kbId: number, pageId: number) =>
http.get(`/wiki/kb/${kbId}/pages/${pageId}/citations`),
// RFC-030: Jobs
getWikiJobs: (kbId: number, rawId: number) =>
http.get(`/wiki/kb/${kbId}/jobs`, { params: { rawId } }),
getKBStats: (kbId: number) =>
http.get(`/wiki/kb/${kbId}/stats`),
// RFC-031: Enrichment & Repair
enrichPage: (kbId: number, slug: string) =>
http.post(`/wiki/kb/${kbId}/pages/${encodeURIComponent(slug)}/enrich`),
repairPage: (kbId: number, slug: string) =>
http.post(`/wiki/kb/${kbId}/pages/${encodeURIComponent(slug)}/repair`),
// RFC-032: Search preview
searchPreview: (kbId: number, data: { query: string; mode?: string; topK?: number }) =>
http.post(`/wiki/kb/${kbId}/search-preview`, data),
}
// ==================== Workspace (Team) ====================
export const workspaceTeamApi = {
list: () => http.get('/workspaces'),
get: (id: string | number) => http.get(`/workspaces/${id}`),
create: (data: any) => http.post('/workspaces', data),
update: (id: string | number, data: any) => http.put(`/workspaces/${id}`, data),
delete: (id: string | number) => http.delete(`/workspaces/${id}`),
listMembers: (id: string | number) => http.get(`/workspaces/${id}/members`),
addMember: (id: string | number, data: { username: string; password?: string; nickname?: string; role?: string }) =>
http.post(`/workspaces/${id}/members`, data),
updateMemberRole: (id: string | number, memberId: string | number, role: string) =>
http.put(`/workspaces/${id}/members/${memberId}`, { role }),
removeMember: (id: string | number, memberId: string | number) =>
http.delete(`/workspaces/${id}/members/${memberId}`),
}
// ==================== Agent Binding ====================
export const agentBindingApi = {
listSkills: (agentId: string | number) => http.get(`/agents/${agentId}/skills`),
setSkills: (agentId: string | number, skillIds: number[]) => http.put(`/agents/${agentId}/skills`, skillIds),
bindSkill: (agentId: string | number, skillId: number) => http.post(`/agents/${agentId}/skills/${skillId}`),
unbindSkill: (agentId: string | number, skillId: number) => http.delete(`/agents/${agentId}/skills/${skillId}`),
listTools: (agentId: string | number) => http.get(`/agents/${agentId}/tools`),
setTools: (agentId: string | number, toolNames: string[]) => http.put(`/agents/${agentId}/tools`, toolNames),
// RFC-009 PR-3: per-agent provider preference order. Empty list = use global chain order.
listProviderPreferences: (agentId: string | number) =>
http.get(`/agents/${agentId}/provider-preferences`),
setProviderPreferences: (agentId: string | number, providerIds: string[]) =>
http.put(`/agents/${agentId}/provider-preferences`, providerIds),
}
// ==================== Dashboard ====================
export const dashboardApi = {
overview: () => http.get('/dashboard/overview'),
trend: (days = 30) => http.get('/dashboard/trend', { params: { days } }),
agentRanking: (days = 7, topN = 10) => http.get('/dashboard/agent-ranking', { params: { days, topN } }),
cronJobRuns: (cronJobId: string | number, limit = 20) => http.get(`/dashboard/cron-runs/${cronJobId}`, { params: { limit } }),
recentRuns: (limit = 20) => http.get('/dashboard/cron-runs', { params: { limit } }),
}
// ==================== Plugins ====================
export const pluginApi = {
list: () => http.get('/plugins'),
get: (name: string) => http.get(`/plugins/${name}`),
disable: (name: string) => http.post(`/plugins/${name}/disable`),
enable: (name: string) => http.post(`/plugins/${name}/enable`),
updateConfig: (name: string, config: Record<string, any>) => http.put(`/plugins/${name}/config`, config),
}
// ==================== Audit Events ====================
export const auditApi = {
listEvents: (params: {
action?: string
resourceType?: string
startTime?: string
endTime?: string
page?: number
size?: number
}) => http.get('/audit/events', { params }),
}