Add Pretty/Raw toggle for webhook test response display.

Render validation JSON as a borderless key/value table in Pretty mode; keep formatted JSON in Raw mode.

Made-with: Cursor
This commit is contained in:
KennyG 2026-04-02 14:44:13 -04:00
parent ba5a4f50c5
commit 6a3094ea19
3 changed files with 98 additions and 2 deletions

View file

@ -450,7 +450,41 @@
</div>
@if (webhookTestResponsePretty) {
<div class="col-12">
<pre class="webhook-response-block">{{ webhookTestResponsePretty }}</pre>
<div class="webhook-response-view-toggle">
<button
type="button"
class="btn btn-sm"
[class.btn-secondary]="webhookTestViewMode === 'pretty'"
[class.btn-outline-secondary]="webhookTestViewMode !== 'pretty'"
(click)="setWebhookTestViewMode('pretty')">
Pretty
</button>
<button
type="button"
class="btn btn-sm"
[class.btn-secondary]="webhookTestViewMode === 'raw'"
[class.btn-outline-secondary]="webhookTestViewMode !== 'raw'"
(click)="setWebhookTestViewMode('raw')">
Raw
</button>
</div>
@if (webhookTestViewMode === 'pretty') {
<div class="webhook-response-block webhook-response-pretty">
<table class="table table-sm table-borderless mb-0">
<tbody>
@for (entry of webhookTestResponseEntries; track entry.key) {
<tr>
<th scope="row">{{ entry.key }}:</th>
<td>{{ entry.value }}</td>
</tr>
}
</tbody>
</table>
</div>
} @else {
<pre class="webhook-response-block">{{ webhookTestResponseRaw }}</pre>
}
</div>
}
</div>

View file

@ -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)

View file

@ -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<string, unknown>;
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();