signalk-container 1.9.0-beta.1 → 1.9.0-beta.2
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,5 +1,6 @@
|
|
|
1
|
-
var e=globalThis.__SK_REACT__;if(!e)throw Error(`signalk-container: window.__SK_REACT__ is not set. The host signalk-server admin UI must expose React on window for plugin federation. Requires signalk-server 2.27.0 or newer.`);var t=e,{Activity:n,Children:r,Component:i,Fragment:a,Profiler:o,PureComponent:s,StrictMode:c,Suspense:l,cache:u,cacheSignal:d,cloneElement:f,createContext:p,createElement:m,createRef:h,forwardRef:g,isValidElement:_,lazy:v,memo:y,startTransition:b,use:x,useActionState:S,useCallback:C,useContext:w,useDebugValue:T,useDeferredValue:E,useEffect:D,useEffectEvent:O,useId:k,useImperativeHandle:A,useInsertionEffect:
|
|
1
|
+
var e=globalThis.__SK_REACT__;if(!e)throw Error(`signalk-container: window.__SK_REACT__ is not set. The host signalk-server admin UI must expose React on window for plugin federation. Requires signalk-server 2.27.0 or newer.`);var t=e,{Activity:n,Children:r,Component:i,Fragment:a,Profiler:o,PureComponent:s,StrictMode:c,Suspense:l,cache:u,cacheSignal:d,cloneElement:f,createContext:p,createElement:m,createRef:h,forwardRef:g,isValidElement:_,lazy:v,memo:y,startTransition:b,use:x,useActionState:S,useCallback:C,useContext:w,useDebugValue:T,useDeferredValue:E,useEffect:D,useEffectEvent:O,useId:k,useImperativeHandle:A,useInsertionEffect:ee,useLayoutEffect:j,useMemo:M,useOptimistic:N,useReducer:P,useRef:F,useState:I,useSyncExternalStore:L,useTransition:R,version:te}=t,z=globalThis.__SK_REACT_JSX_RUNTIME__;if(!z)throw Error(`signalk-container: window.__SK_REACT_JSX_RUNTIME__ is not set by the host.`);var B=z,{Fragment:V,jsx:H,jsxs:U}=B;B.jsxDEV;var W=1e4,G=200,K={overlay:{position:`fixed`,inset:0,background:`rgba(0,0,0,0.5)`,display:`flex`,alignItems:`center`,justifyContent:`center`,zIndex:99999},modal:{background:`#fff`,borderRadius:8,boxShadow:`0 10px 25px rgba(0,0,0,0.2)`,width:`min(90vw, 1100px)`,maxHeight:`85vh`,display:`flex`,flexDirection:`column`,overflow:`hidden`},header:{padding:`12px 16px`,borderBottom:`1px solid #e5e7eb`,display:`flex`,alignItems:`center`,gap:12},title:{fontSize:16,fontWeight:600,color:`#111`,flex:1},statusDot:{width:10,height:10,borderRadius:`50%`,flexShrink:0},statusGreen:{background:`#10b981`},statusAmber:{background:`#f59e0b`},statusRed:{background:`#ef4444`},toolbarBtn:{padding:`5px 10px`,fontSize:12,background:`#fff`,color:`#374151`,border:`1px solid #d1d5db`,borderRadius:4,cursor:`pointer`},toolbarBtnActive:{background:`#3b82f6`,color:`#fff`,borderColor:`#3b82f6`},closeBtn:{padding:`5px 12px`,fontSize:12,background:`#fff`,color:`#374151`,border:`1px solid #d1d5db`,borderRadius:4,cursor:`pointer`},body:{flex:1,overflow:`auto`,background:`#1f2937`,color:`#e5e7eb`,fontFamily:`"SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace`,fontSize:12,lineHeight:1.5,padding:`12px 16px`,margin:0,whiteSpace:`pre-wrap`,wordBreak:`break-word`},endBanner:{padding:`8px 16px`,background:`#fef3c7`,color:`#92400e`,fontSize:12,borderTop:`1px solid #fde68a`},metaLine:{fontSize:11,color:`#6b7280`}};function ne({name:e,onClose:t}){let[n,r]=I([]),[i,a]=I(`connecting`),[o,s]=I(null),[c,l]=I(!0),[u,d]=I(!1),f=F(null),p=F(null),m=F(null),h=F(!1),g=F(!1),_=F(null),v=F(null),y=F(null),b=C(e=>{r(t=>{let n=t.concat(e);return n.length>W?n.slice(n.length-W):n})},[]);D(()=>{let t=!1;return(async()=>{let n=!1;try{let r=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/logs?tail=${G}`);if(t)return;if(r.ok){let e=await r.json();b(Array.isArray(e.lines)?e.lines:[]),a(`backfill`)}else r.status===404?(b([`[error] container not found (HTTP 404)`]),s(`container not found`),a(`disconnected`),n=!0):(b([`[error] backfill failed: HTTP ${r.status}`]),a(`backfill`))}catch(e){if(t)return;b([`[error] backfill failed: ${e instanceof Error?e.message:String(e)}`])}if(t||n)return;let r=new EventSource(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/logs/stream`);m.current=r,r.addEventListener(`hello`,()=>{t||a(`streaming`)}),r.onmessage=e=>{t||e.data!=null&&b([e.data])},r.addEventListener(`end`,e=>{if(t)return;let n=e.data;s(n||`stream ended`),a(`disconnected`),r.close()}),r.onerror=()=>{t||a(e=>e===`disconnected`?e:`connecting`)}})(),()=>{t=!0,m.current&&=(m.current.close(),null)}},[e,b]),j(()=>{if(!c||h.current)return;let e=p.current;e&&(g.current=!0,e.scrollIntoView({block:`end`,inline:`nearest`}),requestAnimationFrame(()=>{g.current=!1}))},[n,c]);let x=C(e=>{if(g.current){g.current=!1;return}let t=f.current;t&&(h.current=!(t.scrollHeight-t.scrollTop-t.clientHeight<8))},[]),S=C(()=>{l(e=>{let t=!e;if(t){h.current=!1;let e=p.current;e&&(g.current=!0,e.scrollIntoView({block:`end`,inline:`nearest`}),requestAnimationFrame(()=>{g.current=!1}))}return t})},[]),w=C(async()=>{let e=n.join(`
|
|
2
2
|
`);(await(async()=>{if(typeof navigator>`u`||!navigator.clipboard)return!1;try{return await navigator.clipboard.writeText(e),!0}catch{return!1}})()||(()=>{if(typeof document>`u`)return!1;let t=document.createElement(`textarea`);t.value=e,t.style.position=`fixed`,t.style.top=`-1000px`,t.style.left=`-1000px`,t.setAttribute(`readonly`,``),document.body.appendChild(t),t.select();let n=!1;try{n=document.execCommand(`copy`)}catch{n=!1}return document.body.removeChild(t),n})())&&(d(!0),setTimeout(()=>d(!1),1500))},[n]),T=C(()=>{let t=new Blob([n.join(`
|
|
3
3
|
`)+`
|
|
4
4
|
`],{type:`text/plain;charset=utf-8`}),r=URL.createObjectURL(t),i=document.createElement(`a`);i.href=r,i.download=`${e}-${new Date().toISOString().replace(/[:.]/g,`-`)}.log`,document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(r)},[n,e]);D(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),D(()=>{y.current=typeof document<`u`?document.activeElement:null;let e=setTimeout(()=>{v.current?.focus()},0);return()=>{clearTimeout(e);let t=y.current;if(t instanceof HTMLElement)try{t.focus()}catch{}}},[]);let E=C(e=>{if(e.key!==`Tab`)return;let t=_.current;if(!t)return;let n=Array.from(t.querySelectorAll(`button:not([disabled]), [tabindex]:not([tabindex="-1"])`)).filter(e=>e.offsetParent!==null);if(n.length===0)return;let r=n[0],i=n[n.length-1],a=document.activeElement;e.shiftKey?(a===r||!t.contains(a))&&(e.preventDefault(),i.focus()):a===i&&(e.preventDefault(),r.focus())},[]),O=C(e=>{e.target===e.currentTarget&&t()},[t]),k=i===`streaming`?K.statusGreen:i===`disconnected`?K.statusRed:K.statusAmber,A=i===`connecting`?`Connecting…`:i===`backfill`?`Backfilled — opening live stream…`:i===`streaming`?`Live`:`Disconnected`;return H(`div`,{style:K.overlay,onClick:O,role:`dialog`,"aria-modal":`true`,"aria-label":`Logs for ${e}`,children:U(`div`,{style:K.modal,ref:_,onKeyDown:E,children:[U(`div`,{style:K.header,children:[H(`span`,{style:{...K.statusDot,...k},title:A}),H(`span`,{style:K.title,children:e}),H(`span`,{style:K.metaLine,children:A}),U(`span`,{style:K.metaLine,children:[`· `,n.length,` lines`]}),H(`button`,{type:`button`,style:{...K.toolbarBtn,...c?K.toolbarBtnActive:{}},onClick:S,title:`Auto-scroll to bottom on new lines`,children:`Auto-scroll`}),H(`button`,{type:`button`,style:K.toolbarBtn,onClick:w,title:`Copy visible log to clipboard`,children:u?`Copied!`:`Copy`}),H(`button`,{type:`button`,style:K.toolbarBtn,onClick:T,title:`Download log as text file`,children:`Download`}),H(`button`,{type:`button`,style:K.closeBtn,onClick:t,ref:v,children:`Close`})]}),U(`pre`,{ref:f,style:K.body,onScroll:x,"aria-label":`Container log output`,tabIndex:0,children:[n.length===0?`(no log lines yet)`:n.join(`
|
|
5
|
-
`),H(`div`,{ref:p,"aria-hidden":`true`})]}),o&&U(`div`,{style:K.endBanner,children:[`Stream ended: `,o]})]})})}var q={root:{fontFamily:`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`,color:`#333`,padding:`16px 0`},sectionTitle:{fontSize:13,fontWeight:600,color:`#888`,textTransform:`uppercase`,letterSpacing:`0.05em`,marginBottom:10,marginTop:24},btn:{display:`inline-flex`,alignItems:`center`,gap:8,padding:`8px 16px`,border:`none`,borderRadius:6,fontSize:13,fontWeight:600,cursor:`pointer`},btnPrimary:{background:`#3b82f6`,color:`#fff`},btnDanger:{background:`#ef4444`,color:`#fff`,padding:`6px 12px`,fontSize:12},btnWarning:{background:`#f59e0b`,color:`#fff`,padding:`6px 12px`,fontSize:12},btnSave:{background:`#3b82f6`,color:`#fff`},btnSuccess:{background:`#10b981`,color:`#fff`},btnDisabled:{opacity:.5,cursor:`not-allowed`},status:{marginTop:8,fontSize:12,minHeight:18},runtimeCard:{display:`flex`,alignItems:`center`,gap:14,padding:`14px 18px`,background:`#f8f9fa`,border:`1px solid #e0e0e0`,borderRadius:10,marginBottom:12},runtimeIcon:{width:44,height:44,borderRadius:10,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:22,flexShrink:0},runtimeInfo:{flex:1},runtimeName:{fontSize:15,fontWeight:600,color:`#333`},runtimeVersion:{fontSize:12,color:`#888`},containerItem:{display:`flex`,alignItems:`center`,gap:12,padding:`10px 14px`,background:`#f8f9fa`,border:`1px solid #e0e0e0`,borderRadius:10,marginBottom:8},stateIndicator:{width:10,height:10,borderRadius:`50%`,flexShrink:0},containerInfo:{flex:1,minWidth:0},containerName:{fontSize:14,fontWeight:600,color:`#333`},containerMeta:{fontSize:11,color:`#888`,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`},containerActions:{display:`flex`,gap:6,flexShrink:0},containerCard:{background:`#f8f9fa`,border:`1px solid #e0e0e0`,borderRadius:10,marginBottom:8,overflow:`hidden`},containerItemFlat:{display:`flex`,alignItems:`center`,gap:12,padding:`10px 14px`},limitsRow:{display:`flex`,alignItems:`center`,gap:10,padding:`6px 14px 10px 34px`,fontSize:11,color:`#666`,flexWrap:`wrap`},updatesRow:{display:`flex`,alignItems:`center`,gap:10,padding:`0 14px 10px 34px`,fontSize:11,color:`#666`,flexWrap:`wrap`},limitBadge:{display:`inline-flex`,alignItems:`center`,padding:`2px 8px`,borderRadius:12,background:`#e5e7eb`,color:`#374151`,fontSize:11,fontWeight:500},overrideBadge:{display:`inline-flex`,alignItems:`center`,padding:`2px 8px`,borderRadius:12,background:`#fef3c7`,color:`#92400e`,fontSize:10,fontWeight:600},editLimitsBtn:{marginLeft:`auto`,padding:`3px 10px`,fontSize:11,background:`#fff`,color:`#3b82f6`,border:`1px solid #3b82f6`,borderRadius:6,cursor:`pointer`,fontWeight:500},limitsEditor:{borderTop:`1px solid #e0e0e0`,padding:`14px 14px 14px 34px`,background:`#fff`},limitsEditorGrid:{display:`grid`,gridTemplateColumns:`160px 1fr auto`,gap:`8px 12px`,alignItems:`center`,marginBottom:10},limitsEditorLabel:{fontSize:12,fontWeight:500,color:`#555`},limitsEditorInput:{padding:`5px 8px`,borderRadius:5,border:`1px solid #ccc`,fontSize:12,background:`#fff`,color:`#333`,fontFamily:`inherit`,width:`100%`,boxSizing:`border-box`},limitsEditorInputDisabled:{background:`#f3f4f6`,color:`#9ca3af`,fontStyle:`italic`},limitsEditorUnsetBtn:{width:24,height:24,borderRadius:4,border:`1px solid #d1d5db`,background:`#fff`,color:`#6b7280`,fontSize:14,lineHeight:1,cursor:`pointer`,display:`inline-flex`,alignItems:`center`,justifyContent:`center`},limitsEditorUnsetBtnActive:{background:`#fee2e2`,color:`#991b1b`,border:`1px solid #fca5a5`},limitsEditorAdvancedToggle:{fontSize:11,color:`#3b82f6`,cursor:`pointer`,userSelect:`none`,marginTop:6,marginBottom:10,display:`inline-block`},limitsEditorActions:{display:`flex`,gap:8,justifyContent:`flex-end`,marginTop:10},limitsEditorResult:{marginTop:10,padding:`8px 10px`,borderRadius:6,fontSize:11,lineHeight:1.5},limitsEditorResultLive:{background:`#d1fae5`,color:`#065f46`},limitsEditorResultRecreated:{background:`#fef3c7`,color:`#92400e`},limitsEditorResultError:{background:`#fee2e2`,color:`#991b1b`},limitsEditorWarning:{marginTop:4,fontSize:10,opacity:.85,fontStyle:`italic`},empty:{textAlign:`center`,padding:`30px 16px`,color:`#999`,fontSize:13},fieldRow:{display:`flex`,alignItems:`center`,gap:12,marginBottom:10},label:{fontSize:13,fontWeight:500,color:`#555`,width:160,flexShrink:0},select:{padding:`6px 10px`,borderRadius:6,border:`1px solid #ccc`,fontSize:13,background:`#fff`,color:`#333`},hint:{fontSize:11,color:`#aaa`,marginLeft:8},pruneResult:{fontSize:12,color:`#10b981`,marginTop:6}},re={running:`#10b981`,stopped:`#f59e0b`,missing:`#94a3b8`,"no-runtime":`#ef4444`},J={running:`Running`,stopped:`Stopped`,missing:`Not created`,"no-runtime":`No runtime`};function Y({label:e,value:t,options:n,onChange:r,hint:i}){return U(`div`,{style:q.fieldRow,children:[H(`span`,{style:q.label,children:e}),H(`select`,{style:q.select,value:t,onChange:e=>r(e.target.value),children:n.map(e=>H(`option`,{value:e.value,children:e.label},e.value))}),i&&H(`span`,{style:q.hint,children:i})]})}function ie({label:e,value:t,onChange:n,hint:r}){return U(`div`,{style:q.fieldRow,children:[H(`span`,{style:q.label,children:e}),U(`label`,{style:{display:`inline-flex`,alignItems:`center`,cursor:`pointer`,gap:8},children:[H(`input`,{type:`checkbox`,checked:t,onChange:e=>n(e.target.checked),style:{width:16,height:16,cursor:`pointer`}}),H(`span`,{style:{fontSize:13,color:`#555`},children:t?`Enabled`:`Disabled`})]}),r&&H(`span`,{style:q.hint,children:r})]})}function X(e){return e&&e.startsWith(`sk-`)?e.slice(3):e}var Z=[{key:`cpus`,label:`CPU (cores)`,type:`number`,step:`0.1`,min:`0.1`,placeholder:`e.g. 1.5`,primary:!0},{key:`memory`,label:`Memory`,type:`text`,placeholder:`e.g. 512m, 2g`,primary:!0},{key:`memorySwap`,label:`Memory + swap`,type:`text`,placeholder:`= memory to disable swap`,primary:!0},{key:`pidsLimit`,label:`Max processes`,type:`number`,step:`1`,min:`1`,placeholder:`e.g. 200`,primary:!0},{key:`cpuShares`,label:`CPU shares (weight)`,type:`number`,step:`1`,min:`2`,placeholder:`default 1024`,primary:!1},{key:`cpusetCpus`,label:`Pin to CPUs`,type:`text`,placeholder:`e.g. "0,1" or "1-3"`,primary:!1},{key:`memoryReservation`,label:`Memory reservation`,type:`text`,placeholder:`soft floor, e.g. 256m`,primary:!1},{key:`oomScoreAdj`,label:`OOM score adjust`,type:`number`,step:`1`,min:`-1000`,max:`1000`,placeholder:`-1000 to 1000`,primary:!1}];function ae(e){try{let t=new Date(e).getTime();if(!Number.isFinite(t))return e;let n=Math.max(0,Math.floor((Date.now()-t)/1e3));if(n<5)return`just now`;if(n<60)return`${n}s ago`;let r=Math.floor(n/60);if(r<60)return`${r}m ago`;let i=Math.floor(r/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}catch{return e}}function oe(e){if(!e)return`No update data`;let{runningTag:t,currentVersion:n,latestVersion:r,updateAvailable:i,reason:a,fromCache:o}=e;return a===`offline`?o?`\u{1F4E1} Offline — last cached result shows ${i?`update available`:`up to date`}`:`📡 Offline — no cached result yet`:a===`newer-version`?`\u2191 Update available: ${n} \u2192 ${r}`:a===`digest-drift`?`\u21BB Rebuild available for :${t}${r?` (latest stable: ${r})`:``}`:a===`up-to-date`?`\u2705 Up to date${n?` (`+n+`)`:``}`:a===`older-than-pinned`?`\u2139 Pinned to ${n}, latest stable is ${r}`:a===`error`?`\u26A0 Check error: ${e.error||`unknown`}`:`State: ${a||`unknown`}`}function se(e){if(!e||!e.reason)return null;let{reason:t,runningTag:n,currentVersion:r,latestVersion:i,fromCache:a}=e;return t===`newer-version`?{label:`\u2191 ${i||`update`} available`,bg:`#fef3c7`,fg:`#92400e`,title:`Update available: ${r} \u2192 ${i}`}:t===`digest-drift`?{label:`↻ rebuild available`,bg:`#fef3c7`,fg:`#92400e`,title:`Image rebuild available for :${n}${i?` (latest stable ${i})`:``}`}:t===`offline`?{label:a?`📡 offline (cached)`:`📡 offline`,bg:`#e5e7eb`,fg:`#4b5563`,title:a?`Network unreachable; showing last cached check result`:`Network unreachable; no cached result yet`}:t===`error`?{label:`⚠ check error`,bg:`#fee2e2`,fg:`#991b1b`,title:e.error||`Update check error`}:t===`up-to-date`?{label:`✅ up to date`,bg:`#dcfce7`,fg:`#166534`,title:`Up to date${r?` (`+r+`)`:``}`}:null}function Q(e){let t={};for(let n of Z){let r=n.key,i=e[n.key];if(i===null){t[r]=null;continue}if(!(i===void 0||i===``))if(n.type===`number`){let e=Number(i);if(!Number.isFinite(e))continue;t[r]=e}else t[r]=i}return t}function ce(e,t){if(t==null||t===``)return null;switch(e){case`cpus`:return`${t} CPU`;case`memory`:return`${t}`;case`memorySwap`:return`swap: ${t}`;case`memoryReservation`:return`reserve: ${t}`;case`pidsLimit`:return`${t} PIDs`;case`cpuShares`:return`shares: ${t}`;case`cpusetCpus`:return`cpus: ${t}`;case`oomScoreAdj`:return`oom: ${t}`;default:return`${e}: ${t}`}}function le({containerName:e,effective:n,initialOverride:r,onApply:i,onResetToDefault:a,onClose:o}){let s=e=>{let t=e??n,r={};for(let e of Z){let n=e.key,i=t?t[n]:void 0;i==null?r[e.key]=``:r[e.key]=String(i)}return r},[c,l]=I(()=>s(n)),[u,d]=I(()=>r?Z.some(e=>!e.primary&&r[e.key]!==void 0):!1),[f,p]=I(!1),[m,h]=I(!1),[g,_]=I(null),v=(e,t)=>{l(n=>({...n,[e]:t}))},y=e=>{l(t=>({...t,[e]:t[e]===null?``:null}))},b=()=>{l(s(n)),_(null)},x=async()=>{p(!0),_(null);try{let e=await i(Q(c));_(e),e&&e.effective&&l(s(e.effective))}catch(e){_({error:e instanceof Error?e.message:String(e)})}p(!1)},S=async()=>{if(window.confirm(`Reset ${e} to the plugin's default resource limits? This will remove your override and may cause a brief container recreate (~5s of downtime) if memory limits need to be unset.`)){h(!0),_(null);try{let e=await a();_(e),e&&e.effective&&l(s(e.effective))}catch(e){_({error:e instanceof Error?e.message:String(e)})}h(!1)}},C=n=>{let r=c[n.key],i=r===null;return U(t.Fragment,{children:[H(`label`,{style:q.limitsEditorLabel,htmlFor:`lim-${e}-${n.key}`,children:n.label}),H(`input`,{id:`lim-${e}-${n.key}`,type:i?`text`:n.type,value:i?``:r,step:n.step,min:n.min,max:n.max,placeholder:i?`(unset — remove limit)`:n.placeholder,disabled:i,onChange:e=>v(n.key,e.target.value),style:{...q.limitsEditorInput,...i?q.limitsEditorInputDisabled:{}}}),H(`button`,{type:`button`,onClick:()=>y(n.key),title:i?`Click to set a value again`:`Click to explicitly unset (remove this limit)`,style:{...q.limitsEditorUnsetBtn,...i?q.limitsEditorUnsetBtnActive:{}},children:i?`↺`:`×`})]},n.key)},w=Z.filter(e=>e.primary),T=Z.filter(e=>!e.primary);return U(`div`,{style:q.limitsEditor,children:[H(`div`,{style:q.limitsEditorGrid,children:w.map(C)}),U(`span`,{onClick:()=>d(!u),style:q.limitsEditorAdvancedToggle,children:[u?`▾`:`▸`,` Advanced (`,T.length,` more fields)`]}),u&&H(`div`,{style:q.limitsEditorGrid,children:T.map(C)}),U(`div`,{style:q.limitsEditorActions,children:[H(`button`,{type:`button`,onClick:o,disabled:f||m,style:{...q.btn,padding:`6px 12px`,fontSize:12,background:`#fff`,color:`#6b7280`,border:`1px solid #d1d5db`,...f||m?q.btnDisabled:{}},children:`Close`}),H(`button`,{type:`button`,onClick:S,disabled:f||m,title:`Clear override and restore plugin-default limits`,style:{...q.btn,padding:`6px 12px`,fontSize:12,background:`#fff`,color:`#d97706`,border:`1px solid #f59e0b`,...f||m?q.btnDisabled:{}},children:m?`Resetting...`:`Reset to default`}),H(`button`,{type:`button`,onClick:b,disabled:f||m,title:`Discard unsaved changes in this form (does not touch the server)`,style:{...q.btn,padding:`6px 12px`,fontSize:12,background:`#fff`,color:`#6b7280`,border:`1px solid #d1d5db`,...f||m?q.btnDisabled:{}},children:`Revert`}),H(`button`,{type:`button`,onClick:x,disabled:f||m,style:{...q.btn,...q.btnPrimary,padding:`6px 14px`,fontSize:12,...f||m?q.btnDisabled:{}},children:f?`Applying...`:`Apply`})]}),g&&H(`div`,{style:{...q.limitsEditorResult,...g.error?q.limitsEditorResultError:g.method===`recreated`?q.limitsEditorResultRecreated:q.limitsEditorResultLive},children:g.error?U(V,{children:[H(`strong`,{children:`Error:`}),` `,g.error]}):U(V,{children:[H(`strong`,{children:g.method===`live`?`Applied live (no restart)`:`Container recreated`}),g.warnings&&g.warnings.length>0&&H(`div`,{style:q.limitsEditorWarning,children:g.warnings.map((e,t)=>U(`div`,{children:[`⚠ `,e]},t))})]})})]})}function $({configuration:e,save:t}){let n=e||{},[r,i]=I(n.runtime||`auto`),[a,o]=I(n.pruneSchedule||`weekly`),[s,c]=I(n.updateCheckInterval||`24h`),[l,u]=I(n.backgroundUpdateChecks!==!1),[d,f]=I(n.containerOverrides||{}),[p,m]=I(null),[h,g]=I([]),[_,v]=I({}),[y,b]=I({}),[x,S]=I({}),[w,T]=I({}),[E,O]=I(new Set),[k,A]=I(new Set),[j,M]=I(null),[N,ee]=I(!0),[P,F]=I(``),[L,R]=I(!1),[te,z]=I(null),B=C(async()=>{try{let[e,t]=await Promise.all([fetch(`/plugins/signalk-container/api/runtime`),fetch(`/plugins/signalk-container/api/containers`)]);e.ok?m(await e.json()):m(null);let n=[];if(t.ok?(n=await t.json(),g(n)):g([]),n.length>0){let e={},t={};await Promise.all(n.map(async n=>{let r=X(n.name);try{let n=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(r)}/resources`);if(n.ok){let i=await n.json();e[r]=i.effective||{},t[r]=i.override??null}}catch{}})),v(e),b(t)}try{let e=await fetch(`/plugins/signalk-container/api/updates`);if(e.ok){let t=await e.json();if(Array.isArray(t)){let e={},n={};for(let r of t)r&&r.containerName&&(n[r.containerName]=r,r.pluginId&&(e[r.containerName]=r.pluginId));S(e=>{let t={...n};for(let r of Object.keys(n)){let i=n[r],a=e[r];i.reason===`unknown`&&a&&a.reason&&a.reason!==`unknown`&&(t[r]=a)}return t}),T(e)}}}catch{}}catch{m(null),g([])}ee(!1)},[]);D(()=>{B();let e=setInterval(B,5e3);return()=>clearInterval(e)},[B]);let V=e=>{let t=X(e);A(e=>{let n=new Set(e);return n.has(t)?n.delete(t):n.add(t),n})},W=async(e,t)=>{let n=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/resources`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)}),r=await n.json().catch(()=>({}));return n.ok?(r.effective&&v(t=>({...t,[e]:r.effective})),b(t=>({...t,[e]:r.override??null})),f(t=>{let n={...t};return r.override&&Object.keys(r.override).length>0?n[e]=r.override:delete n[e],n}),{method:r.method,warnings:r.warnings,effective:r.effective}):{error:r.error||`${n.status} ${n.statusText}`}},G=async e=>{let t=w[e];if(!t){F(`${e}: no update service registered. The consumer plugin hasn't migrated to signalk-container's update detection yet.`),R(!0);return}O(t=>{let n=new Set(t);return n.add(e),n}),F(`Checking ${e} for updates...`),R(!1);try{let n=await fetch(`/plugins/signalk-container/api/updates/${encodeURIComponent(t)}/check`,{method:`POST`});if(n.ok){let t=await n.json();S(n=>({...n,[e]:t})),F(oe(t)),R(!1)}else F(`Check failed: ${(await n.json().catch(()=>({error:n.statusText}))).error}`),R(!0)}catch(e){F(`Check error: ${e instanceof Error?e.message:String(e)}`),R(!0)}O(t=>{let n=new Set(t);return n.delete(e),n})},K=async e=>{let t=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/resources`,{method:`DELETE`}),n=await t.json().catch(()=>({}));return t.ok?(n.effective&&v(t=>({...t,[e]:n.effective})),b(t=>({...t,[e]:null})),f(t=>{let n={...t};return delete n[e],n}),{method:n.method,warnings:n.warnings,effective:n.effective}):{error:n.error||`${t.status} ${t.statusText}`}},Q=()=>{let e={};for(let[t,n]of Object.entries(y))n&&Object.keys(n).length>0&&(e[t]=n);t({...n,runtime:r,pruneSchedule:a,maxConcurrentJobs:n.maxConcurrentJobs||2,updateCheckInterval:s,backgroundUpdateChecks:l,containerOverrides:e}),F(`Saved! Plugin will restart.`),R(!1)},$=async e=>{F(`Starting ${e}...`),R(!1);try{let t=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/start`,{method:`POST`});t.ok?(F(`${e} started.`),B()):(F(`Failed: ${(await t.json().catch(()=>({error:t.statusText}))).error}`),R(!0))}catch(e){F(`Error: ${e instanceof Error?e.message:String(e)}`),R(!0)}},ue=async e=>{F(`Stopping ${e}...`),R(!1);try{let t=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/stop`,{method:`POST`});t.ok?(F(`${e} stopped.`),B()):(F(`Failed: ${(await t.json().catch(()=>({error:t.statusText}))).error}`),R(!0))}catch(e){F(`Error: ${e instanceof Error?e.message:String(e)}`),R(!0)}},de=async(e,t)=>{if(!(t===`running`&&!window.confirm(`${e} is running. Stop and remove it?`))){F(`Removing ${e}...`),R(!1);try{let t=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/remove`,{method:`POST`});t.ok?(F(`${e} removed.`),B()):(F(`Failed: ${(await t.json().catch(()=>({error:t.statusText}))).error}`),R(!0))}catch(e){F(`Error: ${e instanceof Error?e.message:String(e)}`),R(!0)}}},fe=async()=>{F(`Pruning dangling images...`),R(!1),z(null);try{let e=await fetch(`/plugins/signalk-container/api/prune`,{method:`POST`});if(e.ok){let t=await e.json().catch(()=>({error:e.statusText}));z(t),F(`Pruned ${t.imagesRemoved} image(s), reclaimed ${t.spaceReclaimed}.`)}else F(`Prune failed: ${(await e.json().catch(()=>({error:e.statusText}))).error}`),R(!0)}catch(e){F(`Error: ${e instanceof Error?e.message:String(e)}`),R(!0)}};return U(`div`,{style:q.root,children:[H(`div`,{style:q.sectionTitle,children:`Runtime`}),N?H(`div`,{style:q.empty,children:`Detecting container runtime...`}):p?U(`div`,{style:q.runtimeCard,children:[H(`div`,{style:{...q.runtimeIcon,background:p.runtime===`podman`?`#892ca0`:`#2496ed`,color:`#fff`},children:p.runtime===`podman`?`P`:`D`}),U(`div`,{style:q.runtimeInfo,children:[U(`div`,{style:q.runtimeName,children:[p.runtime.charAt(0).toUpperCase()+p.runtime.slice(1),p.isPodmanDockerShim?` (via docker shim)`:``]}),U(`div`,{style:q.runtimeVersion,children:[`Version `,p.version]})]}),H(`div`,{style:{...q.stateIndicator,background:`#10b981`},title:`Runtime available`})]}):U(`div`,{style:q.runtimeCard,children:[H(`div`,{style:{...q.runtimeIcon,background:`#fef2f2`,color:`#ef4444`},children:`!`}),U(`div`,{style:q.runtimeInfo,children:[H(`div`,{style:q.runtimeName,children:`No container runtime found`}),H(`div`,{style:q.runtimeVersion,children:`Install Podman: sudo apt install podman`})]})]}),H(`div`,{style:q.sectionTitle,children:`Settings`}),H(Y,{label:`Preferred runtime`,value:r,onChange:i,options:[{value:`auto`,label:`Auto-detect (Podman preferred)`},{value:`podman`,label:`Podman`},{value:`docker`,label:`Docker`}]}),H(Y,{label:`Auto-prune images`,value:a,onChange:o,options:[{value:`off`,label:`Off`},{value:`weekly`,label:`Weekly`},{value:`monthly`,label:`Monthly`}]}),H(Y,{label:`Update check interval`,value:s,onChange:c,options:[{value:`1h`,label:`Every hour`},{value:`6h`,label:`Every 6 hours`},{value:`12h`,label:`Every 12 hours`},{value:`24h`,label:`Daily (recommended)`},{value:`48h`,label:`Every 2 days`},{value:`168h`,label:`Weekly`}],hint:`How often to check for new container images`}),H(ie,{label:`Background update checks`,value:l,onChange:u,hint:`Disable on metered connections; manual check still works`}),H(`div`,{style:q.sectionTitle,children:`Managed Containers`}),h.length===0?H(`div`,{style:q.empty,children:N?`Loading...`:`No managed containers. Other plugins will create them.`}):h.map(e=>{let t=X(e.name),n=_[t]||{},r=y[t],i=r&&Object.keys(r).length>0,a=k.has(t),o=t.startsWith(`job-`),s=Z.map(e=>ce(e.key,n[e.key])).filter(Boolean),c=x[t],l=se(c),u=!!w[t],d=E.has(t);return U(`div`,{style:q.containerCard,children:[U(`div`,{style:q.containerItemFlat,children:[H(`div`,{style:{...q.stateIndicator,background:re[e.state]||`#94a3b8`},title:J[e.state]||e.state}),U(`div`,{style:q.containerInfo,children:[H(`div`,{style:q.containerName,children:e.name}),U(`div`,{style:q.containerMeta,children:[e.image,` · `,J[e.state]||e.state,e.ports&&e.ports.length>0&&e.ports[0]?` · ${e.ports.join(`, `)}`:``]})]}),U(`div`,{style:q.containerActions,children:[e.state===`stopped`&&H(`button`,{style:{...q.btn,...q.btnPrimary,padding:`6px 12px`,fontSize:12},onClick:()=>$(e.name),children:`Start`}),e.state===`running`&&H(`button`,{style:{...q.btn,...q.btnWarning},onClick:()=>ue(e.name),children:`Stop`}),H(`button`,{style:{...q.btn,background:`#fff`,color:`#374151`,border:`1px solid #d1d5db`},onClick:()=>M(e.name),title:`Stream the container's stdout+stderr log`,children:`Logs`}),H(`button`,{style:{...q.btn,...q.btnDanger},onClick:()=>de(e.name,e.state),children:`Remove`})]})]}),U(`div`,{style:q.limitsRow,children:[s.length===0?H(`span`,{style:{color:`#9ca3af`,fontStyle:`italic`},children:`No resource limits set`}):s.map((e,t)=>H(`span`,{style:q.limitBadge,children:e},t)),i&&H(`span`,{style:q.overrideBadge,title:`You have a user override configured for this container`,children:`Override active`}),e.state===`running`&&!o&&H(`button`,{type:`button`,style:q.editLimitsBtn,onClick:()=>V(e.name),children:a?`Collapse ▾`:`Edit Limits ▸`})]}),u&&e.state===`running`&&U(`div`,{style:q.updatesRow,children:[l?H(`span`,{style:{...q.limitBadge,background:l.bg,color:l.fg},title:l.title,children:l.label}):H(`span`,{style:{color:`#9ca3af`,fontStyle:`italic`},children:`No update check yet`}),c?.lastSuccessfulCheckAt&&U(`span`,{style:{fontSize:10,color:`#9ca3af`},title:`Last successful check: ${c.lastSuccessfulCheckAt}`,children:[`checked`,` `,ae(c.lastSuccessfulCheckAt)]}),H(`button`,{type:`button`,onClick:()=>G(t),disabled:d,title:`Force a fresh update check now`,style:{...q.editLimitsBtn,marginLeft:`auto`,...d?q.btnDisabled:{}},children:d?`Checking...`:`Check now ↻`})]}),a&&H(le,{containerName:t,effective:n,initialOverride:r,onApply:e=>W(t,e),onResetToDefault:()=>K(t),onClose:()=>V(e.name)})]},e.name)}),H(`div`,{style:q.sectionTitle,children:`Maintenance`}),H(`button`,{style:{...q.btn,...q.btnSuccess},onClick:fe,children:`Prune Dangling Images`}),P&&H(`div`,{style:{...q.status,color:L?`#ef4444`:`#10b981`},children:P}),H(`div`,{style:{...q.sectionTitle,marginTop:28},children:`\xA0`}),H(`button`,{style:{...q.btn,...q.btnSave},onClick:Q,children:`Save Configuration`}),j&&H(ne,{name:j,onClose:()=>M(null)})]})}export{$ as default};
|
|
5
|
+
`),H(`div`,{ref:p,"aria-hidden":`true`})]}),o&&U(`div`,{style:K.endBanner,children:[`Stream ended: `,o]})]})})}var q={root:{fontFamily:`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`,color:`#333`,padding:`16px 0`},sectionTitle:{fontSize:13,fontWeight:600,color:`#888`,textTransform:`uppercase`,letterSpacing:`0.05em`,marginBottom:10,marginTop:24},btn:{display:`inline-flex`,alignItems:`center`,gap:8,padding:`8px 16px`,border:`none`,borderRadius:6,fontSize:13,fontWeight:600,cursor:`pointer`},btnPrimary:{background:`#3b82f6`,color:`#fff`},btnDanger:{background:`#ef4444`,color:`#fff`,padding:`6px 12px`,fontSize:12},btnWarning:{background:`#f59e0b`,color:`#fff`,padding:`6px 12px`,fontSize:12},btnSave:{background:`#3b82f6`,color:`#fff`},btnSuccess:{background:`#10b981`,color:`#fff`},btnDisabled:{opacity:.5,cursor:`not-allowed`},status:{marginTop:8,fontSize:12,minHeight:18},runtimeCard:{display:`flex`,alignItems:`center`,gap:14,padding:`14px 18px`,background:`#f8f9fa`,border:`1px solid #e0e0e0`,borderRadius:10,marginBottom:12},runtimeIcon:{width:44,height:44,borderRadius:10,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:22,flexShrink:0},runtimeInfo:{flex:1},runtimeName:{fontSize:15,fontWeight:600,color:`#333`},runtimeVersion:{fontSize:12,color:`#888`},runtimeRemediation:{fontSize:12,color:`#666`,fontFamily:`ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace`,whiteSpace:`pre-wrap`,marginTop:6,lineHeight:1.45},containerItem:{display:`flex`,alignItems:`center`,gap:12,padding:`10px 14px`,background:`#f8f9fa`,border:`1px solid #e0e0e0`,borderRadius:10,marginBottom:8},stateIndicator:{width:10,height:10,borderRadius:`50%`,flexShrink:0},containerInfo:{flex:1,minWidth:0},containerName:{fontSize:14,fontWeight:600,color:`#333`},containerMeta:{fontSize:11,color:`#888`,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`},containerActions:{display:`flex`,gap:6,flexShrink:0},containerCard:{background:`#f8f9fa`,border:`1px solid #e0e0e0`,borderRadius:10,marginBottom:8,overflow:`hidden`},containerItemFlat:{display:`flex`,alignItems:`center`,gap:12,padding:`10px 14px`},limitsRow:{display:`flex`,alignItems:`center`,gap:10,padding:`6px 14px 10px 34px`,fontSize:11,color:`#666`,flexWrap:`wrap`},updatesRow:{display:`flex`,alignItems:`center`,gap:10,padding:`0 14px 10px 34px`,fontSize:11,color:`#666`,flexWrap:`wrap`},limitBadge:{display:`inline-flex`,alignItems:`center`,padding:`2px 8px`,borderRadius:12,background:`#e5e7eb`,color:`#374151`,fontSize:11,fontWeight:500},overrideBadge:{display:`inline-flex`,alignItems:`center`,padding:`2px 8px`,borderRadius:12,background:`#fef3c7`,color:`#92400e`,fontSize:10,fontWeight:600},editLimitsBtn:{marginLeft:`auto`,padding:`3px 10px`,fontSize:11,background:`#fff`,color:`#3b82f6`,border:`1px solid #3b82f6`,borderRadius:6,cursor:`pointer`,fontWeight:500},limitsEditor:{borderTop:`1px solid #e0e0e0`,padding:`14px 14px 14px 34px`,background:`#fff`},limitsEditorGrid:{display:`grid`,gridTemplateColumns:`160px 1fr auto`,gap:`8px 12px`,alignItems:`center`,marginBottom:10},limitsEditorLabel:{fontSize:12,fontWeight:500,color:`#555`},limitsEditorInput:{padding:`5px 8px`,borderRadius:5,border:`1px solid #ccc`,fontSize:12,background:`#fff`,color:`#333`,fontFamily:`inherit`,width:`100%`,boxSizing:`border-box`},limitsEditorInputDisabled:{background:`#f3f4f6`,color:`#9ca3af`,fontStyle:`italic`},limitsEditorUnsetBtn:{width:24,height:24,borderRadius:4,border:`1px solid #d1d5db`,background:`#fff`,color:`#6b7280`,fontSize:14,lineHeight:1,cursor:`pointer`,display:`inline-flex`,alignItems:`center`,justifyContent:`center`},limitsEditorUnsetBtnActive:{background:`#fee2e2`,color:`#991b1b`,border:`1px solid #fca5a5`},limitsEditorAdvancedToggle:{fontSize:11,color:`#3b82f6`,cursor:`pointer`,userSelect:`none`,marginTop:6,marginBottom:10,display:`inline-block`},limitsEditorActions:{display:`flex`,gap:8,justifyContent:`flex-end`,marginTop:10},limitsEditorResult:{marginTop:10,padding:`8px 10px`,borderRadius:6,fontSize:11,lineHeight:1.5},limitsEditorResultLive:{background:`#d1fae5`,color:`#065f46`},limitsEditorResultRecreated:{background:`#fef3c7`,color:`#92400e`},limitsEditorResultError:{background:`#fee2e2`,color:`#991b1b`},limitsEditorWarning:{marginTop:4,fontSize:10,opacity:.85,fontStyle:`italic`},empty:{textAlign:`center`,padding:`30px 16px`,color:`#999`,fontSize:13},fieldRow:{display:`flex`,alignItems:`center`,gap:12,marginBottom:10},label:{fontSize:13,fontWeight:500,color:`#555`,width:160,flexShrink:0},select:{padding:`6px 10px`,borderRadius:6,border:`1px solid #ccc`,fontSize:13,background:`#fff`,color:`#333`},hint:{fontSize:11,color:`#aaa`,marginLeft:8},pruneResult:{fontSize:12,color:`#10b981`,marginTop:6}},re={running:`#10b981`,stopped:`#f59e0b`,missing:`#94a3b8`,"no-runtime":`#ef4444`},J={running:`Running`,stopped:`Stopped`,missing:`Not created`,"no-runtime":`No runtime`};function Y({label:e,value:t,options:n,onChange:r,hint:i}){return U(`div`,{style:q.fieldRow,children:[H(`span`,{style:q.label,children:e}),H(`select`,{style:q.select,value:t,onChange:e=>r(e.target.value),children:n.map(e=>H(`option`,{value:e.value,children:e.label},e.value))}),i&&H(`span`,{style:q.hint,children:i})]})}function ie({label:e,value:t,onChange:n,hint:r}){return U(`div`,{style:q.fieldRow,children:[H(`span`,{style:q.label,children:e}),U(`label`,{style:{display:`inline-flex`,alignItems:`center`,cursor:`pointer`,gap:8},children:[H(`input`,{type:`checkbox`,checked:t,onChange:e=>n(e.target.checked),style:{width:16,height:16,cursor:`pointer`}}),H(`span`,{style:{fontSize:13,color:`#555`},children:t?`Enabled`:`Disabled`})]}),r&&H(`span`,{style:q.hint,children:r})]})}function X(e){return e&&e.startsWith(`sk-`)?e.slice(3):e}var Z=[{key:`cpus`,label:`CPU (cores)`,type:`number`,step:`0.1`,min:`0.1`,placeholder:`e.g. 1.5`,primary:!0},{key:`memory`,label:`Memory`,type:`text`,placeholder:`e.g. 512m, 2g`,primary:!0},{key:`memorySwap`,label:`Memory + swap`,type:`text`,placeholder:`= memory to disable swap`,primary:!0},{key:`pidsLimit`,label:`Max processes`,type:`number`,step:`1`,min:`1`,placeholder:`e.g. 200`,primary:!0},{key:`cpuShares`,label:`CPU shares (weight)`,type:`number`,step:`1`,min:`2`,placeholder:`default 1024`,primary:!1},{key:`cpusetCpus`,label:`Pin to CPUs`,type:`text`,placeholder:`e.g. "0,1" or "1-3"`,primary:!1},{key:`memoryReservation`,label:`Memory reservation`,type:`text`,placeholder:`soft floor, e.g. 256m`,primary:!1},{key:`oomScoreAdj`,label:`OOM score adjust`,type:`number`,step:`1`,min:`-1000`,max:`1000`,placeholder:`-1000 to 1000`,primary:!1}];function ae(e){try{let t=new Date(e).getTime();if(!Number.isFinite(t))return e;let n=Math.max(0,Math.floor((Date.now()-t)/1e3));if(n<5)return`just now`;if(n<60)return`${n}s ago`;let r=Math.floor(n/60);if(r<60)return`${r}m ago`;let i=Math.floor(r/60);return i<24?`${i}h ago`:`${Math.floor(i/24)}d ago`}catch{return e}}function oe(e){if(!e)return`No update data`;let{runningTag:t,currentVersion:n,latestVersion:r,updateAvailable:i,reason:a,fromCache:o}=e;return a===`offline`?o?`\u{1F4E1} Offline — last cached result shows ${i?`update available`:`up to date`}`:`📡 Offline — no cached result yet`:a===`newer-version`?`\u2191 Update available: ${n} \u2192 ${r}`:a===`digest-drift`?`\u21BB Rebuild available for :${t}${r?` (latest stable: ${r})`:``}`:a===`up-to-date`?`\u2705 Up to date${n?` (`+n+`)`:``}`:a===`older-than-pinned`?`\u2139 Pinned to ${n}, latest stable is ${r}`:a===`error`?`\u26A0 Check error: ${e.error||`unknown`}`:`State: ${a||`unknown`}`}function se(e){if(!e||!e.reason)return null;let{reason:t,runningTag:n,currentVersion:r,latestVersion:i,fromCache:a}=e;return t===`newer-version`?{label:`\u2191 ${i||`update`} available`,bg:`#fef3c7`,fg:`#92400e`,title:`Update available: ${r} \u2192 ${i}`}:t===`digest-drift`?{label:`↻ rebuild available`,bg:`#fef3c7`,fg:`#92400e`,title:`Image rebuild available for :${n}${i?` (latest stable ${i})`:``}`}:t===`offline`?{label:a?`📡 offline (cached)`:`📡 offline`,bg:`#e5e7eb`,fg:`#4b5563`,title:a?`Network unreachable; showing last cached check result`:`Network unreachable; no cached result yet`}:t===`error`?{label:`⚠ check error`,bg:`#fee2e2`,fg:`#991b1b`,title:e.error||`Update check error`}:t===`up-to-date`?{label:`✅ up to date`,bg:`#dcfce7`,fg:`#166534`,title:`Up to date${r?` (`+r+`)`:``}`}:null}function Q(e){let t={};for(let n of Z){let r=n.key,i=e[n.key];if(i===null){t[r]=null;continue}if(!(i===void 0||i===``))if(n.type===`number`){let e=Number(i);if(!Number.isFinite(e))continue;t[r]=e}else t[r]=i}return t}function ce(e,t){if(t==null||t===``)return null;switch(e){case`cpus`:return`${t} CPU`;case`memory`:return`${t}`;case`memorySwap`:return`swap: ${t}`;case`memoryReservation`:return`reserve: ${t}`;case`pidsLimit`:return`${t} PIDs`;case`cpuShares`:return`shares: ${t}`;case`cpusetCpus`:return`cpus: ${t}`;case`oomScoreAdj`:return`oom: ${t}`;default:return`${e}: ${t}`}}function le({containerName:e,effective:n,initialOverride:r,onApply:i,onResetToDefault:a,onClose:o}){let s=e=>{let t=e??n,r={};for(let e of Z){let n=e.key,i=t?t[n]:void 0;i==null?r[e.key]=``:r[e.key]=String(i)}return r},[c,l]=I(()=>s(n)),[u,d]=I(()=>r?Z.some(e=>!e.primary&&r[e.key]!==void 0):!1),[f,p]=I(!1),[m,h]=I(!1),[g,_]=I(null),v=(e,t)=>{l(n=>({...n,[e]:t}))},y=e=>{l(t=>({...t,[e]:t[e]===null?``:null}))},b=()=>{l(s(n)),_(null)},x=async()=>{p(!0),_(null);try{let e=await i(Q(c));_(e),e&&e.effective&&l(s(e.effective))}catch(e){_({error:e instanceof Error?e.message:String(e)})}p(!1)},S=async()=>{if(window.confirm(`Reset ${e} to the plugin's default resource limits? This will remove your override and may cause a brief container recreate (~5s of downtime) if memory limits need to be unset.`)){h(!0),_(null);try{let e=await a();_(e),e&&e.effective&&l(s(e.effective))}catch(e){_({error:e instanceof Error?e.message:String(e)})}h(!1)}},C=n=>{let r=c[n.key],i=r===null;return U(t.Fragment,{children:[H(`label`,{style:q.limitsEditorLabel,htmlFor:`lim-${e}-${n.key}`,children:n.label}),H(`input`,{id:`lim-${e}-${n.key}`,type:i?`text`:n.type,value:i?``:r,step:n.step,min:n.min,max:n.max,placeholder:i?`(unset — remove limit)`:n.placeholder,disabled:i,onChange:e=>v(n.key,e.target.value),style:{...q.limitsEditorInput,...i?q.limitsEditorInputDisabled:{}}}),H(`button`,{type:`button`,onClick:()=>y(n.key),title:i?`Click to set a value again`:`Click to explicitly unset (remove this limit)`,style:{...q.limitsEditorUnsetBtn,...i?q.limitsEditorUnsetBtnActive:{}},children:i?`↺`:`×`})]},n.key)},w=Z.filter(e=>e.primary),T=Z.filter(e=>!e.primary);return U(`div`,{style:q.limitsEditor,children:[H(`div`,{style:q.limitsEditorGrid,children:w.map(C)}),U(`span`,{onClick:()=>d(!u),style:q.limitsEditorAdvancedToggle,children:[u?`▾`:`▸`,` Advanced (`,T.length,` more fields)`]}),u&&H(`div`,{style:q.limitsEditorGrid,children:T.map(C)}),U(`div`,{style:q.limitsEditorActions,children:[H(`button`,{type:`button`,onClick:o,disabled:f||m,style:{...q.btn,padding:`6px 12px`,fontSize:12,background:`#fff`,color:`#6b7280`,border:`1px solid #d1d5db`,...f||m?q.btnDisabled:{}},children:`Close`}),H(`button`,{type:`button`,onClick:S,disabled:f||m,title:`Clear override and restore plugin-default limits`,style:{...q.btn,padding:`6px 12px`,fontSize:12,background:`#fff`,color:`#d97706`,border:`1px solid #f59e0b`,...f||m?q.btnDisabled:{}},children:m?`Resetting...`:`Reset to default`}),H(`button`,{type:`button`,onClick:b,disabled:f||m,title:`Discard unsaved changes in this form (does not touch the server)`,style:{...q.btn,padding:`6px 12px`,fontSize:12,background:`#fff`,color:`#6b7280`,border:`1px solid #d1d5db`,...f||m?q.btnDisabled:{}},children:`Revert`}),H(`button`,{type:`button`,onClick:x,disabled:f||m,style:{...q.btn,...q.btnPrimary,padding:`6px 14px`,fontSize:12,...f||m?q.btnDisabled:{}},children:f?`Applying...`:`Apply`})]}),g&&H(`div`,{style:{...q.limitsEditorResult,...g.error?q.limitsEditorResultError:g.method===`recreated`?q.limitsEditorResultRecreated:q.limitsEditorResultLive},children:g.error?U(V,{children:[H(`strong`,{children:`Error:`}),` `,g.error]}):U(V,{children:[H(`strong`,{children:g.method===`live`?`Applied live (no restart)`:`Container recreated`}),g.warnings&&g.warnings.length>0&&H(`div`,{style:q.limitsEditorWarning,children:g.warnings.map((e,t)=>U(`div`,{children:[`⚠ `,e]},t))})]})})]})}function ue(e){if(!e)return`No container runtime found`;switch(e.status){case`no-runtime`:return`No container runtime found`;case`socket-unreachable`:return`Runtime socket unreachable`;case`permission-denied`:return`Runtime socket: permission denied`;case`self-id-unresolved`:return`Signal K container ID unresolved`;case`ok`:return`Container runtime ready`}}function $({configuration:e,save:t}){let n=e||{},[r,i]=I(n.runtime||`auto`),[a,o]=I(n.pruneSchedule||`weekly`),[s,c]=I(n.updateCheckInterval||`24h`),[l,u]=I(n.backgroundUpdateChecks!==!1),[d,f]=I(n.containerOverrides||{}),[p,m]=I(null),[h,g]=I(null),[_,v]=I([]),[y,b]=I({}),[x,S]=I({}),[w,T]=I({}),[E,O]=I({}),[k,A]=I(new Set),[ee,j]=I(new Set),[M,N]=I(null),[P,F]=I(!0),[L,R]=I(``),[te,z]=I(!1),[B,V]=I(null),W=C(async()=>{try{let[e,t]=await Promise.all([fetch(`/plugins/signalk-container/api/runtime`),fetch(`/plugins/signalk-container/api/containers`)]),n=!1;if(e.ok){let t=await e.json();m(t),n=t!==null}else m(null);if(n)g(null);else try{let e=await fetch(`/plugins/signalk-container/api/doctor/deployment`);e.ok?g(await e.json()):g(null)}catch{g(null)}let r=[];if(t.ok?(r=await t.json(),v(r)):v([]),r.length>0){let e={},t={};await Promise.all(r.map(async n=>{let r=X(n.name);try{let n=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(r)}/resources`);if(n.ok){let i=await n.json();e[r]=i.effective||{},t[r]=i.override??null}}catch{}})),b(e),S(t)}try{let e=await fetch(`/plugins/signalk-container/api/updates`);if(e.ok){let t=await e.json();if(Array.isArray(t)){let e={},n={};for(let r of t)r&&r.containerName&&(n[r.containerName]=r,r.pluginId&&(e[r.containerName]=r.pluginId));T(e=>{let t={...n};for(let r of Object.keys(n)){let i=n[r],a=e[r];i.reason===`unknown`&&a&&a.reason&&a.reason!==`unknown`&&(t[r]=a)}return t}),O(e)}}}catch{}}catch{m(null),v([])}F(!1)},[]);D(()=>{W();let e=setInterval(W,5e3);return()=>clearInterval(e)},[W]);let G=e=>{let t=X(e);j(e=>{let n=new Set(e);return n.has(t)?n.delete(t):n.add(t),n})},K=async(e,t)=>{let n=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/resources`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)}),r=await n.json().catch(()=>({}));return n.ok?(r.effective&&b(t=>({...t,[e]:r.effective})),S(t=>({...t,[e]:r.override??null})),f(t=>{let n={...t};return r.override&&Object.keys(r.override).length>0?n[e]=r.override:delete n[e],n}),{method:r.method,warnings:r.warnings,effective:r.effective}):{error:r.error||`${n.status} ${n.statusText}`}},Q=async e=>{let t=E[e];if(!t){R(`${e}: no update service registered. The consumer plugin hasn't migrated to signalk-container's update detection yet.`),z(!0);return}A(t=>{let n=new Set(t);return n.add(e),n}),R(`Checking ${e} for updates...`),z(!1);try{let n=await fetch(`/plugins/signalk-container/api/updates/${encodeURIComponent(t)}/check`,{method:`POST`});if(n.ok){let t=await n.json();T(n=>({...n,[e]:t})),R(oe(t)),z(!1)}else R(`Check failed: ${(await n.json().catch(()=>({error:n.statusText}))).error}`),z(!0)}catch(e){R(`Check error: ${e instanceof Error?e.message:String(e)}`),z(!0)}A(t=>{let n=new Set(t);return n.delete(e),n})},$=async e=>{let t=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/resources`,{method:`DELETE`}),n=await t.json().catch(()=>({}));return t.ok?(n.effective&&b(t=>({...t,[e]:n.effective})),S(t=>({...t,[e]:null})),f(t=>{let n={...t};return delete n[e],n}),{method:n.method,warnings:n.warnings,effective:n.effective}):{error:n.error||`${t.status} ${t.statusText}`}},de=()=>{let e={};for(let[t,n]of Object.entries(x))n&&Object.keys(n).length>0&&(e[t]=n);t({...n,runtime:r,pruneSchedule:a,maxConcurrentJobs:n.maxConcurrentJobs||2,updateCheckInterval:s,backgroundUpdateChecks:l,containerOverrides:e}),R(`Saved! Plugin will restart.`),z(!1)},fe=async e=>{R(`Starting ${e}...`),z(!1);try{let t=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/start`,{method:`POST`});t.ok?(R(`${e} started.`),W()):(R(`Failed: ${(await t.json().catch(()=>({error:t.statusText}))).error}`),z(!0))}catch(e){R(`Error: ${e instanceof Error?e.message:String(e)}`),z(!0)}},pe=async e=>{R(`Stopping ${e}...`),z(!1);try{let t=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/stop`,{method:`POST`});t.ok?(R(`${e} stopped.`),W()):(R(`Failed: ${(await t.json().catch(()=>({error:t.statusText}))).error}`),z(!0))}catch(e){R(`Error: ${e instanceof Error?e.message:String(e)}`),z(!0)}},me=async(e,t)=>{if(!(t===`running`&&!window.confirm(`${e} is running. Stop and remove it?`))){R(`Removing ${e}...`),z(!1);try{let t=await fetch(`/plugins/signalk-container/api/containers/${encodeURIComponent(e)}/remove`,{method:`POST`});t.ok?(R(`${e} removed.`),W()):(R(`Failed: ${(await t.json().catch(()=>({error:t.statusText}))).error}`),z(!0))}catch(e){R(`Error: ${e instanceof Error?e.message:String(e)}`),z(!0)}}},he=async()=>{R(`Pruning dangling images...`),z(!1),V(null);try{let e=await fetch(`/plugins/signalk-container/api/prune`,{method:`POST`});if(e.ok){let t=await e.json().catch(()=>({error:e.statusText}));V(t),R(`Pruned ${t.imagesRemoved} image(s), reclaimed ${t.spaceReclaimed}.`)}else R(`Prune failed: ${(await e.json().catch(()=>({error:e.statusText}))).error}`),z(!0)}catch(e){R(`Error: ${e instanceof Error?e.message:String(e)}`),z(!0)}};return U(`div`,{style:q.root,children:[H(`div`,{style:q.sectionTitle,children:`Runtime`}),P?H(`div`,{style:q.empty,children:`Detecting container runtime...`}):p?U(`div`,{style:q.runtimeCard,children:[H(`div`,{style:{...q.runtimeIcon,background:p.runtime===`podman`?`#892ca0`:`#2496ed`,color:`#fff`},children:p.runtime===`podman`?`P`:`D`}),U(`div`,{style:q.runtimeInfo,children:[U(`div`,{style:q.runtimeName,children:[p.runtime.charAt(0).toUpperCase()+p.runtime.slice(1),p.isPodmanDockerShim?` (via docker shim)`:``]}),U(`div`,{style:q.runtimeVersion,children:[`Version `,p.version]})]}),H(`div`,{style:{...q.stateIndicator,background:`#10b981`},title:`Runtime available`})]}):U(`div`,{style:q.runtimeCard,children:[H(`div`,{style:{...q.runtimeIcon,background:`#fef2f2`,color:`#ef4444`},children:`!`}),U(`div`,{style:q.runtimeInfo,children:[H(`div`,{style:q.runtimeName,children:ue(h)}),h&&h.remediation.length>0?H(`div`,{style:q.runtimeRemediation,children:h.remediation.join(`
|
|
6
|
+
`)}):H(`div`,{style:q.runtimeVersion,children:`See GET /plugins/signalk-container/api/doctor/deployment for details.`})]})]}),H(`div`,{style:q.sectionTitle,children:`Settings`}),H(Y,{label:`Preferred runtime`,value:r,onChange:i,options:[{value:`auto`,label:`Auto-detect (Podman preferred)`},{value:`podman`,label:`Podman`},{value:`docker`,label:`Docker`}]}),H(Y,{label:`Auto-prune images`,value:a,onChange:o,options:[{value:`off`,label:`Off`},{value:`weekly`,label:`Weekly`},{value:`monthly`,label:`Monthly`}]}),H(Y,{label:`Update check interval`,value:s,onChange:c,options:[{value:`1h`,label:`Every hour`},{value:`6h`,label:`Every 6 hours`},{value:`12h`,label:`Every 12 hours`},{value:`24h`,label:`Daily (recommended)`},{value:`48h`,label:`Every 2 days`},{value:`168h`,label:`Weekly`}],hint:`How often to check for new container images`}),H(ie,{label:`Background update checks`,value:l,onChange:u,hint:`Disable on metered connections; manual check still works`}),H(`div`,{style:q.sectionTitle,children:`Managed Containers`}),_.length===0?H(`div`,{style:q.empty,children:P?`Loading...`:`No managed containers. Other plugins will create them.`}):_.map(e=>{let t=X(e.name),n=y[t]||{},r=x[t],i=r&&Object.keys(r).length>0,a=ee.has(t),o=t.startsWith(`job-`),s=Z.map(e=>ce(e.key,n[e.key])).filter(Boolean),c=w[t],l=se(c),u=!!E[t],d=k.has(t);return U(`div`,{style:q.containerCard,children:[U(`div`,{style:q.containerItemFlat,children:[H(`div`,{style:{...q.stateIndicator,background:re[e.state]||`#94a3b8`},title:J[e.state]||e.state}),U(`div`,{style:q.containerInfo,children:[H(`div`,{style:q.containerName,children:e.name}),U(`div`,{style:q.containerMeta,children:[e.image,` · `,J[e.state]||e.state,e.ports&&e.ports.length>0&&e.ports[0]?` · ${e.ports.join(`, `)}`:``]})]}),U(`div`,{style:q.containerActions,children:[e.state===`stopped`&&H(`button`,{style:{...q.btn,...q.btnPrimary,padding:`6px 12px`,fontSize:12},onClick:()=>fe(e.name),children:`Start`}),e.state===`running`&&H(`button`,{style:{...q.btn,...q.btnWarning},onClick:()=>pe(e.name),children:`Stop`}),H(`button`,{style:{...q.btn,background:`#fff`,color:`#374151`,border:`1px solid #d1d5db`},onClick:()=>N(e.name),title:`Stream the container's stdout+stderr log`,children:`Logs`}),H(`button`,{style:{...q.btn,...q.btnDanger},onClick:()=>me(e.name,e.state),children:`Remove`})]})]}),U(`div`,{style:q.limitsRow,children:[s.length===0?H(`span`,{style:{color:`#9ca3af`,fontStyle:`italic`},children:`No resource limits set`}):s.map((e,t)=>H(`span`,{style:q.limitBadge,children:e},t)),i&&H(`span`,{style:q.overrideBadge,title:`You have a user override configured for this container`,children:`Override active`}),e.state===`running`&&!o&&H(`button`,{type:`button`,style:q.editLimitsBtn,onClick:()=>G(e.name),children:a?`Collapse ▾`:`Edit Limits ▸`})]}),u&&e.state===`running`&&U(`div`,{style:q.updatesRow,children:[l?H(`span`,{style:{...q.limitBadge,background:l.bg,color:l.fg},title:l.title,children:l.label}):H(`span`,{style:{color:`#9ca3af`,fontStyle:`italic`},children:`No update check yet`}),c?.lastSuccessfulCheckAt&&U(`span`,{style:{fontSize:10,color:`#9ca3af`},title:`Last successful check: ${c.lastSuccessfulCheckAt}`,children:[`checked`,` `,ae(c.lastSuccessfulCheckAt)]}),H(`button`,{type:`button`,onClick:()=>Q(t),disabled:d,title:`Force a fresh update check now`,style:{...q.editLimitsBtn,marginLeft:`auto`,...d?q.btnDisabled:{}},children:d?`Checking...`:`Check now ↻`})]}),a&&H(le,{containerName:t,effective:n,initialOverride:r,onApply:e=>K(t,e),onResetToDefault:()=>$(t),onClose:()=>G(e.name)})]},e.name)}),H(`div`,{style:q.sectionTitle,children:`Maintenance`}),H(`button`,{style:{...q.btn,...q.btnSuccess},onClick:he,children:`Prune Dangling Images`}),L&&H(`div`,{style:{...q.status,color:te?`#ef4444`:`#10b981`},children:L}),H(`div`,{style:{...q.sectionTitle,marginTop:28},children:`\xA0`}),H(`button`,{style:{...q.btn,...q.btnSave},onClick:de,children:`Save Configuration`}),M&&H(ne,{name:M,onClose:()=>N(null)})]})}export{$ as default};
|