diff --git a/package-lock.json b/package-lock.json index 6cbd24f..4135234 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ros2_medkit_web_ui", - "version": "0.1.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ros2_medkit_web_ui", - "version": "0.1.0", + "version": "0.5.0", "license": "Apache-2.0", "dependencies": { "@radix-ui/react-collapsible": "^1.1.12", @@ -15,7 +15,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tooltip": "^1.2.8", - "@selfpatch/ros2-medkit-client-ts": "^0.1.1", + "@selfpatch/ros2-medkit-client-ts": "^0.5.0", "@tailwindcss/vite": "^4.1.14", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -3249,9 +3249,9 @@ ] }, "node_modules/@selfpatch/ros2-medkit-client-ts": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@selfpatch/ros2-medkit-client-ts/-/ros2-medkit-client-ts-0.1.1.tgz", - "integrity": "sha512-xM4OKvMQN738EEj7ROK+yBKWQwmAb6QZKoT8bDIj/JqaSyAnZPRrpLgsHDryysIzCnDsZkHMCbHINSJ8S1Nc8w==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@selfpatch/ros2-medkit-client-ts/-/ros2-medkit-client-ts-0.5.0.tgz", + "integrity": "sha512-q2G+8E3JEvtxm5cZ1nSIucySKCCVoojBin75r3QkBbyGk3Sn7ulCMrgTKFsSb6ST2C6A1q19ZOeWk0Ltf6zsVQ==", "license": "Apache-2.0", "engines": { "node": ">=18" diff --git a/package.json b/package.json index e61c4bc..5e5f1fa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ros2_medkit_web_ui", "private": true, - "version": "0.1.0", + "version": "0.5.0", "type": "module", "description": "Simple web UI for browsing SOVD entity trees via discovery endpoints", "repository": { @@ -37,7 +37,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tooltip": "^1.2.8", - "@selfpatch/ros2-medkit-client-ts": "^0.1.1", + "@selfpatch/ros2-medkit-client-ts": "^0.5.0", "@tailwindcss/vite": "^4.1.14", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src/components/TopicPublishForm.tsx b/src/components/TopicPublishForm.tsx index ce2b97c..3072737 100644 --- a/src/components/TopicPublishForm.tsx +++ b/src/components/TopicPublishForm.tsx @@ -169,7 +169,8 @@ export function TopicPublishForm({ setIsPublishing(true); try { await publishToEntityData(entityType, entityId, topicName, { - value: { type: messageType, data: dataToPublish }, + type: messageType, + data: dataToPublish, }); toast.success(`Published to ${topic.topic}`); } catch (error) { diff --git a/src/lib/api-dispatch.test.ts b/src/lib/api-dispatch.test.ts index f4323f4..37fbdd6 100644 --- a/src/lib/api-dispatch.test.ts +++ b/src/lib/api-dispatch.test.ts @@ -189,7 +189,7 @@ describe('putEntityDataItem', () => { }); it.each(ENTITY_TYPES)('calls PUT /%s/{id}/data/{data_id} for "%s"', async (entityType) => { - const body = { value: 42 }; + const body = { type: 'std_msgs/msg/Int32', data: 42 }; // eslint-disable-next-line @typescript-eslint/no-explicit-any await putEntityDataItem(client as any, entityType, 'my-entity', 'temp-sensor', body); expectPut(client, `/${entityType}/`, { [ID_PARAM_MAP[entityType]]: 'my-entity', data_id: 'temp-sensor' }, body); diff --git a/src/lib/api-dispatch.ts b/src/lib/api-dispatch.ts index 9199493..fd560ba 100644 --- a/src/lib/api-dispatch.ts +++ b/src/lib/api-dispatch.ts @@ -232,7 +232,7 @@ export function putEntityDataItem( entityType: SovdResourceEntityType, entityId: string, dataId: string, - body: { value: unknown } + body: { type: string; data: unknown } ) { switch (entityType) { case 'apps': @@ -549,24 +549,33 @@ export function getEntityLogs( if (params.severity) query.severity = params.severity; if (params.context) query.context = params.context; + // The 0.5.0 spec omits the /logs `severity`/`context` query params even though + // the gateway reads them at runtime; see selfpatch/ros2_medkit#416. Each call below + // passes the query with a suppressed type error until the client is regenerated from + // the fixed spec; the suppression is self-removing, as it errors once the params are + // typed. switch (entityType) { case 'apps': return client.GET('/apps/{app_id}/logs', { + // @ts-expect-error query params missing from the 0.5.0 spec (#416) params: { path: { app_id: entityId }, query }, signal, }); case 'components': return client.GET('/components/{component_id}/logs', { + // @ts-expect-error query params missing from the 0.5.0 spec (#416) params: { path: { component_id: entityId }, query }, signal, }); case 'areas': return client.GET('/areas/{area_id}/logs', { + // @ts-expect-error query params missing from the 0.5.0 spec (#416) params: { path: { area_id: entityId }, query }, signal, }); case 'functions': return client.GET('/functions/{function_id}/logs', { + // @ts-expect-error query params missing from the 0.5.0 spec (#416) params: { path: { function_id: entityId }, query }, signal, }); diff --git a/src/lib/log-types.ts b/src/lib/log-types.ts index 4c84b55..38b4e27 100644 --- a/src/lib/log-types.ts +++ b/src/lib/log-types.ts @@ -13,49 +13,36 @@ // limitations under the License. /** - * Local TypeScript interfaces mirroring the gateway /logs JSON shape. + * Log types for the gateway /logs API, refined from the generated 0.5.0 schema. * - * These are defined explicitly rather than derived from the generated - * `components['schemas']` re-export because the published - * @selfpatch/ros2-medkit-client-ts@0.1.1 package is missing - * `generated/schema.js`, which silently degrades those generated types - * to `any` (masked by `skipLibCheck: true`). + * The OpenAPI schema types logs more loosely than the gateway actually emits: + * `severity` is a bare `string`, and `context`/configuration fields are + * nullable and optional. The types below are anchored to + * `components['schemas']` (so schema drift surfaces at compile time) and + * tightened to the runtime guarantees the UI relies on: a known severity set, + * an always-present logger context, and required configuration fields. * - * Reference: gateway `log_manager.cpp::entry_to_json` for the source of truth. + * Source of truth: gateway `log_manager.cpp::entry_to_json`. */ +import type { components } from '@selfpatch/ros2-medkit-client-ts'; + +type Schemas = components['schemas']; + +/** Severity levels the gateway emits (the schema types this as a bare `string`). */ export type LogSeverity = 'debug' | 'info' | 'warning' | 'error' | 'fatal'; -export interface LogContext { - /** Logger FQN without leading slash, e.g. "powertrain/engine/temp_sensor" */ - node: string; - function?: string; - file?: string; - line?: number; -} +/** Logger context; the gateway always populates `node`. */ +export type LogContext = Schemas['LogContext']; -export interface LogEntry { - /** Server-assigned monotonic ID, e.g. "log_123" */ - id: string; - /** ISO 8601 UTC with nanosecond precision */ - timestamp: string; +/** A single log entry, with `severity` narrowed and `context` always present. */ +export type LogEntry = Omit & { severity: LogSeverity; - message: string; context: LogContext; -} +}; -export interface XMedkitAggregation { - entity_id?: string; - aggregation_level?: 'function' | 'area'; - aggregated?: boolean; - aggregation_sources?: string[]; - /** Function-level aggregation: number of hosted apps contributing logs */ - host_count?: number; - /** Area-level aggregation: number of components in the area */ - component_count?: number; - /** Area-level aggregation: number of apps aggregated across all components */ - app_count?: number; -} +/** Aggregation provenance attached to multi-source log collections. */ +export type XMedkitAggregation = NonNullable; export interface LogCollection { items: LogEntry[]; @@ -75,6 +62,10 @@ export interface LogsFetchResult { errorStatus?: number; } +/** + * Log configuration. The schema marks both fields optional and nullable; the + * UI treats a configured severity filter and entry cap as required. + */ export interface LogsConfiguration { severity_filter: LogSeverity; max_entries: number; diff --git a/src/lib/store.ts b/src/lib/store.ts index 6453263..8031ea4 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -201,7 +201,7 @@ export interface AppState { entityType: SovdResourceEntityType, entityId: string, dataId: string, - request: { value: unknown } + request: { type: string; data: unknown } ) => Promise; getServerCapabilities: () => Promise; getVersionInfoAction: () => Promise; @@ -1508,7 +1508,7 @@ export const useAppStore = create()( return true; } else if ((result as { status?: string }).status === 'success') { // Legacy format fallback - const legacyResult = result as { parameter: { value: unknown } }; + const legacyResult = result as unknown as { parameter: { value: unknown } }; const newConfigs = new Map(configurations); const params = newConfigs.get(entityId) || []; const updatedParams = params.map((p) => @@ -1893,7 +1893,15 @@ export const useAppStore = create()( try { const { data: faultsData, error: faultsError } = await client.GET('/faults', { - params: { query: { status: 'all' } }, + params: { + // The 0.5.0 spec omits the /faults `status` query param even though + // the gateway reads it; see selfpatch/ros2_medkit#416. `status=all` + // is required to include cleared/healed faults (no param returns only + // active). Remove this cast once the client is regenerated from the + // fixed spec. + // @ts-expect-error query param missing from the 0.5.0 spec + query: { status: 'all' }, + }, }); if (faultsError) throw new Error(faultsError.message || 'Failed to load faults'); const result = transformFaultsResponse(faultsData); @@ -2147,7 +2155,7 @@ export const useAppStore = create()( entityType: SovdResourceEntityType, entityId: string, dataId: string, - request: { value: unknown } + request: { type: string; data: unknown } ) => { const { client } = get(); if (!client) return;