Browse Source

use config structure from frontend

Martin Kunz 1 month ago
parent
commit
b7164b6c39

File diff suppressed because it is too large
+ 401 - 418
package-lock.json


+ 7 - 8
package.json

@@ -15,25 +15,24 @@
   "dependencies": {
     "@guolao/vue-monaco-editor": "^1.5.1",
     "bootstrap": "^5.3.3",
-    "fast-xml-parser": "^4.3.5",
+    "fast-xml-parser": "^4.3.6",
     "jszip": "^3.10.1",
     "pinia": "^2.1.7",
-    "vue": "^3.4.21",
-    "yaml": "^2.4.1"
+    "vue": "^3.4.21"
   },
   "devDependencies": {
-    "@rushstack/eslint-patch": "^1.7.2",
+    "@rushstack/eslint-patch": "^1.8.0",
     "@tsconfig/node18": "^18.2.2",
-    "@types/node": "^20.11.26",
+    "@types/node": "^20.11.30",
     "@vitejs/plugin-vue": "^5.0.4",
     "@vue/eslint-config-typescript": "^13.0.0",
     "@vue/tsconfig": "^0.5.1",
     "eslint": "^8.57.0",
     "eslint-plugin-vue": "^9.23.0",
     "npm-run-all": "^4.1.5",
-    "typescript": "~5.4.2",
-    "vite": "^5.1.6",
+    "typescript": "~5.4.3",
+    "vite": "^5.2.4",
     "vitest": "^1.4.0",
-    "vue-tsc": "^2.0.6"
+    "vue-tsc": "^2.0.7"
   }
 }

+ 1 - 1
src/App.vue

