fix: live SSH update log re-renders in modal (v0.5.7)

Remove cached openLogs computed that blocked Vue updates to output/loading; preserve log tail on empty poll; auto-scroll log panel.
This commit is contained in:
2026-07-08 14:41:14 +10:00
parent c222dd7d41
commit 69fac2f26e
4 changed files with 53 additions and 23 deletions
+1 -1
View File
@@ -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.6" APP_VERSION = "0.5.7"
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}" APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
+26 -4
View File
@@ -24,9 +24,12 @@
<p v-if="message && !loading" class="host-action-log-message" :class="messageClass">{{ message }}</p> <p v-if="message && !loading" class="host-action-log-message" :class="messageClass">{{ message }}</p>
<p v-else-if="message && loading" class="muted host-action-log-sub host-action-log-message">{{ message }}</p> <p v-else-if="message && loading" class="muted host-action-log-sub host-action-log-message">{{ message }}</p>
<p v-if="!loading && ok" class="muted host-action-log-sub">Окно закроется автоматически через ~30 с</p> <p v-if="!loading && ok" class="muted host-action-log-sub">Окно закроется автоматически через ~30 с</p>
<pre v-if="logVisible" class="host-action-log-output">{{ <pre
output || "Ожидание лога с хоста\n(обновление /var/log/update_script.log)" v-show="logVisible"
}}</pre> ref="logPreRef"
:key="`${output.length}-${loading}`"
class="host-action-log-output"
>{{ displayOutput }}</pre>
<div class="host-action-log-actions"> <div class="host-action-log-actions">
<button <button
v-if="loading || output" v-if="loading || output"
@@ -48,7 +51,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue"; import { computed, nextTick, ref, watch } from "vue";
const props = defineProps<{ const props = defineProps<{
open: boolean; open: boolean;
@@ -65,10 +68,29 @@ const emit = defineEmits<{
"toggle-log": []; "toggle-log": [];
}>(); }>();
const logPreRef = ref<HTMLElement | null>(null);
const displayOutput = computed(() => {
const text = props.output.trim();
return text || "Ожидание лога с хоста…\n(обновление /var/log/update_script.log)";
});
const messageClass = computed(() => { const messageClass = computed(() => {
if (props.loading || props.ok == null) return "muted"; if (props.loading || props.ok == null) return "muted";
return props.ok ? "success" : "error"; return props.ok ? "success" : "error";
}); });
watch(
() => [props.output, props.loading, props.logVisible] as const,
async () => {
if (!props.logVisible) return;
await nextTick();
const el = logPreRef.value;
if (el) {
el.scrollTop = el.scrollHeight;
}
},
);
</script> </script>
<style scoped> <style scoped>
+16 -15
View File
@@ -1,18 +1,19 @@
<template> <template>
<div v-if="openLogs.length" class="host-action-log-stack" aria-live="polite"> <div v-if="hasOpenLogs" class="host-action-log-stack" aria-live="polite">
<HostActionLogModal <template v-for="entry in hostRemoteActionLogs" :key="entry.id">
v-for="entry in openLogs" <HostActionLogModal
:key="entry.id" v-if="entry.open"
:open="true" :open="true"
:title="entry.title" :title="entry.title"
:loading="entry.loading" :loading="entry.loading"
:ok="entry.ok" :ok="entry.ok"
:message="entry.message" :message="entry.message"
:output="entry.output" :output="entry.output"
:log-visible="entry.logVisible" :log-visible="entry.logVisible"
@close="closeHostRemoteActionLog(entry.id)" @close="closeHostRemoteActionLog(entry.id)"
@toggle-log="toggleHostRemoteActionLog(entry.id)" @toggle-log="toggleHostRemoteActionLog(entry.id)"
/> />
</template>
</div> </div>
</template> </template>
@@ -25,7 +26,7 @@ import {
toggleHostRemoteActionLog, toggleHostRemoteActionLog,
} from "../composables/useHostRemoteAction"; } from "../composables/useHostRemoteAction";
const openLogs = computed(() => hostRemoteActionLogs.filter((e) => e.open)); const hasOpenLogs = computed(() => hostRemoteActionLogs.some((e) => e.open));
</script> </script>
<style scoped> <style scoped>
@@ -82,9 +82,16 @@ function scheduleSuccessAutoClose(entryId: string) {
} }
function applyJobToEntry(entry: HostRemoteActionLogEntry, job: HostRemoteActionJobStatus) { function applyJobToEntry(entry: HostRemoteActionLogEntry, job: HostRemoteActionJobStatus) {
entry.title = job.title || entry.title; if (job.title) {
entry.message = job.message || entry.message; entry.title = job.title;
entry.output = job.output || job.stdout || ""; }
if (job.message) {
entry.message = job.message;
}
const nextOutput = (job.output || job.stdout || "").trim();
if (nextOutput) {
entry.output = job.output || job.stdout || "";
}
} }
function finishEntry(entry: HostRemoteActionLogEntry, job: HostRemoteActionJobStatus) { function finishEntry(entry: HostRemoteActionLogEntry, job: HostRemoteActionJobStatus) {