fix: ignore stale SSH update job until running or session start (v0.5.10)

Modal finish only after sawRunning or started_at after sessionOpenedAt, so a previous success no longer closes the window instantly while SAC updater is still running on the host.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-07-08 15:05:56 +10:00
parent e7d5b1c60b
commit 310e7f699d
4 changed files with 38 additions and 2 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
"""Единый источник версии SAC (API, health, логи, OpenAPI)."""
APP_NAME = "Security Alert Center"
APP_VERSION = "0.5.9"
APP_VERSION = "0.5.10"
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
+32 -1
View File
@@ -79,6 +79,7 @@ const props = defineProps<{
message: string;
output: string;
logVisible: boolean;
sessionStartedAt: string;
}>();
const emit = defineEmits<{
@@ -98,6 +99,28 @@ const autoCloseSec = ref(0);
let pollTimer: ReturnType<typeof setInterval> | null = null;
let countdownTimer: ReturnType<typeof setInterval> | null = null;
let finishedEmitted = false;
let sawRunning = false;
function isTerminalJob(job: HostRemoteActionJobStatus): boolean {
const st = (job.status || "").toLowerCase();
return st === "success" || st === "failed";
}
function jobStartedAfterSession(job: HostRemoteActionJobStatus): boolean {
const started = job.started_at;
if (!started) return false;
const jobMs = Date.parse(started);
const sessionMs = Date.parse(props.sessionStartedAt);
if (Number.isNaN(jobMs) || Number.isNaN(sessionMs)) return false;
return jobMs >= sessionMs - 3000;
}
function isJobRunning(job: HostRemoteActionJobStatus): boolean {
if (job.active) return true;
const st = (job.status || "").toLowerCase();
if (st === "running") return true;
return (job.agent_update_state || "").toLowerCase() === "running";
}
const displayTitle = computed(() => {
if (localTitle.value) return localTitle.value;
@@ -159,7 +182,14 @@ async function pollJobOnce() {
if (out) polledOutput.value = out;
if (job.message && isLoading.value) polledMessage.value = job.message;
if (!job.active && job.status !== "running") {
if (isJobRunning(job)) {
sawRunning = true;
return;
}
const canFinish =
isTerminalJob(job) && (sawRunning || jobStartedAfterSession(job));
if (canFinish) {
if (!finishedEmitted) {
finishedEmitted = true;
applyFinishedJob(job);
@@ -180,6 +210,7 @@ function startPoll() {
function resetLocalState() {
finishedEmitted = false;
sawRunning = false;
isLoading.value = true;
ok.value = null;
localTitle.value = "";
@@ -11,6 +11,7 @@
:message="entry.message"
:output="entry.output"
:log-visible="entry.logVisible"
:session-started-at="entry.sessionStartedAt"
@close="closeHostRemoteActionLog(entry.id)"
@toggle-log="toggleHostRemoteActionLog(entry.id)"
@finished="syncHostRemoteActionJobFinished(entry.id, $event)"
@@ -22,6 +22,8 @@ export type HostRemoteActionLogEntry = {
title: string;
message: string;
output: string;
/** ISO-время открытия окна — отсекаем stale success от прошлого job */
sessionStartedAt: string;
};
export const hostRemoteActionLogs = reactive<HostRemoteActionLogEntry[]>([]);
@@ -207,6 +209,7 @@ export async function pollHostRemoteAction(
title,
message: "Выполняется на удалённом хосте…",
output: "",
sessionStartedAt: new Date().toISOString(),
};
hostRemoteActionLogs.push(entry);
@@ -257,6 +260,7 @@ export async function runHostRemoteAction(
title,
message: "Запуск на сервере SAC…",
output: "",
sessionStartedAt: new Date().toISOString(),
};
hostRemoteActionLogs.push(entry);