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:
@@ -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}"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<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">
|
||||||
|
<template v-for="entry in hostRemoteActionLogs" :key="entry.id">
|
||||||
<HostActionLogModal
|
<HostActionLogModal
|
||||||
v-for="entry in openLogs"
|
v-if="entry.open"
|
||||||
:key="entry.id"
|
|
||||||
:open="true"
|
:open="true"
|
||||||
:title="entry.title"
|
:title="entry.title"
|
||||||
:loading="entry.loading"
|
:loading="entry.loading"
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
@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;
|
||||||
|
}
|
||||||
|
if (job.message) {
|
||||||
|
entry.message = job.message;
|
||||||
|
}
|
||||||
|
const nextOutput = (job.output || job.stdout || "").trim();
|
||||||
|
if (nextOutput) {
|
||||||
entry.output = job.output || job.stdout || "";
|
entry.output = job.output || job.stdout || "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishEntry(entry: HostRemoteActionLogEntry, job: HostRemoteActionJobStatus) {
|
function finishEntry(entry: HostRemoteActionLogEntry, job: HostRemoteActionJobStatus) {
|
||||||
|
|||||||
Reference in New Issue
Block a user