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:
parent
ba5a4f50c5
commit
6a3094ea19
3 changed files with 98 additions and 2 deletions
|
|
@ -450,7 +450,41 @@
|
||||||
</div>
|
</div>
|
||||||
@if (webhookTestResponsePretty) {
|
@if (webhookTestResponsePretty) {
|
||||||
<div class="col-12">
|
<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>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -255,3 +255,28 @@ main
|
||||||
background: var(--bs-tertiary-bg)
|
background: var(--bs-tertiary-bg)
|
||||||
white-space: pre-wrap
|
white-space: pre-wrap
|
||||||
word-break: break-word
|
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)
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,10 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
||||||
webhookTimeoutSec = 20;
|
webhookTimeoutSec = 20;
|
||||||
webhookSettingsStatus = '';
|
webhookSettingsStatus = '';
|
||||||
webhookTestStatus = '';
|
webhookTestStatus = '';
|
||||||
|
webhookTestViewMode: 'pretty' | 'raw' = 'pretty';
|
||||||
|
webhookTestResponseEntries: Array<{ key: string; value: string }> = [];
|
||||||
webhookTestResponsePretty = '';
|
webhookTestResponsePretty = '';
|
||||||
|
webhookTestResponseRaw = '';
|
||||||
settingsModalOpen = false;
|
settingsModalOpen = false;
|
||||||
focusedRefreshMs = 3000;
|
focusedRefreshMs = 3000;
|
||||||
backgroundRefreshMs = 30000;
|
backgroundRefreshMs = 30000;
|
||||||
|
|
@ -371,6 +374,9 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
||||||
|
|
||||||
testWebhookService() {
|
testWebhookService() {
|
||||||
this.webhookTestStatus = 'Testing webhook endpoint...';
|
this.webhookTestStatus = 'Testing webhook endpoint...';
|
||||||
|
this.webhookTestViewMode = 'pretty';
|
||||||
|
this.webhookTestResponseEntries = [];
|
||||||
|
this.webhookTestResponseRaw = '';
|
||||||
this.webhookTestResponsePretty = '';
|
this.webhookTestResponsePretty = '';
|
||||||
this.cdr.markForCheck();
|
this.cdr.markForCheck();
|
||||||
this.downloads.testWebhookSettings({
|
this.downloads.testWebhookSettings({
|
||||||
|
|
@ -385,18 +391,49 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = resp as WebhookTestResponse;
|
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.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();
|
this.cdr.markForCheck();
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
this.webhookTestStatus = 'Webhook test failed.';
|
this.webhookTestStatus = 'Webhook test failed.';
|
||||||
|
this.webhookTestResponseEntries = [];
|
||||||
|
this.webhookTestResponseRaw = '';
|
||||||
this.webhookTestResponsePretty = '';
|
this.webhookTestResponsePretty = '';
|
||||||
this.cdr.markForCheck();
|
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() {
|
ngAfterViewInit() {
|
||||||
this.downloads.queueChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
this.downloads.queueChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||||
this.queueMasterCheckbox()?.selectionChanged();
|
this.queueMasterCheckbox()?.selectionChanged();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue