Files
SAMY/samy.js
2025-12-21 19:16:19 -05:00

787 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Use globals provided by the PowerShell-generated HTML bridge
const tasks = (window.SAMY_TASKS || []);
const defaultPage = (window.SAMY_DEFAULT_PAGE || "onboard");
let completedTasks = 0;
let totalTasks = 0;
// Progress / title handling
function setTotalTaskCount(count) {
totalTasks = count;
completedTasks = 0;
updateTitle();
}
function logProgress(label, isSuccess) {
const statusBox = document.getElementById("status-box");
completedTasks++;
updateTitle();
const msg = isSuccess
? ` ${completedTasks}/${totalTasks} done: ${label}`
: ` ${completedTasks}/${totalTasks} failed: ${label}`;
const div = document.createElement("div");
div.style.color = isSuccess ? "lime" : "red";
div.textContent = msg;
statusBox?.appendChild(div);
if (completedTasks === totalTasks) {
const finalMsg = document.createElement("div");
finalMsg.style.marginTop = "10px";
finalMsg.innerHTML = `<strong> All tasks completed (${completedTasks}/${totalTasks})</strong>`;
statusBox?.appendChild(finalMsg);
document.title = ` ScriptMonkey - Complete (${completedTasks}/${totalTasks})`;
const sound = new Audio(
"data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAESsAACJWAAACABAAZGF0YQAAAAA="
);
sound.play().catch(() => {});
flashTitle(document.title);
}
}
function updateTitle() {
document.title = `ScriptMonkey - ${completedTasks}/${totalTasks} Done`;
}
function flashTitle(finalTitle) {
let flashes = 0;
const interval = setInterval(() => {
document.title = document.title === "" ? finalTitle : "";
flashes++;
if (flashes >= 10) {
clearInterval(interval);
document.title = finalTitle;
}
}, 800);
}
// =======================================================================
// Tab Navigation
// =======================================================================
document.addEventListener("DOMContentLoaded", () => {
const tabButtons = document.querySelectorAll(".tab-button");
const tabContents = document.querySelectorAll(".tab-content");
if (!tabButtons?.length || !tabContents?.length) {
console.error("ScriptMonkey: no tab buttons or tab contents found.");
return;
}
tabButtons.forEach((btn) => {
btn.addEventListener("click", () => {
tabButtons.forEach((b) => b.classList.remove("active"));
tabContents.forEach((c) => c.classList.remove("active"));
btn.classList.add("active");
const targetId = btn.dataset.tab;
const target = document.getElementById(targetId);
if (target) target.classList.add("active");
});
});
// Default tab from PS (onboard/offboard/devices)
const defaultTabId = `${defaultPage}Tab`;
const defaultBtn = document.querySelector(
`.tab-button[data-tab='${defaultTabId}']`
);
const defaultTab = document.getElementById(defaultTabId);
if (defaultBtn) defaultBtn.classList.add("active");
if (defaultTab) defaultTab.classList.add("active");
});
// =======================================================================
// Onboarding: Select-all left columns
// =======================================================================
function toggleColumn(col) {
const master = document.getElementById(
`selectAll${col[0].toUpperCase() + col.slice(1)}Checkbox`
);
const children = document.querySelectorAll(
`#onboardTab input[type=checkbox][data-column="${col}"]`
);
children.forEach((cb) => {
cb.checked = master.checked;
});
// fire change handlers
setTimeout(() => {
children.forEach((cb) => {
cb.dispatchEvent(new Event("change"));
});
}, 0);
}
function updateSelectAll(col) {
const master = document.getElementById(
`selectAll${col[0].toUpperCase() + col.slice(1)}Checkbox`
);
const children = document.querySelectorAll(
`#onboardTab input[type=checkbox][data-column="${col}"]`
);
if (!master) return;
master.checked = Array.from(children).every((cb) => cb.checked);
}
document.addEventListener("DOMContentLoaded", () => {
["left"].forEach((col) => {
document
.querySelectorAll(`#onboardTab input[type=checkbox][data-column="${col}"]`)
.forEach((cb) =>
cb.addEventListener("change", () => updateSelectAll(col))
);
updateSelectAll(col);
});
});
// =======================================================================
// Onboarding: Right side split Select All (apps + tweaks)
// =======================================================================
// apps = only checkboxes marked data-group="apps"
// tweaks = everything in right column NOT marked as apps
const ONBOARD_RIGHT_GROUPS = {
apps: {
masterId: "selectAllAppsCheckbox",
selector:
'#onboardTab input[type=checkbox][data-column="right"][data-group="apps"]',
},
tweaks: {
masterId: "selectAllTweaksCheckbox",
selector:
'#onboardTab input[type=checkbox][data-column="right"]:not([data-group="apps"])',
},
};
function toggleOnboardGroup(groupKey) {
const cfg = ONBOARD_RIGHT_GROUPS[groupKey];
if (!cfg) return;
const master = document.getElementById(cfg.masterId);
if (!master) return;
const children = document.querySelectorAll(cfg.selector);
children.forEach((cb) => {
cb.checked = master.checked;
});
// fire change handlers so renameComputer / suboptions update properly
setTimeout(() => {
children.forEach((cb) => cb.dispatchEvent(new Event("change")));
}, 0);
}
function updateOnboardGroupSelectAll(groupKey) {
const cfg = ONBOARD_RIGHT_GROUPS[groupKey];
if (!cfg) return;
const master = document.getElementById(cfg.masterId);
if (!master) return;
const children = document.querySelectorAll(cfg.selector);
if (!children.length) {
master.checked = false;
return;
}
master.checked = Array.from(children).every((cb) => cb.checked);
}
document.addEventListener("DOMContentLoaded", () => {
Object.keys(ONBOARD_RIGHT_GROUPS).forEach((groupKey) => {
const cfg = ONBOARD_RIGHT_GROUPS[groupKey];
// When any child checkbox changes, refresh that group's Select All
document.querySelectorAll(cfg.selector).forEach((cb) => {
cb.addEventListener("change", () =>
updateOnboardGroupSelectAll(groupKey)
);
});
// Initial set
updateOnboardGroupSelectAll(groupKey);
});
});
// =======================================================================
// Off-boarding Select All
// =======================================================================
function toggleOffboardAll() {
const master = document.getElementById("offboardSelectAll");
const children = document.querySelectorAll(
"#offboardTab input[type=checkbox]:not(#offboardSelectAll)"
);
children.forEach((cb) => {
cb.checked = master.checked;
});
}
function updateOffboardSelectAll() {
const master = document.getElementById("offboardSelectAll");
if (!master) return;
const children = document.querySelectorAll(
"#offboardTab input[type=checkbox]:not(#offboardSelectAll)"
);
if (!children.length) {
master.checked = false;
return;
}
master.checked = Array.from(children).every((cb) => cb.checked);
}
document.addEventListener("DOMContentLoaded", () => {
const offChildren = document.querySelectorAll(
"#offboardTab input[type=checkbox]:not(#offboardSelectAll)"
);
if (!offChildren?.length) return;
offChildren.forEach((cb) =>
cb.addEventListener("change", updateOffboardSelectAll)
);
updateOffboardSelectAll();
});
// =======================================================================
// DattoRMM options + Enter key handling
// =======================================================================
function toggleDattoRMMOptions() {
const master = document.getElementById("installDattoRMM");
const container = document.getElementById("installDattoRMMOptionsContainer");
if (!container) return;
const checked = master?.checked;
container.style.display = checked ? "block" : "none";
container
.querySelectorAll('input[type="checkbox"]')
.forEach((cb) => (cb.checked = checked));
}
document.addEventListener("DOMContentLoaded", () => {
const master = document.getElementById("installDattoRMM");
if (master) {
master.addEventListener("change", toggleDattoRMMOptions);
}
const passwordField = document.getElementById("Password");
const goButton = document.querySelector("button[onclick='fetchSites()']");
if (passwordField && goButton) {
passwordField.addEventListener("keydown", (e) => {
if (e.key === "Enter") goButton.click();
});
}
const siteDropdown = document.getElementById("dattoDropdown");
const runButton = document.querySelector(".run-button");
if (siteDropdown && runButton) {
siteDropdown.addEventListener("keydown", (e) => {
if (e.key === "Enter" && siteDropdown.value) runButton.click();
});
}
});
// =======================================================================
// Fetch Sites handler (calls /getpw)
// =======================================================================
async function fetchSites() {
const pwdInput = document.getElementById("Password");
const pwd = (pwdInput?.value ?? "").trim(); // allow blank
const dropdown = document.getElementById("dattoDropdown");
if (!dropdown) return;
dropdown.innerHTML = '<option disabled selected>Loading sites...</option>';
try {
const resp = await fetch("/getpw", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password: pwd }),
});
if (!resp.ok) throw new Error("HTTP " + resp.status);
const sites = await resp.json();
if (!Array.isArray(sites) || sites.length === 0) {
dropdown.innerHTML = '<option disabled selected>No sites returned</option>';
alert(
"No Datto sites returned. Verify credentials/allowlist, or try again in a moment."
);
return;
}
dropdown.innerHTML = "";
sites.forEach((site) => {
const option = document.createElement("option");
option.value = site.UID;
option.textContent = site.Name;
dropdown.appendChild(option);
});
const rmmContainer = document.getElementById("dattoRmmContainer");
if (rmmContainer) rmmContainer.style.display = "block";
} catch (e) {
console.error(e);
dropdown.innerHTML = '<option disabled selected>Error loading sites</option>';
alert(
"Failed to fetch sites. Check password or confirm your public IP is allowlisted."
);
}
}
// =======================================================================
// Printer management (Devices tab)
// =======================================================================
let allPrinters = [];
// POST /getprinters with password from Devices tab
async function fetchPrinters() {
const pwdInput = document.getElementById("PrinterPassword");
const pwd = (pwdInput?.value ?? ""); // allow blank
const clientContainer = document.getElementById("printerClientContainer");
const listContainer = document.getElementById("printerListContainer");
const dropdown = document.getElementById("printerClientDropdown");
const checkboxContainer = document.getElementById("printerCheckboxContainer");
if (dropdown) dropdown.innerHTML = '<option disabled selected>Loading clients...</option>';
if (checkboxContainer) checkboxContainer.innerHTML = "";
if (clientContainer) clientContainer.style.display = "none";
if (listContainer) listContainer.style.display = "none";
try {
const resp = await fetch("/getprinters", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password: pwd }),
});
if (!resp.ok) throw new Error("HTTP " + resp.status);
const data = await resp.json();
allPrinters = Array.isArray(data) ? data : [];
if (!allPrinters.length) {
alert("No printers returned. Verify credentials/allowlist, or try again in a moment.");
return;
}
const codes = [...new Set(allPrinters.map((p) => p.ClientCode))].sort();
if (dropdown) {
dropdown.innerHTML = "";
const defaultOpt = new Option("Select a client...", "", true, true);
defaultOpt.disabled = true;
dropdown.appendChild(defaultOpt);
codes.forEach((code) => dropdown.appendChild(new Option(code, code)));
}
if (clientContainer) clientContainer.style.display = "block";
} catch (e) {
console.error("fetchPrinters error:", e);
if (dropdown) dropdown.innerHTML = '<option disabled selected>Error loading clients</option>';
alert("Failed to fetch printers. Check password or confirm your public IP is allowlisted.");
}
}
function renderPrintersForClient(clientCode) {
const container = document.getElementById("printerCheckboxContainer");
const listContainer = document.getElementById("printerListContainer");
if (!container) return;
container.innerHTML = "";
const printers = allPrinters.filter((p) => p.ClientCode === clientCode);
if (!printers.length) {
container.textContent = "No printers found for this client.";
if (listContainer) listContainer.style.display = "block";
return;
}
printers.forEach((p, idx) => {
const id = `printer_${clientCode}_${idx}`;
const label = document.createElement("label");
label.style.display = "block";
label.style.marginBottom = "4px";
const cb = document.createElement("input");
cb.type = "checkbox";
cb.id = id;
cb.dataset.clientCode = p.ClientCode;
cb.dataset.profileName = p.ProfileName;
cb.dataset.displayName = p.DisplayName;
cb.dataset.location = p.Location;
cb.dataset.address = p.Address;
cb.dataset.printServer = p.PrintServer;
cb.dataset.shareName = p.ShareName;
cb.dataset.driverName = p.DriverName;
cb.dataset.driverInfPath = p.DriverInfPath;
const nameText = p.DisplayName || p.ProfileName || "Unnamed printer";
const locText = p.Location || "Unknown location";
label.appendChild(cb);
label.appendChild(document.createTextNode(" "));
label.appendChild(document.createTextNode(`${nameText} (${locText})`));
const defaultWrapper = document.createElement("div");
defaultWrapper.style.marginLeft = "24px";
defaultWrapper.style.fontSize = "0.85em";
defaultWrapper.style.opacity = "0.9";
const radio = document.createElement("input");
radio.type = "radio";
radio.name = "defaultPrinter";
radio.value = id;
const radioLabel = document.createElement("span");
radioLabel.textContent = " Make default";
defaultWrapper.appendChild(radio);
defaultWrapper.appendChild(radioLabel);
label.appendChild(document.createElement("br"));
label.appendChild(defaultWrapper);
container.appendChild(label);
});
if (listContainer) listContainer.style.display = "block";
}
async function installSelectedPrinters() {
const container = document.getElementById("printerCheckboxContainer");
if (!container) return;
const checked = container.querySelectorAll("input[type=checkbox]:checked");
if (!checked.length) {
alert("Please select at least one printer.");
return;
}
const defaultRadio = container.querySelector(
'input[type=radio][name="defaultPrinter"]:checked'
);
const defaultId = defaultRadio ? defaultRadio.value : null;
const selected = Array.from(checked).map((cb) => ({
ClientCode: cb.dataset.clientCode,
ProfileName: cb.dataset.profileName,
DisplayName: cb.dataset.displayName,
Location: cb.dataset.location,
Address: cb.dataset.address,
PrintServer: cb.dataset.printServer,
ShareName: cb.dataset.shareName,
DriverName: cb.dataset.driverName,
DriverInfPath: cb.dataset.driverInfPath,
SetAsDefault: defaultId !== null && cb.id === defaultId,
}));
try {
const resp = await fetch("/installprinters", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ printers: selected }),
});
if (!resp.ok) throw new Error("HTTP " + resp.status);
const result = await resp.json().catch(() => null);
console.log("Printer install result:", result);
} catch (e) {
console.error("installSelectedPrinters error:", e);
alert("Failed to trigger printer install.");
}
}
// =======================================================================
// Run Selected (main trigger)
// =======================================================================
async function triggerInstall() {
const runBtn = document.querySelector(".run-button");
if (!runBtn) return;
runBtn.disabled = true;
const statusBox = document.getElementById("status-box");
if (statusBox) statusBox.innerHTML = "";
try {
// Special-case elements ONCE
const dattoCB = document.getElementById("installDattoRMM");
const svsCB = document.getElementById("installSVSMSPModule");
const renameCB = document.getElementById("renameComputer");
const newNameInput = document.getElementById("txtNewComputerName");
// Standard tasks = all tasks except special-case ones
const checkedTasks = tasks.filter((t) => {
if (["installDattoRMM", "installSVSMSPModule", "renameComputer"].includes(t.id)) return false;
const cb = document.getElementById(t.id);
return cb && cb.checked;
});
// Count special-case tasks
let specialTasks = 0;
if (dattoCB && dattoCB.checked) specialTasks++;
if (svsCB && svsCB.checked) specialTasks++;
const extraTasks = (renameCB && renameCB.checked) ? 1 : 0;
const total = checkedTasks.length + specialTasks + extraTasks;
if (total === 0) {
alert("Please select at least one task.");
return;
}
setTotalTaskCount(total);
// 1) DattoRMM first
if (dattoCB && dattoCB.checked) {
const sub = Array.from(
document.querySelectorAll(".sub-option-installDattoRMM:checked")
).map((x) => x.value);
const dropdown = document.getElementById("dattoDropdown");
const uid = dropdown?.value;
const name = dropdown?.selectedOptions?.[0]?.text || "Datto";
if (!uid) {
alert("Please select a Datto RMM site before running.");
logProgress("Install DattoRMM (no site selected)", false);
} else {
try {
await fetch("/installDattoRMM", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ checkedValues: sub, UID: uid, Name: name }),
});
logProgress("Install DattoRMM", true);
} catch (e) {
logProgress("Install DattoRMM", false);
console.error(e);
}
}
}
// 2) SVSMSP module second
if (svsCB && svsCB.checked) {
try {
await fetch("/installSVSMSPModule", { method: "GET" });
logProgress("Install SVSMSP Module", true);
} catch (e) {
logProgress("Install SVSMSP Module", false);
console.error(e);
}
}
// 3) Remaining tasks
for (const t of tasks) {
if (["installDattoRMM", "installSVSMSPModule", "renameComputer"].includes(t.id)) continue;
const cb = document.getElementById(t.id);
if (!cb || !cb.checked) continue;
try {
await fetch(t.handler, { method: "GET" });
logProgress(t.label || t.id, true);
} catch (e) {
logProgress(t.label || t.id, false);
console.error(`Error running ${t.id}:`, e);
}
}
// 4) Rename computer LAST
if (renameCB && renameCB.checked && newNameInput) {
const newName = newNameInput.value.trim();
const nameIsValid =
newName.length > 0 &&
newName.length <= 15 &&
/^[A-Za-z0-9-]+$/.test(newName);
if (!nameIsValid) {
alert("Invalid computer name. Must be 1-15 characters and only letters, numbers, and hyphens.");
logProgress("Rename computer", false);
} else {
try {
await fetch("/renameComputer", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ newName }),
});
logProgress("Rename computer", true);
} catch (e) {
console.error("Error calling /renameComputer:", e);
logProgress("Rename computer", false);
}
}
}
} catch (e) {
console.error("triggerInstall fatal error:", e);
} finally {
runBtn.disabled = false;
// Best-effort notification to the server
try {
await fetch("/tasksCompleted", { method: "POST" });
} catch (err) {
console.warn("Could not notify server about completion:", err);
}
}
}
// =======================================================================
// Shutdown handler (Exit button & window close)
// =======================================================================
function endSession() {
fetch("/quit", { method: "GET" }).finally(() => window.close());
}
// Sub-options auto-toggle, tagline rotation, and beforeunload hook
document.addEventListener("DOMContentLoaded", () => {
// Sub-option containers
const tasksWithSubOptions = document.querySelectorAll('[id$="OptionsContainer"]');
tasksWithSubOptions.forEach((container) => {
const taskId = container.id.replace("OptionsContainer", "");
const masterCheckbox = document.getElementById(taskId);
if (!masterCheckbox) return;
function updateVisibility() {
const checked = masterCheckbox.checked;
// Show/hide the sub-options panel
container.style.display = checked ? "block" : "none";
// Only sub-options checkboxes live here (text inputs wont be touched)
const subCbs = container.querySelectorAll('input[type="checkbox"]');
// ==========================================================
// Special-case: 1Password sub-options should be user-chosen
// ==========================================================
if (taskId === "install1Password") {
if (!checked) {
// turning OFF -> clear sub-options
subCbs.forEach((cb) => (cb.checked = false));
} else {
// turning ON -> if nothing selected yet, default to desktop
const anySelected = Array.from(subCbs).some((cb) => cb.checked);
if (!anySelected) {
const desktop = container.querySelector(
'input[type="checkbox"][value="desktop"]'
);
if (desktop) desktop.checked = true;
}
}
}
// ==========================================================
// Special-case: Disable Animations sub-options should be user-chosen
// ==========================================================
else if (taskId === "disableAnimations") {
if (!checked) {
// turning OFF -> clear sub-options
subCbs.forEach((cb) => (cb.checked = false));
} else {
// turning ON -> if nothing selected yet, default to ALL sub-options
const anySelected = Array.from(subCbs).some((cb) => cb.checked);
if (!anySelected) {
subCbs.forEach((cb) => (cb.checked = true));
}
}
}
// ==========================================================
// Default behavior: sub-options mirror the master checkbox
// (Datto uses this: check master -> check all sub-options)
// ==========================================================
else {
subCbs.forEach((cb) => (cb.checked = checked));
}
// ==========================================================
// Datto extra UI blocks (password + dropdown) tied to master checkbox
// ==========================================================
if (taskId === "installDattoRMM") {
const pwdBox = document.getElementById("PasswordContainer");
const rmmBox = document.getElementById("dattoRmmContainer");
if (pwdBox) pwdBox.style.display = checked ? "block" : "none";
if (rmmBox) rmmBox.style.display = checked ? "block" : "none";
}
}
// Keep your Datto UI show/hide behavior
if (taskId === "installDattoRMM") {
const pwdBox = document.getElementById("PasswordContainer");
const rmmBox = document.getElementById("dattoRmmContainer");
if (pwdBox) pwdBox.style.display = checked ? "block" : "none";
if (rmmBox) rmmBox.style.display = checked ? "block" : "none";
}
}
masterCheckbox.addEventListener("change", updateVisibility);
updateVisibility();
});
// Tagline rotation
const taglines = [
"Fast deployments, no monkey business.",
"Bananas for better builds.",
"Deploy without flinging code.",
"Tame your stack. Unleash the monkey.",
"Monkey see, monkey deploy.",
"Deploy smarter -- with a monkey on your team.",
"Don't pass the monkey -- let it deploy.",
"No more monkeying around. Stack handled.",
"Own your stack. But let the monkey do the work.",
"Why throw code when the monkey's got it?",
"Deployments so easy, a monkey could do it. Ours does.",
"Monkey in the stack, not on your back.",
];
const el = document.getElementById("tagline");
if (el) {
let idx = Math.floor(Math.random() * taglines.length);
el.textContent = taglines[idx];
setInterval(() => {
idx = (idx + 1) % taglines.length;
el.textContent = taglines[idx];
}, 10_000);
}
});
// printer dropdown
document.addEventListener("DOMContentLoaded", () => {
const clientDropdown = document.getElementById("printerClientDropdown");
if (clientDropdown) {
clientDropdown.addEventListener("change", (e) => {
const code = e.target.value;
if (code) renderPrintersForClient(code);
});
}
});
// notify server on window close
window.addEventListener("beforeunload", () => {
fetch("/quit", { method: "GET", keepalive: true });
});