diff --git a/.vscode/launch.json b/.vscode/launch.json index d4b09b60..a1dc709f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -28,10 +28,7 @@ "CONTENT_ROOT": "projects/aas-server/build", "WEB_ROOT": "projects/aas-portal/dist", "ASSETS": "projects/aas-server/src/assets", - "USER_STORAGE": "mongodb://localhost:27017/aasportal-users", - "FILE_STORAGE": "http://aasportal:aas-server@localhost:1234", - "AAS_INDEX": "mysql://aasportal:aas-server@localhost:3306", - "ENDPOINTS": "[\"file:///samples?name=Samples\"]", + "ENDPOINTS": "[\"file:///endpoints/samples?name=Samples\"]", } }, { @@ -79,6 +76,21 @@ "aas-server" ], }, + { + "name": "Debug aas-server test", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run-script", + "test:debug", + "-w", + "aas-server", + "--testPathPattern", + "${fileBasename}" + ], + }, { "name": "Test common", "type": "node", diff --git a/.vscode/settings.json b/.vscode/settings.json index cc1b6b3b..08c1ef1c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -34,6 +34,7 @@ "tsyringe", "Ungroup", "uuid", + "webdav", "xmldoc", "ZVEI" ], diff --git a/copyright-header.js b/copyright-header.js index d3e29d21..b1fc2a7e 100644 --- a/copyright-header.js +++ b/copyright-header.js @@ -42,7 +42,7 @@ checkFilesAsync([ async function checkFilesAsync(dirs) { const files = []; for (const dir of dirs) { - await traverseFilesAsync(dir, [".ts", ".js", ".html", ".css"], files); + await traverseFilesAsync(dir, [".ts", ".js", ".html", ".css", ".scss"], files); } for (const file of files) { @@ -110,6 +110,7 @@ async function checkFilesAsync(dirs) { case ".ts": case ".js": case ".css": + case ".scss": return hasValidJsCopyrightHeader(text); case ".html": return hasValidHtmlCopyrightHeader(text); @@ -162,6 +163,7 @@ async function checkFilesAsync(dirs) { case ".ts": case ".js": case ".css": + case ".scss": return removeJsCopyrightHeader(text); case ".html": return removeHtmlCopyrightHeader(text); @@ -254,6 +256,7 @@ async function checkFilesAsync(dirs) { case ".ts": case ".js": case ".css": + case ".scss": insertJsCopyrightHeader(text); break; case ".html": diff --git a/docs/source/gettingstarted.md b/docs/source/gettingstarted.md index 2831d180..134ca6bc 100644 --- a/docs/source/gettingstarted.md +++ b/docs/source/gettingstarted.md @@ -89,7 +89,7 @@ AASServer provides a user management. Authentication of a user is based on Json | MAX_WORKERS | Number of background worker that scan AAS containers. | 8 | | NODE_SERVER_PORT | The port number where AASServer is listening. | 80 | | USER_STORAGE | URL of the user database. | './users' | -| FILE_STORAGE | URL of the template storage | | +| TEMPLATE_STORAGE | URL of the template storage | | | TIMEOUT | Timeout until a new scan starts (ms). | 5000 | | WEB_ROOT | The root directory for static file resources. | './wwwroot' | diff --git a/package-lock.json b/package-lock.json index f4904938..2b0b33d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "aas-portal-project", - "version": "3.0.0-development.14", + "version": "3.0.0-development.17", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "aas-portal-project", - "version": "3.0.0-development.14", + "version": "3.0.0-development.17", "license": "Apache-2.0", "workspaces": [ "./projects/common", @@ -53,7 +53,6 @@ "node-opcua": "^2.119.2", "node-opcua-client-crawler": "^2.119.2", "nodemailer": "^6.9.8", - "owncloud-sdk": "^3.1.0-alpha.10", "reflect-metadata": "^0.2.1", "rxjs": "~7.8.1", "swagger-ui-express": "^5.0.0", @@ -61,6 +60,7 @@ "tsoa": "^6.0.0", "tsyringe": "^4.8.0", "uuid": "^8.3.2", + "webdav": "^4.10.0", "winston": "^3.11.0", "winston-daily-rotate-file": "^4.7.1", "ws": "^8.16.0", @@ -8497,7 +8497,8 @@ "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true }, "node_modules/asn1": { "version": "0.2.6", @@ -8602,7 +8603,6 @@ "version": "0.27.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "peer": true, "dependencies": { "follow-redirects": "^1.14.9", "form-data": "^4.0.0" @@ -8900,8 +8900,7 @@ "node_modules/base-64": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "peer": true + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -9295,8 +9294,7 @@ "node_modules/byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/byte-length/-/byte-length-1.0.2.tgz", - "integrity": "sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==", - "peer": true + "integrity": "sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==" }, "node_modules/bytes": { "version": "3.1.2", @@ -9455,7 +9453,6 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "peer": true, "engines": { "node": "*" } @@ -10428,15 +10425,6 @@ "node": ">=8" } }, - "node_modules/cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "peer": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -10454,7 +10442,6 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "peer": true, "engines": { "node": "*" } @@ -12146,7 +12133,6 @@ "version": "3.21.1", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz", "integrity": "sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg==", - "peer": true, "dependencies": { "strnum": "^1.0.4" }, @@ -12961,7 +12947,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "peer": true, "bin": { "he": "bin/he" } @@ -13022,8 +13007,7 @@ "node_modules/hot-patcher": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/hot-patcher/-/hot-patcher-0.5.0.tgz", - "integrity": "sha512-2Uu2W0s8+dnqXzdlg0MRsRzPoDCs1wVjOGSyMRRaMzLDX4bgHw6xDYKccsWafXPPxQpkQfEjgW6+17pwcg60bw==", - "peer": true + "integrity": "sha512-2Uu2W0s8+dnqXzdlg0MRsRzPoDCs1wVjOGSyMRRaMzLDX4bgHw6xDYKccsWafXPPxQpkQfEjgW6+17pwcg60bw==" }, "node_modules/hpack.js": { "version": "2.1.6", @@ -13536,8 +13520,7 @@ "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "peer": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "node_modules/is-builtin-module": { "version": "3.2.1", @@ -16309,8 +16292,7 @@ "node_modules/layerr": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/layerr/-/layerr-0.1.2.tgz", - "integrity": "sha512-ob5kTd9H3S4GOG2nVXyQhOu9O8nBgP555XxWPkJI0tR0JeRilfyTp8WtPdIJHLXBmHMSdEq5+KMxiYABeScsIQ==", - "peer": true + "integrity": "sha512-ob5kTd9H3S4GOG2nVXyQhOu9O8nBgP555XxWPkJI0tR0JeRilfyTp8WtPdIJHLXBmHMSdEq5+KMxiYABeScsIQ==" }, "node_modules/less": { "version": "4.2.0", @@ -16957,7 +16939,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "peer": true, "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", @@ -17701,8 +17682,7 @@ "node_modules/nested-property": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/nested-property/-/nested-property-4.0.0.tgz", - "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==", - "peer": true + "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==" }, "node_modules/ng-packagr": { "version": "17.1.2", @@ -23917,21 +23897,6 @@ "node": ">=0.10.0" } }, - "node_modules/owncloud-sdk": { - "version": "3.1.0-alpha.10", - "resolved": "https://registry.npmjs.org/owncloud-sdk/-/owncloud-sdk-3.1.0-alpha.10.tgz", - "integrity": "sha512-cAq6BKkyDvm5MpcksicpBsfkr4ZEB+nm1Yl74FH3Qtm/wbI4uWwaTjqQmzYF5prAQDBDbi9F6nADp83zqfmPTA==", - "peerDependencies": { - "axios": "^0.27.2", - "cross-fetch": "^3.0.6", - "promise": "^8.1.0", - "qs": "^6.10.3", - "utf8": "^3.0.0", - "uuid": "^8.2.0", - "webdav": "4.10.0", - "xml-js": "^1.6.11" - } - }, "node_modules/p-each-series": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", @@ -24275,8 +24240,7 @@ "node_modules/path-posix": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz", - "integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==", - "peer": true + "integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==" }, "node_modules/path-scurry": { "version": "1.10.1", @@ -24829,15 +24793,6 @@ "node": ">=0.4.0" } }, - "node_modules/promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "peer": true, - "dependencies": { - "asap": "~2.0.6" - } - }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -24972,6 +24927,7 @@ "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dev": true, "dependencies": { "side-channel": "^1.0.4" }, @@ -24985,8 +24941,7 @@ "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "peer": true + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -26996,8 +26951,7 @@ "node_modules/strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "peer": true + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" }, "node_modules/strong-log-transformer": { "version": "2.1.0", @@ -28213,18 +28167,11 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "peer": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, - "node_modules/utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "peer": true - }, "node_modules/utif2": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", @@ -28450,7 +28397,6 @@ "version": "4.10.0", "resolved": "https://registry.npmjs.org/webdav/-/webdav-4.10.0.tgz", "integrity": "sha512-8PevPYhFsgbDhVGQQyrfBDYHiCYtN01qVX9zjFDA/OjIFqu28SsZuZdvGxBIQu2/e3Wp8M5oUpYvLM3uLP8g6A==", - "peer": true, "dependencies": { "axios": "^0.27.2", "base-64": "^1.0.0", @@ -28474,7 +28420,6 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -28485,8 +28430,7 @@ "node_modules/webdav/node_modules/url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", - "peer": true + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" }, "node_modules/webidl-conversions": { "version": "7.0.0", @@ -29120,18 +29064,6 @@ "xtend": "^4.0.0" } }, - "node_modules/xml-js": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "peer": true, - "dependencies": { - "sax": "^1.2.4" - }, - "bin": { - "xml-js": "bin/cli.js" - } - }, "node_modules/xml-parse-from-string": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", @@ -29337,7 +29269,6 @@ "node-opcua": "^2.119.2", "node-opcua-client-crawler": "^2.119.2", "nodemailer": "^6.9.8", - "owncloud-sdk": "^3.1.0-alpha.10", "reflect-metadata": "^0.2.1", "swagger-ui-express": "^5.0.0", "tslib": "^2.6.2", @@ -35012,7 +34943,6 @@ "node-opcua": "^2.119.2", "node-opcua-client-crawler": "^2.119.2", "nodemailer": "^6.9.8", - "owncloud-sdk": "^3.1.0-alpha.10", "reflect-metadata": "^0.2.1", "swagger-ui-express": "^5.0.0", "tslib": "^2.6.2", @@ -35258,7 +35188,8 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true }, "asn1": { "version": "0.2.6", @@ -35328,7 +35259,6 @@ "version": "0.27.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "peer": true, "requires": { "follow-redirects": "^1.14.9", "form-data": "^4.0.0" @@ -35559,8 +35489,7 @@ "base-64": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "peer": true + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" }, "base64-js": { "version": "1.5.1", @@ -35838,8 +35767,7 @@ "byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/byte-length/-/byte-length-1.0.2.tgz", - "integrity": "sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==", - "peer": true + "integrity": "sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==" }, "bytes": { "version": "3.1.2", @@ -35949,8 +35877,7 @@ "charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "peer": true + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==" }, "chart.js": { "version": "4.4.1", @@ -36688,15 +36615,6 @@ } } }, - "cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "peer": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -36710,8 +36628,7 @@ "crypt": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "peer": true + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==" }, "crypto-random-string": { "version": "4.0.0", @@ -37934,7 +37851,6 @@ "version": "3.21.1", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz", "integrity": "sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg==", - "peer": true, "requires": { "strnum": "^1.0.4" } @@ -38546,8 +38462,7 @@ "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "peer": true + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, "hexoid": { "version": "1.0.0", @@ -38586,8 +38501,7 @@ "hot-patcher": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/hot-patcher/-/hot-patcher-0.5.0.tgz", - "integrity": "sha512-2Uu2W0s8+dnqXzdlg0MRsRzPoDCs1wVjOGSyMRRaMzLDX4bgHw6xDYKccsWafXPPxQpkQfEjgW6+17pwcg60bw==", - "peer": true + "integrity": "sha512-2Uu2W0s8+dnqXzdlg0MRsRzPoDCs1wVjOGSyMRRaMzLDX4bgHw6xDYKccsWafXPPxQpkQfEjgW6+17pwcg60bw==" }, "hpack.js": { "version": "2.1.6", @@ -38958,8 +38872,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "peer": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-builtin-module": { "version": "3.2.1", @@ -41026,8 +40939,7 @@ "layerr": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/layerr/-/layerr-0.1.2.tgz", - "integrity": "sha512-ob5kTd9H3S4GOG2nVXyQhOu9O8nBgP555XxWPkJI0tR0JeRilfyTp8WtPdIJHLXBmHMSdEq5+KMxiYABeScsIQ==", - "peer": true + "integrity": "sha512-ob5kTd9H3S4GOG2nVXyQhOu9O8nBgP555XxWPkJI0tR0JeRilfyTp8WtPdIJHLXBmHMSdEq5+KMxiYABeScsIQ==" }, "less": { "version": "4.2.0", @@ -41515,7 +41427,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "peer": true, "requires": { "charenc": "0.0.2", "crypt": "0.0.2", @@ -42058,8 +41969,7 @@ "nested-property": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/nested-property/-/nested-property-4.0.0.tgz", - "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==", - "peer": true + "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==" }, "ng-packagr": { "version": "17.1.2", @@ -46663,12 +46573,6 @@ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true }, - "owncloud-sdk": { - "version": "3.1.0-alpha.10", - "resolved": "https://registry.npmjs.org/owncloud-sdk/-/owncloud-sdk-3.1.0-alpha.10.tgz", - "integrity": "sha512-cAq6BKkyDvm5MpcksicpBsfkr4ZEB+nm1Yl74FH3Qtm/wbI4uWwaTjqQmzYF5prAQDBDbi9F6nADp83zqfmPTA==", - "requires": {} - }, "p-each-series": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", @@ -46927,8 +46831,7 @@ "path-posix": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz", - "integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==", - "peer": true + "integrity": "sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==" }, "path-scurry": { "version": "1.10.1", @@ -47293,15 +47196,6 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" }, - "promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "peer": true, - "requires": { - "asap": "~2.0.6" - } - }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -47407,6 +47301,7 @@ "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dev": true, "requires": { "side-channel": "^1.0.4" } @@ -47414,8 +47309,7 @@ "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "peer": true + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" }, "queue-microtask": { "version": "1.2.3", @@ -48902,8 +48796,7 @@ "strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "peer": true + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" }, "strong-log-transformer": { "version": "2.1.0", @@ -49735,18 +49628,11 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "peer": true, "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, - "utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "peer": true - }, "utif2": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", @@ -49907,7 +49793,6 @@ "version": "4.10.0", "resolved": "https://registry.npmjs.org/webdav/-/webdav-4.10.0.tgz", "integrity": "sha512-8PevPYhFsgbDhVGQQyrfBDYHiCYtN01qVX9zjFDA/OjIFqu28SsZuZdvGxBIQu2/e3Wp8M5oUpYvLM3uLP8g6A==", - "peer": true, "requires": { "axios": "^0.27.2", "base-64": "^1.0.0", @@ -49928,7 +49813,6 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "peer": true, "requires": { "brace-expansion": "^2.0.1" } @@ -49936,8 +49820,7 @@ "url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", - "peer": true + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" } } }, @@ -50387,15 +50270,6 @@ "xtend": "^4.0.0" } }, - "xml-js": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "peer": true, - "requires": { - "sax": "^1.2.4" - } - }, "xml-parse-from-string": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", diff --git a/package.json b/package.json index d9587c06..5141c473 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "node-opcua": "^2.119.2", "node-opcua-client-crawler": "^2.119.2", "nodemailer": "^6.9.8", - "owncloud-sdk": "^3.1.0-alpha.10", "reflect-metadata": "^0.2.1", "rxjs": "~7.8.1", "swagger-ui-express": "^5.0.0", @@ -82,6 +81,7 @@ "tsoa": "^6.0.0", "tsyringe": "^4.8.0", "uuid": "^8.3.2", + "webdav": "^4.10.0", "winston": "^3.11.0", "winston-daily-rotate-file": "^4.7.1", "ws": "^8.16.0", diff --git a/projects/aas-lib/src/lib/aas-table/aas-table.component.scss b/projects/aas-lib/src/lib/aas-table/aas-table.component.scss index fe1a49b1..a6f1c2bb 100644 --- a/projects/aas-lib/src/lib/aas-table/aas-table.component.scss +++ b/projects/aas-lib/src/lib/aas-table/aas-table.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree.component.scss b/projects/aas-lib/src/lib/aas-tree/aas-tree.component.scss index 1edd6619..e3eb8f42 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree.component.scss +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * @@ -37,4 +37,4 @@ div.h-4 { div.wh-4 { width: 16px; height: 16px; -} +} \ No newline at end of file diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts b/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts index a864fada..a400b774 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts @@ -13,6 +13,7 @@ import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { Router } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { isEqual } from 'lodash-es'; import { aas, LiveNode, @@ -48,6 +49,7 @@ import { resolveSemanticId, supportedSubmodelTemplates, } from '../submodel-template/submodel-template'; + import * as AASTreeActions from './aas-tree.actions'; import * as AASTreeSelectors from './aas-tree.selectors'; import { AASTreeApiService } from './aas-tree-api.service'; @@ -122,7 +124,9 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { } public set selected(values: aas.Referable[]) { - this.store.dispatch(AASTreeActions.setSelectedElements({ elements: values })); + if (!isEqual(values, this._selected)) { + this.store.dispatch(AASTreeActions.setSelectedElements({ elements: values })); + } } @Output() @@ -600,4 +604,4 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { return value; } -} \ No newline at end of file +} diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree.reducer.ts b/projects/aas-lib/src/lib/aas-tree/aas-tree.reducer.ts index 49e1dce4..7da44a72 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree.reducer.ts +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree.reducer.ts @@ -425,8 +425,10 @@ function setSelectedElements(state: AASTreeState, elements: aas.Referable[]): AA const set = new Set(elements); for (let i = 0, n = rows.length; i < n; i++) { const row = rows[i]; - if (!row.selected && set.has(row.element)) { - rows[i] = clone(row, true); + if (set.has(row.element)) { + if (!row.selected) { + rows[i] = clone(row, true); + } } else if (row.selected) { rows[i] = clone(row, false); } @@ -654,4 +656,4 @@ function setMatchIndex(state: AASTreeState, index: number): AASTreeState { return rows; } -} \ No newline at end of file +} diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts b/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts index ecdcf4f7..100e6181 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts @@ -106,4 +106,4 @@ export interface AASTreeState { export interface AASTreeFeatureState { tree: AASTreeState; -} \ No newline at end of file +} diff --git a/projects/aas-lib/src/lib/aas-tree/operation-call-form/operation-call-form.component.scss b/projects/aas-lib/src/lib/aas-tree/operation-call-form/operation-call-form.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/aas-tree/operation-call-form/operation-call-form.component.scss +++ b/projects/aas-lib/src/lib/aas-tree/operation-call-form/operation-call-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/aas-tree/show-image-form/show-image-form.component.scss b/projects/aas-lib/src/lib/aas-tree/show-image-form/show-image-form.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/aas-tree/show-image-form/show-image-form.component.scss +++ b/projects/aas-lib/src/lib/aas-tree/show-image-form/show-image-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/aas-tree/show-video-form/show-video-form.component.scss b/projects/aas-lib/src/lib/aas-tree/show-video-form/show-video-form.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/aas-tree/show-video-form/show-video-form.component.scss +++ b/projects/aas-lib/src/lib/aas-tree/show-video-form/show-video-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/auth/auth.component.scss b/projects/aas-lib/src/lib/auth/auth.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/auth/auth.component.scss +++ b/projects/aas-lib/src/lib/auth/auth.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/auth/login-form/login-form.component.scss b/projects/aas-lib/src/lib/auth/login-form/login-form.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/auth/login-form/login-form.component.scss +++ b/projects/aas-lib/src/lib/auth/login-form/login-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/auth/profile-form/profile-form.component.scss b/projects/aas-lib/src/lib/auth/profile-form/profile-form.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/auth/profile-form/profile-form.component.scss +++ b/projects/aas-lib/src/lib/auth/profile-form/profile-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/auth/register-form/register-form.component.scss b/projects/aas-lib/src/lib/auth/register-form/register-form.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/auth/register-form/register-form.component.scss +++ b/projects/aas-lib/src/lib/auth/register-form/register-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/customer-feedback/customer-feedback.component.scss b/projects/aas-lib/src/lib/customer-feedback/customer-feedback.component.scss index e69de29b..1b4785f3 100644 --- a/projects/aas-lib/src/lib/customer-feedback/customer-feedback.component.scss +++ b/projects/aas-lib/src/lib/customer-feedback/customer-feedback.component.scss @@ -0,0 +1,7 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ diff --git a/projects/aas-lib/src/lib/digital-nameplate/digital-nameplate.component.scss b/projects/aas-lib/src/lib/digital-nameplate/digital-nameplate.component.scss index e69de29b..1b4785f3 100644 --- a/projects/aas-lib/src/lib/digital-nameplate/digital-nameplate.component.scss +++ b/projects/aas-lib/src/lib/digital-nameplate/digital-nameplate.component.scss @@ -0,0 +1,7 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ diff --git a/projects/aas-lib/src/lib/library-table/library-table.component.scss b/projects/aas-lib/src/lib/library-table/library-table.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/library-table/library-table.component.scss +++ b/projects/aas-lib/src/lib/library-table/library-table.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/localize/localize.component.scss b/projects/aas-lib/src/lib/localize/localize.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/localize/localize.component.scss +++ b/projects/aas-lib/src/lib/localize/localize.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/message-table/message-table.component.scss b/projects/aas-lib/src/lib/message-table/message-table.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/message-table/message-table.component.scss +++ b/projects/aas-lib/src/lib/message-table/message-table.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/notify/notify.component.scss b/projects/aas-lib/src/lib/notify/notify.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-lib/src/lib/notify/notify.component.scss +++ b/projects/aas-lib/src/lib/notify/notify.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/score/score.component.scss b/projects/aas-lib/src/lib/score/score.component.scss index 2725225f..ae4696e9 100644 --- a/projects/aas-lib/src/lib/score/score.component.scss +++ b/projects/aas-lib/src/lib/score/score.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-lib/src/lib/secured-image/secured-image.component.scss b/projects/aas-lib/src/lib/secured-image/secured-image.component.scss index e69de29b..1b4785f3 100644 --- a/projects/aas-lib/src/lib/secured-image/secured-image.component.scss +++ b/projects/aas-lib/src/lib/secured-image/secured-image.component.scss @@ -0,0 +1,7 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ diff --git a/projects/aas-portal/src/app/aas/aas.actions.ts b/projects/aas-portal/src/app/aas/aas.actions.ts index 33f67e07..5421eac8 100644 --- a/projects/aas-portal/src/app/aas/aas.actions.ts +++ b/projects/aas-portal/src/app/aas/aas.actions.ts @@ -18,7 +18,9 @@ export enum AASActionType { RESET_MODIFIED = '[AAS] reset modified', SET_DASHBOARD = '[AAS] set dashboard', SET_SEARCH = '[AAS] set search', + SET_STATE = '[AAS] Set State', } + export interface GetDocumentAction extends TypedAction { id: string; name?: string; @@ -37,4 +39,7 @@ export const setTemplateStorage = createAction( export const resetModified = createAction(AASActionType.RESET_MODIFIED, props<{ document: AASDocument }>()); -export const setSearch = createAction(AASActionType.SET_SEARCH, props<{ search: string }>()); \ No newline at end of file +export const setSearch = createAction(AASActionType.SET_SEARCH, props<{ search: string }>()); +export const setState = createAction( + AASActionType.SET_STATE, + props<{ value: 'offline' | 'online' }>()); \ No newline at end of file diff --git a/projects/aas-portal/src/app/aas/aas.component.html b/projects/aas-portal/src/app/aas/aas.component.html index c16c70c8..382b09bd 100644 --- a/projects/aas-portal/src/app/aas/aas.component.html +++ b/projects/aas-portal/src/app/aas/aas.component.html @@ -7,97 +7,99 @@ !---------------------------------------------------------------------------->
-
-
-
- -
-
-
-
LABEL_ENDPOINT
-
LABEL_AAS_ID_SHORT
-
LABEL_AAS_ID
-
LABEL_VERSION
-
LABEL_ASSET_ID
+
+
+
+ +
+
+
+
LABEL_ENDPOINT
+
LABEL_AAS_ID_SHORT
+
LABEL_AAS_ID
+
LABEL_VERSION
+
LABEL_ASSET_ID
+
+
+
{{address}}
+
{{idShort}}
+
{{id}}
+
{{version}}
+
{{assetId}}
+
+
-
-
{{address}}
-
{{idShort}}
-
{{id}}
-
{{version}}
-
{{assetId}}
-
-
-
-
- - -
+
+ + +
-
- - -
-
- - - -
-
- -
-
- -
-
- - -
-
- - - -
-
-
- - - +
+ + +
+
+ + + +
+
+ +
+
+ +
+
+ + +
+
+ + + +
+
+
+ + +
- \ No newline at end of file + \ No newline at end of file diff --git a/projects/aas-portal/src/app/aas/aas.component.scss b/projects/aas-portal/src/app/aas/aas.component.scss index 53a42160..f12cda01 100644 --- a/projects/aas-portal/src/app/aas/aas.component.scss +++ b/projects/aas-portal/src/app/aas/aas.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/app/aas/aas.component.ts b/projects/aas-portal/src/app/aas/aas.component.ts index efaeef1d..ad72bfce 100644 --- a/projects/aas-portal/src/app/aas/aas.component.ts +++ b/projects/aas-portal/src/app/aas/aas.component.ts @@ -9,7 +9,7 @@ import { head } from 'lodash-es'; import { AfterViewInit, Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { EMPTY, map, mergeMap, Observable, Subscription, from } from 'rxjs'; +import { EMPTY, map, mergeMap, Observable, Subscription, from, of, first } from 'rxjs'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Store } from '@ngrx/store'; import { @@ -47,7 +47,6 @@ import { AASDocument, equalDocument, TemplateDescriptor, aas, isProperty, isNumb export class AASComponent implements OnInit, OnDestroy, AfterViewInit { private readonly store: Store; private readonly subscription = new Subscription(); - private _state: OnlineState = 'offline'; private _dashboardPage = ''; private templates: TemplateDescriptor[] = []; @@ -66,8 +65,12 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { private readonly clipboard: ClipboardService, ) { this.store = store as Store; + this.state = this.store.select(AASSelectors.selectState); this.search = this.store.select(AASSelectors.selectSearch); - this.editable = this.store.select(AASSelectors.selectEditable); + this.canPlay = this.store.select(AASSelectors.selectCanPlay); + this.canStop = this.store.select(AASSelectors.selectCanStop); + this.readOnly = this.store.select(AASSelectors.selectReadOnly); + this.canSynchronize = this.store.select(AASSelectors.selectCanSynchronize); this.dashboardPages = this.dashboard.pages.pipe(map(pages => pages.map(page => page.name))); } @@ -77,9 +80,7 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { public document: AASDocument | null = null; - public get state(): OnlineState { - return this._state; - } + public readonly state: Observable; public readonly search: Observable; @@ -113,14 +114,6 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { return this.versionToString(head(this.document?.content?.assetAdministrationShells)?.administration); } - public get onlineReady(): boolean { - return !!this.document?.onlineReady; - } - - public get readonly(): boolean { - return this.document?.readonly ?? true; - } - public readonly dashboardPages: Observable; public get dashboardPage(): string { @@ -133,7 +126,36 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { public selectedElements: aas.Referable[] = []; - public readonly editable: Observable; + public readonly readOnly: Observable; + + public get canUndo(): boolean { + return this.commandHandler.canUndo; + } + + public get canRedo(): boolean { + return this.commandHandler.canRedo; + } + + public readonly canPlay: Observable; + + public readonly canStop: Observable; + + public readonly canSynchronize: Observable; + + public get canNewElement(): boolean { + return this.selectedElements.length === 1; + } + + public get canEditElement(): boolean { + return this.selectedElements.length === 1; + } + + public get canDeleteElement(): boolean { + return ( + this.selectedElements.length > 0 && + this.selectedElements.every(item => item.modelType !== 'AssetAdministrationShell') + ); + } public ngOnInit(): void { const params = this.route.snapshot.queryParams as AASQueryParams; @@ -216,13 +238,11 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { } public play(): void { - if (this.onlineReady && this._state === 'offline') { - this._state = 'online'; - } + this.store.dispatch(AASActions.setState({ value: 'online' })); } public stop(): void { - this._state = 'offline'; + this.store.dispatch(AASActions.setState({ value: 'offline' })); } public canAddToDashboard(): boolean { @@ -256,121 +276,113 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { } } - public canSynchronize(): boolean { - return this.document != null && !this.document.readonly && this.document.modified - ? this.document.modified - : false; - } - public synchronize(): void { - if (!this.canSynchronize() || !this.document) return; - - const document = this.document; this.auth .ensureAuthorized('editor') .pipe( - mergeMap(() => this.api.putDocument(document)), - map(messages => { - if (messages && messages.length > 0) { - this.notify.info(messages.join('\r\n')); + mergeMap(() => this.store.select(AASSelectors.selectDocument).pipe(first())), + mergeMap(document => { + if (!document) { + return EMPTY; } - this.store.dispatch(AASActions.resetModified({ document })); + return this.api.putDocument(document).pipe( + map(messages => { + if (messages && messages.length > 0) { + this.notify.info(messages.join('\r\n')); + } + + this.store.dispatch(AASActions.resetModified({ document })); + }), + ); }), ) .subscribe({ error: error => this.notify.error(error) }); } - public canUndo(): boolean { - return !this.readonly && this.commandHandler.canUndo; - } - public undo(): void { - if (this.canUndo()) { - this.commandHandler.undo(); - } - } - - public canRedo(): boolean { - return !this.readonly && this.commandHandler.canRedo; + this.commandHandler.undo(); } public redo(): void { - if (this.canRedo()) { - this.commandHandler.redo(); - } - } - - public canNewElement(): boolean { - return this.selectedElements.length === 1; + this.commandHandler.redo(); } public newElement(): void { - if (!this.document?.content || this.selectedElements.length !== 1) return; - - const document = this.document; this.auth .ensureAuthorized('editor') .pipe( - map(() => this.modal.open(NewElementFormComponent, { backdrop: 'static' })), - mergeMap(modalRef => { - modalRef.componentInstance.initialize(document.content, this.selectedElements[0], this.templates); - return from>(modalRef.result); - }), - map(result => { - if (!result) return; + mergeMap(() => this.store.select(AASSelectors.selectDocument).pipe(first())), + mergeMap(document => { + if (!document || this.selectedElements.length !== 1) { + return EMPTY; + } - this.commandHandler.execute( - new NewElementCommand(this.store, document, this.selectedElements[0], result.element), + return of(this.modal.open(NewElementFormComponent, { backdrop: 'static' })).pipe( + mergeMap(modalRef => { + modalRef.componentInstance.initialize( + document.content, + this.selectedElements[0], + this.templates, + ); + + return from>(modalRef.result); + }), + map(result => { + if (result) { + this.commandHandler.execute( + new NewElementCommand( + this.store, + document, + this.selectedElements[0], + result.element, + ), + ); + } + }), ); }), ) .subscribe({ error: error => this.notify.error(error) }); } - public canEditElement(): boolean { - return this.selectedElements.length === 1; - } - public editElement(): void { - if (!this.document?.content || this.selectedElements.length !== 1) return; - - const document = this.document; this.auth .ensureAuthorized('editor') .pipe( - map(() => this.modal.open(EditElementFormComponent, { backdrop: 'static' })), - mergeMap(modalRef => { - modalRef.componentInstance.initialize(this.selectedElements[0]); - return from>(modalRef.result); - }), - map(result => { - if (!result) return; + mergeMap(() => this.store.select(AASSelectors.selectDocument).pipe(first())), + mergeMap(document => { + if (!document || this.selectedElements.length !== 1) { + return EMPTY; + } - this.commandHandler.execute( - new UpdateElementCommand(this.store, document, this.selectedElements[0], result), + return of(this.modal.open(EditElementFormComponent, { backdrop: 'static' })).pipe( + mergeMap(modalRef => { + modalRef.componentInstance.initialize(this.selectedElements[0]); + return from>(modalRef.result); + }), + map(result => { + if (result) { + this.commandHandler.execute( + new UpdateElementCommand(this.store, document, this.selectedElements[0], result), + ); + } + }), ); }), ) .subscribe({ error: error => this.notify.error(error) }); } - public canDeleteElement(): boolean { - return ( - this.selectedElements.length > 0 && - this.selectedElements.every(item => item.modelType !== 'AssetAdministrationShell') - ); - } - public deleteElement(): void { - if (!this.document || this.selectedElements.length <= 0) return; - - const document = this.document; this.auth .ensureAuthorized('editor') .pipe( - map(() => { - this.commandHandler.execute(new DeleteCommand(this.store, document, this.selectedElements)); + mergeMap(() => this.store.select(AASSelectors.selectDocument).pipe(first())), + map(document => { + if (document && this.selectedElements.length > 0) { + this.commandHandler.execute(new DeleteCommand(this.store, document, this.selectedElements)); + } }), ) .subscribe({ error: error => this.notify.error(error) }); @@ -425,4 +437,4 @@ export class AASComponent implements OnInit, OnDestroy, AfterViewInit { element.contentType === 'application/json' ); } -} \ No newline at end of file +} diff --git a/projects/aas-portal/src/app/aas/aas.reducer.ts b/projects/aas-portal/src/app/aas/aas.reducer.ts index b5dd1ca9..6be7ce11 100644 --- a/projects/aas-portal/src/app/aas/aas.reducer.ts +++ b/projects/aas-portal/src/app/aas/aas.reducer.ts @@ -18,6 +18,7 @@ export const aasReducer = createReducer( on(AASActions.setDocument, (state, { document }) => setDocument(state, document)), on(AASActions.setSearch, (state, { search }) => setSearch(state, search)), on(AASActions.setTemplateStorage, (state, { templates }) => setTemplateStorage(state, templates)), + on(AASActions.setState, (state, { value }) => setState(state, value)), ); function setTemplateStorage(state: AASState, templates: TemplateDescriptor[]): AASState { @@ -38,4 +39,8 @@ function resetModified(state: AASState, document: AASDocument): AASState { function setSearch(state: AASState, search: string): AASState { return { ...state, search }; -} \ No newline at end of file +} + +function setState(state: AASState, value: 'offline' | 'online'): AASState { + return { ...state, state: value }; +} diff --git a/projects/aas-portal/src/app/aas/aas.selectors.ts b/projects/aas-portal/src/app/aas/aas.selectors.ts index e323facb..ce84c292 100644 --- a/projects/aas-portal/src/app/aas/aas.selectors.ts +++ b/projects/aas-portal/src/app/aas/aas.selectors.ts @@ -15,13 +15,12 @@ const getSearch = (state: State) => state.aas.search; const getDocument = (state: State) => state.aas.document; const getTemplateStorage = (state: State) => state.aas.templateStorage; const getTemplates = (state: State) => state.aas.templateStorage.templates; +const getState = (state: State) => state.aas; export const selectOnlineReady = createSelector(getOnlineReady, onlineReady => onlineReady); export const selectReadOnly = createSelector(getReadOnly, readOnly => readOnly); -export const selectEditable = createSelector(getReadOnly, readonly => !readonly); - export const selectSearch = createSelector(getSearch, search => search); export const selectDocument = createSelector(getDocument, document => document); @@ -30,4 +29,20 @@ export const selectHasDocument = createSelector(getDocument, document => documen export const selectTemplateStorage = createSelector(getTemplateStorage, templateStorage => templateStorage); -export const selectTemplates = createSelector(getTemplates, templates => templates); \ No newline at end of file +export const selectTemplates = createSelector(getTemplates, templates => templates); + +export const selectState = createSelector(getState, state => state.state); + +export const selectCanPlay = createSelector( + getState, + state => (state.document?.onlineReady ?? false) && state.state === 'offline', +); + +export const selectCanStop = createSelector( + getState, + state => (state.document?.onlineReady ?? false) && state.state === 'online', +); + +export const selectCanSynchronize = createSelector(getDocument, document => + document != null && !document.readonly && document.modified ? document.modified : false, +); diff --git a/projects/aas-portal/src/app/aas/aas.state.ts b/projects/aas-portal/src/app/aas/aas.state.ts index 3588de2e..555145d0 100644 --- a/projects/aas-portal/src/app/aas/aas.state.ts +++ b/projects/aas-portal/src/app/aas/aas.state.ts @@ -15,6 +15,7 @@ export interface TemplateStorage { export interface AASState { document: AASDocument | null; + state: 'online' | 'offline'; search: string; templateStorage: TemplateStorage; error: Error | null; @@ -23,6 +24,7 @@ export interface AASState { export const initialState: AASState = { document: null, search: '', + state: 'offline', templateStorage: { timestamp: 0, templates: [], @@ -32,4 +34,4 @@ export const initialState: AASState = { export interface State { aas: AASState; -} \ No newline at end of file +} diff --git a/projects/aas-portal/src/app/aas/edit-element-form/edit-element-form.component.scss b/projects/aas-portal/src/app/aas/edit-element-form/edit-element-form.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-portal/src/app/aas/edit-element-form/edit-element-form.component.scss +++ b/projects/aas-portal/src/app/aas/edit-element-form/edit-element-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/app/aas/new-element-form/new-element-form.component.scss b/projects/aas-portal/src/app/aas/new-element-form/new-element-form.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-portal/src/app/aas/new-element-form/new-element-form.component.scss +++ b/projects/aas-portal/src/app/aas/new-element-form/new-element-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/app/about/about.component.scss b/projects/aas-portal/src/app/about/about.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-portal/src/app/about/about.component.scss +++ b/projects/aas-portal/src/app/about/about.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/app/app.component.scss b/projects/aas-portal/src/app/app.component.scss index d9a40aa2..3e12e6db 100644 --- a/projects/aas-portal/src/app/app.component.scss +++ b/projects/aas-portal/src/app/app.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/app/app.component.ts b/projects/aas-portal/src/app/app.component.ts index 5fd2a980..100dc7bf 100644 --- a/projects/aas-portal/src/app/app.component.ts +++ b/projects/aas-portal/src/app/app.component.ts @@ -15,4 +15,4 @@ import { Component } from '@angular/core'; }) export class AppComponent { public title = 'AASPortal'; -} \ No newline at end of file +} diff --git a/projects/aas-portal/src/app/dashboard/dashboard.component.scss b/projects/aas-portal/src/app/dashboard/dashboard.component.scss index 59dbed31..3ee4bc5e 100644 --- a/projects/aas-portal/src/app/dashboard/dashboard.component.scss +++ b/projects/aas-portal/src/app/dashboard/dashboard.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/app/main/main.component.scss b/projects/aas-portal/src/app/main/main.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-portal/src/app/main/main.component.scss +++ b/projects/aas-portal/src/app/main/main.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/app/start/add-endpoint-form/add-endpoint-form.component.scss b/projects/aas-portal/src/app/start/add-endpoint-form/add-endpoint-form.component.scss index a4fac893..cd7fe036 100644 --- a/projects/aas-portal/src/app/start/add-endpoint-form/add-endpoint-form.component.scss +++ b/projects/aas-portal/src/app/start/add-endpoint-form/add-endpoint-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * @@ -12,4 +12,4 @@ .start-10 { left: 10%!important; -} +} \ No newline at end of file diff --git a/projects/aas-portal/src/app/start/remove-endpoint-form/remove-endpoint-form.component.scss b/projects/aas-portal/src/app/start/remove-endpoint-form/remove-endpoint-form.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-portal/src/app/start/remove-endpoint-form/remove-endpoint-form.component.scss +++ b/projects/aas-portal/src/app/start/remove-endpoint-form/remove-endpoint-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/app/start/start.component.scss b/projects/aas-portal/src/app/start/start.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-portal/src/app/start/start.component.scss +++ b/projects/aas-portal/src/app/start/start.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/app/start/upload-form/upload-form.component.scss b/projects/aas-portal/src/app/start/upload-form/upload-form.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-portal/src/app/start/upload-form/upload-form.component.scss +++ b/projects/aas-portal/src/app/start/upload-form/upload-form.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/app/view/view.component.scss b/projects/aas-portal/src/app/view/view.component.scss index c57d4b61..1b4785f3 100644 --- a/projects/aas-portal/src/app/view/view.component.scss +++ b/projects/aas-portal/src/app/view/view.component.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * diff --git a/projects/aas-portal/src/styles.scss b/projects/aas-portal/src/styles.scss index 78eb82c5..bba11807 100644 --- a/projects/aas-portal/src/styles.scss +++ b/projects/aas-portal/src/styles.scss @@ -1,6 +1,6 @@ /****************************************************************************** * - * Copyright (c) 2019-2022 Fraunhofer IOSB-INA Lemgo, + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft * zur Foerderung der angewandten Forschung e.V. * @@ -70,4 +70,4 @@ th[sortable].desc:before { } /* Importing Bootstrap SCSS file. */ -@import 'bootstrap/scss/bootstrap'; +@import 'bootstrap/scss/bootstrap'; \ No newline at end of file diff --git a/projects/aas-portal/src/test/aas/aas.component.spec.ts b/projects/aas-portal/src/test/aas/aas.component.spec.ts index beb8bb1c..76be5c50 100644 --- a/projects/aas-portal/src/test/aas/aas.component.spec.ts +++ b/projects/aas-portal/src/test/aas/aas.component.spec.ts @@ -11,7 +11,7 @@ import { Store, StoreModule } from '@ngrx/store'; import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { AASDocument, aas } from 'common'; -import { Observable, noop, of } from 'rxjs'; +import { Observable, first, noop, of } from 'rxjs'; import { AuthService, DownloadService, NotifyService, OnlineState } from 'projects/aas-lib/src/public-api'; import { CommonModule } from '@angular/common'; @@ -40,7 +40,7 @@ class TestDashboardService implements Partial { @Component({ selector: 'fhg-aas-tree', template: '
', - styleUrls: [] + styleUrls: [], }) class TestAASTreeComponent { @Input() @@ -66,7 +66,7 @@ class TestAASTreeComponent { @Component({ selector: 'fhg-img', template: '
', - styleUrls: [] + styleUrls: [], }) class TestSecureImageComponent { @Input() @@ -92,31 +92,22 @@ describe('AASComponent', () => { beforeEach(() => { api = jasmine.createSpyObj(['getDocument', 'getTemplates', 'putDocument']); - download = jasmine.createSpyObj( - [ - 'downloadDocument', - 'downloadFileAsync', - 'uploadDocuments', - ]); + download = jasmine.createSpyObj(['downloadDocument', 'downloadFileAsync', 'uploadDocuments']); TestBed.configureTestingModule({ - declarations: [ - AASComponent, - TestAASTreeComponent, - TestSecureImageComponent - ], + declarations: [AASComponent, TestAASTreeComponent, TestSecureImageComponent], providers: [ { provide: AASApiService, - useValue: api + useValue: api, }, { provide: NotifyService, - useValue: jasmine.createSpyObj(['error']) + useValue: jasmine.createSpyObj(['error']), }, { provide: DashboardService, - useValue: new TestDashboardService() + useValue: new TestDashboardService(), }, { provide: DownloadService, @@ -136,10 +127,9 @@ describe('AASComponent', () => { AppRoutingModule, HttpClientTestingModule, NgbModule, - StoreModule.forRoot( - { - aas: aasReducer - }), + StoreModule.forRoot({ + aas: aasReducer, + }), TranslateModule.forRoot({ loader: { provide: TranslateLoader, @@ -173,18 +163,31 @@ describe('AASComponent', () => { expect(component.assetId).toEqual(assetId); }); - it('indicates that the sample AAS is not online ready', function () { - expect(component.onlineReady).toBeFalse(); + it('indicates that "play" is disabled while sample AAS is not online ready', (done: DoneFn) => { + component.canPlay.pipe(first()).subscribe(value => { + expect(value).toBeFalse(); + done(); + }); + }); + + it('indicates that "stop" is disabled while sample AAS is not online ready', (done: DoneFn) => { + component.canStop.pipe(first()).subscribe(value => { + expect(value).toBeFalse(); + done(); + }); }); - it('indicates that the sample AAS is editable', function () { - expect(component.readonly).toBeFalse(); + it('indicates that the sample AAS is editable', (done: DoneFn) => { + component.readOnly.pipe(first()).subscribe(value => { + expect(value).toBeFalse(); + done(); + }); }); describe('canAddToDashboard', () => { beforeEach(() => { component.selectedElements = [torque, rotationSpeed]; - }) + }); it('can add the selected properties to the dashboard', async function () { spyOn(dashboard, 'add'); @@ -204,4 +207,4 @@ describe('AASComponent', () => { expect(download.downloadDocument).toHaveBeenCalled(); }); }); -}); \ No newline at end of file +}); diff --git a/projects/aas-server/package.json b/projects/aas-server/package.json index 16576fe9..924215b2 100644 --- a/projects/aas-server/package.json +++ b/projects/aas-server/package.json @@ -39,13 +39,13 @@ "node-opcua": "^2.119.2", "node-opcua-client-crawler": "^2.119.2", "nodemailer": "^6.9.8", - "owncloud-sdk": "^3.1.0-alpha.10", "reflect-metadata": "^0.2.1", "swagger-ui-express": "^5.0.0", "tslib": "^2.6.2", "tsoa": "^6.0.0", "tsyringe": "^4.8.0", "uuid": "^8.3.2", + "webdav": "^4.10.0", "winston": "^3.11.0", "winston-daily-rotate-file": "^4.7.1", "ws": "^8.16.0", diff --git a/projects/aas-server/src/app/aas-provider/aas-resource-scan-factory.ts b/projects/aas-server/src/app/aas-provider/aas-resource-scan-factory.ts index 33430273..97d05ae3 100644 --- a/projects/aas-server/src/app/aas-provider/aas-resource-scan-factory.ts +++ b/projects/aas-server/src/app/aas-provider/aas-resource-scan-factory.ts @@ -18,14 +18,14 @@ import { AasxDirectory } from '../packages/aasx-directory/aasx-directory.js'; import { AasxServer } from '../packages/aasx-server/aasx-server.js'; import { AasxServerV3 } from '../packages/aasx-server/aasx-server-v3.js'; import { AasxServerV0 } from '../packages/aasx-server/aasx-server-v0.js'; -import { FileStorageFactory } from '../file-storage/file-storage-factory.js'; +import { FileStorageProvider } from '../file-storage/file-storage-provider.js'; import { FileStorage } from '../file-storage/file-storage.js'; @singleton() export class AASResourceScanFactory { public constructor( @inject('Logger') private readonly logger: Logger, - @inject(FileStorageFactory) private readonly fileStorageFactory: FileStorageFactory, + @inject(FileStorageProvider) private readonly fileStorageProvider: FileStorageProvider, ) {} public create(endpoint: AASEndpoint): AASResourceScan { @@ -53,7 +53,7 @@ export class AASResourceScanFactory { this.logger, endpoint.url, endpoint.name, - this.createLocalFileStorage(new URL(endpoint.url)), + this.getFileStorage(new URL(endpoint.url)), ), ); default: @@ -61,7 +61,7 @@ export class AASResourceScanFactory { } } - private createLocalFileStorage(url: URL): FileStorage { - return this.fileStorageFactory.create(`file:///endpoints` + url.pathname); + private getFileStorage(url: URL): FileStorage { + return this.fileStorageProvider.get(url); } } diff --git a/projects/aas-server/src/app/aas-provider/directory-scan.ts b/projects/aas-server/src/app/aas-provider/directory-scan.ts index 5073901c..8e506547 100644 --- a/projects/aas-server/src/app/aas-provider/directory-scan.ts +++ b/projects/aas-server/src/app/aas-provider/directory-scan.ts @@ -24,9 +24,8 @@ export class DirectoryScan extends AASResourceScan { public async scanAsync(): Promise { try { await this.source.openAsync(); - const files = await this.source.getStorage().readDir('.'); + const files = await this.source.readDir(); const documents: AASDocument[] = []; - for (const file of files) { try { const extension = extname(file); diff --git a/projects/aas-server/src/app/aas-server.ts b/projects/aas-server/src/app/aas-server.ts index 6fa4ac5a..7c67924e 100644 --- a/projects/aas-server/src/app/aas-server.ts +++ b/projects/aas-server/src/app/aas-server.ts @@ -12,7 +12,6 @@ import { CookieStorageFactory } from './auth/cookie-storage-factory.js'; import { UserStorageFactory } from './auth/user-storage-factory.js'; import { LoggerFactory } from './logging/logger-factory.js'; import { FileLogger } from './logging/file-logger.js'; -import { TemplateStorageFactory } from './template/template-storage-factory.js'; import { WSServer } from './ws-server.js'; import { AASProvider } from './aas-provider/aas-provider.js'; import { AASIndexFactory } from './aas-index/aas-index-factory.js'; @@ -23,7 +22,6 @@ container.register('AASIndex', { useFactory: c => new AASIndexFactory(c).create( container.register('CookieStorage', { useFactory: c => new CookieStorageFactory(c).create() }); container.register('UserStorage', { useFactory: c => new UserStorageFactory(c).create() }); container.register('winston.Logger', { useFactory: () => new LoggerFactory().create() }); -container.register('TemplateStorage', { useFactory: c => new TemplateStorageFactory(c).create() }); container.afterResolution( AASProvider, diff --git a/projects/aas-server/src/app/controller/templates-controller.ts b/projects/aas-server/src/app/controller/templates-controller.ts index 5019f83a..87ecb603 100644 --- a/projects/aas-server/src/app/controller/templates-controller.ts +++ b/projects/aas-server/src/app/controller/templates-controller.ts @@ -26,7 +26,7 @@ export class TemplatesController extends AASController { @inject('Logger') logger: Logger, @inject(AuthService) auth: AuthService, @inject(Variable) variable: Variable, - @inject('TemplateStorage') private readonly templateStorage: TemplateStorage, + @inject(TemplateStorage) private readonly templateStorage: TemplateStorage, ) { super(logger, auth, variable); } diff --git a/projects/aas-server/src/app/convert.ts b/projects/aas-server/src/app/convert.ts index eaf72d11..fc78e440 100644 --- a/projects/aas-server/src/app/convert.ts +++ b/projects/aas-server/src/app/convert.ts @@ -33,3 +33,20 @@ export function parseUrl(url: string): URL { export function toUint8Array(data: T): Uint8Array { return Uint8Array.from(Buffer.from(JSON.stringify(data))); } + +export function join(...args: string[]): string { + let path = ''; + for (const arg of args.map(item => item.trim()).filter(item => item)) { + if (arg === '/') { + path += arg; + } else if (arg.endsWith('/')) { + path += arg.startsWith('/') ? arg.substring(1) : arg; + } else if (path) { + path += arg.startsWith('/') ? arg : '/' + arg; + } else { + path = arg; + } + } + + return path; +} diff --git a/projects/aas-server/src/app/file-storage/file-storage-factory.ts b/projects/aas-server/src/app/file-storage/file-storage-factory.ts deleted file mode 100644 index f70a67f5..00000000 --- a/projects/aas-server/src/app/file-storage/file-storage-factory.ts +++ /dev/null @@ -1,33 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -import path from 'path'; -import { inject, singleton } from 'tsyringe'; -import { FileStorage } from './file-storage.js'; -import { LocalFileStorage } from './local-file-storage.js'; -import { parseUrl } from '../convert.js'; -import { Variable } from '../variable.js'; - -@singleton() -export class FileStorageFactory { - public constructor(@inject(Variable) private readonly variable: Variable) {} - - /** - * Creates a FileStorage for the specified URL. - * @param url The URL of the file storage. - * @returns A new FileStorage instance. - */ - public create(url: string | URL): FileStorage { - const temp = typeof url === 'string' ? parseUrl(url) : url; - if (temp.protocol === 'file:') { - return new LocalFileStorage(path.join(this.variable.ASSETS, temp.pathname)); - } - - throw new Error('Not implemented.'); - } -} diff --git a/projects/aas-server/src/app/file-storage/file-storage-provider.ts b/projects/aas-server/src/app/file-storage/file-storage-provider.ts new file mode 100644 index 00000000..28b9bda2 --- /dev/null +++ b/projects/aas-server/src/app/file-storage/file-storage-provider.ts @@ -0,0 +1,49 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ + +import { inject, singleton } from 'tsyringe'; +import { FileStorage } from './file-storage.js'; +import { LocalFileStorage } from './local-file-storage.js'; +import { Variable } from '../variable.js'; +import { WebDAVStorage } from './webdav-storage.js'; + +@singleton() +export class FileStorageProvider { + private readonly instances = new Map(); + + public constructor(@inject(Variable) private readonly variable: Variable) {} + + /** + * Gets a FileStorage for the specified URL. + * @param url The URL of the file storage. + * @returns A FileStorage instance. + */ + public get(url: string | URL | undefined = 'file:///'): FileStorage { + url = typeof url === 'string' ? new URL(url) : url; + const key = url.protocol + '//' + url.host; + let instance = this.instances.get(key); + if (!instance) { + instance = this.create(url); + this.instances.set(key, instance); + } + + return instance; + } + + private create(url: URL): FileStorage { + switch (url.protocol) { + case 'file:': + return new LocalFileStorage(this.variable.ASSETS); + case 'http:': + case 'https:': + return new WebDAVStorage(url); + default: + throw new Error(`${url.href} is an invalid URL or a not supported file storage.`); + } + } +} diff --git a/projects/aas-server/src/app/file-storage/file-storage.ts b/projects/aas-server/src/app/file-storage/file-storage.ts index 5c74c9c9..de098321 100644 --- a/projects/aas-server/src/app/file-storage/file-storage.ts +++ b/projects/aas-server/src/app/file-storage/file-storage.ts @@ -6,17 +6,21 @@ * *****************************************************************************/ +export interface FileStorageEntry { + name: string; + path: string; + type: 'file' | 'directory'; +} + /** Defines a file based AASX package source. */ export abstract class FileStorage { protected constructor(public readonly root: string) {} public abstract mtime(path: string): Promise; public abstract exists(path: string): Promise; - public abstract isDirectory(path: string): Promise; - public abstract mkdir(path: string, recursive?: boolean): Promise; + public abstract createDir(path: string, recursive?: boolean): Promise; public abstract writeFile(path: string, data: string | Buffer): Promise; - public abstract readDir(path: string): Promise; + public abstract readDir(path: string): Promise; public abstract readFile(path: string): Promise; - public abstract unlink(path: string): Promise; - public abstract rename(oldPath: string, newPath: string): Promise; + public abstract delete(path: string): Promise; public abstract createReadStream(path: string): NodeJS.ReadableStream; } diff --git a/projects/aas-server/src/app/file-storage/local-file-storage.ts b/projects/aas-server/src/app/file-storage/local-file-storage.ts index 09dd5d4e..58567a7c 100644 --- a/projects/aas-server/src/app/file-storage/local-file-storage.ts +++ b/projects/aas-server/src/app/file-storage/local-file-storage.ts @@ -7,8 +7,8 @@ *****************************************************************************/ import fs from 'fs'; -import { resolve } from 'path'; -import { FileStorage } from './file-storage.js'; +import { normalize, resolve, sep } from 'path'; +import { FileStorage, FileStorageEntry } from './file-storage.js'; export class LocalFileStorage extends FileStorage { public constructor(root: string) { @@ -20,38 +20,43 @@ export class LocalFileStorage extends FileStorage { } public exists(path: string): Promise { - return Promise.resolve(fs.existsSync(resolve(this.root, path))); + return Promise.resolve(fs.existsSync(this.resolve(path))); } - public async isDirectory(path: string): Promise { - return (await fs.promises.stat(resolve(this.root, path))).isDirectory(); - } - - public mkdir(path: string, recursive = false): Promise { - return fs.promises.mkdir(resolve(this.root, path), { recursive: recursive }); + public override async createDir(path: string, recursive = false): Promise { + await fs.promises.mkdir(this.resolve(path), { recursive: recursive }); } public writeFile(path: string, data: string | Buffer): Promise { - return fs.promises.writeFile(resolve(this.root, path), data); + return fs.promises.writeFile(this.resolve(path), data); } - public readDir(path: string): Promise { - return fs.promises.readdir(resolve(this.root, path)); + public async readDir(path: string): Promise { + return (await fs.promises.readdir(this.resolve(path), { withFileTypes: true })).map(entry => ({ + name: entry.name, + path: entry.path, + type: entry.isDirectory() ? 'directory' : 'file', + })); } public readFile(path: string): Promise { - return fs.promises.readFile(resolve(this.root, path)); + return fs.promises.readFile(this.resolve(path)); } - public unlink(path: string): Promise { - return fs.promises.unlink(resolve(this.root, path)); + public delete(path: string): Promise { + return fs.promises.unlink(this.resolve(path)); } - public rename(oldPath: string, newPath: string): Promise { - return fs.promises.rename(resolve(this.root, oldPath), resolve(this.root, newPath)); + public createReadStream(path: string): NodeJS.ReadableStream { + return fs.createReadStream(this.resolve(path)); } - public createReadStream(path: string): NodeJS.ReadableStream { - return fs.createReadStream(resolve(this.root, path)); + private resolve(path: string): string { + path = normalize(path); + if (path.startsWith(sep)) { + path = path.substring(1); + } + + return resolve(this.root, path); } } diff --git a/projects/aas-server/src/app/file-storage/own-cloud-storage.ts b/projects/aas-server/src/app/file-storage/own-cloud-storage.ts deleted file mode 100644 index 385360c2..00000000 --- a/projects/aas-server/src/app/file-storage/own-cloud-storage.ts +++ /dev/null @@ -1,123 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -import { noop } from 'lodash-es'; -import { FileStorage } from './file-storage.js'; -import ownCloud, { OwnCloudOptions } from 'owncloud-sdk'; - -export class OwnCloudStorage extends FileStorage { - private oc: ownCloud; - private connected = false; - - public constructor(arg: string | URL, root: string) { - super(root); - - const url = typeof arg === 'string' ? new URL(arg) : arg; - const username = url.username; - const password = url.password; - url.username = ''; - url.password = ''; - - const options: OwnCloudOptions = { - baseUrl: url.href, - }; - - if (username && password) { - options.auth = { basic: { username, password } }; - } - - this.oc = new ownCloud(options); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public mtime(path: string): Promise { - throw new Error('Method not implemented.'); - } - - public async exists(path: string): Promise { - await this.ensureConnected(); - const fileInfo = await this.oc.files.fileInfo(this.join(this.root, path), ['{http://owncloud.org/ns}fileid']); - return fileInfo != null; - } - - public async isDirectory(path: string): Promise { - await this.ensureConnected(); - const fileInfo = await this.oc.files.fileInfo(this.join(this.root, path), ['{http://owncloud.org/ns}fileid']); - return fileInfo.isDir(); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async mkdir(path: string, recursive?: boolean | undefined): Promise { - await this.ensureConnected(); - - throw new Error('Method not implemented.'); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async writeFile(path: string, data: string | Buffer): Promise { - await this.ensureConnected(); - - throw new Error('Method not implemented.'); - } - - public async readDir(path: string): Promise { - await this.ensureConnected(); - const items = await this.oc.files.list(this.join(this.root, path)); - return items.map(item => item.name); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async readFile(path: string): Promise { - await this.ensureConnected(); - - throw new Error('Method not implemented.'); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async unlink(path: string): Promise { - await this.ensureConnected(); - - throw new Error('Method not implemented.'); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public override async rename(oldPath: string, newPath: string): Promise { - await this.ensureConnected(); - - throw new Error('Method not implemented.'); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public override createReadStream(path: string): NodeJS.ReadableStream { - throw new Error('Method not implemented.'); - } - - private join(...args: string[]): string { - let path = ''; - for (const arg of args) { - if (arg.endsWith('/')) { - path += arg.startsWith('/') ? arg.substring(1) : arg; - } else { - path += arg.startsWith('/') ? arg : '/' + arg; - } - } - - return path; - } - - private async ensureConnected(): Promise { - if (!this.connected) { - try { - await this.oc.login(); - this.connected = true; - } catch { - noop(); - } - } - } -} diff --git a/projects/aas-server/src/app/file-storage/webdav-storage.ts b/projects/aas-server/src/app/file-storage/webdav-storage.ts new file mode 100644 index 00000000..2ea15f20 --- /dev/null +++ b/projects/aas-server/src/app/file-storage/webdav-storage.ts @@ -0,0 +1,91 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ + +import { FileStat, WebDAVClient, createClient } from 'webdav'; +import { join, normalize, sep } from 'path/posix'; +import { FileStorage, FileStorageEntry } from './file-storage.js'; + +export class WebDAVStorage extends FileStorage { + private url: URL; + + public constructor( + url: string | URL, + private _client?: WebDAVClient, + ) { + url = typeof url === 'string' ? new URL(url) : url; + super(url.pathname); + + this.url = url; + } + + public async mtime(path: string): Promise { + const stat = (await this.client.stat(this.resolve(path))) as FileStat; + return new Date(stat.lastmod); + } + + public exists(path: string): Promise { + return this.client.exists(this.resolve(path)); + } + + public override createDir(path: string, recursive?: boolean | undefined): Promise { + return this.client.createDirectory(this.resolve(path), { recursive }); + } + + public async writeFile(path: string, data: string | Buffer): Promise { + await this.client.putFileContents(this.resolve(path), data, { overwrite: true }); + } + + public async readDir(path: string): Promise { + const items = (await this.client.getDirectoryContents(this.resolve(path))) as FileStat[]; + return items.map(item => ({ name: item.basename, path: item.filename, type: item.type })); + } + + public async readFile(path: string): Promise { + const contents = await this.client.getFileContents(this.resolve(path)); + if (typeof contents === 'string') { + return Buffer.from(contents); + } + + return contents as Buffer; + } + + public async delete(path: string): Promise { + return this.client.deleteFile(this.resolve(path)); + } + + public override createReadStream(path: string): NodeJS.ReadableStream { + return this.client.createReadStream(this.resolve(path)); + } + + private get client(): WebDAVClient { + if (!this._client) { + const url = new URL(this.url); + url.pathname = '/remote.php/webdav'; + const username = url.username ?? 'admin'; + const password = url.password ?? 'admin'; + url.username = ''; + url.password = ''; + + this._client = createClient(url.href, { + username: username, + password: password, + }); + } + + return this._client; + } + + private resolve(path: string): string { + path = normalize(path); + if (path.startsWith(sep)) { + path = path.substring(1); + } + + return join(this.root, path); + } +} diff --git a/projects/aas-server/src/app/packages/aas-resource-factory.ts b/projects/aas-server/src/app/packages/aas-resource-factory.ts index b6e09210..b8b8095e 100644 --- a/projects/aas-server/src/app/packages/aas-resource-factory.ts +++ b/projects/aas-server/src/app/packages/aas-resource-factory.ts @@ -15,14 +15,14 @@ import { AasxServerV0 } from './aasx-server/aasx-server-v0.js'; import { AasxServerV3 } from './aasx-server/aasx-server-v3.js'; import { OpcuaServer } from './opcua/opcua-server.js'; import { ERRORS } from '../errors.js'; -import { FileStorageFactory } from '../file-storage/file-storage-factory.js'; +import { FileStorageProvider } from '../file-storage/file-storage-provider.js'; import { FileStorage } from '../file-storage/file-storage.js'; @singleton() export class AASResourceFactory { public constructor( @inject('Logger') private readonly logger: Logger, - @inject(FileStorageFactory) private readonly fileStorageFactory: FileStorageFactory, + @inject(FileStorageProvider) private readonly fileStorageProvider: FileStorageProvider, ) {} /** @@ -48,7 +48,7 @@ export class AASResourceFactory { source = new OpcuaServer(this.logger, endpoint.url, endpoint.name); break; case 'file:': - source = new AasxDirectory(this.logger, endpoint.url, endpoint.name, this.createLocalFileStorage(url)); + source = new AasxDirectory(this.logger, endpoint.url, endpoint.name, this.getFileStorage(url)); break; default: throw new Error('Not implemented.'); @@ -86,7 +86,7 @@ export class AASResourceFactory { this.logger, endpoint.url, endpoint.name, - this.createLocalFileStorage(url), + this.getFileStorage(url), ).testAsync(); break; default: @@ -101,7 +101,7 @@ export class AASResourceFactory { } } - private createLocalFileStorage(url: URL): FileStorage { - return this.fileStorageFactory.create(`file:///endpoints` + url.pathname); + private getFileStorage(url: URL): FileStorage { + return this.fileStorageProvider.get(url); } } diff --git a/projects/aas-server/src/app/packages/aasx-directory/aasx-directory.ts b/projects/aas-server/src/app/packages/aasx-directory/aasx-directory.ts index 9926cdc8..06f785d5 100644 --- a/projects/aas-server/src/app/packages/aasx-directory/aasx-directory.ts +++ b/projects/aas-server/src/app/packages/aasx-directory/aasx-directory.ts @@ -7,9 +7,10 @@ *****************************************************************************/ import { aas, ApplicationError } from 'common'; +import { join } from 'path'; import { readFile } from 'fs/promises'; import { ERRORS } from '../../errors.js'; -import { FileStorage } from '../../file-storage/file-storage.js'; +import { FileStorage, FileStorageEntry } from '../../file-storage/file-storage.js'; import { Logger } from '../../logging/logger.js'; import { AASPackage } from '../aas-package.js'; import { AASResource } from '../aas-resource.js'; @@ -17,6 +18,7 @@ import { AasxPackage } from './aasx-package.js'; import { SocketSubscription } from '../../live/socket-subscription.js'; export class AasxDirectory extends AASResource { + private readonly root: string; private reentry = 0; public constructor( @@ -26,6 +28,8 @@ export class AasxDirectory extends AASResource { private readonly fileStorage: FileStorage, ) { super(logger, url, name); + + this.root = new URL(url).pathname; } public get isOpen(): boolean { @@ -38,8 +42,14 @@ export class AasxDirectory extends AASResource { public readonly onlineReady = false; - public getStorage(): FileStorage { - return this.fileStorage; + public readFile(name: string): Promise { + return this.fileStorage.readFile(join(this.root, name)); + } + + public async readDir(): Promise { + return (await this.fileStorage.readDir(this.root)) + .filter(entry => entry.type === 'file') + .map(entry => entry.name); } public async testAsync(): Promise { @@ -54,7 +64,7 @@ export class AasxDirectory extends AASResource { public async openAsync(): Promise { if (this.reentry === 0) { - if (!(await this.fileStorage.exists('.'))) { + if (!(await this.fileStorage.exists(this.root))) { throw new Error(`The directory '${this.url}' does not exist.`); } @@ -81,11 +91,12 @@ export class AasxDirectory extends AASResource { } public async getPackageAsync(_: string, name: string): Promise { - if (!(await this.fileStorage.exists(name))) { - throw new Error(`The file '${name}' does not exist.`); + const path = join(this.root, name); + if (!(await this.fileStorage.exists(path))) { + throw new Error(`The file '${path}' does not exist.`); } - return this.fileStorage.createReadStream(name); + return this.fileStorage.createReadStream(path); } public readEnvironmentAsync(): Promise { @@ -93,10 +104,11 @@ export class AasxDirectory extends AASResource { } public override async postPackageAsync(file: Express.Multer.File): Promise { - const exists = await this.fileStorage.exists(file.filename); + const path = join(this.root, file.filename); + const exists = await this.fileStorage.exists(path); if (exists) { throw new ApplicationError( - `A file with the name '${file.filename}' already exists.`, + `A file with the name '${path}' already exists.`, ERRORS.FileAlreadyExists, file.filename, ); @@ -104,11 +116,11 @@ export class AasxDirectory extends AASResource { try { const buffer = await readFile(file.path); - await this.fileStorage.writeFile(file.filename, buffer); + await this.fileStorage.writeFile(path, buffer); return `${file.filename} successfully written`; } catch (error) { - if (await this.fileStorage.exists(file.filename)) { - await this.fileStorage.unlink(file.filename); + if (await this.fileStorage.exists(path)) { + await this.fileStorage.delete(path); } throw error; @@ -116,8 +128,9 @@ export class AasxDirectory extends AASResource { } public override async deletePackageAsync(_: string, name: string): Promise { - await this.fileStorage.unlink(name); - return `${name} successfully deleted`; + const path = join(this.root, name); + await this.fileStorage.delete(path); + return `${path} successfully deleted`; } public override invoke(): Promise { diff --git a/projects/aas-server/src/app/packages/aasx-directory/aasx-package.ts b/projects/aas-server/src/app/packages/aasx-directory/aasx-package.ts index 2e945540..d12424e4 100644 --- a/projects/aas-server/src/app/packages/aasx-directory/aasx-package.ts +++ b/projects/aas-server/src/app/packages/aasx-directory/aasx-package.ts @@ -133,7 +133,7 @@ export class AasxPackage extends AASPackage { } private async initializeZip(): Promise { - const data = await this.source.getStorage().readFile(this.file); + const data = await this.source.readFile(this.file); return await jszip.loadAsync(data); } diff --git a/projects/aas-server/src/app/packages/opcua/opcua-data-type-dictionary.ts b/projects/aas-server/src/app/packages/opcua/opcua-data-type-dictionary.ts index a0ebfd15..63a73523 100644 --- a/projects/aas-server/src/app/packages/opcua/opcua-data-type-dictionary.ts +++ b/projects/aas-server/src/app/packages/opcua/opcua-data-type-dictionary.ts @@ -7,9 +7,10 @@ *****************************************************************************/ import { aas } from 'common'; -import { BrowseDescriptionLike, ClientSession, NodeClass, NodeIdLike, resolveNodeId } from 'node-opcua'; +import { BrowseDescriptionLike, ClientSession, NodeClass, NodeIdLike, ReferenceDescription, resolveNodeId } from 'node-opcua'; interface DataTypeEntry { + obj: ReferenceDescription; name: string; baseType: string | null; } @@ -37,6 +38,7 @@ export class OpcuaDataTypeDictionary { for (const obj of result.references) { if (obj.nodeClass === NodeClass.DataType && obj.browseName.name) { this.dataTypes.set(obj.nodeId.toString(), { + obj: obj, name: obj.browseName.name, baseType: null, }); diff --git a/projects/aas-server/src/app/packages/opcua/opcua-reader.ts b/projects/aas-server/src/app/packages/opcua/opcua-reader.ts index 3c9291a1..2b63a9e0 100644 --- a/projects/aas-server/src/app/packages/opcua/opcua-reader.ts +++ b/projects/aas-server/src/app/packages/opcua/opcua-reader.ts @@ -7,7 +7,8 @@ *****************************************************************************/ import { aas, convertToString, determineType } from 'common'; -import { ArgumentOptions, LocalizedText, VariantArrayType, VariantOptions } from 'node-opcua'; +import { ArgumentOptions, LocalizedText } from 'node-opcua'; +import { noop } from 'lodash-es'; import { Logger } from '../../logging/logger.js'; import { AASReader } from '../aas-reader.js'; import { OpcuaDataTypeDictionary } from './opcua-data-type-dictionary.js'; @@ -20,7 +21,6 @@ import { UAKeyElements, ValueType, } from './opcua.js'; -import { noop } from 'lodash-es'; export class OpcuaReader extends AASReader { public constructor( @@ -73,12 +73,12 @@ export class OpcuaReader extends AASReader { let globalAssetId: string | undefined; const assetComponent = this.selectComponent(this.origin, 'Asset'); if (assetComponent) { - assetKind = this.readEnumProperty(assetComponent, 'AssetKind') ?? 'Instance'; + assetKind = this.readAssetKind(assetComponent, 'AssetKind') ?? 'Instance'; globalAssetId = this.readIdentifier(assetComponent); } else { const infoComponent = this.selectComponent(component, undefined, 'AASAssetInformationType'); if (infoComponent) { - assetKind = this.readEnumProperty(infoComponent, 'AssetKind') ?? 'Instance'; + assetKind = this.readAssetKind(infoComponent, 'AssetKind') ?? 'Instance'; globalAssetId = this.readReference(infoComponent, 'GlobalAssetId')?.keys[0].value; } } @@ -503,7 +503,7 @@ export class OpcuaReader extends AASReader { referable.parent = parent; } - const category = this.readEnumProperty(component, 'Category'); + const category = this.readCategory(component, 'Category'); if (category) { referable.category = category; } @@ -522,7 +522,7 @@ export class OpcuaReader extends AASReader { } private readHasKind(component: OPCUAComponent): aas.HasKind { - return { kind: this.readEnumProperty(component, 'ModellingKind') ?? 'Instance' }; + return { kind: this.readModellingKind(component, 'ModellingKind') ?? 'Instance' }; } private readHasDataSpecification(component: OPCUAComponent): aas.HasDataSpecification { @@ -594,35 +594,92 @@ export class OpcuaReader extends AASReader { return value; } - private readEnumProperty(component: OPCUAComponent, propertyName: string): T | undefined { + private readAssetKind(component: OPCUAComponent, propertyName: string): aas.AssetKind | undefined { const property = this.findProperty(component, propertyName); - const value = property?.dataValue.value?.value; - if (value && typeof value !== 'string') { - throw new Error(`Unexpected value type: expected string, actual ${typeof value}`); + if (!property) { + return undefined; } - return value; + const value = property.dataValue.value?.value; + if (!value) { + return undefined; + } + + if (typeof value === 'string') { + return value as aas.AssetKind; + } + + if (typeof value === 'number') { + switch (value) { + case 0: + return 'Type'; + case 1: + return 'Instance'; + } + } + + throw new Error(`Unexpected value type: expected string or number, actual ${typeof value}`); } - private readPropertyValue(component: OPCUAComponent, propertyName: string): T { + private readCategory(component: OPCUAComponent, propertyName: string): aas.Category | undefined { const property = this.findProperty(component, propertyName); - return property?.dataValue.value?.value; + if (!property) { + return undefined; + } + + const value = property.dataValue.value?.value; + if (!value) { + return undefined; + } + + if (typeof value === 'string') { + return value as aas.Category; + } + + if (typeof value === 'number') { + switch (value) { + case 0: + return 'CONSTANT'; + case 1: + return 'PARAMETER'; + default: + return 'VARIABLE'; + } + } + + throw new Error(`Unexpected value type: expected string or number, actual ${typeof value}`); } - private getConceptDescription(semanticId: aas.Key | null): aas.ConceptDescription | undefined { - const conceptDescription: aas.ConceptDescription | undefined = undefined; - if (semanticId) { - throw new Error('Not implemented'); + private readModellingKind(component: OPCUAComponent, propertyName: string): aas.ModellingKind | undefined { + const property = this.findProperty(component, propertyName); + if (!property) { + return undefined; + } + + const value = property.dataValue.value?.value; + if (!value) { + return undefined; + } + + if (typeof value === 'string') { + return value as aas.ModellingKind; + } + + if (typeof value === 'number') { + switch (value) { + case 0: + return 'Template'; + case 1: + return 'Instance'; + } } - return conceptDescription; + throw new Error(`Unexpected value type: expected string or number, actual ${typeof value}`); } - private toReference(value: UAKey[]): aas.Reference { - return { - type: 'ModelReference', - keys: value.map(item => this.readKey(item)), - }; + private readPropertyValue(component: OPCUAComponent, propertyName: string): T { + const property = this.findProperty(component, propertyName); + return property?.dataValue.value?.value; } private where(components: OPCUAComponent[] | undefined, ...types: TypeDefinition[]): OPCUAComponent[] { @@ -671,14 +728,6 @@ export class OpcuaReader extends AASReader { return value ? (UAEntityType[value] as aas.EntityType) : undefined; } - private isArray(value: VariantOptions | undefined): boolean { - return value != null && (value.arrayType === VariantArrayType.Array || value.arrayType === 'Array'); - } - - private getArray(value: VariantOptions | undefined): T[] { - return value !== undefined ? (value.value as T[]) : []; - } - private readDataTypeDefXsd(value?: ValueType | string): aas.DataTypeDefXsd | undefined { if (value) { const valueType = typeof value === 'string' ? value : ValueType[value]; diff --git a/projects/aas-server/src/app/template/template-storage-factory.ts b/projects/aas-server/src/app/template/template-storage-factory.ts deleted file mode 100644 index 6ddf51d7..00000000 --- a/projects/aas-server/src/app/template/template-storage-factory.ts +++ /dev/null @@ -1,33 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -import path from 'path'; -import { DependencyContainer } from 'tsyringe'; -import { TemplateStorage } from './template-storage.js'; -import { Variable } from '../variable.js'; -import { OwnCloudStorage } from '../file-storage/own-cloud-storage.js'; -import { FileStorage } from '../file-storage/file-storage.js'; -import { LocalFileStorage } from '../file-storage/local-file-storage.js'; -import { Logger } from '../logging/logger.js'; - -export class TemplateStorageFactory { - public constructor(private readonly container: DependencyContainer) {} - - public create(): TemplateStorage { - let fileStorage: FileStorage; - const variable = this.container.resolve(Variable); - const url = variable.FILE_STORAGE; - if (url) { - fileStorage = new OwnCloudStorage(url, '/'); - } else { - fileStorage = new LocalFileStorage(path.resolve(variable.ASSETS)); - } - - return new TemplateStorage(this.container.resolve('Logger'), fileStorage); - } -} diff --git a/projects/aas-server/src/app/template/template-storage.ts b/projects/aas-server/src/app/template/template-storage.ts index 7387d18d..ce8ffb66 100644 --- a/projects/aas-server/src/app/template/template-storage.ts +++ b/projects/aas-server/src/app/template/template-storage.ts @@ -6,7 +6,7 @@ * *****************************************************************************/ -import { basename, extname, join } from 'path'; +import { basename, extname, join } from 'path/posix'; import { TemplateDescriptor, aas } from 'common'; import { Logger } from '../logging/logger.js'; import { FileStorage } from '../file-storage/file-storage.js'; @@ -14,17 +14,29 @@ import { AASReader } from '../packages/aas-reader.js'; import { JsonReader } from '../packages/json-reader.js'; import { JsonReaderV2 } from '../packages/json-reader-v2.js'; import * as aasV2 from '../types/aas-v2.js'; +import { inject, singleton } from 'tsyringe'; +import { FileStorageProvider } from '../file-storage/file-storage-provider.js'; +import { Variable } from '../variable.js'; +@singleton() export class TemplateStorage { + private readonly fileStorage: FileStorage; + private readonly root: string; + public constructor( - private readonly logger: Logger, - private readonly fileStorage: FileStorage, - private readonly root = 'templates', - ) {} + @inject('Logger') private readonly logger: Logger, + @inject(Variable) variable: Variable, + @inject(FileStorageProvider) provider: FileStorageProvider, + ) { + const url = new URL(variable.TEMPLATE_STORAGE); + this.root = url.pathname; + url.pathname = ''; + this.fileStorage = provider.get(url); + } public async readAsync(): Promise { const descriptors: TemplateDescriptor[] = []; - if (await this.fileStorage.exists(this.root)) { + if ((await this.fileStorage.exists(this.root)) === true) { await this.readDirAsync('', descriptors); } @@ -32,24 +44,29 @@ export class TemplateStorage { } private async readDirAsync(dir: string, descriptors: TemplateDescriptor[]): Promise { - for (const entry of await this.fileStorage.readDir(join(this.root, dir))) { - const path = join(dir, entry); - if (await this.fileStorage.isDirectory(path)) { - await this.readDirAsync(path, descriptors); - } else { - const format = extname(path).toLowerCase(); - switch (format) { - case '.json': - descriptors.push({ - name: basename(path, extname(format)), - endpoint: { type: 'file', address: path }, - format: '.json', - template: await this.readTemplateAsync(path), - }); - break; + const directories: string[] = [dir]; + + while (directories.length > 0) { + const directory = directories.pop()!; + for (const entry of await this.fileStorage.readDir(join(this.root, directory))) { + const path = join(directory, entry.name); + if (entry.type === 'directory') { + directories.push(path); + } else { + const format = extname(path).toLowerCase(); + switch (format) { + case '.json': + descriptors.push({ + name: basename(path, extname(format)), + endpoint: { type: 'file', address: path }, + format: '.json', + template: await this.readTemplateAsync(path), + }); + break; - case '.xml': - throw new Error(`Template format '${format}' is not implemented`); + case '.xml': + throw new Error(`Template format '${format}' is not implemented`); + } } } } diff --git a/projects/aas-server/src/app/types/owncloud-sdk.d.ts b/projects/aas-server/src/app/types/owncloud-sdk.d.ts deleted file mode 100644 index 09034aed..00000000 --- a/projects/aas-server/src/app/types/owncloud-sdk.d.ts +++ /dev/null @@ -1,63 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -declare module 'owncloud-sdk' { - export interface OwnCloudAuthentication { - basic?: { username: string; password: string }; - bearer?: string; - } - - export interface OwnCloudOptions { - baseUrl: string; - auth?: OwnCloudAuthentication; - } - - export class FileInfo { - public name: string; - public type: string; - public processing: boolean; - public fileInfo: { [key: string]: string }; - - public getName(): string; - public getPath(): string; - public getSize(): number; - public getFileId(): number; - public getContentType(): string; - public getLastModified(): string; - public getProperty(property: string): string; - public isDir(): boolean; - } - - export class Files { - public list(path: string, depth = '1', properties: unknown[] = []): Promise; - public getFileContents(path: string, options?: { [key: string]: string }): Promise; - public getFileUrl(path: string): string; - public getPathForFileId(fileId: number): Promise; - public putFileContents( - path: string, - content: string, - options: { [key: string]: string } = {}, - ): Promise; - public createFolder(path: string): Promise; - public delete(path: string): Promise; - public fileInfo(path: string, properties: unknown[]): Promise; - public move(source: string, target: string, overwrite = false): Promise; - public copy(source: string, target: string, overwrite = false): Promise; - } - - export type UserInfo = { [key: string]: unknown }; - - export default class ownCloud { - public constructor(options?: OwnCloudOptions); - - public files: Files; - - public login(): Promise; - public logout(): void; - } -} diff --git a/projects/aas-server/src/app/variable.ts b/projects/aas-server/src/app/variable.ts index be44a85b..2eaf3f84 100644 --- a/projects/aas-server/src/app/variable.ts +++ b/projects/aas-server/src/app/variable.ts @@ -18,7 +18,7 @@ export class Variable { this.NODE_SERVER_PORT = Number(process.env.NODE_SERVER_PORT); this.MAX_WORKERS = process.env.MAX_WORKERS ? Number(process.env.MAX_WORKERS) : 8; this.USER_STORAGE = process.env.USER_STORAGE; - this.FILE_STORAGE = process.env.FILE_STORAGE; + this.TEMPLATE_STORAGE = process.env.TEMPLATE_STORAGE ?? 'file:///templates'; this.CORS_ORIGIN = process.env.CORS_ORIGIN ? JSON.parse(process.env.CORS_ORIGIN) : '*'; this.CONTENT_ROOT = path.resolve(process.env.CONTENT_ROOT ?? './'); this.WEB_ROOT = path.resolve(process.env.WEB_ROOT ?? './wwwroot'); @@ -53,7 +53,7 @@ export class Variable { public readonly USER_STORAGE?: string; /** The URL of the template storage. */ - public readonly FILE_STORAGE?: string; + public readonly TEMPLATE_STORAGE: string; /** */ public readonly CORS_ORIGIN: string | string[]; diff --git a/projects/aas-server/src/test/aas-provider/aas-provider.spec.ts b/projects/aas-server/src/test/aas-provider/aas-provider.spec.ts index 6ee33629..d17d5623 100644 --- a/projects/aas-server/src/test/aas-provider/aas-provider.spec.ts +++ b/projects/aas-server/src/test/aas-provider/aas-provider.spec.ts @@ -17,13 +17,13 @@ import { LocalFileStorage } from '../../app/file-storage/local-file-storage.js'; import { AASResourceFactory } from '../../app/packages/aas-resource-factory.js'; import { createSpyObj } from '../utils.js'; import { Variable } from '../../app/variable.js'; -import { FileStorageFactory } from '../../app/file-storage/file-storage-factory.js'; +import { FileStorageProvider } from '../../app/file-storage/file-storage-provider.js'; import { AASIndex } from '../../app/aas-index/aas-index.js'; describe('AASProvider', function () { let aasProvider: AASProvider; let variable: jest.Mocked; - let fileStorageFactory: jest.Mocked; + let fileStorageFactory: jest.Mocked; let index: jest.Mocked; const logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); const parallel = createSpyObj(['execute', 'on']); @@ -31,8 +31,8 @@ describe('AASProvider', function () { const resourceFactory = createSpyObj(['create', 'testAsync']); beforeEach(function () { - fileStorageFactory = createSpyObj(['create']); - fileStorageFactory.create.mockReturnValue(new LocalFileStorage('./src/test/assets/samples')); + fileStorageFactory = createSpyObj(['get']); + fileStorageFactory.get.mockReturnValue(new LocalFileStorage('./src/test/assets/samples')); resourceFactory.testAsync.mockReturnValue(new Promise(resolve => resolve())); variable = createSpyObj({}, { ENDPOINTS: [] }); index = createSpyObj(['getEndpoints']); diff --git a/projects/aas-server/src/test/application-info.spec.ts b/projects/aas-server/src/test/application-info.spec.ts index 7514422f..8e02cbba 100644 --- a/projects/aas-server/src/test/application-info.spec.ts +++ b/projects/aas-server/src/test/application-info.spec.ts @@ -7,13 +7,13 @@ *****************************************************************************/ import 'reflect-metadata'; +import { describe, beforeEach, it, expect } from '@jest/globals'; import { resolve } from 'path'; import { ApplicationInfo } from '../app/application-info.js'; import { Logger } from '../app/logging/logger.js'; import { readFile } from 'fs/promises'; import { PackageInfo } from 'common'; import { createSpyObj } from './utils.js'; -import { describe, beforeEach, it, expect } from '@jest/globals'; describe('Application Info service', () => { let logger: Logger; diff --git a/projects/aas-server/src/test/controller/templates-controller.spec.ts b/projects/aas-server/src/test/controller/templates-controller.spec.ts index 9045536d..4f6481a4 100644 --- a/projects/aas-server/src/test/controller/templates-controller.spec.ts +++ b/projects/aas-server/src/test/controller/templates-controller.spec.ts @@ -12,9 +12,9 @@ import { describe, beforeEach, it, expect, jest } from '@jest/globals'; import express, { Express, json, urlencoded } from 'express'; import morgan from 'morgan'; import request from 'supertest'; +import { TemplateDescriptor } from 'common'; import { Logger } from '../../app/logging/logger.js'; -import { TemplateDescriptor } from 'common'; import { AuthService } from '../../app/auth/auth-service.js'; import { createSpyObj } from '../utils.js'; import { Variable } from '../../app/variable.js'; @@ -51,7 +51,7 @@ describe('TemplateController', function () { container.registerInstance(AuthService, auth); container.registerInstance('Logger', logger); container.registerInstance(Variable, variable); - container.registerInstance('TemplateStorage', templateStorage); + container.registerInstance(TemplateStorage, templateStorage); container.registerInstance(Authentication, authentication); app = express(); diff --git a/projects/aas-server/src/test/convert.spec.ts b/projects/aas-server/src/test/convert.spec.ts index c3409c5c..c0f91472 100644 --- a/projects/aas-server/src/test/convert.spec.ts +++ b/projects/aas-server/src/test/convert.spec.ts @@ -6,7 +6,7 @@ * *****************************************************************************/ -import { decodeBase64Url, encodeBase64Url } from '../app/convert.js'; +import { decodeBase64Url, encodeBase64Url, join } from '../app/convert.js'; import { describe, it, expect } from '@jest/globals'; describe('convert', () => { diff --git a/projects/aas-server/src/test/file-storage/local-file-storage.spec.ts b/projects/aas-server/src/test/file-storage/local-file-storage.spec.ts new file mode 100644 index 00000000..0717a8ee --- /dev/null +++ b/projects/aas-server/src/test/file-storage/local-file-storage.spec.ts @@ -0,0 +1,77 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ + +import 'reflect-metadata'; +import fs, { Dirent } from 'fs'; +import { describe, beforeEach, it, expect, jest, afterEach } from '@jest/globals'; +import { LocalFileStorage } from '../../app/file-storage/local-file-storage.js'; +import { resolve, sep } from 'path'; +import { createSpyObj } from '../utils.js'; + +describe('LocalFileStorage', () => { + let storage: LocalFileStorage; + + beforeEach(() => { + storage = new LocalFileStorage(sep); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should create', () => { + expect(storage).toBeTruthy(); + }); + + describe('exists', () => { + it('returns true if file exists', async () => { + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + await expect(storage.exists('file.txt')).resolves.toBeTruthy(); + expect(fs.existsSync).toHaveBeenCalledWith(resolve('/file.txt')); + }); + + it('returns false if file does not exist', async () => { + jest.spyOn(fs, 'existsSync').mockReturnValue(false); + await expect(storage.exists('unknown.txt')).resolves.toBeFalsy(); + expect(fs.existsSync).toHaveBeenCalledWith(resolve('/unknown.txt')); + }); + }); + + describe('readDir', () => { + let files: jest.Mocked[]; + + beforeEach(() => { + files = [ + createSpyObj(['isDirectory'], { name: 'A', path: '/A' }), + createSpyObj(['isDirectory'], { name: 'B', path: '/B' }), + ]; + + files[0].isDirectory.mockReturnValue(false); + files[1].isDirectory.mockReturnValue(true); + }); + + it('returns the directory contents', async () => { + jest.spyOn(fs.promises, 'readdir').mockResolvedValue(files); + await expect(storage.readDir('./')).resolves.toEqual([ + { name: 'A', path: '/A', type: 'file' }, + { name: 'B', path: '/B', type: 'directory' }, + ]); + + expect(fs.promises.readdir).toHaveBeenCalledWith(resolve('/'), { withFileTypes: true }); + }); + }); + + describe('readFile', () => { + it('reads the file content', async () => { + jest.spyOn(fs.promises, 'readFile').mockResolvedValue(Buffer.from('Hello world!')); + const buffer = await storage.readFile('./a/file.txt'); + expect(buffer.toString()).toEqual('Hello world!'); + expect(fs.promises.readFile).toHaveBeenCalledWith(resolve('/a/file.txt')); + }); + }); +}); diff --git a/projects/aas-server/src/test/file-storage/webdav-storage.spec.ts b/projects/aas-server/src/test/file-storage/webdav-storage.spec.ts new file mode 100644 index 00000000..6d4843e8 --- /dev/null +++ b/projects/aas-server/src/test/file-storage/webdav-storage.spec.ts @@ -0,0 +1,74 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ + +import { describe, beforeEach, it, expect, afterEach, jest } from '@jest/globals'; +import { WebDAVStorage } from '../../app/file-storage/webdav-storage.js'; +import { FileStat, WebDAVClient } from 'webdav'; +import { createSpyObj } from '../utils.js'; + +describe('WebDAVStorage', () => { + let storage: WebDAVStorage; + let client: jest.Mocked; + + beforeEach(() => { + client = createSpyObj(['exists', 'getDirectoryContents', 'getFileContents']); + storage = new WebDAVStorage('http://localhost:1234/', client); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should create', () => { + expect(storage).toBeTruthy(); + }); + + describe('exists', () => { + it('returns true if file exists', async () => { + client.exists.mockResolvedValue(true); + await expect(storage.exists('file.txt')).resolves.toBeTruthy(); + expect(client.exists).toHaveBeenCalledWith('/file.txt'); + }); + + it('returns false if file does not exist', async () => { + client.exists.mockResolvedValue(false); + await expect(storage.exists('unknown.txt')).resolves.toBeFalsy(); + expect(client.exists).toHaveBeenCalledWith('/unknown.txt'); + }); + }); + + describe('readDir', () => { + let files: FileStat[]; + + beforeEach(() => { + files = [ + { filename: '/A', basename: 'A', lastmod: '', size: 42, type: 'file', etag: null }, + { filename: '/B', basename: 'B', lastmod: '', size: 0, type: 'directory', etag: null }, + ]; + }); + + it('returns the directory contents', async () => { + client.getDirectoryContents.mockResolvedValue(files); + await expect(storage.readDir('./')).resolves.toEqual([ + { name: 'A', path: '/A', type: 'file' }, + { name: 'B', path: '/B', type: 'directory' }, + ]); + + expect(client.getDirectoryContents).toHaveBeenCalledWith('/'); + }); + }); + + describe('readFile', () => { + it('reads the file content', async () => { + client.getFileContents.mockResolvedValue(Buffer.from('Hello world!')); + const buffer = await storage.readFile('./a/file.txt'); + expect(buffer.toString()).toEqual('Hello world!'); + expect(client.getFileContents).toHaveBeenCalledWith('/a/file.txt'); + }); + }); +}); diff --git a/projects/aas-server/src/test/packages/aasx-server/aasx-package.spec.ts b/projects/aas-server/src/test/packages/aasx-server/aasx-package.spec.ts index b728102f..6a5cac34 100644 --- a/projects/aas-server/src/test/packages/aasx-server/aasx-package.spec.ts +++ b/projects/aas-server/src/test/packages/aasx-server/aasx-package.spec.ts @@ -13,6 +13,7 @@ import { Logger } from '../../../app/logging/logger.js'; import { LocalFileStorage } from '../../../app/file-storage/local-file-storage.js'; import { createSpyObj } from '../../utils.js'; import { FileStorage } from '../../../app/file-storage/file-storage.js'; +import { resolve } from 'path'; describe('AasxPackage', function () { let logger: jest.Mocked; @@ -21,7 +22,7 @@ describe('AasxPackage', function () { beforeEach(function () { logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); - fileStorage = new LocalFileStorage('./src/test/assets/samples'); + fileStorage = new LocalFileStorage('./src/test/assets/'); source = new AasxDirectory(logger, 'file:///samples', 'Samples', fileStorage); }); diff --git a/projects/aas-server/src/test/template/template-storage.spec.ts b/projects/aas-server/src/test/template/template-storage.spec.ts index 5c4ffd63..c3c1d89f 100644 --- a/projects/aas-server/src/test/template/template-storage.spec.ts +++ b/projects/aas-server/src/test/template/template-storage.spec.ts @@ -6,23 +6,31 @@ * *****************************************************************************/ +import 'reflect-metadata'; import { describe, beforeEach, it, expect, jest } from '@jest/globals'; -import path from 'path'; import { TemplateDescriptor, aas } from 'common'; import { TemplateStorage } from '../../app/template/template-storage.js'; import { createSpyObj } from '../utils.js'; import { Logger } from '../../app/logging/logger.js'; -import { FileStorage } from '../../app/file-storage/file-storage.js'; +import { FileStorage, FileStorageEntry } from '../../app/file-storage/file-storage.js'; +import { FileStorageProvider } from '../../app/file-storage/file-storage-provider.js'; +import { Variable } from '../../app/variable.js'; describe('TemplateStorage', function () { let templateStorage: TemplateStorage; let logger: jest.Mocked; let fileStorage: jest.Mocked; + let fileStorageProvider: jest.Mocked; + let variable: jest.Mocked; beforeEach(function () { logger = createSpyObj(['error']); - fileStorage = createSpyObj(['exists', 'isDirectory', 'readDir', 'readFile'], { root: './' }); - templateStorage = new TemplateStorage(logger, fileStorage); + fileStorageProvider = createSpyObj(['get']); + fileStorage = createSpyObj(['exists', 'readDir', 'readFile']); + fileStorageProvider.get.mockReturnValue(fileStorage); + + variable = createSpyObj([], { TEMPLATE_STORAGE: 'file:///templates' }); + templateStorage = new TemplateStorage(logger, variable, fileStorageProvider); }); it('should create', function () { @@ -43,8 +51,10 @@ describe('TemplateStorage', function () { it('reads all available templates', async function () { fileStorage.exists.mockResolvedValue(true); - fileStorage.isDirectory.mockResolvedValue(false); - fileStorage.readDir.mockResolvedValue(['submodel.json']); + fileStorage.readDir.mockResolvedValue([ + { name: 'submodel.json', path: '/submodel.json', type: 'file' } as FileStorageEntry, + ]); + fileStorage.readFile.mockResolvedValue(Buffer.from(JSON.stringify(submodel))); await expect(templateStorage.readAsync()).resolves.toEqual([ { @@ -55,7 +65,7 @@ describe('TemplateStorage', function () { }, ] as TemplateDescriptor[]); - expect(fileStorage.readFile).toHaveBeenCalledWith(path.join('templates', 'submodel.json')); + expect(fileStorage.readFile).toHaveBeenCalledWith('/templates/submodel.json'); }); }); }); diff --git a/projects/common/src/lib/types.ts b/projects/common/src/lib/types.ts index 3a2bf6f0..26b24fe7 100644 --- a/projects/common/src/lib/types.ts +++ b/projects/common/src/lib/types.ts @@ -11,7 +11,7 @@ import * as aas from './aas.js'; /** Defines the supported endpoint types. */ export type EndpointType = 'file' | 'http' | 'opc'; -/** Represents an endpoint of a AAS resource. */ +/** Represents an endpoint of an AAS resource. */ export interface Endpoint { address: string; type: EndpointType;