mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
feat(notifications): ntfy/Gotify push channels with live delivery (#69)
Per-user notification_channels (ntfy + Gotify): every
insertUserNotification now fans out best-effort to enabled channels —
deployment/backup/security/agent alerts reach the user's phone via the
ntfy app with zero extra infrastructure.
- POST/GET/DELETE /notifications/channels + POST .../:id/test which
pushes a real probe so misconfiguration surfaces before alerts depend
on it (502 DELIVERY_FAILED on endpoint errors).
- ntfy: POST to endpoint (topic in path), Title/Tags headers, optional
Bearer token. Gotify: POST {endpoint}/message?token=...
- Delivery is best-effort by design — a dead relay never breaks the
producing flow; one shared 5s-timeout client.
- CLI `notifications channels list|add|test|remove`, MCP tools,
OpenAPI paths + NotificationChannel schema.
- Web settings "Push notifications" section and a Push channels card on
the mobile notifications tab (add/test/remove).
Verified live: channel CRUD, test-ping hit a recording endpoint with
correct headers/body, and a real deployment fanned out "Deployment
succeeded" through the channel end-to-end.
Generated with [Devin](https://devin.ai)
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
60145f7b21
commit
9243d020cc
19 files changed
+1261
-6
No files matched your search
@@ -2596,6 +2596,42 @@ export async function markAllNotificationsRead(): Promise<void> {
|
||||
await requestJson('/notifications/read-all', { method: 'POST' });
|
||||
}
|
||||
|
||||
export type NotificationChannel = {
|
||||
id: string;
|
||||
kind: 'ntfy' | 'gotify';
|
||||
endpoint: string;
|
||||
enabled: boolean;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export async function listNotificationChannels(): Promise<NotificationChannel[]> {
|
||||
const payload = await requestJson<{ channels?: NotificationChannel[] }>('/notifications/channels');
|
||||
return payload.channels ?? [];
|
||||
}
|
||||
|
||||
export async function createNotificationChannel(input: {
|
||||
kind: 'ntfy' | 'gotify';
|
||||
endpoint: string;
|
||||
token?: string;
|
||||
}): Promise<NotificationChannel> {
|
||||
const payload = await requestJson<{ channel: NotificationChannel }>('/notifications/channels', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
return payload.channel;
|
||||
}
|
||||
|
||||
export async function deleteNotificationChannel(id: string): Promise<void> {
|
||||
await requestJson(`/notifications/channels/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
export async function testNotificationChannel(id: string): Promise<void> {
|
||||
await requestJson(`/notifications/channels/${encodeURIComponent(id)}/test`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
}
|
||||
|
||||
export type PreviewEnvironmentEntity = components['schemas']['PreviewEnvironment'];
|
||||
|
||||
export async function listPreviewEnvironments(projectId: string): Promise<PreviewEnvironmentEntity[]> {
|
||||
|
||||
Reference in new issue
Block a user