Files
TaskPool/web/src/api/index.ts
T
admin 455f46caa0
Build and Deploy / Build and Push Docker Image (push) Successful in 1m53s
fix(ssr): avoid accessing localStorage during SSR initialization
- Initialize activeInterconnectNodeId with empty string
- Read from localStorage only in client-side
- Defer selected state initialization in NodeSwitcher to useEffect
2026-08-08 03:15:51 +08:00

881 lines
30 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.
// 获取 base URL(从后端注入的全局变量)
const BASE_URL = (window as any).__BASE_URL__ || ''
const API_VERSION = (window as any).__API_VERSION__ || '/api/v1'
const API_BASE_URL = BASE_URL + API_VERSION
interface ApiResponse<T> {
code: number
msg: string
data: T
}
export interface MonitorStats {
env: {
os: string
arch: string
go_version: string
num_cpu: number
goroutines: number
}
host: {
cpu_percent: number
mem_total: number
mem_used: number
mem_percent: number
disk_total: number
disk_used: number
disk_percent: number
uptime: number
platform: string
}
mem: {
alloc: number
total_alloc: number
sys: number
lookups: number
mallocs: number
frees: number
}
heap: {
heap_alloc: number
heap_sys: number
heap_idle: number
heap_inuse: number
heap_released: number
heap_objects: number
}
gc: {
next_gc: number
last_gc: number
pause_total_ns: number
num_gc: number
}
scheduler: {
scheduled: number
running: number
queue_size: number
worker_count: number
workers: {
id: number
status: string
task_id?: string
task_name?: string
start_time?: number
duration?: number
}[]
}
}
// 初始化时使用空字符串,避免 SSR 时访问 localStorage
export let activeInterconnectNodeId = ''
export let activeInterconnectNodeName = ''
// 在客户端初始化时从 localStorage 读取
if (typeof window !== 'undefined') {
activeInterconnectNodeId = localStorage.getItem('activeInterconnectNodeId') || ''
activeInterconnectNodeName = localStorage.getItem('activeInterconnectNodeName') || ''
}
export function setActiveInterconnectNodeId(id: string, name?: string) {
activeInterconnectNodeId = id
if (name !== undefined) {
activeInterconnectNodeName = name
}
localStorage.removeItem('site_settings_cache')
if (id) {
localStorage.setItem('activeInterconnectNodeId', id)
if (name) localStorage.setItem('activeInterconnectNodeName', name)
document.cookie = `active_interconnect_node_id=${id}; path=/; max-age=${7 * 24 * 3600}`
} else {
localStorage.removeItem('activeInterconnectNodeId')
localStorage.removeItem('activeInterconnectNodeName')
activeInterconnectNodeName = ''
document.cookie = `active_interconnect_node_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC`
}
}
let redirectingToLogin = false
function redirectToLogin() {
const loginPath = `${BASE_URL}/login`
if (redirectingToLogin) return
if (window.location.pathname === `${BASE_URL}/login` || window.location.pathname.endsWith('/login')) return
redirectingToLogin = true
window.location.href = loginPath
}
async function parseApiResponse<T>(res: Response, url: string): Promise<ApiResponse<T>> {
const text = await res.text()
const trimmed = text.trim()
if (!trimmed) {
if (res.status === 502 || res.status === 503 || res.status === 504) {
throw new Error('后端服务不可用,请确认已启动 TaskPool server(默认 :8052')
}
if (!res.ok) {
throw new Error(`请求失败(HTTP ${res.status}`)
}
throw new Error('服务器返回了空响应')
}
let json: ApiResponse<T>
try {
json = JSON.parse(trimmed) as ApiResponse<T>
} catch {
if (res.status === 502 || res.status === 503 || res.status === 504) {
throw new Error('后端服务不可用,请确认已启动 TaskPool server(默认 :8052')
}
throw new Error(`服务器返回了非 JSON 响应(HTTP ${res.status}`)
}
if (json.code === 401) {
// 公开接口不应触发硬跳转;其它接口未登录时跳转登录页
const isPublic =
url.startsWith('/settings/public') || url.startsWith('/auth/login') || url.startsWith('/install')
if (!isPublic) redirectToLogin()
throw new Error(json.msg || '请先登录')
}
if (typeof json.code === 'number' && json.code !== 200) {
throw new Error(json.msg || '请求失败')
}
// 兼容非标准成功体:HTTP 非 2xx 时优先报状态码
if (!res.ok && (json as any)?.code === undefined) {
throw new Error(json.msg || `请求失败(HTTP ${res.status}`)
}
return json
}
export async function request<T>(url: string, options?: RequestInit): Promise<T> {
let res: Response
try {
res = await fetch(`${API_BASE_URL}${url}`, {
...options,
credentials: 'include', // 携带 Cookie
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
})
} catch (err: any) {
// 路由切换 / React Query 取消请求时,浏览器会报 net::ERR_ABORTED
if (err?.name === 'AbortError' || options?.signal?.aborted) {
throw err
}
throw new Error(err?.message || '网络请求失败,请检查后端是否已启动')
}
const json = await parseApiResponse<T>(res, url)
return json.data
}
// 检查登录状态(不触发自动跳转)
export async function checkAuth(): Promise<boolean> {
try {
const res = await fetch(`${API_BASE_URL}/auth/me`, {
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
})
const text = await res.text()
if (!text.trim()) return false
const json = JSON.parse(text) as ApiResponse<{ username: string }>
return json.code === 200
} catch {
return false
}
}
export const api = {
auth: {
login: (data: { username: string; password: string }) =>
request<{ user: string }>('/auth/login', { method: 'POST', body: JSON.stringify(data) }),
logout: () => request('/auth/logout', { method: 'POST' }),
me: () => request<{ username: string; role: string }>('/auth/me'),
register: (data: { username: string; password: string; email: string }) =>
request('/auth/register', { method: 'POST', body: JSON.stringify(data) })
},
tasks: {
list: (params?: { page?: number; page_size?: number; name?: string; agent_id?: string; tags?: string; type?: string; sort_by?: string; order?: string }) => {
const query = new URLSearchParams()
if (params?.page) query.set('page', String(params.page))
if (params?.page_size) query.set('page_size', String(params.page_size))
if (params?.name) query.set('name', params.name)
if (params?.tags) query.set('tags', params.tags)
if (params?.agent_id) query.set('agent_id', params.agent_id)
if (params?.type) query.set('type', params.type)
if (params?.sort_by) query.set('sort_by', params.sort_by)
if (params?.order) query.set('order', params.order)
return request<TaskListResponse>(`/tasks?${query}`)
},
create: (data: Partial<Task>) => request<Task>('/tasks', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string, data: Partial<Task>) => request<Task>(`/tasks/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string, params?: { delete_files?: boolean }) => {
const query = new URLSearchParams()
if (params?.delete_files !== undefined) query.set('delete_files', String(params.delete_files))
const queryString = query.toString()
return request(`/tasks/${id}${queryString ? '?' + queryString : ''}`, { method: 'DELETE' })
},
batchDelete: (ids: string[]) => request<{ count: number }>('/tasks/batch-delete', { method: 'POST', body: JSON.stringify({ ids }) }),
batchDeleteByQuery: (params?: { name?: string, agent_id?: string, tags?: string, type?: string }) => {
const query = new URLSearchParams()
if (params?.name) query.append('name', params.name)
if (params?.agent_id) query.append('agent_id', params.agent_id)
if (params?.tags) query.append('tags', params.tags)
if (params?.type && params.type !== 'all') query.append('type', params.type)
return request<{ count: number }>(`/tasks/batch-by-query?${query.toString()}`, { method: 'DELETE' })
},
execute: (id: string) => request<ExecutionResult>(`/execute/task/${id}`, { method: 'POST' }),
stop: (logID: string) => request(`/tasks/stop/${logID}`, { method: 'POST' }),
tags: () => request<string[]>('/tasks/tags')
},
scripts: {
list: () => request<Script[]>('/scripts'),
create: (data: Partial<Script>) => request<Script>('/scripts', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string, data: Partial<Script>) => request<Script>(`/scripts/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string) => request(`/scripts/${id}`, { method: 'DELETE' })
},
env: {
list: (params?: { page?: number; page_size?: number; name?: string; type?: string; tags?: string }) => {
const query = new URLSearchParams()
if (params?.page) query.set('page', String(params.page))
if (params?.page_size) query.set('page_size', String(params.page_size))
if (params?.name) query.set('name', params.name)
if (params?.type && params.type !== 'all') query.set('type', params.type)
if (params?.tags) query.set('tags', params.tags)
return request<EnvListResponse>(`/env?${query}`)
},
tags: () => request<string[]>('/env/tags'),
secretStatus: () => request<boolean>('/env/secret-status'),
all: () => request<EnvVar[]>('/env/all'),
tasks: (id: string) => request<Task[]>(`/env/${id}/tasks`),
create: (data: Partial<EnvVar>) => request<EnvVar>('/env', { method: 'POST', body: JSON.stringify(data) }),
update: (id: string, data: Partial<EnvVar>) => request<EnvVar>(`/env/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string, force?: boolean) => {
const query = force ? '?force=true' : ''
return fetch(`${API_BASE_URL}/env/${id}${query}`, {
method: 'DELETE',
credentials: 'include'
}).then(res => res.json() as Promise<ApiResponse<any>>)
}
},
execute: {
command: (command: string) => request('/execute/command', { method: 'POST', body: JSON.stringify({ command }) }),
results: () => request('/execute/results')
},
logs: {
list: (params?: { page?: number; page_size?: number; task_id?: string; task_name?: string; status?: string }) => {
const query = new URLSearchParams()
if (params?.page) query.set('page', String(params.page))
if (params?.page_size) query.set('page_size', String(params.page_size))
if (params?.task_id) query.set('task_id', params.task_id)
if (params?.task_name) query.set('task_name', params.task_name)
if (params?.status) query.set('status', params.status)
return request<LogListResponse>(`/logs?${query}`)
},
get: (id: string) => request<LogDetail>(`/logs/${id}`),
detail: (id: string) => request<LogDetail>(`/logs/${id}`),
delete: (id: string) => request(`/logs/${id}`, { method: 'DELETE' }),
clear: (taskId?: string) => request('/logs/clear', { method: 'POST', body: JSON.stringify({ task_id: taskId }) })
},
dashboard: {
stats: () => request<Stats>('/stats'),
sentence: () => request<{ sentence: string }>('/sentence'),
sendStats: (days?: number) => request<DailyStats[]>(`/sendstats${days ? `?days=${days}` : ''}`),
taskStats: (days?: number) => request<TaskStatsItem[]>(`/taskstats${days ? `?days=${days}` : ''}`)
},
settings: {
getMonitor: () => request<MonitorStats>('/monitor'),
changePassword: (data: { old_username?: string; username?: string; old_password: string; new_password?: string }) =>
request('/settings/password', { method: 'POST', body: JSON.stringify(data) }),
getSite: () => request<SiteSettings>('/settings/site'),
getPublicSite: () => request<{ title: string; subtitle: string; icon: string; demo_mode: boolean }>('/settings/public'),
updateSite: (data: SiteSettings) =>
request('/settings/site', { method: 'PUT', body: JSON.stringify(data) }),
generateOpenapiToken: () => request<{ token: string }>('/settings/site/openapi-token/generate', { method: 'POST' }),
getScheduler: () => request<SchedulerSettings>('/settings/scheduler'),
updateScheduler: (data: SchedulerSettings) =>
request('/settings/scheduler', { method: 'PUT', body: JSON.stringify(data) }),
getPaths: () => request<{ scripts_dir: string }>('/settings/paths'),
getAbout: () => request<AboutInfo>('/settings/about'),
getChangelog: () => request<string>('/settings/changelog'),
get: (section: string, key: string) => request<string>(`/settings/${section}/${key}`),
getSection: (section: string) => request<Record<string, string>>(`/settings/${section}`),
setSection: (section: string, values: Record<string, string>) =>
request(`/settings/${section}`, { method: 'PUT', body: JSON.stringify(values) }),
generateToken: (section: string, key: string) =>
request<string>(`/settings/${section}/${key}/generate`, { method: 'POST' }),
getLoginLogs: (params?: { page?: number; page_size?: number; username?: string }) => {
const query = new URLSearchParams()
if (params?.page) query.set('page', String(params.page))
if (params?.page_size) query.set('page_size', String(params.page_size))
if (params?.username) query.set('username', params.username)
return request<LoginLogListResponse>(`/settings/loginlogs?${query}`)
},
createBackup: () => request('/settings/backup', { method: 'POST' }),
getBackupStatus: () => request<{ has_backup: boolean; backup_time: string }>('/settings/backup/status'),
downloadBackup: () => `${API_BASE_URL}/settings/backup/download`,
restoreBackup: async (file: File) => {
const formData = new FormData()
formData.append('file', file)
const res = await fetch(`${API_BASE_URL}/settings/restore`, {
method: 'POST',
credentials: 'include',
body: formData
})
const json: ApiResponse<null> = await res.json()
if (json.code === 401) {
window.location.href = BASE_URL + '/login'
throw new Error('请先登录')
}
if (json.code !== 200) throw new Error(json.msg || '恢复失败')
}
},
files: {
tree: () => request<FileNode[]>('/files/tree'),
getContent: (path: string) => request<{ path: string; content: string }>(`/files/content?path=${encodeURIComponent(path)}`),
download: (path: string) => `${API_BASE_URL}/files/download?path=${encodeURIComponent(path)}`,
downloadZip: (path: string) => `${API_BASE_URL}/files/download-zip?path=${encodeURIComponent(path)}`,
saveContent: (path: string, content: string) => request('/files/content', { method: 'POST', body: JSON.stringify({ path, content }) }),
create: (path: string, isDir: boolean) => request('/files/create', { method: 'POST', body: JSON.stringify({ path, isDir }) }),
delete: (path: string) => request('/files/delete', { method: 'POST', body: JSON.stringify({ path }) }),
rename: (oldPath: string, newPath: string) => request('/files/rename', { method: 'POST', body: JSON.stringify({ oldPath, newPath }) }),
move: (oldPath: string, newPath: string) => request('/files/move', { method: 'POST', body: JSON.stringify({ oldPath, newPath }) }),
copy: (sourcePath: string, targetPath: string) => request('/files/copy', { method: 'POST', body: JSON.stringify({ sourcePath, targetPath }) }),
uploadArchive: async (file: File, targetPath?: string) => {
const formData = new FormData()
formData.append('file', file)
if (targetPath) formData.append('path', targetPath)
const res = await fetch(`${API_BASE_URL}/files/upload`, {
method: 'POST',
credentials: 'include',
body: formData
})
const json: ApiResponse<null> = await res.json()
if (json.code === 401) {
window.location.href = BASE_URL + '/login'
throw new Error('请先登录')
}
if (json.code !== 200) throw new Error(json.msg || '上传失败')
},
uploadFiles: async (files: FileList, paths: string[], targetPath?: string) => {
const formData = new FormData()
for (let i = 0; i < files.length; i++) {
const file = files[i]
if (file) {
formData.append('files', file)
formData.append('paths', paths[i] || file.name)
}
}
if (targetPath) formData.append('path', targetPath)
const res = await fetch(`${API_BASE_URL}/files/uploadfiles`, {
method: 'POST',
credentials: 'include',
body: formData
})
const json: ApiResponse<null> = await res.json()
if (json.code === 401) {
window.location.href = BASE_URL + '/login'
throw new Error('请先登录')
}
if (json.code !== 200) throw new Error(json.msg || '上传失败')
}
},
deps: {
list: (params?: { language?: string; lang_version?: string }) => {
const query = new URLSearchParams()
if (params?.language) query.set('language', params.language)
if (params?.lang_version) query.set('lang_version', params.lang_version)
return request<Dependency[]>(`/deps?${query}`)
},
create: (data: { name: string; version?: string; language: string; lang_version?: string; remark?: string }) =>
request<Dependency>('/deps', { method: 'POST', body: JSON.stringify(data) }),
delete: (id: string) => request(`/deps/${id}`, { method: 'DELETE' }),
install: (data: any) => request<any>('/deps/install', { method: 'POST', body: JSON.stringify(data) }),
getInstallCmd: (data: any) => request<{ command: string }>('/deps/install-cmd', { method: 'POST', body: JSON.stringify(data) }),
uninstall: (id: string, force?: boolean) => {
const query = force ? '?force=true' : ''
return request<any>(`/deps/uninstall/${id}${query}`, { method: 'POST' })
},
reinstall: (id: string) => request(`/deps/reinstall/${id}`, { method: 'POST' }),
reinstallAll: (language: string, lang_version?: string) => {
const query = new URLSearchParams({ language })
if (lang_version) query.set('lang_version', lang_version)
return request(`/deps/reinstall-all?${query}`, { method: 'POST' })
},
getReinstallAllCmd: (language: string, lang_version?: string) => {
const query = new URLSearchParams({ language })
if (lang_version) query.set('lang_version', lang_version)
return request<{ command: string }>(`/deps/reinstall-all-cmd?${query}`, { method: 'POST' })
},
getBatchInstallCmd: (data: { items: { name: string; version?: string; language: string; lang_version?: string }[] }) =>
request<{ command: string }>('/deps/batch-install-cmd', { method: 'POST', body: JSON.stringify(data) }),
import: (data: { language: string; lang_version?: string; content: string; import_db?: boolean }) =>
request<{ dependencies: Dependency[]; command: string }>('/deps/import', { method: 'POST', body: JSON.stringify(data) }),
getInstalled: (language: string, lang_version?: string) => {
const query = new URLSearchParams({ language })
if (lang_version) query.set('lang_version', lang_version)
return request<Dependency[]>(`/deps/installed?${query}`)
},
getInstallSuggestCmd: (logID: string) => request<{ command: string }>(`/deps/install-suggest-cmd?log_id=${logID}`)
},
agents: {
list: () => request<Agent[]>('/agents'),
getVersion: () => request<{ version: string; platforms: { os: string; arch: string; filename: string }[] }>('/agents/version'),
update: (id: string, data: { name: string; description?: string; enabled: boolean; scheduler_config: SchedulerConfig | null }) =>
request('/agents/' + id, { method: 'PUT', body: JSON.stringify(data) }),
delete: (id: string) => request('/agents/' + id, { method: 'DELETE' }),
forceUpdate: (id: string) => request('/agents/' + id + '/update', { method: 'POST' }),
downloadUrl: (os: string, arch: string) => `${API_BASE_URL}/agent/download?os=${os}&arch=${arch}`,
// 令牌管理
listTokens: () => request<AgentToken[]>('/agents/tokens'),
createToken: (data: { remark?: string; max_uses?: number; expires_at?: string }) =>
request<AgentToken>('/agents/tokens', { method: 'POST', body: JSON.stringify(data) }),
deleteToken: (id: string) => request('/agents/tokens/' + id, { method: 'DELETE' }),
updateToken: (id: string, data: { remark?: string; max_uses?: number; expires_at?: string }) =>
request<AgentToken>('/agents/tokens/' + id, { method: 'PUT', body: JSON.stringify(data) })
},
mise: {
list: () => request<MiseLanguage[]>('/mise/ls'),
sync: () => request<void>('/mise/sync', { method: 'POST' }),
plugins: () => request<string[]>('/mise/plugins'),
versions: (plugin: string) => request<string[]>(`/mise/versions?plugin=${plugin}`),
verifyCommand: (plugin: string, version: string) => request<{ command: string }>(`/mise/verify-cmd?plugin=${plugin}&version=${version}`),
useGlobal: (plugin: string, version: string) => request<void>('/mise/use-global', { method: 'POST', body: JSON.stringify({ plugin, version }) }),
unsetGlobal: (plugin: string, version: string) => request<void>('/mise/unset-global', { method: 'POST', body: JSON.stringify({ plugin, version }) }),
getEnvs: () => request<Record<string, string>>('/mise/envs'),
setEnv: (key: string, value: string) => request<void>('/mise/envs', { method: 'POST', body: JSON.stringify({ key, value }) }),
unsetEnv: (key: string) => request<void>(`/mise/envs?key=${key}`, { method: 'DELETE' })
},
terminal: {
cmds: () => request<{ name: string, description: string }[]>('/terminal/cmds')
},
notify: {
getTypes: () => request<{ channel_types: ChannelType[]; event_types: EventType[] }>('/notify/types'),
getChannels: () => request<NotifyChannel[]>('/notify/channels'),
saveChannel: (data: Partial<NotifyChannel>) =>
request('/notify/channels', { method: 'POST', body: JSON.stringify(data) }),
deleteChannel: (id: string) => request('/notify/channels/' + id, { method: 'DELETE' }),
testChannel: (data: Partial<NotifyChannel>) =>
request<NotifyResult>('/notify/channels/test', { method: 'POST', body: JSON.stringify(data) }),
getBindings: () => request<NotifyBinding[]>('/notify/bindings'),
saveBinding: (data: Partial<NotifyBinding>) =>
request<NotifyBinding>('/notify/bindings', { method: 'POST', body: JSON.stringify(data) }),
saveBindingsBatch: (data: { type: string; data_id: string; bindings: Partial<NotifyBinding>[] }) =>
request('/notify/bindings/batch', { method: 'POST', body: JSON.stringify(data) }),
deleteBinding: (id: string) => request('/notify/bindings/' + id, { method: 'DELETE' }),
send: (data: { channel_id: string; title: string; text: string }) =>
request<NotifyResult>('/notify/send', { method: 'POST', body: JSON.stringify(data) })
},
appLogs: {
list: (params?: { page?: number; page_size?: number; category?: string; status?: string; level?: string; keyword?: string }) => {
const query = new URLSearchParams()
if (params?.page) query.set('page', String(params.page))
if (params?.page_size) query.set('page_size', String(params.page_size))
if (params?.category) query.set('category', params.category)
if (params?.status) query.set('status', params.status)
if (params?.level) query.set('level', params.level)
if (params?.keyword) query.set('keyword', params.keyword)
return request<AppLogListResponse>(`/app-logs?${query}`)
},
markAsRead: (data: { id?: string; category?: string }) => request('/app-logs/read', { method: 'POST', body: JSON.stringify(data) }),
clear: (category: string) => request('/app-logs/clear', { method: 'POST', body: JSON.stringify({ category }) })
},
webui: {
list: () => request<WebUI[]>('/webui'),
upload: async (file: File) => {
const formData = new FormData()
formData.append('file', file)
const res = await fetch(`${API_BASE_URL}/webui/upload`, {
method: 'POST',
credentials: 'include',
body: formData
})
const json: ApiResponse<{ message: string, theme: string }> = await res.json()
if (json.code === 401) {
window.location.href = BASE_URL + '/login'
throw new Error('请先登录')
}
if (json.code !== 200) throw new Error(json.msg || '上传失败')
return json.data
},
setActive: (name: string) => request<{ message: string }>('/webui/active', { method: 'PUT', body: JSON.stringify({ name }) }),
delete: (name: string) => request<{ message: string }>(`/webui/${name}`, { method: 'DELETE' })
},
system: {
export: (data: { task_ids?: string[], env_ids?: string[] }) => request<any>('/system/export', { method: 'POST', body: JSON.stringify(data) })
}
}
export interface WebUI {
name: string
version: string
author: string
description: string
min_panel_version: string
}
export interface FileNode {
name: string
path: string
isDir: boolean
modTime: number
children?: FileNode[]
}
export interface Task {
id: string
name: string
remark: string
command: string
pre_command: string
post_command: string
tags: string
type: string
trigger_type: string
config: string
schedule: string
timeout: number
work_dir: string
clean_config: string
envs: string
retry_count: number
retry_interval: number
random_range: number
pin_type: 'none' | 'top'
languages: { name: string; version: string }[]
agent_id: string | null
enabled: boolean
last_run: string
next_run: string
running_status?: string
repo_task_id?: string
created_at?: string
updated_at?: string
}
export interface RepoConfig {
source_type: string
source_url: string
target_path: string
branch: string
sparse_path: string
single_file: boolean
proxy: string
proxy_url: string
auth_token: string
whitelist_paths?: string
blacklist?: string
dependence?: string
extensions?: string
auto_add_cron?: boolean
commenttotask?: string
concurrency?: number
repo_source?: string
repo_dir_name?: string
}
export interface ExecutionResult {
task_id: string
log_id?: string
success: boolean
status?: string
output?: string
error?: string
duration?: number
exit_code?: number
start_time?: string
end_time?: string
}
export interface TaskListResponse {
data: Task[]
total: number
page: number
page_size: number
}
export interface Script {
id: string
name: string
content: string
}
export interface EnvVar {
id: string
name: string
value: string
remark: string
type: string
hidden: boolean
enabled: boolean
tags: string
created_at?: string
updated_at?: string
}
export interface EnvListResponse {
data: EnvVar[]
total: number
page: number
page_size: number
}
export interface Stats {
tasks: number
today_execs: number
envs: number
logs: number
scheduled: number
running: number
}
export interface TaskLog {
id: string
task_id: string
task_name: string
task_type: string
command: string
status: string
duration: number
error: string | null
start_time: string | null
end_time: string | null
created_at: string
}
export interface LogListResponse {
data: TaskLog[]
total: number
page: number
page_size: number
}
export interface LogDetail {
id: string
task_id: string
command: string
output: string
error: string | null
status: string
duration: number
start_time: string | null
end_time: string | null
created_at: string
}
export interface AboutInfo {
version: string
remote_version?: string
build_time: string
mem_usage: string
goroutines: number
uptime: string
task_count: number
log_count: number
env_count: number
}
export interface SiteSettings {
title: string
subtitle: string
icon: string
page_size: string
cookie_days: string
openapi_enabled?: boolean
openapi_token?: string
openapi_token_expire?: string
system_notice_days?: string
system_notice_max_count?: string
push_log_days?: string
push_log_max_count?: string
login_log_days?: string
login_log_max_count?: string
scheduler_log_days?: string
scheduler_log_max_count?: string
active_webui?: string
}
export interface SchedulerSettings {
worker_count: string
queue_size: string
rate_interval: string
}
export interface LoginLog {
id: string
username: string
ip: string
user_agent: string
status: string
message: string
created_at: string
}
export interface LoginLogListResponse {
data: LoginLog[]
total: number
page: number
page_size: number
}
export interface DailyStats {
day: string
total: number
success: number
failed: number
}
export interface TaskStatsItem {
task_id: string
task_name: string
count: number
}
export interface Dependency {
id: string
name: string
version: string
language: string
lang_version: string
remark: string
log: string
created_at: string
updated_at: string
}
export interface Agent {
id: string
name: string
token: string
machine_id: string
description: string
status: string
last_seen: string
ip: string
version: string
build_time: string
hostname: string
os: string
arch: string
enabled: boolean
scheduler_config: SchedulerConfig | null
created_at: string
updated_at: string
}
export interface SchedulerConfig {
worker_count: number
queue_size: number
rate_interval: number
verbose: boolean
strict_queue: boolean
}
export interface AgentToken {
id: string
token: string
remark: string
max_uses: number
used_count: number
expires_at: string | null
enabled: boolean
created_at: string
}
export interface MiseLanguage {
plugin: string
version: string
source: { type?: string; path?: string } | string
is_global: boolean
install_path?: string
installed_at?: string // 安装日期
}
export interface ChannelType {
type: string
label: string
}
export interface EventType {
type: string
label: string
binding_type?: string
}
export interface NotifyChannel {
id: string
name: string
type: string
enabled: boolean
created_at?: string
config: Record<string, string>
}
export interface NotifyBinding {
id: string
type: string
event: string
way_id: string
data_id: string
extra?: string
created_at?: string
updated_at?: string
}
export interface BindingExtra {
enable_log: boolean
log_limit: number
}
export interface NotifyResult {
success: boolean
error?: string
}
export interface AppLog {
id: string
category: string
title: string
content: string
level: string
status: string
ref_id: string
channel_name?: string
error_msg: string
created_at: string
read_at: string | null
}
export interface AppLogListResponse {
data: AppLog[]
total: number
}
export const LOG_CATEGORY = {
SYSTEM_NOTICE: 'system_notice',
PUSH_LOG: 'push_log',
LOGIN_LOG: 'login_log',
SCHEDULER_LOG: 'scheduler_log'
} as const
export const LOG_LEVEL = {
INFO: 'info',
WARNING: 'warning',
ERROR: 'error'
} as const
export const LOG_STATUS = {
UNREAD: 'unread',
READ: 'read',
SUCCESS: 'success',
FAILED: 'failed'
} as const