signalk-questdb 1.4.1 → 1.4.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,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-questdb",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "QuestDB history provider for Signal K — replaces signalk-to-influxdb",
5
5
  "keywords": [
6
6
  "signalk-node-server-plugin",
package/public/805.js CHANGED
@@ -1,2 +1 @@
1
- /*! For license information please see 805.js.LICENSE.txt */
2
- "use strict";(self.webpackChunksignalk_questdb=self.webpackChunksignalk_questdb||[]).push([[805],{805(e,t,i){i.r(t),i.d(t,{default:()=>d});var n=i(231),a=i(446);const o={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"},btnSave:{background:"#3b82f6",color:"#fff"},btnDanger:{background:"#ef4444",color:"#fff",padding:"6px 12px",fontSize:12},btnDisabled:{opacity:.5,cursor:"not-allowed"},status:{marginTop:8,fontSize:12,minHeight:18},warnBanner:{padding:"12px 16px",background:"#fef2f2",border:"1px solid #fecaca",borderRadius:10,marginBottom:12,fontSize:13,color:"#991b1b",lineHeight:1.5},warnBannerTitle:{fontWeight:700,marginBottom:4},infoBanner:{padding:"12px 16px",background:"#fffbeb",border:"1px solid #fde68a",borderRadius:10,marginBottom:12,fontSize:13,color:"#92400e",lineHeight:1.5},warnBannerCode:{display:"block",marginTop:8,padding:"8px 10px",background:"#fff",border:"1px solid #fecaca",borderRadius:6,fontFamily:"monospace",fontSize:12,color:"#7f1d1d",whiteSpace:"pre-wrap",wordBreak:"break-word"},card:{display:"flex",alignItems:"center",gap:14,padding:"14px 18px",background:"#f8f9fa",border:"1px solid #e0e0e0",borderRadius:10,marginBottom:12},cardIcon:{width:44,height:44,borderRadius:10,display:"flex",alignItems:"center",justifyContent:"center",fontSize:20,fontWeight:700,flexShrink:0},cardInfo:{flex:1},cardTitle:{fontSize:15,fontWeight:600,color:"#333"},cardMeta:{fontSize:12,color:"#888"},stateIndicator:{width:10,height:10,borderRadius:"50%",flexShrink:0},fieldRow:{display:"flex",alignItems:"center",gap:12,marginBottom:10},label:{fontSize:13,fontWeight:500,color:"#555",width:180,flexShrink:0},select:{padding:"6px 10px",borderRadius:6,border:"1px solid #ccc",fontSize:13,background:"#fff",color:"#333",minWidth:200},input:{padding:"6px 10px",borderRadius:6,border:"1px solid #ccc",fontSize:13,background:"#fff",color:"#333",width:200},inputSmall:{padding:"6px 10px",borderRadius:6,border:"1px solid #ccc",fontSize:13,background:"#fff",color:"#333",width:80},checkbox:{width:16,height:16,accentColor:"#3b82f6"},hint:{fontSize:11,color:"#aaa",marginLeft:8},textarea:{padding:"6px 10px",borderRadius:6,border:"1px solid #ccc",fontSize:13,fontFamily:"monospace",background:"#fff",color:"#333",width:"100%",minHeight:70,boxSizing:"border-box",resize:"vertical"},empty:{textAlign:"center",padding:"30px 16px",color:"#999",fontSize:13},tag:{display:"inline-block",padding:"2px 8px",borderRadius:4,fontSize:10,fontWeight:600,marginLeft:8},tagPre:{background:"#fef3c7",color:"#92400e"},tagLatest:{background:"#dcfce7",color:"#166534"},migrationItem:{display:"flex",alignItems:"center",gap:12,padding:"12px 16px",background:"#f8f9fa",border:"1px solid #e0e0e0",borderRadius:10,marginBottom:8},migrationActions:{display:"flex",gap:8,alignItems:"center"},statsGrid:{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(140px, 1fr))",gap:10,marginBottom:12},statCard:{padding:"12px 16px",background:"#f8f9fa",border:"1px solid #e0e0e0",borderRadius:10,textAlign:"center"},statValue:{fontSize:22,fontWeight:700,color:"#333"},statLabel:{fontSize:11,color:"#888",marginTop:2}};function s({title:e,children:t}){const[i,s]=(0,n.useState)(!1);return(0,a.jsxDEV)("div",{children:[(0,a.jsxDEV)("button",{type:"button","aria-expanded":i,style:{...o.sectionTitle,cursor:"pointer",userSelect:"none",display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",background:"none",border:"none",padding:0},onClick:()=>s(!i),children:[(0,a.jsxDEV)("span",{style:{fontSize:10,transition:"transform 0.15s",transform:i?"rotate(90deg)":"rotate(0deg)"},children:"▶"},void 0,!1),e]},void 0,!0),i&&(0,a.jsxDEV)("div",{style:{marginBottom:16},children:t},void 0,!1)]},void 0,!0)}function l(e){return null==e?"—":e>=1e6?(e/1e6).toFixed(1)+"M":e>=1e3?(e/1e3).toFixed(1)+"K":String(e)}function d({configuration:e,save:t}){const i=e||{},[d,r]=(0,n.useState)(i.questdbHost||"127.0.0.1"),[c,u]=(0,n.useState)(i.questdbIlpPort||9009),[h,p]=(0,n.useState)(i.questdbHttpPort||9e3),[v,g]=(0,n.useState)(i.questdbPgPort||8812),[x,f]=(0,n.useState)(i.questdbVersion||"latest"),[y,b]=(0,n.useState)(!1!==i.managedContainer),[m,D]=(0,n.useState)(!1!==i.recordSelf),[E,j]=(0,n.useState)(i.recordOthers||!1),[V,w]=(0,n.useState)(i.defaultSamplingRate??2e3),[S,k]=(0,n.useState)(i.retentionDays||0),[R,C]=(0,n.useState)("include"===i.pathFilter?.mode?"include":"exclude"),[B,T]=(0,n.useState)((Array.isArray(i.pathFilter?.paths)?i.pathFilter.paths:[]).join("\n")),[I,z]=(0,n.useState)(i.compression||"lz4"),[L,P]=(0,n.useState)(i.compressionLevel||3),[q,Q]=(0,n.useState)(i.networkName||"sk-network"),[$,F]=(0,n.useState)(i.exposeToContainers||!1),[N,A]=(0,n.useState)([]),[U,M]=(0,n.useState)(!1),[W,H]=(0,n.useState)(null),[O,G]=(0,n.useState)(!0),[_,K]=(0,n.useState)(null),[Z,X]=(0,n.useState)(!1),[Y,J]=(0,n.useState)(""),[ee,te]=(0,n.useState)(""),[ie,ne]=(0,n.useState)(!1),[ae,oe]=(0,n.useState)(null),[se,le]=(0,n.useState)(!1),[de,re]=(0,n.useState)(!1),[ce,ue]=(0,n.useState)(!1),he=(0,n.useCallback)(async()=>{M(!0);try{const e=await fetch("/plugins/signalk-questdb/api/versions");if(e.ok){const t=await e.json();A(t)}}catch{}M(!1)},[]),pe=(0,n.useCallback)(async()=>{try{const e=await fetch("/plugins/signalk-questdb/api/status");e.ok?H(await e.json()):H({status:"not_running"})}catch{H({status:"not_running"})}G(!1)},[]);(0,n.useEffect)(()=>{he(),pe();const e=setInterval(pe,5e3);return()=>clearInterval(e)},[he,pe]);const[ve,ge]=(0,n.useState)(""),[xe,fe]=(0,n.useState)(""),[ye,be]=(0,n.useState)("parquet"),[me,De]=(0,n.useState)(!1),Ee=W&&"running"===W.status,je=Ee&&W.suspendedTables||[],Ve=je.length>0,we=Ee&&W.schemaMismatch||!1,Se=Ee&&W.ulimitClamp||null,ke=N.filter(e=>!e.prerelease).slice(0,3),Re=N.filter(e=>e.prerelease).slice(0,2);return(0,a.jsxDEV)("div",{style:o.root,children:[(0,a.jsxDEV)("div",{style:o.sectionTitle,children:"QuestDB Status"},void 0,!1),O?(0,a.jsxDEV)("div",{style:o.empty,children:"Checking QuestDB..."},void 0,!1):Ee?(0,a.jsxDEV)(a.Fragment,{children:[Ve&&(0,a.jsxDEV)("div",{style:o.warnBanner,children:[(0,a.jsxDEV)("div",{style:o.warnBannerTitle,children:"QuestDB WAL suspended — recording stalled"},void 0,!1),"Rows are arriving but no longer commit, so the counts below have stopped advancing. This follows an error while QuestDB applied the write-ahead log — see the reason reported per table below. Resume each suspended table in the QuestDB SQL console (",(0,a.jsxDEV)("code",{children:":9000"},void 0,!1),"), skipping the broken transaction:",(0,a.jsxDEV)("code",{style:o.warnBannerCode,children:je.map(e=>{const t=`"${String(e.name).replace(/"/g,'""')}"`,i=String(e.errorMessage||e.errorTag||"reason not reported").replace(/[\r\n]+/g," ");return`ALTER TABLE ${t} RESUME WAL FROM TXN ${e.writerTxn+1}; -- ${l(e.txnLag)} txns behind; ${i}`}).join("\n")},void 0,!1)]},void 0,!0),we&&(0,a.jsxDEV)("div",{style:o.warnBanner,children:[(0,a.jsxDEV)("div",{style:o.warnBannerTitle,children:"QuestDB table schema mismatch — data not readable"},void 0,!1),"A QuestDB table was re-created by ILP ingestion with the wrong timestamp column, so rows are being stored but reads filtering on time (history, Grafana, and the counts above) return nothing. The plugin rebuilds the affected table with the correct schema automatically within a minute; if this persists, restart the plugin. (The few rows written into the wrong-schema table are lost — they were unreadable anyway.)"]},void 0,!0),Se&&(0,a.jsxDEV)("div",{style:o.infoBanner,children:[(0,a.jsxDEV)("div",{style:o.warnBannerTitle,children:"QuestDB open-files limit capped by the host"},void 0,!1),"QuestDB requested a ",(0,a.jsxDEV)("code",{children:Se.ulimit},void 0,!1)," limit of"," ",l(Se.requested),", but this host only allows"," ",l(Se.granted),", so it was capped. QuestDB is running on the lower limit. To grant the full value, raise the host limit for the user running the container runtime — under rootless Podman that means a systemd ",(0,a.jsxDEV)("code",{children:"user@.service"},void 0,!1)," ",(0,a.jsxDEV)("code",{children:"LimitNOFILE"},void 0,!1)," drop-in (editing"," ",(0,a.jsxDEV)("code",{children:"/etc/security/limits.conf"},void 0,!1)," alone is usually not enough) — then restart the QuestDB container."," ",(0,a.jsxDEV)("a",{href:"https://github.com/dirkwa/signalk-container#raising-the-open-files-limit-nofile",target:"_blank",rel:"noreferrer",style:{color:"#92400e",textDecoration:"underline"},children:"Step-by-step instructions"},void 0,!1),"."]},void 0,!0),(0,a.jsxDEV)("div",{style:o.card,children:[(0,a.jsxDEV)("div",{style:{...o.cardIcon,background:"#7c3aed",color:"#fff"},children:"Q"},void 0,!1),(0,a.jsxDEV)("div",{style:o.cardInfo,children:[(0,a.jsxDEV)("div",{style:o.cardTitle,children:"QuestDB"},void 0,!1),(0,a.jsxDEV)("div",{style:o.cardMeta,children:[W?.endpoint||`${d}:${h}`," ","· ",Ve?"WAL suspended":"Recording"]},void 0,!0)]},void 0,!0),(0,a.jsxDEV)("div",{style:{...o.stateIndicator,background:Ve?"#ef4444":"#10b981"},title:Ve?"WAL suspended":"Running"},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.statsGrid,children:[(0,a.jsxDEV)("div",{style:o.statCard,children:[(0,a.jsxDEV)("div",{style:o.statValue,children:l(W.totalRows)},void 0,!1),(0,a.jsxDEV)("div",{style:o.statLabel,children:"Total Rows"},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.statCard,children:[(0,a.jsxDEV)("div",{style:o.statValue,children:l(W.activePathsToday)},void 0,!1),(0,a.jsxDEV)("div",{style:o.statLabel,children:"Active Paths Today"},void 0,!1)]},void 0,!0)]},void 0,!0),(0,a.jsxDEV)("div",{style:{display:"flex",alignItems:"center",gap:10,marginBottom:12},children:ae&&ae.updateAvailable?(0,a.jsxDEV)(a.Fragment,{children:[(0,a.jsxDEV)("span",{style:{fontSize:13},children:["v",ae.currentVersion," →"," ",(0,a.jsxDEV)("strong",{children:["v",ae.latestVersion]},void 0,!0)," available"]},void 0,!0),(0,a.jsxDEV)("button",{style:{...o.btn,...o.btnPrimary,padding:"4px 12px",fontSize:12,...de?o.btnDisabled:{}},onClick:async()=>{re(!0),te("Pulling new image and restarting..."),ne(!1);try{const e=await fetch("/plugins/signalk-questdb/api/update/apply",{method:"POST"});if(e.ok){const t=await e.json();te(t.message),oe(null),t.newVersion&&f(t.newVersion),pe()}else{const t=await e.json().catch(()=>({error:e.statusText}));te(`Update failed: ${t.error}`),ne(!0)}}catch(e){te(`Update failed: ${e.message}`),ne(!0)}re(!1)},disabled:de,children:de?"Updating...":"Update QuestDB"},void 0,!1)]},void 0,!0):ae&&!ae.updateAvailable?(0,a.jsxDEV)("span",{style:{fontSize:12,color:"#888"},children:["v",ae.currentVersion," (up to date)"]},void 0,!0):(0,a.jsxDEV)("button",{style:{...o.btn,padding:"4px 12px",fontSize:12,background:"#f1f5f9",color:"#475569",border:"1px solid #e2e8f0",...se?o.btnDisabled:{}},onClick:async()=>{le(!0);try{const e=await fetch("/plugins/signalk-questdb/api/update/check");e.ok&&oe(await e.json())}catch{}le(!1)},disabled:se,children:se?"Checking...":"Check for updates"},void 0,!1)},void 0,!1)]},void 0,!0):(0,a.jsxDEV)("div",{style:o.card,children:[(0,a.jsxDEV)("div",{style:{...o.cardIcon,background:"#fef2f2",color:"#ef4444"},children:"Q"},void 0,!1),(0,a.jsxDEV)("div",{style:o.cardInfo,children:[(0,a.jsxDEV)("div",{style:o.cardTitle,children:"QuestDB"},void 0,!1),(0,a.jsxDEV)("div",{style:o.cardMeta,children:["unhealthy"===W?.status?"Not responding":"Not running",y?" — enable plugin to start container":""]},void 0,!0)]},void 0,!0),(0,a.jsxDEV)("div",{style:{...o.stateIndicator,background:"#ef4444"}},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.sectionTitle,children:"Image Version"},void 0,!1),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"QuestDB version"},void 0,!1),(0,a.jsxDEV)("select",{style:o.select,value:x,onChange:e=>f(e.target.value),children:[(0,a.jsxDEV)("option",{value:"latest",children:"latest (recommended)"},void 0,!1),Re.map(e=>(0,a.jsxDEV)("option",{value:e.tag,children:[e.tag," (pre-release)"]},e.tag,!0)),ke.map((e,t)=>(0,a.jsxDEV)("option",{value:e.tag,children:[e.tag,0===t?" (current stable)":""]},e.tag,!0))]},void 0,!0),U&&(0,a.jsxDEV)("span",{style:o.hint,children:"loading releases..."},void 0,!1),(0,a.jsxDEV)("button",{style:{...o.btn,...o.btnPrimary,padding:"4px 10px",fontSize:11},onClick:he,children:"↻"},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.sectionTitle,children:"Connection"},void 0,!1),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"Managed container"},void 0,!1),(0,a.jsxDEV)("input",{type:"checkbox",style:o.checkbox,checked:y,onChange:e=>b(e.target.checked)},void 0,!1),(0,a.jsxDEV)("span",{style:o.hint,children:y?"signalk-container manages QuestDB":"Connect to external QuestDB"},void 0,!1)]},void 0,!0),!y&&(0,a.jsxDEV)(a.Fragment,{children:[(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"QuestDB host"},void 0,!1),(0,a.jsxDEV)("input",{style:o.input,value:d,onChange:e=>r(e.target.value)},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"HTTP port (queries)"},void 0,!1),(0,a.jsxDEV)("input",{style:o.inputSmall,type:"number",value:h,onChange:e=>p(Number(e.target.value))},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"ILP port (writes)"},void 0,!1),(0,a.jsxDEV)("input",{style:o.inputSmall,type:"number",value:c,onChange:e=>u(Number(e.target.value))},void 0,!1)]},void 0,!0)]},void 0,!0),y&&(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"Bind to 0.0.0.0"},void 0,!1),(0,a.jsxDEV)("input",{type:"checkbox",style:o.checkbox,checked:$,onChange:e=>F(e.target.checked)},void 0,!1),(0,a.jsxDEV)("span",{style:{...o.hint,color:$?"#ef4444":void 0},children:$?"Caution! This exposes your data to the network":"Signal K reaches QuestDB automatically — only enable to reach it from another machine or a separate-Docker Grafana"},void 0,!1)]},void 0,!0),y&&$&&(0,a.jsxDEV)(a.Fragment,{children:[(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"HTTP port (queries)"},void 0,!1),(0,a.jsxDEV)("input",{style:o.inputSmall,type:"number",value:h,onChange:e=>p(Number(e.target.value))},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"ILP port (writes)"},void 0,!1),(0,a.jsxDEV)("input",{style:o.inputSmall,type:"number",value:c,onChange:e=>u(Number(e.target.value))},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"PostgreSQL wire port"},void 0,!1),(0,a.jsxDEV)("input",{style:o.inputSmall,type:"number",value:v,onChange:e=>g(Number(e.target.value))},void 0,!1),(0,a.jsxDEV)("span",{style:o.hint,children:"for Grafana"},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"Container network"},void 0,!1),(0,a.jsxDEV)("input",{style:o.input,value:q,onChange:e=>Q(e.target.value)},void 0,!1),(0,a.jsxDEV)("span",{style:o.hint,children:"shared with signalk-grafana"},void 0,!1)]},void 0,!0)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.sectionTitle,children:"Recording"},void 0,!1),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"Default sampling rate (ms)"},void 0,!1),(0,a.jsxDEV)("input",{style:o.inputSmall,type:"number",value:V,onChange:e=>w(Number(e.target.value))},void 0,!1),(0,a.jsxDEV)("span",{style:o.hint,children:"1000 = max 1 write/sec per path (0 = every update)"},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"Record own vessel"},void 0,!1),(0,a.jsxDEV)("input",{type:"checkbox",style:o.checkbox,checked:m,onChange:e=>D(e.target.checked)},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"Record AIS targets"},void 0,!1),(0,a.jsxDEV)("input",{type:"checkbox",style:o.checkbox,checked:E,onChange:e=>j(e.target.checked)},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"Retention (days)"},void 0,!1),(0,a.jsxDEV)("input",{style:o.inputSmall,type:"number",value:S,onChange:e=>k(Number(e.target.value))},void 0,!1),(0,a.jsxDEV)("span",{style:o.hint,children:"0 = keep forever"},void 0,!1)]},void 0,!0),(0,a.jsxDEV)(s,{title:"Path filtering",children:[(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"Filter mode"},void 0,!1),(0,a.jsxDEV)("select",{style:o.select,value:R,onChange:e=>C(e.target.value),children:[(0,a.jsxDEV)("option",{value:"exclude",children:"Exclude matching paths"},void 0,!1),(0,a.jsxDEV)("option",{value:"include",children:"Include only matching paths"},void 0,!1)]},void 0,!0)]},void 0,!0),(0,a.jsxDEV)("div",{style:{marginBottom:10},children:[(0,a.jsxDEV)("div",{style:{...o.label,width:"auto",marginBottom:6},children:"Path patterns (one per line, glob supported)"},void 0,!1),(0,a.jsxDEV)("textarea",{style:o.textarea,value:B,onChange:e=>T(e.target.value),placeholder:"navigation.position\nnavigation.*\nnotifications.*"},void 0,!1),(0,a.jsxDEV)("div",{style:o.hint,children:["exclude"===R?"These paths are NOT recorded; everything else is.":"ONLY these paths are recorded; everything else is dropped."," ","Leave empty to record everything."]},void 0,!0)]},void 0,!0)]},void 0,!0),(0,a.jsxDEV)(s,{title:"Compression (on-disk)",children:[(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"Compression codec"},void 0,!1),(0,a.jsxDEV)("select",{style:o.select,value:I,onChange:e=>z(e.target.value),children:[(0,a.jsxDEV)("option",{value:"none",children:"None"},void 0,!1),(0,a.jsxDEV)("option",{value:"lz4",children:"LZ4 (fast)"},void 0,!1),(0,a.jsxDEV)("option",{value:"zstd",children:"ZSTD (smaller)"},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("span",{style:o.hint,children:"applies to new data after save"},void 0,!1)]},void 0,!0),"zstd"===I&&(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"ZSTD level"},void 0,!1),(0,a.jsxDEV)("input",{style:o.inputSmall,type:"number",min:"1",max:"22",value:L,onChange:e=>P(Number(e.target.value))},void 0,!1),(0,a.jsxDEV)("span",{style:o.hint,children:"1 (fast) to 22 (smallest)"},void 0,!1)]},void 0,!0)]},void 0,!0),(0,a.jsxDEV)(s,{title:"InfluxDB Migration",children:[(0,a.jsxDEV)("div",{style:{display:"flex",gap:10,alignItems:"center",marginBottom:10},children:[(0,a.jsxDEV)("button",{style:{...o.btn,...o.btnPrimary,...Z?o.btnDisabled:{}},onClick:async()=>{X(!0),te("");try{const e=Y?`?url=${encodeURIComponent(Y)}`:"",t=await fetch(`/plugins/signalk-questdb/api/migration/detect${e}`);if(t.ok){const e=await t.json();K(e.sources),0===e.sources.length&&(te(Y?`No InfluxDB found at ${Y}.`:"No InfluxDB instances detected on localhost:8086."),ne(!1))}}catch(e){te("Detection failed: "+e.message),ne(!0)}X(!1)},disabled:Z,children:Z?"Detecting...":"Detect InfluxDB"},void 0,!1),(0,a.jsxDEV)("span",{style:o.hint,children:"Checks localhost:8086 for InfluxDB 1.x and 2.x"},void 0,!1)]},void 0,!0),_&&_.length>0&&(0,a.jsxDEV)("div",{children:_.map((e,t)=>(0,a.jsxDEV)("div",{style:o.migrationItem,children:[(0,a.jsxDEV)("div",{style:{...o.cardIcon,width:36,height:36,fontSize:16,background:"influxdb2"===e.type?"#020a47":"#22adf6",color:"#fff"},children:"influxdb2"===e.type?"2":"1"},void 0,!1),(0,a.jsxDEV)("div",{style:o.cardInfo,children:[(0,a.jsxDEV)("div",{style:o.cardTitle,children:["InfluxDB ","influxdb2"===e.type?"2.x":"1.x",(0,a.jsxDEV)("span",{style:{...o.tag,...o.tagLatest},children:e.status},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.cardMeta,children:[e.url," · v",e.version]},void 0,!0)]},void 0,!0)]},t,!0))},void 0,!1),(0,a.jsxDEV)("div",{style:{...o.fieldRow,marginTop:10},children:[(0,a.jsxDEV)("span",{style:o.label,children:"Manual InfluxDB URL"},void 0,!1),(0,a.jsxDEV)("input",{style:o.input,placeholder:"http://192.168.1.100:8086",value:Y,onChange:e=>J(e.target.value)},void 0,!1),(0,a.jsxDEV)("span",{style:o.hint,children:"for remote instances"},void 0,!1)]},void 0,!0),_&&0===_.length&&!Z&&(0,a.jsxDEV)("div",{style:{...o.empty,padding:"16px",textAlign:"left"},children:"No InfluxDB found on localhost. Use manual URL above for remote instances."},void 0,!1)]},void 0,!0),(0,a.jsxDEV)(s,{title:"Data Export",children:[(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"From"},void 0,!1),(0,a.jsxDEV)("input",{style:o.input,type:"datetime-local",value:ve,onChange:e=>ge(e.target.value)},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"To"},void 0,!1),(0,a.jsxDEV)("input",{style:o.input,type:"datetime-local",value:xe,onChange:e=>fe(e.target.value)},void 0,!1)]},void 0,!0),(0,a.jsxDEV)("div",{style:o.fieldRow,children:[(0,a.jsxDEV)("span",{style:o.label,children:"Format"},void 0,!1),(0,a.jsxDEV)("select",{style:o.select,value:ye,onChange:e=>be(e.target.value),children:[(0,a.jsxDEV)("option",{value:"parquet",children:"Parquet"},void 0,!1),(0,a.jsxDEV)("option",{value:"csv",children:"CSV"},void 0,!1)]},void 0,!0)]},void 0,!0),(0,a.jsxDEV)("button",{style:{...o.btn,...o.btnPrimary,...me?o.btnDisabled:{}},onClick:async()=>{if(!ve||!xe)return te("Set both from and to dates for export."),void ne(!0);De(!0),te(`Exporting ${ye.toUpperCase()}...`),ne(!1);try{const e=`/plugins/signalk-questdb/api/export?from=${encodeURIComponent(ve)}&to=${encodeURIComponent(xe)}&format=${ye}`,t=await fetch(e);if(!t.ok){const e=await t.json().catch(()=>({error:t.statusText}));return te(`Export failed: ${e.error}`),ne(!0),void De(!1)}const i=await t.blob(),n="parquet"===ye?"parquet":"csv",a=document.createElement("a");a.href=URL.createObjectURL(i),a.download=`signalk-export-${ve.slice(0,10)}.${n}`,a.click(),URL.revokeObjectURL(a.href),te(`Exported ${(i.size/1024/1024).toFixed(1)} MB`)}catch(e){te(`Export failed: ${e.message}`),ne(!0)}De(!1)},disabled:me,children:me?"Exporting...":"Export Data"},void 0,!1)]},void 0,!0),!1!==i.managedContainer&&(0,a.jsxDEV)(s,{title:"Danger zone",children:[(0,a.jsxDEV)("div",{style:{fontSize:13,color:"#555",marginBottom:10},children:"Remove the QuestDB container and permanently delete all recorded data. Use this to fully reset QuestDB — Signal K's plugin-uninstall cannot delete this data on rootless Podman. This cannot be undone."},void 0,!1),(0,a.jsxDEV)("button",{style:{...o.btn,...o.btnDanger,...ce?o.btnDisabled:{}},onClick:async()=>{if(window.confirm("Remove the QuestDB container and DELETE ALL recorded data? This cannot be undone.")){ue(!0),te("Removing QuestDB container and data..."),ne(!1);try{const e=await fetch("/plugins/signalk-questdb/api/purge-data",{method:"POST"}),t=await e.json().catch(()=>({error:e.statusText}));e.ok?(te(t.message),pe()):(te(`Remove failed: ${t.error}`),ne(!0))}catch(e){te(`Remove failed: ${e.message}`),ne(!0)}ue(!1)}},disabled:ce,children:ce?"Removing...":"Remove container & all data"},void 0,!1)]},void 0,!0),ee&&(0,a.jsxDEV)("div",{style:{...o.status,color:ie?"#ef4444":"#10b981",marginTop:16},children:ee},void 0,!1),(0,a.jsxDEV)("div",{style:{marginTop:24},children:(0,a.jsxDEV)("button",{style:{...o.btn,...o.btnSave},onClick:()=>{t({questdbHost:d,questdbIlpPort:c,questdbHttpPort:h,questdbPgPort:v,questdbVersion:x,managedContainer:y,defaultSamplingRate:V,recordSelf:m,recordOthers:E,retentionDays:S,compression:I,compressionLevel:L,networkName:q,exposeToContainers:$,pathFilter:{mode:R,paths:B.split("\n").map(e=>e.trim()).filter(Boolean)},samplingRates:i.samplingRates||{}}),te("Saved! Plugin will restart with new configuration."),ne(!1)},children:"Save Configuration"},void 0,!1)},void 0,!1)]},void 0,!0)}},772(e,t){var i=Symbol.for("react.fragment");t.Fragment=i,t.jsxDEV=void 0},446(e,t,i){e.exports=i(772)}}]);
1
+ "use strict";(self.webpackChunksignalk_questdb=self.webpackChunksignalk_questdb||[]).push([[805],{805(e,t,a){a.r(t),a.d(t,{default:()=>i});var n=a(231);const l={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"},btnSave:{background:"#3b82f6",color:"#fff"},btnDanger:{background:"#ef4444",color:"#fff",padding:"6px 12px",fontSize:12},btnDisabled:{opacity:.5,cursor:"not-allowed"},status:{marginTop:8,fontSize:12,minHeight:18},warnBanner:{padding:"12px 16px",background:"#fef2f2",border:"1px solid #fecaca",borderRadius:10,marginBottom:12,fontSize:13,color:"#991b1b",lineHeight:1.5},warnBannerTitle:{fontWeight:700,marginBottom:4},infoBanner:{padding:"12px 16px",background:"#fffbeb",border:"1px solid #fde68a",borderRadius:10,marginBottom:12,fontSize:13,color:"#92400e",lineHeight:1.5},warnBannerCode:{display:"block",marginTop:8,padding:"8px 10px",background:"#fff",border:"1px solid #fecaca",borderRadius:6,fontFamily:"monospace",fontSize:12,color:"#7f1d1d",whiteSpace:"pre-wrap",wordBreak:"break-word"},card:{display:"flex",alignItems:"center",gap:14,padding:"14px 18px",background:"#f8f9fa",border:"1px solid #e0e0e0",borderRadius:10,marginBottom:12},cardIcon:{width:44,height:44,borderRadius:10,display:"flex",alignItems:"center",justifyContent:"center",fontSize:20,fontWeight:700,flexShrink:0},cardInfo:{flex:1},cardTitle:{fontSize:15,fontWeight:600,color:"#333"},cardMeta:{fontSize:12,color:"#888"},stateIndicator:{width:10,height:10,borderRadius:"50%",flexShrink:0},fieldRow:{display:"flex",alignItems:"center",gap:12,marginBottom:10},label:{fontSize:13,fontWeight:500,color:"#555",width:180,flexShrink:0},select:{padding:"6px 10px",borderRadius:6,border:"1px solid #ccc",fontSize:13,background:"#fff",color:"#333",minWidth:200},input:{padding:"6px 10px",borderRadius:6,border:"1px solid #ccc",fontSize:13,background:"#fff",color:"#333",width:200},inputSmall:{padding:"6px 10px",borderRadius:6,border:"1px solid #ccc",fontSize:13,background:"#fff",color:"#333",width:80},checkbox:{width:16,height:16,accentColor:"#3b82f6"},hint:{fontSize:11,color:"#aaa",marginLeft:8},textarea:{padding:"6px 10px",borderRadius:6,border:"1px solid #ccc",fontSize:13,fontFamily:"monospace",background:"#fff",color:"#333",width:"100%",minHeight:70,boxSizing:"border-box",resize:"vertical"},empty:{textAlign:"center",padding:"30px 16px",color:"#999",fontSize:13},tag:{display:"inline-block",padding:"2px 8px",borderRadius:4,fontSize:10,fontWeight:600,marginLeft:8},tagPre:{background:"#fef3c7",color:"#92400e"},tagLatest:{background:"#dcfce7",color:"#166534"},migrationItem:{display:"flex",alignItems:"center",gap:12,padding:"12px 16px",background:"#f8f9fa",border:"1px solid #e0e0e0",borderRadius:10,marginBottom:8},migrationActions:{display:"flex",gap:8,alignItems:"center"},statsGrid:{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(140px, 1fr))",gap:10,marginBottom:12},statCard:{padding:"12px 16px",background:"#f8f9fa",border:"1px solid #e0e0e0",borderRadius:10,textAlign:"center"},statValue:{fontSize:22,fontWeight:700,color:"#333"},statLabel:{fontSize:11,color:"#888",marginTop:2}};function r({title:e,children:t}){const[a,r]=(0,n.useState)(!1);return n.createElement("div",null,n.createElement("button",{type:"button","aria-expanded":a,style:{...l.sectionTitle,cursor:"pointer",userSelect:"none",display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",background:"none",border:"none",padding:0},onClick:()=>r(!a)},n.createElement("span",{style:{fontSize:10,transition:"transform 0.15s",transform:a?"rotate(90deg)":"rotate(0deg)"}},"▶"),e),a&&n.createElement("div",{style:{marginBottom:16}},t))}function o(e){return null==e?"—":e>=1e6?(e/1e6).toFixed(1)+"M":e>=1e3?(e/1e3).toFixed(1)+"K":String(e)}function i({configuration:e,save:t}){const a=e||{},[i,s]=(0,n.useState)(a.questdbHost||"127.0.0.1"),[c,d]=(0,n.useState)(a.questdbIlpPort||9009),[u,m]=(0,n.useState)(a.questdbHttpPort||9e3),[p,g]=(0,n.useState)(a.questdbPgPort||8812),[f,y]=(0,n.useState)(a.questdbVersion||"latest"),[b,h]=(0,n.useState)(!1!==a.managedContainer),[E,v]=(0,n.useState)(!1!==a.recordSelf),[x,w]=(0,n.useState)(a.recordOthers||!1),[S,k]=(0,n.useState)(a.defaultSamplingRate??2e3),[R,C]=(0,n.useState)(a.retentionDays||0),[B,T]=(0,n.useState)("include"===a.pathFilter?.mode?"include":"exclude"),[D,I]=(0,n.useState)((Array.isArray(a.pathFilter?.paths)?a.pathFilter.paths:[]).join("\n")),[z,L]=(0,n.useState)(a.compression||"lz4"),[P,q]=(0,n.useState)(a.compressionLevel||3),[Q,$]=(0,n.useState)(a.networkName||"sk-network"),[F,N]=(0,n.useState)(a.exposeToContainers||!1),[A,U]=(0,n.useState)([]),[M,j]=(0,n.useState)(!1),[W,V]=(0,n.useState)(null),[H,O]=(0,n.useState)(!0),[G,_]=(0,n.useState)(null),[K,Z]=(0,n.useState)(!1),[X,Y]=(0,n.useState)(""),[J,ee]=(0,n.useState)(""),[te,ae]=(0,n.useState)(!1),[ne,le]=(0,n.useState)(null),[re,oe]=(0,n.useState)(!1),[ie,se]=(0,n.useState)(!1),[ce,de]=(0,n.useState)(!1),ue=(0,n.useCallback)(async()=>{j(!0);try{const e=await fetch("/plugins/signalk-questdb/api/versions");if(e.ok){const t=await e.json();U(t)}}catch{}j(!1)},[]),me=(0,n.useCallback)(async()=>{try{const e=await fetch("/plugins/signalk-questdb/api/status");e.ok?V(await e.json()):V({status:"not_running"})}catch{V({status:"not_running"})}O(!1)},[]);(0,n.useEffect)(()=>{ue(),me();const e=setInterval(me,5e3);return()=>clearInterval(e)},[ue,me]);const[pe,ge]=(0,n.useState)(""),[fe,ye]=(0,n.useState)(""),[be,he]=(0,n.useState)("parquet"),[Ee,ve]=(0,n.useState)(!1),xe=W&&"running"===W.status,we=xe&&W.suspendedTables||[],Se=we.length>0,ke=xe&&W.schemaMismatch||!1,Re=xe&&W.ulimitClamp||null,Ce=A.filter(e=>!e.prerelease).slice(0,3),Be=A.filter(e=>e.prerelease).slice(0,2);return n.createElement("div",{style:l.root},n.createElement("div",{style:l.sectionTitle},"QuestDB Status"),H?n.createElement("div",{style:l.empty},"Checking QuestDB..."):xe?n.createElement(n.Fragment,null,Se&&n.createElement("div",{style:l.warnBanner},n.createElement("div",{style:l.warnBannerTitle},"QuestDB WAL suspended — recording stalled"),"Rows are arriving but no longer commit, so the counts below have stopped advancing. This follows an error while QuestDB applied the write-ahead log — see the reason reported per table below. Resume each suspended table in the QuestDB SQL console (",n.createElement("code",null,":9000"),"), skipping the broken transaction:",n.createElement("code",{style:l.warnBannerCode},we.map(e=>{const t=`"${String(e.name).replace(/"/g,'""')}"`,a=String(e.errorMessage||e.errorTag||"reason not reported").replace(/[\r\n]+/g," ");return`ALTER TABLE ${t} RESUME WAL FROM TXN ${e.writerTxn+1}; -- ${o(e.txnLag)} txns behind; ${a}`}).join("\n"))),ke&&n.createElement("div",{style:l.warnBanner},n.createElement("div",{style:l.warnBannerTitle},"QuestDB table schema mismatch — data not readable"),"A QuestDB table was re-created by ILP ingestion with the wrong timestamp column, so rows are being stored but reads filtering on time (history, Grafana, and the counts above) return nothing. The plugin rebuilds the affected table with the correct schema automatically within a minute; if this persists, restart the plugin. (The few rows written into the wrong-schema table are lost — they were unreadable anyway.)"),Re&&n.createElement("div",{style:l.infoBanner},n.createElement("div",{style:l.warnBannerTitle},"QuestDB open-files limit capped by the host"),"QuestDB requested a ",n.createElement("code",null,Re.ulimit)," limit of"," ",o(Re.requested),", but this host only allows"," ",o(Re.granted),", so it was capped. QuestDB is running on the lower limit. To grant the full value, raise the host limit for the user running the container runtime — under rootless Podman that means a systemd ",n.createElement("code",null,"user@.service")," ",n.createElement("code",null,"LimitNOFILE")," drop-in (editing"," ",n.createElement("code",null,"/etc/security/limits.conf")," alone is usually not enough) — then restart the QuestDB container."," ",n.createElement("a",{href:"https://github.com/dirkwa/signalk-container#raising-the-open-files-limit-nofile",target:"_blank",rel:"noreferrer",style:{color:"#92400e",textDecoration:"underline"}},"Step-by-step instructions"),"."),n.createElement("div",{style:l.card},n.createElement("div",{style:{...l.cardIcon,background:"#7c3aed",color:"#fff"}},"Q"),n.createElement("div",{style:l.cardInfo},n.createElement("div",{style:l.cardTitle},"QuestDB"),n.createElement("div",{style:l.cardMeta},W?.endpoint||`${i}:${u}`," ","· ",Se?"WAL suspended":"Recording")),n.createElement("div",{style:{...l.stateIndicator,background:Se?"#ef4444":"#10b981"},title:Se?"WAL suspended":"Running"})),n.createElement("div",{style:l.statsGrid},n.createElement("div",{style:l.statCard},n.createElement("div",{style:l.statValue},o(W.totalRows)),n.createElement("div",{style:l.statLabel},"Total Rows")),n.createElement("div",{style:l.statCard},n.createElement("div",{style:l.statValue},o(W.activePathsToday)),n.createElement("div",{style:l.statLabel},"Active Paths Today"))),n.createElement("div",{style:{display:"flex",alignItems:"center",gap:10,marginBottom:12}},ne&&ne.updateAvailable?n.createElement(n.Fragment,null,n.createElement("span",{style:{fontSize:13}},"v",ne.currentVersion," →"," ",n.createElement("strong",null,"v",ne.latestVersion)," available"),n.createElement("button",{style:{...l.btn,...l.btnPrimary,padding:"4px 12px",fontSize:12,...ie?l.btnDisabled:{}},onClick:async()=>{se(!0),ee("Pulling new image and restarting..."),ae(!1);try{const e=await fetch("/plugins/signalk-questdb/api/update/apply",{method:"POST"});if(e.ok){const t=await e.json();ee(t.message),le(null),t.newVersion&&y(t.newVersion),me()}else{const t=await e.json().catch(()=>({error:e.statusText}));ee(`Update failed: ${t.error}`),ae(!0)}}catch(e){ee(`Update failed: ${e.message}`),ae(!0)}se(!1)},disabled:ie},ie?"Updating...":"Update QuestDB")):ne&&!ne.updateAvailable?n.createElement("span",{style:{fontSize:12,color:"#888"}},"v",ne.currentVersion," (up to date)"):n.createElement("button",{style:{...l.btn,padding:"4px 12px",fontSize:12,background:"#f1f5f9",color:"#475569",border:"1px solid #e2e8f0",...re?l.btnDisabled:{}},onClick:async()=>{oe(!0);try{const e=await fetch("/plugins/signalk-questdb/api/update/check");e.ok&&le(await e.json())}catch{}oe(!1)},disabled:re},re?"Checking...":"Check for updates"))):n.createElement("div",{style:l.card},n.createElement("div",{style:{...l.cardIcon,background:"#fef2f2",color:"#ef4444"}},"Q"),n.createElement("div",{style:l.cardInfo},n.createElement("div",{style:l.cardTitle},"QuestDB"),n.createElement("div",{style:l.cardMeta},"unhealthy"===W?.status?"Not responding":"Not running",b?" — enable plugin to start container":"")),n.createElement("div",{style:{...l.stateIndicator,background:"#ef4444"}})),n.createElement("div",{style:l.sectionTitle},"Image Version"),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"QuestDB version"),n.createElement("select",{style:l.select,value:f,onChange:e=>y(e.target.value)},n.createElement("option",{value:"latest"},"latest (recommended)"),Be.map(e=>n.createElement("option",{key:e.tag,value:e.tag},e.tag," (pre-release)")),Ce.map((e,t)=>n.createElement("option",{key:e.tag,value:e.tag},e.tag,0===t?" (current stable)":""))),M&&n.createElement("span",{style:l.hint},"loading releases..."),n.createElement("button",{style:{...l.btn,...l.btnPrimary,padding:"4px 10px",fontSize:11},onClick:ue},"↻")),n.createElement("div",{style:l.sectionTitle},"Connection"),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Managed container"),n.createElement("input",{type:"checkbox",style:l.checkbox,checked:b,onChange:e=>h(e.target.checked)}),n.createElement("span",{style:l.hint},b?"signalk-container manages QuestDB":"Connect to external QuestDB")),!b&&n.createElement(n.Fragment,null,n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"QuestDB host"),n.createElement("input",{style:l.input,value:i,onChange:e=>s(e.target.value)})),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"HTTP port (queries)"),n.createElement("input",{style:l.inputSmall,type:"number",value:u,onChange:e=>m(Number(e.target.value))})),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"ILP port (writes)"),n.createElement("input",{style:l.inputSmall,type:"number",value:c,onChange:e=>d(Number(e.target.value))}))),b&&n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Bind to 0.0.0.0"),n.createElement("input",{type:"checkbox",style:l.checkbox,checked:F,onChange:e=>N(e.target.checked)}),n.createElement("span",{style:{...l.hint,color:F?"#ef4444":void 0}},F?"Caution! This exposes your data to the network":"Signal K reaches QuestDB automatically — only enable to reach it from another machine or a separate-Docker Grafana")),b&&F&&n.createElement(n.Fragment,null,n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"HTTP port (queries)"),n.createElement("input",{style:l.inputSmall,type:"number",value:u,onChange:e=>m(Number(e.target.value))})),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"ILP port (writes)"),n.createElement("input",{style:l.inputSmall,type:"number",value:c,onChange:e=>d(Number(e.target.value))})),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"PostgreSQL wire port"),n.createElement("input",{style:l.inputSmall,type:"number",value:p,onChange:e=>g(Number(e.target.value))}),n.createElement("span",{style:l.hint},"for Grafana")),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Container network"),n.createElement("input",{style:l.input,value:Q,onChange:e=>$(e.target.value)}),n.createElement("span",{style:l.hint},"shared with signalk-grafana"))),n.createElement("div",{style:l.sectionTitle},"Recording"),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Default sampling rate (ms)"),n.createElement("input",{style:l.inputSmall,type:"number",value:S,onChange:e=>k(Number(e.target.value))}),n.createElement("span",{style:l.hint},"1000 = max 1 write/sec per path (0 = every update)")),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Record own vessel"),n.createElement("input",{type:"checkbox",style:l.checkbox,checked:E,onChange:e=>v(e.target.checked)})),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Record AIS targets"),n.createElement("input",{type:"checkbox",style:l.checkbox,checked:x,onChange:e=>w(e.target.checked)})),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Retention (days)"),n.createElement("input",{style:l.inputSmall,type:"number",value:R,onChange:e=>C(Number(e.target.value))}),n.createElement("span",{style:l.hint},"0 = keep forever")),n.createElement(r,{title:"Path filtering"},n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Filter mode"),n.createElement("select",{style:l.select,value:B,onChange:e=>T(e.target.value)},n.createElement("option",{value:"exclude"},"Exclude matching paths"),n.createElement("option",{value:"include"},"Include only matching paths"))),n.createElement("div",{style:{marginBottom:10}},n.createElement("div",{style:{...l.label,width:"auto",marginBottom:6}},"Path patterns (one per line, glob supported)"),n.createElement("textarea",{style:l.textarea,value:D,onChange:e=>I(e.target.value),placeholder:"navigation.position\nnavigation.*\nnotifications.*"}),n.createElement("div",{style:l.hint},"exclude"===B?"These paths are NOT recorded; everything else is.":"ONLY these paths are recorded; everything else is dropped."," ","Leave empty to record everything."))),n.createElement(r,{title:"Compression (on-disk)"},n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Compression codec"),n.createElement("select",{style:l.select,value:z,onChange:e=>L(e.target.value)},n.createElement("option",{value:"none"},"None"),n.createElement("option",{value:"lz4"},"LZ4 (fast)"),n.createElement("option",{value:"zstd"},"ZSTD (smaller)")),n.createElement("span",{style:l.hint},"applies to new data after save")),"zstd"===z&&n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"ZSTD level"),n.createElement("input",{style:l.inputSmall,type:"number",min:"1",max:"22",value:P,onChange:e=>q(Number(e.target.value))}),n.createElement("span",{style:l.hint},"1 (fast) to 22 (smallest)"))),n.createElement(r,{title:"InfluxDB Migration"},n.createElement("div",{style:{display:"flex",gap:10,alignItems:"center",marginBottom:10}},n.createElement("button",{style:{...l.btn,...l.btnPrimary,...K?l.btnDisabled:{}},onClick:async()=>{Z(!0),ee("");try{const e=X?`?url=${encodeURIComponent(X)}`:"",t=await fetch(`/plugins/signalk-questdb/api/migration/detect${e}`);if(t.ok){const e=await t.json();_(e.sources),0===e.sources.length&&(ee(X?`No InfluxDB found at ${X}.`:"No InfluxDB instances detected on localhost:8086."),ae(!1))}}catch(e){ee("Detection failed: "+e.message),ae(!0)}Z(!1)},disabled:K},K?"Detecting...":"Detect InfluxDB"),n.createElement("span",{style:l.hint},"Checks localhost:8086 for InfluxDB 1.x and 2.x")),G&&G.length>0&&n.createElement("div",null,G.map((e,t)=>n.createElement("div",{key:t,style:l.migrationItem},n.createElement("div",{style:{...l.cardIcon,width:36,height:36,fontSize:16,background:"influxdb2"===e.type?"#020a47":"#22adf6",color:"#fff"}},"influxdb2"===e.type?"2":"1"),n.createElement("div",{style:l.cardInfo},n.createElement("div",{style:l.cardTitle},"InfluxDB ","influxdb2"===e.type?"2.x":"1.x",n.createElement("span",{style:{...l.tag,...l.tagLatest}},e.status)),n.createElement("div",{style:l.cardMeta},e.url," · v",e.version))))),n.createElement("div",{style:{...l.fieldRow,marginTop:10}},n.createElement("span",{style:l.label},"Manual InfluxDB URL"),n.createElement("input",{style:l.input,placeholder:"http://192.168.1.100:8086",value:X,onChange:e=>Y(e.target.value)}),n.createElement("span",{style:l.hint},"for remote instances")),G&&0===G.length&&!K&&n.createElement("div",{style:{...l.empty,padding:"16px",textAlign:"left"}},"No InfluxDB found on localhost. Use manual URL above for remote instances.")),n.createElement(r,{title:"Data Export"},n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"From"),n.createElement("input",{style:l.input,type:"datetime-local",value:pe,onChange:e=>ge(e.target.value)})),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"To"),n.createElement("input",{style:l.input,type:"datetime-local",value:fe,onChange:e=>ye(e.target.value)})),n.createElement("div",{style:l.fieldRow},n.createElement("span",{style:l.label},"Format"),n.createElement("select",{style:l.select,value:be,onChange:e=>he(e.target.value)},n.createElement("option",{value:"parquet"},"Parquet"),n.createElement("option",{value:"csv"},"CSV"))),n.createElement("button",{style:{...l.btn,...l.btnPrimary,...Ee?l.btnDisabled:{}},onClick:async()=>{if(!pe||!fe)return ee("Set both from and to dates for export."),void ae(!0);ve(!0),ee(`Exporting ${be.toUpperCase()}...`),ae(!1);try{const e=`/plugins/signalk-questdb/api/export?from=${encodeURIComponent(pe)}&to=${encodeURIComponent(fe)}&format=${be}`,t=await fetch(e);if(!t.ok){const e=await t.json().catch(()=>({error:t.statusText}));return ee(`Export failed: ${e.error}`),ae(!0),void ve(!1)}const a=await t.blob(),n="parquet"===be?"parquet":"csv",l=document.createElement("a");l.href=URL.createObjectURL(a),l.download=`signalk-export-${pe.slice(0,10)}.${n}`,l.click(),URL.revokeObjectURL(l.href),ee(`Exported ${(a.size/1024/1024).toFixed(1)} MB`)}catch(e){ee(`Export failed: ${e.message}`),ae(!0)}ve(!1)},disabled:Ee},Ee?"Exporting...":"Export Data")),!1!==a.managedContainer&&n.createElement(r,{title:"Danger zone"},n.createElement("div",{style:{fontSize:13,color:"#555",marginBottom:10}},"Remove the QuestDB container and permanently delete all recorded data. Use this to fully reset QuestDB — Signal K's plugin-uninstall cannot delete this data on rootless Podman. This cannot be undone."),n.createElement("button",{style:{...l.btn,...l.btnDanger,...ce?l.btnDisabled:{}},onClick:async()=>{if(window.confirm("Remove the QuestDB container and DELETE ALL recorded data? This cannot be undone.")){de(!0),ee("Removing QuestDB container and data..."),ae(!1);try{const e=await fetch("/plugins/signalk-questdb/api/purge-data",{method:"POST"}),t=await e.json().catch(()=>({error:e.statusText}));e.ok?(ee(t.message),me()):(ee(`Remove failed: ${t.error}`),ae(!0))}catch(e){ee(`Remove failed: ${e.message}`),ae(!0)}de(!1)}},disabled:ce},ce?"Removing...":"Remove container & all data")),J&&n.createElement("div",{style:{...l.status,color:te?"#ef4444":"#10b981",marginTop:16}},J),n.createElement("div",{style:{marginTop:24}},n.createElement("button",{style:{...l.btn,...l.btnSave},onClick:()=>{t({questdbHost:i,questdbIlpPort:c,questdbHttpPort:u,questdbPgPort:p,questdbVersion:f,managedContainer:b,defaultSamplingRate:S,recordSelf:E,recordOthers:x,retentionDays:R,compression:z,compressionLevel:P,networkName:Q,exposeToContainers:F,pathFilter:{mode:B,paths:D.split("\n").map(e=>e.trim()).filter(Boolean)},samplingRates:a.samplingRates||{}}),ee("Saved! Plugin will restart with new configuration."),ae(!1)}},"Save Configuration")))}}}]);
package/webpack.config.js CHANGED
@@ -15,7 +15,16 @@ module.exports = {
15
15
  test: /\.jsx?$/,
16
16
  loader: "babel-loader",
17
17
  exclude: /node_modules/,
18
- options: { presets: ["@babel/preset-react"] },
18
+ // runtime "classic" is load-bearing: Babel 8 changed preset-react's
19
+ // default to the "automatic" runtime, whose emitted code imports
20
+ // react/jsx-runtime. That module is not in the Module Federation
21
+ // `shared` scope below, so webpack bundles a private copy of React's
22
+ // jsx runtime into the remote — a second React instance in the host
23
+ // page — and the Admin UI's panel loader fails. Classic emits
24
+ // React.createElement against the shared singleton React.
25
+ options: {
26
+ presets: [["@babel/preset-react", { runtime: "classic" }]],
27
+ },
19
28
  },
20
29
  ],
21
30
  },
@@ -1,9 +0,0 @@
1
- /**
2
- * @license React
3
- * react-jsx-dev-runtime.production.js
4
- *
5
- * Copyright (c) Meta Platforms, Inc. and affiliates.
6
- *
7
- * This source code is licensed under the MIT license found in the
8
- * LICENSE file in the root directory of this source tree.
9
- */