110 lines
46 KiB
Go
110 lines
46 KiB
Go
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=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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);`
|