home-hosted 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "home-hosted",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "packageManager": "pnpm@12.5.1",
6
- "description": "A self-hosted control panel that keeps your home server processes alive \u2014 with a UI you can replace.",
6
+ "description": "A self-hosted control panel that keeps your home server processes alive - with a UI you can replace.",
7
7
  "author": "NamesMT <dangquoctrung123@gmail.com>",
8
8
  "license": "MIT",
9
9
  "homepage": "https://github.com/NamesMT/home-hosted#readme",
@@ -22,7 +22,7 @@
22
22
  "control-panel",
23
23
  "daemon",
24
24
  "hono",
25
- "vue"
25
+ "ui"
26
26
  ],
27
27
  "imports": {
28
28
  "#src/*": "./src/*"
@@ -64,7 +64,8 @@
64
64
  "prepare": "simple-git-hooks",
65
65
  "prepack": "pnpm run build",
66
66
  "build:ui": "node scripts/build-uis.mjs stock",
67
- "build:uis": "node scripts/build-uis.mjs --zip"
67
+ "build:uis": "node scripts/build-uis.mjs --zip",
68
+ "media": "node scripts/capture-media.mjs"
68
69
  },
69
70
  "dependencies": {
70
71
  "@grammyjs/auto-retry": "^2.0.2",
@@ -88,8 +89,11 @@
88
89
  "@vitest/coverage-v8": "^5.0.1",
89
90
  "clsx": "^2.1.1",
90
91
  "eslint": "^10.11.0",
92
+ "gifenc": "^1.0.3",
91
93
  "lint-staged": "^17.5.1",
92
94
  "lucide-vue-next": "^1.0.0",
95
+ "playwright": "^1.63.0",
96
+ "pngjs": "^7.0.0",
93
97
  "reka-ui": "^2.10.5",
94
98
  "simple-git-hooks": "^2.14.0",
95
99
  "tailwind-merge": "^3.7.0",
@@ -44,4 +44,4 @@ fn("string", ":", "number")(s => s.length)`,tD=class extends xb{$;constructor(e)
44
44
  `),env:l(e.env),dataEnvs:l(e.dataEnvs),backupPaths:e.backupPaths.join(`
45
45
  `),dependsOn:e.dependsOn.join(`, `),envFile:e.envFile,maxRssMb:d(e.resources.maxRssBytes),port:e.port,bind:e.bind,onPortConflict:e.onPortConflict,logBufferLines:e.logBufferLines,enabled:e.enabled,autostart:e.autostart,bootstrapEnabled:e.bootstrap!==null&&e.bootstrap!==void 0,bootstrapCommand:e.bootstrap?.command??``,bootstrapArgs:(e.bootstrap?.args??[]).join(`
46
46
  `),bootstrapEnv:l(e.bootstrap?.env??{}),bootstrapTimeoutMs:e.bootstrap?.timeoutMs??12e4,bootstrapRunOnce:e.bootstrap?.runOnce??!0}}let p=en(f(n.config)),m=A({...n.config.restart}),h=A({...n.config.health}),g=A({...n.config.stop}),_={...n.config},v=U(()=>{let e=[{value:`local`,label:`local — 127.0.0.1`},{value:`lan`,label:`lan — 0.0.0.0`}];return/^\d{1,3}(?:\.\d{1,3}){3}$/.test(n.config.bind)&&e.push({value:n.config.bind,label:`${n.config.bind} — explicit`}),e});function y(){let e=Number.isFinite(p.maxRssMb)?p.maxRssMb:0,t=Number.isFinite(p.logBufferLines)?p.logBufferLines:0,n=Number.isFinite(p.bootstrapTimeoutMs)?p.bootstrapTimeoutMs:0;return{label:p.label.trim(),command:p.command.trim(),cwd:p.cwd.trim()===``?`.`:p.cwd.trim(),args:c(p.args),env:u(p.env),dataEnvs:u(p.dataEnvs),backupPaths:c(p.backupPaths),dependsOn:p.dependsOn.split(`,`).map(e=>e.trim()).filter(e=>e.length>0),envFile:p.envFile.trim(),resources:{maxRssBytes:e>0?Math.round(e*1024*1024):0},port:p.port===null||Number.isNaN(p.port)?null:p.port,bind:p.bind,onPortConflict:p.onPortConflict,logBufferLines:t,enabled:p.enabled,autostart:p.autostart,restart:{...m.value},health:{...h.value},stop:{...g.value},bootstrap:p.bootstrapEnabled?{command:p.bootstrapCommand.trim(),args:c(p.bootstrapArgs),env:u(p.bootstrapEnv),timeoutMs:n,runOnce:p.bootstrapRunOnce}:null}}function b(e,t){let r=n.config[t]??{},i=e[t]??{},a={};for(let[e,t]of Object.entries(i))r[e]!==t&&(a[e]=t);return Object.keys(a).length>0?a:null}function x(e){for(let t of[`restart`,`health`,`stop`])b(e,t)===null&&delete e[t];return aB(n.config,e)}function S(e,t){return JSON.stringify(e??null)===JSON.stringify(t??null)}let C=U(()=>{let e=y(),t=0;for(let[n,r]of Object.entries(e))i.includes(n)?t+=b(e,n)===null?0:1:S(_[n],r)||(t+=1);return t}),w=U(()=>C.value>0),T={...n.config};function ee(e){Object.assign(p,f(e)),m.value={...e.restart},h.value={...e.health},g.value={...e.stop},_={...e},T={...e},s.value=null}jr(()=>n.config,e=>{S(e,T)||ee(e)});async function E(){s.value=null,o.value=!0;try{let e=y(),t=aA(e);if(t instanceof Y.errors){s.value=t.summary;return}let i=x(e);if(Object.keys(i).length===0){r(`saved`);return}if(await a.saveConfig(n.serverId,i),a.lastError.value!==null){s.value=a.lastError.value;return}_={...e},r(`saved`)}finally{o.value=!1}}return(e,t)=>(I(),L(`form`,{class:`border-t border-line bg-page/40 px-4 py-4`,onSubmit:uu(E,[`prevent`])},[z(`header`,dB,[t[26]||=z(`div`,{class:`min-w-0`},[z(`h2`,{class:`text-sm font-semibold text-ink`},` Edit configuration `),z(`p`,{class:`mt-0.5 text-2xs leading-4 text-muted`},[V(` Writes overrides to `),z(`span`,{class:`font-mono`},`servers.config.json`),V(`; anything left alone keeps inheriting the defaults. `)])],-1),z(`div`,fB,[C.value>0?(I(),L(`span`,pB,k(C.value)+` unsaved `+k(C.value===1?`change`:`changes`),1)):(I(),L(`span`,mB,`in sync`))])]),z(`div`,hB,[B(YL,{title:`Process`,description:`What gets spawned, where, and with which environment.`,columns:2},{default:M(()=>[B(cR,{modelValue:p.label,"onUpdate:modelValue":t[0]||=e=>p.label=e,label:`Label`,placeholder:`falls back to the id`},null,8,[`modelValue`]),B(cR,{modelValue:p.command,"onUpdate:modelValue":t[1]||=e=>p.command=e,label:`Command`,placeholder:`node`,required:``,class:`font-mono text-xs`},null,8,[`modelValue`]),B(cR,{modelValue:p.cwd,"onUpdate:modelValue":t[2]||=e=>p.cwd=e,label:`Working directory`,placeholder:`.`,hint:`Relative paths resolve under projectDir.`,class:`font-mono text-xs`},null,8,[`modelValue`]),B(cR,{modelValue:p.envFile,"onUpdate:modelValue":t[3]||=e=>p.envFile=e,label:`Env file`,placeholder:`optional KEY=value file`,hint:`Loaded at spawn; its values override env.`,class:`font-mono text-xs`},null,8,[`modelValue`]),B(rR,{modelValue:p.args,"onUpdate:modelValue":t[4]||=e=>p.args=e,label:`Arguments`,rows:3,hint:`One per line; {port} {host} {home} {projectDir} {dataRoot} {id} {label} {cwd} {bind} {lanIp} are substituted.`,placeholder:`-p
47
- {port}`},null,8,[`modelValue`]),B(rR,{modelValue:p.env,"onUpdate:modelValue":t[5]||=e=>p.env=e,label:`Environment`,rows:3,hint:`KEY=value per line, exported to the process.`,placeholder:`NODE_ENV=production`},null,8,[`modelValue`]),B(rR,{modelValue:p.dataEnvs,"onUpdate:modelValue":t[6]||=e=>p.dataEnvs=e,label:`Data envs`,rows:3,hint:`ENV=path per line — exported to the process and backed up automatically.`,placeholder:`DATA_DIR={home}/.app`},null,8,[`modelValue`]),B(rR,{modelValue:p.backupPaths,"onUpdate:modelValue":t[7]||=e=>p.backupPaths=e,label:`Extra backup paths`,rows:2,hint:`One per line, placeholders allowed.`,placeholder:`{home}/.app/uploads`},null,8,[`modelValue`]),B(cR,{modelValue:p.dependsOn,"onUpdate:modelValue":t[8]||=e=>p.dependsOn=e,label:`Depends on`,placeholder:`postgres, redis`,hint:`Comma separated; started first, stopped last.`,class:`font-mono text-xs`},null,8,[`modelValue`]),B($L,{modelValue:p.maxRssMb,"onUpdate:modelValue":t[9]||=e=>p.maxRssMb=e,label:`Max RSS (MB)`,min:0,hint:`Restart above this; blank or 0 disables it.`},null,8,[`modelValue`])]),_:1}),B(YL,{title:`Port & binding`,description:`How the port is preflighted and where the process is reachable.`,columns:2},{default:M(()=>[B($L,{modelValue:p.port,"onUpdate:modelValue":t[10]||=e=>p.port=e,label:`Port`,nullable:``,min:1,max:65535,hint:`Blank means no port: no probe, no preflight.`},null,8,[`modelValue`]),B(JI,{modelValue:p.bind,"onUpdate:modelValue":t[11]||=e=>p.bind=e,label:`Bind address`,options:v.value,hint:`A custom IPv4 the config already sets stays selectable.`},null,8,[`modelValue`,`options`]),B(JI,{modelValue:p.onPortConflict,"onUpdate:modelValue":t[12]||=e=>p.onPortConflict=e,label:`On port conflict`,options:[{value:`block`,label:`block — refuse to start`},{value:`warn`,label:`warn — start anyway`}]},null,8,[`modelValue`]),B($L,{modelValue:p.logBufferLines,"onUpdate:modelValue":t[13]||=e=>p.logBufferLines=e,label:`Log buffer lines`,min:50,max:1e5,step:50},null,8,[`modelValue`])]),_:1}),B(YL,{title:`Lifecycle`,description:`Whether the supervisor owns this entry at all.`,columns:2},{default:M(()=>[B(dR,{modelValue:p.enabled,"onUpdate:modelValue":t[14]||=e=>p.enabled=e,label:`Enabled`,hint:`Shows up and can be started at all.`},null,8,[`modelValue`]),B(dR,{modelValue:p.autostart,"onUpdate:modelValue":t[15]||=e=>p.autostart=e,label:`Autostart with up`,hint:`Started when the control plane comes up.`},null,8,[`modelValue`])]),_:1}),B(uB,{restart:m.value,"onUpdate:restart":t[16]||=e=>m.value=e,health:h.value,"onUpdate:health":t[17]||=e=>h.value=e,stop:g.value,"onUpdate:stop":t[18]||=e=>g.value=e},null,8,[`restart`,`health`,`stop`]),B(YL,{title:`Bootstrap`,description:`A one-off command run before the first start — installs, migrations, downloads.`,columns:2},{default:M(()=>[B(dR,{modelValue:p.bootstrapEnabled,"onUpdate:modelValue":t[19]||=e=>p.bootstrapEnabled=e,label:`Run a bootstrap`,hint:`Off means the entry has none and the key is sent as null.`,wide:``},null,8,[`modelValue`]),p.bootstrapEnabled?(I(),L(F,{key:0},[B(cR,{modelValue:p.bootstrapCommand,"onUpdate:modelValue":t[20]||=e=>p.bootstrapCommand=e,label:`Command`,placeholder:`./scripts/install.sh`,class:`font-mono text-xs`},null,8,[`modelValue`]),B($L,{modelValue:p.bootstrapTimeoutMs,"onUpdate:modelValue":t[21]||=e=>p.bootstrapTimeoutMs=e,label:`Timeout (ms)`,min:1e3,step:1e3},null,8,[`modelValue`]),B(dR,{modelValue:p.bootstrapRunOnce,"onUpdate:modelValue":t[22]||=e=>p.bootstrapRunOnce=e,label:`Only once per session`,hint:`Off means every start re-runs it.`},null,8,[`modelValue`])],64)):H(``,!0),p.bootstrapEnabled?(I(),R(rR,{key:1,modelValue:p.bootstrapArgs,"onUpdate:modelValue":t[23]||=e=>p.bootstrapArgs=e,label:`Bootstrap arguments`,rows:2,hint:`One per line; same placeholders as the main arguments.`},null,8,[`modelValue`])):H(``,!0),p.bootstrapEnabled?(I(),R(rR,{key:2,modelValue:p.bootstrapEnv,"onUpdate:modelValue":t[24]||=e=>p.bootstrapEnv=e,label:`Bootstrap environment`,rows:2,hint:`KEY=value per line, exported to the bootstrap process only.`},null,8,[`modelValue`])):H(``,!0)]),_:1})]),s.value?(I(),L(`p`,gB,k(s.value),1)):H(``,!0),z(`div`,_B,[z(`button`,{type:`button`,class:`inline-flex h-8 select-none items-center rounded-control border border-transparent bg-transparent px-2.5 text-xs font-medium whitespace-nowrap text-muted transition-colors duration-150 hover:bg-hover hover:text-ink`,onClick:t[25]||=e=>r(`cancel`)},` Cancel `),z(`button`,{type:`submit`,disabled:o.value||!w.value,class:`inline-flex h-8 select-none items-center gap-1.5 rounded-control border border-transparent bg-accent px-2.5 text-xs font-medium whitespace-nowrap text-accent-ink transition-[filter,opacity] duration-150 hover:brightness-110 disabled:pointer-events-none disabled:opacity-45`},k(o.value?`Saving…`:`Save to servers.config.json`),9,vB)])],32))}}),bB=[`aria-label`],xB=N({__name:`CopyButton`,props:{value:{},label:{default:`Copy`},class:{default:void 0}},setup(e){let t=e,n=A(!1),r=null;async function i(){try{await navigator.clipboard.writeText(t.value),n.value=!0,r&&clearTimeout(r),r=setTimeout(()=>{n.value=!1},1400)}catch{}}return ze(()=>{r&&clearTimeout(r)}),(e,r)=>(I(),L(`button`,{type:`button`,class:O(j(Q)(`grid size-6 shrink-0 place-items-center rounded-control text-faint transition-colors duration-150 hover:bg-hover hover:text-ink`,j(ZP),t.class)),"aria-label":n.value?`Copied`:t.label,onClick:i},[n.value?(I(),R(j(Sv),{key:0,class:`size-3.5 text-ok`})):(I(),R(j(Nv),{key:1,class:`size-3.5`}))],10,bB))}}),SB={key:0,class:`mx-auto max-w-3xl p-4 sm:p-5`},CB={key:1,class:`mx-auto max-w-7xl space-y-5 p-4 sm:p-5`},wB={class:`font-mono text-xs text-faint`},TB=[`href`],EB={class:`px-2 py-1.5`},DB={class:`flex flex-col gap-0.5`},OB=[`onClick`],kB={class:`space-y-2`},AB={key:0,class:`rounded-control border border-danger/25 bg-danger-soft px-3 py-2 text-2xs leading-4 text-danger`},jB={key:1,class:`rounded-control border border-warn/25 bg-warn-soft px-3 py-2 text-2xs leading-4 text-warn`},MB={class:`grid grid-cols-2 gap-x-4 gap-y-3 p-4 sm:grid-cols-4 lg:grid-cols-6`},NB={class:`truncate text-2xs text-faint`},PB={class:`truncate font-mono text-xs tabular-nums text-ink capitalize`},FB={class:`grid gap-4 sm:grid-cols-3`},IB={class:`flex items-center justify-between gap-3`},LB=[`title`],RB={key:1,class:`space-y-4`},zB={key:0,class:`max-h-96 overflow-auto rounded-control border border-line bg-page/60 p-3 font-mono text-2xs leading-5 text-muted`},BB={key:1,class:`grid grid-cols-1 gap-x-6 gap-y-2 sm:grid-cols-2`},VB={class:`text-2xs text-faint`},HB=[`title`],UB={key:2,class:`grid gap-4 lg:grid-cols-3`},WB={key:0,class:`flex flex-wrap gap-1.5`},GB={key:1,class:`text-xs text-muted`},KB=N({__name:`ServerDetailView`,setup(e){let t=Dg(),{serverById:n,now:r,seriesOf:i,start:a,stop:o,restart:s,setEnabled:c,setAutostart:l,setBind:u,clearLogs:d,remove:f}=lM(),p=U(()=>String(t.params.id??``)),m=U(()=>n(p.value)),h=U(()=>m.value?.config),g=U(()=>{let e=h.value;return e?.label&&e.label.length>0?e.label:p.value}),_=A(`output`),v=A(!1),y=A(!1),b=A(!1),x=A(!1),S=cM(()=>m.value?p.value:null),C=A(0),w=U(()=>(C.value,S.lines())),T=0,ee=setInterval(()=>{let e=S.count.value;e!==T&&(T=e,C.value+=1)},250);ze(()=>clearInterval(ee));let E=U(()=>i(p.value)),te=U(()=>(C.value,E.value.cpu)),ne=U(()=>E.value.rss),re=U(()=>E.value.probe),ie=U(()=>{let e=m.value;return!e||e.startedAt===null||e.status!==`running`?`—`:nI(r.value-e.startedAt)}),ae=U(()=>m.value?.status===`running`),oe=U(()=>{let e=m.value;if(!e)return[];let t=[{key:`pid`,label:`PID`,value:e.pid===null?`—`:String(e.pid)},{key:`uptime`,label:`Uptime`,value:ie.value},{key:`status`,label:`Status`,value:e.status},{key:`health`,label:`Health`,value:e.health},{key:`port`,label:`Port state`,value:e.portState},{key:`probe`,label:`Last probe`,value:e.responseMs===null?`—`:`${e.responseMs} ms`},{key:`rss`,label:`RSS`,value:sI(e.resources?.rssBytes??null)},{key:`procs`,label:`Processes`,value:e.resources?String(e.resources.processes):`—`},{key:`restarts`,label:`Restarts`,value:`${e.restarts}/${e.maxRetries}`},{key:`up24`,label:`24h uptime`,value:iI(e.history.uptimeRatio)},{key:`crashes`,label:`24h crashes`,value:String(e.history.crashes)},{key:`lastcrash`,label:`Last crash`,value:aI(e.history.lastCrashAt,r.value)}];return e.nextRetryAt!==null&&t.push({key:`retry`,label:`Next attempt`,value:rI(e.nextRetryAt,r.value)}),e.unhealthySince!==null&&t.push({key:`unhealthy`,label:`Unhealthy for`,value:nI(r.value-e.unhealthySince)}),t}),se=U(()=>JSON.stringify(h.value??{},null,2)),ce=U(()=>h.value?`${h.value.command} ${h.value.args.join(` `)}`.trim():``),D=U(()=>{let e=h.value?.bind??`local`,t=[{value:`local`,label:`127.0.0.1 — this machine only`},{value:`lan`,label:`0.0.0.0 — every interface`}];return e!==`local`&&e!==`lan`&&t.push({value:e,label:`${e} — custom`}),t});async function le(e){x.value=!0;try{await{start:a,stop:o,restart:s}[e](p.value)}finally{x.value=!1}}async function ue(){b.value=!1,await f(p.value)}let de=U(()=>rj(p.value,`${p.value}.log`)),fe=[{value:`output`,label:`Live output`},{value:`config`,label:`Configuration`},{value:`history`,label:`History`}];return(e,t)=>{let n=pa(`RouterLink`);return m.value?(I(),L(`div`,CB,[B(_z,{title:g.value,"back-to":`/servers`,"back-label":`All servers`},{badges:M(()=>[B(LR,{status:m.value.status,health:m.value.health},null,8,[`status`,`health`]),z(`span`,wB,k(m.value.id),1),m.value.url?(I(),L(`a`,{key:0,href:m.value.url,target:`_blank`,rel:`noreferrer`,class:`inline-flex items-center gap-1 font-mono text-xs text-accent hover:underline`},[V(k(m.value.url)+` `,1),B(j(Lv),{class:`size-3`})],8,TB)):H(``,!0)]),actions:M(()=>[B($,{variant:`primary`,disabled:x.value||!h.value?.enabled||[`running`,`starting`].includes(m.value.status),onClick:t[0]||=e=>le(`start`)},{default:M(()=>[B(j(ey),{class:`size-3.5`}),t[16]||=V(`Start `,-1)]),_:1},8,[`disabled`]),B($,{disabled:x.value||m.value.status===`stopped`,onClick:t[1]||=e=>le(`stop`)},{default:M(()=>[B(j(py),{class:`size-3.5`}),t[17]||=V(`Stop `,-1)]),_:1},8,[`disabled`]),B($,{disabled:x.value||!h.value?.enabled,onClick:t[2]||=e=>le(`restart`)},{default:M(()=>[B(j(ay),{class:`size-3.5`}),t[18]||=V(`Restart `,-1)]),_:1},8,[`disabled`]),B(rF,{label:`Edit every field of this entry`},{default:M(()=>[B($,{variant:v.value?`secondary`:`ghost`,onClick:t[3]||=e=>{v.value=!v.value,_.value=`config`}},{default:M(()=>[B(j($v),{class:`size-3.5`}),V(k(v.value?`Close editor`:`Edit`),1)]),_:1},8,[`variant`])]),_:1}),B(j(Hh),null,{default:M(()=>[B(j(qh),{"as-child":``},{default:M(()=>[B($,{variant:`ghost`,size:`icon-sm`,"aria-label":`More actions for ${g.value}`},{default:M(()=>[B(j(Iv),{class:`size-4`})]),_:1},8,[`aria-label`])]),_:1}),B(j(Gh),null,{default:M(()=>[B(j(Uh),{"side-offset":4,align:`end`,class:`z-50 min-w-56 rounded-panel border border-line bg-raise p-1 shadow-float data-[state=open]:animate-[hh-fade-in_120ms_ease-out]`},{default:M(()=>[B(j(Wh),{class:`flex cursor-pointer items-center gap-2 rounded-control px-2 py-1.5 text-xs text-muted outline-none data-[highlighted]:bg-hover data-[highlighted]:text-ink`,onSelect:t[4]||=e=>j(c)(m.value.id,!m.value.config.enabled)},{default:M(()=>[V(k(h.value?.enabled?`Disable server`:`Enable server`),1)]),_:1}),B(j(Wh),{class:`flex cursor-pointer items-center gap-2 rounded-control px-2 py-1.5 text-xs text-muted outline-none data-[highlighted]:bg-hover data-[highlighted]:text-ink`,onSelect:t[5]||=e=>j(l)(m.value.id,!m.value.config.autostart)},{default:M(()=>[V(k(h.value?.autostart?`Remove from autostart`:`Autostart with the panel`),1)]),_:1}),B(j(Kh),{class:`my-1 h-px bg-line`}),z(`div`,EB,[t[19]||=z(`p`,{class:`mb-1 text-2xs text-faint`},` Bind `,-1),z(`div`,DB,[(I(!0),L(F,null,ya(D.value,e=>(I(),L(`button`,{key:e.value,type:`button`,class:O([`rounded-control px-2 py-1 text-left font-mono text-2xs transition-colors duration-150 hover:bg-hover`,e.value===h.value?.bind?`text-accent`:`text-muted`]),onClick:t=>j(u)(m.value.id,e.value)},k(e.label),11,OB))),128))])]),B(j(Kh),{class:`my-1 h-px bg-line`}),B(j(Wh),{class:`flex cursor-pointer items-center gap-2 rounded-control px-2 py-1.5 text-xs text-danger outline-none data-[highlighted]:bg-danger-soft`,onSelect:t[6]||=e=>b.value=!0},{default:M(()=>[B(j(vy),{class:`size-3.5`}),t[20]||=V(` Remove server `,-1)]),_:1})]),_:1})]),_:1})]),_:1})]),_:1},8,[`title`]),z(`div`,kB,[m.value.lastError?(I(),L(`p`,AB,k(m.value.lastError),1)):H(``,!0),m.value.health===`unhealthy`?(I(),L(`p`,jB,[t[21]||=V(` The health probe has not passed `,-1),m.value.unhealthySince===null?H(``,!0):(I(),L(F,{key:0},[V(` for `+k(j(nI)(j(r)-m.value.unhealthySince)),1)],64)),t[22]||=V(`. `,-1)])):H(``,!0)]),B(xz,{class:`p-0`},{default:M(()=>[z(`dl`,MB,[(I(!0),L(F,null,ya(oe.value,e=>(I(),L(`div`,{key:e.key,class:`min-w-0`},[z(`dt`,NB,k(e.label),1),z(`dd`,PB,k(e.value),1)]))),128))])]),_:1}),B(xz,null,{header:M(()=>[...t[23]||=[z(`h2`,{class:`text-sm font-semibold text-ink`},` Telemetry `,-1),z(`p`,{class:`text-2xs text-muted`},` Sampled every 5 s while this page is open. `,-1)]]),default:M(()=>[z(`div`,FB,[B(NR,{label:`CPU`,values:te.value,format:j(cI),max:100,"color-class":`text-cpu`,height:40},null,8,[`values`,`format`]),B(NR,{label:`Memory (RSS)`,values:ne.value,format:j(sI),"color-class":`text-mem`,height:40},null,8,[`values`,`format`]),B(NR,{label:`Health probe`,values:re.value,format:e=>e===null?`—`:`${e} ms`,"color-class":`text-probe`,height:40},null,8,[`values`,`format`])])]),_:1}),z(`div`,IB,[B(hI,{modelValue:_.value,"onUpdate:modelValue":t[7]||=e=>_.value=e,options:fe,size:`md`},null,8,[`modelValue`]),z(`p`,{class:`hidden font-mono text-2xs text-faint sm:block`,title:ce.value},k(ce.value),9,LB)]),_.value===`output`?(I(),R(jI,{key:0,lines:w.value,version:C.value,live:``,clearable:``,"download-url":m.value.config.logBufferLines>0?de.value:void 0,"download-name":`${m.value.id}.log`,"empty-hint":ae.value?`Waiting for the first line…`:`This server has not written anything yet.`,"min-height":`22rem`,onClear:t[8]||=e=>j(d)(m.value.id)},null,8,[`lines`,`version`,`download-url`,`download-name`,`empty-hint`])):_.value===`config`?(I(),L(`div`,RB,[v.value?(I(),R(yB,{key:0,"server-id":m.value.id,config:m.value.config,onSaved:t[9]||=e=>v.value=!1,onCancel:t[10]||=e=>v.value=!1},null,8,[`server-id`,`config`])):(I(),R(xz,{key:1},{header:M(()=>[...t[24]||=[z(`h2`,{class:`text-sm font-semibold text-ink`},` Effective configuration `,-1),z(`p`,{class:`text-2xs text-muted`},` Defaults from Settings are already merged into these values. `,-1)]]),actions:M(()=>[B($,{size:`xs`,variant:`ghost`,onClick:t[11]||=e=>y.value=!y.value},{default:M(()=>[B(j(jv),{class:`size-3`}),V(k(y.value?`Fields`:`Raw JSON`),1)]),_:1}),B(xB,{value:se.value,label:`Copy the JSON config`},null,8,[`value`]),B($,{size:`xs`,variant:`primary`,onClick:t[12]||=e=>v.value=!0},{default:M(()=>[B(j($v),{class:`size-3`}),t[25]||=V(`Edit `,-1)]),_:1})]),default:M(()=>[y.value?(I(),L(`pre`,zB,k(se.value),1)):(I(),L(`dl`,BB,[(I(!0),L(F,null,ya([{label:`command`,value:h.value?.command},{label:`args`,value:h.value?.args.join(` `)||`—`},{label:`cwd`,value:h.value?.cwd},{label:`env`,value:Object.keys(h.value?.env??{}).join(`, `)||`—`},{label:`data envs`,value:Object.entries(h.value?.dataEnvs??{}).map(([e,t])=>`${e}=${t}`).join(`, `)||`—`},{label:`env file`,value:h.value?.envFile||`—`},{label:`port · bind`,value:`${h.value?.port??`none`} · ${h.value?.bind}`},{label:`on port conflict`,value:h.value?.onPortConflict},{label:`log buffer`,value:`${h.value?.logBufferLines} lines`},{label:`rtss limit`,value:h.value?.resources.maxRssBytes?j(sI)(h.value.resources.maxRssBytes):`none`},{label:`health`,value:h.value?.health.enabled?`${h.value.health.mode} every ${h.value.health.intervalMs} ms`:`off`},{label:`restart`,value:h.value?.restart.enabled?`${h.value.restart.maxRetries} retries, ×${h.value.restart.factor}`:`off`},{label:`stop`,value:h.value?`${h.value.stop.signal} after ${h.value.stop.graceMs} ms`:`—`},{label:`bootstrap`,value:h.value?.bootstrap?`${h.value.bootstrap.command} ${h.value.bootstrap.args.join(` `)}`:`none`},{label:`backup paths`,value:h.value?.backupPaths.join(`, `)||`—`},{label:`depends on`,value:h.value?.dependsOn.join(`, `)||`—`}],e=>(I(),L(`div`,{key:e.label,class:`min-w-0 border-b border-line-soft pb-1.5 last:border-0`},[z(`dt`,VB,k(e.label),1),z(`dd`,{class:`truncate font-mono text-xs text-ink`,title:String(e.value)},k(e.value),9,HB)]))),128))]))]),_:1}))])):(I(),L(`div`,UB,[B(xz,{class:`lg:col-span-2`},{header:M(()=>[...t[26]||=[z(`h2`,{class:`text-sm font-semibold text-ink`},` Event history `,-1),z(`p`,{class:`text-2xs text-muted`},` Kept by the panel for 24 hours. `,-1)]]),default:M(()=>[B(UL,{servers:[m.value],now:j(r),limit:30},null,8,[`servers`,`now`])]),_:1}),B(xz,null,{header:M(()=>[...t[27]||=[z(`h2`,{class:`text-sm font-semibold text-ink`},` Dependencies `,-1)]]),default:M(()=>[h.value&&h.value.dependsOn.length>0?(I(),L(`div`,WB,[(I(!0),L(F,null,ya(h.value.dependsOn,e=>(I(),R(n,{key:e,to:`/servers/${e}`,class:`rounded-full border border-line px-2 py-0.5 font-mono text-2xs text-muted transition-colors duration-150 hover:border-accent/40 hover:text-accent`},{default:M(()=>[V(k(e),1)]),_:2},1032,[`to`]))),128))])):(I(),L(`p`,GB,[...t[28]||=[V(` Starts on its own. Add ids under `,-1),z(`code`,null,`dependsOn`,-1),V(` to order it after other servers. `,-1)]]))]),_:1})])),B(HI,{open:b.value,"onUpdate:open":t[14]||=e=>b.value=e,title:`Remove this server?`,description:`${g.value} is removed from servers.config.json. Its process is stopped and its persisted logs stay on disk.`,width:`w-[min(92vw,26rem)]`},{footer:M(()=>[B($,{variant:`ghost`,onClick:t[13]||=e=>b.value=!1},{default:M(()=>[...t[29]||=[V(` Keep it `,-1)]]),_:1}),B($,{variant:`danger`,onClick:ue},{default:M(()=>[B(j(vy),{class:`size-3.5`}),t[30]||=V(`Remove `,-1)]),_:1})]),default:M(()=>[t[31]||=z(`p`,{class:`text-xs text-muted`},` This cannot be undone from the panel — the entry has to be added back by hand or from a backup. `,-1)]),_:1},8,[`open`,`description`])])):(I(),L(`div`,SB,[B(LI,{title:`That server is gone`,description:`${p.value} is not in servers.config.json any more. It may have been removed from another tab.`},{action:M(()=>[B(n,{to:`/servers`},{default:M(()=>[B($,{variant:`primary`},{default:M(()=>[...t[15]||=[V(` Back to servers `,-1)]]),_:1})]),_:1})]),_:1},8,[`description`])]))}}}),qB={class:`mx-auto max-w-7xl space-y-5 p-4 sm:p-5`},JB={class:`flex flex-wrap items-center gap-2`},YB={class:`relative ml-auto w-full sm:w-64`},XB={key:0,class:`grid gap-4 lg:grid-cols-2`},ZB={key:3,class:`grid gap-4 lg:grid-cols-2`},QB=N({__name:`ServersView`,setup(e){let{servers:t,appState:n,now:r,seriesOf:i,startAll:a,stopAll:o}=lM(),s=A(!1),c=A(``),l=A(`all`),u=[{value:`all`,label:`All`},{value:`running`,label:`Running`},{value:`stopped`,label:`Stopped`},{value:`attention`,label:`Attention`},{value:`disabled`,label:`Disabled`}],d=U(()=>n.value===null),f=U(()=>{let e=c.value.trim().toLowerCase();return t.value.filter(t=>l.value===`running`&&t.status!==`running`||l.value===`stopped`&&![`stopped`,`stopping`].includes(t.status)||l.value===`disabled`&&t.config.enabled||l.value===`attention`&&t.status!==`crashed`&&t.status!==`conflict`&&t.health!==`unhealthy`?!1:e.length===0||`${t.id} ${t.config.label??``} ${t.config.command} ${t.config.port??``}`.toLowerCase().includes(e))}),p=U(()=>({all:t.value.length,running:t.value.filter(e=>e.status===`running`).length,attention:t.value.filter(e=>e.status===`crashed`||e.status===`conflict`||e.health===`unhealthy`).length}));return(e,n)=>(I(),L(`div`,qB,[B(_z,{title:`Servers`,description:`Every entry in servers.config.json, with live resources, health and history.`},{actions:M(()=>[B($,{variant:`ghost`,onClick:n[0]||=e=>j(a)()},{default:M(()=>[B(j(ey),{class:`size-3.5`}),n[9]||=V(`Start all `,-1)]),_:1}),B($,{variant:`ghost`,onClick:n[1]||=e=>j(o)()},{default:M(()=>[B(j(py),{class:`size-3.5`}),n[10]||=V(`Stop all `,-1)]),_:1}),B($,{variant:`primary`,onClick:n[2]||=e=>s.value=!0},{default:M(()=>[B(j(ty),{class:`size-3.5`}),n[11]||=V(`Add server `,-1)]),_:1})]),_:1}),z(`div`,JB,[B(hI,{modelValue:l.value,"onUpdate:modelValue":n[3]||=e=>l.value=e,options:u.map(e=>({...e,label:`${e.label}${e.value===`all`?` ${p.value.all}`:e.value===`running`?` ${p.value.running}`:e.value===`attention`&&p.value.attention>0?` ${p.value.attention}`:``}`}))},null,8,[`modelValue`,`options`]),z(`div`,YB,[B(j(sy),{class:`pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-faint`}),xr(z(`input`,{"onUpdate:modelValue":n[4]||=e=>c.value=e,type:`search`,placeholder:`Filter by id, label, command or port`,class:O([`pl-8`,[j(eF)]]),"aria-label":`Filter servers`},null,2),[[Yl,c.value]]),c.value.length>0?(I(),L(`button`,{key:0,type:`button`,class:`absolute right-1.5 top-1/2 grid size-6 -translate-y-1/2 place-items-center rounded-control text-faint hover:text-ink`,"aria-label":`Clear the filter`,onClick:n[5]||=e=>c.value=``},[B(j(by),{class:`size-3.5`})])):H(``,!0)])]),d.value?(I(),L(`div`,XB,[(I(),L(F,null,ya(4,e=>B(YI,{key:e,class:`h-72 w-full`,rounded:`rounded-panel`})),64))])):j(t).length===0?(I(),R(LI,{key:1,title:`No servers configured`,description:`Add the first process this panel should supervise. Its command, args, env and working directory all live in the config file.`},{icon:M(()=>[B(j(cy),{class:`size-4`})]),action:M(()=>[B($,{variant:`primary`,onClick:n[6]||=e=>s.value=!0},{default:M(()=>[B(j(ty),{class:`size-3.5`}),n[12]||=V(`Add a server `,-1)]),_:1})]),_:1})):f.value.length===0?(I(),R(LI,{key:2,compact:``,title:`Nothing matches that filter`,description:`Try a different status or clear the search.`},{action:M(()=>[B($,{onClick:n[7]||=e=>{c.value=``,l.value=`all`}},{default:M(()=>[...n[13]||=[V(` Clear filters `,-1)]]),_:1})]),_:1})):(I(),L(`div`,ZB,[(I(!0),L(F,null,ya(f.value,e=>(I(),R(uz,{key:e.id,server:e,series:j(i)(e.id),now:j(r)},null,8,[`server`,`series`,`now`]))),128))])),B(gR,{open:s.value,"onUpdate:open":n[8]||=e=>s.value=e},null,8,[`open`])]))}}),$B=N({__name:`ConfirmButton`,props:{label:{},confirmLabel:{default:`Confirm`},variant:{default:`danger-ghost`},confirmVariant:{default:`danger`},size:{default:`sm`},disabled:{type:Boolean,default:!1},loading:{type:Boolean,default:!1}},emits:[`confirm`],setup(e,{emit:t}){let n=e,r=t,i=A(!1),a=null;function o(){a&&clearTimeout(a),a=null,i.value=!1}function s(){if(!i.value){i.value=!0,a=setTimeout(o,5e3);return}o(),r(`confirm`)}return ze(o),(e,t)=>(I(),R($,{variant:i.value?n.confirmVariant:n.variant,size:n.size,disabled:n.disabled,loading:n.loading,onClick:s},{default:M(()=>[V(k(i.value?n.confirmLabel:n.label),1)]),_:1},8,[`variant`,`size`,`disabled`,`loading`]))}}),eV={class:`min-w-0 flex-1`},tV={key:0,class:`text-xs font-semibold`},nV=N({__name:`Notice`,props:{tone:{default:`info`},title:{default:void 0},class:{default:void 0}},setup(e){let t=e,n={ok:Ov,warn:yy,danger:Zv,info:Hv,neutral:Hv,accent:ly},r=U(()=>n[t.tone]);return(e,n)=>(I(),L(`div`,{class:O(j(Q)(`flex items-start gap-2 rounded-panel border px-3 py-2`,j(XN)[t.tone],t.class))},[(I(),R(ha(r.value),{class:`mt-0.5 size-3.5 shrink-0`,"aria-hidden":`true`})),z(`div`,eV,[t.title?(I(),L(`p`,tV,k(t.title),1)):H(``,!0),z(`div`,{class:O(j(Q)(`text-xs leading-relaxed text-ink/85`,t.title&&`mt-0.5`))},[P(e.$slots,`default`)],2)])],2))}});async function rV(e,t=8e3){let n=Date.now()+t;for(;;){try{if((await fetch(`${e}/api/auth/session`,{cache:`no-store`})).ok)return!0}catch{}if(Date.now()>n)return!1;await new Promise(e=>setTimeout(e,250))}}var iV={enabled:!0,maxRetries:3,baseDelayMs:1e3,factor:2,maxDelayMs:3e4,resetAfterMs:6e4},aV={enabled:!0,mode:`port`,http:{path:`/`,method:`GET`,expectStatusBelow:400,expectBody:``},intervalMs:5e3,timeoutMs:1500,unhealthyThreshold:3,forceRestartAfterMs:0,startTimeoutMs:2e4},oV={signal:`SIGTERM`,killGroup:!0,graceMs:5e3,killPortHolders:!1};function sV(e){return{...e,http:{...e.http}}}function cV(){return{control:{label:`Stock UI`,port:3999,host:`local`,openBrowser:!1,tlsEnabled:!1},auth:{enabled:!1,sessionTtlMs:6048e5,cookieSecure:`auto`,trustProxy:!1,maxLoginAttempts:5,lockoutMs:6e4},defaults:{enabled:!0,autostart:!1,bind:`local`,onPortConflict:`block`,logBufferLines:500,restart:{...iV},health:sV(aV),stop:{...oV}},logs:{persist:!0,maxBytes:2e6,keep:3},host:{enabled:!0,intervalMs:15e3,diskPaths:`.`,diskUsedPercent:90,memoryUsedPercent:90,swapUsedPercent:50,loadPerCpu:2,tempCelsius:85},backups:{enabled:!0,keep:5,includePaths:``},telegram:{enabled:!1,chatId:``,cooldownMs:12e4,onCrash:!0,onUnhealthy:!0,onForcedRestart:!0,onRecovered:!1,onHost:!0}}}function lV(e){return{enabled:e.enabled,sessionTtlMs:e.sessionTtlMs,cookieSecure:e.cookieSecure,trustProxy:e.trustProxy,maxLoginAttempts:e.maxLoginAttempts,lockoutMs:e.lockoutMs}}function uV(e){return{label:e.label,port:e.port,host:e.host,openBrowser:e.openBrowser,tlsEnabled:e.tls.enabled}}function dV(e){return{enabled:e.enabled,chatId:e.chatId,cooldownMs:e.cooldownMs,onCrash:e.onCrash,onUnhealthy:e.onUnhealthy,onForcedRestart:e.onForcedRestart,onRecovered:e.onRecovered,onHost:e.onHost}}function fV(e){return e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)}function pV(e){return{enabled:e.enabled,intervalMs:e.intervalMs,diskPaths:e.diskPaths.join(`, `),diskUsedPercent:e.diskUsedPercent,memoryUsedPercent:e.memoryUsedPercent,swapUsedPercent:e.swapUsedPercent,loadPerCpu:e.loadPerCpu,tempCelsius:e.tempCelsius}}function mV(e){return{enabled:e.enabled,keep:e.keep,includePaths:e.includePaths.join(`, `)}}function hV(e,t){return iB(e,{...t,diskPaths:fV(t.diskPaths)})}function gV(e,t){return iB(e,{...t,includePaths:fV(t.includePaths)})}function _V(e,t){return iB({label:e.label,port:e.port,host:e.host,openBrowser:e.openBrowser,auth:lV(e.auth),tls:{enabled:e.tls.enabled}},{label:t.control.label,port:t.control.port,host:t.control.host,openBrowser:t.control.openBrowser,auth:{...t.auth},tls:{enabled:t.control.tlsEnabled}},[`auth`,`tls`])}function vV(e,t){let n=iB(e,t,[`restart`,`health`,`stop`]),r=n.health;return r!==void 0&&`http`in r&&JSON.stringify(e.health.http)===JSON.stringify(t.health.http)&&(delete r.http,Object.keys(r).length===0&&delete n.health),n}function yV(e,t){return iB(e,t)}function bV(e,t){return iB({telegram:dV(e)},{telegram:{...t}},[`telegram`])}function xV(e){return e.liveAvailable?!e.hydrated||e.changedCount===0:!1}function SV(e,t=``){return typeof e!=`object`||!e||Array.isArray(e)?[{path:t,value:e}]:Object.entries(e).flatMap(([e,n])=>SV(n,t.length===0?e:`${t}.${e}`))}function CV(e,t){let n=new Map(SV(t).map(e=>[e.path,e.value]));return SV(e).map(e=>({path:e.path,from:n.get(e.path),to:e.value}))}function wV(e){let t=0;for(let n of Object.values(e))t+=typeof n==`object`&&n&&!Array.isArray(n)?wV(n):1;return t}function TV(e,t){return e.label!==t.control.label||e.port!==t.control.port||e.host!==t.control.host||e.openBrowser!==t.control.openBrowser}function EV(e,t){return e.tls.enabled!==t.control.tlsEnabled}function DV(e,t){return wV(iB({...lV(e.auth)},{...t.auth}))>0}function OV(e,t,n){return U({get:e,set:e=>{t(e===null||Number.isNaN(e)?n:e)}})}async function kV(e){if(!e.rebinding)return null;let t=e.targetUrl??e.control.url;return t.length===0||t===window.location.origin?null:await rV(t)?(window.location.replace(t),null):`The control panel did not answer on ${t}. Check the console for the new listener.`}var AV={class:`space-y-2`},jV={class:`flex flex-wrap items-center gap-2 sm:col-span-2`},MV=N({__name:`AuthenticationSection`,props:Ba({view:{},dirty:{type:Boolean}},{auth:{required:!0},authModifiers:{}}),emits:Ba([`reset`],[`update:auth`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`auth`),a=OV(()=>i.value.sessionTtlMs,e=>i.value.sessionTtlMs=e,6048e5),o=OV(()=>i.value.maxLoginAttempts,e=>i.value.maxLoginAttempts=e,5),s=OV(()=>i.value.lockoutMs,e=>i.value.lockoutMs=e,6e4),c=U(()=>n.view?.auth.passwordSet===!0),l=U(()=>n.view?.auth.blockedReason??null),u=U(()=>n.view?.auth.exposed===!0),d=U(()=>n.view?.auth.usingDefaultPassword===!0),f=U(()=>i.value.enabled&&!c.value),{setPassword:p,clearPassword:m}=Dj(),h=A(``),g=A(``),_=A(``),v=A(null),y=A(null),b=A(!1),x=U(()=>_.value.length>0&&_.value!==g.value),S=U(()=>g.value.length>0&&g.value.length<8),C=U(()=>g.value.length>0&&!x.value&&(!c.value||h.value.length>0));async function w(){if(C.value&&!b.value){b.value=!0,y.value=null,v.value=null;try{let e=await p(c.value?h.value:void 0,g.value);if(e!==null){y.value=e;return}h.value=``,g.value=``,_.value=``,v.value=`Password updated. Other sessions were signed out.`}finally{b.value=!1}}}async function T(){y.value=null,v.value=null;let e=await m();e===null?v.value=`Password cleared and authentication disabled.`:y.value=e}return(e,t)=>(I(),L(F,null,[B(YL,{title:`Access policy`,description:`Authentication applies whether the panel is on loopback or exposed to the network.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[10]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value.enabled,"onUpdate:modelValue":t[1]||=e=>i.value.enabled=e,label:`Require a password`,hint:`Every request needs a signed-in session.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B($L,{modelValue:j(a),"onUpdate:modelValue":t[2]||=e=>mn(a)?a.value=e:null,label:`Session lifetime (ms)`,min:6e4,hint:`604800000 is one week.`},null,8,[`modelValue`]),B(JI,{modelValue:i.value.cookieSecure,"onUpdate:modelValue":t[3]||=e=>i.value.cookieSecure=e,label:`Cookie Secure`,options:[{value:`auto`,label:`Auto — only over HTTPS`},{value:`always`,label:`Always`},{value:`never`,label:`Never — plain HTTP`}]},null,8,[`modelValue`]),B(dR,{modelValue:i.value.trustProxy,"onUpdate:modelValue":t[4]||=e=>i.value.trustProxy=e,label:`Behind a trusted reverse proxy`,hint:`Trust x-forwarded-* headers for the client address and protocol.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B($L,{modelValue:j(o),"onUpdate:modelValue":t[5]||=e=>mn(o)?o.value=e:null,label:`Max login attempts`,min:1,hint:`Failures before a temporary lockout.`},null,8,[`modelValue`]),B($L,{modelValue:j(s),"onUpdate:modelValue":t[6]||=e=>mn(s)?s.value=e:null,label:`Lockout (ms)`,min:1e3},null,8,[`modelValue`])]),_:1}),z(`div`,AV,[d.value?(I(),R(nV,{key:0,tone:`warn`,title:`The default password is still in use`},{default:M(()=>[...t[11]||=[V(` Set your own password below. Binding beyond `,-1),z(`code`,null,`127.0.0.1`,-1),V(` stays refused until you do. `,-1)]]),_:1})):H(``,!0),l.value?(I(),R(nV,{key:1,tone:`danger`,title:`This listener is being refused`},{default:M(()=>[V(k(l.value),1)]),_:1})):f.value?(I(),R(nV,{key:2,tone:`warn`,title:`Authentication is on but no password is set`},{default:M(()=>[...t[12]||=[V(` Nothing is actually required yet, and binding beyond `,-1),z(`code`,null,`127.0.0.1`,-1),V(` stays refused until a password exists. `,-1)]]),_:1})):u.value?(I(),R(nV,{key:3,tone:`warn`,title:`Reachable beyond 127.0.0.1`},{default:M(()=>[...t[13]||=[V(` Keep the password strong, or put a TLS-terminating proxy in front of the panel. `,-1)]]),_:1})):H(``,!0)]),z(`form`,{class:`mt-3`,onSubmit:uu(w,[`prevent`])},[B(YL,{title:c.value?`Change password`:`Set a password`,description:`Stored as a scrypt hash in the secrets file, never in the config.`},{default:M(()=>[c.value?(I(),R(cR,{key:0,modelValue:h.value,"onUpdate:modelValue":t[7]||=e=>h.value=e,type:`password`,autocomplete:`current-password`,label:`Current password`},null,8,[`modelValue`])):H(``,!0),B(cR,{modelValue:g.value,"onUpdate:modelValue":t[8]||=e=>g.value=e,type:`password`,autocomplete:`new-password`,label:`New password`},null,8,[`modelValue`]),B(cR,{modelValue:_.value,"onUpdate:modelValue":t[9]||=e=>_.value=e,type:`password`,autocomplete:`new-password`,label:`Repeat new password`,error:x.value?`The two passwords do not match.`:null},null,8,[`modelValue`,`error`]),z(`div`,jV,[B($,{type:`submit`,variant:`primary`,disabled:!C.value,loading:b.value},{default:M(()=>[V(k(c.value?`Update password`:`Set password and enable auth`),1)]),_:1},8,[`disabled`,`loading`]),c.value?(I(),R($B,{key:0,label:`Clear password`,"confirm-label":`Confirm clear`,"confirm-variant":`danger`,disabled:b.value,onConfirm:T},null,8,[`disabled`])):H(``,!0)]),S.value||y.value||v.value?(I(),L(F,{key:1},[S.value?(I(),R(nV,{key:0,tone:`warn`,title:`That is a short password`,class:`sm:col-span-2`},{default:M(()=>[...t[14]||=[V(` It is allowed, but longer is the only thing standing between the network and this panel. `,-1)]]),_:1})):H(``,!0),y.value?(I(),R(nV,{key:1,tone:`danger`,class:`sm:col-span-2`},{default:M(()=>[V(k(y.value),1)]),_:1})):H(``,!0),v.value?(I(),R(nV,{key:2,tone:`ok`,class:`sm:col-span-2`},{default:M(()=>[V(k(v.value),1)]),_:1})):H(``,!0)],64)):H(``,!0)]),_:1},8,[`title`])],32)],64))}}),NV={class:`space-y-3`},PV={key:0,class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},FV={class:`flex items-center gap-1.5 rounded-control border border-line bg-page/60 px-2.5 py-1.5`},IV={class:`min-w-0 flex-1 truncate font-mono text-xs text-ink`},LV={class:`text-2xs leading-4 text-faint`},RV={key:0,class:`space-y-2 sm:col-span-2`},zV={key:1,class:`text-xs text-muted sm:col-span-2`},BV={key:2,class:`divide-y divide-line-soft sm:col-span-2`},VV={class:`min-w-0`},HV={class:`truncate font-mono text-xs text-ink`},UV={class:`mt-0.5 text-2xs leading-4 text-muted`},WV={class:`sm:col-span-2`},GV={key:0,class:`space-y-2 sm:col-span-2`},KV={key:1,class:`sm:col-span-2`},qV={key:2,class:`divide-y divide-line-soft sm:col-span-2`},JV={class:`min-w-0 flex-1`},YV={class:`flex items-center gap-2`},XV={class:`truncate font-mono text-xs text-ink`},ZV={class:`mt-0.5 font-mono text-2xs tabular-nums text-muted`},QV={class:`flex items-center gap-1.5`},$V=[`href`],eH={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},tH={key:0,class:`font-mono text-2xs text-muted`},nH={class:`sm:col-span-2`},rH={key:0,class:`sm:col-span-2`},iH={class:`mt-2`},aH={class:`mt-2 max-w-sm`},oH={class:`mt-2`},sH={class:`mt-2`},cH={class:`mt-1 max-h-64 space-y-0.5 overflow-auto`},lH={class:`mt-2 flex flex-wrap items-center gap-2`},uH={class:`mt-2`},dH={class:`mt-2`},fH={key:1,class:`space-y-2 sm:col-span-2`},pH=N({__name:`BackupsSection`,props:Ba({state:{},policy:{},configPath:{},dirty:{type:Boolean}},{form:{required:!0},formModifiers:{}}),emits:Ba([`reset`],[`update:form`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`form`),a=lM(),o=OV(()=>i.value.keep,e=>i.value.keep=e,5),s=A(!1),c=A(null),l=A(null),u=A(``),d=A(null),f=A(null),p=A(``),m=A(null),h=U(()=>n.state?.files??[]),g=U(()=>n.state?.paths??[]),_=U(()=>d.value?.items.filter(e=>e.selected).length??0),v=U(()=>d.value?.items.some(e=>e.id===`config`&&e.selected)??!1),y=Q(eF,`cursor-pointer text-xs`,`file:mr-2 file:cursor-pointer file:rounded-control file:border-0 file:bg-panel-2 file:px-2 file:py-1 file:text-xs file:text-ink`),b=Q(`inline-flex h-6 shrink-0 items-center gap-1 rounded-control border border-line bg-raise px-2 text-2xs font-medium text-ink`,`transition-colors duration-150 hover:bg-hover`,ZP);function x(e){for(let t of d.value?.items??[])t.restorable&&(t.selected=e)}async function S(){s.value=!0,l.value=null,c.value=null;try{await ij(u.value),u.value=``,c.value=`Backup created.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{s.value=!1}}async function C(e){s.value=!0,l.value=null,c.value=null;try{await aj(e),c.value=`Deleted ${e}.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{s.value=!1}}function w(e){f.value=e.target.files?.[0]??null,d.value=null,m.value=null,l.value=null,c.value=null}function T(){return p.value.length>0?{password:p.value}:{}}async function ee(e){s.value=!0,l.value=null,c.value=null,d.value=null;try{e===void 0?f.value===null?m.value=null:(m.value={kind:`upload`},d.value=await lj(f.value,!1,T())):(m.value={kind:`stored`,name:e},d.value=await cj(e,!1,T())),d.value===null&&(l.value=`Choose a stored backup or upload one first.`)}catch(e){m.value=null,l.value=e instanceof Error?e.message:String(e)}finally{s.value=!1}}async function E(){let e=m.value;e!==null&&await ee(e.kind===`stored`?e.name:void 0)}async function te(){let e=m.value,t=d.value;if(e===null||t===null){l.value=`Check an archive first.`;return}s.value=!0,l.value=null;let n={...T(),include:t.items.filter(e=>e.restorable&&e.selected).map(e=>e.id)};try{let t=e.kind===`stored`?await cj(e.name,!0,n):f.value===null?null:await lj(f.value,!0,n);d.value=null,m.value=null,f.value=null,p.value=``;let r=[];t?.reloaded&&r.push(`the restored servers are live, autostart entries starting`),t?.restartRequired&&r.push(`restart the panel to apply its own settings`),c.value=t===null?`The restore failed.`:`Restored ${t.applied.length} item(s)${r.length===0?``:` — ${r.join(`; `)}`}.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{s.value=!1}}return(e,t)=>(I(),L(`div`,NV,[B(YL,{title:`Policy`,description:`What a backup captures, and how many archives are kept.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[9]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value.enabled,"onUpdate:modelValue":t[1]||=e=>i.value.enabled=e,label:`Allow backups`,hint:`Turning this off hides nothing: the archives already on disk stay downloadable.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B($L,{modelValue:j(o),"onUpdate:modelValue":t[2]||=e=>mn(o)?o.value=e:null,label:`Keep`,min:1,hint:`Older archives are pruned after each backup.`},null,8,[`modelValue`]),B(cR,{modelValue:i.value.includePaths,"onUpdate:modelValue":t[3]||=e=>i.value.includePaths=e,label:`Extra paths`,hint:`Comma separated, in addition to every entry's data directories.`,placeholder:`{home}/myapp-data`},null,8,[`modelValue`]),n.policy===null?H(``,!0):(I(),L(`div`,PV,[t[12]||=z(`span`,{class:`text-xs font-medium text-muted`},`Archive directory`,-1),z(`div`,FV,[z(`span`,IV,k(n.policy.dir),1),B(xB,{value:n.policy.dir,label:`Copy the archive directory`},null,8,[`value`])]),z(`p`,LV,[t[10]||=V(` Changing it is a file-level decision: edit `,-1),z(`code`,null,k(n.configPath??`the config file`),1),t[11]||=V(`. `,-1)])]))]),_:1}),B(YL,{title:`Declared paths`,description:`Config, secrets and TLS are always captured. A path already covered by a declared parent is skipped.`},{default:M(()=>[n.state===null?(I(),L(`div`,RV,[B(YI,{class:`h-4 w-full`})])):g.value.length===0?(I(),L(`p`,zV,[...t[13]||=[V(` No data paths are declared yet. Add `,-1),z(`code`,null,`dataEnvs`,-1),V(` or `,-1),z(`code`,null,`backupPaths`,-1),V(` to a server to capture its data. `,-1)]])):(I(),L(`ul`,BV,[(I(!0),L(F,null,ya(g.value,e=>(I(),L(`li`,{key:`${e.origin}:${e.path}`,class:`flex items-start justify-between gap-3 py-1.5`},[z(`div`,VV,[z(`p`,HV,k(e.path),1),z(`p`,UV,k(e.included?`from ${e.origin}`:e.note??`not captured`),1)]),B(vF,{tone:e.included?`ok`:`neutral`,plain:``},{default:M(()=>[V(k(e.included?`included`:`skipped`),1)]),_:2},1032,[`tone`])]))),128))]))]),_:1}),B(YL,{title:`Create a backup now`,description:`Optional password encrypts the archive; restoring it will ask for the same password.`},{default:M(()=>[B(cR,{modelValue:u.value,"onUpdate:modelValue":t[4]||=e=>u.value=e,type:`password`,autocomplete:`new-password`,label:`Password (optional)`,wide:``},null,8,[`modelValue`]),z(`div`,WV,[B($,{variant:`primary`,disabled:s.value,loading:s.value,onClick:S},{default:M(()=>[...t[14]||=[V(` Create backup now `,-1)]]),_:1},8,[`disabled`,`loading`])])]),_:1}),B(YL,{title:`Archives`,description:`Stored in the backup directory and downloadable at any time.`},{default:M(()=>[n.state===null?(I(),L(`div`,GV,[B(YI,{class:`h-4 w-full`}),B(YI,{class:`h-4 w-2/3`})])):h.value.length===0?(I(),L(`div`,KV,[B(LI,{compact:``,title:`No archives yet`,description:`Create one now to capture the config, secrets and every declared data path.`},{icon:M(()=>[B(j(bv),{class:`size-4`})]),action:M(()=>[B($,{size:`sm`,variant:`primary`,disabled:s.value,onClick:S},{default:M(()=>[...t[15]||=[V(` Create backup now `,-1)]]),_:1},8,[`disabled`])]),_:1})])):(I(),L(`ul`,qV,[(I(!0),L(F,null,ya(h.value,e=>(I(),L(`li`,{key:e.name,class:`flex flex-wrap items-center gap-x-3 gap-y-2 py-2 first:pt-0 last:pb-0`},[z(`div`,JV,[z(`div`,YV,[z(`span`,XV,k(e.name),1),e.encrypted?(I(),R(vF,{key:0,tone:`warn`,plain:``},{default:M(()=>[...t[16]||=[V(` encrypted `,-1)]]),_:1})):H(``,!0)]),z(`p`,ZV,k(j(oI)(e.sizeBytes))+` · `+k(j(tI)(e.createdAt)),1)]),z(`div`,QV,[z(`a`,{href:oj(e.name),class:O(j(b))},[B(j(Fv),{class:`size-3`,"aria-hidden":`true`}),t[17]||=V(` Download `,-1)],10,$V),B($,{size:`xs`,variant:`secondary`,disabled:s.value,onClick:t=>ee(e.name)},{default:M(()=>[B(j(iy),{class:`size-3`,"aria-hidden":`true`}),t[18]||=V(` Restore `,-1)]),_:1},8,[`disabled`,`onClick`]),B($B,{label:`Delete`,"confirm-label":`Confirm delete`,size:`xs`,variant:`ghost`,"confirm-variant":`danger`,disabled:s.value,onConfirm:t=>C(e.name)},null,8,[`disabled`,`onConfirm`])])]))),128))]))]),_:1}),B(YL,{title:`Restore`,description:`Nothing is written until the plan is applied.`},{default:M(()=>[z(`div`,eH,[z(`label`,{class:O(j(QP)),for:`restore-upload`},`Restore from an uploaded .zip`,2),z(`input`,{id:`restore-upload`,type:`file`,accept:`.zip,application/zip`,class:O(j(y)),onChange:w},null,34),f.value?(I(),L(`p`,tH,k(f.value.name),1)):H(``,!0)]),z(`div`,nH,[B($,{variant:`secondary`,disabled:s.value||f.value===null,onClick:t[5]||=e=>ee()},{default:M(()=>[...t[19]||=[V(` Check the upload `,-1)]]),_:1},8,[`disabled`])]),d.value?(I(),L(`div`,rH,[B(nV,{tone:`warn`,title:d.value.dryRun?`Nothing has changed yet`:`Applied`},{default:M(()=>[z(`p`,null,[m.value?.kind===`stored`?(I(),L(F,{key:0},[t[20]||=V(` Archive `,-1),z(`code`,null,k(m.value.name),1),t[21]||=V(`. `,-1)],64)):(I(),L(F,{key:1},[V(` The uploaded archive. `)],64))]),d.value.needsPassword?(I(),L(F,{key:0},[z(`p`,iH,k(d.value.error??`This backup is password-protected.`),1),z(`div`,aH,[B(cR,{modelValue:p.value,"onUpdate:modelValue":t[6]||=e=>p.value=e,type:`password`,autocomplete:`off`,label:`Password`},null,8,[`modelValue`])]),z(`div`,oH,[B($,{size:`sm`,variant:`secondary`,disabled:s.value,onClick:E},{default:M(()=>[...t[22]||=[V(` Check again with the password `,-1)]]),_:1},8,[`disabled`])])],64)):(I(),L(F,{key:1},[z(`p`,sH,` Choose what to restore — `+k(_.value)+` of `+k(d.value.items.length)+` selected. `,1),z(`ul`,cH,[(I(!0),L(F,null,ya(d.value.items,e=>(I(),L(`li`,{key:e.id},[B(lB,{modelValue:e.selected,"onUpdate:modelValue":t=>e.selected=t,label:e.label,hint:[e.kind,e.note].filter(Boolean).join(` · `),disabled:!e.restorable},null,8,[`modelValue`,`onUpdate:modelValue`,`label`,`hint`,`disabled`])]))),128))]),z(`div`,lH,[B($,{size:`xs`,variant:`ghost`,disabled:s.value,onClick:t[7]||=e=>x(!0)},{default:M(()=>[...t[23]||=[V(` Select all `,-1)]]),_:1},8,[`disabled`]),B($,{size:`xs`,variant:`ghost`,disabled:s.value,onClick:t[8]||=e=>x(!1)},{default:M(()=>[...t[24]||=[V(` Select none `,-1)]]),_:1},8,[`disabled`])]),z(`p`,uH,[V(` Will restore: `+k(d.value.applied.join(`, `)||`nothing`)+`. `,1),d.value.skipped.length?(I(),L(F,{key:0},[V(` Skipped: `+k(d.value.skipped.join(`, `))+`. `,1)],64)):H(``,!0),d.value.restartRequired?(I(),L(F,{key:1},[V(` The panel's own settings differ, so restart the panel afterwards. `)],64)):v.value?(I(),L(F,{key:2},[V(` Restored servers are reloaded immediately; entries marked autostart start on their own. `)],64)):H(``,!0)]),z(`div`,dH,[B($,{size:`sm`,variant:`danger`,disabled:s.value||_.value===0,loading:s.value,onClick:te},{default:M(()=>[V(k(m.value?.kind===`stored`?`Apply this restore`:`Restore the upload`),1)]),_:1},8,[`disabled`,`loading`])])],64))]),_:1},8,[`title`])])):H(``,!0),l.value||c.value?(I(),L(`div`,fH,[l.value?(I(),R(nV,{key:0,tone:`danger`},{default:M(()=>[V(k(l.value),1)]),_:1})):H(``,!0),c.value?(I(),R(nV,{key:1,tone:`ok`},{default:M(()=>[V(k(c.value),1)]),_:1})):H(``,!0)])):H(``,!0)]),_:1})]))}}),mH={class:`mt-3 space-y-3`},hH=N({__name:`DefaultsSection`,props:Ba({dirty:{type:Boolean}},{defaults:{required:!0},defaultsModifiers:{}}),emits:Ba([`reset`],[`update:defaults`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`defaults`),a=OV(()=>i.value.logBufferLines,e=>i.value.logBufferLines=e,500);return(e,t)=>(I(),L(F,null,[B(YL,{title:`Server defaults`,description:`Applied to every entry that does not set its own value.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[9]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(lB,{modelValue:i.value.enabled,"onUpdate:modelValue":t[1]||=e=>i.value.enabled=e,label:`Enabled`,hint:`New entries start enabled.`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.autostart,"onUpdate:modelValue":t[2]||=e=>i.value.autostart=e,label:`Autostart with the panel`},null,8,[`modelValue`]),B(JI,{modelValue:i.value.bind,"onUpdate:modelValue":t[3]||=e=>i.value.bind=e,label:`Bind`,options:[{value:`local`,label:`Local — 127.0.0.1`},{value:`lan`,label:`LAN — 0.0.0.0`}]},null,8,[`modelValue`]),B(JI,{modelValue:i.value.onPortConflict,"onUpdate:modelValue":t[4]||=e=>i.value.onPortConflict=e,label:`On port conflict`,options:[{value:`block`,label:`Block the start`},{value:`warn`,label:`Warn and start anyway`}]},null,8,[`modelValue`]),B($L,{modelValue:j(a),"onUpdate:modelValue":t[5]||=e=>mn(a)?a.value=e:null,label:`Log buffer lines`,min:50,max:1e5,hint:`Kept in memory per server.`},null,8,[`modelValue`])]),_:1}),z(`div`,mH,[B(uB,{restart:i.value.restart,"onUpdate:restart":t[6]||=e=>i.value.restart=e,health:i.value.health,"onUpdate:health":t[7]||=e=>i.value.health=e,stop:i.value.stop,"onUpdate:stop":t[8]||=e=>i.value.stop=e},null,8,[`restart`,`health`,`stop`])])],64))}}),gH={key:0,class:`mt-3 flex flex-wrap items-center gap-1.5`},_H={key:1,class:`mt-3 text-xs text-muted`},vH=N({__name:`HostSection`,props:Ba({config:{},alerts:{},dirty:{type:Boolean}},{host:{required:!0},hostModifiers:{}}),emits:Ba([`reset`],[`update:host`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`host`),a=OV(()=>i.value.intervalMs,e=>i.value.intervalMs=e,15e3),o=OV(()=>i.value.diskUsedPercent,e=>i.value.diskUsedPercent=e,90),s=OV(()=>i.value.memoryUsedPercent,e=>i.value.memoryUsedPercent=e,90),c=OV(()=>i.value.swapUsedPercent,e=>i.value.swapUsedPercent=e,50),l=OV(()=>i.value.loadPerCpu,e=>i.value.loadPerCpu=e,2),u=OV(()=>i.value.tempCelsius,e=>i.value.tempCelsius=e,85);return(e,t)=>(I(),L(F,null,[B(YL,{title:`Thresholds`,description:`A threshold of 0 disables that alert. Breaches notify once, and once more on recovery.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[9]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value.enabled,"onUpdate:modelValue":t[1]||=e=>i.value.enabled=e,label:`Sample this machine`,hint:`Load, memory, swap, disk and CPU temperature, on the interval below.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B($L,{modelValue:j(a),"onUpdate:modelValue":t[2]||=e=>mn(a)?a.value=e:null,label:`Interval (ms)`,min:5e3,hint:`How often the host is sampled and alerts are checked.`},null,8,[`modelValue`]),B(cR,{modelValue:i.value.diskPaths,"onUpdate:modelValue":t[3]||=e=>i.value.diskPaths=e,label:`Disk paths`,hint:`Comma separated; templates like {home} are expanded.`},null,8,[`modelValue`]),B($L,{modelValue:j(o),"onUpdate:modelValue":t[4]||=e=>mn(o)?o.value=e:null,label:`Disk used (%)`,min:0,max:100,hint:`0 disables the alert.`},null,8,[`modelValue`]),B($L,{modelValue:j(s),"onUpdate:modelValue":t[5]||=e=>mn(s)?s.value=e:null,label:`Memory used (%)`,min:0,max:100,hint:`0 disables the alert.`},null,8,[`modelValue`]),B($L,{modelValue:j(c),"onUpdate:modelValue":t[6]||=e=>mn(c)?c.value=e:null,label:`Swap used (%)`,min:0,max:100,hint:`0 disables the alert.`},null,8,[`modelValue`]),B($L,{modelValue:j(l),"onUpdate:modelValue":t[7]||=e=>mn(l)?l.value=e:null,label:`Load per CPU`,min:0,hint:`1-minute load average divided by the core count.`},null,8,[`modelValue`]),B($L,{modelValue:j(u),"onUpdate:modelValue":t[8]||=e=>mn(u)?u.value=e:null,label:`CPU temperature (°C)`,min:0,hint:`Linux only; ignored where the OS does not expose it.`},null,8,[`modelValue`])]),_:1}),n.alerts.length>0?(I(),L(`div`,gH,[t[10]||=z(`span`,{class:`text-xs text-muted`},`Breaching now:`,-1),(I(!0),L(F,null,ya(n.alerts,e=>(I(),R(vF,{key:e,tone:`danger`,dot:``},{default:M(()=>[V(k(e),1)]),_:2},1024))),128))])):i.value.enabled?(I(),L(`p`,_H,` Nothing is breaching right now. `)):H(``,!0)],64))}}),yH={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},bH={class:`flex items-center gap-1.5 rounded-control border border-line bg-page/60 px-2.5 py-1.5`},xH={class:`min-w-0 flex-1 truncate font-mono text-xs text-ink`},SH={class:`text-2xs leading-4 text-faint`},CH={key:0},wH={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},TH=[`disabled`],EH=N({__name:`InterfaceSection`,props:Ba({ui:{},labelDirty:{type:Boolean}},{label:{required:!0},labelModifiers:{}}),emits:Ba([`changed`,`resetLabel`],[`update:label`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`label`),a=A(null),o=A(!1),s=A(null),c=A(null),l=A(!1),u=U(()=>{let e=n.ui;if(e===null)return`unknown`;if(!e.custom)return`stock`;let t=e.meta;return t===null?`custom`:`custom · ${t.name}${t.version===null?``:` ${t.version}`}`}),d=U(()=>{let e=n.ui?.meta;return e==null?null:`${e.files} files · ${new Date(e.uploadedAt).toLocaleString()}`});function f(e){a.value=e.target.files?.[0]??null,s.value=null,c.value=null}async function p(){if(a.value!==null){o.value=!0,c.value=null,s.value=null;try{let e=await mj(a.value);s.value=`UI replaced with ${e.meta?.name??`your build`} — refresh the page`,a.value=null,r(`changed`)}catch(e){c.value=e instanceof Error?e.message:String(e)}finally{o.value=!1}}}async function m(){if(!l.value){l.value=!0,setTimeout(()=>{l.value=!1},5e3);return}l.value=!1,o.value=!0,c.value=null;try{let e=await hj();s.value=e.removed?`stock UI restored — refresh the page`:`no custom UI was installed`,r(`changed`)}catch(e){c.value=e instanceof Error?e.message:String(e)}finally{o.value=!1}}return(t,h)=>(I(),R(YL,{title:`Interface`,description:`The panel is a static site: keep the stock one, or serve your own build from the state directory.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.labelDirty,onClick:h[1]||=e=>r(`resetLabel`)},{default:M(()=>[...h[4]||=[V(` Reset name `,-1)]]),_:1},8,[`disabled`]),e.ui?.custom?(I(),R($,{key:0,size:`xs`,variant:`ghost`,disabled:o.value,onClick:m},{default:M(()=>[V(k(l.value?`Confirm revert`:`Revert to stock`),1)]),_:1},8,[`disabled`])):H(``,!0),B($,{size:`xs`,variant:`primary`,disabled:o.value||a.value===null,onClick:p},{default:M(()=>[...h[5]||=[V(` Install UI `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[z(`div`,yH,[h[2]||=z(`span`,{class:`text-xs font-medium text-muted`},`Currently serving`,-1),z(`div`,bH,[z(`span`,xH,k(u.value),1),B(xB,{value:e.ui?.dir??``,label:`Copy the UI directory`},null,8,[`value`])]),z(`p`,SH,[d.value?(I(),L(`span`,CH,k(d.value)+` · `,1)):H(``,!0),z(`code`,null,k(e.ui?.dir??`—`),1)])]),B(cR,{modelValue:i.value,"onUpdate:modelValue":h[0]||=e=>i.value=e,label:`Panel name`,hint:`Shown in the sidebar by the stock UI. A custom UI may show its own name instead.`,wide:``},null,8,[`modelValue`]),z(`label`,wH,[h[3]||=z(`span`,{class:`text-xs font-medium text-muted`},`Upload a UI build (.zip with an index.html)`,-1),z(`input`,{type:`file`,accept:`.zip,application/zip`,class:`rounded-control border border-line bg-page/60 px-2.5 py-1.5 text-xs text-ink file:mr-2 file:rounded file:border-0 file:bg-line file:px-2 file:py-1 file:text-xs`,disabled:o.value,onChange:f},null,40,TH)]),c.value?(I(),R(nV,{key:0,tone:`warn`,title:c.value},null,8,[`title`])):s.value?(I(),R(nV,{key:1,tone:`ok`,title:s.value},null,8,[`title`])):H(``,!0),h[6]||=z(`p`,{class:`text-2xs leading-4 text-faint sm:col-span-2`},[V(` See `),z(`code`,null,`UI_CREATION.md`),V(` for the API contract. If a custom UI breaks the panel, `),z(`code`,null,`home-hosted ui-revert`),V(` puts the stock one back from the terminal. `)],-1)]),_:1}))}}),DH={class:`flex min-w-0 flex-col gap-1`},OH={class:`text-2xs leading-4 text-faint`},kH={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},AH={class:`flex items-center gap-1.5 rounded-control border border-line bg-page/60 px-2.5 py-1.5`},jH={class:`min-w-0 flex-1 truncate font-mono text-xs tabular-nums text-ink`},MH=N({__name:`ListenerSection`,props:Ba({view:{},dirty:{type:Boolean}},{control:{required:!0},controlModifiers:{}}),emits:Ba([`reset`],[`update:control`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`control`),a=OV(()=>i.value.port,e=>i.value.port=e,3999),o=U({get:()=>i.value.host===`local`||i.value.host===`lan`?i.value.host:`custom`,set:e=>{if(e===`custom`){jk(i.value.host)===null&&(i.value.host=`127.0.0.1`);return}i.value.host=e}}),s=U(()=>{if(o.value===`custom`&&jk(i.value.host)===null)return`Enter an IPv4 address, for example 192.168.1.20.`}),c=U(()=>n.view?.url??`—`);return(e,t)=>(I(),R(YL,{title:`Listener`,description:`Where the panel answers. Moving this restarts the panel and sends this page to the new address.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[5]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B($L,{modelValue:j(a),"onUpdate:modelValue":t[1]||=e=>mn(a)?a.value=e:null,label:`Control port`,min:1,max:65535,hint:`Default 3999.`},null,8,[`modelValue`]),z(`div`,DH,[t[6]||=z(`span`,{class:`text-xs font-medium text-muted`},`Bind`,-1),B(hI,{modelValue:o.value,"onUpdate:modelValue":t[2]||=e=>o.value=e,options:[{value:`local`,label:`Local`},{value:`lan`,label:`LAN`},{value:`custom`,label:`Custom IP`}]},null,8,[`modelValue`]),z(`p`,OH,k(o.value===`lan`?`0.0.0.0 — reachable from the whole network.`:o.value===`local`?`127.0.0.1 — this machine only.`:`A specific interface address.`),1)]),o.value===`custom`?(I(),R(cR,{key:0,modelValue:i.value.host,"onUpdate:modelValue":t[3]||=e=>i.value.host=e,label:`IPv4 address`,placeholder:`192.168.1.20`,error:s.value,hint:`Binding beyond 127.0.0.1 needs a real password.`},null,8,[`modelValue`,`error`])):H(``,!0),B(lB,{modelValue:i.value.openBrowser,"onUpdate:modelValue":t[4]||=e=>i.value.openBrowser=e,label:`Open a browser when the panel comes up`,wide:``},null,8,[`modelValue`]),z(`div`,kH,[t[8]||=z(`span`,{class:`text-xs font-medium text-muted`},`Live address`,-1),z(`div`,AH,[z(`span`,jH,k(c.value),1),n.view?.restartRequired?(I(),R(vF,{key:0,tone:`warn`,dot:``},{default:M(()=>[...t[7]||=[V(` restart required `,-1)]]),_:1})):H(``,!0),B(xB,{value:c.value,label:`Copy the live address`},null,8,[`value`])]),t[9]||=z(`p`,{class:`text-2xs leading-4 text-faint`},` What the listener is bound to right now; saved changes take effect after the panel rebinds. `,-1)])]),_:1}))}}),NH={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},PH={class:`flex items-center gap-1.5 rounded-control border border-line bg-page/60 px-2.5 py-1.5`},FH={class:`min-w-0 flex-1 truncate font-mono text-xs text-ink`},IH={class:`text-2xs leading-4 text-faint`},LH=N({__name:`LogsSection`,props:Ba({logsDir:{},dirty:{type:Boolean}},{logs:{required:!0},logsModifiers:{}}),emits:Ba([`reset`],[`update:logs`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`logs`),a=OV(()=>i.value.maxBytes,e=>i.value.maxBytes=e,2e6),o=OV(()=>i.value.keep,e=>i.value.keep=e,3),s=U(()=>n.logsDir??`—`);return(e,t)=>{let c=pa(`RouterLink`);return I(),R(YL,{title:`Retention`,description:`The live view always comes from memory; these files survive a restart and feed the Logs page.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[4]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value.persist,"onUpdate:modelValue":t[1]||=e=>i.value.persist=e,label:`Keep logs on disk`,hint:`Turn off to hold everything in memory only.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B($L,{modelValue:j(a),"onUpdate:modelValue":t[2]||=e=>mn(a)?a.value=e:null,label:`Rotate at (bytes)`,min:1e4,max:1e8,hint:`Per server, before rolling to .1, .2, …`},null,8,[`modelValue`]),B($L,{modelValue:j(o),"onUpdate:modelValue":t[3]||=e=>mn(o)?o.value=e:null,label:`Keep rotated files`,min:1,max:10},null,8,[`modelValue`]),z(`div`,NH,[t[8]||=z(`span`,{class:`text-xs font-medium text-muted`},`Logs directory`,-1),z(`div`,PH,[z(`span`,FH,k(s.value),1),B(xB,{value:s.value,label:`Copy the logs directory`},null,8,[`value`])]),z(`p`,IH,[t[6]||=V(` Browse and download these files on the `,-1),B(c,{to:`/logs`,class:`text-accent underline decoration-accent/40 underline-offset-2 hover:decoration-accent`},{default:M(()=>[...t[5]||=[V(` Logs `,-1)]]),_:1}),t[7]||=V(` page. `,-1)])])]),_:1})}}}),RH={class:`flex items-center gap-2 border-t border-line-soft pt-3 sm:col-span-2`},zH={class:`flex flex-wrap items-center gap-2 sm:col-span-2`},BH={key:0,class:`sm:col-span-2`},VH={class:`flex flex-wrap gap-1.5`},HH={key:1,class:`sm:col-span-2`},UH={class:`text-2xs leading-4 text-faint`},WH={key:2,class:`space-y-2 sm:col-span-2`},GH=N({__name:`NotificationsSection`,props:Ba({status:{},dirty:{type:Boolean}},{telegram:{required:!0},telegramModifiers:{}}),emits:Ba([`reset`],[`update:telegram`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`telegram`),a=lM(),o=OV(()=>i.value.cooldownMs,e=>i.value.cooldownMs=e,12e4),s=A(``),c=A(null),l=A(null),u=A([]),d=A(!1),f=U(()=>n.status?.tokenSet===!0),p=U(()=>n.status?.lastResult??null),m=U(()=>n.status?.lastResultAt??null),h=U(()=>f.value?`Saved — type a new token to replace it`:`123456789:AA…`);async function g(){d.value=!0,l.value=null,c.value=null;try{let e=await uj(s.value.trim());s.value=``,c.value=`Token saved${e.username?` for @${e.username}`:``}.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{d.value=!1}}async function _(){d.value=!0,l.value=null,c.value=null;try{await dj(),c.value=`Token removed.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{d.value=!1}}async function v(){d.value=!0,l.value=null,c.value=null;try{let e=s.value.trim(),t=await pj(e.length>0?{botToken:e}:{});u.value=t.chats,t.chats.length===0&&(l.value=`No chats found. Send /start to the bot first, then detect again.`)}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{d.value=!1}}async function y(){d.value=!0,l.value=null,c.value=null;try{let e=s.value.trim(),t=await fj({chatId:i.value.chatId.length>0?i.value.chatId:void 0,...e.length>0?{botToken:e}:{}});t.ok?c.value=`Test message sent.`:l.value=t.error??`The test message failed.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{d.value=!1}}return(e,t)=>(I(),R(YL,{title:`Telegram`,description:`Policy lives in the config; the bot token is a secret and never comes back from the server.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[10]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value.enabled,"onUpdate:modelValue":t[1]||=e=>i.value.enabled=e,label:`Send notifications`,hint:`One message per event, throttled by the cooldown below.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B(cR,{modelValue:i.value.chatId,"onUpdate:modelValue":t[2]||=e=>i.value.chatId=e,label:`Chat id`,placeholder:`123456789 or @channel`,hint:`Use Detect chats to fill this from the bot's updates.`},null,8,[`modelValue`]),B($L,{modelValue:j(o),"onUpdate:modelValue":t[3]||=e=>mn(o)?o.value=e:null,label:`Cooldown per event (ms)`,min:0,hint:`Per server and reason, so a flapping server cannot spam.`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.onCrash,"onUpdate:modelValue":t[4]||=e=>i.value.onCrash=e,label:`Retries exhausted`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.onUnhealthy,"onUpdate:modelValue":t[5]||=e=>i.value.onUnhealthy=e,label:`Port unhealthy`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.onForcedRestart,"onUpdate:modelValue":t[6]||=e=>i.value.onForcedRestart=e,label:`Forced restart`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.onRecovered,"onUpdate:modelValue":t[7]||=e=>i.value.onRecovered=e,label:`Recovered`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.onHost,"onUpdate:modelValue":t[8]||=e=>i.value.onHost=e,label:`Host vitals breached`,hint:`Disk, memory, swap, load and CPU temperature, per the thresholds under Host vitals.`,wide:``},null,8,[`modelValue`]),z(`div`,RH,[t[11]||=z(`span`,{class:`text-xs font-medium text-muted`},`Bot token`,-1),B(vF,{tone:f.value?`ok`:`warn`,dot:``},{default:M(()=>[V(k(f.value?`token set`:`no token`),1)]),_:1},8,[`tone`]),t[12]||=z(`span`,{class:`text-2xs text-faint`},`From @BotFather — a secret, never in servers.config.json.`,-1)]),B(cR,{modelValue:s.value,"onUpdate:modelValue":t[9]||=e=>s.value=e,type:`password`,autocomplete:`off`,label:`Bot token`,placeholder:h.value,wide:``},null,8,[`modelValue`,`placeholder`]),z(`div`,zH,[B($,{variant:`primary`,disabled:d.value||s.value.trim().length===0,loading:d.value&&s.value.trim().length>0,onClick:g},{default:M(()=>[...t[13]||=[V(` Save token `,-1)]]),_:1},8,[`disabled`,`loading`]),B($,{variant:`secondary`,disabled:d.value,onClick:v},{default:M(()=>[...t[14]||=[V(` Detect chats `,-1)]]),_:1},8,[`disabled`]),B($,{variant:`secondary`,disabled:d.value,onClick:y},{default:M(()=>[...t[15]||=[V(` Send test `,-1)]]),_:1},8,[`disabled`]),f.value?(I(),R($B,{key:0,label:`Remove token`,"confirm-label":`Confirm remove`,"confirm-variant":`danger`,disabled:d.value,onConfirm:_},null,8,[`disabled`])):H(``,!0)]),u.value.length>0?(I(),L(`div`,BH,[t[16]||=z(`p`,{class:`mb-1.5 text-xs text-muted`},` Chats the bot has seen: `,-1),z(`div`,VH,[(I(!0),L(F,null,ya(u.value,e=>(I(),R($,{key:String(e.id),size:`xs`,variant:`secondary`,onClick:t=>i.value.chatId=String(e.id)},{default:M(()=>[V(k(e.title)+` (`+k(e.id)+`) `,1)]),_:2},1032,[`onClick`]))),128))])])):H(``,!0),p.value?(I(),L(`div`,HH,[z(`p`,UH,` Last send: `+k(p.value)+k(m.value?` · ${j(aI)(m.value,j(a).now.value)}`:``),1)])):H(``,!0),l.value||c.value?(I(),L(`div`,WH,[l.value?(I(),R(nV,{key:0,tone:`danger`},{default:M(()=>[V(k(l.value),1)]),_:1})):H(``,!0),c.value?(I(),R(nV,{key:1,tone:`ok`},{default:M(()=>[V(k(c.value),1)]),_:1})):H(``,!0)])):H(``,!0)]),_:1}))}}),KH={class:`divide-y divide-line-soft sm:col-span-2`},qH={class:`min-w-0`},JH={class:`text-xs font-medium text-ink`},YH={class:`mt-0.5 text-2xs leading-4 text-faint`},XH={class:`flex min-w-0 flex-1 items-center justify-end gap-1.5`},ZH={class:`min-w-0 truncate font-mono text-xs text-ink`},QH=N({__name:`PathsSection`,props:{configPath:{},dataRoot:{},logsDir:{},projectDir:{},panelUrl:{}},setup(e){let t=e,n=U(()=>[{key:`data`,label:`Data root`,hint:`HHOSTED_HOME — config, secrets, TLS, logs and backups.`,value:t.dataRoot??`—`},{key:`config`,label:`Config file`,hint:`servers.config.json — every policy on this page.`,value:t.configPath??`—`},{key:`logs`,label:`Logs directory`,hint:`Persisted output for every server.`,value:t.logsDir??`—`},{key:`project`,label:`Project directory`,hint:`Base for relative entry paths.`,value:t.projectDir??`—`},{key:`panel`,label:`Panel address`,hint:`Where this control panel is live right now.`,value:t.panelUrl??`—`}]);return(e,t)=>(I(),R(YL,{title:`Where things live`,description:`Read-only. Copy a path straight into a shell or an editor.`},{default:M(()=>[z(`ul`,KH,[(I(!0),L(F,null,ya(n.value,e=>(I(),L(`li`,{key:e.key,class:`flex items-start justify-between gap-3 py-2 first:pt-0 last:pb-0`},[z(`div`,qH,[z(`p`,JH,k(e.label),1),z(`p`,YH,k(e.hint),1)]),z(`div`,XH,[z(`span`,ZH,k(e.value),1),B(xB,{value:e.value,label:`Copy ${e.label.toLowerCase()}`},null,8,[`value`,`label`])])]))),128))])]),_:1}))}}),$H={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},eU={class:`flex items-center gap-1.5 rounded-control border border-line bg-page/60 px-2.5 py-1.5`},tU={class:`min-w-0 flex-1 truncate font-mono text-xs text-ink`},nU={key:0,class:`sm:col-span-2`},rU={key:1,class:`space-y-3 sm:col-span-2`},iU={class:`divide-y divide-line-soft text-xs`},aU={class:`flex items-start justify-between gap-4 py-1.5`},oU={class:`min-w-0 text-right font-mono text-xs text-ink`},sU={class:`flex items-start justify-between gap-4 py-1.5`},cU={class:`min-w-0 text-right font-mono text-xs text-ink`},lU={class:`flex items-start justify-between gap-4 py-1.5`},uU={class:`font-mono text-xs tabular-nums text-ink`},dU={class:`flex items-center justify-between gap-4 py-1.5`},fU={class:`flex items-start justify-between gap-4 py-1.5`},pU={class:`flex min-w-0 items-center gap-1.5`},mU={class:`min-w-0 truncate font-mono text-2xs text-ink`},hU={key:2,class:`sm:col-span-2`},gU={class:`flex min-w-0 flex-col gap-1`},_U={class:`flex min-w-0 flex-col gap-1`},vU={class:`flex flex-wrap items-center gap-2 sm:col-span-2`},yU={key:3,class:`space-y-2 sm:col-span-2`},bU=N({__name:`TlsSection`,props:Ba({view:{},dirty:{type:Boolean}},{tlsEnabled:{type:Boolean,required:!0},tlsEnabledModifiers:{}}),emits:Ba([`reset`],[`update:tlsEnabled`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`tlsEnabled`),a=lM(),o=A(``),s=A(``),c=A(null),l=A(null),u=A(!1),d=U(()=>n.view?.tls??null),f=U(()=>n.view?`${n.view.protocol}://${n.view.bindHost}:${n.view.port}`:`—`),p=U(()=>o.value.trim().length>0&&s.value.trim().length>0&&!u.value),m=Q(eF,`cursor-pointer text-xs`,`file:mr-2 file:cursor-pointer file:rounded-control file:border-0 file:bg-panel-2 file:px-2 file:py-1 file:text-xs file:text-ink`);async function h(e,t){let n=e.target.files?.[0];if(!n)return;let r=await n.text();t===`cert`?o.value=r:s.value=r}async function g(){u.value=!0,l.value=null,c.value=null;try{let e=await gj(o.value,s.value);o.value=``,s.value=``,c.value=`Certificate stored.`,await a.refresh();let t=await kV(e);t!==null&&(l.value=t)}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{u.value=!1}}async function _(){u.value=!0,l.value=null,c.value=null;try{let e=await _j();c.value=`Certificate removed.`,await a.refresh();let t=await kV(e);t!==null&&(l.value=t)}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{u.value=!1}}return(e,t)=>(I(),L(F,null,[B(YL,{title:`HTTPS`,description:`Upload a PEM pair and the panel serves TLS on its own listener. A self-signed certificate is fine on a home network.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[6]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value,"onUpdate:modelValue":t[1]||=e=>i.value=e,label:`Serve the panel over HTTPS`,hint:`Saved with Save settings; the panel rebinds and this page follows it.`,class:`sm:col-span-2`},null,8,[`modelValue`]),z(`div`,$H,[t[7]||=z(`span`,{class:`text-xs font-medium text-muted`},`Live address`,-1),z(`div`,eU,[z(`span`,tU,k(f.value),1),B(xB,{value:f.value,label:`Copy the live address`},null,8,[`value`])])])]),_:1}),B(YL,{class:`mt-3`,title:`Certificate`,description:`PEM-encoded certificate and its private key. Both stay in the secrets file.`},{default:M(()=>[d.value?.error?(I(),L(`div`,nU,[B(nV,{tone:`danger`,title:`The stored certificate cannot be used`},{default:M(()=>[V(k(d.value.error),1)]),_:1})])):d.value?.certPresent?(I(),L(`div`,rU,[z(`dl`,iU,[z(`div`,aU,[t[8]||=z(`dt`,{class:`text-muted`},` Subject `,-1),z(`dd`,oU,k(d.value.subject??`—`),1)]),z(`div`,sU,[t[9]||=z(`dt`,{class:`text-muted`},` Issuer `,-1),z(`dd`,cU,k(d.value.issuer??`—`),1)]),z(`div`,lU,[t[10]||=z(`dt`,{class:`text-muted`},` Valid until `,-1),z(`dd`,uU,[V(k(d.value.validTo?d.value.validTo.slice(0,10):`—`)+` `,1),d.value.daysRemaining===null?H(``,!0):(I(),L(`span`,{key:0,class:O(j(Q)(`ml-1.5`,d.value.daysRemaining<14?`text-warn`:`text-faint`))},` (`+k(d.value.daysRemaining)+` days) `,3))])]),z(`div`,dU,[t[11]||=z(`dt`,{class:`text-muted`},` Private key `,-1),z(`dd`,null,[B(vF,{tone:d.value.keyMatches?`ok`:`danger`,dot:``},{default:M(()=>[V(k(d.value.keyMatches?`matches the certificate`:`does not match`),1)]),_:1},8,[`tone`])])]),z(`div`,fU,[t[12]||=z(`dt`,{class:`text-muted`},` Fingerprint `,-1),z(`dd`,pU,[z(`span`,mU,k(d.value.fingerprint??`—`),1),d.value.fingerprint?(I(),R(xB,{key:0,value:d.value.fingerprint,label:`Copy the fingerprint`},null,8,[`value`])):H(``,!0)])])])])):(I(),L(`div`,hU,[B(nV,{tone:`info`,title:`No certificate uploaded`},{default:M(()=>[...t[13]||=[V(` Upload a PEM certificate and key below, then save. HTTPS is not served until a certificate is present. `,-1)]]),_:1})])),z(`div`,gU,[z(`label`,{class:O(j(QP)),for:`tls-cert-file`},`Certificate file (.pem, .crt)`,2),z(`input`,{id:`tls-cert-file`,type:`file`,accept:`.pem,.crt,.cer,text/plain`,class:O(j(m)),onChange:t[2]||=e=>h(e,`cert`)},null,34)]),z(`div`,_U,[z(`label`,{class:O(j(QP)),for:`tls-key-file`},`Private key file (.pem, .key)`,2),z(`input`,{id:`tls-key-file`,type:`file`,accept:`.pem,.key,text/plain`,class:O(j(m)),onChange:t[3]||=e=>h(e,`key`)},null,34)]),B(rR,{modelValue:o.value,"onUpdate:modelValue":t[4]||=e=>o.value=e,label:`Or paste the certificate`,rows:4,placeholder:`-----BEGIN CERTIFICATE-----`},null,8,[`modelValue`]),B(rR,{modelValue:s.value,"onUpdate:modelValue":t[5]||=e=>s.value=e,label:`Or paste the private key`,rows:4,placeholder:`-----BEGIN PRIVATE KEY-----`},null,8,[`modelValue`]),z(`div`,vU,[B($,{variant:`primary`,disabled:!p.value,loading:u.value,onClick:g},{default:M(()=>[...t[14]||=[V(` Save certificate `,-1)]]),_:1},8,[`disabled`,`loading`]),d.value?.certPresent?(I(),R($B,{key:0,label:`Remove certificate`,"confirm-label":`Confirm remove`,"confirm-variant":`danger`,disabled:u.value,onConfirm:_},null,8,[`disabled`])):H(``,!0)]),l.value||c.value?(I(),L(`div`,yU,[l.value?(I(),R(nV,{key:0,tone:`danger`},{default:M(()=>[V(k(l.value),1)]),_:1})):H(``,!0),c.value?(I(),R(nV,{key:1,tone:`ok`},{default:M(()=>[V(k(c.value),1)]),_:1})):H(``,!0)])):H(``,!0)]),_:1})],64))}}),xU={class:`min-h-full`},SU={class:`mx-auto w-full max-w-5xl px-4 pb-6 pt-5 sm:px-6`},CU={class:`lg:grid lg:grid-cols-[11rem_minmax(0,1fr)] lg:gap-8`},wU={"aria-label":`Settings sections`,class:`hidden lg:block`},TU={class:`sticky top-6 space-y-0.5 border-l border-line`},EU=[`aria-current`,`onClick`],DU={class:`space-y-6`},OU={id:`listener`,class:`scroll-mt-5`},kU={id:`authentication`,class:`scroll-mt-5`},AU={id:`password`,class:`scroll-mt-5`},jU={id:`defaults`,class:`scroll-mt-5`},MU={id:`logs`,class:`scroll-mt-5`},NU={id:`notifications`,class:`scroll-mt-5`},PU={id:`host`,class:`scroll-mt-5`},FU={id:`backups`,class:`scroll-mt-5`},IU={id:`tls`,class:`scroll-mt-5`},LU={id:`interface`,class:`scroll-mt-5`},RU={id:`paths`,class:`scroll-mt-5`},zU={class:`sticky bottom-0 z-30 border-t border-line bg-panel/95 backdrop-blur`},BU={class:`mx-auto flex w-full max-w-5xl flex-wrap items-center gap-x-4 gap-y-2 px-4 py-3 sm:px-6`},VU={class:`min-w-0 flex-1`},HU={class:`text-xs text-muted`},UU={class:`font-mono tabular-nums text-ink`},WU={key:0,class:`mt-0.5 text-xs text-danger`},GU={key:1,class:`mt-0.5 text-xs text-ok`},KU={class:`flex shrink-0 items-center gap-2`},qU={class:`max-h-[60dvh] overflow-auto px-4 py-3`},JU={key:0,class:`divide-y divide-line-soft`},YU={class:`font-mono text-xs text-ink`},XU={class:`ml-auto font-mono text-xs text-muted line-through`},ZU={class:`font-mono text-xs text-accent`},QU={key:1,class:`text-xs text-muted`},$U={class:`flex items-center justify-end gap-2 border-t border-line px-4 py-3`},eW=N({__name:`SettingsView`,setup(e){let t=[{id:`listener`,title:`Listener`},{id:`authentication`,title:`Authentication`},{id:`defaults`,title:`Server defaults`},{id:`logs`,title:`Logs`},{id:`notifications`,title:`Notifications`},{id:`host`,title:`Host vitals`},{id:`backups`,title:`Backups`},{id:`tls`,title:`TLS`},{id:`interface`,title:`Interface`},{id:`paths`,title:`Paths`}],n=Dg(),r=Eg(),i=lM(),a=Mj(),o=en(cV()),s=A(`listener`),c=A(!1),l=A(null),u=A(null),d=A(null),f=A(null),p=U(()=>i.control.value),m=U(()=>i.defaults.value),h=U(()=>i.logsConfig.value),g=U(()=>i.notifications.value?.telegram??null),_=U(()=>i.host.value?.alerts??[]),v=U(()=>f.value?.host??null),y=U(()=>f.value?.backups??null),b=U(()=>f.value?.ui??null),x=U(()=>i.backups.value),S=U(()=>i.appState.value?.configPath??null),C=U(()=>{let e={},t=p.value,n=m.value,r=h.value,i=g.value;if(t!==null){let n=_V(t,o);Object.keys(n).length>0&&(e.control=n)}if(n!==null){let t=vV(n,o.defaults);Object.keys(t).length>0&&(e.defaults=t)}if(r!==null){let t=yV(r,o.logs);Object.keys(t).length>0&&(e.logs=t)}if(i!==null){let t=bV(i,o.telegram);Object.keys(t).length>0&&(e.notifications=t)}let a=v.value;if(a!==null){let t=hV(a,o.host);Object.keys(t).length>0&&(e.host=t)}let s=y.value;if(s!==null){let t=gV(s,o.backups);Object.keys(t).length>0&&(e.backups=t)}return e}),w=U(()=>wV(C.value)),T=U(()=>{let e=p.value,t=g.value,n=v.value,r=y.value;return{control:e===null?{}:{...uV(e),auth:lV(e.auth)},defaults:m.value??{},logs:h.value??{},notifications:t===null?{}:{telegram:dV(t)},host:n??{},backups:r===null?{}:{enabled:r.enabled,keep:r.keep,includePaths:r.includePaths}}}),ee=U(()=>CV(C.value,T.value)),E=A(!1);function te(e){return e===void 0?`unset`:typeof e==`string`?e:JSON.stringify(e)}let ne=U(()=>p.value!==null&&TV(p.value,o)),re=U(()=>p.value!==null&&EV(p.value,o)),ie=U(()=>p.value!==null&&DV(p.value,o)),ae=U(()=>m.value!==null&&Object.keys(vV(m.value,o.defaults)).length>0),oe=U(()=>h.value!==null&&Object.keys(yV(h.value,o.logs)).length>0),se=U(()=>g.value!==null&&Object.keys(bV(g.value,o.telegram)).length>0),ce=U(()=>v.value!==null&&Object.keys(hV(v.value,o.host)).length>0),D=U(()=>y.value!==null&&Object.keys(gV(y.value,o.backups)).length>0),le=A(!1);function ue(){let e=p.value;e!==null&&(Object.assign(o.control,uV(e)),Object.assign(o.auth,lV(e.auth)));let t=h.value;t!==null&&Object.assign(o.logs,t);let n=g.value;n!==null&&Object.assign(o.telegram,dV(n));let r=v.value;r!==null&&Object.assign(o.host,pV(r));let i=y.value;i!==null&&Object.assign(o.backups,mV(i));let a=m.value;a!==null&&(o.defaults.enabled=a.enabled,o.defaults.autostart=a.autostart,o.defaults.bind=a.bind,o.defaults.onPortConflict=a.onPortConflict,o.defaults.logBufferLines=a.logBufferLines,o.defaults.restart={...a.restart},o.defaults.health=sV(a.health),o.defaults.stop={...a.stop}),le.value=!0}jr([p,m,h,g,f],()=>{xV({hydrated:le.value,liveAvailable:p.value!==null,changedCount:w.value})&&ue()},{immediate:!0});async function de(){d.value=null;try{f.value=await QA()}catch(e){d.value=e instanceof Error?e.message:String(e)}}na(()=>{de()});async function fe(){let e=C.value;if(!(Object.keys(e).length===0||c.value)){c.value=!0,l.value=null,u.value=null;try{let t=await i.saveSettings(e);if(!t){l.value=i.lastError.value??`The settings could not be saved.`;return}u.value=`Settings saved.`,a.success(`Settings saved`),await de();let n=await kV(t);n===null?t.rebinding&&(u.value=`Settings saved — the panel is moving to a new address.`):(u.value=null,l.value=n)}finally{c.value=!1}}}function pe(){l.value=null,u.value=null,ue()}function me(){v.value!==null&&Object.assign(o.host,pV(v.value))}function he(){y.value!==null&&Object.assign(o.backups,mV(y.value))}function ge(){if(p.value!==null){let e=uV(p.value);o.control.label=e.label,o.control.port=e.port,o.control.host=e.host,o.control.openBrowser=e.openBrowser}}function _e(){p.value!==null&&Object.assign(o.auth,lV(p.value.auth))}function ve(){let e=m.value;e!==null&&(o.defaults.enabled=e.enabled,o.defaults.autostart=e.autostart,o.defaults.bind=e.bind,o.defaults.onPortConflict=e.onPortConflict,o.defaults.logBufferLines=e.logBufferLines,o.defaults.restart={...e.restart},o.defaults.health=sV(e.health),o.defaults.stop={...e.stop})}function ye(){h.value!==null&&Object.assign(o.logs,h.value)}function be(){g.value!==null&&Object.assign(o.telegram,dV(g.value))}function xe(){p.value!==null&&(o.control.tlsEnabled=p.value.tls.enabled)}function Se(e){rr(()=>{document.getElementById(e)?.scrollIntoView({behavior:`smooth`,block:`start`})})}function Ce(e){s.value=e,n.query.section!==e&&r.replace({query:{...n.query,section:e}}),Se(e)}jr(()=>n.query.section,e=>{let n=typeof e==`string`?e:``;t.some(e=>e.id===n)&&(s.value=n,Se(n))},{immediate:!0});let we=null;return na(()=>{we=new IntersectionObserver(e=>{let t=e.filter(e=>e.isIntersecting).sort((e,t)=>e.boundingClientRect.top-t.boundingClientRect.top)[0];t!==void 0&&t.target.id.length>0&&(s.value=t.target.id)},{rootMargin:`-72px 0px -62% 0px`,threshold:0});for(let e of t){let t=document.getElementById(e.id);t&&we.observe(t)}}),aa(()=>we?.disconnect()),(e,n)=>{let r=pa(`PasswordSection`);return I(),L(F,null,[z(`div`,xU,[z(`div`,SU,[n[24]||=z(`header`,{class:`mb-5`},[z(`h1`,{class:`text-xl font-semibold tracking-tight text-ink`},` Settings `),z(`p`,{class:`mt-1 text-xs text-muted`},` Listener, access, defaults, retention, alerts, backups and paths for this panel. `)],-1),j(i).configError.value?(I(),R(nV,{key:0,tone:`danger`,title:`The config file could not be read`,class:`mb-4`},{default:M(()=>[V(k(j(i).configError.value),1)]),_:1})):d.value?(I(),R(nV,{key:1,tone:`warn`,title:`Some file-only values could not be loaded`,class:`mb-4`},{default:M(()=>[V(k(d.value),1)]),_:1})):H(``,!0),z(`div`,CU,[z(`nav`,wU,[z(`ul`,TU,[(I(),L(F,null,ya(t,e=>z(`li`,{key:e.id},[z(`button`,{type:`button`,"aria-current":s.value===e.id?`true`:void 0,class:O(j(Q)(`-ml-px block w-full border-l-2 py-1.5 pl-3 text-left text-xs transition-colors duration-150`,s.value===e.id?`border-accent font-medium text-accent`:`border-transparent text-muted hover:border-line hover:text-ink`)),onClick:t=>Ce(e.id)},k(e.title),11,EU)])),64))])]),z(`div`,DU,[z(`section`,OU,[n[13]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Listener `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Where the control panel listens and how it announces itself. `)],-1),B(MH,{control:o.control,"onUpdate:control":n[0]||=e=>o.control=e,view:p.value,dirty:ne.value,onReset:ge},null,8,[`control`,`view`,`dirty`])]),z(`section`,kU,[n[14]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Authentication `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Who may sign in, and how a session is trusted. `)],-1),B(MV,{auth:o.auth,"onUpdate:auth":n[1]||=e=>o.auth=e,view:p.value,dirty:ie.value,onReset:_e},null,8,[`auth`,`view`,`dirty`])]),z(`section`,AU,[n[15]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Password `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` The one credential that guards this panel. `)],-1),B(r,{view:p.value},null,8,[`view`])]),z(`section`,jU,[n[16]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Server defaults `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Applied to every entry that does not set its own value. `)],-1),B(hH,{defaults:o.defaults,"onUpdate:defaults":n[2]||=e=>o.defaults=e,dirty:ae.value,onReset:ve},null,8,[`defaults`,`dirty`])]),z(`section`,MU,[n[17]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Logs `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` How much output is kept on disk and for how long. `)],-1),B(LH,{logs:o.logs,"onUpdate:logs":n[3]||=e=>o.logs=e,"logs-dir":j(i).appState.value?.logsDir??null,dirty:oe.value,onReset:ye},null,8,[`logs`,`logs-dir`,`dirty`])]),z(`section`,NU,[n[18]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Notifications `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Crash and health alerts sent to a Telegram chat. `)],-1),B(GH,{telegram:o.telegram,"onUpdate:telegram":n[4]||=e=>o.telegram=e,status:g.value,dirty:se.value,onReset:be},null,8,[`telegram`,`status`,`dirty`])]),z(`section`,PU,[n[19]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Host vitals `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Alert thresholds for this machine's own resources. `)],-1),B(vH,{host:o.host,"onUpdate:host":n[5]||=e=>o.host=e,config:v.value,alerts:_.value,dirty:ce.value,onReset:me},null,8,[`host`,`config`,`alerts`,`dirty`])]),z(`section`,FU,[n[20]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Backups `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Archives of config, secrets and declared data paths. `)],-1),B(pH,{form:o.backups,"onUpdate:form":n[6]||=e=>o.backups=e,state:x.value,policy:y.value,"config-path":S.value,dirty:D.value,onReset:he},null,8,[`form`,`state`,`policy`,`config-path`,`dirty`])]),z(`section`,IU,[n[21]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` TLS `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Serve the panel over HTTPS with your own certificate. `)],-1),B(bU,{"tls-enabled":o.control.tlsEnabled,"onUpdate:tlsEnabled":n[7]||=e=>o.control.tlsEnabled=e,view:p.value,dirty:re.value,onReset:xe},null,8,[`tls-enabled`,`view`,`dirty`])]),z(`section`,LU,[n[22]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Interface `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Serve your own panel UI instead of the stock one. `)],-1),B(EH,{label:o.control.label,"onUpdate:label":n[8]||=e=>o.control.label=e,ui:b.value,"label-dirty":ne.value,onChanged:de,onResetLabel:ge},null,8,[`label`,`ui`,`label-dirty`])]),z(`section`,RU,[n[23]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Paths `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Where home-hosted keeps its files. `)],-1),B(QH,{"config-path":S.value,"data-root":j(i).appState.value?.dataRoot??null,"logs-dir":j(i).appState.value?.logsDir??null,"project-dir":j(i).appState.value?.projectDir??null,"panel-url":p.value?.url??null},null,8,[`config-path`,`data-root`,`logs-dir`,`project-dir`,`panel-url`])])])])]),z(`div`,zU,[z(`div`,BU,[z(`div`,VU,[z(`p`,HU,[w.value>0?(I(),L(`button`,{key:0,type:`button`,class:`rounded-control px-1 py-0.5 text-xs text-muted underline decoration-line underline-offset-2 hover:text-ink`,onClick:n[9]||=e=>E.value=!0},[z(`span`,UU,k(w.value),1),V(` field`+k(w.value===1?``:`s`)+` changed `,1)])):(I(),L(F,{key:1},[V(` No unsaved changes `)],64))]),l.value?(I(),L(`p`,WU,k(l.value),1)):u.value?(I(),L(`p`,GU,k(u.value),1)):H(``,!0)]),z(`div`,KU,[B($,{variant:`ghost`,disabled:w.value===0||c.value,onClick:pe},{default:M(()=>[...n[25]||=[V(` Discard changes `,-1)]]),_:1},8,[`disabled`]),B($,{variant:`primary`,disabled:w.value===0,loading:c.value,onClick:fe},{default:M(()=>[...n[26]||=[V(` Save settings `,-1)]]),_:1},8,[`disabled`,`loading`])])])])]),B(HI,{open:E.value,"onUpdate:open":n[12]||=e=>E.value=e,title:`Unsaved changes`,description:`What Save would write to servers.config.json.`,width:`w-[min(92vw,42rem)]`},{default:M(()=>[z(`div`,qU,[ee.value.length>0?(I(),L(`ul`,JU,[(I(!0),L(F,null,ya(ee.value,e=>(I(),L(`li`,{key:e.path,class:`flex flex-wrap items-baseline gap-x-3 gap-y-1 py-2 first:pt-0`},[z(`code`,YU,k(e.path),1),z(`span`,XU,k(te(e.from)),1),n[27]||=z(`span`,{class:`text-2xs text-faint`},`→`,-1),z(`span`,ZU,k(te(e.to)),1)]))),128))])):(I(),L(`p`,QU,` Nothing to save. `))]),z(`footer`,$U,[B($,{size:`sm`,variant:`ghost`,onClick:n[10]||=e=>E.value=!1},{default:M(()=>[...n[28]||=[V(` Close `,-1)]]),_:1}),B($,{size:`sm`,variant:`primary`,disabled:ee.value.length===0||c.value,loading:c.value,onClick:n[11]||=e=>{fe(),E.value=!1}},{default:M(()=>[V(` Save `+k(ee.value.length)+` change`+k(ee.value.length===1?``:`s`),1)]),_:1},8,[`disabled`,`loading`])])]),_:1},8,[`open`])],64)}}}),tW=uv({history:M_(),routes:[{path:`/`,name:`overview`,component:tB,meta:{title:`Overview`}},{path:`/servers`,name:`servers`,component:QB,meta:{title:`Servers`}},{path:`/servers/:id`,name:`server-detail`,component:KB,meta:{title:`Server`}},{path:`/logs`,name:`logs`,component:_L,meta:{title:`Logs`}},{path:`/settings`,name:`settings`,component:eW,meta:{title:`Settings`}},{path:`/login`,name:`login`,component:VF,meta:{title:`Sign in`}},{path:`/:pathMatch(.*)*`,redirect:`/`}]});tW.beforeEach(async e=>{let{session:t,refresh:n}=Dj();t.value===null&&await n();let r=t.value?.authRequired===!0,i=t.value?.authenticated===!0;return r&&!i&&e.name!==`login`?{name:`login`,query:e.fullPath===`/`?void 0:{next:e.fullPath}}:e.name===`login`&&(!r||i)?{name:`overview`}:!0}),bu(gF).use(tW).mount(`#app`);
47
+ {port}`},null,8,[`modelValue`]),B(rR,{modelValue:p.env,"onUpdate:modelValue":t[5]||=e=>p.env=e,label:`Environment`,rows:3,hint:`KEY=value per line, exported to the process.`,placeholder:`NODE_ENV=production`},null,8,[`modelValue`]),B(rR,{modelValue:p.dataEnvs,"onUpdate:modelValue":t[6]||=e=>p.dataEnvs=e,label:`Data envs`,rows:3,hint:`ENV=path per line — exported to the process and backed up automatically.`,placeholder:`DATA_DIR={home}/.app`},null,8,[`modelValue`]),B(rR,{modelValue:p.backupPaths,"onUpdate:modelValue":t[7]||=e=>p.backupPaths=e,label:`Extra backup paths`,rows:2,hint:`One per line, placeholders allowed.`,placeholder:`{home}/.app/uploads`},null,8,[`modelValue`]),B(cR,{modelValue:p.dependsOn,"onUpdate:modelValue":t[8]||=e=>p.dependsOn=e,label:`Depends on`,placeholder:`postgres, redis`,hint:`Comma separated; started first, stopped last.`,class:`font-mono text-xs`},null,8,[`modelValue`]),B($L,{modelValue:p.maxRssMb,"onUpdate:modelValue":t[9]||=e=>p.maxRssMb=e,label:`Max RSS (MB)`,min:0,hint:`Restart above this; blank or 0 disables it.`},null,8,[`modelValue`])]),_:1}),B(YL,{title:`Port & binding`,description:`How the port is preflighted and where the process is reachable.`,columns:2},{default:M(()=>[B($L,{modelValue:p.port,"onUpdate:modelValue":t[10]||=e=>p.port=e,label:`Port`,nullable:``,min:1,max:65535,hint:`Blank means no port: no probe, no preflight.`},null,8,[`modelValue`]),B(JI,{modelValue:p.bind,"onUpdate:modelValue":t[11]||=e=>p.bind=e,label:`Bind address`,options:v.value,hint:`A custom IPv4 the config already sets stays selectable.`},null,8,[`modelValue`,`options`]),B(JI,{modelValue:p.onPortConflict,"onUpdate:modelValue":t[12]||=e=>p.onPortConflict=e,label:`On port conflict`,options:[{value:`block`,label:`block — refuse to start`},{value:`warn`,label:`warn — start anyway`}]},null,8,[`modelValue`]),B($L,{modelValue:p.logBufferLines,"onUpdate:modelValue":t[13]||=e=>p.logBufferLines=e,label:`Log buffer lines`,min:50,max:1e5,step:50},null,8,[`modelValue`])]),_:1}),B(YL,{title:`Lifecycle`,description:`Whether the supervisor owns this entry at all.`,columns:2},{default:M(()=>[B(dR,{modelValue:p.enabled,"onUpdate:modelValue":t[14]||=e=>p.enabled=e,label:`Enabled`,hint:`Shows up and can be started at all.`},null,8,[`modelValue`]),B(dR,{modelValue:p.autostart,"onUpdate:modelValue":t[15]||=e=>p.autostart=e,label:`Autostart with up`,hint:`Started when the control plane comes up.`},null,8,[`modelValue`])]),_:1}),B(uB,{restart:m.value,"onUpdate:restart":t[16]||=e=>m.value=e,health:h.value,"onUpdate:health":t[17]||=e=>h.value=e,stop:g.value,"onUpdate:stop":t[18]||=e=>g.value=e},null,8,[`restart`,`health`,`stop`]),B(YL,{title:`Bootstrap`,description:`A one-off command run before the first start — installs, migrations, downloads.`,columns:2},{default:M(()=>[B(dR,{modelValue:p.bootstrapEnabled,"onUpdate:modelValue":t[19]||=e=>p.bootstrapEnabled=e,label:`Run a bootstrap`,hint:`Off means the entry has none and the key is sent as null.`,wide:``},null,8,[`modelValue`]),p.bootstrapEnabled?(I(),L(F,{key:0},[B(cR,{modelValue:p.bootstrapCommand,"onUpdate:modelValue":t[20]||=e=>p.bootstrapCommand=e,label:`Command`,placeholder:`./scripts/install.sh`,class:`font-mono text-xs`},null,8,[`modelValue`]),B($L,{modelValue:p.bootstrapTimeoutMs,"onUpdate:modelValue":t[21]||=e=>p.bootstrapTimeoutMs=e,label:`Timeout (ms)`,min:1e3,step:1e3},null,8,[`modelValue`]),B(dR,{modelValue:p.bootstrapRunOnce,"onUpdate:modelValue":t[22]||=e=>p.bootstrapRunOnce=e,label:`Only once per session`,hint:`Off means every start re-runs it.`},null,8,[`modelValue`])],64)):H(``,!0),p.bootstrapEnabled?(I(),R(rR,{key:1,modelValue:p.bootstrapArgs,"onUpdate:modelValue":t[23]||=e=>p.bootstrapArgs=e,label:`Bootstrap arguments`,rows:2,hint:`One per line; same placeholders as the main arguments.`},null,8,[`modelValue`])):H(``,!0),p.bootstrapEnabled?(I(),R(rR,{key:2,modelValue:p.bootstrapEnv,"onUpdate:modelValue":t[24]||=e=>p.bootstrapEnv=e,label:`Bootstrap environment`,rows:2,hint:`KEY=value per line, exported to the bootstrap process only.`},null,8,[`modelValue`])):H(``,!0)]),_:1})]),s.value?(I(),L(`p`,gB,k(s.value),1)):H(``,!0),z(`div`,_B,[z(`button`,{type:`button`,class:`inline-flex h-8 select-none items-center rounded-control border border-transparent bg-transparent px-2.5 text-xs font-medium whitespace-nowrap text-muted transition-colors duration-150 hover:bg-hover hover:text-ink`,onClick:t[25]||=e=>r(`cancel`)},` Cancel `),z(`button`,{type:`submit`,disabled:o.value||!w.value,class:`inline-flex h-8 select-none items-center gap-1.5 rounded-control border border-transparent bg-accent px-2.5 text-xs font-medium whitespace-nowrap text-accent-ink transition-[filter,opacity] duration-150 hover:brightness-110 disabled:pointer-events-none disabled:opacity-45`},k(o.value?`Saving…`:`Save to servers.config.json`),9,vB)])],32))}}),bB=[`aria-label`],xB=N({__name:`CopyButton`,props:{value:{},label:{default:`Copy`},class:{default:void 0}},setup(e){let t=e,n=A(!1),r=null;async function i(){try{await navigator.clipboard.writeText(t.value),n.value=!0,r&&clearTimeout(r),r=setTimeout(()=>{n.value=!1},1400)}catch{}}return ze(()=>{r&&clearTimeout(r)}),(e,r)=>(I(),L(`button`,{type:`button`,class:O(j(Q)(`grid size-6 shrink-0 place-items-center rounded-control text-faint transition-colors duration-150 hover:bg-hover hover:text-ink`,j(ZP),t.class)),"aria-label":n.value?`Copied`:t.label,onClick:i},[n.value?(I(),R(j(Sv),{key:0,class:`size-3.5 text-ok`})):(I(),R(j(Nv),{key:1,class:`size-3.5`}))],10,bB))}}),SB={key:0,class:`mx-auto max-w-3xl p-4 sm:p-5`},CB={key:1,class:`mx-auto max-w-7xl space-y-5 p-4 sm:p-5`},wB={class:`font-mono text-xs text-faint`},TB=[`href`],EB={class:`px-2 py-1.5`},DB={class:`flex flex-col gap-0.5`},OB=[`onClick`],kB={class:`space-y-2`},AB={key:0,class:`rounded-control border border-danger/25 bg-danger-soft px-3 py-2 text-2xs leading-4 text-danger`},jB={key:1,class:`rounded-control border border-warn/25 bg-warn-soft px-3 py-2 text-2xs leading-4 text-warn`},MB={class:`grid grid-cols-2 gap-x-4 gap-y-3 p-4 sm:grid-cols-4 lg:grid-cols-6`},NB={class:`truncate text-2xs text-faint`},PB={class:`truncate font-mono text-xs tabular-nums text-ink capitalize`},FB={class:`grid gap-4 sm:grid-cols-3`},IB={class:`flex items-center justify-between gap-3`},LB=[`title`],RB={key:1,class:`space-y-4`},zB={key:0,class:`max-h-96 overflow-auto rounded-control border border-line bg-page/60 p-3 font-mono text-2xs leading-5 text-muted`},BB={key:1,class:`grid grid-cols-1 gap-x-6 gap-y-2 sm:grid-cols-2`},VB={class:`text-2xs text-faint`},HB=[`title`],UB={key:2,class:`grid gap-4 lg:grid-cols-3`},WB={key:0,class:`flex flex-wrap gap-1.5`},GB={key:1,class:`text-xs text-muted`},KB=N({__name:`ServerDetailView`,setup(e){let t=Dg(),{serverById:n,now:r,seriesOf:i,start:a,stop:o,restart:s,setEnabled:c,setAutostart:l,setBind:u,clearLogs:d,remove:f}=lM(),p=U(()=>String(t.params.id??``)),m=U(()=>n(p.value)),h=U(()=>m.value?.config),g=U(()=>{let e=h.value;return e?.label&&e.label.length>0?e.label:p.value}),_=A(`output`),v=A(!1),y=A(!1),b=A(!1),x=A(!1),S=cM(()=>m.value?p.value:null),C=A(0),w=U(()=>(C.value,S.lines())),T=0,ee=setInterval(()=>{let e=S.count.value;e!==T&&(T=e,C.value+=1)},250);ze(()=>clearInterval(ee));let E=U(()=>i(p.value)),te=U(()=>(C.value,E.value.cpu)),ne=U(()=>E.value.rss),re=U(()=>E.value.probe),ie=U(()=>{let e=m.value;return!e||e.startedAt===null||e.status!==`running`?`—`:nI(r.value-e.startedAt)}),ae=U(()=>m.value?.status===`running`),oe=U(()=>{let e=m.value;if(!e)return[];let t=[{key:`pid`,label:`PID`,value:e.pid===null?`—`:String(e.pid)},{key:`uptime`,label:`Uptime`,value:ie.value},{key:`status`,label:`Status`,value:e.status},{key:`health`,label:`Health`,value:e.health},{key:`port`,label:`Port state`,value:e.portState},{key:`probe`,label:`Last probe`,value:e.responseMs===null?`—`:`${e.responseMs} ms`},{key:`rss`,label:`RSS`,value:sI(e.resources?.rssBytes??null)},{key:`procs`,label:`Processes`,value:e.resources?String(e.resources.processes):`—`},{key:`restarts`,label:`Restarts`,value:`${e.restarts}/${e.maxRetries}`},{key:`up24`,label:`24h uptime`,value:iI(e.history.uptimeRatio)},{key:`crashes`,label:`24h crashes`,value:String(e.history.crashes)},{key:`lastcrash`,label:`Last crash`,value:aI(e.history.lastCrashAt,r.value)}];return e.nextRetryAt!==null&&t.push({key:`retry`,label:`Next attempt`,value:rI(e.nextRetryAt,r.value)}),e.unhealthySince!==null&&t.push({key:`unhealthy`,label:`Unhealthy for`,value:nI(r.value-e.unhealthySince)}),t}),se=U(()=>JSON.stringify(h.value??{},null,2)),ce=U(()=>h.value?`${h.value.command} ${h.value.args.join(` `)}`.trim():``),D=U(()=>{let e=h.value?.bind??`local`,t=[{value:`local`,label:`127.0.0.1 — this machine only`},{value:`lan`,label:`0.0.0.0 — every interface`}];return e!==`local`&&e!==`lan`&&t.push({value:e,label:`${e} — custom`}),t});async function le(e){x.value=!0;try{await{start:a,stop:o,restart:s}[e](p.value)}finally{x.value=!1}}async function ue(){b.value=!1,await f(p.value)}let de=U(()=>rj(p.value,`${p.value}.log`)),fe=[{value:`output`,label:`Live output`},{value:`config`,label:`Configuration`},{value:`history`,label:`History`}];return(e,t)=>{let n=pa(`RouterLink`);return m.value?(I(),L(`div`,CB,[B(_z,{title:g.value,"back-to":`/servers`,"back-label":`All servers`},{badges:M(()=>[B(LR,{status:m.value.status,health:m.value.health},null,8,[`status`,`health`]),z(`span`,wB,k(m.value.id),1),m.value.url?(I(),L(`a`,{key:0,href:m.value.url,target:`_blank`,rel:`noreferrer`,class:`inline-flex items-center gap-1 font-mono text-xs text-accent hover:underline`},[V(k(m.value.url)+` `,1),B(j(Lv),{class:`size-3`})],8,TB)):H(``,!0)]),actions:M(()=>[B($,{variant:`primary`,disabled:x.value||!h.value?.enabled||[`running`,`starting`].includes(m.value.status),onClick:t[0]||=e=>le(`start`)},{default:M(()=>[B(j(ey),{class:`size-3.5`}),t[16]||=V(`Start `,-1)]),_:1},8,[`disabled`]),B($,{disabled:x.value||m.value.status===`stopped`,onClick:t[1]||=e=>le(`stop`)},{default:M(()=>[B(j(py),{class:`size-3.5`}),t[17]||=V(`Stop `,-1)]),_:1},8,[`disabled`]),B($,{disabled:x.value||!h.value?.enabled,onClick:t[2]||=e=>le(`restart`)},{default:M(()=>[B(j(ay),{class:`size-3.5`}),t[18]||=V(`Restart `,-1)]),_:1},8,[`disabled`]),B(rF,{label:`Edit every field of this entry`},{default:M(()=>[B($,{variant:v.value?`secondary`:`ghost`,onClick:t[3]||=e=>{v.value=!v.value,_.value=`config`}},{default:M(()=>[B(j($v),{class:`size-3.5`}),V(k(v.value?`Close editor`:`Edit`),1)]),_:1},8,[`variant`])]),_:1}),B(j(Hh),null,{default:M(()=>[B(j(qh),{"as-child":``},{default:M(()=>[B($,{variant:`ghost`,size:`icon-sm`,"aria-label":`More actions for ${g.value}`},{default:M(()=>[B(j(Iv),{class:`size-4`})]),_:1},8,[`aria-label`])]),_:1}),B(j(Gh),null,{default:M(()=>[B(j(Uh),{"side-offset":4,align:`end`,class:`z-50 min-w-56 rounded-panel border border-line bg-raise p-1 shadow-float data-[state=open]:animate-[hh-fade-in_120ms_ease-out]`},{default:M(()=>[B(j(Wh),{class:`flex cursor-pointer items-center gap-2 rounded-control px-2 py-1.5 text-xs text-muted outline-none data-[highlighted]:bg-hover data-[highlighted]:text-ink`,onSelect:t[4]||=e=>j(c)(m.value.id,!m.value.config.enabled)},{default:M(()=>[V(k(h.value?.enabled?`Disable server`:`Enable server`),1)]),_:1}),B(j(Wh),{class:`flex cursor-pointer items-center gap-2 rounded-control px-2 py-1.5 text-xs text-muted outline-none data-[highlighted]:bg-hover data-[highlighted]:text-ink`,onSelect:t[5]||=e=>j(l)(m.value.id,!m.value.config.autostart)},{default:M(()=>[V(k(h.value?.autostart?`Remove from autostart`:`Autostart with the panel`),1)]),_:1}),B(j(Kh),{class:`my-1 h-px bg-line`}),z(`div`,EB,[t[19]||=z(`p`,{class:`mb-1 text-2xs text-faint`},` Bind `,-1),z(`div`,DB,[(I(!0),L(F,null,ya(D.value,e=>(I(),L(`button`,{key:e.value,type:`button`,class:O([`rounded-control px-2 py-1 text-left font-mono text-2xs transition-colors duration-150 hover:bg-hover`,e.value===h.value?.bind?`text-accent`:`text-muted`]),onClick:t=>j(u)(m.value.id,e.value)},k(e.label),11,OB))),128))])]),B(j(Kh),{class:`my-1 h-px bg-line`}),B(j(Wh),{class:`flex cursor-pointer items-center gap-2 rounded-control px-2 py-1.5 text-xs text-danger outline-none data-[highlighted]:bg-danger-soft`,onSelect:t[6]||=e=>b.value=!0},{default:M(()=>[B(j(vy),{class:`size-3.5`}),t[20]||=V(` Remove server `,-1)]),_:1})]),_:1})]),_:1})]),_:1})]),_:1},8,[`title`]),z(`div`,kB,[m.value.lastError?(I(),L(`p`,AB,k(m.value.lastError),1)):H(``,!0),m.value.health===`unhealthy`?(I(),L(`p`,jB,[t[21]||=V(` The health probe has not passed `,-1),m.value.unhealthySince===null?H(``,!0):(I(),L(F,{key:0},[V(` for `+k(j(nI)(j(r)-m.value.unhealthySince)),1)],64)),t[22]||=V(`. `,-1)])):H(``,!0)]),B(xz,{class:`p-0`},{default:M(()=>[z(`dl`,MB,[(I(!0),L(F,null,ya(oe.value,e=>(I(),L(`div`,{key:e.key,class:`min-w-0`},[z(`dt`,NB,k(e.label),1),z(`dd`,PB,k(e.value),1)]))),128))])]),_:1}),B(xz,null,{header:M(()=>[...t[23]||=[z(`h2`,{class:`text-sm font-semibold text-ink`},` Telemetry `,-1),z(`p`,{class:`text-2xs text-muted`},` Sampled every 5 s while this page is open. `,-1)]]),default:M(()=>[z(`div`,FB,[B(NR,{label:`CPU`,values:te.value,format:j(cI),max:100,"color-class":`text-cpu`,height:40},null,8,[`values`,`format`]),B(NR,{label:`Memory (RSS)`,values:ne.value,format:j(sI),"color-class":`text-mem`,height:40},null,8,[`values`,`format`]),B(NR,{label:`Health probe`,values:re.value,format:e=>e===null?`—`:`${e} ms`,"color-class":`text-probe`,height:40},null,8,[`values`,`format`])])]),_:1}),z(`div`,IB,[B(hI,{modelValue:_.value,"onUpdate:modelValue":t[7]||=e=>_.value=e,options:fe,size:`md`},null,8,[`modelValue`]),z(`p`,{class:`hidden font-mono text-2xs text-faint sm:block`,title:ce.value},k(ce.value),9,LB)]),_.value===`output`?(I(),R(jI,{key:0,lines:w.value,version:C.value,live:``,clearable:``,"download-url":m.value.config.logBufferLines>0?de.value:void 0,"download-name":`${m.value.id}.log`,"empty-hint":ae.value?`Waiting for the first line…`:`This server has not written anything yet.`,"min-height":`22rem`,onClear:t[8]||=e=>j(d)(m.value.id)},null,8,[`lines`,`version`,`download-url`,`download-name`,`empty-hint`])):_.value===`config`?(I(),L(`div`,RB,[v.value?(I(),R(yB,{key:0,"server-id":m.value.id,config:m.value.config,onSaved:t[9]||=e=>v.value=!1,onCancel:t[10]||=e=>v.value=!1},null,8,[`server-id`,`config`])):(I(),R(xz,{key:1},{header:M(()=>[...t[24]||=[z(`h2`,{class:`text-sm font-semibold text-ink`},` Effective configuration `,-1),z(`p`,{class:`text-2xs text-muted`},` Defaults from Settings are already merged into these values. `,-1)]]),actions:M(()=>[B($,{size:`xs`,variant:`ghost`,onClick:t[11]||=e=>y.value=!y.value},{default:M(()=>[B(j(jv),{class:`size-3`}),V(k(y.value?`Fields`:`Raw JSON`),1)]),_:1}),B(xB,{value:se.value,label:`Copy the JSON config`},null,8,[`value`]),B($,{size:`xs`,variant:`primary`,onClick:t[12]||=e=>v.value=!0},{default:M(()=>[B(j($v),{class:`size-3`}),t[25]||=V(`Edit `,-1)]),_:1})]),default:M(()=>[y.value?(I(),L(`pre`,zB,k(se.value),1)):(I(),L(`dl`,BB,[(I(!0),L(F,null,ya([{label:`command`,value:h.value?.command},{label:`args`,value:h.value?.args.join(` `)||`—`},{label:`cwd`,value:h.value?.cwd},{label:`env`,value:Object.keys(h.value?.env??{}).join(`, `)||`—`},{label:`data envs`,value:Object.entries(h.value?.dataEnvs??{}).map(([e,t])=>`${e}=${t}`).join(`, `)||`—`},{label:`env file`,value:h.value?.envFile||`—`},{label:`port · bind`,value:`${h.value?.port??`none`} · ${h.value?.bind}`},{label:`on port conflict`,value:h.value?.onPortConflict},{label:`log buffer`,value:`${h.value?.logBufferLines} lines`},{label:`rtss limit`,value:h.value?.resources.maxRssBytes?j(sI)(h.value.resources.maxRssBytes):`none`},{label:`health`,value:h.value?.health.enabled?`${h.value.health.mode} every ${h.value.health.intervalMs} ms`:`off`},{label:`restart`,value:h.value?.restart.enabled?`${h.value.restart.maxRetries} retries, ×${h.value.restart.factor}`:`off`},{label:`stop`,value:h.value?`${h.value.stop.signal} after ${h.value.stop.graceMs} ms`:`—`},{label:`bootstrap`,value:h.value?.bootstrap?`${h.value.bootstrap.command} ${h.value.bootstrap.args.join(` `)}`:`none`},{label:`backup paths`,value:h.value?.backupPaths.join(`, `)||`—`},{label:`depends on`,value:h.value?.dependsOn.join(`, `)||`—`}],e=>(I(),L(`div`,{key:e.label,class:`min-w-0 border-b border-line-soft pb-1.5 last:border-0`},[z(`dt`,VB,k(e.label),1),z(`dd`,{class:`truncate font-mono text-xs text-ink`,title:String(e.value)},k(e.value),9,HB)]))),128))]))]),_:1}))])):(I(),L(`div`,UB,[B(xz,{class:`lg:col-span-2`},{header:M(()=>[...t[26]||=[z(`h2`,{class:`text-sm font-semibold text-ink`},` Event history `,-1),z(`p`,{class:`text-2xs text-muted`},` Kept by the panel for 24 hours. `,-1)]]),default:M(()=>[B(UL,{servers:[m.value],now:j(r),limit:30},null,8,[`servers`,`now`])]),_:1}),B(xz,null,{header:M(()=>[...t[27]||=[z(`h2`,{class:`text-sm font-semibold text-ink`},` Dependencies `,-1)]]),default:M(()=>[h.value&&h.value.dependsOn.length>0?(I(),L(`div`,WB,[(I(!0),L(F,null,ya(h.value.dependsOn,e=>(I(),R(n,{key:e,to:`/servers/${e}`,class:`rounded-full border border-line px-2 py-0.5 font-mono text-2xs text-muted transition-colors duration-150 hover:border-accent/40 hover:text-accent`},{default:M(()=>[V(k(e),1)]),_:2},1032,[`to`]))),128))])):(I(),L(`p`,GB,[...t[28]||=[V(` Starts on its own. Add ids under `,-1),z(`code`,null,`dependsOn`,-1),V(` to order it after other servers. `,-1)]]))]),_:1})])),B(HI,{open:b.value,"onUpdate:open":t[14]||=e=>b.value=e,title:`Remove this server?`,description:`${g.value} is removed from servers.config.json. Its process is stopped and its persisted logs stay on disk.`,width:`w-[min(92vw,26rem)]`},{footer:M(()=>[B($,{variant:`ghost`,onClick:t[13]||=e=>b.value=!1},{default:M(()=>[...t[29]||=[V(` Keep it `,-1)]]),_:1}),B($,{variant:`danger`,onClick:ue},{default:M(()=>[B(j(vy),{class:`size-3.5`}),t[30]||=V(`Remove `,-1)]),_:1})]),default:M(()=>[t[31]||=z(`p`,{class:`text-xs text-muted`},` This cannot be undone from the panel — the entry has to be added back by hand or from a backup. `,-1)]),_:1},8,[`open`,`description`])])):(I(),L(`div`,SB,[B(LI,{title:`That server is gone`,description:`${p.value} is not in servers.config.json any more. It may have been removed from another tab.`},{action:M(()=>[B(n,{to:`/servers`},{default:M(()=>[B($,{variant:`primary`},{default:M(()=>[...t[15]||=[V(` Back to servers `,-1)]]),_:1})]),_:1})]),_:1},8,[`description`])]))}}}),qB={class:`mx-auto max-w-7xl space-y-5 p-4 sm:p-5`},JB={class:`flex flex-wrap items-center gap-2`},YB={class:`relative ml-auto w-full sm:w-64`},XB={key:0,class:`grid gap-4 lg:grid-cols-2`},ZB={key:3,class:`grid gap-4 lg:grid-cols-2`},QB=N({__name:`ServersView`,setup(e){let{servers:t,appState:n,now:r,seriesOf:i,startAll:a,stopAll:o}=lM(),s=A(!1),c=A(``),l=A(`all`),u=[{value:`all`,label:`All`},{value:`running`,label:`Running`},{value:`stopped`,label:`Stopped`},{value:`attention`,label:`Attention`},{value:`disabled`,label:`Disabled`}],d=U(()=>n.value===null),f=U(()=>{let e=c.value.trim().toLowerCase();return t.value.filter(t=>l.value===`running`&&t.status!==`running`||l.value===`stopped`&&![`stopped`,`stopping`].includes(t.status)||l.value===`disabled`&&t.config.enabled||l.value===`attention`&&t.status!==`crashed`&&t.status!==`conflict`&&t.health!==`unhealthy`?!1:e.length===0||`${t.id} ${t.config.label??``} ${t.config.command} ${t.config.port??``}`.toLowerCase().includes(e))}),p=U(()=>({all:t.value.length,running:t.value.filter(e=>e.status===`running`).length,attention:t.value.filter(e=>e.status===`crashed`||e.status===`conflict`||e.health===`unhealthy`).length}));return(e,n)=>(I(),L(`div`,qB,[B(_z,{title:`Servers`,description:`Every entry in servers.config.json, with live resources, health and history.`},{actions:M(()=>[B($,{variant:`ghost`,onClick:n[0]||=e=>j(a)()},{default:M(()=>[B(j(ey),{class:`size-3.5`}),n[9]||=V(`Start all `,-1)]),_:1}),B($,{variant:`ghost`,onClick:n[1]||=e=>j(o)()},{default:M(()=>[B(j(py),{class:`size-3.5`}),n[10]||=V(`Stop all `,-1)]),_:1}),B($,{variant:`primary`,onClick:n[2]||=e=>s.value=!0},{default:M(()=>[B(j(ty),{class:`size-3.5`}),n[11]||=V(`Add server `,-1)]),_:1})]),_:1}),z(`div`,JB,[B(hI,{modelValue:l.value,"onUpdate:modelValue":n[3]||=e=>l.value=e,options:u.map(e=>({...e,label:`${e.label}${e.value===`all`?` ${p.value.all}`:e.value===`running`?` ${p.value.running}`:e.value===`attention`&&p.value.attention>0?` ${p.value.attention}`:``}`}))},null,8,[`modelValue`,`options`]),z(`div`,YB,[B(j(sy),{class:`pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-faint`}),xr(z(`input`,{"onUpdate:modelValue":n[4]||=e=>c.value=e,type:`search`,placeholder:`Filter by id, label, command or port`,class:O([`pl-8`,[j(eF)]]),"aria-label":`Filter servers`},null,2),[[Yl,c.value]]),c.value.length>0?(I(),L(`button`,{key:0,type:`button`,class:`absolute right-1.5 top-1/2 grid size-6 -translate-y-1/2 place-items-center rounded-control text-faint hover:text-ink`,"aria-label":`Clear the filter`,onClick:n[5]||=e=>c.value=``},[B(j(by),{class:`size-3.5`})])):H(``,!0)])]),d.value?(I(),L(`div`,XB,[(I(),L(F,null,ya(4,e=>B(YI,{key:e,class:`h-72 w-full`,rounded:`rounded-panel`})),64))])):j(t).length===0?(I(),R(LI,{key:1,title:`No servers configured`,description:`Add the first process this panel should supervise. Its command, args, env and working directory all live in the config file.`},{icon:M(()=>[B(j(cy),{class:`size-4`})]),action:M(()=>[B($,{variant:`primary`,onClick:n[6]||=e=>s.value=!0},{default:M(()=>[B(j(ty),{class:`size-3.5`}),n[12]||=V(`Add a server `,-1)]),_:1})]),_:1})):f.value.length===0?(I(),R(LI,{key:2,compact:``,title:`Nothing matches that filter`,description:`Try a different status or clear the search.`},{action:M(()=>[B($,{onClick:n[7]||=e=>{c.value=``,l.value=`all`}},{default:M(()=>[...n[13]||=[V(` Clear filters `,-1)]]),_:1})]),_:1})):(I(),L(`div`,ZB,[(I(!0),L(F,null,ya(f.value,e=>(I(),R(uz,{key:e.id,server:e,series:j(i)(e.id),now:j(r)},null,8,[`server`,`series`,`now`]))),128))])),B(gR,{open:s.value,"onUpdate:open":n[8]||=e=>s.value=e},null,8,[`open`])]))}}),$B=N({__name:`ConfirmButton`,props:{label:{},confirmLabel:{default:`Confirm`},variant:{default:`danger-ghost`},confirmVariant:{default:`danger`},size:{default:`sm`},disabled:{type:Boolean,default:!1},loading:{type:Boolean,default:!1}},emits:[`confirm`],setup(e,{emit:t}){let n=e,r=t,i=A(!1),a=null;function o(){a&&clearTimeout(a),a=null,i.value=!1}function s(){if(!i.value){i.value=!0,a=setTimeout(o,5e3);return}o(),r(`confirm`)}return ze(o),(e,t)=>(I(),R($,{variant:i.value?n.confirmVariant:n.variant,size:n.size,disabled:n.disabled,loading:n.loading,onClick:s},{default:M(()=>[V(k(i.value?n.confirmLabel:n.label),1)]),_:1},8,[`variant`,`size`,`disabled`,`loading`]))}}),eV={class:`min-w-0 flex-1`},tV={key:0,class:`text-xs font-semibold`},nV=N({__name:`Notice`,props:{tone:{default:`info`},title:{default:void 0},class:{default:void 0}},setup(e){let t=e,n={ok:Ov,warn:yy,danger:Zv,info:Hv,neutral:Hv,accent:ly},r=U(()=>n[t.tone]);return(e,n)=>(I(),L(`div`,{class:O(j(Q)(`flex items-start gap-2 rounded-panel border px-3 py-2`,j(XN)[t.tone],t.class))},[(I(),R(ha(r.value),{class:`mt-0.5 size-3.5 shrink-0`,"aria-hidden":`true`})),z(`div`,eV,[t.title?(I(),L(`p`,tV,k(t.title),1)):H(``,!0),z(`div`,{class:O(j(Q)(`text-xs leading-relaxed text-ink/85`,t.title&&`mt-0.5`))},[P(e.$slots,`default`)],2)])],2))}});async function rV(e,t=8e3){let n=Date.now()+t;for(;;){try{if((await fetch(`${e}/api/auth/session`,{cache:`no-store`})).ok)return!0}catch{}if(Date.now()>n)return!1;await new Promise(e=>setTimeout(e,250))}}var iV={enabled:!0,maxRetries:3,baseDelayMs:1e3,factor:2,maxDelayMs:3e4,resetAfterMs:6e4},aV={enabled:!0,mode:`port`,http:{path:`/`,method:`GET`,expectStatusBelow:400,expectBody:``},intervalMs:5e3,timeoutMs:1500,unhealthyThreshold:3,forceRestartAfterMs:0,startTimeoutMs:2e4},oV={signal:`SIGTERM`,killGroup:!0,graceMs:5e3,killPortHolders:!1};function sV(e){return{...e,http:{...e.http}}}function cV(){return{control:{label:`Stock UI`,port:3999,host:`local`,openBrowser:!1,tlsEnabled:!1},auth:{enabled:!1,sessionTtlMs:6048e5,cookieSecure:`auto`,trustProxy:!1,maxLoginAttempts:5,lockoutMs:6e4},defaults:{enabled:!0,autostart:!1,bind:`local`,onPortConflict:`block`,logBufferLines:500,restart:{...iV},health:sV(aV),stop:{...oV}},logs:{persist:!0,maxBytes:2e6,keep:3},host:{enabled:!0,intervalMs:15e3,diskPaths:`.`,diskUsedPercent:90,memoryUsedPercent:90,swapUsedPercent:50,loadPerCpu:2,tempCelsius:85},backups:{enabled:!0,keep:5,includePaths:``},telegram:{enabled:!1,chatId:``,cooldownMs:12e4,onCrash:!0,onUnhealthy:!0,onForcedRestart:!0,onRecovered:!1,onHost:!0}}}function lV(e){return{enabled:e.enabled,sessionTtlMs:e.sessionTtlMs,cookieSecure:e.cookieSecure,trustProxy:e.trustProxy,maxLoginAttempts:e.maxLoginAttempts,lockoutMs:e.lockoutMs}}function uV(e){return{label:e.label,port:e.port,host:e.host,openBrowser:e.openBrowser,tlsEnabled:e.tls.enabled}}function dV(e){return{enabled:e.enabled,chatId:e.chatId,cooldownMs:e.cooldownMs,onCrash:e.onCrash,onUnhealthy:e.onUnhealthy,onForcedRestart:e.onForcedRestart,onRecovered:e.onRecovered,onHost:e.onHost}}function fV(e){return e.split(`,`).map(e=>e.trim()).filter(e=>e.length>0)}function pV(e){return{enabled:e.enabled,intervalMs:e.intervalMs,diskPaths:e.diskPaths.join(`, `),diskUsedPercent:e.diskUsedPercent,memoryUsedPercent:e.memoryUsedPercent,swapUsedPercent:e.swapUsedPercent,loadPerCpu:e.loadPerCpu,tempCelsius:e.tempCelsius}}function mV(e){return{enabled:e.enabled,keep:e.keep,includePaths:e.includePaths.join(`, `)}}function hV(e,t){return iB(e,{...t,diskPaths:fV(t.diskPaths)})}function gV(e,t){return iB(e,{...t,includePaths:fV(t.includePaths)})}function _V(e,t){return iB({label:e.label,port:e.port,host:e.host,openBrowser:e.openBrowser,auth:lV(e.auth),tls:{enabled:e.tls.enabled}},{label:t.control.label,port:t.control.port,host:t.control.host,openBrowser:t.control.openBrowser,auth:{...t.auth},tls:{enabled:t.control.tlsEnabled}},[`auth`,`tls`])}function vV(e,t){let n=iB(e,t,[`restart`,`health`,`stop`]),r=n.health;return r!==void 0&&`http`in r&&JSON.stringify(e.health.http)===JSON.stringify(t.health.http)&&(delete r.http,Object.keys(r).length===0&&delete n.health),n}function yV(e,t){return iB(e,t)}function bV(e,t){return iB({telegram:dV(e)},{telegram:{...t}},[`telegram`])}function xV(e){return e.liveAvailable?!e.hydrated||e.changedCount===0:!1}function SV(e,t=``){return typeof e!=`object`||!e||Array.isArray(e)?[{path:t,value:e}]:Object.entries(e).flatMap(([e,n])=>SV(n,t.length===0?e:`${t}.${e}`))}function CV(e,t){let n=new Map(SV(t).map(e=>[e.path,e.value]));return SV(e).map(e=>({path:e.path,from:n.get(e.path),to:e.value}))}function wV(e){let t=0;for(let n of Object.values(e))t+=typeof n==`object`&&n&&!Array.isArray(n)?wV(n):1;return t}function TV(e,t){return e.label!==t.control.label||e.port!==t.control.port||e.host!==t.control.host||e.openBrowser!==t.control.openBrowser}function EV(e,t){return e.tls.enabled!==t.control.tlsEnabled}function DV(e,t){return wV(iB({...lV(e.auth)},{...t.auth}))>0}function OV(e,t,n){return U({get:e,set:e=>{t(e===null||Number.isNaN(e)?n:e)}})}async function kV(e){if(!e.rebinding)return null;let t=e.targetUrl??e.control.url;return t.length===0||t===window.location.origin?null:await rV(t)?(window.location.replace(t),null):`The control panel did not answer on ${t}. Check the console for the new listener.`}var AV={class:`space-y-2`},jV={class:`flex flex-wrap items-center gap-2 sm:col-span-2`},MV=N({__name:`AuthenticationSection`,props:Ba({view:{},dirty:{type:Boolean}},{auth:{required:!0},authModifiers:{}}),emits:Ba([`reset`],[`update:auth`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`auth`),a=OV(()=>i.value.sessionTtlMs,e=>i.value.sessionTtlMs=e,6048e5),o=OV(()=>i.value.maxLoginAttempts,e=>i.value.maxLoginAttempts=e,5),s=OV(()=>i.value.lockoutMs,e=>i.value.lockoutMs=e,6e4),c=U(()=>n.view?.auth.passwordSet===!0),l=U(()=>n.view?.auth.blockedReason??null),u=U(()=>n.view?.auth.exposed===!0),d=U(()=>n.view?.auth.usingDefaultPassword===!0),f=U(()=>i.value.enabled&&!c.value),{setPassword:p,clearPassword:m}=Dj(),h=A(``),g=A(``),_=A(``),v=A(null),y=A(null),b=A(!1),x=U(()=>_.value.length>0&&_.value!==g.value),S=U(()=>g.value.length>0&&g.value.length<8),C=U(()=>g.value.length>0&&!x.value&&(!c.value||h.value.length>0));async function w(){if(C.value&&!b.value){b.value=!0,y.value=null,v.value=null;try{let e=await p(c.value?h.value:void 0,g.value);if(e!==null){y.value=e;return}h.value=``,g.value=``,_.value=``,v.value=`Password updated. Other sessions were signed out.`}finally{b.value=!1}}}async function T(){y.value=null,v.value=null;let e=await m();e===null?v.value=`Password cleared and authentication disabled.`:y.value=e}return(e,t)=>(I(),L(F,null,[B(YL,{title:`Access policy`,description:`Authentication applies whether the panel is on loopback or exposed to the network.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[10]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value.enabled,"onUpdate:modelValue":t[1]||=e=>i.value.enabled=e,label:`Require a password`,hint:`Every request needs a signed-in session.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B($L,{modelValue:j(a),"onUpdate:modelValue":t[2]||=e=>mn(a)?a.value=e:null,label:`Session lifetime (ms)`,min:6e4,hint:`604800000 is one week.`},null,8,[`modelValue`]),B(JI,{modelValue:i.value.cookieSecure,"onUpdate:modelValue":t[3]||=e=>i.value.cookieSecure=e,label:`Cookie Secure`,options:[{value:`auto`,label:`Auto — only over HTTPS`},{value:`always`,label:`Always`},{value:`never`,label:`Never — plain HTTP`}]},null,8,[`modelValue`]),B(dR,{modelValue:i.value.trustProxy,"onUpdate:modelValue":t[4]||=e=>i.value.trustProxy=e,label:`Behind a trusted reverse proxy`,hint:`Trust x-forwarded-* headers for the client address and protocol.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B($L,{modelValue:j(o),"onUpdate:modelValue":t[5]||=e=>mn(o)?o.value=e:null,label:`Max login attempts`,min:1,hint:`Failures before a temporary lockout.`},null,8,[`modelValue`]),B($L,{modelValue:j(s),"onUpdate:modelValue":t[6]||=e=>mn(s)?s.value=e:null,label:`Lockout (ms)`,min:1e3},null,8,[`modelValue`])]),_:1}),z(`div`,AV,[d.value?(I(),R(nV,{key:0,tone:`warn`,title:`The default password is still in use`},{default:M(()=>[...t[11]||=[V(` Set your own password below. Binding beyond `,-1),z(`code`,null,`127.0.0.1`,-1),V(` stays refused until you do. `,-1)]]),_:1})):H(``,!0),l.value?(I(),R(nV,{key:1,tone:`danger`,title:`This listener is being refused`},{default:M(()=>[V(k(l.value),1)]),_:1})):f.value?(I(),R(nV,{key:2,tone:`warn`,title:`Authentication is on but no password is set`},{default:M(()=>[...t[12]||=[V(` Nothing is actually required yet, and binding beyond `,-1),z(`code`,null,`127.0.0.1`,-1),V(` stays refused until a password exists. `,-1)]]),_:1})):u.value?(I(),R(nV,{key:3,tone:`warn`,title:`Reachable beyond 127.0.0.1`},{default:M(()=>[...t[13]||=[V(` Keep the password strong, or put a TLS-terminating proxy in front of the panel. `,-1)]]),_:1})):H(``,!0)]),z(`form`,{class:`mt-3`,onSubmit:uu(w,[`prevent`])},[B(YL,{title:c.value?`Change password`:`Set a password`,description:`Stored as a scrypt hash in the secrets file, never in the config.`},{default:M(()=>[c.value?(I(),R(cR,{key:0,modelValue:h.value,"onUpdate:modelValue":t[7]||=e=>h.value=e,type:`password`,autocomplete:`current-password`,label:`Current password`},null,8,[`modelValue`])):H(``,!0),B(cR,{modelValue:g.value,"onUpdate:modelValue":t[8]||=e=>g.value=e,type:`password`,autocomplete:`new-password`,label:`New password`},null,8,[`modelValue`]),B(cR,{modelValue:_.value,"onUpdate:modelValue":t[9]||=e=>_.value=e,type:`password`,autocomplete:`new-password`,label:`Repeat new password`,error:x.value?`The two passwords do not match.`:null},null,8,[`modelValue`,`error`]),z(`div`,jV,[B($,{type:`submit`,variant:`primary`,disabled:!C.value,loading:b.value},{default:M(()=>[V(k(c.value?`Update password`:`Set password and enable auth`),1)]),_:1},8,[`disabled`,`loading`]),c.value?(I(),R($B,{key:0,label:`Clear password`,"confirm-label":`Confirm clear`,"confirm-variant":`danger`,disabled:b.value,onConfirm:T},null,8,[`disabled`])):H(``,!0)]),S.value||y.value||v.value?(I(),L(F,{key:1},[S.value?(I(),R(nV,{key:0,tone:`warn`,title:`That is a short password`,class:`sm:col-span-2`},{default:M(()=>[...t[14]||=[V(` It is allowed, but longer is the only thing standing between the network and this panel. `,-1)]]),_:1})):H(``,!0),y.value?(I(),R(nV,{key:1,tone:`danger`,class:`sm:col-span-2`},{default:M(()=>[V(k(y.value),1)]),_:1})):H(``,!0),v.value?(I(),R(nV,{key:2,tone:`ok`,class:`sm:col-span-2`},{default:M(()=>[V(k(v.value),1)]),_:1})):H(``,!0)],64)):H(``,!0)]),_:1},8,[`title`])],32)],64))}}),NV={class:`space-y-3`},PV={key:0,class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},FV={class:`flex items-center gap-1.5 rounded-control border border-line bg-page/60 px-2.5 py-1.5`},IV={class:`min-w-0 flex-1 truncate font-mono text-xs text-ink`},LV={class:`text-2xs leading-4 text-faint`},RV={key:0,class:`space-y-2 sm:col-span-2`},zV={key:1,class:`text-xs text-muted sm:col-span-2`},BV={key:2,class:`divide-y divide-line-soft sm:col-span-2`},VV={class:`min-w-0`},HV={class:`truncate font-mono text-xs text-ink`},UV={class:`mt-0.5 text-2xs leading-4 text-muted`},WV={class:`sm:col-span-2`},GV={key:0,class:`space-y-2 sm:col-span-2`},KV={key:1,class:`sm:col-span-2`},qV={key:2,class:`divide-y divide-line-soft sm:col-span-2`},JV={class:`min-w-0 flex-1`},YV={class:`flex items-center gap-2`},XV={class:`truncate font-mono text-xs text-ink`},ZV={class:`mt-0.5 font-mono text-2xs tabular-nums text-muted`},QV={class:`flex items-center gap-1.5`},$V=[`href`],eH={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},tH={key:0,class:`font-mono text-2xs text-muted`},nH={class:`sm:col-span-2`},rH={key:0,class:`sm:col-span-2`},iH={class:`mt-2`},aH={class:`mt-2 max-w-sm`},oH={class:`mt-2`},sH={class:`mt-2`},cH={class:`mt-1 max-h-64 space-y-0.5 overflow-auto`},lH={class:`mt-2 flex flex-wrap items-center gap-2`},uH={class:`mt-2`},dH={class:`mt-2`},fH={key:1,class:`space-y-2 sm:col-span-2`},pH=N({__name:`BackupsSection`,props:Ba({state:{},policy:{},configPath:{},dirty:{type:Boolean}},{form:{required:!0},formModifiers:{}}),emits:Ba([`reset`],[`update:form`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`form`),a=lM(),o=OV(()=>i.value.keep,e=>i.value.keep=e,5),s=A(!1),c=A(null),l=A(null),u=A(``),d=A(null),f=A(null),p=A(``),m=A(null),h=U(()=>n.state?.files??[]),g=U(()=>n.state?.paths??[]),_=U(()=>d.value?.items.filter(e=>e.selected).length??0),v=U(()=>d.value?.items.some(e=>e.id===`config`&&e.selected)??!1),y=Q(eF,`cursor-pointer text-xs`,`file:mr-2 file:cursor-pointer file:rounded-control file:border-0 file:bg-panel-2 file:px-2 file:py-1 file:text-xs file:text-ink`),b=Q(`inline-flex h-6 shrink-0 items-center gap-1 rounded-control border border-line bg-raise px-2 text-2xs font-medium text-ink`,`transition-colors duration-150 hover:bg-hover`,ZP);function x(e){for(let t of d.value?.items??[])t.restorable&&(t.selected=e)}async function S(){s.value=!0,l.value=null,c.value=null;try{await ij(u.value),u.value=``,c.value=`Backup created.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{s.value=!1}}async function C(e){s.value=!0,l.value=null,c.value=null;try{await aj(e),c.value=`Deleted ${e}.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{s.value=!1}}function w(e){f.value=e.target.files?.[0]??null,d.value=null,m.value=null,l.value=null,c.value=null}function T(){return p.value.length>0?{password:p.value}:{}}async function ee(e){s.value=!0,l.value=null,c.value=null,d.value=null;try{e===void 0?f.value===null?m.value=null:(m.value={kind:`upload`},d.value=await lj(f.value,!1,T())):(m.value={kind:`stored`,name:e},d.value=await cj(e,!1,T())),d.value===null&&(l.value=`Choose a stored backup or upload one first.`)}catch(e){m.value=null,l.value=e instanceof Error?e.message:String(e)}finally{s.value=!1}}async function E(){let e=m.value;e!==null&&await ee(e.kind===`stored`?e.name:void 0)}async function te(){let e=m.value,t=d.value;if(e===null||t===null){l.value=`Check an archive first.`;return}s.value=!0,l.value=null;let n={...T(),include:t.items.filter(e=>e.restorable&&e.selected).map(e=>e.id)};try{let t=e.kind===`stored`?await cj(e.name,!0,n):f.value===null?null:await lj(f.value,!0,n);d.value=null,m.value=null,f.value=null,p.value=``;let r=[];t?.reloaded&&r.push(`the restored servers are live, autostart entries starting`),t?.restartRequired&&r.push(`restart the panel to apply its own settings`),c.value=t===null?`The restore failed.`:`Restored ${t.applied.length} item(s)${r.length===0?``:` — ${r.join(`; `)}`}.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{s.value=!1}}return(e,t)=>(I(),L(`div`,NV,[B(YL,{title:`Policy`,description:`What a backup captures, and how many archives are kept.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[9]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value.enabled,"onUpdate:modelValue":t[1]||=e=>i.value.enabled=e,label:`Allow backups`,hint:`Turning this off hides nothing: the archives already on disk stay downloadable.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B($L,{modelValue:j(o),"onUpdate:modelValue":t[2]||=e=>mn(o)?o.value=e:null,label:`Keep`,min:1,hint:`Older archives are pruned after each backup.`},null,8,[`modelValue`]),B(cR,{modelValue:i.value.includePaths,"onUpdate:modelValue":t[3]||=e=>i.value.includePaths=e,label:`Extra paths`,hint:`Comma separated, in addition to every entry's data directories.`,placeholder:`{home}/myapp-data`},null,8,[`modelValue`]),n.policy===null?H(``,!0):(I(),L(`div`,PV,[t[12]||=z(`span`,{class:`text-xs font-medium text-muted`},`Archive directory`,-1),z(`div`,FV,[z(`span`,IV,k(n.policy.dir),1),B(xB,{value:n.policy.dir,label:`Copy the archive directory`},null,8,[`value`])]),z(`p`,LV,[t[10]||=V(` Changing it is a file-level decision: edit `,-1),z(`code`,null,k(n.configPath??`the config file`),1),t[11]||=V(`. `,-1)])]))]),_:1}),B(YL,{title:`Declared paths`,description:`Config, secrets and TLS are always captured. A path already covered by a declared parent is skipped.`},{default:M(()=>[n.state===null?(I(),L(`div`,RV,[B(YI,{class:`h-4 w-full`})])):g.value.length===0?(I(),L(`p`,zV,[...t[13]||=[V(` No data paths are declared yet. Add `,-1),z(`code`,null,`dataEnvs`,-1),V(` or `,-1),z(`code`,null,`backupPaths`,-1),V(` to a server to capture its data. `,-1)]])):(I(),L(`ul`,BV,[(I(!0),L(F,null,ya(g.value,e=>(I(),L(`li`,{key:`${e.origin}:${e.path}`,class:`flex items-start justify-between gap-3 py-1.5`},[z(`div`,VV,[z(`p`,HV,k(e.path),1),z(`p`,UV,k(e.included?`from ${e.origin}`:e.note??`not captured`),1)]),B(vF,{tone:e.included?`ok`:`neutral`,plain:``},{default:M(()=>[V(k(e.included?`included`:`skipped`),1)]),_:2},1032,[`tone`])]))),128))]))]),_:1}),B(YL,{title:`Create a backup now`,description:`Optional password encrypts the archive; restoring it will ask for the same password.`},{default:M(()=>[B(cR,{modelValue:u.value,"onUpdate:modelValue":t[4]||=e=>u.value=e,type:`password`,autocomplete:`new-password`,label:`Password (optional)`,wide:``},null,8,[`modelValue`]),z(`div`,WV,[B($,{variant:`primary`,disabled:s.value,loading:s.value,onClick:S},{default:M(()=>[...t[14]||=[V(` Create backup now `,-1)]]),_:1},8,[`disabled`,`loading`])])]),_:1}),B(YL,{title:`Archives`,description:`Stored in the backup directory and downloadable at any time.`},{default:M(()=>[n.state===null?(I(),L(`div`,GV,[B(YI,{class:`h-4 w-full`}),B(YI,{class:`h-4 w-2/3`})])):h.value.length===0?(I(),L(`div`,KV,[B(LI,{compact:``,title:`No archives yet`,description:`Create one now to capture the config, secrets and every declared data path.`},{icon:M(()=>[B(j(bv),{class:`size-4`})]),action:M(()=>[B($,{size:`sm`,variant:`primary`,disabled:s.value,onClick:S},{default:M(()=>[...t[15]||=[V(` Create backup now `,-1)]]),_:1},8,[`disabled`])]),_:1})])):(I(),L(`ul`,qV,[(I(!0),L(F,null,ya(h.value,e=>(I(),L(`li`,{key:e.name,class:`flex flex-wrap items-center gap-x-3 gap-y-2 py-2 first:pt-0 last:pb-0`},[z(`div`,JV,[z(`div`,YV,[z(`span`,XV,k(e.name),1),e.encrypted?(I(),R(vF,{key:0,tone:`warn`,plain:``},{default:M(()=>[...t[16]||=[V(` encrypted `,-1)]]),_:1})):H(``,!0)]),z(`p`,ZV,k(j(oI)(e.sizeBytes))+` · `+k(j(tI)(e.createdAt)),1)]),z(`div`,QV,[z(`a`,{href:oj(e.name),class:O(j(b))},[B(j(Fv),{class:`size-3`,"aria-hidden":`true`}),t[17]||=V(` Download `,-1)],10,$V),B($,{size:`xs`,variant:`secondary`,disabled:s.value,onClick:t=>ee(e.name)},{default:M(()=>[B(j(iy),{class:`size-3`,"aria-hidden":`true`}),t[18]||=V(` Restore `,-1)]),_:1},8,[`disabled`,`onClick`]),B($B,{label:`Delete`,"confirm-label":`Confirm delete`,size:`xs`,variant:`ghost`,"confirm-variant":`danger`,disabled:s.value,onConfirm:t=>C(e.name)},null,8,[`disabled`,`onConfirm`])])]))),128))]))]),_:1}),B(YL,{title:`Restore`,description:`Nothing is written until the plan is applied.`},{default:M(()=>[z(`div`,eH,[z(`label`,{class:O(j(QP)),for:`restore-upload`},`Restore from an uploaded .zip`,2),z(`input`,{id:`restore-upload`,type:`file`,accept:`.zip,application/zip`,class:O(j(y)),onChange:w},null,34),f.value?(I(),L(`p`,tH,k(f.value.name),1)):H(``,!0)]),z(`div`,nH,[B($,{variant:`secondary`,disabled:s.value||f.value===null,onClick:t[5]||=e=>ee()},{default:M(()=>[...t[19]||=[V(` Check the upload `,-1)]]),_:1},8,[`disabled`])]),d.value?(I(),L(`div`,rH,[B(nV,{tone:`warn`,title:d.value.dryRun?`Nothing has changed yet`:`Applied`},{default:M(()=>[z(`p`,null,[m.value?.kind===`stored`?(I(),L(F,{key:0},[t[20]||=V(` Archive `,-1),z(`code`,null,k(m.value.name),1),t[21]||=V(`. `,-1)],64)):(I(),L(F,{key:1},[V(` The uploaded archive. `)],64))]),d.value.needsPassword?(I(),L(F,{key:0},[z(`p`,iH,k(d.value.error??`This backup is password-protected.`),1),z(`div`,aH,[B(cR,{modelValue:p.value,"onUpdate:modelValue":t[6]||=e=>p.value=e,type:`password`,autocomplete:`off`,label:`Password`},null,8,[`modelValue`])]),z(`div`,oH,[B($,{size:`sm`,variant:`secondary`,disabled:s.value,onClick:E},{default:M(()=>[...t[22]||=[V(` Check again with the password `,-1)]]),_:1},8,[`disabled`])])],64)):(I(),L(F,{key:1},[z(`p`,sH,` Choose what to restore — `+k(_.value)+` of `+k(d.value.items.length)+` selected. `,1),z(`ul`,cH,[(I(!0),L(F,null,ya(d.value.items,e=>(I(),L(`li`,{key:e.id},[B(lB,{modelValue:e.selected,"onUpdate:modelValue":t=>e.selected=t,label:e.label,hint:[e.kind,e.note].filter(Boolean).join(` · `),disabled:!e.restorable},null,8,[`modelValue`,`onUpdate:modelValue`,`label`,`hint`,`disabled`])]))),128))]),z(`div`,lH,[B($,{size:`xs`,variant:`ghost`,disabled:s.value,onClick:t[7]||=e=>x(!0)},{default:M(()=>[...t[23]||=[V(` Select all `,-1)]]),_:1},8,[`disabled`]),B($,{size:`xs`,variant:`ghost`,disabled:s.value,onClick:t[8]||=e=>x(!1)},{default:M(()=>[...t[24]||=[V(` Select none `,-1)]]),_:1},8,[`disabled`])]),z(`p`,uH,[V(` Will restore: `+k(d.value.applied.join(`, `)||`nothing`)+`. `,1),d.value.skipped.length?(I(),L(F,{key:0},[V(` Skipped: `+k(d.value.skipped.join(`, `))+`. `,1)],64)):H(``,!0),d.value.restartRequired?(I(),L(F,{key:1},[V(` The panel's own settings differ, so restart the panel afterwards. `)],64)):v.value?(I(),L(F,{key:2},[V(` Restored servers are reloaded immediately; entries marked autostart start on their own. `)],64)):H(``,!0)]),z(`div`,dH,[B($,{size:`sm`,variant:`danger`,disabled:s.value||_.value===0,loading:s.value,onClick:te},{default:M(()=>[V(k(m.value?.kind===`stored`?`Apply this restore`:`Restore the upload`),1)]),_:1},8,[`disabled`,`loading`])])],64))]),_:1},8,[`title`])])):H(``,!0),l.value||c.value?(I(),L(`div`,fH,[l.value?(I(),R(nV,{key:0,tone:`danger`},{default:M(()=>[V(k(l.value),1)]),_:1})):H(``,!0),c.value?(I(),R(nV,{key:1,tone:`ok`},{default:M(()=>[V(k(c.value),1)]),_:1})):H(``,!0)])):H(``,!0)]),_:1})]))}}),mH={class:`mt-3 space-y-3`},hH=N({__name:`DefaultsSection`,props:Ba({dirty:{type:Boolean}},{defaults:{required:!0},defaultsModifiers:{}}),emits:Ba([`reset`],[`update:defaults`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`defaults`),a=OV(()=>i.value.logBufferLines,e=>i.value.logBufferLines=e,500);return(e,t)=>(I(),L(F,null,[B(YL,{title:`Server defaults`,description:`Applied to every entry that does not set its own value.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[9]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(lB,{modelValue:i.value.enabled,"onUpdate:modelValue":t[1]||=e=>i.value.enabled=e,label:`Enabled`,hint:`New entries start enabled.`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.autostart,"onUpdate:modelValue":t[2]||=e=>i.value.autostart=e,label:`Autostart with the panel`},null,8,[`modelValue`]),B(JI,{modelValue:i.value.bind,"onUpdate:modelValue":t[3]||=e=>i.value.bind=e,label:`Bind`,options:[{value:`local`,label:`Local — 127.0.0.1`},{value:`lan`,label:`LAN — 0.0.0.0`}]},null,8,[`modelValue`]),B(JI,{modelValue:i.value.onPortConflict,"onUpdate:modelValue":t[4]||=e=>i.value.onPortConflict=e,label:`On port conflict`,options:[{value:`block`,label:`Block the start`},{value:`warn`,label:`Warn and start anyway`}]},null,8,[`modelValue`]),B($L,{modelValue:j(a),"onUpdate:modelValue":t[5]||=e=>mn(a)?a.value=e:null,label:`Log buffer lines`,min:50,max:1e5,hint:`Kept in memory per server.`},null,8,[`modelValue`])]),_:1}),z(`div`,mH,[B(uB,{restart:i.value.restart,"onUpdate:restart":t[6]||=e=>i.value.restart=e,health:i.value.health,"onUpdate:health":t[7]||=e=>i.value.health=e,stop:i.value.stop,"onUpdate:stop":t[8]||=e=>i.value.stop=e},null,8,[`restart`,`health`,`stop`])])],64))}}),gH={key:0,class:`mt-3 flex flex-wrap items-center gap-1.5`},_H={key:1,class:`mt-3 text-xs text-muted`},vH=N({__name:`HostSection`,props:Ba({config:{},alerts:{},dirty:{type:Boolean}},{host:{required:!0},hostModifiers:{}}),emits:Ba([`reset`],[`update:host`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`host`),a=OV(()=>i.value.intervalMs,e=>i.value.intervalMs=e,15e3),o=OV(()=>i.value.diskUsedPercent,e=>i.value.diskUsedPercent=e,90),s=OV(()=>i.value.memoryUsedPercent,e=>i.value.memoryUsedPercent=e,90),c=OV(()=>i.value.swapUsedPercent,e=>i.value.swapUsedPercent=e,50),l=OV(()=>i.value.loadPerCpu,e=>i.value.loadPerCpu=e,2),u=OV(()=>i.value.tempCelsius,e=>i.value.tempCelsius=e,85);return(e,t)=>(I(),L(F,null,[B(YL,{title:`Thresholds`,description:`A threshold of 0 disables that alert. Breaches notify once, and once more on recovery.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[9]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value.enabled,"onUpdate:modelValue":t[1]||=e=>i.value.enabled=e,label:`Sample this machine`,hint:`Load, memory, swap, disk and CPU temperature, on the interval below.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B($L,{modelValue:j(a),"onUpdate:modelValue":t[2]||=e=>mn(a)?a.value=e:null,label:`Interval (ms)`,min:5e3,hint:`How often the host is sampled and alerts are checked.`},null,8,[`modelValue`]),B(cR,{modelValue:i.value.diskPaths,"onUpdate:modelValue":t[3]||=e=>i.value.diskPaths=e,label:`Disk paths`,hint:`Comma separated; templates like {home} are expanded.`},null,8,[`modelValue`]),B($L,{modelValue:j(o),"onUpdate:modelValue":t[4]||=e=>mn(o)?o.value=e:null,label:`Disk used (%)`,min:0,max:100,hint:`0 disables the alert.`},null,8,[`modelValue`]),B($L,{modelValue:j(s),"onUpdate:modelValue":t[5]||=e=>mn(s)?s.value=e:null,label:`Memory used (%)`,min:0,max:100,hint:`0 disables the alert.`},null,8,[`modelValue`]),B($L,{modelValue:j(c),"onUpdate:modelValue":t[6]||=e=>mn(c)?c.value=e:null,label:`Swap used (%)`,min:0,max:100,hint:`0 disables the alert.`},null,8,[`modelValue`]),B($L,{modelValue:j(l),"onUpdate:modelValue":t[7]||=e=>mn(l)?l.value=e:null,label:`Load per CPU`,min:0,hint:`1-minute load average divided by the core count.`},null,8,[`modelValue`]),B($L,{modelValue:j(u),"onUpdate:modelValue":t[8]||=e=>mn(u)?u.value=e:null,label:`CPU temperature (°C)`,min:0,hint:`Linux only; ignored where the OS does not expose it.`},null,8,[`modelValue`])]),_:1}),n.alerts.length>0?(I(),L(`div`,gH,[t[10]||=z(`span`,{class:`text-xs text-muted`},`Breaching now:`,-1),(I(!0),L(F,null,ya(n.alerts,e=>(I(),R(vF,{key:e,tone:`danger`,dot:``},{default:M(()=>[V(k(e),1)]),_:2},1024))),128))])):i.value.enabled?(I(),L(`p`,_H,` Nothing is breaching right now. `)):H(``,!0)],64))}}),yH={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},bH={class:`flex items-center gap-1.5 rounded-control border border-line bg-page/60 px-2.5 py-1.5`},xH={class:`min-w-0 flex-1 truncate font-mono text-xs text-ink`},SH={class:`text-2xs leading-4 text-faint`},CH={key:0},wH={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},TH=[`disabled`],EH=N({__name:`InterfaceSection`,props:Ba({ui:{},labelDirty:{type:Boolean}},{label:{required:!0},labelModifiers:{}}),emits:Ba([`changed`,`resetLabel`],[`update:label`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`label`),a=A(null),o=A(!1),s=A(null),c=A(null),l=A(!1),u=U(()=>{let e=n.ui;if(e===null)return`unknown`;if(!e.custom)return`stock`;let t=e.meta;return t===null?`custom`:`custom · ${t.name}${t.version===null?``:` ${t.version}`}`}),d=U(()=>{let e=n.ui?.meta;return e==null?null:`${e.files} files · ${new Date(e.uploadedAt).toLocaleString()}`});function f(e){a.value=e.target.files?.[0]??null,s.value=null,c.value=null}async function p(){if(a.value!==null){o.value=!0,c.value=null,s.value=null;try{let e=await mj(a.value);s.value=`UI replaced with ${e.meta?.name??`your build`} — refresh the page`,a.value=null,r(`changed`)}catch(e){c.value=e instanceof Error?e.message:String(e)}finally{o.value=!1}}}async function m(){if(!l.value){l.value=!0,setTimeout(()=>{l.value=!1},5e3);return}l.value=!1,o.value=!0,c.value=null;try{let e=await hj();s.value=e.removed?`stock UI restored — refresh the page`:`no custom UI was installed`,r(`changed`)}catch(e){c.value=e instanceof Error?e.message:String(e)}finally{o.value=!1}}return(t,h)=>(I(),R(YL,{title:`Interface`,description:`The panel is a static site: keep the stock one, or serve your own build from the state directory.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.labelDirty,onClick:h[1]||=e=>r(`resetLabel`)},{default:M(()=>[...h[4]||=[V(` Reset name `,-1)]]),_:1},8,[`disabled`]),e.ui?.custom?(I(),R($,{key:0,size:`xs`,variant:`ghost`,disabled:o.value,onClick:m},{default:M(()=>[V(k(l.value?`Confirm revert`:`Revert to stock`),1)]),_:1},8,[`disabled`])):H(``,!0),B($,{size:`xs`,variant:`primary`,disabled:o.value||a.value===null,onClick:p},{default:M(()=>[...h[5]||=[V(` Install UI `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[z(`div`,yH,[h[2]||=z(`span`,{class:`text-xs font-medium text-muted`},`Currently serving`,-1),z(`div`,bH,[z(`span`,xH,k(u.value),1),B(xB,{value:e.ui?.dir??``,label:`Copy the UI directory`},null,8,[`value`])]),z(`p`,SH,[d.value?(I(),L(`span`,CH,k(d.value)+` · `,1)):H(``,!0),z(`code`,null,k(e.ui?.dir??`—`),1)])]),B(cR,{modelValue:i.value,"onUpdate:modelValue":h[0]||=e=>i.value=e,label:`Panel name`,hint:`Shown in the sidebar by the stock UI. A custom UI may show its own name instead.`,wide:``},null,8,[`modelValue`]),z(`label`,wH,[h[3]||=z(`span`,{class:`text-xs font-medium text-muted`},`Upload a UI build (.zip with an index.html)`,-1),z(`input`,{type:`file`,accept:`.zip,application/zip`,class:`rounded-control border border-line bg-page/60 px-2.5 py-1.5 text-xs text-ink file:mr-2 file:rounded file:border-0 file:bg-line file:px-2 file:py-1 file:text-xs`,disabled:o.value,onChange:f},null,40,TH)]),c.value?(I(),R(nV,{key:0,tone:`warn`,title:c.value},null,8,[`title`])):s.value?(I(),R(nV,{key:1,tone:`ok`,title:s.value},null,8,[`title`])):H(``,!0),h[6]||=z(`p`,{class:`text-2xs leading-4 text-faint sm:col-span-2`},[V(` See `),z(`code`,null,`UI_CREATION.md`),V(` for the API contract. If a custom UI breaks the panel, `),z(`code`,null,`home-hosted ui-revert`),V(` puts the stock one back from the terminal. `)],-1)]),_:1}))}}),DH={class:`flex min-w-0 flex-col gap-1`},OH={class:`text-2xs leading-4 text-faint`},kH={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},AH={class:`flex items-center gap-1.5 rounded-control border border-line bg-page/60 px-2.5 py-1.5`},jH={class:`min-w-0 flex-1 truncate font-mono text-xs tabular-nums text-ink`},MH=N({__name:`ListenerSection`,props:Ba({view:{},dirty:{type:Boolean}},{control:{required:!0},controlModifiers:{}}),emits:Ba([`reset`],[`update:control`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`control`),a=OV(()=>i.value.port,e=>i.value.port=e,3999),o=[`local`,`lan`],s=U({get:()=>o.includes(i.value.host)?i.value.host:`custom`,set:e=>{if(e!==`custom`){i.value.host=e;return}if(!o.includes(i.value.host))return;let t=n.view?.bindHost??``,r=t!==``&&t!==`0.0.0.0`&&t!==`127.0.0.1`&&jk(t)!==null;i.value.host=r?t:`127.0.0.1`}}),c=U(()=>{if(s.value===`custom`&&jk(i.value.host)===null)return`Enter an IPv4 address, for example 192.168.1.20.`}),l=U(()=>n.view?.url??`—`);return(e,t)=>(I(),R(YL,{title:`Listener`,description:`Where the panel answers. Moving this restarts the panel and sends this page to the new address.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[5]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B($L,{modelValue:j(a),"onUpdate:modelValue":t[1]||=e=>mn(a)?a.value=e:null,label:`Control port`,min:1,max:65535,hint:`Default 3999.`},null,8,[`modelValue`]),z(`div`,DH,[t[6]||=z(`span`,{class:`text-xs font-medium text-muted`},`Bind`,-1),B(hI,{modelValue:s.value,"onUpdate:modelValue":t[2]||=e=>s.value=e,options:[{value:`local`,label:`Local`},{value:`lan`,label:`LAN`},{value:`custom`,label:`Custom`}]},null,8,[`modelValue`]),z(`p`,OH,k(s.value===`lan`?`0.0.0.0 — reachable from the whole network.`:s.value===`local`?`127.0.0.1 — this machine only.`:`A specific interface address you type in below.`),1)]),s.value===`custom`?(I(),R(cR,{key:0,modelValue:i.value.host,"onUpdate:modelValue":t[3]||=e=>i.value.host=e,label:`IPv4 address`,placeholder:`192.168.1.20`,error:c.value,hint:`Binding beyond 127.0.0.1 needs a real password.`},null,8,[`modelValue`,`error`])):H(``,!0),B(lB,{modelValue:i.value.openBrowser,"onUpdate:modelValue":t[4]||=e=>i.value.openBrowser=e,label:`Open a browser when the panel comes up`,wide:``},null,8,[`modelValue`]),z(`div`,kH,[t[8]||=z(`span`,{class:`text-xs font-medium text-muted`},`Live address`,-1),z(`div`,AH,[z(`span`,jH,k(l.value),1),n.view?.restartRequired?(I(),R(vF,{key:0,tone:`warn`,dot:``},{default:M(()=>[...t[7]||=[V(` restart required `,-1)]]),_:1})):H(``,!0),B(xB,{value:l.value,label:`Copy the live address`},null,8,[`value`])]),t[9]||=z(`p`,{class:`text-2xs leading-4 text-faint`},` What the listener is bound to right now; saved changes take effect after the panel rebinds. `,-1)])]),_:1}))}}),NH={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},PH={class:`flex items-center gap-1.5 rounded-control border border-line bg-page/60 px-2.5 py-1.5`},FH={class:`min-w-0 flex-1 truncate font-mono text-xs text-ink`},IH={class:`text-2xs leading-4 text-faint`},LH=N({__name:`LogsSection`,props:Ba({logsDir:{},dirty:{type:Boolean}},{logs:{required:!0},logsModifiers:{}}),emits:Ba([`reset`],[`update:logs`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`logs`),a=OV(()=>i.value.maxBytes,e=>i.value.maxBytes=e,2e6),o=OV(()=>i.value.keep,e=>i.value.keep=e,3),s=U(()=>n.logsDir??`—`);return(e,t)=>{let c=pa(`RouterLink`);return I(),R(YL,{title:`Retention`,description:`The live view always comes from memory; these files survive a restart and feed the Logs page.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[4]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value.persist,"onUpdate:modelValue":t[1]||=e=>i.value.persist=e,label:`Keep logs on disk`,hint:`Turn off to hold everything in memory only.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B($L,{modelValue:j(a),"onUpdate:modelValue":t[2]||=e=>mn(a)?a.value=e:null,label:`Rotate at (bytes)`,min:1e4,max:1e8,hint:`Per server, before rolling to .1, .2, …`},null,8,[`modelValue`]),B($L,{modelValue:j(o),"onUpdate:modelValue":t[3]||=e=>mn(o)?o.value=e:null,label:`Keep rotated files`,min:1,max:10},null,8,[`modelValue`]),z(`div`,NH,[t[8]||=z(`span`,{class:`text-xs font-medium text-muted`},`Logs directory`,-1),z(`div`,PH,[z(`span`,FH,k(s.value),1),B(xB,{value:s.value,label:`Copy the logs directory`},null,8,[`value`])]),z(`p`,IH,[t[6]||=V(` Browse and download these files on the `,-1),B(c,{to:`/logs`,class:`text-accent underline decoration-accent/40 underline-offset-2 hover:decoration-accent`},{default:M(()=>[...t[5]||=[V(` Logs `,-1)]]),_:1}),t[7]||=V(` page. `,-1)])])]),_:1})}}}),RH={class:`flex items-center gap-2 border-t border-line-soft pt-3 sm:col-span-2`},zH={class:`flex flex-wrap items-center gap-2 sm:col-span-2`},BH={key:0,class:`sm:col-span-2`},VH={class:`flex flex-wrap gap-1.5`},HH={key:1,class:`sm:col-span-2`},UH={class:`text-2xs leading-4 text-faint`},WH={key:2,class:`space-y-2 sm:col-span-2`},GH=N({__name:`NotificationsSection`,props:Ba({status:{},dirty:{type:Boolean}},{telegram:{required:!0},telegramModifiers:{}}),emits:Ba([`reset`],[`update:telegram`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`telegram`),a=lM(),o=OV(()=>i.value.cooldownMs,e=>i.value.cooldownMs=e,12e4),s=A(``),c=A(null),l=A(null),u=A([]),d=A(!1),f=U(()=>n.status?.tokenSet===!0),p=U(()=>n.status?.lastResult??null),m=U(()=>n.status?.lastResultAt??null),h=U(()=>f.value?`Saved — type a new token to replace it`:`123456789:AA…`);async function g(){d.value=!0,l.value=null,c.value=null;try{let e=await uj(s.value.trim());s.value=``,c.value=`Token saved${e.username?` for @${e.username}`:``}.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{d.value=!1}}async function _(){d.value=!0,l.value=null,c.value=null;try{await dj(),c.value=`Token removed.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{d.value=!1}}async function v(){d.value=!0,l.value=null,c.value=null;try{let e=s.value.trim(),t=await pj(e.length>0?{botToken:e}:{});u.value=t.chats,t.chats.length===0&&(l.value=`No chats found. Send /start to the bot first, then detect again.`)}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{d.value=!1}}async function y(){d.value=!0,l.value=null,c.value=null;try{let e=s.value.trim(),t=await fj({chatId:i.value.chatId.length>0?i.value.chatId:void 0,...e.length>0?{botToken:e}:{}});t.ok?c.value=`Test message sent.`:l.value=t.error??`The test message failed.`,await a.refresh()}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{d.value=!1}}return(e,t)=>(I(),R(YL,{title:`Telegram`,description:`Policy lives in the config; the bot token is a secret and never comes back from the server.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[10]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value.enabled,"onUpdate:modelValue":t[1]||=e=>i.value.enabled=e,label:`Send notifications`,hint:`One message per event, throttled by the cooldown below.`,class:`sm:col-span-2`},null,8,[`modelValue`]),B(cR,{modelValue:i.value.chatId,"onUpdate:modelValue":t[2]||=e=>i.value.chatId=e,label:`Chat id`,placeholder:`123456789 or @channel`,hint:`Use Detect chats to fill this from the bot's updates.`},null,8,[`modelValue`]),B($L,{modelValue:j(o),"onUpdate:modelValue":t[3]||=e=>mn(o)?o.value=e:null,label:`Cooldown per event (ms)`,min:0,hint:`Per server and reason, so a flapping server cannot spam.`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.onCrash,"onUpdate:modelValue":t[4]||=e=>i.value.onCrash=e,label:`Retries exhausted`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.onUnhealthy,"onUpdate:modelValue":t[5]||=e=>i.value.onUnhealthy=e,label:`Port unhealthy`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.onForcedRestart,"onUpdate:modelValue":t[6]||=e=>i.value.onForcedRestart=e,label:`Forced restart`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.onRecovered,"onUpdate:modelValue":t[7]||=e=>i.value.onRecovered=e,label:`Recovered`},null,8,[`modelValue`]),B(lB,{modelValue:i.value.onHost,"onUpdate:modelValue":t[8]||=e=>i.value.onHost=e,label:`Host vitals breached`,hint:`Disk, memory, swap, load and CPU temperature, per the thresholds under Host vitals.`,wide:``},null,8,[`modelValue`]),z(`div`,RH,[t[11]||=z(`span`,{class:`text-xs font-medium text-muted`},`Bot token`,-1),B(vF,{tone:f.value?`ok`:`warn`,dot:``},{default:M(()=>[V(k(f.value?`token set`:`no token`),1)]),_:1},8,[`tone`]),t[12]||=z(`span`,{class:`text-2xs text-faint`},`From @BotFather — a secret, never in servers.config.json.`,-1)]),B(cR,{modelValue:s.value,"onUpdate:modelValue":t[9]||=e=>s.value=e,type:`password`,autocomplete:`off`,label:`Bot token`,placeholder:h.value,wide:``},null,8,[`modelValue`,`placeholder`]),z(`div`,zH,[B($,{variant:`primary`,disabled:d.value||s.value.trim().length===0,loading:d.value&&s.value.trim().length>0,onClick:g},{default:M(()=>[...t[13]||=[V(` Save token `,-1)]]),_:1},8,[`disabled`,`loading`]),B($,{variant:`secondary`,disabled:d.value,onClick:v},{default:M(()=>[...t[14]||=[V(` Detect chats `,-1)]]),_:1},8,[`disabled`]),B($,{variant:`secondary`,disabled:d.value,onClick:y},{default:M(()=>[...t[15]||=[V(` Send test `,-1)]]),_:1},8,[`disabled`]),f.value?(I(),R($B,{key:0,label:`Remove token`,"confirm-label":`Confirm remove`,"confirm-variant":`danger`,disabled:d.value,onConfirm:_},null,8,[`disabled`])):H(``,!0)]),u.value.length>0?(I(),L(`div`,BH,[t[16]||=z(`p`,{class:`mb-1.5 text-xs text-muted`},` Chats the bot has seen: `,-1),z(`div`,VH,[(I(!0),L(F,null,ya(u.value,e=>(I(),R($,{key:String(e.id),size:`xs`,variant:`secondary`,onClick:t=>i.value.chatId=String(e.id)},{default:M(()=>[V(k(e.title)+` (`+k(e.id)+`) `,1)]),_:2},1032,[`onClick`]))),128))])])):H(``,!0),p.value?(I(),L(`div`,HH,[z(`p`,UH,` Last send: `+k(p.value)+k(m.value?` · ${j(aI)(m.value,j(a).now.value)}`:``),1)])):H(``,!0),l.value||c.value?(I(),L(`div`,WH,[l.value?(I(),R(nV,{key:0,tone:`danger`},{default:M(()=>[V(k(l.value),1)]),_:1})):H(``,!0),c.value?(I(),R(nV,{key:1,tone:`ok`},{default:M(()=>[V(k(c.value),1)]),_:1})):H(``,!0)])):H(``,!0)]),_:1}))}}),KH={class:`divide-y divide-line-soft sm:col-span-2`},qH={class:`min-w-0`},JH={class:`text-xs font-medium text-ink`},YH={class:`mt-0.5 text-2xs leading-4 text-faint`},XH={class:`flex min-w-0 flex-1 items-center justify-end gap-1.5`},ZH={class:`min-w-0 truncate font-mono text-xs text-ink`},QH=N({__name:`PathsSection`,props:{configPath:{},dataRoot:{},logsDir:{},projectDir:{},panelUrl:{}},setup(e){let t=e,n=U(()=>[{key:`data`,label:`Data root`,hint:`HHOSTED_HOME — config, secrets, TLS, logs and backups.`,value:t.dataRoot??`—`},{key:`config`,label:`Config file`,hint:`servers.config.json — every policy on this page.`,value:t.configPath??`—`},{key:`logs`,label:`Logs directory`,hint:`Persisted output for every server.`,value:t.logsDir??`—`},{key:`project`,label:`Project directory`,hint:`Base for relative entry paths.`,value:t.projectDir??`—`},{key:`panel`,label:`Panel address`,hint:`Where this control panel is live right now.`,value:t.panelUrl??`—`}]);return(e,t)=>(I(),R(YL,{title:`Where things live`,description:`Read-only. Copy a path straight into a shell or an editor.`},{default:M(()=>[z(`ul`,KH,[(I(!0),L(F,null,ya(n.value,e=>(I(),L(`li`,{key:e.key,class:`flex items-start justify-between gap-3 py-2 first:pt-0 last:pb-0`},[z(`div`,qH,[z(`p`,JH,k(e.label),1),z(`p`,YH,k(e.hint),1)]),z(`div`,XH,[z(`span`,ZH,k(e.value),1),B(xB,{value:e.value,label:`Copy ${e.label.toLowerCase()}`},null,8,[`value`,`label`])])]))),128))])]),_:1}))}}),$H={class:`flex min-w-0 flex-col gap-1 sm:col-span-2`},eU={class:`flex items-center gap-1.5 rounded-control border border-line bg-page/60 px-2.5 py-1.5`},tU={class:`min-w-0 flex-1 truncate font-mono text-xs text-ink`},nU={key:0,class:`sm:col-span-2`},rU={key:1,class:`space-y-3 sm:col-span-2`},iU={class:`divide-y divide-line-soft text-xs`},aU={class:`flex items-start justify-between gap-4 py-1.5`},oU={class:`min-w-0 text-right font-mono text-xs text-ink`},sU={class:`flex items-start justify-between gap-4 py-1.5`},cU={class:`min-w-0 text-right font-mono text-xs text-ink`},lU={class:`flex items-start justify-between gap-4 py-1.5`},uU={class:`font-mono text-xs tabular-nums text-ink`},dU={class:`flex items-center justify-between gap-4 py-1.5`},fU={class:`flex items-start justify-between gap-4 py-1.5`},pU={class:`flex min-w-0 items-center gap-1.5`},mU={class:`min-w-0 truncate font-mono text-2xs text-ink`},hU={key:2,class:`sm:col-span-2`},gU={class:`flex min-w-0 flex-col gap-1`},_U={class:`flex min-w-0 flex-col gap-1`},vU={class:`flex flex-wrap items-center gap-2 sm:col-span-2`},yU={key:3,class:`space-y-2 sm:col-span-2`},bU=N({__name:`TlsSection`,props:Ba({view:{},dirty:{type:Boolean}},{tlsEnabled:{type:Boolean,required:!0},tlsEnabledModifiers:{}}),emits:Ba([`reset`],[`update:tlsEnabled`]),setup(e,{emit:t}){let n=e,r=t,i=co(e,`tlsEnabled`),a=lM(),o=A(``),s=A(``),c=A(null),l=A(null),u=A(!1),d=U(()=>n.view?.tls??null),f=U(()=>n.view?`${n.view.protocol}://${n.view.bindHost}:${n.view.port}`:`—`),p=U(()=>o.value.trim().length>0&&s.value.trim().length>0&&!u.value),m=Q(eF,`cursor-pointer text-xs`,`file:mr-2 file:cursor-pointer file:rounded-control file:border-0 file:bg-panel-2 file:px-2 file:py-1 file:text-xs file:text-ink`);async function h(e,t){let n=e.target.files?.[0];if(!n)return;let r=await n.text();t===`cert`?o.value=r:s.value=r}async function g(){u.value=!0,l.value=null,c.value=null;try{let e=await gj(o.value,s.value);o.value=``,s.value=``,c.value=`Certificate stored.`,await a.refresh();let t=await kV(e);t!==null&&(l.value=t)}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{u.value=!1}}async function _(){u.value=!0,l.value=null,c.value=null;try{let e=await _j();c.value=`Certificate removed.`,await a.refresh();let t=await kV(e);t!==null&&(l.value=t)}catch(e){l.value=e instanceof Error?e.message:String(e)}finally{u.value=!1}}return(e,t)=>(I(),L(F,null,[B(YL,{title:`HTTPS`,description:`Upload a PEM pair and the panel serves TLS on its own listener. A self-signed certificate is fine on a home network.`},{actions:M(()=>[B($,{size:`xs`,variant:`ghost`,disabled:!n.dirty,onClick:t[0]||=e=>r(`reset`)},{default:M(()=>[...t[6]||=[V(` Reset `,-1)]]),_:1},8,[`disabled`])]),default:M(()=>[B(dR,{modelValue:i.value,"onUpdate:modelValue":t[1]||=e=>i.value=e,label:`Serve the panel over HTTPS`,hint:`Saved with Save settings; the panel rebinds and this page follows it.`,class:`sm:col-span-2`},null,8,[`modelValue`]),z(`div`,$H,[t[7]||=z(`span`,{class:`text-xs font-medium text-muted`},`Live address`,-1),z(`div`,eU,[z(`span`,tU,k(f.value),1),B(xB,{value:f.value,label:`Copy the live address`},null,8,[`value`])])])]),_:1}),B(YL,{class:`mt-3`,title:`Certificate`,description:`PEM-encoded certificate and its private key. Both stay in the secrets file.`},{default:M(()=>[d.value?.error?(I(),L(`div`,nU,[B(nV,{tone:`danger`,title:`The stored certificate cannot be used`},{default:M(()=>[V(k(d.value.error),1)]),_:1})])):d.value?.certPresent?(I(),L(`div`,rU,[z(`dl`,iU,[z(`div`,aU,[t[8]||=z(`dt`,{class:`text-muted`},` Subject `,-1),z(`dd`,oU,k(d.value.subject??`—`),1)]),z(`div`,sU,[t[9]||=z(`dt`,{class:`text-muted`},` Issuer `,-1),z(`dd`,cU,k(d.value.issuer??`—`),1)]),z(`div`,lU,[t[10]||=z(`dt`,{class:`text-muted`},` Valid until `,-1),z(`dd`,uU,[V(k(d.value.validTo?d.value.validTo.slice(0,10):`—`)+` `,1),d.value.daysRemaining===null?H(``,!0):(I(),L(`span`,{key:0,class:O(j(Q)(`ml-1.5`,d.value.daysRemaining<14?`text-warn`:`text-faint`))},` (`+k(d.value.daysRemaining)+` days) `,3))])]),z(`div`,dU,[t[11]||=z(`dt`,{class:`text-muted`},` Private key `,-1),z(`dd`,null,[B(vF,{tone:d.value.keyMatches?`ok`:`danger`,dot:``},{default:M(()=>[V(k(d.value.keyMatches?`matches the certificate`:`does not match`),1)]),_:1},8,[`tone`])])]),z(`div`,fU,[t[12]||=z(`dt`,{class:`text-muted`},` Fingerprint `,-1),z(`dd`,pU,[z(`span`,mU,k(d.value.fingerprint??`—`),1),d.value.fingerprint?(I(),R(xB,{key:0,value:d.value.fingerprint,label:`Copy the fingerprint`},null,8,[`value`])):H(``,!0)])])])])):(I(),L(`div`,hU,[B(nV,{tone:`info`,title:`No certificate uploaded`},{default:M(()=>[...t[13]||=[V(` Upload a PEM certificate and key below, then save. HTTPS is not served until a certificate is present. `,-1)]]),_:1})])),z(`div`,gU,[z(`label`,{class:O(j(QP)),for:`tls-cert-file`},`Certificate file (.pem, .crt)`,2),z(`input`,{id:`tls-cert-file`,type:`file`,accept:`.pem,.crt,.cer,text/plain`,class:O(j(m)),onChange:t[2]||=e=>h(e,`cert`)},null,34)]),z(`div`,_U,[z(`label`,{class:O(j(QP)),for:`tls-key-file`},`Private key file (.pem, .key)`,2),z(`input`,{id:`tls-key-file`,type:`file`,accept:`.pem,.key,text/plain`,class:O(j(m)),onChange:t[3]||=e=>h(e,`key`)},null,34)]),B(rR,{modelValue:o.value,"onUpdate:modelValue":t[4]||=e=>o.value=e,label:`Or paste the certificate`,rows:4,placeholder:`-----BEGIN CERTIFICATE-----`},null,8,[`modelValue`]),B(rR,{modelValue:s.value,"onUpdate:modelValue":t[5]||=e=>s.value=e,label:`Or paste the private key`,rows:4,placeholder:`-----BEGIN PRIVATE KEY-----`},null,8,[`modelValue`]),z(`div`,vU,[B($,{variant:`primary`,disabled:!p.value,loading:u.value,onClick:g},{default:M(()=>[...t[14]||=[V(` Save certificate `,-1)]]),_:1},8,[`disabled`,`loading`]),d.value?.certPresent?(I(),R($B,{key:0,label:`Remove certificate`,"confirm-label":`Confirm remove`,"confirm-variant":`danger`,disabled:u.value,onConfirm:_},null,8,[`disabled`])):H(``,!0)]),l.value||c.value?(I(),L(`div`,yU,[l.value?(I(),R(nV,{key:0,tone:`danger`},{default:M(()=>[V(k(l.value),1)]),_:1})):H(``,!0),c.value?(I(),R(nV,{key:1,tone:`ok`},{default:M(()=>[V(k(c.value),1)]),_:1})):H(``,!0)])):H(``,!0)]),_:1})],64))}}),xU={class:`min-h-full`},SU={class:`mx-auto w-full max-w-5xl px-4 pb-6 pt-5 sm:px-6`},CU={class:`lg:grid lg:grid-cols-[11rem_minmax(0,1fr)] lg:gap-8`},wU={"aria-label":`Settings sections`,class:`hidden lg:block`},TU={class:`sticky top-6 space-y-0.5 border-l border-line`},EU=[`aria-current`,`onClick`],DU={class:`space-y-6`},OU={id:`listener`,class:`scroll-mt-5`},kU={id:`authentication`,class:`scroll-mt-5`},AU={id:`password`,class:`scroll-mt-5`},jU={id:`defaults`,class:`scroll-mt-5`},MU={id:`logs`,class:`scroll-mt-5`},NU={id:`notifications`,class:`scroll-mt-5`},PU={id:`host`,class:`scroll-mt-5`},FU={id:`backups`,class:`scroll-mt-5`},IU={id:`tls`,class:`scroll-mt-5`},LU={id:`interface`,class:`scroll-mt-5`},RU={id:`paths`,class:`scroll-mt-5`},zU={class:`sticky bottom-0 z-30 border-t border-line bg-panel/95 backdrop-blur`},BU={class:`mx-auto flex w-full max-w-5xl flex-wrap items-center gap-x-4 gap-y-2 px-4 py-3 sm:px-6`},VU={class:`min-w-0 flex-1`},HU={class:`text-xs text-muted`},UU={class:`font-mono tabular-nums text-ink`},WU={key:0,class:`mt-0.5 text-xs text-danger`},GU={key:1,class:`mt-0.5 text-xs text-ok`},KU={class:`flex shrink-0 items-center gap-2`},qU={class:`max-h-[60dvh] overflow-auto px-4 py-3`},JU={key:0,class:`divide-y divide-line-soft`},YU={class:`font-mono text-xs text-ink`},XU={class:`ml-auto font-mono text-xs text-muted line-through`},ZU={class:`font-mono text-xs text-accent`},QU={key:1,class:`text-xs text-muted`},$U={class:`flex items-center justify-end gap-2 border-t border-line px-4 py-3`},eW=N({__name:`SettingsView`,setup(e){let t=[{id:`listener`,title:`Listener`},{id:`authentication`,title:`Authentication`},{id:`defaults`,title:`Server defaults`},{id:`logs`,title:`Logs`},{id:`notifications`,title:`Notifications`},{id:`host`,title:`Host vitals`},{id:`backups`,title:`Backups`},{id:`tls`,title:`TLS`},{id:`interface`,title:`Interface`},{id:`paths`,title:`Paths`}],n=Dg(),r=Eg(),i=lM(),a=Mj(),o=en(cV()),s=A(`listener`),c=A(!1),l=A(null),u=A(null),d=A(null),f=A(null),p=U(()=>i.control.value),m=U(()=>i.defaults.value),h=U(()=>i.logsConfig.value),g=U(()=>i.notifications.value?.telegram??null),_=U(()=>i.host.value?.alerts??[]),v=U(()=>f.value?.host??null),y=U(()=>f.value?.backups??null),b=U(()=>f.value?.ui??null),x=U(()=>i.backups.value),S=U(()=>i.appState.value?.configPath??null),C=U(()=>{let e={},t=p.value,n=m.value,r=h.value,i=g.value;if(t!==null){let n=_V(t,o);Object.keys(n).length>0&&(e.control=n)}if(n!==null){let t=vV(n,o.defaults);Object.keys(t).length>0&&(e.defaults=t)}if(r!==null){let t=yV(r,o.logs);Object.keys(t).length>0&&(e.logs=t)}if(i!==null){let t=bV(i,o.telegram);Object.keys(t).length>0&&(e.notifications=t)}let a=v.value;if(a!==null){let t=hV(a,o.host);Object.keys(t).length>0&&(e.host=t)}let s=y.value;if(s!==null){let t=gV(s,o.backups);Object.keys(t).length>0&&(e.backups=t)}return e}),w=U(()=>wV(C.value)),T=U(()=>{let e=p.value,t=g.value,n=v.value,r=y.value;return{control:e===null?{}:{...uV(e),auth:lV(e.auth)},defaults:m.value??{},logs:h.value??{},notifications:t===null?{}:{telegram:dV(t)},host:n??{},backups:r===null?{}:{enabled:r.enabled,keep:r.keep,includePaths:r.includePaths}}}),ee=U(()=>CV(C.value,T.value)),E=A(!1);function te(e){return e===void 0?`unset`:typeof e==`string`?e:JSON.stringify(e)}let ne=U(()=>p.value!==null&&TV(p.value,o)),re=U(()=>p.value!==null&&EV(p.value,o)),ie=U(()=>p.value!==null&&DV(p.value,o)),ae=U(()=>m.value!==null&&Object.keys(vV(m.value,o.defaults)).length>0),oe=U(()=>h.value!==null&&Object.keys(yV(h.value,o.logs)).length>0),se=U(()=>g.value!==null&&Object.keys(bV(g.value,o.telegram)).length>0),ce=U(()=>v.value!==null&&Object.keys(hV(v.value,o.host)).length>0),D=U(()=>y.value!==null&&Object.keys(gV(y.value,o.backups)).length>0),le=A(!1);function ue(){let e=p.value;e!==null&&(Object.assign(o.control,uV(e)),Object.assign(o.auth,lV(e.auth)));let t=h.value;t!==null&&Object.assign(o.logs,t);let n=g.value;n!==null&&Object.assign(o.telegram,dV(n));let r=v.value;r!==null&&Object.assign(o.host,pV(r));let i=y.value;i!==null&&Object.assign(o.backups,mV(i));let a=m.value;a!==null&&(o.defaults.enabled=a.enabled,o.defaults.autostart=a.autostart,o.defaults.bind=a.bind,o.defaults.onPortConflict=a.onPortConflict,o.defaults.logBufferLines=a.logBufferLines,o.defaults.restart={...a.restart},o.defaults.health=sV(a.health),o.defaults.stop={...a.stop}),le.value=!0}jr([p,m,h,g,f],()=>{xV({hydrated:le.value,liveAvailable:p.value!==null,changedCount:w.value})&&ue()},{immediate:!0});async function de(){d.value=null;try{f.value=await QA()}catch(e){d.value=e instanceof Error?e.message:String(e)}}na(()=>{de()});async function fe(){let e=C.value;if(!(Object.keys(e).length===0||c.value)){c.value=!0,l.value=null,u.value=null;try{let t=await i.saveSettings(e);if(!t){l.value=i.lastError.value??`The settings could not be saved.`;return}u.value=`Settings saved.`,a.success(`Settings saved`),await de();let n=await kV(t);n===null?t.rebinding&&(u.value=`Settings saved — the panel is moving to a new address.`):(u.value=null,l.value=n)}finally{c.value=!1}}}function pe(){l.value=null,u.value=null,ue()}function me(){v.value!==null&&Object.assign(o.host,pV(v.value))}function he(){y.value!==null&&Object.assign(o.backups,mV(y.value))}function ge(){if(p.value!==null){let e=uV(p.value);o.control.label=e.label,o.control.port=e.port,o.control.host=e.host,o.control.openBrowser=e.openBrowser}}function _e(){p.value!==null&&Object.assign(o.auth,lV(p.value.auth))}function ve(){let e=m.value;e!==null&&(o.defaults.enabled=e.enabled,o.defaults.autostart=e.autostart,o.defaults.bind=e.bind,o.defaults.onPortConflict=e.onPortConflict,o.defaults.logBufferLines=e.logBufferLines,o.defaults.restart={...e.restart},o.defaults.health=sV(e.health),o.defaults.stop={...e.stop})}function ye(){h.value!==null&&Object.assign(o.logs,h.value)}function be(){g.value!==null&&Object.assign(o.telegram,dV(g.value))}function xe(){p.value!==null&&(o.control.tlsEnabled=p.value.tls.enabled)}function Se(e){rr(()=>{document.getElementById(e)?.scrollIntoView({behavior:`smooth`,block:`start`})})}function Ce(e){s.value=e,n.query.section!==e&&r.replace({query:{...n.query,section:e}}),Se(e)}jr(()=>n.query.section,e=>{let n=typeof e==`string`?e:``;t.some(e=>e.id===n)&&(s.value=n,Se(n))},{immediate:!0});let we=null;return na(()=>{we=new IntersectionObserver(e=>{let t=e.filter(e=>e.isIntersecting).sort((e,t)=>e.boundingClientRect.top-t.boundingClientRect.top)[0];t!==void 0&&t.target.id.length>0&&(s.value=t.target.id)},{rootMargin:`-72px 0px -62% 0px`,threshold:0});for(let e of t){let t=document.getElementById(e.id);t&&we.observe(t)}}),aa(()=>we?.disconnect()),(e,n)=>{let r=pa(`PasswordSection`);return I(),L(F,null,[z(`div`,xU,[z(`div`,SU,[n[24]||=z(`header`,{class:`mb-5`},[z(`h1`,{class:`text-xl font-semibold tracking-tight text-ink`},` Settings `),z(`p`,{class:`mt-1 text-xs text-muted`},` Listener, access, defaults, retention, alerts, backups and paths for this panel. `)],-1),j(i).configError.value?(I(),R(nV,{key:0,tone:`danger`,title:`The config file could not be read`,class:`mb-4`},{default:M(()=>[V(k(j(i).configError.value),1)]),_:1})):d.value?(I(),R(nV,{key:1,tone:`warn`,title:`Some file-only values could not be loaded`,class:`mb-4`},{default:M(()=>[V(k(d.value),1)]),_:1})):H(``,!0),z(`div`,CU,[z(`nav`,wU,[z(`ul`,TU,[(I(),L(F,null,ya(t,e=>z(`li`,{key:e.id},[z(`button`,{type:`button`,"aria-current":s.value===e.id?`true`:void 0,class:O(j(Q)(`-ml-px block w-full border-l-2 py-1.5 pl-3 text-left text-xs transition-colors duration-150`,s.value===e.id?`border-accent font-medium text-accent`:`border-transparent text-muted hover:border-line hover:text-ink`)),onClick:t=>Ce(e.id)},k(e.title),11,EU)])),64))])]),z(`div`,DU,[z(`section`,OU,[n[13]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Listener `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Where the control panel listens and how it announces itself. `)],-1),B(MH,{control:o.control,"onUpdate:control":n[0]||=e=>o.control=e,view:p.value,dirty:ne.value,onReset:ge},null,8,[`control`,`view`,`dirty`])]),z(`section`,kU,[n[14]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Authentication `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Who may sign in, and how a session is trusted. `)],-1),B(MV,{auth:o.auth,"onUpdate:auth":n[1]||=e=>o.auth=e,view:p.value,dirty:ie.value,onReset:_e},null,8,[`auth`,`view`,`dirty`])]),z(`section`,AU,[n[15]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Password `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` The one credential that guards this panel. `)],-1),B(r,{view:p.value},null,8,[`view`])]),z(`section`,jU,[n[16]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Server defaults `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Applied to every entry that does not set its own value. `)],-1),B(hH,{defaults:o.defaults,"onUpdate:defaults":n[2]||=e=>o.defaults=e,dirty:ae.value,onReset:ve},null,8,[`defaults`,`dirty`])]),z(`section`,MU,[n[17]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Logs `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` How much output is kept on disk and for how long. `)],-1),B(LH,{logs:o.logs,"onUpdate:logs":n[3]||=e=>o.logs=e,"logs-dir":j(i).appState.value?.logsDir??null,dirty:oe.value,onReset:ye},null,8,[`logs`,`logs-dir`,`dirty`])]),z(`section`,NU,[n[18]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Notifications `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Crash and health alerts sent to a Telegram chat. `)],-1),B(GH,{telegram:o.telegram,"onUpdate:telegram":n[4]||=e=>o.telegram=e,status:g.value,dirty:se.value,onReset:be},null,8,[`telegram`,`status`,`dirty`])]),z(`section`,PU,[n[19]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Host vitals `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Alert thresholds for this machine's own resources. `)],-1),B(vH,{host:o.host,"onUpdate:host":n[5]||=e=>o.host=e,config:v.value,alerts:_.value,dirty:ce.value,onReset:me},null,8,[`host`,`config`,`alerts`,`dirty`])]),z(`section`,FU,[n[20]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Backups `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Archives of config, secrets and declared data paths. `)],-1),B(pH,{form:o.backups,"onUpdate:form":n[6]||=e=>o.backups=e,state:x.value,policy:y.value,"config-path":S.value,dirty:D.value,onReset:he},null,8,[`form`,`state`,`policy`,`config-path`,`dirty`])]),z(`section`,IU,[n[21]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` TLS `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Serve the panel over HTTPS with your own certificate. `)],-1),B(bU,{"tls-enabled":o.control.tlsEnabled,"onUpdate:tlsEnabled":n[7]||=e=>o.control.tlsEnabled=e,view:p.value,dirty:re.value,onReset:xe},null,8,[`tls-enabled`,`view`,`dirty`])]),z(`section`,LU,[n[22]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Interface `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Serve your own panel UI instead of the stock one. `)],-1),B(EH,{label:o.control.label,"onUpdate:label":n[8]||=e=>o.control.label=e,ui:b.value,"label-dirty":ne.value,onChanged:de,onResetLabel:ge},null,8,[`label`,`ui`,`label-dirty`])]),z(`section`,RU,[n[23]||=z(`header`,{class:`mb-3`},[z(`h2`,{class:`text-lg font-semibold text-ink`},` Paths `),z(`p`,{class:`mt-0.5 text-xs text-muted`},` Where home-hosted keeps its files. `)],-1),B(QH,{"config-path":S.value,"data-root":j(i).appState.value?.dataRoot??null,"logs-dir":j(i).appState.value?.logsDir??null,"project-dir":j(i).appState.value?.projectDir??null,"panel-url":p.value?.url??null},null,8,[`config-path`,`data-root`,`logs-dir`,`project-dir`,`panel-url`])])])])]),z(`div`,zU,[z(`div`,BU,[z(`div`,VU,[z(`p`,HU,[w.value>0?(I(),L(`button`,{key:0,type:`button`,class:`rounded-control px-1 py-0.5 text-xs text-muted underline decoration-line underline-offset-2 hover:text-ink`,onClick:n[9]||=e=>E.value=!0},[z(`span`,UU,k(w.value),1),V(` field`+k(w.value===1?``:`s`)+` changed `,1)])):(I(),L(F,{key:1},[V(` No unsaved changes `)],64))]),l.value?(I(),L(`p`,WU,k(l.value),1)):u.value?(I(),L(`p`,GU,k(u.value),1)):H(``,!0)]),z(`div`,KU,[B($,{variant:`ghost`,disabled:w.value===0||c.value,onClick:pe},{default:M(()=>[...n[25]||=[V(` Discard changes `,-1)]]),_:1},8,[`disabled`]),B($,{variant:`primary`,disabled:w.value===0,loading:c.value,onClick:fe},{default:M(()=>[...n[26]||=[V(` Save settings `,-1)]]),_:1},8,[`disabled`,`loading`])])])])]),B(HI,{open:E.value,"onUpdate:open":n[12]||=e=>E.value=e,title:`Unsaved changes`,description:`What Save would write to servers.config.json.`,width:`w-[min(92vw,42rem)]`},{default:M(()=>[z(`div`,qU,[ee.value.length>0?(I(),L(`ul`,JU,[(I(!0),L(F,null,ya(ee.value,e=>(I(),L(`li`,{key:e.path,class:`flex flex-wrap items-baseline gap-x-3 gap-y-1 py-2 first:pt-0`},[z(`code`,YU,k(e.path),1),z(`span`,XU,k(te(e.from)),1),n[27]||=z(`span`,{class:`text-2xs text-faint`},`→`,-1),z(`span`,ZU,k(te(e.to)),1)]))),128))])):(I(),L(`p`,QU,` Nothing to save. `))]),z(`footer`,$U,[B($,{size:`sm`,variant:`ghost`,onClick:n[10]||=e=>E.value=!1},{default:M(()=>[...n[28]||=[V(` Close `,-1)]]),_:1}),B($,{size:`sm`,variant:`primary`,disabled:ee.value.length===0||c.value,loading:c.value,onClick:n[11]||=e=>{fe(),E.value=!1}},{default:M(()=>[V(` Save `+k(ee.value.length)+` change`+k(ee.value.length===1?``:`s`),1)]),_:1},8,[`disabled`,`loading`])])]),_:1},8,[`open`])],64)}}}),tW=uv({history:M_(),routes:[{path:`/`,name:`overview`,component:tB,meta:{title:`Overview`}},{path:`/servers`,name:`servers`,component:QB,meta:{title:`Servers`}},{path:`/servers/:id`,name:`server-detail`,component:KB,meta:{title:`Server`}},{path:`/logs`,name:`logs`,component:_L,meta:{title:`Logs`}},{path:`/settings`,name:`settings`,component:eW,meta:{title:`Settings`}},{path:`/login`,name:`login`,component:VF,meta:{title:`Sign in`}},{path:`/:pathMatch(.*)*`,redirect:`/`}]});tW.beforeEach(async e=>{let{session:t,refresh:n}=Dj();t.value===null&&await n();let r=t.value?.authRequired===!0,i=t.value?.authenticated===!0;return r&&!i&&e.name!==`login`?{name:`login`,query:e.fullPath===`/`?void 0:{next:e.fullPath}}:e.name===`login`&&(!r||i)?{name:`overview`}:!0}),bu(gF).use(tW).mount(`#app`);
@@ -16,7 +16,7 @@
16
16
  catch (error) { /* storage unavailable — fall through to light */ }
17
17
  })()
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-DqcdQi-W.js"></script>
19
+ <script type="module" crossorigin src="/assets/index-CjmgiKjn.js"></script>
20
20
  <link rel="stylesheet" crossorigin href="/assets/index-CmkrtNU0.css">
21
21
  </head>
22
22
  <body>