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:
2026-08-16 09:20:49 +02:00
co-authored by Claude Fable 5
parent 80acf342f4
commit 895bd89b2b
13 changed files with 1713 additions and 66 deletions
+187 -1
View File
@@ -3,7 +3,7 @@
import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
import type { AlertsReadAlertsConfigResponse, AlertsSaveAlertsConfigData, AlertsSaveAlertsConfigResponse, AlertsTestChannelData, AlertsTestChannelResponse, FlowsReadFlowsResponse, FlowsReadNodeTypesResponse, FlowsReadLibraryResponse, FlowsDeleteSharedNodeData, FlowsDeleteSharedNodeResponse, FlowsReadFlowData, FlowsReadFlowResponse, FlowsSaveFlowData, FlowsSaveFlowResponse, FlowsDeleteFlowData, FlowsDeleteFlowResponse, FlowsPublishFlowData, FlowsPublishFlowResponse, FlowsDiscardDraftData, FlowsDiscardDraftResponse, FlowsRenameFlowData, FlowsRenameFlowResponse, FlowsReadNodeSourceData, FlowsReadNodeSourceResponse, FlowsSaveNodeSourceData, FlowsSaveNodeSourceResponse, FlowsShareNodeData, FlowsShareNodeResponse, FlowsUnshareNodeData, FlowsUnshareNodeResponse, FlowsStartFlowData, FlowsStartFlowResponse, FlowsStopFlowData, FlowsStopFlowResponse, FlowsPauseFlowData, FlowsPauseFlowResponse, FlowsResumeFlowData, FlowsResumeFlowResponse, FlowsValidateFlowData, FlowsValidateFlowResponse, FlowsRunFlowData, FlowsRunFlowResponse, FlowsTriggerNodeData, FlowsTriggerNodeResponse, FlowsReadFlowStateData, FlowsReadFlowStateResponse, FlowsReadMessageHistoryData, FlowsReadMessageHistoryResponse, LoginLoginAccessTokenData, LoginLoginAccessTokenResponse, LoginTestTokenResponse, LoginRecoverPasswordData, LoginRecoverPasswordResponse, LoginResetPasswordData, LoginResetPasswordResponse, LoginRecoverPasswordHtmlContentData, LoginRecoverPasswordHtmlContentResponse, OauthRegisterClientData, OauthRegisterClientResponse, OauthAuthorizeValidateData, OauthAuthorizeValidateResponse, OauthAuthorizeData, OauthAuthorizeResponse, OauthTokenData, OauthTokenResponse, PrivateCreateUserData, PrivateCreateUserResponse, SecretsReadSecretsResponse, SecretsSaveSecretData, SecretsSaveSecretResponse, SecretsDeleteSecretData, SecretsDeleteSecretResponse, UsersReadUsersData, UsersReadUsersResponse, UsersCreateUserData, UsersCreateUserResponse, UsersReadUserMeResponse, UsersDeleteUserMeResponse, UsersUpdateUserMeData, UsersUpdateUserMeResponse, UsersUpdatePasswordMeData, UsersUpdatePasswordMeResponse, UsersRegisterUserData, UsersRegisterUserResponse, UsersReadUserByIdData, UsersReadUserByIdResponse, UsersUpdateUserData, UsersUpdateUserResponse, UsersDeleteUserData, UsersDeleteUserResponse, UtilsTestEmailData, UtilsTestEmailResponse, UtilsHealthCheckResponse, UtilsHealthResponse } from './types.gen';
import type { AlertsReadAlertsConfigResponse, AlertsSaveAlertsConfigData, AlertsSaveAlertsConfigResponse, AlertsTestChannelData, AlertsTestChannelResponse, DashboardsReadDashboardsResponse, DashboardsReadDashboardData, DashboardsReadDashboardResponse, DashboardsCreateDashboardData, DashboardsCreateDashboardResponse, DashboardsSaveDashboardData, DashboardsSaveDashboardResponse, DashboardsDeleteDashboardData, DashboardsDeleteDashboardResponse, DashboardsRenameDashboardData, DashboardsRenameDashboardResponse, FlowsReadFlowsResponse, FlowsReadNodeTypesResponse, FlowsReadLibraryResponse, FlowsDeleteSharedNodeData, FlowsDeleteSharedNodeResponse, FlowsReadFlowData, FlowsReadFlowResponse, FlowsSaveFlowData, FlowsSaveFlowResponse, FlowsDeleteFlowData, FlowsDeleteFlowResponse, FlowsPublishFlowData, FlowsPublishFlowResponse, FlowsDiscardDraftData, FlowsDiscardDraftResponse, FlowsRenameFlowData, FlowsRenameFlowResponse, FlowsReadNodeSourceData, FlowsReadNodeSourceResponse, FlowsSaveNodeSourceData, FlowsSaveNodeSourceResponse, FlowsShareNodeData, FlowsShareNodeResponse, FlowsUnshareNodeData, FlowsUnshareNodeResponse, FlowsStartFlowData, FlowsStartFlowResponse, FlowsStopFlowData, FlowsStopFlowResponse, FlowsPauseFlowData, FlowsPauseFlowResponse, FlowsResumeFlowData, FlowsResumeFlowResponse, FlowsValidateFlowData, FlowsValidateFlowResponse, FlowsRunFlowData, FlowsRunFlowResponse, FlowsTriggerNodeData, FlowsTriggerNodeResponse, FlowsReadFlowStateData, FlowsReadFlowStateResponse, FlowsReadMessageHistoryData, FlowsReadMessageHistoryResponse, LoginLoginAccessTokenData, LoginLoginAccessTokenResponse, LoginTestTokenResponse, LoginRecoverPasswordData, LoginRecoverPasswordResponse, LoginResetPasswordData, LoginResetPasswordResponse, LoginRecoverPasswordHtmlContentData, LoginRecoverPasswordHtmlContentResponse, MessagesReadMessagesResponse, MessagesPublishMessageData, MessagesPublishMessageResponse, MessagesReadMessageHistoryData, MessagesReadMessageHistoryResponse, OauthRegisterClientData, OauthRegisterClientResponse, OauthAuthorizeValidateData, OauthAuthorizeValidateResponse, OauthAuthorizeData, OauthAuthorizeResponse, OauthTokenData, OauthTokenResponse, PrivateCreateUserData, PrivateCreateUserResponse, SecretsReadSecretsResponse, SecretsSaveSecretData, SecretsSaveSecretResponse, SecretsDeleteSecretData, SecretsDeleteSecretResponse, UsersReadUsersData, UsersReadUsersResponse, UsersCreateUserData, UsersCreateUserResponse, UsersReadUserMeResponse, UsersDeleteUserMeResponse, UsersUpdateUserMeData, UsersUpdateUserMeResponse, UsersUpdatePasswordMeData, UsersUpdatePasswordMeResponse, UsersRegisterUserData, UsersRegisterUserResponse, UsersReadUserByIdData, UsersReadUserByIdResponse, UsersUpdateUserData, UsersUpdateUserResponse, UsersDeleteUserData, UsersDeleteUserResponse, UtilsTestEmailData, UtilsTestEmailResponse, UtilsHealthCheckResponse, UtilsHealthResponse } from './types.gen';
export class AlertsService {
/**
@@ -61,6 +61,129 @@ export class AlertsService {
}
}
export class DashboardsService {
/**
* Read Dashboards
* Every dashboard, without its contents.
* @returns DashboardsPublic Successful Response
* @throws ApiError
*/
public static readDashboards(): CancelablePromise<DashboardsReadDashboardsResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/dashboards/'
});
}
/**
* Read Dashboard
* @param data The data for the request.
* @param data.name
* @returns DashboardDef_Output Successful Response
* @throws ApiError
*/
public static readDashboard(data: DashboardsReadDashboardData): CancelablePromise<DashboardsReadDashboardResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/dashboards/{name}',
path: {
name: data.name
},
errors: {
422: 'Validation Error'
}
});
}
/**
* Create Dashboard
* Start a dashboard: one page, one section, nothing on it yet.
* @param data The data for the request.
* @param data.name
* @returns DashboardDef_Output Successful Response
* @throws ApiError
*/
public static createDashboard(data: DashboardsCreateDashboardData): CancelablePromise<DashboardsCreateDashboardResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/dashboards/{name}',
path: {
name: data.name
},
errors: {
422: 'Validation Error'
}
});
}
/**
* Save Dashboard
* Replace a dashboard, refusing a save someone else has moved past.
* @param data The data for the request.
* @param data.name
* @param data.requestBody
* @returns DashboardDef_Output Successful Response
* @throws ApiError
*/
public static saveDashboard(data: DashboardsSaveDashboardData): CancelablePromise<DashboardsSaveDashboardResponse> {
return __request(OpenAPI, {
method: 'PUT',
url: '/api/v1/dashboards/{name}',
path: {
name: data.name
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Delete Dashboard
* @param data The data for the request.
* @param data.name
* @returns Message Successful Response
* @throws ApiError
*/
public static deleteDashboard(data: DashboardsDeleteDashboardData): CancelablePromise<DashboardsDeleteDashboardResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/api/v1/dashboards/{name}',
path: {
name: data.name
},
errors: {
422: 'Validation Error'
}
});
}
/**
* Rename Dashboard
* @param data The data for the request.
* @param data.name
* @param data.requestBody
* @returns DashboardDef_Output Successful Response
* @throws ApiError
*/
public static renameDashboard(data: DashboardsRenameDashboardData): CancelablePromise<DashboardsRenameDashboardResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/dashboards/{name}/rename',
path: {
name: data.name
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
}
export class FlowsService {
/**
* Read Flows
@@ -671,6 +794,69 @@ export class LoginService {
}
}
export class MessagesService {
/**
* Read Messages
* Every message any published flow declares, with its last value.
* @returns MessagesPublic Successful Response
* @throws ApiError
*/
public static readMessages(): CancelablePromise<MessagesReadMessagesResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/messages/'
});
}
/**
* Publish Message
* Put a value into the graph, as a dashboard control does.
*
* Only a message some flow declares can be published to: flows own the
* namespace, and a dashboard is a client of it rather than a second author.
* @param data The data for the request.
* @param data.name
* @param data.requestBody
* @returns app__api__routes__messages__MessageValue Successful Response
* @throws ApiError
*/
public static publishMessage(data: MessagesPublishMessageData): CancelablePromise<MessagesPublishMessageResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/messages/{name}',
path: {
name: data.name
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Read Message History
* The series behind a chart. Numbers only — nothing else plots.
* @param data The data for the request.
* @param data.name
* @returns MessagePoints Successful Response
* @throws ApiError
*/
public static readMessageHistory(data: MessagesReadMessageHistoryData): CancelablePromise<MessagesReadMessageHistoryResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/messages/{name}/history',
path: {
name: data.name
},
errors: {
422: 'Validation Error'
}
});
}
}
export class OauthService {
/**
* Register Client