mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
feat: remove legacy alert rules, superseded by automations
Alert rules already auto-migrate into automations on boot and the old engine stands down after. Deletes the rule/firing tables, routes, UI, and AI tools; keeps notification channels since automations still use them. Also fixes automations' notify step, which still called the deleted repository.
This commit is contained in:
1 parent
e0f14f2ecf
commit
e372b0eda7
101 files changed
+29118
-5539
No files matched your search
@@ -129,9 +129,6 @@ const AdminSettingsPanel = lazy(() =>
|
||||
default: m.AdminSettingsPanel,
|
||||
})),
|
||||
);
|
||||
const AlertsPanel = lazy(() =>
|
||||
import("@/sidebar/AlertsPanel").then((m) => ({ default: m.AlertsPanel })),
|
||||
);
|
||||
const CredentialsPanel = lazy(() =>
|
||||
import("@/sidebar/CredentialsPanel").then((m) => ({
|
||||
default: m.CredentialsPanel,
|
||||
@@ -2898,12 +2895,6 @@ export function AppShell({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{railView === "alerts" && (
|
||||
<div className="flex-1 min-h-0 overflow-hidden flex flex-col">
|
||||
<AlertsPanel />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
import { rbacApi } from "@/main-axios";
|
||||
|
||||
export interface NotificationChannel {
|
||||
id: number;
|
||||
userId: string;
|
||||
name: string;
|
||||
type: "webhook" | "ntfy" | "discord";
|
||||
config: string;
|
||||
enabled: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface AlertRule {
|
||||
id: number;
|
||||
userId: string;
|
||||
hostId: number | null;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
triggerType: string;
|
||||
thresholdValue: number | null;
|
||||
thresholdDurationSeconds: number | null;
|
||||
cooldownMinutes: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
channelIds: number[];
|
||||
}
|
||||
|
||||
export interface AlertFiring {
|
||||
id: number;
|
||||
userId: string;
|
||||
ruleId: number;
|
||||
hostId: number;
|
||||
hostName: string;
|
||||
firedAt: string;
|
||||
resolvedAt: string | null;
|
||||
value: number | null;
|
||||
message: string;
|
||||
severity: "info" | "warning" | "critical";
|
||||
acknowledged: boolean;
|
||||
ruleName?: string;
|
||||
}
|
||||
|
||||
function stringValue(value: unknown, fallback = ""): string {
|
||||
return typeof value === "string" ? value : fallback;
|
||||
}
|
||||
|
||||
function numberValue(value: unknown, fallback = 0): number {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
||||
}
|
||||
|
||||
function nullableNumberValue(value: unknown): number | null {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function boolValue(value: unknown): boolean {
|
||||
return value === true || value === 1 || value === "1";
|
||||
}
|
||||
|
||||
function severityValue(value: unknown): AlertFiring["severity"] {
|
||||
return value === "info" || value === "critical" || value === "warning"
|
||||
? value
|
||||
: "warning";
|
||||
}
|
||||
|
||||
export async function getNotificationChannels(): Promise<
|
||||
NotificationChannel[]
|
||||
> {
|
||||
const res = await rbacApi.get("/notification-channels");
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export type NotificationChannelPayload = Partial<
|
||||
Omit<NotificationChannel, "config">
|
||||
> & {
|
||||
// When creating/updating the channel the UI may pass a parsed object
|
||||
// for `config` (e.g., { url, username }) — the backend stores it as
|
||||
// a JSON string. Accept either a string or any structured object here.
|
||||
// Use `unknown` to allow the component-local config types to be passed
|
||||
// without importing them into this module.
|
||||
config: string | unknown;
|
||||
};
|
||||
|
||||
export async function createNotificationChannel(
|
||||
data: NotificationChannelPayload,
|
||||
): Promise<NotificationChannel> {
|
||||
const res = await rbacApi.post("/notification-channels", data);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function updateNotificationChannel(
|
||||
id: number,
|
||||
data: NotificationChannelPayload,
|
||||
): Promise<NotificationChannel> {
|
||||
const res = await rbacApi.put(`/notification-channels/${id}`, data);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteNotificationChannel(id: number): Promise<void> {
|
||||
await rbacApi.delete(`/notification-channels/${id}`);
|
||||
}
|
||||
|
||||
export async function testNotificationChannel(id: number): Promise<void> {
|
||||
const res = await rbacApi.post(`/notification-channels/${id}/test`);
|
||||
const data = res.data as { success?: boolean; error?: string };
|
||||
if (data && data.success === false) {
|
||||
throw new Error(data.error || "Test notification failed");
|
||||
}
|
||||
}
|
||||
|
||||
function mapRule(r: Record<string, unknown>): AlertRule {
|
||||
return {
|
||||
id: r.id as number,
|
||||
userId: (r.user_id ?? r.userId) as string,
|
||||
hostId: (r.host_id ?? r.hostId ?? null) as number | null,
|
||||
name: r.name as string,
|
||||
enabled: Boolean(r.enabled),
|
||||
triggerType: (r.trigger_type ?? r.triggerType) as string,
|
||||
thresholdValue: (r.threshold_value ?? r.thresholdValue ?? null) as
|
||||
number | null,
|
||||
thresholdDurationSeconds: (r.threshold_duration_seconds ??
|
||||
r.thresholdDurationSeconds ??
|
||||
null) as number | null,
|
||||
cooldownMinutes: (r.cooldown_minutes ?? r.cooldownMinutes) as number,
|
||||
createdAt: (r.created_at ?? r.createdAt) as string,
|
||||
updatedAt: (r.updated_at ?? r.updatedAt) as string,
|
||||
channelIds: Array.isArray(r.channels) ? (r.channels as number[]) : [],
|
||||
};
|
||||
}
|
||||
|
||||
export function mapAlertFiring(r: Record<string, unknown>): AlertFiring {
|
||||
return {
|
||||
id: numberValue(r.id),
|
||||
userId: stringValue(r.userId ?? r.user_id),
|
||||
ruleId: numberValue(r.ruleId ?? r.rule_id),
|
||||
hostId: numberValue(r.hostId ?? r.host_id),
|
||||
hostName: stringValue(r.hostName ?? r.host_name),
|
||||
firedAt: stringValue(r.firedAt ?? r.fired_at, new Date(0).toISOString()),
|
||||
resolvedAt: stringValue(r.resolvedAt ?? r.resolved_at) || null,
|
||||
value: nullableNumberValue(r.value),
|
||||
message: stringValue(r.message),
|
||||
severity: severityValue(r.severity),
|
||||
acknowledged: boolValue(r.acknowledged),
|
||||
ruleName: stringValue(r.ruleName ?? r.rule_name) || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAlertRules(): Promise<AlertRule[]> {
|
||||
const res = await rbacApi.get("/alert-rules");
|
||||
return (res.data as Record<string, unknown>[]).map(mapRule);
|
||||
}
|
||||
|
||||
export async function createAlertRule(
|
||||
data: Partial<AlertRule> & { channels?: number[] },
|
||||
): Promise<AlertRule> {
|
||||
const res = await rbacApi.post("/alert-rules", data);
|
||||
return mapRule(res.data as Record<string, unknown>);
|
||||
}
|
||||
|
||||
export async function updateAlertRule(
|
||||
id: number,
|
||||
data: Partial<AlertRule> & { channels?: number[] },
|
||||
): Promise<AlertRule> {
|
||||
const res = await rbacApi.put(`/alert-rules/${id}`, data);
|
||||
return mapRule(res.data as Record<string, unknown>);
|
||||
}
|
||||
|
||||
export async function deleteAlertRule(id: number): Promise<void> {
|
||||
await rbacApi.delete(`/alert-rules/${id}`);
|
||||
}
|
||||
|
||||
export async function getAlertFirings(opts?: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
acknowledged?: boolean;
|
||||
}): Promise<AlertFiring[]> {
|
||||
const res = await rbacApi.get("/alert-firings", { params: opts });
|
||||
const data = res.data as { firings?: unknown } | unknown[];
|
||||
const rows = Array.isArray(data)
|
||||
? data
|
||||
: Array.isArray(data.firings)
|
||||
? data.firings
|
||||
: [];
|
||||
return rows.map((row) => mapAlertFiring(row as Record<string, unknown>));
|
||||
}
|
||||
|
||||
export async function acknowledgeAlertFiring(id: number): Promise<void> {
|
||||
await rbacApi.post(`/alert-firings/${id}/acknowledge`);
|
||||
}
|
||||
|
||||
export async function acknowledgeAllAlertFirings(): Promise<void> {
|
||||
await rbacApi.post("/alert-firings/acknowledge-all");
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { rbacApi } from "@/main-axios";
|
||||
|
||||
export interface NotificationChannel {
|
||||
id: number;
|
||||
userId: string;
|
||||
name: string;
|
||||
type: "webhook" | "ntfy" | "discord";
|
||||
config: string;
|
||||
enabled: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export async function getNotificationChannels(): Promise<
|
||||
NotificationChannel[]
|
||||
> {
|
||||
const res = await rbacApi.get("/notification-channels");
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export type NotificationChannelPayload = Partial<
|
||||
Omit<NotificationChannel, "config">
|
||||
> & {
|
||||
// When creating/updating the channel the UI may pass a parsed object
|
||||
// for `config` (e.g., { url, username }) — the backend stores it as
|
||||
// a JSON string. Accept either a string or any structured object here.
|
||||
// Use `unknown` to allow the component-local config types to be passed
|
||||
// without importing them into this module.
|
||||
config: string | unknown;
|
||||
};
|
||||
|
||||
export async function createNotificationChannel(
|
||||
data: NotificationChannelPayload,
|
||||
): Promise<NotificationChannel> {
|
||||
const res = await rbacApi.post("/notification-channels", data);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function updateNotificationChannel(
|
||||
id: number,
|
||||
data: NotificationChannelPayload,
|
||||
): Promise<NotificationChannel> {
|
||||
const res = await rbacApi.put(`/notification-channels/${id}`, data);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function deleteNotificationChannel(id: number): Promise<void> {
|
||||
await rbacApi.delete(`/notification-channels/${id}`);
|
||||
}
|
||||
|
||||
export async function testNotificationChannel(id: number): Promise<void> {
|
||||
const res = await rbacApi.post(`/notification-channels/${id}/test`);
|
||||
const data = res.data as { success?: boolean; error?: string };
|
||||
if (data && data.success === false) {
|
||||
throw new Error(data.error || "Test notification failed");
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Turns the internal identifiers the assistant works in into text a user can
|
||||
* read. Tool names, proposal kinds and payload keys all surface in the panel,
|
||||
* and raw ids like "propose_create_alert_rule" or "thresholddurationseconds"
|
||||
* and raw ids like "propose_create_automation" or "thresholddurationseconds"
|
||||
* are not something to show anyone.
|
||||
*/
|
||||
|
||||
@@ -14,9 +14,7 @@ const TOOL_LABELS: Record<string, string> = {
|
||||
list_automations: "Reading automations",
|
||||
get_automation: "Reading automation",
|
||||
list_workspaces: "Reading workspaces",
|
||||
list_alert_rules: "Reading alert rules",
|
||||
list_notification_channels: "Reading notification channels",
|
||||
get_alert_firings: "Reading recent alerts",
|
||||
list_homepage_items: "Reading homepage items",
|
||||
get_command_history: "Reading command history",
|
||||
get_network_topology: "Reading network topology",
|
||||
@@ -28,7 +26,6 @@ const TOOL_LABELS: Record<string, string> = {
|
||||
propose_delete_snippet: "Delete snippet",
|
||||
propose_create_automation: "Create automation",
|
||||
propose_create_fleet: "Create fleet",
|
||||
propose_create_alert_rule: "Create alert rule",
|
||||
propose_run_command: "Run command",
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import "./widgets/IframeWidget";
|
||||
import "./widgets/RssFeedWidget";
|
||||
import "./widgets/MetricsChartWidget";
|
||||
import "./widgets/HostGridWidget";
|
||||
import "./widgets/AlertFeedWidget";
|
||||
import "./widgets/PingStatusWidget";
|
||||
import "./widgets/RecentActivityWidget";
|
||||
import "./widgets/TermixUptimeWidget";
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type {
|
||||
AlertFeedConfig,
|
||||
WidgetEditFormProps,
|
||||
} from "@/types/homepage-types";
|
||||
import { Input } from "@/components/input";
|
||||
|
||||
export function AlertFeedEditForm({
|
||||
config,
|
||||
onChange,
|
||||
}: WidgetEditFormProps<AlertFeedConfig>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
{t("homepage.maxItems")}
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
max={50}
|
||||
value={config.maxItems}
|
||||
onChange={(e) =>
|
||||
onChange({
|
||||
...config,
|
||||
maxItems: Math.max(1, Number(e.target.value)),
|
||||
})
|
||||
}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-xs text-muted-foreground cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.showAcknowledged}
|
||||
onChange={(e) =>
|
||||
onChange({ ...config, showAcknowledged: e.target.checked })
|
||||
}
|
||||
className="accent-accent-brand"
|
||||
/>
|
||||
{t("homepage.showAcknowledged")}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import type {
|
||||
RssFeedConfig,
|
||||
MetricsChartConfig,
|
||||
HostGridConfig,
|
||||
AlertFeedConfig,
|
||||
PingStatusConfig,
|
||||
RecentActivityConfig,
|
||||
TermixUptimeConfig,
|
||||
@@ -55,7 +54,6 @@ import { IframeEditForm } from "./IframeEditForm";
|
||||
import { RssFeedEditForm } from "./RssFeedEditForm";
|
||||
import { MetricsChartEditForm } from "./MetricsChartEditForm";
|
||||
import { HostGridEditForm } from "./HostGridEditForm";
|
||||
import { AlertFeedEditForm } from "./AlertFeedEditForm";
|
||||
import { PingStatusEditForm } from "./PingStatusEditForm";
|
||||
import { RecentActivityEditForm } from "./RecentActivityEditForm";
|
||||
import { TermixUptimeEditForm } from "./TermixUptimeEditForm";
|
||||
@@ -197,13 +195,6 @@ export function WidgetEditDialog({
|
||||
onChange={(c) => setConfig(c as unknown as Record<string, unknown>)}
|
||||
/>
|
||||
);
|
||||
case "alert_feed":
|
||||
return (
|
||||
<AlertFeedEditForm
|
||||
config={config as unknown as AlertFeedConfig}
|
||||
onChange={(c) => setConfig(c as unknown as Record<string, unknown>)}
|
||||
/>
|
||||
);
|
||||
case "ping_status":
|
||||
return (
|
||||
<PingStatusEditForm
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Bell,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
AlertCircle,
|
||||
Info,
|
||||
Check,
|
||||
} from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { registerWidget } from "./WidgetRegistry";
|
||||
import type {
|
||||
AlertFeedConfig,
|
||||
WidgetComponentProps,
|
||||
} from "@/types/homepage-types";
|
||||
import { GRID_SIZE } from "@/types/homepage-types";
|
||||
import {
|
||||
acknowledgeAlertFiring,
|
||||
getAlertFirings,
|
||||
type AlertFiring,
|
||||
} from "@/api/alerts-api";
|
||||
import { WidgetTitle } from "./WidgetTitle";
|
||||
|
||||
function relativeTime(ts: string): string {
|
||||
const diff = Date.now() - new Date(ts).getTime();
|
||||
const m = Math.floor(diff / 60_000);
|
||||
if (m < 1) return "just now";
|
||||
if (m < 60) return `${m}m ago`;
|
||||
const h = Math.floor(m / 60);
|
||||
if (h < 24) return `${h}h ago`;
|
||||
return `${Math.floor(h / 24)}d ago`;
|
||||
}
|
||||
|
||||
function SeverityIcon({ severity }: { severity: AlertFiring["severity"] }) {
|
||||
if (severity === "critical")
|
||||
return <AlertCircle size={11} className="shrink-0 text-red-500" />;
|
||||
if (severity === "warning")
|
||||
return <AlertTriangle size={11} className="shrink-0 text-amber-500" />;
|
||||
return <Info size={11} className="shrink-0 text-blue-400" />;
|
||||
}
|
||||
|
||||
function AlertFeedWidget({
|
||||
widget,
|
||||
config,
|
||||
}: WidgetComponentProps<AlertFeedConfig>) {
|
||||
const { t } = useTranslation();
|
||||
const { maxItems, showAcknowledged } = config;
|
||||
const [firings, setFirings] = useState<AlertFiring[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const data = await getAlertFirings({ limit: maxItems * 2 });
|
||||
const filtered = showAcknowledged
|
||||
? data
|
||||
: data.filter((f) => !f.acknowledged);
|
||||
setFirings(filtered.slice(0, maxItems));
|
||||
} catch {
|
||||
/* ignore */
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
const start = () => {
|
||||
if (intervalId !== null) return;
|
||||
intervalId = setInterval(() => {
|
||||
if (document.visibilityState === "hidden") return;
|
||||
void fetchData();
|
||||
}, 30_000);
|
||||
};
|
||||
const stop = () => {
|
||||
if (intervalId === null) return;
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
};
|
||||
const onVisibility = () => {
|
||||
if (document.visibilityState === "hidden") {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
void fetchData();
|
||||
start();
|
||||
};
|
||||
|
||||
if (document.visibilityState !== "hidden") start();
|
||||
document.addEventListener("visibilitychange", onVisibility);
|
||||
return () => {
|
||||
stop();
|
||||
document.removeEventListener("visibilitychange", onVisibility);
|
||||
};
|
||||
}, [maxItems, showAcknowledged]);
|
||||
|
||||
const handleAck = async (id: number) => {
|
||||
try {
|
||||
await acknowledgeAlertFiring(id);
|
||||
setFirings((prev) =>
|
||||
prev.map((f) => (f.id === id ? { ...f, acknowledged: true } : f)),
|
||||
);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center w-full h-full text-xs text-muted-foreground">
|
||||
{t("homepage.loading")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (firings.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center w-full h-full gap-2">
|
||||
<CheckCircle size={20} className="text-green-500" />
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t("homepage.allClear")}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full overflow-hidden">
|
||||
<WidgetTitle
|
||||
title={widget.title || t("homepage.widgetAlertFeedName")}
|
||||
icon={<Bell size={11} />}
|
||||
/>
|
||||
<div className="flex-1 overflow-auto">
|
||||
{firings.map((f) => (
|
||||
<div
|
||||
key={f.id}
|
||||
className={`flex items-start gap-2 px-2 py-1.5 border-b border-border/30 ${f.acknowledged ? "opacity-50" : ""}`}
|
||||
>
|
||||
<SeverityIcon severity={f.severity} />
|
||||
<div className="flex flex-col min-w-0 flex-1">
|
||||
<span className="text-[10px] font-semibold text-foreground truncate">
|
||||
{f.ruleName ?? f.message}
|
||||
</span>
|
||||
<span className="text-[9px] text-muted-foreground truncate">
|
||||
{f.hostName} · {relativeTime(f.firedAt)}
|
||||
</span>
|
||||
</div>
|
||||
{!f.acknowledged && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleAck(f.id);
|
||||
}}
|
||||
className="p-0.5 text-muted-foreground hover:text-green-500 transition-colors shrink-0"
|
||||
title={t("homepage.acknowledgeAlert")}
|
||||
>
|
||||
<Check size={10} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
registerWidget<AlertFeedConfig>({
|
||||
id: "alert_feed",
|
||||
name: "Alert Feed",
|
||||
description: "Live feed of active alert firings",
|
||||
category: "monitoring",
|
||||
icon: <Bell size={14} />,
|
||||
defaultConfig: { maxItems: 10, showAcknowledged: false },
|
||||
defaultSize: { w: GRID_SIZE * 10, h: GRID_SIZE * 8 },
|
||||
minSize: { w: GRID_SIZE * 4, h: GRID_SIZE * 3 },
|
||||
component: AlertFeedWidget,
|
||||
});
|
||||
|
||||
export { AlertFeedWidget };
|
||||
+4
-34
@@ -287,7 +287,6 @@
|
||||
"loading": "Loading...",
|
||||
"noData": "No data available",
|
||||
"allClear": "All clear",
|
||||
"acknowledgeAlert": "Acknowledge",
|
||||
"noPingUrls": "No URLs configured",
|
||||
"pingLabel": "Label",
|
||||
"addPingUrl": "Add URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Historical CPU, memory, disk or network chart for a host",
|
||||
"widgetHostGridName": "Host Grid",
|
||||
"widgetHostGridDesc": "Grid view of SSH host statuses",
|
||||
"widgetAlertFeedName": "Alert Feed",
|
||||
"widgetAlertFeedDesc": "Live alert firings with acknowledge support",
|
||||
"widgetPingStatusName": "Ping Status",
|
||||
"widgetPingStatusDesc": "HTTP ping status for one or more URLs",
|
||||
"widgetRecentActivityName": "Recent Activity",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Forwarding",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Connections",
|
||||
"alerts": "Alerts",
|
||||
"serial": "Serial",
|
||||
"keepSideMenuOpen": "Keep Menu Open",
|
||||
"collapseSideMenu": "Collapse Menu",
|
||||
@@ -4950,21 +4946,6 @@
|
||||
"viewHistory": "View History"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Alerts",
|
||||
"tabRules": "Rules",
|
||||
"tabChannels": "Channels",
|
||||
"noFirings": "No unacknowledged alerts",
|
||||
"noRules": "No alert rules configured",
|
||||
"noChannels": "No notification channels configured",
|
||||
"rulesDesc": "Alert rules trigger notifications",
|
||||
"channelsDesc": "Where alert notifications are sent",
|
||||
"acknowledge": "Acknowledge",
|
||||
"ackAll": "Ack All",
|
||||
"allAcknowledged": "All alerts acknowledged",
|
||||
"ackFailed": "Failed to acknowledge alert",
|
||||
"ackAllFailed": "Failed to acknowledge all alerts",
|
||||
"showAcknowledged": "Show All",
|
||||
"hideAcknowledged": "Hide Acked",
|
||||
"addChannel": "Add Channel",
|
||||
"editChannel": "Edit Channel",
|
||||
"channelName": "Name",
|
||||
@@ -4978,21 +4959,10 @@
|
||||
"ntfyTopicRequired": "Topic is required",
|
||||
"ntfyToken": "Access Token (optional)",
|
||||
"channelSaveFailed": "Failed to save channel",
|
||||
"test": "Test",
|
||||
"testSent": "Test notification sent",
|
||||
"testFailed": "Test notification failed",
|
||||
"testFailedWithReason": "Test notification failed: {{reason}}",
|
||||
"addRule": "Add Alert Rule",
|
||||
"editRule": "Edit Alert Rule",
|
||||
"ruleName": "Rule Name",
|
||||
"ruleNameRequired": "Name is required",
|
||||
"triggerType": "Trigger",
|
||||
"thresholdValue": "Threshold (%)",
|
||||
"durationSeconds": "Duration (seconds, 0 = fire immediately)",
|
||||
"cooldownMinutes": "Cooldown (minutes)",
|
||||
"channels": "Notification Channels",
|
||||
"noChannelsHint": "Add channels in the Channels tab first",
|
||||
"ruleSaveFailed": "Failed to save rule"
|
||||
"discordUrlInvalid": "Discord URL must be a valid Discord webhook URL",
|
||||
"discordWebhookDesc": "POST a Discord embed to this webhook URL on each alert firing",
|
||||
"discordUsername": "Display name (optional)",
|
||||
"discordAvatar": "Avatar URL (optional)"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistant",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Laai tans...",
|
||||
"noData": "Geen data beskikbaar nie",
|
||||
"allClear": "Alles duidelik",
|
||||
"acknowledgeAlert": "Erken",
|
||||
"noPingUrls": "Geen URL'e gekonfigureer nie",
|
||||
"pingLabel": "Etiket",
|
||||
"addPingUrl": "Voeg URL by",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Historiese SVE-, geheue-, skyf- of netwerkgrafiek vir 'n gasheer",
|
||||
"widgetHostGridName": "Gasheerrooster",
|
||||
"widgetHostGridDesc": "Roosteraansig van SSH-gasheerstatusse",
|
||||
"widgetAlertFeedName": "Waarskuwingsvoer",
|
||||
"widgetAlertFeedDesc": "Regstreekse waarskuwingsvuurwerke met erkenningsondersteuning",
|
||||
"widgetPingStatusName": "Ping-status",
|
||||
"widgetPingStatusDesc": "HTTP-pingstatus vir een of meer URL'e",
|
||||
"widgetRecentActivityName": "Onlangse Aktiwiteit",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Aanstuur",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Verbindings",
|
||||
"alerts": "Waarskuwings",
|
||||
"serial": "Reeks",
|
||||
"keepSideMenuOpen": "Hou Kieslys Oop",
|
||||
"collapseSideMenu": "Vou Kieslys in",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Bekyk Geskiedenis"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Waarskuwings",
|
||||
"tabRules": "Reëls",
|
||||
"tabChannels": "Kanale",
|
||||
"noFirings": "Geen onerkende waarskuwings nie",
|
||||
"noRules": "Geen waarskuwingsreëls opgestel nie",
|
||||
"noChannels": "Geen kennisgewingkanale gekonfigureer nie",
|
||||
"rulesDesc": "Waarskuwingsreëls aktiveer kennisgewings",
|
||||
"channelsDesc": "Waar waarskuwingskennisgewings gestuur word",
|
||||
"acknowledge": "Erken",
|
||||
"ackAll": "Bevestig alles",
|
||||
"allAcknowledged": "Alle waarskuwings erken",
|
||||
"ackFailed": "Kon nie waarskuwing erken nie",
|
||||
"ackAllFailed": "Kon nie alle waarskuwings erken nie",
|
||||
"showAcknowledged": "Wys alles",
|
||||
"hideAcknowledged": "Versteek Goedgekeur",
|
||||
"addChannel": "Voeg kanaal by",
|
||||
"editChannel": "Wysig kanaal",
|
||||
"channelName": "Naam",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Onderwerp",
|
||||
"ntfyTopicRequired": "Onderwerp is vereis",
|
||||
"ntfyToken": "Toegangstoken (opsioneel)",
|
||||
"channelSaveFailed": "Kon nie kanaal stoor nie",
|
||||
"test": "Toets",
|
||||
"testSent": "Toetskennisgewing gestuur",
|
||||
"testFailed": "Toetskennisgewing het misluk",
|
||||
"testFailedWithReason": "Toetskennisgewing het misluk: {{reason}}",
|
||||
"addRule": "Voeg waarskuwingsreël by",
|
||||
"editRule": "Wysig waarskuwingsreël",
|
||||
"ruleName": "Reëlnaam",
|
||||
"ruleNameRequired": "Naam word vereis",
|
||||
"triggerType": "Sneller",
|
||||
"thresholdValue": "Drempel (%)",
|
||||
"durationSeconds": "Duur (sekondes, 0 = onmiddellike vuur)",
|
||||
"cooldownMinutes": "Afkoeltyd (minute)",
|
||||
"channels": "Kennisgewingskanale",
|
||||
"noChannelsHint": "Voeg eers kanale by die Kanale-oortjie",
|
||||
"ruleSaveFailed": "Kon nie reël stoor nie"
|
||||
"channelSaveFailed": "Kon nie kanaal stoor nie"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistent",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "جار التحميل...",
|
||||
"noData": "لا توجد بيانات",
|
||||
"allClear": "كل شيء طبيعي",
|
||||
"acknowledgeAlert": "إقرار",
|
||||
"noPingUrls": "لم يتم تكوين عناوين URL",
|
||||
"pingLabel": "التسمية",
|
||||
"addPingUrl": "إضافة URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "مخطط تاريخي لوحدة المعالجة المركزية أو الذاكرة أو القرص أو الشبكة لمضيف",
|
||||
"widgetHostGridName": "شبكة المضيفات",
|
||||
"widgetHostGridDesc": "عرض شبكي لحالات مضيفات SSH",
|
||||
"widgetAlertFeedName": "موجز التنبيهات",
|
||||
"widgetAlertFeedDesc": "تنبيهات حية مع دعم الإقرار",
|
||||
"widgetPingStatusName": "حالة Ping",
|
||||
"widgetPingStatusDesc": "حالة ping HTTP لعنوان URL واحد أو أكثر",
|
||||
"widgetRecentActivityName": "النشاط الأخير",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "إعادة التوجيه",
|
||||
"sftp": "SFTP",
|
||||
"connections": "الاتصالات",
|
||||
"alerts": "التنبيهات",
|
||||
"serial": "تسلسلي",
|
||||
"keepSideMenuOpen": "أبقِ قائمة الطعام مفتوحة",
|
||||
"collapseSideMenu": "قائمة قابلة للطي",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "عرض السجل"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "التنبيهات",
|
||||
"tabRules": "القواعد",
|
||||
"tabChannels": "القنوات",
|
||||
"noFirings": "لا توجد تنبيهات غير مُقَرَّة",
|
||||
"noRules": "لم يتم تكوين قواعد تنبيه",
|
||||
"noChannels": "لم يتم تكوين قنوات إشعار",
|
||||
"rulesDesc": "تقوم قواعد التنبيه بتشغيل الإشعارات",
|
||||
"channelsDesc": "مكان إرسال إشعارات التنبيه",
|
||||
"acknowledge": "إقرار",
|
||||
"ackAll": "إقرار الكل",
|
||||
"allAcknowledged": "تم إقرار جميع التنبيهات",
|
||||
"ackFailed": "فشل في إقرار التنبيه",
|
||||
"ackAllFailed": "فشل في إقرار جميع التنبيهات",
|
||||
"showAcknowledged": "إظهار الكل",
|
||||
"hideAcknowledged": "إخفاء المُقَرَّة",
|
||||
"addChannel": "إضافة قناة",
|
||||
"editChannel": "تعديل القناة",
|
||||
"channelName": "الاسم",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "الموضوع",
|
||||
"ntfyTopicRequired": "الموضوع مطلوب",
|
||||
"ntfyToken": "رمز الوصول (اختياري)",
|
||||
"channelSaveFailed": "فشل حفظ القناة",
|
||||
"test": "اختبار",
|
||||
"testSent": "تم إرسال إشعار الاختبار",
|
||||
"testFailed": "فشل إشعار الاختبار",
|
||||
"testFailedWithReason": "فشل إشعار الاختبار: {{reason}}",
|
||||
"addRule": "إضافة قاعدة تنبيه",
|
||||
"editRule": "تعديل قاعدة تنبيه",
|
||||
"ruleName": "اسم القاعدة",
|
||||
"ruleNameRequired": "الاسم مطلوب",
|
||||
"triggerType": "المشغّل",
|
||||
"thresholdValue": "الحد (%)",
|
||||
"durationSeconds": "المدة (بالثواني، 0 = إطلاق فوري)",
|
||||
"cooldownMinutes": "فترة التهدئة (بالدقائق)",
|
||||
"channels": "قنوات الإشعارات",
|
||||
"noChannelsHint": "أضف قنوات في تبويب القنوات أولاً",
|
||||
"ruleSaveFailed": "فشل حفظ القاعدة"
|
||||
"channelSaveFailed": "فشل حفظ القناة"
|
||||
},
|
||||
"ai": {
|
||||
"title": "مساعد",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Зареждане...",
|
||||
"noData": "Няма налични данни",
|
||||
"allClear": "Всичко е чисто",
|
||||
"acknowledgeAlert": "Потвърждаване",
|
||||
"noPingUrls": "Няма конфигурирани URL адреси",
|
||||
"pingLabel": "Етикет",
|
||||
"addPingUrl": "Добавяне на URL адрес",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Историческа диаграма на процесора, паметта, диска или мрежата за хост",
|
||||
"widgetHostGridName": "Хост мрежа",
|
||||
"widgetHostGridDesc": "Табелен изглед на състоянията на SSH хоста",
|
||||
"widgetAlertFeedName": "Канал за предупреждения",
|
||||
"widgetAlertFeedDesc": "Активиране на аларми в реално време с поддръжка на потвърждение",
|
||||
"widgetPingStatusName": "Състояние на пинг",
|
||||
"widgetPingStatusDesc": "HTTP ping статус за един или повече URL адреси",
|
||||
"widgetRecentActivityName": "Последна активност",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Препращане",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Връзки",
|
||||
"alerts": "Сигнали",
|
||||
"serial": "Сериен",
|
||||
"keepSideMenuOpen": "Запази менюто отворено",
|
||||
"collapseSideMenu": "Свиване на менюто",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Преглед на историята"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Сигнали",
|
||||
"tabRules": "Правила",
|
||||
"tabChannels": "Канали",
|
||||
"noFirings": "Няма непотвърдени сигнали",
|
||||
"noRules": "Няма конфигурирани правила за предупреждения",
|
||||
"noChannels": "Няма конфигурирани канали за известия",
|
||||
"rulesDesc": "Правилата за известия задействат известия",
|
||||
"channelsDesc": "Къде се изпращат известия за предупреждения",
|
||||
"acknowledge": "Потвърждаване",
|
||||
"ackAll": "Потвърждение на всички",
|
||||
"allAcknowledged": "Всички сигнали са потвърдени",
|
||||
"ackFailed": "Неуспешно потвърждаване на предупреждението",
|
||||
"ackAllFailed": "Неуспешно потвърждаване на всички сигнали",
|
||||
"showAcknowledged": "Покажи всички",
|
||||
"hideAcknowledged": "Скриване на одобрено",
|
||||
"addChannel": "Добавяне на канал",
|
||||
"editChannel": "Редактиране на канал",
|
||||
"channelName": "Име",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Тема",
|
||||
"ntfyTopicRequired": "Темата е задължителна",
|
||||
"ntfyToken": "Токен за достъп (по избор)",
|
||||
"channelSaveFailed": "Запазването на канала не бе успешно",
|
||||
"test": "Тест",
|
||||
"testSent": "Тестово известие е изпратено",
|
||||
"testFailed": "Тестовото известие не беше успешно",
|
||||
"testFailedWithReason": "Тестовото известие не беше успешно: {{reason}}",
|
||||
"addRule": "Добавяне на правило за предупреждение",
|
||||
"editRule": "Редактиране на правило за предупреждение",
|
||||
"ruleName": "Име на правилото",
|
||||
"ruleNameRequired": "Името е задължително",
|
||||
"triggerType": "Спусък",
|
||||
"thresholdValue": "Праг (%)",
|
||||
"durationSeconds": "Продължителност (секунди, 0 = незабавно задействане)",
|
||||
"cooldownMinutes": "Охлаждане (минути)",
|
||||
"channels": "Канали за известия",
|
||||
"noChannelsHint": "Първо добавете канали в раздела „Канали“",
|
||||
"ruleSaveFailed": "Запазването на правилото не бе успешно"
|
||||
"channelSaveFailed": "Запазването на канала не бе успешно"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Асистент",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "লোড হচ্ছে...",
|
||||
"noData": "কোন ডেটা উপলব্ধ নেই",
|
||||
"allClear": "সব পরিষ্কার",
|
||||
"acknowledgeAlert": "স্বীকার করুন",
|
||||
"noPingUrls": "কোন URL কনফিগার করা নেই",
|
||||
"pingLabel": "লেবেল",
|
||||
"addPingUrl": "URL যোগ করুন",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "একটি হোস্টের ঐতিহাসিক সিপিইউ, মেমরি, ডিস্ক বা নেটওয়ার্ক চার্ট",
|
||||
"widgetHostGridName": "হোস্ট গ্রিড",
|
||||
"widgetHostGridDesc": "SSH হোস্ট স্ট্যাটাসের গ্রিড ভিউ",
|
||||
"widgetAlertFeedName": "সতর্কতা ফিড",
|
||||
"widgetAlertFeedDesc": "স্বীকৃতি সমর্থন সহ লাইভ অ্যালার্ট চালু",
|
||||
"widgetPingStatusName": "পিং স্ট্যাটাস",
|
||||
"widgetPingStatusDesc": "এক বা একাধিক URL-এর জন্য HTTP পিং স্ট্যাটাস",
|
||||
"widgetRecentActivityName": "সাম্প্রতিক কার্যকলাপ",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "ফরোয়ার্ডিং",
|
||||
"sftp": "এসএফটিপি",
|
||||
"connections": "সংযোগ",
|
||||
"alerts": "সতর্কতা",
|
||||
"serial": "সিরিয়াল",
|
||||
"keepSideMenuOpen": "মেনু খোলা রাখুন",
|
||||
"collapseSideMenu": "মেনু সংকুচিত করুন",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "ইতিহাস দেখুন"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "সতর্কতা",
|
||||
"tabRules": "নিয়ম",
|
||||
"tabChannels": "চ্যানেল",
|
||||
"noFirings": "কোনো অস্বীকৃত সতর্কতা নেই",
|
||||
"noRules": "কোনো সতর্কতা নিয়ম কনফিগার করা নেই",
|
||||
"noChannels": "কোন বিজ্ঞপ্তি চ্যানেল কনফিগার করা নেই",
|
||||
"rulesDesc": "সতর্কতা নিয়মগুলি বিজ্ঞপ্তি ট্রিগার করে",
|
||||
"channelsDesc": "যেখানে সতর্কবার্তা পাঠানো হয়",
|
||||
"acknowledge": "স্বীকার করুন",
|
||||
"ackAll": "সবাইকে ধন্যবাদ",
|
||||
"allAcknowledged": "সকল সতর্কতা স্বীকার করা হয়েছে",
|
||||
"ackFailed": "সতর্কতা স্বীকার করতে ব্যর্থ হয়েছে",
|
||||
"ackAllFailed": "সমস্ত সতর্কতা স্বীকার করতে ব্যর্থ হয়েছে",
|
||||
"showAcknowledged": "সব দেখান",
|
||||
"hideAcknowledged": "লুকান অ্যাকড",
|
||||
"addChannel": "চ্যানেল যোগ করুন",
|
||||
"editChannel": "চ্যানেল সম্পাদনা করুন",
|
||||
"channelName": "নাম",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "বিষয়",
|
||||
"ntfyTopicRequired": "বিষয় আবশ্যক",
|
||||
"ntfyToken": "অ্যাক্সেস টোকেন (ঐচ্ছিক)",
|
||||
"channelSaveFailed": "চ্যানেল সংরক্ষণ করতে ব্যর্থ হয়েছে",
|
||||
"test": "পরীক্ষা",
|
||||
"testSent": "পরীক্ষার বিজ্ঞপ্তি পাঠানো হয়েছে",
|
||||
"testFailed": "পরীক্ষার বিজ্ঞপ্তি ব্যর্থ হয়েছে",
|
||||
"testFailedWithReason": "পরীক্ষার বিজ্ঞপ্তি ব্যর্থ হয়েছে: {{reason}}",
|
||||
"addRule": "সতর্কতা নিয়ম যোগ করুন",
|
||||
"editRule": "সতর্কতা নিয়ম সম্পাদনা করুন",
|
||||
"ruleName": "নিয়মের নাম",
|
||||
"ruleNameRequired": "নাম আবশ্যক",
|
||||
"triggerType": "ট্রিগার",
|
||||
"thresholdValue": "থ্রেশহোল্ড (%)",
|
||||
"durationSeconds": "সময়কাল (সেকেন্ড, ০ = অবিলম্বে চালু হবে)",
|
||||
"cooldownMinutes": "কুলডাউন (মিনিট)",
|
||||
"channels": "বিজ্ঞপ্তি চ্যানেল",
|
||||
"noChannelsHint": "প্রথমে চ্যানেল ট্যাবে চ্যানেল যোগ করুন।",
|
||||
"ruleSaveFailed": "নিয়ম সংরক্ষণ করতে ব্যর্থ হয়েছে"
|
||||
"channelSaveFailed": "চ্যানেল সংরক্ষণ করতে ব্যর্থ হয়েছে"
|
||||
},
|
||||
"ai": {
|
||||
"title": "সহকারী",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "S'està carregant...",
|
||||
"noData": "No hi ha dades disponibles",
|
||||
"allClear": "Tot clar",
|
||||
"acknowledgeAlert": "Reconeix",
|
||||
"noPingUrls": "No s'han configurat URL",
|
||||
"pingLabel": "Etiqueta",
|
||||
"addPingUrl": "Afegeix URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Gràfic històric de CPU, memòria, disc o xarxa per a un host",
|
||||
"widgetHostGridName": "Graella d'amfitrió",
|
||||
"widgetHostGridDesc": "Vista de quadrícula dels estats dels hosts SSH",
|
||||
"widgetAlertFeedName": "Canal d'alertes",
|
||||
"widgetAlertFeedDesc": "Trets d'alerta en directe amb suport de confirmació",
|
||||
"widgetPingStatusName": "Estat del ping",
|
||||
"widgetPingStatusDesc": "Estat del ping HTTP per a una o més URL",
|
||||
"widgetRecentActivityName": "Activitat recent",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Reenviament",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Connexions",
|
||||
"alerts": "Alertes",
|
||||
"serial": "Sèrie",
|
||||
"keepSideMenuOpen": "Mantenir el menú obert",
|
||||
"collapseSideMenu": "Menú replegable",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Veure l'historial"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Alertes",
|
||||
"tabRules": "Regles",
|
||||
"tabChannels": "Canals",
|
||||
"noFirings": "Sense alertes no confirmades",
|
||||
"noRules": "No hi ha cap regla d'alerta configurada",
|
||||
"noChannels": "No hi ha canals de notificació configurats",
|
||||
"rulesDesc": "Les regles d'alerta activen les notificacions",
|
||||
"channelsDesc": "On s'envien les notificacions d'alerta",
|
||||
"acknowledge": "Reconeix",
|
||||
"ackAll": "Ack Tot",
|
||||
"allAcknowledged": "Totes les alertes han estat reconegudes",
|
||||
"ackFailed": "No s'ha pogut confirmar l'alerta",
|
||||
"ackAllFailed": "No s'han pogut reconèixer totes les alertes",
|
||||
"showAcknowledged": "Mostra-ho tot",
|
||||
"hideAcknowledged": "Amaga l'acusat",
|
||||
"addChannel": "Afegeix un canal",
|
||||
"editChannel": "Edita el canal",
|
||||
"channelName": "Nom",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Tema",
|
||||
"ntfyTopicRequired": "El tema és obligatori",
|
||||
"ntfyToken": "Token d'accés (opcional)",
|
||||
"channelSaveFailed": "No s'ha pogut desar el canal",
|
||||
"test": "Prova",
|
||||
"testSent": "Notificació de prova enviada",
|
||||
"testFailed": "La notificació de prova ha fallat",
|
||||
"testFailedWithReason": "La notificació de prova ha fallat: {{reason}}",
|
||||
"addRule": "Afegeix una regla d'alerta",
|
||||
"editRule": "Edita la regla d'alerta",
|
||||
"ruleName": "Nom de la regla",
|
||||
"ruleNameRequired": "El nom és obligatori",
|
||||
"triggerType": "Disparador",
|
||||
"thresholdValue": "Llindar (%)",
|
||||
"durationSeconds": "Durada (segons, 0 = dispara immediatament)",
|
||||
"cooldownMinutes": "Refredament (minuts)",
|
||||
"channels": "Canals de notificació",
|
||||
"noChannelsHint": "Afegiu primer els canals a la pestanya Canals",
|
||||
"ruleSaveFailed": "No s'ha pogut desar la regla"
|
||||
"channelSaveFailed": "No s'ha pogut desar el canal"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistent",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Načítání...",
|
||||
"noData": "Nejsou k dispozici žádná data",
|
||||
"allClear": "Všechno čisté",
|
||||
"acknowledgeAlert": "Potvrdit",
|
||||
"noPingUrls": "Žádné adresy URL nejsou nakonfigurovány",
|
||||
"pingLabel": "Označení",
|
||||
"addPingUrl": "Přidat URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Historický graf CPU, paměti, disku nebo sítě pro hostitele",
|
||||
"widgetHostGridName": "Hostitelská mřížka",
|
||||
"widgetHostGridDesc": "Mřížkové zobrazení stavů hostitelů SSH",
|
||||
"widgetAlertFeedName": "Zdroj upozornění",
|
||||
"widgetAlertFeedDesc": "Živé výstrahy s podporou potvrzení",
|
||||
"widgetPingStatusName": "Stav pingu",
|
||||
"widgetPingStatusDesc": "Stav HTTP pingu pro jednu nebo více URL adres",
|
||||
"widgetRecentActivityName": "Nedávná aktivita",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Přeposílání",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Připojení",
|
||||
"alerts": "Upozornění",
|
||||
"serial": "Seriál",
|
||||
"keepSideMenuOpen": "Ponechat nabídku otevřenou",
|
||||
"collapseSideMenu": "Sbalit nabídku",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Zobrazit historii"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Upozornění",
|
||||
"tabRules": "Pravidla",
|
||||
"tabChannels": "Kanály",
|
||||
"noFirings": "Žádná nepotvrzená upozornění",
|
||||
"noRules": "Nejsou nakonfigurována žádná pravidla upozornění",
|
||||
"noChannels": "Žádné nakonfigurované kanály oznámení",
|
||||
"rulesDesc": "Pravidla upozornění spouštějí oznámení",
|
||||
"channelsDesc": "Kam se odesílají upozornění",
|
||||
"acknowledge": "Potvrdit",
|
||||
"ackAll": "Potvrzení všeho",
|
||||
"allAcknowledged": "Všechna upozornění potvrzena",
|
||||
"ackFailed": "Nepodařilo se potvrdit upozornění",
|
||||
"ackAllFailed": "Nepodařilo se potvrdit všechna upozornění",
|
||||
"showAcknowledged": "Zobrazit vše",
|
||||
"hideAcknowledged": "Skrýt potvrzeno",
|
||||
"addChannel": "Přidat kanál",
|
||||
"editChannel": "Upravit kanál",
|
||||
"channelName": "Jméno",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Téma",
|
||||
"ntfyTopicRequired": "Téma je povinné",
|
||||
"ntfyToken": "Přístupový token (volitelné)",
|
||||
"channelSaveFailed": "Uložení kanálu se nezdařilo",
|
||||
"test": "Test",
|
||||
"testSent": "Testovací oznámení odesláno",
|
||||
"testFailed": "Testovací oznámení selhalo",
|
||||
"testFailedWithReason": "Testovací oznámení selhalo: {{reason}}",
|
||||
"addRule": "Přidat pravidlo upozornění",
|
||||
"editRule": "Upravit pravidlo upozornění",
|
||||
"ruleName": "Název pravidla",
|
||||
"ruleNameRequired": "Jméno je povinné",
|
||||
"triggerType": "Spoušť",
|
||||
"thresholdValue": "Prahová hodnota (%)",
|
||||
"durationSeconds": "Trvání (sekundy, 0 = okamžitá výstřel)",
|
||||
"cooldownMinutes": "Doba ochlazování (minuty)",
|
||||
"channels": "Oznamovací kanály",
|
||||
"noChannelsHint": "Nejprve přidejte kanály na kartě Kanály",
|
||||
"ruleSaveFailed": "Uložení pravidla se nezdařilo"
|
||||
"channelSaveFailed": "Uložení kanálu se nezdařilo"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Asistent",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Indlæser...",
|
||||
"noData": "Ingen data tilgængelige",
|
||||
"allClear": "Alt klart",
|
||||
"acknowledgeAlert": "Anerkende",
|
||||
"noPingUrls": "Ingen URL'er konfigureret",
|
||||
"pingLabel": "Mærke",
|
||||
"addPingUrl": "Tilføj URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Historisk CPU-, hukommelses-, disk- eller netværksdiagram for en vært",
|
||||
"widgetHostGridName": "Værtgitter",
|
||||
"widgetHostGridDesc": "Gittervisning af SSH-værtsstatusser",
|
||||
"widgetAlertFeedName": "Alarmfeed",
|
||||
"widgetAlertFeedDesc": "Live alarmudløsninger med bekræftelsesstøtte",
|
||||
"widgetPingStatusName": "Ping-status",
|
||||
"widgetPingStatusDesc": "HTTP-pingstatus for en eller flere URL'er",
|
||||
"widgetRecentActivityName": "Seneste aktivitet",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Videresendelse",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Forbindelser",
|
||||
"alerts": "Advarsler",
|
||||
"serial": "Seriel",
|
||||
"keepSideMenuOpen": "Hold menuen åben",
|
||||
"collapseSideMenu": "Skjul menu",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Vis historik"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Advarsler",
|
||||
"tabRules": "Regler",
|
||||
"tabChannels": "Kanaler",
|
||||
"noFirings": "Ingen ubekræftede advarsler",
|
||||
"noRules": "Ingen alarmregler konfigureret",
|
||||
"noChannels": "Ingen notifikationskanaler konfigureret",
|
||||
"rulesDesc": "Alarmregler udløser notifikationer",
|
||||
"channelsDesc": "Hvor alarmmeddelelser sendes",
|
||||
"acknowledge": "Anerkende",
|
||||
"ackAll": "Bekræft alle",
|
||||
"allAcknowledged": "Alle alarmer bekræftet",
|
||||
"ackFailed": "Kunne ikke bekræfte advarslen",
|
||||
"ackAllFailed": "Kunne ikke bekræfte alle advarsler",
|
||||
"showAcknowledged": "Vis alle",
|
||||
"hideAcknowledged": "Skjul bekræftet",
|
||||
"addChannel": "Tilføj kanal",
|
||||
"editChannel": "Rediger kanal",
|
||||
"channelName": "Navn",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Emne",
|
||||
"ntfyTopicRequired": "Emne er påkrævet",
|
||||
"ntfyToken": "Adgangstoken (valgfrit)",
|
||||
"channelSaveFailed": "Kanalen kunne ikke gemmes",
|
||||
"test": "Prøve",
|
||||
"testSent": "Testnotifikation sendt",
|
||||
"testFailed": "Testnotifikation mislykkedes",
|
||||
"testFailedWithReason": "Testnotifikation mislykkedes: {{reason}}",
|
||||
"addRule": "Tilføj alarmregel",
|
||||
"editRule": "Rediger alarmregel",
|
||||
"ruleName": "Regelnavn",
|
||||
"ruleNameRequired": "Navn er påkrævet",
|
||||
"triggerType": "Udløser",
|
||||
"thresholdValue": "Tærskel (%)",
|
||||
"durationSeconds": "Varighed (sekunder, 0 = affyres øjeblikkeligt)",
|
||||
"cooldownMinutes": "Nedkølingstid (minutter)",
|
||||
"channels": "Meddelelseskanaler",
|
||||
"noChannelsHint": "Tilføj kanaler i fanen Kanaler først",
|
||||
"ruleSaveFailed": "Reglen kunne ikke gemmes"
|
||||
"channelSaveFailed": "Kanalen kunne ikke gemmes"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistent",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Wird geladen...",
|
||||
"noData": "Keine Daten verfügbar",
|
||||
"allClear": "Alles in Ordnung",
|
||||
"acknowledgeAlert": "Bestätigen",
|
||||
"noPingUrls": "Keine URLs konfiguriert",
|
||||
"pingLabel": "Bezeichnung",
|
||||
"addPingUrl": "URL hinzufügen",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Historisches CPU-, Arbeitsspeicher-, Festplatten- oder Netzwerkdiagramm für einen Host",
|
||||
"widgetHostGridName": "Host-Raster",
|
||||
"widgetHostGridDesc": "Rasteransicht der SSH-Host-Status",
|
||||
"widgetAlertFeedName": "Alarm-Feed",
|
||||
"widgetAlertFeedDesc": "Live-Alarmauslösungen mit Bestätigungsfunktion",
|
||||
"widgetPingStatusName": "Ping-Status",
|
||||
"widgetPingStatusDesc": "HTTP-Ping-Status für eine oder mehrere URLs",
|
||||
"widgetRecentActivityName": "Letzte Aktivität",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Weiterleitung",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Verbindungen",
|
||||
"alerts": "Benachrichtigungen",
|
||||
"serial": "Seriell",
|
||||
"keepSideMenuOpen": "Menü geöffnet lassen",
|
||||
"collapseSideMenu": "Menü ausklappen",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Verlauf anzeigen"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Alarme",
|
||||
"tabRules": "Regeln",
|
||||
"tabChannels": "Kanäle",
|
||||
"noFirings": "Keine unbestätigten Alarme",
|
||||
"noRules": "Keine Alarmregeln konfiguriert",
|
||||
"noChannels": "Keine Benachrichtigungskanäle konfiguriert",
|
||||
"rulesDesc": "Alarmregeln lösen Benachrichtigungen aus",
|
||||
"channelsDesc": "Wohin Alarmbenachrichtigungen gesendet werden",
|
||||
"acknowledge": "Quittieren",
|
||||
"ackAll": "Alle quittieren",
|
||||
"allAcknowledged": "Alle Alarme quittiert",
|
||||
"ackFailed": "Quittieren des Alarms fehlgeschlagen",
|
||||
"ackAllFailed": "Quittieren aller Alarme fehlgeschlagen",
|
||||
"showAcknowledged": "Alle anzeigen",
|
||||
"hideAcknowledged": "Quittierte ausblenden",
|
||||
"addChannel": "Kanal hinzufügen",
|
||||
"editChannel": "Kanal bearbeiten",
|
||||
"channelName": "Name",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Topic",
|
||||
"ntfyTopicRequired": "Topic ist erforderlich",
|
||||
"ntfyToken": "Zugriffstoken (optional)",
|
||||
"channelSaveFailed": "Kanal konnte nicht gespeichert werden",
|
||||
"test": "Test",
|
||||
"testSent": "Testbenachrichtigung gesendet",
|
||||
"testFailed": "Testbenachrichtigung fehlgeschlagen",
|
||||
"testFailedWithReason": "Testbenachrichtigung fehlgeschlagen: {{reason}}",
|
||||
"addRule": "Alarmregel hinzufügen",
|
||||
"editRule": "Alarmregel bearbeiten",
|
||||
"ruleName": "Regelname",
|
||||
"ruleNameRequired": "Name ist erforderlich",
|
||||
"triggerType": "Auslöser",
|
||||
"thresholdValue": "Schwellenwert (%)",
|
||||
"durationSeconds": "Dauer (Sekunden, 0 = sofort auslösen)",
|
||||
"cooldownMinutes": "Abklingzeit (Minuten)",
|
||||
"channels": "Benachrichtigungskanäle",
|
||||
"noChannelsHint": "Fügen Sie zuerst Kanäle im Tab 'Kanäle' hinzu",
|
||||
"ruleSaveFailed": "Regel konnte nicht gespeichert werden"
|
||||
"channelSaveFailed": "Kanal konnte nicht gespeichert werden"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistent",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Φόρτωση...",
|
||||
"noData": "Δεν υπάρχουν διαθέσιμα δεδομένα",
|
||||
"allClear": "Ορίζοντας είναι σαφής",
|
||||
"acknowledgeAlert": "Αναγνωρίζω",
|
||||
"noPingUrls": "Δεν έχουν ρυθμιστεί διευθύνσεις URL",
|
||||
"pingLabel": "Επιγραφή",
|
||||
"addPingUrl": "Προσθήκη διεύθυνσης URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Ιστορικό διάγραμμα CPU, μνήμης, δίσκου ή δικτύου για έναν κεντρικό υπολογιστή",
|
||||
"widgetHostGridName": "Πλέγμα κεντρικού υπολογιστή",
|
||||
"widgetHostGridDesc": "Προβολή πλέγματος των καταστάσεων κεντρικού υπολογιστή SSH",
|
||||
"widgetAlertFeedName": "Ροή ειδοποιήσεων",
|
||||
"widgetAlertFeedDesc": "Ενεργοποιήσεις ζωντανών ειδοποιήσεων με υποστήριξη αναγνώρισης",
|
||||
"widgetPingStatusName": "Κατάσταση Ping",
|
||||
"widgetPingStatusDesc": "Κατάσταση ping HTTP για μία ή περισσότερες διευθύνσεις URL",
|
||||
"widgetRecentActivityName": "Πρόσφατη δραστηριότητα",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Προώθηση",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Συνδέσεις",
|
||||
"alerts": "Ειδοποιήσεις",
|
||||
"serial": "Σειράς",
|
||||
"keepSideMenuOpen": "Διατήρηση μενού ανοιχτού",
|
||||
"collapseSideMenu": "Σύμπτυξη μενού",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Προβολή ιστορικού"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Ειδοποιήσεις",
|
||||
"tabRules": "Κανόνες",
|
||||
"tabChannels": "Κανάλια",
|
||||
"noFirings": "Δεν υπάρχουν μη επιβεβαιωμένες ειδοποιήσεις",
|
||||
"noRules": "Δεν έχουν ρυθμιστεί κανόνες ειδοποίησης",
|
||||
"noChannels": "Δεν έχουν ρυθμιστεί κανάλια ειδοποιήσεων",
|
||||
"rulesDesc": "Οι κανόνες ειδοποίησης ενεργοποιούν ειδοποιήσεις",
|
||||
"channelsDesc": "Πού αποστέλλονται ειδοποιήσεις",
|
||||
"acknowledge": "Αναγνωρίζω",
|
||||
"ackAll": "Επιβεβαίωση όλων",
|
||||
"allAcknowledged": "Όλες οι ειδοποιήσεις επιβεβαιώθηκαν",
|
||||
"ackFailed": "Αποτυχία επιβεβαίωσης ειδοποίησης",
|
||||
"ackAllFailed": "Αποτυχία επιβεβαίωσης όλων των ειδοποιήσεων",
|
||||
"showAcknowledged": "Εμφάνιση όλων",
|
||||
"hideAcknowledged": "Απόκρυψη επικυρωμένων",
|
||||
"addChannel": "Προσθήκη καναλιού",
|
||||
"editChannel": "Επεξεργασία καναλιού",
|
||||
"channelName": "Ονομα",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Θέμα",
|
||||
"ntfyTopicRequired": "Το θέμα είναι υποχρεωτικό",
|
||||
"ntfyToken": "Διακριτικό πρόσβασης (προαιρετικό)",
|
||||
"channelSaveFailed": "Η αποθήκευση του καναλιού απέτυχε",
|
||||
"test": "Δοκιμή",
|
||||
"testSent": "Αποστολή ειδοποίησης δοκιμής",
|
||||
"testFailed": "Η ειδοποίηση δοκιμής απέτυχε.",
|
||||
"testFailedWithReason": "Η ειδοποίηση δοκιμής απέτυχε: {{reason}}",
|
||||
"addRule": "Προσθήκη κανόνα ειδοποίησης",
|
||||
"editRule": "Επεξεργασία κανόνα ειδοποίησης",
|
||||
"ruleName": "Όνομα κανόνα",
|
||||
"ruleNameRequired": "Απαιτείται όνομα",
|
||||
"triggerType": "Σκανδάλη",
|
||||
"thresholdValue": "Όριο (%)",
|
||||
"durationSeconds": "Διάρκεια (δευτερόλεπτα, 0 = άμεση πυροδότηση)",
|
||||
"cooldownMinutes": "Χρόνος αναμονής (λεπτά)",
|
||||
"channels": "Κανάλια ειδοποιήσεων",
|
||||
"noChannelsHint": "Προσθέστε πρώτα κανάλια στην καρτέλα Κανάλια",
|
||||
"ruleSaveFailed": "Η αποθήκευση του κανόνα απέτυχε"
|
||||
"channelSaveFailed": "Η αποθήκευση του καναλιού απέτυχε"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Βοηθός",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Cargando...",
|
||||
"noData": "Sin datos disponibles",
|
||||
"allClear": "Todo correcto",
|
||||
"acknowledgeAlert": "Reconocer",
|
||||
"noPingUrls": "No hay URLs configuradas",
|
||||
"pingLabel": "Etiqueta",
|
||||
"addPingUrl": "Añadir URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Gráfico histórico de CPU, memoria, disco o red para un host",
|
||||
"widgetHostGridName": "Cuadrícula de hosts",
|
||||
"widgetHostGridDesc": "Vista en cuadrícula de los estados de los hosts SSH",
|
||||
"widgetAlertFeedName": "Feed de alertas",
|
||||
"widgetAlertFeedDesc": "Alertas en tiempo real con soporte para reconocimiento",
|
||||
"widgetPingStatusName": "Estado de ping",
|
||||
"widgetPingStatusDesc": "Estado de ping HTTP para una o más URLs",
|
||||
"widgetRecentActivityName": "Actividad reciente",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Reenvío",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Conexiones",
|
||||
"alerts": "Alertas",
|
||||
"serial": "Serial",
|
||||
"keepSideMenuOpen": "Mantener el menú abierto",
|
||||
"collapseSideMenu": "Contraer menú",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Ver Historial"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Alertas",
|
||||
"tabRules": "Reglas",
|
||||
"tabChannels": "Canales",
|
||||
"noFirings": "No hay alertas sin reconocer",
|
||||
"noRules": "No hay reglas de alerta configuradas",
|
||||
"noChannels": "No hay canales de notificación configurados",
|
||||
"rulesDesc": "Las reglas de alerta disparan notificaciones",
|
||||
"channelsDesc": "Dónde se envían las notificaciones de alerta",
|
||||
"acknowledge": "Reconocer",
|
||||
"ackAll": "Reconocer todo",
|
||||
"allAcknowledged": "Todas las alertas reconocidas",
|
||||
"ackFailed": "Error al reconocer la alerta",
|
||||
"ackAllFailed": "Error al reconocer todas las alertas",
|
||||
"showAcknowledged": "Mostrar todas",
|
||||
"hideAcknowledged": "Ocultar reconocidas",
|
||||
"addChannel": "Añadir canal",
|
||||
"editChannel": "Editar canal",
|
||||
"channelName": "Nombre",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Tema",
|
||||
"ntfyTopicRequired": "El tema es obligatorio",
|
||||
"ntfyToken": "Token de acceso (opcional)",
|
||||
"channelSaveFailed": "No se pudo guardar el canal",
|
||||
"test": "Probar",
|
||||
"testSent": "Notificación de prueba enviada",
|
||||
"testFailed": "La notificación de prueba falló",
|
||||
"testFailedWithReason": "La notificación de prueba falló: {{reason}}",
|
||||
"addRule": "Añadir regla de alerta",
|
||||
"editRule": "Editar regla de alerta",
|
||||
"ruleName": "Nombre de la regla",
|
||||
"ruleNameRequired": "El nombre es obligatorio",
|
||||
"triggerType": "Disparador",
|
||||
"thresholdValue": "Umbral (%)",
|
||||
"durationSeconds": "Duración (segundos, 0 = disparar inmediatamente)",
|
||||
"cooldownMinutes": "Tiempo de espera (minutos)",
|
||||
"channels": "Canales de notificación",
|
||||
"noChannelsHint": "Añade primero canales en la pestaña Canales",
|
||||
"ruleSaveFailed": "No se pudo guardar la regla"
|
||||
"channelSaveFailed": "No se pudo guardar el canal"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Asistente",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Ladataan...",
|
||||
"noData": "Ei tietoja saatavilla",
|
||||
"allClear": "Kaikki selvää",
|
||||
"acknowledgeAlert": "Kuittaa",
|
||||
"noPingUrls": "Ei määritettyjä URL-osoitteita",
|
||||
"pingLabel": "Tarra",
|
||||
"addPingUrl": "Lisää URL-osoite",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Isännän historiallinen suorittimen, muistin, levyn tai verkon kaavio",
|
||||
"widgetHostGridName": "Isäntäverkko",
|
||||
"widgetHostGridDesc": "SSH-isäntätilojen ruudukkonäkymä",
|
||||
"widgetAlertFeedName": "Hälytyssyöte",
|
||||
"widgetAlertFeedDesc": "Reaaliaikaiset hälytyslaukaisut kuittaustuella",
|
||||
"widgetPingStatusName": "Ping-tila",
|
||||
"widgetPingStatusDesc": "Yhden tai useamman URL-osoitteen HTTP-ping-tila",
|
||||
"widgetRecentActivityName": "Viimeaikaiset toiminnot",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Edelleenlähetys",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Yhteydet",
|
||||
"alerts": "Hälytykset",
|
||||
"serial": "Sarjanumero",
|
||||
"keepSideMenuOpen": "Pidä valikko auki",
|
||||
"collapseSideMenu": "Pienennä valikko",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Näytä historia"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Hälytykset",
|
||||
"tabRules": "Säännöt",
|
||||
"tabChannels": "Kanavat",
|
||||
"noFirings": "Ei kuittaamattomia hälytyksiä",
|
||||
"noRules": "Ei määritettyjä hälytyssääntöjä",
|
||||
"noChannels": "Ei määritettyjä ilmoituskanavia",
|
||||
"rulesDesc": "Hälytyssäännöt käynnistävät ilmoituksia",
|
||||
"channelsDesc": "Minne hälytysilmoitukset lähetetään",
|
||||
"acknowledge": "Kuittaa",
|
||||
"ackAll": "Ack All",
|
||||
"allAcknowledged": "Kaikki hälytykset kuitattu",
|
||||
"ackFailed": "Hälytyksen kuittaus epäonnistui",
|
||||
"ackAllFailed": "Kaikkien hälytysten kuittaus epäonnistui",
|
||||
"showAcknowledged": "Näytä kaikki",
|
||||
"hideAcknowledged": "Piilota kuitattu",
|
||||
"addChannel": "Lisää kanava",
|
||||
"editChannel": "Muokkaa kanavaa",
|
||||
"channelName": "Nimi",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Aihe",
|
||||
"ntfyTopicRequired": "Aihe on pakollinen",
|
||||
"ntfyToken": "Käyttöoikeustunnus (valinnainen)",
|
||||
"channelSaveFailed": "Kanavan tallentaminen epäonnistui",
|
||||
"test": "Testata",
|
||||
"testSent": "Testi-ilmoitus lähetetty",
|
||||
"testFailed": "Testi-ilmoitus epäonnistui",
|
||||
"testFailedWithReason": "Testi-ilmoitus epäonnistui: {{reason}}",
|
||||
"addRule": "Lisää hälytyssääntö",
|
||||
"editRule": "Muokkaa hälytyssääntöä",
|
||||
"ruleName": "Säännön nimi",
|
||||
"ruleNameRequired": "Nimi on pakollinen",
|
||||
"triggerType": "Laukaista",
|
||||
"thresholdValue": "Kynnysarvo (%)",
|
||||
"durationSeconds": "Kesto (sekuntia, 0 = käynnistyy välittömästi)",
|
||||
"cooldownMinutes": "Jäähdyttelyaika (minuuttia)",
|
||||
"channels": "Ilmoituskanavat",
|
||||
"noChannelsHint": "Lisää kanavat ensin Kanavat-välilehdellä",
|
||||
"ruleSaveFailed": "Säännön tallentaminen epäonnistui"
|
||||
"channelSaveFailed": "Kanavan tallentaminen epäonnistui"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistentti",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Chargement…",
|
||||
"noData": "Aucune donnée disponible",
|
||||
"allClear": "Aucune alerte",
|
||||
"acknowledgeAlert": "Acquitter",
|
||||
"noPingUrls": "Aucune URL configurée",
|
||||
"pingLabel": "Libellé",
|
||||
"addPingUrl": "Ajouter une URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Graphique historique CPU, mémoire, disque ou réseau pour un hôte",
|
||||
"widgetHostGridName": "Grille d'hôtes",
|
||||
"widgetHostGridDesc": "Vue en grille des statuts des hôtes SSH",
|
||||
"widgetAlertFeedName": "Flux d'alertes",
|
||||
"widgetAlertFeedDesc": "Déclenchements d'alertes en direct avec acquittement",
|
||||
"widgetPingStatusName": "Statut du ping",
|
||||
"widgetPingStatusDesc": "Statut du ping HTTP pour une ou plusieurs URL",
|
||||
"widgetRecentActivityName": "Activité récente",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Expéditeur",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Connexions",
|
||||
"alerts": "Alertes",
|
||||
"serial": "Série",
|
||||
"keepSideMenuOpen": "Garder le menu ouvert",
|
||||
"collapseSideMenu": "Réduire le menu",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Voir l'historique"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Alertes",
|
||||
"tabRules": "Règles",
|
||||
"tabChannels": "Canaux",
|
||||
"noFirings": "Aucune alerte non acquittée",
|
||||
"noRules": "Aucune règle d'alerte configurée",
|
||||
"noChannels": "Aucun canal de notification configuré",
|
||||
"rulesDesc": "Les règles d'alerte déclenchent des notifications",
|
||||
"channelsDesc": "Où les notifications d'alerte sont envoyées",
|
||||
"acknowledge": "Acquitter",
|
||||
"ackAll": "Tout acquitter",
|
||||
"allAcknowledged": "Toutes les alertes acquittées",
|
||||
"ackFailed": "Échec de l'acquittement de l'alerte",
|
||||
"ackAllFailed": "Échec de l'acquittement de toutes les alertes",
|
||||
"showAcknowledged": "Tout afficher",
|
||||
"hideAcknowledged": "Masquer les acquittées",
|
||||
"addChannel": "Ajouter un canal",
|
||||
"editChannel": "Modifier le canal",
|
||||
"channelName": "Nom",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Sujet",
|
||||
"ntfyTopicRequired": "Le sujet est obligatoire",
|
||||
"ntfyToken": "Jeton d'accès (facultatif)",
|
||||
"channelSaveFailed": "Échec de l'enregistrement du canal",
|
||||
"test": "Tester",
|
||||
"testSent": "Notification de test envoyée",
|
||||
"testFailed": "Échec de la notification de test",
|
||||
"testFailedWithReason": "Échec de la notification de test : {{reason}}",
|
||||
"addRule": "Ajouter une règle d'alerte",
|
||||
"editRule": "Modifier la règle d'alerte",
|
||||
"ruleName": "Nom de la règle",
|
||||
"ruleNameRequired": "Le nom est obligatoire",
|
||||
"triggerType": "Déclencheur",
|
||||
"thresholdValue": "Seuil (%)",
|
||||
"durationSeconds": "Durée (secondes, 0 = déclenchement immédiat)",
|
||||
"cooldownMinutes": "Temporisation (minutes)",
|
||||
"channels": "Canaux de notification",
|
||||
"noChannelsHint": "Ajoutez d'abord des canaux dans l'onglet Canaux",
|
||||
"ruleSaveFailed": "Échec de l'enregistrement de la règle"
|
||||
"channelSaveFailed": "Échec de l'enregistrement du canal"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistant",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "טְעִינָה...",
|
||||
"noData": "אין נתונים זמינים",
|
||||
"allClear": "הכל ברור",
|
||||
"acknowledgeAlert": "לְהוֹדוֹת",
|
||||
"noPingUrls": "לא הוגדרו כתובות URL",
|
||||
"pingLabel": "מַדבֵּקָה",
|
||||
"addPingUrl": "הוסף כתובת URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "תרשים היסטורי של מעבד, זיכרון, דיסק או רשת עבור מחשב מארח",
|
||||
"widgetHostGridName": "רשת מארחים",
|
||||
"widgetHostGridDesc": "תצוגת רשת של סטטוסי מארח SSH",
|
||||
"widgetAlertFeedName": "הזנת התראות",
|
||||
"widgetAlertFeedDesc": "ירי התרעה בזמן אמת עם תמיכה באישור",
|
||||
"widgetPingStatusName": "סטטוס פינג",
|
||||
"widgetPingStatusDesc": "סטטוס פינג HTTP עבור כתובת URL אחת או יותר",
|
||||
"widgetRecentActivityName": "פעילות אחרונה",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "העברה",
|
||||
"sftp": "SFTP",
|
||||
"connections": "חיבורים",
|
||||
"alerts": "התראות",
|
||||
"serial": "סִדרָתִי",
|
||||
"keepSideMenuOpen": "שמור את התפריט פתוח",
|
||||
"collapseSideMenu": "כווץ תפריט",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "הצג היסטוריה"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "התראות",
|
||||
"tabRules": "כללים",
|
||||
"tabChannels": "ערוצים",
|
||||
"noFirings": "אין התראות שלא אושרו",
|
||||
"noRules": "לא הוגדרו כללי התראות",
|
||||
"noChannels": "לא הוגדרו ערוצי התראות",
|
||||
"rulesDesc": "כללי התראות מפעילים התראות",
|
||||
"channelsDesc": "לאן נשלחות התראות",
|
||||
"acknowledge": "לְהוֹדוֹת",
|
||||
"ackAll": "אשר הכל",
|
||||
"allAcknowledged": "כל ההתראות אושרו",
|
||||
"ackFailed": "נכשל אישור ההתראה",
|
||||
"ackAllFailed": "נכשל אישור כל ההתראות",
|
||||
"showAcknowledged": "הצג הכל",
|
||||
"hideAcknowledged": "הסתר אישור",
|
||||
"addChannel": "הוסף ערוץ",
|
||||
"editChannel": "עריכת ערוץ",
|
||||
"channelName": "שֵׁם",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "נוֹשֵׂא",
|
||||
"ntfyTopicRequired": "נושא נדרש",
|
||||
"ntfyToken": "אסימון גישה (אופציונלי)",
|
||||
"channelSaveFailed": "שמירת הערוץ נכשלה",
|
||||
"test": "מִבְחָן",
|
||||
"testSent": "הודעת בדיקה נשלחה",
|
||||
"testFailed": "הודעת הבדיקה נכשלה",
|
||||
"testFailedWithReason": "הודעת הבדיקה נכשלה: {{reason}}",
|
||||
"addRule": "הוסף כלל התראה",
|
||||
"editRule": "עריכת כלל התראה",
|
||||
"ruleName": "שם הכלל",
|
||||
"ruleNameRequired": "שם נדרש",
|
||||
"triggerType": "לְהַפְעִיל",
|
||||
"thresholdValue": "סף (%)",
|
||||
"durationSeconds": "משך (שניות, 0 = ירי מיידי)",
|
||||
"cooldownMinutes": "זמן קירור (דקות)",
|
||||
"channels": "ערוצי התראות",
|
||||
"noChannelsHint": "הוסף תחילה ערוצים בכרטיסייה ערוצים",
|
||||
"ruleSaveFailed": "שמירת הכלל נכשלה"
|
||||
"channelSaveFailed": "שמירת הערוץ נכשלה"
|
||||
},
|
||||
"ai": {
|
||||
"title": "עוֹזֵר",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "लोड हो रहा है...",
|
||||
"noData": "कोई डेटा उपलब्ध नहीं",
|
||||
"allClear": "सब ठीक है",
|
||||
"acknowledgeAlert": "स्वीकार करें",
|
||||
"noPingUrls": "कोई URL कॉन्फ़िगर नहीं किया गया",
|
||||
"pingLabel": "लेबल",
|
||||
"addPingUrl": "URL जोड़ें",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "होस्ट के लिए ऐतिहासिक CPU, मेमोरी, डिस्क या नेटवर्क चार्ट",
|
||||
"widgetHostGridName": "होस्ट ग्रिड",
|
||||
"widgetHostGridDesc": "SSH होस्ट की स्थितियों का ग्रिड दृश्य",
|
||||
"widgetAlertFeedName": "अलर्ट फीड",
|
||||
"widgetAlertFeedDesc": "स्वीकृति सहित लाइव अलर्ट फायरिंग",
|
||||
"widgetPingStatusName": "पिंग स्थिति",
|
||||
"widgetPingStatusDesc": "एक या अधिक URLs के लिए HTTP पिंग स्थिति",
|
||||
"widgetRecentActivityName": "हाल की गतिविधि",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "अग्रेषित करना",
|
||||
"sftp": "एसएफटीपी",
|
||||
"connections": "कनेक्शन्स",
|
||||
"alerts": "अलर्ट्स",
|
||||
"serial": "सीरियल",
|
||||
"keepSideMenuOpen": "मेनू खुला रखें",
|
||||
"collapseSideMenu": "मेनू को छोटा करें",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "इतिहास देखें"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "अलर्ट",
|
||||
"tabRules": "नियम",
|
||||
"tabChannels": "चैनल",
|
||||
"noFirings": "कोई अपुष्ट अलर्ट नहीं",
|
||||
"noRules": "कोई अलर्ट नियम कॉन्फ़िगर नहीं",
|
||||
"noChannels": "कोई सूचना चैनल कॉन्फ़िगर नहीं",
|
||||
"rulesDesc": "अलर्ट नियम सूचनाएं ट्रिगर करते हैं",
|
||||
"channelsDesc": "जहां अलर्ट सूचनाएं भेजी जाती हैं",
|
||||
"acknowledge": "पुष्टि करें",
|
||||
"ackAll": "सभी की पुष्टि करें",
|
||||
"allAcknowledged": "सभी अलर्ट की पुष्टि हो गई",
|
||||
"ackFailed": "अलर्ट की पुष्टि करने में विफल",
|
||||
"ackAllFailed": "सभी अलर्ट की पुष्टि करने में विफल",
|
||||
"showAcknowledged": "सभी दिखाएं",
|
||||
"hideAcknowledged": "पुष्टि किए गए छिपाएं",
|
||||
"addChannel": "चैनल जोड़ें",
|
||||
"editChannel": "चैनल संपादित करें",
|
||||
"channelName": "नाम",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "विषय",
|
||||
"ntfyTopicRequired": "विषय आवश्यक है",
|
||||
"ntfyToken": "एक्सेस टोकन (वैकल्पिक)",
|
||||
"channelSaveFailed": "चैनल सहेजने में विफल",
|
||||
"test": "परीक्षण",
|
||||
"testSent": "परीक्षण सूचना भेजी गई",
|
||||
"testFailed": "परीक्षण सूचना विफल",
|
||||
"testFailedWithReason": "परीक्षण अधिसूचना विफल: {{reason}}",
|
||||
"addRule": "अलर्ट नियम जोड़ें",
|
||||
"editRule": "अलर्ट नियम संपादित करें",
|
||||
"ruleName": "नियम नाम",
|
||||
"ruleNameRequired": "नाम आवश्यक है",
|
||||
"triggerType": "ट्रिगर",
|
||||
"thresholdValue": "सीमा (%)",
|
||||
"durationSeconds": "अवधि (सेकंड, 0 = तुरंत सक्रिय करें)",
|
||||
"cooldownMinutes": "कूलडाउन (मिनट)",
|
||||
"channels": "अधिसूचना चैनल",
|
||||
"noChannelsHint": "पहले चैनल टैब में चैनल जोड़ें",
|
||||
"ruleSaveFailed": "नियम सहेजने में विफल"
|
||||
"channelSaveFailed": "चैनल सहेजने में विफल"
|
||||
},
|
||||
"ai": {
|
||||
"title": "सहायक",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Terhelés...",
|
||||
"noData": "Nincsenek elérhető adatok",
|
||||
"allClear": "Minden tiszta",
|
||||
"acknowledgeAlert": "Elismerés",
|
||||
"noPingUrls": "Nincsenek konfigurált URL-ek",
|
||||
"pingLabel": "Címke",
|
||||
"addPingUrl": "URL hozzáadása",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Egy gazdagép CPU-, memória-, lemez- vagy hálózati előzménydiagramja",
|
||||
"widgetHostGridName": "Host Grid",
|
||||
"widgetHostGridDesc": "SSH-gazdagépek állapotainak rácsnézete",
|
||||
"widgetAlertFeedName": "Értesítési hírcsatorna",
|
||||
"widgetAlertFeedDesc": "Élő riasztási tüzelések nyugtázási támogatással",
|
||||
"widgetPingStatusName": "Ping állapot",
|
||||
"widgetPingStatusDesc": "HTTP ping állapot egy vagy több URL-hez",
|
||||
"widgetRecentActivityName": "Legutóbbi tevékenységek",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Szállítmányozás",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Kapcsolatok",
|
||||
"alerts": "Riasztások",
|
||||
"serial": "Sorozatszám",
|
||||
"keepSideMenuOpen": "Menü nyitva tartása",
|
||||
"collapseSideMenu": "Menü összecsukása",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Előzmények megtekintése"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Riasztások",
|
||||
"tabRules": "Szabályok",
|
||||
"tabChannels": "Csatornák",
|
||||
"noFirings": "Nincsenek nyugtázatlan riasztások",
|
||||
"noRules": "Nincsenek riasztási szabályok konfigurálva",
|
||||
"noChannels": "Nincsenek értesítési csatornák konfigurálva",
|
||||
"rulesDesc": "Riasztási szabályok aktiválják az értesítéseket",
|
||||
"channelsDesc": "Hová küldik a riasztási értesítéseket",
|
||||
"acknowledge": "Elismerés",
|
||||
"ackAll": "Mindent elfogad",
|
||||
"allAcknowledged": "Minden riasztás nyugtázva",
|
||||
"ackFailed": "Nem sikerült visszaigazolni a riasztást",
|
||||
"ackAllFailed": "Nem sikerült nyugtázni az összes riasztást",
|
||||
"showAcknowledged": "Összes megjelenítése",
|
||||
"hideAcknowledged": "Elrejtés Elfogadva",
|
||||
"addChannel": "Csatorna hozzáadása",
|
||||
"editChannel": "Csatorna szerkesztése",
|
||||
"channelName": "Név",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Téma",
|
||||
"ntfyTopicRequired": "A téma megadása kötelező",
|
||||
"ntfyToken": "Hozzáférési token (opcionális)",
|
||||
"channelSaveFailed": "Nem sikerült menteni a csatornát",
|
||||
"test": "Teszt",
|
||||
"testSent": "Tesztértesítés elküldve",
|
||||
"testFailed": "Tesztértesítés sikertelen",
|
||||
"testFailedWithReason": "Tesztértesítés sikertelen: {{reason}}",
|
||||
"addRule": "Riasztási szabály hozzáadása",
|
||||
"editRule": "Riasztási szabály szerkesztése",
|
||||
"ruleName": "Szabály neve",
|
||||
"ruleNameRequired": "Név megadása kötelező",
|
||||
"triggerType": "Trigger",
|
||||
"thresholdValue": "Küszöbérték (%)",
|
||||
"durationSeconds": "Időtartam (másodperc, 0 = azonnali aktiválás)",
|
||||
"cooldownMinutes": "Lehűlési idő (perc)",
|
||||
"channels": "Értesítési csatornák",
|
||||
"noChannelsHint": "Először adj hozzá csatornákat a Csatornák lapon",
|
||||
"ruleSaveFailed": "Nem sikerült menteni a szabályt"
|
||||
"channelSaveFailed": "Nem sikerült menteni a csatornát"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Helyettes",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Memuat...",
|
||||
"noData": "Tidak ada data yang tersedia",
|
||||
"allClear": "Semuanya aman.",
|
||||
"acknowledgeAlert": "Mengakui",
|
||||
"noPingUrls": "Tidak ada URL yang dikonfigurasi",
|
||||
"pingLabel": "Label",
|
||||
"addPingUrl": "Tambahkan URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Grafik historis CPU, memori, disk, atau jaringan untuk sebuah host.",
|
||||
"widgetHostGridName": "Jaringan Host",
|
||||
"widgetHostGridDesc": "Tampilan grid status host SSH",
|
||||
"widgetAlertFeedName": "Umpan Peringatan",
|
||||
"widgetAlertFeedDesc": "Peringatan penembakan langsung dengan dukungan konfirmasi.",
|
||||
"widgetPingStatusName": "Status Ping",
|
||||
"widgetPingStatusDesc": "Status ping HTTP untuk satu atau lebih URL",
|
||||
"widgetRecentActivityName": "Aktivitas Terkini",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Meneruskan",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Koneksi",
|
||||
"alerts": "Peringatan",
|
||||
"serial": "Serial",
|
||||
"keepSideMenuOpen": "Biarkan Menu Tetap Terbuka",
|
||||
"collapseSideMenu": "Ciutkan Menu",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Lihat Riwayat"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Peringatan",
|
||||
"tabRules": "Aturan",
|
||||
"tabChannels": "Saluran",
|
||||
"noFirings": "Tidak ada peringatan yang belum diakui",
|
||||
"noRules": "Tidak ada aturan peringatan yang dikonfigurasi.",
|
||||
"noChannels": "Tidak ada saluran notifikasi yang dikonfigurasi.",
|
||||
"rulesDesc": "Aturan peringatan memicu notifikasi.",
|
||||
"channelsDesc": "Di mana notifikasi peringatan dikirim",
|
||||
"acknowledge": "Mengakui",
|
||||
"ackAll": "Akui semuanya",
|
||||
"allAcknowledged": "Semua peringatan telah diterima.",
|
||||
"ackFailed": "Gagal menanggapi peringatan",
|
||||
"ackAllFailed": "Gagal menanggapi semua peringatan",
|
||||
"showAcknowledged": "Tampilkan Semua",
|
||||
"hideAcknowledged": "Sembunyikan Terakui",
|
||||
"addChannel": "Tambahkan Saluran",
|
||||
"editChannel": "Edit Saluran",
|
||||
"channelName": "Nama",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Topik",
|
||||
"ntfyTopicRequired": "Topik wajib diisi",
|
||||
"ntfyToken": "Token Akses (opsional)",
|
||||
"channelSaveFailed": "Gagal menyimpan saluran",
|
||||
"test": "Tes",
|
||||
"testSent": "Notifikasi pengujian telah dikirim.",
|
||||
"testFailed": "Notifikasi pengujian gagal",
|
||||
"testFailedWithReason": "Notifikasi pengujian gagal: {{reason}}",
|
||||
"addRule": "Tambahkan Aturan Peringatan",
|
||||
"editRule": "Edit Aturan Peringatan",
|
||||
"ruleName": "Nama Aturan",
|
||||
"ruleNameRequired": "Nama wajib diisi.",
|
||||
"triggerType": "Pemicu",
|
||||
"thresholdValue": "Ambang batas (%)",
|
||||
"durationSeconds": "Durasi (detik, 0 = tembak segera)",
|
||||
"cooldownMinutes": "Waktu pendinginan (menit)",
|
||||
"channels": "Saluran Pemberitahuan",
|
||||
"noChannelsHint": "Tambahkan saluran di tab Saluran terlebih dahulu.",
|
||||
"ruleSaveFailed": "Gagal menyimpan aturan"
|
||||
"channelSaveFailed": "Gagal menyimpan saluran"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Asisten",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Caricamento...",
|
||||
"noData": "Nessun dato disponibile",
|
||||
"allClear": "Tutto a posto",
|
||||
"acknowledgeAlert": "Riconosci",
|
||||
"noPingUrls": "Nessun URL configurato",
|
||||
"pingLabel": "Etichetta",
|
||||
"addPingUrl": "Aggiungi URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Grafico storico di CPU, memoria, disco o rete per un host",
|
||||
"widgetHostGridName": "Griglia host",
|
||||
"widgetHostGridDesc": "Vista a griglia degli stati degli host SSH",
|
||||
"widgetAlertFeedName": "Feed degli avvisi",
|
||||
"widgetAlertFeedDesc": "Avvisi in tempo reale con supporto di riconoscimento",
|
||||
"widgetPingStatusName": "Stato ping",
|
||||
"widgetPingStatusDesc": "Stato del ping HTTP per uno o più URL",
|
||||
"widgetRecentActivityName": "Attività recente",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Inoltro",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Connessioni",
|
||||
"alerts": "Avvisi",
|
||||
"serial": "Seriale",
|
||||
"keepSideMenuOpen": "Mantieni il menu aperto",
|
||||
"collapseSideMenu": "Comprimi menu",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Visualizza cronologia"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Avvisi",
|
||||
"tabRules": "Regole",
|
||||
"tabChannels": "Canali",
|
||||
"noFirings": "Nessun avviso non confermato",
|
||||
"noRules": "Nessuna regola di avviso configurata",
|
||||
"noChannels": "Nessun canale di notifica configurato",
|
||||
"rulesDesc": "Le regole di avviso attivano le notifiche",
|
||||
"channelsDesc": "Dove vengono inviate le notifiche di avviso",
|
||||
"acknowledge": "Conferma",
|
||||
"ackAll": "Conferma tutto",
|
||||
"allAcknowledged": "Tutti gli avvisi confermati",
|
||||
"ackFailed": "Conferma dell'avviso non riuscita",
|
||||
"ackAllFailed": "Conferma di tutti gli avvisi non riuscita",
|
||||
"showAcknowledged": "Mostra tutti",
|
||||
"hideAcknowledged": "Nascondi confermati",
|
||||
"addChannel": "Aggiungi canale",
|
||||
"editChannel": "Modifica canale",
|
||||
"channelName": "Nome",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Argomento",
|
||||
"ntfyTopicRequired": "Argomento obbligatorio",
|
||||
"ntfyToken": "Token di accesso (facoltativo)",
|
||||
"channelSaveFailed": "Salvataggio del canale non riuscito",
|
||||
"test": "Test",
|
||||
"testSent": "Notifica di test inviata",
|
||||
"testFailed": "Notifica di test fallita",
|
||||
"testFailedWithReason": "Notifica di prova non riuscita: {{reason}}",
|
||||
"addRule": "Aggiungi regola di avviso",
|
||||
"editRule": "Modifica regola di avviso",
|
||||
"ruleName": "Nome regola",
|
||||
"ruleNameRequired": "Nome obbligatorio",
|
||||
"triggerType": "Trigger",
|
||||
"thresholdValue": "Soglia (%)",
|
||||
"durationSeconds": "Durata (secondi, 0 = attiva immediatamente)",
|
||||
"cooldownMinutes": "Cooldown (minuti)",
|
||||
"channels": "Canali di notifica",
|
||||
"noChannelsHint": "Aggiungi prima i canali nella scheda Canali",
|
||||
"ruleSaveFailed": "Salvataggio della regola non riuscito"
|
||||
"channelSaveFailed": "Salvataggio del canale non riuscito"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistente",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "読み込み中...",
|
||||
"noData": "利用可能なデータはありません",
|
||||
"allClear": "すべて正常",
|
||||
"acknowledgeAlert": "確認",
|
||||
"noPingUrls": "URLが設定されていません",
|
||||
"pingLabel": "ラベル",
|
||||
"addPingUrl": "URLを追加",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "ホストのCPU、メモリ、ディスク、またはネットワークの履歴チャート",
|
||||
"widgetHostGridName": "ホストグリッド",
|
||||
"widgetHostGridDesc": "SSHホストステータスのグリッドビュー",
|
||||
"widgetAlertFeedName": "アラートフィード",
|
||||
"widgetAlertFeedDesc": "確認機能付きのライブアラート通知",
|
||||
"widgetPingStatusName": "Pingステータス",
|
||||
"widgetPingStatusDesc": "1つ以上のURLのHTTP pingステータス",
|
||||
"widgetRecentActivityName": "最近のアクティビティ",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "転送",
|
||||
"sftp": "SFTP",
|
||||
"connections": "接続",
|
||||
"alerts": "アラート",
|
||||
"serial": "シリアル",
|
||||
"keepSideMenuOpen": "メニューを開いたままにする",
|
||||
"collapseSideMenu": "メニューを折りたたむ",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "履歴を表示"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "アラート",
|
||||
"tabRules": "ルール",
|
||||
"tabChannels": "チャンネル",
|
||||
"noFirings": "未確認のアラートはありません",
|
||||
"noRules": "アラートルールが設定されていません",
|
||||
"noChannels": "通知チャンネルが設定されていません",
|
||||
"rulesDesc": "アラートルールは通知をトリガーします",
|
||||
"channelsDesc": "アラート通知の送信先です",
|
||||
"acknowledge": "確認",
|
||||
"ackAll": "すべて確認",
|
||||
"allAcknowledged": "すべてのアラートが確認済みです",
|
||||
"ackFailed": "アラートの確認に失敗しました",
|
||||
"ackAllFailed": "すべてのアラートの確認に失敗しました",
|
||||
"showAcknowledged": "すべて表示",
|
||||
"hideAcknowledged": "確認済みを非表示",
|
||||
"addChannel": "チャンネルを追加",
|
||||
"editChannel": "チャンネルを編集",
|
||||
"channelName": "名前",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "トピック",
|
||||
"ntfyTopicRequired": "トピックは必須です",
|
||||
"ntfyToken": "アクセストークン(任意)",
|
||||
"channelSaveFailed": "チャンネルの保存に失敗しました",
|
||||
"test": "テスト",
|
||||
"testSent": "テスト通知を送信しました",
|
||||
"testFailed": "テスト通知に失敗しました",
|
||||
"testFailedWithReason": "テスト通知が失敗しました: {{reason}}",
|
||||
"addRule": "アラートルールを追加",
|
||||
"editRule": "アラートルールを編集",
|
||||
"ruleName": "ルール名",
|
||||
"ruleNameRequired": "ルール名は必須です",
|
||||
"triggerType": "トリガー",
|
||||
"thresholdValue": "しきい値 (%)",
|
||||
"durationSeconds": "継続時間(秒、0は即時発火)",
|
||||
"cooldownMinutes": "クールダウン(分)",
|
||||
"channels": "通知チャンネル",
|
||||
"noChannelsHint": "先にチャンネルタブでチャンネルを追加してください",
|
||||
"ruleSaveFailed": "ルールの保存に失敗しました"
|
||||
"channelSaveFailed": "チャンネルの保存に失敗しました"
|
||||
},
|
||||
"ai": {
|
||||
"title": "アシスタント",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "로딩 중...",
|
||||
"noData": "데이터 없음",
|
||||
"allClear": "모두 정상",
|
||||
"acknowledgeAlert": "확인",
|
||||
"noPingUrls": "구성된 URL 없음",
|
||||
"pingLabel": "레이블",
|
||||
"addPingUrl": "URL 추가",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "호스트의 CPU, 메모리, 디스크, 네트워크 기록 차트",
|
||||
"widgetHostGridName": "호스트 그리드",
|
||||
"widgetHostGridDesc": "SSH 호스트 상태 그리드 뷰",
|
||||
"widgetAlertFeedName": "알림 피드",
|
||||
"widgetAlertFeedDesc": "실시간 알림 및 확인 지원",
|
||||
"widgetPingStatusName": "Ping 상태",
|
||||
"widgetPingStatusDesc": "하나 이상의 URL에 대한 HTTP 핑 상태",
|
||||
"widgetRecentActivityName": "최근 활동",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "전송",
|
||||
"sftp": "SFTP",
|
||||
"connections": "연결",
|
||||
"alerts": "알림",
|
||||
"serial": "시리얼",
|
||||
"keepSideMenuOpen": "메뉴를 열어 두세요",
|
||||
"collapseSideMenu": "메뉴 접기",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "기록 보기"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "알림",
|
||||
"tabRules": "규칙",
|
||||
"tabChannels": "채널",
|
||||
"noFirings": "미확인 알림 없음",
|
||||
"noRules": "구성된 알림 규칙 없음",
|
||||
"noChannels": "구성된 알림 채널 없음",
|
||||
"rulesDesc": "알림 규칙은 알림을 트리거합니다",
|
||||
"channelsDesc": "알림이 전송되는 위치",
|
||||
"acknowledge": "확인",
|
||||
"ackAll": "모두 확인",
|
||||
"allAcknowledged": "모든 알림 확인됨",
|
||||
"ackFailed": "알림 확인 실패",
|
||||
"ackAllFailed": "모든 알림 확인 실패",
|
||||
"showAcknowledged": "모두 표시",
|
||||
"hideAcknowledged": "확인된 항목 숨기기",
|
||||
"addChannel": "채널 추가",
|
||||
"editChannel": "채널 편집",
|
||||
"channelName": "이름",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "토픽",
|
||||
"ntfyTopicRequired": "토픽은 필수입니다",
|
||||
"ntfyToken": "액세스 토큰 (선택 사항)",
|
||||
"channelSaveFailed": "채널 저장에 실패했습니다",
|
||||
"test": "테스트",
|
||||
"testSent": "테스트 알림이 전송되었습니다",
|
||||
"testFailed": "테스트 알림 전송에 실패했습니다",
|
||||
"testFailedWithReason": "테스트 알림 실패: {{reason}}",
|
||||
"addRule": "알람 규칙 추가",
|
||||
"editRule": "알람 규칙 편집",
|
||||
"ruleName": "규칙 이름",
|
||||
"ruleNameRequired": "이름은 필수입니다",
|
||||
"triggerType": "트리거",
|
||||
"thresholdValue": "임계값 (%)",
|
||||
"durationSeconds": "지속 시간 (초, 0 = 즉시 발생)",
|
||||
"cooldownMinutes": "쿨다운 (분)",
|
||||
"channels": "알림 채널",
|
||||
"noChannelsHint": "먼저 채널 탭에서 채널을 추가하세요",
|
||||
"ruleSaveFailed": "규칙 저장에 실패했습니다"
|
||||
"channelSaveFailed": "채널 저장에 실패했습니다"
|
||||
},
|
||||
"ai": {
|
||||
"title": "어시스턴트",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Laden...",
|
||||
"noData": "Geen gegevens beschikbaar",
|
||||
"allClear": "Alles in orde",
|
||||
"acknowledgeAlert": "Erkennen",
|
||||
"noPingUrls": "Geen URL's geconfigureerd",
|
||||
"pingLabel": "Label",
|
||||
"addPingUrl": "URL toevoegen",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Historische grafiek van CPU, geheugen, schijf of netwerk voor een host.",
|
||||
"widgetHostGridName": "Host Grid",
|
||||
"widgetHostGridDesc": "Rasterweergave van de status van SSH-hosts",
|
||||
"widgetAlertFeedName": "Waarschuwingsfeed",
|
||||
"widgetAlertFeedDesc": "Live waarschuwingen met bevestigingsondersteuning",
|
||||
"widgetPingStatusName": "Pingstatus",
|
||||
"widgetPingStatusDesc": "HTTP-pingstatus voor een of meer URL's",
|
||||
"widgetRecentActivityName": "Recente activiteit",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Doorsturen",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Verbindingen",
|
||||
"alerts": "Waarschuwingen",
|
||||
"serial": "Serieel",
|
||||
"keepSideMenuOpen": "Menu open houden",
|
||||
"collapseSideMenu": "Menu inklappen",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Geschiedenis bekijken"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Waarschuwingen",
|
||||
"tabRules": "Regels",
|
||||
"tabChannels": "Kanalen",
|
||||
"noFirings": "Geen niet-bevestigde meldingen",
|
||||
"noRules": "Er zijn geen waarschuwingsregels geconfigureerd.",
|
||||
"noChannels": "Er zijn geen notificatiekanalen geconfigureerd.",
|
||||
"rulesDesc": "Waarschuwingsregels activeren meldingen.",
|
||||
"channelsDesc": "Waar waarschuwingsmeldingen worden verzonden",
|
||||
"acknowledge": "Erkennen",
|
||||
"ackAll": "Bevestig alles",
|
||||
"allAcknowledged": "Alle meldingen bevestigd",
|
||||
"ackFailed": "Melding niet ontvangen",
|
||||
"ackAllFailed": "Niet alle meldingen zijn bevestigd.",
|
||||
"showAcknowledged": "Alles weergeven",
|
||||
"hideAcknowledged": "Verberg Acked",
|
||||
"addChannel": "Kanaal toevoegen",
|
||||
"editChannel": "Bewerk kanaal",
|
||||
"channelName": "Naam",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Onderwerp",
|
||||
"ntfyTopicRequired": "Onderwerp is verplicht",
|
||||
"ntfyToken": "Toegangstoken (optioneel)",
|
||||
"channelSaveFailed": "Het opslaan van het kanaal is mislukt.",
|
||||
"test": "Test",
|
||||
"testSent": "Testmelding verzonden",
|
||||
"testFailed": "Testmelding mislukt",
|
||||
"testFailedWithReason": "Testmelding mislukt: {{reason}}",
|
||||
"addRule": "Voeg een waarschuwingsregel toe",
|
||||
"editRule": "Regel voor waarschuwing bewerken",
|
||||
"ruleName": "Regelnaam",
|
||||
"ruleNameRequired": "Naam is verplicht",
|
||||
"triggerType": "Trekker",
|
||||
"thresholdValue": "Drempelwaarde (%)",
|
||||
"durationSeconds": "Duur (seconden, 0 = direct vuren)",
|
||||
"cooldownMinutes": "Afkoeltijd (minuten)",
|
||||
"channels": "Meldingskanalen",
|
||||
"noChannelsHint": "Voeg eerst kanalen toe via het tabblad Kanalen.",
|
||||
"ruleSaveFailed": "Het opslaan van de regel is mislukt."
|
||||
"channelSaveFailed": "Het opslaan van het kanaal is mislukt."
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistent",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Laster inn...",
|
||||
"noData": "Ingen data tilgjengelig",
|
||||
"allClear": "Alt klart",
|
||||
"acknowledgeAlert": "Bekrefte",
|
||||
"noPingUrls": "Ingen nettadresser konfigurert",
|
||||
"pingLabel": "Merkelapp",
|
||||
"addPingUrl": "Legg til nettadresse",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Historisk CPU-, minne-, disk- eller nettverksdiagram for en vert",
|
||||
"widgetHostGridName": "Vertsnett",
|
||||
"widgetHostGridDesc": "Rutenettvisning av SSH-vertstatuser",
|
||||
"widgetAlertFeedName": "Varselstrøm",
|
||||
"widgetAlertFeedDesc": "Live-varslingsutløsninger med bekreftelsesstøtte",
|
||||
"widgetPingStatusName": "Ping-status",
|
||||
"widgetPingStatusDesc": "HTTP-pingstatus for én eller flere URL-er",
|
||||
"widgetRecentActivityName": "Nylig aktivitet",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Videresending",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Tilkoblinger",
|
||||
"alerts": "Varsler",
|
||||
"serial": "Seriell",
|
||||
"keepSideMenuOpen": "Hold menyen åpen",
|
||||
"collapseSideMenu": "Skjul menyen",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Vis historikk"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Varsler",
|
||||
"tabRules": "Regler",
|
||||
"tabChannels": "Kanaler",
|
||||
"noFirings": "Ingen ubekreftede varsler",
|
||||
"noRules": "Ingen varslingsregler konfigurert",
|
||||
"noChannels": "Ingen varslingskanaler konfigurert",
|
||||
"rulesDesc": "Varslingsregler utløser varsler",
|
||||
"channelsDesc": "Hvor varsler sendes",
|
||||
"acknowledge": "Bekrefte",
|
||||
"ackAll": "Bekreft alle",
|
||||
"allAcknowledged": "Alle varsler bekreftet",
|
||||
"ackFailed": "Kunne ikke bekrefte varselet",
|
||||
"ackAllFailed": "Kunne ikke bekrefte alle varsler",
|
||||
"showAcknowledged": "Vis alle",
|
||||
"hideAcknowledged": "Skjul bekreftet",
|
||||
"addChannel": "Legg til kanal",
|
||||
"editChannel": "Rediger kanal",
|
||||
"channelName": "Navn",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Emne",
|
||||
"ntfyTopicRequired": "Emne er obligatorisk",
|
||||
"ntfyToken": "Tilgangstoken (valgfritt)",
|
||||
"channelSaveFailed": "Kunne ikke lagre kanalen",
|
||||
"test": "Test",
|
||||
"testSent": "Testvarsel sendt",
|
||||
"testFailed": "Testvarsel mislyktes",
|
||||
"testFailedWithReason": "Testvarsling mislyktes: {{reason}}",
|
||||
"addRule": "Legg til varslingsregel",
|
||||
"editRule": "Rediger varslingsregel",
|
||||
"ruleName": "Regelnavn",
|
||||
"ruleNameRequired": "Navn er obligatorisk",
|
||||
"triggerType": "Avtrekker",
|
||||
"thresholdValue": "Terskel (%)",
|
||||
"durationSeconds": "Varighet (sekunder, 0 = avfyr umiddelbart)",
|
||||
"cooldownMinutes": "Nedkjølingstid (minutter)",
|
||||
"channels": "Varslingskanaler",
|
||||
"noChannelsHint": "Legg først til kanaler i Kanaler-fanen",
|
||||
"ruleSaveFailed": "Kunne ikke lagre regelen"
|
||||
"channelSaveFailed": "Kunne ikke lagre kanalen"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistent",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Ładowanie...",
|
||||
"noData": "Brak dostępnych danych",
|
||||
"allClear": "Bez alertów",
|
||||
"acknowledgeAlert": "Potwierdź",
|
||||
"noPingUrls": "Brak skonfigurowanych adresów URL",
|
||||
"pingLabel": "Etykieta",
|
||||
"addPingUrl": "Dodaj adres URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Historyczny wykres procesora, pamięci, dysku lub sieci dla hosta",
|
||||
"widgetHostGridName": "Siatka hostów",
|
||||
"widgetHostGridDesc": "Widok siatki statusów hostów SSH",
|
||||
"widgetAlertFeedName": "Kanał alertów",
|
||||
"widgetAlertFeedDesc": "Wyzwolone alerty na żywo z możliwością potwierdzenia",
|
||||
"widgetPingStatusName": "Status pingu",
|
||||
"widgetPingStatusDesc": "Status pingu HTTP dla jednego lub większej liczby adresów URL",
|
||||
"widgetRecentActivityName": "Ostatnia aktywność",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Przekierowanie",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Połączenia",
|
||||
"alerts": "Alerty",
|
||||
"serial": "Port szeregowy",
|
||||
"keepSideMenuOpen": "Zachowaj otwarte menu",
|
||||
"collapseSideMenu": "Zwiń menu",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Wyświetl historię"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Alerty",
|
||||
"tabRules": "Reguły",
|
||||
"tabChannels": "Kanały",
|
||||
"noFirings": "Brak niepotwierdzonych alertów",
|
||||
"noRules": "Nie skonfigurowano żadnych reguł alertów",
|
||||
"noChannels": "Brak skonfigurowanych kanałów powiadomień",
|
||||
"rulesDesc": "Reguły alertów wyzwalają powiadomienia",
|
||||
"channelsDesc": "Gdzie wysyłane są powiadomienia o alertach",
|
||||
"acknowledge": "Potwierdź",
|
||||
"ackAll": "Potwierdź wszystkie",
|
||||
"allAcknowledged": "Wszystkie alerty potwierdzone",
|
||||
"ackFailed": "Nie udało się potwierdzić alertu",
|
||||
"ackAllFailed": "Nie udało się potwierdzić wszystkich alertów",
|
||||
"showAcknowledged": "Pokaż wszystkie",
|
||||
"hideAcknowledged": "Ukryj potwierdzone",
|
||||
"addChannel": "Dodaj kanał",
|
||||
"editChannel": "Edytuj kanał",
|
||||
"channelName": "Nazwa",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Temat",
|
||||
"ntfyTopicRequired": "Temat jest wymagany",
|
||||
"ntfyToken": "Token dostępu (opcjonalnie)",
|
||||
"channelSaveFailed": "Nie udało się zapisać kanału",
|
||||
"test": "Test",
|
||||
"testSent": "Wysłano powiadomienie testowe",
|
||||
"testFailed": "Powiadomienie testowe nie powiodło się",
|
||||
"testFailedWithReason": "Powiadomienie testowe nie powiodło się: {{reason}}",
|
||||
"addRule": "Dodaj regułę alertu",
|
||||
"editRule": "Edytuj regułę alertu",
|
||||
"ruleName": "Nazwa reguły",
|
||||
"ruleNameRequired": "Nazwa jest wymagana",
|
||||
"triggerType": "Wyzwalacz",
|
||||
"thresholdValue": "Próg (%)",
|
||||
"durationSeconds": "Czas trwania (sekundy, 0 = wyzwól natychmiast)",
|
||||
"cooldownMinutes": "Czas wyciszenia (minuty)",
|
||||
"channels": "Kanały powiadomień",
|
||||
"noChannelsHint": "Najpierw dodaj kanały na karcie Kanały",
|
||||
"ruleSaveFailed": "Nie udało się zapisać reguły"
|
||||
"channelSaveFailed": "Nie udało się zapisać kanału"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Asystent",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Carregando...",
|
||||
"noData": "Não há dados disponíveis.",
|
||||
"allClear": "Tudo certo",
|
||||
"acknowledgeAlert": "Reconhecer",
|
||||
"noPingUrls": "Nenhuma URL configurada",
|
||||
"pingLabel": "Rótulo",
|
||||
"addPingUrl": "Adicionar URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Gráfico histórico de CPU, memória, disco ou rede para um host",
|
||||
"widgetHostGridName": "Grade de anfitriões",
|
||||
"widgetHostGridDesc": "Visualização em grade dos status dos hosts SSH",
|
||||
"widgetAlertFeedName": "Feed de alertas",
|
||||
"widgetAlertFeedDesc": "Disparos de alertas em tempo real com suporte de confirmação",
|
||||
"widgetPingStatusName": "Status do ping",
|
||||
"widgetPingStatusDesc": "Status do ping HTTP para um ou mais URLs",
|
||||
"widgetRecentActivityName": "Atividade recente",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Encaminhamento",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Conexões",
|
||||
"alerts": "Alertas",
|
||||
"serial": "Serial",
|
||||
"keepSideMenuOpen": "Mantenha o menu aberto",
|
||||
"collapseSideMenu": "Recolher menu",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Ver histórico"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Alertas",
|
||||
"tabRules": "Regras",
|
||||
"tabChannels": "Canais",
|
||||
"noFirings": "Nenhum alerta não reconhecido",
|
||||
"noRules": "Nenhuma regra de alerta configurada",
|
||||
"noChannels": "Nenhum canal de notificação configurado",
|
||||
"rulesDesc": "As regras de alerta acionam notificações.",
|
||||
"channelsDesc": "Para onde são enviadas as notificações de alerta",
|
||||
"acknowledge": "Reconhecer",
|
||||
"ackAll": "Concordo com tudo",
|
||||
"allAcknowledged": "Todos os alertas foram reconhecidos.",
|
||||
"ackFailed": "Falha em reconhecer o alerta",
|
||||
"ackAllFailed": "Não reconheceu todos os alertas.",
|
||||
"showAcknowledged": "Mostrar tudo",
|
||||
"hideAcknowledged": "Esconder Acked",
|
||||
"addChannel": "Adicionar canal",
|
||||
"editChannel": "Editar canal",
|
||||
"channelName": "Nome",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Tópico",
|
||||
"ntfyTopicRequired": "É necessário definir o tema.",
|
||||
"ntfyToken": "Token de acesso (opcional)",
|
||||
"channelSaveFailed": "Falha ao salvar o canal",
|
||||
"test": "Teste",
|
||||
"testSent": "Notificação de teste enviada",
|
||||
"testFailed": "Falha na notificação de teste",
|
||||
"testFailedWithReason": "Falha na notificação de teste: {{reason}}",
|
||||
"addRule": "Adicionar regra de alerta",
|
||||
"editRule": "Editar regra de alerta",
|
||||
"ruleName": "Nome da regra",
|
||||
"ruleNameRequired": "O nome é obrigatório.",
|
||||
"triggerType": "Acionar",
|
||||
"thresholdValue": "Limite (%)",
|
||||
"durationSeconds": "Duração (segundos, 0 = disparar imediatamente)",
|
||||
"cooldownMinutes": "Tempo de resfriamento (minutos)",
|
||||
"channels": "Canais de notificação",
|
||||
"noChannelsHint": "Adicione os canais primeiro na aba Canais.",
|
||||
"ruleSaveFailed": "Falha ao salvar a regra"
|
||||
"channelSaveFailed": "Falha ao salvar o canal"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistente",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "A carregar...",
|
||||
"noData": "Sem dados disponíveis",
|
||||
"allClear": "Tudo em ordem",
|
||||
"acknowledgeAlert": "Reconhecer",
|
||||
"noPingUrls": "Nenhum URL configurado",
|
||||
"pingLabel": "Etiqueta",
|
||||
"addPingUrl": "Adicionar URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Gráfico histórico de CPU, memória, disco ou rede para um host",
|
||||
"widgetHostGridName": "Grelha de hosts",
|
||||
"widgetHostGridDesc": "Vista em grelha dos estados dos hosts SSH",
|
||||
"widgetAlertFeedName": "Feed de alertas",
|
||||
"widgetAlertFeedDesc": "Disparos de alertas em tempo real com suporte a reconhecimento",
|
||||
"widgetPingStatusName": "Estado do ping",
|
||||
"widgetPingStatusDesc": "Estado do ping HTTP para um ou mais URLs",
|
||||
"widgetRecentActivityName": "Atividade recente",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Encaminhamento",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Ligações",
|
||||
"alerts": "Alertas",
|
||||
"serial": "Serial",
|
||||
"keepSideMenuOpen": "Mantenha o menu aberto",
|
||||
"collapseSideMenu": "Recolher menu",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Ver Histórico"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Alertas",
|
||||
"tabRules": "Regras",
|
||||
"tabChannels": "Canais",
|
||||
"noFirings": "Nenhum alerta por reconhecer",
|
||||
"noRules": "Nenhuma regra de alerta configurada",
|
||||
"noChannels": "Nenhum canal de notificação configurado",
|
||||
"rulesDesc": "As regras de alerta disparam notificações",
|
||||
"channelsDesc": "Onde as notificações de alerta são enviadas",
|
||||
"acknowledge": "Reconhecer",
|
||||
"ackAll": "Reconhecer Todos",
|
||||
"allAcknowledged": "Todos os alertas reconhecidos",
|
||||
"ackFailed": "Falha ao reconhecer o alerta",
|
||||
"ackAllFailed": "Falha ao reconhecer todos os alertas",
|
||||
"showAcknowledged": "Mostrar Todos",
|
||||
"hideAcknowledged": "Ocultar Reconhecidos",
|
||||
"addChannel": "Adicionar Canal",
|
||||
"editChannel": "Editar Canal",
|
||||
"channelName": "Nome",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Tópico",
|
||||
"ntfyTopicRequired": "O tópico é obrigatório",
|
||||
"ntfyToken": "Token de acesso (opcional)",
|
||||
"channelSaveFailed": "Falha ao guardar o canal",
|
||||
"test": "Testar",
|
||||
"testSent": "Notificação de teste enviada",
|
||||
"testFailed": "Falha na notificação de teste",
|
||||
"testFailedWithReason": "Falha na notificação de teste: {{reason}}",
|
||||
"addRule": "Adicionar regra de alerta",
|
||||
"editRule": "Editar regra de alerta",
|
||||
"ruleName": "Nome da regra",
|
||||
"ruleNameRequired": "O nome é obrigatório",
|
||||
"triggerType": "Gatilho",
|
||||
"thresholdValue": "Limiar (%)",
|
||||
"durationSeconds": "Duração (segundos, 0 = disparar imediatamente)",
|
||||
"cooldownMinutes": "Período de arrefecimento (minutos)",
|
||||
"channels": "Canais de notificação",
|
||||
"noChannelsHint": "Adicione canais primeiro no separador Canais",
|
||||
"ruleSaveFailed": "Falha ao guardar a regra"
|
||||
"channelSaveFailed": "Falha ao guardar o canal"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistente",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Încărcare...",
|
||||
"noData": "Nu sunt disponibile date",
|
||||
"allClear": "Totul este clar",
|
||||
"acknowledgeAlert": "Recunoaște",
|
||||
"noPingUrls": "Nicio adresă URL configurată",
|
||||
"pingLabel": "Eticheta",
|
||||
"addPingUrl": "Adăugați adresa URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Diagramă istorică CPU, memorie, disc sau rețea pentru o gazdă",
|
||||
"widgetHostGridName": "Grilă gazdă",
|
||||
"widgetHostGridDesc": "Vizualizare grilă a stărilor gazdelor SSH",
|
||||
"widgetAlertFeedName": "Flux de alerte",
|
||||
"widgetAlertFeedDesc": "Declanșări în direct cu asistență pentru confirmare",
|
||||
"widgetPingStatusName": "Starea ping-ului",
|
||||
"widgetPingStatusDesc": "Starea ping-ului HTTP pentru una sau mai multe adrese URL",
|
||||
"widgetRecentActivityName": "Activitate recentă",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Redirecționare",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Conexiuni",
|
||||
"alerts": "Alerte",
|
||||
"serial": "Serial",
|
||||
"keepSideMenuOpen": "Mențineți meniul deschis",
|
||||
"collapseSideMenu": "Restrângeți meniul",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Vizualizați istoricul"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Alerte",
|
||||
"tabRules": "Reguli",
|
||||
"tabChannels": "Canale",
|
||||
"noFirings": "Nicio alertă neconfirmată",
|
||||
"noRules": "Nicio regulă de alertă configurată",
|
||||
"noChannels": "Nu sunt configurate canale de notificare",
|
||||
"rulesDesc": "Regulile de alertă declanșează notificări",
|
||||
"channelsDesc": "Unde sunt trimise notificările de alertă",
|
||||
"acknowledge": "Recunoaște",
|
||||
"ackAll": "Confirmare tuturor",
|
||||
"allAcknowledged": "Toate alertele au fost confirmate",
|
||||
"ackFailed": "Nu s-a putut confirma alerta",
|
||||
"ackAllFailed": "Nu s-au putut confirma toate alertele",
|
||||
"showAcknowledged": "Afișează tot",
|
||||
"hideAcknowledged": "Ascundeți confirmarea",
|
||||
"addChannel": "Adăugați canal",
|
||||
"editChannel": "Editați canalul",
|
||||
"channelName": "Nume",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Subiect",
|
||||
"ntfyTopicRequired": "Subiectul este obligatoriu",
|
||||
"ntfyToken": "Token de acces (opțional)",
|
||||
"channelSaveFailed": "Canalul nu a putut fi salvat.",
|
||||
"test": "Test",
|
||||
"testSent": "Notificare de testare trimisă",
|
||||
"testFailed": "Notificarea de testare a eșuat",
|
||||
"testFailedWithReason": "Notificarea de testare a eșuat: {{reason}}",
|
||||
"addRule": "Adăugați o regulă de alertă",
|
||||
"editRule": "Editați regula de alertă",
|
||||
"ruleName": "Nume regulă",
|
||||
"ruleNameRequired": "Numele este obligatoriu",
|
||||
"triggerType": "Declanșator",
|
||||
"thresholdValue": "Prag (%)",
|
||||
"durationSeconds": "Durată (secunde, 0 = declanșează imediat)",
|
||||
"cooldownMinutes": "Timp de răcire (minute)",
|
||||
"channels": "Canale de notificare",
|
||||
"noChannelsHint": "Adăugați mai întâi canale în fila Canale",
|
||||
"ruleSaveFailed": "Nu s-a putut salva regula"
|
||||
"channelSaveFailed": "Canalul nu a putut fi salvat."
|
||||
},
|
||||
"ai": {
|
||||
"title": "Asistent",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Загрузка...",
|
||||
"noData": "Нет данных",
|
||||
"allClear": "Всё в порядке",
|
||||
"acknowledgeAlert": "Подтвердить",
|
||||
"noPingUrls": "URL-адреса не настроены",
|
||||
"pingLabel": "Метка",
|
||||
"addPingUrl": "Добавить URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "График истории использования ЦП, памяти, диска или сети для хоста",
|
||||
"widgetHostGridName": "Сетка хостов",
|
||||
"widgetHostGridDesc": "Представление статусов хостов SSH в виде сетки",
|
||||
"widgetAlertFeedName": "Лента оповещений",
|
||||
"widgetAlertFeedDesc": "Живые срабатывания оповещений с возможностью подтверждения",
|
||||
"widgetPingStatusName": "Статус ping",
|
||||
"widgetPingStatusDesc": "Статус HTTP ping для одного или нескольких URL",
|
||||
"widgetRecentActivityName": "Недавняя активность",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Пересылка",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Подключения",
|
||||
"alerts": "Оповещения",
|
||||
"serial": "Последовательный порт",
|
||||
"keepSideMenuOpen": "Оставить меню открытым",
|
||||
"collapseSideMenu": "Свернуть меню",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Просмотреть историю"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Оповещения",
|
||||
"tabRules": "Правила",
|
||||
"tabChannels": "Каналы",
|
||||
"noFirings": "Нет неподтверждённых оповещений",
|
||||
"noRules": "Не настроены правила оповещений",
|
||||
"noChannels": "Не настроены каналы уведомлений",
|
||||
"rulesDesc": "Правила оповещений инициируют уведомления",
|
||||
"channelsDesc": "Куда отправляются уведомления об оповещениях",
|
||||
"acknowledge": "Подтвердить",
|
||||
"ackAll": "Подтвердить все",
|
||||
"allAcknowledged": "Все оповещения подтверждены",
|
||||
"ackFailed": "Не удалось подтвердить оповещение",
|
||||
"ackAllFailed": "Не удалось подтвердить все оповещения",
|
||||
"showAcknowledged": "Показать все",
|
||||
"hideAcknowledged": "Скрыть подтверждённые",
|
||||
"addChannel": "Добавить канал",
|
||||
"editChannel": "Изменить канал",
|
||||
"channelName": "Название",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Тема",
|
||||
"ntfyTopicRequired": "Тема обязательна",
|
||||
"ntfyToken": "Токен доступа (необязательно)",
|
||||
"channelSaveFailed": "Не удалось сохранить канал",
|
||||
"test": "Проверить",
|
||||
"testSent": "Тестовое уведомление отправлено",
|
||||
"testFailed": "Ошибка отправки тестового уведомления",
|
||||
"testFailedWithReason": "Тестовое уведомление не пройдено: {{reason}}",
|
||||
"addRule": "Добавить правило оповещения",
|
||||
"editRule": "Редактировать правило оповещения",
|
||||
"ruleName": "Имя правила",
|
||||
"ruleNameRequired": "Имя обязательно",
|
||||
"triggerType": "Триггер",
|
||||
"thresholdValue": "Порог (%)",
|
||||
"durationSeconds": "Длительность (секунд, 0 = сработать немедленно)",
|
||||
"cooldownMinutes": "Задержка повторения (минут)",
|
||||
"channels": "Каналы уведомлений",
|
||||
"noChannelsHint": "Сначала добавьте каналы на вкладке «Каналы»",
|
||||
"ruleSaveFailed": "Не удалось сохранить правило"
|
||||
"channelSaveFailed": "Не удалось сохранить канал"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Помощник",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Учитавање...",
|
||||
"noData": "Нема доступних података",
|
||||
"allClear": "Све је чисто",
|
||||
"acknowledgeAlert": "Потврди",
|
||||
"noPingUrls": "Није конфигурисан ниједан URL",
|
||||
"pingLabel": "Ознака",
|
||||
"addPingUrl": "Додај URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Историјски графикон процесора, меморије, диска или мреже за хост",
|
||||
"widgetHostGridName": "Хост мрежа",
|
||||
"widgetHostGridDesc": "Приказ мреже статуса SSH хоста",
|
||||
"widgetAlertFeedName": "Фид обавештења",
|
||||
"widgetAlertFeedDesc": "Укључивање упозорења уживо са подршком за потврду",
|
||||
"widgetPingStatusName": "Статус пинга",
|
||||
"widgetPingStatusDesc": "HTTP пинг статус за један или више URL-ова",
|
||||
"widgetRecentActivityName": "Недавне активности",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Преусмеравање",
|
||||
"sftp": "СФТП",
|
||||
"connections": "Везе",
|
||||
"alerts": "Упозорења",
|
||||
"serial": "Серијски",
|
||||
"keepSideMenuOpen": "Остави мени отвореним",
|
||||
"collapseSideMenu": "Сакриј мени",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Прикажи историју"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Упозорења",
|
||||
"tabRules": "Правила",
|
||||
"tabChannels": "Канали",
|
||||
"noFirings": "Нема непотврђених упозорења",
|
||||
"noRules": "Није конфигурисано ниједно правило упозорења",
|
||||
"noChannels": "Није конфигурисан ниједан канал за обавештења",
|
||||
"rulesDesc": "Правила упозорења покрећу обавештења",
|
||||
"channelsDesc": "Где се шаљу обавештења",
|
||||
"acknowledge": "Потврди",
|
||||
"ackAll": "Потврди све",
|
||||
"allAcknowledged": "Сва упозорења су потврђена",
|
||||
"ackFailed": "Потврђивање упозорења није успело",
|
||||
"ackAllFailed": "Није успело потврђивање свих упозорења",
|
||||
"showAcknowledged": "Прикажи све",
|
||||
"hideAcknowledged": "Сакриј потврђено",
|
||||
"addChannel": "Додај канал",
|
||||
"editChannel": "Измени канал",
|
||||
"channelName": "Име",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Тема",
|
||||
"ntfyTopicRequired": "Тема је обавезна",
|
||||
"ntfyToken": "Приступни токен (опционо)",
|
||||
"channelSaveFailed": "Чување канала није успело",
|
||||
"test": "Тест",
|
||||
"testSent": "Послато је пробно обавештење",
|
||||
"testFailed": "Обавештење о тестирању није успело",
|
||||
"testFailedWithReason": "Обавештење о тестирању није успело: {{reason}}",
|
||||
"addRule": "Додај правило упозорења",
|
||||
"editRule": "Измени правило упозорења",
|
||||
"ruleName": "Назив правила",
|
||||
"ruleNameRequired": "Име је обавезно",
|
||||
"triggerType": "Окидач",
|
||||
"thresholdValue": "Праг (%)",
|
||||
"durationSeconds": "Трајање (секунде, 0 = одмах се активира)",
|
||||
"cooldownMinutes": "Хлађење (минути)",
|
||||
"channels": "Канали за обавештења",
|
||||
"noChannelsHint": "Прво додајте канале на картици Канали",
|
||||
"ruleSaveFailed": "Чување правила није успело"
|
||||
"channelSaveFailed": "Чување канала није успело"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Асистент",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Belastning...",
|
||||
"noData": "Inga data tillgängliga",
|
||||
"allClear": "Faran över",
|
||||
"acknowledgeAlert": "Erkänna",
|
||||
"noPingUrls": "Inga webbadresser konfigurerade",
|
||||
"pingLabel": "Märka",
|
||||
"addPingUrl": "Lägg till URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Historiskt diagram över CPU, minne, disk eller nätverk för en värd",
|
||||
"widgetHostGridName": "Värdnät",
|
||||
"widgetHostGridDesc": "Rutnätsvy över SSH-värdstatusar",
|
||||
"widgetAlertFeedName": "Aviseringsflöde",
|
||||
"widgetAlertFeedDesc": "Live-avfyrningar med bekräftelsestöd",
|
||||
"widgetPingStatusName": "Pingstatus",
|
||||
"widgetPingStatusDesc": "HTTP-pingstatus för en eller flera URL:er",
|
||||
"widgetRecentActivityName": "Senaste aktivitet",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Spedition",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Anslutningar",
|
||||
"alerts": "Aviseringar",
|
||||
"serial": "Serie",
|
||||
"keepSideMenuOpen": "Håll menyn öppen",
|
||||
"collapseSideMenu": "Minimera menyn",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Visa historik"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Aviseringar",
|
||||
"tabRules": "Regler",
|
||||
"tabChannels": "Kanaler",
|
||||
"noFirings": "Inga okvitterade varningar",
|
||||
"noRules": "Inga varningsregler konfigurerade",
|
||||
"noChannels": "Inga aviseringskanaler konfigurerade",
|
||||
"rulesDesc": "Aviseringsregler utlöser aviseringar",
|
||||
"channelsDesc": "Vart varningsmeddelanden skickas",
|
||||
"acknowledge": "Erkänna",
|
||||
"ackAll": "Bekräfta alla",
|
||||
"allAcknowledged": "Alla varningar bekräftade",
|
||||
"ackFailed": "Kunde inte bekräfta varningen",
|
||||
"ackAllFailed": "Kunde inte bekräfta alla varningar",
|
||||
"showAcknowledged": "Visa alla",
|
||||
"hideAcknowledged": "Dölj godkända",
|
||||
"addChannel": "Lägg till kanal",
|
||||
"editChannel": "Redigera kanal",
|
||||
"channelName": "Namn",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Ämne",
|
||||
"ntfyTopicRequired": "Ämne är obligatoriskt",
|
||||
"ntfyToken": "Åtkomsttoken (valfritt)",
|
||||
"channelSaveFailed": "Misslyckades med att spara kanalen",
|
||||
"test": "Testa",
|
||||
"testSent": "Testmeddelande skickat",
|
||||
"testFailed": "Testmeddelandet misslyckades",
|
||||
"testFailedWithReason": "Testmeddelande misslyckades: {{reason}}",
|
||||
"addRule": "Lägg till varningsregel",
|
||||
"editRule": "Redigera varningsregel",
|
||||
"ruleName": "Regelnamn",
|
||||
"ruleNameRequired": "Namn krävs",
|
||||
"triggerType": "Utlösare",
|
||||
"thresholdValue": "Tröskelvärde (%)",
|
||||
"durationSeconds": "Varaktighet (sekunder, 0 = avfyras omedelbart)",
|
||||
"cooldownMinutes": "Nedkylningstid (minuter)",
|
||||
"channels": "Aviseringskanaler",
|
||||
"noChannelsHint": "Lägg först till kanaler på fliken Kanaler",
|
||||
"ruleSaveFailed": "Det gick inte att spara regeln"
|
||||
"channelSaveFailed": "Misslyckades med att spara kanalen"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Assistent",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "กำลังโหลด...",
|
||||
"noData": "ไม่มีข้อมูล",
|
||||
"allClear": "ทุกอย่างเรียบร้อย",
|
||||
"acknowledgeAlert": "รับทราบ",
|
||||
"noPingUrls": "ไม่มีการกำหนดค่า URL",
|
||||
"pingLabel": "ฉลาก",
|
||||
"addPingUrl": "เพิ่ม URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "แผนภูมิแสดงประวัติการใช้งาน CPU, หน่วยความจำ, ดิสก์ หรือเครือข่ายสำหรับโฮสต์",
|
||||
"widgetHostGridName": "โฮสต์กริด",
|
||||
"widgetHostGridDesc": "การแสดงผลแบบตารางของสถานะโฮสต์ SSH",
|
||||
"widgetAlertFeedName": "ฟีดแจ้งเตือน",
|
||||
"widgetAlertFeedDesc": "การแจ้งเตือนแบบเรียลไทม์พร้อมการยืนยันการรับการแจ้งเตือน",
|
||||
"widgetPingStatusName": "สถานะการปิง",
|
||||
"widgetPingStatusDesc": "สถานะการ ping HTTP สำหรับ URL หนึ่งรายการขึ้นไป",
|
||||
"widgetRecentActivityName": "กิจกรรมล่าสุด",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "การส่งต่อ",
|
||||
"sftp": "เอสเอฟทีพี",
|
||||
"connections": "การเชื่อมต่อ",
|
||||
"alerts": "การแจ้งเตือน",
|
||||
"serial": "ซีเรียล",
|
||||
"keepSideMenuOpen": "เปิดเมนูค้างไว้",
|
||||
"collapseSideMenu": "ยุบเมนู",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "ประวัติการดู"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "การแจ้งเตือน",
|
||||
"tabRules": "กฎ",
|
||||
"tabChannels": "ช่องต่างๆ",
|
||||
"noFirings": "ไม่มีการแจ้งเตือนที่ยังไม่ได้รับการยืนยัน",
|
||||
"noRules": "ไม่มีการกำหนดค่ากฎการแจ้งเตือน",
|
||||
"noChannels": "ไม่ได้กำหนดค่าช่องทางการแจ้งเตือนใดๆ",
|
||||
"rulesDesc": "กฎการแจ้งเตือนจะเรียกใช้การแจ้งเตือน",
|
||||
"channelsDesc": "ส่งการแจ้งเตือนไปยังที่ต่างๆ",
|
||||
"acknowledge": "รับทราบ",
|
||||
"ackAll": "ยอมรับทั้งหมด",
|
||||
"allAcknowledged": "รับทราบการแจ้งเตือนทั้งหมดแล้ว",
|
||||
"ackFailed": "ไม่ตอบรับการแจ้งเตือน",
|
||||
"ackAllFailed": "ไม่สามารถตอบรับการแจ้งเตือนทั้งหมดได้",
|
||||
"showAcknowledged": "แสดงทั้งหมด",
|
||||
"hideAcknowledged": "ซ่อน ตอบรับ",
|
||||
"addChannel": "เพิ่มช่องทาง",
|
||||
"editChannel": "แก้ไขช่อง",
|
||||
"channelName": "ชื่อ",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "หัวข้อ",
|
||||
"ntfyTopicRequired": "จำเป็นต้องระบุหัวข้อ",
|
||||
"ntfyToken": "โทเค็นการเข้าถึง (ไม่บังคับ)",
|
||||
"channelSaveFailed": "ไม่สามารถบันทึกช่องได้",
|
||||
"test": "ทดสอบ",
|
||||
"testSent": "ส่งการแจ้งเตือนการทดสอบแล้ว",
|
||||
"testFailed": "การแจ้งเตือนการทดสอบล้มเหลว",
|
||||
"testFailedWithReason": "การแจ้งเตือนการทดสอบล้มเหลว: {{reason}}",
|
||||
"addRule": "เพิ่มกฎการแจ้งเตือน",
|
||||
"editRule": "แก้ไขกฎการแจ้งเตือน",
|
||||
"ruleName": "ชื่อกฎ",
|
||||
"ruleNameRequired": "ต้องระบุชื่อ",
|
||||
"triggerType": "สิ่งกระตุ้น",
|
||||
"thresholdValue": "เกณฑ์ (%)",
|
||||
"durationSeconds": "ระยะเวลา (วินาที, 0 = ยิงทันที)",
|
||||
"cooldownMinutes": "ระยะเวลาคูลดาวน์ (นาที)",
|
||||
"channels": "ช่องทางการแจ้งเตือน",
|
||||
"noChannelsHint": "เพิ่มช่องในแท็บช่องก่อน",
|
||||
"ruleSaveFailed": "ไม่สามารถบันทึกกฎได้"
|
||||
"channelSaveFailed": "ไม่สามารถบันทึกช่องได้"
|
||||
},
|
||||
"ai": {
|
||||
"title": "ผู้ช่วย",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Yükleniyor...",
|
||||
"noData": "Veri yok",
|
||||
"allClear": "Her şey normal",
|
||||
"acknowledgeAlert": "Onayla",
|
||||
"noPingUrls": "Hiçbir URL yapılandırılmamış",
|
||||
"pingLabel": "Etiket",
|
||||
"addPingUrl": "URL Ekle",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Bir sunucu için geçmiş CPU, bellek, disk veya ağ grafiği",
|
||||
"widgetHostGridName": "Sunucu Izgarası",
|
||||
"widgetHostGridDesc": "SSH sunucu durumlarının ızgara görünümü",
|
||||
"widgetAlertFeedName": "Uyarı Akışı",
|
||||
"widgetAlertFeedDesc": "Onay desteği ile canlı uyarı tetiklemeleri",
|
||||
"widgetPingStatusName": "Ping Durumu",
|
||||
"widgetPingStatusDesc": "Bir veya daha fazla URL için HTTP ping durumu",
|
||||
"widgetRecentActivityName": "Son Etkinlikler",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "İletme",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Bağlantılar",
|
||||
"alerts": "Uyarılar",
|
||||
"serial": "Seri",
|
||||
"keepSideMenuOpen": "Menüyü Açık Tutun",
|
||||
"collapseSideMenu": "Menüyü Daralt",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Geçmişi Görüntüle"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Uyarılar",
|
||||
"tabRules": "Kurallar",
|
||||
"tabChannels": "Kanallar",
|
||||
"noFirings": "Onaylanmamış uyarı yok",
|
||||
"noRules": "Uyarı kuralı yapılandırılmamış",
|
||||
"noChannels": "Bildirim kanalı yapılandırılmamış",
|
||||
"rulesDesc": "Uyarı kuralları bildirimleri tetikler",
|
||||
"channelsDesc": "Uyarı bildirimlerinin gönderildiği yer",
|
||||
"acknowledge": "Onayla",
|
||||
"ackAll": "Tümünü Onayla",
|
||||
"allAcknowledged": "Tüm uyarılar onaylandı",
|
||||
"ackFailed": "Uyarı onaylanamadı",
|
||||
"ackAllFailed": "Tüm uyarılar onaylanamadı",
|
||||
"showAcknowledged": "Tümünü Göster",
|
||||
"hideAcknowledged": "Onaylananları Gizle",
|
||||
"addChannel": "Kanal Ekle",
|
||||
"editChannel": "Kanalı Düzenle",
|
||||
"channelName": "Ad",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Konu",
|
||||
"ntfyTopicRequired": "Konu zorunludur",
|
||||
"ntfyToken": "Erişim Anahtarı (isteğe bağlı)",
|
||||
"channelSaveFailed": "Kanal kaydedilemedi",
|
||||
"test": "Test Et",
|
||||
"testSent": "Test bildirimi gönderildi",
|
||||
"testFailed": "Test bildirimi başarısız oldu",
|
||||
"testFailedWithReason": "Test bildirimi başarısız oldu: {{reason}}",
|
||||
"addRule": "Uyarı Kuralı Ekle",
|
||||
"editRule": "Uyarı Kuralını Düzenle",
|
||||
"ruleName": "Kural Adı",
|
||||
"ruleNameRequired": "Ad zorunludur",
|
||||
"triggerType": "Tetikleyici",
|
||||
"thresholdValue": "Eşik (%)",
|
||||
"durationSeconds": "Süre (saniye, 0 = hemen tetikle)",
|
||||
"cooldownMinutes": "Bekleme süresi (dakika)",
|
||||
"channels": "Bildirim Kanalları",
|
||||
"noChannelsHint": "Önce Kanallar sekmesinde kanal ekleyin",
|
||||
"ruleSaveFailed": "Kural kaydedilemedi"
|
||||
"channelSaveFailed": "Kanal kaydedilemedi"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Asistan",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Завантаження...",
|
||||
"noData": "Дані недоступні",
|
||||
"allClear": "Все чисто",
|
||||
"acknowledgeAlert": "Підтвердити",
|
||||
"noPingUrls": "URL-адреси не налаштовано",
|
||||
"pingLabel": "Мітка",
|
||||
"addPingUrl": "Додати URL-адресу",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Історична діаграма процесора, пам'яті, диска або мережі для хоста",
|
||||
"widgetHostGridName": "Сітка хоста",
|
||||
"widgetHostGridDesc": "Таблиця статусів хостів SSH",
|
||||
"widgetAlertFeedName": "Стрічка сповіщень",
|
||||
"widgetAlertFeedDesc": "Активація тривог у реальному часі з підтримкою підтвердження",
|
||||
"widgetPingStatusName": "Стан пінгу",
|
||||
"widgetPingStatusDesc": "Статус HTTP-пінгу для однієї або кількох URL-адрес",
|
||||
"widgetRecentActivityName": "Нещодавня активність",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Переадресація",
|
||||
"sftp": "SFTP",
|
||||
"connections": "З'єднання",
|
||||
"alerts": "Сповіщення",
|
||||
"serial": "Серійний",
|
||||
"keepSideMenuOpen": "Залишити меню відкритим",
|
||||
"collapseSideMenu": "Згорнути меню",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Переглянути історію"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Сповіщення",
|
||||
"tabRules": "Правила",
|
||||
"tabChannels": "Канали",
|
||||
"noFirings": "Немає непідтверджених сповіщень",
|
||||
"noRules": "Правила сповіщень не налаштовано",
|
||||
"noChannels": "Канали сповіщень не налаштовано",
|
||||
"rulesDesc": "Правила сповіщень запускають сповіщення",
|
||||
"channelsDesc": "Куди надсилаються сповіщення",
|
||||
"acknowledge": "Підтвердити",
|
||||
"ackAll": "Підтвердження всім",
|
||||
"allAcknowledged": "Усі сповіщення підтверджено",
|
||||
"ackFailed": "Не вдалося підтвердити сповіщення",
|
||||
"ackAllFailed": "Не вдалося підтвердити всі сповіщення",
|
||||
"showAcknowledged": "Показати все",
|
||||
"hideAcknowledged": "Приховати підтверджено",
|
||||
"addChannel": "Додати канал",
|
||||
"editChannel": "Редагувати канал",
|
||||
"channelName": "Ім'я",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Тема",
|
||||
"ntfyTopicRequired": "Тема обов'язкова",
|
||||
"ntfyToken": "Маркер доступу (необов'язково)",
|
||||
"channelSaveFailed": "Не вдалося зберегти канал",
|
||||
"test": "Тест",
|
||||
"testSent": "Тестове сповіщення надіслано",
|
||||
"testFailed": "Не вдалося виконати тестове сповіщення",
|
||||
"testFailedWithReason": "Не вдалося виконати тестове сповіщення: {{reason}}",
|
||||
"addRule": "Додати правило сповіщення",
|
||||
"editRule": "Редагувати правило сповіщення",
|
||||
"ruleName": "Назва правила",
|
||||
"ruleNameRequired": "Ім'я обов'язкове",
|
||||
"triggerType": "Тригер",
|
||||
"thresholdValue": "Поріг (%)",
|
||||
"durationSeconds": "Тривалість (секунди, 0 = негайно спрацьовувати)",
|
||||
"cooldownMinutes": "Охолодження (хвилини)",
|
||||
"channels": "Канали сповіщень",
|
||||
"noChannelsHint": "Спочатку додайте канали на вкладці «Канали»",
|
||||
"ruleSaveFailed": "Не вдалося зберегти правило"
|
||||
"channelSaveFailed": "Не вдалося зберегти канал"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Помічник",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "Đang tải...",
|
||||
"noData": "Không có dữ liệu",
|
||||
"allClear": "Tất cả ổn",
|
||||
"acknowledgeAlert": "Xác nhận",
|
||||
"noPingUrls": "Chưa cấu hình URL",
|
||||
"pingLabel": "Nhãn",
|
||||
"addPingUrl": "Thêm URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "Biểu đồ lịch sử CPU, bộ nhớ, ổ đĩa hoặc mạng cho một máy chủ",
|
||||
"widgetHostGridName": "Lưới máy chủ",
|
||||
"widgetHostGridDesc": "Hiển thị trạng thái máy chủ SSH dạng lưới",
|
||||
"widgetAlertFeedName": "Luồng cảnh báo",
|
||||
"widgetAlertFeedDesc": "Cảnh báo trực tiếp kèm hỗ trợ xác nhận",
|
||||
"widgetPingStatusName": "Trạng thái ping",
|
||||
"widgetPingStatusDesc": "Trạng thái ping HTTP cho một hoặc nhiều URL",
|
||||
"widgetRecentActivityName": "Hoạt động gần đây",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "Chuyển tiếp",
|
||||
"sftp": "SFTP",
|
||||
"connections": "Kết nối",
|
||||
"alerts": "Cảnh báo",
|
||||
"serial": "Nối tiếp",
|
||||
"keepSideMenuOpen": "Giữ menu luôn mở",
|
||||
"collapseSideMenu": "Menu thu gọn",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "Xem lịch sử"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "Cảnh báo",
|
||||
"tabRules": "Quy tắc",
|
||||
"tabChannels": "Kênh",
|
||||
"noFirings": "Không có cảnh báo chưa xác nhận",
|
||||
"noRules": "Chưa cấu hình quy tắc cảnh báo",
|
||||
"noChannels": "Chưa cấu hình kênh thông báo",
|
||||
"rulesDesc": "Quy tắc cảnh báo kích hoạt thông báo",
|
||||
"channelsDesc": "Nơi gửi thông báo cảnh báo",
|
||||
"acknowledge": "Xác nhận",
|
||||
"ackAll": "Xác nhận tất cả",
|
||||
"allAcknowledged": "Tất cả cảnh báo đã được xác nhận",
|
||||
"ackFailed": "Không thể xác nhận cảnh báo",
|
||||
"ackAllFailed": "Không thể xác nhận tất cả cảnh báo",
|
||||
"showAcknowledged": "Hiện tất cả",
|
||||
"hideAcknowledged": "Ẩn đã xác nhận",
|
||||
"addChannel": "Thêm kênh",
|
||||
"editChannel": "Sửa kênh",
|
||||
"channelName": "Tên",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "Chủ đề",
|
||||
"ntfyTopicRequired": "Chủ đề là bắt buộc",
|
||||
"ntfyToken": "Mã truy cập (tùy chọn)",
|
||||
"channelSaveFailed": "Lỗi khi lưu kênh",
|
||||
"test": "Kiểm tra",
|
||||
"testSent": "Đã gửi thông báo kiểm tra",
|
||||
"testFailed": "Gửi thông báo kiểm tra thất bại",
|
||||
"testFailedWithReason": "Thông báo kiểm tra thất bại: {{reason}}",
|
||||
"addRule": "Thêm quy tắc cảnh báo",
|
||||
"editRule": "Sửa quy tắc cảnh báo",
|
||||
"ruleName": "Tên quy tắc",
|
||||
"ruleNameRequired": "Tên là bắt buộc",
|
||||
"triggerType": "Kích hoạt",
|
||||
"thresholdValue": "Ngưỡng (%)",
|
||||
"durationSeconds": "Thời gian (giây, 0 = kích hoạt ngay)",
|
||||
"cooldownMinutes": "Thời gian chờ (phút)",
|
||||
"channels": "Kênh thông báo",
|
||||
"noChannelsHint": "Vui lòng thêm kênh trong tab Kênh trước",
|
||||
"ruleSaveFailed": "Lỗi khi lưu quy tắc"
|
||||
"channelSaveFailed": "Lỗi khi lưu kênh"
|
||||
},
|
||||
"ai": {
|
||||
"title": "Trợ lý",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "加载中...",
|
||||
"noData": "暂无数据",
|
||||
"allClear": "一切正常",
|
||||
"acknowledgeAlert": "确认告警",
|
||||
"noPingUrls": "未配置 URL",
|
||||
"pingLabel": "标签",
|
||||
"addPingUrl": "添加 URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "主机的历史 CPU、内存、磁盘或网络图表",
|
||||
"widgetHostGridName": "主机网格",
|
||||
"widgetHostGridDesc": "SSH 主机状态的网格视图",
|
||||
"widgetAlertFeedName": "告警信息流",
|
||||
"widgetAlertFeedDesc": "实时告警触发信息,支持确认操作",
|
||||
"widgetPingStatusName": "Ping 状态",
|
||||
"widgetPingStatusDesc": "一个或多个 URL 的 HTTP ping 状态",
|
||||
"widgetRecentActivityName": "近期活动",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "转发",
|
||||
"sftp": "SFTP",
|
||||
"connections": "连接",
|
||||
"alerts": "告警",
|
||||
"serial": "串口",
|
||||
"keepSideMenuOpen": "保持菜单打开",
|
||||
"collapseSideMenu": "折叠菜单",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "查看历史"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "告警",
|
||||
"tabRules": "规则",
|
||||
"tabChannels": "通道",
|
||||
"noFirings": "没有未确认告警",
|
||||
"noRules": "未配置告警规则",
|
||||
"noChannels": "未配置通知渠道",
|
||||
"rulesDesc": "告警规则触发通知",
|
||||
"channelsDesc": "告警通知的发送渠道",
|
||||
"acknowledge": "确认告警",
|
||||
"ackAll": "全部确认",
|
||||
"allAcknowledged": "所有告警已确认",
|
||||
"ackFailed": "确认告警失败",
|
||||
"ackAllFailed": "确认所有告警失败",
|
||||
"showAcknowledged": "显示全部",
|
||||
"hideAcknowledged": "隐藏已确认",
|
||||
"addChannel": "添加渠道",
|
||||
"editChannel": "编辑渠道",
|
||||
"channelName": "名称",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "主题",
|
||||
"ntfyTopicRequired": "主题为必填项",
|
||||
"ntfyToken": "访问令牌(选填)",
|
||||
"channelSaveFailed": "保存渠道失败",
|
||||
"test": "测试",
|
||||
"testSent": "测试通知已发送",
|
||||
"testFailed": "测试通知失败",
|
||||
"testFailedWithReason": "测试通知失败:{{reason}}",
|
||||
"addRule": "添加告警规则",
|
||||
"editRule": "编辑告警规则",
|
||||
"ruleName": "规则名称",
|
||||
"ruleNameRequired": "名称为必填项",
|
||||
"triggerType": "触发条件",
|
||||
"thresholdValue": "阈值(%)",
|
||||
"durationSeconds": "持续时间(秒,0 表示立即触发)",
|
||||
"cooldownMinutes": "冷却时间(分钟)",
|
||||
"channels": "通知渠道",
|
||||
"noChannelsHint": "请先在渠道标签页中添加渠道",
|
||||
"ruleSaveFailed": "保存规则失败"
|
||||
"channelSaveFailed": "保存渠道失败"
|
||||
},
|
||||
"ai": {
|
||||
"title": "助手",
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
"loading": "載入中...",
|
||||
"noData": "無可用資料",
|
||||
"allClear": "一切正常",
|
||||
"acknowledgeAlert": "確認",
|
||||
"noPingUrls": "未設定 URL",
|
||||
"pingLabel": "標籤",
|
||||
"addPingUrl": "新增 URL",
|
||||
@@ -324,8 +323,6 @@
|
||||
"widgetMetricsChartDesc": "主機的歷史 CPU、記憶體、磁碟或網路圖表",
|
||||
"widgetHostGridName": "主機方格",
|
||||
"widgetHostGridDesc": "SSH 主機狀態的方格檢視",
|
||||
"widgetAlertFeedName": "警報動態",
|
||||
"widgetAlertFeedDesc": "即時警報觸發並支援確認",
|
||||
"widgetPingStatusName": "Ping 狀態",
|
||||
"widgetPingStatusDesc": "一個或多個 URL 的 HTTP ping 狀態",
|
||||
"widgetRecentActivityName": "近期活動",
|
||||
@@ -747,7 +744,6 @@
|
||||
"portForwarding": "轉發",
|
||||
"sftp": "SFTP",
|
||||
"connections": "連線",
|
||||
"alerts": "警示",
|
||||
"serial": "序列",
|
||||
"keepSideMenuOpen": "保持菜單打開",
|
||||
"collapseSideMenu": "折疊菜單",
|
||||
@@ -4890,21 +4886,6 @@
|
||||
"viewHistory": "檢視歷史記錄"
|
||||
},
|
||||
"alerts": {
|
||||
"tabFirings": "警示",
|
||||
"tabRules": "規則",
|
||||
"tabChannels": "通道",
|
||||
"noFirings": "沒有未確認的警示",
|
||||
"noRules": "未設定警示規則",
|
||||
"noChannels": "未設定通知通道",
|
||||
"rulesDesc": "警示規則會觸發通知",
|
||||
"channelsDesc": "警示通知傳送的位置",
|
||||
"acknowledge": "確認",
|
||||
"ackAll": "全部確認",
|
||||
"allAcknowledged": "所有警示已確認",
|
||||
"ackFailed": "確認警示失敗",
|
||||
"ackAllFailed": "確認所有警示失敗",
|
||||
"showAcknowledged": "顯示全部",
|
||||
"hideAcknowledged": "隱藏已確認",
|
||||
"addChannel": "新增通道",
|
||||
"editChannel": "編輯通道",
|
||||
"channelName": "名稱",
|
||||
@@ -4917,22 +4898,7 @@
|
||||
"ntfyTopic": "主題",
|
||||
"ntfyTopicRequired": "主題為必填",
|
||||
"ntfyToken": "存取權杖(選填)",
|
||||
"channelSaveFailed": "無法儲存頻道",
|
||||
"test": "測試",
|
||||
"testSent": "已傳送測試通知",
|
||||
"testFailed": "測試通知失敗",
|
||||
"testFailedWithReason": "測試通知失敗: {{reason}}",
|
||||
"addRule": "新增警示規則",
|
||||
"editRule": "編輯警示規則",
|
||||
"ruleName": "規則名稱",
|
||||
"ruleNameRequired": "名稱為必填",
|
||||
"triggerType": "觸發條件",
|
||||
"thresholdValue": "閾值(%)",
|
||||
"durationSeconds": "持續時間(秒,0 = 立即觸發)",
|
||||
"cooldownMinutes": "冷卻時間(分鐘)",
|
||||
"channels": "通知頻道",
|
||||
"noChannelsHint": "請先於「頻道」分頁新增頻道",
|
||||
"ruleSaveFailed": "無法儲存規則"
|
||||
"channelSaveFailed": "無法儲存頻道"
|
||||
},
|
||||
"ai": {
|
||||
"title": "助理",
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
Fingerprint,
|
||||
Hammer,
|
||||
Play,
|
||||
Plug,
|
||||
ScrollText,
|
||||
Sparkles,
|
||||
Presentation,
|
||||
@@ -154,9 +153,6 @@ const FleetInventoryTab = lazy(() =>
|
||||
const TermixIdPanel = lazy(() =>
|
||||
import("@/sidebar/TermixIdPanel").then((m) => ({ default: m.TermixIdPanel })),
|
||||
);
|
||||
const AlertsPanel = lazy(() =>
|
||||
import("@/sidebar/AlertsPanel").then((m) => ({ default: m.AlertsPanel })),
|
||||
);
|
||||
const SessionLogsPanel = lazy(() =>
|
||||
import("@/sidebar/SessionLogsPanel").then((m) => ({
|
||||
default: m.SessionLogsPanel,
|
||||
@@ -391,8 +387,6 @@ export function tabIcon(type: TabType) {
|
||||
return <Presentation className="size-3.5" />;
|
||||
case "termix-id":
|
||||
return <Fingerprint className="size-3.5" />;
|
||||
case "alerts":
|
||||
return <Plug className="size-3.5" />;
|
||||
case "session-logs":
|
||||
return <ScrollText className="size-3.5" />;
|
||||
case "snippets":
|
||||
@@ -721,13 +715,6 @@ export function renderTabContent(
|
||||
</PanelTabFrame>,
|
||||
);
|
||||
|
||||
case "alerts":
|
||||
return withTabSuspense(
|
||||
<PanelTabFrame>
|
||||
<AlertsPanel />
|
||||
</PanelTabFrame>,
|
||||
);
|
||||
|
||||
case "session-logs":
|
||||
return withTabSuspense(
|
||||
<PanelTabFrame>
|
||||
|
||||
@@ -26,7 +26,6 @@ export const WORKSPACE_CAPTURABLE_TYPES: TabType[] = [
|
||||
"homepage",
|
||||
"fleet-inventory",
|
||||
"termix-id",
|
||||
"alerts",
|
||||
"session-logs",
|
||||
"snippets",
|
||||
"macros",
|
||||
@@ -44,7 +43,6 @@ export const HOSTLESS_WORKSPACE_TAB_TYPES: TabType[] = [
|
||||
"fleet-inventory",
|
||||
"docker",
|
||||
"termix-id",
|
||||
"alerts",
|
||||
"session-logs",
|
||||
"snippets",
|
||||
"macros",
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/dialog";
|
||||
import { Button } from "@/components/button";
|
||||
import { Input } from "@/components/input";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
createAlertRule,
|
||||
updateAlertRule,
|
||||
type AlertRule,
|
||||
type NotificationChannel,
|
||||
} from "@/api/alerts-api";
|
||||
|
||||
const TRIGGER_TYPES = [
|
||||
{ value: "host_offline", label: "Host Offline", hasThreshold: false },
|
||||
{ value: "host_online", label: "Host Online", hasThreshold: false },
|
||||
{ value: "cpu_threshold", label: "CPU Usage Threshold", hasThreshold: true },
|
||||
{
|
||||
value: "memory_threshold",
|
||||
label: "Memory Usage Threshold",
|
||||
hasThreshold: true,
|
||||
},
|
||||
{
|
||||
value: "disk_threshold",
|
||||
label: "Disk Usage Threshold",
|
||||
hasThreshold: true,
|
||||
},
|
||||
{
|
||||
value: "health_check_failure",
|
||||
label: "Health Check Failure",
|
||||
hasThreshold: false,
|
||||
},
|
||||
{ value: "user_login", label: "User SSH Login", hasThreshold: false },
|
||||
];
|
||||
|
||||
interface AlertRuleDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
rule: AlertRule | null;
|
||||
channels: NotificationChannel[];
|
||||
onSaved: () => void;
|
||||
}
|
||||
|
||||
export function AlertRuleDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
rule,
|
||||
channels,
|
||||
onSaved,
|
||||
}: AlertRuleDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = useState("");
|
||||
const [triggerType, setTriggerType] = useState("host_offline");
|
||||
const [threshold, setThreshold] = useState("80");
|
||||
const [durationSeconds, setDurationSeconds] = useState("0");
|
||||
const [cooldownMinutes, setCooldownMinutes] = useState("15");
|
||||
const [selectedChannels, setSelectedChannels] = useState<number[]>([]);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const triggerDef = TRIGGER_TYPES.find((tt) => tt.value === triggerType);
|
||||
const hasThreshold = triggerDef?.hasThreshold ?? false;
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
if (rule) {
|
||||
setName(rule.name);
|
||||
setTriggerType(rule.triggerType);
|
||||
setThreshold(
|
||||
rule.thresholdValue != null ? String(rule.thresholdValue) : "80",
|
||||
);
|
||||
setDurationSeconds(
|
||||
rule.thresholdDurationSeconds != null
|
||||
? String(rule.thresholdDurationSeconds)
|
||||
: "0",
|
||||
);
|
||||
setCooldownMinutes(String(rule.cooldownMinutes));
|
||||
setSelectedChannels(rule.channelIds ?? []);
|
||||
} else {
|
||||
setName("");
|
||||
setTriggerType("host_offline");
|
||||
setThreshold("80");
|
||||
setDurationSeconds("0");
|
||||
setCooldownMinutes("15");
|
||||
setSelectedChannels([]);
|
||||
}
|
||||
}, [open, rule]);
|
||||
|
||||
function toggleChannel(id: number) {
|
||||
setSelectedChannels((prev) =>
|
||||
prev.includes(id) ? prev.filter((c) => c !== id) : [...prev, id],
|
||||
);
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!name.trim()) {
|
||||
toast.error(t("alerts.ruleNameRequired", "Name is required"));
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const payload = {
|
||||
name: name.trim(),
|
||||
triggerType,
|
||||
thresholdValue: hasThreshold ? Number(threshold) : null,
|
||||
thresholdDurationSeconds:
|
||||
hasThreshold && Number(durationSeconds) > 0
|
||||
? Number(durationSeconds)
|
||||
: null,
|
||||
cooldownMinutes: Number(cooldownMinutes) || 15,
|
||||
channels: selectedChannels,
|
||||
};
|
||||
if (rule) {
|
||||
await updateAlertRule(rule.id, payload);
|
||||
} else {
|
||||
await createAlertRule(payload);
|
||||
}
|
||||
onSaved();
|
||||
} catch {
|
||||
toast.error(t("alerts.ruleSaveFailed", "Failed to save rule"));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-md max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{rule
|
||||
? t("alerts.editRule", "Edit Alert Rule")
|
||||
: t("alerts.addRule", "Add Alert Rule")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex flex-col gap-3 py-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
|
||||
{t("alerts.ruleName", "Rule Name")}
|
||||
</label>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
|
||||
{t("alerts.triggerType", "Trigger")}
|
||||
</label>
|
||||
<div className="flex flex-col gap-1">
|
||||
{TRIGGER_TYPES.map((tt) => (
|
||||
<button
|
||||
key={tt.value}
|
||||
onClick={() => setTriggerType(tt.value)}
|
||||
className={`flex items-center gap-2 px-2 py-1.5 text-xs border transition-colors text-left ${triggerType === tt.value ? "border-accent-brand/40 bg-accent-brand/10 text-accent-brand" : "border-border text-muted-foreground hover:text-foreground"}`}
|
||||
>
|
||||
<span
|
||||
className={`size-2 shrink-0 rounded-full border-2 ${triggerType === tt.value ? "border-accent-brand bg-accent-brand" : "border-muted-foreground"}`}
|
||||
/>
|
||||
{tt.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasThreshold && (
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
|
||||
{t("alerts.thresholdValue", "Threshold (%)")}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
max={100}
|
||||
value={threshold}
|
||||
onChange={(e) => setThreshold(e.target.value)}
|
||||
className="w-20 text-sm"
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
|
||||
{t(
|
||||
"alerts.durationSeconds",
|
||||
"Duration (seconds, 0 = fire immediately)",
|
||||
)}
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
value={durationSeconds}
|
||||
onChange={(e) => setDurationSeconds(e.target.value)}
|
||||
className="w-24 text-sm"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
|
||||
{t("alerts.cooldownMinutes", "Cooldown (minutes)")}
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
value={cooldownMinutes}
|
||||
onChange={(e) => setCooldownMinutes(e.target.value)}
|
||||
className="w-24 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
|
||||
{t("alerts.channels", "Notification Channels")}
|
||||
</label>
|
||||
{channels.length === 0 ? (
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{t(
|
||||
"alerts.noChannelsHint",
|
||||
"Add channels in the Channels tab first",
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1">
|
||||
{channels.map((ch) => (
|
||||
<button
|
||||
key={ch.id}
|
||||
onClick={() => toggleChannel(ch.id)}
|
||||
className={`flex items-center gap-2 px-2 py-1.5 text-xs border transition-colors text-left ${selectedChannels.includes(ch.id) ? "border-accent-brand/40 bg-accent-brand/10 text-accent-brand" : "border-border text-muted-foreground hover:text-foreground"}`}
|
||||
>
|
||||
<span
|
||||
className={`size-2 shrink-0 rounded-sm border-2 ${selectedChannels.includes(ch.id) ? "border-accent-brand bg-accent-brand" : "border-muted-foreground"}`}
|
||||
/>
|
||||
{ch.name}
|
||||
<span className="ml-auto text-[9px] font-mono uppercase opacity-60">
|
||||
{ch.type}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
{t("common.cancel", "Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
|
||||
>
|
||||
{saving
|
||||
? t("common.saving", "Saving...")
|
||||
: t("common.save", "Save")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,471 +0,0 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Bell,
|
||||
BellOff,
|
||||
Check,
|
||||
CheckCheck,
|
||||
Trash2,
|
||||
Plus,
|
||||
Settings2,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/button";
|
||||
import {
|
||||
getAlertFirings,
|
||||
acknowledgeAlertFiring,
|
||||
acknowledgeAllAlertFirings,
|
||||
getAlertRules,
|
||||
getNotificationChannels,
|
||||
deleteAlertRule,
|
||||
deleteNotificationChannel,
|
||||
testNotificationChannel,
|
||||
type AlertFiring,
|
||||
type AlertRule,
|
||||
type NotificationChannel,
|
||||
} from "@/api/alerts-api";
|
||||
import { AlertRuleDialog } from "./AlertRuleDialog";
|
||||
import { NotificationChannelDialog } from "./NotificationChannelDialog";
|
||||
|
||||
type PanelTab = "firings" | "rules" | "channels";
|
||||
|
||||
const SEVERITY_CLASS: Record<string, string> = {
|
||||
info: "bg-blue-500/20 text-blue-400 border-blue-500/30",
|
||||
warning: "bg-yellow-500/20 text-yellow-400 border-yellow-500/30",
|
||||
critical: "bg-red-500/20 text-red-400 border-red-500/30",
|
||||
};
|
||||
|
||||
function timeAgo(isoStr: string): string {
|
||||
const ms = Date.now() - new Date(isoStr).getTime();
|
||||
if (!Number.isFinite(ms)) return "";
|
||||
const sec = Math.floor(ms / 1000);
|
||||
if (sec < 60) return `${sec}s ago`;
|
||||
const min = Math.floor(sec / 60);
|
||||
if (min < 60) return `${min}m ago`;
|
||||
const hr = Math.floor(min / 60);
|
||||
if (hr < 24) return `${hr}h ago`;
|
||||
return `${Math.floor(hr / 24)}d ago`;
|
||||
}
|
||||
|
||||
export function AlertsPanel() {
|
||||
const { t } = useTranslation();
|
||||
const [tab, setTab] = useState<PanelTab>("firings");
|
||||
|
||||
// Firings state
|
||||
const [firings, setFirings] = useState<AlertFiring[]>([]);
|
||||
const [firingsLoading, setFiringsLoading] = useState(true);
|
||||
const [showAcknowledged, setShowAcknowledged] = useState(false);
|
||||
|
||||
// Rules state
|
||||
const [rules, setRules] = useState<AlertRule[]>([]);
|
||||
const [rulesLoading, setRulesLoading] = useState(false);
|
||||
const [ruleDialogOpen, setRuleDialogOpen] = useState(false);
|
||||
const [editRule, setEditRule] = useState<AlertRule | null>(null);
|
||||
|
||||
// Channels state
|
||||
const [channels, setChannels] = useState<NotificationChannel[]>([]);
|
||||
const [channelsLoading, setChannelsLoading] = useState(false);
|
||||
const [channelDialogOpen, setChannelDialogOpen] = useState(false);
|
||||
const [editChannel, setEditChannel] = useState<NotificationChannel | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const loadFirings = useCallback(() => {
|
||||
setFiringsLoading(true);
|
||||
getAlertFirings({ limit: 100 })
|
||||
.then(setFirings)
|
||||
.catch(() => {})
|
||||
.finally(() => setFiringsLoading(false));
|
||||
}, []);
|
||||
|
||||
const loadRules = useCallback(() => {
|
||||
setRulesLoading(true);
|
||||
getAlertRules()
|
||||
.then(setRules)
|
||||
.catch(() => {})
|
||||
.finally(() => setRulesLoading(false));
|
||||
}, []);
|
||||
|
||||
const loadChannels = useCallback(() => {
|
||||
setChannelsLoading(true);
|
||||
getNotificationChannels()
|
||||
.then(setChannels)
|
||||
.catch(() => {})
|
||||
.finally(() => setChannelsLoading(false));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadFirings();
|
||||
loadChannels();
|
||||
}, [loadFirings, loadChannels]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tab === "rules") loadRules();
|
||||
if (tab === "channels") loadChannels();
|
||||
}, [tab, loadRules, loadChannels]);
|
||||
|
||||
const visibleFirings = firings.filter((f) =>
|
||||
showAcknowledged ? true : !f.acknowledged,
|
||||
);
|
||||
|
||||
async function handleAck(id: number) {
|
||||
try {
|
||||
await acknowledgeAlertFiring(id);
|
||||
setFirings((prev) =>
|
||||
prev.map((f) => (f.id === id ? { ...f, acknowledged: true } : f)),
|
||||
);
|
||||
} catch {
|
||||
toast.error(t("alerts.ackFailed", "Failed to acknowledge alert"));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAckAll() {
|
||||
try {
|
||||
await acknowledgeAllAlertFirings();
|
||||
setFirings((prev) => prev.map((f) => ({ ...f, acknowledged: true })));
|
||||
toast.success(t("alerts.allAcknowledged", "All alerts acknowledged"));
|
||||
} catch {
|
||||
toast.error(t("alerts.ackAllFailed", "Failed to acknowledge all alerts"));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteRule(id: number) {
|
||||
if (!confirm(t("common.confirmDelete", "Delete this rule?"))) return;
|
||||
try {
|
||||
await deleteAlertRule(id);
|
||||
setRules((prev) => prev.filter((r) => r.id !== id));
|
||||
toast.success(t("common.deleted", "Deleted"));
|
||||
} catch {
|
||||
toast.error(t("common.deleteFailed", "Delete failed"));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteChannel(id: number) {
|
||||
if (!confirm(t("common.confirmDelete", "Delete this channel?"))) return;
|
||||
try {
|
||||
await deleteNotificationChannel(id);
|
||||
setChannels((prev) => prev.filter((c) => c.id !== id));
|
||||
toast.success(t("common.deleted", "Deleted"));
|
||||
} catch {
|
||||
toast.error(t("common.deleteFailed", "Delete failed"));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTestChannel(id: number) {
|
||||
try {
|
||||
await testNotificationChannel(id);
|
||||
toast.success(t("alerts.testSent", "Test notification sent"));
|
||||
} catch (err) {
|
||||
const detail = err instanceof Error ? err.message : undefined;
|
||||
toast.error(
|
||||
detail
|
||||
? t(
|
||||
"alerts.testFailedWithReason",
|
||||
"Test notification failed: {{reason}}",
|
||||
{ reason: detail },
|
||||
)
|
||||
: t("alerts.testFailed", "Test notification failed"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const unreadCount = firings.filter((f) => !f.acknowledged).length;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Tab strip */}
|
||||
<div className="flex shrink-0 border-b border-border">
|
||||
{(["firings", "rules", "channels"] as PanelTab[]).map((t_) => (
|
||||
<button
|
||||
key={t_}
|
||||
onClick={() => setTab(t_)}
|
||||
className={`flex-1 py-2 text-xs font-semibold uppercase tracking-widest transition-colors ${
|
||||
tab === t_
|
||||
? "border-b-2 border-accent-brand text-accent-brand"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{t_ === "firings"
|
||||
? t("alerts.tabFirings", "Alerts")
|
||||
: t_ === "rules"
|
||||
? t("alerts.tabRules", "Rules")
|
||||
: t("alerts.tabChannels", "Channels")}
|
||||
{t_ === "firings" && unreadCount > 0 && (
|
||||
<span className="ml-1.5 rounded-full bg-destructive px-1 text-[9px] font-bold text-white">
|
||||
{unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Firings tab */}
|
||||
{tab === "firings" && (
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-border shrink-0">
|
||||
<button
|
||||
onClick={() => setShowAcknowledged((v) => !v)}
|
||||
className={`text-[10px] font-semibold px-2 py-1 border transition-colors ${showAcknowledged ? "border-accent-brand/40 bg-accent-brand/10 text-accent-brand" : "border-border text-muted-foreground hover:text-foreground"}`}
|
||||
>
|
||||
{showAcknowledged
|
||||
? t("alerts.hideAcknowledged", "Hide Acked")
|
||||
: t("alerts.showAcknowledged", "Show All")}
|
||||
</button>
|
||||
<div className="flex-1" />
|
||||
{unreadCount > 0 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs text-muted-foreground"
|
||||
onClick={handleAckAll}
|
||||
>
|
||||
<CheckCheck className="size-3 mr-1" />
|
||||
{t("alerts.ackAll", "Ack All")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 text-muted-foreground"
|
||||
onClick={loadFirings}
|
||||
title={t("common.refresh", "Refresh")}
|
||||
>
|
||||
<Settings2 className="size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||
{firingsLoading && (
|
||||
<div className="flex items-center justify-center py-12 text-sm text-muted-foreground">
|
||||
{t("common.loading", "Loading...")}
|
||||
</div>
|
||||
)}
|
||||
{!firingsLoading && visibleFirings.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center py-12 gap-2 text-muted-foreground">
|
||||
<BellOff className="size-8 opacity-40" />
|
||||
<span className="text-xs">
|
||||
{t("alerts.noFirings", "No unacknowledged alerts")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!firingsLoading &&
|
||||
visibleFirings.map((firing) => (
|
||||
<div
|
||||
key={firing.id}
|
||||
className={`flex flex-col gap-1 px-3 py-2.5 border-b border-border transition-colors ${firing.acknowledged ? "opacity-50" : ""}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`shrink-0 text-[9px] font-bold uppercase px-1.5 py-0.5 border ${SEVERITY_CLASS[firing.severity] ?? "bg-muted text-muted-foreground border-border"}`}
|
||||
>
|
||||
{firing.severity}
|
||||
</span>
|
||||
<span className="flex-1 truncate text-xs font-semibold">
|
||||
{firing.hostName}
|
||||
</span>
|
||||
{!firing.acknowledged && (
|
||||
<button
|
||||
onClick={() => handleAck(firing.id)}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground transition-colors"
|
||||
title={t("alerts.acknowledge", "Acknowledge")}
|
||||
>
|
||||
<Check className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-[11px] text-foreground/80">
|
||||
{firing.message}
|
||||
</span>
|
||||
<div className="flex items-center gap-2 text-[10px] text-muted-foreground">
|
||||
{firing.ruleName && <span>{firing.ruleName}</span>}
|
||||
<span>{timeAgo(firing.firedAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Rules tab */}
|
||||
{tab === "rules" && (
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-border shrink-0">
|
||||
<span className="flex-1 text-xs text-muted-foreground">
|
||||
{t("alerts.rulesDesc", "Alert rules trigger notifications")}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
|
||||
onClick={() => {
|
||||
setEditRule(null);
|
||||
setRuleDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus className="size-3 mr-1" />
|
||||
{t("common.add", "Add")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||
{rulesLoading && (
|
||||
<div className="flex items-center justify-center py-12 text-sm text-muted-foreground">
|
||||
{t("common.loading", "Loading...")}
|
||||
</div>
|
||||
)}
|
||||
{!rulesLoading && rules.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center py-12 gap-2 text-muted-foreground">
|
||||
<Bell className="size-8 opacity-40" />
|
||||
<span className="text-xs">
|
||||
{t("alerts.noRules", "No alert rules configured")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!rulesLoading &&
|
||||
rules.map((rule) => (
|
||||
<div
|
||||
key={rule.id}
|
||||
className="flex items-center gap-2 px-3 py-2.5 border-b border-border"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs font-semibold truncate">
|
||||
{rule.name}
|
||||
</div>
|
||||
<div className="text-[10px] text-muted-foreground">
|
||||
{(rule.triggerType || "").replace(/_/g, " ")}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`size-2 shrink-0 rounded-full ${rule.enabled ? "bg-green-500" : "bg-muted"}`}
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditRule(rule);
|
||||
setRuleDialogOpen(true);
|
||||
}}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
title={t("common.edit", "Edit")}
|
||||
>
|
||||
<Settings2 className="size-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteRule(rule.id)}
|
||||
className="shrink-0 text-muted-foreground hover:text-destructive"
|
||||
title={t("common.delete", "Delete")}
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Channels tab */}
|
||||
{tab === "channels" && (
|
||||
<div className="flex flex-col flex-1 min-h-0">
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-border shrink-0">
|
||||
<span className="flex-1 text-xs text-muted-foreground">
|
||||
{t("alerts.channelsDesc", "Where alert notifications are sent")}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
|
||||
onClick={() => {
|
||||
setEditChannel(null);
|
||||
setChannelDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus className="size-3 mr-1" />
|
||||
{t("common.add", "Add")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||
{channelsLoading && (
|
||||
<div className="flex items-center justify-center py-12 text-sm text-muted-foreground">
|
||||
{t("common.loading", "Loading...")}
|
||||
</div>
|
||||
)}
|
||||
{!channelsLoading && channels.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center py-12 gap-2 text-muted-foreground">
|
||||
<Bell className="size-8 opacity-40" />
|
||||
<span className="text-xs">
|
||||
{t(
|
||||
"alerts.noChannels",
|
||||
"No notification channels configured",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!channelsLoading &&
|
||||
channels.map((channel) => (
|
||||
<div
|
||||
key={channel.id}
|
||||
className="flex items-center gap-2 px-3 py-2.5 border-b border-border"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs font-semibold truncate">
|
||||
{channel.name}
|
||||
</div>
|
||||
<div className="text-[10px] text-muted-foreground font-mono uppercase">
|
||||
{channel.type}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`size-2 shrink-0 rounded-full ${channel.enabled ? "bg-green-500" : "bg-muted"}`}
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleTestChannel(channel.id)}
|
||||
className="shrink-0 text-[10px] font-semibold text-muted-foreground hover:text-foreground border border-border px-1.5 py-0.5"
|
||||
title={t("alerts.test", "Test")}
|
||||
>
|
||||
{t("alerts.test", "Test")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEditChannel(channel);
|
||||
setChannelDialogOpen(true);
|
||||
}}
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
title={t("common.edit", "Edit")}
|
||||
>
|
||||
<Settings2 className="size-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteChannel(channel.id)}
|
||||
className="shrink-0 text-muted-foreground hover:text-destructive"
|
||||
title={t("common.delete", "Delete")}
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AlertRuleDialog
|
||||
open={ruleDialogOpen}
|
||||
onOpenChange={setRuleDialogOpen}
|
||||
rule={editRule}
|
||||
channels={channels}
|
||||
onSaved={() => {
|
||||
setRuleDialogOpen(false);
|
||||
loadRules();
|
||||
}}
|
||||
/>
|
||||
|
||||
<NotificationChannelDialog
|
||||
open={channelDialogOpen}
|
||||
onOpenChange={setChannelDialogOpen}
|
||||
channel={editChannel}
|
||||
onSaved={() => {
|
||||
setChannelDialogOpen(false);
|
||||
loadChannels();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+24
-74
@@ -1,7 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, type ReactElement } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Bell,
|
||||
Check,
|
||||
LogOut,
|
||||
PanelRight,
|
||||
@@ -12,7 +11,6 @@ import {
|
||||
User,
|
||||
} from "lucide-react";
|
||||
import type { SplitMode, TabType, ToolsTab } from "@/types/ui-types";
|
||||
import { getAlertFirings } from "@/api/alerts-api";
|
||||
import { isElectron } from "@/lib/electron";
|
||||
import { readRailPreference, setRailPreference } from "./rail-preferences";
|
||||
import { visibleRailItems } from "./rail-items";
|
||||
@@ -31,7 +29,6 @@ export type RailView =
|
||||
| "session-logs"
|
||||
| "user-profile"
|
||||
| "admin-settings"
|
||||
| "alerts"
|
||||
| "automations"
|
||||
| "ai"
|
||||
| "fleets"
|
||||
@@ -148,49 +145,6 @@ export function AppRail({
|
||||
promotable?: boolean;
|
||||
rightDockable?: boolean;
|
||||
} | null>(null);
|
||||
const [unreadAlerts, setUnreadAlerts] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const poll = () => {
|
||||
if (document.visibilityState === "hidden") return;
|
||||
getAlertFirings({ acknowledged: false, limit: 50 })
|
||||
.then((firings) => {
|
||||
if (!cancelled) setUnreadAlerts(firings.length);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const start = () => {
|
||||
if (intervalId !== null) return;
|
||||
intervalId = setInterval(poll, 30000);
|
||||
};
|
||||
const stop = () => {
|
||||
if (intervalId === null) return;
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
};
|
||||
const onVisibility = () => {
|
||||
if (document.visibilityState === "hidden") {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
poll();
|
||||
start();
|
||||
};
|
||||
|
||||
poll();
|
||||
if (document.visibilityState !== "hidden") start();
|
||||
document.addEventListener("visibilitychange", onVisibility);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
stop();
|
||||
document.removeEventListener("visibilitychange", onVisibility);
|
||||
};
|
||||
}, []);
|
||||
const [hiddenTabs, setHiddenTabs] = useState<Set<string>>(() => {
|
||||
try {
|
||||
const stored = localStorage.getItem("hiddenRailTabs");
|
||||
@@ -457,28 +411,29 @@ export function AppRail({
|
||||
style={{ width: railExpanded ? "calc(100% - 16px)" : 20 }}
|
||||
/>
|
||||
)}
|
||||
{[
|
||||
{
|
||||
view: "alerts" as RailView,
|
||||
icon: <Bell size={16} />,
|
||||
title: t("nav.alerts"),
|
||||
promotable: true,
|
||||
},
|
||||
{
|
||||
view: "user-profile" as RailView,
|
||||
icon: <User size={16} />,
|
||||
title: t("nav.userProfile"),
|
||||
},
|
||||
...(isAdmin
|
||||
? [
|
||||
{
|
||||
view: "admin-settings" as RailView,
|
||||
icon: <Settings size={16} />,
|
||||
title: t("nav.admin"),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
].map((item) => (
|
||||
{(
|
||||
[
|
||||
{
|
||||
view: "user-profile" as RailView,
|
||||
icon: <User size={16} />,
|
||||
title: t("nav.userProfile"),
|
||||
},
|
||||
...(isAdmin
|
||||
? [
|
||||
{
|
||||
view: "admin-settings" as RailView,
|
||||
icon: <Settings size={16} />,
|
||||
title: t("nav.admin"),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
] as {
|
||||
view: RailView;
|
||||
icon: ReactElement;
|
||||
title: string;
|
||||
promotable?: boolean;
|
||||
}[]
|
||||
).map((item) => (
|
||||
<button
|
||||
key={item.view}
|
||||
onClick={(e) => {
|
||||
@@ -520,11 +475,6 @@ export function AppRail({
|
||||
style={{ width: 16, height: 16 }}
|
||||
>
|
||||
{item.icon}
|
||||
{item.view === "alerts" && unreadAlerts > 0 && (
|
||||
<span className="absolute -top-1 -right-1 flex size-3 items-center justify-center rounded-full bg-destructive text-[8px] font-bold text-white leading-none">
|
||||
{unreadAlerts > 9 ? "9+" : unreadAlerts}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className={`text-xs font-medium whitespace-nowrap overflow-hidden transition-[opacity,width] duration-150 ${railExpanded ? "opacity-100 delay-75" : "opacity-0 w-0"}`}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
createNotificationChannel,
|
||||
updateNotificationChannel,
|
||||
type NotificationChannel,
|
||||
} from "@/api/alerts-api";
|
||||
} from "@/api/notification-channels-api";
|
||||
|
||||
interface NotificationChannelDialogProps {
|
||||
open: boolean;
|
||||
|
||||
@@ -235,13 +235,6 @@ export function visibleRailItems(): RailItemDef[] {
|
||||
* title and a mobile entry.
|
||||
*/
|
||||
export const RAIL_UTILITY_ITEMS: RailItemDef[] = [
|
||||
{
|
||||
id: "alerts",
|
||||
icon: Plug,
|
||||
labelKey: "nav.alerts",
|
||||
promotable: true,
|
||||
rightDockable: true,
|
||||
},
|
||||
{ id: "user-profile", icon: User, labelKey: "nav.userProfile" },
|
||||
{ id: "admin-settings", icon: Settings, labelKey: "nav.admin" },
|
||||
];
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { mapAlertFiring } from "../../api/alerts-api";
|
||||
|
||||
describe("mapAlertFiring", () => {
|
||||
it("normalizes sqlite snake_case rows for the alerts UI", () => {
|
||||
expect(
|
||||
mapAlertFiring({
|
||||
id: 9,
|
||||
user_id: "u1",
|
||||
rule_id: 4,
|
||||
host_id: 12,
|
||||
host_name: "db-01",
|
||||
fired_at: "2026-06-30 12:00:00",
|
||||
resolved_at: null,
|
||||
value: 91,
|
||||
message: "CPU threshold exceeded",
|
||||
severity: "critical",
|
||||
acknowledged: 0,
|
||||
rule_name: "CPU",
|
||||
}),
|
||||
).toEqual({
|
||||
id: 9,
|
||||
userId: "u1",
|
||||
ruleId: 4,
|
||||
hostId: 12,
|
||||
hostName: "db-01",
|
||||
firedAt: "2026-06-30 12:00:00",
|
||||
resolvedAt: null,
|
||||
value: 91,
|
||||
message: "CPU threshold exceeded",
|
||||
severity: "critical",
|
||||
acknowledged: false,
|
||||
ruleName: "CPU",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses safe defaults for malformed rows", () => {
|
||||
const firing = mapAlertFiring({ acknowledged: 1, severity: "bad" });
|
||||
|
||||
expect(firing.id).toBe(0);
|
||||
expect(firing.hostName).toBe("");
|
||||
expect(firing.severity).toBe("warning");
|
||||
expect(firing.acknowledged).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -16,8 +16,8 @@ describe("humanize", () => {
|
||||
});
|
||||
|
||||
it("splits snake_case into words", () => {
|
||||
expect(humanize("propose_create_alert_rule")).toBe(
|
||||
"Propose create alert rule",
|
||||
expect(humanize("propose_create_automation")).toBe(
|
||||
"Propose create automation",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -51,7 +51,7 @@ describe("fieldLabel", () => {
|
||||
describe("toolLabel", () => {
|
||||
it("phrases tools as actions rather than function names", () => {
|
||||
expect(toolLabel("list_hosts")).toBe("Reading hosts");
|
||||
expect(toolLabel("propose_create_alert_rule")).toBe("Create alert rule");
|
||||
expect(toolLabel("propose_create_fleet")).toBe("Create fleet");
|
||||
});
|
||||
|
||||
it("covers every tool in the catalog", () => {
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
cleanup,
|
||||
fireEvent,
|
||||
} from "@testing-library/react";
|
||||
import type { NotificationChannel } from "@/api/alerts-api";
|
||||
|
||||
const alertsApi = vi.hoisted(() => ({
|
||||
getAlertFirings: vi.fn(async () => []),
|
||||
acknowledgeAlertFiring: vi.fn(async () => {}),
|
||||
acknowledgeAllAlertFirings: vi.fn(async () => {}),
|
||||
getAlertRules: vi.fn(async () => []),
|
||||
getNotificationChannels: vi.fn(async () => [] as NotificationChannel[]),
|
||||
deleteAlertRule: vi.fn(async () => {}),
|
||||
deleteNotificationChannel: vi.fn(async () => {}),
|
||||
testNotificationChannel: vi.fn(async () => {}),
|
||||
}));
|
||||
|
||||
vi.mock("@/api/alerts-api", () => alertsApi);
|
||||
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({
|
||||
t: (_key: string, fallback?: string) => fallback ?? _key,
|
||||
}),
|
||||
}));
|
||||
|
||||
// A real (non-null) fake so tests can see what channels actually reach the
|
||||
// rule dialog, instead of only asserting the API call count.
|
||||
vi.mock("../../sidebar/AlertRuleDialog", () => ({
|
||||
AlertRuleDialog: ({
|
||||
open,
|
||||
channels,
|
||||
}: {
|
||||
open: boolean;
|
||||
channels: NotificationChannel[];
|
||||
}) =>
|
||||
open ? (
|
||||
<div data-testid="rule-dialog-channels">
|
||||
{channels.length === 0
|
||||
? "no channels"
|
||||
: channels.map((c) => c.name).join(",")}
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
vi.mock("../../sidebar/NotificationChannelDialog", () => ({
|
||||
NotificationChannelDialog: () => null,
|
||||
}));
|
||||
|
||||
import { AlertsPanel } from "../../sidebar/AlertsPanel";
|
||||
|
||||
function channel(
|
||||
overrides: Partial<NotificationChannel> = {},
|
||||
): NotificationChannel {
|
||||
return {
|
||||
id: 1,
|
||||
userId: "user-1",
|
||||
name: "ntfy",
|
||||
type: "ntfy",
|
||||
config: "{}",
|
||||
enabled: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
alertsApi.getAlertFirings.mockReset().mockResolvedValue([]);
|
||||
alertsApi.getAlertRules.mockReset().mockResolvedValue([]);
|
||||
alertsApi.getNotificationChannels.mockReset().mockResolvedValue([]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe("AlertsPanel - notification channels", () => {
|
||||
it("loads notification channels on mount, before the Channels tab is ever opened", async () => {
|
||||
alertsApi.getNotificationChannels.mockResolvedValue([channel()]);
|
||||
|
||||
render(<AlertsPanel />);
|
||||
|
||||
// Regression: channels used to only load once the user switched to the
|
||||
// "channels" tab, so editing a rule before ever visiting that tab showed
|
||||
// an empty channel picker even though channels existed.
|
||||
await waitFor(() => {
|
||||
expect(alertsApi.getNotificationChannels).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("populates the rule dialog's channel picker without ever visiting the Channels tab", async () => {
|
||||
alertsApi.getNotificationChannels.mockResolvedValue([
|
||||
channel({ id: 1, name: "ntfy-home" }),
|
||||
channel({ id: 2, name: "webhook-slack" }),
|
||||
]);
|
||||
|
||||
render(<AlertsPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(alertsApi.getNotificationChannels).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Go straight to Rules and open the "new rule" dialog, skipping Channels
|
||||
// entirely, this is exactly the sequence the original bug broke.
|
||||
fireEvent.click(screen.getByText("Rules"));
|
||||
fireEvent.click(screen.getByText("Add"));
|
||||
|
||||
expect(screen.getByTestId("rule-dialog-channels").textContent).toBe(
|
||||
"ntfy-home,webhook-slack",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not refetch channels again when switching to the Rules tab", async () => {
|
||||
render(<AlertsPanel />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(alertsApi.getNotificationChannels).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Switching tabs only reloads rules here, not channels, since channels
|
||||
// were already fetched on mount by the fix under test.
|
||||
fireEvent.click(screen.getByText("Rules"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(alertsApi.getAlertRules).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(alertsApi.getNotificationChannels).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -29,10 +29,6 @@ vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({ t: (key: string) => key }),
|
||||
}));
|
||||
|
||||
vi.mock("@/api/alerts-api", () => ({
|
||||
getAlertFirings: vi.fn().mockResolvedValue([]),
|
||||
}));
|
||||
|
||||
vi.mock("@/hooks/use-ai-availability", () => ({
|
||||
useAiAvailability: () => ({ userEnabled: false }),
|
||||
}));
|
||||
|
||||
@@ -92,7 +92,6 @@ describe("RAIL_ITEMS", () => {
|
||||
"ai",
|
||||
"history",
|
||||
"session-logs",
|
||||
"alerts",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -138,7 +137,6 @@ describe("RAIL_ITEMS", () => {
|
||||
"ai",
|
||||
"history",
|
||||
"session-logs",
|
||||
"alerts",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user