@adcops/autocore-react 3.3.75 → 3.3.77

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.
Files changed (56) hide show
  1. package/dist/components/Indicator.d.ts +29 -52
  2. package/dist/components/Indicator.d.ts.map +1 -1
  3. package/dist/components/Indicator.js +1 -1
  4. package/dist/components/ams/AmsProvider.d.ts +7 -0
  5. package/dist/components/ams/AmsProvider.d.ts.map +1 -1
  6. package/dist/components/ams/AssetDetailView.d.ts.map +1 -1
  7. package/dist/components/ams/AssetDetailView.js +1 -1
  8. package/dist/components/ams/AssetRegistryTable.d.ts.map +1 -1
  9. package/dist/components/ams/AssetRegistryTable.js +1 -1
  10. package/dist/components/ams/CalibrationEntryDialog.d.ts.map +1 -1
  11. package/dist/components/ams/CalibrationEntryDialog.js +1 -1
  12. package/dist/components/ams/MissingAssetsBanner.d.ts +11 -0
  13. package/dist/components/ams/MissingAssetsBanner.d.ts.map +1 -0
  14. package/dist/components/ams/MissingAssetsBanner.js +1 -0
  15. package/dist/components/ams/PlaceholderHealthPanel.d.ts +3 -0
  16. package/dist/components/ams/PlaceholderHealthPanel.d.ts.map +1 -0
  17. package/dist/components/ams/PlaceholderHealthPanel.js +1 -0
  18. package/dist/components/ams/index.d.ts +2 -0
  19. package/dist/components/ams/index.d.ts.map +1 -1
  20. package/dist/components/ams/index.js +1 -1
  21. package/dist/components/index.d.ts +8 -0
  22. package/dist/components/index.d.ts.map +1 -1
  23. package/dist/components/index.js +1 -1
  24. package/dist/components/network/NetworkPanel.d.ts +8 -0
  25. package/dist/components/network/NetworkPanel.d.ts.map +1 -0
  26. package/dist/components/network/NetworkPanel.js +1 -0
  27. package/dist/components/network/NetworkProvider.d.ts +72 -0
  28. package/dist/components/network/NetworkProvider.d.ts.map +1 -0
  29. package/dist/components/network/NetworkProvider.js +1 -0
  30. package/dist/components/network/StagedChangeBanner.d.ts +8 -0
  31. package/dist/components/network/StagedChangeBanner.d.ts.map +1 -0
  32. package/dist/components/network/StagedChangeBanner.js +1 -0
  33. package/dist/components/network/index.d.ts +7 -0
  34. package/dist/components/network/index.d.ts.map +1 -0
  35. package/dist/components/network/index.js +1 -0
  36. package/dist/components/tis/ProjectManager.d.ts +7 -0
  37. package/dist/components/tis/ProjectManager.d.ts.map +1 -0
  38. package/dist/components/tis/ProjectManager.js +1 -0
  39. package/dist/components/tis/ResultHistoryTable.d.ts.map +1 -1
  40. package/dist/components/tis/ResultHistoryTable.js +1 -1
  41. package/package.json +1 -1
  42. package/src/components/Indicator.tsx +166 -162
  43. package/src/components/ams/AmsProvider.tsx +7 -0
  44. package/src/components/ams/AssetDetailView.tsx +287 -4
  45. package/src/components/ams/AssetRegistryTable.tsx +325 -21
  46. package/src/components/ams/CalibrationEntryDialog.tsx +163 -30
  47. package/src/components/ams/MissingAssetsBanner.tsx +124 -0
  48. package/src/components/ams/PlaceholderHealthPanel.tsx +188 -0
  49. package/src/components/ams/index.ts +2 -0
  50. package/src/components/index.ts +26 -0
  51. package/src/components/network/NetworkPanel.tsx +363 -0
  52. package/src/components/network/NetworkProvider.tsx +349 -0
  53. package/src/components/network/StagedChangeBanner.tsx +101 -0
  54. package/src/components/network/index.ts +17 -0
  55. package/src/components/tis/ProjectManager.tsx +392 -0
  56. package/src/components/tis/ResultHistoryTable.tsx +126 -188
