Merge commit from fork
* Config: require admin password for script plugins * refactor
This commit is contained in:
parent
a193236395
commit
4c907c5afa
17 changed files with 594 additions and 207 deletions
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/evcc-io/evcc/meter"
|
||||
"github.com/evcc-io/evcc/server/db/settings"
|
||||
"github.com/evcc-io/evcc/tariff"
|
||||
"github.com/evcc-io/evcc/util/auth"
|
||||
"github.com/evcc-io/evcc/util/config"
|
||||
"github.com/evcc-io/evcc/util/templates"
|
||||
"github.com/evcc-io/evcc/vehicle"
|
||||
|
|
@ -308,66 +309,72 @@ func newDevice[T any](ctx context.Context, class templates.Class, req configReq,
|
|||
}
|
||||
|
||||
// newDeviceHandler creates a new device by class
|
||||
func newDeviceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
func newDeviceHandler(authObject auth.Auth) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
class, err := templates.ClassString(vars["class"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
class, err := templates.ClassString(vars["class"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := decodeDeviceConfig(r.Body)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !requireCriticalConfigAuth(w, r, authObject, req) {
|
||||
return
|
||||
}
|
||||
|
||||
var conf *config.Config
|
||||
ctx, cancel, done := startDeviceTimeout()
|
||||
|
||||
force := r.URL.Query().Get("force") == "true"
|
||||
|
||||
switch class {
|
||||
case templates.Charger:
|
||||
conf, err = newDevice(ctx, class, req, charger.NewFromConfig, config.Chargers(), force)
|
||||
|
||||
case templates.Meter:
|
||||
conf, err = newDevice(ctx, class, req, meter.NewFromConfig, config.Meters(), force)
|
||||
|
||||
case templates.Vehicle:
|
||||
conf, err = newDevice(ctx, class, req, vehicle.NewFromConfig, config.Vehicles(), force)
|
||||
|
||||
case templates.Circuit:
|
||||
conf, err = newDevice(ctx, class, req, circuit.NewFromConfig, config.Circuits(), force)
|
||||
|
||||
case templates.Tariff:
|
||||
conf, err = newDevice(ctx, class, req, tariff.NewFromConfig, config.Tariffs(), force)
|
||||
|
||||
case templates.Messenger:
|
||||
conf, err = newDevice(ctx, class, req, messenger.NewFromConfig, config.Messengers(), force)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cancel()
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// prevent context from being cancelled
|
||||
close(done)
|
||||
|
||||
setConfigDirty()
|
||||
|
||||
res := struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}{
|
||||
ID: conf.ID,
|
||||
Name: config.NameForID(conf.ID),
|
||||
}
|
||||
|
||||
jsonWrite(w, res)
|
||||
}
|
||||
|
||||
req, err := decodeDeviceConfig(r.Body)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
var conf *config.Config
|
||||
ctx, cancel, done := startDeviceTimeout()
|
||||
|
||||
force := r.URL.Query().Get("force") == "true"
|
||||
|
||||
switch class {
|
||||
case templates.Charger:
|
||||
conf, err = newDevice(ctx, class, req, charger.NewFromConfig, config.Chargers(), force)
|
||||
|
||||
case templates.Meter:
|
||||
conf, err = newDevice(ctx, class, req, meter.NewFromConfig, config.Meters(), force)
|
||||
|
||||
case templates.Vehicle:
|
||||
conf, err = newDevice(ctx, class, req, vehicle.NewFromConfig, config.Vehicles(), force)
|
||||
|
||||
case templates.Circuit:
|
||||
conf, err = newDevice(ctx, class, req, circuit.NewFromConfig, config.Circuits(), force)
|
||||
|
||||
case templates.Tariff:
|
||||
conf, err = newDevice(ctx, class, req, tariff.NewFromConfig, config.Tariffs(), force)
|
||||
|
||||
case templates.Messenger:
|
||||
conf, err = newDevice(ctx, class, req, messenger.NewFromConfig, config.Messengers(), force)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cancel()
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// prevent context from being cancelled
|
||||
close(done)
|
||||
|
||||
setConfigDirty()
|
||||
|
||||
res := struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}{
|
||||
ID: conf.ID,
|
||||
Name: config.NameForID(conf.ID),
|
||||
}
|
||||
|
||||
jsonWrite(w, res)
|
||||
}
|
||||
|
||||
func updateDevice[T any](ctx context.Context, id int, class templates.Class, req configReq, newFromConf newFromConfFunc[T], h config.Handler[T], force bool) error {
|
||||
|
|
@ -388,69 +395,75 @@ func updateDevice[T any](ctx context.Context, id int, class templates.Class, req
|
|||
}
|
||||
|
||||
// updateDeviceHandler updates database device's configuration by class
|
||||
func updateDeviceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
func updateDeviceHandler(authObject auth.Auth) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
class, err := templates.ClassString(vars["class"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
class, err := templates.ClassString(vars["class"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := decodeDeviceConfig(r.Body)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !requireCriticalConfigAuth(w, r, authObject, req) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel, done := startDeviceTimeout()
|
||||
|
||||
force := r.URL.Query().Get("force") == "true"
|
||||
|
||||
switch class {
|
||||
case templates.Charger:
|
||||
err = updateDevice(ctx, id, class, req, charger.NewFromConfig, config.Chargers(), force)
|
||||
|
||||
case templates.Meter:
|
||||
err = updateDevice(ctx, id, class, req, meter.NewFromConfig, config.Meters(), force)
|
||||
|
||||
case templates.Vehicle:
|
||||
err = updateDevice(ctx, id, class, req, vehicle.NewFromConfig, config.Vehicles(), force)
|
||||
|
||||
case templates.Circuit:
|
||||
err = updateDevice(ctx, id, class, req, circuit.NewFromConfig, config.Circuits(), force)
|
||||
|
||||
case templates.Tariff:
|
||||
err = updateDevice(ctx, id, class, req, tariff.NewFromConfig, config.Tariffs(), force)
|
||||
|
||||
case templates.Messenger:
|
||||
err = updateDevice(ctx, id, class, req, messenger.NewFromConfig, config.Messengers(), force)
|
||||
}
|
||||
|
||||
setConfigDirty()
|
||||
|
||||
if err != nil {
|
||||
cancel()
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// prevent context from being cancelled
|
||||
close(done)
|
||||
|
||||
res := struct {
|
||||
ID int `json:"id"`
|
||||
}{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
jsonWrite(w, res)
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := decodeDeviceConfig(r.Body)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel, done := startDeviceTimeout()
|
||||
|
||||
force := r.URL.Query().Get("force") == "true"
|
||||
|
||||
switch class {
|
||||
case templates.Charger:
|
||||
err = updateDevice(ctx, id, class, req, charger.NewFromConfig, config.Chargers(), force)
|
||||
|
||||
case templates.Meter:
|
||||
err = updateDevice(ctx, id, class, req, meter.NewFromConfig, config.Meters(), force)
|
||||
|
||||
case templates.Vehicle:
|
||||
err = updateDevice(ctx, id, class, req, vehicle.NewFromConfig, config.Vehicles(), force)
|
||||
|
||||
case templates.Circuit:
|
||||
err = updateDevice(ctx, id, class, req, circuit.NewFromConfig, config.Circuits(), force)
|
||||
|
||||
case templates.Tariff:
|
||||
err = updateDevice(ctx, id, class, req, tariff.NewFromConfig, config.Tariffs(), force)
|
||||
|
||||
case templates.Messenger:
|
||||
err = updateDevice(ctx, id, class, req, messenger.NewFromConfig, config.Messengers(), force)
|
||||
}
|
||||
|
||||
setConfigDirty()
|
||||
|
||||
if err != nil {
|
||||
cancel()
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// prevent context from being cancelled
|
||||
close(done)
|
||||
|
||||
res := struct {
|
||||
ID int `json:"id"`
|
||||
}{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
jsonWrite(w, res)
|
||||
}
|
||||
|
||||
func configurableDevice[T any](name string, h config.Handler[T]) (config.ConfigurableDevice[T], error) {
|
||||
|
|
@ -644,67 +657,73 @@ func testConfig[T any](ctx context.Context, id int, class templates.Class, req c
|
|||
}
|
||||
|
||||
// testConfigHandler tests a configuration by class
|
||||
func testConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
func testConfigHandler(authObject auth.Auth) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
class, err := templates.ClassString(vars["class"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
var id int
|
||||
if vars["id"] != "" {
|
||||
// test existing device with updated config
|
||||
id, err = strconv.Atoi(vars["id"])
|
||||
class, err := templates.ClassString(vars["class"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
var id int
|
||||
if vars["id"] != "" {
|
||||
// test existing device with updated config
|
||||
id, err = strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
req, err := decodeDeviceConfig(r.Body)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !requireCriticalConfigAuth(w, r, authObject, req) {
|
||||
return
|
||||
}
|
||||
|
||||
var instance any
|
||||
ctx, cancel, done := startDeviceTimeout()
|
||||
|
||||
switch class {
|
||||
case templates.Charger:
|
||||
instance, err = testConfig(ctx, id, class, req, charger.NewFromConfig, config.Chargers())
|
||||
|
||||
case templates.Meter:
|
||||
instance, err = testConfig(ctx, id, class, req, meter.NewFromConfig, config.Meters())
|
||||
|
||||
case templates.Vehicle:
|
||||
instance, err = testConfig(ctx, id, class, req, vehicle.NewFromConfig, config.Vehicles())
|
||||
|
||||
case templates.Circuit:
|
||||
instance, err = testConfig(ctx, id, class, req, circuit.NewFromConfig, config.Circuits())
|
||||
|
||||
case templates.Tariff:
|
||||
instance, err = testConfig(ctx, id, class, req, tariff.NewFromConfig, config.Tariffs())
|
||||
|
||||
case templates.Messenger:
|
||||
instance, err = testConfig(ctx, id, class, req, messenger.NewFromConfig, config.Messengers())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cancel()
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// prevent context from being cancelled during test
|
||||
close(done)
|
||||
defer cancel()
|
||||
|
||||
// bound the value-probe phase so a blocking getter cannot stall the response
|
||||
probeCtx, probeCancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer probeCancel()
|
||||
|
||||
jsonWrite(w, testInstance(probeCtx, instance))
|
||||
}
|
||||
|
||||
req, err := decodeDeviceConfig(r.Body)
|
||||
if err != nil {
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
var instance any
|
||||
ctx, cancel, done := startDeviceTimeout()
|
||||
|
||||
switch class {
|
||||
case templates.Charger:
|
||||
instance, err = testConfig(ctx, id, class, req, charger.NewFromConfig, config.Chargers())
|
||||
|
||||
case templates.Meter:
|
||||
instance, err = testConfig(ctx, id, class, req, meter.NewFromConfig, config.Meters())
|
||||
|
||||
case templates.Vehicle:
|
||||
instance, err = testConfig(ctx, id, class, req, vehicle.NewFromConfig, config.Vehicles())
|
||||
|
||||
case templates.Circuit:
|
||||
instance, err = testConfig(ctx, id, class, req, circuit.NewFromConfig, config.Circuits())
|
||||
|
||||
case templates.Tariff:
|
||||
instance, err = testConfig(ctx, id, class, req, tariff.NewFromConfig, config.Tariffs())
|
||||
|
||||
case templates.Messenger:
|
||||
instance, err = testConfig(ctx, id, class, req, messenger.NewFromConfig, config.Messengers())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cancel()
|
||||
jsonError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// prevent context from being cancelled during test
|
||||
close(done)
|
||||
defer cancel()
|
||||
|
||||
// bound the value-probe phase so a blocking getter cannot stall the response
|
||||
probeCtx, probeCancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer probeCancel()
|
||||
|
||||
jsonWrite(w, testInstance(probeCtx, instance))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue