feat: git-based agent version reference and unified outdated badge (0.20.0)
SAC reads latest RDP/ssh-monitor versions from configured git repos for outdated detection, update targets, and settings test-git.
This commit is contained in:
@@ -267,6 +267,8 @@ export interface HostListResponse {
|
||||
page: number;
|
||||
page_size: number;
|
||||
latest_agent_versions?: Record<string, string>;
|
||||
/** Эталон для «устарела»: max(git latest, min, max в fleet) */
|
||||
reference_agent_versions?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ProblemSummary {
|
||||
@@ -548,9 +550,24 @@ export interface AgentUpdateSettings {
|
||||
min_rdp_version: string | null;
|
||||
min_ssh_version: string | null;
|
||||
win_agent_update_script: string | null;
|
||||
rdp_git_repo_url: string | null;
|
||||
ssh_git_repo_url: string | null;
|
||||
git_branch: string;
|
||||
git_latest_rdp_version: string | null;
|
||||
git_latest_ssh_version: string | null;
|
||||
git_fetched_at: string | null;
|
||||
git_errors: Record<string, string>;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface AgentGitTestResult {
|
||||
git_latest_rdp_version: string | null;
|
||||
git_latest_ssh_version: string | null;
|
||||
git_fetched_at: string | null;
|
||||
git_errors: Record<string, string>;
|
||||
from_cache: boolean;
|
||||
}
|
||||
|
||||
export function fetchAgentUpdateSettings(): Promise<AgentUpdateSettings> {
|
||||
return apiFetch<AgentUpdateSettings>("/api/v1/settings/agent-updates");
|
||||
}
|
||||
@@ -564,6 +581,12 @@ export function saveAgentUpdateSettings(
|
||||
});
|
||||
}
|
||||
|
||||
export function testAgentGitRelease(): Promise<AgentGitTestResult> {
|
||||
return apiFetch<AgentGitTestResult>("/api/v1/settings/agent-updates/test-git", {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
export interface DashboardSummary {
|
||||
events_last_24h: number;
|
||||
hosts_total: number;
|
||||
|
||||
@@ -13,6 +13,9 @@ export function patchHostSummaryFromDetail(target: HostSummary, detail: HostDeta
|
||||
target.last_daily_report_at = detail.last_daily_report_at;
|
||||
target.last_inventory_at = detail.last_inventory_at;
|
||||
target.agent_status = detail.agent_status;
|
||||
if (detail.version_status) {
|
||||
target.version_status = detail.version_status;
|
||||
}
|
||||
}
|
||||
|
||||
export function bumpLatestAgentVersion(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
|
||||
export const APP_NAME = "Security Alert Center";
|
||||
export const APP_VERSION = "0.12.1";
|
||||
export const APP_VERSION = "0.20.0";
|
||||
export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;
|
||||
|
||||
@@ -202,16 +202,23 @@ function agentLabel(status: string) {
|
||||
function isVersionOutdated(h: HostSummary): boolean {
|
||||
if (h.version_status === "outdated") return true;
|
||||
if (h.version_status === "ok") return false;
|
||||
const latest = data.value?.latest_agent_versions?.[h.product];
|
||||
return isAgentVersionOutdated(h.product_version, latest);
|
||||
const reference =
|
||||
data.value?.reference_agent_versions?.[h.product] ??
|
||||
data.value?.latest_agent_versions?.[h.product];
|
||||
return isAgentVersionOutdated(h.product_version, reference);
|
||||
}
|
||||
|
||||
function versionTitle(h: HostSummary): string {
|
||||
const latest = data.value?.latest_agent_versions?.[h.product];
|
||||
const reference =
|
||||
data.value?.reference_agent_versions?.[h.product] ??
|
||||
data.value?.latest_agent_versions?.[h.product];
|
||||
if (!isVersionOutdated(h)) return "";
|
||||
return latest
|
||||
? `Устарела относительно последней для ${h.product}: ${latest}`
|
||||
: "Устаревшая версия агента";
|
||||
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)`;
|
||||
}
|
||||
return `Устарела относительно эталона ${reference}`;
|
||||
}
|
||||
|
||||
function openHost(id: number) {
|
||||
|
||||
@@ -126,6 +126,8 @@
|
||||
Режим <code>gpo</code> — обновления вручную/GPO; SAC только показывает устаревшие версии.
|
||||
Режим <code>sac</code> — «Запросить обновление» ставит флаг в poll; агент self-update;
|
||||
через {{ agentUpdateForm.fallback_after_minutes }} мин без ответа — fallback SSH/WinRM с SAC.
|
||||
<strong>Эталон «устарела»</strong> берётся из <code>version.txt</code> / скриптов в git-репозиториях
|
||||
(ветка <code>main</code> по умолчанию); при недоступности git — fallback на max версию в fleet.
|
||||
</p>
|
||||
<form class="settings-form" @submit.prevent="saveAgentUpdateSettingsForm">
|
||||
<label class="settings-field">
|
||||
@@ -144,13 +146,34 @@
|
||||
<input v-model.number="agentUpdateForm.fallback_after_minutes" type="number" min="1" max="1440" />
|
||||
</label>
|
||||
<label class="settings-field">
|
||||
Recommended RDP-login-monitor
|
||||
<input v-model="agentUpdateForm.recommended_rdp_version" type="text" placeholder="2.1.0-SAC" />
|
||||
Git: RDP-login-monitor
|
||||
<input
|
||||
v-model="agentUpdateForm.rdp_git_repo_url"
|
||||
type="text"
|
||||
placeholder="https://git.kalinamall.ru/PapaTramp/RDP-login-monitor.git"
|
||||
/>
|
||||
</label>
|
||||
<label class="settings-field">
|
||||
Recommended ssh-monitor
|
||||
<input v-model="agentUpdateForm.recommended_ssh_version" type="text" placeholder="2.1.0-SAC" />
|
||||
Git: ssh-monitor
|
||||
<input
|
||||
v-model="agentUpdateForm.ssh_git_repo_url"
|
||||
type="text"
|
||||
placeholder="https://git.kalinamall.ru/PapaTramp/ssh-monitor.git"
|
||||
/>
|
||||
</label>
|
||||
<label class="settings-field">
|
||||
Git-ветка
|
||||
<input v-model="agentUpdateForm.git_branch" type="text" placeholder="main" />
|
||||
</label>
|
||||
<p v-if="agentUpdateLoaded?.git_latest_rdp_version || agentUpdateLoaded?.git_latest_ssh_version" class="settings-meta">
|
||||
Latest из git:
|
||||
RDP <code>{{ agentUpdateLoaded?.git_latest_rdp_version ?? "—" }}</code>,
|
||||
SSH <code>{{ agentUpdateLoaded?.git_latest_ssh_version ?? "—" }}</code>
|
||||
<span v-if="agentUpdateLoaded?.git_fetched_at">
|
||||
· обновлено {{ formatDateTime(agentUpdateLoaded.git_fetched_at) }}
|
||||
</span>
|
||||
</p>
|
||||
<p v-if="agentGitTestErrors" class="settings-error-inline">{{ agentGitTestErrors }}</p>
|
||||
<label class="settings-field">
|
||||
WinRM: путь к Deploy-LoginMonitor.ps1 (fallback)
|
||||
<input
|
||||
@@ -163,6 +186,9 @@
|
||||
Источник: <code>{{ agentUpdateLoaded.source }}</code>
|
||||
</p>
|
||||
<div class="settings-actions">
|
||||
<button type="button" :disabled="testingAgentGit" @click="runAgentGitTest">
|
||||
{{ testingAgentGit ? "Проверка git…" : "Проверить git" }}
|
||||
</button>
|
||||
<button type="submit" :disabled="savingAgentUpdate">
|
||||
{{ savingAgentUpdate ? "Сохранение…" : "Сохранить обновления агентов" }}
|
||||
</button>
|
||||
@@ -566,6 +592,7 @@ import {
|
||||
fetchLinuxAdminSettings,
|
||||
fetchAgentUpdateSettings,
|
||||
saveAgentUpdateSettings,
|
||||
testAgentGitRelease,
|
||||
revokeMobileDevice,
|
||||
deleteMobileDevice,
|
||||
revokeMobileEnrollmentCode,
|
||||
@@ -658,6 +685,8 @@ const savingUi = ref(false);
|
||||
const savingWinAdmin = ref(false);
|
||||
const savingLinuxAdmin = ref(false);
|
||||
const savingAgentUpdate = ref(false);
|
||||
const testingAgentGit = ref(false);
|
||||
const agentGitTestErrors = ref("");
|
||||
const savingTelegram = ref(false);
|
||||
const savingWebhook = ref(false);
|
||||
const savingEmail = ref(false);
|
||||
@@ -754,8 +783,9 @@ const agentUpdateForm = reactive({
|
||||
mode: "gpo" as "gpo" | "sac",
|
||||
fallback_enabled: true,
|
||||
fallback_after_minutes: 15,
|
||||
recommended_rdp_version: "",
|
||||
recommended_ssh_version: "",
|
||||
rdp_git_repo_url: "",
|
||||
ssh_git_repo_url: "",
|
||||
git_branch: "main",
|
||||
win_agent_update_script: "",
|
||||
});
|
||||
|
||||
@@ -973,15 +1003,51 @@ async function saveLinuxAdminSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateTime(iso: string | null | undefined): string {
|
||||
if (!iso) return "—";
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return iso;
|
||||
return d.toLocaleString("ru-RU");
|
||||
}
|
||||
|
||||
async function loadAgentUpdateSettings() {
|
||||
const data = await fetchAgentUpdateSettings();
|
||||
agentUpdateLoaded.value = data;
|
||||
agentUpdateForm.mode = data.mode;
|
||||
agentUpdateForm.fallback_enabled = data.fallback_enabled;
|
||||
agentUpdateForm.fallback_after_minutes = data.fallback_after_minutes;
|
||||
agentUpdateForm.recommended_rdp_version = data.recommended_rdp_version ?? "";
|
||||
agentUpdateForm.recommended_ssh_version = data.recommended_ssh_version ?? "";
|
||||
agentUpdateForm.rdp_git_repo_url = data.rdp_git_repo_url ?? "";
|
||||
agentUpdateForm.ssh_git_repo_url = data.ssh_git_repo_url ?? "";
|
||||
agentUpdateForm.git_branch = data.git_branch ?? "main";
|
||||
agentUpdateForm.win_agent_update_script = data.win_agent_update_script ?? "";
|
||||
agentGitTestErrors.value = Object.values(data.git_errors ?? {}).join("; ");
|
||||
}
|
||||
|
||||
async function runAgentGitTest() {
|
||||
testingAgentGit.value = true;
|
||||
agentGitTestErrors.value = "";
|
||||
error.value = "";
|
||||
try {
|
||||
const result = await testAgentGitRelease();
|
||||
if (agentUpdateLoaded.value) {
|
||||
agentUpdateLoaded.value = {
|
||||
...agentUpdateLoaded.value,
|
||||
git_latest_rdp_version: result.git_latest_rdp_version,
|
||||
git_latest_ssh_version: result.git_latest_ssh_version,
|
||||
git_fetched_at: result.git_fetched_at,
|
||||
git_errors: result.git_errors,
|
||||
};
|
||||
}
|
||||
const errs = Object.values(result.git_errors ?? {});
|
||||
agentGitTestErrors.value = errs.length ? errs.join("; ") : "";
|
||||
success.value = result.from_cache
|
||||
? "Версии из кэша (git не менялся)."
|
||||
: "Версии успешно прочитаны из git.";
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Ошибка проверки git";
|
||||
} finally {
|
||||
testingAgentGit.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAgentUpdateSettingsForm() {
|
||||
@@ -993,10 +1059,12 @@ async function saveAgentUpdateSettingsForm() {
|
||||
mode: agentUpdateForm.mode,
|
||||
fallback_enabled: agentUpdateForm.fallback_enabled,
|
||||
fallback_after_minutes: agentUpdateForm.fallback_after_minutes,
|
||||
recommended_rdp_version: agentUpdateForm.recommended_rdp_version.trim() || null,
|
||||
recommended_ssh_version: agentUpdateForm.recommended_ssh_version.trim() || null,
|
||||
rdp_git_repo_url: agentUpdateForm.rdp_git_repo_url.trim() || null,
|
||||
ssh_git_repo_url: agentUpdateForm.ssh_git_repo_url.trim() || null,
|
||||
git_branch: agentUpdateForm.git_branch.trim() || "main",
|
||||
win_agent_update_script: agentUpdateForm.win_agent_update_script.trim() || null,
|
||||
});
|
||||
agentGitTestErrors.value = Object.values(agentUpdateLoaded.value.git_errors ?? {}).join("; ");
|
||||
success.value = "Настройки обновления агентов сохранены.";
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Ошибка сохранения agent-updates";
|
||||
|
||||
Reference in New Issue
Block a user