xetup/web/spec/index.html
X9 Dev a10a3a8aa2 fix: move Dell Command | Update card to 'Stavajici kroky' section
Was incorrectly placed under 'Nove kroky (planovane)'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:51:33 +02:00

1355 lines
56 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Specifikace &amp; anotace xetup</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<style>
:root {
--bg: #0f1117;
--card: #1a1d27;
--card2: #141720;
--border: #2a2d3a;
--text: #e0e0e0;
--muted: #888;
--accent: #223B47;
--accent-bright: #2d5266;
--green: #2ea043;
--blue: #58a6ff;
--yellow: #d29922;
--red: #da3633;
--purple: #8b949e;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
}
/* ---- HEADER ---- */
header {
border-bottom: 1px solid var(--border);
padding: 1rem 2rem;
display: flex;
align-items: center;
gap: .75rem;
position: sticky;
top: 0;
background: var(--bg);
z-index: 100;
}
.logo-text { font-size: 1.1rem; font-weight: 700; color: #fff; letter-spacing: -.02em; }
.logo-sub { font-size: .8rem; color: var(--muted); margin-left: .2rem; }
header nav { margin-left: auto; display: flex; gap: 1.5rem; }
header nav a { color: var(--muted); text-decoration: none; font-size: .85rem; transition: color .15s; }
header nav a:hover, header nav a.active { color: var(--text); }
/* ---- LAYOUT ---- */
.layout {
display: grid;
grid-template-columns: 220px 1fr;
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1.5rem;
gap: 2rem;
align-items: start;
}
/* ---- SIDEBAR ---- */
.sidebar {
position: sticky;
top: 57px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--card);
padding: 1rem;
}
.sidebar h4 {
font-size: .72rem;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--muted);
margin-bottom: .75rem;
}
.sidebar a {
display: block;
color: var(--muted);
text-decoration: none;
font-size: .83rem;
padding: .3rem .4rem;
border-radius: 5px;
transition: background .1s, color .1s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar a:hover { background: var(--card2); color: var(--text); }
.sidebar-divider { border: none; border-top: 1px solid var(--border); margin: .6rem 0; }
/* ---- CONTENT ---- */
.content h1 {
font-size: 1.7rem;
font-weight: 800;
letter-spacing: -.03em;
color: #fff;
margin-bottom: .4rem;
}
.meta {
font-size: .82rem;
color: var(--muted);
margin-bottom: 2rem;
display: flex;
gap: 1.2rem;
flex-wrap: wrap;
align-items: center;
}
.meta a { color: var(--muted); text-decoration: none; }
.meta a:hover { color: var(--blue); }
/* ---- LEGEND ---- */
.legend {
display: flex;
gap: .6rem;
flex-wrap: wrap;
margin-bottom: 2rem;
padding: .8rem 1rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
font-size: .8rem;
}
.legend span { display: flex; align-items: center; gap: .3rem; color: var(--muted); }
/* ---- STEP CARD ---- */
.step {
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
margin-bottom: 1.2rem;
overflow: hidden;
}
.step-header {
display: flex;
align-items: center;
gap: .75rem;
padding: .9rem 1.2rem;
border-bottom: 1px solid var(--border);
background: var(--card2);
}
.step-num {
font-size: .72rem;
font-weight: 700;
color: var(--muted);
font-family: monospace;
min-width: 30px;
}
.step-title {
font-size: .95rem;
font-weight: 600;
color: #fff;
flex: 1;
}
.step-body { padding: 1rem 1.2rem; }
/* ---- ITEM ROWS ---- */
.items { width: 100%; border-collapse: collapse; font-size: .83rem; }
.items td {
padding: .45rem .6rem;
border-bottom: 1px solid var(--border);
vertical-align: top;
line-height: 1.45;
}
.items tr:last-child td { border-bottom: none; }
.items td:first-child { color: var(--text); width: 55%; }
.items td:last-child { color: var(--muted); width: 45%; font-size: .78rem; }
.items tr.flag-mustfix td:first-child { color: #f0853a; }
.items tr.flag-done td:first-child { color: var(--green); }
.items tr.flag-todo td:first-child { color: var(--yellow); }
.items tr.flag-open td:first-child { color: var(--blue); }
.items tr.flag-warn td:first-child { color: #da3633; }
/* ---- BADGES ---- */
.badge {
display: inline-block;
border-radius: 4px;
padding: .15rem .4rem;
font-size: .68rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .04em;
white-space: nowrap;
margin-left: .3rem;
vertical-align: middle;
}
.badge-ok { background: #1a2e1a; color: var(--green); border: 1px solid var(--green); }
.badge-mustfix { background: #2e1c10; color: #f0853a; border: 1px solid #f0853a; }
.badge-todo { background: #2e250a; color: var(--yellow); border: 1px solid var(--yellow); }
.badge-open { background: #0d1e30; color: var(--blue); border: 1px solid var(--blue); }
.badge-warn { background: #2e0d0c; color: #da3633; border: 1px solid #da3633; }
.badge-future { background: #1e1e2e; color: var(--purple); border: 1px solid var(--purple); }
.badge-new { background: #0d1e30; color: #79c0ff; border: 1px solid #79c0ff; }
/* ---- ROW EXPAND ---- */
.items td.xp { width: 54px; padding: .25rem .3rem; text-align: right; vertical-align: middle; }
.xp-btn {
background: rgba(88,166,255,.08);
border: 1px solid rgba(88,166,255,.25);
color: var(--blue);
cursor: pointer; font-size: .7rem; padding: .15rem .4rem;
border-radius: 4px; line-height: 1.4; transition: background .1s, border-color .1s;
white-space: nowrap;
}
.xp-btn:hover { background: rgba(88,166,255,.18); border-color: var(--blue); }
.xp-btn.open { background: rgba(88,166,255,.2); border-color: var(--blue); }
.items tr.detail-tr td {
background: rgba(88,166,255,.04);
border-bottom: 1px solid var(--border);
padding: .8rem 1rem .8rem 1.2rem;
font-size: .82rem;
color: var(--muted);
}
.detail-heading {
font-size: .7rem; text-transform: uppercase; letter-spacing: .08em;
color: var(--muted); margin-bottom: .4rem;
}
.detail-desc-script {
color: var(--text); line-height: 1.65; font-size: .85rem;
margin-bottom: .6rem; white-space: pre-wrap; word-break: break-word;
}
.detail-desc-script--empty { color: var(--muted); font-style: italic; }
.detail-note {
margin-top: .3rem; margin-bottom: .5rem; color: var(--muted);
font-size: .78rem; font-style: italic; line-height: 1.45;
}
.detail-divider {
border: none; border-top: 1px solid var(--border); margin: .65rem 0;
}
/* row comment thread */
.rcl { margin-bottom: .4rem; }
.rci { padding: .3rem 0; border-bottom: 1px dashed rgba(255,255,255,.06); font-size: .8rem; }
.rci:last-child { border-bottom: none; }
.rci-meta { font-size: .7rem; color: var(--muted); margin-bottom: .1rem; }
.rci-body { color: var(--text); white-space: pre-wrap; word-break: break-word; }
.rcf { display: flex; gap: .35rem; align-items: flex-start; margin-top: .5rem; flex-wrap: wrap; }
.rcf-name {
width: 110px; background: var(--bg); border: 1px solid var(--border);
border-radius: 5px; color: var(--text); font-size: .78rem;
padding: .28rem .45rem; font-family: inherit;
}
.rcf-text {
flex: 1; min-width: 140px; background: var(--bg); border: 1px solid var(--border);
border-radius: 5px; color: var(--text); font-size: .8rem;
padding: .28rem .45rem; font-family: inherit; resize: none; min-height: 36px;
}
.rcf-name:focus, .rcf-text:focus { outline: none; border-color: var(--accent-bright); }
.rcf-send {
background: var(--accent-bright); color: #fff; border: none;
border-radius: 5px; padding: .28rem .6rem; font-size: .78rem;
cursor: pointer; white-space: nowrap; line-height: 1.4;
}
.rcf-send:hover { opacity: .85; }
.rcf-send:disabled { opacity: .35; cursor: default; }
/* ---- NOTES ---- */
.note {
margin-top: .75rem;
padding: .6rem .8rem;
background: rgba(88,166,255,.06);
border-left: 3px solid var(--accent-bright);
border-radius: 0 5px 5px 0;
font-size: .8rem;
color: var(--muted);
line-height: 1.5;
}
.note strong { color: var(--text); }
.note code {
font-family: monospace;
font-size: .78rem;
background: rgba(255,255,255,.07);
padding: .1rem .3rem;
border-radius: 3px;
}
/* ---- STEP FOOTER ---- */
.step-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: .6rem 1.2rem;
border-top: 1px solid var(--border);
background: var(--card2);
gap: .5rem;
flex-wrap: wrap;
}
.step-footer code {
font-family: monospace;
font-size: .78rem;
background: rgba(255,255,255,.07);
padding: .1rem .3rem;
border-radius: 3px;
color: var(--muted);
}
.step-status { font-size: .78rem; color: var(--muted); }
/* ---- SECTION HEADING ---- */
.section-label {
font-size: .72rem;
text-transform: uppercase;
letter-spacing: .1em;
color: var(--muted);
margin: 1.8rem 0 .7rem;
padding-bottom: .3rem;
border-bottom: 1px solid var(--border);
}
.section-label:first-child { margin-top: 0; }
/* ---- INLINE CODE ---- */
code {
font-family: monospace;
font-size: .82em;
background: rgba(255,255,255,.08);
padding: .1rem .3rem;
border-radius: 3px;
}
/* ---- COMMENT WIDGET ---- */
.comment-widget { width: 100%; }
.comment-toggle {
background: none;
border: 1px dashed var(--border);
border-radius: 5px;
color: var(--muted);
font-size: .8rem;
padding: .25rem .7rem;
cursor: pointer;
transition: border-color .15s, color .15s;
display: flex;
align-items: center;
gap: .4rem;
}
.comment-toggle:hover { border-color: var(--blue); color: var(--blue); }
.comment-toggle .cnt {
background: var(--accent-bright);
color: #fff;
border-radius: 10px;
font-size: .7rem;
padding: .05rem .35rem;
display: none;
}
.comment-toggle .cnt.visible { display: inline; }
.comment-panel {
display: none;
margin-top: .5rem;
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
}
.comment-panel.open { display: block; }
.comment-list { padding: .5rem; }
.comment-item {
padding: .5rem .6rem;
border-bottom: 1px solid var(--border);
font-size: .82rem;
line-height: 1.5;
}
.comment-item:last-child { border-bottom: none; }
.comment-meta {
font-size: .72rem;
color: var(--muted);
margin-bottom: .25rem;
}
.comment-body { color: var(--text); white-space: pre-wrap; word-break: break-word; }
.comment-empty {
padding: .6rem;
font-size: .8rem;
color: var(--muted);
text-align: center;
font-style: italic;
}
.comment-form {
border-top: 1px solid var(--border);
padding: .6rem;
display: flex;
flex-direction: column;
gap: .4rem;
}
.comment-input-name {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 5px;
color: var(--text);
font-size: .8rem;
padding: .35rem .5rem;
resize: none;
font-family: inherit;
}
.comment-input {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 5px;
color: var(--text);
font-size: .82rem;
padding: .4rem .5rem;
resize: vertical;
min-height: 60px;
font-family: inherit;
line-height: 1.4;
}
.comment-input:focus, .comment-input-name:focus {
outline: none;
border-color: var(--accent-bright);
}
.comment-submit {
align-self: flex-end;
background: var(--accent-bright);
color: #fff;
border: none;
border-radius: 5px;
padding: .3rem .8rem;
font-size: .8rem;
cursor: pointer;
transition: opacity .15s;
}
.comment-submit:hover { opacity: .85; }
.comment-submit:disabled { opacity: .4; cursor: default; }
.comment-error { font-size: .75rem; color: var(--red); }
/* ---- NEW REQUEST WIDGET ---- */
.req-list {
margin-bottom: .8rem;
}
.req-item {
padding: .55rem .7rem;
border: 1px solid var(--border);
border-radius: 7px;
margin-bottom: .45rem;
font-size: .83rem;
line-height: 1.5;
background: var(--card2);
}
.req-item-meta { font-size: .72rem; color: var(--muted); margin-bottom: .2rem; }
.req-item-body { color: var(--text); white-space: pre-wrap; word-break: break-word; }
.req-form { display: flex; flex-direction: column; gap: .45rem; }
.req-name {
width: 220px;
background: var(--bg); border: 1px solid var(--border); border-radius: 5px;
color: var(--text); font-size: .8rem; padding: .35rem .5rem; font-family: inherit;
}
.req-text {
width: 100%;
background: var(--bg); border: 1px solid var(--border); border-radius: 5px;
color: var(--text); font-size: .82rem; padding: .4rem .5rem;
resize: vertical; min-height: 70px; font-family: inherit; line-height: 1.4;
}
.req-name:focus, .req-text:focus { outline: none; border-color: var(--accent-bright); }
.req-submit {
align-self: flex-start;
background: var(--accent-bright); color: #fff; border: none;
border-radius: 5px; padding: .32rem .9rem; font-size: .8rem; cursor: pointer;
transition: opacity .15s;
}
.req-submit:hover { opacity: .85; }
.req-submit:disabled { opacity: .4; cursor: default; }
.req-error { font-size: .75rem; color: var(--red); margin-top: .1rem; }
footer {
border-top: 1px solid var(--border);
padding: 1.2rem 2rem;
text-align: center;
font-size: .8rem;
color: var(--muted);
margin-top: 3rem;
}
footer a { color: var(--muted); text-decoration: none; }
footer a:hover { color: var(--text); }
@media (max-width: 700px) {
.layout { grid-template-columns: 1fr; }
.sidebar { position: static; }
}
</style>
</head>
<body>
<header>
<div>
<a href="/" style="text-decoration:none;display:flex;align-items:center;gap:.6rem">
<img src="/x9-logo.jpeg" alt="X9.cz" style="height:28px;width:28px;border-radius:5px;object-fit:cover;">
<span class="logo-text">xetup</span><span class="logo-sub">by X9.cz</span>
</a>
</div>
<nav>
<a href="/spec/" class="active">Specifikace</a>
<a href="https://git.xetup.x9.cz/x9/xetup">Git</a>
<a href="https://git.xetup.x9.cz/x9/xetup/issues">Issues</a>
</nav>
</header>
<div class="layout">
<!-- SIDEBAR -->
<aside class="sidebar">
<h4>Kroky (scripty)</h4>
<a href="#step-00">00 &ndash; Admin ucet</a>
<a href="#step-01">01 &ndash; Bloatware</a>
<a href="#step-02">02 &ndash; Software (winget)</a>
<a href="#step-03">03 &ndash; Registry (HKLM)</a>
<a href="#step-04">04 &ndash; Default Profile</a>
<a href="#step-05">05 &ndash; Personalizace</a>
<a href="#step-06">06 &ndash; Scheduled Tasks</a>
<a href="#step-07">07 &ndash; BackInfo</a>
<a href="#step-08">08 &ndash; Aktivace Windows</a>
<hr class="sidebar-divider">
<h4>Planovane kroky</h4>
<a href="#step-pc">09 &ndash; PC identita + C:\X9</a>
<a href="#step-net">10 &ndash; Network discovery</a>
<a href="#step-dell">11 &ndash; Dell Command | Update</a>
<a href="#step-taskbar">Taskbar profily</a>
<hr class="sidebar-divider">
<h4>Architektura</h4>
<a href="#arch-xetup">xetup.exe (Go TUI)</a>
<a href="#arch-spec">spec.yaml</a>
<hr class="sidebar-divider">
<a href="#new-requests">+ Novy pozadavek</a>
</aside>
<!-- CONTENT -->
<div class="content">
<h1>Specifikace &amp; anotace</h1>
<div class="meta">
<span>Verze: 0.3-draft</span>
<span>Datum: 2026-04-16</span>
<span><a href="https://git.xetup.x9.cz/x9/xetup">x9/xetup</a></span>
<span>Status: aktivni vyvoj</span>
</div>
<div class="legend">
<span><span class="badge badge-ok">OK</span> hotovo, v provozu</span>
<span><span class="badge badge-mustfix">Must fix</span> zname chyby, nutno opravit</span>
<span><span class="badge badge-todo">TODO</span> naplanovano, nerealizovano</span>
<span><span class="badge badge-open">Open</span> otevrena otazka</span>
<span><span class="badge badge-warn">Warn</span> potencialni problem</span>
<span><span class="badge badge-new">New</span> nova feature</span>
<span><span class="badge badge-future">Future</span> dlouhodoby plan</span>
</div>
<!-- ============================================================ -->
<p class="section-label">Stavajici kroky (scripty)</p>
<!-- STEP 00 -->
<div class="step" id="step-00">
<div class="step-header">
<span class="step-num">00</span>
<span class="step-title">Admin ucet (adminx9)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Vytvorit lokalni ucet <code>adminx9</code></td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Pridat do skupiny Administrators</td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Skryt z login obrazovky (SpecialAccounts\UserList = 0)</td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Heslo nevypirsi, uzivatel nesmeni heslo</td><td>Hotovo</td></tr>
<tr class="flag-done"><td>Zadne heslo (aktualne nastavovano z config.json)</td><td>Opraveno &ndash; prazdny SecureString, config.json heslo odstranen</td></tr>
<tr class="flag-done"><td>FullName = "X9.cz s.r.o." (via ADSI)</td><td>Opraveno &ndash; ADSI SetInfo() po vytvoreni uctu</td></tr>
</table>
<div class="note">
<strong>Proc bez hesla:</strong> Ucet je skryty pred uzivateli, slouzi pouze MSP adminstraci.
Heslo v config.json by bylo ulozene citelne.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>00-admin-account.ps1</code></span>
<div class="comment-widget" data-issue="1"></div>
</div>
</div>
<!-- STEP 01 -->
<div class="step" id="step-01">
<div class="step-header">
<span class="step-num">01</span>
<span class="step-title">Bloatware removal</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>AppX balicky &ndash; odstraneni pro vsechny uzivatele a provisioned</td><td>Remove-AppxPackage -AllUsers + Remove-AppxProvisionedPackage</td></tr>
<tr class="flag-done"><td>Zachovano: Microsoft.WindowsCalculator</td><td>Zamerny vyjimek</td></tr>
<tr class="flag-done"><td>Windows Capabilities (Fax, IE, OpenSSH, WMP, WordPad, …)</td><td>Remove-WindowsCapability</td></tr>
<tr class="flag-done"><td>Windows Optional Features (PS 2.0, MediaPlayback, Recall, …)</td><td>Disable-WindowsOptionalFeature</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>01-bloatware.ps1</code></span>
<div class="comment-widget" data-issue="2"></div>
</div>
</div>
<!-- STEP 02 -->
<div class="step" id="step-02">
<div class="step-header">
<span class="step-num">02</span>
<span class="step-title">Software (winget)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>7-Zip (<code>7zip.7zip</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Adobe Acrobat Reader 64-bit (<code>Adobe.Acrobat.Reader.64-bit</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>OpenVPN Connect (<code>OpenVPNTechnologies.OpenVPNConnect</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Atera Agent install</td><td>Invoke-WebRequest + <code>msiexec /i /qn</code></td></tr>
<tr class="flag-done"><td>Adobe PDF default: .pdf -&gt; AcroRd32 po instalaci</td><td>OK &ndash; UCPD stop/start kolem zápisu asociace</td></tr>
<tr class="flag-done"><td>UCPD.sys (kernel driver, od Feb 2024) blokuje UserChoice</td><td>Reseno: Stop-Service ucpd &rarr; HKCR zapis &rarr; Start-Service ucpd</td></tr>
</table>
<div class="note">
<strong>Atera Agent URL:</strong><br>
<code>https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&amp;aeid=50b72e7113e54a63ac76b96c54c7e337</code>
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>02-software.ps1</code></span>
<div class="comment-widget" data-issue="3"></div>
</div>
</div>
<!-- STEP 03 -->
<div class="step" id="step-03">
<div class="step-header">
<span class="step-num">03</span>
<span class="step-title">System Registry (HKLM)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Bypass NRO (OOBE\BypassNRO = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>Zakaz auto-instalace Teams</td><td>ConfigureChatAutoInstall = 0</td></tr>
<tr class="flag-done"><td>Zakaz Cloud Optimized Content</td><td>OK</td></tr>
<tr class="flag-done"><td>Zakaz Widgets / News and Interests</td><td>OK</td></tr>
<tr class="flag-done"><td>Hesla bez expirace (<code>net accounts /maxpwage:UNLIMITED</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Casova zona: Central Europe Standard Time</td><td>OK</td></tr>
<tr class="flag-done"><td>Zakaz GameDVR</td><td>OK</td></tr>
<tr class="flag-done"><td>Edge &ndash; skryt First Run Experience + zakaz default browser prompt</td><td>HideFirstRunExperience=1, DefaultBrowserSettingEnabled=0</td></tr>
<tr class="flag-done"><td>Edge policies &ndash; panel oblibeny, vyhledavac Google</td><td>FavoritesBarEnabled=1, DefaultSearchProviderName=Google, ManagedSearchEngines</td></tr>
<tr class="flag-done"><td>Edge policies &ndash; tlacitka zobrazit (Historie, Stahnout)</td><td>DownloadsButtonEnabled=1, HistoryButtonEnabled=1</td></tr>
<tr class="flag-done"><td>Edge policies &ndash; tlacitka skryt (Home, Kolekce, Split, Drop, Screenshot, Share, Zpetna vazba)</td><td>HomeButtonEnabled=0, SplitScreenEnabled=0, EdgeEDropEnabled=0, WebCaptureEnabled=0, ShareAllowed=0, FeedbackSurveysEnabled=0, EdgeCollectionsEnabled=0</td></tr>
<tr class="flag-done"><td>Edge policies &ndash; obsah a telemetrie</td><td>NewTabPageContentEnabled=0, ShowRecommendationsEnabled=0, EdgeShoppingAssistantEnabled=0, DiagnosticData=0, &hellip;</td></tr>
<tr class="flag-done"><td>OneDrive uninstall (intentional)</td><td>OneDriveSetup.exe /uninstall &ndash; odstrani pre-installed verzi. M365 si nainstaluje vlastni.</td></tr>
<tr class="flag-done"><td>Powercfg nastaveni (spotreba energie)</td><td>standby-ac 0, monitor-ac 60, standby-dc 30, monitor-dc 15</td></tr>
<tr class="flag-done"><td>Proxy auto-detect zakaz (AutoDetect = 0)</td><td>HKLM\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>03-system-registry.ps1</code></span>
<div class="comment-widget" data-issue="4"></div>
</div>
</div>
<!-- STEP 04 -->
<div class="step" id="step-04">
<div class="step-header">
<span class="step-num">04</span>
<span class="step-title">Default Profile (NTUSER.DAT)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Taskbar: zarovnat vlevo (TaskbarAl = 0)</td><td>Win11 default je center</td></tr>
<tr class="flag-done"><td>Taskbar: skryt Search, Copilot, Task View, Widgets, Chat</td><td>OK</td></tr>
<tr class="flag-done"><td>Taskbar: zobrazit vsechny ikonky v tray (Scheduled task)</td><td>ShowAllTrayIcons</td></tr>
<tr class="flag-done"><td>Taskbar: vyprazdnit pinlist (TaskbarLayoutModification.xml)</td><td>OK</td></tr>
<tr class="flag-done"><td>Explorer: zobrazovat pripony souboru (HideFileExt = 0)</td><td>OK</td></tr>
<tr class="flag-done"><td>Explorer: otevrit na This PC (LaunchTo = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>Start menu: vyprazdnit piny (Win11)</td><td>ConfigureStartPins = {"pinnedList":[]}</td></tr>
<tr class="flag-done"><td>Start menu: zakaz Bing vyhledavani</td><td>DisableSearchBoxSuggestions = 1</td></tr>
<tr class="flag-done"><td>Copilot: zakaz (TurnOffWindowsCopilot = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>NumLock zapnout pri startu (InitialKeyboardIndicators = 2)</td><td>OK</td></tr>
<tr class="flag-done"><td>Accent barva na titulnich listech (ColorPrevalence = 1)</td><td>OK</td></tr>
<tr class="flag-done"><td>OneDrive RunOnce klic je tady &ndash; smazat</td><td>Opraveno &ndash; blok odstranen ze scriptu (brani reinstalaci pres M365)</td></tr>
<tr class="flag-done"><td>Explorer: ShowRecent = 0, ShowFrequent = 0</td><td>Skryt nedavne a caste soubory v Quick Access</td></tr>
<tr class="flag-done"><td>Explorer: FullPath = 1 (CabinetState)</td><td>Zobrazovat plnou cestu v titulku okna Explorera</td></tr>
</table>
<div class="note">
<strong>Metoda:</strong> <code>reg load HKU\DefaultProfile C:\Users\Default\NTUSER.DAT</code>
&rarr; zapsat zmeny &rarr; <code>reg unload HKU\DefaultProfile</code>.<br>
Tato operace musi probihat PRED prvnim prihlasenim uzivatele.
Aktualne prihlaseny uzivatel dostava zmeny pres primy zapis do HKCU.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>04-default-profile.ps1</code></span>
<div class="comment-widget" data-issue="5"></div>
</div>
</div>
<!-- STEP 05 -->
<div class="step" id="step-05">
<div class="step-header">
<span class="step-num">05</span>
<span class="step-title">Personalizace (barvy, tapeta)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>System tema (taskbar, Start): Dark</td><td>OK</td></tr>
<tr class="flag-done"><td>Aplikacni tema: Light</td><td>OK</td></tr>
<tr class="flag-done"><td>Accent barva: #223B47 (tmave modroseda)</td><td>OK</td></tr>
<tr class="flag-done"><td>Accent barva na Start a taskbaru: ano</td><td>OK</td></tr>
<tr class="flag-done"><td>Pruhlednost: vypnuta</td><td>OK</td></tr>
<tr class="flag-done"><td>Tapeta: jednobarevna #223B47 (bez obrazku)</td><td>BackInfo prepise tapetu svym BMP</td></tr>
</table>
<div class="note">
BackInfo.exe (STEP 07) prepise tapetu BMP se systemovymi informacemi.
Jednobarevna tapeta je fallback pro pripad, ze BackInfo nedobehne nebo se nespusti.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>05-personalization.ps1</code></span>
<div class="comment-widget" data-issue="6"></div>
</div>
</div>
<!-- STEP 06 -->
<div class="step" id="step-06">
<div class="step-header">
<span class="step-num">06</span>
<span class="step-title">Scheduled Tasks</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>ShowAllTrayIcons &ndash; pri logonu + kazdou 1 min</td><td>Win11 automaticky skryva tray ikony</td></tr>
<tr class="flag-done"><td>UnlockStartLayout &ndash; jednou po aplikaci layoutu</td><td>Odemkne Start menu pro uzivatelske zmeny</td></tr>
<tr class="flag-done"><td>PDF-DefaultApp pri kazdem logonu &ndash; odstranen</td><td>PDF asociace nastavena jednou v kroku 02 (UCPD stop/start). Task nebyl nutny.</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>06-scheduled-tasks.ps1</code></span>
<div class="comment-widget" data-issue="7"></div>
</div>
</div>
<!-- STEP 07 -->
<div class="step" id="step-07">
<div class="step-header">
<span class="step-num">07</span>
<span class="step-title">BackInfo (systemovy info na tapete)</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td><code>07-desktop-info.ps1</code> SMAZAT &ndash; stary pristup</td><td>Nahrazeno novym <code>07-backinfo.ps1</code></td></tr>
<tr class="flag-done"><td>Zkopirovat <code>assets/Backinfo/</code> do <code>C:\Program Files\Backinfo\</code></td><td>Implementovano v 07-backinfo.ps1</td></tr>
<tr class="flag-done"><td>Spustit <code>backinfo_W11.ps1</code> (detekce OS, registry, Startup)</td><td>Logika inlinovana v 07-backinfo.ps1</td></tr>
<tr class="flag-done"><td>BackInfo.exe v assets/Backinfo/ k dispozici</td><td>Hotovo</td></tr>
<tr class="flag-done"><td>BackInfo auto-start pri kazdem logonu via Startup shortcut</td><td>Shortcut do ProgramData\StartUp vytvori 07-backinfo.ps1</td></tr>
</table>
<div class="note">
<strong>BackInfo.ini konfiguruje:</strong> hostname (velky, centrovan), uzivatelske jmeno,
OS verze, HW info (CPU, RAM, disk), sitove informace (IP, hostname).<br><br>
<strong>Proc BackInfo misto vlastniho PS:</strong>
BackInfo.exe podporuje Win10 i Win11 bez specialnich hacku, je stabilni a uz je v assets.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>07-backinfo.ps1</code></span>
<div class="comment-widget" data-issue="8"></div>
</div>
</div>
<!-- STEP 08 -->
<div class="step" id="step-08">
<div class="step-header">
<span class="step-num">08</span>
<span class="step-title">Windows aktivace</span>
<span class="badge badge-ok">OK</span>
<span class="badge badge-open">Open</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>OA3 BIOS/UEFI klic &ndash; kontrola embedded key</td><td>WMI: SoftwareLicensingService.OA3xOriginalProductKey</td></tr>
<tr class="flag-done"><td>Klic z config.json (<code>activation.productKey</code>)</td><td>OK &ndash; priorita nad OA3 a GVLK</td></tr>
<tr class="flag-done"><td>Fallback na GVLK (KMS client key) dle edice OS</td><td>OK</td></tr>
<tr class="flag-done"><td>Volitelny KMS server (<code>activation.kmsServer</code>)</td><td>OK</td></tr>
<tr class="flag-done"><td>Preskocit pokud jiz aktivovano</td><td>OK</td></tr>
<tr class="flag-open"><td>Typ klice: MAK vs KMS vs retail?</td><td>Zavisi na klientovi &ndash; otevrena otazka</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>08-activation.ps1</code></span>
<div class="comment-widget" data-issue="9"></div>
</div>
</div>
<!-- STEP 11 -->
<div class="step" id="step-dell">
<div class="step-header">
<span class="step-num">11</span>
<span class="step-title">Dell Command | Update</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Detekce Dell hardware (<code>Win32_ComputerSystem</code>)</td><td>Non-Dell stroj krok preskoci bez chyby &ndash; stejny skript pro vsechny HW</td></tr>
<tr class="flag-done"><td>Instalace Dell Command | Update via winget</td><td><code>Dell.CommandUpdate.Universal</code> &ndash; silent, Win10 + Win11</td></tr>
<tr class="flag-done"><td>Spusteni vsech aktualizaci: drivery, firmware, BIOS</td><td><code>dcu-cli.exe /applyUpdates -silent -reboot=disable</code></td></tr>
<tr class="flag-done"><td>BIOS/firmware se staging &ndash; dokonci se pri restartu</td><td>Restart po konci deploymenty (krok 10 rename) vse dokonci</td></tr>
</table>
<div class="note">
Non-Dell stroje: krok se preskoci automaticky, zadna chyba. Dell Latitude, OptiPlex,
Precision, Vostro, XPS &ndash; vsechny podporovane DCU Universal.<br><br>
<strong>Casova narocnost:</strong> 5&ndash;20 minut podle poctu dostupnych aktualizaci a rychlosti siteho pripojeni.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>11-dell-update.ps1</code></span>
<div class="comment-widget" data-issue="16"></div>
</div>
</div>
<!-- ============================================================ -->
<p class="section-label">Nove kroky (planovane)</p>
<!-- STEP 09 -->
<div class="step" id="step-pc">
<div class="step-header">
<span class="step-num">09</span>
<span class="step-title">PC identita &ndash; Rename + C:\X9</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Rename-Computer dle parametru z TUI nebo config.json</td><td><code>deployment.pcName</code> v config.json; preskoci pokud neni nastaveno</td></tr>
<tr class="flag-done"><td>Nastavit popis pocitace (Computer Description)</td><td>LanmanServer\Parameters\SrvComment; default "X9 deployment"</td></tr>
<tr class="flag-done"><td>Vytvorit <code>C:\X9\</code> adresarovou strukturu</td><td>C:\X9\Logs, Scripts, Assets</td></tr>
<tr class="flag-done"><td>Vlastni ikonka pro <code>C:\X9\</code> slozku</td><td>Desktop.ini + X9-ikona.ico z assets\Logo\</td></tr>
</table>
<div class="note">
Rename-Computer vyzaduje restart. Tento krok bezi jako posledni pred finalnim shrnutim.
</div>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>09-pc-identity.ps1</code></span>
<div class="comment-widget" data-issue="12"></div>
</div>
</div>
<!-- STEP 10 -->
<div class="step" id="step-net">
<div class="step-header">
<span class="step-num">10</span>
<span class="step-title">Network discovery + firewall</span>
<span class="badge badge-ok">OK</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Nastavit sitovy profil jako Private (ne Public)</td><td>Set-NetConnectionProfile pro vsechny pripojene adaptery</td></tr>
<tr class="flag-done"><td>Povolit ping (ICMP) pro diagnostiku</td><td>Enable-NetFirewallRule: FPS-ICMP4-ERQ-In + FPS-ICMP6-ERQ-In</td></tr>
<tr class="flag-done"><td>Zapnout Network Discovery pro Private profil</td><td>Set-NetFirewallRule + netsh advfirewall jako fallback</td></tr>
</table>
</div>
<div class="step-footer">
<span class="step-status">Script: <code>10-network.ps1</code></span>
<div class="comment-widget" data-issue="10"></div>
</div>
</div>
<!-- TASKBAR -->
<div class="step" id="step-taskbar">
<div class="step-header">
<span class="step-num">04+</span>
<span class="step-title">Taskbar pinned apps (profily)</span>
<span class="badge badge-ok">OK</span>
<span class="badge badge-open">Open</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td><code>-ProfileType</code> parametr: admin vs user varianta</td><td>Deploy-Windows.ps1 -ProfileType [default|admin|user]; predano do 04</td></tr>
<tr class="flag-done"><td>XML layout pro "admin": Explorer, PS, Edge</td><td>TaskbarLayoutModification.xml; File Explorer.lnk + PowerShell.lnk + Edge.lnk</td></tr>
<tr class="flag-done"><td>XML layout pro "user": Explorer, Edge</td><td>Konzervativni sada &ndash; Outlook/Teams pridany az po instalaci M365</td></tr>
<tr class="flag-open"><td>Win11 24H2 kompatibilita layoutu</td><td>24H2 vyzaduje ProvisionedLayoutModification.xml &ndash; nutno otestovat na realne instalaci</td></tr>
</table>
<div class="note">
Aplikace pinnutych appek: <code>Deploy-Windows.ps1 -ProfileType admin</code> nebo <code>-ProfileType user</code>.<br>
Layout se zablokuje, UnlockStartLayout task (krok 06) ho odemkne 5 min po startu.
</div>
</div>
<div class="step-footer">
<span class="step-status">Integrovan do <code>04-default-profile.ps1</code></span>
<div class="comment-widget" data-issue="13"></div>
</div>
</div>
<!-- ============================================================ -->
<p class="section-label">Architektura (budoucnost)</p>
<!-- ARCH XETUP -->
<div class="step" id="arch-xetup">
<div class="step-header">
<span class="step-num">Arc</span>
<span class="step-title">xetup.exe &ndash; Go TUI launcher</span>
<span class="badge badge-future">Future</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-done"><td>Single binary (go:embed scripty + assets)</td><td><code>embed.go</code> + <code>cmd/xetup/main.go</code>; builduje se jako 5 MB .exe</td></tr>
<tr class="flag-done"><td>TUI form (huh/bubbletea): PC name, popis, product key</td><td><code>internal/tui/tui.go</code> &ndash; huh form, 2 stranky</td></tr>
<tr class="flag-done"><td>Checklist kroku (on/off per-script) + ulozit do config.json</td><td>MultiSelect v TUI; <code>internal/config/config.go</code></td></tr>
<tr class="flag-done"><td>Live log output behem spousteni PS scriptu</td><td><code>internal/runner/runner.go</code>; channel + bubbletea cmd</td></tr>
<tr class="flag-done"><td>Finalni summary OK/ERROR</td><td>viewDone() v tui.go</td></tr>
<tr class="flag-todo"><td>Self-update: stahnout novou verzi z xetup.x9.cz</td><td>Overit hash pred spustenim</td></tr>
<tr class="flag-future"><td>config.json: per-klient preset (prefix jmena PC, SW, klic)</td><td>Lezi vedle .exe na USB klienta</td></tr>
<tr class="flag-future"><td>OpenVPN soubor + doménovy join + domén. uzivatel pro profil</td><td>Rozsireni TUI formulare v budoucnu</td></tr>
</table>
<div class="note">
<strong>Struktura:</strong> <code>cmd/xetup/</code>, <code>internal/config/</code>,
<code>internal/spec/</code>, <code>internal/tui/</code>, <code>internal/runner/</code><br><br>
<strong>Go zavislosti:</strong>
bubbletea (TUI framework), huh (forms), lipgloss (styling)
</div>
</div>
<div class="step-footer">
<span class="step-status">Status: v implementaci &ndash; <code>cmd/xetup/</code>, <code>internal/{config,runner,tui}/</code></span>
<div class="comment-widget" data-issue="11"></div>
</div>
</div>
<!-- ARCH SPEC -->
<div class="step" id="arch-spec">
<div class="step-header">
<span class="step-num">Arc</span>
<span class="step-title">spec.yaml &ndash; single source of truth</span>
<span class="badge badge-future">Future</span>
</div>
<div class="step-body">
<table class="items">
<tr class="flag-todo"><td>Popis vsech kroku: id, label, script, default</td><td>xetup.exe cte spec.yaml pro TUI checklist</td></tr>
<tr class="flag-todo"><td>Pole "requires" (napr. activation vyzaduje productKey)</td><td>TUI upozorni pokud chybi</td></tr>
<tr class="flag-future"><td>Auto-generovana dokumentace z spec.yaml</td><td>CI akce: spec.yaml &rarr; tato stranka</td></tr>
<tr class="flag-future"><td>spec.yaml jako SSOT pro tuto stranku i deploy skripty</td><td>Idealni stav: stranka vzdy odpovida kodu</td></tr>
</table>
<div class="note">
<strong>Navrh struktury spec.yaml:</strong><br>
<code style="display:block; white-space:pre; line-height:1.6">steps:
- id: admin-account
label: "Admin account (adminx9)"
script: 00-admin-account.ps1
default: true
- id: activation
label: "Windows activation"
script: 08-activation.ps1
default: true
requires: [productKey]</code>
</div>
</div>
<div class="step-footer">
<span class="step-status">Status: navrh, zatim zadny soubor</span>
<div class="comment-widget" data-issue="14"></div>
</div>
</div>
<!-- ============================================================ -->
<p class="section-label">Nove nastaveni &ndash; pozadavky</p>
<!-- NEW REQUESTS -->
<div class="step" id="new-requests">
<div class="step-header">
<span class="step-num" style="font-size:1.1rem">+</span>
<span class="step-title">Novy pozadavek na automatizaci</span>
<span class="badge badge-new">Pozadavky</span>
</div>
<div class="step-body">
<p style="color:var(--muted);font-size:.84rem;line-height:1.5;margin-bottom:1rem">
Chcete automatizovat neco, co skript zatim neresi?
Napiste pozadavek sem &ndash; ulozi se do repozitare.
Technicky tym ho projde a zaradi do planu.
</p>
<div class="req-list" id="req-list">
<div style="font-size:.8rem;color:var(--muted);font-style:italic">Nacitam pozadavky...</div>
</div>
<div class="req-form" id="req-form">
<input class="req-name" id="req-name" placeholder="Vase jmeno (volitelne)" maxlength="60">
<textarea class="req-text" id="req-text" placeholder="Co by mel automat delat? Popiste konkretne, idealne i proc." rows="3"></textarea>
<button class="req-submit" id="req-submit">Odeslat pozadavek</button>
<span class="req-error" id="req-error"></span>
</div>
</div>
</div>
</div><!-- /content -->
</div><!-- /layout -->
<footer>
&copy; 2026 <a href="https://x9.cz">X9.cz s.r.o.</a>
&nbsp;&middot;&nbsp;
<a href="https://git.xetup.x9.cz/x9/xetup">Forgejo</a>
&nbsp;&middot;&nbsp;
<a href="/">xetup.x9.cz</a>
</footer>
<script>
(function() {
const API = '/forgejo-api';
const REPO = 'x9/xetup';
const TOKEN = 'e67f674af71847c4349b79b51d2b66a1ea41d031';
const HEADS = { 'Authorization': 'token ' + TOKEN, 'Content-Type': 'application/json' };
function timeAgo(iso) {
const s = Math.floor((Date.now() - new Date(iso)) / 1000);
if (s < 60) return 'prave ted';
if (s < 3600) return Math.floor(s/60) + ' min';
if (s < 86400)return Math.floor(s/3600) + ' hod';
return Math.floor(s/86400) + ' dni';
}
function renderComments(list, listEl) {
listEl.innerHTML = '';
if (!list.length) {
listEl.innerHTML = '<div class="comment-empty">Zatim zadne komentare. Bud prvni.</div>';
return;
}
list.forEach(c => {
const el = document.createElement('div');
el.className = 'comment-item';
const name = c.user.login === 'xetup-bot'
? (c.body.match(/^\*\*(.+?)\*\*/) ? c.body.match(/^\*\*(.+?)\*\*/)[1] : 'anon')
: c.user.login;
const body = c.body.replace(/^\*\*.+?\*\*\n/, '');
el.innerHTML =
'<div class="comment-meta">' + name + ' &middot; ' + timeAgo(c.created_at) + '</div>' +
'<div class="comment-body">' + body.replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</div>';
listEl.appendChild(el);
});
}
function initWidget(el) {
const issue = el.dataset.issue;
if (!issue || issue === '0') return;
el.innerHTML =
'<button class="comment-toggle" title="Komentare k teto sekci">' +
'&#128172; Komentare <span class="cnt"></span>' +
'</button>' +
'<div class="comment-panel">' +
'<div class="comment-list"><div class="comment-empty">Nacitam...</div></div>' +
'<div class="comment-form">' +
'<input class="comment-input-name" placeholder="Tvoje jmeno (volitelne)" maxlength="40">' +
'<textarea class="comment-input" placeholder="Napiste komentar..."></textarea>' +
'<div style="display:flex;gap:.5rem;align-items:center;justify-content:flex-end">' +
'<span class="comment-error"></span>' +
'<button class="comment-submit">Odeslat</button>' +
'</div>' +
'</div>' +
'</div>';
const toggle = el.querySelector('.comment-toggle');
const panel = el.querySelector('.comment-panel');
const listEl = el.querySelector('.comment-list');
const nameEl = el.querySelector('.comment-input-name');
const textEl = el.querySelector('.comment-input');
const submitEl= el.querySelector('.comment-submit');
const errEl = el.querySelector('.comment-error');
const cntEl = el.querySelector('.cnt');
let loaded = false;
let comments = [];
function load() {
fetch(API + '/repos/' + REPO + '/issues/' + issue + '/comments', { headers: HEADS })
.then(r => r.json())
.then(data => {
comments = Array.isArray(data) ? data : [];
renderComments(comments, listEl);
if (comments.length) {
cntEl.textContent = comments.length;
cntEl.classList.add('visible');
}
loaded = true;
})
.catch(() => { listEl.innerHTML = '<div class="comment-empty">Chyba nacitani.</div>'; });
}
toggle.addEventListener('click', () => {
const open = panel.classList.toggle('open');
if (open && !loaded) load();
});
submitEl.addEventListener('click', () => {
const text = textEl.value.trim();
if (!text) { errEl.textContent = 'Komentar nesmi byt prazdny.'; return; }
errEl.textContent = '';
submitEl.disabled = true;
submitEl.textContent = 'Odesilam...';
const name = nameEl.value.trim() || 'anon';
const body = '**' + name + '**\n' + text;
fetch(API + '/repos/' + REPO + '/issues/' + issue + '/comments', {
method: 'POST', headers: HEADS, body: JSON.stringify({ body: body })
})
.then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
.then(c => {
comments.push(c);
renderComments(comments, listEl);
cntEl.textContent = comments.length;
cntEl.classList.add('visible');
textEl.value = '';
nameEl.value = '';
submitEl.disabled = false;
submitEl.textContent = 'Odeslat';
})
.catch(() => {
errEl.textContent = 'Chyba odeslani. Zkus znovu.';
submitEl.disabled = false;
submitEl.textContent = 'Odeslat';
});
});
}
document.querySelectorAll('.comment-widget').forEach(initWidget);
// ---- DESCRIPTIONS FROM scripts/*.ps1 headers ----
let DESCRIPTIONS = {};
fetch('/data/descriptions.json')
.then(r => r.json())
.then(d => { DESCRIPTIONS = d; })
.catch(() => {});
// Map spec step id -> script id
const STEP_SCRIPT = {
'step-00': '00-admin-account',
'step-01': '01-bloatware',
'step-02': '02-software',
'step-03': '03-system-registry',
'step-04': '04-default-profile',
'step-05': '05-personalization',
'step-06': '06-scheduled-tasks',
'step-07': '07-backinfo',
'step-pc': '09-pc-identity',
'step-net': '10-network',
'step-08': '08-activation',
'step-dell': '11-dell-update',
};
function getItemDesc(stepId, slug) {
const scriptId = STEP_SCRIPT[stepId];
if (!scriptId || !DESCRIPTIONS[scriptId]) return null;
return DESCRIPTIONS[scriptId].items[slug] || null;
}
// ---- ROW-LEVEL EXPAND + COMMENTS ----
function rSlug(txt) {
return txt.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').substring(0, 40);
}
function esc(s) { return s.replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function renderRowComments(all, slug, listEl) {
const prefix = '[row:' + slug + ']';
const relevant = all.filter(c => c.body.startsWith(prefix));
listEl.innerHTML = '';
if (!relevant.length) {
listEl.innerHTML = '<div style="font-size:.75rem;color:var(--muted);font-style:italic;padding:.2rem 0">Zatim zadne komentare.</div>';
return;
}
relevant.forEach(c => {
const body = c.body.slice(prefix.length + 1);
const m = body.match(/^\*\*(.+?)\*\*\n([\s\S]*)$/);
const name = m ? m[1] : 'anon';
const text = m ? m[2] : body;
const el = document.createElement('div');
el.className = 'rci';
el.innerHTML = '<div class="rci-meta">' + esc(name) + ' &middot; ' + timeAgo(c.created_at) + '</div>' +
'<div class="rci-body">' + esc(text) + '</div>';
listEl.appendChild(el);
});
}
function enhanceRows() {
document.querySelectorAll('.step').forEach(step => {
const widget = step.querySelector('.comment-widget');
const issueNum = widget ? widget.dataset.issue : null;
const stepId = step.id;
step.querySelectorAll('.items tr:not(.detail-tr)').forEach(tr => {
const cells = tr.querySelectorAll('td');
if (!cells.length) return;
const rowText = cells[0].textContent.trim();
const noteText = cells[1] ? cells[1].textContent.trim() : '';
const slug = rSlug(rowText);
// Expand button as last cell
const xpCell = document.createElement('td');
xpCell.className = 'xp';
const btn = document.createElement('button');
btn.className = 'xp-btn';
btn.innerHTML = 'detail ▸';
btn.title = 'Zobrazit detail a komentare';
xpCell.appendChild(btn);
tr.appendChild(xpCell);
// Detail row
const dtr = document.createElement('tr');
dtr.className = 'detail-tr';
dtr.style.display = 'none';
const dtd = document.createElement('td');
dtd.colSpan = 3;
// Description from script header (loaded async - may not be ready yet,
// will be filled in when detail opens)
const scriptDesc = () => getItemDesc(stepId, slug);
dtd.innerHTML =
// description from script header
'<div class="detail-heading">Co to dela</div>' +
'<div class="detail-desc-script"></div>' +
// brief note from col 2 as secondary context
(noteText ? '<div class="detail-note">' + esc(noteText) + '</div>' : '') +
'<hr class="detail-divider">' +
// comments
'<div class="detail-heading">Komentare</div>' +
'<div class="rcl"></div>' +
'<div class="rcf">' +
'<input class="rcf-name" placeholder="Jmeno" maxlength="40">' +
'<textarea class="rcf-text" placeholder="Komentar k tomuto radku..." rows="2"></textarea>' +
'<button class="rcf-send">Odeslat</button>' +
'</div>';
dtr.appendChild(dtd);
tr.after(dtr);
let loaded = false;
let allComments = [];
const getAll = () => allComments;
btn.addEventListener('click', () => {
const opening = dtr.style.display === 'none';
dtr.style.display = opening ? '' : 'none';
btn.classList.toggle('open', opening);
btn.innerHTML = opening ? 'detail ▾' : 'detail ▸';
if (opening) {
// Fill script description (JSON may now be loaded)
const descEl = dtd.querySelector('.detail-desc-script');
const desc = scriptDesc();
if (desc) {
descEl.textContent = desc;
descEl.className = 'detail-desc-script';
} else {
descEl.textContent = 'Popis bude dostupny po spusteni tools/extract-docs.py.';
descEl.className = 'detail-desc-script detail-desc-script--empty';
}
}
if (opening && !loaded && issueNum) {
loaded = true;
const listEl = dtd.querySelector('.rcl');
listEl.innerHTML = '<div style="font-size:.75rem;color:var(--muted)">Nacitam...</div>';
fetch(API + '/repos/' + REPO + '/issues/' + issueNum + '/comments', { headers: HEADS })
.then(r => r.json())
.then(data => {
allComments = Array.isArray(data) ? data : [];
renderRowComments(allComments, slug, listEl);
})
.catch(() => {
listEl.innerHTML = '<div style="font-size:.75rem;color:var(--red)">Chyba nacitani.</div>';
});
}
});
dtd.querySelector('.rcf-send').addEventListener('click', function() {
const nameEl = dtd.querySelector('.rcf-name');
const textEl = dtd.querySelector('.rcf-text');
const text = textEl.value.trim();
if (!text || !issueNum) return;
this.disabled = true;
const name = nameEl.value.trim() || 'anon';
const body = '[row:' + slug + ']\n**' + name + '**\n' + text;
fetch(API + '/repos/' + REPO + '/issues/' + issueNum + '/comments', {
method: 'POST', headers: HEADS, body: JSON.stringify({ body: body })
})
.then(r => r.json())
.then(c => {
allComments.push(c);
renderRowComments(allComments, slug, dtd.querySelector('.rcl'));
textEl.value = ''; nameEl.value = '';
this.disabled = false;
})
.catch(() => { this.disabled = false; });
});
});
});
}
// ---- NEW-REQUEST WIDGET (issue #15) ----
(function() {
const ISSUE = 15;
const listEl = document.getElementById('req-list');
const nameEl = document.getElementById('req-name');
const textEl = document.getElementById('req-text');
const submitEl = document.getElementById('req-submit');
const errorEl = document.getElementById('req-error');
if (!listEl) return;
function renderRequests(data) {
listEl.innerHTML = '';
if (!data.length) {
listEl.innerHTML = '<div style="font-size:.8rem;color:var(--muted);font-style:italic;margin-bottom:.5rem">Zatim zadne pozadavky. Bud prvni.</div>';
return;
}
data.forEach(c => {
const m = c.body.match(/^\*\*(.+?)\*\*\n([\s\S]*)$/);
const name = m ? m[1] : (c.user.login === 'xetup-bot' ? 'anon' : c.user.login);
const text = m ? m[2] : c.body;
const el = document.createElement('div');
el.className = 'req-item';
el.innerHTML =
'<div class="req-item-meta">' + name.replace(/</g,'&lt;') + ' &middot; ' + timeAgo(c.created_at) + '</div>' +
'<div class="req-item-body">' + text.replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</div>';
listEl.appendChild(el);
});
}
fetch(API + '/repos/' + REPO + '/issues/' + ISSUE + '/comments', { headers: HEADS })
.then(r => r.json())
.then(d => renderRequests(Array.isArray(d) ? d : []))
.catch(() => { listEl.innerHTML = '<div style="font-size:.8rem;color:var(--red)">Chyba nacitani.</div>'; });
submitEl.addEventListener('click', () => {
const text = textEl.value.trim();
if (!text) { errorEl.textContent = 'Pozadavek nesmi byt prazdny.'; return; }
errorEl.textContent = '';
submitEl.disabled = true;
submitEl.textContent = 'Odesilam...';
const name = nameEl.value.trim() || 'anon';
const body = '**' + name + '**\n' + text;
fetch(API + '/repos/' + REPO + '/issues/' + ISSUE + '/comments', {
method: 'POST', headers: HEADS, body: JSON.stringify({ body: body })
})
.then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
.then(c => {
textEl.value = ''; nameEl.value = '';
submitEl.disabled = false;
submitEl.textContent = 'Odeslat pozadavek';
// re-fetch to show updated list
return fetch(API + '/repos/' + REPO + '/issues/' + ISSUE + '/comments', { headers: HEADS });
})
.then(r => r.json())
.then(d => renderRequests(Array.isArray(d) ? d : []))
.catch(() => {
errorEl.textContent = 'Chyba odeslani. Zkus znovu.';
submitEl.disabled = false;
submitEl.textContent = 'Odeslat pozadavek';
});
});
})();
enhanceRows();
})();
</script>
</body>
</html>