Update samy.ps1

This commit is contained in:
2025-11-26 17:58:30 -05:00
parent efc99d1844
commit b087e0ae54

707
samy.ps1
View File

@@ -1034,632 +1034,33 @@ Write-LogHybrid "Tasks by page: onboard=$(
#region UIHtml #region UIHtml
function Get-UIHtml { function Get-UIHtml {
param([string]$Page = 'onboard') param([string]$Page = 'onboard')
if (-not $Page) { $Page = 'onboard' } if (-not $Page) { $Page = 'onboard' }
# no spaces before $style #
$style = @' # 1) Build checkbox HTML per page/column
#
$onboardLeft = Build-Checkboxes -Page 'onboard' -Column 'left'
$onboardRight = Build-Checkboxes -Page 'onboard' -Column 'right'
$offboard = Build-Checkboxes -Page 'offboard' -Column ''
$tweaks = Build-Checkboxes -Page 'tweaks' -Column ''
$apps = Build-Checkboxes -Page 'SVSApps' -Column ''
<style> #
:root { # 2) Build the JS tasks array once (this is the only dynamic JS piece)
/* Cool Palette */ #
--background-color: rgba(18, 18, 18, 1); $tasksJsAll = (
--border-color: rgba(255,127,0,0.25); $Global:SamyTasks | ForEach-Object {
/* Neutral Colors */ " { id: '$($_.Id)', handler: '/$($_.Name)', label: '$($_.Label)' }"
--white-color: rgba(255,255,255);
--gray-color: rgba(102,102,102);
--dark-gray-color: rgba(51,51,51);
--light-gray-color: rgba(187,187,187);
/* Sidebar Button Colors */
--btn-sidebar-light-gray: rgba(68,68,68);
--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);
--btn-success-disabled: rgba(108,117,125);
--btn-danger: rgba(220,53,69);
/* 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 */
} }
) -join ",`n"
body { #
font-family: Arial, sans-serif; # 3) HTML template that references external CSS/JS hosted on Gitea
margin: 0; padding: 0; # (adjust the URLs to match your repo + branch)
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; }
/* single, consolidated sidebar rule */
.sidebar{
width:200px;
background:var(--background-color);
padding:10px;
position:relative;
/* leave enough room for status box + monkey */
padding-bottom: calc(var(--monkey-bottom) + var(--monkey-size) + var(--status-gap) + 10px);
}
/* status panel sits ABOVE the monkey (make sure #status-box is inside .sidebar in HTML) */
#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; /* keep above pseudo-element just in case */
}
/* 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("https://git.svstools.com/syelle/Logo/raw/branch/main/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;
overflow-y:auto;
max-height:calc(100vh - 50px);
}
.fixed-buttons {
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
gap: 10px; /* space between Exit and Run */
z-index: 1000;
}
.exit-button,
.run-button {
border: none;
border-radius: 5px;
padding: 10px 20px;
cursor: pointer;
color: var(--white-color);
}
/* Specific overrides */
.exit-button { background-color: var(--btn-danger); }
.run-button { background-color: var(--btn-success); }
.tab-content { display:none; }
.tab-content.active { display:block; }
.columns-container { display:flex; gap:20px; flex-wrap:wrap; align-items:flex-start; }
/* column styling, same as old script */
.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; }
.button-group { text-align:right; margin-top:20px; }
.exit-button {
background:var(--btn-danger); color:var(--white-color);
padding:10px 20px; border:none; border-radius:5px; cursor:pointer;
}
#PasswordContainer, #dattoRmmContainer { margin-top: 1em; }
/* Common styles for inputs, buttons, and selects */
#PasswordContainer input,
#PasswordContainer button,
#dattoRmmContainer 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;
display: block;
width: 40%;
max-width: 200px;
}
#PasswordContainer button {
background-color: var(--btn-sidebar-blue);
cursor: pointer;
transition: background-color 0.3s ease;
}
#PasswordContainer button:hover { background-color: var(--btn-hover); }
/* Tag line */
#tagline {
font-size: 1.2rem;
color: var(--light-gray-color);
font-weight: bold;
justify-self: center;
}
@media (max-width:768px) {
.container { flex-direction:column; }
.sidebar { width:100%; }
}
</style>
'@
# no spaces before $script
$script = @'
<script>
let completedTasks = 0;
let totalTasks = 0;
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 complete (${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 (DOM-safe)
// =======================================================================
document.addEventListener('DOMContentLoaded', () => {
const tabButtons = document.querySelectorAll(".tab-button");
const tabContents = document.querySelectorAll(".tab-content");
if (!tabButtons || !tabButtons.length || !tabContents || !tabContents.length) {
console.error("ScriptMonkey: no tab buttons or tab contents found.");
return;
}
tabButtons.forEach(btn => {
btn.addEventListener("click", () => {
// clear active state
tabButtons.forEach(b => b.classList.remove("active"));
tabContents.forEach(c => c.classList.remove("active"));
// set new active
btn.classList.add("active");
const targetId = btn.dataset.tab;
const target = document.getElementById(targetId);
if (target) target.classList.add("active");
});
});
// initialize default tab on load
const defaultBtn = document.querySelector(".tab-button[data-tab='{{defaultPage}}Tab']");
const defaultTab = document.getElementById("{{defaultPage}}Tab");
if (defaultBtn) defaultBtn.classList.add("active");
if (defaultTab) defaultTab.classList.add("active");
});
// =======================================================================
// Task Trigger
// =======================================================================
const tasks = [
{{tasksJsAll}}
];
// =======================================================================
// Column “Select All” toggling for On-Boarding
// =======================================================================
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;
});
// Now simulate change events after setting all checkboxes
setTimeout(() => {
children.forEach(cb => {
cb.dispatchEvent(new Event('change'));
});
}, 0);
}
// =======================================================================
// Un-check “Select All” if any child is unchecked (& re-check if all are checked)
// =======================================================================
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);
}
// Attach listeners on load
['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 === 0) {
master.checked = false;
return;
}
master.checked = Array.from(children).every(cb => cb.checked);
}
// Attach off-board checkbox change handlers (DOM-safe)
document.addEventListener('DOMContentLoaded', () => {
const offChildren = document.querySelectorAll(
'#offboardTab input[type=checkbox]:not(#offboardSelectAll)'
);
if (!offChildren || !offChildren.length) {
// This just means no off-boarding items were rendered; safe to ignore.
return;
}
offChildren.forEach(cb =>
cb.addEventListener('change', updateOffboardSelectAll)
);
// Initialize master checkbox state on load
updateOffboardSelectAll();
});
// =======================================================================
// DattoRMM Options
// =======================================================================
function toggleDattoRMMOptions() {
const master = document.getElementById('installDattoRMM');
const container = document.getElementById('installDattoRMMOptionsContainer');
if (!container) return;
container.style.display = master.checked ? 'block' : 'none';
container.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = master.checked);
}
document.addEventListener('DOMContentLoaded', () => {
// Hook up the main DattoRMM checkbox → show/hide its sub-options/password/dropdown
const master = document.getElementById('installDattoRMM');
if (master) {
master.addEventListener('change', toggleDattoRMMOptions);
}
// Fetch sites when the "Enter" key is pressed in the password input field
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(); // Trigger the 'Go' button click
}
});
}
// Trigger 'Run Selected' button click when 'Enter' is pressed after selecting a site
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(); // Trigger the 'Run Selected' button click
}
});
}
});
// =======================================================================
// Fetch Sites Handler
// =======================================================================
async function fetchSites() {
const pwd = document.getElementById("Password").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 = ''; // clear the loading message
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.");
}
}
async function triggerInstall() {
const runBtn = document.querySelector('.run-button');
runBtn.disabled = true;
const statusBox = document.getElementById('status-box');
if (statusBox) statusBox.innerHTML = '';
try {
// Figure out how many total tasks are selected
const checkedTasks = tasks.filter(t => {
const cb = document.getElementById(t.id);
return cb && cb.checked;
});
setTotalTaskCount(checkedTasks.length);
// 1. Run DattoRMM first
const dattoCB = document.getElementById('installDattoRMM');
if (dattoCB && dattoCB.checked) {
try {
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';
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. Run SVSMSP 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);
}
}
} catch (e) {
console.error('triggerInstall fatal error:', e);
} finally {
runBtn.disabled = false;
}
}
// =======================================================================
// Shutdown Handler
// =======================================================================
function endSession() {
fetch("/quit", { method: "GET" })
.finally(() => window.close());
}
// =======================================================================
// Sub-Options Auto-Toggle for Tasks
// =======================================================================
document.addEventListener('DOMContentLoaded', function () {
// Auto-handle visibility and checking for tasks with sub-options
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);
// Show/hide Password and RMM only if it's installDattoRMM
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(); // call once on load
});
});
// ===========================================
// ─ rotating tagline ───────────────────────────────
// ===========================================
document.addEventListener('DOMContentLoaded', () => {
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");
let idx = Math.floor(Math.random() * taglines.length);
el.textContent = taglines[idx];
setInterval(() => {
idx = (idx + 1) % taglines.length;
el.textContent = taglines[idx];
}, 10_000);
});
// when the browser window is closed (X), notify the server to quit
window.addEventListener('beforeunload', () => {
// keepalive: true ensures the request is sent even as the page unloads
fetch('/quit', { method: 'GET', keepalive: true });
});
</script>
'@
# no spaces before $htmlTemplate
$htmlTemplate = @" $htmlTemplate = @"
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@@ -1668,7 +1069,9 @@ $htmlTemplate = @"
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Script Monkey</title> <title>Script Monkey</title>
<link rel="icon" href="https://git.svstools.com/syelle/Logo/raw/branch/main/SVS_Favicon.ico"> <link rel="icon" href="https://git.svstools.com/syelle/Logo/raw/branch/main/SVS_Favicon.ico">
$style
<!-- External CSS from Gitea -->
<link rel="stylesheet" href="https://git.svstools.com/syelle/ScriptMonkey/raw/branch/main/samy.css">
</head> </head>
<body> <body>
<div class="logo-container"> <div class="logo-container">
@@ -1679,12 +1082,11 @@ $style
</div> </div>
<!-- Centered rotating tagline --> <!-- Centered rotating tagline -->
<div id="tagline" style="font-size:1.1rem; color:var(--light-gray-color); font-weight:bold; text-align:center;"> <div id="tagline" class="tagline">
Script Automation Monkey (Yeah!) Script Automation Monkey (Yeah!)
</div> </div>
</div> </div>
<div class="container"> <div class="container">
<div class="sidebar"> <div class="sidebar">
<button class="tab-button" data-tab="onboardTab">On-Boarding</button> <button class="tab-button" data-tab="onboardTab">On-Boarding</button>
@@ -1699,7 +1101,6 @@ $style
<h2>On-Boarding</h2> <h2>On-Boarding</h2>
<h3 class="subtitle">This new deployment method ensures everything is successfully deployed with greater ease!</h3> <h3 class="subtitle">This new deployment method ensures everything is successfully deployed with greater ease!</h3>
<!-- 1) Dynamic task checkboxes -->
<div class="columns-container"> <div class="columns-container">
<div class="checkbox-group column"> <div class="checkbox-group column">
<h3>SVSMSP Stack</h3> <h3>SVSMSP Stack</h3>
@@ -1713,16 +1114,11 @@ $style
</div> </div>
</div> </div>
<!-- 2) Password and Datto Site dropdown shown conditionally -->
<div id="PasswordContainer" style="display:none; margin-bottom:1em;"> <div id="PasswordContainer" style="display:none; margin-bottom:1em;">
<label for="Password">Enter Password:</label> <label for="Password">Enter Password:</label>
<div style="display:flex; gap:5px;"> <div style="display:flex; gap:5px;">
<input type="password" id="Password" placeholder="Enter Password" style="flex:1;" /> <input type="password" id="Password" placeholder="Enter Password" style="flex:1;" />
<button onclick="fetchSites()" class="go-button">GO!</button>
<button onclick="fetchSites()" style="padding:4px 10px; background-color: var(--btn-sidebar-blue); color: var(--white-color); border: none; border-radius: 4px;">GO!</button>
</div> </div>
</div> </div>
@@ -1732,10 +1128,8 @@ $style
<option disabled selected>Fetching sites...</option> <option disabled selected>Fetching sites...</option>
</select> </select>
</div> </div>
</div> <!-- end onboardTab --> </div> <!-- end onboardTab -->
<div id="offboardTab" class="tab-content"> <div id="offboardTab" class="tab-content">
<h2>Off-Boarding</h2> <h2>Off-Boarding</h2>
<div class="columns-container"> <div class="columns-container">
@@ -1750,8 +1144,6 @@ $style
</div> </div>
</div> </div>
<div id="tweaksTab" class="tab-content"> <div id="tweaksTab" class="tab-content">
<h2>Tweaks</h2> <h2>Tweaks</h2>
<div class="columns-container"> <div class="columns-container">
@@ -1762,7 +1154,6 @@ $style
</div> </div>
</div> </div>
<div id="SVSAppsTab" class="tab-content"> <div id="SVSAppsTab" class="tab-content">
<h2>SVS APPs</h2> <h2>SVS APPs</h2>
<div class="columns-container"> <div class="columns-container">
@@ -1772,47 +1163,33 @@ $style
</div> </div>
</div> </div>
</div> </div>
</div> </div>
$script </div>
<!-- floating button group -->
<!-- Tiny inline bridge: pass dynamic data to external JS -->
<script>
window.SAMY_TASKS = [
{{tasksJsAll}}
];
window.SAMY_DEFAULT_PAGE = "{{defaultPage}}";
</script>
<!-- External JS from Gitea -->
<script src="https://git.svstools.com/syelle/ScriptMonkey/raw/branch/main/samy.js"></script>
<!-- Floating button group -->
<div class="fixed-buttons"> <div class="fixed-buttons">
<button class="exit-button" onclick="endSession()">Exit</button> <button class="exit-button" onclick="endSession()">Exit</button>
<button class="run-button" onclick="triggerInstall()">Run Selected</button> <button class="run-button" onclick="triggerInstall()">Run Selected</button>
</div> </div>
</body> </body>
</html> </html>
"@ "@
# #
# 4) Build the checkbox HTML and tasks JS from $Global:SamyTasks # 4) Replace placeholders
#
# On-boarding now has two columns:
$onboardLeft = Build-Checkboxes -Page 'onboard' -Column 'left'
$onboardRight = Build-Checkboxes -Page 'onboard' -Column 'right'
# Off-boarding, Tweaks, SVSApps stay one-column:
$offboard = Build-Checkboxes -Page 'offboard' -Column ''
$tweaks = Build-Checkboxes -Page 'tweaks' -Column ''
$apps = Build-Checkboxes -Page 'SVSApps' -Column ''
# Tasks JS array (fixed)
$tasksJsAll = (
$Global:SamyTasks | ForEach-Object {
" { id: '$($_.Id)', handler: '/$($_.Name)', label: '$($_.Label)' }"
}
) -join ",`n"
#
# 5) Inject into template
# #
$html = $htmlTemplate $html = $htmlTemplate
$html = $html.Replace('{{moduleVersion}}', (Get-ModuleVersionHtml)) $html = $html.Replace('{{moduleVersion}}', (Get-ModuleVersionHtml))
@@ -1824,10 +1201,10 @@ $script
$html = $html.Replace('{{tasksJsAll}}', $tasksJsAll) $html = $html.Replace('{{tasksJsAll}}', $tasksJsAll)
$html = $html.Replace('{{defaultPage}}', $Page) $html = $html.Replace('{{defaultPage}}', $Page)
return $html return $html
} }
#endregion UIHtml #endregion UIHtml
#region Handler Stubs #region Handler Stubs