feat: agent version tracking, update button, device test, COM port list
- Migration 043: agent_version in workstations, netup device type - agent-ws: sendCommandAndWait for async commands, save version on hello - workstations route: list-ports, test-device, update, update-all endpoints - agent-release route: GET /api/agents/latest-release reads latest.yml - app.ts: serve /agent-updates/ static files - api.ts: fix camelCase→snake_case for outgoing requests (fixes null:null) - api.ts: new methods testDevice, sendUpdate, updateAll, listPorts - EquipmentPage: version badge, update button, test (zap) button per device - EquipmentPage: COM-port dropdown loaded from agent when online Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,10 @@ function toCamel(s: string): string {
|
||||
return s.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase())
|
||||
}
|
||||
|
||||
function toSnake(s: string): string {
|
||||
return s.replace(/[A-Z]/g, c => '_' + c.toLowerCase())
|
||||
}
|
||||
|
||||
function transformKeys(val: unknown): unknown {
|
||||
if (Array.isArray(val)) return val.map(transformKeys)
|
||||
if (val && typeof val === 'object' && !(val instanceof Date)) {
|
||||
@@ -55,6 +59,18 @@ function transformKeys(val: unknown): unknown {
|
||||
return val
|
||||
}
|
||||
|
||||
function transformKeysToSnake(val: unknown): unknown {
|
||||
if (Array.isArray(val)) return val.map(transformKeysToSnake)
|
||||
if (val && typeof val === 'object' && !(val instanceof Date)) {
|
||||
const result: Record<string, unknown> = {}
|
||||
for (const [k, v] of Object.entries(val as Record<string, unknown>)) {
|
||||
result[toSnake(k)] = transformKeysToSnake(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// ── Core request ─────────────────────────────────────────────────────────────
|
||||
|
||||
async function req<T>(
|
||||
@@ -72,7 +88,7 @@ async function req<T>(
|
||||
method,
|
||||
headers: t ? { ...headers, Authorization: `Bearer ${t}` } : headers,
|
||||
credentials: 'include',
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
body: body !== undefined ? JSON.stringify(transformKeysToSnake(body)) : undefined,
|
||||
})
|
||||
|
||||
let res = await doFetch(token)
|
||||
@@ -335,6 +351,19 @@ export const api = {
|
||||
req<WorkstationDevice>('PATCH', `/api/hotels/${slug}/workstations/${id}/devices/${deviceId}`, data),
|
||||
removeDevice: (slug: string, id: string, deviceId: string) =>
|
||||
req<void>('DELETE', `/api/hotels/${slug}/workstations/${id}/devices/${deviceId}`),
|
||||
testDevice: (slug: string, wsId: string, deviceId: string) =>
|
||||
req<{ ok: boolean; error?: string; message?: string }>('POST', `/api/hotels/${slug}/workstations/${wsId}/test-device/${deviceId}`),
|
||||
sendUpdate: (slug: string, wsId: string) =>
|
||||
req<{ ok: true }>('POST', `/api/hotels/${slug}/workstations/${wsId}/update`),
|
||||
updateAll: (slug: string) =>
|
||||
req<{ ok: true; sent: number }>('POST', `/api/hotels/${slug}/workstations/update-all`),
|
||||
listPorts: (slug: string, wsId: string) =>
|
||||
req<{ ports: { port: string; description?: string }[] }>('GET', `/api/hotels/${slug}/workstations/${wsId}/ports`),
|
||||
},
|
||||
|
||||
// ── Agents ────────────────────────────────────────────────────────────────
|
||||
agents: {
|
||||
getLatestRelease: () => req<{ version: string; fileName: string; downloadUrl: string }>('GET', '/api/agents/latest-release'),
|
||||
},
|
||||
|
||||
// ── Hotels ────────────────────────────────────────────────────────────────
|
||||
@@ -970,7 +999,7 @@ export interface ChatMessage {
|
||||
export interface WorkstationDevice {
|
||||
id: string
|
||||
workstationId: string
|
||||
type: 'kkt' | 'printer'
|
||||
type: 'kkt' | 'printer' | 'netup'
|
||||
name: string
|
||||
connection: 'usb' | 'network' | 'com'
|
||||
networkHost?: string
|
||||
@@ -989,6 +1018,7 @@ export interface Workstation {
|
||||
ipAddress: string | null
|
||||
isOnline: boolean
|
||||
lastSeen: string | null
|
||||
agentVersion?: string
|
||||
createdAt: string
|
||||
devices: WorkstationDevice[] | null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user