diff options
Diffstat (limited to 'sdnr/wt/odlux/framework')
41 files changed, 804 insertions, 543 deletions
diff --git a/sdnr/wt/odlux/framework/pom.xml b/sdnr/wt/odlux/framework/pom.xml index 414f49827..3dd0f1a9d 100644 --- a/sdnr/wt/odlux/framework/pom.xml +++ b/sdnr/wt/odlux/framework/pom.xml @@ -19,6 +19,7 @@ ~ ============LICENSE_END======================================================= ~ --> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> @@ -45,7 +46,7 @@ <properties> <buildtime>${maven.build.timestamp}</buildtime> <distversion>ONAP Frankfurt (Neon, mdsal ${odl.mdsal.version})</distversion> - <buildno>142.63ceae1(22/01/31)</buildno> + <buildno>178.3bd8a2a9(23/03/16)</buildno> <odlux.version>ONAP SDN-R | ONF Wireless for ${distversion} - Build: ${buildtime} ${buildno} ${project.version}</odlux.version> </properties> @@ -102,7 +103,7 @@ <!-- optional: default phase is "generate-resources" --> <phase>initialize</phase> <configuration> - <nodeVersion>v12.13.0</nodeVersion> + <nodeVersion>v12.22.0</nodeVersion> <yarnVersion>v1.22.10</yarnVersion> </configuration> </execution> diff --git a/sdnr/wt/odlux/framework/src/actions/settingsAction.ts b/sdnr/wt/odlux/framework/src/actions/settingsAction.ts index 1c18ddc8e..092b31814 100644 --- a/sdnr/wt/odlux/framework/src/actions/settingsAction.ts +++ b/sdnr/wt/odlux/framework/src/actions/settingsAction.ts @@ -19,7 +19,7 @@ import { Dispatch } from "../flux/store"; import { Action } from "../flux/action"; import { GeneralSettings, Settings, TableSettings, TableSettingsColumn } from "../models/settings"; -import { getSettings, putSettings } from "../services/settingsService"; +import { getUserdata, saveUserdata } from "../services/userdataService"; import { startWebsocketSession, suspendWebsocketSession } from "../services/notificationService"; import { IApplicationStoreState } from "../store/applicationStore"; @@ -67,7 +67,7 @@ export const setGeneralSettingsAction = (value: boolean) => (dispatcher: Dispatc export const updateGeneralSettingsAction = (activateNotifications: boolean) => async (dispatcher: Dispatch) => { const value: GeneralSettings = { general: { areNotificationsEnabled: activateNotifications } }; - const result = await putSettings("/general", JSON.stringify(value.general)); + const result = await saveUserdata("/general", JSON.stringify(value.general)); dispatcher(setGeneralSettingsAction(activateNotifications)); } @@ -86,14 +86,14 @@ export const updateTableSettings = (tableName: string, columns: TableSettingsCol // would only save latest entry //const json = JSON.stringify({ [tableName]: { columns: columns } }); - const result = await putSettings("/tables", json); + const result = await saveUserdata("/tables", json); dispatcher(new SetTableSettings(tableName, columns)); } export const getGeneralSettingsAction = () => async (dispatcher: Dispatch) => { - const result = await getSettings<GeneralSettings>(); + const result = await getUserdata<GeneralSettings>(); if (result && result.general) { dispatcher(new SetGeneralSettingsAction(result.general.areNotificationsEnabled!)) diff --git a/sdnr/wt/odlux/framework/src/app.tsx b/sdnr/wt/odlux/framework/src/app.tsx index 9b03a216d..bbe1f9ec8 100644 --- a/sdnr/wt/odlux/framework/src/app.tsx +++ b/sdnr/wt/odlux/framework/src/app.tsx @@ -32,14 +32,15 @@ import { applicationStoreCreator } from './store/applicationStore'; import { ApplicationStoreProvider } from './flux/connect'; import { startHistoryListener } from './middleware/navigation'; -import { startRestService } from './services/restService'; +import { startSoreService } from './services/storeService'; import { startUserSessionService } from './services/userSessionService'; import { startNotificationService } from './services/notificationService'; +import { startBroadcastChannel } from './services/broadcastService'; + import theme from './design/default'; import '!style-loader!css-loader!./app.css'; -import { startBroadcastChannel } from './services/broadcastService'; declare module '@mui/material/styles' { @@ -98,7 +99,7 @@ export const runApplication = () => { }; - startRestService(applicationStore); + startSoreService(applicationStore); startHistoryListener(applicationStore); startNotificationService(applicationStore); diff --git a/sdnr/wt/odlux/framework/src/assets/icons/About.svg b/sdnr/wt/odlux/framework/src/assets/icons/About.svg new file mode 100644 index 000000000..156e36efe --- /dev/null +++ b/sdnr/wt/odlux/framework/src/assets/icons/About.svg @@ -0,0 +1,18 @@ +<!-- highstreet technologies GmbH colour scheme
+ Grey #565656
+ LBlue #36A9E1
+ DBlue #246DA2
+ Green #003F2C / #006C4B
+ Yellw #C8D400
+ Red #D81036
+-->
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+
+<path fill="#36A9E1" d="M 13 13 V 17.5 A 1 1 0 0 1 11 17.5 V 13 A 1 1 0 0 1 13 13 Z "/>
+
+<path fill="#36A9E1" d="M 12 11 A 1 1 0 1 0 11 10 A 1 1 0 0 0 12 11 Z "/>
+
+<path fill="#565656" d="M 1.125 20.485 A 1 1 0 0 1 1.152 19.47 L 11.152 3.47 A 1.039 1.039 0 0 1 12.852 3.47 L 22.852 19.47 A 1 1 0 0 1 22 21 H 2 A 1 1 0 0 1 1.125 20.485 Z M 3.8 19 H 20.2 L 12 5.887 Z"/>
+
+</svg>
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/assets/icons/Home.svg b/sdnr/wt/odlux/framework/src/assets/icons/Home.svg new file mode 100644 index 000000000..0836714b4 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/assets/icons/Home.svg @@ -0,0 +1,28 @@ +<!-- highstreet technologies GmbH colour scheme
+ Grey #565656
+ LBlue #36A9E1
+ DBlue #246DA2
+ Green #003F2C / #006C4B
+ Yellw #C8D400
+ Red #D81036
+-->
+
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 460.298 460.297" style="enable-background:new 0 0 460.298 460.297;"
+ xml:space="preserve">
+<g>
+ <g>
+ <path fill="#565656" d="M230.149,120.939L65.986,256.274c0,0.191-0.048,0.472-0.144,0.855c-0.094,0.38-0.144,0.656-0.144,0.852v137.041
+ c0,4.948,1.809,9.236,5.426,12.847c3.616,3.613,7.898,5.431,12.847,5.431h109.63V303.664h73.097v109.64h109.629
+ c4.948,0,9.236-1.814,12.847-5.435c3.617-3.607,5.432-7.898,5.432-12.847V257.981c0-0.76-0.104-1.334-0.288-1.707L230.149,120.939
+ z"/>
+ <path fill="#565656" d="M457.122,225.438L394.6,173.476V56.989c0-2.663-0.856-4.853-2.574-6.567c-1.704-1.712-3.894-2.568-6.563-2.568h-54.816
+ c-2.666,0-4.855,0.856-6.57,2.568c-1.711,1.714-2.566,3.905-2.566,6.567v55.673l-69.662-58.245
+ c-6.084-4.949-13.318-7.423-21.694-7.423c-8.375,0-15.608,2.474-21.698,7.423L3.172,225.438c-1.903,1.52-2.946,3.566-3.14,6.136
+ c-0.193,2.568,0.472,4.811,1.997,6.713l17.701,21.128c1.525,1.712,3.521,2.759,5.996,3.142c2.285,0.192,4.57-0.476,6.855-1.998
+ L230.149,95.817l197.57,164.741c1.526,1.328,3.521,1.991,5.996,1.991h0.858c2.471-0.376,4.463-1.43,5.996-3.138l17.703-21.125
+ c1.522-1.906,2.189-4.145,1.991-6.716C460.068,229.007,459.021,226.961,457.122,225.438z"/>
+
+<path fill="#246DA2" d="M 457.122 225.438 L 251.849 54.417 L 251.849 54.417 C 245.765 49.468 238.531 46.994 230.155 46.994 C 221.78 46.994 214.547 49.468 208.457 54.417 L 3.172 225.438 C 1.269 226.958 0.226 229.004 0.032 231.574 C -0.161 234.142 0.504 236.385 2.029 238.287 L 19.73 259.415 C 21.255 261.127 23.251 262.174 25.726 262.557 C 28.011 262.749 30.296 262.081 32.581 260.559 L 230.149 95.817 L 427.719 260.558 C 429.245 261.886 431.24 262.549 433.715 262.549 H 434.573 C 437.044 262.173 439.036 261.119 440.569 259.411 L 458.272 238.286 C 459.794 236.38 460.461 234.141 460.263 231.57 C 460.068 229.007 459.021 226.961 457.122 225.438 Z"/>
+ </g>
+</g>
+</svg>
diff --git a/sdnr/wt/odlux/framework/src/assets/icons/Menu.svg b/sdnr/wt/odlux/framework/src/assets/icons/Menu.svg new file mode 100644 index 000000000..ea0312802 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/assets/icons/Menu.svg @@ -0,0 +1,18 @@ +<!-- highstreet technologies GmbH colour scheme
+ Grey #565656
+ LBlue #36A9E1
+ DBlue #246DA2
+ Green #003F2C / #006C4B
+ Yellw #C8D400
+ Red #D81036
+-->
+
+<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<path fill="#36A9E1" d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z"/>
+
+<path fill="#36A9E1" d="M28,14H4c-1.104,0-2,0.896-2,2 s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z"/>
+
+<path fill="#36A9E1" d="M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2 S29.104,22,28,22z"/>
+
+</svg>
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/assets/icons/Tools.svg b/sdnr/wt/odlux/framework/src/assets/icons/Tools.svg new file mode 100644 index 000000000..1595cdc1c --- /dev/null +++ b/sdnr/wt/odlux/framework/src/assets/icons/Tools.svg @@ -0,0 +1,35 @@ +<!-- highstreet technologies GmbH colour scheme + Grey #565656 + LBlue #36A9E1 + DBlue #246DA2 + Green #003F2C / #006C4B + Yellw #C8D400 + Red #D81036 +--> + +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 265 265"> + +<g transform="translate(0.000000,265.000000) scale(0.100000,-0.100000)"> + +<path fill="#565656" d="M601 2515 c-35 -8 -66 -17 -69 -20 -3 -3 76 -87 175 -188 243 -244 +243 -246 59 -425 -106 -103 -137 -120 -194 -107 -19 4 -88 66 -207 185 -99 99 +-183 180 -186 180 -17 0 -34 -104 -33 -200 0 -82 6 -120 23 -170 61 -174 191 +-306 362 -367 62 -22 93 -27 187 -28 l113 -2 602 -600 c673 -671 633 -638 777 +-638 93 1 151 27 225 101 99 98 129 219 85 339 -21 57 -49 87 -626 665 l-603 +605 5 70 c18 217 -117 451 -316 549 -127 62 -250 78 -379 51z m1696 -1906 c70 +-34 113 -125 93 -199 -31 -116 -158 -168 -262 -107 -77 45 -107 133 -73 214 +21 50 42 72 90 94 53 24 99 24 152 -2z"/> + +<path fill="#565656" d="M2183 2496 c-23 -7 -56 -23 -75 -34 -18 -12 -172 -161 -343 -331 +l-310 -311 45 -45 c24 -25 49 -45 55 -45 5 0 152 142 325 315 264 264 319 315 +340 313 19 -2 26 -10 28 -32 3 -26 -26 -59 -315 -348 l-318 -318 38 -37 37 +-38 319 318 c287 287 321 318 347 315 24 -2 30 -8 32 -32 3 -26 -26 -59 -315 +-348 l-318 -318 48 -47 47 -48 320 320 c225 225 327 333 342 365 31 66 30 159 +-4 225 -66 127 -210 198 -325 161z"/> + +<path fill="#36A9E1" d="M800 875 l-175 -175 -85 0 -85 0 -135 -222 c-74 -121 -137 -225 -139 +-231 -4 -11 89 -107 104 -107 5 0 109 60 230 134 l220 133 3 84 3 84 177 178 +177 177 -60 60 -60 60 -175 -175z"/> + +</g> +</svg> diff --git a/sdnr/wt/odlux/framework/src/assets/icons/User.svg b/sdnr/wt/odlux/framework/src/assets/icons/User.svg new file mode 100644 index 000000000..99618cf57 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/assets/icons/User.svg @@ -0,0 +1,21 @@ +<!-- highstreet technologies GmbH colour scheme
+ Grey #565656
+ LBlue #36A9E1
+ DBlue #246DA2
+ Green #003F2C / #006C4B
+ Yellw #C8D400
+ Red #D81036
+-->
+
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 600 600">
+
+ <defs>
+ <clipPath id="circular-border">
+ <circle cx="300" cy="300" r="250" />
+ </clipPath>
+ </defs>
+
+ <circle fill="#36A9E1" cx="300" cy="300" r="280"/>
+ <circle fill="#ffffff" cx="300" cy="230" r="100" />
+ <circle fill="#ffffff" cx="300" cy="550" r="190" clip-path="url(#circular-border)" />
+</svg>
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/assets/icons/ht.Connect.png b/sdnr/wt/odlux/framework/src/assets/icons/ht.Connect.png Binary files differnew file mode 100644 index 000000000..412390c79 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/assets/icons/ht.Connect.png diff --git a/sdnr/wt/odlux/framework/src/assets/icons/ht.Connect.svg b/sdnr/wt/odlux/framework/src/assets/icons/ht.Connect.svg new file mode 100644 index 000000000..c1d74bc96 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/assets/icons/ht.Connect.svg @@ -0,0 +1,81 @@ +<!-- highstreet technologies GmbH colour scheme + Grey #565656 + LBlue #36A9E1 + DBlue #246DA2 + Green #003F2C / #006C4B + Yellw #C8D400 + Red #D81036 +--> + +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 795.000000 139.000000"> + +<g transform="translate(0.000000,139.000000) scale(0.100000,-0.100000)"> + +<path fill="#565656" d="M355 1163 c-38 -2 -75 -9 -82 -15 -10 -8 -13 -53 -13 -175 0 -175 -9 +-215 -50 -226 -17 -4 -20 -13 -20 -59 0 -48 3 -56 32 -80 l33 -27 3 -190 c4 +-226 -4 -213 135 -209 l92 3 0 70 0 70 -32 3 -33 3 0 148 c0 135 -2 151 -21 +175 l-20 26 20 26 c19 24 21 40 21 170 l0 143 33 3 32 3 0 70 0 70 -30 1 c-16 +0 -61 -1 -100 -3z"/> + +<path fill="#565656" d="M7473 1154 c-2 -6 -3 -38 -1 -70 3 -59 3 -59 36 -62 l32 -3 0 -137 +c0 -119 3 -141 20 -169 20 -33 20 -33 0 -65 -17 -29 -20 -51 -20 -175 l0 -142 +-32 -3 -33 -3 0 -70 0 -70 97 -3 c141 -4 138 -9 138 212 0 96 3 182 6 191 4 9 +17 21 30 26 21 8 24 15 24 67 0 53 -3 60 -30 80 l-30 23 0 172 c0 123 -4 178 +-12 189 -16 20 -217 31 -225 12z"/> + +<path fill="#246DA2" d="M645 1138 c-3 -7 -4 -215 -3 -463 l3 -450 90 0 90 0 0 195 c0 215 5 +237 65 280 71 50 146 45 178 -13 14 -27 17 -64 19 -247 l2 -215 88 0 88 0 0 +250 0 250 -27 55 c-33 67 -71 97 -138 110 -89 17 -181 -12 -240 -75 l-25 -27 +-5 179 -5 178 -88 3 c-64 2 -89 -1 -92 -10z"/> + +<path fill="#C8D400" d="M2499 1111 c-150 -48 -271 -193 -301 -361 -32 -184 85 -406 259 -492 +165 -81 408 -24 514 120 31 43 31 44 13 61 -10 10 -45 33 -76 50 l-57 32 -15 +-28 c-75 -145 -315 -142 -403 6 -135 226 18 510 249 462 69 -15 114 -42 139 +-86 12 -19 26 -35 31 -35 22 0 138 91 132 103 -89 153 -301 227 -485 168z"/> + +<path fill="#246DA2" d="M1467 1094 c-4 -4 -7 -51 -7 -105 l0 -98 -42 -3 -43 -3 0 -70 0 -70 +43 -3 42 -3 0 -197 c0 -185 1 -201 23 -242 26 -53 89 -90 152 -90 45 0 171 32 +187 48 7 7 4 30 -9 74 -16 52 -23 62 -39 60 -73 -11 -101 -12 -114 -3 -12 8 +-16 43 -18 180 l-3 170 68 3 68 3 0 70 0 70 -67 3 -67 3 -3 102 -3 102 -80 3 +c-45 1 -84 0 -88 -4z"/> + +<path fill="#C8D400" d="M7017 1094 c-4 -4 -7 -51 -7 -105 l0 -98 -37 -3 -38 -3 0 -70 0 -70 +36 -3 36 -3 7 -67 c3 -37 6 -130 6 -208 0 -130 2 -144 23 -175 27 -41 98 -79 +147 -79 36 0 120 19 172 39 l26 10 -15 58 c-19 74 -28 85 -59 72 -35 -13 -88 +-11 -102 3 -8 8 -12 63 -12 180 l0 167 68 3 67 3 0 70 0 70 -67 3 -67 3 -3 +102 -3 102 -85 3 c-47 1 -89 0 -93 -4z"/> + +<path fill="#C8D400" d="M3349 890 c-63 -11 -123 -43 -174 -95 -55 -54 -82 -106 -94 -181 -25 +-161 50 -304 198 -372 77 -36 211 -38 285 -4 75 34 129 84 167 154 32 59 34 +69 34 158 0 82 -4 103 -26 151 -46 98 -138 168 -249 189 -59 11 -75 11 -141 0z +m168 -178 c44 -31 67 -86 67 -157 0 -113 -53 -176 -153 -183 -70 -5 -115 22 +-148 88 -53 103 -11 233 87 273 31 13 117 1 147 -21z"/> + +<path fill="#C8D400" d="M4254 890 c-61 -13 -106 -34 -147 -69 l-36 -32 -3 48 -3 48 -80 0 +-80 0 0 -330 0 -330 85 0 85 0 5 202 c5 184 7 204 26 229 45 60 111 88 171 72 +54 -14 58 -31 63 -278 l5 -225 85 0 85 0 0 266 0 266 -30 48 c-46 74 -132 106 +-231 85z"/> + +<path fill="#C8D400" d="M5040 890 c-63 -11 -89 -23 -142 -63 l-47 -36 -3 47 -3 47 -80 0 -80 +0 0 -330 0 -330 90 0 90 0 1 196 c1 158 4 201 18 226 37 73 151 111 205 69 28 +-22 35 -68 40 -276 l6 -215 85 0 85 0 0 245 c-1 264 -7 307 -51 360 -47 55 +-122 76 -214 60z"/> + +<path fill="#C8D400" d="M5709 890 c-99 -17 -200 -94 -250 -190 -21 -39 -24 -59 -24 -145 0 +-90 3 -105 28 -153 79 -149 241 -220 407 -178 79 21 145 61 188 118 39 51 34 +58 -54 77 -56 13 -56 13 -88 -16 -17 -16 -44 -35 -59 -41 -42 -18 -117 -15 +-157 7 -33 17 -79 81 -80 108 0 10 60 13 253 15 l252 3 -1 70 c-2 211 -197 +364 -415 325z m151 -150 c36 -19 80 -79 80 -110 0 -6 -56 -10 -160 -10 -149 0 +-160 1 -160 19 0 29 43 85 80 103 46 24 112 23 160 -2z"/> + +<path fill="#C8D400" d="M6483 890 c-186 -39 -303 -204 -273 -386 19 -114 95 -214 200 -263 +46 -22 69 -26 145 -26 76 0 99 4 145 26 65 30 125 80 146 120 17 33 15 35 -84 +64 l-62 18 -28 -25 c-40 -38 -65 -48 -120 -48 -91 0 -162 80 -162 182 0 84 36 +148 99 174 65 27 133 15 176 -32 l25 -27 80 23 c44 13 83 26 85 30 6 10 -42 +71 -82 104 -68 58 -194 86 -290 66z"/> + +<path fill="#36A9E1" d="M1934 407 c-2 -7 -4 -52 -2 -98 l3 -84 70 0 70 0 0 95 0 95 -68 3 +c-50 2 -69 -1 -73 -11z"/> + +</g> +</svg> diff --git a/sdnr/wt/odlux/framework/src/components/errorDisplay.tsx b/sdnr/wt/odlux/framework/src/components/errorDisplay.tsx index a04ab16af..d41d82687 100644 --- a/sdnr/wt/odlux/framework/src/components/errorDisplay.tsx +++ b/sdnr/wt/odlux/framework/src/components/errorDisplay.tsx @@ -15,7 +15,7 @@ * the License. * ============LICENSE_END========================================================================== */ -import * as React from 'react'; +import React from 'react'; import { Theme } from '@mui/material/styles'; import { WithStyles } from '@mui/styles'; @@ -31,7 +31,7 @@ import Typography from '@mui/material/Typography'; import { ClearErrorInfoAction, RemoveErrorInfoAction } from '../actions/errorActions'; -import connect, { Connect } from '../flux/connect'; +import { connect, Connect } from '../flux/connect'; const styles = (theme: Theme) => createStyles({ modal: { diff --git a/sdnr/wt/odlux/framework/src/components/icons/menuIcon.tsx b/sdnr/wt/odlux/framework/src/components/icons/menuIcon.tsx new file mode 100644 index 000000000..0d7d734c9 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/components/icons/menuIcon.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +type MenuIconPropsBase = { + className?: string; + size?: number | string; +}; + +type MenuIconPropsWithColor = MenuIconPropsBase & { + color: string; +}; + +type MenuIconProps = MenuIconPropsBase | MenuIconPropsWithColor; + +const MenuIcon = (props: MenuIconProps) => { + const { className, size = '30px' } = props; + const color = 'color' in props ? props.color : '#36A9E1'; + + return ( + <svg className={className} width={size} height={size} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink"> + <path fill={color} d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z" /> + <path fill={color} d="M28,14H4c-1.104,0-2,0.896-2,2 s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z" /> + <path fill={color} d="M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2 S29.104,22,28,22z" /> + </svg> + ); +}; + +MenuIcon.defaultName = 'MenuIcon'; + +export default MenuIcon;
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/components/logo.tsx b/sdnr/wt/odlux/framework/src/components/logo.tsx index b10cc8ce1..f2bb1f575 100644 --- a/sdnr/wt/odlux/framework/src/components/logo.tsx +++ b/sdnr/wt/odlux/framework/src/components/logo.tsx @@ -42,7 +42,7 @@ import withStyles from '@mui/styles/withStyles'; import createStyles from '@mui/styles/createStyles'; -import defaultLogo from '../assets/images/defaultLogo.svg'; +const defaultLogo = require('../assets/icons/ht.Connect.svg'); const styles = (theme: Theme) => createStyles({ headerLogo: { @@ -91,7 +91,7 @@ class LogoComponent extends React.Component<LogoProps, ILogoState> { console.info([ "Logo hidden, because browser window width (", this.state.windowWidth, - "px) is lower thershold (", + "px) is lower threshold (", this.hideLogoWhenWindowWidthIsLower, "px)."].join('')); } diff --git a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx index 8541cfe56..c1a5005d4 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx @@ -450,13 +450,13 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate if (filterExpressionAsString.length === 0 || isNaN(valueAsNumber)) return true; if (filterExpressionAsString.startsWith('>=')) { - return valueAsNumber >= Number(filterExpressionAsString.substr(2).trim()); + return valueAsNumber >= Number(filterExpressionAsString.substring(2).trim()); } else if (filterExpressionAsString.startsWith('<=')) { - return valueAsNumber <= Number(filterExpressionAsString.substr(2).trim()); + return valueAsNumber <= Number(filterExpressionAsString.substring(2).trim()); } else if (filterExpressionAsString.startsWith('>')) { - return valueAsNumber > Number(filterExpressionAsString.substr(1).trim()); + return valueAsNumber > Number(filterExpressionAsString.substring(1).trim()); } else if (filterExpressionAsString.startsWith('<')) { - return valueAsNumber < Number(filterExpressionAsString.substr(1).trim()); + return valueAsNumber < Number(filterExpressionAsString.substring(1).trim()); } } else if (column.type === ColumnType.date){ const valueAsString = String(dataValue); @@ -480,13 +480,13 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate const filterExpressionAsString = String(filterExpression).trim(); if (filterExpressionAsString.startsWith('>=')) { - return valueAsDate >= convertToDate(filterExpressionAsString.substr(2).trim()); + return valueAsDate >= convertToDate(filterExpressionAsString.substring(2).trim()); } else if (filterExpressionAsString.startsWith('<=')) { - return valueAsDate <= convertToDate(filterExpressionAsString.substr(2).trim()); + return valueAsDate <= convertToDate(filterExpressionAsString.substring(2).trim()); } else if (filterExpressionAsString.startsWith('>')) { - return valueAsDate > convertToDate(filterExpressionAsString.substr(1).trim()); + return valueAsDate > convertToDate(filterExpressionAsString.substring(1).trim()); } else if (filterExpressionAsString.startsWith('<')) { - return valueAsDate < convertToDate(filterExpressionAsString.substr(1).trim()); + return valueAsDate < convertToDate(filterExpressionAsString.substring(1).trim()); } diff --git a/sdnr/wt/odlux/framework/src/components/material-table/showColumnDialog.tsx b/sdnr/wt/odlux/framework/src/components/material-table/showColumnDialog.tsx index f8ae6ea97..ab0d465e7 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/showColumnDialog.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/showColumnDialog.tsx @@ -16,9 +16,9 @@ * ============LICENSE_END========================================================================== */ -import { Button, Checkbox, FormControlLabel, MenuItem, Popover, Switch, Typography } from '@mui/material'; -import connect, { Connect, IDispatcher } from '../../flux/connect'; -import * as React from 'react'; +import React from 'react'; +import { Button, FormControlLabel, Popover, Switch, Typography } from '@mui/material'; +import { connect, Connect, IDispatcher } from '../../flux/connect'; import { ColumnModel } from './columnModel'; import { IApplicationStoreState } from '../../store/applicationStore'; diff --git a/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts b/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts index f9015493f..e2fda7647 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts +++ b/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts @@ -51,6 +51,7 @@ export type ExternalMethodes<TData> = { onHandleChangeRowsPerPage: (rowsPerPage: number | null) => void; onHideColumns: (columnName: string[]) => void; onShowColumns: (columnName: string[]) => void; + onClearFilters: () => void; }, createPreActions: (dispatch: Dispatch, skipRefresh?: boolean) => { onPreFilterChanged: (preFilter: { @@ -328,7 +329,13 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState dispatch((dispatch: Dispatch) => { dispatch(new ShowColumnsAction(columnName)); }) - } + }, + onClearFilters: () => { + dispatch((dispatch: Dispatch) => { + let filter = { }; + dispatch(new SetFilterChangedAction(filter)); + }); + }, // selected: }; }; diff --git a/sdnr/wt/odlux/framework/src/components/material-ui/listItemLink.tsx b/sdnr/wt/odlux/framework/src/components/material-ui/listItemLink.tsx index 744cb0d24..626cb8978 100644 --- a/sdnr/wt/odlux/framework/src/components/material-ui/listItemLink.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-ui/listItemLink.tsx @@ -27,6 +27,8 @@ import { WithStyles } from '@mui/styles'; import withStyles from '@mui/styles/withStyles'; import createStyles from '@mui/styles/createStyles'; import { toAriaLabel } from '../../utilities/yangHelper'; +import { IconType } from '../../models/iconDefinition'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; const styles = (theme: Theme) => createStyles({ active: { @@ -35,7 +37,7 @@ const styles = (theme: Theme) => createStyles({ }); export interface IListItemLinkProps extends WithStyles<typeof styles> { - icon: JSX.Element | null; + icon: IconType | null; primary: string | React.ComponentType; secondary?: React.ComponentType; to: string; @@ -48,13 +50,19 @@ export const ListItemLink = withStyles(styles)((props: IListItemLinkProps) => { const renderLink = (itemProps: any): JSX.Element => ( props.external ? <a target="_blank" href={to} { ...itemProps }></a> : <NavLink exact={ exact } to={ to } activeClassName={ classes.active } { ...itemProps } />); - + + const customIconHeight = 22; const ariaLabel = typeof Primary === 'string' ? toAriaLabel("link-to-"+Primary) : toAriaLabel("link-to-"+Primary.displayName); + + //create menu icon, either using an faIcon or a link to a custom svg icon + //moved to one place for easier usage + const listItemIcon = icon && ( typeof icon === 'string' ? <img height={customIconHeight} src={icon} /> : <FontAwesomeIcon icon={icon} /> ); + return ( <> <ListItem button component={ renderLink } aria-label={ariaLabel}> { icon - ? <ListItemIcon>{ icon }</ListItemIcon> + ? <ListItemIcon>{ listItemIcon }</ListItemIcon> : null } { typeof Primary === 'string' diff --git a/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx b/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx index 195706d28..d969286b7 100644 --- a/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx +++ b/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx @@ -15,15 +15,14 @@ * the License. * ============LICENSE_END========================================================================== */ -import * as React from 'react'; +import React from 'react'; import { Theme } from '@mui/material/styles'; +import classNames from 'classnames'; import { WithStyles } from '@mui/styles'; import withStyles from '@mui/styles/withStyles'; import createStyles from '@mui/styles/createStyles'; -import { faHome, faAddressBook } from '@fortawesome/free-solid-svg-icons'; - import Drawer from '@mui/material/Drawer'; import List from '@mui/material/List'; @@ -34,11 +33,14 @@ import { faProjectDiagram } from '@fortawesome/free-solid-svg-icons'; import ListItemLink from '../components/material-ui/listItemLink'; -import connect, { Connect } from '../flux/connect'; +import { connect, Connect } from '../flux/connect'; import { MenuAction } from '../actions/menuAction'; -import * as classNames from 'classnames'; import { transportPCEUrl } from '../app'; +const aboutIcon = require('../assets/icons/About.svg'); +const homeIcon = require('../assets/icons/Home.svg'); +const loginIcon = require('../assets/icons/User.svg'); +const settingsIcon = require('../assets/icons/Tools.svg'); const drawerWidth = 240; @@ -141,16 +143,15 @@ export const NavigationMenu = withStyles(styles)(connect()(({ classes, state, di window.dispatchEvent(new Event('menu-resized')); }, [isOpen]) - let menuItems = state.framework.applicationRegistraion && Object.keys(state.framework.applicationRegistraion).map(key => { - const reg = state.framework.applicationRegistraion[key]; - const icon = !reg.icon ? null :( typeof reg.icon === 'string' ? <img height={22} src={reg.icon} /> : <FontAwesomeIcon icon={reg.icon} /> ) + let menuItems = state.framework.applicationRegistration && Object.keys(state.framework.applicationRegistration).map(key => { + const reg = state.framework.applicationRegistration[key]; return reg && ( <ListItemLink key={reg.name} to={reg.path || `/${reg.name}`} primary={reg.menuEntry || reg.name} secondary={reg.subMenuEntry} - icon={icon} /> + icon={reg.icon || null} /> ) || null; }) || null; @@ -160,7 +161,7 @@ export const NavigationMenu = withStyles(styles)(connect()(({ classes, state, di key={"transportPCE"} to={transportUrl} primary={"TransportPCE"} - icon={<FontAwesomeIcon icon={faProjectDiagram} />} + icon={faProjectDiagram} external />; const linkFound = menuItems.find(obj => obj.key === "linkCalculation"); @@ -191,17 +192,17 @@ export const NavigationMenu = withStyles(styles)(connect()(({ classes, state, di <div className={classes.toolbar} /> { /* https://fiffty.github.io/react-treeview-mui/ */} <List className={classes.menu} component="nav"> - <ListItemLink exact to="/" primary="Home" icon={<FontAwesomeIcon icon={faHome} />} /> + <ListItemLink exact to="/" primary="Home" icon={homeIcon} /> <Divider /> { menuItems } <Divider /> - <ListItemLink to="/about" primary="About" icon={<FontAwesomeIcon icon={faAddressBook} />} /> + <ListItemLink to="/about" primary="About" icon={aboutIcon} /> {(false && process.env.NODE_ENV === "development") ? <> <Divider /> - <ListItemLink to="/test" primary="Test" icon={<FontAwesomeIcon icon={faHome} />} /> + <ListItemLink to="/test" primary="Test" icon={settingsIcon} /> </> : null } diff --git a/sdnr/wt/odlux/framework/src/components/routing/appFrame.tsx b/sdnr/wt/odlux/framework/src/components/routing/appFrame.tsx index d055b8a87..aa22f17f4 100644 --- a/sdnr/wt/odlux/framework/src/components/routing/appFrame.tsx +++ b/sdnr/wt/odlux/framework/src/components/routing/appFrame.tsx @@ -15,9 +15,9 @@ * the License. * ============LICENSE_END========================================================================== */ -import * as React from 'react'; +import React from 'react'; -import connect, { Connect } from '../../flux/connect'; +import { connect, Connect } from '../../flux/connect'; import { SetTitleAction } from '../../actions/titleActions'; import { AddErrorInfoAction } from '../../actions/errorActions'; diff --git a/sdnr/wt/odlux/framework/src/components/settings/general.tsx b/sdnr/wt/odlux/framework/src/components/settings/general.tsx index 90f15c1d2..ffd516ba1 100644 --- a/sdnr/wt/odlux/framework/src/components/settings/general.tsx +++ b/sdnr/wt/odlux/framework/src/components/settings/general.tsx @@ -16,11 +16,11 @@ * ============LICENSE_END========================================================================== */ +import React from 'react'; import { Button, FormControlLabel, Switch, Typography } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import { SettingsComponentProps } from '../../models/settings'; -import * as React from 'react'; -import connect, { Connect, IDispatcher } from '../../flux/connect'; +import { connect, Connect, IDispatcher } from '../../flux/connect'; import { IApplicationStoreState } from '../../store/applicationStore'; import { getGeneralSettingsAction, SetGeneralSettingsAction, updateGeneralSettingsAction } from '../../actions/settingsAction'; import { sendMessage, SettingsMessage } from '../../services/broadcastService'; diff --git a/sdnr/wt/odlux/framework/src/components/titleBar.tsx b/sdnr/wt/odlux/framework/src/components/titleBar.tsx index 19d3bdf74..40c0fc736 100644 --- a/sdnr/wt/odlux/framework/src/components/titleBar.tsx +++ b/sdnr/wt/odlux/framework/src/components/titleBar.tsx @@ -15,7 +15,7 @@ * the License. * ============LICENSE_END========================================================================== */ -import * as React from 'react'; +import React from 'react'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { Theme } from '@mui/material/styles'; @@ -27,9 +27,6 @@ import Toolbar from '@mui/material/Toolbar'; import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; import IconButton from '@mui/material/IconButton'; -import Block from '@mui/icons-material/Block'; -import Adjust from '@mui/icons-material/Adjust'; -import MenuIcon from '@mui/icons-material/Menu'; import AccountCircle from '@mui/icons-material/AccountCircle'; import MenuItem from '@mui/material/MenuItem'; import Menu from '@mui/material/Menu'; @@ -41,10 +38,12 @@ import { faDotCircle } from '@fortawesome/free-solid-svg-icons'; import { logoutUser } from '../actions/authentication'; import { PushAction, ReplaceAction } from '../actions/navigationActions'; -import connect, { Connect, IDispatcher } from '../flux/connect'; -import Logo from './logo'; +import { connect, Connect, IDispatcher } from '../flux/connect'; import { MenuAction, MenuClosedByUser } from '../actions/menuAction'; +import MenuIcon from './icons/menuIcon'; +import Logo from './logo'; + const styles = (theme: Theme) => createStyles({ appBar: { zIndex: theme.zIndex.drawer + 1, @@ -58,7 +57,8 @@ const styles = (theme: Theme) => createStyles({ }, icon: { marginLeft: 16, - marginRight: 8 + marginRight: 8, + marginBottom: -2, }, connected: { color: "green" @@ -112,10 +112,10 @@ class TitleBarComponent extends React.Component<TitleBarProps, { anchorEl: HTMLE // add notificationInfo element before help - if (state.framework.applicationRegistraion) { + if (state.framework.applicationRegistration) { let isNotificationInfoAdded = false; - Object.keys(state.framework.applicationRegistraion).map(key => { - const reg = state.framework.applicationRegistraion[key]; + Object.keys(state.framework.applicationRegistration).map(key => { + const reg = state.framework.applicationRegistration[key]; if (reg && reg.statusBarElement) { if (key === "help") { isNotificationInfoAdded = true; @@ -132,7 +132,12 @@ class TitleBarComponent extends React.Component<TitleBarProps, { anchorEl: HTMLE } const stateIcon = state.framework.applicationState.icon; - const icon = !stateIcon ? null :( typeof stateIcon === 'string' ? <img className={classes.icon} height={22} src={stateIcon} /> : <FontAwesomeIcon className={classes.icon} icon={stateIcon} /> ) + const customIconHeight = 22; + const icon = !stateIcon + ? null + : (typeof stateIcon === 'string' + ? <img className={classes.icon} height={customIconHeight} src={stateIcon} /> + : <FontAwesomeIcon className={classes.icon} icon={stateIcon} />) return ( diff --git a/sdnr/wt/odlux/framework/src/flux/connect.ts b/sdnr/wt/odlux/framework/src/flux/connect.tsx index f54e4e0f0..09d30dae7 100644 --- a/sdnr/wt/odlux/framework/src/flux/connect.ts +++ b/sdnr/wt/odlux/framework/src/flux/connect.tsx @@ -15,13 +15,14 @@ * the License. * ============LICENSE_END========================================================================== */ -import * as React from 'react'; -import * as PropTypes from 'prop-types'; +import React, { FC, useContext, createContext, useState, useEffect, useRef } from 'react'; -import { Dispatch } from '../flux/store'; +import { Dispatch } from './store'; import { ApplicationStore, IApplicationStoreState } from '../store/applicationStore'; +const LogLevel = +(localStorage.getItem('log.odlux.framework.flux.connect') || 0); + interface IApplicationStoreContext { applicationStore: ApplicationStore; } @@ -38,12 +39,12 @@ interface IDispatchProps { dispatch: Dispatch; } -type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> - type ComponentDecoratorInfer<TMergedProps> = { <TProps>(wrappedComponent: React.ComponentType<TProps & TMergedProps>): React.ComponentClass<Omit<TProps & TMergedProps, keyof TMergedProps>>; }; +const ApplicationStoreContext = createContext<IApplicationStoreContext | undefined>(undefined); + export type Connect<TMapProps extends ((...args: any) => any) | undefined = undefined, TMapDispatch extends ((...args: any) => any) | undefined = undefined> = (TMapProps extends ((...args: any) => any) ? ReturnType<TMapProps> : IApplicationStoreProps) & (TMapDispatch extends ((...args: any) => any) ? ReturnType<TMapDispatch> : IDispatchProps); @@ -75,9 +76,7 @@ export function connect<TProps, TStateProps, TDispatchProps>( const injectApplicationStore = (WrappedComponent: React.ComponentType<TProps & (IApplicationStoreProps | TStateProps) & IDispatchProps>): React.ComponentType<TProps> => { class StoreAdapter extends React.Component<TProps, {}> { - public static contextTypes = { ...WrappedComponent.contextTypes, applicationStore: PropTypes.object.isRequired }; - context: IApplicationStoreContext; - + render(): JSX.Element { if (isWrappedComponentIsVersion1(WrappedComponent)) { @@ -112,7 +111,7 @@ export function connect<TProps, TStateProps, TDispatchProps>( this.forceUpdate(); } } - + StoreAdapter.contextType = ApplicationStoreContext; return StoreAdapter; } @@ -138,24 +137,77 @@ export function connect<TProps, TStateProps, TDispatchProps>( } } -interface ApplicationStoreProviderProps extends React.Props<ApplicationStoreProvider> { +type ApplicationStoreProviderProps = { applicationStore: ApplicationStore; } -export class ApplicationStoreProvider extends React.Component<ApplicationStoreProviderProps> - implements /* React.ComponentLifecycle<ApplicationStoreProviderProps, any>, */ React.ChildContextProvider<IApplicationStoreContext> { +export const ApplicationStoreProvider: FC<ApplicationStoreProviderProps> = (props) => { + const { applicationStore, children } = props; - public static childContextTypes = { applicationStore: PropTypes.object.isRequired }; + return ( + <ApplicationStoreContext.Provider value={{ applicationStore }}> + {children} + </ApplicationStoreContext.Provider> + ); +}; - getChildContext(): IApplicationStoreContext { - return { - applicationStore: this.props.applicationStore - }; +export const useApplicationStore = (): ApplicationStore => { + const context = useContext(ApplicationStoreContext); + if (context == null || context.applicationStore == null) { + throw new Error("Requires application store provider!") } + return context.applicationStore +}; - render(): JSX.Element { - return React.Children.only(this.props.children) as any; //type error, fix when possible +export const useSelectApplicationState = <TProp extends unknown >( selector: (state: IApplicationStoreState) => TProp, eqFunc = (a: TProp, b: TProp) => a === b ): TProp => { + const context = useContext(ApplicationStoreContext); + if (context == null || context.applicationStore == null) { + throw new Error("Requires application store provider!") } -} + + const [propState, setPropState] = useState<TProp>(selector(context.applicationStore.state)); + + const selectorRef = useRef(selector); + selectorRef.current = selector; + + const propStateRef = useRef({propState}); + propStateRef.current.propState = propState; + + useEffect(() => { + if (context == null || context.applicationStore == null) { + throw new Error("Requires application store provider!") + } + + const changedHandler = () => { + const newState = selectorRef.current(context.applicationStore.state); + if (!eqFunc(newState, propStateRef.current.propState)) { + setPropState(newState); + } + }; + + if (LogLevel > 3) { + console.log("useSelectApplicationState: adding handler", changedHandler); + } + + context.applicationStore.changed.addHandler(changedHandler); + + return () => { + if (LogLevel > 3) { + console.log("useSelectApplicationState: removing handler", changedHandler); + } -export default connect;
\ No newline at end of file + context.applicationStore.changed.removeHandler(changedHandler); + } + }, [context]); + + return propState; + +}; + +export const useApplicationDispatch = (): Dispatch => { + const context = useContext(ApplicationStoreContext); + if (context == null || context.applicationStore == null) { + throw new Error("Requires application store provider!") + } + return context.applicationStore.dispatch; +}; diff --git a/sdnr/wt/odlux/framework/src/flux/store.ts b/sdnr/wt/odlux/framework/src/flux/store.ts index dd80ce7ba..347d295e0 100644 --- a/sdnr/wt/odlux/framework/src/flux/store.ts +++ b/sdnr/wt/odlux/framework/src/flux/store.ts @@ -20,6 +20,8 @@ import { Event } from "../common/event" import { Action } from './action'; import { IActionHandler } from './action'; +const LogLevel = +(localStorage.getItem('log.odlux.framework.flux.store') || 0); + export interface Dispatch { <TAction extends Action>(action: TAction): TAction; } @@ -28,8 +30,8 @@ export interface Enhancer<TStoreState> { (store: Store<TStoreState>): Dispatch; } -class InitialisationAction extends Action { }; -const initialisationAction = new InitialisationAction(); +class InitializationAction extends Action { }; +const initializationAction = new InitializationAction(); export class Store<TStoreState> { @@ -43,19 +45,22 @@ export class Store<TStoreState> { this._isDispatching = false; - this.changed = new Event<void>(); // sollten wir hier eventuell sogar den state mit übergeben ? + this.changed = new Event<void>(); this._actionHandler = actionHandler; this._state = initialState as TStoreState; if (enhancer) this._dispatch = enhancer(this); - this._dispatch(initialisationAction); + this._dispatch(initializationAction); } public changed: Event<void>; private _dispatch: Dispatch = <TAction extends Action>(payload: TAction): TAction => { + if (LogLevel > 2) { + console.log('Store::Dispatch - ', payload); + } if (payload == null || !(payload instanceof Action)) { throw new Error( 'Actions must inherit from type Action. ' + @@ -76,6 +81,9 @@ export class Store<TStoreState> { } if (this._state !== oldState) { + if (LogLevel > 3) { + console.log('Store::Dispatch - state has changed', this._state); + } this.changed.invoke(); } diff --git a/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts b/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts index f98d77487..71b9e33d1 100644 --- a/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts +++ b/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts @@ -23,7 +23,7 @@ import { AuthPolicy, User } from '../models/authentication'; import { onLogin, onLogout } from '../services/applicationApi'; import { startWebsocketSession, endWebsocketSession } from '../services/notificationService'; import { startUserSession, endUserSession } from '../services/userSessionService'; -import { getSettings } from '../services/settingsService'; +import { getUserdata } from '../services/userdataService'; export interface IAuthenticationState { user?: User; diff --git a/sdnr/wt/odlux/framework/src/middleware/logger.ts b/sdnr/wt/odlux/framework/src/middleware/logger.ts index 47725e696..fb0874f3e 100644 --- a/sdnr/wt/odlux/framework/src/middleware/logger.ts +++ b/sdnr/wt/odlux/framework/src/middleware/logger.ts @@ -18,16 +18,17 @@ import { Dispatch } from '../flux/store'; import { MiddlewareApi } from '../store/applicationStore'; +const LogLevel = +(localStorage.getItem('log.odlux.framework.middleware.logger') || 0); function createLoggerMiddleware() { return function logger({ getState }: MiddlewareApi) { return (next: Dispatch): Dispatch => action => { - console.log('will dispatch', action); + if (LogLevel > 2) console.log('will dispatch', action); const returnValue = next(action); - console.log('state after dispatch', getState()); + if (LogLevel > 2) console.log('state after dispatch', getState()); return returnValue; }; - } + }; } export const logger = createLoggerMiddleware(); diff --git a/sdnr/wt/odlux/framework/src/middleware/navigation.ts b/sdnr/wt/odlux/framework/src/middleware/navigation.ts index 5f3eed55d..44035fec3 100644 --- a/sdnr/wt/odlux/framework/src/middleware/navigation.ts +++ b/sdnr/wt/odlux/framework/src/middleware/navigation.ts @@ -55,7 +55,7 @@ const routerMiddlewareCreator = (history: History) => () => (next: Dispatch): Di // ensure user is logged in and token is valid if (action.pathname.startsWith("/oauth") && (action.search.startsWith("?token="))){ const ind = action.search.lastIndexOf("token="); - const tokenStr = ind > -1 ? action.search.substr(ind+6) : null; + const tokenStr = ind > -1 ? action.search.substring(ind+6) : null; const token = tokenStr && jwt.decode(tokenStr); if (tokenStr && token) { // @ts-ignore diff --git a/sdnr/wt/odlux/framework/src/services/applicationApi.ts b/sdnr/wt/odlux/framework/src/services/applicationApi.ts index 36523f9eb..8246ee8fa 100644 --- a/sdnr/wt/odlux/framework/src/services/applicationApi.ts +++ b/sdnr/wt/odlux/framework/src/services/applicationApi.ts @@ -15,13 +15,10 @@ * the License. * ============LICENSE_END========================================================================== */ -import { GeneralSettings } from '../models/settings'; -import { setGeneralSettingsAction, SetGeneralSettingsAction } from '../actions/settingsAction'; + import { Event } from '../common/event'; import { ApplicationStore } from '../store/applicationStore'; import { AuthMessage, getBroadcastChannel, sendMessage } from './broadcastService'; -import { endWebsocketSession } from './notificationService'; -import { getSettings } from './settingsService'; let resolveApplicationStoreInitialized: (store: ApplicationStore) => void; let applicationStore: ApplicationStore | null = null; diff --git a/sdnr/wt/odlux/framework/src/services/applicationManager.ts b/sdnr/wt/odlux/framework/src/services/applicationManager.ts index 14e3ed0b2..bd620a020 100644 --- a/sdnr/wt/odlux/framework/src/services/applicationManager.ts +++ b/sdnr/wt/odlux/framework/src/services/applicationManager.ts @@ -23,7 +23,7 @@ import { applicationApi } from './applicationApi'; /** Represents registry to manage all applications. */ class ApplicationManager { - /** Stores all registerd applications. */ + /** Stores all registered applications. */ private _applications: { [key: string]: ApplicationInfo }; /** Initializes a new instance of this class. */ @@ -32,7 +32,7 @@ class ApplicationManager { this.changed = new Event<void>(); } - /** The chaged event will fire if the registration has changed. */ + /** The changed event will fire if the registration has changed. */ public changed: Event<void>; /** Registers a new application. */ diff --git a/sdnr/wt/odlux/framework/src/services/broadcastService.ts b/sdnr/wt/odlux/framework/src/services/broadcastService.ts index f2c3ebc57..40968e54f 100644 --- a/sdnr/wt/odlux/framework/src/services/broadcastService.ts +++ b/sdnr/wt/odlux/framework/src/services/broadcastService.ts @@ -22,89 +22,95 @@ import { ReplaceAction } from "../actions/navigationActions"; import { User } from "../models/authentication"; import { ApplicationStore } from "../store/applicationStore"; -type Broadcaster = {channel: BroadcastChannel, key: String}; +type Broadcaster = { + channel: BroadcastChannel; + key: String; +}; type AuthTypes = 'login' | 'logout'; -export type AuthMessage={key: AuthTypes, data: any}; +export type AuthMessage = { + key: AuthTypes; + data: any; +}; type SettingsType = 'general'; -export type SettingsMessage={key: SettingsType, enableNotifications: boolean, user: string}; +export type SettingsMessage = { + key: SettingsType; + enableNotifications: boolean; + user: string; +}; -let channels: Broadcaster[] = []; -let store : ApplicationStore | null = null; +const channels: Broadcaster[] = []; +let store: ApplicationStore | null = null; export const saveChannel = (channel: BroadcastChannel, channelName: string) => { - channels.push({channel: channel, key: channelName}); -} - -export const startBroadcastChannel = (applicationStore: ApplicationStore)=>{ - store=applicationStore; - - //might decide to use one general broadcast channel with more keys in the future - createAuthBroadcastChannel(); - createSettingsBroadcastChannel(); -} - -const createSettingsBroadcastChannel = () =>{ - - const name = "odlux_settings"; - const bc: BroadcastChannel = new BroadcastChannel(name); - channels.push({ channel: bc, key: name }); - - bc.onmessage = (eventMessage: MessageEvent<SettingsMessage>) => { - console.log(eventMessage) - - if (eventMessage.data.key === 'general') { - - if (store?.state.framework.authenticationState.user) { - const data = eventMessage.data; - if(store.state.framework.authenticationState.user.user === data.user){ - store?.dispatch(setGeneralSettingsAction(data.enableNotifications)); - } - } - } - } + channels.push({ channel: channel, key: channelName }); +}; -} +export const startBroadcastChannel = (applicationStore: ApplicationStore) => { + store = applicationStore; -const createAuthBroadcastChannel = () => { - const name = "odlux_auth"; - const bc: BroadcastChannel = new BroadcastChannel(name); - channels.push({ channel: bc, key: name }); - - bc.onmessage = (eventMessage: MessageEvent<AuthMessage>) => { - console.log(eventMessage) - - if (eventMessage.data.key === 'login') { - if (!store?.state.framework.authenticationState.user) { - const initialToken = localStorage.getItem("userToken"); - if (initialToken) { - store?.dispatch(loginUserAction(User.fromString(initialToken))); - store?.dispatch(new ReplaceAction("/")); - } - } - } - else if (eventMessage.data.key === 'logout') { + //might decide to use one general broadcast channel with more keys in the future + createAuthBroadcastChannel(); + createSettingsBroadcastChannel(); +}; - if (store?.state.framework.authenticationState.user) { - store?.dispatch(logoutUser()); - store?.dispatch(new ReplaceAction("/login")); - } - } - } -} +const createSettingsBroadcastChannel = () => { -export const getBroadcastChannel = (channelName: string) =>{ - const foundChannel = channels.find(s =>s.key===channelName); - return foundChannel?.channel; -} + const name = "odlux_settings"; + const bc: BroadcastChannel = new BroadcastChannel(name); + channels.push({ channel: bc, key: name }); + bc.onmessage = (eventMessage: MessageEvent<SettingsMessage>) => { + console.log(eventMessage); -export const sendMessage = (data: any, channel: string) =>{ + if (eventMessage.data.key === 'general') { - const foundChannel = channels.find(s =>s.key===channel); - if(foundChannel){ - foundChannel.channel.postMessage(data); - } + if (store?.state.framework.authenticationState.user) { + const data = eventMessage.data; + if (store.state.framework.authenticationState.user.user === data.user) { + store?.dispatch(setGeneralSettingsAction(data.enableNotifications)); + } + } + } + } +}; + +const createAuthBroadcastChannel = () => { + const name = "odlux_auth"; + const bc: BroadcastChannel = new BroadcastChannel(name); + channels.push({ channel: bc, key: name }); + + bc.onmessage = (eventMessage: MessageEvent<AuthMessage>) => { + console.log(eventMessage) + + if (eventMessage.data.key === 'login') { + if (!store?.state.framework.authenticationState.user) { + const initialToken = localStorage.getItem("userToken"); + if (initialToken) { + store?.dispatch(loginUserAction(User.fromString(initialToken))); + store?.dispatch(new ReplaceAction("/")); + } + } + } + else if (eventMessage.data.key === 'logout') { + if (store?.state.framework.authenticationState.user) { + store?.dispatch(logoutUser()); + store?.dispatch(new ReplaceAction("/login")); + } } + } +}; + +export const getBroadcastChannel = (channelName: string) => { + const foundChannel = channels.find(s => s.key === channelName); + return foundChannel?.channel; +}; + +export const sendMessage = (data: any, channel: string) => { + const foundChannel = channels.find(s => s.key === channel); + if (foundChannel) { + foundChannel.channel.postMessage(data); + } +}; diff --git a/sdnr/wt/odlux/framework/src/services/index.ts b/sdnr/wt/odlux/framework/src/services/index.ts index 19b451345..85f0708a6 100644 --- a/sdnr/wt/odlux/framework/src/services/index.ts +++ b/sdnr/wt/odlux/framework/src/services/index.ts @@ -18,5 +18,5 @@ export { applicationManager } from './applicationManager'; export { subscribe, unsubscribe } from './notificationService'; export { requestRest } from './restService'; -export { putSettings, getSettings} from './settingsService'; +export { saveUserdata, getUserdata } from './userdataService'; diff --git a/sdnr/wt/odlux/framework/src/services/restService.ts b/sdnr/wt/odlux/framework/src/services/restService.ts index d727e4c9e..a296c52a4 100644 --- a/sdnr/wt/odlux/framework/src/services/restService.ts +++ b/sdnr/wt/odlux/framework/src/services/restService.ts @@ -16,18 +16,13 @@ * ============LICENSE_END========================================================================== */ +import { ReplaceAction } from '../actions/navigationActions'; +import { AddErrorInfoAction } from '../actions/errorActions'; -import { ApplicationStore } from "../store/applicationStore"; -import { ReplaceAction } from "../actions/navigationActions"; -import { AddErrorInfoAction } from "../actions/errorActions"; +import { storeService } from './storeService'; const baseUri = `${ window.location.origin }`; const absUrlPattern = /^https?:\/\//; -let applicationStore: ApplicationStore | null = null; - -export const startRestService = (store: ApplicationStore) => { - applicationStore = store; -}; export const formEncode = (params: { [key: string]: string | number }) => Object.keys(params).map((key) => { return encodeURIComponent(key) + '=' + encodeURIComponent(params[key].toString()); @@ -46,9 +41,9 @@ export const getAccessPolicyByUrl = (url: string) => { DELETE: false, }; - if (!applicationStore) return result; + if (!storeService.applicationStore) return result; - const { state: { framework: { applicationState: { enablePolicy }, authenticationState: { policies }}} } = applicationStore!; + const { state: { framework: { applicationState: { enablePolicy }, authenticationState: { policies } } } } = storeService.applicationStore!; result.GET = true; result.POST = true; @@ -71,7 +66,7 @@ export const getAccessPolicyByUrl = (url: string) => { return result; -} +}; /** Sends a rest request to the given path. * @returns The data, or null it there was any error @@ -87,8 +82,8 @@ export async function requestRest<TData>(path: string = '', init: RequestInit = /** Sends a rest request to the given path and reports the server state. * @returns An object with the server state, a message and the data or undefined in case of a json parse error. */ -export async function requestRestExt<TData>(path: string = '', init: RequestInit = {}, authenticate: boolean = true, isResource: boolean = false): Promise<{ status: number, message?: string, data: TData | null | undefined }> { - const result: { status: number, message?: string, data: TData | null } = { +export async function requestRestExt<TData>(path: string = '', init: RequestInit = {}, authenticate: boolean = true, isResource: boolean = false): Promise<{ status: number; message?: string; data: TData | null | undefined }> { + const result: { status: number; message?: string; data: TData | null } = { status: -1, data: null, }; @@ -100,60 +95,59 @@ export async function requestRestExt<TData>(path: string = '', init: RequestInit headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', - ...init.headers - } + ...init.headers, + }, }; - if (!isAbsUrl && authenticate && applicationStore) { - const { state: { framework: { authenticationState: { user } } } } = applicationStore; + if (!isAbsUrl && authenticate && storeService.applicationStore) { + const { state: { framework: { authenticationState: { user } } } } = storeService.applicationStore; // do not request if the user is not valid if (!user || !user.isValid) { return { ...result, - message: "User is not valid or not logged in." + message: 'User is not valid or not logged in.', }; } (init.headers = { ...init.headers, - 'Authorization': `${user.tokenType} ${user.token}` + 'Authorization': `${user.tokenType} ${user.token}`, //'Authorization': 'Basic YWRtaW46YWRtaW4=' }); } const fetchResult = await fetch(uri, init); - if(fetchResult.status === 403){ - applicationStore && applicationStore.dispatch(new AddErrorInfoAction({title: "Forbidden", message:"Status: [403], access denied."})); + if (fetchResult.status === 403) { + storeService.applicationStore && storeService.applicationStore.dispatch(new AddErrorInfoAction({ title: 'Forbidden', message:'Status: [403], access denied.' })); return { ...result, status: 403, - message: "Forbidden." + message: 'Forbidden.', }; - } - else if (fetchResult.status === 401) { - applicationStore && applicationStore.dispatch(new ReplaceAction(`/login?returnTo=${applicationStore.state.framework.navigationState.pathname}`)); + } else if (fetchResult.status === 401) { + storeService.applicationStore && storeService.applicationStore.dispatch(new ReplaceAction(`/login?returnTo=${storeService.applicationStore.state.framework.navigationState.pathname}`)); return { ...result, status: 401, - message: "Authentication requested by server." + message: 'Authentication requested by server.', }; } - const contentType = fetchResult.headers.get("Content-Type") || fetchResult.headers.get("content-type"); - const isJson = contentType && (contentType.toLowerCase().startsWith("application/json") || contentType.toLowerCase().startsWith("application/yang-data+json")); + const contentType = fetchResult.headers.get('Content-Type') || fetchResult.headers.get('content-type'); + const isJson = contentType && (contentType.toLowerCase().startsWith('application/json') || contentType.toLowerCase().startsWith('application/yang-data+json')); try { const data = (isJson ? await fetchResult.json() : await fetchResult.text()) as TData; return { ...result, status: fetchResult.status, message: fetchResult.statusText, - data: data + data: data, }; } catch (error) { return { ...result, status: fetchResult.status, message: error && error.message || String(error), - data: undefined + data: undefined, }; } }
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/services/storeService.ts b/sdnr/wt/odlux/framework/src/services/storeService.ts new file mode 100644 index 000000000..cbb5987de --- /dev/null +++ b/sdnr/wt/odlux/framework/src/services/storeService.ts @@ -0,0 +1,11 @@ +import { ApplicationStore } from "../store/applicationStore"; + +let applicationStore: ApplicationStore | null = null; + +export const startSoreService = (store: ApplicationStore) => { + applicationStore = store; +}; + +export const storeService = { + get applicationStore() { return applicationStore; }, + };
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/services/settingsService.ts b/sdnr/wt/odlux/framework/src/services/userdataService.ts index 6633a794d..5c9b576c3 100644 --- a/sdnr/wt/odlux/framework/src/services/settingsService.ts +++ b/sdnr/wt/odlux/framework/src/services/userdataService.ts @@ -22,7 +22,7 @@ import { requestRest } from "./restService"; const settingsPath ="/userdata"; - export function getSettings<TData>(partialPath?: string){ + export function getUserdata<TData>(partialPath?: string){ let path = settingsPath; if(partialPath){ path+=partialPath @@ -32,7 +32,7 @@ import { requestRest } from "./restService"; return result; } - export function putSettings<TData>(partialPath: string, data: string){ + export function saveUserdata<TData>(partialPath: string, data: string){ const result = requestRest<TData>(settingsPath+partialPath, {method: "PUT", body: data}) return result; diff --git a/sdnr/wt/odlux/framework/src/store/applicationStore.ts b/sdnr/wt/odlux/framework/src/store/applicationStore.ts index a4545eff9..cbe8c20da 100644 --- a/sdnr/wt/odlux/framework/src/store/applicationStore.ts +++ b/sdnr/wt/odlux/framework/src/store/applicationStore.ts @@ -37,7 +37,7 @@ import { updatePolicies } from '../middleware/policies'; export type MiddlewareApi = MiddlewareArg<IApplicationStoreState>; export interface IFrameworkStoreState { - applicationRegistraion: IApplicationRegistration; + applicationRegistration: IApplicationRegistration; applicationState: IApplicationState; authenticationState: IAuthenticationState; navigationState: INavigationState; @@ -48,7 +48,7 @@ export interface IApplicationStoreState { } const frameworkHandlers = combineActionHandler({ - applicationRegistraion: applicationRegistryHandler, + applicationRegistration: applicationRegistryHandler, applicationState: applicationStateHandler, authenticationState: authenticationStateHandler, navigationState: navigationStateHandler @@ -62,7 +62,7 @@ export const applicationStoreCreator = (): ApplicationStore => { const actionHandlers = Object.keys(applicationService.applications).reduce((acc, cur) => { const reg = applicationService.applications[cur]; reg && typeof reg.rootActionHandler === 'function' && (acc[cur] = reg.rootActionHandler); - reg && +(reg.middlewares || 0) && middlewares.push(...(reg.middlewares as Middleware<IApplicationStoreState>[])); + reg && reg.middlewares && Array.isArray(reg.middlewares) && middlewares.push(...(reg.middlewares as Middleware<IApplicationStoreState>[])); return acc; }, { framework: frameworkHandlers } as any); diff --git a/sdnr/wt/odlux/framework/src/utilities/logLevel.ts b/sdnr/wt/odlux/framework/src/utilities/logLevel.ts new file mode 100644 index 000000000..a198d98a9 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/utilities/logLevel.ts @@ -0,0 +1,8 @@ +export enum LogLevel { + Always = 0, + Error = 1, + Warning = 2, + Info = 3, + Debug = 4, + Trace = 5, +} diff --git a/sdnr/wt/odlux/framework/src/views/about.tsx b/sdnr/wt/odlux/framework/src/views/about.tsx index ac219708d..937e74f33 100644 --- a/sdnr/wt/odlux/framework/src/views/about.tsx +++ b/sdnr/wt/odlux/framework/src/views/about.tsx @@ -15,22 +15,18 @@ * the License. * ============LICENSE_END========================================================================== */ -import * as React from 'react'; +import React, { FC, useEffect, useState } from 'react'; import * as marked from 'marked'; import * as hljs from 'highlight.js'; import { requestRestExt } from '../services/restService'; import { Button, Typography } from '@mui/material'; + const defaultRenderer = new marked.Renderer(); defaultRenderer.link = (href, title, text) => ( `<a target="_blank" rel="noopener noreferrer" href="${href}" title="${title}">${text}</a>` ); -interface AboutState { - content: string | null; - isCopiedSuccessfully: boolean; - isContentLoadedSucessfully: boolean; -} -type odluxVersion= {version:string,build:string, framework: string, +type OdluxVersion= {version:string,build:string, framework: string, applications:{ configurationApp: string, connectApp: string, @@ -38,25 +34,27 @@ type odluxVersion= {version:string,build:string, framework: string, faultApp: string, helpApp: string, inventoryApp: string, + linkCalculationApp: string, maintenanceApp: string, mediatorApp: string, + networkMapApp: string, permanceHistoryApp: string }}; -type topologyVersion = {version: string, buildTimestamp: string}; - -class AboutComponent extends React.Component<any, AboutState> { - textarea: React.RefObject<HTMLTextAreaElement>; +type TopologyVersion = {version: string, buildTimestamp: string}; +const AboutComponent: FC = (props) => { + + const textareaRef = React.createRef<HTMLTextAreaElement>(); + const [content, setContent] = useState<string | null>(null); + const [isCopiedSuccessfully, setCopySuccess] = useState(false); + const [isContetLoaded, setContentLoaded] = useState(false); - constructor(props: any) { - super(props); - this.state = { content: null, isCopiedSuccessfully:false, isContentLoadedSucessfully: false } - this.textarea = React.createRef(); - this.loadAboutContent(); - } + useEffect(()=>{ + loadAboutContent(); + },[]); - private getMarkOdluxVersionMarkdownTable(data:odluxVersion|null|undefined):string{ + const getMarkOdluxVersionMarkdownTable = (data:OdluxVersion|null|undefined):string => { if(!data) { return ""; }else{ @@ -72,6 +70,8 @@ class AboutComponent extends React.Component<any, AboutState> { `| InventoryApp | ${data.applications.inventoryApp}|\n `+ `| EventLogApp | ${data.applications.eventLogApp}|\n `+ `| MediatorApp | ${data.applications.mediatorApp}|\n `+ + `| NetworkMapApp | ${data.applications.networkMapApp}|\n `+ + `| LinkCalculatorApp | ${data.applications.linkCalculationApp}|\n `+ `| HelpApp | ${data.applications.helpApp}|\n `; } @@ -80,7 +80,7 @@ class AboutComponent extends React.Component<any, AboutState> { } } - private getTopologyVersionMarkdownTable(data: topologyVersion|null|undefined){ + const getTopologyVersionMarkdownTable = (data: TopologyVersion|null|undefined) => { if(!data){ return "No version"; } @@ -92,7 +92,7 @@ class AboutComponent extends React.Component<any, AboutState> { } } - private loadAboutContent(): void { + const loadAboutContent = (): void => { const baseUri = window.location.pathname.substring(0,window.location.pathname.lastIndexOf("/")+1); const init = { 'method': 'GET', @@ -102,7 +102,7 @@ class AboutComponent extends React.Component<any, AboutState> { } }; const p1 = requestRestExt<string>('/about',init); - const p2 = requestRestExt<odluxVersion>(`${baseUri}version.json`); + const p2 = requestRestExt<OdluxVersion>(`${baseUri}version.json`); const p3 = requestRestExt<any>(`/topology/info/version`); Promise.all([p1,p2, p3]).then((responses) => { @@ -110,31 +110,30 @@ class AboutComponent extends React.Component<any, AboutState> { const response2 = responses[1]; const response3 = responses[2]; const content = response.status == 200 ? response.data : `${response.status} ${response.message}` || "Server error"; - const content2 = `\n## ODLUX Version Info\n`+(response2.status == 200 ? this.getMarkOdluxVersionMarkdownTable(response2.data) : `${response2.message}` || "ODLUX Server error"); - const content3 = `\n## Topology API Version Info\n`+(response3.status == 200 ? this.getTopologyVersionMarkdownTable(response3.data): `Topology API not available`); + const content2 = `\n## ODLUX Version Info\n`+(response2.status == 200 ? getMarkOdluxVersionMarkdownTable(response2.data) : `${response2.message}` || "ODLUX Server error"); + const content3 = `\n## Topology API Version Info\n`+(response3.status == 200 ? getTopologyVersionMarkdownTable(response3.data): `Topology API not available`); const loadedSucessfully = response.status == 200 ? true : false; - this.setState({ content: (content + content2 + content3 ) || null, isContentLoadedSucessfully: loadedSucessfully }); + setContent((content + content2 + content3 ) || null); + setContentLoaded(loadedSucessfully); }).catch((error) => { - this.setState({ content: error }) - }) + setContent(error); + }); } - copyToClipboard = (e: React.MouseEvent<HTMLButtonElement>) =>{ + const copyToClipboard = (e: React.MouseEvent<HTMLButtonElement>) =>{ e.preventDefault(); - if(this.textarea.current!==null){ - this.textarea.current.select(); + if(textareaRef.current!==null){ + textareaRef.current.select(); document.execCommand('copy'); if(e.currentTarget != null){ // refocus on button, otherwhise the textarea would be focused e.currentTarget.focus(); } - this.setState({isCopiedSuccessfully: true}); - window.setTimeout(()=>{this.setState({isCopiedSuccessfully: false});},2000); + setCopySuccess(true); + window.setTimeout(()=>{ setCopySuccess(false);},2000); } } - render() { - const markedOptions: marked.MarkedOptions = { gfm: true, breaks: false, @@ -157,17 +156,17 @@ class AboutComponent extends React.Component<any, AboutState> { const style: React.CSSProperties = {}; const containerStyle = { overflow: "auto", paddingRight: "20px" } - const html = (marked(this.state.content || 'loading', { renderer: markedOptions && markedOptions.renderer || defaultRenderer })); + const html = (marked(content || 'loading', { renderer: markedOptions && markedOptions.renderer || defaultRenderer })); return ( <div style={containerStyle}> - { this.state.isContentLoadedSucessfully && + { isContetLoaded && <div style={{float: "right", marginRight: "10px"}}> - <Button aria-label="copy-version-information-button" color="inherit" variant="contained" onClick={e => this.copyToClipboard(e)}> + <Button aria-label="copy-version-information-button" color="inherit" variant="contained" onClick={e => copyToClipboard(e)}> Copy to clipboard </Button> { - this.state.isCopiedSuccessfully && + isCopiedSuccessfully && <Typography variant="body1" style={{color: "green"}} align="center"> copied successfully </Typography> @@ -183,13 +182,12 @@ class AboutComponent extends React.Component<any, AboutState> { <form> <textarea style={{opacity: ".01"}} - ref={this.textarea} - value={this.state.content || ''} + ref={textareaRef} + value={content || ''} /> </form> </div> ); - } }; export const About = AboutComponent; diff --git a/sdnr/wt/odlux/framework/src/views/frame.tsx b/sdnr/wt/odlux/framework/src/views/frame.tsx index 4676f5ac2..4a93cf0ae 100644 --- a/sdnr/wt/odlux/framework/src/views/frame.tsx +++ b/sdnr/wt/odlux/framework/src/views/frame.tsx @@ -15,15 +15,11 @@ * the License. * ============LICENSE_END========================================================================== */ -import * as React from 'react'; +import React, { FC, memo } from 'react'; import { HashRouter as Router, Route, Redirect, Switch } from 'react-router-dom'; import { Theme } from '@mui/material/styles'; -import { WithStyles } from '@mui/styles'; -import withStyles from '@mui/styles/withStyles'; -import createStyles from '@mui/styles/createStyles'; -import { faHome, faAddressBook, faSignInAlt, faCog } from '@fortawesome/free-solid-svg-icons' - +import { makeStyles } from '@mui/styles'; import { SnackbarProvider } from 'notistack'; import { ConfirmProvider } from 'material-ui-confirm'; @@ -42,8 +38,14 @@ import UserSettings from '../views/settings'; import applicationService from '../services/applicationManager'; +const aboutIcon = require('../assets/icons/About.svg'); +const homeIcon = require('../assets/icons/Home.svg'); +const loginIcon = require('../assets/icons/User.svg'); +const settingsIcon = require('../assets/icons/Tools.svg'); + +const styles = makeStyles((theme: Theme) => { -const styles = (theme: Theme) => createStyles({ + return { root: { flexGrow: 1, height: '100%', @@ -61,74 +63,69 @@ const styles = (theme: Theme) => createStyles({ minWidth: 0, // So the Typography noWrap works }, toolbar: theme.mixins.toolbar as any + }; }); +const FrameComponent: FC = memo(() => { + const registrations = applicationService.applications; + const classes = styles(); + return ( + <ConfirmProvider> + <SnackbarProvider maxSnack={3}> + <Router> + <div className={classes.root}> + <SnackDisplay /> + <ErrorDisplay /> + <TitleBar /> + <Menu /> + <main className={classes.content}> + { + <div className={classes.toolbar} /> //needed for margins, don't remove! + } + <Switch> + <Route exact path="/" component={() => ( + <AppFrame title={"Home"} icon={homeIcon} > + <Home /> + </AppFrame> + )} /> + <Route path="/about" component={() => ( + <AppFrame title={"About"} icon={aboutIcon} > + <About /> + </AppFrame> + )} /> + <Route path="/settings" component={() => ( + <AppFrame title={"Settings"} icon={settingsIcon} > + <UserSettings /> + </AppFrame> + )} /> + {process.env.NODE_ENV === "development" ? <Route path="/test" component={() => ( + <AppFrame title={"Test"} icon={settingsIcon} > + <Test /> + </AppFrame> + )} /> : null} + <Route path="/login" component={() => ( + <AppFrame title={"Login"} icon={loginIcon} > + <Login /> + </AppFrame> + )} /> + {Object.keys(registrations).map(p => { + const application = registrations[p]; + return (<Route key={application.name} path={application.path || `/${application.name}`} component={() => ( + <AppFrame title={application.title || (typeof application.menuEntry === 'string' && application.menuEntry) || application.name} icon={application.icon} appId={application.name} > + <application.rootComponent /> + </AppFrame> + )} />) + })} + <Redirect to="/" /> + </Switch> + </main> + </div> + </Router> + </SnackbarProvider> + </ConfirmProvider> + ); +}); -type FrameProps = WithStyles<typeof styles>; - -class FrameComponent extends React.Component<FrameProps>{ - - render() { - const registrations = applicationService.applications; - const { classes } = this.props; - return ( - <ConfirmProvider> - <SnackbarProvider maxSnack={3}> - <Router> - <div className={classes.root}> - <SnackDisplay /> - <ErrorDisplay /> - <TitleBar /> - <Menu /> - <main className={classes.content}> - { - <div className={classes.toolbar} /> //needed for margins, don't remove! - } - <Switch> - <Route exact path="/" component={() => ( - <AppFrame title={"Home"} icon={faHome} > - <Home /> - </AppFrame> - )} /> - <Route path="/about" component={() => ( - <AppFrame title={"About"} icon={faAddressBook} > - <About /> - </AppFrame> - )} /> - <Route path="/settings" component={() => ( - <AppFrame title={"Settings"} icon={faCog} > - <UserSettings /> - </AppFrame> - )} /> - {process.env.NODE_ENV === "development" ? <Route path="/test" component={() => ( - <AppFrame title={"Test"} icon={faAddressBook} > - <Test /> - </AppFrame> - )} /> : null} - <Route path="/login" component={() => ( - <AppFrame title={"Login"} icon={faSignInAlt} > - <Login /> - </AppFrame> - )} /> - { Object.keys(registrations).map(p => { - const application = registrations[p]; - return (<Route key={application.name} path={application.path || `/${application.name}`} component={() => ( - <AppFrame title={application.title || (typeof application.menuEntry === 'string' && application.menuEntry) || application.name} icon={application.icon} appId={application.name} > - <application.rootComponent /> - </AppFrame> - )} />) - })} - <Redirect to="/" /> - </Switch> - </main> - </div> - </Router> - </SnackbarProvider> - </ConfirmProvider> - ); - } -} - -export const Frame = withStyles(styles)(FrameComponent); +export const Frame = FrameComponent; export default Frame; diff --git a/sdnr/wt/odlux/framework/src/views/home.tsx b/sdnr/wt/odlux/framework/src/views/home.tsx index 92fd0b262..72c5059e1 100644 --- a/sdnr/wt/odlux/framework/src/views/home.tsx +++ b/sdnr/wt/odlux/framework/src/views/home.tsx @@ -16,50 +16,39 @@ * ============LICENSE_END========================================================================== */ -import * as React from 'react'; -import { IApplicationStoreState } from "../store/applicationStore"; -import connect, { Connect, IDispatcher } from "../flux/connect"; +import React, {FC, useState} from 'react'; import applicationService from '../services/applicationManager'; -type props = Connect<typeof mapProps, typeof mapDispatch>; -type SettingsEntry = { name: string, element: JSX.Element } +type DashboardElement = { name: string, element: JSX.Element }; - -const mapProps = (state: IApplicationStoreState) => ({ -}); - -const mapDispatch = (dispatcher: IDispatcher) => ({ -}); - -const DashboardView: React.FunctionComponent<props> = (props) => { +const DashboardView: FC = (props) => { const registrations = applicationService.applications; - const [selectedIndex] = React.useState(0); + const [selectedIndex] = useState(0); - let settingsArray: SettingsEntry[] = []; + let dashboardArray: DashboardElement[] = []; - let settingsElements: (SettingsEntry)[] = Object.keys(registrations).map(p => { + let dashboardElements: (DashboardElement)[] = Object.keys(registrations).map(p => { const application = registrations[p]; if (application.dashbaordElement) { - const value: SettingsEntry = { name: application.menuEntry?.toString()!, element: <application.dashbaordElement /> }; + const value: DashboardElement = { name: application.menuEntry?.toString()!, element: <application.dashbaordElement /> }; return value; } else { return null; } - }).filter((x): x is SettingsEntry => x !== null); - + }).filter((x): x is DashboardElement => x !== null); - settingsArray.push(...settingsElements); + dashboardArray.push(...dashboardElements); return <div> <div> <div> { - settingsArray[selectedIndex]?.element + dashboardArray[selectedIndex]?.element } </div> </div> @@ -67,4 +56,4 @@ const DashboardView: React.FunctionComponent<props> = (props) => { } -export default connect(mapProps, mapDispatch)(DashboardView); +export default DashboardView;
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/views/login.tsx b/sdnr/wt/odlux/framework/src/views/login.tsx index e037edf82..46c0872bc 100644 --- a/sdnr/wt/odlux/framework/src/views/login.tsx +++ b/sdnr/wt/odlux/framework/src/views/login.tsx @@ -15,39 +15,34 @@ * the License. * ============LICENSE_END========================================================================== */ -import * as React from 'react'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; +import React, { FC, useEffect, useState } from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; import Alert from '@mui/material/Alert'; import Avatar from '@mui/material/Avatar'; import Button from '@mui/material/Button'; import CssBaseline from '@mui/material/CssBaseline'; import FormControl from '@mui/material/FormControl'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import Checkbox from '@mui/material/Checkbox'; import Input from '@mui/material/Input'; import InputLabel from '@mui/material/InputLabel'; -import LockIcon from '@mui/icons-material/LockOutlined'; import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; import { Theme } from '@mui/material/styles'; +import Typography from '@mui/material/Typography'; -import { WithStyles } from '@mui/styles'; -import withStyles from '@mui/styles/withStyles'; -import createStyles from '@mui/styles/createStyles'; +import { makeStyles } from '@mui/styles'; -import connect, { Connect, IDispatcher } from '../flux/connect'; +import { useApplicationDispatch, useSelectApplicationState } from '../flux/connect'; import authenticationService from '../services/authenticationService'; -import { updateExternalLoginProviderAsyncActionCreator } from '../actions/loginProvider'; import { loginUserAction, UpdatePolicies } from '../actions/authentication'; +import { updateExternalLoginProviderAsyncActionCreator } from '../actions/loginProvider'; -import { IApplicationStoreState } from '../store/applicationStore'; import { AuthPolicy, AuthToken, User } from '../models/authentication'; -import Menu from '@mui/material/Menu'; -import { MenuItem } from '@mui/material'; -const styles = (theme: Theme) => createStyles({ +const loginIcon = require('../assets/icons/User.svg'); + +const styles = makeStyles((theme: Theme) =>{ + return{ layout: { width: 'auto', display: 'block', // Fix IE11 issue. @@ -91,204 +86,165 @@ const styles = (theme: Theme) => createStyles({ padding: '0 10px', color: 'grey' } +}; }); -const mapProps = (state: IApplicationStoreState) => ({ - search: state.framework.navigationState.search, - authentication: state.framework.applicationState.authentication, - externalLoginProviders: state.framework.applicationState.externalLoginProviders , -}); - -const mapDispatch = (dispatcher: IDispatcher) => ({ - updateExternalProviders: () => dispatcher.dispatch(updateExternalLoginProviderAsyncActionCreator()), - updateAuthentication: (token: AuthToken | null) => { - const user = token && new User(token) || undefined; - dispatcher.dispatch(loginUserAction(user)); - }, - updatePolicies: (policies?: AuthPolicy[]) => { - return dispatcher.dispatch(new UpdatePolicies(policies)); - }, -}); - -type LoginProps = RouteComponentProps<{}> & WithStyles<typeof styles> & Connect<typeof mapProps, typeof mapDispatch>; - -interface ILoginState { - externalProviderAnchor: HTMLElement | null; - busy: boolean; - username: string; - password: string; - scope: string; - message: string; - isServerReady: boolean; - providers: { - id: string; - title: string; - loginUrl: string; - }[] | null; -} +type LoginProps = RouteComponentProps; // todo: ggf. redirect to einbauen -class LoginComponent extends React.Component<LoginProps, ILoginState> { +const LoginComponent: FC<LoginProps> = (props) => { - constructor(props: LoginProps) { - super(props); - - this.state = { - externalProviderAnchor: null, - busy: false, - username: '', - password: '', - scope: 'sdn', - message: '', - providers: null, - isServerReady: false - }; + const search = useSelectApplicationState(state => state.framework.navigationState.search); + const authentication = useSelectApplicationState(state => state.framework.applicationState.authentication); + const externalLoginProviders = useSelectApplicationState(state => state.framework.applicationState.externalLoginProviders); + + const dispatch = useApplicationDispatch(); + const updateExternalProviders = () => dispatch(updateExternalLoginProviderAsyncActionCreator()); + const updateAuthentication = (token: AuthToken | null) => { + const user = token && new User(token) || undefined; + dispatch(loginUserAction(user)); + } + const updatePolicies = (policies?: AuthPolicy[]) => { + return dispatch(new UpdatePolicies(policies)); } - async componentDidMount(){ - if (this.props.authentication === "oauth" && (this.props.externalLoginProviders == null || this.props.externalLoginProviders.length === 0)){ - this.props.updateExternalProviders(); + const [isBusy, setBusy] = useState(false); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [scope, setScope] = useState("sdn"); + const [message, setMessage] = useState(""); + const [isServerReady, setIsServerReady] = useState(false); + + useEffect(()=>{ + if (authentication === "oauth" && (externalLoginProviders == null || externalLoginProviders.length === 0)){ + updateExternalProviders(); } authenticationService.getServerReadyState().then(result =>{ - this.setState({isServerReady: result}); + setIsServerReady(result); }) + },[]); - - - } - - private setExternalProviderAnchor = (el: HTMLElement | null) => { - this.setState({externalProviderAnchor: el }) - } - - render(): JSX.Element { - const { classes } = this.props; - const areProvidersAvailable = this.props.externalLoginProviders && this.props.externalLoginProviders.length > 0; - return ( - <> - <CssBaseline /> - <main className={classes.layout}> - <Paper className={classes.paper}> - <Avatar className={classes.avatar}> - <LockIcon /> - </Avatar> - <Typography variant="caption">Sign in</Typography> - <form className={classes.form}> - - - {areProvidersAvailable && - <> - { - this.props.externalLoginProviders!.map((provider, index) => ( - <Button - aria-controls="externalLogin" - aria-label={"external-login-identity-provider-" + (index + 1)} - aria-haspopup="true" - fullWidth - variant="contained" - color="inherit" - className={classes.submit} onClick={() => { window.location = provider.loginUrl as any; }}> - {provider.title} - </Button>)) - } - - <div className={classes.lineContainer}> - <span className={classes.thirdPartyDivider}> - OR - </span> - </div> - </> - } - - <FormControl variant="standard" margin="normal" required fullWidth> - <InputLabel htmlFor="username">Username</InputLabel> - <Input id="username" name="username" autoComplete="username" autoFocus - disabled={this.state.busy} - value={this.state.username} - onChange={event => { this.setState({ username: event.target.value }) }} /> - </FormControl> - <FormControl variant="standard" margin="normal" required fullWidth> - <InputLabel htmlFor="password">Password</InputLabel> - <Input - name="password" - type="password" - id="password" - autoComplete="current-password" - disabled={this.state.busy} - value={this.state.password} - onChange={event => { this.setState({ password: event.target.value }) }} - /> - </FormControl> - <FormControl variant="standard" margin="normal" required fullWidth> - <InputLabel htmlFor="password">Domain</InputLabel> - <Input - name="scope" - type="scope" - id="scope" - disabled={this.state.busy} - value={this.state.scope} - onChange={event => { this.setState({ scope: event.target.value }) }} - /> - </FormControl> - <Button - aria-label="login-button" - type="submit" - fullWidth - variant="contained" - color="inherit" - disabled={this.state.busy} - className={classes.submit} - onClick={this.onSignIn} - > - Sign in - </Button> - - </form> - {this.state.message && <Alert severity="error">{this.state.message}</Alert>} - </Paper> - </main> - </> - ); - } - - private onSignIn = async (event: React.MouseEvent<HTMLButtonElement>) => { + const onSignIn = async (event: React.MouseEvent<HTMLButtonElement>) => { event.preventDefault(); + + setBusy(true); - this.setState({ busy: true }); - - const token = this.props.authentication === "oauth" - ? await authenticationService.authenticateUserOAuth(this.state.username, this.state.password, this.state.scope) - : await authenticationService.authenticateUserBasicAuth(this.state.username, this.state.password, this.state.scope); + const token = authentication === "oauth" + ? await authenticationService.authenticateUserOAuth(username, password, scope) + : await authenticationService.authenticateUserBasicAuth(username, password, scope); - this.props.updateAuthentication(token); - this.setState({ busy: false }); + updateAuthentication(token); + setBusy(false); if (token) { - const query = this.props.search && this.props.search.replace(/^\?/, "").split('&').map(e => e.split("=")); + const query = search && search.replace(/^\?/, "").split('&').map(e => e.split("=")); const returnTo = query && query.find(e => e[0] === "returnTo"); - this.props.history.replace(returnTo && returnTo[1] || "/"); + props.history.replace(returnTo && returnTo[1] || "/"); } else { - if(!this.state.isServerReady){ + if(!isServerReady){ const ready = await authenticationService.getServerReadyState(); if(ready){ - this.setState({isServerReady: true}); + setIsServerReady(true); }else{ - this.setState({message: "Login is currently not possible. Please re-try in a few minutes. If the problem persits, ask your administrator for assistence."}); + setMessage("Login is currently not possible. Please re-try in a few minutes. If the problem persists, ask your administrator for assistance."); } }else{ - this.setState({ - message: "Could not log in. Please check your credentials or ask your administrator for assistence.", - password: "" - }) + setMessage("Could not log in. Please check your credentials or ask your administrator for assistance."); + setPassword(""); } } } + + const classes = styles(); + const areProvidersAvailable = externalLoginProviders && externalLoginProviders.length > 0; + + return ( + <> + <CssBaseline /> + <main className={classes.layout}> + <Paper className={classes.paper}> + <Avatar className={classes.avatar}> + <img src={loginIcon} alt="loginIcon" /> + </Avatar> + <Typography variant="caption">Sign in</Typography> + <form className={classes.form}> + {areProvidersAvailable && + <> + { + externalLoginProviders!.map((provider, index) => ( + <Button + aria-controls="externalLogin" + aria-label={"external-login-identity-provider-" + (index + 1)} + aria-haspopup="true" + fullWidth + variant="contained" + color="inherit" + className={classes.submit} onClick={() => { window.location = provider.loginUrl as any; }}> + {provider.title} + </Button>)) + } + <div className={classes.lineContainer}> + <span className={classes.thirdPartyDivider}> + OR + </span> + </div> + </> + } + <FormControl variant="standard" margin="normal" required fullWidth> + <InputLabel htmlFor="username">Username</InputLabel> + <Input id="username" name="username" autoComplete="username" autoFocus + disabled={isBusy} + value={username} + onChange={event => { setUsername(event.target.value); }} /> + </FormControl> + <FormControl variant="standard" margin="normal" required fullWidth> + <InputLabel htmlFor="password">Password</InputLabel> + <Input + name="password" + type="password" + id="password" + autoComplete="current-password" + disabled={isBusy} + value={password} + onChange={event => { setPassword(event.target.value); }} + /> + </FormControl> + <FormControl variant="standard" margin="normal" required fullWidth> + <InputLabel htmlFor="password">Domain</InputLabel> + <Input + name="scope" + type="scope" + id="scope" + disabled={isBusy} + value={scope} + onChange={event => { setScope(event.target.value); }} + /> + </FormControl> + <Button + aria-label="login-button" + type="submit" + fullWidth + variant="contained" + color="inherit" + disabled={isBusy} + className={classes.submit} + onClick={onSignIn} + > + Sign in + </Button> + + </form> + {message && <Alert severity="error">{message}</Alert>} + </Paper> + </main> + </> + ); } -export const Login = withStyles(styles)(withRouter(connect(mapProps, mapDispatch)(LoginComponent))); +export const Login = withRouter(LoginComponent); export default Login;
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/views/settings.tsx b/sdnr/wt/odlux/framework/src/views/settings.tsx index a6b940bfa..5973db9a0 100644 --- a/sdnr/wt/odlux/framework/src/views/settings.tsx +++ b/sdnr/wt/odlux/framework/src/views/settings.tsx @@ -16,30 +16,18 @@ * ============LICENSE_END========================================================================== */ -import * as React from 'react'; -import { IApplicationStoreState } from "../store/applicationStore"; -import connect, { Connect, IDispatcher } from "../flux/connect"; +import React, {FC, useState } from 'react'; +import { useApplicationDispatch } from "../flux/connect"; -import applicationService from '../services/applicationManager'; -import { makeStyles } from '@mui/styles'; import { Divider, List, ListItem, ListItemText, Paper } from '@mui/material'; +import { makeStyles } from '@mui/styles'; +import applicationService from '../services/applicationManager'; -import { GeneralUserSettings } from '../components/settings/general' import { GoBackAction } from '../actions/navigationActions'; +import { GeneralUserSettings } from '../components/settings/general'; import { toAriaLabel } from '../utilities/yangHelper'; -type props = Connect<typeof mapProps, typeof mapDispatch>; - -type SettingsEntry = { name: string, element: JSX.Element } - - -const mapProps = (state: IApplicationStoreState) => ({ - -}); - -const mapDispatch = (dispatcher: IDispatcher) => ({ - goBack: () => dispatcher.dispatch(new GoBackAction()) -}); +type SettingsEntry = { name: string, element: JSX.Element }; const styles = makeStyles({ sectionMargin: { @@ -47,7 +35,6 @@ const styles = makeStyles({ marginBottom: "15px" }, elementMargin: { - marginLeft: "10px" }, menu: { @@ -55,15 +42,17 @@ const styles = makeStyles({ } }); -const UserSettings: React.FunctionComponent<props> = (props) => { +const UserSettings: FC = (props) => { - const classes = styles(); - const registrations = applicationService.applications; + const dispatch = useApplicationDispatch(); + const goBack = () => dispatch(new GoBackAction()); + + const [selectedIndex, setSelectedIndex] = useState(0); - const [selectedIndex, setSelectedIndex] = React.useState(0); + const registrations = applicationService.applications; const navigateBack = () => { - props.goBack(); + goBack(); } let settingsArray: SettingsEntry[] = []; @@ -71,7 +60,6 @@ const UserSettings: React.FunctionComponent<props> = (props) => { //add all framework specific settings settingsArray.push({name:"General", element: <GeneralUserSettings onClose={navigateBack} />}) - //get app settings let settingsElements : (SettingsEntry) [] = Object.keys(registrations).map(p => { const application = registrations[p]; @@ -93,6 +81,8 @@ const UserSettings: React.FunctionComponent<props> = (props) => { setSelectedIndex(newValue); } + const classes = styles(); + return <div style={{ display: "flex", flexDirection: "row", height: "100%" }}> <div style={{ display: "flex", flexDirection: "column", height: "100%", width: "15%" }}> <Paper variant="outlined" style={{ height: "70%" }}> @@ -101,7 +91,7 @@ const UserSettings: React.FunctionComponent<props> = (props) => { settingsArray.map((el, index) => { return ( <> - <ListItem selected={selectedIndex === index} button onClick={e => { onSelectElement(e, index) }} aria-label={toAriaLabel(el?.name+"-settings")}> + <ListItem key={"settings-key-"+index} selected={selectedIndex === index} button onClick={e => { onSelectElement(e, index) }} aria-label={toAriaLabel(el?.name+"-settings")}> <ListItemText primary={el?.name} style={{ padding: 0 }} /> </ListItem> <Divider /> @@ -110,7 +100,6 @@ const UserSettings: React.FunctionComponent<props> = (props) => { } </List> </Paper> - </div> <div style={{ height: "100%", width: "80%", marginLeft: 15 }}> <div style={{ height: "100%" }}> @@ -123,4 +112,4 @@ const UserSettings: React.FunctionComponent<props> = (props) => { } -export default connect(mapProps, mapDispatch)(UserSettings); +export default UserSettings;
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src2/main/resources/version.json b/sdnr/wt/odlux/framework/src2/main/resources/version.json index f9e974672..1d9af9029 100644 --- a/sdnr/wt/odlux/framework/src2/main/resources/version.json +++ b/sdnr/wt/odlux/framework/src2/main/resources/version.json @@ -9,8 +9,10 @@ "faultApp":"##odlux.apps.faultApp.buildno##", "helpApp":"##odlux.apps.helpApp.buildno##", "inventoryApp":"##odlux.apps.inventoryApp.buildno##", + "linkCalculationApp":"##odlux.apps.linkCalculationApp.buildno##", "maintenanceApp":"##odlux.apps.maintenanceApp.buildno##", "mediatorApp":"##odlux.apps.mediatorApp.buildno##", + "networkMapApp":"##odlux.apps.networkMapApp.buildno##", "permanceHistoryApp":"##odlux.apps.permanceHistoryApp.buildno##" } |