Start UI early (#23767)
Co-authored-by: Michael Geers <michael@geers.tv>
This commit is contained in:
parent
6a0cdbb724
commit
bb61acc99c
13 changed files with 108 additions and 47 deletions
|
|
@ -108,7 +108,13 @@ function debounedUpdateAuthStatus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update auth status on reconnect or server restart
|
// update auth status on reconnect or server restart
|
||||||
watch(() => store.state.offline, debounedUpdateAuthStatus);
|
watch(
|
||||||
watch(() => store.state.startup, debounedUpdateAuthStatus);
|
() => store.state.offline,
|
||||||
|
(offline) => offline && debounedUpdateAuthStatus()
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => store.state.startupCompleted,
|
||||||
|
(startupCompleted) => startupCompleted && debounedUpdateAuthStatus()
|
||||||
|
);
|
||||||
|
|
||||||
export default auth;
|
export default auth;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div data-testid="offline-indicator" :aria-hidden="!visible">
|
<div data-testid="offline-indicator" :aria-hidden="!visible">
|
||||||
<div v-if="offline" class="modal-backdrop" />
|
<div v-if="offline || starting" class="modal-backdrop" />
|
||||||
<div
|
<div
|
||||||
class="fixed-bottom alert d-flex justify-content-center align-items-center mb-0 rounded-0 p-2"
|
class="fixed-bottom alert d-flex justify-content-center align-items-center mb-0 rounded-0 p-2"
|
||||||
:class="{ visible: visible, 'alert-danger': showError, 'alert-secondary': !showError }"
|
:class="{ visible: visible, 'alert-danger': showError, 'alert-secondary': !showError }"
|
||||||
|
|
@ -23,6 +23,14 @@
|
||||||
<CloudOffline class="m-2" />
|
<CloudOffline class="m-2" />
|
||||||
{{ $t("offline.message") }}
|
{{ $t("offline.message") }}
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="starting" class="d-flex align-items-center">
|
||||||
|
<span
|
||||||
|
class="spinner-border spinner-border-sm m-1 me-2"
|
||||||
|
role="status"
|
||||||
|
aria-hidden="true"
|
||||||
|
></span>
|
||||||
|
{{ $t("offline.starting") }}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="showError"
|
v-else-if="showError"
|
||||||
class="d-flex align-items-center container px-4 justify-content-center"
|
class="d-flex align-items-center container px-4 justify-content-center"
|
||||||
|
|
@ -67,6 +75,7 @@ export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
offline: Boolean,
|
offline: Boolean,
|
||||||
fatal: { type: Array as PropType<FatalError[]>, default: () => [] },
|
fatal: { type: Array as PropType<FatalError[]>, default: () => [] },
|
||||||
|
startupCompleted: Boolean,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return { dismissed: false };
|
return { dismissed: false };
|
||||||
|
|
@ -78,8 +87,17 @@ export default defineComponent({
|
||||||
restarting() {
|
restarting() {
|
||||||
return restart.restarting;
|
return restart.restarting;
|
||||||
},
|
},
|
||||||
|
starting() {
|
||||||
|
return this.startupCompleted === false;
|
||||||
|
},
|
||||||
visible() {
|
visible() {
|
||||||
return this.offline || this.restartNeeded || this.restarting || this.showError;
|
return (
|
||||||
|
this.starting ||
|
||||||
|
this.offline ||
|
||||||
|
this.restartNeeded ||
|
||||||
|
this.restarting ||
|
||||||
|
this.showError
|
||||||
|
);
|
||||||
},
|
},
|
||||||
showError() {
|
showError() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -115,16 +133,19 @@ export default defineComponent({
|
||||||
.alert {
|
.alert {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
|
min-height: 58px;
|
||||||
transition:
|
transition:
|
||||||
transform var(--evcc-transition-fast) ease-in,
|
transform var(--evcc-transition-fast) ease-in,
|
||||||
opacity var(--evcc-transition-fast) ease-in;
|
opacity var(--evcc-transition-fast) ease-in;
|
||||||
min-height: 58px;
|
|
||||||
/* above backdrop, below modal https://getbootstrap.com/docs/5.3/layout/z-index/ */
|
/* above backdrop, below modal https://getbootstrap.com/docs/5.3/layout/z-index/ */
|
||||||
z-index: 1054 !important;
|
z-index: 1054 !important;
|
||||||
}
|
}
|
||||||
.alert.visible {
|
.alert.visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
|
transition:
|
||||||
|
transform var(--evcc-transition-medium) ease-in,
|
||||||
|
opacity var(--evcc-transition-medium) ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fatal-icon {
|
.fatal-icon {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
data-testid="header"
|
data-testid="header"
|
||||||
>
|
>
|
||||||
<h1 class="d-block my-0">
|
<h1 class="d-block my-0">
|
||||||
<span v-if="!isInitialSetup">
|
<span v-if="!setupRequired">
|
||||||
{{ siteTitle || "evcc" }}
|
{{ siteTitle || "evcc" }}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
@ -20,17 +20,25 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HemsWarning :circuits="circuits" />
|
<HemsWarning :circuits="circuits" />
|
||||||
<Energyflow v-if="loadpoints.length > 0" v-bind="energyflow" />
|
<Energyflow v-if="!setupRequired" v-bind="energyflow" />
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column justify-content-between content-area">
|
<div class="d-flex flex-column justify-content-between content-area">
|
||||||
<div
|
<div
|
||||||
v-if="hasFatalError"
|
v-if="hasFatalError"
|
||||||
class="flex-grow-1 align-items-center d-flex justify-content-center"
|
class="flex-grow-1 align-items-center d-flex justify-content-center"
|
||||||
>
|
>
|
||||||
<h1 class="mb-5 text-gray fs-4">{{ $t("startupError.title") }}</h1>
|
<div class="d-flex flex-column align-items-center mb-5 gap-4 mx-4 text-center">
|
||||||
|
<h1 class="text-gray fs-4 my-0">{{ $t("startupError.title") }}</h1>
|
||||||
|
<p v-for="fatalText in fatalTexts" :key="fatalText" class="text-break my-0">
|
||||||
|
{{ fatalText }}
|
||||||
|
</p>
|
||||||
|
<router-link class="btn btn-secondary" to="/config">
|
||||||
|
{{ $t("startupError.editConfiguration") }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="isInitialSetup"
|
v-else-if="setupRequired"
|
||||||
class="flex-grow-1 d-flex align-items-center justify-content-center p-3"
|
class="flex-grow-1 d-flex align-items-center justify-content-center p-3"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
@ -118,6 +126,7 @@ export default defineComponent({
|
||||||
|
|
||||||
notifications: { type: Array as PropType<Notification[]>, default: () => [] },
|
notifications: { type: Array as PropType<Notification[]>, default: () => [] },
|
||||||
offline: Boolean,
|
offline: Boolean,
|
||||||
|
setupRequired: Boolean,
|
||||||
|
|
||||||
// details
|
// details
|
||||||
gridConfigured: Boolean,
|
gridConfigured: Boolean,
|
||||||
|
|
@ -199,9 +208,6 @@ export default defineComponent({
|
||||||
// work in progess
|
// work in progess
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
isInitialSetup() {
|
|
||||||
return this.loadpoints.length === 0;
|
|
||||||
},
|
|
||||||
footer() {
|
footer() {
|
||||||
return {
|
return {
|
||||||
version: {
|
version: {
|
||||||
|
|
@ -226,6 +232,11 @@ export default defineComponent({
|
||||||
hasFatalError() {
|
hasFatalError() {
|
||||||
return this.fatal.length > 0;
|
return this.fatal.length > 0;
|
||||||
},
|
},
|
||||||
|
fatalTexts() {
|
||||||
|
return this.fatal.map(({ error, class: errorClass }) =>
|
||||||
|
errorClass ? `${errorClass}: ${error}` : error
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selectedLoadpointChanged(id: string | undefined) {
|
selectedLoadpointChanged(id: string | undefined) {
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ export interface FatalError {
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
offline: boolean;
|
offline: boolean;
|
||||||
startup?: boolean;
|
setupRequired?: boolean;
|
||||||
|
startupCompleted?: boolean;
|
||||||
loadpoints: Loadpoint[];
|
loadpoints: Loadpoint[];
|
||||||
forecast: Forecast;
|
forecast: Forecast;
|
||||||
currency?: CURRENCY;
|
currency?: CURRENCY;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="app">
|
<div class="app">
|
||||||
<router-view :notifications="notifications" :offline="offline"></router-view>
|
<router-view
|
||||||
|
v-if="showRoutes"
|
||||||
|
:notifications="notifications"
|
||||||
|
:offline="offline"
|
||||||
|
></router-view>
|
||||||
|
|
||||||
<GlobalSettingsModal v-bind="globalSettingsProps" />
|
<GlobalSettingsModal v-bind="globalSettingsProps" />
|
||||||
<BatterySettingsModal v-if="batteryModalAvailabe" v-bind="batterySettingsProps" />
|
<BatterySettingsModal v-if="batteryModalAvailabe" v-bind="batterySettingsProps" />
|
||||||
|
|
@ -67,6 +71,9 @@ export default defineComponent({
|
||||||
batteryModalAvailabe() {
|
batteryModalAvailabe() {
|
||||||
return store.state.battery?.length;
|
return store.state.battery?.length;
|
||||||
},
|
},
|
||||||
|
showRoutes() {
|
||||||
|
return this.state.startupCompleted;
|
||||||
|
},
|
||||||
state() {
|
state() {
|
||||||
const { state, uiLoadpoints } = store;
|
const { state, uiLoadpoints } = store;
|
||||||
return { ...state, uiLoadpoints: uiLoadpoints.value };
|
return { ...state, uiLoadpoints: uiLoadpoints.value };
|
||||||
|
|
@ -182,7 +189,7 @@ export default defineComponent({
|
||||||
this.ws.onmessage = (evt) => {
|
this.ws.onmessage = (evt) => {
|
||||||
try {
|
try {
|
||||||
const msg = JSON.parse(evt.data);
|
const msg = JSON.parse(evt.data);
|
||||||
if (msg.startup) {
|
if (msg.startupCompleted) {
|
||||||
store.reset();
|
store.reset();
|
||||||
}
|
}
|
||||||
store.update(msg);
|
store.update(msg);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<Site
|
<Site
|
||||||
v-if="state.startup"
|
|
||||||
:notifications="notifications"
|
:notifications="notifications"
|
||||||
v-bind="state"
|
v-bind="state"
|
||||||
:selected-loadpoint-index="selectedLoadpointIndex"
|
:selected-loadpoint-index="selectedLoadpointIndex"
|
||||||
|
|
|
||||||
50
cmd/root.go
50
cmd/root.go
|
|
@ -157,8 +157,6 @@ func runRoot(cmd *cobra.Command, args []string) {
|
||||||
err = networkSettings(&conf.Network)
|
err = networkSettings(&conf.Network)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.INFO.Printf("UI listening at :%d", conf.Network.Port)
|
|
||||||
|
|
||||||
// start broadcasting values
|
// start broadcasting values
|
||||||
tee := new(util.Tee)
|
tee := new(util.Tee)
|
||||||
valueChan := make(chan util.Param, 64)
|
valueChan := make(chan util.Param, 64)
|
||||||
|
|
@ -172,6 +170,21 @@ func runRoot(cmd *cobra.Command, args []string) {
|
||||||
socketHub := server.NewSocketHub()
|
socketHub := server.NewSocketHub()
|
||||||
httpd := server.NewHTTPd(fmt.Sprintf(":%d", conf.Network.Port), socketHub, customCssFile)
|
httpd := server.NewHTTPd(fmt.Sprintf(":%d", conf.Network.Port), socketHub, customCssFile)
|
||||||
|
|
||||||
|
// start serving in background, watch for “routine‐only” errors
|
||||||
|
go func() {
|
||||||
|
if err := wrapFatalError(httpd.Server.ListenAndServe()); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.FATAL.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
log.INFO.Printf("UI listening at :%d", conf.Network.Port)
|
||||||
|
|
||||||
|
// publish to UI
|
||||||
|
go socketHub.Run(pipe.NewDropper(ignoreEmpty).Pipe(tee.Attach()), cache)
|
||||||
|
|
||||||
|
// signal ui listening
|
||||||
|
valueChan <- util.Param{Key: keys.StartupCompleted, Val: false}
|
||||||
|
|
||||||
// metrics
|
// metrics
|
||||||
if viper.GetBool("metrics") {
|
if viper.GetBool("metrics") {
|
||||||
httpd.Router().Handle("/metrics", promhttp.Handler())
|
httpd.Router().Handle("/metrics", promhttp.Handler())
|
||||||
|
|
@ -182,9 +195,6 @@ func runRoot(cmd *cobra.Command, args []string) {
|
||||||
httpd.Router().PathPrefix("/debug/").Handler(http.DefaultServeMux)
|
httpd.Router().PathPrefix("/debug/").Handler(http.DefaultServeMux)
|
||||||
}
|
}
|
||||||
|
|
||||||
// publish to UI
|
|
||||||
go socketHub.Run(pipe.NewDropper(ignoreEmpty).Pipe(tee.Attach()), cache)
|
|
||||||
|
|
||||||
// capture log messages for UI
|
// capture log messages for UI
|
||||||
util.CaptureLogs(valueChan)
|
util.CaptureLogs(valueChan)
|
||||||
|
|
||||||
|
|
@ -236,8 +246,10 @@ func runRoot(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// signal restart
|
// signal devices initialized
|
||||||
valueChan <- util.Param{Key: keys.Startup, Val: true}
|
valueChan <- util.Param{Key: keys.StartupCompleted, Val: true}
|
||||||
|
// show onboarding UI
|
||||||
|
valueChan <- util.Param{Key: keys.SetupRequired, Val: site == nil || len(site.Loadpoints()) == 0}
|
||||||
|
|
||||||
// setup mqtt publisher
|
// setup mqtt publisher
|
||||||
if err == nil && conf.Mqtt.Broker != "" && conf.Mqtt.Topic != "" {
|
if err == nil && conf.Mqtt.Broker != "" && conf.Mqtt.Topic != "" {
|
||||||
|
|
@ -310,19 +322,6 @@ func runRoot(cmd *cobra.Command, args []string) {
|
||||||
once.Do(func() { close(stopC) }) // signal loop to end
|
once.Do(func() { close(stopC) }) // signal loop to end
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// wait for shutdown
|
|
||||||
go func() {
|
|
||||||
<-stopC
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-shutdownDoneC(): // wait for shutdown
|
|
||||||
case <-time.After(conf.Interval):
|
|
||||||
}
|
|
||||||
|
|
||||||
// exit code 1 on error
|
|
||||||
os.Exit(cast.ToInt(err != nil))
|
|
||||||
}()
|
|
||||||
|
|
||||||
// allow web access for vehicles
|
// allow web access for vehicles
|
||||||
configureAuth(httpd.Router(), valueChan)
|
configureAuth(httpd.Router(), valueChan)
|
||||||
|
|
||||||
|
|
@ -383,5 +382,14 @@ func runRoot(cmd *cobra.Command, args []string) {
|
||||||
// uds health check listener
|
// uds health check listener
|
||||||
go server.HealthListener(site)
|
go server.HealthListener(site)
|
||||||
|
|
||||||
log.FATAL.Println(wrapFatalError(httpd.ListenAndServe()))
|
// wait for shutdown
|
||||||
|
<-stopC
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-shutdownDoneC(): // wait for shutdown
|
||||||
|
case <-time.After(conf.Interval):
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit code 1 on error
|
||||||
|
os.Exit(cast.ToInt(err != nil))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ const (
|
||||||
Config = "config"
|
Config = "config"
|
||||||
Database = "database"
|
Database = "database"
|
||||||
Fatal = "fatal"
|
Fatal = "fatal"
|
||||||
Startup = "startup"
|
StartupCompleted = "startupCompleted" // false: starting, true: started
|
||||||
|
SetupRequired = "setupRequired" // initial setup is required (lp = 0), fresh installation
|
||||||
Plant = "plant"
|
Plant = "plant"
|
||||||
Telemetry = "telemetry"
|
Telemetry = "telemetry"
|
||||||
DemoMode = "demoMode"
|
DemoMode = "demoMode"
|
||||||
|
|
|
||||||
|
|
@ -1017,7 +1017,8 @@
|
||||||
"message": "Keine Verbindung zum Server.",
|
"message": "Keine Verbindung zum Server.",
|
||||||
"restart": "Neustart",
|
"restart": "Neustart",
|
||||||
"restartNeeded": "Erforderlich, um Änderungen zu übernehmen.",
|
"restartNeeded": "Erforderlich, um Änderungen zu übernehmen.",
|
||||||
"restarting": "Server ist gleich wieder verfügbar."
|
"restarting": "Server ist gleich wieder verfügbar.",
|
||||||
|
"starting": "Server wird gestartet..."
|
||||||
},
|
},
|
||||||
"passwordModal": {
|
"passwordModal": {
|
||||||
"description": "Setze ein Passwort, um die Konfiguration zu schützen. Die Hauptansicht bleibt ohne Login zugänglich.",
|
"description": "Setze ein Passwort, um die Konfiguration zu schützen. Die Hauptansicht bleibt ohne Login zugänglich.",
|
||||||
|
|
@ -1206,6 +1207,7 @@
|
||||||
"configuration": "Konfiguration",
|
"configuration": "Konfiguration",
|
||||||
"description": "Bitte überprüfe deine Konfigurationsdatei. Sollte dir die Fehlermeldung nicht weiterhelfen, suche in unseren {0} nach einer Lösung.",
|
"description": "Bitte überprüfe deine Konfigurationsdatei. Sollte dir die Fehlermeldung nicht weiterhelfen, suche in unseren {0} nach einer Lösung.",
|
||||||
"discussions": "GitHub Diskussionen",
|
"discussions": "GitHub Diskussionen",
|
||||||
|
"editConfiguration": "Konfiguration bearbeiten",
|
||||||
"fixAndRestart": "Behebe das Problem und starte den Server neu.",
|
"fixAndRestart": "Behebe das Problem und starte den Server neu.",
|
||||||
"hint": "Hinweis: Ein weiterer Grund könnte ein fehlerhaftes Gerät (Wechselrichter, Zähler, …) sein. Überprüfe deine Netzwerkverbindungen.",
|
"hint": "Hinweis: Ein weiterer Grund könnte ein fehlerhaftes Gerät (Wechselrichter, Zähler, …) sein. Überprüfe deine Netzwerkverbindungen.",
|
||||||
"lineError": "In {0} wurde ein Fehler gefunden.",
|
"lineError": "In {0} wurde ein Fehler gefunden.",
|
||||||
|
|
|
||||||
|
|
@ -1018,7 +1018,8 @@
|
||||||
"message": "Not connected to a server.",
|
"message": "Not connected to a server.",
|
||||||
"restart": "Restart",
|
"restart": "Restart",
|
||||||
"restartNeeded": "Required to apply changes.",
|
"restartNeeded": "Required to apply changes.",
|
||||||
"restarting": "Server will be back in a moment."
|
"restarting": "Server will be back in a moment.",
|
||||||
|
"starting": "Starting server..."
|
||||||
},
|
},
|
||||||
"passwordModal": {
|
"passwordModal": {
|
||||||
"description": "Set a password to protect the configuration settings. Using the main screen is still possible without login.",
|
"description": "Set a password to protect the configuration settings. Using the main screen is still possible without login.",
|
||||||
|
|
@ -1207,6 +1208,7 @@
|
||||||
"configuration": "Config",
|
"configuration": "Config",
|
||||||
"description": "Please check your configuration file. If the error message does not help, check out the {0}.",
|
"description": "Please check your configuration file. If the error message does not help, check out the {0}.",
|
||||||
"discussions": "GitHub Discussions",
|
"discussions": "GitHub Discussions",
|
||||||
|
"editConfiguration": "Edit configuration",
|
||||||
"fixAndRestart": "Please fix the problem and restart the server.",
|
"fixAndRestart": "Please fix the problem and restart the server.",
|
||||||
"hint": "Note: It could also be you have a faulty device (inverter, meter, …). Check your network connections.",
|
"hint": "Note: It could also be you have a faulty device (inverter, meter, …). Check your network connections.",
|
||||||
"lineError": "Error in {0}.",
|
"lineError": "Error in {0}.",
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ test.describe("modbus fields", async () => {
|
||||||
const modal = await openMeterModal(page, "TCP Test");
|
const modal = await openMeterModal(page, "TCP Test");
|
||||||
await expect(page.getByLabel("Network")).toBeChecked();
|
await expect(page.getByLabel("Network")).toBeChecked();
|
||||||
await expect(page.getByLabel("TCP")).toBeChecked();
|
await expect(page.getByLabel("TCP")).toBeChecked();
|
||||||
await expect(modal.getByLabel("IP address or hostname")).toHaveValue("192.168.1.10");
|
await expect(modal.getByLabel("IP address or hostname")).toHaveValue("192.0.2.1");
|
||||||
await expect(modal.getByLabel("Port", { exact: true })).toHaveValue("5020");
|
await expect(modal.getByLabel("Port", { exact: true })).toHaveValue("5020");
|
||||||
await expect(modal.getByLabel("Modbus ID")).toHaveValue("10");
|
await expect(modal.getByLabel("Modbus ID")).toHaveValue("10");
|
||||||
});
|
});
|
||||||
|
|
@ -41,7 +41,7 @@ test.describe("modbus fields", async () => {
|
||||||
const modal = await openMeterModal(page, "RTU/IP Test");
|
const modal = await openMeterModal(page, "RTU/IP Test");
|
||||||
await expect(page.getByLabel("Network")).toBeChecked();
|
await expect(page.getByLabel("Network")).toBeChecked();
|
||||||
await expect(page.getByLabel("RTU")).toBeChecked();
|
await expect(page.getByLabel("RTU")).toBeChecked();
|
||||||
await expect(modal.getByLabel("IP address or hostname")).toHaveValue("192.168.1.20");
|
await expect(modal.getByLabel("IP address or hostname")).toHaveValue("198.51.100.1");
|
||||||
await expect(modal.getByLabel("Port", { exact: true })).toHaveValue("8899");
|
await expect(modal.getByLabel("Port", { exact: true })).toHaveValue("8899");
|
||||||
await expect(modal.getByLabel("Modbus ID")).toHaveValue("20");
|
await expect(modal.getByLabel("Modbus ID")).toHaveValue("20");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ CREATE TABLE `settings` (
|
||||||
, PRIMARY KEY(`key`)
|
, PRIMARY KEY(`key`)
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(1, 2, 'template', 'TCP Test', '', 'SunSpec Inverter', '{"host":"192.168.1.10","id":10,"modbus":"tcpip","port":5020,"template":"sunspec-inverter","usage":"pv"}');
|
-- using RFC 5737 TEST-NET addresses (192.0.2.0/24, 198.51.100.0/24) that are guaranteed to fail connection attempts
|
||||||
INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(2, 2, 'template', 'RTU/IP Test', '', 'SunSpec Inverter', '{"host":"192.168.1.20","id":20,"modbus":"rs485tcpip","port":8899,"template":"sunspec-inverter","usage":"pv"}');
|
INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(1, 2, 'template', 'TCP Test', '', 'SunSpec Inverter', '{"host":"192.0.2.1","id":10,"modbus":"tcpip","port":5020,"template":"sunspec-inverter","usage":"pv"}');
|
||||||
|
INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(2, 2, 'template', 'RTU/IP Test', '', 'SunSpec Inverter', '{"host":"198.51.100.1","id":20,"modbus":"rs485tcpip","port":8899,"template":"sunspec-inverter","usage":"pv"}');
|
||||||
INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(3, 2, 'template', 'Serial Test', '', 'SunSpec Inverter', '{"baudrate":19200,"comset":"8E1","device":"/dev/ttyUSB5","id":30,"modbus":"rs485serial","template":"sunspec-inverter","usage":"pv"}');
|
INSERT INTO configs(id, class, type, title, icon, product, value) VALUES(3, 2, 'template', 'Serial Test', '', 'SunSpec Inverter', '{"baudrate":19200,"comset":"8E1","device":"/dev/ttyUSB5","id":30,"modbus":"rs485serial","template":"sunspec-inverter","usage":"pv"}');
|
||||||
|
|
||||||
INSERT INTO settings("key", value) VALUES('pvMeters', 'db:1,db:2,db:3');
|
INSERT INTO settings("key", value) VALUES('pvMeters', 'db:1,db:2,db:3');
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import { test, expect } from "@playwright/test";
|
import { test, expect } from "@playwright/test";
|
||||||
import { start, stop, baseUrl } from "./evcc";
|
import { start, stop, baseUrl } from "./evcc";
|
||||||
test.use({ baseURL: baseUrl() });
|
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
test.use({ baseURL: baseUrl() });
|
||||||
|
test.describe.configure({ mode: "parallel" });
|
||||||
|
|
||||||
|
test.beforeEach(async () => {
|
||||||
await start("basics.evcc.yaml");
|
await start("basics.evcc.yaml");
|
||||||
});
|
});
|
||||||
test.afterAll(async () => {
|
test.afterEach(async () => {
|
||||||
await stop();
|
await stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue