feat: global notification policy severity to channels (notif-22)
Singleton notification_policy table, NOTIFY_MIN_SEVERITY/CHANNELS env, central dispatch gate, Settings policy UI, migration 007. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,6 +11,48 @@
|
||||
<p v-else-if="loading">Загрузка…</p>
|
||||
|
||||
<template v-else>
|
||||
<section class="card settings-card settings-policy">
|
||||
<h2>Правило оповещений</h2>
|
||||
<p class="settings-hint settings-hint-top">
|
||||
События и problems с severity <strong>≥</strong> порога уходят в выбранные каналы (если канал включён и
|
||||
настроен).
|
||||
</p>
|
||||
<form class="settings-form" @submit.prevent="savePolicy">
|
||||
<label class="settings-field">
|
||||
Мин. severity
|
||||
<select v-model="policyForm.min_severity">
|
||||
<option value="info">info</option>
|
||||
<option value="warning">warning</option>
|
||||
<option value="high">high</option>
|
||||
<option value="critical">critical</option>
|
||||
</select>
|
||||
</label>
|
||||
<fieldset class="settings-channels">
|
||||
<legend>Каналы</legend>
|
||||
<label class="settings-inline">
|
||||
<input v-model="policyForm.use_telegram" type="checkbox" />
|
||||
Telegram
|
||||
</label>
|
||||
<label class="settings-inline">
|
||||
<input v-model="policyForm.use_webhook" type="checkbox" />
|
||||
Webhook
|
||||
</label>
|
||||
<label class="settings-inline">
|
||||
<input v-model="policyForm.use_email" type="checkbox" />
|
||||
Email
|
||||
</label>
|
||||
</fieldset>
|
||||
<p v-if="policyLoaded" class="settings-meta">
|
||||
Источник: <code>{{ policyLoaded.source }}</code>
|
||||
</p>
|
||||
<div class="settings-actions">
|
||||
<button type="submit" :disabled="savingPolicy">
|
||||
{{ savingPolicy ? "Сохранение…" : "Сохранить правило" }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card settings-card">
|
||||
<h2>Telegram</h2>
|
||||
<form class="settings-form" @submit.prevent="saveTelegram">
|
||||
@@ -18,15 +60,6 @@
|
||||
<input v-model="telegramForm.enabled" type="checkbox" />
|
||||
Включить Telegram
|
||||
</label>
|
||||
<label class="settings-field">
|
||||
Мин. severity
|
||||
<select v-model="telegramForm.min_severity">
|
||||
<option value="info">info</option>
|
||||
<option value="warning">warning</option>
|
||||
<option value="high">high</option>
|
||||
<option value="critical">critical</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="settings-field">
|
||||
Bot token
|
||||
<input
|
||||
@@ -71,15 +104,6 @@
|
||||
<input v-model="webhookForm.enabled" type="checkbox" />
|
||||
Включить webhook
|
||||
</label>
|
||||
<label class="settings-field">
|
||||
Мин. severity
|
||||
<select v-model="webhookForm.min_severity">
|
||||
<option value="info">info</option>
|
||||
<option value="warning">warning</option>
|
||||
<option value="high">high</option>
|
||||
<option value="critical">critical</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="settings-field">
|
||||
URL
|
||||
<input
|
||||
@@ -125,15 +149,6 @@
|
||||
<input v-model="emailForm.enabled" type="checkbox" />
|
||||
Включить email
|
||||
</label>
|
||||
<label class="settings-field">
|
||||
Мин. severity
|
||||
<select v-model="emailForm.min_severity">
|
||||
<option value="info">info</option>
|
||||
<option value="warning">warning</option>
|
||||
<option value="high">high</option>
|
||||
<option value="critical">critical</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="settings-field">
|
||||
SMTP host
|
||||
<input v-model="emailForm.smtp_host" type="text" autocomplete="off" :placeholder="smtpHostPlaceholder" />
|
||||
@@ -227,7 +242,16 @@ interface EmailSettings {
|
||||
source: string;
|
||||
}
|
||||
|
||||
interface NotificationPolicy {
|
||||
min_severity: string;
|
||||
use_telegram: boolean;
|
||||
use_webhook: boolean;
|
||||
use_email: boolean;
|
||||
source: string;
|
||||
}
|
||||
|
||||
const loading = ref(true);
|
||||
const savingPolicy = ref(false);
|
||||
const savingTelegram = ref(false);
|
||||
const savingWebhook = ref(false);
|
||||
const savingEmail = ref(false);
|
||||
@@ -239,17 +263,23 @@ const success = ref("");
|
||||
const telegramLoaded = ref<TelegramSettings | null>(null);
|
||||
const webhookLoaded = ref<WebhookSettings | null>(null);
|
||||
const emailLoaded = ref<EmailSettings | null>(null);
|
||||
const policyLoaded = ref<NotificationPolicy | null>(null);
|
||||
|
||||
const policyForm = reactive({
|
||||
min_severity: "warning",
|
||||
use_telegram: true,
|
||||
use_webhook: false,
|
||||
use_email: false,
|
||||
});
|
||||
|
||||
const telegramForm = reactive({
|
||||
enabled: false,
|
||||
min_severity: "warning",
|
||||
bot_token: "",
|
||||
chat_id: "",
|
||||
});
|
||||
|
||||
const webhookForm = reactive({
|
||||
enabled: false,
|
||||
min_severity: "warning",
|
||||
url: "",
|
||||
secret_header: "",
|
||||
secret: "",
|
||||
@@ -257,7 +287,6 @@ const webhookForm = reactive({
|
||||
|
||||
const emailForm = reactive({
|
||||
enabled: false,
|
||||
min_severity: "warning",
|
||||
smtp_host: "",
|
||||
smtp_port: 587,
|
||||
smtp_user: "",
|
||||
@@ -298,23 +327,28 @@ async function load() {
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const data = await apiFetch<{ telegram: TelegramSettings; webhook: WebhookSettings; email: EmailSettings }>(
|
||||
"/api/v1/settings/notifications",
|
||||
);
|
||||
const data = await apiFetch<{
|
||||
policy: NotificationPolicy;
|
||||
telegram: TelegramSettings;
|
||||
webhook: WebhookSettings;
|
||||
email: EmailSettings;
|
||||
}>("/api/v1/settings/notifications");
|
||||
policyLoaded.value = data.policy;
|
||||
policyForm.min_severity = data.policy.min_severity;
|
||||
policyForm.use_telegram = data.policy.use_telegram;
|
||||
policyForm.use_webhook = data.policy.use_webhook;
|
||||
policyForm.use_email = data.policy.use_email;
|
||||
telegramLoaded.value = data.telegram;
|
||||
webhookLoaded.value = data.webhook;
|
||||
emailLoaded.value = data.email;
|
||||
telegramForm.enabled = data.telegram.enabled;
|
||||
telegramForm.min_severity = data.telegram.min_severity;
|
||||
telegramForm.bot_token = "";
|
||||
telegramForm.chat_id = "";
|
||||
webhookForm.enabled = data.webhook.enabled;
|
||||
webhookForm.min_severity = data.webhook.min_severity;
|
||||
webhookForm.url = "";
|
||||
webhookForm.secret_header = data.webhook.secret_header ?? "";
|
||||
webhookForm.secret = "";
|
||||
emailForm.enabled = data.email.enabled;
|
||||
emailForm.min_severity = data.email.min_severity;
|
||||
emailForm.smtp_host = "";
|
||||
emailForm.smtp_port = data.email.smtp_port || 587;
|
||||
emailForm.smtp_user = data.email.smtp_user ?? "";
|
||||
@@ -330,6 +364,28 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
async function savePolicy() {
|
||||
savingPolicy.value = true;
|
||||
error.value = "";
|
||||
success.value = "";
|
||||
try {
|
||||
policyLoaded.value = await apiFetch<NotificationPolicy>("/api/v1/settings/notifications/policy", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
min_severity: policyForm.min_severity,
|
||||
use_telegram: policyForm.use_telegram,
|
||||
use_webhook: policyForm.use_webhook,
|
||||
use_email: policyForm.use_email,
|
||||
}),
|
||||
});
|
||||
success.value = "Правило оповещений сохранено.";
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Ошибка сохранения";
|
||||
} finally {
|
||||
savingPolicy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTelegram() {
|
||||
savingTelegram.value = true;
|
||||
error.value = "";
|
||||
@@ -337,7 +393,6 @@ async function saveTelegram() {
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
enabled: telegramForm.enabled,
|
||||
min_severity: telegramForm.min_severity,
|
||||
};
|
||||
if (telegramForm.bot_token.trim()) body.bot_token = telegramForm.bot_token.trim();
|
||||
if (telegramForm.chat_id.trim()) body.chat_id = telegramForm.chat_id.trim();
|
||||
@@ -363,7 +418,6 @@ async function saveWebhook() {
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
enabled: webhookForm.enabled,
|
||||
min_severity: webhookForm.min_severity,
|
||||
secret_header: webhookForm.secret_header.trim() || null,
|
||||
};
|
||||
if (webhookForm.url.trim()) body.url = webhookForm.url.trim();
|
||||
@@ -422,7 +476,6 @@ async function saveEmail() {
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
enabled: emailForm.enabled,
|
||||
min_severity: emailForm.min_severity,
|
||||
smtp_port: emailForm.smtp_port,
|
||||
smtp_starttls: emailForm.smtp_starttls,
|
||||
smtp_ssl: emailForm.smtp_ssl,
|
||||
@@ -505,6 +558,30 @@ onMounted(load);
|
||||
max-width: 28rem;
|
||||
}
|
||||
|
||||
.settings-policy {
|
||||
border-color: #3d4f66;
|
||||
}
|
||||
|
||||
.settings-hint-top {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.settings-channels {
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.settings-channels legend {
|
||||
font-size: 0.9rem;
|
||||
color: #9aa4b2;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.settings-inline {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user