diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 000000000..145a9383e
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,13 @@
+module.exports = {
+ env: {
+ browser: true,
+ node: true,
+ es6: true,
+ },
+ extends: [
+ "eslint:recommended",
+ "plugin:vue/essential",
+ "plugin:prettier/recommended",
+ "prettier",
+ ],
+};
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 000000000..5f9a0815f
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ printWidth: 100,
+};
diff --git a/Makefile b/Makefile
index 6a24dd112..bad095b5d 100644
--- a/Makefile
+++ b/Makefile
@@ -21,6 +21,7 @@ install:
lint:
golangci-lint run
+ npm run lint
test:
@echo "Running testsuite"
diff --git a/assets/css/app.css b/assets/css/app.css
index d121d6367..6fdde5d8e 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -1,26 +1,29 @@
/* reverse loading animation */
.progress-bar-animated {
- animation-direction: reverse;
+ animation-direction: reverse;
}
.btn.caption {
- opacity: 1;
+ opacity: 1;
}
.btn.first {
- border-top-left-radius: .2rem;
- border-bottom-left-radius: .2rem;
+ border-top-left-radius: 0.2rem;
+ border-bottom-left-radius: 0.2rem;
}
code {
- font-size: 87.5%;
- color: #e83e8c;
- word-wrap: break-word;
+ font-size: 87.5%;
+ color: #e83e8c;
+ word-wrap: break-word;
}
-.value, input[type=radio], label.btn {
- white-space: nowrap !important;
+.value,
+input[type="radio"],
+label.btn {
+ white-space: nowrap !important;
}
-.text-muted a, .text-muted a:hover {
- color: #6c757d!important;
- text-decoration: none;
+.text-muted a,
+.text-muted a:hover {
+ color: #6c757d !important;
+ text-decoration: none;
}
.bg-muted {
- opacity: 0.25;
-}
\ No newline at end of file
+ opacity: 0.25;
+}
diff --git a/assets/ico/favicon.svg b/assets/ico/favicon.svg
index 04a56b43e..73b7efaa7 100644
--- a/assets/ico/favicon.svg
+++ b/assets/ico/favicon.svg
@@ -1,58 +1,27 @@
-
+
\ No newline at end of file
diff --git a/assets/ico/safari-pinned-tab.svg b/assets/ico/safari-pinned-tab.svg
index 977605fa5..4d91df165 100644
--- a/assets/ico/safari-pinned-tab.svg
+++ b/assets/ico/safari-pinned-tab.svg
@@ -1,15 +1,12 @@
-
-
\ No newline at end of file
diff --git a/assets/ico/site.webmanifest b/assets/ico/site.webmanifest
index 1f1869a28..c9a28f674 100644
--- a/assets/ico/site.webmanifest
+++ b/assets/ico/site.webmanifest
@@ -1,8 +1,8 @@
{
"start_url": "../#",
- "name": "EV Charge Controller",
- "short_name": "EV Charge Controller",
- "theme_color": "#ffffff",
- "background_color": "#ffffff",
- "display": "standalone"
+ "name": "EV Charge Controller",
+ "short_name": "EV Charge Controller",
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
}
diff --git a/assets/index.html b/assets/index.html
index ba0983482..43da3a3db 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -1,507 +1,55 @@
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
- evcc
+ evcc
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/assets/js/app.js b/assets/js/app.js
index 594782ef3..b0ed5225b 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -1,227 +1,54 @@
-import $ from "jquery";
-import "popper.js"
-import "bootstrap"
-import Vue from "vue"
-import VueRouter from "vue-router"
-import axios from "axios"
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { faSun, faArrowUp, faArrowDown, faTemperatureLow, faTemperatureHigh, faThermometerHalf, faLeaf, faChevronUp, faChevronDown, faExclamationTriangle }from '@fortawesome/free-solid-svg-icons'
-import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
+import "popper.js";
+import "bootstrap";
+import Vue from "vue";
+import axios from "axios";
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faSun } from "@fortawesome/free-solid-svg-icons/faSun";
+import { faArrowUp } from "@fortawesome/free-solid-svg-icons/faArrowUp";
+import { faArrowDown } from "@fortawesome/free-solid-svg-icons/faArrowDown";
+import { faTemperatureLow } from "@fortawesome/free-solid-svg-icons/faTemperatureLow";
+import { faTemperatureHigh } from "@fortawesome/free-solid-svg-icons/faTemperatureHigh";
+import { faThermometerHalf } from "@fortawesome/free-solid-svg-icons/faThermometerHalf";
+import { faLeaf } from "@fortawesome/free-solid-svg-icons/faLeaf";
+import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
+import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
+import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
+import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
+import App from "./views/App";
+import Toasts from "./components/Toasts";
+import router from "./router";
+import store from "./store";
-library.add(faSun, faArrowUp, faArrowDown, faTemperatureLow, faTemperatureHigh, faThermometerHalf, faLeaf, faChevronUp, faChevronDown, faExclamationTriangle)
+library.add(
+ faSun,
+ faArrowUp,
+ faArrowDown,
+ faTemperatureLow,
+ faTemperatureHigh,
+ faThermometerHalf,
+ faLeaf,
+ faChevronUp,
+ faChevronDown,
+ faExclamationTriangle
+);
-Vue.component('fa-icon', FontAwesomeIcon)
-
-Vue.use(VueRouter)
+Vue.component("font-awesome-icon", FontAwesomeIcon);
const loc = window.location;
-axios.defaults.baseURL = loc.protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") + loc.pathname + "api";
-axios.defaults.headers.post['Content-Type'] = 'application/json';
+axios.defaults.baseURL =
+ loc.protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") + loc.pathname + "api";
+axios.defaults.headers.post["Content-Type"] = "application/json";
-//
-// Mixins
-//
-
-let formatter = {
- data: function () {
- return {
- fmtLimit: 100,
- fmtDigits: 1,
- }
- },
- methods: {
- round: function(num, precision) {
- var base = 10 ** precision;
- return (Math.round(num * base) / base).toFixed(precision);
- },
- fmt: function (val) {
- if (val === undefined || val === null) {
- return 0;
- }
- val = Math.abs(val);
- return val >= this.fmtLimit ? this.round(val / 1e3, this.fmtDigits) : this.round(val, 0);
- },
- fmtUnit: function (val) {
- return Math.abs(val) >= this.fmtLimit ? "k" : "";
- },
- fmtDuration: function (d) {
- if (d <= 0 || d == null) {
- return '—';
- }
- var seconds = "0" + (d % 60);
- var minutes = "0" + (Math.floor(d / 60) % 60);
- var hours = "" + Math.floor(d / 3600);
- if (hours.length < 2) {
- hours = "0" + hours;
- }
- return hours + ":" + minutes.substr(-2) + ":" + seconds.substr(-2);
- },
- fmtShortDuration: function (d) {
- if (d <= 0 || d == null) {
- return '—';
- }
- var minutes = (Math.floor(d / 60) % 60);
- var hours = Math.floor(d / 3600);
- var tm;
- if (hours >= 1) {
- minutes = "0" + minutes;
- tm = hours + ":" + minutes.substr(-2);
- } else {
- var seconds = "0" + (d % 60);
- tm = minutes + ":" + seconds.substr(-2);
- }
- return tm;
- },
- fmtShortDurationUnit: function (d) {
- if (d <= 0 || d == null) {
- return '';
- }
- var hours = Math.floor(d / 3600);
- if (hours >= 1) {
- return "h";
- }
- return "m";
- },
- }
-}
-
-//
-// State
-//
-
-function setProperty(obj, props, value) {
- const prop = props.shift()
- if (!obj[prop]) {
- Vue.set(obj, prop, {})
- }
-
- if (!props.length) {
- if (value && typeof value === 'object' && !Array.isArray(value)) {
- obj[prop] = { ...obj[prop], ...value }
- } else {
- obj[prop] = value
- }
- return
- }
-
- setProperty(obj[prop], props, value)
-}
-
-let store = {
- state: {
- loadpoints: [], // ensure array type
- },
- update: function(msg) {
- Object.keys(msg).forEach(function (k) {
- if (typeof toasts[k] === "function") {
- toasts[k]({message: msg[k]})
- } else {
- setProperty(store.state, k.split('.'), msg[k])
- }
- });
- }
-};
-
-//
-// Heartbeat
-//
-
-window.setInterval(function() {
- axios.get("health").catch(function(res) {
- res.message = "Server unavailable";
- toasts.error(res)
- });
-}, 5000);
-
-//
-// App & Routing
-//
-
-const main = Vue.component('Main', {
- template: '#main-template',
- data: function() {
- return {
- state: store.state // global state
- }
- },
- methods: {
- configured: function (val) {
- // for development purposes
- if (val == '[[.Configured]]') {
- return true;
- }
- if (!isNaN(parseInt(val)) && parseInt(val) > 0) {
- return true;
- }
- return false;
- }
- }
-});
-
-const config = Vue.component("config", {
- template: "#config-template",
- data: function() {
- return {
- state: store.state // global state
- };
- },
-});
-
-const routes = [
- { path: "/", component: main },
- { path: "/config", component: config },
-];
-
-const router = new VueRouter({
- routes, // short for `routes: routes`
- linkExactActiveClass: "active", // Bootstrap