- config-editor.hta: lightweight WYSIWYG HTA editor for config.json - Step on/off toggles with info tooltips - Editable software list (winget packages) - Settings: timezone, admin account, desktopInfo, PDF default - Run.cmd: USB launcher with UAC auto-elevation and deployment menu - flash/: minimal USB-ready subset (Deploy, scripts, config, GUI, launcher) - config.json: add steps section for per-step enable/disable - Deploy-Windows.ps1: read steps from config, CLI switches override - 03-system-registry.ps1: add SearchOnTaskbarMode HKLM policy (Win11 search fix) - 04-default-profile.ps1: fix systray - clear TrayNotify cache + proper Explorer restart - 06-scheduled-tasks.ps1: fix Register-Task trigger array, ShowAllTrayIcons Win11 fix, PDF-DefaultApp runs as SYSTEM via HKCR (bypasses UserChoice Hash validation) - 02-software.ps1: remove unreliable UserChoice ProgId write without Hash Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
632 lines
20 KiB
HTML
632 lines
20 KiB
HTML
<html>
|
|
<head>
|
|
<title>X9 - Deployment Config Editor</title>
|
|
<HTA:APPLICATION
|
|
ID="ConfigEditor"
|
|
APPLICATIONNAME="X9 Config Editor"
|
|
SCROLL="no"
|
|
SINGLEINSTANCE="yes"
|
|
WINDOWSTATE="normal"
|
|
INNERBORDER="no"
|
|
SELECTION="no"
|
|
CONTEXTMENU="no"
|
|
/>
|
|
<meta http-equiv="x-ua-compatible" content="ie=11">
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: Segoe UI, Arial, sans-serif;
|
|
font-size: 13px;
|
|
background: #1a2a33;
|
|
color: #d0dde3;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
#header {
|
|
background: #223B47;
|
|
padding: 12px 18px;
|
|
border-bottom: 2px solid #2e5568;
|
|
flex-shrink: 0;
|
|
}
|
|
#header h1 {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #e8f4f8;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
#header .config-path {
|
|
font-size: 11px;
|
|
color: #7aabbd;
|
|
margin-top: 3px;
|
|
word-break: break-all;
|
|
}
|
|
#tabs {
|
|
display: flex;
|
|
background: #1a2a33;
|
|
border-bottom: 1px solid #2e5568;
|
|
flex-shrink: 0;
|
|
}
|
|
.tab {
|
|
padding: 8px 18px;
|
|
cursor: pointer;
|
|
color: #7aabbd;
|
|
border-bottom: 2px solid transparent;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
.tab:hover { color: #b0d4e0; }
|
|
.tab.active { color: #e8f4f8; border-bottom: 2px solid #4a9aba; background: #1e3340; }
|
|
#content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 0;
|
|
}
|
|
.tab-panel { display: none; padding: 16px 18px; }
|
|
.tab-panel.active { display: block; }
|
|
table { width: 100%; border-collapse: collapse; }
|
|
th {
|
|
background: #223B47;
|
|
color: #9ac8d8;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
padding: 7px 10px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #2e5568;
|
|
}
|
|
td {
|
|
padding: 6px 10px;
|
|
border-bottom: 1px solid #1e3340;
|
|
vertical-align: middle;
|
|
}
|
|
tr:hover td { background: #1e3340; }
|
|
.step-num {
|
|
color: #4a9aba;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
width: 36px;
|
|
}
|
|
.step-name { color: #d0dde3; }
|
|
.info-btn {
|
|
background: #2e5568;
|
|
border: none;
|
|
color: #7aabbd;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
line-height: 20px;
|
|
text-align: center;
|
|
position: relative;
|
|
}
|
|
.info-btn:hover { background: #3a6f8a; color: #e8f4f8; }
|
|
input[type="checkbox"] {
|
|
width: 16px;
|
|
height: 16px;
|
|
cursor: pointer;
|
|
accent-color: #4a9aba;
|
|
}
|
|
input[type="text"], select {
|
|
background: #1a2a33;
|
|
border: 1px solid #2e5568;
|
|
color: #d0dde3;
|
|
padding: 5px 8px;
|
|
border-radius: 3px;
|
|
font-size: 12px;
|
|
width: 100%;
|
|
}
|
|
input[type="text"]:focus, select:focus {
|
|
outline: none;
|
|
border-color: #4a9aba;
|
|
}
|
|
.label-cell {
|
|
width: 160px;
|
|
color: #9ac8d8;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
padding-top: 10px;
|
|
vertical-align: top;
|
|
}
|
|
.section-title {
|
|
color: #4a9aba;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin: 16px 0 8px 0;
|
|
padding-bottom: 4px;
|
|
border-bottom: 1px solid #2e5568;
|
|
}
|
|
.section-title:first-child { margin-top: 0; }
|
|
.btn {
|
|
background: #223B47;
|
|
border: 1px solid #2e5568;
|
|
color: #d0dde3;
|
|
padding: 5px 12px;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
}
|
|
.btn:hover { background: #2e5568; color: #e8f4f8; }
|
|
.btn-danger {
|
|
background: #3d1f1f;
|
|
border-color: #6b2c2c;
|
|
color: #d08080;
|
|
}
|
|
.btn-danger:hover { background: #5a2020; color: #f0a0a0; }
|
|
.btn-add {
|
|
background: #1a3a2a;
|
|
border-color: #2e6a4a;
|
|
color: #80c8a0;
|
|
margin-top: 8px;
|
|
}
|
|
.btn-add:hover { background: #235a38; color: #a0e0b8; }
|
|
#footer {
|
|
background: #223B47;
|
|
border-top: 2px solid #2e5568;
|
|
padding: 10px 18px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
flex-shrink: 0;
|
|
}
|
|
#btn-save {
|
|
background: #1a5c3a;
|
|
border: 1px solid #2a8a58;
|
|
color: #80e0b0;
|
|
padding: 7px 22px;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
#btn-save:hover { background: #206e46; color: #a0f0c8; }
|
|
#status {
|
|
font-size: 12px;
|
|
color: #7aabbd;
|
|
flex: 1;
|
|
}
|
|
#tooltip {
|
|
position: fixed;
|
|
background: #0f1e26;
|
|
border: 1px solid #3a7a9a;
|
|
color: #c0dde8;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
max-width: 340px;
|
|
line-height: 1.5;
|
|
z-index: 9999;
|
|
display: none;
|
|
pointer-events: none;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
|
|
}
|
|
.settings-grid { display: grid; grid-template-columns: 160px 1fr; gap: 8px 12px; align-items: center; }
|
|
.settings-grid .span2 { grid-column: 1 / -1; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div id="header">
|
|
<h1>X9 - Deployment Config</h1>
|
|
<div class="config-path" id="config-path-display">Loading...</div>
|
|
</div>
|
|
|
|
<div id="tabs">
|
|
<div class="tab active" onclick="showTab('steps')">Steps</div>
|
|
<div class="tab" onclick="showTab('software')">Software</div>
|
|
<div class="tab" onclick="showTab('settings')">Settings</div>
|
|
</div>
|
|
|
|
<div id="content">
|
|
|
|
<!-- STEPS TAB -->
|
|
<div class="tab-panel active" id="tab-steps">
|
|
<table id="steps-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:40px;">On</th>
|
|
<th style="width:36px;">Step</th>
|
|
<th>Name</th>
|
|
<th style="width:32px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="steps-tbody">
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- SOFTWARE TAB -->
|
|
<div class="tab-panel" id="tab-software">
|
|
<table id="software-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Package Name</th>
|
|
<th>Winget ID</th>
|
|
<th style="width:70px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="software-tbody">
|
|
</tbody>
|
|
</table>
|
|
<button class="btn btn-add" onclick="addSoftwareRow()">+ Add package</button>
|
|
</div>
|
|
|
|
<!-- SETTINGS TAB -->
|
|
<div class="tab-panel" id="tab-settings">
|
|
<div class="section-title">Deployment</div>
|
|
<div class="settings-grid">
|
|
<div class="label-cell">Timezone</div>
|
|
<div><input type="text" id="s-timezone" onchange="updateSetting('deployment','timezone',this.value)"></div>
|
|
|
|
<div class="label-cell">Locale</div>
|
|
<div><input type="text" id="s-locale" onchange="updateSetting('deployment','locale',this.value)"></div>
|
|
</div>
|
|
|
|
<div class="section-title">Admin Account</div>
|
|
<div class="settings-grid">
|
|
<div class="label-cell">Username</div>
|
|
<div><input type="text" id="s-admin-user" onchange="updateSetting('adminAccount','username',this.value)"></div>
|
|
|
|
<div class="label-cell">Password</div>
|
|
<div><input type="text" id="s-admin-pass" onchange="updateSetting('adminAccount','password',this.value)"></div>
|
|
|
|
<div class="label-cell">Description</div>
|
|
<div><input type="text" id="s-admin-desc" onchange="updateSetting('adminAccount','description',this.value)"></div>
|
|
</div>
|
|
|
|
<div class="section-title">PDF Default</div>
|
|
<div class="settings-grid">
|
|
<div class="label-cell">Force Adobe Reader</div>
|
|
<div><input type="checkbox" id="s-force-adobe" onchange="updateSettingBool('pdfDefault','forceAdobeReader',this.checked)"></div>
|
|
|
|
<div class="label-cell">Scheduled Task</div>
|
|
<div><input type="checkbox" id="s-pdf-task" onchange="updateSettingBool('pdfDefault','scheduledTaskEnabled',this.checked)"></div>
|
|
</div>
|
|
|
|
<div class="section-title">Desktop Info</div>
|
|
<div class="settings-grid">
|
|
<div class="label-cell">Enabled</div>
|
|
<div><input type="checkbox" id="s-di-enabled" onchange="updateSettingBool('desktopInfo','enabled',this.checked)"></div>
|
|
|
|
<div class="label-cell">Position</div>
|
|
<div>
|
|
<select id="s-di-position" onchange="updateSetting('desktopInfo','position',this.value)">
|
|
<option value="bottomRight">Bottom Right</option>
|
|
<option value="bottomLeft">Bottom Left</option>
|
|
<option value="topRight">Top Right</option>
|
|
<option value="topLeft">Top Left</option>
|
|
<option value="center">Center</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="label-cell">Font Size</div>
|
|
<div><input type="text" id="s-di-fontsize" onchange="updateSettingInt('desktopInfo','fontSize',this.value)"></div>
|
|
|
|
<div class="label-cell">Font Color</div>
|
|
<div><input type="text" id="s-di-fontcolor" onchange="updateSetting('desktopInfo','fontColor',this.value)" placeholder="#FFFFFF"></div>
|
|
</div>
|
|
|
|
<div class="section-title">Activation</div>
|
|
<div class="settings-grid">
|
|
<div class="label-cell">Product Key</div>
|
|
<div><input type="text" id="s-act-key" onchange="updateSetting('activation','productKey',this.value)" placeholder="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"></div>
|
|
|
|
<div class="label-cell">KMS Server</div>
|
|
<div><input type="text" id="s-act-kms" onchange="updateSetting('activation','kmsServer',this.value)" placeholder="kms.example.com"></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div id="footer">
|
|
<button id="btn-save" onclick="saveConfig()">Save config.json</button>
|
|
<div id="status">Ready.</div>
|
|
</div>
|
|
|
|
<div id="tooltip"></div>
|
|
|
|
<script language="JScript">
|
|
|
|
var STEP_DEFS = [
|
|
{ key: "adminAccount", num: "00", label: "Admin account", info: "Creates local admin account for MSP remote access. Account name and generated password are saved to Deploy.log." },
|
|
{ key: "bloatware", num: "01", label: "Bloatware removal", info: "Removes 44 pre-installed UWP apps (Teams, Xbox, Cortana, Solitaire, Office Hub, Skype...) and unused Windows Capabilities and Features. Calculator is kept." },
|
|
{ key: "software", num: "02", label: "Software install", info: "Installs packages from the list below via winget. Also sets Adobe Reader as default PDF viewer via HKCR." },
|
|
{ key: "systemRegistry", num: "03", label: "System registry", info: "HKLM-wide tweaks: disables Widgets, Teams auto-install, Edge first run, OneDrive, Outlook auto-install, GameDVR, Recall. Sets timezone. Hides taskbar search box via policy (Win11)." },
|
|
{ key: "defaultProfile", num: "04", label: "Default profile", info: "Modifies C:\\Users\\Default\\NTUSER.DAT so ALL future users inherit: left-aligned taskbar, hidden search/Copilot/TaskView/Widgets buttons, file extensions visible, Explorer opens to This PC, Num Lock on." },
|
|
{ key: "personalization", num: "05", label: "Personalization", info: "Dark system theme, light app theme, accent color #223B47, transparency off, accent on title bars. Applied to Default profile and current user." },
|
|
{ key: "scheduledTasks", num: "06", label: "Scheduled tasks", info: "Registers 4 tasks: ShowAllTrayIcons (logon - clears systray cache + restarts Explorer), UnlockStartLayout (once), PDF-DefaultApp (logon as SYSTEM - restores HKCR association), DesktopInfo (logon - renders wallpaper)." },
|
|
{ key: "desktopInfo", num: "07", label: "Desktop info", info: "Custom desktop wallpaper showing: computer name, IP, OS version, username, deployment date. Rendered as BMP on every logon via scheduled task. Replaces BackInfo.exe." },
|
|
{ key: "activation", num: "08", label: "Windows activation", info: "Checks and applies Windows activation." }
|
|
];
|
|
|
|
var configPath = "";
|
|
var config = null;
|
|
var fso = null;
|
|
|
|
function init() {
|
|
try {
|
|
fso = new ActiveXObject("Scripting.FileSystemObject");
|
|
|
|
// Derive config path from HTA location
|
|
var htaPath = location.pathname.replace(/\//g, "\\");
|
|
// Remove leading backslash from pathname
|
|
if (htaPath.charAt(0) === "\\") {
|
|
htaPath = htaPath.substring(1);
|
|
}
|
|
var dir = fso.GetParentFolderName(htaPath);
|
|
configPath = dir + "\\config\\config.json";
|
|
|
|
document.getElementById("config-path-display").innerText = configPath;
|
|
|
|
loadConfig();
|
|
buildStepsTable();
|
|
resizeWindow();
|
|
} catch(e) {
|
|
setStatus("Init error: " + e.message, true);
|
|
}
|
|
}
|
|
|
|
function resizeWindow() {
|
|
window.resizeTo(800, 720);
|
|
window.moveTo(
|
|
(screen.width - 800) / 2,
|
|
(screen.height - 720) / 2
|
|
);
|
|
}
|
|
|
|
function loadConfig() {
|
|
try {
|
|
if (!fso.FileExists(configPath)) {
|
|
setStatus("config.json not found: " + configPath, true);
|
|
config = {};
|
|
return;
|
|
}
|
|
var f = fso.OpenTextFile(configPath, 1, false, -1);
|
|
var raw = f.ReadAll();
|
|
f.Close();
|
|
config = JSON.parse(raw);
|
|
setStatus("Loaded: " + configPath);
|
|
populateSettings();
|
|
buildSoftwareTable();
|
|
} catch(e) {
|
|
setStatus("Load error: " + e.message, true);
|
|
config = {};
|
|
}
|
|
}
|
|
|
|
function buildStepsTable() {
|
|
var tbody = document.getElementById("steps-tbody");
|
|
var html = "";
|
|
for (var i = 0; i < STEP_DEFS.length; i++) {
|
|
var s = STEP_DEFS[i];
|
|
var checked = "";
|
|
// Check config.steps if loaded, default true
|
|
if (config && config.steps && config.steps[s.key] === false) {
|
|
checked = "";
|
|
} else {
|
|
checked = "checked";
|
|
}
|
|
html += "<tr>";
|
|
html += "<td><input type='checkbox' " + checked + " onclick=\"toggleStep('" + s.key + "', this.checked)\"></td>";
|
|
html += "<td class='step-num'>" + s.num + "</td>";
|
|
html += "<td class='step-name'>" + s.label + "</td>";
|
|
html += "<td><button class='info-btn' onmouseover=\"showTooltip(event, '" + escapeQ(s.info) + "')\" onmouseout='hideTooltip()'>i</button></td>";
|
|
html += "</tr>";
|
|
}
|
|
tbody.innerHTML = html;
|
|
}
|
|
|
|
function buildSoftwareTable() {
|
|
var tbody = document.getElementById("software-tbody");
|
|
var html = "";
|
|
if (config && config.software && config.software.install) {
|
|
var list = config.software.install;
|
|
for (var i = 0; i < list.length; i++) {
|
|
html += makeSoftwareRow(i, list[i].name, list[i].wingetId);
|
|
}
|
|
}
|
|
tbody.innerHTML = html;
|
|
}
|
|
|
|
function makeSoftwareRow(idx, name, wingetId) {
|
|
return "<tr id='sw-row-" + idx + "'>" +
|
|
"<td><input type='text' value='" + escapeQ(name) + "' onchange=\"updateSoftwareName(" + idx + ", this.value)\"></td>" +
|
|
"<td><input type='text' value='" + escapeQ(wingetId) + "' onchange=\"updateSoftwareId(" + idx + ", this.value)\"></td>" +
|
|
"<td><button class='btn btn-danger' onclick='removeSoftwareRow(" + idx + ")'>Remove</button></td>" +
|
|
"</tr>";
|
|
}
|
|
|
|
function addSoftwareRow() {
|
|
if (!config.software) { config.software = {}; }
|
|
if (!config.software.install) { config.software.install = []; }
|
|
config.software.install.push({ name: "", wingetId: "" });
|
|
buildSoftwareTable();
|
|
setStatus("Package added. Fill in name and Winget ID, then save.");
|
|
}
|
|
|
|
function removeSoftwareRow(idx) {
|
|
if (!config || !config.software || !config.software.install) { return; }
|
|
config.software.install.splice(idx, 1);
|
|
buildSoftwareTable();
|
|
setStatus("Package removed. Click Save to persist.");
|
|
}
|
|
|
|
function updateSoftwareName(idx, val) {
|
|
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
|
|
config.software.install[idx].name = val;
|
|
}
|
|
}
|
|
|
|
function updateSoftwareId(idx, val) {
|
|
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
|
|
config.software.install[idx].wingetId = val;
|
|
}
|
|
}
|
|
|
|
function populateSettings() {
|
|
if (!config) { return; }
|
|
|
|
if (config.deployment) {
|
|
setVal("s-timezone", config.deployment.timezone || "");
|
|
setVal("s-locale", config.deployment.locale || "");
|
|
}
|
|
if (config.adminAccount) {
|
|
setVal("s-admin-user", config.adminAccount.username || "");
|
|
setVal("s-admin-pass", config.adminAccount.password || "");
|
|
setVal("s-admin-desc", config.adminAccount.description || "");
|
|
}
|
|
if (config.pdfDefault) {
|
|
setChk("s-force-adobe", config.pdfDefault.forceAdobeReader !== false);
|
|
setChk("s-pdf-task", config.pdfDefault.scheduledTaskEnabled !== false);
|
|
}
|
|
if (config.desktopInfo) {
|
|
setChk("s-di-enabled", config.desktopInfo.enabled !== false);
|
|
setSelVal("s-di-position", config.desktopInfo.position || "bottomRight");
|
|
setVal("s-di-fontsize", config.desktopInfo.fontSize !== undefined ? String(config.desktopInfo.fontSize) : "12");
|
|
setVal("s-di-fontcolor", config.desktopInfo.fontColor || "#FFFFFF");
|
|
}
|
|
if (config.activation) {
|
|
setVal("s-act-key", config.activation.productKey || "");
|
|
setVal("s-act-kms", config.activation.kmsServer || "");
|
|
}
|
|
}
|
|
|
|
function setVal(id, val) {
|
|
var el = document.getElementById(id);
|
|
if (el) { el.value = val; }
|
|
}
|
|
|
|
function setChk(id, val) {
|
|
var el = document.getElementById(id);
|
|
if (el) { el.checked = !!val; }
|
|
}
|
|
|
|
function setSelVal(id, val) {
|
|
var el = document.getElementById(id);
|
|
if (!el) { return; }
|
|
for (var i = 0; i < el.options.length; i++) {
|
|
if (el.options[i].value === val) {
|
|
el.selectedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function toggleStep(key, enabled) {
|
|
if (!config) { return; }
|
|
if (!config.steps) { config.steps = {}; }
|
|
config.steps[key] = enabled;
|
|
}
|
|
|
|
function updateSetting(section, key, val) {
|
|
if (!config) { return; }
|
|
if (!config[section]) { config[section] = {}; }
|
|
config[section][key] = val;
|
|
}
|
|
|
|
function updateSettingBool(section, key, val) {
|
|
if (!config) { return; }
|
|
if (!config[section]) { config[section] = {}; }
|
|
config[section][key] = !!val;
|
|
}
|
|
|
|
function updateSettingInt(section, key, val) {
|
|
if (!config) { return; }
|
|
if (!config[section]) { config[section] = {}; }
|
|
var n = parseInt(val, 10);
|
|
config[section][key] = isNaN(n) ? val : n;
|
|
}
|
|
|
|
function saveConfig() {
|
|
if (!config) { setStatus("No config loaded.", true); return; }
|
|
try {
|
|
// Sync steps checkboxes before save (in case table was rebuilt)
|
|
syncStepsFromTable();
|
|
|
|
var json = JSON.stringify(config, null, 2);
|
|
var f = fso.CreateTextFile(configPath, true, true);
|
|
f.Write(json);
|
|
f.Close();
|
|
setStatus("Saved: " + configPath + " [" + now() + "]");
|
|
} catch(e) {
|
|
setStatus("Save error: " + e.message, true);
|
|
}
|
|
}
|
|
|
|
function syncStepsFromTable() {
|
|
if (!config.steps) { config.steps = {}; }
|
|
var rows = document.getElementById("steps-tbody").getElementsByTagName("tr");
|
|
for (var i = 0; i < rows.length; i++) {
|
|
var chk = rows[i].getElementsByTagName("input")[0];
|
|
if (chk && STEP_DEFS[i]) {
|
|
config.steps[STEP_DEFS[i].key] = chk.checked;
|
|
}
|
|
}
|
|
}
|
|
|
|
function showTab(name) {
|
|
var panels = document.getElementsByClassName("tab-panel");
|
|
for (var i = 0; i < panels.length; i++) {
|
|
panels[i].className = "tab-panel";
|
|
}
|
|
var tabs = document.getElementsByClassName("tab");
|
|
for (var i = 0; i < tabs.length; i++) {
|
|
tabs[i].className = "tab";
|
|
}
|
|
document.getElementById("tab-" + name).className = "tab-panel active";
|
|
var allTabs = document.getElementById("tabs").getElementsByClassName("tab");
|
|
var nameMap = { steps: 0, software: 1, settings: 2 };
|
|
if (nameMap[name] !== undefined) {
|
|
allTabs[nameMap[name]].className = "tab active";
|
|
}
|
|
}
|
|
|
|
function showTooltip(evt, text) {
|
|
var t = document.getElementById("tooltip");
|
|
t.innerText = text;
|
|
t.style.display = "block";
|
|
positionTooltip(evt);
|
|
}
|
|
|
|
function positionTooltip(evt) {
|
|
var t = document.getElementById("tooltip");
|
|
var x = evt.clientX + 12;
|
|
var y = evt.clientY + 12;
|
|
if (x + 350 > document.body.clientWidth) { x = evt.clientX - 350; }
|
|
if (y + 80 > document.body.clientHeight) { y = evt.clientY - 80; }
|
|
t.style.left = x + "px";
|
|
t.style.top = y + "px";
|
|
}
|
|
|
|
function hideTooltip() {
|
|
document.getElementById("tooltip").style.display = "none";
|
|
}
|
|
|
|
function setStatus(msg, isError) {
|
|
var el = document.getElementById("status");
|
|
el.innerText = msg;
|
|
el.style.color = isError ? "#d08080" : "#7aabbd";
|
|
}
|
|
|
|
function now() {
|
|
var d = new Date();
|
|
return d.getHours() + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds());
|
|
}
|
|
|
|
function pad(n) { return n < 10 ? "0" + n : String(n); }
|
|
|
|
function escapeQ(s) {
|
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, """);
|
|
}
|
|
|
|
window.onload = function() { init(); };
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|