fix: show logoff actions in RDG qwinsta modal (0.20.7)
Put session table before raw output, add manual session ID logoff, improve qwinsta line parsing.
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.20.6"
|
APP_VERSION = "0.20.7"
|
||||||
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
|
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="modal.open" class="modal-backdrop" @click.self="emit('close')">
|
<div v-if="modal.open" class="modal-backdrop" @click.self="emit('close')">
|
||||||
<div class="modal-card qwinsta-modal" role="dialog" aria-modal="true">
|
<div class="modal-card qwinsta-modal" role="dialog" aria-modal="true">
|
||||||
<h3>qwinsta — {{ modal.clientHostname || modal.target || `#${modal.eventId}` }}</h3>
|
<h3>qwinsta / logoff — {{ modal.clientHostname || modal.target || `#${modal.eventId}` }}</h3>
|
||||||
<p v-if="modal.internalIp" class="muted qwinsta-meta">Клиентский ПК: {{ modal.internalIp }}</p>
|
<p v-if="modal.internalIp" class="muted qwinsta-meta">Клиентский ПК: {{ modal.internalIp }}</p>
|
||||||
<p v-if="modal.error" class="error">{{ modal.error }}</p>
|
<p v-if="modal.error" class="error">{{ modal.error }}</p>
|
||||||
<p v-else-if="modal.loading">Ожидание ответа агента…</p>
|
<p v-else-if="modal.loading">Ожидание ответа…</p>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<pre v-if="modal.stdout" class="qwinsta-raw">{{ modal.stdout }}</pre>
|
<template v-if="modal.sessions.length">
|
||||||
<p v-else class="muted">Нет вывода</p>
|
<p class="qwinsta-hint">
|
||||||
<table v-if="modal.sessions.length" class="dash-table">
|
Сеансы на ПК пользователя — нажмите <strong>logoff</strong>, чтобы оборвать зависшую сессию:
|
||||||
|
</p>
|
||||||
|
<table class="dash-table qwinsta-sessions-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>SESSION</th>
|
<th>SESSION</th>
|
||||||
@@ -38,6 +40,35 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
|
<div v-else-if="modal.stdout" class="qwinsta-manual">
|
||||||
|
<p class="muted">
|
||||||
|
Сеансы не распознаны автоматически. Введите ID из вывода qwinsta ниже:
|
||||||
|
</p>
|
||||||
|
<div class="qwinsta-manual-row">
|
||||||
|
<input
|
||||||
|
v-model="manualSessionId"
|
||||||
|
type="text"
|
||||||
|
inputmode="numeric"
|
||||||
|
class="qwinsta-manual-input"
|
||||||
|
placeholder="ID сеанса"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="danger"
|
||||||
|
:disabled="!manualSessionId.trim() || modal.logoffId !== null"
|
||||||
|
@click="submitManualLogoff"
|
||||||
|
>
|
||||||
|
logoff
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-else class="muted">Нет вывода</p>
|
||||||
|
|
||||||
|
<details v-if="modal.stdout" class="qwinsta-raw-details">
|
||||||
|
<summary>Вывод qwinsta</summary>
|
||||||
|
<pre class="qwinsta-raw">{{ modal.stdout }}</pre>
|
||||||
|
</details>
|
||||||
|
</template>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button type="button" class="secondary" @click="emit('close')">Закрыть</button>
|
<button type="button" class="secondary" @click="emit('close')">Закрыть</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,6 +77,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
import type { QwinstaSessionRow } from "../composables/useRdgQwinsta";
|
import type { QwinstaSessionRow } from "../composables/useRdgQwinsta";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -67,6 +99,14 @@ const emit = defineEmits<{
|
|||||||
close: [];
|
close: [];
|
||||||
logoff: [sessionId: number];
|
logoff: [sessionId: number];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const manualSessionId = ref("");
|
||||||
|
|
||||||
|
function submitManualLogoff() {
|
||||||
|
const id = Number.parseInt(manualSessionId.value.trim(), 10);
|
||||||
|
if (!Number.isFinite(id)) return;
|
||||||
|
emit("logoff", id);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -97,6 +137,48 @@ const emit = defineEmits<{
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qwinsta-hint {
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #e6edf3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qwinsta-sessions-table {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qwinsta-manual {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qwinsta-manual-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qwinsta-manual-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0.45rem 0.6rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #3d4f63;
|
||||||
|
background: #0f1419;
|
||||||
|
color: #e6edf3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qwinsta-raw-details {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qwinsta-raw-details summary {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #9aa4b2;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.qwinsta-raw {
|
.qwinsta-raw {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
max-height: 12rem;
|
max-height: 12rem;
|
||||||
@@ -105,6 +187,7 @@ const emit = defineEmits<{
|
|||||||
background: #0d1117;
|
background: #0d1117;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-actions {
|
.modal-actions {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function parseQwinstaSessions(stdout: string, actorUser: string): QwinstaSession
|
|||||||
if (/^SESSION/i.test(line) || /^---/.test(line)) continue;
|
if (/^SESSION/i.test(line) || /^---/.test(line)) continue;
|
||||||
const parts = line.split(/\s+/);
|
const parts = line.split(/\s+/);
|
||||||
if (parts.length < 4) continue;
|
if (parts.length < 4) continue;
|
||||||
const sessionName = parts[0];
|
const sessionName = parts[0].replace(/^>/, "");
|
||||||
const userName = parts[1];
|
const userName = parts[1];
|
||||||
const id = Number.parseInt(parts[2], 10);
|
const id = Number.parseInt(parts[2], 10);
|
||||||
const state = parts.slice(3).join(" ");
|
const state = parts.slice(3).join(" ");
|
||||||
@@ -39,7 +39,7 @@ function parseQwinstaSessions(stdout: string, actorUser: string): QwinstaSession
|
|||||||
const id = Number.parseInt(parts[2], 10);
|
const id = Number.parseInt(parts[2], 10);
|
||||||
if (!Number.isFinite(id)) continue;
|
if (!Number.isFinite(id)) continue;
|
||||||
rows.push({
|
rows.push({
|
||||||
sessionName: parts[0],
|
sessionName: parts[0].replace(/^>/, ""),
|
||||||
userName: parts[1],
|
userName: parts[1],
|
||||||
id,
|
id,
|
||||||
state: parts.slice(3).join(" "),
|
state: parts.slice(3).join(" "),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
|
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
|
||||||
export const APP_NAME = "Security Alert Center";
|
export const APP_NAME = "Security Alert Center";
|
||||||
export const APP_VERSION = "0.20.6";
|
export const APP_VERSION = "0.20.7";
|
||||||
export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;
|
export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user