fix(ui): ensure UI server can only be started once
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 21 Feb 2024 20:57:14 +0000 (21:57 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 21 Feb 2024 20:57:14 +0000 (21:57 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/Bootstrap.ts
src/charging-station/ui-server/AbstractUIServer.ts
src/charging-station/ui-server/UIServerFactory.ts
ui/web/.vscode/extensions.json
ui/web/src/components/actions/SetSupervisionUrl.vue
ui/web/src/components/actions/StartTransaction.vue
ui/web/src/components/buttons/ReloadButton.vue
ui/web/src/components/charging-stations/CSConnector.vue
ui/web/src/components/charging-stations/CSData.vue
ui/web/src/components/charging-stations/CSTable.vue

index 433b04cda951fe7486be9d1d78d9368f33ff912a..047b321b55c36c910211e381482f09439422a63b 100644 (file)
@@ -65,7 +65,7 @@ interface TemplateChargingStations {
 export class Bootstrap extends EventEmitter {
   private static instance: Bootstrap | null = null
   private workerImplementation?: WorkerAbstract<ChargingStationWorkerData>
-  private readonly uiServer?: AbstractUIServer
+  private readonly uiServer: AbstractUIServer
   private storage?: Storage
   private readonly templatesChargingStations: Map<string, TemplateChargingStations>
   private readonly version: string = version
@@ -73,6 +73,7 @@ export class Bootstrap extends EventEmitter {
   private started: boolean
   private starting: boolean
   private stopping: boolean
+  private uiServerStarted: boolean
 
   private constructor () {
     super()
@@ -85,10 +86,11 @@ export class Bootstrap extends EventEmitter {
     this.started = false
     this.starting = false
     this.stopping = false
-    this.templatesChargingStations = new Map<string, TemplateChargingStations>()
+    this.uiServerStarted = false
     this.uiServer = UIServerFactory.getUIServerImplementation(
       Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
     )
+    this.templatesChargingStations = new Map<string, TemplateChargingStations>()
     this.initializedCounters = false
     this.initializeCounters()
     Configuration.configurationChangeCallback = async () => {
@@ -189,8 +191,15 @@ export class Bootstrap extends EventEmitter {
           )
           await this.storage?.open()
         }
-        Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
-          .enabled === true && this.uiServer?.start()
+        if (
+          !this.uiServerStarted &&
+          Configuration.getConfigurationSection<UIServerConfiguration>(
+            ConfigurationSection.uiServer
+          ).enabled === true
+        ) {
+          this.uiServer.start()
+          this.uiServerStarted = true
+        }
         // Start ChargingStation object instance in worker thread
         // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
         for (const stationTemplateUrl of Configuration.getStationTemplateUrls()!) {
@@ -246,7 +255,7 @@ export class Bootstrap extends EventEmitter {
     if (this.started) {
       if (!this.stopping) {
         this.stopping = true
-        await this.uiServer?.sendInternalRequest(
+        await this.uiServer.sendInternalRequest(
           this.uiServer.buildProtocolRequest(
             generateUUID(),
             ProcedureName.STOP_CHARGING_STATION,
@@ -261,7 +270,7 @@ export class Bootstrap extends EventEmitter {
         await this.workerImplementation?.stop()
         delete this.workerImplementation
         this.removeAllListeners()
-        this.uiServer?.chargingStations.clear()
+        this.uiServer.clearCaches()
         this.initializedCounters = false
         await this.storage?.close()
         delete this.storage
@@ -277,8 +286,14 @@ export class Bootstrap extends EventEmitter {
 
   private async restart (): Promise<void> {
     await this.stop()
-    Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
-      .enabled !== true && this.uiServer?.stop()
+    if (
+      this.uiServerStarted &&
+      Configuration.getConfigurationSection<UIServerConfiguration>(ConfigurationSection.uiServer)
+        .enabled !== true
+    ) {
+      this.uiServer.stop()
+      this.uiServerStarted = false
+    }
     await this.start()
   }
 
@@ -400,7 +415,7 @@ export class Bootstrap extends EventEmitter {
   }
 
   private readonly workerEventAdded = (data: ChargingStationData): void => {
-    this.uiServer?.chargingStations.set(data.stationInfo.hashId, data)
+    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
     logger.info(
       `${this.logPrefix()} ${moduleName}.workerEventAdded: Charging station ${
         data.stationInfo.chargingStationId
@@ -411,7 +426,7 @@ export class Bootstrap extends EventEmitter {
   }
 
   private readonly workerEventDeleted = (data: ChargingStationData): void => {
-    this.uiServer?.chargingStations.delete(data.stationInfo.hashId)
+    this.uiServer.chargingStations.delete(data.stationInfo.hashId)
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     const templateChargingStations = this.templatesChargingStations.get(
       data.stationInfo.templateName
@@ -428,7 +443,7 @@ export class Bootstrap extends EventEmitter {
   }
 
   private readonly workerEventStarted = (data: ChargingStationData): void => {
-    this.uiServer?.chargingStations.set(data.stationInfo.hashId, data)
+    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     ++this.templatesChargingStations.get(data.stationInfo.templateName)!.started
     logger.info(
@@ -441,7 +456,7 @@ export class Bootstrap extends EventEmitter {
   }
 
   private readonly workerEventStopped = (data: ChargingStationData): void => {
-    this.uiServer?.chargingStations.set(data.stationInfo.hashId, data)
+    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     --this.templatesChargingStations.get(data.stationInfo.templateName)!.started
     logger.info(
@@ -454,7 +469,7 @@ export class Bootstrap extends EventEmitter {
   }
 
   private readonly workerEventUpdated = (data: ChargingStationData): void => {
-    this.uiServer?.chargingStations.set(data.stationInfo.hashId, data)
+    this.uiServer.chargingStations.set(data.stationInfo.hashId, data)
   }
 
   private readonly workerEventPerformanceStatistics = (data: Statistics): void => {
@@ -485,7 +500,7 @@ export class Bootstrap extends EventEmitter {
             started: 0,
             indexes: new Set<number>()
           })
-          this.uiServer?.chargingStationTemplates.add(templateName)
+          this.uiServer.chargingStationTemplates.add(templateName)
         }
         if (this.templatesChargingStations.size !== stationTemplateUrls.length) {
           console.error(
@@ -544,7 +559,8 @@ export class Bootstrap extends EventEmitter {
     this.stop()
       .then(() => {
         console.info(chalk.green('Graceful shutdown'))
-        this.uiServer?.stop()
+        this.uiServer.stop()
+        this.uiServerStarted = false
         this.waitChargingStationsStopped()
           .then(() => {
             exit(exitCodes.succeeded)
index e24a5769956cb40163aa29b2d05f727694029bed..61593db54b1e233cda4b489c39a4d937c518a71f 100644 (file)
@@ -66,7 +66,7 @@ export abstract class AbstractUIServer {
     this.clearCaches()
   }
 
-  private clearCaches (): void {
+  public clearCaches (): void {
     this.chargingStations.clear()
     this.chargingStationTemplates.clear()
   }
index d6dea71b5483ac512df27c6dd4b5eb827ba708bf..d7de7ddd93b152a6bd26b75007dd72b1d31f05e9 100644 (file)
@@ -20,7 +20,7 @@ export class UIServerFactory {
 
   public static getUIServerImplementation (
     uiServerConfiguration: UIServerConfiguration
-  ): AbstractUIServer | undefined {
+  ): AbstractUIServer {
     if (
       uiServerConfiguration.authentication?.enabled === true &&
       !Object.values(AuthenticationType).includes(uiServerConfiguration.authentication.type)
@@ -59,10 +59,11 @@ export class UIServerFactory {
       uiServerConfiguration.version = ApplicationProtocolVersion.VERSION_11
     }
     switch (uiServerConfiguration.type) {
-      case ApplicationProtocol.WS:
-        return new UIWebSocketServer(uiServerConfiguration)
       case ApplicationProtocol.HTTP:
         return new UIHttpServer(uiServerConfiguration)
+      case ApplicationProtocol.WS:
+      default:
+        return new UIWebSocketServer(uiServerConfiguration)
     }
   }
 }
index fc76e3d0daffa42a9678e5853b816316f69a3d99..1d8c5898202501b930d57b94675d584032b2a778 100644 (file)
@@ -7,6 +7,6 @@
     "sonarsource.sonarlint-vscode",
     "streetsidesoftware.code-spell-checker",
     "Vue.volar",
-    "zixuanchen.vitest-explorer"
+    "vitest.explorer"
   ]
 }
index 33b6767bd093b4d9d4ff7ecb2d358615d9bb4994..e50316bf844f57775adb8712865f78adcae997ea 100644 (file)
@@ -36,7 +36,7 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps, getCurrentInstance, reactive } from 'vue'
+import { getCurrentInstance, reactive } from 'vue'
 import Button from '@/components/buttons/Button.vue'
 
 const props = defineProps<{
index b887357bf81761ee33e76c9bcde418b224dde7eb..5e83983f34512ab1057d65f98a069bafc3c1200d 100644 (file)
@@ -30,7 +30,7 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps, getCurrentInstance, reactive } from 'vue'
+import { getCurrentInstance, reactive } from 'vue'
 import Button from '@/components/buttons/Button.vue'
 
 const props = defineProps<{
index 3ff4bc89915ca838c9b87ee255bcc53a59ecaadb..2fa291af8ae536ceaf4adcfb486482bda08f637e 100644 (file)
@@ -5,7 +5,6 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps } from 'vue'
 import FlatButton from '@/components/buttons/FlatButton.vue'
 
 defineProps<{
index cb03fbca9f77bc3ae29e58ebe1561039796c9ad7..7c31ae988e1f64b6363361ab7b870c44219b4b8e 100644 (file)
@@ -27,7 +27,7 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps, getCurrentInstance } from 'vue'
+import { getCurrentInstance } from 'vue'
 import { useToast } from 'vue-toast-notification'
 import Button from '@/components/buttons/Button.vue'
 import type { ConnectorStatus, Status } from '@/types'
index 22ed784f2ac94255b3b35ab0d8df7e44cbada873..01480020337b9d89d70ebd44e24509f5cf3fc3f4 100644 (file)
@@ -67,7 +67,7 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps, getCurrentInstance } from 'vue'
+import { getCurrentInstance } from 'vue'
 import { useToast } from 'vue-toast-notification'
 import CSConnector from '@/components/charging-stations/CSConnector.vue'
 import Button from '@/components/buttons/Button.vue'
index 49d576e51d0bb510b52c0741b87e165e9872cf95..b098c0e5558a61728e92030164071df9471a39fd 100644 (file)
@@ -21,7 +21,7 @@
     <tbody id="cs-table__body">
       <CSData
         v-for="chargingStation in chargingStations"
-        :key="chargingStation.stationInfo?.hashId"
+        :key="chargingStation.stationInfo?.chargingStationId"
         :charging-station="chargingStation"
       />
     </tbody>
@@ -29,7 +29,6 @@
 </template>
 
 <script setup lang="ts">
-import { defineProps } from 'vue'
 import CSData from '@/components/charging-stations/CSData.vue'
 import type { ChargingStationData } from '@/types'