fix(frontend): match sessions panel height to agent config until loaded (0.20.30)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<section class="card host-sessions">
|
||||
<section
|
||||
ref="rootRef"
|
||||
class="card host-sessions"
|
||||
:class="{ 'host-sessions--expanded': loaded }"
|
||||
:style="panelMinHeight ? { minHeight: panelMinHeight } : undefined"
|
||||
>
|
||||
<h2>Залогиненные пользователи</h2>
|
||||
<p class="muted">
|
||||
Текущие сессии на хосте (Linux: loginctl, Windows: qwinsta). Требуются учётные данные admin в настройках SAC.
|
||||
@@ -38,22 +43,28 @@
|
||||
</table>
|
||||
<p v-if="message" :class="messageOk ? 'success' : 'error'">{{ message }}</p>
|
||||
</template>
|
||||
<button type="button" class="secondary" :disabled="loading" @click="load">
|
||||
{{ loading ? "Загрузка…" : loaded ? "Обновить" : "Показать пользователей" }}
|
||||
</button>
|
||||
<div class="host-sessions-footer">
|
||||
<button type="button" class="secondary" :disabled="loading" @click="load">
|
||||
{{ loading ? "Загрузка…" : loaded ? "Обновить" : "Показать пользователей" }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import {
|
||||
fetchHostSessions,
|
||||
terminateHostSession,
|
||||
type HostSessionItem,
|
||||
} from "../api";
|
||||
|
||||
const props = defineProps<{ hostId: number }>();
|
||||
const props = defineProps<{
|
||||
hostId: number;
|
||||
heightSource?: HTMLElement | null;
|
||||
}>();
|
||||
|
||||
const rootRef = ref<HTMLElement | null>(null);
|
||||
const loading = ref(false);
|
||||
const loaded = ref(false);
|
||||
const sessions = ref<HostSessionItem[]>([]);
|
||||
@@ -61,6 +72,48 @@ const error = ref("");
|
||||
const message = ref("");
|
||||
const messageOk = ref(true);
|
||||
const terminatingId = ref<string | null>(null);
|
||||
const panelMinHeight = ref<string | undefined>(undefined);
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
let observedSource: HTMLElement | null = null;
|
||||
|
||||
function updateMatchedHeight() {
|
||||
if (loaded.value) {
|
||||
panelMinHeight.value = undefined;
|
||||
return;
|
||||
}
|
||||
const source = props.heightSource;
|
||||
if (!source) {
|
||||
panelMinHeight.value = undefined;
|
||||
return;
|
||||
}
|
||||
panelMinHeight.value = `${source.offsetHeight}px`;
|
||||
}
|
||||
|
||||
function bindHeightObserver(source: HTMLElement | null | undefined) {
|
||||
if (observedSource && resizeObserver) {
|
||||
resizeObserver.unobserve(observedSource);
|
||||
observedSource = null;
|
||||
}
|
||||
if (!source || typeof ResizeObserver === "undefined") {
|
||||
updateMatchedHeight();
|
||||
return;
|
||||
}
|
||||
if (!resizeObserver) {
|
||||
resizeObserver = new ResizeObserver(() => updateMatchedHeight());
|
||||
}
|
||||
resizeObserver.observe(source);
|
||||
observedSource = source;
|
||||
updateMatchedHeight();
|
||||
}
|
||||
|
||||
function resetSessionsState() {
|
||||
loaded.value = false;
|
||||
sessions.value = [];
|
||||
error.value = "";
|
||||
message.value = "";
|
||||
terminatingId.value = null;
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
@@ -79,6 +132,8 @@ async function load() {
|
||||
error.value = e instanceof Error ? e.message : "Ошибка загрузки сессий";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
await nextTick();
|
||||
updateMatchedHeight();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,19 +155,55 @@ async function terminate(sessionId: string) {
|
||||
terminatingId.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.heightSource,
|
||||
(source) => {
|
||||
bindHeightObserver(source ?? null);
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.hostId,
|
||||
() => {
|
||||
resetSessionsState();
|
||||
nextTick(() => updateMatchedHeight());
|
||||
},
|
||||
);
|
||||
|
||||
watch(loaded, () => {
|
||||
nextTick(() => updateMatchedHeight());
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
bindHeightObserver(props.heightSource ?? null);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
resizeObserver?.disconnect();
|
||||
resizeObserver = null;
|
||||
observedSource = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.host-sessions {
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.host-sessions:not(.host-sessions--expanded) .host-sessions-footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.host-sessions table {
|
||||
width: 100%;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.host-sessions {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.host-sessions button.secondary {
|
||||
margin-top: 0.5rem;
|
||||
.host-sessions-footer {
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
|
||||
export const APP_NAME = "Security Alert Center";
|
||||
export const APP_VERSION = "0.20.29";
|
||||
export const APP_VERSION = "0.20.30";
|
||||
export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
</div>
|
||||
|
||||
<div v-if="showAgentConfig" class="host-config-sessions-row">
|
||||
<section class="card host-agent-config">
|
||||
<section ref="agentConfigSectionRef" class="card host-agent-config">
|
||||
<h2>Desired config (poll агента)</h2>
|
||||
<p class="muted">Агент заберёт настройки при следующем poll. Revision: {{ host.agent_config_revision ?? 0 }}</p>
|
||||
<form class="agent-config-form" @submit.prevent="saveAgentConfig">
|
||||
@@ -167,7 +167,7 @@
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<HostSessionsPanel :host-id="host.id" />
|
||||
<HostSessionsPanel :host-id="host.id" :height-source="agentConfigSectionRef" />
|
||||
</div>
|
||||
|
||||
<section v-if="inventory" class="host-inventory card">
|
||||
@@ -305,6 +305,7 @@ const HOST_ACTION_FEEDBACK_MS = 30_000;
|
||||
const props = defineProps<{ id: string }>();
|
||||
const route = useRoute();
|
||||
|
||||
const agentConfigSectionRef = ref<HTMLElement | null>(null);
|
||||
const host = ref<HostDetail | null>(null);
|
||||
const events = ref<EventListResponse | null>(null);
|
||||
const loading = ref(false);
|
||||
@@ -860,12 +861,6 @@ watch(
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.host-config-sessions-row > .host-agent-config,
|
||||
.host-config-sessions-row > .host-sessions {
|
||||
flex: 1 1 22rem;
|
||||
min-width: min(100%, 22rem);
|
||||
}
|
||||
|
||||
.agent-config-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user