@adcops/autocore-react 3.3.39 → 3.3.42

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../src/components/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAM/D,MAAM,WAAW,uBAAuB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAiDhE,CAAC"}
1
+ {"version":3,"file":"ResultHistoryTable.d.ts","sourceRoot":"","sources":["../../src/components/ResultHistoryTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAO/D,MAAM,WAAW,uBAAuB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAiDhE,CAAC"}
@@ -1 +1 @@
1
- import{jsxs as _jsxs,jsx as _jsx}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";export const ResultHistoryTable=({projectId:t,definitionId:e})=>{const[s,r]=useState([]),[i,o]=useState(!1),{invoke:n}=useContext(EventEmitterContext),a=async()=>{o(!0);try{const s=await n("results.list_tests",{project_id:t,definition_id:e});s.success&&s.data&&s.data.tests&&r(s.data.tests)}catch(t){}o(!1)};useEffect(()=>{a()},[t,e]);return _jsxs("div",{children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem"},children:[_jsxs("h3",{children:["Test History: ",e]}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",onClick:a,disabled:i})]}),_jsxs(DataTable,{value:s,loading:i,paginator:!0,rows:10,emptyMessage:"No tests found.",children:[_jsx(Column,{field:"run_id",header:"Run ID",sortable:!0}),_jsx(Column,{field:"start_time",header:"Date/Time",body:t=>{return(e=t.start_time)?new Date(e).toLocaleString():"";var e},sortable:!0}),_jsx(Column,{header:"Config / Results",body:t=>_jsxs("div",{style:{fontSize:"0.85em",color:"var(--text-secondary-color)"},children:[_jsxs("div",{children:[_jsx("strong",{children:"Config:"})," ",JSON.stringify(t.config)]}),_jsxs("div",{children:[_jsx("strong",{children:"Results:"})," ",JSON.stringify(t.results)]})]})})]})]})};
1
+ import{jsxs as _jsxs,jsx as _jsx}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";export const ResultHistoryTable=({projectId:e,definitionId:t})=>{const[s,r]=useState([]),[i,o]=useState(!1),{invoke:n}=useContext(EventEmitterContext),a=async()=>{o(!0);try{const s=await n("results.list_tests",MessageType.Request,{project_id:e,definition_id:t});s.success&&s.data&&s.data.tests&&r(s.data.tests)}catch(e){}o(!1)};useEffect(()=>{a()},[e,t]);return _jsxs("div",{children:[_jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"1rem"},children:[_jsxs("h3",{children:["Test History: ",t]}),_jsx(Button,{icon:"pi pi-refresh",label:"Refresh",onClick:a,disabled:i})]}),_jsxs(DataTable,{value:s,loading:i,paginator:!0,rows:10,emptyMessage:"No tests found.",children:[_jsx(Column,{field:"run_id",header:"Run ID",sortable:!0}),_jsx(Column,{field:"start_time",header:"Date/Time",body:e=>{return(t=e.start_time)?new Date(t).toLocaleString():"";var t},sortable:!0}),_jsx(Column,{header:"Config / Results",body:e=>_jsxs("div",{style:{fontSize:"0.85em",color:"var(--text-secondary-color)"},children:[_jsxs("div",{children:[_jsx("strong",{children:"Config:"})," ",JSON.stringify(e.config)]}),_jsxs("div",{children:[_jsx("strong",{children:"Results:"})," ",JSON.stringify(e.results)]})]})})]})]})};
@@ -1 +1 @@
1
- {"version":3,"file":"TestSetupForm.d.ts","sourceRoot":"","sources":["../../src/components/TestSetupForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAO/D,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC3B,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,cAAc,EAAE,YAAY,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAChE;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAgKtD,CAAC"}
1
+ {"version":3,"file":"TestSetupForm.d.ts","sourceRoot":"","sources":["../../src/components/TestSetupForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAS/D,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC3B,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,cAAc,EAAE,YAAY,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAChE;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA4MtD,CAAC"}
@@ -1 +1 @@
1
- import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext}from"react";import{InputText}from"primereact/inputtext";import{AutoComplete}from"primereact/autocomplete";import{EventEmitterContext}from"../core/EventEmitterContext";import{MessageType}from"../hub/CommandMessage";export const TestSetupForm=({schema:e,defaultProjectId:s="",defaultDefinitionId:a="default",onProjectChange:t,onDefinitionChange:n,onValidationChange:r})=>{const[i,o]=useState({}),[c,p]=useState(s),[l,m]=useState(a);useEffect(()=>{t&&t(c)},[c,t]),useEffect(()=>{n&&n(l)},[l,n]);const[u,d]=useState([]),[f,x]=useState([]),[j,g]=useState(!1),{invoke:h}=useContext(EventEmitterContext);useEffect(()=>{(async()=>{try{const e=await h("results.list_projects",MessageType.Request,{});e.success&&e.data&&e.data.projects&&d(e.data.projects)}catch(e){}})()},[h]);useEffect(()=>{let s=!0;c&&""!==c.trim()||(s=!1),l&&""!==l.trim()||(s=!1);const a=[...e.project_fields,...e.config_fields];for(const e of a)if(e.required&&!e.source&&(void 0===i[e.name]||""===i[e.name])){s=!1;break}g(s),r&&r(s,i)},[i,e,c,l,r]);const _=e=>{if(e.source)return _jsxs("div",{className:"ac-form-span p-inputgroup",children:[_jsx("span",{className:"p-inputgroup-addon",children:e.name}),_jsx(InputText,{value:`Auto-fetched from ${e.source}`,disabled:!0}),e.units&&_jsx("span",{className:"p-inputgroup-addon",children:e.units}),_jsx("span",{className:"p-inputgroup-addon",style:{color:"var(--green-500)"},children:_jsx("i",{className:"pi pi-check"})})]},e.name);const s=(e=>!(e.required&&!e.source)||void 0!==i[e.name]&&""!==i[e.name])(e);return _jsxs("div",{className:"ac-form-span p-inputgroup",children:[_jsx("span",{className:"p-inputgroup-addon",children:e.name}),_jsx(InputText,{value:i[e.name]||"",onChange:s=>o({...i,[e.name]:s.target.value}),placeholder:`Enter ${e.name}`,className:s?"":"p-invalid"}),e.units&&_jsx("span",{className:"p-inputgroup-addon",children:e.units}),_jsx("span",{className:"p-inputgroup-addon",style:{color:s?"var(--green-500)":"var(--red-500)"},children:_jsx("i",{className:s?"pi pi-check":"pi pi-times"})})]},e.name)};return _jsxs("div",{className:"ac-form-grid",style:{padding:"1.25rem"},children:[_jsxs("h3",{className:"ac-form-section",style:{display:"flex",alignItems:"center",gap:"10px"},children:["Project & Definition",_jsx("span",{style:{color:j?"var(--green-500)":"var(--red-500)"},children:_jsx("i",{className:j?"pi pi-check-circle":"pi pi-exclamation-circle"})})]}),_jsxs("div",{className:"ac-form-span p-inputgroup",children:[_jsx("span",{className:"p-inputgroup-addon",children:"Project ID"}),_jsx(AutoComplete,{value:c,suggestions:f,completeMethod:e=>{const s=e.query.toLowerCase();x(u.filter(e=>e.toLowerCase().includes(s)))},onChange:e=>(e=>{const s=e.replace(/[^a-zA-Z0-9_]/g,"");p(s)})(e.value),dropdown:!0,placeholder:"Enter or select Project ID",className:c&&""!==c.trim()?"":"p-invalid"}),_jsx("span",{className:"p-inputgroup-addon",style:{color:c&&""!==c.trim()?"var(--green-500)":"var(--red-500)"},children:_jsx("i",{className:c&&""!==c.trim()?"pi pi-check":"pi pi-times"})})]}),_jsxs("div",{className:"ac-form-span p-inputgroup",children:[_jsx("span",{className:"p-inputgroup-addon",children:"Definition ID"}),_jsx(InputText,{value:l,onChange:e=>(e=>{const s=e.replace(/[^a-zA-Z0-9_]/g,"");m(s)})(e.target.value),placeholder:"Enter Definition ID",className:l&&""!==l.trim()?"":"p-invalid"}),_jsx("span",{className:"p-inputgroup-addon",style:{color:l&&""!==l.trim()?"var(--green-500)":"var(--red-500)"},children:_jsx("i",{className:l&&""!==l.trim()?"pi pi-check":"pi pi-times"})})]}),_jsx("h3",{className:"ac-form-section",style:{marginTop:"1rem"},children:"Project Information"}),e.project_fields.map(_),_jsx("h3",{className:"ac-form-section",style:{marginTop:"1rem"},children:"Test Configuration"}),e.config_fields.map(_)]})};
1
+ import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect,useContext}from"react";import{AutoComplete}from"primereact/autocomplete";import{EventEmitterContext}from"../core/EventEmitterContext";import{MessageType}from"../hub/CommandMessage";import{ValueInput}from"./ValueInput";import{TextInput}from"./TextInput";import{InputText}from"primereact/inputtext";export const TestSetupForm=({schema:e,defaultProjectId:t="",defaultDefinitionId:s="default",onProjectChange:a,onDefinitionChange:n,onValidationChange:i})=>{const[r,o]=useState({}),[c,l]=useState(t),[m,p]=useState(s);useEffect(()=>{a&&a(c)},[c,a]),useEffect(()=>{n&&n(m)},[m,n]);const[u,f]=useState([]),[d,x]=useState([]),[j,g]=useState(!1),{invoke:h,write:_,subscribe:v,unsubscribe:y}=useContext(EventEmitterContext);useEffect(()=>{if(!e)return;const t=[...e.project_fields,...e.config_fields],s=[];for(const e of t)if(e.source){const t=v(e.source,t=>{o(s=>s[e.name]!==t?{...s,[e.name]:t}:s)});s.push(t)}return()=>{for(const e of s)y(e)}},[e,v,y]),useEffect(()=>{(async()=>{try{const e=await h("results.list_projects",MessageType.Request,{});e.success&&e.data&&e.data.projects&&f(e.data.projects)}catch(e){}})()},[h]);useEffect(()=>{if(!e)return;let t=!0;c&&""!==c.trim()||(t=!1),m&&""!==m.trim()||(t=!1);const s=[...e.project_fields,...e.config_fields];for(const e of s)if(e.required&&(void 0===r[e.name]||""===r[e.name]||null===r[e.name])){t=!1;break}g(t),i&&i(t,r)},[r,e,c,m,i]);const I=async(e,t)=>{if(o({...r,[e.name]:t}),e.source)try{await _(e.source,t)}catch(e){}},N=e=>{const t=(e=>!e.required||void 0!==r[e.name]&&""!==r[e.name]&&null!==r[e.name])(e),s="string"!==e.type&&"bool"!==e.type;return _jsxs(React.Fragment,{children:[_jsx("span",{className:"ac-form-label",children:e.name}),s?_jsx(ValueInput,{label:void 0,value:null!=r[e.name]?Number(r[e.name]):null,suffix:e.units?` ${e.units}`:void 0,onValueChanged:t=>I(e,t),className:t?"":"p-invalid"}):_jsx(TextInput,{label:void 0,value:null!=r[e.name]?String(r[e.name]):"",suffix:e.units?` ${e.units}`:void 0,onValueChanged:t=>I(e,t),className:t?"":"p-invalid"}),_jsx("span",{style:{color:t?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:t?"pi pi-check":"pi pi-times"})})]},e.name)};return e?_jsxs("div",{className:"ac-form-grid",style:{padding:"1.25rem"},children:[_jsxs("h3",{className:"ac-form-section",style:{display:"flex",alignItems:"center",gap:"10px"},children:["Project & Definition",_jsx("span",{style:{color:j?"var(--green-500)":"var(--red-500)"},children:_jsx("i",{className:j?"pi pi-check-circle":"pi pi-exclamation-circle"})})]}),_jsx("span",{className:"ac-form-label",children:"Project ID"}),_jsx(AutoComplete,{value:c,suggestions:d,completeMethod:e=>{const t=e.query.toLowerCase();x(u.filter(e=>e.toLowerCase().includes(t)))},onChange:e=>(e=>{const t=(e||"").replace(/[^a-zA-Z0-9_]/g,"");l(t)})(e.value),dropdown:!0,placeholder:"Enter or select Project ID",className:c&&""!==c.trim()?"":"p-invalid"}),_jsx("span",{style:{color:c&&""!==c.trim()?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:c&&""!==c.trim()?"pi pi-check":"pi pi-times"})}),_jsx("span",{className:"ac-form-label",children:"Definition ID"}),_jsx(InputText,{value:m,onChange:e=>(e=>{const t=e.replace(/[^a-zA-Z0-9_]/g,"");p(t)})(e.target.value),placeholder:"Enter Definition ID",className:m&&""!==m.trim()?"":"p-invalid"}),_jsx("span",{style:{color:m&&""!==m.trim()?"var(--green-500)":"var(--red-500)",display:"flex",alignItems:"center"},children:_jsx("i",{className:m&&""!==m.trim()?"pi pi-check":"pi pi-times"})}),_jsx("h3",{className:"ac-form-section",style:{marginTop:"1rem"},children:"Project Information"}),e.project_fields.map(N),_jsx("h3",{className:"ac-form-section",style:{marginTop:"1rem"},children:"Test Configuration"}),e.config_fields.map(N)]}):_jsx("div",{className:"ac-form-grid",style:{padding:"1.25rem"},children:_jsx("h3",{className:"ac-form-section",children:"No Test Definition Selected"})})};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcops/autocore-react",
3
- "version": "3.3.39",
3
+ "version": "3.3.42",
4
4
  "description": "A React component library for industrial user interfaces.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -3,6 +3,7 @@ import { DataTable } from 'primereact/datatable';
