186 lines
7.6 KiB
JavaScript
186 lines
7.6 KiB
JavaScript
async function handleInventoryActions(event, target, action, repository) {
|
|
if (action === "scan-server-inventory") {
|
|
setLoading(true, "Scanning Docker, Compose and DockerMan workloads…");
|
|
try {
|
|
await refreshDeploymentTruth(true);
|
|
const detected = (ui.serverDiscovery || []).reduce(
|
|
(total, item) => total + Number(item.detected || 0),
|
|
0,
|
|
);
|
|
const review = (ui.serverDiscovery || []).reduce(
|
|
(total, item) => total + Number(item.needsReview || 0),
|
|
0,
|
|
);
|
|
const failures = (ui.serverDiscovery || []).filter((item) => item.error);
|
|
if (failures.length) {
|
|
showToast(
|
|
"Server scan failed",
|
|
failures.map((item) => `${item.serverName || item.serverId}: ${item.error}`).join(" · "),
|
|
"error",
|
|
);
|
|
} else {
|
|
showToast(
|
|
"Server inventory updated",
|
|
`${detected} workload${detected === 1 ? "" : "s"} detected; ${review} require manual review.`,
|
|
review ? "info" : "success",
|
|
);
|
|
}
|
|
} catch (error) {
|
|
showToast("Server scan failed", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
} else if (action === "plan-server-reconciliation") {
|
|
setLoading(true, "Building a read-only reconciliation preview…");
|
|
try {
|
|
const result = await window.forgeflow.planServerReconciliation(target.dataset.serverId);
|
|
ui.modal = { type: "server-reconciliation-plan", result };
|
|
render();
|
|
} catch (error) {
|
|
showToast("Could not build reconciliation plan", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
} else if (action === "apply-server-reconciliation") {
|
|
setLoading(true, "Applying the reviewed configuration plan…");
|
|
try {
|
|
const result = await window.forgeflow.applyServerReconciliation(target.dataset.serverId, target.dataset.planId);
|
|
if (result.state) ui.boot.state = result.state;
|
|
ui.modal = null;
|
|
await refreshRepositories(false, true);
|
|
await refreshDeploymentTruth(false);
|
|
showToast("Reconciliation applied", `${result.adopted || 0} link(s) added and ${result.refreshed || 0} profile(s) refreshed. No containers were changed.`, "success");
|
|
} catch (error) {
|
|
showToast("Reconciliation was not applied", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
} else if (action === "quick-link-server-workload") {
|
|
const serverResult = (ui.serverDiscovery || []).find(
|
|
(item) => item.serverId === target.dataset.serverId,
|
|
);
|
|
const workload = serverResult?.workloads?.find(
|
|
(item) => item.workloadId === target.dataset.workloadId,
|
|
);
|
|
const linkedRepository = ui.repositories.find(
|
|
(item) => item.fullName === target.dataset.repository,
|
|
);
|
|
if (!workload || !linkedRepository || !workload.remoteFolderCandidate) {
|
|
showToast("Automatic link unavailable", "Scan the server again and use Review & link.", "error");
|
|
return;
|
|
}
|
|
setLoading(true, `Linking ${workload.displayName} to ${linkedRepository.fullName}…`);
|
|
try {
|
|
const result = await window.forgeflow.linkServerWorkload(
|
|
linkedRepository,
|
|
target.dataset.serverId,
|
|
target.dataset.workloadId,
|
|
"server-git",
|
|
workload.remoteFolderCandidate,
|
|
);
|
|
if (result.state) ui.boot.state = result.state;
|
|
ui.selectedProfileId = result.profile?.id || null;
|
|
await refreshRepositories(false, true);
|
|
await refreshDeploymentTruth(false);
|
|
showToast(
|
|
"Deployment linked",
|
|
`${linkedRepository.fullName} is linked to ${workload.compose?.workingDir || workload.remoteFolderCandidate}. Compose values were read from the server.`,
|
|
"success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Could not link deployment", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
} else if (action === "preview-inventory-review") {
|
|
const reviewAction = document.querySelector("#inventory-review-action")?.value || "ignore";
|
|
const reason = document.querySelector("#inventory-review-reason")?.value.trim() || "";
|
|
const serverId = target.dataset.serverId;
|
|
const workloadId = target.dataset.workloadId;
|
|
try {
|
|
ui.inventoryReviewPlan = await window.forgeflow.planInventoryReview(serverId, workloadId, reviewAction, reason, document.querySelector("#workload-repository")?.value || null);
|
|
ui.modal = { type: "inventory-review-plan" };
|
|
render();
|
|
} catch (error) { showToast("Review preview unavailable", error.message, "error"); }
|
|
} else if (action === "apply-inventory-review") {
|
|
const plan = ui.inventoryReviewPlan;
|
|
if (!plan?.id) return;
|
|
setLoading(true, "Saving the evidence-bound inventory decision…");
|
|
try {
|
|
const result = await window.forgeflow.applyInventoryReview(plan.serverId, plan.workloadId, plan.action, plan.reason, plan.repositoryFullName, plan.id);
|
|
if (result.state) ui.boot.state = result.state;
|
|
ui.serverDiscovery = (ui.serverDiscovery || []).map((item) => item.serverId === plan.serverId ? result.inventory : item);
|
|
ui.inventoryReviewPlan = null; ui.modal = null; render();
|
|
showToast("Inventory decision saved", "Containers and Compose runtime were not changed.", "success");
|
|
} catch (error) { showToast("Inventory review failed safely", error.message, "error"); }
|
|
finally { setLoading(false); }
|
|
} else if (action === "link-server-workload") {
|
|
const serverResult = (ui.serverDiscovery || []).find(
|
|
(item) => item.serverId === target.dataset.serverId,
|
|
);
|
|
const workload = serverResult?.workloads?.find(
|
|
(item) => item.workloadId === target.dataset.workloadId,
|
|
);
|
|
if (!workload) {
|
|
showToast(
|
|
"Workload unavailable",
|
|
"Scan the server inventory again before linking this workload.",
|
|
"error",
|
|
);
|
|
return;
|
|
}
|
|
ui.modal = {
|
|
type: "workload-link",
|
|
serverId: target.dataset.serverId,
|
|
workloadId: target.dataset.workloadId,
|
|
repositoryFullName:
|
|
workload.candidates?.[0]?.repositoryFullName ||
|
|
repository?.fullName ||
|
|
ui.repositories[0]?.fullName ||
|
|
"",
|
|
remoteFolder: workload.remoteFolderCandidate || "",
|
|
};
|
|
render();
|
|
} else if (action === "confirm-link-server-workload") {
|
|
const repositoryFullName = document
|
|
.querySelector("#workload-repository")
|
|
?.value.trim();
|
|
const deploymentMode = document.querySelector("#workload-deployment-mode")?.value || "server-git";
|
|
const remoteFolder = document
|
|
.querySelector("#workload-remote-folder")
|
|
?.value.trim();
|
|
const linkedRepository = ui.repositories.find(
|
|
(item) => item.fullName === repositoryFullName,
|
|
);
|
|
if (!linkedRepository) {
|
|
showToast(
|
|
"Choose a repository",
|
|
"The workload must be linked to a ForgeFlow project.",
|
|
"error",
|
|
);
|
|
return;
|
|
}
|
|
setLoading(true, "Saving the permanent server workload link…");
|
|
try {
|
|
const result = await window.forgeflow.linkServerWorkload(
|
|
linkedRepository,
|
|
target.dataset.serverId,
|
|
target.dataset.workloadId,
|
|
deploymentMode,
|
|
remoteFolder,
|
|
);
|
|
if (result.state) ui.boot.state = result.state;
|
|
ui.modal = null;
|
|
ui.selectedProfileId = result.profile?.id || null;
|
|
await refreshRepositories(false, true);
|
|
await refreshDeploymentTruth(false);
|
|
showToast(
|
|
"Workload linked",
|
|
`${linkedRepository.fullName} now uses direct desktop-to-Unraid copy and the Compose configuration detected on the server.`,
|
|
"success",
|
|
);
|
|
} catch (error) {
|
|
showToast("Could not link workload", error.message, "error");
|
|
}
|
|
setLoading(false);
|
|
}
|
|
else return false;
|
|
return true;
|
|
}
|