diff --git a/api/api.go b/api/api.go index 1f27c44d2..7e5e5e100 100644 --- a/api/api.go +++ b/api/api.go @@ -196,25 +196,10 @@ type WebController interface { WebControl(*mux.Router) } -type Callback struct { - Path string - Handler RedirectHandlerFunc -} - -// RedirectHandlerFunc should return an http.HandlerFunc responding with an http.Redirect(..., redirectURi, ...) -type RedirectHandlerFunc func(redirectURI string) http.HandlerFunc +// ProviderLogin is the ability to provide OAuth authentication through the ui type ProviderLogin interface { - SetBasePath(basePath string) - - // Provides .... - Callback() Callback - SetOAuthCallbackURI(uri string) - - LoggedIn() bool - - LoginPath() string + SetCallbackParams(uri string, authenticated chan<- bool) LoginHandler() http.HandlerFunc - - LogoutPath() string LogoutHandler() http.HandlerFunc + CallbackHandler(baseURI string) http.HandlerFunc } diff --git a/assets/js/authapi.js b/assets/js/baseapi.js similarity index 90% rename from assets/js/authapi.js rename to assets/js/baseapi.js index 8056fab38..db04e6c47 100644 --- a/assets/js/authapi.js +++ b/assets/js/baseapi.js @@ -4,9 +4,6 @@ const { protocol, hostname, port, pathname } = window.location; const baseAPI = axios.create({ baseURL: protocol + "//" + hostname + (port ? ":" + port : "") + pathname + "/", - headers: { - Accept: "application/json", - }, }); // global error handling diff --git a/assets/js/views/App.vue b/assets/js/views/App.vue index 5e6e2b9b7..61c802129 100644 --- a/assets/js/views/App.vue +++ b/assets/js/views/App.vue @@ -104,7 +104,7 @@ import Notifications from "../components/Notifications"; import store from "../store"; -import authAPI from "../authapi"; +import baseAPI from "../baseapi"; export default { name: "App", @@ -155,14 +155,14 @@ export default { return this.providerLogins.filter((login) => !login.loggedIn).length; }, providerLogins() { - return this.store.state.loadpoints - .filter((lp) => lp.vehicleProviderLoginPath && lp.vehicleProviderLogoutPath) - .map((lp) => ({ - title: lp.vehicleTitle, - loggedIn: lp.vehicleProviderLoggedIn, - loginPath: lp.vehicleProviderLoginPath, - logoutPath: lp.vehicleProviderLogoutPath, - })); + return this.store.state.auth + ? Object.entries(this.store.state.auth.vehicles).map(([k, v]) => ({ + title: k, + loggedIn: v.authenticated, + loginPath: v.uri + "/login", + logoutPath: v.uri + "/logout", + })) + : []; }, }, created: function () { @@ -212,11 +212,11 @@ export default { }, handleProviderAuthorization: async function (provider) { if (!provider.loggedIn) { - authAPI.post(provider.loginPath).then(function (response) { + baseAPI.post(provider.loginPath).then(function (response) { window.location.href = response.data.loginUri; }); } else { - authAPI.post(provider.logoutPath); + baseAPI.post(provider.logoutPath); } }, }, diff --git a/cmd/config.go b/cmd/config.go index 9572fac48..12079c5ac 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -14,8 +14,10 @@ import ( "github.com/evcc-io/evcc/provider/mqtt" "github.com/evcc-io/evcc/push" "github.com/evcc-io/evcc/server" + "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/vehicle" "github.com/evcc-io/evcc/vehicle/wrapper" + "github.com/gorilla/handlers" ) type config struct { @@ -79,6 +81,7 @@ type ConfigProvider struct { chargers map[string]api.Charger vehicles map[string]api.Vehicle visited map[string]bool + auth *util.AuthCollection } func (cp *ConfigProvider) TrackVisitors() { @@ -200,25 +203,40 @@ func (cp *ConfigProvider) configureVehicles(conf config) error { return nil } -// webControl handles implemented routes by devices. -// for now only api.ProviderLogin related routes -func (cp *ConfigProvider) webControl(httpd *server.HTTPd) { +func canonicalName(s string) string { + return strings.ToLower(strings.ReplaceAll(s, " ", "_")) +} + +// webControl handles routing for devices. For now only api.ProviderLogin related routes +func (cp *ConfigProvider) webControl(httpd *server.HTTPd, paramC chan<- util.Param) { router := httpd.Router() + + auth := router.PathPrefix("/auth").Subrouter() + auth.Use(handlers.CompressHandler) + auth.Use(handlers.CORS( + handlers.AllowedHeaders([]string{"Content-Type"}), + )) + + // initialize + cp.auth = util.NewAuthCollection(paramC) + for _, v := range cp.vehicles { if provider, ok := v.(api.ProviderLogin); ok { - title := url.QueryEscape(strings.ToLower(strings.ReplaceAll(v.Title(), " ", "_"))) + title := url.QueryEscape(canonicalName(v.Title())) + basePath := fmt.Sprintf("vehicles/%s", title) - basePath := fmt.Sprintf("/auth/vehicles/%s", title) - provider.SetBasePath(basePath) + // TODO make evccURI configurable, add warnings for any network/ localhost + evccURI := fmt.Sprintf("http://%s", httpd.Addr) + baseURI := fmt.Sprintf("%s/auth/%s", evccURI, basePath) - callback := provider.Callback() - callbackURI := fmt.Sprintf("http://%s%s", httpd.Addr, callback.Path) - { - provider.SetOAuthCallbackURI(callbackURI) - log.INFO.Printf("ensure the oauth client redirect/callback is configured for %s: %s", v.Title(), callbackURI) - } + // register vehicle + ap := cp.auth.Register(v.Title(), baseURI) - // TODO: how to handle multiple vehicles of the same type + redirectURI := fmt.Sprintf("%s/callback", baseURI) + provider.SetCallbackParams(redirectURI, ap.Handler()) + log.INFO.Printf("ensure the oauth client redirect/callback is configured for %s: %s", v.Title(), redirectURI) + + // TODO how to handle multiple vehicles of the same type // // problems, thoughts and ideas: // conflicting callbacks! @@ -226,20 +244,20 @@ func (cp *ConfigProvider) webControl(httpd *server.HTTPd) { // - or a general callback handler and the specific vehicle is transported in the state? // - callback handler needs an option to set the token at the right vehicle and use the right code exchange - // TODO: what about https? - router. + auth. Methods(http.MethodGet). - Path(callback.Path). - HandlerFunc(callback.Handler(fmt.Sprintf("http://%s", httpd.Addr))) - - router. + Path(fmt.Sprintf("/%s/callback", basePath)). + HandlerFunc(provider.CallbackHandler(evccURI)) + auth. Methods(http.MethodPost). - Path(provider.LoginPath()). + Path(fmt.Sprintf("/%s/login", basePath)). HandlerFunc(provider.LoginHandler()) - router. + auth. Methods(http.MethodPost). - Path(provider.LogoutPath()). + Path(fmt.Sprintf("/%s/logout", basePath)). HandlerFunc(provider.LogoutHandler()) } } + + cp.auth.Publish() } diff --git a/cmd/root.go b/cmd/root.go index 26f1793fb..e22bae2bb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -188,9 +188,6 @@ func run(cmd *cobra.Command, args []string) { socketHub := server.NewSocketHub() httpd := server.NewHTTPd(uri, site, socketHub, cache) - // allow web access for vehicles - cp.webControl(httpd) - // metrics if viper.GetBool("metrics") { httpd.Router().Handle("/metrics", promhttp.Handler()) @@ -219,6 +216,9 @@ func run(cmd *cobra.Command, args []string) { valueChan <- util.Param{Key: "sponsor", Val: sponsor.Subject} } + // allow web access for vehicles + cp.webControl(httpd, valueChan) + // version check go updater.Run(log, httpd, tee, valueChan) diff --git a/core/loadpoint.go b/core/loadpoint.go index b3ec04302..e5de57f02 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -542,9 +542,6 @@ func (lp *LoadPoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even lp.startVehicleDetection() } - // publish providerLogins - lp.publishProviderLogins() - // read initial charger state to prevent immediately disabling charger if enabled, err := lp.charger.Enabled(); err == nil { if lp.enabled = enabled; enabled { @@ -1404,9 +1401,6 @@ func (lp *LoadPoint) Update(sitePower float64, cheap bool, batteryBuffered bool) // update progress and soc before status is updated lp.publishChargeProgress() - // publish providerLogins - lp.publishProviderLogins() - // read and publish status if err := lp.updateChargerStatus(); err != nil { lp.log.ERROR.Printf("charger: %v", err) @@ -1536,13 +1530,3 @@ func (lp *LoadPoint) Update(sitePower float64, cheap bool, batteryBuffered bool) lp.log.ERROR.Println(err) } } - -func (lp *LoadPoint) publishProviderLogins() { - for _, vehicle := range lp.vehicles { - if provider, ok := vehicle.(api.ProviderLogin); ok { - lp.publish("vehicleProviderLoggedIn", provider.LoggedIn()) - lp.publish("vehicleProviderLoginPath", provider.LoginPath()) - lp.publish("vehicleProviderLogoutPath", provider.LogoutPath()) - } - } -} diff --git a/dist/css/index.55163eb0.css b/dist/css/index.6aeecebf.css similarity index 97% rename from dist/css/index.55163eb0.css rename to dist/css/index.6aeecebf.css index 603c37514..c463dc624 100644 --- a/dist/css/index.55163eb0.css +++ b/dist/css/index.6aeecebf.css @@ -1 +1 @@ -:root{--evcc-green:#66d85a;--evcc-dark-green:#3aba2c;--evcc-yellow:#ffe000;--evcc-grid:var(--bs-gray-dark);--evcc-self:var(--evcc-dark-green);--evcc-export:var(--evcc-yellow);--bs-primary:var(--evcc-dark-green);--bs-primary-rgb:58,186,44}.bg-primary{background-color:var(--evcc-dark-green)!important}a{color:var(--evcc-dark-green)}.progress-bar-animated{animation-direction:reverse}.bg-muted{opacity:.25}.btn-primary,.btn-primary:focus{background-color:var(--bs-gray-dark);border-color:var(--bs-gray-dark)}.btn-primary:active,.btn-primary:hover{background-color:var(--evcc-dark-green);border-color:var(--evcc-dark-green)}.btn-outline-primary{color:var(--bs-gray-dark);border-color:var(--bs-gray-dark)}.btn-outline-primary:hover{background-color:var(--evcc-dark-green);border-color:var(--evcc-dark-green)}.text-evcc{color:var(--evcc-dark-green)}.text-grid{color:var(--evcc-grid)}@media screen and (min-width:400px) and (max-width:574px){.d-xs-none{display:none!important}.d-xs-inline{display:inline!important}}.cursor-pointer{cursor:pointer}.v-popper__inner{margin:0 12px!important}.icon[data-v-7872ae76]{color:var(--evcc-dark-green)}.confetti-button[data-v-6edb9fae]{touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media (max-width:575px){.modal-dialog.modal-dialog-centered[data-v-bd18982c]{align-items:flex-end;margin-bottom:0}.modal.fade .modal-dialog[data-v-bd18982c]{transition:transform .4s ease;transform:translateY(150px)}.modal.show .modal-dialog[data-v-bd18982c]{transform:none}.modal-dialog-scrollable[data-v-bd18982c]{height:calc(100% - .5rem)}.modal-content[data-v-bd18982c]{border-radius:1rem 1rem 0 0}}.chart[data-v-bd18982c]{height:1.6rem}.chart-item--self[data-v-bd18982c]{background-color:var(--evcc-self)}.chart-item--grid[data-v-bd18982c]{background-color:var(--evcc-grid)}.chart-item--no-data[data-v-bd18982c]{background-color:var(--bs-gray)}.chart-item[data-v-bd18982c]{transition-property:width;transition-duration:.5s;transition-timing-function:linear}.logo[data-v-236938bc]{width:85px}.app[data-v-236938bc]{min-height:100vh}.title[data-v-236938bc]{position:relative;top:.1rem}.battery[data-v-2dcca16c]{transform:translateX(.35rem) rotate(-90deg);transition-property:transform;transition-duration:.25s;transition-timing-function:ease}.power--in .battery[data-v-2dcca16c]{transform:translateX(.7rem) rotate(-90deg)}.power--out .battery[data-v-2dcca16c]{transform:translateX(0) rotate(-90deg)}.arrow[data-v-2dcca16c]{margin-left:-.2rem;opacity:0;transform:translateX(-.5rem);transition-property:opacity,transform;transition-duration:.25s;transition-timing-function:ease}.power--in .arrow[data-v-2dcca16c]{opacity:1;transform:translateX(-1rem) scaleX(1)}.power--out .arrow[data-v-2dcca16c]{opacity:1;transform:translateX(0) scaleX(1)}.grid[data-v-c8e84f48]{transform:translateX(.35rem);transition-property:transform;transition-duration:.25s;transition-timing-function:ease}.power-export .grid[data-v-c8e84f48]{transform:translateX(.9rem)}.power-import .grid[data-v-c8e84f48]{transform:translateX(-.1rem)}.arrow[data-v-c8e84f48]{margin-left:-.2rem;opacity:0;transform:translateX(-.5rem);transition-property:opacity,transform;transition-duration:.25s;transition-timing-function:ease}.power-export .arrow[data-v-c8e84f48]{opacity:1;transform:translateX(-.9rem) scaleX(1)}.power-import .arrow[data-v-c8e84f48]{opacity:1;transform:translateX(.1em) scaleX(1)}.label-bar[data-v-17da7e54]{width:0;margin:0;height:1.7rem;padding:.6rem 0;opacity:1;overflow:hidden}.label-bar-scale--hidden[data-v-17da7e54]{opacity:0}.label-bar-scale[data-v-17da7e54]{border:1px solid var(--bs-gray);height:.5rem;background:none;display:flex;justify-content:center;align-items:center;white-space:nowrap;border-radius:0;transition:border-radius .5s linear}.label-bar--top .label-bar-scale[data-v-17da7e54]{border-bottom:none}.label-bar--bottom .label-bar-scale[data-v-17da7e54]{border-top:none}.label-bar--top.label-bar--first .label-bar-scale[data-v-17da7e54]{border-top-left-radius:4px}.label-bar--top.label-bar--last .label-bar-scale[data-v-17da7e54]{border-top-right-radius:4px}.label-bar--bottom.label-bar--first .label-bar-scale[data-v-17da7e54]{border-bottom-left-radius:4px}.label-bar--bottom.label-bar--last .label-bar-scale[data-v-17da7e54]{border-bottom-right-radius:4px}.label-bar:not(.label-bar--first) .label-bar-scale[data-v-17da7e54]{border-left:none}.label-bar-icon[data-v-17da7e54]{background-color:#fff;color:var(--bs-gray);padding:0 .3rem;opacity:1}.label-bar--top .label-bar-icon[data-v-17da7e54]{margin-top:-6px}.label-bar--bottom .label-bar-icon[data-v-17da7e54]{margin-top:6px}.label-bar--hidden[data-v-17da7e54],.label-bar--hide-icon .label-bar-icon[data-v-17da7e54]{opacity:0}.site-progress[data-v-0c035ffc]{--height:38px;height:var(--height);margin:.25rem 0;border-radius:5px;display:flex;overflow:hidden}.site-progress-bar[data-v-0c035ffc]{display:flex;justify-content:center;align-items:center;overflow:hidden;position:relative;width:0}.visualization--ready .site-progress-bar[data-v-0c035ffc]{transition-property:width;transition-duration:.5s;transition-timing-function:linear}.grid-import[data-v-0c035ffc]{background-color:var(--evcc-grid);color:var(--bs-white)}.self-consumption[data-v-0c035ffc]{background-color:var(--evcc-self);color:var(--bs-white)}.pv-export[data-v-0c035ffc]{background-color:var(--evcc-export);color:var(--bs-dark)}.power[data-v-0c035ffc]{display:block;margin:0 .2rem;white-space:nowrap;overflow:hidden}.visualization--ready[data-v-0c035ffc] .label-bar{transition-property:width,opacity;transition-duration:.5s,.25s;transition-timing-function:linear,ease}.visualization--ready[data-v-0c035ffc] .label-bar-icon{transition:opacity .25s ease-in}.energyflow[data-v-c2b4f05e]{cursor:pointer}.color-grid[data-v-c2b4f05e]{color:var(--evcc-grid)}.color-self[data-v-c2b4f05e]{color:var(--evcc-self)}.color-export[data-v-c2b4f05e]{color:var(--evcc-export)}.details-icon[data-v-c2b4f05e]{text-align:center;width:30px;margin-right:.25rem;white-space:nowrap;flex-shrink:0}.btn[data-v-1ff39d22]{flex-basis:0;white-space:nowrap}.vehicle-soc[data-v-66f68a62]{--height:38px;--thumb-overlap:3px;--thumb-width:3px;--thumb-horizontal-padding:15px;--label-height:26px;position:relative;height:var(--height)}.progress[data-v-66f68a62]{height:100%;font-size:1rem}.progress-bar.bg-muted[data-v-66f68a62]{color:var(--white)}.bg-light[data-v-66f68a62]{color:var(--bs-gray-dark)}.target-label[data-v-66f68a62]{width:3em;margin-left:-1.5em;height:var(--label-height);position:absolute;top:calc(var(--label-height)*-1 + var(--thumb-overlap)*-1);text-align:center;color:var(--bs-gray-dark);font-size:1rem;opacity:1;transition:opacity .2s ease 1s}.target-slider[data-v-66f68a62]{-webkit-appearance:none;position:absolute;top:calc(var(--thumb-overlap)*-1);left:calc(var(--thumb-horizontal-padding)*-1);height:calc(100% + var(--thumb-overlap)*2);width:calc(100% + var(--thumb-horizontal-padding)*2);background:transparent}.target-slider[data-v-66f68a62]:focus{outline:none}.target-slider[data-v-66f68a62]::-webkit-slider-runnable-track{position:relative;background:transparent;border:none;height:100%;cursor:pointer}.target-slider[data-v-66f68a62]::-moz-range-track{background:transparent;border:none;height:100%;cursor:pointer}.target-slider[data-v-66f68a62]::-webkit-slider-thumb{-webkit-appearance:none;position:relative;top:calc(var(--label-height)*-1);height:100%;width:var(--thumb-width);padding:var(--label-height) var(--thumb-horizontal-padding) 0;box-sizing:content-box;background-clip:content-box;background-color:var(--bs-gray-dark);cursor:-webkit-grab;cursor:grab;border:none;opacity:1;-webkit-transition:opacity .2s ease 1s;transition:opacity .2s ease 1s;box-shadow:none}.target-slider[data-v-66f68a62]::-moz-range-thumb{position:relative;top:calc(var(--label-height)*-1);height:100%;width:var(--thumb-width);padding:0 var(--thumb-horizontal-padding) 0;box-sizing:content-box;background-clip:content-box;background-color:var(--bs-gray-dark);cursor:grab;border:none;opacity:1;-moz-transition:opacity .5s ease 1s;transition:opacity .5s ease 1s}.target--slider-hidden .target-label[data-v-66f68a62],.target--slider-hidden .target-slider[data-v-66f68a62]::-webkit-slider-thumb{opacity:0}.target--slider-hidden .target-label[data-v-66f68a62],.target--slider-hidden .target-slider[data-v-66f68a62]::-moz-range-thumb{opacity:0}.target:hover .target-label[data-v-66f68a62],.target:hover .target-slider[data-v-66f68a62]::-webkit-slider-thumb{opacity:1;transition-delay:0s}.target:hover .target-label[data-v-66f68a62],.target:hover .target-slider[data-v-66f68a62]::-moz-range-thumb{opacity:1;transition-delay:0s}.dots[data-v-60c12baf]{height:1em;width:1em}.dots-back[data-v-60c12baf],.dots-front[data-v-60c12baf]{display:block}.dots-back[data-v-60c12baf]{opacity:.25}.dots-front[data-v-60c12baf]{opacity:1}.direction-up .dots-front[data-v-60c12baf]{-webkit-animation:dots-vertical-data-v-60c12baf 2s steps(4,jump-none) infinite normal;animation:dots-vertical-data-v-60c12baf 2s steps(4,jump-none) infinite normal}.direction-down .dots-front[data-v-60c12baf]{animation:dots-vertical-data-v-60c12baf 2s steps(4,jump-none) infinite reverse}@-webkit-keyframes dots-vertical-data-v-60c12baf{25%{-webkit-clip-path:inset(100% 0 0 0);clip-path:inset(100% 0 0 0)}75%{-webkit-clip-path:inset(0 0 0 0);clip-path:inset(0 0 0 0)}}@keyframes dots-vertical-data-v-60c12baf{25%{-webkit-clip-path:inset(100% 0 0 0);clip-path:inset(100% 0 0 0)}75%{-webkit-clip-path:inset(0 0 0 0);clip-path:inset(0 0 0 0)}}.root[data-v-60c12baf] svg{display:block} \ No newline at end of file +:root{--evcc-green:#66d85a;--evcc-dark-green:#3aba2c;--evcc-yellow:#ffe000;--evcc-grid:var(--bs-gray-dark);--evcc-self:var(--evcc-dark-green);--evcc-export:var(--evcc-yellow);--bs-primary:var(--evcc-dark-green);--bs-primary-rgb:58,186,44}.bg-primary{background-color:var(--evcc-dark-green)!important}a{color:var(--evcc-dark-green)}.progress-bar-animated{animation-direction:reverse}.bg-muted{opacity:.25}.btn-primary,.btn-primary:focus{background-color:var(--bs-gray-dark);border-color:var(--bs-gray-dark)}.btn-primary:active,.btn-primary:hover{background-color:var(--evcc-dark-green);border-color:var(--evcc-dark-green)}.btn-outline-primary{color:var(--bs-gray-dark);border-color:var(--bs-gray-dark)}.btn-outline-primary:hover{background-color:var(--evcc-dark-green);border-color:var(--evcc-dark-green)}.text-evcc{color:var(--evcc-dark-green)}.text-grid{color:var(--evcc-grid)}@media screen and (min-width:400px) and (max-width:574px){.d-xs-none{display:none!important}.d-xs-inline{display:inline!important}}.cursor-pointer{cursor:pointer}.v-popper__inner{margin:0 12px!important}.icon[data-v-7872ae76]{color:var(--evcc-dark-green)}.confetti-button[data-v-6edb9fae]{touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@media (max-width:575px){.modal-dialog.modal-dialog-centered[data-v-bd18982c]{align-items:flex-end;margin-bottom:0}.modal.fade .modal-dialog[data-v-bd18982c]{transition:transform .4s ease;transform:translateY(150px)}.modal.show .modal-dialog[data-v-bd18982c]{transform:none}.modal-dialog-scrollable[data-v-bd18982c]{height:calc(100% - .5rem)}.modal-content[data-v-bd18982c]{border-radius:1rem 1rem 0 0}}.chart[data-v-bd18982c]{height:1.6rem}.chart-item--self[data-v-bd18982c]{background-color:var(--evcc-self)}.chart-item--grid[data-v-bd18982c]{background-color:var(--evcc-grid)}.chart-item--no-data[data-v-bd18982c]{background-color:var(--bs-gray)}.chart-item[data-v-bd18982c]{transition-property:width;transition-duration:.5s;transition-timing-function:linear}.logo[data-v-63bd1ae6]{width:85px}.app[data-v-63bd1ae6]{min-height:100vh}.title[data-v-63bd1ae6]{position:relative;top:.1rem}.battery[data-v-2dcca16c]{transform:translateX(.35rem) rotate(-90deg);transition-property:transform;transition-duration:.25s;transition-timing-function:ease}.power--in .battery[data-v-2dcca16c]{transform:translateX(.7rem) rotate(-90deg)}.power--out .battery[data-v-2dcca16c]{transform:translateX(0) rotate(-90deg)}.arrow[data-v-2dcca16c]{margin-left:-.2rem;opacity:0;transform:translateX(-.5rem);transition-property:opacity,transform;transition-duration:.25s;transition-timing-function:ease}.power--in .arrow[data-v-2dcca16c]{opacity:1;transform:translateX(-1rem) scaleX(1)}.power--out .arrow[data-v-2dcca16c]{opacity:1;transform:translateX(0) scaleX(1)}.grid[data-v-c8e84f48]{transform:translateX(.35rem);transition-property:transform;transition-duration:.25s;transition-timing-function:ease}.power-export .grid[data-v-c8e84f48]{transform:translateX(.9rem)}.power-import .grid[data-v-c8e84f48]{transform:translateX(-.1rem)}.arrow[data-v-c8e84f48]{margin-left:-.2rem;opacity:0;transform:translateX(-.5rem);transition-property:opacity,transform;transition-duration:.25s;transition-timing-function:ease}.power-export .arrow[data-v-c8e84f48]{opacity:1;transform:translateX(-.9rem) scaleX(1)}.power-import .arrow[data-v-c8e84f48]{opacity:1;transform:translateX(.1em) scaleX(1)}.label-bar[data-v-17da7e54]{width:0;margin:0;height:1.7rem;padding:.6rem 0;opacity:1;overflow:hidden}.label-bar-scale--hidden[data-v-17da7e54]{opacity:0}.label-bar-scale[data-v-17da7e54]{border:1px solid var(--bs-gray);height:.5rem;background:none;display:flex;justify-content:center;align-items:center;white-space:nowrap;border-radius:0;transition:border-radius .5s linear}.label-bar--top .label-bar-scale[data-v-17da7e54]{border-bottom:none}.label-bar--bottom .label-bar-scale[data-v-17da7e54]{border-top:none}.label-bar--top.label-bar--first .label-bar-scale[data-v-17da7e54]{border-top-left-radius:4px}.label-bar--top.label-bar--last .label-bar-scale[data-v-17da7e54]{border-top-right-radius:4px}.label-bar--bottom.label-bar--first .label-bar-scale[data-v-17da7e54]{border-bottom-left-radius:4px}.label-bar--bottom.label-bar--last .label-bar-scale[data-v-17da7e54]{border-bottom-right-radius:4px}.label-bar:not(.label-bar--first) .label-bar-scale[data-v-17da7e54]{border-left:none}.label-bar-icon[data-v-17da7e54]{background-color:#fff;color:var(--bs-gray);padding:0 .3rem;opacity:1}.label-bar--top .label-bar-icon[data-v-17da7e54]{margin-top:-6px}.label-bar--bottom .label-bar-icon[data-v-17da7e54]{margin-top:6px}.label-bar--hidden[data-v-17da7e54],.label-bar--hide-icon .label-bar-icon[data-v-17da7e54]{opacity:0}.site-progress[data-v-0c035ffc]{--height:38px;height:var(--height);margin:.25rem 0;border-radius:5px;display:flex;overflow:hidden}.site-progress-bar[data-v-0c035ffc]{display:flex;justify-content:center;align-items:center;overflow:hidden;position:relative;width:0}.visualization--ready .site-progress-bar[data-v-0c035ffc]{transition-property:width;transition-duration:.5s;transition-timing-function:linear}.grid-import[data-v-0c035ffc]{background-color:var(--evcc-grid);color:var(--bs-white)}.self-consumption[data-v-0c035ffc]{background-color:var(--evcc-self);color:var(--bs-white)}.pv-export[data-v-0c035ffc]{background-color:var(--evcc-export);color:var(--bs-dark)}.power[data-v-0c035ffc]{display:block;margin:0 .2rem;white-space:nowrap;overflow:hidden}.visualization--ready[data-v-0c035ffc] .label-bar{transition-property:width,opacity;transition-duration:.5s,.25s;transition-timing-function:linear,ease}.visualization--ready[data-v-0c035ffc] .label-bar-icon{transition:opacity .25s ease-in}.energyflow[data-v-c2b4f05e]{cursor:pointer}.color-grid[data-v-c2b4f05e]{color:var(--evcc-grid)}.color-self[data-v-c2b4f05e]{color:var(--evcc-self)}.color-export[data-v-c2b4f05e]{color:var(--evcc-export)}.details-icon[data-v-c2b4f05e]{text-align:center;width:30px;margin-right:.25rem;white-space:nowrap;flex-shrink:0}.btn[data-v-1ff39d22]{flex-basis:0;white-space:nowrap}.vehicle-soc[data-v-66f68a62]{--height:38px;--thumb-overlap:3px;--thumb-width:3px;--thumb-horizontal-padding:15px;--label-height:26px;position:relative;height:var(--height)}.progress[data-v-66f68a62]{height:100%;font-size:1rem}.progress-bar.bg-muted[data-v-66f68a62]{color:var(--white)}.bg-light[data-v-66f68a62]{color:var(--bs-gray-dark)}.target-label[data-v-66f68a62]{width:3em;margin-left:-1.5em;height:var(--label-height);position:absolute;top:calc(var(--label-height)*-1 + var(--thumb-overlap)*-1);text-align:center;color:var(--bs-gray-dark);font-size:1rem;opacity:1;transition:opacity .2s ease 1s}.target-slider[data-v-66f68a62]{-webkit-appearance:none;position:absolute;top:calc(var(--thumb-overlap)*-1);left:calc(var(--thumb-horizontal-padding)*-1);height:calc(100% + var(--thumb-overlap)*2);width:calc(100% + var(--thumb-horizontal-padding)*2);background:transparent}.target-slider[data-v-66f68a62]:focus{outline:none}.target-slider[data-v-66f68a62]::-webkit-slider-runnable-track{position:relative;background:transparent;border:none;height:100%;cursor:pointer}.target-slider[data-v-66f68a62]::-moz-range-track{background:transparent;border:none;height:100%;cursor:pointer}.target-slider[data-v-66f68a62]::-webkit-slider-thumb{-webkit-appearance:none;position:relative;top:calc(var(--label-height)*-1);height:100%;width:var(--thumb-width);padding:var(--label-height) var(--thumb-horizontal-padding) 0;box-sizing:content-box;background-clip:content-box;background-color:var(--bs-gray-dark);cursor:-webkit-grab;cursor:grab;border:none;opacity:1;-webkit-transition:opacity .2s ease 1s;transition:opacity .2s ease 1s;box-shadow:none}.target-slider[data-v-66f68a62]::-moz-range-thumb{position:relative;top:calc(var(--label-height)*-1);height:100%;width:var(--thumb-width);padding:0 var(--thumb-horizontal-padding) 0;box-sizing:content-box;background-clip:content-box;background-color:var(--bs-gray-dark);cursor:grab;border:none;opacity:1;-moz-transition:opacity .5s ease 1s;transition:opacity .5s ease 1s}.target--slider-hidden .target-label[data-v-66f68a62],.target--slider-hidden .target-slider[data-v-66f68a62]::-webkit-slider-thumb{opacity:0}.target--slider-hidden .target-label[data-v-66f68a62],.target--slider-hidden .target-slider[data-v-66f68a62]::-moz-range-thumb{opacity:0}.target:hover .target-label[data-v-66f68a62],.target:hover .target-slider[data-v-66f68a62]::-webkit-slider-thumb{opacity:1;transition-delay:0s}.target:hover .target-label[data-v-66f68a62],.target:hover .target-slider[data-v-66f68a62]::-moz-range-thumb{opacity:1;transition-delay:0s}.dots[data-v-60c12baf]{height:1em;width:1em}.dots-back[data-v-60c12baf],.dots-front[data-v-60c12baf]{display:block}.dots-back[data-v-60c12baf]{opacity:.25}.dots-front[data-v-60c12baf]{opacity:1}.direction-up .dots-front[data-v-60c12baf]{-webkit-animation:dots-vertical-data-v-60c12baf 2s steps(4,jump-none) infinite normal;animation:dots-vertical-data-v-60c12baf 2s steps(4,jump-none) infinite normal}.direction-down .dots-front[data-v-60c12baf]{animation:dots-vertical-data-v-60c12baf 2s steps(4,jump-none) infinite reverse}@-webkit-keyframes dots-vertical-data-v-60c12baf{25%{-webkit-clip-path:inset(100% 0 0 0);clip-path:inset(100% 0 0 0)}75%{-webkit-clip-path:inset(0 0 0 0);clip-path:inset(0 0 0 0)}}@keyframes dots-vertical-data-v-60c12baf{25%{-webkit-clip-path:inset(100% 0 0 0);clip-path:inset(100% 0 0 0)}75%{-webkit-clip-path:inset(0 0 0 0);clip-path:inset(0 0 0 0)}}.root[data-v-60c12baf] svg{display:block} \ No newline at end of file diff --git a/dist/index.html b/dist/index.html index 336d5f710..1789618ac 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1,5 +1,5 @@ -