feat(pfsense): add AW poller and host group sections

This commit is contained in:
igor04091968
2026-04-27 04:41:32 +03:00
parent 70ffd1f3e8
commit e80ae5fbc3
13 changed files with 702 additions and 3 deletions
+223 -3
View File
@@ -1,6 +1,6 @@
(function () {
window.__awRuPatchVersion = "template-v4-dlp-review";
document.documentElement.setAttribute("data-aw-ru-patch", "template-v4-dlp-review");
window.__awRuPatchVersion = "template-v5-dlp-review-host-groups";
document.documentElement.setAttribute("data-aw-ru-patch", "template-v5-dlp-review-host-groups");
const exact = new Map([
["ActivityWatch", "АктивВотч"],
@@ -322,7 +322,17 @@
'.aw-ru-dlp-message { margin-top: 8px; font-size: 12px; }',
'.aw-ru-dlp-actions { display: flex; gap: 6px; flex-wrap: wrap; }',
'.aw-ru-dlp-section { margin-top: 18px; }',
'.aw-ru-dlp-section h5 { margin: 0 0 8px; }'
'.aw-ru-dlp-section h5 { margin: 0 0 8px; }',
'.aw-ru-host-groups { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(20,20,20,.03); }',
'.aw-ru-host-groups-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }',
'.aw-ru-host-group-card { border: 1px solid rgba(120,120,120,.25); border-radius: 8px; padding: 12px; background: rgba(255,255,255,.02); }',
'.aw-ru-host-group-card h4 { margin: 0 0 8px; }',
'.aw-ru-host-group-card p { margin: 0 0 12px; font-size: 13px; opacity: .85; }',
'.aw-ru-host-list { display: flex; flex-direction: column; gap: 8px; }',
'.aw-ru-host-item { border: 1px solid rgba(120,120,120,.2); border-radius: 6px; padding: 8px; }',
'.aw-ru-host-item-title { font-weight: 600; margin-bottom: 6px; }',
'.aw-ru-host-links { display: flex; flex-wrap: wrap; gap: 6px; }',
'.aw-ru-host-links a { display: inline-block; padding: 4px 8px; border-radius: 999px; background: rgba(90,140,255,.15); text-decoration: none; }'
].join("\n");
document.head.appendChild(style);
}
@@ -619,6 +629,215 @@
}
}
function isHomeRoute() {
const hash = window.location.hash || "";
return !hash || /^#\/home(?:[/?#]|$)/i.test(hash);
}
function getDefaultHostGroupsConfig() {
return {
groups: [
{
id: "windows-rdp",
name: "Windows RDP",
description: "Пользовательские Windows/RDP хосты.",
patterns: ["^(SHARKON|WIN|RDP|TERM|TS-|WS-)"],
links: [
{ label: "Активность", type: "activity" },
{ label: "DLP", type: "bucket", bucket_prefix: "aw-dlp-endpoint-signals_" }
]
},
{
id: "virtual-infra",
name: "Virtual servers + Proxmox",
description: "Инфраструктурные VM, Proxmox и сетевые узлы.",
patterns: ["^(PFSENSE|PVE|PROXMOX|DEBIAN|UBUNTU|LINUX|VM-|SRV-|INFRA-)"],
links: [
{ label: "pfSense health", type: "bucket", bucket_prefix: "aw-pfsense-health_" },
{ label: "pfSense gateways", type: "bucket", bucket_prefix: "aw-pfsense-gateways_" },
{ label: "Все бакеты", type: "buckets" }
]
}
],
ungrouped_name: "Прочие хосты"
};
}
function getHostGroupsState() {
if (!window.__awRuHostGroupsState) {
window.__awRuHostGroupsState = {
config: null,
buckets: null,
loading: false
};
}
return window.__awRuHostGroupsState;
}
async function ensureHostGroupsData() {
const state = getHostGroupsState();
if (state.loading) return state;
if (state.config && state.buckets) return state;
state.loading = true;
try {
if (!state.config) {
try {
state.config = await awApiJson("/js/aw-host-groups.json?v=" + encodeURIComponent(window.__awRuPatchVersion), { method: "GET", headers: {} });
} catch (error) {
state.config = getDefaultHostGroupsConfig();
}
}
if (!state.buckets) {
state.buckets = await awApiJson("/api/0/buckets/", { method: "GET", headers: {} });
}
} finally {
state.loading = false;
}
return state;
}
function extractHostFromBucket(bucketId, bucketMeta) {
if (bucketMeta && bucketMeta.hostname) return String(bucketMeta.hostname);
const prefixes = [
"aw-watcher-window_",
"aw-watcher-afk_",
"aw-dlp-endpoint-signals_",
"aw-dlp-incidents_",
"aw-pfsense-health_",
"aw-pfsense-gateways_",
"aw-pfsense-interfaces_"
];
for (const prefix of prefixes) {
if (bucketId.indexOf(prefix) === 0) {
return bucketId.slice(prefix.length);
}
}
return "";
}
function buildHostBucketMap(rawBuckets) {
const result = new Map();
const entries = Array.isArray(rawBuckets)
? rawBuckets.map(function (item) { return [item.id || "", item]; })
: Object.entries(rawBuckets || {});
entries.forEach(function (entry) {
const bucketId = entry[0];
const meta = entry[1] || {};
const host = extractHostFromBucket(bucketId, meta);
if (!host) return;
if (!result.has(host)) result.set(host, []);
result.get(host).push(bucketId);
});
return result;
}
function matchHostGroup(host, groups) {
for (const group of groups) {
const patterns = Array.isArray(group.patterns) ? group.patterns : [];
for (const pattern of patterns) {
try {
if (new RegExp(pattern, "i").test(host)) {
return group.id;
}
} catch (error) {
}
}
}
return "";
}
function buildHostLink(host, hostBuckets, linkDef) {
if (!linkDef || !linkDef.type) return "";
if (linkDef.type === "activity") {
return '#/activity/' + encodeURIComponent(host) + '/day/' + encodeURIComponent(new Date().toISOString().slice(0, 10)) + '/view/summary';
}
if (linkDef.type === "buckets") {
return "#/buckets";
}
if (linkDef.type === "bucket" && linkDef.bucket_prefix) {
const bucketId = String(linkDef.bucket_prefix) + host;
return hostBuckets.indexOf(bucketId) >= 0 ? '#/buckets/' + encodeURIComponent(bucketId) : "";
}
return "";
}
function renderHostGroupCards(state) {
const config = state.config || getDefaultHostGroupsConfig();
const groups = Array.isArray(config.groups) ? config.groups : [];
const hostBuckets = buildHostBucketMap(state.buckets);
const grouped = new Map();
groups.forEach(function (group) {
grouped.set(group.id, []);
});
grouped.set("__ungrouped__", []);
Array.from(hostBuckets.keys()).sort().forEach(function (host) {
const groupId = matchHostGroup(host, groups) || "__ungrouped__";
grouped.get(groupId).push(host);
});
const cards = [];
groups.forEach(function (group) {
const hosts = grouped.get(group.id) || [];
const items = hosts.map(function (host) {
const links = (group.links || []).map(function (linkDef) {
const href = buildHostLink(host, hostBuckets.get(host) || [], linkDef);
return href ? '<a href="' + escapeHtml(href) + '">' + escapeHtml(linkDef.label || "Открыть") + '</a>' : "";
}).filter(Boolean).join("");
return '<div class="aw-ru-host-item">' +
'<div class="aw-ru-host-item-title">' + escapeHtml(host) + '</div>' +
'<div class="aw-ru-host-links">' + links + '</div>' +
'</div>';
}).join("");
cards.push(
'<section class="aw-ru-host-group-card">' +
'<h4>' + escapeHtml(group.name || group.id) + '</h4>' +
'<p>' + escapeHtml(group.description || "") + '</p>' +
'<div class="aw-ru-host-list">' + (items || '<div class="aw-ru-host-item">Хосты пока не обнаружены.</div>') + '</div>' +
'</section>'
);
});
const ungroupedHosts = grouped.get("__ungrouped__") || [];
if (ungroupedHosts.length) {
cards.push(
'<section class="aw-ru-host-group-card">' +
'<h4>' + escapeHtml(config.ungrouped_name || "Прочие хосты") + '</h4>' +
'<p>Хосты, которые пока не попали под шаблоны группировки.</p>' +
'<div class="aw-ru-host-list">' +
ungroupedHosts.map(function (host) {
return '<div class="aw-ru-host-item"><div class="aw-ru-host-item-title">' + escapeHtml(host) + '</div></div>';
}).join("") +
'</div>' +
'</section>'
);
}
return cards.join("");
}
async function injectHostGroupsCenter(root) {
if (!isHomeRoute()) return;
const heading = root.querySelector("h3");
if (!heading) return;
let center = root.querySelector("[data-aw-ru-host-groups='1']");
if (!center) {
center = document.createElement("section");
center.className = "aw-ru-host-groups";
center.setAttribute("data-aw-ru-host-groups", "1");
center.innerHTML =
'<h4>Разделы хостов</h4>' +
'<p>Здесь хосты разделены на пользовательские Windows RDP и инфраструктурные виртуальные серверы/Proxmox.</p>' +
'<div class="aw-ru-host-groups-grid" data-aw-ru-host-groups-grid><section class="aw-ru-host-group-card"><p>Загрузка...</p></section></div>';
heading.parentElement.insertBefore(center, heading.nextSibling);
}
const state = await ensureHostGroupsData();
center.querySelector("[data-aw-ru-host-groups-grid]").innerHTML = renderHostGroupCards(state);
}
function renderDlpTableRows(center, host) {
const state = getSuppressionState();
const tbody = center.querySelector("[data-aw-ru-dlp-events]");
@@ -1144,6 +1363,7 @@
injectDlpNavigation(document.body);
injectDlpReviewCenter(document.body);
injectDlpAlertsCenter(document.body);
injectHostGroupsCenter(document.body).catch(function () {});
redirectBareTrendsRoute();
}