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();