@@ -0,0 +1 @@
1
+ import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import React,{useCallback,useContext,useEffect,useState}from"react";import{Button}from"primereact/button";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{ConfirmDialog,confirmDialog}from"primereact/confirmdialog";import{ProgressBar}from"primereact/progressbar";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";const formatBytes=e=>{if(!Number.isFinite(e)||e<=0)return"0 B";const t=["B","KiB","MiB","GiB","TiB","PiB"];let r=0,s=e;for(;s>=1024&&r<t.length-1;)s/=1024,r++;return`${s.toFixed(s>=100?0:s>=10?1:2)} ${t[r]}`},formatDate=e=>{if(!e)return"";const t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString()};export const ProjectManager=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,{invoke:s}=useContext(EventEmitterContext),[i,a]=useState([]),[o,n]=useState(!1),[l,c]=useState(null),[d,m]=useState(!1),[p,u]=useState(!1),[f,h]=useState(null),[g,_]=useState(""),y=useCallback(async()=>{if(r){n(!0);try{const e=await s("tis.list_tests",MessageType.Request,{project_id:r});e?.success&&e.data?.tests?a(e.data.tests):a([])}catch(e){a([])}finally{n(!1)}}else a([])},[r,s]),j=useCallback(async()=>{try{const e=await s("tis.disk_usage",MessageType.Request,{});e?.success&&e.data?(h(e.data),_("")):(h(null),_(e?.error_message??"disk_usage failed"))}catch(e){h(null),_(e instanceof Error?e.message:String(e))}},[s]);useEffect(()=>{y()},[y,t.state.activeRunId]),useEffect(()=>{j()},[j]);const b=e=>{const i=e?.run_id??"",a=e?.sample_id??"";confirmDialog({header:"Delete test",icon:"pi pi-exclamation-triangle",acceptLabel:"Delete",rejectLabel:"Cancel",acceptClassName:"p-button-danger",message:_jsxs("div",{children:[_jsxs("p",{style:{marginTop:0},children:["Permanently delete run ",_jsx("code",{children:i}),a?_jsxs(_Fragment,{children:[" (sample ",_jsx("code",{children:a}),")"]}):null,"?"]}),_jsxs("p",{style:{marginBottom:0,fontSize:"0.875rem",color:"#9ca3af"},children:["Removes ",_jsx("code",{children:"test.json"}),", cycles, raw and filtered data for this run. This action cannot be undone."]})]}),accept:()=>{(async e=>{const i=e?.run_id,a=e?.method_id;if(r&&a&&i){c(i);try{const e=await s("tis.delete_test",MessageType.Request,{project_id:r,method_id:a,run_id:i});if(!e?.success)return void alert(`Failed to delete test ${i}`+(e?.error_message?`: ${e.error_message}`:""));await y(),t.selection.runId===i&&t.setSelection({runId:null})}catch(e){alert(`Delete failed: ${e instanceof Error?e.message:String(e)}`)}finally{c(null)}}})(e)}})},x=e=>{const t="string"==typeof e?.sample_id?e.sample_id:"";if(t)return t;const r=e?.config;return r&&"object"==typeof r&&"string"==typeof r.sample_id?r.sample_id:""},v=f&&f.total_bytes>0?Math.min(100,Math.round(f.used_bytes/f.total_bytes*100)):0;return _jsxs("div",{style:{width:"100%",maxWidth:"100%",boxSizing:"border-box"},children:[_jsx(ConfirmDialog,{}),_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem",gap:"0.5rem",flexWrap:"wrap"},children:[_jsx("h3",{style:{margin:0},children:r?`Project Manager: ${r}`:"Project Manager (no project selected)"}),_jsxs("div",{style:{display:"flex",gap:"0.4rem",alignItems:"center",flexWrap:"wrap"},children:[_jsx(Button,{icon:p?"pi pi-spin pi-spinner":"pi pi-box",label:"Download Archive",size:"small",outlined:!0,disabled:!r||p||d,onClick:async()=>{if(r){u(!0);try{const e=await s("tis.export_project_zip",MessageType.Request,{project_id:r});if(!e?.success)return void alert("Failed to build project archive"+(e?.error_message?`: ${e.error_message}`:""));const t="string"==typeof e.data?.download_url?e.data.download_url:"";if(!t)return void alert("Server did not return a download URL for the archive.");const i=document.createElement("a");i.href=t,i.download="string"==typeof e.data?.filename?e.data.filename:`${r}_project_archive.zip`,document.body.appendChild(i),i.click(),i.remove()}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{u(!1)}}},tooltip:"Download a ZIP of the entire project directory",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:d?"pi pi-spin pi-spinner":"pi pi-trash",label:"Delete Project",size:"small",severity:"danger",disabled:!r||d||p,onClick:()=>{if(!r)return;const e=i.length;confirmDialog({header:"Delete project",icon:"pi pi-exclamation-triangle",acceptLabel:"Delete Project",rejectLabel:"Cancel",acceptClassName:"p-button-danger",message:_jsxs("div",{children:[_jsxs("p",{style:{marginTop:0},children:["Permanently delete project ",_jsx("code",{children:r})," and all ",_jsx("strong",{children:e})," test",1===e?"":"s"," inside it?"]}),_jsx("p",{style:{marginBottom:0,fontSize:"0.875rem",color:"#9ca3af"},children:"This removes every method, run, cycle, and raw/filtered blob under the project. Consider downloading the archive first. This action cannot be undone."})]}),accept:()=>{(async()=>{if(r){m(!0);try{const e=await s("tis.delete_project",MessageType.Request,{project_id:r});if(!e?.success)return void alert(`Failed to delete project ${r}`+(e?.error_message?`: ${e.error_message}`:""));t.setSelection({projectId:null,methodId:null,sampleId:null,runId:null}),await t.refreshProjects(),await j(),a([])}catch(e){alert(`Delete failed: ${e instanceof Error?e.message:String(e)}`)}finally{m(!1)}}})()}})},tooltip:"Permanently delete the project and every test inside it",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",size:"small",onClick:()=>{y(),j()},disabled:o})]})]}),_jsxs("div",{style:{marginBottom:"1rem",padding:"0.75rem 1rem",border:"1px solid #2a2a2a",borderRadius:4,background:"#161616"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",flexWrap:"wrap",gap:"0.5rem"},children:[_jsx("strong",{children:"Server Disk Space"}),f?_jsxs("span",{style:{fontSize:"0.875rem",color:"#9ca3af"},children:[formatBytes(f.available_bytes)," free of ",formatBytes(f.total_bytes)," ","(",100-v,"% available)"]}):_jsx("span",g?{style:{fontSize:"0.875rem",color:"#f87171"},children:g}:{style:{fontSize:"0.875rem",color:"#9ca3af"},children:"Loading…"})]}),f&&_jsxs(_Fragment,{children:[_jsx(ProgressBar,{value:v,showValue:!1,style:{height:"0.5rem",marginTop:"0.5rem"},color:v>=90?"#dc2626":v>=75?"#f59e0b":void 0}),_jsx("div",{style:{fontSize:"0.75rem",color:"#6b7280",marginTop:"0.4rem"},children:_jsx("code",{children:f.base_directory})})]})]}),_jsxs(DataTable,{value:i,loading:o,paginator:!0,rows:10,emptyMessage:r?"No tests in this project.":"Select a project to manage.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},children:[_jsx(Column,{header:"Sample ID",body:x,sortable:!0,sortFunction:e=>{const t=[...e.data];return t.sort((t,r)=>x(t).localeCompare(x(r))*(e.order??1)),t},style:{minWidth:"8rem"}}),_jsx(Column,{field:"start_time",header:"Date/Time",sortable:!0,body:e=>formatDate(e.start_time),style:{minWidth:"12rem"}}),_jsx(Column,{field:"method_id",header:"Test Method",sortable:!0,style:{minWidth:"10rem"}}),_jsx(Column,{field:"run_id",header:"Run ID",sortable:!0,style:{minWidth:"12rem"}}),_jsx(Column,{header:"Action",style:{width:"8rem"},body:e=>{const t=l===e.run_id;return _jsx(Button,{icon:t?"pi pi-spin pi-spinner":"pi pi-trash",label:"Delete",size:"small",severity:"danger",outlined:!0,disabled:null!==l||d,onClick:()=>b(e),tooltip:"Permanently delete this test",tooltipOptions:{position:"left"}})}})]})]})};
@@ -1 +1 @@
1
- {"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAQ/D,MAAM,WAAW,uBAAuB;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAkHD,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CA4PhE,CAAC"}
1
+ {"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../../src/components/tis/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAQ/D,MAAM,WAAW,uBAAuB;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAwBD,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAwRhE,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";const unwrapEnvelope=e=>e&&"object"==typeof e?"data"in e&&e.data&&"object"==typeof e.data&&Object.values(e.data).some(e=>Array.isArray(e))?e.data:e:{},escapeCsv=e=>{if(null==e)return"";const t=String(e);return/[",\n\r]/.test(t)?`"${t.replace(/"/g,'""')}"`:t},unifyColumns=e=>{const t=new Set,r=[];for(const s of e)for(const[e,o]of Object.entries(s))Array.isArray(o)&&!t.has(e)&&(t.add(e),r.push(e));return r.sort((e,t)=>"t"===e?-1:"t"===t?1:0),r},cyclesToCsv=e=>{if(0===e.length)return"";const t=e.map(e=>({cycleIndex:e.cycleIndex,blob:unwrapEnvelope(e.blob)})),r=unifyColumns(t.map(e=>e.blob));if(0===r.length)return"";const s=[["cycle_index",...r].join(",")];for(const{cycleIndex:e,blob:o}of t){const t=r.reduce((e,t)=>Array.isArray(o[t])?Math.min(e,o[t].length):e,1/0),a=Number.isFinite(t)?t:0;for(let t=0;t<a;t++){const a=[escapeCsv(e)];for(const e of r){const r=o[e];a.push(escapeCsv(Array.isArray(r)?r[t]:""))}s.push(a.join(","))}}return s.join("\n")},rawBlobToCsv=e=>{const t=unwrapEnvelope(e);return cyclesToCsv([{cycleIndex:0,blob:t}]).split("\n").map(e=>{const t=e.indexOf(",");return t>=0?e.slice(t+1):""}).join("\n")},downloadCsv=(e,t)=>{const r=new Blob([t],{type:"text/csv;charset=utf-8;"}),s=URL.createObjectURL(r),o=document.createElement("a");o.href=s,o.download=e,document.body.appendChild(o),o.click(),o.remove(),URL.revokeObjectURL(s)};export const ResultHistoryTable=e=>{const t=useTis(),r=e.projectId??t.selection.projectId,s=e.methodId,[o,a]=useState([]),[n,i]=useState(!1),[l,d]=useState(null),{invoke:c}=useContext(EventEmitterContext),u=async()=>{if(r){i(!0);try{const e={project_id:r};s&&(e.method_id=s);const t=await c("tis.list_tests",MessageType.Request,e);t.success&&t.data&&t.data.tests&&a(t.data.tests)}catch(e){}i(!1)}else a([])};useEffect(()=>{u()},[r,s,t.state.activeRunId]);const m=async(e,t)=>{const o=e?.run_id,a=e?.method_id??s;if(!o||!a)return;const n="raw"===t?"tis.read_raw":"tis.read_filtered",i="raw"===t?"raw trace":"filtered trace",l="raw"===t?"raw":"filtered";d({runId:o,kind:t});try{let s="";if("raw"===t){const e=await c("tis.list_raw",MessageType.Request,{project_id:r,method_id:a,run_id:o}),t=(e?.data?.cycles??[]).filter(e=>"trace"===e?.name&&"number"==typeof e?.cycle_index).map(e=>e.cycle_index).sort((e,t)=>e-t);if(0===t.length){const e=await c(n,MessageType.Request,{project_id:r,method_id:a,run_id:o,name:"trace"});if(!e?.success||!e.data)return void alert(`No ${i} available for ${o}`+(e?.error_message?`: ${e.error_message}`:""));s=rawBlobToCsv(e.data)}else{const e=(await Promise.all(t.map(async e=>{const t=await c("tis.read_raw",MessageType.Request,{project_id:r,method_id:a,run_id:o,name:"trace",cycle_index:e});return t?.success?{cycleIndex:e,blob:t.data}:null}))).filter(e=>null!==e);if(0===e.length)return void alert(`No ${i} cycles readable for ${o}.`);s=cyclesToCsv(e)}}else{const e=await c(n,MessageType.Request,{project_id:r,method_id:a,run_id:o,name:"trace"});if(!e?.success||!e.data)return void alert(`No ${i} available for ${o}`+(e?.error_message?`: ${e.error_message}`:""));s=rawBlobToCsv(e.data)}if(!s)return void alert(`${i} for ${o} is empty or has no array columns.`);const d=f(p(e));downloadCsv(`${r}_${a}_${d?`${d}_`:""}${o}_${l}.csv`,s)}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{d(null)}},p=e=>{const t="string"==typeof e?.sample_id?e.sample_id:"";if(t)return t;const r=e?.config;return r&&"object"==typeof r&&"string"==typeof r.sample_id?r.sample_id:""},f=e=>e.replace(/[\/\\:*?"<>|\0\x00-\x1f]/g,"_");return _jsxs("div",{style:{width:"100%",maxWidth:"100%",overflow:"hidden",boxSizing:"border-box"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem"},children:[_jsx("h3",{style:{margin:0},children:r?`Test History: ${r}${s?` / ${s}`:""}`:"Test History (no project selected)"}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",onClick:u,disabled:n})]}),_jsxs(DataTable,{value:o,loading:n,paginator:!0,rows:10,emptyMessage:"No tests found.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},selectionMode:"single",onSelectionChange:e=>{const r=e.value;r?.run_id&&t.setSelection({projectId:r.project_id??null,methodId:r.method_id??null,runId:r.run_id})},children:[_jsx(Column,{header:"Sample ID",sortable:!0,body:p,sortFunction:e=>{const t=[...e.data];return t.sort((t,r)=>p(t).localeCompare(p(r))*(e.order??1)),t},style:{minWidth:"8rem"}}),_jsx(Column,{field:"start_time",header:"Date/Time",sortable:!0,body:e=>{return(t=e.start_time)?new Date(t).toLocaleString():"";var t},style:{minWidth:"12rem"}}),_jsx(Column,{field:"method_id",header:"Test Method",sortable:!0,style:{minWidth:"10rem"}}),_jsx(Column,{field:"run_id",header:"Run ID",sortable:!0,style:{minWidth:"12rem"}}),_jsx(Column,{header:"Download",style:{width:"14rem"},body:e=>{const t=l?.runId===e.run_id&&"raw"===l?.kind,r=l?.runId===e.run_id&&"filtered"===l?.kind,s=null!==l;return _jsxs("div",{style:{display:"flex",gap:"0.4rem"},children:[_jsx(Button,{icon:t?"pi pi-spin pi-spinner":"pi pi-download",label:"Raw",size:"small",outlined:!0,disabled:s,onClick:()=>m(e,"raw"),tooltip:"Download raw_data/trace.json as CSV",tooltipOptions:{position:"left"}}),_jsx(Button,{icon:r?"pi pi-spin pi-spinner":"pi pi-download",label:"Filtered",size:"small",outlined:!0,disabled:s,onClick:()=>m(e,"filtered"),tooltip:"Download filtered_data/trace.json as CSV",tooltipOptions:{position:"left"}})]})}})]})]})};
1
+ import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext}from"react";import{DataTable}from"primereact/datatable";import{Column}from"primereact/column";import{Button}from"primereact/button";import{EventEmitterContext}from"../../core/EventEmitterContext";import{MessageType}from"../../hub/CommandMessage";import{useTis}from"./TisProvider";const downloadCsvBlob=(e,t)=>{const o=new Blob([t],{type:"text/csv;charset=utf-8;"}),r=URL.createObjectURL(o),i=document.createElement("a");i.href=r,i.download=e,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(r)};export const ResultHistoryTable=e=>{const t=useTis(),o=e.projectId??t.selection.projectId,r=e.methodId,[i,s]=useState([]),[a,n]=useState(!1),[l,d]=useState(null),[c,p]=useState(null),{invoke:m}=useContext(EventEmitterContext),u=async()=>{if(o){n(!0);try{const e={project_id:o};r&&(e.method_id=r);const t=await m("tis.list_tests",MessageType.Request,e);t.success&&t.data&&t.data.tests&&s(t.data.tests)}catch(e){}n(!1)}else s([])};useEffect(()=>{u()},[o,r,t.state.activeRunId]);const f=async(e,t)=>{const i=e?.run_id,s=e?.method_id??r;if(!i||!s||!o)return;const a="report"===t?"tis.export_test_csv":"tis.export_test_data_csv",n="report"===t?"test report":"test data";d({runId:i,kind:t});try{const e=await m(a,MessageType.Request,{project_id:o,method_id:s,run_id:i});if(!e?.success)return void alert(`Failed to build ${n} for ${i}`+(e?.error_message?`: ${e.error_message}`:""));const r="string"==typeof e.data?.csv?e.data.csv:"";if(!r)return void alert(`${n} for ${i} is empty.`);const l="string"==typeof e.data?.filename&&e.data.filename?e.data.filename:`${o}_${s}_${i}_${t}.csv`;downloadCsvBlob(l,r)}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{d(null)}},_=async e=>{if(!o)return;const t="report"===e?"tis.export_project_csv":"tis.export_project_zip",r="report"===e?"project report":"project archive";p(e);try{const i=await m(t,MessageType.Request,{project_id:o});if(!i?.success)return void alert(`Failed to build ${r}`+(i?.error_message?`: ${i.error_message}`:""));if("report"===e){const e="string"==typeof i.data?.csv?i.data.csv:"";if(!e)return void alert(`Project ${o} has no tests to report.`);const t="string"==typeof i.data?.filename&&i.data.filename?i.data.filename:`${o}_project_report.csv`;downloadCsvBlob(t,e)}else{const e="string"==typeof i.data?.download_url?i.data.download_url:"";if(!e)return void alert(`Server did not return a download URL for ${r}.`);const t=document.createElement("a");t.href=e,t.download="string"==typeof i.data?.filename?i.data.filename:`${o}_project_archive.zip`,document.body.appendChild(t),t.click(),t.remove()}}catch(e){alert(`Download failed: ${e instanceof Error?e.message:String(e)}`)}finally{p(null)}},h=e=>{const t="string"==typeof e?.sample_id?e.sample_id:"";if(t)return t;const o=e?.config;return o&&"object"==typeof o&&"string"==typeof o.sample_id?o.sample_id:""};return _jsxs("div",{style:{width:"100%",maxWidth:"100%",overflow:"hidden",boxSizing:"border-box"},children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem",gap:"0.5rem",flexWrap:"wrap"},children:[_jsx("h3",{style:{margin:0},children:o?`Test History: ${o}${r?` / ${r}`:""}`:"Test History (no project selected)"}),_jsxs("div",{style:{display:"flex",gap:"0.4rem",alignItems:"center"},children:[_jsx(Button,{icon:"report"===c?"pi pi-spin pi-spinner":"pi pi-file",label:"Download Report",size:"small",outlined:!0,disabled:!o||null!==c,onClick:()=>_("report"),tooltip:"Download a CSV report of every test in this project",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"archive"===c?"pi pi-spin pi-spinner":"pi pi-box",label:"Download Archive",size:"small",outlined:!0,disabled:!o||null!==c,onClick:()=>_("archive"),tooltip:"Download a ZIP of the entire project directory (all tests, raw data, configs)",tooltipOptions:{position:"bottom"}}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",size:"small",onClick:u,disabled:a})]})]}),_jsxs(DataTable,{value:i,loading:a,paginator:!0,rows:10,emptyMessage:"No tests found.",scrollable:!0,scrollHeight:"flex",tableStyle:{minWidth:0},style:{width:"100%"},selectionMode:"single",onSelectionChange:e=>{const o=e.value;o?.run_id&&t.setSelection({projectId:o.project_id??null,methodId:o.method_id??null,runId:o.run_id})},children:[_jsx(Column,{header:"Sample ID",sortable:!0,body:h,sortFunction:e=>{const t=[...e.data];return t.sort((t,o)=>h(t).localeCompare(h(o))*(e.order??1)),t},style:{minWidth:"8rem"}}),_jsx(Column,{field:"start_time",header:"Date/Time",sortable:!0,body:e=>{return(t=e.start_time)?new Date(t).toLocaleString():"";var t},style:{minWidth:"12rem"}}),_jsx(Column,{field:"method_id",header:"Test Method",sortable:!0,style:{minWidth:"10rem"}}),_jsx(Column,{field:"run_id",header:"Run ID",sortable:!0,style:{minWidth:"12rem"}}),_jsx(Column,{header:"Download",style:{width:"14rem"},body:e=>{const t=l?.runId===e.run_id&&"data"===l?.kind,o=l?.runId===e.run_id&&"report"===l?.kind,r=null!==l;return _jsxs("div",{style:{display:"flex",gap:"0.4rem"},children:[_jsx(Button,{icon:t?"pi pi-spin pi-spinner":"pi pi-download",label:"Data",size:"small",outlined:!0,disabled:r,onClick:()=>f(e,"data"),tooltip:"Download raw + filtered trace data as one CSV",tooltipOptions:{position:"left"}}),_jsx(Button,{icon:o?"pi pi-spin pi-spinner":"pi pi-file",label:"Report",size:"small",outlined:!0,disabled:r,onClick:()=>f(e,"report"),tooltip:"Download a CSV report (metadata + cycles + results) for this test",tooltipOptions:{position:"left"}})]})}})]})]})};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcops/autocore-react",
3
- "version": "3.3.75",
3
+ "version": "3.3.77",
4
4
  "description": "A React component library for industrial user interfaces.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -1,194 +1,198 @@
1
1
  /*
2
2
  * Copyright (C) 2024 Automated Design Corp. All Rights Reserved.
3
- * Created Date: 2024-01-16 14:39:41
4
- * -----
5
- * Last Modified: 2025-09-05 14:52:12
6
- * -----
7
- *
3
+ *
4
+ * <Indicator> — non-interactive visual state lamp.
5
+ *
6
+ * Two usage shapes are supported:
7
+ *
8
+ * 1. Icon / children mode (matches <IndicatorButton>):
9
+ *
10
+ * <Indicator
11
+ * className="ac-toolbar-icon-btn"
12
+ * onColor={IndicatorColor.IndicatorGreen}
13
+ * offColor={IndicatorColor.IndicatorRed}
14
+ * value={controlPowerOk}
15
+ * >
16
+ * <i className="pi pi-power-off" />
17
+ * </Indicator>
18
+ *
19
+ * Renders a square (non-rounded) box whose background is driven by
20
+ * `value` (and `onColor` / `offColor`). The children are layered on
21
+ * top — typically an icon — and the box gets a subtle inset shadow
22
+ * so it visually reads as a passive indicator rather than a button.
23
+ *
24
+ * 2. Legacy label mode:
25
+ *
26
+ * <Indicator label="Pump Running" value={pumpOk} />
27
+ *
28
+ * A small color swatch followed by a text label. Kept for callers
29
+ * that pre-date the icon form.
30
+ *
31
+ * State source priority: explicit `value` prop > subscribed `topic` value.
32
+ * `undefined` displays as `invalidColor` (default black) — meaning "not
33
+ * available," not "off."
8
34
  */
9
35
 
10
-
11
- import React, { Component } from 'react';
12
- //import clsx from 'clsx';
36
+ import React, { useContext, useEffect, useState } from 'react';
37
+ import clsx from 'clsx';
13
38
  import { EventEmitterContext, type EventEmitterContextType } from '../core/EventEmitterContext';
14
- //import { IPositionContext } from '../core/PositionContext';
15
- import {IndicatorColor} from "../core/IndicatorColor";
16
- export {IndicatorColor}
17
-
18
- //import {Lamp} from "./Lamp";
19
-
20
-
21
-
22
-
23
- interface IndicatorProps {
24
-
39
+ import { IndicatorColor } from '../core/IndicatorColor';
40
+ export { IndicatorColor };
25
41
 
42
+ export interface IndicatorProps {
26
43
  /**
27
- * Label of the group/row. Can be any element.
28
- *
29
- * ## Examples
30
- * ```
31
- * <Indicator label="Simple Text" />
32
- * <Indicator label={<span><i className="pi pi-check"></i> Icon and Text</span>} />
33
- * <Indicator label={<YourCustomIconComponent />} />
34
- * ```
35
- */
36
- label? : React.ReactNode;
37
-
38
-
39
- /**
40
- * State to be displayed<br/>
41
- *
42
- * Available states: **on**, **off** and **not available**:
44
+ * State to be displayed.
43
45
  *
44
- * * `true` is treated as **on**
45
- * * `false` is treated as **off**
46
- * * `undefined` is treated as **not available**
47
- */
48
- value?: boolean | undefined;
49
- /**
50
- * CSS color for **enabled** state
46
+ * * `true` on
47
+ * * `false` off
48
+ * * `undefined` not available (invalid color)
51
49
  */
50
+ value?: boolean;
51
+
52
+ /** CSS color when value is true. Defaults to IndicatorColor.IndicatorGreen. */
52
53
  onColor?: string;
53
- /**
54
- * CSS color for **disabled** state
55
- */
54
+ /** CSS color when value is false. Defaults to IndicatorColor.IndicatorOff (gray). */
56
55
  offColor?: string;
57
- /**
58
- * CSS color for **not available** state
59
- */
56
+ /** CSS color when value is undefined. Defaults to IndicatorColor.IndicatorInvalid (black). */
60
57
  invalidColor?: string;
61
- /**
62
- * Custom class name to be attached
63
- */
64
- className?: string;
65
58
 
66
59
  /**
67
- * The topic to monitor to display. Optional.
60
+ * Optional broadcast topic to subscribe to. The latest broadcast value
61
+ * is used when `value` is left undefined; an explicit `value` always
62
+ * wins so callers can drive the indicator from React state instead.
68
63
  */
69
64
  topic?: string;
70
65
 
66
+ /** Additional CSS class names applied to the outer box. */
67
+ className?: string;
71
68
 
72
- }
69
+ /** Inline style overrides for the outer box. */
70
+ style?: React.CSSProperties;
73
71
 
72
+ /**
73
+ * Legacy text label rendered next to a small color swatch. Mutually
74
+ * exclusive with `children` — pass one or the other. Plain strings or
75
+ * React nodes both work.
76
+ */
77
+ label?: React.ReactNode;
74
78
 
75
- interface IndicatorState {
76
- subscribedValue?: boolean | null;
77
- fontSize?: string;
79
+ /**
80
+ * Icon (or any node) to display on top of the colored backdrop.
81
+ * Use this for the modern square-button-shaped indicator, e.g.
82
+ * `<Indicator ...><i className="pi pi-power-off" /></Indicator>`.
83
+ */
84
+ children?: React.ReactNode;
78
85
  }
79
86
 
80
-
81
- export class Indicator extends Component<IndicatorProps, IndicatorState> {
82
- static contextType = EventEmitterContext;
83
- protected unsubscribeTopicId: number | null = null;
84
-
85
- // Define default properties
86
- static defaultProps = {
87
- onColor: "green",
88
- offColor: "gray",
89
- invalidColor: "black"
90
- };
91
-
92
- constructor(props: IndicatorProps) {
93
- super(props);
94
- this.state = {
95
- subscribedValue: props.value,
96
- fontSize: "100%"
87
+ /**
88
+ * Pick the color to paint based on the displayed state. Kept outside the
89
+ * component so the rendering logic is trivial to read.
90
+ */
91
+ const pickColor = (
92
+ displayValue: boolean | undefined,
93
+ onColor: string,
94
+ offColor: string,
95
+ invalidColor: string,
96
+ ): string => {
97
+ if (displayValue === true) return onColor;
98
+ if (displayValue === false) return offColor;
99
+ return invalidColor;
100
+ };
101
+
102
+ export const Indicator: React.FC<IndicatorProps> = ({
103
+ value,
104
+ onColor = IndicatorColor.IndicatorGreen,
105
+ offColor = IndicatorColor.IndicatorOff,
106
+ invalidColor = IndicatorColor.IndicatorInvalid,
107
+ topic,
108
+ className,
109
+ style,
110
+ label,
111
+ children,
112
+ }) => {
113
+ const { subscribe, unsubscribe } = useContext(EventEmitterContext) as EventEmitterContextType;
114
+
115
+ const [subscribedValue, setSubscribedValue] = useState<boolean | undefined>(undefined);
116
+
117
+ useEffect(() => {
118
+ if (!topic || !subscribe) return;
119
+ const token = subscribe(topic, (v: any) => {
120
+ // The Hub publishes any payload; coerce to boolean for boolean
121
+ // indicators. Non-boolean topics should use IndicatorRect instead.
122
+ setSubscribedValue(v === undefined || v === null ? undefined : !!v);
123
+ });
124
+ return () => {
125
+ try {
126
+ if (unsubscribe && token !== undefined) unsubscribe(token);
127
+ } catch {
128
+ /* no-op */
129
+ }
97
130
  };
98
- }
99
-
100
- componentDidMount() {
101
- const { topic } = this.props;
102
- if (topic && this.unsubscribeTopicId === null) {
103
- const { subscribe } = this.context as EventEmitterContextType;
104
- this.unsubscribeTopicId = subscribe(topic, (value: any) => {
105
- this.setState({ subscribedValue: value });
106
- });
107
- }
108
- }
109
-
110
- componentDidUpdate(prevProps: IndicatorProps) {
111
- if (prevProps.value !== this.props.value) {
112
- this.setState({ subscribedValue: this.props.value });
113
- }
114
- }
115
-
116
- componentWillUnmount() {
117
- if (this.unsubscribeTopicId !== null) {
118
- const { unsubscribe } = this.context as EventEmitterContextType;
119
- unsubscribe(this.unsubscribeTopicId);
120
- this.unsubscribeTopicId = null;
121
- }
122
- }
131
+ }, [topic, subscribe, unsubscribe]);
123
132
 
124
- render() {
125
- const { label, value, onColor, offColor, invalidColor } = this.props;
126
- const { subscribedValue } = this.state;
127
-
128
- //
129
- // Load the value from the proper source.
130
- // If the `value` property is undefined, that takes precedence.
131
- //
132
- let displayValue = subscribedValue;
133
- if (value !== undefined) {
134
- displayValue = value;
135
- }
136
-
137
- let color;
138
- switch (displayValue) {
139
- case true:
140
- color = onColor;
141
- break;
142
- case false:
143
- color = offColor;
144
- break;
145
- default:
146
- color = invalidColor;
147
- break;
148
- }
149
-
150
- // const style: React.CSSProperties = {
151
- // position: useAbsolutePositioning ? 'absolute' : 'relative',
152
- // display: useAbsolutePositioning ? "" : "inline-block",
153
- // verticalAlign: 'middle',
154
- // top: useAbsolutePositioning && y ? `${yOffset + adjScale * y}px` : undefined,
155
- // left: useAbsolutePositioning && x ? `${xOffset + adjScale * x}px` : undefined,
156
- // width: adjWidth ? `${adjWidth * adjScale}px` : undefined,
157
- // height: adjHeight ? `${adjHeight * adjScale}px` : undefined,
158
- // lineHeight: adjHeight ? `${adjHeight * adjScale}px` : undefined,
159
- // backgroundColor: color,
160
- // borderRadius: "20px"
161
- // };
162
-
163
-
164
- const groupStyle : React.CSSProperties = {
165
- alignItems: "center",
166
- // PrimeReact injects `.p-inputgroup { width: 100% }`, which makes
167
- // this Indicator hog its parent flex row and squeeze siblings
168
- // (e.g. an adjacent ToggleGroup's buttons wrap vertically).
169
- // Size to content instead.
170
- width: "auto",
171
- flex: "0 0 auto",
172
- };
133
+ // Explicit value prop wins over the subscription.
134
+ const displayValue = value !== undefined ? value : subscribedValue;
135
+ const color = pickColor(displayValue, onColor, offColor, invalidColor);
173
136
 
174
- const lampDivStyle : React.CSSProperties = {
137
+ // Children mode: square, filled, inset look. Sized by the caller's
138
+ // className (e.g. `ac-toolbar-icon-btn`) so it lines up with sibling
139
+ // IndicatorButtons in the same toolbar.
140
+ if (children !== undefined && children !== null) {
141
+ const boxStyle: React.CSSProperties = {
175
142
  backgroundColor: color,
176
- width: "22px"
143
+ color: 'white',
144
+ display: 'inline-flex',
145
+ alignItems: 'center',
146
+ justifyContent: 'center',
147
+ // Square corners distinguish a passive indicator from an
148
+ // interactive button (PrimeReact buttons round to ~6px).
149
+ borderRadius: 0,
150
+ // Subtle inset shadow reinforces "this is a readout, not a
151
+ // control." Light enough to stay readable against any of the
152
+ // IndicatorColor palette.
153
+ boxShadow: 'inset 0 1px 3px rgba(0, 0, 0, 0.45)',
154
+ boxSizing: 'border-box',
155
+ ...style,
177
156
  };
178
-
179
157
  return (
180
-
181
- <div className="p-inputgroup" style={groupStyle} >
182
- <span className="p-inputgroup-addon" style={lampDivStyle} >
183
- &nbsp;
184
- </span>
185
- <span className="p-inputgroup-addon">
186
- {label}
187
- </span>
188
- </div>
189
-
158
+ <span
159
+ className={clsx('indicator', className)}
160
+ style={boxStyle}
161
+ role="status"
162
+ aria-label={typeof label === 'string' ? label : undefined}
163
+ >
164
+ {children}
165
+ </span>
190
166
  );
191
167
  }
192
- }
168
+
169
+ // Legacy label mode: small color swatch + text. Kept so existing
170
+ // <Indicator label="..." /> callers don't have to migrate. The
171
+ // `p-inputgroup` width:auto guard prevents PrimeReact from stretching
172
+ // this to fill its parent flex row.
173
+ const groupStyle: React.CSSProperties = {
174
+ alignItems: 'center',
175
+ width: 'auto',
176
+ flex: '0 0 auto',
177
+ ...style,
178
+ };
179
+ const lampStyle: React.CSSProperties = {
180
+ backgroundColor: color,
181
+ width: '22px',
182
+ borderRadius: 0,
183
+ boxShadow: 'inset 0 1px 2px rgba(0, 0, 0, 0.4)',
184
+ };
185
+
186
+ return (
187
+ <div className={clsx('p-inputgroup indicator', className)} style={groupStyle} role="status">
188
+ <span className="p-inputgroup-addon" style={lampStyle}>
189
+ &nbsp;
190
+ </span>
191
+ {label !== undefined && (
192
+ <span className="p-inputgroup-addon">{label}</span>
193
+ )}
194
+ </div>
195
+ );
196
+ };
193
197
 
194
198
  export default Indicator;
@@ -48,6 +48,13 @@ export interface AmsRole {
48
48
  description: string | null;
49
49
  /** Test method IDs whose start_test will pick up an asset in this role. */
50
50
  used_by: string[];
51
+ /** Module names whose configs reference this role via
52
+ * `${ams.by_location.<location>.*}` placeholders. Surfaces in the
53
+ * AIS Role dropdown so the operator can see *why* a role exists
54
+ * even when no test method directly resolves it (e.g., a load_cell
55
+ * feeding NI bridge channel calibration). Empty for purely
56
+ * test-method-driven roles. */
57
+ used_by_modules: string[];
51
58
  }
52
59
  export type AmsRoleRegistry = { [assetType: string]: AmsRole[] };
53
60