Truncated

This commit is contained in:
2025-12-09 19:20:31 -05:00
commit f34a95d290
7 changed files with 3970 additions and 0 deletions

Binary file not shown.

BIN
SAMY.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
SVS_Favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

23
SVS_logo.svg Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 480 140">
<!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 91) -->
<defs>
<style>
.st0 {
fill: #750000;
}
.st1 {
fill: #267eb2;
}
</style>
</defs>
<path class="st1" d="M155.08,115.14c-5.99,0-11.27-.66-15.86-1.99s-8.6-3.2-12.05-5.63c-3.45-2.42-6.54-5.27-9.27-8.53l14.21-15.92c3.79,4.85,7.75,8.05,11.88,9.61,4.13,1.55,8.01,2.33,11.65,2.33,1.44,0,2.73-.13,3.87-.4,1.14-.26,2.01-.7,2.61-1.31.61-.61.91-1.44.91-2.5,0-.98-.32-1.82-.97-2.5s-1.5-1.27-2.56-1.76-2.22-.91-3.47-1.25-2.46-.62-3.64-.85c-1.18-.23-2.22-.46-3.13-.68-4.55-1.06-8.53-2.35-11.94-3.87-3.41-1.51-6.25-3.33-8.53-5.46-2.27-2.12-3.96-4.55-5.06-7.28s-1.65-5.8-1.65-9.21c0-3.87.89-7.39,2.67-10.57s4.17-5.91,7.16-8.19c2.99-2.27,6.4-4.02,10.23-5.23,3.83-1.21,7.79-1.82,11.88-1.82,5.99,0,10.99.55,15.01,1.65,4.02,1.1,7.39,2.67,10.12,4.72,2.73,2.05,5.08,4.43,7.05,7.16l-14.32,13.76c-1.67-1.59-3.41-2.9-5.23-3.92-1.82-1.02-3.69-1.78-5.63-2.27-1.93-.49-3.85-.74-5.74-.74-1.74,0-3.22.13-4.43.4s-2.16.68-2.84,1.25-1.02,1.35-1.02,2.33.44,1.8,1.31,2.44,1.97,1.19,3.3,1.65c1.32.46,2.65.82,3.98,1.08,1.33.27,2.44.47,3.35.62,4.17.76,8,1.8,11.48,3.13,3.49,1.33,6.54,3,9.15,5,2.61,2.01,4.62,4.51,6.03,7.5,1.4,3,2.1,6.54,2.1,10.63,0,5.84-1.46,10.73-4.38,14.67s-6.84,6.92-11.77,8.92c-4.93,2.01-10.42,3.01-16.48,3.01l.02.02Z"/>
<path class="st1" d="M219.31,114.01l-31.72-79.58h24.56l12.73,34.79c.91,2.35,1.65,4.36,2.22,6.03s1.08,3.24,1.53,4.72c.46,1.48.89,3.05,1.31,4.72.42,1.67.89,3.71,1.42,6.14h-3.98c.76-3.18,1.42-5.8,1.99-7.84.57-2.05,1.21-4.07,1.93-6.08.72-2.01,1.65-4.56,2.79-7.67l12.73-34.79h23.76l-31.95,79.58h-19.33v-.02Z"/>
<path class="st1" d="M302.98,115.14c-5.99,0-11.27-.66-15.86-1.99-4.59-1.33-8.6-3.2-12.05-5.63-3.45-2.42-6.54-5.27-9.27-8.53l14.21-15.92c3.79,4.85,7.75,8.05,11.88,9.61,4.13,1.55,8.02,2.33,11.65,2.33,1.44,0,2.73-.13,3.87-.4,1.14-.26,2.01-.7,2.62-1.31s.91-1.44.91-2.5c0-.98-.32-1.82-.97-2.5-.64-.68-1.5-1.27-2.56-1.76s-2.22-.91-3.47-1.25-2.46-.62-3.64-.85-2.22-.46-3.13-.68c-4.55-1.06-8.53-2.35-11.94-3.87-3.41-1.51-6.25-3.33-8.53-5.46-2.27-2.12-3.96-4.55-5.06-7.28s-1.65-5.8-1.65-9.21c0-3.87.89-7.39,2.67-10.57,1.78-3.18,4.17-5.91,7.16-8.19,2.99-2.27,6.4-4.02,10.23-5.23,3.83-1.21,7.79-1.82,11.88-1.82,5.99,0,10.99.55,15.01,1.65s7.39,2.67,10.12,4.72,5.08,4.43,7.05,7.16l-14.33,13.76c-1.67-1.59-3.41-2.9-5.23-3.92-1.82-1.02-3.69-1.78-5.63-2.27-1.93-.49-3.85-.74-5.74-.74-1.74,0-3.22.13-4.43.4s-2.16.68-2.84,1.25c-.68.57-1.02,1.35-1.02,2.33s.44,1.8,1.31,2.44,1.97,1.19,3.3,1.65,2.65.82,3.98,1.08c1.33.27,2.44.47,3.35.62,4.17.76,8,1.8,11.48,3.13,3.49,1.33,6.54,3,9.15,5,2.62,2.01,4.62,4.51,6.03,7.5,1.4,3,2.1,6.54,2.1,10.63,0,5.84-1.46,10.73-4.38,14.67s-6.84,6.92-11.77,8.92c-4.93,2.01-10.42,3.01-16.48,3.01l.02.02Z"/>
<path class="st1" d="M345.72,72.6v-39.35h10.45l14.33,23.33-8.49-.06,14.5-23.27h10.12v39.35h-11.69v-9.39c0-3.37.08-6.41.25-9.11.17-2.7.46-5.38.87-8.04l1.35,3.54-9.5,14.73h-3.71l-9.33-14.73,1.41-3.54c.41,2.51.7,5.09.87,7.73s.25,5.78.25,9.42v9.39h-11.69.01Z"/>
<path class="st1" d="M409.4,73.16c-2.96,0-5.57-.33-7.84-.98-2.27-.66-4.25-1.58-5.96-2.78s-3.23-2.6-4.58-4.22l7.03-7.87c1.87,2.4,3.83,3.98,5.87,4.75s3.96,1.15,5.76,1.15c.71,0,1.35-.07,1.91-.2s.99-.35,1.29-.65.45-.71.45-1.24c0-.49-.16-.9-.48-1.24s-.74-.63-1.26-.87c-.53-.24-1.1-.45-1.71-.62-.62-.17-1.22-.31-1.8-.42-.58-.11-1.1-.22-1.55-.34-2.25-.52-4.22-1.16-5.9-1.91-1.69-.75-3.09-1.65-4.22-2.7-1.12-1.05-1.96-2.25-2.5-3.6s-.81-2.87-.81-4.55c0-1.91.44-3.65,1.32-5.23.88-1.57,2.06-2.92,3.54-4.05s3.17-1.99,5.06-2.58,3.85-.9,5.87-.9c2.96,0,5.43.27,7.42.81s3.65,1.32,5,2.33c1.35,1.01,2.51,2.19,3.49,3.54l-7.08,6.8c-.83-.79-1.69-1.43-2.59-1.94s-1.83-.88-2.78-1.12c-.96-.24-1.9-.37-2.84-.37-.86,0-1.59.07-2.19.2s-1.07.34-1.41.62c-.34.28-.51.67-.51,1.15s.21.89.65,1.21c.43.32.97.59,1.63.82.66.22,1.31.4,1.97.53s1.21.23,1.66.31c2.06.38,3.95.89,5.68,1.55,1.72.66,3.23,1.48,4.53,2.47s2.29,2.23,2.98,3.71c.69,1.48,1.04,3.23,1.04,5.26,0,2.89-.72,5.3-2.16,7.25s-3.38,3.42-5.82,4.41-5.15,1.49-8.15,1.49v.02Z"/>
<path class="st1" d="M431.66,72.6v-39.35h17.71c2.7,0,5.1.58,7.2,1.74,2.1,1.16,3.75,2.75,4.95,4.78,1.2,2.02,1.8,4.35,1.8,6.97s-.6,5.17-1.8,7.31c-1.2,2.14-2.85,3.81-4.95,5.03-2.1,1.22-4.5,1.83-7.2,1.83h-5.56v11.69h-12.15ZM443.58,50.57h3.54c.71,0,1.35-.14,1.91-.42s1-.68,1.32-1.21c.32-.52.48-1.18.48-1.97s-.16-1.42-.48-1.91-.76-.85-1.32-1.1c-.56-.24-1.2-.37-1.91-.37h-3.54v6.97h0Z"/>
<polyline class="st1" points="17.09 25.67 109.17 25.67 63.13 108.6"/>
<polyline class="st0" points="73.19 103.73 113.01 32 125.59 32 79.54 114.93 73.19 103.73"/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

