refactor(ui): use JSON format as runtime configuration
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 14 Feb 2024 20:37:25 +0000 (21:37 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 14 Feb 2024 20:37:25 +0000 (21:37 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
18 files changed:
.github/workflows/ci.yml
docker/Dockerfile
ui/web/.gitignore
ui/web/README.md
ui/web/package.json
ui/web/src/assets/config-template.json [new file with mode: 0644]
ui/web/src/assets/config-template.ts [deleted file]
ui/web/src/assets/logo.png [deleted file]
ui/web/src/assets/webui.png [moved from ui/web/assets/webui.png with 100% similarity]
ui/web/src/components/charging-stations/CSConnector.vue
ui/web/src/components/charging-stations/CSData.vue
ui/web/src/components/charging-stations/CSTable.vue
ui/web/src/components/charging-stations/IdTagInputModal.vue
ui/web/src/composables/UIClient.ts
ui/web/src/composables/Utils.ts
ui/web/src/composables/index.ts [new file with mode: 0644]
ui/web/src/main.ts
ui/web/src/views/ChargingStationsView.vue

index 16d7381696cd608c351b11ef3fe22c4aff54ff7c..48e5cfb9a9de7e94d2fe1950d8f674fce2f63535 100644 (file)
@@ -112,9 +112,7 @@ jobs:
         if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '20.x' }}
         run: pnpm lint
       - name: pnpm build
-        run: |
-          pnpm build:prepare
-          pnpm build
+        run: pnpm build
       - name: pnpm test
         run: pnpm test
       - name: pnpm coverage
index 24881891034f75a33a7410811dd2fc54ff0c3d37..0ffd4bf28ef4d82b5db836008c8f7cc4587c733c 100644 (file)
@@ -25,7 +25,6 @@ RUN set -ex \
   && pnpm set progress=false \
   && pnpm config set depth 0 \
   && pnpm install --ignore-scripts --frozen-lockfile \
-  && pnpm build:prepare \
   && pnpm build
 
 FROM node:lts-alpine
index 734750000b235421e5e8dd3169bfc88eaa1f9a2c..447b9ddd9fe333e5efd092c4b81f62a3fc45471e 100644 (file)
@@ -1,7 +1,7 @@
 .DS_Store
 node_modules
 /dist
-/src/assets/config.ts
+/public/config.json
 
 # Created by git for backups. To disable backups in git:
 # $ git config --global mergetool.keepBackup false
index 1f5fdd3341e76793fbf8a842dcf418cc2525e90c..f2c21d13074db20a4238a3f88d15dc27bdb0311f 100644 (file)
@@ -35,7 +35,7 @@ See [here](./../../README.md#charging-stations-simulator-configuration) for more
 
 #### Web UI configuration
 
-Copy the configuration template [src/assets/config-template.ts](src/assets/config-template.ts) to `src/assets/config.ts`.
+Copy the configuration template [src/assets/config-template.json](src/assets/config-template.json) to `public/config.json`.
 
 ### Run
 
@@ -61,7 +61,7 @@ pnpm start
 
 For both options above you can then follow the link displayed in the terminal at the end of compilation. The Web UI looks like the following
 
-![webui](./assets/webui.png)
+![webui](./src/assets/webui.png)
 
 1. With the top 2 buttons you can now stop and afterwards start the simulator and inspect the server console for the number of charging stations, e.g. with the default configuration: `Charging stations simulator ... started with 10 charging station(s)`
 2. Each charging station is a row in the table below, try "Stop Charging Station" and refresh with the large blue button and see the status Started turns from Yes into No.
index 80d418fb01a4c389147e1087e4d42fae43dab448..3d9d373c5558574341ac451293f38558dfe1bbeb 100644 (file)
@@ -22,7 +22,6 @@
     "start": "pnpm build && node start.js",
     "dev": "vite",
     "preview": "vite preview",
-    "build:prepare": "node build-prepare.js",
     "build": "vite build",
     "clean:dist": "npx rimraf dist",
     "clean:node_modules": "npx rimraf node_modules",
diff --git a/ui/web/src/assets/config-template.json b/ui/web/src/assets/config-template.json
new file mode 100644 (file)
index 0000000..1997f9d
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "uiServer": {
+    "host": "localhost",
+    "port": 8080,
+    "protocol": "ui",
+    "version": "0.0.1",
+    "authentication": {
+      "enabled": false,
+      "type": "basic-auth",
+      "username": "admin",
+      "password": "admin"
+    }
+  }
+}
diff --git a/ui/web/src/assets/config-template.ts b/ui/web/src/assets/config-template.ts
deleted file mode 100644 (file)
index b5807ce..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-import { AuthenticationType, type ConfigurationData, Protocol, ProtocolVersion } from '@/types'
-
-const configuration: ConfigurationData = {
-  uiServer: {
-    host: 'localhost',
-    port: 8080,
-    protocol: Protocol.UI,
-    version: ProtocolVersion['0.0.1'],
-    authentication: {
-      enabled: false,
-      type: AuthenticationType.BASIC_AUTH,
-      username: 'admin',
-      password: 'admin'
-    }
-  }
-}
-
-export default configuration
diff --git a/ui/web/src/assets/logo.png b/ui/web/src/assets/logo.png
deleted file mode 100644 (file)
index f3d2503..0000000
Binary files a/ui/web/src/assets/logo.png and /dev/null differ
index 29bb07185804be2e641a934c2df1994447d85495..08852683919d87167451d24c402e8866170fbf4e 100644 (file)
 </template>
 
 <script setup lang="ts">
