diff --git a/.gitignore b/.gitignore index 81e4c508cf..7bd2fa6b84 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __debug_bin* !templates/**/*.yaml !templates/**/*-schema.json !util/templates/**/*.yaml +!assets/js/**/*.yaml !package*.json !evcc.dist.yaml !tests/**/*.evcc.yaml diff --git a/api/globalconfig/types.go b/api/globalconfig/types.go new file mode 100644 index 0000000000..a69765674e --- /dev/null +++ b/api/globalconfig/types.go @@ -0,0 +1,142 @@ +package globalconfig + +import ( + "fmt" + "net" + "strconv" + "time" + + "github.com/evcc-io/evcc/charger/eebus" + "github.com/evcc-io/evcc/provider/mqtt" + "github.com/evcc-io/evcc/push" + "github.com/evcc-io/evcc/util/config" + "github.com/evcc-io/evcc/util/modbus" +) + +type All struct { + Network Network + Log string + SponsorToken string + Plant string // telemetry plant id + Telemetry bool + Metrics bool + Profile bool + Levels map[string]string + Interval time.Duration + Database DB + Mqtt Mqtt + ModbusProxy []ModbusProxy + Javascript []Javascript + Go []Go + Influx Influx + EEBus eebus.Config + HEMS config.Typed + Messaging Messaging + Meters []config.Named + Chargers []config.Named + Vehicles []config.Named + Tariffs Tariffs + Site map[string]interface{} + Loadpoints []map[string]interface{} + Circuits []config.Named +} + +type Javascript struct { + VM string + Script string +} + +type Go struct { + VM string + Script string +} + +type ModbusProxy struct { + Port int + ReadOnly string + modbus.Settings `mapstructure:",squash"` +} + +type Mqtt struct { + mqtt.Config `mapstructure:",squash"` + Topic string `json:"topic"` +} + +// Redacted implements the redactor interface used by the tee publisher +func (m Mqtt) Redacted() any { + // TODO add masked password + return struct { + Broker string `json:"broker"` + Topic string `json:"topic"` + User string `json:"user"` + ClientID string `json:"clientID"` + Insecure bool `json:"insecure"` + }{ + Broker: m.Broker, + Topic: m.Topic, + User: m.User, + ClientID: m.ClientID, + Insecure: m.Insecure, + } +} + +// Influx is the influx db configuration +type Influx struct { + URL string `json:"url"` + Database string `json:"database"` + Token string `json:"token"` + Org string `json:"org"` + User string `json:"user"` + Password string `json:"password"` +} + +// Redacted implements the redactor interface used by the tee publisher +func (c Influx) Redacted() any { + // TODO add masked password + return struct { + URL string `json:"url"` + Database string `json:"database"` + Org string `json:"org"` + User string `json:"user"` + }{ + URL: c.URL, + Database: c.Database, + Org: c.Org, + User: c.User, + } +} + +type DB struct { + Type string + Dsn string +} + +type Messaging struct { + Events map[string]push.EventTemplateConfig + Services []config.Typed +} + +type Tariffs struct { + Currency string + Grid config.Typed + FeedIn config.Typed + Co2 config.Typed + Planner config.Typed +} + +type Network struct { + Schema string `json:"schema"` + Host string `json:"host"` + Port int `json:"port"` +} + +func (c Network) HostPort() string { + if c.Schema == "http" && c.Port == 80 || c.Schema == "https" && c.Port == 443 { + return c.Host + } + return net.JoinHostPort(c.Host, strconv.Itoa(c.Port)) +} + +func (c Network) URI() string { + return fmt.Sprintf("%s://%s", c.Schema, c.HostPort()) +} diff --git a/assets/css/app.css b/assets/css/app.css index eae80f4f60..f3ae7cee3d 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -35,9 +35,11 @@ --evcc-red-rgb: 252, 68, 15; --bs-gray-deep: #010322; --bs-gray-dark: #28293e; + --bs-gray-darker: #151630; --bs-gray-medium: #93949e; --bs-gray-light: #b5b6c3; --bs-gray-bright: #f3f3f7; + --bs-gray-brighter: #f9f9fb; --evcc-grid: var(--bs-gray-dark); --evcc-self: var(--evcc-dark-green); @@ -46,6 +48,7 @@ --evcc-export: var(--evcc-yellow); --evcc-background: var(--bs-gray-bright); --evcc-box: var(--bs-white); + --evcc-box-border: var(--bs-gray-brighter); --evcc-default-text: var(--bs-gray-dark); --evcc-gray: var(--bs-gray-medium); --evcc-gray-50: #93949e80; @@ -65,6 +68,10 @@ --bs-warning: var(--evcc-orange); --bs-warning-rgb: var(--evcc-orange-rgb); + --bs-danger-50: #dc354580; + + --bs-danger: var(--evcc-red); + --bs-danger-rgb: var(--evcc-red-rgb); --bs-danger: var(--evcc-red); --bs-danger-rgb: var(--evcc-red-rgb); @@ -76,12 +83,14 @@ --bs-success: var(--evcc-primary); --bs-success-rgb: var(--bs-primary-rgb); --bs-code-color: var(--evcc-darkest-green); + --evcc-backdrop: #93949eaa; /* --bs-gray-medium + transparent */ } :root.dark { --evcc-grid: var(--bs-gray-medium); --evcc-background: var(--bs-gray-deep); --evcc-box: var(--bs-gray-dark); + --evcc-box-border: var(--vs-gray-darker); --evcc-default-text: var(--bs-white); --evcc-gray: var(--bs-gray-light); --evcc-accent1: var(--evcc-yellow); @@ -90,15 +99,10 @@ --bs-primary: var(--evcc-dark-green); --bs-border-color-translucent: rgba(255, 255, 255, 0.175); --bs-code-color: var(--evcc-dark-green); + --evcc-backdrop: #000000aa; /* black + transparent */ color-scheme: dark; } -html { - /* prevents content jump when scrollbar is activate */ - width: 100vw; - overflow-x: hidden; -} - html.no-transitions * { transition: none !important; } @@ -322,7 +326,19 @@ a:hover { } .modal-backdrop.show { - opacity: 0.8; + opacity: 1; +} + +.modal-backdrop { + --bs-backdrop-bg: var(--evcc-backdrop); + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); +} + +.modal-backdrop.fade { + transition-property: opacity, backdrop-filter, background-color; + transition-duration: var(--evcc-transition-fast); + transition-timing-function: linear; } .modal-header { @@ -342,6 +358,13 @@ a:hover { color: var(--evcc-default-text); } +.modal-content h6 { + margin-top: 2rem; + margin-bottom: 1.5rem; + font-size: 1rem; + font-weight: bold; +} + /* breakpoint sm */ @media (min-width: 576px) { .modal-content { @@ -362,6 +385,10 @@ a:hover { margin: 0; } +html:has(.modal.show) { + overflow-y: hidden; +} + .cursor-pointer { cursor: pointer; } @@ -514,6 +541,9 @@ input[type="time"]::-webkit-calendar-picker-indicator { .w-sm-50 { width: 50% !important; } + .w-sm-25 { + width: 25% !important; + } } html.app .hide-in-app { @@ -553,13 +583,20 @@ html.app .modal-dialog { .round-box { border-radius: 1rem; - box-shadow: 0 0 0 0 var(--evcc-gray-50); color: var(--evcc-default-text); background: var(--evcc-box); padding: 1rem; - border: 1px solid var(--evcc-gray-50); - transition: box-shadow var(--evcc-transition-fast) linear; } -.round-box:hover { - border-color: var(--evcc-gray); + +.round-box--error { + box-shadow: 0 0 0.5rem var(--bs-danger); + border: 1px solid var(--bs-danger); +} + +.round-box--error .tags * { + color: var(--bs-danger-text-emphasis) !important; +} + +.alert-danger code { + color: var(--evcc-darkest-green); } diff --git a/assets/js/components/ChargingSessionModal.vue b/assets/js/components/ChargingSessionModal.vue index d5f8c2d466..a712cb765f 100644 --- a/assets/js/components/ChargingSessionModal.vue +++ b/assets/js/components/ChargingSessionModal.vue @@ -37,8 +37,9 @@ @@ -202,6 +203,12 @@ export default { solarEnergy: function () { return this.chargedEnergy * (this.session.solarPercentage / 100); }, + vehicleOptions: function () { + return this.vehicles.map((v) => ({ + name: v.title, + title: v.title, + })); + }, }, methods: { openSessionDetailsModal() { @@ -217,15 +224,11 @@ export default { formatKm: function (value) { return `${this.fmtNumber(distanceValue(value), 0)} ${distanceUnit()}`; }, - async changeVehicle(name) { - await this.updateSession({ - vehicle: this.vehicles.find((v) => v.name === name)?.title, - }); + async changeVehicle(title) { + await this.updateSession({ vehicle: title }); }, async removeVehicle() { - await this.updateSession({ - vehicle: null, - }); + await this.updateSession({ vehicle: null }); }, async updateSession(data) { try { diff --git a/assets/js/components/Config/CircuitsModal.vue b/assets/js/components/Config/CircuitsModal.vue new file mode 100644 index 0000000000..d11227866d --- /dev/null +++ b/assets/js/components/Config/CircuitsModal.vue @@ -0,0 +1,26 @@ + + + diff --git a/assets/js/components/Config/ControlModal.vue b/assets/js/components/Config/ControlModal.vue new file mode 100644 index 0000000000..bd563e9678 --- /dev/null +++ b/assets/js/components/Config/ControlModal.vue @@ -0,0 +1,194 @@ + + + + diff --git a/assets/js/components/Config/DeviceCard.vue b/assets/js/components/Config/DeviceCard.vue index 57c7049b6e..20b68e5b93 100644 --- a/assets/js/components/Config/DeviceCard.vue +++ b/assets/js/components/Config/DeviceCard.vue @@ -1,21 +1,12 @@ - + diff --git a/assets/js/components/Config/EebusModal.vue b/assets/js/components/Config/EebusModal.vue new file mode 100644 index 0000000000..fb242bf248 --- /dev/null +++ b/assets/js/components/Config/EebusModal.vue @@ -0,0 +1,25 @@ + + + diff --git a/assets/js/components/Config/FormRow.vue b/assets/js/components/Config/FormRow.vue index ed0d22e64d..119d194eda 100644 --- a/assets/js/components/Config/FormRow.vue +++ b/assets/js/components/Config/FormRow.vue @@ -12,13 +12,19 @@
{{ $t("config.form.example") }}: {{ example }}
-
+
+ + + {{ $t("config.general.docsLink") }} + +
diff --git a/assets/js/components/Config/GeneralConfig.vue b/assets/js/components/Config/GeneralConfig.vue index 6b90345de7..0f9e9366d3 100644 --- a/assets/js/components/Config/GeneralConfig.vue +++ b/assets/js/components/Config/GeneralConfig.vue @@ -1,93 +1,111 @@ diff --git a/assets/js/components/Config/InfluxModal.vue b/assets/js/components/Config/InfluxModal.vue new file mode 100644 index 0000000000..914ecddc95 --- /dev/null +++ b/assets/js/components/Config/InfluxModal.vue @@ -0,0 +1,122 @@ + + + diff --git a/assets/js/components/Config/JsonModal.vue b/assets/js/components/Config/JsonModal.vue new file mode 100644 index 0000000000..ec8001ca3f --- /dev/null +++ b/assets/js/components/Config/JsonModal.vue @@ -0,0 +1,174 @@ + + + + diff --git a/assets/js/components/Config/MessagingModal.vue b/assets/js/components/Config/MessagingModal.vue new file mode 100644 index 0000000000..32747b4c3e --- /dev/null +++ b/assets/js/components/Config/MessagingModal.vue @@ -0,0 +1,25 @@ + + + diff --git a/assets/js/components/Config/ModbusProxyModal.vue b/assets/js/components/Config/ModbusProxyModal.vue new file mode 100644 index 0000000000..1bf1de41fe --- /dev/null +++ b/assets/js/components/Config/ModbusProxyModal.vue @@ -0,0 +1,26 @@ + + + diff --git a/assets/js/components/Config/MqttModal.vue b/assets/js/components/Config/MqttModal.vue new file mode 100644 index 0000000000..ee22621aae --- /dev/null +++ b/assets/js/components/Config/MqttModal.vue @@ -0,0 +1,79 @@ + + + diff --git a/assets/js/components/Config/NetworkModal.vue b/assets/js/components/Config/NetworkModal.vue new file mode 100644 index 0000000000..400bffc9f6 --- /dev/null +++ b/assets/js/components/Config/NetworkModal.vue @@ -0,0 +1,75 @@ + + + diff --git a/assets/js/components/Config/SponsorModal.vue b/assets/js/components/Config/SponsorModal.vue new file mode 100644 index 0000000000..e8a3f4456b --- /dev/null +++ b/assets/js/components/Config/SponsorModal.vue @@ -0,0 +1,96 @@ +