visual and effects are working
attempting to recreate a cleaner version of the script
This commit is contained in:
576
testTaskGate.ps1
576
testTaskGate.ps1
@@ -1,147 +1,497 @@
|
|||||||
# Sample functional PowerShell script using data-driven checkbox definitions
|
#region Config & Task Definitions
|
||||||
# Single-file, dynamic UI & routing based on a single $Global:Tasks table
|
|
||||||
|
|
||||||
#region Config
|
# Listening port for HTTP UI
|
||||||
$Port = 8081
|
$Port = 8081
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Task Definitions
|
|
||||||
# Each task: Id → checkbox HTML id
|
|
||||||
# Name → route name & JS handler path
|
|
||||||
# Label → displayed text
|
|
||||||
# HandlerFn → PowerShell function to invoke
|
|
||||||
|
|
||||||
|
# Define every task once here:
|
||||||
|
# Id → checkbox HTML `id`
|
||||||
|
# Name → URL path (`/Name`)
|
||||||
|
# Label → user-visible text
|
||||||
|
# HandlerFn → the PowerShell function to invoke
|
||||||
|
# Page → which tab/page it appears on
|
||||||
$Global:Tasks = @(
|
$Global:Tasks = @(
|
||||||
@{ Id = 'setSVSPowerplan'; Name = 'setSVSPowerplan'; Label = 'Set SVS Powerplan'; HandlerFn = 'Set-SVSPowerPlan' },
|
# On-Boarding
|
||||||
@{ Id = 'installSVSMSPModule'; Name = 'installSVSMSPModule'; Label = 'Install SVSMSP Module'; HandlerFn = 'Install-SVSMSPModule' },
|
@{ Id='setSVSPowerplan'; Name='setSVSPowerplan'; Label='Set SVS Powerplan'; HandlerFn='Set-SVSPowerPlan'; Page='onboard' },
|
||||||
@{ Id = 'installCyberQP'; Name = 'installCyberQP'; Label = 'Install CyberQP'; HandlerFn = 'Install-CyberQP' }
|
@{ Id='installSVSMSPModule'; Name='installSVSMSPModule'; Label='Install SVSMSP Module'; HandlerFn='Install-SVSMSPModule'; Page='onboard' },
|
||||||
# ...add more tasks here
|
@{ Id='installCyberQP'; Name='installCyberQP'; Label='Install CyberQP'; HandlerFn='Install-CyberQP'; Page='onboard' },
|
||||||
|
|
||||||
|
# Off-Boarding
|
||||||
|
@{ Id='uninstallCyberQP'; Name='uninstallCyberQP'; Label='Uninstall CyberQP'; HandlerFn='Uninstall-CyberQP'; Page='offboard' },
|
||||||
|
@{ Id='uninstallSVSMSPModule';Name='uninstallSVSMSPModule';Label='Uninstall SVSMSP Module'; HandlerFn='Cleanup-SVSMSP'; Page='offboard' },
|
||||||
|
|
||||||
|
# Tweaks
|
||||||
|
@{ Id='disableAnimations'; Name='disableAnimations'; Label='Disable Animations'; HandlerFn='Disable-Animations'; Page='tweaks' },
|
||||||
|
|
||||||
|
# SVS Apps
|
||||||
|
@{ Id='wingetLastpass'; Name='wingetLastpass'; Label='LastPass Desktop App'; HandlerFn='Install-WingetLastPass'; Page='SVSApps' }
|
||||||
)
|
)
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Handler Function Stubs
|
#region Logging Helpers
|
||||||
function Set-SVSPowerPlan {
|
|
||||||
param($Context)
|
# Initialize a global in-memory log cache
|
||||||
Write-LogHybrid -Message "[Handler] PowerPlan set" -Level Success
|
if (-not $Global:LogCache -or -not ($Global:LogCache -is [System.Collections.ArrayList])) {
|
||||||
if ($Context) { Respond-Text $Context "Powerplan applied" }
|
$Global:LogCache = [System.Collections.ArrayList]::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
function Install-SVSMSPModule {
|
# Core Write-Log function (advanced with event-log support)
|
||||||
param($Context)
|
function Write-LogHelper {
|
||||||
Write-LogHybrid -Message "[Handler] SVSMSP Module installed" -Level Success
|
[CmdletBinding()]
|
||||||
if ($Context) { Respond-Text $Context "SVSMSP Module installed" }
|
param(
|
||||||
}
|
[Parameter(Mandatory)][string]$Message,
|
||||||
|
[ValidateSet("Info","Warning","Error","Success","General")]
|
||||||
|
[string]$Level = "Info",
|
||||||
|
[string]$TaskCategory = "GeneralTask",
|
||||||
|
[switch]$LogToEvent,
|
||||||
|
[string]$EventSource = "SVSMSP_Module",
|
||||||
|
[string]$EventLog = "Application",
|
||||||
|
[int]$CustomEventID
|
||||||
|
)
|
||||||
|
$EventID = @{ Info=1000; Warning=2000; Error=3000; Success=4000; General=1000 }[$Level]
|
||||||
|
$Icon = @{ Info="📝"; Warning="⚠️"; Error="❌"; Success="✅"; General="📦" }[$Level]
|
||||||
|
$logEntry = [PSCustomObject]@{
|
||||||
|
Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
|
||||||
|
Level = $Level
|
||||||
|
Message = "$Icon [$Level] [$TaskCategory] $Message (EventID:$EventID)"
|
||||||
|
}
|
||||||
|
[void]$Global:LogCache.Add($logEntry)
|
||||||
|
|
||||||
function Install-CyberQP {
|
if ($LogToEvent) {
|
||||||
param($Context)
|
try {
|
||||||
Write-LogHybrid -Message "[Handler] CyberQP installed" -Level Success
|
if (-not (Get-EventLog -LogName $EventLog -Source $EventSource -ErrorAction SilentlyContinue)) {
|
||||||
if ($Context) { Respond-Text $Context "CyberQP installed" }
|
New-EventLog -LogName $EventLog -Source $EventSource -ErrorAction SilentlyContinue
|
||||||
}
|
|
||||||
|
|
||||||
# Utility for sending plain-text HTTP responses
|
|
||||||
function Respond-Text {
|
|
||||||
param($Context, $Text)
|
|
||||||
$buffer = [Text.Encoding]::UTF8.GetBytes($Text)
|
|
||||||
$Context.Response.ContentType = 'text/plain'
|
|
||||||
$Context.Response.ContentLength64 = $buffer.Length
|
|
||||||
$Context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
|
|
||||||
$Context.Response.OutputStream.Close()
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region UI Generation
|
|
||||||
function Get-UIHtml {
|
|
||||||
# Build checkbox HTML (wrap pipeline in parentheses so -join applies to result array)
|
|
||||||
$checkboxes = (
|
|
||||||
$Global:Tasks | ForEach-Object {
|
|
||||||
"<label><input type=`"checkbox`" id=`"$($_.Id)`" name=`"$($_.Name)`"> $($_.Label)</label>"
|
|
||||||
}
|
|
||||||
) -join "`n"
|
|
||||||
|
|
||||||
# Build JS tasks array
|
|
||||||
$tasksJs = (
|
|
||||||
$Global:Tasks | ForEach-Object {
|
|
||||||
"{ id: '$($_.Id)', handler: '/$($_.Name)' }"
|
|
||||||
}
|
|
||||||
) -join ",`n "
|
|
||||||
|
|
||||||
$html = @"
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head><meta charset="UTF-8"><title>Dynamic UI</title></head>
|
|
||||||
<body>
|
|
||||||
<h1>Task Launcher</h1>
|
|
||||||
<div class="checkbox-group">
|
|
||||||
$checkboxes
|
|
||||||
</div>
|
|
||||||
<button onclick="triggerInstall()">Run Selected</button>
|
|
||||||
<pre id="logArea"></pre>
|
|
||||||
<script>
|
|
||||||
const tasks = [
|
|
||||||
$tasksJs
|
|
||||||
];
|
|
||||||
async function triggerInstall() {
|
|
||||||
const log = document.getElementById('logArea');
|
|
||||||
log.textContent = '';
|
|
||||||
for (const t of tasks) {
|
|
||||||
const cb = document.getElementById(t.id);
|
|
||||||
if (cb.checked) {
|
|
||||||
log.textContent += `Calling ${t.id}...\n`;
|
|
||||||
try {
|
|
||||||
const resp = await fetch(t.handler, { method: 'GET' });
|
|
||||||
const txt = await resp.text();
|
|
||||||
log.textContent += txt + "\n";
|
|
||||||
} catch (e) {
|
|
||||||
log.textContent += `Error ${t.id}: ${e.message}\n`;
|
|
||||||
}
|
}
|
||||||
|
Write-EventLog -LogName $EventLog -Source $EventSource `
|
||||||
|
-EntryType $Level -EventId $EventID `
|
||||||
|
-Message $Message
|
||||||
|
} catch {
|
||||||
|
Write-Host "⚠️ [EventLog] Failed to write: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
</body>
|
# Hybrid wrapper: uses your module's Write-Log if available, else falls back
|
||||||
</html>
|
if (Get-Command Write-Log -ErrorAction SilentlyContinue) {
|
||||||
"@
|
function Write-LogHybrid { param($Message,$Level,$TaskCategory,$LogToEvent) Write-Log @PSBoundParameters }
|
||||||
return $html
|
} else {
|
||||||
|
function Write-LogHybrid { param($Message,$Level,$TaskCategory,$LogToEvent) Write-LogHelper @PSBoundParameters }
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region HTTP Server & Routing
|
#region Handler Stubs
|
||||||
|
|
||||||
|
function Respond-Text {
|
||||||
|
param($Context, $Text)
|
||||||
|
$bytes = [Text.Encoding]::UTF8.GetBytes($Text)
|
||||||
|
$Context.Response.ContentType = 'text/plain'
|
||||||
|
$Context.Response.ContentLength64 = $bytes.Length
|
||||||
|
$Context.Response.OutputStream.Write($bytes,0,$bytes.Length)
|
||||||
|
$Context.Response.OutputStream.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
function Respond-HTML {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)][object] $Context,
|
||||||
|
[Parameter(Mandatory = $true)][string] $Html
|
||||||
|
)
|
||||||
|
$bytes = [Text.Encoding]::UTF8.GetBytes($Html)
|
||||||
|
$Context.Response.ContentType = 'text/html'
|
||||||
|
$Context.Response.ContentLength64 = $bytes.Length
|
||||||
|
$Context.Response.OutputStream.Write($bytes, 0, $bytes.Length)
|
||||||
|
$Context.Response.OutputStream.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# On-boarding handlers
|
||||||
|
function Set-SVSPowerPlan {
|
||||||
|
param($Context)
|
||||||
|
Write-LogHybrid "PowerPlan set" "Success" "OnBoard"
|
||||||
|
Respond-Text $Context "Powerplan applied"
|
||||||
|
}
|
||||||
|
function Install-SVSMSPModule {
|
||||||
|
param($Context)
|
||||||
|
Write-LogHybrid "SVSMSP Module installed" "Success" "OnBoard"
|
||||||
|
Respond-Text $Context "SVSMSP Module installed"
|
||||||
|
}
|
||||||
|
function Install-CyberQP {
|
||||||
|
param($Context)
|
||||||
|
Write-LogHybrid "CyberQP installed" "Success" "OnBoard"
|
||||||
|
Respond-Text $Context "CyberQP installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Off-boarding handlers
|
||||||
|
function Uninstall-CyberQP {
|
||||||
|
param($Context)
|
||||||
|
Write-LogHybrid "CyberQP uninstalled" "Success" "OffBoard"
|
||||||
|
Respond-Text $Context "CyberQP uninstalled"
|
||||||
|
}
|
||||||
|
function Cleanup-SVSMSP {
|
||||||
|
param($Context)
|
||||||
|
Write-LogHybrid "SVSMSP cleaned up" "Success" "OffBoard"
|
||||||
|
Respond-Text $Context "SVSMSP cleaned up"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Tweaks handler
|
||||||
|
function Disable-Animations {
|
||||||
|
param($Context)
|
||||||
|
Write-LogHybrid "Animations disabled" "Success" "Tweaks"
|
||||||
|
Respond-Text $Context "Animations disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
# SVSApps handler
|
||||||
|
function Install-WingetLastPass {
|
||||||
|
param($Context)
|
||||||
|
Write-LogHybrid "Winget LastPass installed" "Success" "SVSApps"
|
||||||
|
Respond-Text $Context "Winget LastPass installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region UI Generation
|
||||||
|
|
||||||
|
function Get-UIHtml {
|
||||||
|
param([string]$Page = 'onboard')
|
||||||
|
|
||||||
|
#
|
||||||
|
# 1) Inline your full original CSS here
|
||||||
|
#
|
||||||
|
$style = @'
|
||||||
|
<style>
|
||||||
|
: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);
|
||||||
|
--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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 { text-align:left; padding:20px; }
|
||||||
|
.logo-container img { max-width:300px; height:auto; }
|
||||||
|
|
||||||
|
.container { display:flex; height:100vh; overflow:hidden; }
|
||||||
|
.sidebar { width:200px; background:var(--background-color); padding:10px; }
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:768px) {
|
||||||
|
.container { flex-direction:column; }
|
||||||
|
.sidebar { width:100%; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
'@
|
||||||
|
|
||||||
|
$script = @'
|
||||||
|
<script>
|
||||||
|
// =======================================================================
|
||||||
|
// Tab Navigation
|
||||||
|
// =======================================================================
|
||||||
|
const tabButtons = document.querySelectorAll(".tab-button");
|
||||||
|
const tabContents = document.querySelectorAll(".tab-content");
|
||||||
|
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");
|
||||||
|
document.getElementById(btn.dataset.tab).classList.add("active");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// initialize default tab on load
|
||||||
|
document.querySelector(".tab-button[data-tab='{{defaultPage}}Tab']").classList.add("active");
|
||||||
|
document.getElementById("{{defaultPage}}Tab").classList.add("active");
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// Task Trigger
|
||||||
|
// =======================================================================
|
||||||
|
const tasks = [
|
||||||
|
{{tasksJsAll}}
|
||||||
|
];
|
||||||
|
|
||||||
|
async function triggerInstall() {
|
||||||
|
for (const t of tasks) {
|
||||||
|
const cb = document.getElementById(t.id);
|
||||||
|
if (cb && cb.checked) {
|
||||||
|
try {
|
||||||
|
await fetch(t.handler, { method: "GET" });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error running ${t.label}:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================================================================
|
||||||
|
// Shutdown Handler
|
||||||
|
// =======================================================================
|
||||||
|
function endSession() {
|
||||||
|
fetch("/quit", { method: "GET" })
|
||||||
|
.finally(() => window.close());
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
'@
|
||||||
|
|
||||||
|
#
|
||||||
|
# 3) The HTML skeleton with placeholders
|
||||||
|
#
|
||||||
|
$htmlTemplate = @"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<title>SVS TaskGate</title>
|
||||||
|
<link rel="icon" href="https://git.svstools.com/syelle/Logo/raw/branch/main/SVS_Favicon.ico">
|
||||||
|
$style
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="logo-container">
|
||||||
|
<img src="https://git.svstools.com/syelle/Logo/raw/branch/main/SVS_logo.svg" alt="SVS Logo">
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="sidebar">
|
||||||
|
<button class="tab-button" data-tab="onboardTab">On-Boarding</button>
|
||||||
|
<button class="tab-button" data-tab="offboardTab">Off-Boarding</button>
|
||||||
|
<button class="tab-button" data-tab="tweaksTab">Tweaks</button>
|
||||||
|
<button class="tab-button" data-tab="SVSAppsTab">SVS APPs</button>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div id="onboardTab" class="tab-content">
|
||||||
|
<h2>On-Boarding</h2>
|
||||||
|
<div class="columns-container">
|
||||||
|
{{onboardCheckboxes}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="offboardTab" class="tab-content">
|
||||||
|
<h2>Off-Boarding</h2>
|
||||||
|
<div class="columns-container">
|
||||||
|
{{offboardCheckboxes}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="tweaksTab" class="tab-content">
|
||||||
|
<h2>Tweaks</h2>
|
||||||
|
<div class="columns-container">
|
||||||
|
{{tweaksCheckboxes}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="SVSAppsTab" class="tab-content">
|
||||||
|
<h2>SVS APPs</h2>
|
||||||
|
<div class="columns-container">
|
||||||
|
{{appsCheckboxes}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
$script
|
||||||
|
<!-- floating button group -->
|
||||||
|
<div class="fixed-buttons">
|
||||||
|
<button class="exit-button" onclick="endSession()">Exit</button>
|
||||||
|
<button class="run-button" onclick="triggerInstall()">Run Selected</button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
"@
|
||||||
|
|
||||||
|
#
|
||||||
|
# 4) Build the checkbox HTML and tasks JS from $Global:Tasks
|
||||||
|
#
|
||||||
|
$build = {
|
||||||
|
param($page)
|
||||||
|
($Global:Tasks | Where Page -EQ $page | ForEach-Object {
|
||||||
|
"<label><input type='checkbox' id='$($_.Id)' name='$($_.Name)'> $($_.Label)</label>"
|
||||||
|
}) -join "`n"
|
||||||
|
}
|
||||||
|
$onboard = & $build 'onboard'
|
||||||
|
$offboard = & $build 'offboard'
|
||||||
|
$tweaks = & $build 'tweaks'
|
||||||
|
$apps = & $build 'SVSApps'
|
||||||
|
|
||||||
|
$tasksJsAll = ($Global:Tasks | ForEach-Object {
|
||||||
|
" { id: '$($_.Id)', handler: '/$($_.Name)', label: '$($_.Label)' }"
|
||||||
|
}) -join ",`n"
|
||||||
|
|
||||||
|
#
|
||||||
|
# 5) Inject into template
|
||||||
|
#
|
||||||
|
$html = $htmlTemplate
|
||||||
|
$html = $html.Replace('{{onboardCheckboxes}}', $onboard)
|
||||||
|
$html = $html.Replace('{{offboardCheckboxes}}', $offboard)
|
||||||
|
$html = $html.Replace('{{tweaksCheckboxes}}', $tweaks)
|
||||||
|
$html = $html.Replace('{{appsCheckboxes}}', $apps)
|
||||||
|
$html = $html.Replace('{{tasksJsAll}}', $tasksJsAll)
|
||||||
|
$html = $html.Replace('{{defaultPage}}', $Page)
|
||||||
|
|
||||||
|
return $html
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region HTTP Listener & Routing
|
||||||
|
|
||||||
|
# Handle shutdown command
|
||||||
|
if ($path -eq 'quit') {
|
||||||
|
Write-LogHybrid "Shutdown requested" "Info" "Server"
|
||||||
|
Respond-Text $Context "Server shutting down."
|
||||||
|
# This will break out of the while loop in Start-Server
|
||||||
|
$Global:Listener.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sends the HTML for a given page or invokes a task handler
|
||||||
function Dispatch-Request {
|
function Dispatch-Request {
|
||||||
param($Context)
|
param($Context)
|
||||||
|
|
||||||
|
# figure out the path
|
||||||
$path = $Context.Request.Url.AbsolutePath.TrimStart('/')
|
$path = $Context.Request.Url.AbsolutePath.TrimStart('/')
|
||||||
# Match route to a task
|
|
||||||
|
# ---- Shutdown handler ----
|
||||||
|
if ($path -eq 'quit') {
|
||||||
|
Write-LogHybrid "Shutdown requested" "Info" "Server"
|
||||||
|
Respond-Text $Context "Server shutting down."
|
||||||
|
# stop the listener loop
|
||||||
|
$Global:Listener.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Serve UI pages ----
|
||||||
|
if ($path -in @('', 'onboard', 'offboard', 'tweaks', 'SVSApps')) {
|
||||||
|
$page = if ($path -eq '') { 'onboard' } else { $path }
|
||||||
|
$html = Get-UIHtml -Page $page
|
||||||
|
Respond-HTML $Context $html
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- Task invocation ----
|
||||||
$task = $Global:Tasks | Where-Object Name -EQ $path
|
$task = $Global:Tasks | Where-Object Name -EQ $path
|
||||||
if ($task) {
|
if ($task) {
|
||||||
& $task.HandlerFn $Context
|
& $task.HandlerFn $Context
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
# Serve UI
|
|
||||||
if ($path -eq '' -or $path -eq '/') {
|
# ---- 404 ----
|
||||||
$html = Get-UIHtml
|
|
||||||
$buffer = [Text.Encoding]::UTF8.GetBytes($html)
|
|
||||||
$Context.Response.ContentType = 'text/html'
|
|
||||||
$Context.Response.ContentLength64 = $buffer.Length
|
|
||||||
$Context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
|
|
||||||
$Context.Response.OutputStream.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
# Not found
|
|
||||||
$Context.Response.StatusCode = 404
|
$Context.Response.StatusCode = 404
|
||||||
Respond-Text $Context '404 - Not Found'
|
Respond-Text $Context '404 - Not Found'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Starts the HTTP listener loop
|
||||||
function Start-Server {
|
function Start-Server {
|
||||||
$listener = New-Object System.Net.HttpListener
|
# make it accessible to Dispatch-Request
|
||||||
$listener.Prefixes.Add("http://localhost:$Port/")
|
$Global:Listener = [System.Net.HttpListener]::new()
|
||||||
$listener.Start()
|
$Global:Listener.Prefixes.Add("http://localhost:$Port/")
|
||||||
Write-Host "Listening on http://localhost:$Port/"
|
$Global:Listener.Start()
|
||||||
while ($listener.IsListening) {
|
Write-Host "Listening on http://localhost:$Port/ ..."
|
||||||
$ctx = $listener.GetContext()
|
|
||||||
try { Dispatch-Request $ctx } catch { Write-Host "Error:" $_ }
|
try {
|
||||||
|
while ($Global:Listener.IsListening) {
|
||||||
|
$ctx = $Global:Listener.GetContext()
|
||||||
|
try {
|
||||||
|
Dispatch-Request $ctx
|
||||||
|
} catch {
|
||||||
|
Write-LogHybrid "Dispatch error: $_" "Error" "Server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
# once the loop exits, clean up
|
||||||
|
$Global:Listener.Close()
|
||||||
|
Write-LogHybrid "Listener closed." "Info" "Server"
|
||||||
}
|
}
|
||||||
$listener.Stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
# Bootstrap
|
# Bootstrap: launch the server
|
||||||
Start-Server
|
Start-Server
|
||||||
|
|||||||
Reference in New Issue
Block a user