mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 04:20:45 +08:00
fix(ume): forward stats by rule_key, rule toggle, and list filters
Aggregate push counts per monitor rule, add enabled PATCH and paginated filtered list API/UI so keyword rules show correct stats without deleting. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
300fd74565
commit
17270a26b6
10 changed files with 286 additions and 21 deletions
|
|
@ -94,6 +94,7 @@ def maybe_forward_key_alert(
|
|||
rule_label=str(rule.label or ""),
|
||||
)
|
||||
payload["ne"] = _ne_payload(db, ne_id)
|
||||
payload["rule_key"] = str(rule.notification_id or "")
|
||||
|
||||
queued = enqueue_alarm_forward(payload)
|
||||
if not queued:
|
||||
|
|
@ -104,6 +105,7 @@ def maybe_forward_key_alert(
|
|||
row = UmeKeyAlertForwardLog(
|
||||
alarm_key=str(alarm_key or ""),
|
||||
action=act,
|
||||
rule_key=str(rule.notification_id or ""),
|
||||
notification_id=notification_id_from_norm(norm),
|
||||
forwarded_at=_utc_now_naive(),
|
||||
oclaw_ok=0,
|
||||
|
|
@ -112,6 +114,7 @@ def maybe_forward_key_alert(
|
|||
db.add(row)
|
||||
else:
|
||||
row.notification_id = notification_id_from_norm(norm)
|
||||
row.rule_key = str(rule.notification_id or "")
|
||||
row.forwarded_at = _utc_now_naive()
|
||||
row.oclaw_ok = 0
|
||||
row.error = "queued"
|
||||
|
|
@ -122,7 +125,14 @@ def maybe_forward_key_alert(
|
|||
return True
|
||||
|
||||
|
||||
def record_forward_result(*, alarm_key: str, action: str, ok: bool, error: str = "") -> None:
|
||||
def record_forward_result(
|
||||
*,
|
||||
alarm_key: str,
|
||||
action: str,
|
||||
ok: bool,
|
||||
error: str = "",
|
||||
rule_key: str = "",
|
||||
) -> None:
|
||||
from .db import SessionLocal
|
||||
|
||||
key = str(alarm_key or "").strip()
|
||||
|
|
@ -140,12 +150,15 @@ def record_forward_result(*, alarm_key: str, action: str, ok: bool, error: str =
|
|||
row = UmeKeyAlertForwardLog(
|
||||
alarm_key=key,
|
||||
action=act,
|
||||
rule_key=str(rule_key or "").strip(),
|
||||
forwarded_at=_utc_now_naive(),
|
||||
oclaw_ok=1 if ok else 0,
|
||||
error="" if ok else str(error or "forward_failed")[:240],
|
||||
)
|
||||
db.add(row)
|
||||
else:
|
||||
if rule_key and not str(row.rule_key or "").strip():
|
||||
row.rule_key = str(rule_key or "").strip()
|
||||
row.forwarded_at = _utc_now_naive()
|
||||
row.oclaw_ok = 1 if ok else 0
|
||||
row.error = "" if ok else str(error or "forward_failed")[:240]
|
||||
|
|
|
|||
105
netx_api/main.py
105
netx_api/main.py
|
|
@ -735,6 +735,10 @@ def _migrate_key_alert_rule_schema() -> None:
|
|||
"UPDATE ume_key_alert_monitor_config SET forward_on_clear = 1, updated_at = NOW() "
|
||||
"WHERE id = 1 AND EXISTS (SELECT 1 FROM ume_key_alert_rule WHERE forward_on_clear = 1)",
|
||||
),
|
||||
(
|
||||
"add forward_log rule_key",
|
||||
"ALTER TABLE ume_key_alert_forward_log ADD COLUMN IF NOT EXISTS rule_key VARCHAR(128) DEFAULT ''",
|
||||
),
|
||||
]
|
||||
for label, sql in steps:
|
||||
try:
|
||||
|
|
@ -1266,28 +1270,63 @@ def ume_alarm_subscription_clear_local(db: Session = Depends(get_db)) -> dict[st
|
|||
|
||||
|
||||
@app.get("/v1/ume/key-alert-rules")
|
||||
def ume_list_key_alert_rules(db: Session = Depends(get_db)) -> dict[str, Any]:
|
||||
from sqlalchemy import func
|
||||
def ume_list_key_alert_rules(
|
||||
db: Session = Depends(get_db),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=50, ge=1, le=200),
|
||||
keyword: str = Query(default=""),
|
||||
enabled: str | None = Query(default=None),
|
||||
match_type: str | None = Query(default=None),
|
||||
) -> dict[str, Any]:
|
||||
from sqlalchemy import func, or_
|
||||
|
||||
rows = db.query(UmeKeyAlertRule).order_by(UmeKeyAlertRule.notification_id.asc()).all()
|
||||
q = db.query(UmeKeyAlertRule)
|
||||
kw = str(keyword or "").strip()
|
||||
if kw:
|
||||
like = f"%{kw}%"
|
||||
q = q.filter(
|
||||
or_(
|
||||
UmeKeyAlertRule.notification_id.ilike(like),
|
||||
UmeKeyAlertRule.match_value.ilike(like),
|
||||
UmeKeyAlertRule.label.ilike(like),
|
||||
)
|
||||
)
|
||||
if enabled is not None:
|
||||
en = str(enabled).strip().lower()
|
||||
if en in {"1", "true", "yes", "on"}:
|
||||
q = q.filter(UmeKeyAlertRule.enabled == 1)
|
||||
elif en in {"0", "false", "no", "off"}:
|
||||
q = q.filter(UmeKeyAlertRule.enabled == 0)
|
||||
if match_type:
|
||||
mt = normalize_match_type(str(match_type))
|
||||
q = q.filter(UmeKeyAlertRule.match_type == mt)
|
||||
|
||||
total = int(q.count())
|
||||
rows = (
|
||||
q.order_by(UmeKeyAlertRule.notification_id.asc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
stat_rows = (
|
||||
db.query(
|
||||
UmeKeyAlertForwardLog.notification_id,
|
||||
UmeKeyAlertForwardLog.rule_key,
|
||||
func.count(UmeKeyAlertForwardLog.id).label("attempts"),
|
||||
func.sum(UmeKeyAlertForwardLog.oclaw_ok).label("published_ok"),
|
||||
func.max(UmeKeyAlertForwardLog.forwarded_at).label("last_forwarded_at"),
|
||||
)
|
||||
.group_by(UmeKeyAlertForwardLog.notification_id)
|
||||
.filter(UmeKeyAlertForwardLog.rule_key != "")
|
||||
.group_by(UmeKeyAlertForwardLog.rule_key)
|
||||
.all()
|
||||
)
|
||||
stat_map = {
|
||||
str(nid or ""): {
|
||||
str(rk or ""): {
|
||||
"attempts": int(attempts or 0),
|
||||
"published_ok": int(published_ok or 0),
|
||||
"last_forwarded_at": (_ensure_utc(last_at) or datetime.now(timezone.utc)).isoformat() if last_at else "",
|
||||
}
|
||||
for nid, attempts, published_ok, last_at in stat_rows
|
||||
if str(nid or "").strip()
|
||||
for rk, attempts, published_ok, last_at in stat_rows
|
||||
if str(rk or "").strip()
|
||||
}
|
||||
items = [
|
||||
{
|
||||
|
|
@ -1307,15 +1346,32 @@ def ume_list_key_alert_rules(db: Session = Depends(get_db)) -> dict[str, Any]:
|
|||
for row in rows
|
||||
]
|
||||
fwd = forwarder_status()
|
||||
return {"items": items, "total": len(items), "forwarder": fwd}
|
||||
return {"items": items, "total": total, "page": page, "page_size": page_size, "forwarder": fwd}
|
||||
|
||||
|
||||
@app.get("/v1/ume/key-alert-monitor")
|
||||
def ume_key_alert_monitor(db: Session = Depends(get_db)) -> dict[str, Any]:
|
||||
base = ume_list_key_alert_rules(db)
|
||||
def ume_key_alert_monitor(
|
||||
db: Session = Depends(get_db),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=50, ge=1, le=200),
|
||||
keyword: str = Query(default=""),
|
||||
enabled: str | None = Query(default=None),
|
||||
match_type: str | None = Query(default=None),
|
||||
) -> dict[str, Any]:
|
||||
base = ume_list_key_alert_rules(
|
||||
db=db,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
keyword=keyword,
|
||||
enabled=enabled,
|
||||
match_type=match_type,
|
||||
)
|
||||
return {
|
||||
"ok": True,
|
||||
"rules": base.get("items") or [],
|
||||
"total": int(base.get("total") or 0),
|
||||
"page": int(base.get("page") or page),
|
||||
"page_size": int(base.get("page_size") or page_size),
|
||||
"config": get_key_alert_monitor_config(db),
|
||||
"forwarder": base.get("forwarder") or forwarder_status(),
|
||||
}
|
||||
|
|
@ -1375,6 +1431,33 @@ def ume_upsert_key_alert_rule(payload: dict[str, Any], db: Session = Depends(get
|
|||
return {"ok": True, "item": saved}
|
||||
|
||||
|
||||
@app.patch("/v1/ume/key-alert-rules/{rule_key:path}")
|
||||
def ume_patch_key_alert_rule(rule_key: str, payload: dict[str, Any], db: Session = Depends(get_db)) -> dict[str, Any]:
|
||||
key = str(rule_key or "").strip()
|
||||
if not key:
|
||||
raise HTTPException(status_code=400, detail="rule_key_required")
|
||||
row = db.get(UmeKeyAlertRule, key)
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="rule_not_found")
|
||||
if "enabled" not in payload:
|
||||
raise HTTPException(status_code=400, detail="enabled_required")
|
||||
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
row.enabled = 1 if bool(payload.get("enabled")) else 0
|
||||
row.updated_at = now
|
||||
db.commit()
|
||||
invalidate_key_alert_rule_cache()
|
||||
return {
|
||||
"ok": True,
|
||||
"item": {
|
||||
"notification_id": key,
|
||||
"match_type": rule_match_type(row),
|
||||
"match_value": rule_match_value(row),
|
||||
"enabled": bool(int(row.enabled or 0)),
|
||||
"label": str(row.label or ""),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@app.delete("/v1/ume/key-alert-rules/{rule_key:path}")
|
||||
def ume_delete_key_alert_rule(rule_key: str, db: Session = Depends(get_db)) -> dict[str, Any]:
|
||||
key = str(rule_key or "").strip()
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ class UmeKeyAlertForwardLog(Base):
|
|||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
alarm_key: Mapped[str] = mapped_column(Text, index=True)
|
||||
action: Mapped[str] = mapped_column(String(32), default="", index=True)
|
||||
rule_key: Mapped[str] = mapped_column(String(128), default="", index=True)
|
||||
notification_id: Mapped[str] = mapped_column(String(128), default="", index=True)
|
||||
forwarded_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True)
|
||||
oclaw_ok: Mapped[int] = mapped_column(Integer, default=0)
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ def _run_loop() -> None:
|
|||
action=str(payload.get("action") or ""),
|
||||
ok=ok,
|
||||
error=err,
|
||||
rule_key=str(payload.get("rule_key") or ""),
|
||||
)
|
||||
except Exception as rec_exc:
|
||||
_log.warning("forward result record failed: %s", str(rec_exc)[:120])
|
||||
|
|
|
|||
|
|
@ -4,7 +4,14 @@ export const queryKeys = {
|
|||
integrationsStatus: ["integrationsStatus"] as const,
|
||||
umeTokenStatus: ["umeTokenStatus"] as const,
|
||||
umeAlarmSubscription: ["umeAlarmSubscription"] as const,
|
||||
umeKeyAlertMonitor: ["umeKeyAlertMonitor"] as const,
|
||||
umeKeyAlertMonitorAll: ["umeKeyAlertMonitor"] as const,
|
||||
umeKeyAlertMonitor: (
|
||||
page: number,
|
||||
pageSize: number,
|
||||
keyword: string,
|
||||
enabled: string,
|
||||
matchType: string,
|
||||
) => ["umeKeyAlertMonitor", page, pageSize, keyword, enabled, matchType] as const,
|
||||
/** Prefix keys for invalidating all pages/variants of a domain. */
|
||||
umeSyncStatusAll: ["umeSyncStatus"] as const,
|
||||
umeNEAll: ["umeNE"] as const,
|
||||
|
|
|
|||
|
|
@ -367,6 +367,14 @@ const en = {
|
|||
colAttempts: "attempts",
|
||||
colLast: "last push",
|
||||
colActions: "actions",
|
||||
colMonitor: "monitor",
|
||||
monitorOn: "on",
|
||||
monitorOff: "off",
|
||||
filterKeywordPh: "Filter match / label",
|
||||
filterMatchAll: "All types",
|
||||
filterEnabledAll: "All states",
|
||||
filterEnabledOn: "Monitoring on",
|
||||
filterEnabledOff: "Monitoring off",
|
||||
addOk: "Monitor rule added",
|
||||
deleteOk: "Deleted",
|
||||
confirmDelete: "Delete this monitor rule?",
|
||||
|
|
|
|||
|
|
@ -365,6 +365,14 @@ const zh = {
|
|||
colAttempts: "尝试次数",
|
||||
colLast: "最近推送",
|
||||
colActions: "操作",
|
||||
colMonitor: "是否监控",
|
||||
monitorOn: "开启",
|
||||
monitorOff: "关闭",
|
||||
filterKeywordPh: "筛选匹配内容 / 描述",
|
||||
filterMatchAll: "全部类型",
|
||||
filterEnabledAll: "全部状态",
|
||||
filterEnabledOn: "监控中",
|
||||
filterEnabledOff: "已暂停",
|
||||
addOk: "已添加监控规则",
|
||||
deleteOk: "已删除",
|
||||
confirmDelete: "确定删除该监控规则?",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
refreshUmeToken,
|
||||
upsertUmeKeyAlertRule,
|
||||
deleteUmeKeyAlertRule,
|
||||
patchUmeKeyAlertRule,
|
||||
updateUmeKeyAlertMonitorConfig,
|
||||
} from "../services/api";
|
||||
import { HelpHint } from "../components/HelpHint";
|
||||
|
|
@ -56,6 +57,11 @@ export function UmePage() {
|
|||
const [keyAlertIdHints, setKeyAlertIdHints] = useState<
|
||||
Array<{ notification_id: string; native_probable_cause_sample: string }>
|
||||
>([]);
|
||||
const [keyAlertPage, setKeyAlertPage] = useState(1);
|
||||
const [keyAlertPageSize, setKeyAlertPageSize] = useState(20);
|
||||
const [keyAlertFilterKeyword, setKeyAlertFilterKeyword] = useState("");
|
||||
const [keyAlertFilterEnabled, setKeyAlertFilterEnabled] = useState<"" | "true" | "false">("");
|
||||
const [keyAlertFilterMatchType, setKeyAlertFilterMatchType] = useState<"" | "notification_id" | "keyword">("");
|
||||
|
||||
const syncMutation = useMutation({
|
||||
mutationFn: async (domains: string[]) => apiPost<{ ok: boolean; jobs: unknown[] }>("/v1/ume/sync", { domains }),
|
||||
|
|
@ -106,8 +112,21 @@ export function UmePage() {
|
|||
refetchInterval: 5000,
|
||||
});
|
||||
const keyAlertMonitorQuery = useQuery({
|
||||
queryKey: queryKeys.umeKeyAlertMonitor,
|
||||
queryFn: fetchUmeKeyAlertMonitor,
|
||||
queryKey: queryKeys.umeKeyAlertMonitor(
|
||||
keyAlertPage,
|
||||
keyAlertPageSize,
|
||||
keyAlertFilterKeyword,
|
||||
keyAlertFilterEnabled,
|
||||
keyAlertFilterMatchType,
|
||||
),
|
||||
queryFn: () =>
|
||||
fetchUmeKeyAlertMonitor({
|
||||
page: keyAlertPage,
|
||||
pageSize: keyAlertPageSize,
|
||||
keyword: keyAlertFilterKeyword,
|
||||
enabled: keyAlertFilterEnabled,
|
||||
matchType: keyAlertFilterMatchType,
|
||||
}),
|
||||
staleTime: 3000,
|
||||
refetchInterval: 5000,
|
||||
});
|
||||
|
|
@ -306,7 +325,7 @@ export function UmePage() {
|
|||
setKeyAlertMatchValue("");
|
||||
setKeyAlertLabel("");
|
||||
showOk(t("ume.keyAlert.addOk"));
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitor });
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitorAll });
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.integrationsStatus });
|
||||
},
|
||||
onError: (err) => {
|
||||
|
|
@ -321,7 +340,7 @@ export function UmePage() {
|
|||
onMutate: () => setKeyAlertOpError(""),
|
||||
onSuccess: async () => {
|
||||
showOk(t("ume.keyAlert.deleteOk"));
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitor });
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitorAll });
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.integrationsStatus });
|
||||
},
|
||||
onError: (err) => {
|
||||
|
|
@ -331,11 +350,25 @@ export function UmePage() {
|
|||
},
|
||||
});
|
||||
|
||||
const keyAlertToggleMutation = useMutation({
|
||||
mutationFn: (vars: { ruleKey: string; enabled: boolean }) =>
|
||||
patchUmeKeyAlertRule(vars.ruleKey, { enabled: vars.enabled }),
|
||||
onMutate: () => setKeyAlertOpError(""),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitorAll });
|
||||
},
|
||||
onError: (err) => {
|
||||
const msg = String(err);
|
||||
setKeyAlertOpError(msg);
|
||||
showError(msg);
|
||||
},
|
||||
});
|
||||
|
||||
const keyAlertConfigMutation = useMutation({
|
||||
mutationFn: (forwardOnClear: boolean) => updateUmeKeyAlertMonitorConfig({ forward_on_clear: forwardOnClear }),
|
||||
onMutate: () => setKeyAlertOpError(""),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitor });
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.umeKeyAlertMonitorAll });
|
||||
},
|
||||
onError: (err) => {
|
||||
const msg = String(err);
|
||||
|
|
@ -353,6 +386,8 @@ export function UmePage() {
|
|||
|
||||
const keyAlertForwarder = keyAlertMonitorQuery.data?.forwarder;
|
||||
const keyAlertRules = keyAlertMonitorQuery.data?.rules || [];
|
||||
const keyAlertTotal = Number(keyAlertMonitorQuery.data?.total || keyAlertRules.length);
|
||||
const keyAlertPages = pageCount(keyAlertTotal, keyAlertPageSize);
|
||||
const keyAlertForwardOnClear = Boolean(keyAlertMonitorQuery.data?.config?.forward_on_clear);
|
||||
const oclawWsPill =
|
||||
!keyAlertForwarder?.enabled
|
||||
|
|
@ -740,9 +775,43 @@ export function UmePage() {
|
|||
{t("common.opFailed")}: {keyAlertOpError}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="actions-row actions-row--inline" style={{ marginTop: 12, flexWrap: "wrap", gap: 8 }}>
|
||||
<input
|
||||
type="search"
|
||||
placeholder={t("ume.keyAlert.filterKeywordPh")}
|
||||
value={keyAlertFilterKeyword}
|
||||
onChange={(e) => {
|
||||
setKeyAlertFilterKeyword(e.target.value);
|
||||
setKeyAlertPage(1);
|
||||
}}
|
||||
/>
|
||||
<select
|
||||
value={keyAlertFilterMatchType}
|
||||
onChange={(e) => {
|
||||
setKeyAlertFilterMatchType(e.target.value as "" | "notification_id" | "keyword");
|
||||
setKeyAlertPage(1);
|
||||
}}
|
||||
>
|
||||
<option value="">{t("ume.keyAlert.filterMatchAll")}</option>
|
||||
<option value="notification_id">{t("ume.keyAlert.matchNotificationId")}</option>
|
||||
<option value="keyword">{t("ume.keyAlert.matchKeyword")}</option>
|
||||
</select>
|
||||
<select
|
||||
value={keyAlertFilterEnabled}
|
||||
onChange={(e) => {
|
||||
setKeyAlertFilterEnabled(e.target.value as "" | "true" | "false");
|
||||
setKeyAlertPage(1);
|
||||
}}
|
||||
>
|
||||
<option value="">{t("ume.keyAlert.filterEnabledAll")}</option>
|
||||
<option value="true">{t("ume.keyAlert.filterEnabledOn")}</option>
|
||||
<option value="false">{t("ume.keyAlert.filterEnabledOff")}</option>
|
||||
</select>
|
||||
</div>
|
||||
<table style={{ marginTop: 12 }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("ume.keyAlert.colMonitor")}</th>
|
||||
<th>{t("ume.keyAlert.colType")}</th>
|
||||
<th>{t("ume.keyAlert.colMatch")}</th>
|
||||
<th>{t("ume.keyAlert.colLabel")}</th>
|
||||
|
|
@ -754,7 +823,23 @@ export function UmePage() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{keyAlertRules.map((rule) => (
|
||||
<tr key={rule.notification_id}>
|
||||
<tr key={rule.notification_id} className={rule.enabled ? undefined : "row--muted"}>
|
||||
<td>
|
||||
<label className="checkbox-inline">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(rule.enabled)}
|
||||
disabled={keyAlertToggleMutation.isPending}
|
||||
onChange={(e) =>
|
||||
keyAlertToggleMutation.mutate({
|
||||
ruleKey: rule.notification_id,
|
||||
enabled: e.target.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
{rule.enabled ? t("ume.keyAlert.monitorOn") : t("ume.keyAlert.monitorOff")}
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
{rule.match_type === "keyword"
|
||||
? t("ume.keyAlert.matchKeyword")
|
||||
|
|
@ -786,11 +871,44 @@ export function UmePage() {
|
|||
))}
|
||||
{!keyAlertMonitorQuery.isLoading && keyAlertRules.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={7}>{t("ume.keyAlert.emptyRules")}</td>
|
||||
<td colSpan={8}>{t("ume.keyAlert.emptyRules")}</td>
|
||||
</tr>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="pager">
|
||||
<div className="pager__meta">
|
||||
{t("common.pagerMeta", { total: keyAlertTotal, page: keyAlertPage, pages: keyAlertPages })}
|
||||
</div>
|
||||
<div className="pager__controls">
|
||||
<button
|
||||
className="pager__btn"
|
||||
onClick={() => setKeyAlertPage(Math.max(1, keyAlertPage - 1))}
|
||||
disabled={keyAlertPage <= 1}
|
||||
>
|
||||
{t("common.prevPage")}
|
||||
</button>
|
||||
<button
|
||||
className="pager__btn"
|
||||
onClick={() => setKeyAlertPage(keyAlertPage + 1)}
|
||||
disabled={keyAlertPage >= keyAlertPages}
|
||||
>
|
||||
{t("common.nextPage")}
|
||||
</button>
|
||||
<select
|
||||
className="pager__size"
|
||||
value={String(keyAlertPageSize)}
|
||||
onChange={(e) => {
|
||||
setKeyAlertPageSize(Number(e.target.value) || 20);
|
||||
setKeyAlertPage(1);
|
||||
}}
|
||||
>
|
||||
<option value="10">10 / page</option>
|
||||
<option value="20">20 / page</option>
|
||||
<option value="50">50 / page</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article className="card card--full">
|
||||
|
|
|
|||
|
|
@ -80,7 +80,24 @@ export const cancelUmeAlarmSubscription = (opts?: { forceClearLocal?: boolean })
|
|||
export const clearLocalUmeAlarmSubscription = () =>
|
||||
apiPost<UmeAlarmSubscriptionStatus>("/v1/ume/alarm-subscription/clear-local", {});
|
||||
|
||||
export const fetchUmeKeyAlertMonitor = () => apiGet<UmeKeyAlertMonitorResponse>("/v1/ume/key-alert-monitor");
|
||||
export const fetchUmeKeyAlertMonitor = (params?: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
keyword?: string;
|
||||
enabled?: "" | "true" | "false";
|
||||
matchType?: "" | "notification_id" | "keyword";
|
||||
}) => {
|
||||
const p = new URLSearchParams();
|
||||
p.set("page", String(Math.max(1, Number(params?.page || 1))));
|
||||
p.set("page_size", String(Math.max(1, Math.min(200, Number(params?.pageSize || 50)))));
|
||||
const kw = String(params?.keyword || "").trim();
|
||||
if (kw) p.set("keyword", kw);
|
||||
const en = String(params?.enabled || "").trim();
|
||||
if (en) p.set("enabled", en);
|
||||
const mt = String(params?.matchType || "").trim();
|
||||
if (mt) p.set("match_type", mt);
|
||||
return apiGet<UmeKeyAlertMonitorResponse>(`/v1/ume/key-alert-monitor?${p.toString()}`);
|
||||
};
|
||||
|
||||
export const upsertUmeKeyAlertRule = (payload: {
|
||||
match_type: "notification_id" | "keyword";
|
||||
|
|
@ -95,6 +112,12 @@ export const updateUmeKeyAlertMonitorConfig = (payload: { forward_on_clear: bool
|
|||
export const deleteUmeKeyAlertRule = (ruleKey: string) =>
|
||||
apiDelete<{ ok: boolean }>(`/v1/ume/key-alert-rules/${encodeURIComponent(ruleKey)}`);
|
||||
|
||||
export const patchUmeKeyAlertRule = (ruleKey: string, payload: { enabled: boolean }) =>
|
||||
apiPatch<{ ok: boolean; item?: unknown }>(
|
||||
`/v1/ume/key-alert-rules/${encodeURIComponent(ruleKey)}`,
|
||||
payload,
|
||||
);
|
||||
|
||||
export const fetchUmeNotificationIds = (limit = 200) =>
|
||||
apiGet<{ items: Array<{ notification_id: string; native_probable_cause_sample: string }> }>(
|
||||
`/v1/ume/notification-ids?limit=${encodeURIComponent(String(limit))}`,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ export type UmeKeyAlertForwarderStatus = {
|
|||
export type UmeKeyAlertMonitorResponse = {
|
||||
ok: boolean;
|
||||
rules: UmeKeyAlertRuleItem[];
|
||||
total?: number;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
config?: {
|
||||
forward_on_clear: boolean;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue