Parcourir la source

load/export server/project config

Martin Kunz il y a 1 an
Parent
commit
6d3313f5e5

+ 1 - 1
src/App.vue

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

+ 10 - 10
src/components/TheDynamics.vue

@@ -5,7 +5,7 @@ import { UABaseNode } from '@/ua/UABaseNode';
 import { ObjectIds, ReferenceTypeIds } from '@/ua/opcua_node_ids';
 import type { UAReferenceType } from '@/ua/UAReferenceType';
 import type { UAVariableType } from '@/ua/UAVariableType';
-import { DynamicNode} from '@/ua/DynamicNode';
+import { DynamicNode, type DynamicNodeData} from '@/ua/DynamicNode';
 import { storeToRefs } from 'pinia';
 const store = useStore();
 const { selectedNode} = storeToRefs(store)
@@ -17,7 +17,7 @@ const nameSpaceName = computed(() => {
   return store.addressSpace?.nst.getUri(nsIdx);
 })
 
-let opt = {
+let opt: DynamicNodeData = {
   ident: "...",
   namespaceUri:"MyNamespace",
   parentNodeId: "MyNodeId",
@@ -86,13 +86,13 @@ function getNodeVersion(nid:string){
 async function createDynamic() {
   //dynNode.namespaceUri = nameSpaceName.value;
   //dynNode.parentNodeId = selectedNode.value?.nodeId.toString();
-  dynNode.value.typeNodeId = typedef.value.toString();
+  // dynNode.value.typeNodeId = typedef.value.toString();
 
  // console.log(dynNode);
   //console.log(c_man);
   //console.log(c_opt.value);
-  console.log(dynNode.value.optionals)
-  console.log(dynNode.value.o_check)
+  // console.log(dynNode.value.dynamicNodeData.optionals)
+  // console.log(dynNode.value.o_check)
   //dynNode = new DynamicNode(opt);
 
   /*
@@ -138,14 +138,14 @@ defineExpose({ okPressed })
             <span class="input-group-text" id="inputGroup-sizing-default">Name Dynamic Definition</span>
           </div>
           <input type="text" class="form-control" aria-label="Default"
-            aria-describedby="inputGroup-sizing-default" v-model="dynNode.name" v-bind:key="'...'"> 
+            aria-describedby="inputGroup-sizing-default" v-model="dynNode.dynamicNodeData.name"> 
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Update Interval in ms</span>
           </div>
           <input type="text" class="form-control" aria-label="Default"
-            aria-describedby="inputGroup-sizing-default" v-model="dynNode.checkInterval" v-bind:key="1000"> 
+            aria-describedby="inputGroup-sizing-default" v-model="dynNode.dynamicNodeData.checkInterval"> 
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
@@ -169,7 +169,7 @@ defineExpose({ okPressed })
             <span class="input-group-text" id="inputGroup-sizing-default">Ident</span>
           </div>
           <input type="text" class="form-control" aria-label="Default"
-            aria-describedby="inputGroup-sizing-default" v-model="dynNode.ident" v-bind:key="'...'"> 
+            aria-describedby="inputGroup-sizing-default" v-model="dynNode.dynamicNodeData.ident"> 
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
@@ -197,8 +197,8 @@ defineExpose({ okPressed })
         </div>
         <div>
           <ul class="no-bullets"><b>Optional Components</b>
-            <li v-for="(item1,index) of getInstanceDecl(typedef, 'Optional')" v-bind:key = item1>
-              <input type="checkbox" id="checkbox-digg-{{ item1 }}" v-model="dynNode.o_check![index]" :value="item1" :checked="dynNode.o_check![index]">
+            <li v-for="(item1) of getInstanceDecl(typedef, 'Optional')" v-bind:key = item1>
+              <input type="checkbox" id="checkbox-digg-{{ item1 }}"  :value="item1">
               <label for="checkbox-digg-{{ item1 }}">{{ item1 }}</label>
             </li>
           </ul>

+ 1 - 1
src/components/TheEditor.vue

@@ -11,7 +11,7 @@ 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/AddressSpace';
+import type { IMappingValue } from '@/ua/IAddressSpace';
 self.MonacoEnvironment = {
   getWorker(_, label) {
     if (label === "json") {

+ 32 - 25
src/components/TheModels.vue

@@ -1,6 +1,6 @@
 <!-- eslint-disable no-fallthrough -->
 <script setup lang="ts">
-import { AddressSpace, type IMappingValue } from '@/ua/AddressSpace';
+import { AddressSpace } from '@/ua/AddressSpace';
 import { UANodeSet } from '@/ua/UANodeSet';
 import { useStore } from '@/util/store'
 import { ref } from 'vue';
@@ -8,20 +8,32 @@ 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 '@/util/sconfig';
 
 const store = useStore()
 const newDialogOpen = ref(false);
 const newServerConfigOpen = ref(false);
 const newDialogDisabled = ref(false); //Disables buttons while loading project
 const projectType = ref("ua");
+const { serverConfig } = storeToRefs(store)
 
 
-let sconfig = store.sConfig;
-
 async function exportProject() {
-  const blob = await store.addressSpace?.exportProject();
-  if (blob)
-    downloadBlob(blob, "project.zip");
+        const zip = new JSZip();
+        const fileNames:string[]=[];
+        const as=store.addressSpace;
+        for(const ns of as.nodesets) {
+            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)
+        const blob =await zip.generateAsync({type:'blob'});
+        downloadBlob(blob, "project.zip");
 }
 
 function downloadBlob(blob: Blob, filename: string) {
@@ -33,12 +45,6 @@ function downloadBlob(blob: Blob, filename: string) {
   document.body.removeChild(link);
 }
 
-async function exportConf() {
-  const blob = await sconfig?.exportConfig();
-  if (blob)
-    downloadBlob(blob, "config.zip");
-}
-
 async function newProject() {
   newDialogDisabled.value = true;
   store.addressSpace = new AddressSpace([] as UANodeSet[])
@@ -79,9 +85,10 @@ async function handleDrop(e: DragEvent) {
 
 async function  loadZip(file: File){
   const zip= await JSZip.loadAsync(file.arrayBuffer())
-  const filenames=JSON.parse(await zip.files['project.json'].async("string"));
+  const config=ServerConfig.load(JSON.parse(await zip.files["project.json"].async("string")));
+
   const as=new AddressSpace([]);
-  for(const fileName of filenames) {
+  for(const fileName of config.configData.nodesets||[]) {
     as.addNodeset(await UANodeSet.parse(await zip.files[fileName].async("string"), fileName, as));
   }
   const mlist=YAML.parse(await zip.files['mapping.yaml'].async("string")) as IMappingValue[];
@@ -90,6 +97,7 @@ async function  loadZip(file: File){
     mapping.set(entry.path, entry);
   }
   as.mapping=mapping;
+  store.serverConfig=config;
   store.setAddressSpace(as);
 }
 </script>
@@ -155,64 +163,63 @@ async function  loadZip(file: File){
 
   <v-dialog :open="newServerConfigOpen" @cancel="newServerConfigOpen = false">
     <p>Server Configuration</p>
-    <div v-if="sconfig">
+    <div v-if="serverConfig">
         <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="sconfig.manufacturerName" v-bind:key="'...'">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.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="sconfig.productName" v-bind:key="'...'">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.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="sconfig.softwareVersion" v-bind:key="'...'">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.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="sconfig.applicationName" v-bind:key="'...'">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.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="sconfig.applicationUri" v-bind:key="'...'">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.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="sconfig.productUri" v-bind:key="'...'">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.productUri">
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Port</span>
             </div>
-            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="sconfig.port" v-bind:key="4840">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.port">
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
             <span class="input-group-text" id="inputGroup-sizing-default">Max Connections Per Endpoint</span>
             </div>
-            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="sconfig.maxConnections" v-bind:key="100">
+            <input type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" v-model="serverConfig.configData.maxConnections">
         </div>
         <div class="input-group mb-3">
           <div class="input-group-prepend">
-            <input type="checkbox" id="checkbox" v-model="sconfig.allowAnonymous" />
+            <input type="checkbox" id="checkbox" v-model="serverConfig.configData.allowAnonymous" />
             <label for="checkbox"> Allow Anonymous</label>
           </div>
         </div>
        
       </div>
-    <button @click="exportConf()"> Export</button>
     <button @click="newServerConfigOpen = false" :disabled="newDialogDisabled">Close</button>
   </v-dialog>
 </template>

+ 1 - 0
src/components/TheParent.vue

@@ -24,6 +24,7 @@ watch(node, async (newNode, _oldNode) => {
 })
 
 function okPressed() {
+  //TODO: check if valid
   node.value?.setParent(selectedParent.value, selectedRefType.value);
 }
 defineExpose({ okPressed })

+ 5 - 22
src/ua/AddressSpace.ts

@@ -1,9 +1,7 @@
 import { UANodeSet } from "./UANodeSet";
 import { UABaseNode } from "./UABaseNode";
 import { NamespaceTable } from "./NameSpaceTable";
-import YAML from 'yaml'
-import JSZip from "jszip";
-import { type IAddressSpace } from "./IAddressSpace";
+import { type IAddressSpace, type IMappingValue } from "./IAddressSpace";
 
 export class AddressSpace implements IAddressSpace{
 
@@ -12,7 +10,6 @@ export class AddressSpace implements IAddressSpace{
     nodesets: UANodeSet[];
     mapping:  Map<string, IMappingValue>;
 
-
     constructor(nodesets: UANodeSet[]) {
         this.nst=new NamespaceTable();
         this.nodeMap=new Map();
@@ -22,6 +19,9 @@ export class AddressSpace implements IAddressSpace{
             this.addNodeset(nodeset);
         }
     }
+    getNodeSets(): UANodeSet[]{
+        return this.nodesets;
+    }
 
     public findNode(nodeId: string):UABaseNode|undefined {
         return this.nodeMap.get(nodeId)
@@ -38,7 +38,6 @@ export class AddressSpace implements IAddressSpace{
         }
         return nlist;
     }
-
     
     public  addNodeset(nodeset: UANodeSet) {
         nodeset.reIndex(this.nst);
@@ -60,25 +59,9 @@ export class AddressSpace implements IAddressSpace{
         }
         return as;
     }
-
-    public exportProject() {
-        const zip = new JSZip();
-        const fileNames:string[]=[];
-        for(const ns of this.nodesets) {
-            fileNames.push(ns.fileName);
-            zip.file(ns.fileName, ns.toXML(ns.nameSpaceTable, this.nst).toString());
-        }
-        const mapString=YAML.stringify(this.mapping.values());
-        zip.file("mapping.yaml", mapString)
-        return zip.generateAsync({type:'blob'});
-    }
 }
 
 
-export interface IMappingValue {
-    path: string;
-    read: string;
-    write: string;
-  }
+
 
 

+ 12 - 51
src/ua/DynamicNode.ts

@@ -1,46 +1,7 @@
-
-
-
 export class DynamicNode {
-    public ident: string;
-    public namespaceUri: string;
-    public parentNodeId : string;
-    public checkInterval :number;
-    public nodeClass : string;
-    public typeNodeId : string;
-    public name : string;
-    public startIndex:number;
-    public mandatory:  string[]=[];
-    public optionals?: string[]=[];
-    public o_check?: boolean[]=[];
-    public nodeVersionId?: string;
-
-
-    constructor(options: DynamicNodeOptions) {
-        this.ident = options.ident;
-        this.namespaceUri =  options.namespaceUri;
-        this.parentNodeId = options.parentNodeId;
-        this.checkInterval = options.checkInterval;
-        this.nodeClass =  options.nodeClass;
-        this.typeNodeId = options.typeNodeId;
-        this.name =  options.name;
-        this.startIndex = options.startIndex;  
-    }
-
-    toPlainObject():any{
-        return { 
-            ident: this.ident,
-            namespaceUri: this.namespaceUri,
-            parentNodeId : this.parentNodeId,
-            checkInterval : this.checkInterval,
-            nodeClass : this.nodeClass,
-            typeNodeId : this.typeNodeId,
-            name : this.name,
-            startIndex: this.startIndex,
-            mandatory: this.mandatory,
-            optionals: this.optionals,
-            nodeVersionId: this.nodeVersionId
-        }
+    public dynamicNodeData: DynamicNodeData={};
+    constructor(ndata:DynamicNodeData) {
+        this.dynamicNodeData=ndata;
     }
 }
 
@@ -54,13 +15,13 @@ export class ComponentPair{
     }
 }
 
-export interface DynamicNodeOptions {
-    ident:string;
-    namespaceUri:string;
-    parentNodeId:string;
-    checkInterval:number;
-    nodeClass:string;
-    typeNodeId:string;
-    name:string;
-    startIndex:number;
+export interface DynamicNodeData {
+    ident?:string;
+    namespaceUri?:string;
+    parentNodeId?:string;
+    checkInterval?:number;
+    nodeClass?:string;
+    typeNodeId?:string;
+    name?:string;
+    startIndex?:number;
 }

+ 12 - 0
src/ua/IAddressSpace.ts

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

+ 2 - 2
src/ua/UAMethod.ts

@@ -1,7 +1,7 @@
 import { XMLElem } from "@/util/XmlElem";
 import { UABaseNode, type UABaseNodeOptions } from "./UABaseNode";
 import type { NamespaceTable } from "./NameSpaceTable";
-import type { AddressSpace } from "./AddressSpace";
+import type { IAddressSpace } from "./IAddressSpace";
 
 export class UAMethod extends UABaseNode {
     constructor(options: UAMethodNodeOptions) {
@@ -9,7 +9,7 @@ export class UAMethod extends UABaseNode {
                     this.nodeClass="Method";
     }
 
-    static  fromXML(uaMethod: any, addressSpace: AddressSpace): UAMethod{
+    static  fromXML(uaMethod: any, addressSpace: IAddressSpace): UAMethod{
         const bn=super.fromXML(uaMethod, addressSpace)
         return new UAMethod({nodeId: bn.nodeId, 
                             browseName: bn.browseName, 

+ 2 - 1
src/ua/UANodeSet.ts

@@ -10,6 +10,7 @@ import { UAReferenceType } from './UAReferenceType';
 import { UAObjectType } from './UAObjectType';
 import { UAVariableType } from './UAVariableType';
 import type { AddressSpace } from './AddressSpace';
+import type { IAddressSpace } from './IAddressSpace';
 
 export class UANodeSet implements IToXML{
 
@@ -59,7 +60,7 @@ export class UANodeSet implements IToXML{
         const fileName= url.split('/').pop()||url
         return this.parse(xml, fileName, addressSpace);
     }
-    static async parse(xml: string, fileName: string, addressSpace: AddressSpace): Promise<UANodeSet> {
+    static async parse(xml: string, fileName: string, addressSpace: IAddressSpace): Promise<UANodeSet> {
         const parseOptions:Partial<X2jOptions>={
             ignoreAttributes: false,
             alwaysCreateTextNode: true, //force consistent result

+ 2 - 5
src/ua/UAObjectType.ts

@@ -1,10 +1,7 @@
 import { XMLElem } from "@/util/XmlElem";
 import type { NamespaceTable } from "./NameSpaceTable";
 import { UABaseNode, type UABaseNodeOptions } from "./UABaseNode";
-import type { AddressSpace } from "./AddressSpace";
-
-
-
+import type { IAddressSpace } from "./IAddressSpace";
 
 export class UAObjectType extends UABaseNode{
     public isAbstract: boolean;
@@ -15,7 +12,7 @@ export class UAObjectType extends UABaseNode{
         this.isAbstract=options.isAbstract||false;
     }
 
-    static  fromXML(xmlObjType: any, addressSpace: AddressSpace): UAObjectType{
+    static  fromXML(xmlObjType: any, addressSpace: IAddressSpace): UAObjectType{
         const bn=super.fromXML(xmlObjType, addressSpace)
         return new UAObjectType({nodeId: bn.nodeId, 
                             browseName: bn.browseName, 

+ 2 - 2
src/ua/UAReferenceType.ts

@@ -1,7 +1,7 @@
 import { XMLElem } from "@/util/XmlElem";
 import type { NamespaceTable } from "./NameSpaceTable";
 import { UABaseNode, type UABaseNodeOptions } from "./UABaseNode";
-import type { AddressSpace } from "./AddressSpace";
+import type { IAddressSpace } from "./IAddressSpace";
 
 export class UAReferenceType extends UABaseNode{
     public isAbstract: boolean;
@@ -11,7 +11,7 @@ export class UAReferenceType extends UABaseNode{
         this.isAbstract=options.isAbstract||false;
     }
 
-    static  fromXML(xmlRefType: any, addressSpace: AddressSpace): UAReferenceType{
+    static  fromXML(xmlRefType: any, addressSpace: IAddressSpace): UAReferenceType{
         const bn=super.fromXML(xmlRefType, addressSpace)
         return new UAReferenceType({nodeId: bn.nodeId, 
                             browseName: bn.browseName, 

+ 2 - 2
src/ua/UAVariable.ts

@@ -1,7 +1,7 @@
 import { XMLElem } from "@/util/XmlElem";
 import { UABaseNode, type UABaseNodeOptions } from "./UABaseNode";
 import type { NamespaceTable } from "./NameSpaceTable";
-import type { AddressSpace } from "./AddressSpace";
+import type { IAddressSpace } from "./IAddressSpace";
 
 
 
@@ -13,7 +13,7 @@ export class UAVariable extends UABaseNode {
                     this.nodeClass="Variable";
     }
 
-    static  fromXML(uaObject: any, addressSpace: AddressSpace): UAVariable{
+    static  fromXML(uaObject: any, addressSpace: IAddressSpace): UAVariable{
         const bn=super.fromXML(uaObject, addressSpace)
         return new UAVariable({nodeId: bn.nodeId, 
                                 browseName: bn.browseName, 

+ 2 - 3
src/ua/UAVariableType.ts

@@ -1,12 +1,11 @@
 import { XMLElem } from "@/util/XmlElem";
 import type { NamespaceTable } from "./NameSpaceTable";
 import { UABaseNode, type UABaseNodeOptions } from "./UABaseNode";
-import type { AddressSpace } from "./AddressSpace";
+import type { IAddressSpace } from "./IAddressSpace";
 
 export class UAVariableType extends UABaseNode{
     public isAbstract: boolean;
     
-
     constructor(options: UAVariableTypeOptions) {
         super(options)
         this.isAbstract=options.isAbstract||false;
@@ -14,7 +13,7 @@ export class UAVariableType extends UABaseNode{
  
     }
 
-    static  fromXML(xmlObjType: any, addressSpace: AddressSpace): UAVariableType{
+    static  fromXML(xmlObjType: any, addressSpace: IAddressSpace): UAVariableType{
         const bn=super.fromXML(xmlObjType, addressSpace)
         return new UAVariableType({nodeId: bn.nodeId, 
                             browseName: bn.browseName, 

+ 0 - 1
src/util/aggregates.ts

@@ -1 +0,0 @@
-export const aggregates = ["HasComponent","HasProperty","HasAddIn","HasOrderedComponent","HasPhysicalComponent"];

+ 36 - 76
src/util/sconfig.ts

@@ -1,98 +1,58 @@
-import { DynamicNode} from '@/ua/DynamicNode';
-import JSZip from "jszip";
-
+import { type DynamicNodeData} from '@/ua/DynamicNode';
 
 export class ServerConfig {
-	public manufacturerName: string;
-    public productName: string;
-    public softwareVersion: string;
-    public applicationUri: string;
-    public productUri: string;
-    public applicationName: string;
-    public port: number;
-    public allowAnonymous: boolean;
-    public maxConnections: number;
-    public dynamics?:DynamicNode[]=[];
-    public nodesets?:string[]=[];
-
-
+	public configData;
 
-    constructor() {
-        this.manufacturerName =  "...";
-        this.productName = "...";
-        this.softwareVersion =  "...";
-        this.applicationUri=  "...";
-        this.productUri = "...";
-        this.applicationName =  "...";
-        this.allowAnonymous = true;
-        this.port = 4840;
-        this.maxConnections = 100;
+    constructor(configData: ServerConfigData) {
+        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.port=obj.port||4096;
+        d.allowAnonymous=obj.allowAnonymous||false;
+        d.maxConnections=obj.maxConnections||1000;
+        d.dynamics=obj.dynamics||[];
+        d.nodesets=obj.nodesets||[];
+        return new ServerConfig(d);
+    }
 
-    addDynamic(dyn:DynamicNode){
-        this.dynamics?.forEach((dyn)=>{
+    addDynamic(dyn:DynamicNodeData){
+        this.configData.dynamics?.forEach((dyn)=>{
             if(dyn.ident == dyn.ident){
                 return false;
             }
         })
-        this.dynamics?.push(dyn);
+        this.configData.dynamics?.push(dyn);
         return true;
     }
 
     removeDynamic(ident:string){
-        this.dynamics?.forEach((dyn, idx)=>{
+        this.configData.dynamics?.forEach((dyn, idx)=>{
             if(dyn.ident == ident){
-                this.dynamics?.splice(idx,1);
+                this.configData.dynamics?.splice(idx,1);
                 return true;
             }
         })
         return false;
     }
-
-    toPlainObject(){
-        const obj = {
-            manufacturerName: this.manufacturerName,
-            productName: this.productName,
-            softwareVersion: this.softwareVersion,
-            applicationUri: this.applicationUri,
-            productUri: this.productUri,
-            applicationName: this.applicationName,
-            port: this.port,
-            maxConnections: this.maxConnections,
-            allowAnonymous: this.allowAnonymous,
-            dynamics: [{}]
-        }    
-        this.dynamics?.forEach((d)=>{
-            obj.dynamics.push(d.toPlainObject());
-        })
-        return obj;
-    }
-
-
-    public exportConfig() {
-        try{
-            const zip = new JSZip();
-            zip.file("serverconfig.json", JSON.stringify(this.toPlainObject()));
-            return zip.generateAsync({type:'blob'});}
-        catch(err){
-            console.error(err);
-        }
-    }
 }
-
-
-export interface ServerConfigOptions {
-	manufacturerName: string;
-    productName: string;
-    softwareVersion: string;
-    applicationUri: string;
-    productUri: string;
-    applicationName: string;
-    port: number;
-    allowAnonymous: boolean;
-    maxConnections: number;
-    dynamics:DynamicNode[];
-    nodesets:string[];
-    
+export interface ServerConfigData {
+	manufacturerName?: string;
+    productName?: string;
+    softwareVersion?: string;
+    applicationUri?: string;
+    productUri?: string;
+    applicationName?: string;
+    port?: number;
+    allowAnonymous?: boolean;
+    maxConnections?: number;
+    dynamics?:DynamicNodeData[];
+    nodesets?:string[];
 }

+ 4 - 4
src/util/store.ts

@@ -3,15 +3,15 @@ import { type UABaseNode } from '@/ua/UABaseNode'
 import type { UANodeSet } from '@/ua/UANodeSet'
 import { defineStore } from 'pinia'
 import { ref } from 'vue'
-import type { ServerConfig } from './sconfig'
-
+import { ServerConfig } from './sconfig'
+import type { IAddressSpace } from '@/ua/IAddressSpace'
 
 export const useStore = defineStore('user', {
   state: () => ({
-      addressSpace: null as AddressSpace | null,
+      addressSpace: new AddressSpace([]) as IAddressSpace,
       rootNode: ref<UABaseNode | null>(null),
       selectedNode: ref<UABaseNode | null>(null),
-      sConfig: null as ServerConfig | null
+      serverConfig: ServerConfig.load({})
   }),
   actions: {
     setAddressSpace(as: AddressSpace) {