320
samy.css Normal file
View File

@@ -0,0 +1,320 @@
:root {
/* Cool Palette */
--background-color: rgba(18, 18, 18, 1);
--border-color: rgba(255, 127, 0, 0.25);
/* Neutral Colors */
--white-color: rgba(255, 255, 255, 1);
--gray-color: rgba(102, 102, 102, 1);
--dark-gray-color: rgba(51, 51, 51, 1);
--light-gray-color: rgba(187, 187, 187, 1);
/* Sidebar Button Colors */
--btn-sidebar-light-gray: rgba(68, 68, 68, 1);
--btn-sidebar-blue: rgba(30, 144, 255, 1);
--btn-hover: rgba(0, 86, 179, 1);
--btn-hover-scale: 1.05;
/* Button Colors */
--btn-success: rgba(40, 167, 69, 1);
--btn-success-disabled: rgba(108, 117, 125, 1);
--btn-danger: rgba(220, 53, 69, 1);
/* Monkey + status panel settings */
--monkey-size: 160px; /* size of SAMY */
--monkey-bottom: 135px; /* how high from bottom of sidebar */
--status-gap: 20px; /* space between status box and monkey */
--status-height: 140px; /* max height of status box */
}
/* Make sizing easier to reason about */
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: var(--background-color);
color: var(--white-color);
height: 100%;
overflow: hidden;
}
.logo-container {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
padding: 20px;
}
.logo-container img {
max-width: 300px;
height: auto;
}
.subtitle {
font-size: 1.2rem;
color: var(--gray-color);
margin-top: 0.5em;
}
.container {
display: flex;
height: 100vh;
overflow: hidden;
}
/* Sidebar */
.sidebar {
width: 200px;
background: var(--background-color);
padding: 10px;
position: relative;
padding-bottom: calc(var(--monkey-bottom) + var(--monkey-size) + var(--status-gap) + 10px);
}
/* Status panel above monkey */
#status-box {
position: absolute;
left: 10px;
bottom: calc(var(--monkey-bottom) + var(--monkey-size) + var(--status-gap));
width: calc(100% - 20px);
max-height: var(--status-height);
overflow-y: auto;
padding: 8px 10px;
border: 1px solid var(--border-color);
border-radius: 8px;
background: rgba(255, 255, 255, 0.06);
font-family: "Segoe UI", "Segoe UI Emoji", "Segoe UI Symbol", system-ui, sans-serif;
font-size: 12px;
line-height: 1.35;
z-index: 1;
}
/* SAMY bottom-left */
.sidebar::after {
content: "";
position: absolute;
left: 10px;
bottom: var(--monkey-bottom);
width: var(--monkey-size);
height: var(--monkey-size);
background-image: url("SAMY.png");
background-repeat: no-repeat;
background-size: contain;
opacity: 0.95;
pointer-events: none;
}
.sidebar button {
display: block;
width: 100%;
margin-bottom: 10px;
padding: 10px;
color: var(--white-color);
background: var(--btn-sidebar-light-gray);
border: none;
border-radius: 5px;
cursor: pointer;
text-align: left;
transition: background-color 0.3s, transform 0.2s;
}
.sidebar button.active {
background: var(--btn-sidebar-blue);
}
.sidebar button:hover {
background: var(--btn-hover);
transform: scale(var(--btn-hover-scale));
}
.content {
position: relative;
flex: 1;
padding: 20px;
padding-bottom: 200px;
overflow-y: auto;
max-height: calc(100vh - 50px);
}
/* Floating buttons (Exit / Run) */
.fixed-buttons {
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
gap: 10px;
z-index: 1000;
}
.exit-button,
.run-button {
border: none;
border-radius: 5px;
padding: 10px 20px;
cursor: pointer;
color: var(--white-color);
}
.exit-button {
background-color: var(--btn-danger);
}
.run-button {
background-color: var(--btn-success);
}
/* Tabs */
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* Columns & checkboxes */
.columns-container {
display: flex;
gap: 20px;
flex-wrap: wrap;
align-items: flex-start;
}
.column {
flex: 1;
max-width: 45%;
border: 2px solid var(--border-color);
border-radius: 8px;
padding: 10px;
background-color: var(--dark-gray-color);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
}
.checkbox-group label {
display: flex;
align-items: center;
margin-bottom: 8px;
}
/* Datto password + site dropdown (On-Boarding) */
#PasswordContainer,
#dattoRmmContainer {
margin: 16px 0;
padding: 10px 12px;
border-radius: 8px;
background-color: var(--dark-gray-color);
border: 1px solid var(--border-color);
/* Narrower so it fits under the stack column */
width: calc(25% - 10px);
max-width: 480px;
}
/* Printer panels on Devices tab */
#printerPasswordContainer,
#printerClientContainer,
#printerListContainer {
margin: 16px 0;
padding: 10px 12px;
border-radius: 8px;
background-color: var(--dark-gray-color);
border: 1px solid var(--border-color);
/* Full width in the Devices tab */
width: 100%;
max-width: 600px;
}
#PasswordContainer input,
#PasswordContainer button,
#dattoRmmContainer select,
#printerPasswordContainer input,
#printerPasswordContainer button,
#printerClientContainer select {
background-color: var(--dark-gray-color);
color: var(--white-color);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 8px;
font-size: 14px;
}
/* Input + GO button inline rows */
#PasswordContainer > div,
#printerPasswordContainer > div {
display: flex;
gap: 6px;
}
#PasswordContainer input,
#printerPasswordContainer input {
flex: 1;
min-width: 0;
}
/* Dropdown fills panel width */
#dattoRmmContainer select,
#printerClientContainer select {
width: 100%;
}
/* GO button styling */
#PasswordContainer button,
.go-button {
background-color: var(--btn-sidebar-blue);
cursor: pointer;
transition: background-color 0.3s ease, transform 0.1s ease;
}
#PasswordContainer button:hover,
.go-button:hover {
background-color: var(--btn-hover);
transform: scale(1.03);
}
/* Tag line */
.tagline {
font-size: 1.2rem;
color: var(--light-gray-color);
font-weight: bold;
text-align: center;
}
/* Big red notice under tagline */
.samy-hint {
margin-top: 0.25rem;
font-size: 3rem;
color: #ff4d4d;
font-weight: 900;
text-transform: uppercase;
text-align: center;
grid-column: 1 / -1; /* span both grid columns */
}
/* Responsive */
@media (max-width: 768px) {
.container {
flex-direction: column;
}
.sidebar {
width: 100%;
}
.column {
max-width: 100%;
}
#PasswordContainer,
#dattoRmmContainer {
width: 100%;
max-width: 100%;
}
}

