feat: one-click agent upgrade from hosts list (0.3.9)
Add Omada-style git version arrow in the agent version column to trigger WinRM/SSH background updates, and soften host detail live refresh with debounced incremental event patches. Co-authored-by: Cursor <cursoragent@cursor.com>
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.3.8"
|
APP_VERSION = "0.3.9"
|
||||||
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
|
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<span class="agent-version-cell">
|
||||||
|
<span :class="{ 'agent-version-outdated': outdatedHighlight }" :title="currentTitle">
|
||||||
|
{{ host.product_version || "—" }}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
v-if="showUpgrade"
|
||||||
|
type="button"
|
||||||
|
class="agent-version-upgrade"
|
||||||
|
:disabled="updating"
|
||||||
|
:title="upgradeTitle"
|
||||||
|
@click.stop="emit('upgrade')"
|
||||||
|
>
|
||||||
|
<span class="agent-version-upgrade__arrow" aria-hidden="true">↑</span>
|
||||||
|
<span class="agent-version-upgrade__target">{{ gitTargetVersion }}</span>
|
||||||
|
</button>
|
||||||
|
<span v-if="updating" class="agent-version-upgrade__busy muted">…</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import type { HostSummary } from "../api";
|
||||||
|
import {
|
||||||
|
gitTargetVersionForHost,
|
||||||
|
isGitAgentUpgradeAvailable,
|
||||||
|
type AgentGitVersions,
|
||||||
|
} from "../utils/hostAgentUpgrade";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
host: HostSummary;
|
||||||
|
git: AgentGitVersions | null | undefined;
|
||||||
|
updating?: boolean;
|
||||||
|
outdatedHighlight?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
upgrade: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const gitTargetVersion = computed(() => gitTargetVersionForHost(props.host, props.git));
|
||||||
|
|
||||||
|
const showUpgrade = computed(
|
||||||
|
() => !props.updating && isGitAgentUpgradeAvailable(props.host, props.git),
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentTitle = computed(() => {
|
||||||
|
if (!props.host.product_version) return "";
|
||||||
|
if (showUpgrade.value && gitTargetVersion.value) {
|
||||||
|
return `Текущая версия ${props.host.product_version}. Доступна ${gitTargetVersion.value}`;
|
||||||
|
}
|
||||||
|
return props.host.product_version;
|
||||||
|
});
|
||||||
|
|
||||||
|
const upgradeTitle = computed(() => {
|
||||||
|
if (!gitTargetVersion.value) return "";
|
||||||
|
return `Обновить агент до ${gitTargetVersion.value}`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.agent-version-cell {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.2rem 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-version-upgrade {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.15rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-version-upgrade:disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-version-upgrade__arrow {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(63, 185, 80, 0.18);
|
||||||
|
color: #3fb950;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-version-upgrade__target {
|
||||||
|
color: #3fb950;
|
||||||
|
font-size: 0.92em;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-version-upgrade:hover:not(:disabled) .agent-version-upgrade__target {
|
||||||
|
color: #56d364;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-version-upgrade:hover:not(:disabled) .agent-version-upgrade__arrow {
|
||||||
|
background: rgba(63, 185, 80, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-version-upgrade__busy {
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -53,3 +53,14 @@ export function isAgentVersionNewer(
|
|||||||
if (!prev) return true;
|
if (!prev) return true;
|
||||||
return compareVersions(next, prev) > 0;
|
return compareVersions(next, prev) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Хост строго младше целевой версии (например из git), без порога lag. */
|
||||||
|
export function isAgentVersionBehind(
|
||||||
|
hostVersion: string | null | undefined,
|
||||||
|
targetVersion: string | null | undefined,
|
||||||
|
): boolean {
|
||||||
|
const host = parseAgentVersion(hostVersion);
|
||||||
|
const target = parseAgentVersion(targetVersion);
|
||||||
|
if (!host || !target) return false;
|
||||||
|
return compareVersions(host, target) < 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import type { HostSummary } from "../api";
|
||||||
|
import type { HostRemoteActionKind } from "../composables/useHostRemoteAction";
|
||||||
|
import { isAgentVersionBehind } from "./agentVersion";
|
||||||
|
|
||||||
|
export const PRODUCT_RDP = "rdp-login-monitor";
|
||||||
|
export const PRODUCT_SSH = "ssh-monitor";
|
||||||
|
|
||||||
|
export interface AgentGitVersions {
|
||||||
|
git_latest_rdp_version?: string | null;
|
||||||
|
git_latest_ssh_version?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWindowsAgentHost(host: Pick<HostSummary, "os_family" | "product">): boolean {
|
||||||
|
if ((host.os_family || "").toLowerCase() === "windows") return true;
|
||||||
|
return host.product === PRODUCT_RDP;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLinuxAgentHost(host: Pick<HostSummary, "os_family" | "product">): boolean {
|
||||||
|
if ((host.os_family || "").toLowerCase() === "linux") return true;
|
||||||
|
return host.product === PRODUCT_SSH;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gitTargetVersionForHost(
|
||||||
|
host: Pick<HostSummary, "os_family" | "product">,
|
||||||
|
git: AgentGitVersions | null | undefined,
|
||||||
|
): string | null {
|
||||||
|
if (!git) return null;
|
||||||
|
if (isWindowsAgentHost(host)) {
|
||||||
|
return git.git_latest_rdp_version?.trim() || null;
|
||||||
|
}
|
||||||
|
if (isLinuxAgentHost(host)) {
|
||||||
|
return git.git_latest_ssh_version?.trim() || null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGitAgentUpgradeAvailable(
|
||||||
|
host: Pick<HostSummary, "os_family" | "product" | "product_version">,
|
||||||
|
git: AgentGitVersions | null | undefined,
|
||||||
|
): boolean {
|
||||||
|
const target = gitTargetVersionForHost(host, git);
|
||||||
|
if (!target || !host.product_version?.trim()) return false;
|
||||||
|
return isAgentVersionBehind(host.product_version, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remoteActionKindForHost(
|
||||||
|
host: Pick<HostSummary, "os_family" | "product">,
|
||||||
|
): HostRemoteActionKind | null {
|
||||||
|
if (isWindowsAgentHost(host)) return "fallback";
|
||||||
|
if (isLinuxAgentHost(host)) return "ssh-update";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remoteActionTitleForHost(host: HostSummary): string {
|
||||||
|
const label = host.display_name || host.hostname;
|
||||||
|
if (isWindowsAgentHost(host)) {
|
||||||
|
return `Обновление ${label} через WinRM`;
|
||||||
|
}
|
||||||
|
return `Обновление ssh-monitor (SSH): ${label}`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import type { EventListResponse, EventSummary, HostDetail } from "../api";
|
||||||
|
import { isAgentVersionBehind } from "./agentVersion";
|
||||||
|
import type { AgentGitVersions } from "./hostAgentUpgrade";
|
||||||
|
import { gitTargetVersionForHost } from "./hostAgentUpgrade";
|
||||||
|
|
||||||
|
/** Точечное обновление карточки хоста по событию ingest (без полного GET /hosts/{id}). */
|
||||||
|
export function patchHostDetailFromEvent(host: HostDetail, ev: EventSummary): void {
|
||||||
|
host.last_seen_at = ev.received_at || ev.occurred_at;
|
||||||
|
host.event_count = (host.event_count ?? 0) + 1;
|
||||||
|
if (ev.product_version?.trim()) {
|
||||||
|
host.product_version = ev.product_version.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recalcVersionStatusFromGit(
|
||||||
|
host: HostDetail,
|
||||||
|
git: AgentGitVersions | null | undefined,
|
||||||
|
): void {
|
||||||
|
const target = gitTargetVersionForHost(host, git);
|
||||||
|
if (!host.product_version?.trim() || !target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
host.version_status = isAgentVersionBehind(host.product_version, target) ? "outdated" : "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Добавляет событие в начало списка на стр. 1, если его ещё нет. */
|
||||||
|
export function prependHostEventIfMissing(
|
||||||
|
events: EventListResponse | null,
|
||||||
|
ev: EventSummary,
|
||||||
|
page: number,
|
||||||
|
): boolean {
|
||||||
|
if (!events || page !== 1) return false;
|
||||||
|
if (events.items.some((row) => row.id === ev.id)) return false;
|
||||||
|
events.items = [ev, ...events.items];
|
||||||
|
events.total = (events.total ?? 0) + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -21,8 +21,14 @@
|
|||||||
<div class="detail-field">
|
<div class="detail-field">
|
||||||
<dt>Версия агента</dt>
|
<dt>Версия агента</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<span :class="versionStatusClass">{{ host.product_version || "—" }}</span>
|
<HostAgentVersionUpgrade
|
||||||
<span v-if="host.version_status === 'outdated'" class="muted version-hint"> (устарела)</span>
|
v-if="host"
|
||||||
|
:host="host"
|
||||||
|
:git="agentGitVersions"
|
||||||
|
:updating="updatingAgent || runningAgentFallback"
|
||||||
|
:outdated-highlight="host.version_status === 'outdated'"
|
||||||
|
@upgrade="runAgentUpgradeFromCell"
|
||||||
|
/>
|
||||||
<span v-if="host.pending_agent_update" class="pending-update-badge">обновление запрошено</span>
|
<span v-if="host.pending_agent_update" class="pending-update-badge">обновление запрошено</span>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
@@ -299,6 +305,19 @@ import {
|
|||||||
} from "../api";
|
} from "../api";
|
||||||
import { emitHostPatch } from "../utils/hostPatchBus";
|
import { emitHostPatch } from "../utils/hostPatchBus";
|
||||||
import HostSessionsPanel from "../components/HostSessionsPanel.vue";
|
import HostSessionsPanel from "../components/HostSessionsPanel.vue";
|
||||||
|
import HostAgentVersionUpgrade from "../components/HostAgentVersionUpgrade.vue";
|
||||||
|
import {
|
||||||
|
patchHostDetailFromEvent,
|
||||||
|
prependHostEventIfMissing,
|
||||||
|
recalcVersionStatusFromGit,
|
||||||
|
} from "../utils/hostDetailLive";
|
||||||
|
import {
|
||||||
|
isLinuxAgentHost,
|
||||||
|
remoteActionKindForHost,
|
||||||
|
remoteActionTitleForHost,
|
||||||
|
type AgentGitVersions,
|
||||||
|
} from "../utils/hostAgentUpgrade";
|
||||||
|
import { fetchAgentUpdateSettings } from "../api";
|
||||||
|
|
||||||
const HOST_ACTION_FEEDBACK_MS = 30_000;
|
const HOST_ACTION_FEEDBACK_MS = 30_000;
|
||||||
|
|
||||||
@@ -339,9 +358,13 @@ let sshTestHideTimer: ReturnType<typeof setTimeout> | null = null;
|
|||||||
let winRmTestHideTimer: ReturnType<typeof setTimeout> | null = null;
|
let winRmTestHideTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
let sshProbeSeq = 0;
|
let sshProbeSeq = 0;
|
||||||
let refreshingLive = false;
|
let refreshingLive = false;
|
||||||
|
let liveRefreshTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
const LIVE_REFRESH_DEBOUNCE_MS = 400;
|
||||||
|
|
||||||
|
const agentGitVersions = ref<AgentGitVersions | null>(null);
|
||||||
|
|
||||||
useSacLiveEventStream(() => {
|
useSacLiveEventStream(() => {
|
||||||
void refreshFromLatestEvent();
|
scheduleLiveRefreshFromEvent();
|
||||||
});
|
});
|
||||||
|
|
||||||
const hostId = computed(() => parseInt(props.id, 10));
|
const hostId = computed(() => parseInt(props.id, 10));
|
||||||
@@ -370,15 +393,12 @@ const sshVerifiedTitle = computed(() => {
|
|||||||
return "SSH доступен";
|
return "SSH доступен";
|
||||||
});
|
});
|
||||||
|
|
||||||
const versionStatusClass = computed(() => {
|
|
||||||
const status = host.value?.version_status;
|
|
||||||
if (status === "outdated") return "agent-version-outdated";
|
|
||||||
if (status === "ok") return "agent-version-ok";
|
|
||||||
return "";
|
|
||||||
});
|
|
||||||
|
|
||||||
const showAgentConfig = computed(() => isWindowsHost.value || isLinuxHost.value);
|
const showAgentConfig = computed(() => isWindowsHost.value || isLinuxHost.value);
|
||||||
|
|
||||||
|
function isLinuxHostDetail(detail: HostDetail): boolean {
|
||||||
|
return isLinuxAgentHost(detail);
|
||||||
|
}
|
||||||
|
|
||||||
function syncConfigFormFromHost(detail: HostDetail) {
|
function syncConfigFormFromHost(detail: HostDetail) {
|
||||||
const cfg = detail.agent_config ?? {};
|
const cfg = detail.agent_config ?? {};
|
||||||
configForm.serverDisplayName = String(
|
configForm.serverDisplayName = String(
|
||||||
@@ -469,11 +489,6 @@ function clearSshTestFeedbackTimer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLinuxHostDetail(detail: HostDetail): boolean {
|
|
||||||
if ((detail.os_family || "").toLowerCase() === "linux") return true;
|
|
||||||
return detail.product === "ssh-monitor";
|
|
||||||
}
|
|
||||||
|
|
||||||
function applySshAdminStatus(result: { ok: boolean; ssh_admin_ok?: boolean | null }) {
|
function applySshAdminStatus(result: { ok: boolean; ssh_admin_ok?: boolean | null }) {
|
||||||
if (!host.value) return;
|
if (!host.value) return;
|
||||||
host.value = {
|
host.value = {
|
||||||
@@ -706,23 +721,67 @@ async function runAgentUpdate() {
|
|||||||
detail.product_version = job.product_version;
|
detail.product_version = job.product_version;
|
||||||
}
|
}
|
||||||
applyHostDetail(detail);
|
applyHostDetail(detail);
|
||||||
|
recalcVersionStatusFromGit(detail, agentGitVersions.value);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
updatingAgent.value = false;
|
updatingAgent.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runAgentUpgradeFromCell() {
|
||||||
|
if (!host.value) return;
|
||||||
|
const kind = remoteActionKindForHost(host.value);
|
||||||
|
if (!kind) return;
|
||||||
|
if (kind === "ssh-update") {
|
||||||
|
await runAgentUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runningAgentFallback.value = true;
|
||||||
|
try {
|
||||||
|
const job = await runHostRemoteAction(
|
||||||
|
hostId.value,
|
||||||
|
remoteActionTitleForHost(host.value),
|
||||||
|
kind,
|
||||||
|
);
|
||||||
|
if (job?.ok) {
|
||||||
|
const detail = await fetchHost(hostId.value);
|
||||||
|
if (job.product_version) {
|
||||||
|
detail.product_version = job.product_version;
|
||||||
|
}
|
||||||
|
applyHostDetail(detail);
|
||||||
|
recalcVersionStatusFromGit(detail, agentGitVersions.value);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
runningAgentFallback.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLiveRefreshTimer() {
|
||||||
|
if (liveRefreshTimer != null) {
|
||||||
|
clearTimeout(liveRefreshTimer);
|
||||||
|
liveRefreshTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleLiveRefreshFromEvent() {
|
||||||
|
clearLiveRefreshTimer();
|
||||||
|
liveRefreshTimer = setTimeout(() => {
|
||||||
|
liveRefreshTimer = null;
|
||||||
|
void refreshFromLatestEvent();
|
||||||
|
}, LIVE_REFRESH_DEBOUNCE_MS);
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshFromLatestEvent() {
|
async function refreshFromLatestEvent() {
|
||||||
if (refreshingLive) return;
|
if (refreshingLive || !host.value) return;
|
||||||
refreshingLive = true;
|
refreshingLive = true;
|
||||||
try {
|
try {
|
||||||
const recent = await fetchRecentEvents(1);
|
const recent = await fetchRecentEvents(1);
|
||||||
const ev = recent[0];
|
const ev = recent[0];
|
||||||
if (!ev || ev.host_id !== hostId.value) return;
|
if (!ev || ev.host_id !== hostId.value) return;
|
||||||
applyHostDetail(await fetchHost(hostId.value));
|
patchHostDetailFromEvent(host.value, ev);
|
||||||
if (eventsPage.value === 1) {
|
recalcVersionStatusFromGit(host.value, agentGitVersions.value);
|
||||||
await loadEvents(1);
|
emitHostPatch(host.value);
|
||||||
}
|
prependHostEventIfMissing(events.value, ev, eventsPage.value);
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore transient errors */
|
/* ignore transient errors */
|
||||||
} finally {
|
} finally {
|
||||||
@@ -730,6 +789,21 @@ async function refreshFromLatestEvent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadAgentGitVersions() {
|
||||||
|
try {
|
||||||
|
const settings = await fetchAgentUpdateSettings();
|
||||||
|
agentGitVersions.value = {
|
||||||
|
git_latest_rdp_version: settings.git_latest_rdp_version,
|
||||||
|
git_latest_ssh_version: settings.git_latest_ssh_version,
|
||||||
|
};
|
||||||
|
if (host.value) {
|
||||||
|
recalcVersionStatusFromGit(host.value, agentGitVersions.value);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
agentGitVersions.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadEvents(page = 1) {
|
async function loadEvents(page = 1) {
|
||||||
eventsLoading.value = true;
|
eventsLoading.value = true;
|
||||||
eventsError.value = "";
|
eventsError.value = "";
|
||||||
@@ -749,6 +823,7 @@ async function loadEvents(page = 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
void loadAgentGitVersions();
|
||||||
await loadHost();
|
await loadHost();
|
||||||
await loadEvents(1);
|
await loadEvents(1);
|
||||||
});
|
});
|
||||||
@@ -756,6 +831,7 @@ onMounted(async () => {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clearSshTestFeedbackTimer();
|
clearSshTestFeedbackTimer();
|
||||||
clearWinRmTestFeedbackTimer();
|
clearWinRmTestFeedbackTimer();
|
||||||
|
clearLiveRefreshTimer();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|||||||
@@ -67,13 +67,14 @@
|
|||||||
<td>{{ h.id }}</td>
|
<td>{{ h.id }}</td>
|
||||||
<td>{{ h.display_name || h.hostname }}</td>
|
<td>{{ h.display_name || h.hostname }}</td>
|
||||||
<td>{{ h.hostname }}</td>
|
<td>{{ h.hostname }}</td>
|
||||||
<td>
|
<td @click.stop>
|
||||||
<span
|
<HostAgentVersionUpgrade
|
||||||
:class="{ 'agent-version-outdated': isVersionOutdated(h) }"
|
:host="h"
|
||||||
:title="versionTitle(h)"
|
:git="data"
|
||||||
>
|
:updating="upgradingHostId === h.id"
|
||||||
{{ h.product_version || "—" }}
|
:outdated-highlight="isVersionOutdated(h)"
|
||||||
</span>
|
@upgrade="startAgentUpgrade(h)"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ h.os_family }}</td>
|
<td>{{ h.os_family }}</td>
|
||||||
<td>{{ h.ipv4 || "—" }}</td>
|
<td>{{ h.ipv4 || "—" }}</td>
|
||||||
@@ -125,6 +126,13 @@ import {
|
|||||||
import { bumpLatestAgentVersion, patchHostSummaryFromDetail } from "../utils/hostsLiveUpdate";
|
import { bumpLatestAgentVersion, patchHostSummaryFromDetail } from "../utils/hostsLiveUpdate";
|
||||||
import { subscribeHostPatch } from "../utils/hostPatchBus";
|
import { subscribeHostPatch } from "../utils/hostPatchBus";
|
||||||
import { isAgentVersionOutdated } from "../utils/agentVersion";
|
import { isAgentVersionOutdated } from "../utils/agentVersion";
|
||||||
|
import {
|
||||||
|
isGitAgentUpgradeAvailable,
|
||||||
|
remoteActionKindForHost,
|
||||||
|
remoteActionTitleForHost,
|
||||||
|
} from "../utils/hostAgentUpgrade";
|
||||||
|
import { runHostRemoteAction } from "../composables/useHostRemoteAction";
|
||||||
|
import HostAgentVersionUpgrade from "../components/HostAgentVersionUpgrade.vue";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -188,6 +196,7 @@ const initialSort = loadHostsSortPrefs();
|
|||||||
const sortBy = ref<SortKey>(initialSort.sortBy);
|
const sortBy = ref<SortKey>(initialSort.sortBy);
|
||||||
const sortDir = ref<"asc" | "desc">(initialSort.sortDir);
|
const sortDir = ref<"asc" | "desc">(initialSort.sortDir);
|
||||||
const deletingId = ref<number | null>(null);
|
const deletingId = ref<number | null>(null);
|
||||||
|
const upgradingHostId = ref<number | null>(null);
|
||||||
let patchingHostRow = false;
|
let patchingHostRow = false;
|
||||||
let unsubscribeHostPatch: (() => void) | null = null;
|
let unsubscribeHostPatch: (() => void) | null = null;
|
||||||
|
|
||||||
@@ -223,17 +232,18 @@ function isVersionOutdated(h: HostSummary): boolean {
|
|||||||
return isAgentVersionOutdated(h.product_version, reference);
|
return isAgentVersionOutdated(h.product_version, reference);
|
||||||
}
|
}
|
||||||
|
|
||||||
function versionTitle(h: HostSummary): string {
|
async function startAgentUpgrade(h: HostSummary) {
|
||||||
const reference =
|
if (!isGitAgentUpgradeAvailable(h, data.value) || upgradingHostId.value != null) {
|
||||||
data.value?.reference_agent_versions?.[h.product] ??
|
return;
|
||||||
data.value?.latest_agent_versions?.[h.product];
|
}
|
||||||
if (!isVersionOutdated(h)) return "";
|
const kind = remoteActionKindForHost(h);
|
||||||
if (!reference) return "Устаревшая версия агента";
|
if (!kind) return;
|
||||||
const fromSettings = data.value?.reference_agent_versions?.[h.product];
|
upgradingHostId.value = h.id;
|
||||||
if (fromSettings && fromSettings !== data.value?.latest_agent_versions?.[h.product]) {
|
try {
|
||||||
return `Устарела относительно эталона ${reference} (настройки SAC)`;
|
await runHostRemoteAction(h.id, remoteActionTitleForHost(h), kind);
|
||||||
|
} finally {
|
||||||
|
upgradingHostId.value = null;
|
||||||
}
|
}
|
||||||
return `Устарела относительно эталона ${reference}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openHost(id: number) {
|
function openHost(id: number) {
|
||||||
|
|||||||
Reference in New Issue
Block a user