export const outputTable = (payload: ResponsePayload): void => {
if (payload.hashIdsSucceeded != null && payload.hashIdsSucceeded.length > 0) {
- process.stdout.write(chalk.green(`✓ Succeeded (${String(payload.hashIdsSucceeded.length)}):\n`))
+ process.stdout.write(
+ chalk.green(`✓ Succeeded (${payload.hashIdsSucceeded.length.toString()}):\n`)
+ )
const table = hashIdTable(payload.hashIdsSucceeded)
process.stdout.write(table.toString() + '\n')
}
if (payload.hashIdsFailed != null && payload.hashIdsFailed.length > 0) {
- process.stderr.write(chalk.red(`✗ Failed (${String(payload.hashIdsFailed.length)}):\n`))
+ process.stderr.write(chalk.red(`✗ Failed (${payload.hashIdsFailed.length.toString()}):\n`))
if (payload.responsesFailed != null && payload.responsesFailed.length > 0) {
const table = new Table({ head: [chalk.white('Hash ID'), chalk.white('Error')] })
for (const entry of payload.responsesFailed) {
import type { ClientConfig, ResponseHandler, WebSocketFactory, WebSocketLike } from './types.js'
import { UI_WEBSOCKET_REQUEST_TIMEOUT_MS } from '../constants.js'
+import { ServerFailureError } from '../errors.js'
import { AuthenticationType, ResponseStatus } from '../types/UIProtocol.js'
import { randomUUID, validateUUID } from '../utils/UUID.js'
import { WebSocketReadyState } from './types.js'
-export class ServerFailureError extends Error {
- public readonly payload: ResponsePayload
-
- public constructor (payload: ResponsePayload) {
- const details =
- payload.hashIdsFailed != null && payload.hashIdsFailed.length > 0
- ? `: ${payload.hashIdsFailed.length.toString()} station(s) failed`
- : ''
- super(`Server returned failure status${details}`)
- this.name = 'ServerFailureError'
- this.payload = payload
- }
-}
+export { ServerFailureError } from '../errors.js'
export class WebSocketClient {
public get url (): string {
+import type { ResponsePayload } from './types/UIProtocol.js'
+
export class ConnectionError extends Error {
public readonly url: string
}
}
+export class ServerFailureError extends Error {
+ public readonly payload: ResponsePayload
+
+ public constructor (payload: ResponsePayload) {
+ const details =
+ payload.hashIdsFailed != null && payload.hashIdsFailed.length > 0
+ ? `: ${payload.hashIdsFailed.length.toString()} station(s) failed`
+ : ''
+ super(`Server returned failure status${details}`)
+ this.name = 'ServerFailureError'
+ this.payload = payload
+ }
+}
+
export const extractErrorMessage = (error: unknown): string =>
error instanceof Error ? error.message : String(error)
}),
'Charging stations successfully added',
'Error at adding charging stations',
- () => {
- resetToggleButtonState('add-charging-stations', true)
- $router.push({ name: ROUTE_NAMES.CHARGING_STATIONS })
+ {
+ onFinally: () => {
+ resetToggleButtonState('add-charging-stations', true)
+ $router.push({ name: ROUTE_NAMES.CHARGING_STATIONS })
+ },
}
)
}
$uiClient.setSupervisionUrl(props.hashId, state.value.supervisionUrl),
'Supervision url successfully set',
'Error at setting supervision url',
- () => {
- resetToggleButtonState(`${props.hashId}-set-supervision-url`, true)
- $router.push({ name: ROUTE_NAMES.CHARGING_STATIONS })
+ {
+ onFinally: () => {
+ resetToggleButtonState(`${props.hashId}-set-supervision-url`, true)
+ $router.push({ name: ROUTE_NAMES.CHARGING_STATIONS })
+ },
}
)
}
const click = (): void => {
if (props.shared === true) {
- for (const key in localStorage) {
+ for (const key of Object.keys(localStorage)) {
if (key !== id && key.startsWith(SHARED_TOGGLE_BUTTON_KEY_PREFIX)) {
setToLocalStorage<boolean>(key, false)
- state.value.status = getFromLocalStorage<boolean>(key, false)
}
}
}
- setToLocalStorage<boolean>(id, !getFromLocalStorage<boolean>(id, props.status ?? false))
- state.value.status = getFromLocalStorage<boolean>(id, props.status ?? false)
- if (getFromLocalStorage<boolean>(id, props.status ?? false)) {
+ const current = getFromLocalStorage<boolean>(id, props.status ?? false)
+ const newStatus = !current
+ setToLocalStorage<boolean>(id, newStatus)
+ state.value.status = newStatus
+ if (newStatus) {
props.on?.()
} else {
props.off?.()
}
- $emit('clicked', getFromLocalStorage<boolean>(id, props.status ?? false))
+ $emit('clicked', newStatus)
}
</script>
action: Promise<unknown>,
successMsg: string,
errorMsg: string,
- callbacks?: (() => void) | ExecuteActionCallbacks
+ callbacks?: ExecuteActionCallbacks
): void => {
- const { onFinally, onSuccess } =
- typeof callbacks === 'function'
- ? { onFinally: callbacks, onSuccess: undefined }
- : (callbacks ?? {})
+ const { onFinally, onSuccess } = callbacks ?? {}
action
.then(() => {
try {
id="ui-server-selector"
v-model="state.uiServerIndex"
class="ui-server-selector"
- @change="
- () => {
- if (
- getFromLocalStorage<number>(UI_SERVER_CONFIGURATION_INDEX_KEY, 0) !==
- state.uiServerIndex
- ) {
- $uiClient.setConfiguration(
- ($configuration.uiServer as UIServerConfigurationSection[])[state.uiServerIndex]
- )
- registerWSEventListeners()
- $uiClient.registerWSEventListener(
- 'open',
- () => {
- setToLocalStorage<number>(
- UI_SERVER_CONFIGURATION_INDEX_KEY,
- state.uiServerIndex
- )
- clearToggleButtons()
- refresh()
- $route.name !== ROUTE_NAMES.CHARGING_STATIONS &&
- $router.push({ name: ROUTE_NAMES.CHARGING_STATIONS })
- },
- { once: true }
- )
- $uiClient.registerWSEventListener(
- 'error',
- () => {
- state.uiServerIndex = getFromLocalStorage<number>(
- UI_SERVER_CONFIGURATION_INDEX_KEY,
- 0
- )
- $uiClient.setConfiguration(
- ($configuration.uiServer as UIServerConfigurationSection[])[
- getFromLocalStorage<number>(UI_SERVER_CONFIGURATION_INDEX_KEY, 0)
- ]
- )
- registerWSEventListeners()
- },
- { once: true }
- )
- }
- }
- "
+ @change="handleUIServerChange"
>
<option
v-for="uiServerConfiguration in uiServerConfigurations"
type UUIDv4,
} from 'ui-common'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
import StateButton from '@/components/buttons/StateButton.vue'
import ToggleButton from '@/components/buttons/ToggleButton.vue'
const $configuration = useConfiguration()
const $templates = useTemplates()
const $chargingStations = useChargingStations()
+const $route = useRoute()
+const $router = useRouter()
watch($chargingStations, () => {
state.value.renderChargingStations = randomUUID()
$uiClient.unregisterWSEventListener('close', clearChargingStations)
}
+const handleUIServerChange = (): void => {
+ const currentIndex = getFromLocalStorage<number>(UI_SERVER_CONFIGURATION_INDEX_KEY, 0)
+ if (currentIndex === state.value.uiServerIndex) return
+
+ $uiClient.setConfiguration(
+ ($configuration.value.uiServer as UIServerConfigurationSection[])[state.value.uiServerIndex]
+ )
+ registerWSEventListeners()
+
+ $uiClient.registerWSEventListener(
+ 'open',
+ () => {
+ setToLocalStorage<number>(UI_SERVER_CONFIGURATION_INDEX_KEY, state.value.uiServerIndex)
+ clearToggleButtons()
+ refresh()
+ if ($route.name !== ROUTE_NAMES.CHARGING_STATIONS) {
+ $router.push({ name: ROUTE_NAMES.CHARGING_STATIONS })
+ }
+ },
+ { once: true }
+ )
+
+ $uiClient.registerWSEventListener(
+ 'error',
+ () => {
+ state.value.uiServerIndex = getFromLocalStorage<number>(
+ UI_SERVER_CONFIGURATION_INDEX_KEY,
+ 0
+ )
+ $uiClient.setConfiguration(
+ ($configuration.value.uiServer as UIServerConfigurationSection[])[
+ state.value.uiServerIndex
+ ]
+ )
+ registerWSEventListeners()
+ },
+ { once: true }
+ )
+}
+
let unsubscribeRefresh: (() => void) | undefined
onMounted(() => {
return { ...(actual as Record<string, unknown>), useUIClient: vi.fn() }
})
+vi.mock('vue-router', async importOriginal => {
+ const actual: Record<string, unknown> = await importOriginal()
+ return {
+ ...actual,
+ useRoute: vi.fn().mockReturnValue({ name: 'charging-stations' }),
+ useRouter: vi.fn().mockReturnValue({ push: vi.fn() }),
+ }
+})
+
// ── Configuration fixtures ────────────────────────────────────────────────────
const singleServerConfiguration = {