Files
jbergner 7c69432097
Some checks failed
release-tag / release-image (push) Successful in 2m3s
release-main / release-images (push) Failing after 3m9s
RC-2 0.5.2
2026-08-24 05:46:42 +02:00

110 lines
46 KiB
Go
Raw Permalink 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.
package agent
const agentHTML = `<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="dark light"><title>SessionGuard Agent</title><style>
:root{
color-scheme:dark;
--bg:#07101f;--bg-soft:#0b1527;--surface:#101c30;--surface-2:#15243b;--surface-3:#1a2b45;
--border:#263a58;--border-strong:#365174;--text:#f1f5fb;--muted:#91a3bd;--subtle:#677b99;
--primary:#6d8dff;--primary-strong:#5276ff;--primary-soft:rgba(109,141,255,.14);
--success:#43d17b;--success-soft:rgba(67,209,123,.12);--warning:#f2be55;--warning-soft:rgba(242,190,85,.13);
--danger:#ff6b78;--danger-soft:rgba(255,107,120,.12);--info:#56c7ff;--shadow:0 18px 45px rgba(0,0,0,.24);
--radius:16px;--radius-sm:10px;--sidebar:250px;
font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
}
:root[data-theme="light"]{
color-scheme:light;
--bg:#f3f6fb;--bg-soft:#eaf0f8;--surface:#fff;--surface-2:#f8faff;--surface-3:#eef3fa;
--border:#dce5f0;--border-strong:#c6d4e5;--text:#172235;--muted:#64748b;--subtle:#8795aa;
--primary:#4f6ff2;--primary-strong:#3d5fe8;--primary-soft:rgba(79,111,242,.10);
--success:#169a52;--success-soft:rgba(22,154,82,.09);--warning:#b87807;--warning-soft:rgba(184,120,7,.10);
--danger:#d94758;--danger-soft:rgba(217,71,88,.09);--info:#138fbf;--shadow:0 16px 40px rgba(31,50,81,.10);
}
*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:84px}body{margin:0;background:var(--bg);color:var(--text);min-height:100vh;font-size:14px;line-height:1.45}
button,input,textarea,select{font:inherit}button{border:0}.app-shell{min-height:100vh;display:grid;grid-template-columns:var(--sidebar) minmax(0,1fr)}
.sidebar{position:sticky;top:0;height:100vh;padding:20px 14px;display:flex;flex-direction:column;gap:18px;background:linear-gradient(180deg,var(--surface),var(--bg-soft));border-right:1px solid var(--border);z-index:30}
.brand-block{display:flex;align-items:center;gap:11px;padding:3px 8px 12px}.logo{width:42px;height:42px;border-radius:13px;display:grid;place-items:center;background:linear-gradient(135deg,var(--primary),#8a67ff);box-shadow:0 8px 24px rgba(83,107,255,.28);font-weight:900;color:white;letter-spacing:-.04em}.brand-name{font-size:16px;font-weight:800;letter-spacing:-.01em}.brand-sub{font-size:11px;color:var(--muted);margin-top:2px}.nav-group{display:grid;gap:4px}.nav-label{padding:8px 11px 5px;color:var(--subtle);font-size:10px;text-transform:uppercase;letter-spacing:.12em;font-weight:800}.nav-link{display:flex;align-items:center;gap:10px;padding:9px 11px;border-radius:10px;color:var(--muted);text-decoration:none;font-weight:650;transition:.16s ease}.nav-link:hover,.nav-link.active{background:var(--primary-soft);color:var(--text)}.nav-icon{width:20px;text-align:center;color:var(--primary);font-weight:900}.sidebar-spacer{flex:1}.sidebar-footer{display:grid;gap:8px;border-top:1px solid var(--border);padding-top:14px}.app-main{min-width:0}.topbar{height:68px;position:sticky;top:0;z-index:20;display:flex;align-items:center;justify-content:space-between;gap:18px;padding:0 28px;background:color-mix(in srgb,var(--bg) 84%,transparent);backdrop-filter:blur(18px);border-bottom:1px solid color-mix(in srgb,var(--border) 78%,transparent)}
.topbar-left{display:flex;align-items:center;gap:12px;min-width:0}.menu-toggle{display:none}.topbar-title{font-size:15px;font-weight:780}.topbar-sub{font-size:11px;color:var(--muted)}.topbar-actions{display:flex;align-items:center;gap:8px}.live-pill{display:inline-flex;align-items:center;gap:7px;padding:6px 10px;border:1px solid var(--border);border-radius:999px;color:var(--muted);font-size:11px}.live-dot{width:7px;height:7px;border-radius:50%;background:var(--success);box-shadow:0 0 0 4px var(--success-soft)}
.page{max-width:1740px;margin:0 auto;padding:24px 28px 52px}.page-section{margin-bottom:22px}.section-heading{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin:0 0 12px}.section-heading h1,.section-heading h2{margin:0;font-size:20px;letter-spacing:-.025em}.section-heading h1{font-size:25px}.section-copy{margin-top:4px;color:var(--muted);font-size:12px}.eyebrow{font-size:10px;text-transform:uppercase;letter-spacing:.13em;color:var(--primary);font-weight:850;margin-bottom:5px}
.metrics{display:grid;grid-template-columns:repeat(8,minmax(125px,1fr));gap:10px}.card,.metric-card,.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 1px 0 rgba(255,255,255,.02)}.metric-card,.card{padding:14px 15px;min-height:98px;position:relative;overflow:hidden}.metric-card:after,.card:after{content:"";position:absolute;right:-22px;top:-28px;width:82px;height:82px;border-radius:50%;background:var(--primary-soft)}.metric-head{display:flex;justify-content:space-between;gap:8px;align-items:center}.metric-label,.card .muted:first-child{color:var(--muted);font-size:11px;font-weight:700}.metric-icon{width:28px;height:28px;border-radius:9px;display:grid;place-items:center;background:var(--primary-soft);color:var(--primary);font-size:13px;font-weight:900}.value{font-size:25px;font-weight:850;letter-spacing:-.035em;margin-top:9px;position:relative;z-index:1}.workspace-grid,.split{display:grid;grid-template-columns:minmax(380px,.82fr) minmax(600px,1.45fr);gap:14px}.panel{overflow:hidden;margin-top:0;min-width:0}.panel-head,.panel>h2{min-height:57px;margin:0;padding:13px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:14px;font-weight:800;background:linear-gradient(180deg,color-mix(in srgb,var(--surface-2) 66%,transparent),transparent)}.panel-head h2{font-size:14px;margin:0}.panel-kicker{font-size:10px;color:var(--muted);font-weight:500;margin-top:2px}.panel-body{min-width:0}.content{max-height:400px;overflow:auto}.empty{padding:32px 20px;color:var(--muted);text-align:center}.empty:before{content:"—";display:block;color:var(--border-strong);font-size:24px;margin-bottom:5px}
.table{width:100%;border-collapse:separate;border-spacing:0}.table th,.table td{padding:11px 12px;border-bottom:1px solid var(--border);text-align:left;font-size:12px;vertical-align:middle}.table th{position:sticky;top:0;background:var(--surface-2);color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.055em;font-weight:800;z-index:2}.table tr:last-child td{border-bottom:0}.table tbody tr{transition:background .14s ease}.table tbody tr:hover{background:color-mix(in srgb,var(--primary-soft) 65%,transparent)}.row{cursor:pointer}.row.selected{background:var(--primary-soft)!important}.muted{color:var(--muted)}code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;background:var(--surface-3);border:1px solid var(--border);padding:2px 5px;border-radius:6px;color:var(--text)}
.status-badge,.badge{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border-radius:999px;font-size:10px;font-weight:800;border:1px solid var(--border);white-space:nowrap}.status-online,.badge.good{color:var(--success);background:var(--success-soft);border-color:color-mix(in srgb,var(--success) 30%,var(--border))}.status-offline,.badge.neutral{color:var(--muted);background:var(--surface-3)}.status-warning,.badge.warning{color:var(--warning);background:var(--warning-soft);border-color:color-mix(in srgb,var(--warning) 30%,var(--border))}.status-danger,.badge.bad{color:var(--danger);background:var(--danger-soft);border-color:color-mix(in srgb,var(--danger) 30%,var(--border))}.dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--success);box-shadow:0 0 0 3px var(--success-soft)}.dot.off{background:var(--subtle);box-shadow:none}.good{color:var(--success)}.bad,.event-error{color:var(--danger)}.warning,.event-warning,.event-dry-run{color:var(--warning)}
.form{padding:16px;display:grid;gap:14px}.cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:11px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px}label{display:grid;gap:6px;color:var(--muted);font-size:11px;font-weight:700}input,textarea,select{width:100%;color:var(--text);background:var(--surface-2);border:1px solid var(--border);border-radius:10px;padding:9px 10px;outline:0;transition:border-color .15s,box-shadow .15s,background .15s}input:hover,textarea:hover,select:hover{border-color:var(--border-strong)}input:focus,textarea:focus,select:focus{border-color:var(--primary);box-shadow:0 0 0 3px var(--primary-soft);background:var(--surface)}textarea{min-height:90px;resize:vertical;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}.check{display:flex;align-items:center;gap:9px;min-height:30px}.check input{appearance:none;width:36px;height:20px;min-width:36px;padding:0;border-radius:999px;position:relative;background:var(--surface-3);cursor:pointer}.check input:before{content:"";position:absolute;left:3px;top:3px;width:12px;height:12px;border-radius:50%;background:var(--muted);transition:.16s}.check input:checked{background:var(--primary);border-color:var(--primary)}.check input:checked:before{left:19px;background:white}.check label{display:block;color:var(--text);font-weight:650;font-size:12px}
button{display:inline-flex;align-items:center;justify-content:center;gap:6px;background:var(--primary);color:#fff;border:1px solid transparent;border-radius:10px;padding:8px 11px;font-weight:750;font-size:11px;cursor:pointer;transition:transform .12s,filter .12s,background .12s,border-color .12s}button:hover{filter:brightness(1.08)}button:active{transform:translateY(1px)}button.secondary{background:var(--surface-3);border-color:var(--border);color:var(--text)}button.ghost{background:transparent;border-color:var(--border);color:var(--muted)}button.danger{background:var(--danger-soft);border-color:color-mix(in srgb,var(--danger) 25%,var(--border));color:var(--danger)}button:disabled{opacity:.45;cursor:not-allowed}.actions{display:flex;gap:6px;flex-wrap:wrap}.toolbar{display:flex;justify-content:space-between;align-items:center;gap:10px}.section-title{font-weight:800;padding:10px 0 6px;margin-top:3px;border:0;color:var(--text);font-size:12px}.section{border-top:1px solid var(--border);padding-top:13px;margin-top:4px}.list{display:grid;gap:9px}.item{border:1px solid var(--border);background:var(--surface-2);border-radius:12px;padding:12px;display:grid;gap:10px}.item-head{display:flex;justify-content:space-between;align-items:center;gap:10px}.note{padding:10px 12px;border:1px solid var(--border);border-left:3px solid var(--primary);background:var(--primary-soft);border-radius:9px;color:var(--muted);font-size:11px}.log{max-height:420px;overflow:auto}.toast{position:fixed;right:22px;bottom:22px;max-width:min(420px,calc(100vw - 44px));background:var(--surface);border:1px solid var(--border-strong);box-shadow:var(--shadow);padding:11px 14px;border-radius:11px;display:none;z-index:100;font-size:12px}.theme-toggle{width:100%}.mobile-overlay{display:none}
#agents,#farmData,#resourceData,#sessions,#profiles,#events,#history,#policyHistory,#alerts,#audit,#sessionList,#profileList,#processList,#telemetryList,#eventList{overflow:auto}
.agent-metrics{grid-template-columns:repeat(6,minmax(130px,1fr))}@media(max-width:1350px){.metrics{grid-template-columns:repeat(4,1fr)}.agent-metrics{grid-template-columns:repeat(3,1fr)}.workspace-grid,.split{grid-template-columns:1fr}.cols{grid-template-columns:repeat(2,1fr)}}
@media(max-width:900px){:root{--sidebar:220px}.page{padding:18px}.topbar{padding:0 18px}.metrics{grid-template-columns:repeat(3,1fr)}.cols,.two{grid-template-columns:1fr}}
@media(max-width:720px){.agent-metrics{grid-template-columns:repeat(2,1fr)}.app-shell{display:block}.sidebar{position:fixed;left:0;top:0;transform:translateX(-102%);width:min(290px,86vw);transition:transform .2s ease;box-shadow:var(--shadow)}body.nav-open .sidebar{transform:translateX(0)}body.nav-open .mobile-overlay{display:block;position:fixed;inset:0;background:rgba(0,0,0,.48);z-index:25}.menu-toggle{display:inline-flex}.topbar{height:60px}.live-pill{display:none}.page{padding:14px}.metrics{grid-template-columns:repeat(2,1fr)}.section-heading{align-items:flex-start;flex-direction:column}.table th,.table td{white-space:nowrap}.form{padding:13px}}
@media(max-width:430px){.metrics{grid-template-columns:1fr 1fr}.metric-card,.card{min-height:88px;padding:12px}.value{font-size:22px}.topbar-actions .theme-top{display:none}}
/* SessionGuard UI v0.5.2 — modal-first responsive console */
:root{--radius-lg:22px;--radius-xl:28px;--focus:0 0 0 4px var(--primary-soft)}
body{background:radial-gradient(circle at 88% -10%,color-mix(in srgb,var(--primary) 13%,transparent),transparent 34rem),var(--bg)}
.page{max-width:1680px}.topbar{box-shadow:0 1px 0 rgba(0,0,0,.03)}
.sidebar{background:linear-gradient(180deg,color-mix(in srgb,var(--surface) 92%,var(--primary) 8%),var(--bg-soft))}
.logo{border-radius:15px}.nav-link{border:1px solid transparent}.nav-link.active{border-color:color-mix(in srgb,var(--primary) 22%,transparent)}
.section-heading{align-items:center}.section-heading .section-actions{display:flex;gap:8px;flex-wrap:wrap}
.metrics{grid-template-columns:repeat(auto-fit,minmax(145px,1fr));gap:12px}.metric-card{min-height:106px;border-radius:18px}.metric-card:hover{border-color:var(--border-strong);transform:translateY(-1px);transition:.18s}
.panel{border-radius:18px;box-shadow:0 8px 34px rgba(0,0,0,.05)}.panel-head{padding:15px 18px}.panel-toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.table-wrap{width:100%;overflow:auto}.table td{max-width:420px}.table tbody tr{cursor:default}.table tbody tr[data-id]{cursor:pointer}
.actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.actions.end{justify-content:flex-end}.toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
button{min-height:36px;padding:8px 13px;border-radius:11px}button.icon-btn{width:38px;min-width:38px;padding:0;font-size:18px}button.primary-soft{background:var(--primary-soft);border-color:color-mix(in srgb,var(--primary) 28%,var(--border));color:var(--primary)}
.section-title{font-size:12px;text-transform:uppercase;letter-spacing:.09em;color:var(--muted);font-weight:850;margin:18px 0 9px}.note{border:1px solid var(--border);background:var(--surface-2);padding:11px 13px;border-radius:12px;color:var(--muted);font-size:11px}
.list{display:grid;gap:10px}.item{border:1px solid var(--border);background:var(--surface-2);border-radius:14px;padding:13px}.item-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}
/* modal */
dialog.sg-modal{width:min(1120px,calc(100vw - 32px));max-height:calc(100dvh - 32px);padding:0;border:1px solid var(--border);border-radius:var(--radius-xl);background:var(--surface);color:var(--text);box-shadow:0 32px 100px rgba(0,0,0,.42);overflow:hidden}
dialog.sg-modal::backdrop{background:rgba(4,9,18,.68);backdrop-filter:blur(6px)}dialog.sg-modal.wide{width:min(1480px,calc(100vw - 24px))}dialog.sg-modal.compact{width:min(620px,calc(100vw - 32px))}
.modal-shell{display:grid;grid-template-rows:auto minmax(0,1fr);max-height:calc(100dvh - 32px)}.modal-head{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:17px 20px;border-bottom:1px solid var(--border);background:linear-gradient(180deg,var(--surface-2),var(--surface))}.modal-title{font-size:18px;font-weight:850;letter-spacing:-.02em}.modal-sub{font-size:11px;color:var(--muted);margin-top:2px}.modal-body{overflow:auto;min-height:0}.modal-body.pad{padding:18px}.modal-close{background:transparent;color:var(--muted);border:1px solid var(--border)}
body.modal-open{overflow:hidden}
/* detail tabs */
.detail-tabs{position:sticky;top:0;z-index:4;display:flex;gap:5px;overflow:auto;padding:10px 14px;border-bottom:1px solid var(--border);background:color-mix(in srgb,var(--surface) 94%,transparent);backdrop-filter:blur(12px)}.detail-tab{white-space:nowrap;background:transparent;color:var(--muted);border:1px solid transparent}.detail-tab.active{background:var(--primary-soft);border-color:color-mix(in srgb,var(--primary) 25%,var(--border));color:var(--text)}.detail-pane{display:none;padding:16px}.detail-pane.active{display:block}.detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.detail-block{border:1px solid var(--border);border-radius:16px;background:var(--surface-2);overflow:hidden}.detail-block>.section-title{margin:0;padding:13px 15px;border-bottom:1px solid var(--border);background:var(--surface)}.detail-block>.content{max-height:460px}.detail-block.full{grid-column:1/-1}
/* cards / compact lists */
.entity-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;padding:15px}.entity-card{position:relative;border:1px solid var(--border);border-radius:16px;background:var(--surface-2);padding:14px;display:grid;gap:11px}.entity-card:hover{border-color:var(--border-strong)}.entity-card-head{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.entity-title{font-weight:850;font-size:14px}.entity-meta{display:flex;gap:6px;flex-wrap:wrap}.entity-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:7px}.mini-stat{padding:9px;border-radius:11px;background:var(--surface);border:1px solid var(--border)}.mini-stat b{display:block;font-size:16px}.mini-stat span{font-size:10px;color:var(--muted)}
.hero-status{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(260px,.6fr);gap:14px}.hero-card{padding:20px;border-radius:20px;background:linear-gradient(135deg,var(--surface),color-mix(in srgb,var(--surface-2) 72%,var(--primary) 28%));border:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:18px}.hero-card h2{margin:0;font-size:22px}.hero-meta{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.form-section{border:1px solid var(--border);border-radius:15px;background:var(--surface-2);padding:14px}.form-section>h3{font-size:12px;margin:0 0 12px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
.form{padding:18px}.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:13px}.form-grid.three{grid-template-columns:repeat(3,minmax(0,1fr))}
.dirty-pill{display:inline-flex;align-items:center;gap:6px;color:var(--warning);background:var(--warning-soft);padding:5px 8px;border-radius:999px;font-size:10px;font-weight:800}
.mobile-only{display:none}
@media(max-width:1050px){.detail-grid{grid-template-columns:1fr}.hero-status{grid-template-columns:1fr}.form-grid.three{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:720px){dialog.sg-modal,dialog.sg-modal.wide,dialog.sg-modal.compact{width:100vw;max-width:none;height:100dvh;max-height:100dvh;border-radius:0;border:0}.modal-shell{max-height:100dvh;height:100dvh}.modal-head{padding:13px 14px}.modal-title{font-size:16px}.modal-body.pad,.detail-pane{padding:12px}.detail-tabs{padding:8px}.form{padding:12px}.form-grid,.form-grid.three,.entity-stats{grid-template-columns:1fr}.entity-grid{grid-template-columns:1fr;padding:10px}.mobile-only{display:inline-flex}.table th,.table td{white-space:nowrap}.section-heading .section-actions{width:100%}.section-heading .section-actions button{flex:1}}
</style></head><body>
<div class="mobile-overlay" id="mobileOverlay"></div><div class="app-shell"><aside class="sidebar" id="sidebar"><div class="brand-block"><div class="logo">SG</div><div><div class="brand-name">SessionGuard</div><div class="brand-sub">Local Agent · v0.5.2</div></div></div><nav class="nav-group"><div class="nav-label">Server</div><a class="nav-link active" href="#overview"><span class="nav-icon">⌂</span>Übersicht</a><a class="nav-link" href="#sessions-section"><span class="nav-icon">▶</span>Sitzungen</a><a class="nav-link" href="#operations-section"><span class="nav-icon">◇</span>Betriebsdaten</a><a class="nav-link" href="#policy-section"><span class="nav-icon">⚙</span>Lokale Policy</a></nav><div class="sidebar-spacer"></div><div class="sidebar-footer"><button class="secondary theme-toggle" id="themeToggle" type="button">◐ Theme wechseln</button><form action="/logout" method="post"><button class="ghost" style="width:100%">Abmelden</button></form></div></aside>
<div class="app-main"><header class="topbar"><div class="topbar-left"><button class="secondary menu-toggle" id="menuToggle" type="button">☰</button><div><div class="topbar-title">Lokaler Agent</div><div class="topbar-sub" id="host">Lokaler Terminalserver</div></div></div><div class="topbar-actions"><span class="live-pill"><span class="live-dot"></span>Live · 5s</span><button class="secondary theme-top" id="themeTop" type="button">◐</button></div></header>
<main class="page"><section class="page-section" id="overview"><div class="section-heading"><div><div class="eyebrow">Local Control</div><h1>Terminalserver-Status</h1><div class="section-copy">Sitzungen, Profil-Pipeline und Master-Verbindung lokal überwachen.</div></div></div><div class="metrics agent-metrics"><div class="metric-card"><div class="metric-head"><span class="metric-label">Aktiv</span><span class="metric-icon">▶</span></div><div class="value" id="active"></div></div><div class="metric-card"><div class="metric-head"><span class="metric-label">Getrennt</span><span class="metric-icon">Ⅱ</span></div><div class="value" id="disc"></div></div><div class="metric-card"><div class="metric-head"><span class="metric-label">Sitzungen</span><span class="metric-icon">◎</span></div><div class="value" id="total"></div></div><div class="metric-card"><div class="metric-head"><span class="metric-label">Profil-Jobs</span><span class="metric-icon">↕</span></div><div class="value" id="profileJobs"></div></div><div class="metric-card"><div class="metric-head"><span class="metric-label">Cleanup</span><span class="metric-icon">⌫</span></div><div class="value" id="pending"></div></div><div class="metric-card"><div class="metric-head"><span class="metric-label">RemoteApps</span><span class="metric-icon">◇</span></div><div class="value" id="remoteAppCount"></div></div><div class="metric-card"><div class="metric-head"><span class="metric-label">Master</span><span class="metric-icon">⇄</span></div><div class="value" style="font-size:14px;margin-top:14px" id="master"></div></div></div></section>
<section class="page-section" id="sessions-section"><div class="section-heading"><div><div class="eyebrow">RDS</div><h2>Sitzungen</h2><div class="section-copy">Aktive und getrennte Benutzer sowie administrative Aktionen.</div></div></div><section class="panel"><div id="sessions"></div></section></section>
<section class="page-section" id="operations-section"><div class="section-heading"><div><div class="eyebrow">Operations</div><h2>Betriebsdaten</h2><div class="section-copy">RemoteApps, Profil-Pipeline und lokale Ereignisse kompakt in einer Arbeitsfläche.</div></div></div><section class="panel"><div class="detail-tabs" style="position:static"><button class="detail-tab active" type="button" data-agent-tab="remoteapps">RemoteApps</button><button class="detail-tab" type="button" data-agent-tab="profiles">Profil-Pipeline</button><button class="detail-tab" type="button" data-agent-tab="events">Aktivitätslog</button></div><div class="detail-pane active" data-agent-pane="remoteapps"><div id="remoteapps"></div></div><div class="detail-pane" data-agent-pane="profiles"><div id="profiles"></div></div><div class="detail-pane" data-agent-pane="events"><div id="events"></div></div></section></section>
<section class="page-section" id="policy-section"><div class="section-heading"><div><div class="eyebrow">Configuration</div><h2>Lokale Policy</h2><div class="section-copy">Fallback-Konfiguration für Profile, Sessions, Cleanup und Templates.</div></div><div class="section-actions"><button type="button" class="primary-soft" id="openPolicyModal">⚙ Policy bearbeiten</button></div></div><section class="panel"><div class="form"><div class="note"><strong>Fallback-Verhalten:</strong> Die lokale Policy bleibt als Notfallkonfiguration verfügbar. Eine aktive Master-Soll-Policy hat im zentral verwalteten Betrieb Vorrang.</div><div id="policySummary" class="entity-grid" style="padding:0"></div></div></section></section>
</main></div></div><dialog class="sg-modal wide" id="policyModal"><div class="modal-shell"><div class="modal-head"><div><div class="modal-title">Lokale Fallback-Policy</div><div class="modal-sub">Änderungen werden erst nach „Lokal speichern“ übernommen.</div></div><div class="actions"><span id="policyDirtyPill"></span><button type="button" class="modal-close icon-btn" id="closePolicyModal">×</button></div></div><div class="modal-body"><div id="policy"></div></div></div></dialog><script src="/app.js?v=0.5.2"></script></body></html>`
const agentJS = `
let policyTemplates=[],profileFolders=[],lastSnapshot=null,policyDirty=false,policyLoaded=false;
const $=id=>document.getElementById(id);const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
function openModal(id){let d=$(id);if(!d)return;document.body.classList.add('modal-open');if(!d.open)d.showModal()}function closeModal(id){let d=$(id);if(!d)return;if(d.open)d.close();document.body.classList.remove('modal-open')}function renderPolicySummary(p){let root=$('policySummary');if(!root)return;let pr=p.profiles||{},sp=p.sessions||{},c=p.cleanup||{};root.innerHTML='<article class="entity-card"><div class="entity-title">Profil-Sync</div><div class="entity-meta"><span class="badge '+(pr.enabled?'good':'neutral')+'">'+(pr.enabled?'Aktiv':'Aus')+'</span><span class="badge neutral">'+((pr.folders||[]).length)+' Ordner</span></div><div class="muted">'+esc(pr.store_root||'Kein Store konfiguriert')+'</div></article><article class="entity-card"><div class="entity-title">Sitzungsrichtlinie</div><div class="entity-meta"><span class="badge '+(sp.control_enabled?'good':'neutral')+'">Steuerung '+(sp.control_enabled?'an':'aus')+'</span><span class="badge '+(sp.disconnected_logoff_enabled?'warning':'neutral')+'">Auto-Logoff '+(sp.disconnected_logoff_enabled?'an':'aus')+'</span></div><div class="muted">Timeout: '+Math.round((sp.disconnected_timeout_seconds||0)/60)+' min</div></article><article class="entity-card"><div class="entity-title">Cleanup</div><div class="entity-meta"><span class="badge '+(c.enabled?'good':'neutral')+'">'+(c.enabled?'Aktiv':'Aus')+'</span><span class="badge '+(c.dry_run?'warning':'neutral')+'">'+(c.dry_run?'Dry-Run':'Live')+'</span></div><div class="muted">Grace: '+(c.grace_seconds||0)+' s</div></article>'}function initShell(){
let saved='';try{saved=localStorage.getItem('sessionguard-theme')||''}catch(e){}
let theme=saved||((window.matchMedia&&window.matchMedia('(prefers-color-scheme: light)').matches)?'light':'dark');
function applyTheme(t){document.documentElement.dataset.theme=t;try{localStorage.setItem('sessionguard-theme',t)}catch(e){};let label=t==='light'?'☾ Dunkles Theme':'☀ Helles Theme';let b=$('themeToggle');if(b)b.textContent=label;let bt=$('themeTop');if(bt)bt.textContent=t==='light'?'☾':'☀'}
function toggleTheme(){theme=document.documentElement.dataset.theme==='light'?'dark':'light';applyTheme(theme)}
applyTheme(theme);let b=$('themeToggle'),bt=$('themeTop');if(b)b.onclick=toggleTheme;if(bt)bt.onclick=toggleTheme;
let mt=$('menuToggle'),ov=$('mobileOverlay');if(mt)mt.onclick=()=>document.body.classList.toggle('nav-open');if(ov)ov.onclick=()=>document.body.classList.remove('nav-open');
document.querySelectorAll('.nav-link').forEach(a=>a.addEventListener('click',()=>{document.body.classList.remove('nav-open');document.querySelectorAll('.nav-link').forEach(x=>x.classList.remove('active'));a.classList.add('active')}));
document.querySelectorAll('[data-agent-tab]').forEach(b=>b.onclick=()=>{let n=b.dataset.agentTab;document.querySelectorAll('[data-agent-tab]').forEach(x=>x.classList.toggle('active',x===b));document.querySelectorAll('[data-agent-pane]').forEach(x=>x.classList.toggle('active',x.dataset.agentPane===n))});let op=$('openPolicyModal'),cp=$('closePolicyModal'),pm=$('policyModal');if(op)op.onclick=()=>openModal('policyModal');if(cp)cp.onclick=()=>{if(policyDirty&&!confirm('Ungespeicherte Änderungen verwerfen und schließen?'))return;closeModal('policyModal')};if(pm)pm.addEventListener('cancel',e=>{if(policyDirty&&!confirm('Ungespeicherte Änderungen verwerfen und schließen?')){e.preventDefault();return}document.body.classList.remove('modal-open')});if('IntersectionObserver' in window){let links=[...document.querySelectorAll('.nav-link[href^="#"]')],map=new Map(links.map(a=>[a.getAttribute('href').slice(1),a]));let io=new IntersectionObserver(entries=>{let best=entries.filter(e=>e.isIntersecting).sort((a,b)=>b.intersectionRatio-a.intersectionRatio)[0];if(!best)return;let a=map.get(best.target.id);if(a){links.forEach(x=>x.classList.remove('active'));a.classList.add('active')}},{rootMargin:'-18% 0px -65% 0px',threshold:[0,.15,.4]});map.forEach((_,id)=>{let el=document.getElementById(id);if(el)io.observe(el)})}
}
async function api(u,o){let r=await fetch(u,o);if(r.status===401){location='/login';return}let j=await r.json().catch(()=>({}));if(!r.ok)throw new Error(j.error||r.statusText);return j}function lines(id){return $(id).value.split('\n').map(x=>x.trim()).filter(Boolean)}function when(v){if(!v)return '';let d=new Date(v);return Number.isNaN(d.getTime())||d.getFullYear()<2000?'':d.toLocaleString('de-DE')}function dur(v){if(!v)return '';let s=Math.max(0,Math.floor((Date.now()-new Date(v).getTime())/1000));return Math.floor(s/3600)+'h '+Math.floor(s%3600/60)+'m'}
function renderEvents(events){let rows=(events||[]).slice().reverse().slice(0,200);$('events').innerHTML=rows.length?'<div class="log"><table class="table"><thead><tr><th>Zeit</th><th>Typ</th><th>Benutzer</th><th>Meldung</th></tr></thead><tbody>'+rows.map(x=>'<tr class="event-'+esc(x.level||'info')+'"><td>'+esc(when(x.time))+'</td><td>'+esc(x.level||'info')+'</td><td>'+esc(x.user||'')+'</td><td>'+esc(x.message||'')+'</td></tr>').join('')+'</tbody></table></div>':'<div class="empty">Noch keine Ereignisse.</div>'}
function renderRemoteApps(s){let rows=s.remote_apps||[];$('remoteapps').innerHTML=rows.length?'<table class="table"><thead><tr><th>Status</th><th>Alias</th><th>Pfad</th><th>Verwaltung</th><th>Fehler</th></tr></thead><tbody>'+rows.map(x=>'<tr><td><span class="badge '+(x.published&&x.path_exists&&x.in_sync?'good':'bad')+'">'+(x.published&&x.path_exists&&x.in_sync?'Bereit':'Nicht bereit')+'</span></td><td><strong>||'+esc(x.alias)+'</strong><br><span class="muted">'+esc(x.display_name||x.resource_id||'')+'</span></td><td>'+esc(x.path||'')+'</td><td>'+(x.managed?'<span class="badge good">Master</span>'+(x.owned?' <span class="muted">(angelegt)</span>':' <span class="muted">(übernommen)</span>'):'<span class="badge neutral">lokal entdeckt</span>')+'</td><td>'+esc(x.error||'')+'</td></tr>').join('')+'</tbody></table>':'<div class="empty">Keine RemoteApps gefunden. Bei Agent-gesteuerten Apps erscheint der Status nach dem nächsten Master-Heartbeat.</div>'}
function renderProfiles(s){let jobs=s.profile_jobs||[],status=Object.values(s.profile_status||{});let html='';if(jobs.length)html+='<table class="table"><thead><tr><th>Job</th><th>Benutzer</th><th>Fällig</th><th>Versuche</th><th>Fehler</th></tr></thead><tbody>'+jobs.map(j=>'<tr><td>'+esc(j.operation)+'</td><td>'+esc(j.user)+'</td><td>'+esc(when(j.due_at))+'</td><td>'+j.attempts+'</td><td>'+esc(j.last_error||'')+'</td></tr>').join('')+'</tbody></table>';if(status.length)html+='<table class="table"><thead><tr><th>Benutzer</th><th>Letztes Backup</th><th>Letzter Restore</th><th>Status</th></tr></thead><tbody>'+status.map(x=>'<tr><td>'+esc(x.user||x.sid)+'</td><td>'+esc(when(x.last_backup_at))+'</td><td>'+esc(when(x.last_restore_at))+'</td><td>'+esc(x.last_backup_error||x.last_restore_error||'OK')+'</td></tr>').join('')+'</tbody></table>';$('profiles').innerHTML=html||'<div class="empty">Keine Profil-Jobs oder -Historie.</div>'}
async function sessionAction(id,action){let body={action};if(action==='message'){let m=prompt('Nachricht an die Sitzung:');if(!m)return;body.message=m;body.title='SessionGuard'}if(action==='logoff'&&!confirm('Sitzung '+id+' wirklich abmelden? Die Profil-Pipeline startet nach dem Sitzungsende.'))return;try{await api('/api/v1/sessions/'+id+'/action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});setTimeout(refresh,400)}catch(e){alert(e.message)}}
function renderSessions(s){let p=s.policy||{},control=p.sessions&&p.sessions.control_enabled,ss=s.sessions||[];$('sessions').innerHTML='<table class="table"><thead><tr><th>ID</th><th>Benutzer</th><th>Status</th><th>Getrennt seit</th><th>Client</th><th>Aktionen</th></tr></thead><tbody>'+ss.map(x=>'<tr><td>'+x.id+'</td><td>'+esc((x.domain?x.domain+'\\':'')+x.user)+'</td><td>'+esc(x.state)+'</td><td>'+esc(x.disconnected_since?when(x.disconnected_since)+' ('+dur(x.disconnected_since)+')':'')+'</td><td>'+esc(x.client_name||'')+'</td><td>'+(control&&x.user?'<div class="actions"><button class="secondary" data-session="'+x.id+'" data-session-action="message">Nachricht</button><button class="secondary" data-session="'+x.id+'" data-session-action="disconnect">Trennen</button><button class="danger" data-session="'+x.id+'" data-session-action="logoff">Abmelden</button></div>':'')+'</td></tr>').join('')+'</tbody></table>'}
async function refresh(){try{let d=await api('/api/v1/status'),s=d.snapshot||{};lastSnapshot=s;let ss=s.sessions||[];$('host').textContent=(s.server&&s.server.hostname)||'Lokaler Terminalserver';$('active').textContent=ss.filter(x=>x.state==='Active').length;$('disc').textContent=ss.filter(x=>x.state==='Disconnected').length;$('total').textContent=ss.filter(x=>x.user).length;$('pending').textContent=(s.pending_cleanup||[]).length;$('profileJobs').textContent=(s.profile_jobs||[]).length;$('remoteAppCount').textContent=(s.remote_apps||[]).filter(x=>x.published&&x.path_exists&&x.in_sync).length+'/'+(s.remote_apps||[]).length;$('master').innerHTML=d.master_error?'<span class="status-badge status-danger">Offline</span>':'<span class="status-badge status-online">Verbunden</span>';renderSessions(s);renderRemoteApps(s);renderProfiles(s);renderEvents(s.events||[])}catch(e){$('master').innerHTML='<span class="bad">'+esc(e.message)+'</span>'}}
function templateDefault(){return{id:'neues-template',kind:'file',target:'Desktop\\Beispiel.txt',source:'',content:'',content_base64:'',url:'',shortcut:{target:'',arguments:'',working_directory:'',icon_location:'',description:''},overwrite:true}}function templateSpecific(t){let k=(t.kind||'file').toLowerCase();if(k==='directory')return'<div class="muted">Keine weiteren Angaben.</div>';if(k==='url')return'<label>URL<input data-field="url" value="'+esc(t.url||'')+'"></label>';if(k==='shortcut'){let s=t.shortcut||{};return'<div class="two"><label>Zielprogramm<input data-field="shortcut.target" value="'+esc(s.target||'')+'"></label><label>Argumente<input data-field="shortcut.arguments" value="'+esc(s.arguments||'')+'"></label><label>Arbeitsverzeichnis<input data-field="shortcut.working_directory" value="'+esc(s.working_directory||'')+'"></label><label>Icon<input data-field="shortcut.icon_location" value="'+esc(s.icon_location||'')+'"></label></div><label>Beschreibung<input data-field="shortcut.description" value="'+esc(s.description||'')+'"></label>'}return'<label>Quelldatei / UNC-Pfad<input data-field="source" value="'+esc(t.source||'')+'"></label><div class="two"><label>Inline-Inhalt<textarea data-field="content">'+esc(t.content||'')+'</textarea></label><label>Inline Base64<textarea data-field="content_base64">'+esc(t.content_base64||'')+'</textarea></label></div>'}
function renderTemplates(){let h=$('templateList');if(!h)return;h.innerHTML=policyTemplates.length?policyTemplates.map((t,i)=>'<div class="item template-card"><div class="item-head"><strong>'+esc(t.id||('Template '+(i+1)))+'</strong><button type="button" class="danger" data-action="remove-template" data-index="'+i+'">Löschen</button></div><div class="cols"><label>ID<input data-field="id" value="'+esc(t.id||'')+'"></label><label>Typ<select data-field="kind" data-action="template-kind"><option value="file" '+((t.kind||'file')==='file'?'selected':'')+'>Datei</option><option value="directory" '+(t.kind==='directory'?'selected':'')+'>Ordner</option><option value="url" '+(t.kind==='url'?'selected':'')+'>URL</option><option value="shortcut" '+(t.kind==='shortcut'?'selected':'')+'>.lnk</option></select></label><label>Ziel relativ zum Profil<input data-field="target" value="'+esc(t.target||'')+'"></label></div><div class="check"><input data-field="overwrite" type="checkbox" '+(t.overwrite?'checked':'')+'><label>Aktualisieren/überschreiben</label></div>'+templateSpecific(t)+'</div>').join(''):'<div class="empty">Keine Templates konfiguriert.</div>'}
function collectTemplates(){return[...document.querySelectorAll('.template-card')].map(c=>{let g=n=>{let e=c.querySelector('[data-field="'+n+'"]');return e?e.value:''},k=g('kind')||'file',t={id:g('id').trim(),kind:k,target:g('target').trim(),overwrite:!!c.querySelector('[data-field="overwrite"]:checked')};if(k==='file'){t.source=g('source').trim();t.content=g('content');t.content_base64=g('content_base64').trim()}else if(k==='url')t.url=g('url').trim();else if(k==='shortcut')t.shortcut={target:g('shortcut.target').trim(),arguments:g('shortcut.arguments'),working_directory:g('shortcut.working_directory').trim(),icon_location:g('shortcut.icon_location').trim(),description:g('shortcut.description')};return t})}
function renderProfileFolders(){let h=$('profileFolderList');if(!h)return;h.innerHTML=profileFolders.length?profileFolders.map((f,i)=>'<div class="item profile-folder"><div class="item-head"><strong>'+esc(f.path||('Ordner '+(i+1)))+'</strong><button type="button" class="danger" data-action="remove-profile-folder" data-index="'+i+'">Löschen</button></div><div class="two"><label>Pfad relativ zum Profil<input data-field="path" value="'+esc(f.path||'')+'" placeholder="AppData\\Roaming\\Hersteller"></label><label>Ausschlüsse (eine Zeile; * oder Pfad/**)<textarea data-field="exclude">'+esc((f.exclude_globs||[]).join('\n'))+'</textarea></label></div></div>').join(''):'<div class="empty">Keine Profilordner konfiguriert.</div>'}
function collectProfileFolders(){return[...document.querySelectorAll('.profile-folder')].map(c=>({path:c.querySelector('[data-field="path"]').value.trim(),exclude_globs:c.querySelector('[data-field="exclude"]').value.split('\n').map(x=>x.trim()).filter(Boolean)})).filter(x=>x.path)}
function policyHTML(p){let c=p.cleanup||{},pr=p.profiles||{},sp=p.sessions||{};return'<div class="form"><div class="section-title">Profil-Sicherung & Wiederherstellung</div><div class="check"><input id="profEnabled" type="checkbox" '+(pr.enabled?'checked':'')+'><label>Profil-Synchronisation aktiv</label></div><div class="two"><label>Profil-Store (UNC oder lokaler Pfad)<input id="storeRoot" value="'+esc(pr.store_root||'')+'" placeholder="\\\\fileserver\\SessionGuardProfiles"></label><label>Backup-Verzögerung (s)<input id="backupDelay" type="number" min="1" value="'+(pr.backup_delay_seconds||5)+'"></label><label>Retry (s)<input id="profRetry" type="number" min="1" value="'+(pr.retry_seconds||60)+'"></label><label>Restore-Fenster (s)<input id="restoreWindow" type="number" min="10" value="'+(pr.restore_window_seconds||120)+'"></label><label>Historische Versionen<input id="keepVersions" type="number" min="0" max="50" value="'+(pr.keep_versions??2)+'"></label></div><div class="check"><input id="backupOnLogoff" type="checkbox" '+(pr.backup_on_logoff?'checked':'')+'><label>Nach Logoff sichern</label></div><div class="check"><input id="restoreOnLogon" type="checkbox" '+(pr.restore_on_logon?'checked':'')+'><label>Bei Logon wiederherstellen</label></div><label>Von Profil-Sync ausgeschlossene Benutzer<textarea id="profUsers">'+esc((pr.exclude_users||[]).join('\n'))+'</textarea></label><label>Von Profil-Sync ausgeschlossene SIDs<textarea id="profSids">'+esc((pr.exclude_sids||[]).join('\n'))+'</textarea></label><div class="toolbar"><strong>Zu sichernde Profilordner</strong><button type="button" class="secondary" data-action="add-profile-folder">Ordner hinzufügen</button></div><div id="profileFolderList" class="list"></div><div class="note">Gesichert werden nur konfigurierte Teilbäume. Reparse-Points/Symlinks werden nicht verfolgt. Cleanup beginnt nach einem Logoff erst nach erfolgreichem Backup.</div><div class="section"><div class="section-title">Sitzungsrichtlinie</div><div class="check"><input id="controlEnabled" type="checkbox" '+(sp.control_enabled?'checked':'')+'><label>Administrative Sitzungssteuerung erlauben</label></div><div class="check"><input id="autoLogoff" type="checkbox" '+(sp.disconnected_logoff_enabled?'checked':'')+'><label>Getrennte Sitzungen automatisch abmelden</label></div><label>Timeout für getrennte Sitzung (s; min. 60)<input id="discTimeout" type="number" min="60" value="'+(sp.disconnected_timeout_seconds||3600)+'"></label><label>Vom Auto-Logoff ausgeschlossene Benutzer<textarea id="sessUsers">'+esc((sp.exclude_users||[]).join('\n'))+'</textarea></label><label>Vom Auto-Logoff ausgeschlossene SIDs<textarea id="sessSids">'+esc((sp.exclude_sids||[]).join('\n'))+'</textarea></label></div><div class="section"><div class="section-title">Profil-Cleanup</div><div class="cols"><label>Grace (s)<input id="grace" type="number" min="1" value="'+(c.grace_seconds||600)+'"></label><label>Polling (s)<input id="poll" type="number" min="2" value="'+(c.poll_seconds||10)+'"></label><label>Retry (s)<input id="retry" type="number" min="1" value="'+(c.retry_seconds||60)+'"></label></div><div class="check"><input id="enabled" type="checkbox" '+(c.enabled?'checked':'')+'><label>Cleanup aktiv</label></div><div class="check"><input id="dry" type="checkbox" '+(c.dry_run?'checked':'')+'><label>Cleanup Dry-Run</label></div><label>Cleanup: Benutzer ausschließen<textarea id="users">'+esc((c.exclude_users||[]).join('\n'))+'</textarea></label><label>Cleanup: SIDs ausschließen<textarea id="sids">'+esc((c.exclude_sids||[]).join('\n'))+'</textarea></label><label>Erlaubte Profil-Roots<textarea id="roots">'+esc((c.allowed_profile_roots||[]).join('\n'))+'</textarea></label></div><div class="section"><div class="toolbar"><strong>Templates</strong><button type="button" class="secondary" data-action="add-template">Template hinzufügen</button></div><div id="templateList" class="list"></div></div><div class="actions"><button type="button" data-action="save-policy">Lokal speichern</button><button type="button" class="secondary" data-action="reload-policy">Editor neu laden</button><span id="policyDirtyState" class="warning"></span></div></div>'}
function updatePolicyDirty(){let e=$('policyDirtyState');if(e)e.textContent=policyDirty?'Ungespeicherte Änderungen':'';let p=$('policyDirtyPill');if(p)p.innerHTML=policyDirty?'<span class="dirty-pill">● Ungespeichert</span>':''}function markPolicyDirty(){policyDirty=true;updatePolicyDirty()}async function reloadPolicy(){if(policyDirty&&!confirm('Ungespeicherte Änderungen verwerfen und Policy neu laden?'))return;policyDirty=false;await loadPolicy(true)}
async function loadPolicy(force){if(policyDirty&&!force)return;try{let p=await api('/api/v1/policy');policyTemplates=JSON.parse(JSON.stringify(p.templates||[]));profileFolders=JSON.parse(JSON.stringify((p.profiles&&p.profiles.folders)||[]));$('policy').innerHTML=policyHTML(p);renderTemplates();renderProfileFolders();renderPolicySummary(p);policyDirty=false;policyLoaded=true;updatePolicyDirty()}catch(e){if(!policyLoaded)$('policy').textContent=e.message}}
async function savePolicy(){try{policyTemplates=collectTemplates();profileFolders=collectProfileFolders();let p={profiles:{enabled:$('profEnabled').checked,store_root:$('storeRoot').value.trim(),backup_on_logoff:$('backupOnLogoff').checked,restore_on_logon:$('restoreOnLogon').checked,backup_delay_seconds:+$('backupDelay').value,retry_seconds:+$('profRetry').value,restore_window_seconds:+$('restoreWindow').value,keep_versions:+$('keepVersions').value,exclude_users:lines('profUsers'),exclude_sids:lines('profSids'),folders:profileFolders},sessions:{control_enabled:$('controlEnabled').checked,disconnected_logoff_enabled:$('autoLogoff').checked,disconnected_timeout_seconds:+$('discTimeout').value,exclude_users:lines('sessUsers'),exclude_sids:lines('sessSids')},cleanup:{enabled:$('enabled').checked,grace_seconds:+$('grace').value,poll_seconds:+$('poll').value,retry_seconds:+$('retry').value,dry_run:$('dry').checked,exclude_users:lines('users'),exclude_sids:lines('sids'),allowed_profile_roots:lines('roots')},templates:policyTemplates};await api('/api/v1/policy',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(p)});policyDirty=false;await loadPolicy(true);closeModal('policyModal');refresh()}catch(e){alert(e.message)}}
$('sessions').addEventListener('click',e=>{let b=e.target.closest('button[data-session-action]');if(b)sessionAction(+b.dataset.session,b.dataset.sessionAction)});$('policy').addEventListener('click',e=>{let b=e.target.closest('button[data-action]');if(!b)return;let a=b.dataset.action;if(a==='save-policy')savePolicy();if(a==='reload-policy')reloadPolicy();if(a==='add-template'){policyTemplates=collectTemplates();policyTemplates.push(templateDefault());renderTemplates();markPolicyDirty()}if(a==='remove-template'){policyTemplates=collectTemplates();policyTemplates.splice(+b.dataset.index,1);renderTemplates();markPolicyDirty()}if(a==='add-profile-folder'){profileFolders=collectProfileFolders();profileFolders.push({path:'AppData\\Roaming\\Hersteller',exclude_globs:['Cache/**']});renderProfileFolders();markPolicyDirty()}if(a==='remove-profile-folder'){profileFolders=collectProfileFolders();profileFolders.splice(+b.dataset.index,1);renderProfileFolders();markPolicyDirty()}});$('policy').addEventListener('input',e=>{if(e.target.matches('input,textarea,select'))markPolicyDirty()});$('policy').addEventListener('change',e=>{if(e.target.matches('select[data-action="template-kind"]')){policyTemplates=collectTemplates();renderTemplates();markPolicyDirty()}});initShell();refresh();loadPolicy(true);setInterval(refresh,5000);`