1
0
forked from wrenn/wrenn

feat: channel audit logging, name cleaning, message formatting, and dashboard UI

- Add audit log entries for channel create, update, rotate_config, delete
- Clean channel names on create/update (trim, lowercase, spaces → hyphens,
  SafeName validation)
- Format chat notifications with full event details (resource, actor, team,
  timestamp) instead of one-liners
- Fix Discord split-line embeds by setting splitLines=No on shoutrrr URL
- Add channels dashboard page and sidebar navigation
This commit is contained in:
2026-04-10 01:17:03 +06:00
parent 84dd15d22b
commit 0f78982186
11 changed files with 1624 additions and 20 deletions

View File

@ -0,0 +1,72 @@
import { apiFetch, type ApiResult } from '$lib/api/client';
export type Channel = {
id: string;
team_id: string;
name: string;
provider: string;
events: string[];
created_at: string;
updated_at: string;
secret?: string; // only present immediately after creation (webhook provider)
};
export const PROVIDERS = [
{ value: 'discord', label: 'Discord', fields: ['webhook_url'] },
{ value: 'slack', label: 'Slack', fields: ['webhook_url'] },
{ value: 'teams', label: 'Teams', fields: ['webhook_url'] },
{ value: 'googlechat', label: 'Google Chat', fields: ['webhook_url'] },
{ value: 'telegram', label: 'Telegram', fields: ['bot_token', 'chat_id'] },
{ value: 'matrix', label: 'Matrix', fields: ['homeserver_url', 'access_token', 'room_id'] },
{ value: 'webhook', label: 'Webhook', fields: ['url'] }
] as const;
export const EVENT_TYPES = [
{ value: 'capsule.created', group: 'Capsule' },
{ value: 'capsule.running', group: 'Capsule' },
{ value: 'capsule.paused', group: 'Capsule' },
{ value: 'capsule.destroyed', group: 'Capsule' },
{ value: 'template.snapshot.created', group: 'Template' },
{ value: 'template.snapshot.deleted', group: 'Template' },
{ value: 'host.up', group: 'Host' },
{ value: 'host.down', group: 'Host' }
] as const;
export async function listChannels(): Promise<ApiResult<Channel[]>> {
return apiFetch('GET', '/api/v1/channels');
}
export async function createChannel(
name: string,
provider: string,
config: Record<string, string>,
events: string[]
): Promise<ApiResult<Channel>> {
return apiFetch('POST', '/api/v1/channels', { name, provider, config, events });
}
export async function updateChannel(
id: string,
name: string,
events: string[]
): Promise<ApiResult<Channel>> {
return apiFetch('PATCH', `/api/v1/channels/${id}`, { name, events });
}
export async function deleteChannel(id: string): Promise<ApiResult<void>> {
return apiFetch('DELETE', `/api/v1/channels/${id}`);
}
export async function rotateConfig(
id: string,
config: Record<string, string>
): Promise<ApiResult<Channel>> {
return apiFetch('PUT', `/api/v1/channels/${id}/config`, { config });
}
export async function testChannel(
provider: string,
config: Record<string, string>
): Promise<ApiResult<{ status: string }>> {
return apiFetch('POST', '/api/v1/channels/test', { provider, config });
}