Tell someone when the engine breaks
Everything that goes wrong already travelled the event bus, but the only subscriber was the editor's websocket — so a flow quarantined at three in the morning was invisible until someone opened the browser. An alert manager now watches the same bus and forwards failures to ntfy, email or a webhook. Most of what it does is decline to send: the same node failing every second is one alert with a count of what followed, a connection flapping up and down is muted until it settles, and nothing gets past ten notifications an hour. Verified against a live instance — six identical failures produced one alert carrying the real traceback message. Channels and rules are configured through the API, with a test send so a channel can be proven before anything depends on it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011LF61rxW1FG5YCD2J9YqjY
This commit is contained in:
@@ -1,5 +1,32 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
export const AlertsConfigSchema = {
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
title: 'Enabled',
|
||||
default: true
|
||||
},
|
||||
channels: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/Channel'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Channels'
|
||||
},
|
||||
rules: {
|
||||
items: {
|
||||
'$ref': '#/components/schemas/Rule'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Rules'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
title: 'AlertsConfig',
|
||||
description: 'The whole alerting setup, as stored and as the API sees it.'
|
||||
} as const;
|
||||
|
||||
export const Body_login_login_access_tokenSchema = {
|
||||
properties: {
|
||||
grant_type: {
|
||||
@@ -135,6 +162,34 @@ export const Body_oauth_tokenSchema = {
|
||||
title: 'Body_oauth-token'
|
||||
} as const;
|
||||
|
||||
export const ChannelSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
title: 'Name'
|
||||
},
|
||||
kind: {
|
||||
type: 'string',
|
||||
enum: ['ntfy', 'smtp', 'webhook'],
|
||||
title: 'Kind'
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
title: 'Enabled',
|
||||
default: true
|
||||
},
|
||||
config: {
|
||||
additionalProperties: true,
|
||||
type: 'object',
|
||||
title: 'Config'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
required: ['name', 'kind'],
|
||||
title: 'Channel',
|
||||
description: 'Somewhere to send an alert.'
|
||||
} as const;
|
||||
|
||||
export const DTypeSchema = {
|
||||
type: 'string',
|
||||
enum: ['float', 'int', 'str', 'bool', 'json'],
|
||||
@@ -1013,6 +1068,33 @@ export const RenameRequestSchema = {
|
||||
title: 'RenameRequest'
|
||||
} as const;
|
||||
|
||||
export const RuleSchema = {
|
||||
properties: {
|
||||
events: {
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Events'
|
||||
},
|
||||
channels: {
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Channels'
|
||||
},
|
||||
cooldown_s: {
|
||||
type: 'number',
|
||||
title: 'Cooldown S',
|
||||
default: 900
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
title: 'Rule',
|
||||
description: 'Which events go to which channels.'
|
||||
} as const;
|
||||
|
||||
export const RunRequestSchema = {
|
||||
properties: {
|
||||
inputs: {
|
||||
|
||||
@@ -3,7 +3,63 @@
|
||||
import type { CancelablePromise } from './core/CancelablePromise';
|
||||
import { OpenAPI } from './core/OpenAPI';
|
||||
import { request as __request } from './core/request';
|
||||
import type { 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, 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';
|
||||
|
||||
export class AlertsService {
|
||||
/**
|
||||
* Read Alerts Config
|
||||
* What the engine alerts on, and where it sends it.
|
||||
* @returns AlertsConfig Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static readAlertsConfig(): CancelablePromise<AlertsReadAlertsConfigResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/alerts/config'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Alerts Config
|
||||
* Replace the configuration. Takes effect immediately.
|
||||
* @param data The data for the request.
|
||||
* @param data.requestBody
|
||||
* @returns AlertsConfig Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static saveAlertsConfig(data: AlertsSaveAlertsConfigData): CancelablePromise<AlertsSaveAlertsConfigResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'PUT',
|
||||
url: '/api/v1/alerts/config',
|
||||
body: data.requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
422: 'Validation Error'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Channel
|
||||
* Send one alert, to prove the channel works before relying on it.
|
||||
* @param data The data for the request.
|
||||
* @param data.channelName
|
||||
* @returns Message Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static testChannel(data: AlertsTestChannelData): CancelablePromise<AlertsTestChannelResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/api/v1/alerts/test/{channel_name}',
|
||||
path: {
|
||||
channel_name: data.channelName
|
||||
},
|
||||
errors: {
|
||||
422: 'Validation Error'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FlowsService {
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
/**
|
||||
* The whole alerting setup, as stored and as the API sees it.
|
||||
*/
|
||||
export type AlertsConfig = {
|
||||
enabled?: boolean;
|
||||
channels?: Array<Channel>;
|
||||
rules?: Array<Rule>;
|
||||
};
|
||||
|
||||
export type Body_login_login_access_token = {
|
||||
grant_type?: (string | null);
|
||||
username: string;
|
||||
@@ -19,6 +28,20 @@ export type Body_oauth_token = {
|
||||
resource?: (string | null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Somewhere to send an alert.
|
||||
*/
|
||||
export type Channel = {
|
||||
name: string;
|
||||
kind: 'ntfy' | 'smtp' | 'webhook';
|
||||
enabled?: boolean;
|
||||
config?: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export type kind = 'ntfy' | 'smtp' | 'webhook';
|
||||
|
||||
/**
|
||||
* Serializable payload types.
|
||||
*
|
||||
@@ -301,6 +324,15 @@ export type RenameRequest = {
|
||||
new_name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Which events go to which channels.
|
||||
*/
|
||||
export type Rule = {
|
||||
events?: Array<(string)>;
|
||||
channels?: Array<(string)>;
|
||||
cooldown_s?: number;
|
||||
};
|
||||
|
||||
export type RunRequest = {
|
||||
inputs?: {
|
||||
[key: string]: unknown;
|
||||
@@ -402,6 +434,20 @@ export type ValidationResult = {
|
||||
issues?: Array<ValidationIssue>;
|
||||
};
|
||||
|
||||
export type AlertsReadAlertsConfigResponse = (AlertsConfig);
|
||||
|
||||
export type AlertsSaveAlertsConfigData = {
|
||||
requestBody: AlertsConfig;
|
||||
};
|
||||
|
||||
export type AlertsSaveAlertsConfigResponse = (AlertsConfig);
|
||||
|
||||
export type AlertsTestChannelData = {
|
||||
channelName: string;
|
||||
};
|
||||
|
||||
export type AlertsTestChannelResponse = (Message);
|
||||
|
||||
export type FlowsReadFlowsResponse = (FlowsPublic);
|
||||
|
||||
export type FlowsReadNodeTypesResponse = (Array<NodeTypeInfo>);
|
||||
|
||||
Reference in New Issue
Block a user