(null);
useSacLiveEventStream(() => {
- void refreshFromLatestEvent();
+ scheduleLiveRefreshFromEvent();
});
const hostId = computed(() => parseInt(props.id, 10));
@@ -370,15 +393,12 @@ const sshVerifiedTitle = computed(() => {
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);
+function isLinuxHostDetail(detail: HostDetail): boolean {
+ return isLinuxAgentHost(detail);
+}
+
function syncConfigFormFromHost(detail: HostDetail) {
const cfg = detail.agent_config ?? {};
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 }) {
if (!host.value) return;
host.value = {
@@ -706,23 +721,67 @@ async function runAgentUpdate() {
detail.product_version = job.product_version;
}
applyHostDetail(detail);
+ recalcVersionStatusFromGit(detail, agentGitVersions.value);
}
} finally {
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() {
- if (refreshingLive) return;
+ if (refreshingLive || !host.value) return;
refreshingLive = true;
try {
const recent = await fetchRecentEvents(1);
const ev = recent[0];
if (!ev || ev.host_id !== hostId.value) return;
- applyHostDetail(await fetchHost(hostId.value));
- if (eventsPage.value === 1) {
- await loadEvents(1);
- }
+ patchHostDetailFromEvent(host.value, ev);
+ recalcVersionStatusFromGit(host.value, agentGitVersions.value);
+ emitHostPatch(host.value);
+ prependHostEventIfMissing(events.value, ev, eventsPage.value);
} catch {
/* ignore transient errors */
} 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) {
eventsLoading.value = true;
eventsError.value = "";
@@ -749,6 +823,7 @@ async function loadEvents(page = 1) {
}
onMounted(async () => {
+ void loadAgentGitVersions();
await loadHost();
await loadEvents(1);
});
@@ -756,6 +831,7 @@ onMounted(async () => {
onUnmounted(() => {
clearSshTestFeedbackTimer();
clearWinRmTestFeedbackTimer();
+ clearLiveRefreshTimer();
});
watch(
diff --git a/frontend/src/views/HostsView.vue b/frontend/src/views/HostsView.vue
index da6f8b2..77ba59e 100644
--- a/frontend/src/views/HostsView.vue
+++ b/frontend/src/views/HostsView.vue
@@ -67,13 +67,14 @@
| {{ h.id }} |
{{ h.display_name || h.hostname }} |
{{ h.hostname }} |
-
-
- {{ h.product_version || "—" }}
-
+ |
+
|
{{ h.os_family }} |
{{ h.ipv4 || "—" }} |
@@ -125,6 +126,13 @@ import {
import { bumpLatestAgentVersion, patchHostSummaryFromDetail } from "../utils/hostsLiveUpdate";
import { subscribeHostPatch } from "../utils/hostPatchBus";
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 route = useRoute();
@@ -188,6 +196,7 @@ const initialSort = loadHostsSortPrefs();
const sortBy = ref(initialSort.sortBy);
const sortDir = ref<"asc" | "desc">(initialSort.sortDir);
const deletingId = ref(null);
+const upgradingHostId = ref(null);
let patchingHostRow = false;
let unsubscribeHostPatch: (() => void) | null = null;
@@ -223,17 +232,18 @@ function isVersionOutdated(h: HostSummary): boolean {
return isAgentVersionOutdated(h.product_version, reference);
}
-function versionTitle(h: HostSummary): string {
- const reference =
- data.value?.reference_agent_versions?.[h.product] ??
- data.value?.latest_agent_versions?.[h.product];
- if (!isVersionOutdated(h)) return "";
- if (!reference) return "Устаревшая версия агента";
- const fromSettings = data.value?.reference_agent_versions?.[h.product];
- if (fromSettings && fromSettings !== data.value?.latest_agent_versions?.[h.product]) {
- return `Устарела относительно эталона ${reference} (настройки SAC)`;
+async function startAgentUpgrade(h: HostSummary) {
+ if (!isGitAgentUpgradeAvailable(h, data.value) || upgradingHostId.value != null) {
+ return;
+ }
+ const kind = remoteActionKindForHost(h);
+ if (!kind) return;
+ upgradingHostId.value = h.id;
+ try {
+ await runHostRemoteAction(h.id, remoteActionTitleForHost(h), kind);
+ } finally {
+ upgradingHostId.value = null;
}
- return `Устарела относительно эталона ${reference}`;
}
function openHost(id: number) {