3
3
  import { Column } from 'primereact/column';
4
4
  import { Button } from 'primereact/button';
5
5
  import { EventEmitterContext } from '../core/EventEmitterContext';
6
+ import { MessageType } from '../hub/CommandMessage';
6
7
 
7
8
  export interface ResultHistoryTableProps {
8
9
  projectId: string;
@@ -17,7 +18,7 @@ export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = ({ projectI
17
18
  const loadTests = async () => {
18
19
  setLoading(true);
19
20
  try {
20
- const resp: any = await invoke('results.list_tests' as any, {
21
+ const resp: any = await invoke('results.list_tests' as any, MessageType.Request, {
21
22
  project_id: projectId,
22
23
  definition_id: definitionId
23
24
  } as any);
@@ -1,9 +1,11 @@
1
1
  import React, { useState, useEffect, useContext } from 'react';
2
- import { InputText } from 'primereact/inputtext';
3
2
  import { AutoComplete } from 'primereact/autocomplete';
4
3
  import type { AutoCompleteCompleteEvent } from 'primereact/autocomplete';
5
4
  import { EventEmitterContext } from '../core/EventEmitterContext';
6
5
  import { MessageType } from '../hub/CommandMessage';
6
+ import { ValueInput } from './ValueInput';
7
+ import { TextInput } from './TextInput';
8
+ import { InputText } from 'primereact/inputtext';
7
9
 
8
10
  export interface TestFieldDef {
9
11
  name: string;
@@ -54,7 +56,36 @@ export const TestSetupForm: React.FC<TestSetupFormProps> = ({
54
56
  const [filteredProjects, setFilteredProjects] = useState<string[]>([]);
55
57
 
56
58
  const [isValid, setIsValid] = useState(false);
57
- const { invoke } = useContext(EventEmitterContext);
59
+ const { invoke, write, subscribe, unsubscribe } = useContext(EventEmitterContext);
60
+
61
+ // Subscribe to external tags if a field defines a source
62
+ useEffect(() => {
63
+ if (!schema) return;
64
+
65
+ const allFields = [...schema.project_fields, ...schema.config_fields];
66
+ const subs: any[] = [];
67
+
68
+ for (const field of allFields) {
69
+ if (field.source) {
70
+ const subId = subscribe(field.source, (newVal: any) => {
71
+ setConfig((prev: any) => {
72
+ // Only update if it actually changed to prevent infinite loops
73
+ if (prev[field.name] !== newVal) {
74
+ return { ...prev, [field.name]: newVal };
75
+ }
76
+ return prev;
77
+ });
78
+ });
79
+ subs.push(subId);
80
+ }
81
+ }
82
+
83
+ return () => {
84
+ for (const subId of subs) {
85
+ unsubscribe(subId);
86
+ }
87
+ };
88
+ }, [schema, subscribe, unsubscribe]);
58
89
 
59
90
  useEffect(() => {
60
91
  const fetchProjects = async () => {
@@ -75,17 +106,13 @@ export const TestSetupForm: React.FC<TestSetupFormProps> = ({
75
106
  setFilteredProjects(existingProjects.filter(p => p.toLowerCase().includes(query)));
76
107
  };
77
108
 
78
- const handleProjectIdChange = (value: string) => {
79
- const sanitized = value.replace(/[^a-zA-Z0-9_]/g, '');
80
- setProjectId(sanitized);
81
- };
82
-
83
109
  const handleDefinitionIdChange = (value: string) => {
84
110
  const sanitized = value.replace(/[^a-zA-Z0-9_]/g, '');
85
111
  setDefinitionId(sanitized);
86
112
  };
87
113
 
88
114
  useEffect(() => {
115
+ if (!schema) return;
89
116
  let valid = true;
90
117
  if (!projectId || projectId.trim() === '') valid = false;
91
118
  if (!definitionId || definitionId.trim() === '') valid = false;
@@ -93,8 +120,8 @@ export const TestSetupForm: React.FC<TestSetupFormProps> = ({
93
120
  const allFields = [...schema.project_fields, ...schema.config_fields];
94
121
 
95
122
  for (const field of allFields) {
96
- if (field.required && !field.source) {
97
- if (config[field.name] === undefined || config[field.name] === '') {
123
+ if (field.required) {
124
+ if (config[field.name] === undefined || config[field.name] === '' || config[field.name] === null) {
98
125
  valid = false;
99
126
  break;
100
127
  }
@@ -107,43 +134,66 @@ export const TestSetupForm: React.FC<TestSetupFormProps> = ({
107
134
  }, [config, schema, projectId, definitionId, onValidationChange]);
108
135
 
109
136
  const isFieldValid = (field: TestFieldDef) => {
110
- if (!field.required || field.source) return true;
111
- return config[field.name] !== undefined && config[field.name] !== '';
137
+ if (!field.required) return true;
138
+ return config[field.name] !== undefined && config[field.name] !== '' && config[field.name] !== null;
112
139
  };
113
140
 
114
- const renderField = (field: TestFieldDef) => {
141
+ const handleProjectIdChange = (value: string | null | undefined) => {
142
+ const val = value || '';
143
+ const sanitized = val.replace(/[^a-zA-Z0-9_]/g, '');
144
+ setProjectId(sanitized);
145
+ };
146
+
147
+ const handleFieldChange = async (field: TestFieldDef, val: any) => {
148
+ setConfig({...config, [field.name]: val});
115
149
  if (field.source) {
116
- return (
117
- <div key={field.name} className="ac-form-span p-inputgroup">
118
- <span className="p-inputgroup-addon">{field.name}</span>
119
- <InputText value={`Auto-fetched from ${field.source}`} disabled />
120
- {field.units && <span className="p-inputgroup-addon">{field.units}</span>}
121
- <span className="p-inputgroup-addon" style={{ color: 'var(--green-500)' }}>
122
- <i className="pi pi-check"></i>
123
- </span>
124
- </div>
125
- );
150
+ try {
151
+ await write(field.source, val);
152
+ } catch(e) {
153
+ console.error("Failed to write to source:", e);
154
+ }
126
155
  }
156
+ };
127
157
 
158
+ const renderField = (field: TestFieldDef) => {
128
159
  const valid = isFieldValid(field);
160
+ const isNum = field.type !== 'string' && field.type !== 'bool';
129
161
 
130
162
  return (
131
- <div key={field.name} className="ac-form-span p-inputgroup">
132
- <span className="p-inputgroup-addon">{field.name}</span>
133
- <InputText
134
- value={config[field.name] || ''}
135
- onChange={(e) => setConfig({...config, [field.name]: e.target.value})}
136
- placeholder={`Enter ${field.name}`}
137
- className={!valid ? 'p-invalid' : ''}
138
- />
139
- {field.units && <span className="p-inputgroup-addon">{field.units}</span>}
140
- <span className="p-inputgroup-addon" style={{ color: valid ? 'var(--green-500)' : 'var(--red-500)' }}>
163
+ <React.Fragment key={field.name}>
164
+ <span className="ac-form-label">{field.name}</span>
165
+ {isNum ? (
166
+ <ValueInput
167
+ label={undefined}
168
+ value={config[field.name] != null ? Number(config[field.name]) : null}
169
+ suffix={field.units ? ` ${field.units}` : undefined}
170
+ onValueChanged={(val) => handleFieldChange(field, val)}
171
+ className={!valid ? 'p-invalid' : ''}
172
+ />
173
+ ) : (
174
+ <TextInput
175
+ label={undefined}
176
+ value={config[field.name] != null ? String(config[field.name]) : ''}
177
+ suffix={field.units ? ` ${field.units}` : undefined}
178
+ onValueChanged={(val) => handleFieldChange(field, val)}
179
+ className={!valid ? 'p-invalid' : ''}
180
+ />
181
+ )}
182
+ <span style={{ color: valid ? 'var(--green-500)' : 'var(--red-500)', display: 'flex', alignItems: 'center' }}>
141
183
  <i className={valid ? "pi pi-check" : "pi pi-times"}></i>
142
184
  </span>
143
- </div>
185
+ </React.Fragment>
144
186
  );
145
187
  };
146
188
 
189
+ if (!schema) {
190
+ return (
191
+ <div className="ac-form-grid" style={{ padding: '1.25rem' }}>
192
+ <h3 className="ac-form-section">No Test Definition Selected</h3>
193
+ </div>
194
+ );
195
+ }
196
+
147
197
  return (
148
198
  <div className="ac-form-grid" style={{ padding: '1.25rem' }}>
149
199
  <h3 className="ac-form-section" style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
@@ -153,34 +203,30 @@ export const TestSetupForm: React.FC<TestSetupFormProps> = ({
153
203
  </span>
154
204
  </h3>
155
205
 
156
- <div className="ac-form-span p-inputgroup">
157
- <span className="p-inputgroup-addon">Project ID</span>
158
- <AutoComplete
159
- value={projectId}
160
- suggestions={filteredProjects}
161
- completeMethod={searchProjects}
162
- onChange={(e) => handleProjectIdChange(e.value)}
163
- dropdown
164
- placeholder="Enter or select Project ID"
165
- className={!projectId || projectId.trim() === '' ? 'p-invalid' : ''}
166
- />
167
- <span className="p-inputgroup-addon" style={{ color: projectId && projectId.trim() !== '' ? 'var(--green-500)' : 'var(--red-500)' }}>
168
- <i className={projectId && projectId.trim() !== '' ? "pi pi-check" : "pi pi-times"}></i>
169
- </span>
170
- </div>
206
+ <span className="ac-form-label">Project ID</span>
207
+ <AutoComplete
208
+ value={projectId}
209
+ suggestions={filteredProjects}
210
+ completeMethod={searchProjects}
211
+ onChange={(e) => handleProjectIdChange(e.value)}
212
+ dropdown
213
+ placeholder="Enter or select Project ID"
214
+ className={!projectId || projectId.trim() === '' ? 'p-invalid' : ''}
215
+ />
216
+ <span style={{ color: projectId && projectId.trim() !== '' ? 'var(--green-500)' : 'var(--red-500)', display: 'flex', alignItems: 'center' }}>
217
+ <i className={projectId && projectId.trim() !== '' ? "pi pi-check" : "pi pi-times"}></i>
218
+ </span>
171
219
 
172
- <div className="ac-form-span p-inputgroup">
173
- <span className="p-inputgroup-addon">Definition ID</span>
174
- <InputText
175
- value={definitionId}
176
- onChange={(e) => handleDefinitionIdChange(e.target.value)}
177
- placeholder="Enter Definition ID"
178
- className={!definitionId || definitionId.trim() === '' ? 'p-invalid' : ''}
179
- />
180
- <span className="p-inputgroup-addon" style={{ color: definitionId && definitionId.trim() !== '' ? 'var(--green-500)' : 'var(--red-500)' }}>
181
- <i className={definitionId && definitionId.trim() !== '' ? "pi pi-check" : "pi pi-times"}></i>
182
- </span>
183
- </div>
220
+ <span className="ac-form-label">Definition ID</span>
221
+ <InputText
222
+ value={definitionId}
223
+ onChange={(e) => handleDefinitionIdChange(e.target.value)}
224
+ placeholder="Enter Definition ID"
225
+ className={!definitionId || definitionId.trim() === '' ? 'p-invalid' : ''}
226
+ />
227
+ <span style={{ color: definitionId && definitionId.trim() !== '' ? 'var(--green-500)' : 'var(--red-500)', display: 'flex', alignItems: 'center' }}>
228
+ <i className={definitionId && definitionId.trim() !== '' ? "pi pi-check" : "pi pi-times"}></i>
229
+ </span>
184
230
 
185
231
  <h3 className="ac-form-section" style={{ marginTop: '1rem' }}>Project Information</h3>
186
232
  {schema.project_fields.map(renderField)}