492 lines
14 KiB
JavaScript
492 lines
14 KiB
JavaScript
(function () {
|
|
"use strict";
|
|
|
|
if (document.body.dataset.view !== "agent_management") return;
|
|
|
|
const ids = [
|
|
"agentManagementLoading",
|
|
"agentManagementError",
|
|
"agentManagementErrorText",
|
|
"agentManagementRetry",
|
|
"agentManagementContent",
|
|
"agentManagementRefresh",
|
|
"agentCount",
|
|
"agentZoneCount",
|
|
"agentWaitingEnrollmentCount",
|
|
"agentRows",
|
|
"agentEmpty",
|
|
"agentZoneRows",
|
|
"agentZoneEmpty",
|
|
"agentEnrollmentRows",
|
|
"agentEnrollmentEmpty",
|
|
"agentCreateForm",
|
|
"agentSite",
|
|
"agentZone",
|
|
"agentDisplayName",
|
|
"agentPlatform",
|
|
"agentCreateSubmit",
|
|
"agentCreateStatus",
|
|
"agentEnrollmentResult",
|
|
"agentEnrollmentToken",
|
|
"agentEnrollmentExpires",
|
|
"agentEnrollmentDownload",
|
|
"agentEnrollmentCopy",
|
|
"agentEnrollmentDismiss",
|
|
"agentEnrollmentCopyStatus"
|
|
];
|
|
|
|
const el = {};
|
|
|
|
ids.forEach(function (id) {
|
|
el[id] = document.getElementById(id);
|
|
});
|
|
|
|
let registry = {
|
|
agents: [],
|
|
sites: [],
|
|
zones: []
|
|
};
|
|
|
|
let enrollments = [];
|
|
|
|
function text(value, fallback) {
|
|
if (value === null || value === undefined || value === "") {
|
|
return fallback || "Unavailable";
|
|
}
|
|
return String(value);
|
|
}
|
|
|
|
function dateTime(value) {
|
|
if (!value) return "Never";
|
|
const parsed = new Date(value);
|
|
if (Number.isNaN(parsed.valueOf())) return "Unavailable";
|
|
return parsed.toLocaleString();
|
|
}
|
|
|
|
function words(value) {
|
|
return text(value, "Unknown")
|
|
.replaceAll("_", " ")
|
|
.replace(/\b\w/g, function (letter) {
|
|
return letter.toUpperCase();
|
|
});
|
|
}
|
|
|
|
function cell(value, className) {
|
|
const node = document.createElement("td");
|
|
if (className) node.className = className;
|
|
node.textContent = value;
|
|
return node;
|
|
}
|
|
|
|
function setState(name) {
|
|
el.agentManagementLoading.hidden = name !== "loading";
|
|
el.agentManagementError.hidden = name !== "error";
|
|
el.agentManagementContent.hidden = name !== "ready";
|
|
}
|
|
|
|
async function api(path, options) {
|
|
const response = await fetch(path, Object.assign({
|
|
credentials: "same-origin",
|
|
cache: "no-store",
|
|
headers: {
|
|
"Accept": "application/json"
|
|
}
|
|
}, options || {}));
|
|
|
|
const payload = await response.json().catch(function () {
|
|
return {};
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const detail = payload && payload.detail
|
|
? String(payload.detail)
|
|
: "Request failed with status " + response.status + ".";
|
|
throw new Error(detail);
|
|
}
|
|
|
|
return payload;
|
|
}
|
|
|
|
function renderAgents() {
|
|
el.agentRows.replaceChildren();
|
|
|
|
registry.agents.forEach(function (agent) {
|
|
const row = document.createElement("tr");
|
|
|
|
const identity = document.createElement("td");
|
|
const name = document.createElement("strong");
|
|
const id = document.createElement("span");
|
|
|
|
name.textContent = text(agent.display_name, "Unnamed Agent");
|
|
id.className = "secondary";
|
|
id.textContent = agent.agent_id;
|
|
|
|
identity.append(name, id);
|
|
|
|
const location = [
|
|
text(agent.site_name, "Unknown site"),
|
|
text(agent.agent_zone_name, "No Agent Zone")
|
|
].join(" / ");
|
|
|
|
const host = [
|
|
text(agent.hostname, "Unknown host"),
|
|
text(agent.ip_address, "No IP")
|
|
].join(" / ");
|
|
|
|
row.append(
|
|
identity,
|
|
cell(location),
|
|
cell(host),
|
|
cell(text(agent.version, "Unknown")),
|
|
cell(dateTime(agent.last_checkin_at)),
|
|
cell(words(agent.credential_state)),
|
|
cell(words(agent.routing_state))
|
|
);
|
|
|
|
el.agentRows.appendChild(row);
|
|
});
|
|
|
|
el.agentEmpty.hidden = registry.agents.length !== 0;
|
|
}
|
|
|
|
function renderZones() {
|
|
el.agentZoneRows.replaceChildren();
|
|
|
|
registry.zones.forEach(function (zone) {
|
|
const row = document.createElement("tr");
|
|
|
|
const networks = Array.isArray(zone.networks) && zone.networks.length
|
|
? zone.networks.join(", ")
|
|
: "No networks recorded";
|
|
|
|
row.append(
|
|
cell(zone.site_name),
|
|
cell(zone.name),
|
|
cell(networks),
|
|
cell(text(zone.assigned_agent_id, "Unassigned")),
|
|
cell(zone.is_enabled ? "Enabled" : "Disabled")
|
|
);
|
|
|
|
el.agentZoneRows.appendChild(row);
|
|
});
|
|
|
|
el.agentZoneEmpty.hidden = registry.zones.length !== 0;
|
|
}
|
|
|
|
function renderEnrollments() {
|
|
el.agentEnrollmentRows.replaceChildren();
|
|
|
|
enrollments.forEach(function (enrollment) {
|
|
const row = document.createElement("tr");
|
|
|
|
row.append(
|
|
cell(enrollment.display_name),
|
|
cell(
|
|
enrollment.site_name + " / " +
|
|
enrollment.agent_zone_name
|
|
),
|
|
cell(words(enrollment.status)),
|
|
cell(dateTime(enrollment.created_at)),
|
|
cell(dateTime(enrollment.expires_at)),
|
|
cell(text(enrollment.enrolled_agent_id, "Not enrolled"))
|
|
);
|
|
|
|
el.agentEnrollmentRows.appendChild(row);
|
|
});
|
|
|
|
el.agentEnrollmentEmpty.hidden = enrollments.length !== 0;
|
|
}
|
|
|
|
function populateSites() {
|
|
if (!el.agentSite) return;
|
|
|
|
const selected = el.agentSite.value;
|
|
|
|
el.agentSite.replaceChildren();
|
|
|
|
const placeholder = document.createElement("option");
|
|
placeholder.value = "";
|
|
placeholder.textContent = "Choose a site";
|
|
el.agentSite.appendChild(placeholder);
|
|
|
|
registry.sites.forEach(function (site) {
|
|
const option = document.createElement("option");
|
|
option.value = site.id;
|
|
option.textContent = site.name;
|
|
el.agentSite.appendChild(option);
|
|
});
|
|
|
|
if ([...el.agentSite.options].some(function (option) {
|
|
return option.value === selected;
|
|
})) {
|
|
el.agentSite.value = selected;
|
|
}
|
|
|
|
populateZones();
|
|
}
|
|
|
|
function populateZones() {
|
|
if (!el.agentZone || !el.agentSite) return;
|
|
|
|
const siteId = el.agentSite.value;
|
|
const selected = el.agentZone.value;
|
|
|
|
el.agentZone.replaceChildren();
|
|
|
|
const placeholder = document.createElement("option");
|
|
placeholder.value = "";
|
|
|
|
if (!siteId) {
|
|
placeholder.textContent = "Choose a site first";
|
|
el.agentZone.disabled = true;
|
|
el.agentZone.appendChild(placeholder);
|
|
return;
|
|
}
|
|
|
|
placeholder.textContent = "Choose an Agent Zone";
|
|
el.agentZone.appendChild(placeholder);
|
|
|
|
const available = registry.zones.filter(function (zone) {
|
|
return (
|
|
zone.site_id === siteId &&
|
|
zone.is_enabled === true &&
|
|
!zone.assigned_agent_id
|
|
);
|
|
});
|
|
|
|
available.forEach(function (zone) {
|
|
const option = document.createElement("option");
|
|
option.value = zone.id;
|
|
option.textContent = zone.name;
|
|
el.agentZone.appendChild(option);
|
|
});
|
|
|
|
el.agentZone.disabled = available.length === 0;
|
|
|
|
if ([...el.agentZone.options].some(function (option) {
|
|
return option.value === selected;
|
|
})) {
|
|
el.agentZone.value = selected;
|
|
}
|
|
}
|
|
|
|
function renderSummary() {
|
|
el.agentCount.textContent = String(registry.agents.length);
|
|
el.agentZoneCount.textContent = String(registry.zones.length);
|
|
|
|
const waiting = enrollments.filter(function (item) {
|
|
return item.status === "waiting";
|
|
}).length;
|
|
|
|
el.agentWaitingEnrollmentCount.textContent = String(waiting);
|
|
}
|
|
|
|
function render() {
|
|
renderSummary();
|
|
renderAgents();
|
|
renderZones();
|
|
renderEnrollments();
|
|
populateSites();
|
|
}
|
|
|
|
async function refreshData(showLoading) {
|
|
if (showLoading !== false) setState("loading");
|
|
|
|
try {
|
|
const results = await Promise.all([
|
|
api("/api/agents"),
|
|
api("/api/agent-enrollments")
|
|
]);
|
|
|
|
registry = results[0] && results[0].data
|
|
? results[0].data
|
|
: { agents: [], sites: [], zones: [] };
|
|
|
|
enrollments = results[1] && Array.isArray(results[1].data)
|
|
? results[1].data
|
|
: [];
|
|
|
|
render();
|
|
setState("ready");
|
|
} catch (error) {
|
|
el.agentManagementErrorText.textContent =
|
|
error.message || "Agent information could not be loaded.";
|
|
setState("error");
|
|
}
|
|
}
|
|
|
|
async function createEnrollment(event) {
|
|
event.preventDefault();
|
|
|
|
if (!el.agentCreateForm) return;
|
|
|
|
el.agentCreateSubmit.disabled = true;
|
|
el.agentCreateStatus.textContent = "Creating enrollment...";
|
|
|
|
try {
|
|
const payload = await api("/api/agent-enrollments", {
|
|
method: "POST",
|
|
headers: {
|
|
"Accept": "application/json",
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify({
|
|
site_id: el.agentSite.value,
|
|
agent_zone_id: el.agentZone.value,
|
|
display_name: el.agentDisplayName.value.trim()
|
|
})
|
|
});
|
|
|
|
const enrollment = payload.data;
|
|
|
|
el.agentEnrollmentToken.textContent =
|
|
enrollment.enrollment_token;
|
|
|
|
el.agentEnrollmentExpires.textContent =
|
|
"Expires " + dateTime(enrollment.expires_at);
|
|
|
|
el.agentEnrollmentCopyStatus.textContent = "";
|
|
el.agentEnrollmentResult.hidden = false;
|
|
el.agentCreateStatus.textContent = "Enrollment created.";
|
|
|
|
el.agentDisplayName.value = "";
|
|
|
|
await refreshData(false);
|
|
} catch (error) {
|
|
el.agentCreateStatus.textContent =
|
|
error.message || "Enrollment could not be created.";
|
|
} finally {
|
|
el.agentCreateSubmit.disabled = false;
|
|
}
|
|
}
|
|
|
|
async function downloadWindowsPackage() {
|
|
const token = el.agentEnrollmentToken.textContent;
|
|
|
|
if (!token) {
|
|
el.agentEnrollmentCopyStatus.textContent =
|
|
"No enrollment token is available to download.";
|
|
return;
|
|
}
|
|
|
|
el.agentEnrollmentDownload.disabled = true;
|
|
el.agentEnrollmentCopyStatus.textContent =
|
|
"Building Windows Agent package...";
|
|
|
|
try {
|
|
const response = await fetch(
|
|
"/api/agent-enrollments/windows-package",
|
|
{
|
|
method: "POST",
|
|
credentials: "same-origin",
|
|
cache: "no-store",
|
|
headers: {
|
|
"Accept": "application/zip",
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify({
|
|
token: token
|
|
})
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const payload = await response.json().catch(function () {
|
|
return {};
|
|
});
|
|
|
|
throw new Error(
|
|
payload && payload.detail
|
|
? String(payload.detail)
|
|
: "Package request failed with status " +
|
|
response.status + "."
|
|
);
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
const downloadUrl = window.URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
|
|
link.href = downloadUrl;
|
|
link.download = "MinerOpsAgent-Windows.zip";
|
|
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
|
|
window.URL.revokeObjectURL(downloadUrl);
|
|
|
|
dismissToken();
|
|
|
|
el.agentCreateStatus.textContent =
|
|
"Windows Agent package downloaded. " +
|
|
"The on-screen token has been cleared.";
|
|
} catch (error) {
|
|
el.agentEnrollmentCopyStatus.textContent =
|
|
error.message || "Windows package could not be downloaded.";
|
|
} finally {
|
|
el.agentEnrollmentDownload.disabled = false;
|
|
}
|
|
}
|
|
|
|
async function copyToken() {
|
|
const token = el.agentEnrollmentToken.textContent;
|
|
|
|
if (!token) return;
|
|
|
|
try {
|
|
await navigator.clipboard.writeText(token);
|
|
el.agentEnrollmentCopyStatus.textContent =
|
|
"Enrollment token copied.";
|
|
} catch (error) {
|
|
el.agentEnrollmentCopyStatus.textContent =
|
|
"Copy was unavailable. Select and copy the token manually.";
|
|
}
|
|
}
|
|
|
|
function dismissToken() {
|
|
el.agentEnrollmentToken.textContent = "";
|
|
el.agentEnrollmentExpires.textContent = "";
|
|
el.agentEnrollmentCopyStatus.textContent = "";
|
|
el.agentEnrollmentResult.hidden = true;
|
|
}
|
|
|
|
el.agentManagementRetry.addEventListener("click", function () {
|
|
refreshData(true);
|
|
});
|
|
|
|
if (el.agentManagementRefresh) {
|
|
el.agentManagementRefresh.addEventListener("click", function () {
|
|
refreshData(true);
|
|
});
|
|
}
|
|
|
|
if (el.agentSite) {
|
|
el.agentSite.addEventListener("change", populateZones);
|
|
}
|
|
|
|
if (el.agentCreateForm) {
|
|
el.agentCreateForm.addEventListener("submit", createEnrollment);
|
|
}
|
|
|
|
if (el.agentEnrollmentDownload) {
|
|
el.agentEnrollmentDownload.addEventListener(
|
|
"click",
|
|
downloadWindowsPackage
|
|
);
|
|
}
|
|
|
|
if (el.agentEnrollmentCopy) {
|
|
el.agentEnrollmentCopy.addEventListener("click", copyToken);
|
|
}
|
|
|
|
if (el.agentEnrollmentDismiss) {
|
|
el.agentEnrollmentDismiss.addEventListener(
|
|
"click",
|
|
dismissToken
|
|
);
|
|
}
|
|
|
|
refreshData(true);
|
|
}());
|