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:
@@ -1,5 +1,5 @@
|
|||||||
"""Единый источник версии SAC (API, health, логи, OpenAPI)."""
|
"""Единый источник версии SAC (API, health, логи, OpenAPI)."""
|
||||||
|
|
||||||
APP_NAME = "Security Alert Center"
|
APP_NAME = "Security Alert Center"
|
||||||
APP_VERSION = "0.5.9"
|
APP_VERSION = "0.5.10"
|
||||||
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
|
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ const props = defineProps<{
|
|||||||
message: string;
|
message: string;
|
||||||
output: string;
|
output: string;
|
||||||
logVisible: boolean;
|
logVisible: boolean;
|
||||||
|
sessionStartedAt: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -98,6 +99,28 @@ const autoCloseSec = ref(0);
|
|||||||
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
||||||
let countdownTimer: ReturnType<typeof setInterval> | null = null;
|
let countdownTimer: ReturnType<typeof setInterval> | null = null;
|
||||||
let finishedEmitted = false;
|
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(() => {
|
const displayTitle = computed(() => {
|
||||||
if (localTitle.value) return localTitle.value;
|
if (localTitle.value) return localTitle.value;
|
||||||
@@ -159,7 +182,14 @@ async function pollJobOnce() {
|
|||||||
if (out) polledOutput.value = out;
|
if (out) polledOutput.value = out;
|
||||||
if (job.message && isLoading.value) polledMessage.value = job.message;
|
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) {
|
if (!finishedEmitted) {
|
||||||
finishedEmitted = true;
|
finishedEmitted = true;
|
||||||
applyFinishedJob(job);
|
applyFinishedJob(job);
|
||||||
@@ -180,6 +210,7 @@ function startPoll() {
|
|||||||
|
|
||||||
function resetLocalState() {
|
function resetLocalState() {
|
||||||
finishedEmitted = false;
|
finishedEmitted = false;
|
||||||
|
sawRunning = false;
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
ok.value = null;
|
ok.value = null;
|
||||||
localTitle.value = "";
|
localTitle.value = "";
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
:message="entry.message"
|
:message="entry.message"
|
||||||
:output="entry.output"
|
:output="entry.output"
|
||||||
:log-visible="entry.logVisible"
|
:log-visible="entry.logVisible"
|
||||||
|
:session-started-at="entry.sessionStartedAt"
|
||||||
@close="closeHostRemoteActionLog(entry.id)"
|
@close="closeHostRemoteActionLog(entry.id)"
|
||||||
@toggle-log="toggleHostRemoteActionLog(entry.id)"
|
@toggle-log="toggleHostRemoteActionLog(entry.id)"
|
||||||
@finished="syncHostRemoteActionJobFinished(entry.id, $event)"
|
@finished="syncHostRemoteActionJobFinished(entry.id, $event)"
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export type HostRemoteActionLogEntry = {
|
|||||||
title: string;
|
title: string;
|
||||||
message: string;
|
message: string;
|
||||||
output: string;
|
output: string;
|
||||||
|
/** ISO-время открытия окна — отсекаем stale success от прошлого job */
|
||||||
|
sessionStartedAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hostRemoteActionLogs = reactive<HostRemoteActionLogEntry[]>([]);
|
export const hostRemoteActionLogs = reactive<HostRemoteActionLogEntry[]>([]);
|
||||||
@@ -207,6 +209,7 @@ export async function pollHostRemoteAction(
|
|||||||
title,
|
title,
|
||||||
message: "Выполняется на удалённом хосте…",
|
message: "Выполняется на удалённом хосте…",
|
||||||
output: "",
|
output: "",
|
||||||
|
sessionStartedAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
hostRemoteActionLogs.push(entry);
|
hostRemoteActionLogs.push(entry);
|
||||||
|
|
||||||
@@ -257,6 +260,7 @@ export async function runHostRemoteAction(
|
|||||||
title,
|
title,
|
||||||
message: "Запуск на сервере SAC…",
|
message: "Запуск на сервере SAC…",
|
||||||
output: "",
|
output: "",
|
||||||
|
sessionStartedAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
hostRemoteActionLogs.push(entry);
|
hostRemoteActionLogs.push(entry);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user