Live-Status der RDS-Farm, Profile und Broker-Ressourcen.
Server▣
–
Online●
–
Aktiv▶
–
GetrenntⅡ
–
Profil-Jobs↕
–
Cleanup⌫
–
Farms⌘
–
Access◎
–
Alerts!
–
Director
Terminalserver
Server auswählen, Sitzungen steuern und Health-Details prüfen.
Serverliste
Server anklicken, um Live-Details und Konfiguration im Arbeitsdialog zu öffnen.
Live-Ansicht
Broker
Farms & Broker
Hosts gruppieren, Tags definieren und aktive Leases nachvollziehen.
Delivery
Apps & Desktops
Guacamole-Ressourcen zu Farms zuordnen und RemoteApps optional direkt durch die Windows-Agents veröffentlichen.
Access Plane
Guacamole Access-Sessions
Serverseitige PocketID-Sitzungen, die der Reverse Proxy zur Laufzeit vor Guacamole-Requests prüft. Eine widerrufene oder abgelaufene Session liefert keinen Benutzer-Header mehr.
Access-Sessions werden geladen…
Historie
Session-Historie
Logon, Reconnect, Disconnect und Profilereignisse nachvollziehen.
Governance
Policy-Historie & Rollback
Revisionen vergleichen und frühere Sollstände wieder aktivieren.
Monitoring
Alerts
Aktive und gelöste Betriebswarnungen.
Compliance
Audit-Log
Administrative Änderungen und Aktionen revisionsfreundlich nachvollziehen.
`
const masterJS = `
let selected=null,current=null,editorAgent=null,resourceEditID=null,policyTemplates=[],profileFolders=[],agentCache=[],farmCache=[],resourceCache=[],brokerLeases=[],policyHistory=[],me=null;const $=id=>document.getElementById(id);const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));function when(v){if(!v)return'–';let d=new Date(v);return Number.isNaN(d.getTime())||d.getFullYear()<2000?'–':d.toLocaleString('de-DE')}function bytes(n){if(!n)return'–';let u=['B','KB','MB','GB','TB'],i=0;while(n>=1024&&i1?1:0)+' '+u[i]}function toast(t){let e=$('toast');e.textContent=t;e.style.display='block';setTimeout(()=>e.style.display='none',3000)}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 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();if(!document.querySelector('dialog[open]'))document.body.classList.remove('modal-open')}function activateDetailTab(name){document.querySelectorAll('#detail .detail-tab').forEach(b=>b.classList.toggle('active',b.dataset.tab===name));document.querySelectorAll('#detail .detail-pane').forEach(p=>p.classList.toggle('active',p.dataset.pane===name))}
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('dialog.sg-modal').forEach(d=>d.addEventListener('close',()=>{if(!document.querySelector('dialog[open]'))document.body.classList.remove('modal-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')}));
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)})}
}
function renderAgentTable(a){$('agents').innerHTML='
'}}catch(e){toast(e.message)}}
async function selectAgent(id,quiet,preserve){let same=selected===id;selected=id;try{current=await api('/api/v1/agents/'+encodeURIComponent(id));renderDetail(current,!!preserve&&same)}catch(e){if(!quiet)toast(e.message)}}
function renderPolicyHistory(){let rows=(policyHistory||[]).slice().reverse().slice(0,250);$('policyHistory').innerHTML=rows.length?'
Zeit
Ziel
Revision
Akteur
'+rows.map(x=>'
'+when(x.created_at)+'
'+esc(x.target)+'
'+esc(x.revision)+'
'+esc(x.actor||'–')+'
').join('')+'
':'
Noch keine Policy-Versionen.
'}
function renderAudit(rows){rows=(rows||[]).slice().reverse().slice(0,250);$('audit').innerHTML=rows.length?'
'}
function overviewStats(a){let s=a.snapshot||{},v=s.server||{},h=s.health||{},mem=v.memory_total?((v.memory_total-v.memory_available)*100/v.memory_total):0;return'
'}
function controlHTML(a){let tags=Object.entries(a.tags||{}).map(([k,v])=>k+'='+v).join('\n');return'
'}
async function sessionAction(id,action){if(!selected)return;let body={action};if(action==='message'){let m=prompt('Nachricht an Sitzung '+id+':');if(!m)return;body.message=m;body.title='SessionGuard'}if(action==='logoff'&&!confirm('Sitzung '+id+' wirklich abmelden? Die Profilsicherung startet nach dem Sitzungsende.'))return;try{await api('/api/v1/agents/'+encodeURIComponent(selected)+'/sessions/'+id+'/action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});toast('Sitzungsaktion an Agent gesendet');setTimeout(()=>selectAgent(selected,true,true),1000)}catch(e){toast(e.message)}}
function sessionsHTML(a){let s=a.snapshot||{},ss=s.sessions||[],p=(a.desired_policy&&a.desired_policy.revision)?a.desired_policy:s.policy,control=p&&p.sessions&&p.sessions.control_enabled;return ss.length?'
ID
Benutzer
Status
Logon / Idle
Client
Aktionen
'+ss.map(x=>'
'+x.id+'
'+esc((x.domain?x.domain+'\\':'')+x.user)+'
'+esc(x.state)+'
'+esc(when(x.logon_at))+' Idle '+Math.round((x.idle_seconds||0)/60)+' min
'}
function processesHTML(a){let ps=((a.snapshot||{}).processes||[]).filter(p=>p.session_id!==0).sort((x,y)=>y.memory_bytes-x.memory_bytes).slice(0,300);return ps.length?'
PID
Session
Prozess
RAM
'+ps.map(p=>'
'+p.pid+'
'+p.session_id+'
'+esc(p.name)+'
'+bytes(p.memory_bytes)+'
').join('')+'
':'
Keine Session-Prozesse erfasst.
'}
function telemetryHTML(a){let rows=Object.values(((a.snapshot||{}).telemetry)||{}).sort((x,y)=>new Date(y.first_seen_at)-new Date(x.first_seen_at)).slice(0,100);return rows.length?'
Session
Benutzer
Logon erkannt
Restore
Bis Ready
'+rows.map(x=>'
'+x.session_id+'
'+esc(x.user||x.sid||'–')+'
'+when(x.logon_at||x.first_seen_at)+'
'+((x.restore_duration_ms||0)/1000).toFixed(2)+' s
'+((x.observed_logon_ms||0)/1000).toFixed(2)+' s
').join('')+'
':'
Noch keine Logon-Telemetrie.
'}
function renderHistory(rows){rows=(rows||[]).slice().reverse();$('history').innerHTML=rows.length?'
Zeit
Server
Benutzer
Ereignis
Client
Details
'+rows.map(x=>'
'+when(x.time)+'
'+esc(x.hostname)+'
'+esc(x.user)+'
'+esc(x.event)+'
'+esc(x.client_name||'–')+'
'+esc(x.details||'–')+'
').join('')+'
':'
Noch keine Session-Historie.
'}
function renderAlerts(rows){$('alerts').innerHTML=rows.length?'
Noch keine Resources. Lege einen Desktop oder eine RemoteApp an.
';updateResourceEditorState()}
function eventsHTML(a){let e=((a.snapshot||{}).events||[]).slice().reverse().slice(0,160);return e.length?'
Zeit
Typ
Benutzer
Meldung
'+e.map(x=>'
'+when(x.time)+'
'+esc(x.level)+'
'+esc(x.user||'–')+'
'+esc(x.message)+'
').join('')+'
':'
Keine Ereignisse.
'}
function activePolicy(a){if(a.desired_policy&&a.desired_policy.revision)return a.desired_policy;if(a.snapshot&&a.snapshot.policy)return a.snapshot.policy;return{cleanup:{grace_seconds:600,poll_seconds:10,retry_seconds:60,dry_run:true,allowed_profile_roots:['C:\\Users']},profiles:{retry_seconds:60,keep_versions:2,folders:[]},sessions:{disconnected_timeout_seconds:3600},templates:[]}}
function renderDetail(a,preserve){$('detailTitle').textContent=a.name||'Terminalserver';if(!preserve||editorAgent!==a.id||!$('overviewStats')){let p=activePolicy(a);policyTemplates=JSON.parse(JSON.stringify(p.templates||[]));profileFolders=JSON.parse(JSON.stringify((p.profiles&&p.profiles.folders)||[]));$('detail').className='';$('detail').innerHTML='
Serversteuerung
Logon Performance
RemoteApps
Benutzersitzungen
RemoteApps
Prozesse
Profil-Pipeline
Agent-Ereignisse
Policy
'+policyForm(p,a)+'
';$('controlEditor').innerHTML=controlHTML(a);editorAgent=a.id;renderTemplates();renderProfileFolders()}$('overviewStats').innerHTML=overviewStats(a);$('sessionList').innerHTML=sessionsHTML(a);$('telemetryList').innerHTML=telemetryHTML(a);$('processList').innerHTML=processesHTML(a);$('remoteAppList').innerHTML=remoteAppsHTML(a);if($('remoteAppSummary'))$('remoteAppSummary').innerHTML=remoteAppsHTML(a);$('profileList').innerHTML=profileHTML(a);$('eventList').innerHTML=eventsHTML(a)}
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.
'}
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.
'}
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 policyForm(p,a){let c=p.cleanup||{},pr=p.profiles||{},sp=p.sessions||{},source=a.desired_policy&&a.desired_policy.revision?'Master-Soll-Policy':'Agent-Ist-Policy';return'