package agent const agentHTML = `SessionGuard Agent
Lokaler Agent
Lokaler Terminalserver
Live · 5s
Local Control

Terminalserver-Status

Sitzungen, Profil-Pipeline und Master-Verbindung lokal überwachen.
Aktiv
Getrennt
Sitzungen
Profil-Jobs
Cleanup
RemoteApps
Master
RDS

Sitzungen

Aktive und getrennte Benutzer sowie administrative Aktionen.
Operations

Betriebsdaten

RemoteApps, Profil-Pipeline und lokale Ereignisse kompakt in einer Arbeitsfläche.
Configuration

Lokale Policy

Fallback-Konfiguration für Profile, Sessions, Cleanup und Templates.
Fallback-Verhalten: Die lokale Policy bleibt als Notfallkonfiguration verfügbar. Eine aktive Master-Soll-Policy hat im zentral verwalteten Betrieb Vorrang.
` 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='
Profil-Sync
'+(pr.enabled?'Aktiv':'Aus')+''+((pr.folders||[]).length)+' Ordner
'+esc(pr.store_root||'Kein Store konfiguriert')+'
Sitzungsrichtlinie
Steuerung '+(sp.control_enabled?'an':'aus')+'Auto-Logoff '+(sp.disconnected_logoff_enabled?'an':'aus')+'
Timeout: '+Math.round((sp.disconnected_timeout_seconds||0)/60)+' min
Cleanup
'+(c.enabled?'Aktiv':'Aus')+''+(c.dry_run?'Dry-Run':'Live')+'
Grace: '+(c.grace_seconds||0)+' s
'}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?'
'+rows.map(x=>'').join('')+'
ZeitTypBenutzerMeldung
'+esc(when(x.time))+''+esc(x.level||'info')+''+esc(x.user||'–')+''+esc(x.message||'')+'
':'
Noch keine Ereignisse.
'} function renderRemoteApps(s){let rows=s.remote_apps||[];$('remoteapps').innerHTML=rows.length?''+rows.map(x=>'').join('')+'
StatusAliasPfadVerwaltungFehler
'+(x.published&&x.path_exists&&x.in_sync?'Bereit':'Nicht bereit')+'||'+esc(x.alias)+'
'+esc(x.display_name||x.resource_id||'–')+'
'+esc(x.path||'–')+''+(x.managed?'Master'+(x.owned?' (angelegt)':' (übernommen)'):'lokal entdeckt')+''+esc(x.error||'–')+'
':'
Keine RemoteApps gefunden. Bei Agent-gesteuerten Apps erscheint der Status nach dem nächsten Master-Heartbeat.
'} function renderProfiles(s){let jobs=s.profile_jobs||[],status=Object.values(s.profile_status||{});let html='';if(jobs.length)html+=''+jobs.map(j=>'').join('')+'
JobBenutzerFälligVersucheFehler
'+esc(j.operation)+''+esc(j.user)+''+esc(when(j.due_at))+''+j.attempts+''+esc(j.last_error||'–')+'
';if(status.length)html+=''+status.map(x=>'').join('')+'
BenutzerLetztes BackupLetzter RestoreStatus
'+esc(x.user||x.sid)+''+esc(when(x.last_backup_at))+''+esc(when(x.last_restore_at))+''+esc(x.last_backup_error||x.last_restore_error||'OK')+'
';$('profiles').innerHTML=html||'
Keine Profil-Jobs oder -Historie.
'} 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=''+ss.map(x=>'').join('')+'
IDBenutzerStatusGetrennt seitClientAktionen
'+x.id+''+esc((x.domain?x.domain+'\\':'')+x.user)+''+esc(x.state)+''+esc(x.disconnected_since?when(x.disconnected_since)+' ('+dur(x.disconnected_since)+')':'–')+''+esc(x.client_name||'–')+''+(control&&x.user?'
':'–')+'
'} 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?'Offline':'Verbunden';renderSessions(s);renderRemoteApps(s);renderProfiles(s);renderEvents(s.events||[])}catch(e){$('master').innerHTML=''+esc(e.message)+''}} 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'
Keine weiteren Angaben.
';if(k==='url')return'';if(k==='shortcut'){let s=t.shortcut||{};return'
'}return'
'} function renderTemplates(){let h=$('templateList');if(!h)return;h.innerHTML=policyTemplates.length?policyTemplates.map((t,i)=>'
'+esc(t.id||('Template '+(i+1)))+'
'+templateSpecific(t)+'
').join(''):'
Keine Templates konfiguriert.
'} 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)=>'
'+esc(f.path||('Ordner '+(i+1)))+'
').join(''):'
Keine Profilordner konfiguriert.
'} 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'
Profil-Sicherung & Wiederherstellung
Zu sichernde Profilordner
Gesichert werden nur konfigurierte Teilbäume. Reparse-Points/Symlinks werden nicht verfolgt. Cleanup beginnt nach einem Logoff erst nach erfolgreichem Backup.
Sitzungsrichtlinie
Profil-Cleanup
Templates
'} function updatePolicyDirty(){let e=$('policyDirtyState');if(e)e.textContent=policyDirty?'Ungespeicherte Änderungen':'';let p=$('policyDirtyPill');if(p)p.innerHTML=policyDirty?'● Ungespeichert':''}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);`