685
samy.js Normal file
View File

@@ -0,0 +1,685 @@
// 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/tweaks/SVSApps)
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/right 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;
});
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}]`
);
master.checked = Array.from(children).every((cb) => cb.checked);
}
document.addEventListener("DOMContentLoaded", () => {
["left", "right"].forEach((col) => {
document
.querySelectorAll(`#onboardTab input[type=checkbox][data-column=${col}]`)
.forEach((cb) =>
cb.addEventListener("change", () => updateSelectAll(col))
);
});
});
// =======================================================================
// 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;
if (!pwd) {
alert("Please enter the password.");
return;
}
const dropdown = document.getElementById("dattoDropdown");
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 "HTTP " + resp.status;
const sites = await resp.json();
dropdown.innerHTML = "";
sites.forEach((site) => {
const option = document.createElement("option");
option.value = site.UID;
option.textContent = site.Name;
dropdown.appendChild(option);
});
document.getElementById("dattoRmmContainer").style.display = "block";
} catch (e) {
console.error(e);
dropdown.innerHTML =
'<option disabled selected>Error loading sites</option>';
alert("Failed to fetch sites. Check password and try again.");
}
}
// =======================================================================
// 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;
if (!pwd) {
alert("Please enter the printer password.");
return;
}
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 for this password.");
return;
}
// Build unique sorted ClientCode list
const codes = [...new Set(allPrinters.map((p) => p.ClientCode))].sort();
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 and try again.");
}
}
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";
//Install-Checkbox
const cb = document.createElement("input");
cb.type = "checkbox";
cb.id = id;
// stash all fields we might need later
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";
// Line 1: install checkbox + printer label
label.appendChild(cb);
label.appendChild(document.createTextNode(" "));
label.appendChild(
document.createTextNode(`${nameText} (${locText})`)
);
// Line 2: radio for "Make default"
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; // associate default choice with this checkbox/printer
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;
}
// See which radio is checked for "Make default"
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,
// Only the printer whose checkbox id matches the selected radio gets SetAsDefault=true
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 {
// Figure out which standard tasks are checked
const checkedTasks = tasks.filter((t) => {
const cb = document.getElementById(t.id);
return cb && cb.checked;
});
// Rename checkbox / textbox
const renameCB = document.getElementById("chkRenameComputer");
const newNameInput = document.getElementById("txtNewComputerName");
// Count how many "extra" tasks (rename) we're doing
let extraTasks = 0;
if (renameCB && renameCB.checked) {
extraTasks = 1; // treat rename as one task in the progress counter
}
setTotalTaskCount(checkedTasks.length + extraTasks);
// 1. DattoRMM first
const dattoCB = document.getElementById("installDattoRMM");
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
const svsCB = document.getElementById("installSVSMSPModule");
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"].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();
// Same basic rules you'll enforce server-side
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."
);
// still mark it as a failed task so progress reaches 100%
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;
if (totalTasks > 0) {
console.info(
`[Info] All tasks completed (${completedTasks}/${totalTasks})`
);
}
// 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;
container.style.display = checked ? "block" : "none";
container
.querySelectorAll('input[type="checkbox"]')
.forEach((cb) => (cb.checked = checked));
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();
});
// NEW: Rename computer checkbox -> show/hide text box
const renameCheckbox = document.getElementById("chkRenameComputer");
const renameBlock = document.getElementById("renameComputerBlock");
if (renameCheckbox && renameBlock) {
function updateRenameVisibility() {
renameBlock.style.display = renameCheckbox.checked ? "block" : "none";
}
renameCheckbox.addEventListener("change", updateRenameVisibility);
updateRenameVisibility();
}
// 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 });
});

2942
samy.ps1 Normal file

File diff suppressed because it is too large Load Diff