feat: adds shortcut for creating alerts (#4411)

This commit is contained in:
Amir Raminfar
2026-02-07 08:16:12 -08:00
committed by GitHub
parent 9ac5fcc2c4
commit 40f335265f
22 changed files with 66 additions and 11 deletions
+1
View File
@@ -80,6 +80,7 @@ declare module 'vue' {
'MaterialSymbols:eyeTracking': typeof import('~icons/material-symbols/eye-tracking')['default']
'MaterialSymbols:link': typeof import('~icons/material-symbols/link')['default']
'MaterialSymbols:logout': typeof import('~icons/material-symbols/logout')['default']
'MaterialSymbols:notificationsAdd': typeof import('~icons/material-symbols/notifications-add')['default']
'MaterialSymbols:person': typeof import('~icons/material-symbols/person')['default']
'MaterialSymbols:terminal': typeof import('~icons/material-symbols/terminal')['default']
'MaterialSymbolsLight:collapseAll': typeof import('~icons/material-symbols-light/collapse-all')['default']
@@ -73,6 +73,12 @@
{{ $t("action.show-details") }}
</a>
</li>
<li>
<a @click="createAlert()">
<mdi:bell />
{{ $t("action.create-alert") }}
</a>
</li>
</ul>
</div>
</template>
@@ -82,6 +88,7 @@ import stripAnsi from "strip-ansi";
import { Container } from "@/models/Container";
import { LogEntry, SimpleLogEntry, ComplexLogEntry, GroupedLogEntry, JSONObject } from "@/models/LogEntry";
import LogDetails from "./LogDetails.vue";
import AlertForm from "@/components/Notification/AlertForm.vue";
const { logEntry, container } = defineProps<{
logEntry: LogEntry<string | JSONObject>;
@@ -147,6 +154,22 @@ async function copyPermalink() {
}
}
function createAlert() {
const containerExpr = `name contains "${container.name}"`;
let logExpr = "";
if (logEntry.level && logEntry.level !== "unknown") {
logExpr = `level == "${logEntry.level}"`;
}
const nameParts = [container.name];
if (logEntry.level && logEntry.level !== "unknown") {
nameParts.push(logEntry.level);
}
const name = nameParts.join(" ");
showDrawer(AlertForm, { prefill: { name, containerExpression: containerExpr, logExpression: logExpr } }, "lg");
}
function hideMenu(e: MouseEvent) {
if (e.target instanceof HTMLAnchorElement) {
setTimeout(() => {
@@ -21,6 +21,9 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
<path fill="currentColor" d="M11 17H7q-2.075 0-3.537-1.463T2 12t1.463-3.537T7 7h4v2H7q-1.25 0-2.125.875T4 12t.875 2.125T7 15h4zm-3-4v-2h8v2zm5 4v-2h4q1.25 0 2.125-.875T20 12t-.875-2.125T17 9h-4V7h4q2.075 0 3.538 1.463T22 12t-1.463 3.538T17 17z"></path>
</svg> action.copy-link</a></li>
<!--v-if-->
<li><a><svg viewBox="0 0 24 24" width="1.2em" height="1.2em">
<path fill="currentColor" d="M21 19v1H3v-1l2-2v-6c0-3.1 2.03-5.83 5-6.71V4a2 2 0 0 1 2-2a2 2 0 0 1 2 2v.29c2.97.88 5 3.61 5 6.71v6zm-7 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2"></path>
</svg> action.create-alert</a></li>
</ul>
</div>
<!--v-if-->
@@ -59,6 +62,9 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
<path fill="currentColor" d="M11 17H7q-2.075 0-3.537-1.463T2 12t1.463-3.537T7 7h4v2H7q-1.25 0-2.125.875T4 12t.875 2.125T7 15h4zm-3-4v-2h8v2zm5 4v-2h4q1.25 0 2.125-.875T20 12t-.875-2.125T17 9h-4V7h4q2.075 0 3.538 1.463T22 12t-1.463 3.538T17 17z"></path>
</svg> action.copy-link</a></li>
<!--v-if-->
<li><a><svg viewBox="0 0 24 24" width="1.2em" height="1.2em">
<path fill="currentColor" d="M21 19v1H3v-1l2-2v-6c0-3.1 2.03-5.83 5-6.71V4a2 2 0 0 1 2-2a2 2 0 0 1 2 2v.29c2.97.88 5 3.61 5 6.71v6zm-7 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2"></path>
</svg> action.create-alert</a></li>
</ul>
</div>
<!--v-if-->
@@ -97,6 +103,9 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
<path fill="currentColor" d="M11 17H7q-2.075 0-3.537-1.463T2 12t1.463-3.537T7 7h4v2H7q-1.25 0-2.125.875T4 12t.875 2.125T7 15h4zm-3-4v-2h8v2zm5 4v-2h4q1.25 0 2.125-.875T20 12t-.875-2.125T17 9h-4V7h4q2.075 0 3.538 1.463T22 12t-1.463 3.538T17 17z"></path>
</svg> action.copy-link</a></li>
<!--v-if-->
<li><a><svg viewBox="0 0 24 24" width="1.2em" height="1.2em">
<path fill="currentColor" d="M21 19v1H3v-1l2-2v-6c0-3.1 2.03-5.83 5-6.71V4a2 2 0 0 1 2-2a2 2 0 0 1 2 2v.29c2.97.88 5 3.61 5 6.71v6zm-7 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2"></path>
</svg> action.create-alert</a></li>
</ul>
</div>
<!--v-if-->
+15 -10
View File
@@ -147,10 +147,11 @@ import { createExprEditor, createContainerHints, createLogHints } from "@/compos
import type { Dispatcher, NotificationRule, PreviewResult } from "@/types/notifications";
const { close, onCreated, alert } = defineProps<{
const { close, onCreated, alert, prefill } = defineProps<{
close?: () => void;
onCreated?: () => void;
alert?: NotificationRule;
prefill?: { name?: string; containerExpression?: string; logExpression?: string };
}>();
// Fetch dispatchers
@@ -177,9 +178,9 @@ const destinationDropdown = ref<HTMLDetailsElement>();
// Form state
const isEditing = computed(() => !!alert);
const alertName = ref(alert?.name ?? "");
const containerExpression = ref(alert?.containerExpression ?? "");
const logExpression = ref(alert?.logExpression ?? "");
const alertName = ref(alert?.name ?? prefill?.name ?? "");
const containerExpression = ref(alert?.containerExpression ?? prefill?.containerExpression ?? "");
const logExpression = ref(alert?.logExpression ?? prefill?.logExpression ?? "");
const dispatcherId = ref(alert?.dispatcher?.id ?? 0);
const selectedDestination = computed(() => destinations.value.find((d) => d.id === dispatcherId.value));
useFocus(alertNameInput, { initialValue: true });
@@ -301,10 +302,14 @@ async function validateExpressions() {
const debouncedValidate = useDebounceFn(validateExpressions, 500);
watch([containerExpression, logExpression], () => {
isLoading.value = true;
debouncedValidate();
});
watch(
[containerExpression, logExpression],
() => {
isLoading.value = true;
debouncedValidate();
},
{ immediate: true },
);
let containerEditorView: Awaited<ReturnType<typeof createExprEditor>> | undefined;
let logEditorView: Awaited<ReturnType<typeof createExprEditor>> | undefined;
@@ -314,7 +319,7 @@ onMounted(async () => {
containerEditorView = await createExprEditor({
parent: containerEditorRef.value,
placeholder: 'name contains "api"',
initialValue: alert?.containerExpression ?? "",
initialValue: alert?.containerExpression ?? prefill?.containerExpression ?? "",
getHints: () => createContainerHints(containerNames.value, imageNames.value, hostNames.value),
onChange: (v) => (containerExpression.value = v),
});
@@ -324,7 +329,7 @@ onMounted(async () => {
logEditorView = await createExprEditor({
parent: logEditorRef.value,
placeholder: 'level == "error" && message contains "timeout"',
initialValue: alert?.logExpression ?? "",
initialValue: alert?.logExpression ?? prefill?.logExpression ?? "",
getHints: createLogHints,
onChange: (v) => (logExpression.value = v),
});
+1 -1
View File
@@ -491,7 +491,7 @@ func (h *handler) previewExpression(w http.ResponseWriter, r *http.Request) {
continue
}
from := time.Now().Add(-5 * time.Minute)
from := time.Now().Add(-2 * time.Hour)
to := time.Now()
logChan, err := containerService.LogsBetweenDates(ctx, from, to, container.STDALL)
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Kopier permalink
see-in-context: Se i kontekst
show-details: Vis detaljer
create-alert: Opret advarsel
label:
containers: Containere
container: Ingen containere | 1 container | {count} containere
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Permalink kopieren
see-in-context: Im Kontext anzeigen
show-details: Details anzeigen
create-alert: Alarm erstellen
label:
containers: Container
container: Keine Container | 1 Container | {count} Container
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Copy permalink
see-in-context: See in context
show-details: Show details
create-alert: Create alert
label:
containers: Containers
container: No containers | 1 container | {count} containers
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Copiar enlace permanente
see-in-context: Ver en contexto
show-details: Mostrar detalles
create-alert: Crear alerta
label:
containers: Contenedores
container: No contenedores | 1 contenedor | {count} contenedores
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Copier le permalien
see-in-context: Voir dans le contexte
show-details: Afficher les détails
create-alert: Créer une alerte
label:
containers: Conteneurs
container: Pas de conteneur | 1 conteneur | {count} conteneurs
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Salin permalink
see-in-context: Lihat dalam konteks
show-details: Tampilkan detail
create-alert: Buat peringatan
label:
containers: Kontainer
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Copia permalink
see-in-context: Vedi nel contesto
show-details: Mostra dettagli
create-alert: Crea avviso
label:
containers: Container
container: Nessun container | 1 container | {count} container
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: 영구 링크 복사
see-in-context: 컨텍스트에서 보기
show-details: 세부 정보 표시
create-alert: 알림 생성
label:
containers: 컨테이너
container: 컨테이너가 없습니다 | 컨테이너 1개 | 컨테이너 {count}개
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Permalink kopiëren
see-in-context: Bekijk in context
show-details: Toon details
create-alert: Waarschuwing aanmaken
label:
containers: Containers
container: Geen containers | 1 container | {count} containers
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Skopiuj permalink
see-in-context: Zobacz w kontekście
show-details: Pokaż szczegóły
create-alert: Utwórz alert
label:
service: Brak usług | 1 usługa | {count} usług
host-count: Brak Hostów | 1 Host | {count} Hostów
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Copiar hiperligação permanente
see-in-context: Ver no contexto
show-details: Mostrar detalhes
create-alert: Criar alerta
label:
containers: Contentores
container: Nenhum contentor | 1 contentor | {count} contentores
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Copiar permalink
see-in-context: Ver no contexto
show-details: Mostrar detalhes
create-alert: Criar alerta
label:
containers: Contêineres
container: Nenhum container | 1 container | {count} containers
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Копировать постоянную ссылку
see-in-context: Посмотреть в контексте
show-details: Показать детали
create-alert: Создать оповещение
label:
containers: Контейнеры
container: Нет контейнеров | 1 контейнер | {count} контейнеров
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Kopiraj trajno povezavo
see-in-context: Glej v kontekstu
show-details: Prikaži podrobnosti
create-alert: Ustvari opozorilo
label:
containers: Zabojniki
host-count: Ni Gostiteljev | 1 Gostitelj | {count} Gostiteljev
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: Kalıcı bağlantıyı kopyala
see-in-context: Bağlamda gör
show-details: Detayları göster
create-alert: Uyarı oluştur
label:
containers: Konteynerlar
container: Konteyner yok | 1 konteyner | {count} konteyner
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: 複製永久連結
see-in-context: 在內容中查看
show-details: 顯示詳細資訊
create-alert: 建立警報
label:
containers: 容器
container: 無容器 | 1 個容器 | {count} 個容器
+1
View File
@@ -17,6 +17,7 @@ action:
copy-link: 复制永久链接
see-in-context: 在上下文中查看
show-details: 显示详细信息
create-alert: 创建告警
label:
containers: 容器
container: 无容器 | 1 容器 | {count} 容器