Files
security-alert-center/frontend/src/components/HostActionLogModal.vue
T
PapaTramp dd2ae51b43 feat: parallel agent upgrades with per-host log panels (0.4.2)
Allow multiple remote update jobs at once; each upgrade gets its own
bottom-right log dock that auto-closes 30s after success.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 10:22:31 +10:00

163 lines
3.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div v-if="open" class="host-action-log-dock" aria-live="polite">
<div class="host-action-log-panel" role="dialog" :aria-label="title">
<div class="host-action-log-header">
<h3>{{ title }}</h3>
<button
type="button"
class="host-action-log-icon-btn"
:title="loading ? 'Свернуть — обновление продолжится на сервере' : 'Закрыть'"
:aria-label="loading ? 'Свернуть' : 'Закрыть'"
@click="emit('close')"
>
×
</button>
</div>
<p v-if="loading" class="host-action-log-status">
<span class="host-action-log-spinner" aria-hidden="true" />
Выполняется на удалённом хосте Можно запустить обновление других хостов.
</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>
<pre v-if="output" class="host-action-log-output">{{ output }}</pre>
<div class="host-action-log-actions">
<button v-if="loading" type="button" class="secondary" @click="emit('close')">
Свернуть
</button>
<button v-else type="button" class="secondary" @click="emit('close')">
Закрыть
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps<{
open: boolean;
title: string;
loading: boolean;
ok: boolean | null;
message: string;
output: string;
}>();
const emit = defineEmits<{
close: [];
}>();
const messageClass = computed(() => {
if (props.loading || props.ok == null) return "muted";
return props.ok ? "success" : "error";
});
</script>
<style scoped>
.host-action-log-dock {
width: 100%;
pointer-events: none;
}
.host-action-log-panel {
pointer-events: auto;
background: var(--card-bg, #1e2430);
border: 1px solid var(--border, #3a4556);
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);
max-height: min(75vh, 36rem);
overflow: auto;
padding: 0.85rem 1rem 1rem;
}
.host-action-log-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.5rem;
}
.host-action-log-header h3 {
margin: 0;
font-size: 1rem;
line-height: 1.3;
}
.host-action-log-icon-btn {
flex-shrink: 0;
width: 1.75rem;
height: 1.75rem;
padding: 0;
border: none;
border-radius: 4px;
background: transparent;
color: #9aa4b2;
font-size: 1.25rem;
line-height: 1;
cursor: pointer;
}
.host-action-log-icon-btn:hover {
background: rgba(255, 255, 255, 0.08);
color: #e6edf3;
}
.host-action-log-status {
display: flex;
align-items: flex-start;
gap: 0.65rem;
margin: 0.65rem 0 0;
color: #9aa4b2;
font-size: 0.9rem;
}
.host-action-log-sub {
margin: 0.35rem 0 0;
font-size: 0.85rem;
}
.host-action-log-message {
overflow-wrap: anywhere;
word-break: break-word;
}
.host-action-log-spinner {
width: 1rem;
height: 1rem;
margin-top: 0.15rem;
border: 2px solid #3a4556;
border-top-color: #58a6ff;
border-radius: 50%;
animation: host-action-log-spin 0.8s linear infinite;
flex-shrink: 0;
}
@keyframes host-action-log-spin {
to {
transform: rotate(360deg);
}
}
.host-action-log-output {
margin-top: 0.65rem;
max-height: min(45vh, 22rem);
overflow: auto;
padding: 0.65rem;
background: #0d1117;
border-radius: 6px;
font-size: 0.8rem;
line-height: 1.45;
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
}
.host-action-log-actions {
margin-top: 0.75rem;
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
</style>