48 lines
8.7 KiB
TypeScript
48 lines
8.7 KiB
TypeScript
import { http, HttpResponse } from 'msw'
|
|
import type { Admin, Desktop, Terminal, User, Task, Resource, Audit } from '../api'
|
|
const now = () => new Date().toISOString()
|
|
const administrators: Record<string, Admin> = {
|
|
admin: { id: 'mock-admin', username: 'admin', displayName: '系统管理员', role: 'super_admin' },
|
|
operator: { id: 'mock-operator', username: 'operator', displayName: '运维人员', role: 'operator' },
|
|
viewer: { id: 'mock-viewer', username: 'viewer', displayName: '只读人员', role: 'viewer' },
|
|
}
|
|
const desktops: Desktop[] = [
|
|
{ id: 'desk_mock_windows', name: '设计专属桌面', os: 'Windows', node: 'demo-node-a', vmid: 201, pool: '设计专属池', powerState: 'running', serviceState: 'unknown', provider: 'demo', endpoint: '', app: 'Desktop', width: 2560, height: 1440, fps: 30, updatedAt: now() },
|
|
{ id: 'desk_mock_ubuntu', name: '研发办公桌面', os: 'Ubuntu', node: 'demo-node-a', vmid: 202, pool: '研发专属池', powerState: 'running', serviceState: 'unknown', provider: 'demo', endpoint: '', app: 'Desktop', width: 2560, height: 1440, fps: 30, updatedAt: now() },
|
|
{ id: 'desk_mock_kylin', name: '国产办公桌面', os: '麒麟', node: 'demo-node-a', vmid: 203, pool: '通用办公池', powerState: 'stopped', serviceState: 'unknown', provider: 'demo', endpoint: '', app: 'Desktop', width: 2560, height: 1440, fps: 30, updatedAt: now() },
|
|
]
|
|
const terminals: Terminal[] = [{ id: 'term_mock_001', sn: 'DEMO-RK-001', name: '设计工位 01', group: '设计部', status: 'offline', agentVersion: '演示版本', lastSeen: null, sessionDesktopId: null, configVersion: 1, fps: null, rtt: null }]
|
|
const users: User[] = [{ id: 'user_mock_wang', username: 'wang.demo', displayName: '王工', enabled: true, terminalId: 'term_mock_001', desktopIds: ['desk_mock_windows'] }]
|
|
const tasks: Task[] = [], audits: Audit[] = []
|
|
const resources: Resource[] = [{ id: 'demo-node-a', name: 'demo-node-a', status: 'online', cpu: 0.24, memory: 0.48, storage: 0.35, provider: 'demo', updatedAt: now() }]
|
|
const revoked = new Set<string>()
|
|
const keyFor = (r: Request) => r.headers.get('Authorization')?.replace('Bearer mock-', '') || ''
|
|
const admin = (r: Request) => !revoked.has(keyFor(r)) ? administrators[keyFor(r)] : undefined
|
|
const error = (status: number, message: string) => HttpResponse.json({ code: `mock_${status}`, message, requestId: `mock-${crypto.randomUUID()}` }, { status })
|
|
function guard(r: Request, write = false, adminOnly = false) { const a = admin(r); if (!a) return error(401, '登录已失效,请重新登录'); if ((write && a.role === 'viewer') || (adminOnly && a.role !== 'super_admin')) return error(403, '当前账号没有该操作权限') }
|
|
function list<T>(request: Request, items: T[]) { const q = new URL(request.url).searchParams; const page = Math.max(1, Number(q.get('page')) || 1), pageSize = Math.min(100, Math.max(1, Number(q.get('pageSize')) || 20)); return HttpResponse.json({ items: items.slice((page - 1) * pageSize, page * pageSize), page, pageSize, total: items.length, requestId: `mock-${crypto.randomUUID()}` }) }
|
|
function audit(r: Request, action: string, target: string, result = 'success') { audits.unshift({ id: crypto.randomUUID(), actor: admin(r)?.username || '', action, target, result, requestId: crypto.randomUUID(), createdAt: now() }) }
|
|
function submit(request: Request, kind: string, action: string, targetId: string, targetName: string, finish: () => void) {
|
|
if (tasks.some(t => t.targetId === targetId && ['queued', 'running'].includes(t.status))) return error(409, '该对象已有任务执行中')
|
|
const t: Task = { id: `task_mock_${crypto.randomUUID()}`, kind, action, targetId, targetName, actor: admin(request)?.username || '', status: 'queued', message: '模拟任务已提交', providerRef: '', createdAt: now(), updatedAt: now() }
|
|
tasks.unshift(t); audit(request, action, targetName, 'submitted')
|
|
setTimeout(() => { t.status = 'running'; t.message = '模拟执行中'; t.updatedAt = now() }, 400)
|
|
setTimeout(() => { t.status = kind === 'terminal' ? 'failed' : 'success'; t.message = kind === 'terminal' ? '模拟终端离线,未执行' : '模拟任务已完成;没有操作真实资源'; t.updatedAt = now(); if (t.status === 'success') finish(); audit(request, action, targetName, t.status) }, 1500)
|
|
return HttpResponse.json(t, { status: 202 })
|
|
}
|
|
export const handlers = [
|
|
http.post('/api/v1/auth/login', async ({ request }) => { const b = await request.json() as { username: string; password: string }; const a = administrators[b.username]; if (!a || b.password !== 'Demo@2026') return error(401, '用户名或密码错误'); revoked.delete(b.username); audit(new Request(request.url, { headers: { Authorization: `Bearer mock-${b.username}` } }), 'login', b.username); return HttpResponse.json({ accessToken: `mock-${b.username}`, expiresIn: 3600, user: a }) }),
|
|
http.get('/api/v1/auth/me', ({ request }) => guard(request) || HttpResponse.json(admin(request))),
|
|
http.post('/api/v1/auth/logout', ({ request }) => { const e = guard(request); if (e) return e; revoked.add(keyFor(request)); return new HttpResponse(null, { status: 204 }) }),
|
|
http.get('/api/v1/settings', ({ request }) => guard(request) || HttpResponse.json({ mode: 'demo', computeProvider: 'demo', pveConfigured: false, terminalTransport: 'mock', capabilities: ['auth', 'resources', 'desktops', 'terminals', 'bindings', 'tasks', 'audit'] })),
|
|
http.get('/api/v1/overview', ({ request }) => guard(request) || HttpResponse.json({ terminalCount: terminals.length, onlineTerminals: 0, desktopCount: desktops.length, runningDesktops: desktops.filter(d => d.powerState === 'running').length, activeSessions: 0, failedTasks: tasks.filter(t => ['failed', 'timeout'].includes(t.status)).length, mode: 'demo' })),
|
|
...([['desktops', desktops], ['terminals', terminals], ['users', users], ['resources', resources], ['tasks', tasks], ['audit-logs', audits]] as const).map(([path, data]) => http.get(`/api/v1/${path}`, ({ request }) => guard(request) || list(request, data as unknown[]))),
|
|
http.post('/api/v1/desktops/:id/actions', async ({ request, params }) => { const e = guard(request, true); if (e) return e; const d = desktops.find(d => d.id === params.id); if (!d) return error(404, '桌面不存在'); const b = await request.json() as { action: string }; if (!['start', 'shutdown', 'reboot'].includes(b.action)) return error(400, '不支持的操作'); return submit(request, 'desktop', b.action, d.id, d.name, () => { d.powerState = b.action === 'shutdown' ? 'stopped' : 'running'; d.updatedAt = now() }) }),
|
|
http.post('/api/v1/terminals', async ({ request }) => { const e = guard(request, true, true); if (e) return e; const b = await request.json() as { sn: string; name: string; group: string }; if (terminals.some(t => t.sn === b.sn)) return error(409, 'SN 已登记'); const id = `term_mock_${crypto.randomUUID()}`; terminals.push({ ...b, id, status: 'unregistered', agentVersion: '', lastSeen: null, sessionDesktopId: null, configVersion: 1, fps: null, rtt: null }); audit(request, 'enroll_terminal', id); return HttpResponse.json({ id, activationSecret: `mock-only-${crypto.randomUUID()}` }, { status: 201 }) }),
|
|
http.post('/api/v1/terminals/:id/commands', async ({ request, params }) => { const e = guard(request, true); if (e) return e; const t = terminals.find(t => t.id === params.id); if (!t) return error(404, '终端不存在'); const b = await request.json() as { action: string }; return submit(request, 'terminal', b.action, t.id, t.name, () => {}) }),
|
|
http.post('/api/v1/users', async ({ request }) => { const e = guard(request, true, true); if (e) return e; const b = await request.json() as { username: string; displayName: string }; if (users.some(u => u.username === b.username)) return error(409, '账号已存在'); const u: User = { ...b, id: `user_mock_${crypto.randomUUID()}`, enabled: true, terminalId: null, desktopIds: [] }; users.push(u); audit(request, 'create_user', u.id); return HttpResponse.json(u, { status: 201 }) }),
|
|
http.put('/api/v1/users/:id/bindings', async ({ request, params }) => { const e = guard(request, true, true); if (e) return e; const u = users.find(u => u.id === params.id); if (!u) return error(404, '用户不存在'); const b = await request.json() as { terminalId: string | null; desktopIds: string[]; enabled: boolean }; if (b.terminalId && users.some(x => x.id !== u.id && x.terminalId === b.terminalId)) return error(409, '终端已关联其他用户'); Object.assign(u, b); audit(request, 'update_bindings', u.id); return HttpResponse.json(u) }),
|
|
http.get('/api/v1/resources/vms', ({ request }) => guard(request) || error(503, '模拟环境未接入 Proxmox')),
|
|
http.post('/api/v1/resources/sync', ({ request }) => guard(request, true, true) || error(503, '模拟环境未接入 Proxmox')),
|
|
]
|