Files
MixedAssets/src/components/WebhookSettings.vue

163 lines
5.3 KiB
Vue

<template>
<v-card class="span-12 panel-pad">
<h2 class="section-title mb-1">Alert webhook</h2>
<p class="quiet text-body-2 mb-3">
Send every newly raised alert to an external HTTP endpoint as a JSON object (one POST per alert).
Delivery runs on the alert cycle alongside (or instead of) email. Leave the secret blank to keep the stored value.
</p>
<div class="form-grid">
<v-switch v-model="form.webhook_enabled" label="Enable webhook delivery" color="primary" density="compact" hide-details />
<div />
<v-text-field v-model="form.webhook_url" class="full" label="Webhook URL" placeholder="https://example.com/hooks/deprecore" />
<v-text-field
v-model="form.webhook_secret"
type="password"
:label="settings.webhook_secret_set ? 'Signing secret (unchanged)' : 'Signing secret (optional)'"
hint="If set, each request is signed with HMAC-SHA256 in the X-DepreCore-Signature header"
persistent-hint
autocomplete="new-password"
/>
<div class="d-flex align-center">
<v-btn
v-if="settings.webhook_secret_set"
variant="text"
size="small"
color="error"
prepend-icon="mdi-key-remove"
@click="clearSecret"
>Clear secret</v-btn>
</div>
</div>
<div class="toolbar-row mt-3">
<v-btn variant="tonal" prepend-icon="mdi-webhook" :loading="testing" :disabled="!form.webhook_url" @click="sendTest">Send test event</v-btn>
<v-spacer />
<v-btn color="primary" prepend-icon="mdi-content-save" :loading="saving" @click="save">Save webhook</v-btn>
</div>
<v-alert v-if="status" class="mt-3" density="compact" :type="statusType">{{ status }}</v-alert>
<v-divider class="my-4" />
<div class="quiet text-caption mb-1">Example payload delivered per alert:</div>
<pre class="payload-sample">{{ samplePayload }}</pre>
</v-card>
</template>
<script>
import { apiRequest } from '../services/api';
export default {
props: {
token: { type: String, required: true }
},
data() {
return {
settings: {},
form: {
webhook_enabled: false,
webhook_url: '',
webhook_secret: ''
},
saving: false,
testing: false,
status: '',
statusType: 'success',
samplePayload: JSON.stringify({
event: 'alert',
id: 142,
type: 'pm_overdue',
severity: 'critical',
title: 'PM overdue: Lift quarterly service',
message: 'Lift quarterly service on FA-1042 was due 2026-05-20.',
due_date: '2026-05-20',
status: 'open',
reference_type: 'asset_pm',
reference_id: 88,
asset_id: 1042,
asset_code: 'FA-1042',
created_at: '2026-06-04T12:00:00.000Z',
sent_at: '2026-06-04T12:05:00.000Z'
}, null, 2)
};
},
mounted() {
this.load();
},
methods: {
async api(path, options = {}) {
return apiRequest(path, this.token, options);
},
async load() {
const data = await (await this.api('/api/notifications/settings')).json();
this.settings = data.settings;
this.form = {
webhook_enabled: data.settings.webhook_enabled,
webhook_url: data.settings.webhook_url,
webhook_secret: ''
};
},
async save() {
this.saving = true;
this.status = '';
try {
const payload = {
webhook_enabled: this.form.webhook_enabled,
webhook_url: this.form.webhook_url
};
if (this.form.webhook_secret) payload.webhook_secret = this.form.webhook_secret;
const data = await (await this.api('/api/notifications/settings', { method: 'PUT', body: JSON.stringify(payload) })).json();
this.settings = data.settings;
this.form.webhook_secret = '';
this.statusType = 'success';
this.status = 'Webhook settings saved.';
} catch (error) {
this.statusType = 'error';
this.status = error.message;
} finally {
this.saving = false;
}
},
async clearSecret() {
this.status = '';
try {
const data = await (await this.api('/api/notifications/settings', { method: 'PUT', body: JSON.stringify({ webhook_secret_clear: true }) })).json();
this.settings = data.settings;
this.form.webhook_secret = '';
this.statusType = 'success';
this.status = 'Signing secret cleared.';
} catch (error) {
this.statusType = 'error';
this.status = error.message;
}
},
async sendTest() {
this.testing = true;
this.status = '';
try {
// Persist the current URL/secret first so the test hits what the user typed.
await this.save();
const result = await (await this.api('/api/notifications/webhook-test', { method: 'POST', body: JSON.stringify({}) })).json();
this.statusType = 'success';
this.status = `Test event delivered (HTTP ${result.status}).`;
} catch (error) {
this.statusType = 'error';
this.status = `Test failed: ${error.message}`;
} finally {
this.testing = false;
}
}
}
};
</script>
<style scoped>
.payload-sample {
margin: 0;
padding: 12px 14px;
border-radius: 8px;
background: rgba(127, 127, 127, 0.1);
font-size: 0.78rem;
line-height: 1.45;
overflow-x: auto;
white-space: pre;
}
</style>