@adcops/autocore-react 3.3.19 → 3.3.23
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/dist/components/ResultHistoryTable.d.ts +7 -0
- package/dist/components/ResultHistoryTable.d.ts.map +1 -0
- package/dist/components/ResultHistoryTable.js +1 -0
- package/dist/components/TestSetupForm.d.ts +23 -0
- package/dist/components/TestSetupForm.d.ts.map +1 -0
- package/dist/components/TestSetupForm.js +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/useServeletData.d.ts +36 -0
- package/dist/hooks/useServeletData.d.ts.map +1 -0
- package/dist/hooks/useServeletData.js +1 -0
- package/package.json +1 -1
- package/src/components/ResultHistoryTable.tsx +61 -0
- package/src/components/TestSetupForm.tsx +98 -0
- package/src/components/index.ts +2 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useServeletData.ts +95 -0
|
@@ -0,0 +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"}
|
|
@@ -0,0 +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)]})]})})]})]})};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface TestFieldDef {
|
|
3
|
+
name: string;
|
|
4
|
+
type: string;
|
|
5
|
+
units?: string;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
source?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TestDefinition {
|
|
10
|
+
project_fields: TestFieldDef[];
|
|
11
|
+
config_fields: TestFieldDef[];
|
|
12
|
+
cycle_fields: TestFieldDef[];
|
|
13
|
+
results_fields: TestFieldDef[];
|
|
14
|
+
}
|
|
15
|
+
export interface TestSetupFormProps {
|
|
16
|
+
projectId: string;
|
|
17
|
+
definitionId: string;
|
|
18
|
+
schema: TestDefinition;
|
|
19
|
+
onStartTest: (config: any) => void;
|
|
20
|
+
onCancel?: () => void;
|
|
21
|
+
}
|
|
22
|
+
export declare const TestSetupForm: React.FC<TestSetupFormProps>;
|
|
23
|
+
//# sourceMappingURL=TestSetupForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestSetupForm.d.ts","sourceRoot":"","sources":["../../src/components/TestSetupForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,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,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,WAAW,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAsEtD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";import React,{useState,useEffect}from"react";import{Button}from"primereact/button";import{InputText}from"primereact/inputtext";export const TestSetupForm=({schema:e,onStartTest:s,onCancel:a})=>{const[t,n]=useState({}),[r,i]=useState(!1);useEffect(()=>{let s=!0;const a=[...e.project_fields,...e.config_fields];for(const e of a)if(e.required&&!e.source&&(void 0===t[e.name]||""===t[e.name])){s=!1;break}i(s)},[t,e]);const o=e=>e.source?_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})]},e.name):_jsxs("div",{className:"ac-form-span p-inputgroup",children:[_jsxs("span",{className:"p-inputgroup-addon",children:[e.name," ",e.required?"*":""]}),_jsx(InputText,{value:t[e.name]||"",onChange:s=>n({...t,[e.name]:s.target.value}),placeholder:`Enter ${e.name}`}),e.units&&_jsx("span",{className:"p-inputgroup-addon",children:e.units})]},e.name);return _jsxs("div",{className:"ac-form-grid",style:{padding:"1.25rem"},children:[_jsx("h3",{className:"ac-form-section",children:"Project Information"}),e.project_fields.map(o),_jsx("h3",{className:"ac-form-section",style:{marginTop:"1rem"},children:"Test Configuration"}),e.config_fields.map(o),_jsxs("div",{className:"ac-form-wide",style:{display:"flex",gap:"1rem",marginTop:"2rem"},children:[_jsx(Button,{label:"Start Test",icon:"pi pi-play",severity:"success",disabled:!r,onClick:()=>s(t)}),a&&_jsx(Button,{label:"Cancel",icon:"pi pi-times",severity:"secondary",onClick:a})]})]})};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export*from"./TestSetupForm";export*from"./ResultHistoryTable";
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAC,qBAAqB,EAAE,gBAAgB,EAAE,cAAc,EAAC,MAAM,YAAY,CAAC;AACnF,OAAO,EAAC,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAC,qBAAqB,EAAE,gBAAgB,EAAE,cAAc,EAAC,MAAM,YAAY,CAAC;AACnF,OAAO,EAAC,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACtE,cAAc,mBAAmB,CAAC"}
|
package/dist/hooks/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{useAdsRegisterSymbols,useAdsWriteValue,useAdsTapValue}from"./adsHooks";export{useScaledValue,kMillimeters2Inches}from"./useScaledValue";
|
|
1
|
+
export{useAdsRegisterSymbols,useAdsWriteValue,useAdsTapValue}from"./adsHooks";export{useScaledValue,kMillimeters2Inches}from"./useScaledValue";export*from"./useServeletData";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to read, write, and subscribe to a value in a generic servelet like MemoryStore or GNV.
|
|
3
|
+
*/
|
|
4
|
+
export declare function useServeletData<T = any>(domain: string, key: string, defaultValue?: T): {
|
|
5
|
+
value: T | undefined;
|
|
6
|
+
write: (newValue: T) => Promise<void>;
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
error: string | null;
|
|
9
|
+
refresh: () => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Read, write, and subscribe to a non-volatile key in the Global Non-Volatile (GNV) store.
|
|
13
|
+
* @param group The config group (e.g. "app")
|
|
14
|
+
* @param key The config key (e.g. "description")
|
|
15
|
+
* @param defaultValue Optional fallback before load
|
|
16
|
+
*/
|
|
17
|
+
export declare function useGnv<T = any>(group: string, key: string, defaultValue?: T): {
|
|
18
|
+
value: T | undefined;
|
|
19
|
+
write: (newValue: T) => Promise<void>;
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
error: string | null;
|
|
22
|
+
refresh: () => Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Read, write, and subscribe to a volatile key in the MemoryStore.
|
|
26
|
+
* @param key The cache key (e.g. "temp_state")
|
|
27
|
+
* @param defaultValue Optional fallback before load
|
|
28
|
+
*/
|
|
29
|
+
export declare function useMemoryStore<T = any>(key: string, defaultValue?: T): {
|
|
30
|
+
value: T | undefined;
|
|
31
|
+
write: (newValue: T) => Promise<void>;
|
|
32
|
+
isLoading: boolean;
|
|
33
|
+
error: string | null;
|
|
34
|
+
refresh: () => Promise<void>;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=useServeletData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useServeletData.d.ts","sourceRoot":"","sources":["../../src/hooks/useServeletData.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,GAAG,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC;;sBAuDlC,CAAC;;;;EAapD;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC;;;;;;EAE3E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC;;;;;;EAEpE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useState,useEffect,useContext,useCallback}from"react";import{EventEmitterContext}from"../core/EventEmitterContext";import{MessageType}from"../hub/CommandMessage";export function useServeletData(e,t,r){const{invoke:s,subscribe:n,unsubscribe:a,isConnected:o}=useContext(EventEmitterContext),[u,c]=useState(r),[l,i]=useState(!0),[m,f]=useState(null),v=`${e}.${t}`,y=useCallback(async()=>{if(o())try{i(!0);const e=await s(v,MessageType.Read,{});e.success?(c(e.data),f(null)):f(e.error_message)}catch(e){f(e.message)}finally{i(!1)}},[v,s,o]);useEffect(()=>{let e=null,t=null,r=!0;const s=()=>{y(),e=n(v,e=>{r&&c(e.data)})};return o()?s():t=n("HUB/connected",()=>{r&&s()}),()=>{r=!1,null!==e&&a(e),null!==t&&a(t)}},[v,y,n,a,o]);return{value:u,write:useCallback(async e=>{try{const t=await s(v,MessageType.Write,e);if(!t.success)throw new Error(t.error_message)}catch(e){throw e}},[v,s]),isLoading:l,error:m,refresh:y}}export function useGnv(e,t,r){return useServeletData("gnv",`${e}.${t}`,r)}export function useMemoryStore(e,t){return useServeletData("memorystore",e,t)}
|
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React, { useState, useEffect, useContext } from 'react';
|
|
2
|
+
import { DataTable } from 'primereact/datatable';
|
|
3
|
+
import { Column } from 'primereact/column';
|
|
4
|
+
import { Button } from 'primereact/button';
|
|
5
|
+
import { EventEmitterContext } from '../core/EventEmitterContext';
|
|
6
|
+
|
|
7
|
+
export interface ResultHistoryTableProps {
|
|
8
|
+
projectId: string;
|
|
9
|
+
definitionId: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ResultHistoryTable: React.FC<ResultHistoryTableProps> = ({ projectId, definitionId }) => {
|
|
13
|
+
const [tests, setTests] = useState<any[]>([]);
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
const { invoke } = useContext(EventEmitterContext);
|
|
16
|
+
|
|
17
|
+
const loadTests = async () => {
|
|
18
|
+
setLoading(true);
|
|
19
|
+
try {
|
|
20
|
+
const resp: any = await invoke('results.list_tests' as any, {
|
|
21
|
+
project_id: projectId,
|
|
22
|
+
definition_id: definitionId
|
|
23
|
+
} as any);
|
|
24
|
+
if (resp.success && resp.data && resp.data.tests) {
|
|
25
|
+
setTests(resp.data.tests);
|
|
26
|
+
}
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error("Failed to load tests", err);
|
|
29
|
+
}
|
|
30
|
+
setLoading(false);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
loadTests();
|
|
35
|
+
}, [projectId, definitionId]);
|
|
36
|
+
|
|
37
|
+
const formatDate = (dateStr: string) => {
|
|
38
|
+
if (!dateStr) return '';
|
|
39
|
+
return new Date(dateStr).toLocaleString();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div>
|
|
44
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
|
45
|
+
<h3>Test History: {definitionId}</h3>
|
|
46
|
+
<Button icon="pi pi-refresh" label="Refresh" onClick={loadTests} disabled={loading} />
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<DataTable value={tests} loading={loading} paginator rows={10} emptyMessage="No tests found.">
|
|
50
|
+
<Column field="run_id" header="Run ID" sortable />
|
|
51
|
+
<Column field="start_time" header="Date/Time" body={(rowData) => formatDate(rowData.start_time)} sortable />
|
|
52
|
+
<Column header="Config / Results" body={(rowData) => (
|
|
53
|
+
<div style={{ fontSize: '0.85em', color: 'var(--text-secondary-color)' }}>
|
|
54
|
+
<div><strong>Config:</strong> {JSON.stringify(rowData.config)}</div>
|
|
55
|
+
<div><strong>Results:</strong> {JSON.stringify(rowData.results)}</div>
|
|
56
|
+
</div>
|
|
57
|
+
)} />
|
|
58
|
+
</DataTable>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Button } from 'primereact/button';
|
|
3
|
+
import { InputText } from 'primereact/inputtext';
|
|
4
|
+
|
|
5
|
+
export interface TestFieldDef {
|
|
6
|
+
name: string;
|
|
7
|
+
type: string;
|
|
8
|
+
units?: string;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
source?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TestDefinition {
|
|
14
|
+
project_fields: TestFieldDef[];
|
|
15
|
+
config_fields: TestFieldDef[];
|
|
16
|
+
cycle_fields: TestFieldDef[];
|
|
17
|
+
results_fields: TestFieldDef[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TestSetupFormProps {
|
|
21
|
+
projectId: string;
|
|
22
|
+
definitionId: string;
|
|
23
|
+
schema: TestDefinition;
|
|
24
|
+
onStartTest: (config: any) => void;
|
|
25
|
+
onCancel?: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const TestSetupForm: React.FC<TestSetupFormProps> = ({ schema, onStartTest, onCancel }) => {
|
|
29
|
+
const [config, setConfig] = useState<any>({});
|
|
30
|
+
const [isValid, setIsValid] = useState(false);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
let valid = true;
|
|
34
|
+
const allFields = [...schema.project_fields, ...schema.config_fields];
|
|
35
|
+
|
|
36
|
+
for (const field of allFields) {
|
|
37
|
+
if (field.required && !field.source) {
|
|
38
|
+
if (config[field.name] === undefined || config[field.name] === '') {
|
|
39
|
+
valid = false;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
setIsValid(valid);
|
|
45
|
+
}, [config, schema]);
|
|
46
|
+
|
|
47
|
+
const renderField = (field: TestFieldDef) => {
|
|
48
|
+
if (field.source) {
|
|
49
|
+
return (
|
|
50
|
+
<div key={field.name} className="ac-form-span p-inputgroup">
|
|
51
|
+
<span className="p-inputgroup-addon">{field.name}</span>
|
|
52
|
+
<InputText value={`Auto-fetched from ${field.source}`} disabled />
|
|
53
|
+
{field.units && <span className="p-inputgroup-addon">{field.units}</span>}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div key={field.name} className="ac-form-span p-inputgroup">
|
|
60
|
+
<span className="p-inputgroup-addon">{field.name} {field.required ? '*' : ''}</span>
|
|
61
|
+
<InputText
|
|
62
|
+
value={config[field.name] || ''}
|
|
63
|
+
onChange={(e) => setConfig({...config, [field.name]: e.target.value})}
|
|
64
|
+
placeholder={`Enter ${field.name}`}
|
|
65
|
+
/>
|
|
66
|
+
{field.units && <span className="p-inputgroup-addon">{field.units}</span>}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="ac-form-grid" style={{ padding: '1.25rem' }}>
|
|
73
|
+
<h3 className="ac-form-section">Project Information</h3>
|
|
74
|
+
{schema.project_fields.map(renderField)}
|
|
75
|
+
|
|
76
|
+
<h3 className="ac-form-section" style={{ marginTop: '1rem' }}>Test Configuration</h3>
|
|
77
|
+
{schema.config_fields.map(renderField)}
|
|
78
|
+
|
|
79
|
+
<div className="ac-form-wide" style={{ display: 'flex', gap: '1rem', marginTop: '2rem' }}>
|
|
80
|
+
<Button
|
|
81
|
+
label="Start Test"
|
|
82
|
+
icon="pi pi-play"
|
|
83
|
+
severity="success"
|
|
84
|
+
disabled={!isValid}
|
|
85
|
+
onClick={() => onStartTest(config)}
|
|
86
|
+
/>
|
|
87
|
+
{onCancel && (
|
|
88
|
+
<Button
|
|
89
|
+
label="Cancel"
|
|
90
|
+
icon="pi pi-times"
|
|
91
|
+
severity="secondary"
|
|
92
|
+
onClick={onCancel}
|
|
93
|
+
/>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
};
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useState, useEffect, useContext, useCallback } from 'react';
|
|
2
|
+
import { EventEmitterContext } from '../core/EventEmitterContext';
|
|
3
|
+
import { MessageType } from '../hub/CommandMessage';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook to read, write, and subscribe to a value in a generic servelet like MemoryStore or GNV.
|
|
7
|
+
*/
|
|
8
|
+
export function useServeletData<T = any>(domain: string, key: string, defaultValue?: T) {
|
|
9
|
+
const { invoke, subscribe, unsubscribe, isConnected } = useContext(EventEmitterContext);
|
|
10
|
+
const [value, setValue] = useState<T | undefined>(defaultValue);
|
|
11
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
12
|
+
const [error, setError] = useState<string | null>(null);
|
|
13
|
+
|
|
14
|
+
const fqdn = `${domain}.${key}`;
|
|
15
|
+
|
|
16
|
+
const fetchValue = useCallback(async () => {
|
|
17
|
+
if (!isConnected()) return;
|
|
18
|
+
try {
|
|
19
|
+
setIsLoading(true);
|
|
20
|
+
const resp = await invoke(fqdn, MessageType.Read, {});
|
|
21
|
+
if (resp.success) {
|
|
22
|
+
setValue(resp.data as T);
|
|
23
|
+
setError(null);
|
|
24
|
+
} else {
|
|
25
|
+
setError(resp.error_message);
|
|
26
|
+
}
|
|
27
|
+
} catch (err: any) {
|
|
28
|
+
setError(err.message);
|
|
29
|
+
} finally {
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
}
|
|
32
|
+
}, [fqdn, invoke, isConnected]);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
let subId: number | null = null;
|
|
36
|
+
let connSubId: number | null = null;
|
|
37
|
+
let isMounted = true;
|
|
38
|
+
|
|
39
|
+
const setup = () => {
|
|
40
|
+
fetchValue();
|
|
41
|
+
subId = subscribe(fqdn, (msg) => {
|
|
42
|
+
if (isMounted) {
|
|
43
|
+
setValue(msg.data as T);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (isConnected()) {
|
|
49
|
+
setup();
|
|
50
|
+
} else {
|
|
51
|
+
connSubId = subscribe("HUB/connected", () => {
|
|
52
|
+
if (isMounted) setup();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
isMounted = false;
|
|
58
|
+
if (subId !== null) unsubscribe(subId);
|
|
59
|
+
if (connSubId !== null) unsubscribe(connSubId);
|
|
60
|
+
};
|
|
61
|
+
}, [fqdn, fetchValue, subscribe, unsubscribe, isConnected]);
|
|
62
|
+
|
|
63
|
+
const writeValue = useCallback(async (newValue: T) => {
|
|
64
|
+
try {
|
|
65
|
+
// Use invoke directly to avoid Hub's default behavior of wrapping the payload in { value: ... }
|
|
66
|
+
const resp = await invoke(fqdn, MessageType.Write, newValue as any);
|
|
67
|
+
if (!resp.success) {
|
|
68
|
+
throw new Error(resp.error_message);
|
|
69
|
+
}
|
|
70
|
+
} catch (err: any) {
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}, [fqdn, invoke]);
|
|
74
|
+
|
|
75
|
+
return { value, write: writeValue, isLoading, error, refresh: fetchValue };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read, write, and subscribe to a non-volatile key in the Global Non-Volatile (GNV) store.
|
|
80
|
+
* @param group The config group (e.g. "app")
|
|
81
|
+
* @param key The config key (e.g. "description")
|
|
82
|
+
* @param defaultValue Optional fallback before load
|
|
83
|
+
*/
|
|
84
|
+
export function useGnv<T = any>(group: string, key: string, defaultValue?: T) {
|
|
85
|
+
return useServeletData<T>('gnv', `${group}.${key}`, defaultValue);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Read, write, and subscribe to a volatile key in the MemoryStore.
|
|
90
|
+
* @param key The cache key (e.g. "temp_state")
|
|
91
|
+
* @param defaultValue Optional fallback before load
|
|
92
|
+
*/
|
|
93
|
+
export function useMemoryStore<T = any>(key: string, defaultValue?: T) {
|
|
94
|
+
return useServeletData<T>('memorystore', key, defaultValue);
|
|
95
|
+
}
|