package master const masterHTML = `SessionGuard Master
Master Console
Broker, Director, Profile & Policy Management
Live · 5s
Control Plane

Infrastruktur auf einen Blick

Live-Status der RDS-Farm, Profile und Broker-Ressourcen.
Server
Online
Aktiv
Getrennt
Profil-Jobs
Cleanup
Farms
Alerts!
Director

Terminalserver

Server auswählen, Sitzungen steuern und Health-Details prüfen.

Serverliste

Online, Sessions, Health und Jobs

Server auswählen

Live-Details und Konfiguration
Links einen Agent auswählen.
Broker

Farms & Broker

Hosts gruppieren, Tags definieren und aktive Leases nachvollziehen.
Delivery

Published Resources

Desktops und RemoteApps mit Guacamole-Verbindungen und Farms verknüpfen.
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,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 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')})); 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=''+a.map(x=>'').join('')+'
StatusServerModeHealthSitzungenJobs
'+(x.online?'Online':'Offline')+''+esc(x.name)+'
'+esc(x.snapshot.agent_version||'–')+'
'+esc(x.maintenance_mode||'online')+''+esc((x.snapshot.health||{}).score??'–')+''+x.active_sessions+' aktiv / '+x.total_sessions+''+((x.snapshot.profile_jobs||[]).length)+' P / '+((x.snapshot.pending_cleanup||[]).length)+' C
';document.querySelectorAll('.row').forEach(r=>r.onclick=()=>selectAgent(r.dataset.id,false,false))} async function refresh(){try{let d=await api('/api/v1/dashboard'),a=d.agents||[];agentCache=a;$('mServers').textContent=a.length;$('mOnline').textContent=a.filter(x=>x.online).length;$('mActive').textContent=a.reduce((n,x)=>n+x.active_sessions,0);$('mDisc').textContent=a.reduce((n,x)=>n+(x.disconnected_sessions||0),0);$('mProfile').textContent=a.reduce((n,x)=>n+(x.snapshot.profile_jobs||[]).length,0);$('mCleanup').textContent=a.reduce((n,x)=>n+(x.snapshot.pending_cleanup||[]).length,0);$('mFarms').textContent=d.farms||0;$('mAlerts').textContent=d.active_alerts||0;renderAgentTable(a);if(selected)await selectAgent(selected,true,true);let fr=await api('/api/v1/farms');farmCache=fr.farms||[];let rr=await api('/api/v1/resources');resourceCache=rr.resources||[];let lr=await api('/api/v1/leases');brokerLeases=lr.leases||[];renderFarms();renderResources();let hi=await api('/api/v1/history?limit=500');renderHistory(hi.history||[]);try{let ph=await api('/api/v1/policy/history');policyHistory=ph.history||[];renderPolicyHistory()}catch(e){$('policyHistory').innerHTML='
Policy-Historie erfordert Policy-Admin/Admin-Rolle.
'}let al=await api('/api/v1/alerts');renderAlerts(al.alerts||[]);if(!me)me=await api('/api/v1/me');try{let au=await api('/api/v1/audit');renderAudit(au.audit||[])}catch(e){$('audit').innerHTML='
Audit-Log erfordert Auditor/Admin-Rolle.
'}}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?''+rows.map(x=>'').join('')+'
ZeitZielRevisionAkteur
'+when(x.created_at)+''+esc(x.target)+''+esc(x.revision)+''+esc(x.actor||'–')+'
':'
Noch keine Policy-Versionen.
'} function renderAudit(rows){rows=(rows||[]).slice().reverse().slice(0,250);$('audit').innerHTML=rows.length?''+rows.map(x=>'').join('')+'
ZeitAkteurAktionZielErgebnis
'+esc(when(x.time))+''+esc(x.actor)+''+esc(x.action)+''+esc(x.target||'–')+''+esc(x.result)+(x.details?'
'+esc(x.details)+'':'')+'
':'
Noch keine Audit-Einträge.
'} 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'
OS
'+esc(v.os||'–')+'
Health
'+esc(h.score??'–')+'/100
CPU
'+Number(v.cpu_percent||0).toFixed(1)+' %
RAM
'+mem.toFixed(1)+' % · '+bytes(v.memory_available)+' frei
Systemdisk
'+bytes(v.disk_free)+' frei
Heartbeat
'+when(a.last_seen)+'
RDP: '+(h.rdp_listener_ok?'✓':'✗')+' · Profile Store: '+(h.profile_store_ok?'✓':'✗')+' · Broker Score: '+esc(Math.round((h.score||0)*10-(v.cpu_percent||0)*2))+' · Pending Commands: '+(a.pending_commands||[]).length+'
'} 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?''+ss.map(x=>'').join('')+'
IDBenutzerStatusLogon / IdleClientAktionen
'+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
'+esc(x.client_name||'–')+'
'+esc(x.client_address||'')+'
'+(control&&x.user?'
':'–')+'
':'
Keine Benutzersitzungen.
'} function profileHTML(a){let s=a.snapshot||{},jobs=s.profile_jobs||[],st=Object.values(s.profile_status||{});let h=jobs.length?''+jobs.map(j=>'').join('')+'
OperationBenutzerFälligFehler
'+esc(j.operation)+''+esc(j.user)+''+when(j.due_at)+''+esc(j.last_error||'–')+'
':'';if(st.length)h+=''+st.map(x=>'').join('')+'
BenutzerBackupRestoreFehler
'+esc(x.user||x.sid)+''+when(x.last_backup_at)+''+when(x.last_restore_at)+''+esc(x.last_backup_error||x.last_restore_error||'–')+'
';return h||'
Keine Profil-Jobs/Statusdaten.
'} 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?''+ps.map(p=>'').join('')+'
PIDSessionProzessRAM
'+p.pid+''+p.session_id+''+esc(p.name)+''+bytes(p.memory_bytes)+'
':'
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?''+rows.map(x=>'').join('')+'
SessionBenutzerLogon erkanntRestoreBis Ready
'+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
':'
Noch keine Logon-Telemetrie.
'} function renderHistory(rows){rows=(rows||[]).slice().reverse();$('history').innerHTML=rows.length?''+rows.map(x=>'').join('')+'
ZeitServerBenutzerEreignisClientDetails
'+when(x.time)+''+esc(x.hostname)+''+esc(x.user)+''+esc(x.event)+''+esc(x.client_name||'–')+''+esc(x.details||'–')+'
':'
Noch keine Session-Historie.
'} function renderAlerts(rows){$('alerts').innerHTML=rows.length?''+rows.map(x=>'').join('')+'
StatusServerTypMeldungZuletzt
'+(x.active?'AKTIV':'gelöst')+''+esc(x.hostname||'–')+''+esc(x.type)+''+esc(x.message)+''+when(x.last_seen_at)+'
':'
Keine Alerts.
'} function parseTags(v){let o={};String(v||'').split('\n').map(x=>x.trim()).filter(Boolean).forEach(x=>{let i=x.indexOf('=');if(i>0)o[x.slice(0,i).trim()]=x.slice(i+1).trim()});return o}function agentMatchesFarm(a,f){if(!a||!f)return false;if((f.agent_ids||[]).includes(a.id)||(a.farm_ids||[]).includes(f.id))return true;let required=Object.entries(f.required_tags||{});return required.length>0&&required.every(([k,v])=>String((a.tags||{})[k]??'')===String(v))}function farmMembers(f){return agentCache.filter(a=>agentMatchesFarm(a,f))}function renderFarms(){let root=$('farms');if(!root)return;if(!$('farmEditor'))root.innerHTML='
';let lease=brokerLeases.length?'

Aktive Broker-Leases

'+brokerLeases.map(l=>'').join('')+'
BenutzerAgentFarmResourceGrundBis
'+esc(l.user_key)+''+esc(l.agent_id)+''+esc(l.farm_id||'–')+''+esc(l.resource_id||'–')+''+esc(l.reason||'–')+''+when(l.expires_at)+'
':'';$('farmData').innerHTML=(farmCache.length?''+farmCache.map(f=>'').join('')+'
NameIDAgentenRequired Tags
'+esc(f.name)+''+esc(f.id)+''+farmMembers(f).length+''+esc(Object.entries(f.required_tags||{}).map(([k,v])=>k+'='+v).join(', '))+'
':'
Noch keine Farms.
')+lease} function renderResources(){let root=$('resources');if(!root)return;if(!$('resourceEditor'))root.innerHTML='
';let sel=$('resFarm'),cur=sel?sel.value:'';if(sel&&document.activeElement!==sel){sel.innerHTML=farmCache.map(f=>'').join('');if(cur&&farmCache.some(f=>f.id===cur))sel.value=cur}$('resourceData').innerHTML=resourceCache.length?''+resourceCache.map(x=>'').join('')+'
NameTypFarmGuacamoleRemoteApp
'+esc(x.name)+''+esc(x.kind)+''+esc(x.farm_id)+''+esc(x.guacamole_connection_id||x.guacamole_connection_name||'–')+''+esc(x.remote_app||'–')+'
':'
Noch keine Resources.
'} function eventsHTML(a){let e=((a.snapshot||{}).events||[]).slice().reverse().slice(0,160);return e.length?''+e.map(x=>'').join('')+'
ZeitTypBenutzerMeldung
'+when(x.time)+''+esc(x.level)+''+esc(x.user||'–')+''+esc(x.message)+'
':'
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||'Server';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
Sitzungen
Logon Performance
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);$('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'
Editor-Basis: '+esc(source)+'. Live-Refresh verändert offene Eingaben nicht.
Profil-Sicherung & Wiederherstellung
Profilordner
Sitzungsrichtlinie
Profil-Cleanup
Templates
'} async function bulkSessionAction(action,scope){if(!selected)return;let body={action,scope};if(action==='message'){let m=prompt('Nachricht an alle Benutzersitzungen:');if(!m)return;body.message=m;body.title='SessionGuard'}if(action==='logoff'&&!confirm('Die ausgewählten Sitzungen wirklich abmelden? Die Profil-Pipeline läuft anschließend.'))return;try{let r=await api('/api/v1/agents/'+encodeURIComponent(selected)+'/sessions/bulk',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});toast((r.queued||0)+' Sitzungsaktionen eingeplant')}catch(e){toast(e.message)}} async function saveAgentControl(){if(!selected)return;let tags={};($('agentTags').value||'').split('\n').map(x=>x.trim()).filter(Boolean).forEach(x=>{let i=x.indexOf('=');if(i>0)tags[x.slice(0,i).trim()]=x.slice(i+1).trim()});let body={mode:$('agentMode').value,restart_when_drained:$('restartDrained').checked,tags,farm_ids:$('agentFarms').value.split('\n').map(x=>x.trim()).filter(Boolean)};try{await api('/api/v1/agents/'+encodeURIComponent(selected)+'/control',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});toast('Serversteuerung gespeichert');await selectAgent(selected,true,false)}catch(e){toast(e.message)}} async function killProcess(pid){if(!selected||!confirm('Prozess '+pid+' wirklich beenden?'))return;try{await api('/api/v1/agents/'+encodeURIComponent(selected)+'/processes/'+pid+'/kill',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'});toast('Prozess-Beenden an Agent gesendet')}catch(e){toast(e.message)}} async function savePolicy(all){if(!selected)return;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(all?'/api/v1/policy/all':'/api/v1/agents/'+encodeURIComponent(selected)+'/policy',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(p)});toast(all?'Policy auf alle Server angewendet':'Policy gespeichert');await selectAgent(selected,true,false)}catch(e){toast(e.message)}} $('detail').addEventListener('click',e=>{let sb=e.target.closest('button[data-session-action]');if(sb){sessionAction(+sb.dataset.session,sb.dataset.sessionAction);return}let b=e.target.closest('button[data-action]');if(!b)return;let a=b.dataset.action;if(a==='save-control')saveAgentControl();if(a==='reload-control'&¤t&&$('controlEditor'))$('controlEditor').innerHTML=controlHTML(current);if(a==='broadcast')bulkSessionAction('message','all');if(a==='logoff-disconnected')bulkSessionAction('logoff','disconnected');if(a==='kill-process')killProcess(+b.dataset.process);if(a==='save-one')savePolicy(false);if(a==='save-all')savePolicy(true);if(a==='reload-policy')selectAgent(selected,false,false);if(a==='add-template'){policyTemplates=collectTemplates();policyTemplates.push(templateDefault());renderTemplates()}if(a==='remove-template'){policyTemplates=collectTemplates();policyTemplates.splice(+b.dataset.index,1);renderTemplates()}if(a==='add-profile-folder'){profileFolders=collectProfileFolders();profileFolders.push({path:'AppData\\Roaming\\Hersteller',exclude_globs:['Cache/**']});renderProfileFolders()}if(a==='remove-profile-folder'){profileFolders=collectProfileFolders();profileFolders.splice(+b.dataset.index,1);renderProfileFolders()}});$('detail').addEventListener('change',e=>{if(e.target.matches('select[data-action="template-kind"]')){policyTemplates=collectTemplates();renderTemplates()}});document.addEventListener('click',async e=>{let b=e.target.closest('button[data-global-action]');if(!b)return;let a=b.dataset.globalAction;try{if(a==='clear-farm-form'){$('farmName').value='';$('farmDesc').value='';$('farmTags').value='';return}if(a==='create-farm'){let name=$('farmName').value.trim();if(!name)return;await api('/api/v1/farms',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,description:$('farmDesc').value.trim(),enabled:true,required_tags:parseTags($('farmTags').value)})});$('farmName').value='';$('farmDesc').value='';$('farmTags').value='';toast('Farm angelegt');await refresh()}if(a==='delete-farm'){if(!confirm('Farm löschen?'))return;await api('/api/v1/farms/'+encodeURIComponent(b.dataset.id),{method:'DELETE'});toast('Farm gelöscht');await refresh()}if(a==='clear-resource-form'){$('resName').value='';$('resKind').value='desktop';$('resConnID').value='';$('resConnName').value='';$('resRemoteApp').value='';$('resRemoteDir').value='';$('resRemoteArgs').value='';return}if(a==='create-resource'){let name=$('resName').value.trim(),farm_id=$('resFarm').value;if(!name||!farm_id)return;await api('/api/v1/resources',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,kind:$('resKind').value,farm_id,guacamole_connection_id:$('resConnID').value.trim(),guacamole_connection_name:$('resConnName').value.trim(),remote_app:$('resRemoteApp').value.trim(),remote_app_dir:$('resRemoteDir').value.trim(),remote_app_args:$('resRemoteArgs').value.trim(),enabled:true})});$('resName').value='';$('resConnID').value='';$('resConnName').value='';$('resRemoteApp').value='';$('resRemoteDir').value='';$('resRemoteArgs').value='';toast('Resource angelegt');await refresh()}if(a==='delete-resource'){if(!confirm('Resource löschen?'))return;await api('/api/v1/resources/'+encodeURIComponent(b.dataset.id),{method:'DELETE'});toast('Resource gelöscht');await refresh()}if(a==='rollback-policy'){if(!confirm('Policy auf Revision '+b.dataset.revision+' zurückrollen?'))return;let target=b.dataset.target,rev=encodeURIComponent(b.dataset.revision),url;if(target==='global')url='/api/v1/policy/global/rollback/'+rev;else if(target.startsWith('agent:'))url='/api/v1/agents/'+encodeURIComponent(target.slice(6))+'/policy/rollback/'+rev;else if(target.startsWith('farm:'))url='/api/v1/farms/'+encodeURIComponent(target.slice(5))+'/policy/rollback/'+rev;else throw new Error('Unbekanntes Policy-Ziel');await api(url,{method:'POST'});toast('Rollback eingeplant');editorAgent=null;await refresh()}}catch(err){toast(err.message)}}); initShell();refresh();setInterval(refresh,5000);`