-// import { reactive } from 'vue'
-import Button from '../buttons/Button.vue'
-// import IdTagInputModal from './IdTagInputModal.vue'
+import { getCurrentInstance } from 'vue'
+// import { reactive } from 'vue';
+// import IdTagInputModal from '@/components/charging-stations/IdTagInputModal.vue'
+import Button from '@/components/buttons/Button.vue'
 import type { ConnectorStatus } from '@/types'
-import { UIClient } from '@/composables/UIClient'
-// import { compose } from '@/composables/Utils'
+// import { compose } from '@/composables'
 
 const props = defineProps<{
   hashId: string
@@ -62,28 +62,30 @@ const props = defineProps<{
 //   state.isIdTagModalVisible = false
 // }
 
+const UIClient = getCurrentInstance()?.appContext.config.globalProperties.$UIClient
+
 function startChargingStation(): void {
-  UIClient.getInstance().startChargingStation(props.hashId)
+  UIClient.startChargingStation(props.hashId)
 }
 function stopChargingStation(): void {
-  UIClient.getInstance().stopChargingStation(props.hashId)
+  UIClient.stopChargingStation(props.hashId)
 }
 function openConnection(): void {
-  UIClient.getInstance().openConnection(props.hashId)
+  UIClient.openConnection(props.hashId)
 }
 function closeConnection(): void {
-  UIClient.getInstance().closeConnection(props.hashId)
+  UIClient.closeConnection(props.hashId)
 }
 function startTransaction(): void {
-  UIClient.getInstance().startTransaction(props.hashId, props.connectorId, props.idTag)
+  UIClient.startTransaction(props.hashId, props.connectorId, props.idTag)
 }
 function stopTransaction(): void {
-  UIClient.getInstance().stopTransaction(props.hashId, props.transactionId)
+  UIClient.stopTransaction(props.hashId, props.transactionId)
 }
 function startAutomaticTransactionGenerator(): void {
-  UIClient.getInstance().startAutomaticTransactionGenerator(props.hashId, props.connectorId)
+  UIClient.startAutomaticTransactionGenerator(props.hashId, props.connectorId)
 }
 function stopAutomaticTransactionGenerator(): void {
-  UIClient.getInstance().stopAutomaticTransactionGenerator(props.hashId, props.connectorId)
+  UIClient.stopAutomaticTransactionGenerator(props.hashId, props.connectorId)
 }
 </script>
index befdbb4b8b71d626a0bc636ece3fb40818ad3aca..d077ca6eba882f74f3ce52ae07d4b2b01ea168a3 100644 (file)
@@ -21,7 +21,7 @@
 
 <script setup lang="ts">
 // import { reactive } from 'vue'
-import CSConnector from './CSConnector.vue'
+import CSConnector from '@/components/charging-stations/CSConnector.vue'
 import type { ChargingStationData, ChargingStationInfo, ConnectorStatus } from '@/types'
 
 const props = defineProps<{
index c80c190b5e37b7b0af82acfa9a70cfa218046f29..30f469d99ebc3ab24cec71de311187b24f14f2cd 100644 (file)
@@ -32,7 +32,7 @@
 </template>
 
 <script setup lang="ts">
-import CSData from './CSData.vue'
+import CSData from '@/components/charging-stations/CSData.vue'
 import type { ChargingStationData } from '@/types'
 
 const props = defineProps<{
index 1839a43c3f746e0d210734d2e5718ddfeffe1431..ab965a7bbe909d27138b2165c91f8df4408eb990 100644 (file)
@@ -10,7 +10,7 @@
 </template>
 
 <script setup lang="ts">
-import Button from '../buttons/Button.vue'
+import Button from '@/components/buttons/Button.vue'
 import Modal from '@/components/Modal.vue'
 
 const props = defineProps<{
index 9848fac0b03fd136c5fea25a9ed84fa42d282702..0251512d2f233df9e551948d1de3f1bd7054e188 100644 (file)
@@ -1,14 +1,12 @@
 import {
   ApplicationProtocol,
+  type ConfigurationData,
   ProcedureName,
   type ProtocolResponse,
   type RequestPayload,
   type ResponsePayload,
   ResponseStatus
 } from '@/types'
-// @ts-expect-error: configuration file can be non existent
-// eslint-disable-next-line import/no-unresolved
-import configuration from '@/assets/config'
 
 type ResponseHandler = {
   procedureName: ProcedureName
@@ -22,14 +20,14 @@ export class UIClient {
   private ws!: WebSocket
   private responseHandlers: Map<string, ResponseHandler>
 
-  private constructor() {
+  private constructor(private configuration: ConfigurationData) {
     this.openWS()
     this.responseHandlers = new Map<string, ResponseHandler>()
   }
 
-  public static getInstance() {
+  public static getInstance(configuration: ConfigurationData) {
     if (UIClient.instance === null) {
-      UIClient.instance = new UIClient()
+      UIClient.instance = new UIClient(configuration)
     }
     return UIClient.instance
   }
@@ -114,8 +112,8 @@ export class UIClient {
 
   private openWS(): void {
     this.ws = new WebSocket(
-      `${configuration.uiServer.secure === true ? ApplicationProtocol.WSS : ApplicationProtocol.WS}://${configuration.uiServer.host}:${configuration.uiServer.port}`,
-      `${configuration.uiServer.protocol}${configuration.uiServer.version}`
+      `${this.configuration.uiServer.secure === true ? ApplicationProtocol.WSS : ApplicationProtocol.WS}://${this.configuration.uiServer.host}:${this.configuration.uiServer.port}`,
+      `${this.configuration.uiServer.protocol}${this.configuration.uiServer.version}`
     )
     this.ws.onmessage = this.responseHandler.bind(this)
     this.ws.onerror = errorEvent => {
@@ -140,7 +138,7 @@ export class UIClient {
         const sendTimeout = setTimeout(() => {
           this.responseHandlers.delete(uuid)
           return reject(new Error(`Send request '${procedureName}' message timeout`))
-        }, 60 * 1000)
+        }, 60000)
         try {
           this.ws.send(msg)
           this.responseHandlers.set(uuid, { procedureName, resolve, reject })
index 2f9c1ac5e92e669e7113813a378da986f8f11236..54d926ade4012a93944481ef50ff85aa57e04fc0 100644 (file)
@@ -1,3 +1,3 @@
-// export const compose = <T>(...fns: ((arg: T) => T)[]): ((x: T) => T) => {
-//   return (x: T) => fns.reduceRight((y, fn) => fn(y), x)
-// }
+export const compose = <T>(...fns: ((arg: T) => T)[]): ((x: T) => T) => {
+  return (x: T) => fns.reduceRight((y, fn) => fn(y), x)
+}
diff --git a/ui/web/src/composables/index.ts b/ui/web/src/composables/index.ts
new file mode 100644 (file)
index 0000000..c5bd756
--- /dev/null
@@ -0,0 +1,2 @@
+export { UIClient } from './UIClient'
+export { compose } from './Utils'
index 3e79677c7c8b6ab8d99d4214acbe4ea1082e6967..1b4d7ac607a6177f9aa8d20e1704493c5f86003d 100644 (file)
@@ -1,5 +1,13 @@
 import { createApp } from 'vue'
-import App from './App.vue'
-import router from './router'
+import router from '@/router'
+import { UIClient } from '@/composables'
+import App from '@/App.vue'
 
-createApp(App).use(router).mount('#app')
+const app = createApp(App)
+
+fetch('/config.json')
+  .then(response => response.json())
+  .then(config => {
+    app.config.globalProperties.$UIClient = UIClient.getInstance(config)
+    app.use(router).mount('#app')
+  })
index f500591a551c2c5332add353dded21d7647ddc5a..be7aefbed233f22451e5ace386402222145ad0d0 100644 (file)
 </template>
 
 <script setup lang="ts">
-import { onMounted, reactive } from 'vue'
+import { getCurrentInstance, onMounted, reactive } from 'vue'
 import CSTable from '@/components/charging-stations/CSTable.vue'
 import type { ChargingStationData } from '@/types'
 import Container from '@/components/Container.vue'
 import ReloadButton from '@/components/buttons/ReloadButton.vue'
-import { UIClient } from '@/composables/UIClient'
 
-const UIClientInstance = UIClient.getInstance()
+const UIClient = getCurrentInstance()?.appContext.config.globalProperties.$UIClient
 
 onMounted(() => {
-  UIClientInstance.registerWSonOpenListener(load)
+  UIClient.registerWSonOpenListener(load)
 })
 
 type State = {
@@ -45,17 +44,17 @@ const state: State = reactive({
 async function load(): Promise<void> {
   if (state.isLoading === true) return
   state.isLoading = true
-  const listChargingStationsPayload = await UIClientInstance.listChargingStations()
+  const listChargingStationsPayload = await UIClient.listChargingStations()
   state.chargingStations =
     listChargingStationsPayload.chargingStations as unknown as ChargingStationData[]
   state.isLoading = false
 }
 
 function startSimulator(): void {
-  UIClientInstance.startSimulator()
+  UIClient.startSimulator()
 }
 function stopSimulator(): void {
-  UIClientInstance.stopSimulator()
+  UIClient.stopSimulator()
 }
 </script>