diff --git a/ui/src/app/app.html b/ui/src/app/app.html index f62bfcb..ebb01fc 100644 --- a/ui/src/app/app.html +++ b/ui/src/app/app.html @@ -450,7 +450,41 @@ @if (webhookTestResponsePretty) {
-
{{ webhookTestResponsePretty }}
+
+ + +
+ + @if (webhookTestViewMode === 'pretty') { +
+ + + @for (entry of webhookTestResponseEntries; track entry.key) { + + + + + } + +
{{ entry.key }}:{{ entry.value }}
+
+ } @else { +
{{ webhookTestResponseRaw }}
+ }
} diff --git a/ui/src/app/app.sass b/ui/src/app/app.sass index b868682..daa73b1 100644 --- a/ui/src/app/app.sass +++ b/ui/src/app/app.sass @@ -255,3 +255,28 @@ main background: var(--bs-tertiary-bg) white-space: pre-wrap word-break: break-word + +.webhook-response-view-toggle + display: inline-flex + gap: 0.35rem + margin-top: 0.25rem + +.webhook-response-pretty + table + margin: 0 + + th, + td + padding: 0 + border: 0 + vertical-align: top + line-height: 1.5 + + th + width: 170px + font-weight: 700 + color: var(--bs-emphasis-color) + + td + font-weight: 400 + color: var(--bs-emphasis-color) diff --git a/ui/src/app/app.ts b/ui/src/app/app.ts index d2bb814..898d560 100644 --- a/ui/src/app/app.ts +++ b/ui/src/app/app.ts @@ -112,7 +112,10 @@ export class App implements AfterViewInit, OnInit, OnDestroy { webhookTimeoutSec = 20; webhookSettingsStatus = ''; webhookTestStatus = ''; + webhookTestViewMode: 'pretty' | 'raw' = 'pretty'; + webhookTestResponseEntries: Array<{ key: string; value: string }> = []; webhookTestResponsePretty = ''; + webhookTestResponseRaw = ''; settingsModalOpen = false; focusedRefreshMs = 3000; backgroundRefreshMs = 30000; @@ -371,6 +374,9 @@ export class App implements AfterViewInit, OnInit, OnDestroy { testWebhookService() { this.webhookTestStatus = 'Testing webhook endpoint...'; + this.webhookTestViewMode = 'pretty'; + this.webhookTestResponseEntries = []; + this.webhookTestResponseRaw = ''; this.webhookTestResponsePretty = ''; this.cdr.markForCheck(); this.downloads.testWebhookSettings({ @@ -385,18 +391,49 @@ export class App implements AfterViewInit, OnInit, OnDestroy { return; } const result = resp as WebhookTestResponse; + const display = result.response ?? result; this.webhookTestStatus = result.message || (result.status === 'ok' ? 'Webhook test succeeded.' : 'Webhook test completed with errors.'); - this.webhookTestResponsePretty = JSON.stringify(result.response ?? result, null, 2); + this.webhookTestResponseRaw = JSON.stringify(display, null, 2); + this.webhookTestResponsePretty = this.webhookTestResponseRaw; + this.webhookTestResponseEntries = this.toWebhookResponseEntries(display); this.cdr.markForCheck(); }, error: () => { this.webhookTestStatus = 'Webhook test failed.'; + this.webhookTestResponseEntries = []; + this.webhookTestResponseRaw = ''; this.webhookTestResponsePretty = ''; this.cdr.markForCheck(); }, }); } + setWebhookTestViewMode(mode: 'pretty' | 'raw') { + this.webhookTestViewMode = mode; + } + + private toWebhookResponseEntries(value: unknown): Array<{ key: string; value: string }> { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return [{ key: 'value', value: this.stringifyWebhookValue(value) }]; + } + const obj = value as Record; + return Object.entries(obj).map(([key, entryValue]) => ({ + key, + value: this.stringifyWebhookValue(entryValue), + })); + } + + private stringifyWebhookValue(value: unknown): string { + if (value === null || value === undefined) return ''; + if (typeof value === 'string') return value; + if (typeof value === 'number' || typeof value === 'boolean') return String(value); + try { + return JSON.stringify(value); + } catch { + return String(value); + } + } + ngAfterViewInit() { this.downloads.queueChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => { this.queueMasterCheckbox()?.selectionChanged();