Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion src/components/TopicPublishForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/api-dispatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 10 additions & 1 deletion src/lib/api-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down Expand Up @@ -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,
});
Expand Down
57 changes: 24 additions & 33 deletions src/lib/log-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Schemas['LogEntry'], 'severity' | 'context'> & {
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<Schemas['LogEntryList']['x-medkit']>;

export interface LogCollection {
items: LogEntry[];
Expand All @@ -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;
Expand Down
16 changes: 12 additions & 4 deletions src/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export interface AppState {
entityType: SovdResourceEntityType,
entityId: string,
dataId: string,
request: { value: unknown }
request: { type: string; data: unknown }
) => Promise<void>;
getServerCapabilities: () => Promise<unknown>;
getVersionInfoAction: () => Promise<VersionInfo | null>;
Expand Down Expand Up @@ -1508,7 +1508,7 @@ export const useAppStore = create<AppState>()(
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) =>
Expand Down Expand Up @@ -1893,7 +1893,15 @@ export const useAppStore = create<AppState>()(

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);
Expand Down Expand Up @@ -2147,7 +2155,7 @@ export const useAppStore = create<AppState>()(
entityType: SovdResourceEntityType,
entityId: string,
dataId: string,
request: { value: unknown }
request: { type: string; data: unknown }
) => {
const { client } = get();
if (!client) return;
Expand Down
Loading