From 73b3049e0194f6ffdc8e6264db861d37e7f525db Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Fri, 28 Aug 2026 16:31:43 +0800 Subject: [PATCH 1/5] fix(store-account): export bank account crypto helpers --- furniture-3d-system/src/lib/store-account-crypto.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/furniture-3d-system/src/lib/store-account-crypto.ts b/furniture-3d-system/src/lib/store-account-crypto.ts index 0ed4b07f..2140e047 100644 --- a/furniture-3d-system/src/lib/store-account-crypto.ts +++ b/furniture-3d-system/src/lib/store-account-crypto.ts @@ -30,3 +30,6 @@ export function decryptStoreAccountPassword(value: string) { decipher.setAuthTag(Buffer.from(tagPart, 'base64url')) return Buffer.concat([decipher.update(Buffer.from(dataPart, 'base64url')), decipher.final()]).toString('utf8') } + +export const encryptStoreAccountBankAccount = encryptStoreAccountPassword +export const decryptStoreAccountBankAccount = decryptStoreAccountPassword From e34c80cdd2dc42ad1e8368592156c4a0cee0f217 Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Fri, 28 Aug 2026 17:38:51 +0800 Subject: [PATCH 2/5] feat(supply-chain): add script monitor operations --- .../agents/script-monitor/README.md | 5 + .../agents/script-monitor/agent.mjs | 129 ++++++- .../agents/script-monitor/agent.test.mjs | 79 +++++ .../agents/script-monitor/config.example.json | 2 + .../migration.sql | 32 ++ furniture-3d-system/prisma/schema.prisma | 32 +- .../script-monitor/commands/claim/route.ts | 25 ++ .../script-monitor/scripts/[id]/run/route.ts | 36 ++ .../script-monitor/ScriptMonitorView.test.tsx | 51 ++- .../script-monitor/ScriptMonitorView.tsx | 329 ++++++++++++++---- .../app/supply-chain/script-monitor/page.tsx | 2 + .../src/lib/supply-chain-script-monitor.ts | 31 ++ .../src/server/dto/script-monitor.dto.ts | 2 + .../repositories/script-monitor.repository.ts | 153 ++++++++ .../services/script-monitor.service.test.ts | 70 ++++ .../server/services/script-monitor.service.ts | 84 +++++ 16 files changed, 992 insertions(+), 70 deletions(-) create mode 100644 furniture-3d-system/agents/script-monitor/agent.test.mjs create mode 100644 furniture-3d-system/prisma/migrations/20260828170000_add_script_monitor_manual_commands/migration.sql create mode 100644 furniture-3d-system/src/app/api/internal/script-monitor/commands/claim/route.ts create mode 100644 furniture-3d-system/src/app/api/supply-chain/script-monitor/scripts/[id]/run/route.ts diff --git a/furniture-3d-system/agents/script-monitor/README.md b/furniture-3d-system/agents/script-monitor/README.md index 5ae5ebaf..8d4ee1dc 100644 --- a/furniture-3d-system/agents/script-monitor/README.md +++ b/furniture-3d-system/agents/script-monitor/README.md @@ -8,6 +8,11 @@ 2. 复制本目录到目标机器,把 `config.example.json` 复制为 `config.json`,填入凭据、内外网地址和脚本任务说明。 3. 先运行 `node agent.mjs` 确认上报,再使用 `pm2 start ecosystem.config.cjs` 常驻。 +需要在平台点击“立即运行”的脚本,必须在 `config.json` 对应任务中显式配置 +`"allowManualRun": true`。平台只会下发脚本 `key`,Agent 始终执行本机配置里预存的 +`command`,不会接收平台传来的任意命令。Agent 默认每 5 秒主动领取一次执行请求, +因此内网机器不需要开放入站端口。 + `connectionMode` 支持: - `intranet`:只走内网地址。 diff --git a/furniture-3d-system/agents/script-monitor/agent.mjs b/furniture-3d-system/agents/script-monitor/agent.mjs index e40c0116..e18dc953 100644 --- a/furniture-3d-system/agents/script-monitor/agent.mjs +++ b/furniture-3d-system/agents/script-monitor/agent.mjs @@ -1,5 +1,5 @@ import { createHash } from 'node:crypto' -import { execFileSync } from 'node:child_process' +import { execFileSync, spawn } from 'node:child_process' import { existsSync, readFileSync, statSync } from 'node:fs' import { hostname, platform, release } from 'node:os' import { dirname, resolve } from 'node:path' @@ -26,6 +26,7 @@ export function loadConfig(configPath = process.env.SCRIPT_MONITOR_CONFIG || res } config.reportIntervalSec = Math.max(15, Number(config.reportIntervalSec) || 60) config.requestTimeoutMs = Math.max(1000, Number(config.requestTimeoutMs) || 10000) + config.commandPollIntervalSec = Math.min(60, Math.max(2, Number(config.commandPollIntervalSec) || 5)) config.scripts = Array.isArray(config.scripts) ? config.scripts : [] return config } @@ -120,8 +121,9 @@ function scanPm2() { schedule: null, commandPreview: redactCommand(processInfo.pm2_env?.pm_exec_path || ''), expectedIntervalSec: null, - status, - isEnabled: status !== 'DISABLED', + status, + isEnabled: status !== 'DISABLED', + canManualRun: false, lastStartedAt: processInfo.pm2_env?.pm_uptime ? new Date(processInfo.pm2_env.pm_uptime).toISOString() : null, @@ -152,6 +154,7 @@ function parseCronLine(line) { expectedIntervalSec: null, status: 'UNKNOWN', isEnabled: true, + canManualRun: false, } } @@ -229,6 +232,7 @@ function normalizeConfiguredScript(script) { expectedIntervalSec: script.expectedIntervalSec ? Number(script.expectedIntervalSec) : null, status: result.status, isEnabled: script.enabled !== false, + canManualRun: script.allowManualRun === true && Boolean(script.command), lastResultSummary: result.summary, } } @@ -260,6 +264,110 @@ export async function reportSnapshot(config) { console.log(`[script-monitor] 已通过${result.endpoint.kind === 'INTRANET' ? '内网' : '公网'}上报 ${scripts.length} 个脚本`) } +function keepTail(current, chunk, limit = 12000) { + const next = current + chunk.toString() + return next.length > limit ? next.slice(-limit) : next +} + +async function reportManualRun(config, command, input) { + return postToCenter(config, '/api/internal/script-monitor/run', { + commandId: command.id, + scriptKey: command.scriptKey, + scriptName: command.scriptName, + taskDescription: command.taskDescription || '', + jobType: String(command.jobType || 'OTHER').toUpperCase(), + ...input, + }) +} + +export async function executeManualCommand(config, command) { + const script = config.scripts.find((item) => item.key === command.scriptKey) + const startedAt = new Date() + const externalRunId = `manual:${command.id}` + if (!script?.command || script.allowManualRun !== true) { + const message = 'Agent 配置未开放该脚本的手动运行权限' + await reportManualRun(config, command, { + externalRunId, + status: 'FAILED', + trigger: 'manual', + startedAt: startedAt.toISOString(), + finishedAt: new Date().toISOString(), + durationMs: 0, + exitCode: 1, + resultSummary: message, + errorTail: message, + }) + return + } + + await reportManualRun(config, command, { + externalRunId, + status: 'RUNNING', + trigger: 'manual', + startedAt: startedAt.toISOString(), + }) + let outputTail = '' + let errorTail = '' + try { + const child = spawn(script.command, { + cwd: script.cwd ? resolve(script.cwd) : process.cwd(), + env: process.env, + shell: true, + stdio: ['ignore', 'pipe', 'pipe'], + }) + child.stdout.on('data', (chunk) => { + outputTail = keepTail(outputTail, chunk) + process.stdout.write(chunk) + }) + child.stderr.on('data', (chunk) => { + errorTail = keepTail(errorTail, chunk) + process.stderr.write(chunk) + }) + const exitCode = await new Promise((resolveExit, reject) => { + child.once('error', reject) + child.once('close', (code) => resolveExit(code ?? 1)) + }) + const finishedAt = new Date() + await reportManualRun(config, command, { + externalRunId, + status: exitCode === 0 ? 'SUCCESS' : 'FAILED', + trigger: 'manual', + startedAt: startedAt.toISOString(), + finishedAt: finishedAt.toISOString(), + durationMs: finishedAt.getTime() - startedAt.getTime(), + exitCode, + resultSummary: exitCode === 0 ? '手动运行成功' : `手动运行失败,退出码 ${exitCode}`, + outputTail, + errorTail, + }) + } catch (error) { + const finishedAt = new Date() + await reportManualRun(config, command, { + externalRunId, + status: 'FAILED', + trigger: 'manual', + startedAt: startedAt.toISOString(), + finishedAt: finishedAt.toISOString(), + durationMs: finishedAt.getTime() - startedAt.getTime(), + exitCode: 1, + resultSummary: `手动运行启动失败:${error.message}`, + errorTail: error.message, + }) + } +} + +export async function pollManualCommand(config) { + const result = await postToCenter( + config, + '/api/internal/script-monitor/commands/claim', + {}, + ) + const command = result.body?.data?.command + if (!command) return false + await executeManualCommand(config, command) + return true +} + async function main() { const config = loadConfig() const run = async () => { @@ -269,10 +377,25 @@ async function main() { console.error('[script-monitor] 上报失败:', error.message) } } + let commandBusy = false + const poll = async () => { + if (commandBusy) return + commandBusy = true + try { + await pollManualCommand(config) + } catch (error) { + console.error('[script-monitor] 手动任务轮询失败:', error.message) + } finally { + commandBusy = false + } + } await run() + await poll() const timer = setInterval(run, config.reportIntervalSec * 1000) + const commandTimer = setInterval(poll, config.commandPollIntervalSec * 1000) const stop = () => { clearInterval(timer) + clearInterval(commandTimer) process.exit(0) } process.on('SIGINT', stop) diff --git a/furniture-3d-system/agents/script-monitor/agent.test.mjs b/furniture-3d-system/agents/script-monitor/agent.test.mjs new file mode 100644 index 00000000..9591e837 --- /dev/null +++ b/furniture-3d-system/agents/script-monitor/agent.test.mjs @@ -0,0 +1,79 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { executeManualCommand } from './agent.mjs' + +function config(overrides = {}) { + return { + agentId: 'agent-1', + agentToken: 'secret-token', + connectionMode: 'internet', + centerUrls: { internet: 'https://monitor.example.test' }, + requestTimeoutMs: 5000, + scripts: [{ + key: 'local-check', + name: '本机检查', + taskDescription: '验证手动触发', + jobType: 'MANUAL', + allowManualRun: true, + command: `${JSON.stringify(process.execPath)} -e "console.log('manual-ok')"`, + }], + ...overrides, + } +} + +function command() { + return { + id: 'command-1', + scriptKey: 'local-check', + scriptName: '本机检查', + taskDescription: '验证手动触发', + jobType: 'MANUAL', + } +} + +test('manual command executes only the local configured command and reports its result', async () => { + const previousFetch = globalThis.fetch + const reports = [] + globalThis.fetch = async (_url, init) => { + reports.push(JSON.parse(init.body)) + return new Response(JSON.stringify({ success: true, data: { ok: true } }), { + status: 200, + headers: { 'content-type': 'application/json' }, + }) + } + try { + await executeManualCommand(config(), command()) + } finally { + globalThis.fetch = previousFetch + } + + assert.equal(reports.length, 2) + assert.equal(reports[0].status, 'RUNNING') + assert.equal(reports[1].status, 'SUCCESS') + assert.equal(reports[1].exitCode, 0) + assert.match(reports[1].outputTail, /manual-ok/) + assert.equal(reports[1].commandId, 'command-1') +}) + +test('manual command is rejected when allowManualRun is not explicitly enabled', async () => { + const previousFetch = globalThis.fetch + const reports = [] + globalThis.fetch = async (_url, init) => { + reports.push(JSON.parse(init.body)) + return new Response(JSON.stringify({ success: true, data: { ok: true } }), { + status: 200, + headers: { 'content-type': 'application/json' }, + }) + } + const blockedConfig = config() + blockedConfig.scripts[0].allowManualRun = false + try { + await executeManualCommand(blockedConfig, command()) + } finally { + globalThis.fetch = previousFetch + } + + assert.equal(reports.length, 1) + assert.equal(reports[0].status, 'FAILED') + assert.match(reports[0].resultSummary, /未开放/) +}) diff --git a/furniture-3d-system/agents/script-monitor/config.example.json b/furniture-3d-system/agents/script-monitor/config.example.json index 63cfcc50..f55c0822 100644 --- a/furniture-3d-system/agents/script-monitor/config.example.json +++ b/furniture-3d-system/agents/script-monitor/config.example.json @@ -8,6 +8,7 @@ "internet": "https://cp.jiansun.vip" }, "reportIntervalSec": 60, + "commandPollIntervalSec": 5, "requestTimeoutMs": 10000, "autoDiscover": { "cron": true, @@ -21,6 +22,7 @@ "jobType": "CRON", "schedule": "*/15 * * * *", "expectedIntervalSec": 900, + "allowManualRun": true, "command": "npm run sync:supply-chain-product-archive", "cwd": "/opt/furniture-3d-system-prod/current", "resultCheck": { diff --git a/furniture-3d-system/prisma/migrations/20260828170000_add_script_monitor_manual_commands/migration.sql b/furniture-3d-system/prisma/migrations/20260828170000_add_script_monitor_manual_commands/migration.sql new file mode 100644 index 00000000..72208e72 --- /dev/null +++ b/furniture-3d-system/prisma/migrations/20260828170000_add_script_monitor_manual_commands/migration.sql @@ -0,0 +1,32 @@ +-- CreateEnum +CREATE TYPE "ScriptMonitorCommandStatus" AS ENUM ('PENDING', 'RUNNING', 'SUCCESS', 'FAILED', 'EXPIRED'); + +-- AlterTable +ALTER TABLE "ScriptMonitorScript" +ADD COLUMN "canManualRun" BOOLEAN NOT NULL DEFAULT false; + +-- CreateTable +CREATE TABLE "ScriptMonitorCommand" ( + "id" TEXT NOT NULL, + "agentId" TEXT NOT NULL, + "scriptId" TEXT NOT NULL, + "status" "ScriptMonitorCommandStatus" NOT NULL DEFAULT 'PENDING', + "requestedById" TEXT, + "requestedByName" TEXT, + "requestedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expiresAt" TIMESTAMP(3) NOT NULL, + "claimedAt" TIMESTAMP(3), + "finishedAt" TIMESTAMP(3), + "externalRunId" TEXT, + "errorMessage" TEXT, + + CONSTRAINT "ScriptMonitorCommand_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "ScriptMonitorCommand_agentId_status_requestedAt_idx" ON "ScriptMonitorCommand"("agentId", "status", "requestedAt"); +CREATE INDEX "ScriptMonitorCommand_scriptId_requestedAt_idx" ON "ScriptMonitorCommand"("scriptId", "requestedAt" DESC); + +-- AddForeignKey +ALTER TABLE "ScriptMonitorCommand" ADD CONSTRAINT "ScriptMonitorCommand_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "ScriptMonitorAgent"("id") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "ScriptMonitorCommand" ADD CONSTRAINT "ScriptMonitorCommand_scriptId_fkey" FOREIGN KEY ("scriptId") REFERENCES "ScriptMonitorScript"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/furniture-3d-system/prisma/schema.prisma b/furniture-3d-system/prisma/schema.prisma index f22eb25b..aa8b9b1e 100644 --- a/furniture-3d-system/prisma/schema.prisma +++ b/furniture-3d-system/prisma/schema.prisma @@ -3969,6 +3969,14 @@ enum ScriptMonitorRunStatus { FAILED } +enum ScriptMonitorCommandStatus { + PENDING + RUNNING + SUCCESS + FAILED + EXPIRED +} + model ScriptMonitorAgent { id String @id @default(cuid()) name String @@ -3986,6 +3994,7 @@ model ScriptMonitorAgent { updatedAt DateTime @updatedAt scripts ScriptMonitorScript[] runs ScriptMonitorRun[] + commands ScriptMonitorCommand[] @@index([isActive, lastSeenAt]) } @@ -4003,6 +4012,7 @@ model ScriptMonitorScript { expectedIntervalSec Int? status ScriptMonitorJobStatus @default(UNKNOWN) isEnabled Boolean @default(true) + canManualRun Boolean @default(false) lastStartedAt DateTime? lastFinishedAt DateTime? lastDurationMs Int? @@ -4014,6 +4024,7 @@ model ScriptMonitorScript { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt runs ScriptMonitorRun[] + commands ScriptMonitorCommand[] @@unique([agentId, externalKey]) @@index([agentId, status]) @@ -4042,4 +4053,23 @@ model ScriptMonitorRun { @@index([scriptId, startedAt(sort: Desc)]) @@index([agentId, startedAt(sort: Desc)]) } - + +model ScriptMonitorCommand { + id String @id @default(cuid()) + agentId String + agent ScriptMonitorAgent @relation(fields: [agentId], references: [id], onDelete: Cascade) + scriptId String + script ScriptMonitorScript @relation(fields: [scriptId], references: [id], onDelete: Cascade) + status ScriptMonitorCommandStatus @default(PENDING) + requestedById String? + requestedByName String? + requestedAt DateTime @default(now()) + expiresAt DateTime + claimedAt DateTime? + finishedAt DateTime? + externalRunId String? + errorMessage String? @db.Text + + @@index([agentId, status, requestedAt]) + @@index([scriptId, requestedAt(sort: Desc)]) +} diff --git a/furniture-3d-system/src/app/api/internal/script-monitor/commands/claim/route.ts b/furniture-3d-system/src/app/api/internal/script-monitor/commands/claim/route.ts new file mode 100644 index 00000000..73ffae0e --- /dev/null +++ b/furniture-3d-system/src/app/api/internal/script-monitor/commands/claim/route.ts @@ -0,0 +1,25 @@ +import { NextRequest } from 'next/server' +import { handleRequest, jsonSuccess } from '@/server/core/response' +import { scriptMonitorService } from '@/server/services/script-monitor.service' + +function readAgentCredentials(request: NextRequest) { + const agentId = request.headers.get('x-script-monitor-agent-id')?.trim() ?? '' + const authorization = request.headers.get('authorization') ?? '' + const token = authorization.startsWith('Bearer ') ? authorization.slice(7).trim() : '' + return { agentId, token } +} + +export async function POST(request: NextRequest) { + return handleRequest( + request, + async (ctx) => { + const { agentId, token } = readAgentCredentials(request) + return jsonSuccess( + await scriptMonitorService.claimCommand(agentId, token), + 200, + ctx.requestId, + ) + }, + { requireAuth: false }, + ) +} diff --git a/furniture-3d-system/src/app/api/supply-chain/script-monitor/scripts/[id]/run/route.ts b/furniture-3d-system/src/app/api/supply-chain/script-monitor/scripts/[id]/run/route.ts new file mode 100644 index 00000000..da6d9a62 --- /dev/null +++ b/furniture-3d-system/src/app/api/supply-chain/script-monitor/scripts/[id]/run/route.ts @@ -0,0 +1,36 @@ +import { NextRequest } from 'next/server' +import { AppError, ErrorCode } from '@/server/core/errors' +import { handleRequest, jsonSuccess } from '@/server/core/response' +import { scriptMonitorService } from '@/server/services/script-monitor.service' +import { supplyChainPermissionSettingsService } from '@/server/services/supply-chain-permission-settings.service' + +export async function POST( + request: NextRequest, + context: { params: Promise<{ id: string }> }, +) { + return handleRequest( + request, + async (ctx) => { + const access = await supplyChainPermissionSettingsService.getUserAccess( + ctx.user, + 'scriptMonitor', + ) + if (!access.edit) { + throw new AppError( + ErrorCode.AUTH_PERMISSION_DENIED, + '没有手动运行脚本的权限', + ) + } + const { id } = await context.params + return jsonSuccess( + await scriptMonitorService.requestManualRun(id, { + id: ctx.user.id, + name: ctx.user.name, + }), + 202, + ctx.requestId, + ) + }, + { requireAuth: true }, + ) +} diff --git a/furniture-3d-system/src/app/supply-chain/script-monitor/ScriptMonitorView.test.tsx b/furniture-3d-system/src/app/supply-chain/script-monitor/ScriptMonitorView.test.tsx index 43d88f81..045062df 100644 --- a/furniture-3d-system/src/app/supply-chain/script-monitor/ScriptMonitorView.test.tsx +++ b/furniture-3d-system/src/app/supply-chain/script-monitor/ScriptMonitorView.test.tsx @@ -10,6 +10,7 @@ const overview: ScriptMonitorOverview = { hostname: 'warehouse-01', networkMode: 'AUTO', connectionKind: 'INTRANET', + connectionState: 'ONLINE', online: true, isActive: true, os: 'linux 6.8', @@ -30,6 +31,7 @@ const overview: ScriptMonitorOverview = { expectedIntervalSec: 900, status: 'SUCCESS', isEnabled: true, + canManualRun: true, lastStartedAt: '2026-08-28T11:45:00.000Z', lastFinishedAt: '2026-08-28T11:45:12.000Z', lastDurationMs: 12_000, @@ -44,14 +46,18 @@ const overview: ScriptMonitorOverview = { hostname: 'warehouse-01', networkMode: 'AUTO', connectionKind: 'INTRANET', + connectionState: 'ONLINE', online: true, lastSeenAt: '2026-08-28T11:59:30.000Z', }, latestRun: null, + latestCommand: null, }], metrics: { agentTotal: 1, + agentConnected: 1, agentOnline: 1, + agentPending: 0, scriptTotal: 1, healthy: 1, running: 0, @@ -67,9 +73,11 @@ describe('ScriptMonitorView', () => { expect(screen.getByRole('heading', { name: '脚本监控' })).toBeInTheDocument() expect(screen.getByText('供应链产品档案同步')).toBeInTheDocument() expect(screen.getByText('同步商品、仓库和供应商扩展字段')).toBeInTheDocument() - expect(screen.getAllByText('仓库服务器 01')).toHaveLength(2) + expect(screen.getByText('仓库服务器 01')).toBeInTheDocument() expect(screen.getByText('同步 182 条档案')).toBeInTheDocument() - expect(screen.getByText('内网')).toBeInTheDocument() + expect(screen.getByText('在线 · 内网')).toBeInTheDocument() + expect(screen.getAllByText('最近成功')).toHaveLength(2) + expect(screen.getByRole('button', { name: '立即运行' })).toBeEnabled() }) it('keeps the empty state actionable for editors', () => { @@ -78,7 +86,7 @@ describe('ScriptMonitorView', () => { ...overview, agents: [], scripts: [], - metrics: { agentTotal: 0, agentOnline: 0, scriptTotal: 0, healthy: 0, running: 0, abnormal: 0, unknown: 0 }, + metrics: { agentTotal: 0, agentConnected: 0, agentOnline: 0, agentPending: 0, scriptTotal: 0, healthy: 0, running: 0, abnormal: 0, unknown: 0 }, }} canEdit databaseReady @@ -87,4 +95,41 @@ describe('ScriptMonitorView', () => { expect(screen.getByText('还没有接入机器')).toBeInTheDocument() expect(screen.getByRole('button', { name: /接入第一台机器/ })).toBeInTheDocument() }) + + it('shows a never-connected credential as pending without counting it as connected', () => { + render() + + expect(screen.getAllByText('待接入')).toHaveLength(2) + expect(screen.getByText(/不计入已接入机器/)).toBeInTheDocument() + expect(screen.getByText('共 0 台已接入')).toBeInTheDocument() + }) }) diff --git a/furniture-3d-system/src/app/supply-chain/script-monitor/ScriptMonitorView.tsx b/furniture-3d-system/src/app/supply-chain/script-monitor/ScriptMonitorView.tsx index c79732ac..8023b5f4 100644 --- a/furniture-3d-system/src/app/supply-chain/script-monitor/ScriptMonitorView.tsx +++ b/furniture-3d-system/src/app/supply-chain/script-monitor/ScriptMonitorView.tsx @@ -3,11 +3,12 @@ import { Activity, Check, - CircleCheck, + Clock3, Copy, Globe2, LoaderCircle, Network, + Play, Plus, RefreshCw, Search, @@ -35,12 +36,12 @@ type ApiEnvelope = { } const STATUS_META: Record = { - RUNNING: { label: '运行中', className: 'monitor-status-running' }, - SUCCESS: { label: '正常', className: 'monitor-status-success' }, - FAILED: { label: '失败', className: 'monitor-status-failed' }, + RUNNING: { label: '正在运行', className: 'monitor-status-running' }, + SUCCESS: { label: '最近成功', className: 'monitor-status-success' }, + FAILED: { label: '最近失败', className: 'monitor-status-failed' }, SILENT: { label: '超时未运行', className: 'monitor-status-silent' }, DISABLED: { label: '已停用', className: 'monitor-status-disabled' }, - UNKNOWN: { label: '待确认', className: 'monitor-status-unknown' }, + UNKNOWN: { label: '尚无结果', className: 'monitor-status-unknown' }, } const JOB_TYPE_LABELS = { @@ -103,23 +104,55 @@ function StatusBadge({ status }: { status: ScriptMonitorJobStatus }) { ) } -function ConnectionBadge({ script }: { script: ScriptMonitorScriptRow }) { - if (!script.agent.online) { +function AgentConnectionBadge({ + agent, +}: { + agent: ScriptMonitorOverview['agents'][number] | ScriptMonitorScriptRow['agent'] +}) { + if (agent.connectionState === 'PENDING') { return ( - -