Dashboards as documents, and messages as something to bind to
A dashboard is its own document rather than widgets placed in a flow. Node-RED's dashboard tab is 260 nodes, about forty of them pure layout, which is exactly what the small-graph principle exists to avoid — and since the graph is already wired by message name, a widget can bind to a name without belonging to any flow. Stored beside the flows in the same repository, sharing their write lock and commit, under a directory the flow listing ignores. No draft/publish split: nothing executes a dashboard, so edit mode is its own staging area. Two things it needs from the engine. A message catalog spanning every flow, because a wall panel shows the heating next to the solar and the flow-scoped API is the wrong shape for that. And a way to put a value in without owning a node — a slider is a real value that happened to come from a person — which runs whatever consumes it and applies the same type check a node's output gets. Only a message some flow declares can be published to; flows own the namespace. Charts also need more past than the 120 points a sparkline wanted, so a chart widget declares its depth and the engine keeps that message's series that deep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011LF61rxW1FG5YCD2J9YqjY
This commit is contained in:
@@ -200,6 +200,113 @@ Binary payloads (tensors, images) will arrive later as explicitly declared
|
||||
codec fields; until then everything on the wire is JSON.`
|
||||
} as const;
|
||||
|
||||
export const DashboardDef_InputSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
title: 'Name'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
default: ''
|
||||
},
|
||||
pages: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/PageDef-Input'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Pages'
|
||||
},
|
||||
version: {
|
||||
type: 'integer',
|
||||
title: 'Version',
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
title: 'DashboardDef',
|
||||
description: 'A dashboard as stored, and as the API hands it over.'
|
||||
} as const;
|
||||
|
||||
export const DashboardDef_OutputSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
title: 'Name'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
default: ''
|
||||
},
|
||||
pages: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/PageDef-Output'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Pages'
|
||||
},
|
||||
version: {
|
||||
type: 'integer',
|
||||
title: 'Version',
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
title: 'DashboardDef',
|
||||
description: 'A dashboard as stored, and as the API hands it over.'
|
||||
} as const;
|
||||
|
||||
export const DashboardSummarySchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
title: 'Name'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
default: ''
|
||||
},
|
||||
page_count: {
|
||||
type: 'integer',
|
||||
title: 'Page Count',
|
||||
default: 0
|
||||
},
|
||||
widget_count: {
|
||||
type: 'integer',
|
||||
title: 'Widget Count',
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
title: 'DashboardSummary',
|
||||
description: 'A dashboard in a list, without its contents.'
|
||||
} as const;
|
||||
|
||||
export const DashboardsPublicSchema = {
|
||||
properties: {
|
||||
data: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/DashboardSummary'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Data'
|
||||
},
|
||||
count: {
|
||||
type: 'integer',
|
||||
title: 'Count'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['data', 'count'],
|
||||
title: 'DashboardsPublic'
|
||||
} as const;
|
||||
|
||||
export const FlowDef_InputSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
@@ -367,7 +474,7 @@ export const FlowStatePublicSchema = {
|
||||
properties: {
|
||||
values: {
|
||||
additionalProperties: {
|
||||
'$ref': '#/components/schemas/MessageValue'
|
||||
'$ref': '#/components/schemas/app__flow__schemas__MessageValue'
|
||||
},
|
||||
type: 'object',
|
||||
title: 'Values'
|
||||
@@ -541,6 +648,85 @@ Only numbers are recorded, so \`\`numeric\`\` tells the panel whether an empty
|
||||
series means "nothing plottable here" or "nothing has arrived yet".`
|
||||
} as const;
|
||||
|
||||
export const MessageInfoSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
title: 'Name'
|
||||
},
|
||||
flow: {
|
||||
type: 'string',
|
||||
title: 'Flow'
|
||||
},
|
||||
dtype: {
|
||||
type: 'string',
|
||||
title: 'Dtype'
|
||||
},
|
||||
providers: {
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Providers',
|
||||
default: []
|
||||
},
|
||||
writable: {
|
||||
type: 'boolean',
|
||||
title: 'Writable',
|
||||
default: true
|
||||
},
|
||||
numeric: {
|
||||
type: 'boolean',
|
||||
title: 'Numeric',
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
title: 'Value'
|
||||
},
|
||||
ts: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
title: 'Ts'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['name', 'flow', 'dtype'],
|
||||
title: 'MessageInfo',
|
||||
description: 'One message, as something to bind a widget to.'
|
||||
} as const;
|
||||
|
||||
export const MessagePointsSchema = {
|
||||
properties: {
|
||||
message: {
|
||||
type: 'string',
|
||||
title: 'Message'
|
||||
},
|
||||
numeric: {
|
||||
type: 'boolean',
|
||||
title: 'Numeric'
|
||||
},
|
||||
points: {
|
||||
items: {
|
||||
additionalProperties: {
|
||||
type: 'number'
|
||||
},
|
||||
type: 'object'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Points'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['message', 'numeric', 'points'],
|
||||
title: 'MessagePoints'
|
||||
} as const;
|
||||
|
||||
export const MessageSpecSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
@@ -588,26 +774,23 @@ export const MessageSpecSchema = {
|
||||
message it also produces without depending on itself.`
|
||||
} as const;
|
||||
|
||||
export const MessageValueSchema = {
|
||||
export const MessagesPublicSchema = {
|
||||
properties: {
|
||||
value: {
|
||||
title: 'Value'
|
||||
data: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/MessageInfo'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Data'
|
||||
},
|
||||
ts: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
title: 'Ts'
|
||||
count: {
|
||||
type: 'integer',
|
||||
title: 'Count'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
title: 'MessageValue',
|
||||
description: 'The last payload seen on a message.'
|
||||
required: ['data', 'count'],
|
||||
title: 'MessagesPublic'
|
||||
} as const;
|
||||
|
||||
export const NewPasswordSchema = {
|
||||
@@ -1006,6 +1189,94 @@ export const OAuthClientRegisterSchema = {
|
||||
description: 'RFC 7591 dynamic client registration request.'
|
||||
} as const;
|
||||
|
||||
export const PageDef_InputSchema = {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: 'Id'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
default: ''
|
||||
},
|
||||
icon: {
|
||||
type: 'string',
|
||||
title: 'Icon',
|
||||
default: ''
|
||||
},
|
||||
sections: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/SectionDef-Input'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Sections'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['id'],
|
||||
title: 'PageDef',
|
||||
description: 'One tab of a dashboard.'
|
||||
} as const;
|
||||
|
||||
export const PageDef_OutputSchema = {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: 'Id'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
default: ''
|
||||
},
|
||||
icon: {
|
||||
type: 'string',
|
||||
title: 'Icon',
|
||||
default: ''
|
||||
},
|
||||
sections: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/SectionDef-Output'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Sections'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['id'],
|
||||
title: 'PageDef',
|
||||
description: 'One tab of a dashboard.'
|
||||
} as const;
|
||||
|
||||
export const PlacementSchema = {
|
||||
properties: {
|
||||
x: {
|
||||
type: 'integer',
|
||||
title: 'X',
|
||||
default: 0
|
||||
},
|
||||
y: {
|
||||
type: 'integer',
|
||||
title: 'Y',
|
||||
default: 0
|
||||
},
|
||||
w: {
|
||||
type: 'integer',
|
||||
title: 'W',
|
||||
default: 3
|
||||
},
|
||||
h: {
|
||||
type: 'integer',
|
||||
title: 'H',
|
||||
default: 2
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
title: 'Placement',
|
||||
description: "Where a widget sits in its section's grid, in grid units."
|
||||
} as const;
|
||||
|
||||
export const PositionSchema = {
|
||||
properties: {
|
||||
x: {
|
||||
@@ -1049,30 +1320,6 @@ export const PrivateUserCreateSchema = {
|
||||
title: 'PrivateUserCreate'
|
||||
} as const;
|
||||
|
||||
export const PublishRequestSchema = {
|
||||
properties: {
|
||||
version: {
|
||||
type: 'integer',
|
||||
title: 'Version'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['version'],
|
||||
title: 'PublishRequest'
|
||||
} as const;
|
||||
|
||||
export const RenameRequestSchema = {
|
||||
properties: {
|
||||
new_name: {
|
||||
type: 'string',
|
||||
title: 'New Name'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['new_name'],
|
||||
title: 'RenameRequest'
|
||||
} as const;
|
||||
|
||||
export const RuleSchema = {
|
||||
properties: {
|
||||
events: {
|
||||
@@ -1144,6 +1391,56 @@ export const SecretValueSchema = {
|
||||
title: 'SecretValue'
|
||||
} as const;
|
||||
|
||||
export const SectionDef_InputSchema = {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: 'Id'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
default: ''
|
||||
},
|
||||
widgets: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/WidgetDef'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Widgets'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['id'],
|
||||
title: 'SectionDef',
|
||||
description: 'A grid of widgets under a heading.'
|
||||
} as const;
|
||||
|
||||
export const SectionDef_OutputSchema = {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: 'Id'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
default: ''
|
||||
},
|
||||
widgets: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/WidgetDef'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Widgets'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['id'],
|
||||
title: 'SectionDef',
|
||||
description: 'A grid of widgets under a heading.'
|
||||
} as const;
|
||||
|
||||
export const ShareRequestSchema = {
|
||||
properties: {
|
||||
lib_name: {
|
||||
@@ -1546,4 +1843,138 @@ export const ValidationResultSchema = {
|
||||
},
|
||||
type: 'object',
|
||||
title: 'ValidationResult'
|
||||
} as const;
|
||||
|
||||
export const WidgetDefSchema = {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: 'Id'
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['stat', 'gauge', 'chart', 'markdown', 'button', 'switch', 'slider', 'input', 'dropdown'],
|
||||
title: 'Type'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
default: ''
|
||||
},
|
||||
layout: {
|
||||
additionalProperties: {
|
||||
'$ref': '#/components/schemas/Placement'
|
||||
},
|
||||
type: 'object',
|
||||
title: 'Layout'
|
||||
},
|
||||
config: {
|
||||
additionalProperties: true,
|
||||
type: 'object',
|
||||
title: 'Config'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['id', 'type'],
|
||||
title: 'WidgetDef',
|
||||
description: `One tile: what it shows or does, and where it sits.
|
||||
|
||||
\`\`config\`\` is per type — a chart names its series, a button names the
|
||||
message it publishes — and is validated against the type below rather than
|
||||
by a schema per class, because the whole set is small and closed.`
|
||||
} as const;
|
||||
|
||||
export const app__api__routes__dashboards__RenameRequestSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
title: 'Name'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
title: 'RenameRequest'
|
||||
} as const;
|
||||
|
||||
export const app__api__routes__flows__PublishRequestSchema = {
|
||||
properties: {
|
||||
version: {
|
||||
type: 'integer',
|
||||
title: 'Version'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['version'],
|
||||
title: 'PublishRequest'
|
||||
} as const;
|
||||
|
||||
export const app__api__routes__flows__RenameRequestSchema = {
|
||||
properties: {
|
||||
new_name: {
|
||||
type: 'string',
|
||||
title: 'New Name'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['new_name'],
|
||||
title: 'RenameRequest'
|
||||
} as const;
|
||||
|
||||
export const app__api__routes__messages__MessageValueSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
title: 'Name'
|
||||
},
|
||||
value: {
|
||||
title: 'Value'
|
||||
},
|
||||
ts: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
title: 'Ts'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['name', 'value'],
|
||||
title: 'MessageValue'
|
||||
} as const;
|
||||
|
||||
export const app__api__routes__messages__PublishRequestSchema = {
|
||||
properties: {
|
||||
value: {
|
||||
title: 'Value'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['value'],
|
||||
title: 'PublishRequest'
|
||||
} as const;
|
||||
|
||||
export const app__flow__schemas__MessageValueSchema = {
|
||||
properties: {
|
||||
value: {
|
||||
title: 'Value'
|
||||
},
|
||||
ts: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
type: 'null'
|
||||
}
|
||||
],
|
||||
title: 'Ts'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
title: 'MessageValue',
|
||||
description: 'The last payload seen on a message.'
|
||||
} as const;
|
||||
Reference in New Issue
Block a user