@@ -28,7 +28,7 @@ async function load(): Promise<AddressSpace> {
 
   const loadData = async () => {
     const store = useStore();
-    store.serverConfig = ServerConfig.load({});
+    store.config = ServerConfig.load({});
     store.addressSpace=await load();
     assert(store.addressSpace)
     const rootNode=store.addressSpace.findNode("ns=0;i=84");

+ 54 - 44
src/ServerConfig.ts

@@ -1,55 +1,65 @@
-import { type DynamicNodeData} from '@/ua/DynamicNode';
-
 export class ServerConfig {
 	public configData;
 
-    constructor(configData: ServerConfigData) {
+    constructor(configData: IConfig) {
         this.configData=configData;
     }
 
-    public static load(obj: any) :ServerConfig{
-        const d:ServerConfigData={};
-        d.manufacturerName=obj.manufacturerName||"";
-        d.productName=obj.productName||"";
-        d.softwareVersion=obj.softwareVersion||"";
-        d.applicationUri=obj.applicationUri||"";
-        d.productUri=obj.productUri||"";
-        d.applicationName=obj.applicationName||"";
-        d.allowAnonymous=obj.allowAnonymous||false;
-        d.dynamics=obj.dynamics||[];
-        d.nodesets=obj.nodesets||[];
-        return new ServerConfig(d);
+    public static load(obj: any) :IConfig{
+        return {
+            manufacturerName:obj.manufacturerName||"",
+            productName:obj.productName||"",
+            softwareVersion:obj.softwareVersion||"",
+            applicationUri:obj.applicationUri||"",
+            productUri:obj.productUri||"",
+            applicationName:obj.applicationName||"",
+            allowAnonymous:obj.allowAnonymous||false,
+            dynamics:obj.dynamics||[],
+            nodesets:obj.nodesets||[],
+        } as IConfig;
     }
+}
 
-    addDynamic(dyn:DynamicNodeData){
-        this.configData.dynamics?.forEach((dyn)=>{
-            if(dyn.ident == dyn.ident){
-                return false;
-            }
-        })
-        this.configData.dynamics?.push(dyn);
-        return true;
-    }
+export interface IMappingEntry {
+    path: string;
+    read: string;
+	write: string;
+}
 
-    removeDynamic(ident:string){
-        this.configData.dynamics?.forEach((dyn, idx)=>{
-            if(dyn.ident == ident){
-                this.configData.dynamics?.splice(idx,1);
-                return true;
-            }
-        })
-        return false;
-    }
+export interface IComponentEntry {
+	"ident": string,
+	"namespaceUri": string,
+	"nodeClass": string,
+	"typeNodeId": string,
+	"mandatory": IComponentEntry[]
+	"optionals": IComponentEntry[]
+}
+
+export interface IDynamicConfig {
+	"ident": string
+	"namespaceUri": string
+	"parentNodeId": string
+	"checkInterval": number
+	"nodeClass": string
+	"typeNodeId": string
+	"name": string
+	"startIndex": number,
+	"mandatory": IComponentEntry[]
+	"optionals": IComponentEntry[]
+	"nodeVersionId": string
 }
-export interface ServerConfigData {
-	manufacturerName?: string;
-    productName?: string;
-    softwareVersion?: string;
-    applicationUri?: string;
-    productUri?: string;
-    applicationName?: string;
-    allowAnonymous?: boolean;
-    maxConnections?: number;
-    dynamics?:DynamicNodeData[];
-    nodesets?:string[];
+
+export interface IConfig{
+	"manufacturerName": string
+	"productName": string
+	"softwareVersion": string
+	"applicationUri": string
+	"productUri": string
+	"applicationName": string
+	"port": number,
+	"maxConnections": number,
+	"allowAnonymous": boolean,
+	"nodesets": string[],
+	"mapping": IMappingEntry[]
+	"dynamics": IDynamicConfig[]
 }

+ 2 - 2
src/components/TheDynamics.vue

@@ -34,7 +34,7 @@ const typedef = ref("")
 watch(selectedNode, async (newNode, _oldNode) => {
   if(!newNode)
     return;
-  let dynConfig=store.serverConfig.configData.dynamics?.find((d) => {d.parentNodeId==newNode.nodeId.toString()})||{}
+  let dynConfig=store.config.dynamics?.find((d) => {d.parentNodeId==newNode.nodeId.toString()})||{}
   dynNode.value=dynConfig;
   //TODO: what id does/should typedef hold?
   typedef.value=newNode.nodeId.toString();
@@ -86,7 +86,7 @@ function getNodeVersion(nid:string){
 
 
 function okPressed() {
-  store.serverConfig.addDynamic(dynNode.value);
+  // store.config.addDynamic(dynNode.value);
 }
 defineExpose({ okPressed })
 </script>

+ 2 - 2
src/components/TheMapping.vue

@@ -12,8 +12,8 @@ import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker"
 import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker"
 import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker"
 import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"
-import type { IMappingValue } from '@/ua/IAddressSpace';
 import { storeToRefs } from 'pinia';
+import type { IMappingEntry } from '@/ServerConfig';
 self.MonacoEnvironment = {
   getWorker(_, label) {
     if (label === "json") {
@@ -74,7 +74,7 @@ function okPressed() {
     return;
   if(!selectedNode.value)
     return;
-  store.addressSpace?.mapping.set(selectedNode.value.nodeId.toString(), {path: selectedNode.value.nodeId.toString(), read: code.value, write: ""} as IMappingValue)
+  store.addressSpace?.mapping.set(selectedNode.value.nodeId.toString(), {path: selectedNode.value.nodeId.toString(), read: code.value, write: ""} as IMappingEntry)
   dirty=false;
 }
 defineExpose({ okPressed })

+ 16 - 23
src/components/TheModels.vue

@@ -6,19 +6,14 @@ import { useStore } from '@/store'
 import { ref } from 'vue';
 import VDialog from './VDialog.vue'
 import JSZip from "jszip";
-import YAML from 'yaml'
 import { assert } from '@/util/assert';
-import { storeToRefs } from 'pinia';
-import type { IMappingValue } from '@/ua/IAddressSpace';
-import { ServerConfig } from '@/ServerConfig';
+import { ServerConfig, type IMappingEntry } from '@/ServerConfig';
 
 const store = useStore()
 const newProjectOpen = ref(false);
 const newProjectDisabled = ref(false); //Disables buttons while loading project
 const serverConfigOpen = ref(false);
 const projectType = ref("ua");
-const { serverConfig } = storeToRefs(store)
-
 
 async function exportProject() {
         const zip = new JSZip();
@@ -28,10 +23,9 @@ async function exportProject() {
             fileNames.push(ns.fileName);
             zip.file(ns.fileName, ns.toXML(ns.nameSpaceTable, as.nst).toString());
         }
-        store.serverConfig.configData.nodesets = fileNames;
-        zip.file("project.json", JSON.stringify(store.serverConfig.configData));
-        const mapString=YAML.stringify(as.mapping.values());
-        zip.file("mapping.yaml", mapString)
+        store.config.nodesets = fileNames;
+        store.config.mapping= [...as.mapping.values()]
+        zip.file("project.json", JSON.stringify(store.config));
         const blob =await zip.generateAsync({type:'blob'});
         downloadBlob(blob, "project.zip");
 }
@@ -88,16 +82,15 @@ async function  loadZip(file: File){
   const config=ServerConfig.load(JSON.parse(await zip.files["project.json"].async("string")));
 
   const as=new AddressSpace([]);
-  for(const fileName of config.configData.nodesets||[]) {
+  for(const fileName of config.nodesets||[]) {
     as.addNodeset(UANodeSet.parse(await zip.files[fileName].async("string"), fileName, as));
   }
-  const mlist=YAML.parse(await zip.files['mapping.yaml'].async("string")) as IMappingValue[];
-  const mapping=new Map<string, IMappingValue>();
-  for(const entry of mlist) {
+  const mapping=new Map<string, IMappingEntry>();
+  for(const entry of config.mapping) {
     mapping.set(entry.path, entry);
   }
   as.mapping=mapping;
-  store.serverConfig=config;
+  store.config=config;
   store.setAddressSpace(as);
 }
 </script>
@@ -168,46 +161,46 @@ async function  loadZip(file: File){
   <v-dialog :open="serverConfigOpen" @cancel="serverConfigOpen = false">
     <h5>Server Configuration</h5>
     <p></p>
-    <div v-if="serverConfig">
+    <div v-if="store.config">
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Manufacturer Name</span>
             </div>
-            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.manufacturerName">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="store.config.manufacturerName">
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Product Name</span>
             </div>
-            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.productName">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="store.config.productName">
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Software Version</span>
             </div>
-            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.softwareVersion">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="store.config.softwareVersion">
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Application Name</span>
             </div>
-            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.applicationName">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="store.config.applicationName">
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Application Uri</span>
             </div>
-            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.applicationUri">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="store.config.applicationUri">
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Product Uri</span>
             </div>
-            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.productUri">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="store.config.productUri">
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend form-check">
-            <input class="form-check-input" type="checkbox" id="checkAllowAnon" v-model="serverConfig.configData.allowAnonymous" />
+            <input class="form-check-input" type="checkbox" id="checkAllowAnon" v-model="store.config.allowAnonymous" />
             <label class="form-check-label" for="checkAllowAnon">Allow Anonymous</label>
           </div>
         </div>

+ 1 - 1
src/store.ts

@@ -11,7 +11,7 @@ export const useStore = defineStore('user', {
       addressSpace: new AddressSpace([]) as IAddressSpace,
       rootNode: ref<UABaseNode | null>(null),
       selectedNode: ref<UABaseNode | null>(null),
-      serverConfig: ServerConfig.load({})
+      config: ServerConfig.load({})
   }),
   actions: {
     setAddressSpace(as: AddressSpace) {

+ 3 - 2
src/ua/AddressSpace.ts

@@ -1,14 +1,15 @@
 import { UANodeSet } from "./UANodeSet";
 import { UABaseNode } from "./UABaseNode";
 import { NamespaceTable } from "./NameSpaceTable";
-import { type IAddressSpace, type IMappingValue } from "./IAddressSpace";
+import { type IAddressSpace } from "./IAddressSpace";
+import type { IMappingEntry } from "@/ServerConfig";
 
 export class AddressSpace implements IAddressSpace{
 
     nodeMap:  Map<string, UABaseNode>;
     nst: NamespaceTable;
     nodesets: UANodeSet[];
-    mapping:  Map<string, IMappingValue>;
+    mapping:  Map<string, IMappingEntry>;
 
     constructor(nodesets: UANodeSet[]) {
         this.nst=new NamespaceTable();

+ 2 - 7
src/ua/IAddressSpace.ts

@@ -1,3 +1,4 @@
+import type { IMappingEntry } from "@/ServerConfig";
 import type { NamespaceTable } from "./NameSpaceTable";
 import { UABaseNode } from "./UABaseNode";
 import type { UANodeSet } from "./UANodeSet";
@@ -6,16 +7,10 @@ import type { UANodeSet } from "./UANodeSet";
 export interface IAddressSpace {
     nst: NamespaceTable
     nodesets: UANodeSet[];
-    mapping:  Map<string, IMappingValue>;
+    mapping:  Map<string, IMappingEntry>;
     getSubTreeAsList(nodeId: string): UABaseNode[];
     findNode(nodeId: string):UABaseNode|undefined;
     addNodeset(nodeset: UANodeSet):void;
     getNodeSets(): UANodeSet[];
-
 }
 
-export interface IMappingValue {
-    path: string;
-    read: string;
-    write: string;
-  }