feat(web-sentinel): add dashboard manual trigger
This commit is contained in:
@@ -152,6 +152,44 @@ select {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.toolbar-button:disabled {
|
||||
cursor: progress;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.trigger-button {
|
||||
border-color: #9fbbb4;
|
||||
background: #eff7f4;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.trigger-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 220px;
|
||||
min-height: 30px;
|
||||
padding: 0 9px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.trigger-status[data-trigger-state="triggered"],
|
||||
.trigger-status[data-trigger-state="already-active"] {
|
||||
border-color: #b9ddc9;
|
||||
background: var(--green-soft);
|
||||
color: #17633f;
|
||||
}
|
||||
|
||||
.trigger-status[data-trigger-state="failed"] {
|
||||
border-color: #efb3ad;
|
||||
background: var(--red-soft);
|
||||
color: #8b1f1a;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 0 28px 0 10px;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ createApp({
|
||||
const initialDeepLink = readMonitorDeepLink();
|
||||
const loading = ref(true);
|
||||
const error = ref("");
|
||||
const manualTriggering = ref(false);
|
||||
const manualTriggerResult = ref(null);
|
||||
const manualTriggerError = ref("");
|
||||
const overview = ref(null);
|
||||
const runs = ref([]);
|
||||
const findings = ref([]);
|
||||
@@ -189,6 +192,20 @@ createApp({
|
||||
text: `${key} ${value?.ok === false ? "异常" : "ok"}`,
|
||||
}));
|
||||
});
|
||||
const manualTriggerState = computed(() => {
|
||||
if (manualTriggering.value) return "triggering";
|
||||
if (manualTriggerError.value) return "failed";
|
||||
return String(manualTriggerResult.value?.status || "idle");
|
||||
});
|
||||
const manualTriggerJobName = computed(() => String(manualTriggerResult.value?.jobName || ""));
|
||||
const manualTriggerStatusText = computed(() => {
|
||||
if (manualTriggering.value) return "触发中";
|
||||
if (manualTriggerError.value) return manualTriggerError.value;
|
||||
const result = manualTriggerResult.value || {};
|
||||
if (result.status === "triggered") return `已触发 ${shortId(result.jobName)}`;
|
||||
if (result.status === "already-active") return `运行中 ${shortId(result.jobName)}`;
|
||||
return "";
|
||||
});
|
||||
|
||||
async function loadAll(options = {}) {
|
||||
if (!options.silent) loading.value = true;
|
||||
@@ -254,6 +271,27 @@ createApp({
|
||||
void loadAll();
|
||||
}
|
||||
|
||||
async function triggerQuickVerify() {
|
||||
if (manualTriggering.value) return;
|
||||
manualTriggering.value = true;
|
||||
manualTriggerError.value = "";
|
||||
manualTriggerResult.value = null;
|
||||
try {
|
||||
const payload = await postJson("/api/quick-verify/trigger", {
|
||||
source: "monitor-web",
|
||||
reason: "manual-web-ui",
|
||||
valuesRedacted: true,
|
||||
});
|
||||
manualTriggerResult.value = payload;
|
||||
autoRefresh.value = true;
|
||||
await loadAll({ silent: true });
|
||||
} catch (cause) {
|
||||
manualTriggerError.value = String(cause?.message || cause).slice(0, 160);
|
||||
} finally {
|
||||
manualTriggering.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function selectCheckRun() {
|
||||
const run = runs.value.find((item) => item.id === checkRunId.value);
|
||||
if (run) void selectRun(run);
|
||||
@@ -429,6 +467,9 @@ createApp({
|
||||
bootstrap,
|
||||
loading,
|
||||
error,
|
||||
manualTriggering,
|
||||
manualTriggerResult,
|
||||
manualTriggerError,
|
||||
overview,
|
||||
runs,
|
||||
findings,
|
||||
@@ -480,12 +521,16 @@ createApp({
|
||||
checkScopeText,
|
||||
cadence,
|
||||
healthChecks,
|
||||
manualTriggerState,
|
||||
manualTriggerJobName,
|
||||
manualTriggerStatusText,
|
||||
loadAll,
|
||||
selectRun,
|
||||
selectCheckRun,
|
||||
openCheckDetail,
|
||||
closeCheckDetail,
|
||||
refreshNow,
|
||||
triggerQuickVerify,
|
||||
currentHref,
|
||||
showTrendTooltip,
|
||||
hideTrendTooltip,
|
||||
@@ -532,7 +577,13 @@ createApp({
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="monitor-shell" data-monitor-shell="true" :data-monitor-status="currentStatus">
|
||||
<div
|
||||
class="monitor-shell"
|
||||
data-monitor-shell="true"
|
||||
:data-monitor-status="currentStatus"
|
||||
:data-manual-trigger-state="manualTriggerState"
|
||||
:data-manual-trigger-job="manualTriggerJobName"
|
||||
>
|
||||
<header class="topbar">
|
||||
<div class="title-block">
|
||||
<div class="mark" aria-hidden="true"></div>
|
||||
@@ -554,6 +605,26 @@ createApp({
|
||||
</select>
|
||||
<button class="toolbar-button" type="button" @click="autoRefresh = !autoRefresh">{{ autoRefresh ? "自动" : "手动" }}</button>
|
||||
<button class="toolbar-button" type="button" @click="refreshNow">刷新</button>
|
||||
<button
|
||||
class="toolbar-button trigger-button"
|
||||
data-monitor-manual-trigger="true"
|
||||
type="button"
|
||||
:disabled="manualTriggering"
|
||||
:data-trigger-state="manualTriggerState"
|
||||
:data-trigger-job="manualTriggerJobName"
|
||||
@click="triggerQuickVerify"
|
||||
>
|
||||
{{ manualTriggering ? "触发中" : "触发巡检" }}
|
||||
</button>
|
||||
<span
|
||||
v-if="manualTriggerStatusText"
|
||||
class="trigger-status"
|
||||
data-monitor-manual-trigger-status="true"
|
||||
:data-trigger-state="manualTriggerState"
|
||||
:data-trigger-job="manualTriggerJobName"
|
||||
>
|
||||
{{ manualTriggerStatusText }}
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -1049,6 +1120,21 @@ async function fetchJson(path) {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async function postJson(path, body) {
|
||||
const response = await fetch(apiUrl(path), {
|
||||
method: "POST",
|
||||
cache: "no-store",
|
||||
headers: { "content-type": "application/json", accept: "application/json" },
|
||||
body: JSON.stringify(body || {}),
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok || payload.ok === false) {
|
||||
const reason = payload.reason || payload.error || `${path} HTTP ${response.status}`;
|
||||
throw new Error(String(reason));
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
function apiUrl(path) {
|
||||
const prefix = bootstrap.basePath || "";
|
||||
const suffix = path.startsWith("/") ? path : `/${path}`;
|
||||
|
||||
Reference in New Issue
Block a user