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
         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
       - 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 set progress=false \
   && pnpm config set depth 0 \
   && pnpm install --ignore-scripts --frozen-lockfile \
-  && pnpm build:prepare \
   && pnpm build
 
 FROM node:lts-alpine
   && pnpm build
 
 FROM node:lts-alpine
index 734750000b235421e5e8dd3169bfc88eaa1f9a2c..447b9ddd9fe333e5efd092c4b81f62a3fc45471e 100644 (file)
@@ -1,7 +1,7 @@
 .DS_Store
 node_modules
 /dist
 .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
 
 # 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
 
 
 #### 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
 
 
 ### 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
 
 
 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.
 
 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",
     "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",
     "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">
 </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 type { ConnectorStatus } from '@/types'
-import { UIClient } from '@/composables/UIClient'
-// import { compose } from '@/composables/Utils'
+// import { compose } from '@/composables'
 
 const props = defineProps<{
   hashId: string
 
 const props = defineProps<{
   hashId: string
@@ -62,28 +62,30 @@ const props = defineProps<{
 //   state.isIdTagModalVisible = false
 // }
 
 //   state.isIdTagModalVisible = false
 // }
 
+const UIClient = getCurrentInstance()?.appContext.config.globalProperties.$UIClient
+
 function startChargingStation(): void {
 function startChargingStation(): void {
-  UIClient.getInstance().startChargingStation(props.hashId)
+  UIClient.startChargingStation(props.hashId)
 }
 function stopChargingStation(): void {
 }
 function stopChargingStation(): void {
-  UIClient.getInstance().stopChargingStation(props.hashId)
+  UIClient.stopChargingStation(props.hashId)
 }
 function openConnection(): void {
 }
 function openConnection(): void {
-  UIClient.getInstance().openConnection(props.hashId)
+  UIClient.openConnection(props.hashId)
 }
 function closeConnection(): void {
 }
 function closeConnection(): void {
-  UIClient.getInstance().closeConnection(props.hashId)
+  UIClient.closeConnection(props.hashId)
 }
 function startTransaction(): void {
 }
 function startTransaction(): void {
-  UIClient.getInstance().startTransaction(props.hashId, props.connectorId, props.idTag)
+  UIClient.startTransaction(props.hashId, props.connectorId, props.idTag)
 }
 function stopTransaction(): void {
 }
 function stopTransaction(): void {
-  UIClient.getInstance().stopTransaction(props.hashId, props.transactionId)
+  UIClient.stopTransaction(props.hashId, props.transactionId)
 }
 function startAutomaticTransactionGenerator(): void {
 }
 function startAutomaticTransactionGenerator(): void {
-  UIClient.getInstance().startAutomaticTransactionGenerator(props.hashId, props.connectorId)
+  UIClient.startAutomaticTransactionGenerator(props.hashId, props.connectorId)
 }
 function stopAutomaticTransactionGenerator(): void {
 }
 function stopAutomaticTransactionGenerator(): void {
-  UIClient.getInstance().stopAutomaticTransactionGenerator(props.hashId, props.connectorId)
+  UIClient.stopAutomaticTransactionGenerator(props.hashId, props.connectorId)
 }
 </script>
 }
 </script>
index befdbb4b8b71d626a0bc636ece3fb40818ad3aca..d077ca6eba882f74f3ce52ae07d4b2b01ea168a3 100644 (file)
@@ -21,7 +21,7 @@
 
 <script setup lang="ts">
 // import { reactive } from 'vue'
 
 <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<{
 import type { ChargingStationData, ChargingStationInfo, ConnectorStatus } from '@/types'
 
 const props = defineProps<{
index c80c190b5e37b7b0af82acfa9a70cfa218046f29..30f469d99ebc3ab24cec71de311187b24f14f2cd 100644 (file)
@@ -32,7 +32,7 @@
 </template>
 
 <script setup lang="ts">
 </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<{
 import type { ChargingStationData } from '@/types'
 
 const props = defineProps<{
index 1839a43c3f746e0d210734d2e5718ddfeffe1431..ab965a7bbe909d27138b2165c91f8df4408eb990 100644 (file)
@@ -10,7 +10,7 @@
 </template>
 
 <script setup lang="ts">
 </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<{
 import Modal from '@/components/Modal.vue'
 
 const props = defineProps<{
index 9848fac0b03fd136c5fea25a9ed84fa42d282702..0251512d2f233df9e551948d1de3f1bd7054e188 100644 (file)
@@ -1,14 +1,12 @@
 import {
   ApplicationProtocol,
 import {
   ApplicationProtocol,
+  type ConfigurationData,
   ProcedureName,
   type ProtocolResponse,
   type RequestPayload,
   type ResponsePayload,
   ResponseStatus
 } from '@/types'
   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
 
 type ResponseHandler = {
   procedureName: ProcedureName
@@ -22,14 +20,14 @@ export class UIClient {
   private ws!: WebSocket
   private responseHandlers: Map<string, ResponseHandler>
 
   private ws!: WebSocket
   private responseHandlers: Map<string, ResponseHandler>
 
-  private constructor() {
+  private constructor(private configuration: ConfigurationData) {
     this.openWS()
     this.responseHandlers = new Map<string, ResponseHandler>()
   }
 
     this.openWS()
     this.responseHandlers = new Map<string, ResponseHandler>()
   }
 
-  public static getInstance() {
+  public static getInstance(configuration: ConfigurationData) {
     if (UIClient.instance === null) {
     if (UIClient.instance === null) {
-      UIClient.instance = new UIClient()
+      UIClient.instance = new UIClient(configuration)
     }
     return UIClient.instance
   }
     }
     return UIClient.instance
   }
@@ -114,8 +112,8 @@ export class UIClient {
 
   private openWS(): void {
     this.ws = new WebSocket(
 
   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 => {
     )
     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`))
         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 })
         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 { 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">
 </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 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(() => {
 
 onMounted(() => {
-  UIClientInstance.registerWSonOpenListener(load)
+  UIClient.registerWSonOpenListener(load)
 })
 
 type State = {
 })
 
 type State = {
@@ -45,17 +44,17 @@ const state: State = reactive({
 async function load(): Promise<void> {
   if (state.isLoading === true) return
   state.isLoading = true
 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 {
   state.chargingStations =
     listChargingStationsPayload.chargingStations as unknown as ChargingStationData[]
   state.isLoading = false
 }
 
 function startSimulator(): void {
-  UIClientInstance.startSimulator()
+  UIClient.startSimulator()
 }
 function stopSimulator(): void {
 }
 function stopSimulator(): void {
-  UIClientInstance.stopSimulator()
+  UIClient.stopSimulator()
 }
 </script>
 
 }
 </script>