From be564bab65067e719e84850896ad8958d8684266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Gerrit=20G=C3=B6bel?= <86782124+jggoebel@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:00:46 +0100 Subject: [PATCH 1/6] Add mermaid-js to admin-ui (#202) --- package-lock.json | 904 ++++++++++++++++++++++---- package.json | 1 + src/app/step/hf-markdown.component.ts | 77 ++- 3 files changed, 844 insertions(+), 138 deletions(-) diff --git a/package-lock.json b/package-lock.json index e401ce09..d60a7b0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "chartjs-plugin-datalabels": "^2.1.0", "font-awesome": "^4.7.0", "marked": "^4.3.0", + "mermaid": "^10.6.0", "moment": "^2.29.4", "ng2-charts": "^4.0.0", "ng2-dragula": "^5.0.0", @@ -2470,8 +2471,7 @@ }, "node_modules/@braintree/sanitize-url": { "version": "6.0.4", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/@cds/city": { "version": "1.1.0", @@ -3427,6 +3427,32 @@ "@types/node": "*" } }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==" + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/dragula": { "version": "2.1.39", "resolved": "https://registry.npmjs.org/@types/dragula/-/dragula-2.1.39.tgz", @@ -3510,11 +3536,24 @@ "version": "4.3.2", "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, "node_modules/@types/node": { "version": "12.20.55", "dev": true, @@ -3587,6 +3626,11 @@ "version": "2.0.7", "license": "MIT" }, + "node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, "node_modules/@types/ws": { "version": "8.5.10", "dev": true, @@ -5010,6 +5054,15 @@ "node": ">=4" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chardet": { "version": "0.7.0", "dev": true, @@ -5187,7 +5240,6 @@ "node_modules/commander": { "version": "8.3.0", "license": "MIT", - "optional": true, "engines": { "node": ">= 12" } @@ -5457,7 +5509,6 @@ "node_modules/cose-base": { "version": "1.0.3", "license": "MIT", - "optional": true, "dependencies": { "layout-base": "^1.0.0" } @@ -5693,7 +5744,6 @@ "node_modules/cytoscape": { "version": "3.28.1", "license": "MIT", - "optional": true, "dependencies": { "heap": "^0.2.6", "lodash": "^4.17.21" @@ -5705,7 +5755,6 @@ "node_modules/cytoscape-cose-bilkent": { "version": "4.1.0", "license": "MIT", - "optional": true, "dependencies": { "cose-base": "^1.0.0" }, @@ -5715,7 +5764,8 @@ }, "node_modules/cytoscape-fcose": { "version": "2.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", "optional": true, "dependencies": { "cose-base": "^2.2.0" @@ -5726,7 +5776,8 @@ }, "node_modules/cytoscape-fcose/node_modules/cose-base": { "version": "2.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", "optional": true, "dependencies": { "layout-base": "^2.0.0" @@ -5734,13 +5785,14 @@ }, "node_modules/cytoscape-fcose/node_modules/layout-base": { "version": "2.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", "optional": true }, "node_modules/d3": { "version": "7.8.5", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", "dependencies": { "d3-array": "3", "d3-axis": "3", @@ -5779,8 +5831,8 @@ }, "node_modules/d3-array": { "version": "3.2.4", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", "dependencies": { "internmap": "1 - 2" }, @@ -5790,16 +5842,16 @@ }, "node_modules/d3-axis": { "version": "3.0.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", "engines": { "node": ">=12" } }, "node_modules/d3-brush": { "version": "3.0.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -5813,8 +5865,8 @@ }, "node_modules/d3-chord": { "version": "3.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", "dependencies": { "d3-path": "1 - 3" }, @@ -5824,16 +5876,16 @@ }, "node_modules/d3-color": { "version": "3.1.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", "engines": { "node": ">=12" } }, "node_modules/d3-contour": { "version": "4.0.2", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", "dependencies": { "d3-array": "^3.2.0" }, @@ -5843,8 +5895,8 @@ }, "node_modules/d3-delaunay": { "version": "6.0.4", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "dependencies": { "delaunator": "5" }, @@ -5854,16 +5906,16 @@ }, "node_modules/d3-dispatch": { "version": "3.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", "engines": { "node": ">=12" } }, "node_modules/d3-drag": { "version": "3.0.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" @@ -5874,8 +5926,8 @@ }, "node_modules/d3-dsv": { "version": "3.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "dependencies": { "commander": "7", "iconv-lite": "0.6", @@ -5898,16 +5950,16 @@ }, "node_modules/d3-dsv/node_modules/commander": { "version": "7.2.0", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "engines": { "node": ">= 10" } }, "node_modules/d3-dsv/node_modules/iconv-lite": { "version": "0.6.3", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -5917,16 +5969,16 @@ }, "node_modules/d3-ease": { "version": "3.0.1", - "license": "BSD-3-Clause", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", "engines": { "node": ">=12" } }, "node_modules/d3-fetch": { "version": "3.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "dependencies": { "d3-dsv": "1 - 3" }, @@ -5936,8 +5988,8 @@ }, "node_modules/d3-force": { "version": "3.0.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", @@ -5949,16 +6001,16 @@ }, "node_modules/d3-format": { "version": "3.1.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", "engines": { "node": ">=12" } }, "node_modules/d3-geo": { "version": "3.1.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", "dependencies": { "d3-array": "2.5.0 - 3" }, @@ -5968,16 +6020,16 @@ }, "node_modules/d3-hierarchy": { "version": "3.1.2", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", "engines": { "node": ">=12" } }, "node_modules/d3-interpolate": { "version": "3.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", "dependencies": { "d3-color": "1 - 3" }, @@ -5987,40 +6039,75 @@ }, "node_modules/d3-path": { "version": "3.1.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "engines": { "node": ">=12" } }, "node_modules/d3-polygon": { "version": "3.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", "engines": { "node": ">=12" } }, "node_modules/d3-quadtree": { "version": "3.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", "engines": { "node": ">=12" } }, "node_modules/d3-random": { "version": "3.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", "engines": { "node": ">=12" } }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, "node_modules/d3-scale": { "version": "4.0.2", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", @@ -6034,8 +6121,8 @@ }, "node_modules/d3-scale-chromatic": { "version": "3.0.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" @@ -6046,16 +6133,16 @@ }, "node_modules/d3-selection": { "version": "3.0.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "engines": { "node": ">=12" } }, "node_modules/d3-shape": { "version": "3.2.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "dependencies": { "d3-path": "^3.1.0" }, @@ -6065,8 +6152,8 @@ }, "node_modules/d3-time": { "version": "3.1.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "dependencies": { "d3-array": "2 - 3" }, @@ -6076,8 +6163,8 @@ }, "node_modules/d3-time-format": { "version": "4.1.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "dependencies": { "d3-time": "1 - 3" }, @@ -6087,16 +6174,16 @@ }, "node_modules/d3-timer": { "version": "3.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", "engines": { "node": ">=12" } }, "node_modules/d3-transition": { "version": "3.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", @@ -6113,8 +6200,8 @@ }, "node_modules/d3-zoom": { "version": "3.0.0", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", @@ -6127,9 +6214,9 @@ } }, "node_modules/dagre-d3-es": { - "version": "7.0.9", - "license": "MIT", - "optional": true, + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", + "integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", "dependencies": { "d3": "^7.8.2", "lodash-es": "^4.17.21" @@ -6158,12 +6245,10 @@ }, "node_modules/dayjs": { "version": "1.11.10", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/debug": { "version": "4.3.4", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -6182,6 +6267,18 @@ "dev": true, "license": "MIT" }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", "dev": true, @@ -6251,8 +6348,8 @@ }, "node_modules/delaunator": { "version": "5.0.1", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", "dependencies": { "robust-predicates": "^3.0.2" } @@ -6285,7 +6382,6 @@ }, "node_modules/dequal": { "version": "2.0.3", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6310,6 +6406,14 @@ "dev": true, "license": "MIT" }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "dev": true, @@ -6412,9 +6516,9 @@ } }, "node_modules/dompurify": { - "version": "2.4.3", - "license": "(MPL-2.0 OR Apache-2.0)", - "optional": true + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.9.tgz", + "integrity": "sha512-uyb4NDIvQ3hRn6NiC+SIFaP4mJ/MdXlvtunaqK9Bn6dD3RuB/1S/gasEjDHD8eiaqdSael2vBv+hOs7Y+jhYOQ==" }, "node_modules/domutils": { "version": "3.1.0", @@ -6482,9 +6586,9 @@ "license": "ISC" }, "node_modules/elkjs": { - "version": "0.8.2", - "license": "EPL-2.0", - "optional": true + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.2.tgz", + "integrity": "sha512-2Y/RaA1pdgSHpY0YG4TYuYCD2wh97CRvu22eLG3Kz0pgQ/6KbIFTxsTnDc4MH/6hFlg2L/9qXrDMG0nMjP63iw==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -8272,8 +8376,7 @@ }, "node_modules/heap": { "version": "0.2.7", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/hosted-git-info": { "version": "6.1.1", @@ -8770,8 +8873,8 @@ }, "node_modules/internmap": { "version": "2.0.3", - "license": "ISC", - "optional": true, + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", "engines": { "node": ">=12" } @@ -9751,7 +9854,6 @@ "https://github.com/sponsors/katex" ], "license": "MIT", - "optional": true, "dependencies": { "commander": "^8.3.0" }, @@ -9768,8 +9870,7 @@ } }, "node_modules/khroma": { - "version": "2.1.0", - "optional": true + "version": "2.1.0" }, "node_modules/kind-of": { "version": "6.0.3", @@ -9779,6 +9880,14 @@ "node": ">=0.10.0" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/klona": { "version": "2.0.6", "dev": true, @@ -9798,8 +9907,7 @@ }, "node_modules/layout-base": { "version": "1.0.2", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/less": { "version": "4.1.3", @@ -9978,7 +10086,6 @@ }, "node_modules/lodash": { "version": "4.17.21", - "devOptional": true, "license": "MIT" }, "node_modules/lodash-es": { @@ -10335,6 +10442,41 @@ "node": ">= 12" } }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/media-typer": { "version": "0.3.0", "dev": true, @@ -10373,23 +10515,27 @@ } }, "node_modules/mermaid": { - "version": "9.4.3", - "license": "MIT", - "optional": true, - "dependencies": { - "@braintree/sanitize-url": "^6.0.0", - "cytoscape": "^3.23.0", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.9.0.tgz", + "integrity": "sha512-swZju0hFox/B/qoLKK0rOxxgh8Cf7rJSfAUc1u8fezVihYMvrJAS45GzAxTVf4Q+xn9uMgitBcmWk7nWGXOs/g==", + "dependencies": { + "@braintree/sanitize-url": "^6.0.1", + "@types/d3-scale": "^4.0.3", + "@types/d3-scale-chromatic": "^3.0.0", + "cytoscape": "^3.28.1", "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.1.0", "d3": "^7.4.0", - "dagre-d3-es": "7.0.9", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.10", "dayjs": "^1.11.7", - "dompurify": "2.4.3", - "elkjs": "^0.8.2", + "dompurify": "^3.0.5", + "elkjs": "^0.9.0", + "katex": "^0.16.9", "khroma": "^2.0.0", "lodash-es": "^4.17.21", + "mdast-util-from-markdown": "^1.3.0", "non-layered-tidy-tree-layout": "^2.0.2", - "stylis": "^4.1.2", + "stylis": "^4.1.3", "ts-dedent": "^2.2.0", "uuid": "^9.0.0", "web-worker": "^1.2.0" @@ -10403,6 +10549,427 @@ "node": ">= 0.6" } }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, "node_modules/micromatch": { "version": "4.0.5", "dev": true, @@ -10725,6 +11292,14 @@ "node": "*" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, "node_modules/mrmime": { "version": "1.0.1", "dev": true, @@ -10735,7 +11310,6 @@ }, "node_modules/ms": { "version": "2.1.2", - "dev": true, "license": "MIT" }, "node_modules/multicast-dns": { @@ -10888,6 +11462,52 @@ "zone.js": "~0.13.0" } }, + "node_modules/ngx-markdown/node_modules/dagre-d3-es": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz", + "integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==", + "optional": true, + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, + "node_modules/ngx-markdown/node_modules/dompurify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", + "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==", + "optional": true + }, + "node_modules/ngx-markdown/node_modules/elkjs": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", + "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==", + "optional": true + }, + "node_modules/ngx-markdown/node_modules/mermaid": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.4.3.tgz", + "integrity": "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==", + "optional": true, + "dependencies": { + "@braintree/sanitize-url": "^6.0.0", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.4.0", + "dagre-d3-es": "7.0.9", + "dayjs": "^1.11.7", + "dompurify": "2.4.3", + "elkjs": "^0.8.2", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.2", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, "node_modules/node-addon-api": { "version": "3.2.1", "dev": true, @@ -10942,8 +11562,7 @@ }, "node_modules/non-layered-tidy-tree-layout": { "version": "2.0.2", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/nopt": { "version": "6.0.0", @@ -12691,8 +13310,8 @@ }, "node_modules/robust-predicates": { "version": "3.0.2", - "license": "Unlicense", - "optional": true + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" }, "node_modules/rollup": { "version": "3.29.4", @@ -12741,8 +13360,8 @@ }, "node_modules/rw": { "version": "1.3.3", - "license": "BSD-3-Clause", - "optional": true + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "node_modules/rxjs": { "version": "7.8.1", @@ -12751,6 +13370,17 @@ "tslib": "^2.1.0" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-array-concat": { "version": "1.1.0", "dev": true, @@ -12805,7 +13435,6 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "devOptional": true, "license": "MIT" }, "node_modules/sass": { @@ -13754,8 +14383,7 @@ }, "node_modules/stylis": { "version": "4.3.1", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/supports-color": { "version": "5.5.0", @@ -14112,7 +14740,6 @@ "node_modules/ts-dedent": { "version": "2.2.0", "license": "MIT", - "optional": true, "engines": { "node": ">=6.10" } @@ -14474,6 +15101,18 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "dev": true, @@ -14556,11 +15195,27 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", - "optional": true, "bin": { "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "dev": true, @@ -14660,8 +15315,7 @@ }, "node_modules/web-worker": { "version": "1.3.0", - "license": "Apache-2.0", - "optional": true + "license": "Apache-2.0" }, "node_modules/webidl-conversions": { "version": "6.1.0", diff --git a/package.json b/package.json index 7577da07..e91eb764 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "chartjs-plugin-datalabels": "^2.1.0", "font-awesome": "^4.7.0", "marked": "^4.3.0", + "mermaid": "^10.6.0", "moment": "^2.29.4", "ng2-charts": "^4.0.0", "ng2-dragula": "^5.0.0", diff --git a/src/app/step/hf-markdown.component.ts b/src/app/step/hf-markdown.component.ts index a14deec7..3253da4f 100644 --- a/src/app/step/hf-markdown.component.ts +++ b/src/app/step/hf-markdown.component.ts @@ -1,7 +1,23 @@ import { Component, Input, OnChanges } from '@angular/core'; import { MarkdownService } from 'ngx-markdown'; -import { VirtualMachine as VM } from '../data/virtualmachine'; import { CtrService } from '../data/ctr.service'; +import { VirtualMachine as VM } from '../data/virtualmachine'; + +import Prism from 'prismjs'; +import mermaid from 'mermaid'; +// Load desired languages +// TODO: Import all available languages +import 'prismjs/components/prism-css'; +import 'prismjs/components/prism-javascript'; +import 'prismjs/components/prism-java'; +import 'prismjs/components/prism-markup'; +import 'prismjs/components/prism-typescript'; +import 'prismjs/components/prism-sass'; +import 'prismjs/components/prism-scss'; +import 'prismjs/components/prism-go'; +import 'prismjs/components/prism-docker'; +import 'prismjs/components/prism-python'; +import 'prismjs/components/prism-yaml'; // Replacement for lodash's escape const escape = (s: string) => @@ -31,8 +47,11 @@ export class HfMarkdownComponent implements OnChanges { constructor( public markdownService: MarkdownService, - private ctrService: CtrService + private ctrService: CtrService, ) { + mermaid.initialize({ + startOnLoad: false, + }); this.markdownService.renderer.code = (code: string, language = '') => { const [tag, ...args] = language.split(':'); if (tag in this.taggedCodeRenderers) { @@ -103,8 +122,7 @@ export class HfMarkdownComponent implements OnChanges { const filename = parts[parts.length - 1]; const n = 5; //Length of randomized token // Using only EOF as a token can cause trouble when the token is inside the file content. Let's use EOL together with a random string - const token = - 'EOF_' + (Math.random().toString(36) + '0000').slice(2, n + 2); + const token = 'EOF_' + this.uniqueString(n); const fileContent = `cat << ${token} > ${filepath} ${code} ${token}`; @@ -116,19 +134,46 @@ ${token}`; title="Click to create ${filepath} on ${target}" >${this.renderHighlightedCode(code, language, filename)}`; }, + + mermaid(code: string) { + const n = 5; + const containerId = `mermaid-${this.uniqueString(n)}`; + // Start the async rendering process + setTimeout(() => this.renderMermaidGraph(code, containerId), 0); + // Return a placeholder with the unique ID + return `
Loading mermaid graph...
`; + }, }; private renderHighlightedCode( code: string, language: string, - fileName?: string + fileName?: string, ) { const fileNameTag = fileName ? `

${fileName}

` : `

${language}

`; - const classAttr = `class="language-${language}"`; - const codeNode = `${escape(code)}`; - return `
${fileNameTag}${codeNode}
`; + const classAttr = `language-${language}`; + + if (Prism.languages[language]) { + code = Prism.highlight(code, Prism.languages[language], language); + } + + return `
${fileNameTag}${code}
`; + } + + private renderMermaidGraph(code: string, containerId: string) { + mermaid + .render('svg-' + containerId, code) + .then((renderResult) => { + const container = document.getElementById(containerId); + if (container) { + container.innerHTML = renderResult.svg; + } + }) + .catch((error) => { + console.error('Mermaid rendering failed:', error); + }); } private renderNestedPlainCode(code: string) { @@ -175,21 +220,27 @@ ${token}`; const contentWithReplacedTokens = this.replaceSessionToken( this.replaceVmInfoTokens(this.content), ); - // the compile method internally uses the Angular Dom Sanitizer and is therefore safe to use - this.processedContent = this.markdownService.parse(contentWithReplacedTokens); + // the parse method internally uses the Angular Dom Sanitizer and is therefore safe to use + this.processedContent = this.markdownService.parse( + contentWithReplacedTokens, + ); } private replaceVmInfoTokens(content: string) { - return content?.replace( + return content.replace( /\$\{vminfo:([^:]*):([^}]*)\}/g, (match, vmName, propName) => { const vm = this.context.vmInfo?.[vmName.toLowerCase()]; return String(vm?.[propName as keyof VM] ?? match); - } + }, ); } private replaceSessionToken(content: string) { - return content?.replace(/\$\{session\}/g, this.context.session); + return content.replace(/\$\{session\}/g, this.context.session); + } + + private uniqueString(n: number) { + return `${(Math.random().toString(36) + '0000').slice(2, n + 2)}`; } } From 8eb82283098e0d1f3eb0e18832023ed0a3919a5c Mon Sep 17 00:00:00 2001 From: maxsva <110593497+maxsva@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:41:52 +0100 Subject: [PATCH 2/6] configuration fix new VMTemplate create (#200) Co-authored-by: Maksym Veres --- .../edit-vmtemplate.component.html | 100 +++++++++++------- .../edit-vmtemplate.component.ts | 4 +- 2 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/app/configuration/vmtemplates/edit-vmtemplate/edit-vmtemplate.component.html b/src/app/configuration/vmtemplates/edit-vmtemplate/edit-vmtemplate.component.html index a8c8baf7..9cf52726 100644 --- a/src/app/configuration/vmtemplates/edit-vmtemplate/edit-vmtemplate.component.html +++ b/src/app/configuration/vmtemplates/edit-vmtemplate/edit-vmtemplate.component.html @@ -86,22 +86,35 @@

Basic Information

- - Name - {{ template.name }} - - {{ uneditedTemplate.name }} - {{ template.name }} - - - - Image - {{ template.image }} - - {{ uneditedTemplate.image }} - {{ template.image }} - - + + + Name + {{ template.name }} + + {{ uneditedTemplate.name }} + {{ template.name }} + + + + Image + {{ template.image }} + + {{ uneditedTemplate.image }} + {{ template.image }} + + + + + + Name + {{ template.name }} + + + Image + {{ template.image }} + + + @@ -147,31 +160,40 @@

Cloud Config

- - - {{ item.key }} - {{ item.value }} - - - {{ item.key }} - {{ item.value }} - - - - {{ uneditedTemplate.config_map[item.key] }} - {{ item.value }} - - + + + + {{ item.key }} + {{ item.value }} + + + {{ item.key }} + {{ item.value }} + + + + {{ uneditedTemplate.config_map[item.key] }} + {{ item.value }} + + + + {{ item.key }} + {{ item.value }} + - - - - - {{ item.key }} - {{ item.value }} - - + + + + + + {{ item.key }} + {{ item.value }} + + + + + diff --git a/src/app/configuration/vmtemplates/edit-vmtemplate/edit-vmtemplate.component.ts b/src/app/configuration/vmtemplates/edit-vmtemplate/edit-vmtemplate.component.ts index d1b47dd1..83893fbf 100644 --- a/src/app/configuration/vmtemplates/edit-vmtemplate/edit-vmtemplate.component.ts +++ b/src/app/configuration/vmtemplates/edit-vmtemplate/edit-vmtemplate.component.ts @@ -61,7 +61,7 @@ export class EditVmtemplateComponent implements OnInit, OnChanges { ) {} ngOnInit(): void { - this._build(); + this._build(); } @ViewChild('wizard', { static: true }) wizard: ClrWizard; @@ -242,4 +242,6 @@ export class EditVmtemplateComponent implements OnInit, OnChanges { } this.uneditedTemplate = JSON.parse(JSON.stringify(this.template)); } + } + From 430a434e49b61060445e04edb4fdc499347b71fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Gerrit=20G=C3=B6bel?= <86782124+jggoebel@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:45:11 +0100 Subject: [PATCH 3/6] Add scopes to configuration menu (#203) * Add scopes to menu * remove logs --- src/app/app-routing.module.ts | 76 ++++++++----------- .../configuration.component.html | 26 +++++-- .../configuration/configuration.component.ts | 32 ++++++-- .../edit-environment.component.ts | 1 - .../environment-detail.component.ts | 1 - .../settings/settings.component.html | 76 ++++++------------- .../settings/settings.component.ts | 39 ++++++++-- src/app/content/content.component.ts | 2 +- .../course-wizard/course-wizard.component.ts | 1 - 9 files changed, 134 insertions(+), 120 deletions(-) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index e8f4bf31..09ce80b4 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -17,93 +17,85 @@ import { StepComponent } from './step/step-component/step.component'; import { TerminalComponent } from './step/terminal/terminal.component'; import { RolesComponent } from './configuration/roles/roles/roles.component'; import { SessionStatisticsComponent } from './session-statistics/session-statistics.component'; -import {SettingsComponent} from './configuration/settings/settings.component'; +import { SettingsComponent } from './configuration/settings/settings.component'; import { DashboardDetailsComponent } from './dashboards/dashboard-details/dashboard-details.component'; const routes: Routes = [ - {path: '', redirectTo: '/home', pathMatch: 'full'}, - {path: 'login', component: LoginComponent}, + { path: '', redirectTo: '/home', pathMatch: 'full' }, + { path: 'login', component: LoginComponent }, { path: 'home', component: HomeComponent, - canActivate: [ - AuthGuard - ], + canActivate: [AuthGuard], children: [ { path: 'statistics/sessions', - component: SessionStatisticsComponent - } - ] + component: SessionStatisticsComponent, + }, + ], }, { path: 'dashboards', component: DashboardsComponent, - canActivate: [ - AuthGuard - ], + canActivate: [AuthGuard], children: [ { path: 'event/:id', - component: DashboardDetailsComponent - } - ] + component: DashboardDetailsComponent, + }, + ], }, { path: 'events', component: EventComponent, - canActivate: [ - AuthGuard - ] + canActivate: [AuthGuard], }, { path: 'content', component: ContentComponent, - canActivate: [ - AuthGuard - ], + canActivate: [AuthGuard], children: [ { path: 'scenarios', - component: ScenarioComponent + component: ScenarioComponent, }, { path: 'courses', - component: CourseComponent - } - ] + component: CourseComponent, + }, + ], }, { path: 'users', component: UserComponent, - canActivate: [ - AuthGuard - ] + canActivate: [AuthGuard], }, { path: 'configuration', component: ConfigurationComponent, - canActivate: [ - AuthGuard - ], + canActivate: [AuthGuard], children: [ + { + path: 'settings/:scope', + component: SettingsComponent, + }, { path: 'settings', - component: SettingsComponent + component: SettingsComponent, }, { path: 'environments', - component: EnvironmentsComponent + component: EnvironmentsComponent, }, { path: 'vmtemplates', - component: VmtemplatesComponent + component: VmtemplatesComponent, }, { path: 'roles', - component: RolesComponent - } - ] + component: RolesComponent, + }, + ], }, { path: 'session/:session/steps/:step', @@ -113,14 +105,12 @@ const routes: Routes = [ { path: 'scenario/:scenario/printable', component: PrintableComponent, - canActivate: [ - AuthGuard - ] - } + canActivate: [AuthGuard], + }, ]; @NgModule({ imports: [RouterModule.forRoot(routes, {})], - exports: [RouterModule] + exports: [RouterModule], }) -export class AppRoutingModule { } \ No newline at end of file +export class AppRoutingModule {} diff --git a/src/app/configuration/configuration.component.html b/src/app/configuration/configuration.component.html index 453ff1d6..d6409d1b 100644 --- a/src/app/configuration/configuration.component.html +++ b/src/app/configuration/configuration.component.html @@ -2,13 +2,6 @@
- Settings Roles + + + + Settings + + + {{ scope.displayName }} + + +
diff --git a/src/app/configuration/configuration.component.ts b/src/app/configuration/configuration.component.ts index 3bf3d97e..0dfcd654 100644 --- a/src/app/configuration/configuration.component.ts +++ b/src/app/configuration/configuration.component.ts @@ -1,6 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { RbacService } from '../data/rbac.service'; import { Router } from '@angular/router'; +import { + PreparedScope, + TypedSettingsService, +} from '../data/typedSettings.service'; @Component({ selector: 'app-configuration', @@ -12,7 +16,15 @@ export class ConfigurationComponent implements OnInit { public listVMTemplates = false; public listRoles = false; - constructor(private rbacService: RbacService, private router: Router) {} + public scopes: PreparedScope[] = []; + public scopesLoading: boolean = true; + public expandedSettingsGroup = true; + + constructor( + private rbacService: RbacService, + private router: Router, + private typedSettingsService: TypedSettingsService + ) {} ngOnInit() { const authorizationRequests = Promise.all([ @@ -31,15 +43,19 @@ export class ConfigurationComponent implements OnInit { this.listRoles = permissions[4]; if (this.showSettings) { - this.router.navigateByUrl(`/configuration/settings`); - } else if (this.listEnvironments) { - this.router.navigateByUrl(`/configuration/environments`); - } else if (this.listVMTemplates) { - this.router.navigateByUrl(`/configuration/vmtemplates`); - } else if (this.listRoles) { - this.router.navigateByUrl(`/configuration/roles`); + this.getScopes(); } } ); } + + getScopes() { + this.scopes = []; + this.typedSettingsService.listScopes().subscribe({ + next: (scopes: PreparedScope[]) => { + this.scopes = scopes; + this.scopesLoading = false; + }, + }); + } } diff --git a/src/app/configuration/environments/edit-environment/edit-environment.component.ts b/src/app/configuration/environments/edit-environment/edit-environment.component.ts index 6871e64f..641caabd 100644 --- a/src/app/configuration/environments/edit-environment/edit-environment.component.ts +++ b/src/app/configuration/environments/edit-environment/edit-environment.component.ts @@ -81,7 +81,6 @@ export class EditEnvironmentComponent implements OnInit, OnChanges { .Grants('virtualmachinetemplates', 'list') .then((allowVMTemplateList: boolean) => { if (!allowVMTemplateList) { - console.log('Disallow'); return; } vmTemplateService diff --git a/src/app/configuration/environments/environment-detail/environment-detail.component.ts b/src/app/configuration/environments/environment-detail/environment-detail.component.ts index 79aae051..8df7f788 100644 --- a/src/app/configuration/environments/environment-detail/environment-detail.component.ts +++ b/src/app/configuration/environments/environment-detail/environment-detail.component.ts @@ -29,7 +29,6 @@ export class EnvironmentDetailComponent implements OnInit { .Grants('virtualmachinetemplates', 'list') .then((allowVMTemplateList: boolean) => { if (!allowVMTemplateList) { - console.log("Disallow") return; } vmTemplateService diff --git a/src/app/configuration/settings/settings.component.html b/src/app/configuration/settings/settings.component.html index a74ca3c4..c39684e2 100644 --- a/src/app/configuration/settings/settings.component.html +++ b/src/app/configuration/settings/settings.component.html @@ -2,64 +2,34 @@

Settings - {{ this.selectedScope?.displayName ?? "scope" }} + {{ + this.selectedScope?.displayName ?? "scope" + }}

- - + + + +
Please wait... - Scopes are being loaded... + Settings are being loaded...
- - - - - - - - {{ sc.displayName }} - - - - - -
- Please wait... - Settings are being loaded... -
-
- - -
- No settings available for scope - {{ this.selectedScope.displayName }}. -
-
+ + +
+ No settings available for scope + {{ this.selectedScope.displayName }}. +
diff --git a/src/app/configuration/settings/settings.component.ts b/src/app/configuration/settings/settings.component.ts index 7c19fccc..12f3cde9 100644 --- a/src/app/configuration/settings/settings.component.ts +++ b/src/app/configuration/settings/settings.component.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild } from '@angular/core'; +import { Component, OnChanges, OnInit, ViewChild } from '@angular/core'; import { TypedInput, FormGroupType } from '../../typed-form/TypedInput'; import { PreparedScope, @@ -6,6 +6,7 @@ import { } from 'src/app/data/typedSettings.service'; import { AlertComponent } from 'src/app/alert/alert.component'; import { ServerResponse } from 'src/app/step/ServerResponse'; +import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; @Component({ selector: 'app-settings', @@ -27,9 +28,19 @@ export class SettingsComponent { private alertTime = 2000; private alertErrorTime = 10000; + public alertClosed: boolean = true; - constructor(public typedSettingsService: TypedSettingsService) { + constructor( + public typedSettingsService: TypedSettingsService, + private route: ActivatedRoute, + private router: Router + ) { this.getScopes(); + this.router.events.subscribe((val) => { + if (val instanceof NavigationEnd) { + this.testPath(); + } + }); } onFormChange(data: TypedInput[]) { @@ -45,10 +56,8 @@ export class SettingsComponent { if (!this.updatedSettings) { return; } - console.log(this.updatedSettings); this.typedSettingsService.updateCollection(this.updatedSettings).subscribe({ next: (resp: ServerResponse) => { - console.log(resp); this.hasChanges = false; this.alert.success( 'Settings successfully saved', @@ -82,11 +91,31 @@ export class SettingsComponent { next: (scopes: PreparedScope[]) => { this.scopes = scopes; this.scopesLoading = false; - this.setScope(this.scopes[0]); + this.testPath(); }, error: (err) => { this.alert.danger(err.error.message, true, this.alertErrorTime); }, }); } + + testPath() { + const { paramMap } = this.route.snapshot; + const scope = paramMap.get('scope')!; + if (this.scopes.length < 1) { + return; + } + + if (scope != '') { + const findScope = this.scopes.filter((a) => { + return a.name == scope; + }); + + if (findScope && findScope[0]) { + this.setScope(findScope[0]); + } + } else { + this.setScope(this.scopes[0]); + } + } } diff --git a/src/app/content/content.component.ts b/src/app/content/content.component.ts index 03edfd5b..70dd8144 100644 --- a/src/app/content/content.component.ts +++ b/src/app/content/content.component.ts @@ -20,7 +20,7 @@ export class ContentComponent implements OnInit { ngOnInit(): void { this.contentNavigation(); } - ngOnChanges(changes: SimpleChanges): void { + ngOnChanges(): void { this.contentNavigation(); } diff --git a/src/app/course/course-wizard/course-wizard.component.ts b/src/app/course/course-wizard/course-wizard.component.ts index 955ae6e4..b4d17479 100644 --- a/src/app/course/course-wizard/course-wizard.component.ts +++ b/src/app/course/course-wizard/course-wizard.component.ts @@ -214,7 +214,6 @@ export class CourseWizardComponent implements OnChanges, OnInit { } setVM(vms: {}[]) { - console.log("vms = " + vms) this.editVirtualMachines = vms; this.VMSAllow(); this.setModified(); From e0586ad32f1e60c21609b9c8baddab7f3507b946 Mon Sep 17 00:00:00 2001 From: Abdellah Brahimi <119412567+abrahimi93@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:00:43 +0100 Subject: [PATCH 4/6] Refactor Scenario Modal to use a Wizard (#177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Replace New Scenario workflow with Wizard with 3 steps instead of modal * Saving some commit * increment some changes * Making new functionalities * Validation pattern for Input when creating a new scenario that validate only latin letter and number strings * Fixing the bug issue #327 * Refactoring Scenario Replacing modal for creating scenario by a Step by step Wizard * Create a button to edit the available Scenario by Wizard * Fixing Bug mention in the issue #327 * Adding the functionality for deleting Scenario * Change var to const in scenario.component and scenerio.service * Fixing the step when adding VM to scenario during the creation of the Scenario, * Fixing the bug with Step Scenario, and quality code. * Fixing some bugs, and changing style fixing the bug with step, and enlarging datagrid to dominate the whole screen * Adding Virtual Machine in stepNav in Edit Scenario wizard * Checking all Set of VM if they have VM * Checking all Set of VM if they have VM * Checking all Set of VM if they have VM * Checking all Set of VM if they have VM * refactoring Scenario, creating Scenario-Wizard component * Correcting Edit course to Edit Scenario in Scenario-wizard * Prettify the code * add Scenario-detail component to the scenario component * create steps-scenario component * Fixing the bug with Steps Component, step content now will retain its default value after it was cancelled. * fixing the bug with step content * Fixing the alert notification when creating, updating or deleting scenario. Fixing a bug in Step-Scenario component * Giving access to delete and edit to user that have the persmission. * Adding an error alert for Scenario details to emit to parent component Scenario * Fixing caching with filtered-scenario when choosing category in Scenario component * fixing bug with scenario component with error emitting from scenario details. * Fixing some bugs * Fixing bug * Fixing the issue with Steps * optimize code in step * Fix deprecated subscription syntax * Refactoring: Use alert component & rm unused code * Some more refactoring * Prevent warning that form is not connected to button --------- Co-authored-by: Jan-Gerrit Göbel <86782124+jggoebel@users.noreply.github.com> Co-authored-by: philipab --- src/app/alert/alert.component.ts | 2 +- src/app/alert/alert.ts | 8 + src/app/app.module.ts | 6 + src/app/content/content.component.ts | 6 +- src/app/data/scenario.service.ts | 17 +- .../filter-scenarios.component.ts | 8 + .../scenario-detail.component.html | 60 ++ .../scenario-detail.component.scss | 0 .../scenario-detail.component.ts | 29 + .../scenario-wizard.component.html | 489 ++++++++++++ .../scenario-wizard.component.scss | 0 .../scenario-wizard.component.ts | 394 ++++++++++ src/app/scenario/scenario.component.html | 735 +----------------- src/app/scenario/scenario.component.ts | 505 ++---------- .../steps-scenario.component.html | 141 ++++ .../steps-scenario.component.scss | 0 .../steps-scenario.component.ts | 122 +++ 17 files changed, 1387 insertions(+), 1135 deletions(-) create mode 100644 src/app/alert/alert.ts create mode 100644 src/app/scenario/scenario-detail/scenario-detail.component.html create mode 100644 src/app/scenario/scenario-detail/scenario-detail.component.scss create mode 100644 src/app/scenario/scenario-detail/scenario-detail.component.ts create mode 100644 src/app/scenario/scenario-wizard/scenario-wizard.component.html create mode 100644 src/app/scenario/scenario-wizard/scenario-wizard.component.scss create mode 100644 src/app/scenario/scenario-wizard/scenario-wizard.component.ts create mode 100644 src/app/scenario/steps-scenario/steps-scenario.component.html create mode 100644 src/app/scenario/steps-scenario/steps-scenario.component.scss create mode 100644 src/app/scenario/steps-scenario/steps-scenario.component.ts diff --git a/src/app/alert/alert.component.ts b/src/app/alert/alert.component.ts index 93d4ad6f..95c19fe8 100644 --- a/src/app/alert/alert.component.ts +++ b/src/app/alert/alert.component.ts @@ -32,7 +32,7 @@ export class AlertComponent { this.doAlert(text, ClrAlertType.Warning, closable, timeout) } - private doAlert(text: string, type: ClrAlertType = ClrAlertType.Info, closable: boolean = true, timeout: number = 0) { + public doAlert(text: string, type: ClrAlertType = ClrAlertType.Info, closable: boolean = true, timeout: number = 0) { this.alert.alertType = type; this.text = text; this.alert.closable = closable; diff --git a/src/app/alert/alert.ts b/src/app/alert/alert.ts new file mode 100644 index 00000000..e1966158 --- /dev/null +++ b/src/app/alert/alert.ts @@ -0,0 +1,8 @@ +import { ClrAlertType } from '../clr-alert-type'; + +export class AlertDetails { + type: ClrAlertType; + message: string; + closable?: boolean = true; + duration?: number = 0; +} \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2ba33b0d..5343e63a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -102,6 +102,9 @@ import { TypedArrayNumberComponent } from './typed-form/typed-array-number.compo import { TypedArrayBooleanComponent } from './typed-form/typed-array-boolean.component'; import { CourseWizardComponent } from './course/course-wizard/course-wizard.component'; import { CourseDetailsComponent } from './course/course-details/course-details.component'; +import { ScenarioWizardComponent } from './scenario/scenario-wizard/scenario-wizard.component'; +import { ScenarioDetailComponent } from './scenario/scenario-detail/scenario-detail.component'; +import { StepsScenarioComponent } from './scenario/steps-scenario/steps-scenario.component'; import { DashboardDetailsComponent } from './dashboards/dashboard-details/dashboard-details.component'; import '@cds/core/icon/register.js'; import { @@ -270,6 +273,9 @@ export function jwtOptionsFactory(): JwtConfig { TypedArrayNumberComponent, TypedArrayBooleanComponent, CourseWizardComponent, + ScenarioWizardComponent, + ScenarioDetailComponent, + StepsScenarioComponent, DashboardDetailsComponent, ], imports: [ diff --git a/src/app/content/content.component.ts b/src/app/content/content.component.ts index 70dd8144..73bd4cff 100644 --- a/src/app/content/content.component.ts +++ b/src/app/content/content.component.ts @@ -1,10 +1,6 @@ import { Component, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { RbacService } from '../data/rbac.service'; -import { ScenarioService } from '../data/scenario.service'; -import { CourseService } from '../data/course.service'; -import { Scenario } from '../data/scenario'; -import { Course } from '../data/course'; -import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-content', diff --git a/src/app/data/scenario.service.ts b/src/app/data/scenario.service.ts index 99e7fc35..9b016058 100644 --- a/src/app/data/scenario.service.ts +++ b/src/app/data/scenario.service.ts @@ -117,7 +117,7 @@ export class ScenarioService { st.content = utoa(st.content); }); - var params = new HttpParams({ encoder: new CustomEncoder() }) + const params = new HttpParams({ encoder: new CustomEncoder() }) .set('name', utoa(s.name)) .set('description', utoa(s.description)) .set('steps', JSON.stringify(steps)) @@ -146,10 +146,19 @@ export class ScenarioService { } public create(s: Scenario) { - var params = new HttpParams() + s.steps.forEach((st: Step) => { + st.title = utoa(st.title); + st.content = utoa(st.content); + }); + + const params = new HttpParams() .set('name', utoa(s.name)) .set('description', utoa(s.description)) + .set('virtualmachines', JSON.stringify(s.virtualmachines)) + .set('steps', JSON.stringify(s.steps)) .set('pause_duration', s.pause_duration) + .set('categories', JSON.stringify(s.categories)) + .set('tags', JSON.stringify(s.tags)) .set('keepalive_duration', s.keepalive_duration); return this.http.post(environment.server + '/a/scenario/new', params).pipe( @@ -178,4 +187,8 @@ export class ScenarioService { { responseType: 'text' } ); } + + public delete(id: string) { + return this.http.delete(environment.server + '/a/scenario/' + id); + } } diff --git a/src/app/filter-scenarios/filter-scenarios.component.ts b/src/app/filter-scenarios/filter-scenarios.component.ts index 0fcbbd9f..36901282 100644 --- a/src/app/filter-scenarios/filter-scenarios.component.ts +++ b/src/app/filter-scenarios/filter-scenarios.component.ts @@ -43,6 +43,14 @@ export class FilterScenariosComponent implements OnInit { this.categoryFilterForm.reset({ categoryControl: [] }); } + reloadScenarios(){ + this.scenarioService.list(true).subscribe((s: Scenario[]) => { + this.scenarios = s; + this.clearCategoryFilter(); + this.emitScenarios(this.scenarios); + }); + } + filterScenarioList() { if (this.selectedCategories.length === 0) { this.filteredScenarios = this.scenarios; diff --git a/src/app/scenario/scenario-detail/scenario-detail.component.html b/src/app/scenario/scenario-detail/scenario-detail.component.html new file mode 100644 index 00000000..71ee8aad --- /dev/null +++ b/src/app/scenario/scenario-detail/scenario-detail.component.html @@ -0,0 +1,60 @@ +
+ + + Basic Information + + Option + Value + + + Name + {{ scenario.name }} + + + Description + {{ scenario.description }} + + + Keepalive Duration + {{ scenario.keepalive_duration }} + + + Pause Duration + {{ scenario.pause_duration }} + + + Virtual Machine + {{ + scenario.virtualmachines.length + }} + + + Steps + + {{ step.title }} + + + + Categories & Tags + + Categories + + {{ categorie }} + + + Tags + + {{ tag }} + + + +
diff --git a/src/app/scenario/scenario-detail/scenario-detail.component.scss b/src/app/scenario/scenario-detail/scenario-detail.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/scenario/scenario-detail/scenario-detail.component.ts b/src/app/scenario/scenario-detail/scenario-detail.component.ts new file mode 100644 index 00000000..61ab9b63 --- /dev/null +++ b/src/app/scenario/scenario-detail/scenario-detail.component.ts @@ -0,0 +1,29 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Scenario } from 'src/app/data/scenario'; +import { ScenarioService } from 'src/app/data/scenario.service'; + +@Component({ + selector: 'scenario-detail', + templateUrl: './scenario-detail.component.html', + styleUrls: ['./scenario-detail.component.scss'], +}) +export class ScenarioDetailComponent implements OnInit { + @Input() + scenario: Scenario; + + constructor(public scenarioService: ScenarioService) {} + ngOnInit(): void { + let scenarioObservable: Observable; + scenarioObservable = this.scenarioService.get(this.scenario.id); + scenarioObservable.subscribe({ + next: (S: Scenario) => { + this.scenario = S; + }, + error: (e: HttpErrorResponse) => { + console.log(e); + }, + }); + } +} diff --git a/src/app/scenario/scenario-wizard/scenario-wizard.component.html b/src/app/scenario/scenario-wizard/scenario-wizard.component.html new file mode 100644 index 00000000..409fc82d --- /dev/null +++ b/src/app/scenario/scenario-wizard/scenario-wizard.component.html @@ -0,0 +1,489 @@ + + {{ wizardTitle }} + + Cancel + Back + Next + Finish + + + Basic Information + + + + + Virtual Machine + +
+ + + + Virtual Machines + + + + Virtual Machine Set {{ i + 1 }} + + + + + {{ item.key }} + {{ item.value }} + + + + + + + + + +
+
+ + + Steps + + + + + + + Categories + + + + +

+ You can provide multiple categories by passing them as a + Comma-separated List +

+
+ + + + Category required. + +
+ +
+
+
+ + Category + + + + + {{ a }} + + +
+
+ + + Tags + + + +

+ You can provide multiple tags by passing them as a Comma-separated + List +

+
+ + + + Tag required. + +
+ +
+
+
+ + Tag + + + + + {{ a }} + + +
+
+ + + Finalize +

Confirm the following details before finishing

+

Scenario

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionValue
Name{{ selectedscenario.name }}
Description{{ selectedscenario.description }}
Keepalive Duration{{ selectedscenario.keepalive_duration }}
Pause Duration{{ selectedscenario.pause_duration }}
Virtual Machine{{ selectedscenario.virtualmachines.length }}
Steps + + {{ step.title }} +
Categories + + {{ categorie }} +
Tags + + {{ tag }} + +
+
+
+ + + + + + + + + + + + + + diff --git a/src/app/scenario/scenario-wizard/scenario-wizard.component.scss b/src/app/scenario/scenario-wizard/scenario-wizard.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/scenario/scenario-wizard/scenario-wizard.component.ts b/src/app/scenario/scenario-wizard/scenario-wizard.component.ts new file mode 100644 index 00000000..ebd22db8 --- /dev/null +++ b/src/app/scenario/scenario-wizard/scenario-wizard.component.ts @@ -0,0 +1,394 @@ +import { + Component, + EventEmitter, + Input, + OnInit, + Output, + ViewChild, +} from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { ClrModal, ClrWizard } from '@clr/angular'; +import { RbacService } from 'src/app/data/rbac.service'; +import { Scenario } from 'src/app/data/scenario'; +import { ScenarioService } from 'src/app/data/scenario.service'; +import { ServerResponse } from 'src/app/data/serverresponse'; +import { VMTemplate } from 'src/app/data/vmtemplate'; +import { VmtemplateService } from 'src/app/data/vmtemplate.service'; +import { KeepaliveValidator } from 'src/app/validators/keepalive.validator'; +import { StepsScenarioComponent } from '../steps-scenario/steps-scenario.component'; +import { HttpErrorResponse } from '@angular/common/http'; +import { CategoryFormGroup, ScenarioFormGroup } from 'src/app/data/forms'; +import { AlertDetails } from 'src/app/alert/alert'; +import { ClrAlertType } from 'src/app/clr-alert-type'; + +@Component({ + selector: 'scenario-wizard', + templateUrl: './scenario-wizard.component.html', + styleUrls: ['./scenario-wizard.component.scss'], +}) +export class ScenarioWizardComponent implements OnInit { + @Output() + onWizardFinished: EventEmitter = + new EventEmitter(); + + public wizardMode: 'create' | 'edit' = 'create'; + public wizardTitle: 'Create new Scenario' | 'Edit Scenario' = + 'Create new Scenario'; + + public _open: boolean = false; + public selectRbac: boolean = false; + public deleteVMSetOpen: boolean = false; + public createVMOpen: boolean = false; + public newCategory: boolean = false; + public newTag: boolean = false; + + public deletingVMSetIndex: number = 0; + public newvmindex: number = 0; + public stepsToBeAdded: number = 0; + public deletingStepIndex: number = 0; + public editingIndex: number = 0; + + public vmtemplates: VMTemplate[] = []; + + public selectedscenario: Scenario; + + get keepaliveRequired() { + const ka = this.scenarioDetails.controls.keepalive_amount; + const ku = this.scenarioDetails.controls.keepalive_unit; + + // validate + if ((ka.dirty || ka.touched) && ka.invalid && ka.errors.required) { + return true; + } else if ((ku.dirty || ku.touched) && ku.invalid && ku.errors.required) { + return true; + } else { + return false; + } + } + + @ViewChild('deletevmsetmodal', { static: true }) deleteVMSetModal: ClrModal; + @ViewChild('createvmmodal', { static: true }) createVMModal: ClrModal; + @ViewChild('stepsscenario', { static: true }) + stepScenario: StepsScenarioComponent; + + constructor( + public scenarioService: ScenarioService, + public vmTemplateService: VmtemplateService, + public rbacService: RbacService + ) {} + + @ViewChild('wizard', { static: true }) wizard: ClrWizard; + + public scenarioDetails: ScenarioFormGroup = new FormGroup( + { + scenario_name: new FormControl(null, [ + Validators.required, + Validators.minLength(4), + ]), + scenario_description: new FormControl(null, [ + Validators.required, + Validators.minLength(4), + ]), + keepalive_amount: new FormControl(10, { + validators: Validators.required, + nonNullable: true, + }), + keepalive_unit: new FormControl('m', { + validators: Validators.required, + nonNullable: true, + }), + pause_duration: new FormControl(1, { + validators: [ + Validators.required, + Validators.min(1), + Validators.pattern('^[0-9]+$'), + ], + nonNullable: true, + }), + }, + { validators: KeepaliveValidator } + ); + + public vmform: FormGroup<{ + vm_name: FormControl; + vm_template: FormControl; + }> = new FormGroup({ + vm_name: new FormControl(null, [ + Validators.required, + Validators.minLength(4), + ]), + vm_template: new FormControl(null, [Validators.required]), + }); + + public newCategoryForm: CategoryFormGroup = new FormGroup({ + category: new FormControl(null, [Validators.required]), + }); + public newTagForm: FormGroup<{ + tag: FormControl; + }> = new FormGroup({ + tag: new FormControl(null, [Validators.required]), + }); + + ngOnInit() { + // let's initialize our selected scenario to prevent TypeErrors in our HTML template + this._initSelectedScenario(); + // "Get" Permission on scenarios is required to load step content + this.rbacService.Grants('scenarios', 'get').then((allowed: boolean) => { + this.selectRbac = allowed; + }); + + this.rbacService + .Grants('virtualmachinesets', 'list') + .then((listVmSets: boolean) => { + if (listVmSets) { + this.vmTemplateService.list().subscribe((v: VMTemplate[]) => { + this.vmtemplates = v; + }); + } + }); + } + + open(wizardMode: 'create' | 'edit', scenario?: Scenario) { + this.wizardMode = wizardMode; + if (this.wizardMode == 'create') { + this._createScenarioWizardfunction(); + } else { + // this wizardMode == 'edit' + this._editScenarioWizardfunction(scenario); + } + } + + doCancel(): void { + this.wizard.reset(); + this.resetScenarioForm(); + + this.wizard.close(); + } + + finishScenario() { + if (this.wizardMode == 'create') this.saveCreatedScenario(); + if (this.wizardMode == 'edit') this.saveUpdatedScenario(); + } + + saveCreatedScenario() { + this.scenarioService.create(this.selectedscenario).subscribe({ + next: (s: Scenario) => { + this.onWizardFinished.emit({ + type: ClrAlertType.Success, + message: `Scenario ${s.name} created`, + closable: true, + duration: 3000, + }); + }, + error: (e: HttpErrorResponse) => { + const errorMessage: string = e.message; + this.onWizardFinished.emit({ + type: ClrAlertType.Danger, + message: errorMessage, + closable: true, + duration: 3000, + }); + }, + }); + this.resetScenarioForm(); + } + + public copyScenarioDetails() { + this.selectedscenario.keepalive_duration = + this.scenarioDetails.controls.keepalive_amount.value + + this.scenarioDetails.controls.keepalive_unit.value; + this.selectedscenario.pause_duration = + this.scenarioDetails.controls.pause_duration.value + 'h'; + this.selectedscenario.name = + this.scenarioDetails.controls.scenario_name.value; + this.selectedscenario.description = + this.scenarioDetails.controls.scenario_description.value; + } + + saveUpdatedScenario() { + this.scenarioService + .update(this.selectedscenario) + .subscribe((s: ServerResponse) => { + if (s.type == 'updated') { + this.onWizardFinished.emit({ + type: ClrAlertType.Success, + message: `Scenario updated`, + closable: true, + duration: 3000, + }); + } else { + const errorMsg = 'Unable to update scenario: ' + s.message; + this.onWizardFinished.emit({ + type: ClrAlertType.Success, + message: errorMsg, + closable: true, + duration: 3000, + }); + } + }); + } + resetScenarioForm() { + this.wizard.reset(); + this.scenarioDetails.reset({ + keepalive_amount: 10, + keepalive_unit: 'm', + pause_duration: 1, + }); + this.selectedscenario.virtualmachines = []; + this.selectedscenario.steps = []; + this.selectedscenario.categories = []; + this.selectedscenario.tags = []; + } + addCategory() { + const categories: string[] | undefined = + this.newCategoryForm.controls.category.value?.split(','); + categories?.forEach((category) => { + category = category.replace(/\s/g, ''); //remove all whitespaces + if ( + category != '' && + !this.selectedscenario.categories.includes(category) + ) { + this.selectedscenario.categories.push(category); + } + }); + this.newCategoryForm.reset(); + this.newCategory = false; + } + deleteCategory(category: string) { + this.selectedscenario.categories.forEach((element, index) => { + if (element == category) + this.selectedscenario.categories.splice(index, 1); + }); + } + addTag() { + const tags: string[] | undefined = + this.newTagForm.controls.tag.value?.split(','); + tags?.forEach((tag) => { + tag = tag.replace(/\s/g, ''); //remove all whitespaces + if (tag != '' && !this.selectedscenario.tags.includes(tag)) { + this.selectedscenario.tags.push(tag); + } + }); + this.newTagForm.reset(); + this.newTag = false; + } + deleteTag(tag: string) { + this.selectedscenario.tags.forEach((element, index) => { + if (element == tag) this.selectedscenario.tags.splice(index, 1); + }); + } + selectedScenarioHasVM(): boolean { + if (this.selectedscenario.virtualmachines.length != 0) { + const validVMSet = this.selectedscenario.virtualmachines.filter( + (virtualmachine, i) => { + if (Object.keys(virtualmachine).length != 0) { + return true; + } + return false; + } + ); + if (validVMSet.length == this.selectedscenario.virtualmachines.length) { + return true; + } + } + return false; + } + addVMSet() { + this.selectedscenario.virtualmachines.push({}); + } + addVM() { + this.selectedScenarioHasVM(); + this.selectedscenario.virtualmachines[this.newvmindex][ + this.vmform.controls.vm_name.value + ] = this.vmform.controls.vm_template.value; + this.createVMModal.close(); + } + + deleteVMSet(i: number) { + this.deletingVMSetIndex = i; + this.deleteVMSetModal.open(); + } + + public deleteVM(setIndex: number, key: string) { + delete this.selectedscenario.virtualmachines[setIndex][key]; + } + doDeleteVMSet() { + this.selectedscenario.virtualmachines.splice(this.deletingVMSetIndex, 1); + this.deleteVMSetModal.close(); + } + public openCreateVM(i: number) { + this.vmform.reset(); + this.newvmindex = i; + this.createVMModal.open(); + } + + private _editScenarioWizardfunction(scenario?: Scenario) { + if (!scenario) { + // somehow scenario is undefined -> display an error alert + this.onWizardFinished.emit({ + type: ClrAlertType.Danger, + message: + 'Could not edit scenario. The provided scenario is not defined.', + }); + } + this.wizardTitle = 'Edit Scenario'; + this._editScenario(scenario); + } + + private _editScenario(s: Scenario) { + if (s != undefined) { + // this is only a partial scenario, we need to get the full + this.scenarioService.get(s.id).subscribe({ + next: (s: Scenario) => { + this.selectedscenario = s; + this.scenarioDetails.reset({ + scenario_name: s.name, + scenario_description: s.description, + keepalive_amount: Number( + s.keepalive_duration.substring(0, s.keepalive_duration.length - 1) + ), + keepalive_unit: s.keepalive_duration.substring( + s.keepalive_duration.length - 1, + s.keepalive_duration.length + ), + }); + const pauseDuration = Number(s.pause_duration?.slice(0, -1)); + if (!Number.isNaN(pauseDuration)) { + this.scenarioDetails.patchValue({ + pause_duration: pauseDuration, + }); + } + this.wizard.navService.goTo(this.wizard.pages.last, true); + this.wizard.pages.first.makeCurrent(); + this.wizard.open(); + }, + error: () => { + this.onWizardFinished.emit({ + type: ClrAlertType.Danger, + message: 'error editing scenario', + closable: true, + duration: 3000, + }); + }, + }); + } + } + + private _createScenarioWizardfunction() { + this.wizardTitle = 'Create new Scenario'; + this.resetScenarioForm(); + this._initSelectedScenario(); + this.wizard.open(); + } + + private _initSelectedScenario() { + this.selectedscenario = new Scenario(); + this.selectedscenario.id = ''; + this.selectedscenario.name = ''; + this.selectedscenario.virtualmachines = []; + this.selectedscenario.tags = []; + this.selectedscenario.steps = []; + this.selectedscenario.virtualmachines[0] = {}; + this.selectedscenario.categories = []; + } +} diff --git a/src/app/scenario/scenario.component.html b/src/app/scenario/scenario.component.html index 045c9b24..754d7fe8 100644 --- a/src/app/scenario/scenario.component.html +++ b/src/app/scenario/scenario.component.html @@ -1,42 +1,10 @@ - - - - Scenario has been modified. Save your changes. - - - - - - - {{ scenarioDangerAlert }} - - - - - - - {{ scenarioSuccessAlert }} - - - +

Scenarios

-
+
@@ -58,687 +26,56 @@

Scenarios

>Name + + + + {{ s.id }} {{ s.name }} +
- -
- - - - - -
- - - - Scenario name is required - Scenario name must be longer than 4 characters - - - - - - Scenario description is required - Scenario description must be longer than 4 characters - - -
- -
-
-
- - -
- -
- Period before VMs are reaped upon user inactivity. - Keepalive duration is required. - Invalid keepalive period. Valid periods: at least 1m. -
-
- - - - Period in hours that a user may pause their - scenario. - Pause duration is required. - Pause duration must be 1 hour minimum. - Pause duration must be 48 hours maximum. - - -
- - - Virtual Machines - - - - Virtual Machine Set {{ i + 1 }} - - - - - - {{ item.key }} - {{ item.value }} - - - - - - - - - - - - - -
-
- - - - - - - Index - Title - - - - - - - - - - {{ i + 1 }} - {{ s.title }} - - - - - - - - - - - - -

- You can provide multiple categories by passing them as a - Comma-separated List -

-
- - - - Category required. - -
- -
-
-
- - Category - - - - - {{ a }} - - - -
-
- - - - - -

- You can provide multiple tags by passing them as a - Comma-separated List -

-
- - - - Tag required. - -
- -
-
-
- - Tag - - - - - {{ a }} - - - -
-
-
-
-
- - + + + + - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/app/scenario/scenario.component.ts b/src/app/scenario/scenario.component.ts index 3e1dd381..e1ccf083 100644 --- a/src/app/scenario/scenario.component.ts +++ b/src/app/scenario/scenario.component.ts @@ -2,17 +2,13 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { Scenario } from '../data/scenario'; import { ScenarioService } from '../data/scenario.service'; import { ClrDatagridSortOrder, ClrModal } from '@clr/angular'; -import { Step } from '../data/step'; import { ServerResponse } from '../data/serverresponse'; -import { deepCopy } from '../deepcopy'; -import { VmtemplateService } from '../data/vmtemplate.service'; -import { VMTemplate } from '../data/vmtemplate'; -import { VirtualMachine } from '../data/virtualmachine'; -import { Validators, FormGroup, FormControl } from '@angular/forms'; -import { KeepaliveValidator } from '../validators/keepalive.validator'; +import { FilterScenariosComponent } from '../filter-scenarios/filter-scenarios.component'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ScenarioWizardComponent } from './scenario-wizard/scenario-wizard.component'; import { RbacService } from '../data/rbac.service'; -import { CategoryFormGroup, ScenarioFormGroup } from '../data/forms'; -import { delay, tap } from 'rxjs'; +import { AlertDetails } from '../alert/alert'; +import { AlertComponent } from '../alert/alert.component'; @Component({ selector: 'app-scenario', @@ -20,42 +16,11 @@ import { delay, tap } from 'rxjs'; styleUrls: ['./scenario.component.scss'], }) export class ScenarioComponent implements OnInit { - public unusedSelectedScenario: any = {}; // only exists to satisfy a datagrid requirement - public filteredScenarios: Scenario[] = []; - public vmtemplates: VMTemplate[] = []; public selectedscenario: Scenario; - public editingStep: Step = new Step(); - public editingIndex: number = 0; - - public scenarioNotTainted: boolean = true; - - public editDangerClosed: boolean = true; - public editSuccessClosed: boolean = true; - public scenarioDangerClosed: boolean = true; - public scenarioSuccessClosed: boolean = true; - - public editDangerAlert: string = ''; - public editSuccessAlert: string = ''; - public scenarioDangerAlert: string = ''; - public scenarioSuccessAlert: string = ''; - public newvmindex: number = 0; - - public deletingVMSetIndex: number = 0; - public deletingStepIndex: number = 0; - - public editOpen: boolean = false; - public deleteVMSetOpen: boolean = false; - public deleteStepOpen: boolean = false; - public createVMOpen: boolean = false; - public newScenarioOpen: boolean = false; - public newCategory: boolean = false; - public newTag: boolean = false; - - public newScenario: Scenario = new Scenario(); - - public vmProps: string[] = Object.keys(new VirtualMachine()); + public deleteScenarioSetOpen: boolean = false; + public showActionOverflow: boolean = false; public selectRbac: boolean = false; @@ -63,426 +28,110 @@ export class ScenarioComponent implements OnInit { constructor( public scenarioService: ScenarioService, - public vmTemplateService: VmtemplateService, - public rbacService: RbacService, + public rbacService: RbacService ) {} - public vmform: FormGroup<{ - vm_name: FormControl; - vm_template: FormControl; - }> = new FormGroup({ - vm_name: new FormControl(null, [ - Validators.required, - Validators.minLength(4), - ]), - vm_template: new FormControl(null, [Validators.required]), - }); - - public editScenarioForm: ScenarioFormGroup = new FormGroup( - { - scenario_name: new FormControl(null, [ - Validators.required, - Validators.minLength(4), - ]), - scenario_description: new FormControl(null, [ - Validators.required, - Validators.minLength(4), - ]), - keepalive_amount: new FormControl(10, { - validators: Validators.required, - nonNullable: true, - }), - keepalive_unit: new FormControl('m', { - validators: Validators.required, - nonNullable: true, - }), - pause_duration: new FormControl(1, { - validators: [ - Validators.required, - Validators.min(1), - Validators.pattern('^[0-9]+$'), - ], - nonNullable: true, - }), - }, - { validators: KeepaliveValidator } - ); - - public scenarioDetails: ScenarioFormGroup = new FormGroup( - { - scenario_name: new FormControl(null, [ - Validators.required, - Validators.minLength(4), - ]), - scenario_description: new FormControl(null, [ - Validators.required, - Validators.minLength(4), - ]), - keepalive_amount: new FormControl(10, { - validators: Validators.required, - nonNullable: true, - }), - keepalive_unit: new FormControl('m', { - validators: Validators.required, - nonNullable: true, - }), - pause_duration: new FormControl(1, { - validators: [ - Validators.required, - Validators.min(1), - Validators.pattern('^[0-9]+$'), - ], - nonNullable: true, - }), - }, - { validators: KeepaliveValidator } - ); - - public newCategoryForm: CategoryFormGroup = new FormGroup({ - category: new FormControl(null, [Validators.required]), - }); + @ViewChild('deletescenariomodal', { static: true }) + deleteScenarioModal: ClrModal; - public newTagForm: FormGroup<{ - tag: FormControl; - }> = new FormGroup({ - tag: new FormControl(null, [Validators.required]), - }); - - get keepaliveAmount() { - return this.scenarioDetails.controls.keepalive_amount; - } - - get keepaliveUnit() { - return this.scenarioDetails.controls.keepalive_unit; - } - - get keepaliveRequired() { - const ka = this.keepaliveAmount; - const ku = this.keepaliveUnit; - - // validate - if ((ka.dirty || ka.touched) && ka.invalid && ka.errors.required) { - return true; - } else if ((ku.dirty || ku.touched) && ku.invalid && ku.errors.required) { - return true; - } else { - return false; - } - } - - @ViewChild('editmodal', { static: true }) editModal: ClrModal; - @ViewChild('deletevmsetmodal', { static: true }) deleteVMSetModal: ClrModal; - @ViewChild('createvmmodal', { static: true }) createVMModal: ClrModal; - @ViewChild('deletestepmodal', { static: true }) deleteStepModal: ClrModal; - @ViewChild('newscenariomodal', { static: true }) newScenarioModal: ClrModal; - - openEdit(s: Step, i: number) { - if (this.selectedscenario.steps.length == 0) { - this.openNewStep(); - return; - } - this.editingStep = s; - this.editingIndex = i; - this.editModal.open(); - } + @ViewChild('scenarioFilter', { static: true }) + scenarioFilter: FilterScenariosComponent; + @ViewChild('scenariowizard', { static: true }) + scenarioWizard: ScenarioWizardComponent; + @ViewChild('alert') alert: AlertComponent; editScenario(s: Scenario) { if (s != undefined) { // this is only a partial scenario, we need to get the full - this.scenarioService.get(s.id).subscribe((s: Scenario) => { - this.selectedscenario = s; - this.editScenarioForm.reset({ - scenario_name: s.name, - scenario_description: s.description, - keepalive_amount: Number( - s.keepalive_duration.substring(0, s.keepalive_duration.length - 1) - ), - keepalive_unit: s.keepalive_duration.substring( - s.keepalive_duration.length - 1, - s.keepalive_duration.length - ), - }); - const pauseDuration = Number(s.pause_duration?.slice(0, -1)); - if (!Number.isNaN(pauseDuration)) { - this.editScenarioForm.patchValue({ - pause_duration: pauseDuration, - }); - } + this.scenarioService.get(s.id).subscribe({ + next: (s: Scenario) => { + this.selectedscenario = s; + }, + error: (e: HttpErrorResponse) => { + this.alert.danger('Error retrieving object: ' + e.error.message, true, 3000); + }, }); } } - openNewScenario() { - this.newScenario = new Scenario(); - this.newScenarioModal.open(); - } - - openNewStep() { - this.editingIndex = this.selectedscenario.steps.length; - this.editingStep = new Step(); - this.editingStep.title = 'Step ' + (this.editingIndex + 1); - // Provide default content with syntax examples - this.editingStep.content = - "## Your Content\n```ctr:node1\necho 'hello world'\n```\n```hidden:Syntax reference\navailable at [the hobbyfarm docs](https://hobbyfarm.github.io/docs/appendix/markdown_syntax/)\n```"; - this.selectedscenario.steps[this.editingIndex] = this.editingStep; - this.editModal.open(); - } - - isFirstStep() { - return this.editingIndex == 0; - } - - isLastStep() { - return this.editingIndex >= this.selectedscenario?.steps.length - 1; - } - - nextStep() { - if (this.isLastStep()) { - return; - } - this.selectedscenario.steps[this.editingIndex] = this.editingStep; - this.editingIndex++; - this.editingStep = this.selectedscenario.steps[this.editingIndex]; - } - - previousStep() { - if (this.isFirstStep()) { - return; - } - this.selectedscenario.steps[this.editingIndex] = this.editingStep; - this.editingIndex--; - this.editingStep = this.selectedscenario.steps[this.editingIndex]; - } - - public openDeleteStep(i: number) { - this.deletingStepIndex = i; - this.deleteStepModal.open(); - } - - public doDeleteStep() { - this.selectedscenario.steps.splice(this.deletingStepIndex, 1); - this.deleteStepModal.close(); - this.savescenario(); + openScenarioWizard(wizardMode: 'create' | 'edit', scenario?: Scenario) { + this.scenarioWizard.open(wizardMode, scenario); } - private _displayAlert(alert: string, success: boolean, duration?: number) { - if (success) { - this.scenarioSuccessAlert = alert; - this.scenarioSuccessClosed = false; - setTimeout(() => { - this.scenarioSuccessClosed = true; - }, duration || 1000); - } else { - this.scenarioDangerAlert = alert; - this.scenarioDangerClosed = false; - setTimeout(() => { - this.scenarioDangerClosed = true; - }, duration || 1000); - } - } - - public addNewScenario() { - this.newScenario.name = this.scenarioDetails.controls.scenario_name.value; - this.newScenario.description = - this.scenarioDetails.controls.scenario_description.value; - this.newScenario.keepalive_duration = - this.scenarioDetails.controls.keepalive_amount.value + - this.scenarioDetails.controls.keepalive_unit.value; - this.newScenario.pause_duration = - this.scenarioDetails.controls.pause_duration.value + 'h'; - - // should be able to save at this point - this.scenarioService.create(this.newScenario).subscribe({ - next: (s: Scenario) => { - this._displayAlert(s.name, true); - }, - error: (s: string) => { - this._displayAlert(s, false); + openDeleteScenario(scenario: Scenario) { + this.scenarioService.get(scenario.id).subscribe({ + next: (scenario) => (this.selectedscenario = scenario), + error: (e: HttpErrorResponse) => { + this.alert.danger('Error deleting object: ' + e.error.message, true, 3000); }, }); - - this.newScenarioModal.close(); - } - - cancelEdit() { - this.editModal.close(); - } - - saveStep() { - this.selectedscenario.steps[this.editingIndex] = this.editingStep; - this.scenarioService - .update(this.selectedscenario) - .subscribe((s: ServerResponse) => { - if (s.type == 'updated') { - this.editSuccessAlert = 'Steps successfully saved'; - this.editSuccessClosed = false; - setTimeout(() => { - this.editSuccessClosed = true; - this.editModal.close(); - }, 1000); - } else { - this.editDangerAlert = 'Unable to save steps: ' + s.message; - this.editDangerClosed = false; - setTimeout(() => { - this.editDangerClosed = true; - }, 1000); - } - }); - } - - public openCreateVM(i: number) { - this.vmform.reset(); - this.newvmindex = i; - this.createVMModal.open(); - } - - public deleteVM(setIndex: number, key: string) { - this.scenarioNotTainted = false; - delete this.selectedscenario.virtualmachines[setIndex][key]; - } - - addVM() { - this.scenarioNotTainted = false; - // TODO: As soon as we introduce strictNullChecks, this will fail... - // ... and we need to check if this.vmform.controls.vm_name.value and this.vmform.controls.vm_template.value are not null - this.selectedscenario.virtualmachines[this.newvmindex][ - this.vmform.controls.vm_name.value - ] = this.vmform.controls.vm_template.value; - this.createVMModal.close(); - } - - savescenario() { - this.selectedscenario.keepalive_duration = - this.editScenarioForm.controls.keepalive_amount.value + - this.editScenarioForm.controls.keepalive_unit.value; - this.selectedscenario.pause_duration = - this.editScenarioForm.controls.pause_duration.value + 'h'; - this.selectedscenario.name = - this.editScenarioForm.controls.scenario_name.value; - this.selectedscenario.description = - this.editScenarioForm.controls.scenario_description.value; - - this.scenarioService - .update(this.selectedscenario) - .pipe( - tap((s: ServerResponse) => { - if (s.type == 'updated') { - this.scenarioSuccessAlert = 'Scenario updated'; - this.scenarioSuccessClosed = false; - } else { - this.scenarioDangerAlert = - 'Unable to update scenario: ' + s.message; - this.scenarioDangerClosed = false; - } - }), - delay(1000), - tap((s: ServerResponse) => { - if (s.type == 'updated') { - this.scenarioSuccessClosed = true; - this.scenarioNotTainted = true; - } else { - this.scenarioDangerClosed = true; - } - }) - ) - .subscribe(); - } - - moveStepUp(i: number) { - this.scenarioNotTainted = false; - // get a copy of the to-be-moved item - var obj = deepCopy(this.selectedscenario.steps[i]); - // delete at the index currently - this.selectedscenario.steps.splice(i, 1); - // put into the i-1 index - this.selectedscenario.steps.splice(i - 1, 0, obj); - } - - moveStepDown(i: number) { - this.scenarioNotTainted = false; - // get a copy of the to-be-moved item - var obj = deepCopy(this.selectedscenario.steps[i]); - // delete at the index currently - this.selectedscenario.steps.splice(i, 1); - // put into the i+1 index - this.selectedscenario.steps.splice(i + 1, 0, obj); - } - - deleteCategory(category: string) { - this.selectedscenario.categories.forEach((element, index) => { - if (element == category) - this.selectedscenario.categories.splice(index, 1); - }); + this.deleteScenarioModal.open(); } - addCategory() { - const categories: string[] | undefined = - this.newCategoryForm.controls.category.value?.split(','); - categories?.forEach((category) => { - category = category.replace(/\s/g, ''); //remove all whitespaces - if ( - category != '' && - !this.selectedscenario.categories.includes(category) - ) { - this.selectedscenario.categories.push(category); - } - }); - this.newCategoryForm.reset(); - this.newCategory = false; + setScenarioList(scenarios: Scenario[]) { + this.filteredScenarios = scenarios; } - deleteTag(tag: string) { - this.selectedscenario.tags.forEach((element, index) => { - if (element == tag) this.selectedscenario.tags.splice(index, 1); + refresh(): void { + this.scenarioService.list(true).subscribe({ + next: (sList: Scenario[]) => (this.filteredScenarios = sList), + error: (e: HttpErrorResponse) => { + this.alert.danger('Error listing objects: ' + e.error.message, true, 3000); + }, }); } - addTag() { - const tags: string[] | undefined = - this.newTagForm.controls.tag.value?.split(','); - tags?.forEach((tag) => { - tag = tag.replace(/\s/g, ''); //remove all whitespaces - if (tag != '' && !this.selectedscenario.tags.includes(tag)) { - this.selectedscenario.tags.push(tag); - } + deleteScenario(scenarioId: string) { + this.scenarioService.delete(scenarioId).subscribe({ + next: (_s: ServerResponse) => { + this.alert.success('Scenario deleted', true, 3000); + this.refresh(); + }, + error: (e: HttpErrorResponse) => { + this.alert.danger('Error deleting object: ' + e.error.message, true, 3000); + }, }); - this.newTagForm.reset(); - this.newTag = false; - } - - addVMSet() { - this.scenarioNotTainted = false; - this.selectedscenario.virtualmachines.push({}); } - deleteVMSet(i: number) { - this.scenarioNotTainted = false; - this.deletingVMSetIndex = i; - this.deleteVMSetModal.open(); + doDeleteScenario() { + this.deleteScenario(this.selectedscenario.id); + this.deleteScenarioModal.close(); + this.selectedscenario = + this.filteredScenarios[this.filteredScenarios.length - 1]; } - doDeleteVMSet() { - this.selectedscenario.virtualmachines.splice(this.deletingVMSetIndex, 1); - this.deleteVMSetModal.close(); - } - setScenarioList(scenarios: Scenario[]) { - this.filteredScenarios = scenarios; + refreshAndDisplayAlert(alertDetails: AlertDetails) { + this._reloadScenario(); + this.alert.doAlert(alertDetails.message, alertDetails.type, alertDetails.closable, alertDetails.duration); } ngOnInit() { + this.selectedscenario = new Scenario(); + this.selectedscenario.name = ''; + this.selectedscenario.virtualmachines = []; + this.selectedscenario.steps = []; + this.selectedscenario.virtualmachines[0] = {}; // "Get" Permission on scenarios is required to load step content this.rbacService.Grants('scenarios', 'get').then((allowed: boolean) => { this.selectRbac = allowed; }); - this.rbacService - .Grants('virtualmachinesets', 'list') - .then((listVmSets: boolean) => { - if (listVmSets) { - this.vmTemplateService.list().subscribe((v: VMTemplate[]) => { - this.vmtemplates = v; - }); - } - }); + const authorizationRequests = Promise.all([ + this.rbacService.Grants('scenarios', 'get'), + this.rbacService.Grants('scenarios', 'update'), + this.rbacService.Grants('scenarios', 'delete'), + ]); + authorizationRequests.then((permissions: [boolean, boolean, boolean]) => { + const allowGet: boolean = permissions[0]; + const allowUpdate: boolean = permissions[1]; + const allowDelete: boolean = permissions[2]; + this.showActionOverflow = allowDelete || (allowGet && allowUpdate); + }); + this.refresh(); + } + + private _reloadScenario() { + this.scenarioFilter.reloadScenarios(); + this.refresh(); } } diff --git a/src/app/scenario/steps-scenario/steps-scenario.component.html b/src/app/scenario/steps-scenario/steps-scenario.component.html new file mode 100644 index 00000000..ebbafa49 --- /dev/null +++ b/src/app/scenario/steps-scenario/steps-scenario.component.html @@ -0,0 +1,141 @@ + + + + Index + Title + + + + + + + + + {{ i + 1 }} + {{ s.title }} + + + + + + + + + + + + + + diff --git a/src/app/scenario/steps-scenario/steps-scenario.component.scss b/src/app/scenario/steps-scenario/steps-scenario.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/scenario/steps-scenario/steps-scenario.component.ts b/src/app/scenario/steps-scenario/steps-scenario.component.ts new file mode 100644 index 00000000..402e09e1 --- /dev/null +++ b/src/app/scenario/steps-scenario/steps-scenario.component.ts @@ -0,0 +1,122 @@ +import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { ClrModal } from '@clr/angular'; +import { Scenario } from 'src/app/data/scenario'; +import { Step } from 'src/app/data/step'; +import { deepCopy } from 'src/app/deepcopy'; + +@Component({ + selector: 'steps-scenario', + templateUrl: './steps-scenario.component.html', + styleUrls: ['./steps-scenario.component.scss'], +}) +export class StepsScenarioComponent { + @Input() + scenario: Scenario; + + + + public scenarioTainted: boolean = false; + public editOpen: boolean = false; + public editDangerClosed: boolean = true; + public editSuccessClosed: boolean = true; + public deleteStepOpen: boolean = false; + public isStepsLengthNull: boolean = true; + + public editingStep: Step = new Step(); + public editingSteps: Step[] = []; + + public editingIndex: number = 0; + public stepsToBeAdded: number = 0; + public deletingStepIndex: number = 0; + + public editDangerAlert: string = ''; + public editSuccessAlert: string = ''; + + @ViewChild('editmodal', { static: true }) editModal: ClrModal; + @ViewChild('deletestepmodal', { static: true }) deleteStepModal: ClrModal; + + moveStepUp(i: number) { + this.scenarioTainted = true; + // get a copy of the to-be-moved item + const obj = deepCopy(this.scenario.steps[i]); + // delete at the index currently + this.scenario.steps.splice(i, 1); + // put into the i-1 index + this.scenario.steps.splice(i - 1, 0, obj); + } + moveStepDown(i: number) { + this.scenarioTainted = true; + // get a copy of the to-be-moved item + const obj = deepCopy(this.scenario.steps[i]); + // delete at the index currently + this.scenario.steps.splice(i, 1); + // put into the i+1 index + this.scenario.steps.splice(i + 1, 0, obj); + } + + openEdit(s: Step, i: number) { + this.editingSteps = deepCopy(this.scenario.steps); + if (this.editingSteps.length == 0) { + this.openNewStep(); + return; + } + this.editingIndex = i; + this.editingStep = this.editingSteps[i]; + this.editModal.open(); + } + + openNewStep() { + this.editingSteps = deepCopy(this.scenario.steps); + this.editingIndex = this.editingSteps.length; + this.editingStep = new Step(); + this.editingStep.title = 'Step ' + (this.editingIndex + 1); + // Provide default content with syntax examples + this.editingStep.content = + "## Your Content\n```ctr:node1\necho 'hello world'\n```\n```hidden:Syntax reference\navailable at [the hobbyfarm docs](https://hobbyfarm.github.io/docs/appendix/markdown_syntax/)\n```"; + this.editingSteps[this.editingIndex] = this.editingStep; + this.editModal.open(); + } + + public openDeleteStep(i: number) { + this.deletingStepIndex = i; + this.deleteStepModal.open(); + } + + previousStep() { + if (this.isFirstStep()) { + return; + } + this.editingSteps[this.editingIndex] = this.editingStep; + this.editingIndex--; + this.editingStep = this.editingSteps[this.editingIndex]; + } + isFirstStep() { + return this.editingIndex == 0; + } + nextStep() { + if (this.isLastStep()) { + return; + } + this.editingSteps[this.editingIndex] = this.editingStep; + this.editingIndex++; + this.editingStep = this.editingSteps[this.editingIndex]; + } + isLastStep() { + return this.editingIndex >= this.editingSteps?.length - 1; + } + cancelEdit() { + this.editModal.close(); + } + + saveCreatedStep() { + this.scenario.steps = deepCopy(this.editingSteps); + this.isStepsLengthNull = false; + this.stepsToBeAdded = 0; + this.editModal.close(); + } + public doDeleteStep() { + this.scenario.steps.splice(this.deletingStepIndex, 1); + this.deleteStepModal.close(); + if (this.scenario.steps.length == 0) this.isStepsLengthNull = true; + } +} From 5518bd5f94c6e3821974e447ae7754e76e350211 Mon Sep 17 00:00:00 2001 From: maxsva <110593497+maxsva@users.noreply.github.com> Date: Tue, 12 Mar 2024 17:06:41 +0100 Subject: [PATCH 5/6] fix new environment param create (#204) Co-authored-by: Maksym Veres --- .../edit-environment.component.html | 131 +++++++++++++----- 1 file changed, 96 insertions(+), 35 deletions(-) diff --git a/src/app/configuration/environments/edit-environment/edit-environment.component.html b/src/app/configuration/environments/edit-environment/edit-environment.component.html index 86504d85..c6feb4d0 100644 --- a/src/app/configuration/environments/edit-environment/edit-environment.component.html +++ b/src/app/configuration/environments/edit-environment/edit-environment.component.html @@ -346,38 +346,58 @@

Basic Information

- - Display Name - {{ env.display_name }} - - {{ uneditedEnv.display_name }} - {{ env.display_name }} - - - - DNS Suffix - {{ env.dnssuffix }} - - {{ uneditedEnv.dnssuffix }} - {{ env.dnssuffix }} - - - - Provider - {{ env.provider }} - - {{ uneditedEnv.provider }} - {{ env.provider }} - - - - Websocket Endpoint - {{ env.ws_endpoint }} - - {{ uneditedEnv.ws_endpoint }} - {{ env.ws_endpoint }} - - + + + Display Name + {{ env.display_name }} + + {{ uneditedEnv.display_name }} + {{ env.display_name }} + + + + DNS Suffix + {{ env.dnssuffix }} + + {{ uneditedEnv.dnssuffix }} + {{ env.dnssuffix }} + + + + Provider + {{ env.provider }} + + {{ uneditedEnv.provider }} + {{ env.provider }} + + + + Websocket Endpoint + {{ env.ws_endpoint }} + + {{ uneditedEnv.ws_endpoint }} + {{ env.ws_endpoint }} + + + + + + Display Name + {{ env.display_name }} + + + DNS Suffix + {{ env.dnssuffix }} + + + Provider + {{ env.provider }} + + + Websocket Endpoint + {{ env.ws_endpoint }} + + @@ -390,6 +410,7 @@

Environment Specifics

+ {{ item.key }} @@ -411,7 +432,14 @@

Environment Specifics

{{ item.key }} {{ item.value }}
- + + + + {{ item.key }} + {{ item.value }} + + + @@ -425,7 +453,8 @@

Template Mappings

- + + {{ getVirtualMachineTemplateName(template.key) }} {{ getTemplateCount(template.key) }} @@ -481,6 +510,30 @@

Template Mappings

+
+ + + {{ template.key }} + {{ getTemplateCount(template.key) }} + + + + + + + + + + + + + + +
KeyValue
{{ item.key }}{{ item.value }}
+ + +
+ @@ -493,7 +546,8 @@

IP Mappings

- + + {{ item.key }} {{ item.value }} @@ -515,6 +569,13 @@

IP Mappings

{{ item.value }}
+
+ + + {{ item.key }} + {{ item.value }} + + From 3a4a465e6039291a4d2c1c696f32998a004665a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Gerrit=20G=C3=B6bel?= <86782124+jggoebel@users.noreply.github.com> Date: Wed, 13 Mar 2024 09:32:51 +0100 Subject: [PATCH 6/6] Fix markdown no content (#205) --- src/app/step/hf-markdown.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/step/hf-markdown.component.ts b/src/app/step/hf-markdown.component.ts index 3253da4f..93fe6ead 100644 --- a/src/app/step/hf-markdown.component.ts +++ b/src/app/step/hf-markdown.component.ts @@ -217,6 +217,10 @@ ${token}`; } ngOnChanges() { + if(!this.content){ + return + } + const contentWithReplacedTokens = this.replaceSessionToken( this.replaceVmInfoTokens(this.content), );