feat(windows): add recoverable existing-agent credential upgrade

This commit is contained in:
lussierj committed 2026-10-14 13:50:19 +00:00
1 parent 3cfb0d1abe
commit 97a0139e3d
25 files changed
+11122 -108

No files matched your search

+88 -12
View File
@@ -27,6 +27,8 @@
"agentCreateSubmit",
"agentCreateStatus",
"agentEnrollmentResult",
"agentEnrollmentResultTitle",
"agentEnrollmentResultCopy",
"agentEnrollmentToken",
"agentEnrollmentExpires",
"agentEnrollmentDownload",
@@ -48,6 +50,8 @@
};
let enrollments = [];
let packageEndpoint = "/api/agent-enrollments/windows-package";
let packageFilename = "MinerOpsAgent-Windows.zip";
function text(value, fallback) {
if (value === null || value === undefined || value === "") {
@@ -143,6 +147,27 @@
cell(words(agent.routing_state))
);
const action = document.createElement("td");
const canManage =
el.agentManagementContent.dataset.canManageAgents === "true";
if (
canManage &&
agent.credential_state === "legacy" &&
agent.routing_state === "assigned"
) {
const button = document.createElement("button");
button.type = "button";
button.className = "quiet-button";
button.textContent = "Upgrade credential";
button.addEventListener("click", function () {
createCredentialTransition(agent);
});
action.appendChild(button);
} else {
action.textContent = "—";
}
row.appendChild(action);
el.agentRows.appendChild(row);
});
@@ -188,7 +213,10 @@
cell(words(enrollment.status)),
cell(dateTime(enrollment.created_at)),
cell(dateTime(enrollment.expires_at)),
cell(text(enrollment.enrolled_agent_id, "Not enrolled"))
cell(text(
enrollment.enrolled_agent_id || enrollment.target_agent_id,
"Not enrolled"
))
);
el.agentEnrollmentRows.appendChild(row);
@@ -338,15 +366,15 @@
});
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;
showOneTimeToken({
token: enrollment.enrollment_token,
expiresAt: enrollment.expires_at,
title: "Enrollment created",
copy: "This token creates a new Agent identity. Keep it private. " +
"MinerOps does not store the plaintext token.",
endpoint: "/api/agent-enrollments/windows-package",
filename: "MinerOpsAgent-Windows.zip"
});
el.agentCreateStatus.textContent = "Enrollment created.";
el.agentDisplayName.value = "";
@@ -360,6 +388,54 @@
}
}
function showOneTimeToken(options) {
el.agentEnrollmentToken.textContent = options.token;
el.agentEnrollmentExpires.textContent =
"Expires " + dateTime(options.expiresAt);
el.agentEnrollmentResultTitle.textContent = options.title;
el.agentEnrollmentResultCopy.textContent = options.copy;
el.agentEnrollmentCopyStatus.textContent = "";
el.agentEnrollmentResult.hidden = false;
packageEndpoint = options.endpoint;
packageFilename = options.filename;
}
async function createCredentialTransition(agent) {
const confirmed = window.confirm(
"Create a one-time credential upgrade for " + agent.agent_id +
"? Its Agent identity, Site, and Zone will remain unchanged."
);
if (!confirmed) return;
try {
const payload = await api("/api/agent-credential-transitions", {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({agent_id: agent.agent_id})
});
const transition = payload.data;
showOneTimeToken({
token: transition.transition_token,
expiresAt: transition.expires_at,
title: "Existing Agent credential upgrade created",
copy: "This token is bound to the existing Agent identity " +
transition.target_agent_id + ". It cannot enroll a new " +
"Agent or transfer its Zone.",
endpoint: "/api/agent-credential-transitions/windows-package",
filename: "MinerOpsAgent-Credential-Upgrade-Windows.zip"
});
el.agentCreateStatus.textContent =
"Credential upgrade created for " + transition.target_agent_id + ".";
await refreshData(false);
} catch (error) {
el.agentCreateStatus.textContent =
error.message || "Credential upgrade could not be created.";
}
}
async function downloadWindowsPackage() {
const token = el.agentEnrollmentToken.textContent;
@@ -375,7 +451,7 @@
try {
const response = await fetch(
"/api/agent-enrollments/windows-package",
packageEndpoint,
{
method: "POST",
credentials: "same-origin",
@@ -408,7 +484,7 @@
const link = document.createElement("a");
link.href = downloadUrl;
link.download = "MinerOpsAgent-Windows.zip";
link.download = packageFilename;
document.body.appendChild(link);
link.click();