Merge remote-tracking branch 'tbnobody/OpenDTU/master'
This commit is contained in:
commit
2109520bde
@ -154,7 +154,7 @@ This can be achieved by editing the 'platformio.ini' file and add/change one or
|
||||
* Adjust the COM port in the file "platformio.ini" for your USB-serial-converter. It occurs twice:
|
||||
* upload_port
|
||||
* monitor_port
|
||||
* Select the arrow button in the status bar (PlatformIO: Upload) to compile and upload the firmware. During the compilation, all required libraries are downloaded automatically.
|
||||
* Select the arrow button in the blue bottom status bar (PlatformIO: Upload) to compile and upload the firmware. During the compilation, all required libraries are downloaded automatically.
|
||||
* There are two videos showing these steps:
|
||||
* [Git Clone and compilation](https://youtu.be/9cA_esv3zeA)
|
||||
* [Full installation and compilation](https://youtu.be/xs6TqHn7QWM)
|
||||
@ -263,6 +263,7 @@ A documentation of the Web API can be found here: [Web-API Documentation](docs/W
|
||||
* OpenDTU needs access to a working NTP server to get the current date & time.
|
||||
* If your problem persists, check the [Issues on Github](https://github.com/tbnobody/OpenDTU/issues). Please inspect not only the open issues, also the closed issues contain useful information.
|
||||
* Another source of information are the [Discussions](https://github.com/tbnobody/OpenDTU/discussions/)
|
||||
* When flashing with VSCode Plattform.IO fails and also with ESPRESSIF tool a demo bin file cannot be flashed to the ESP32 with error message "A fatal error occurred: MD5 of file does not match data in flash!" than un-wire/unconnect ESP32 from the NRF24L01+ board. Try to flash again and rewire afterwards.
|
||||
|
||||
## Related Projects
|
||||
- [Ahoy](https://github.com/grindylow/ahoy)
|
||||
|
||||
@ -91,6 +91,7 @@ struct CONFIG_T {
|
||||
bool Mqtt_Hass_Expire;
|
||||
|
||||
char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1];
|
||||
bool Security_AllowReadonly;
|
||||
};
|
||||
|
||||
class ConfigurationClass {
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include "WebApi_firmware.h"
|
||||
#include "WebApi_inverter.h"
|
||||
#include "WebApi_limit.h"
|
||||
#include "WebApi_maintenance.h"
|
||||
#include "WebApi_mqtt.h"
|
||||
#include "WebApi_network.h"
|
||||
#include "WebApi_ntp.h"
|
||||
@ -28,6 +29,7 @@ public:
|
||||
void loop();
|
||||
|
||||
static bool checkCredentials(AsyncWebServerRequest* request);
|
||||
static bool checkCredentialsReadonly(AsyncWebServerRequest* request);
|
||||
|
||||
private:
|
||||
AsyncWebServer _server;
|
||||
@ -40,6 +42,7 @@ private:
|
||||
WebApiFirmwareClass _webApiFirmware;
|
||||
WebApiInverterClass _webApiInverter;
|
||||
WebApiLimitClass _webApiLimit;
|
||||
WebApiMaintenanceClass _webApiMaintenance;
|
||||
WebApiMqttClass _webApiMqtt;
|
||||
WebApiNetworkClass _webApiNetwork;
|
||||
WebApiNtpClass _webApiNtp;
|
||||
|
||||
15
include/WebApi_maintenance.h
Normal file
15
include/WebApi_maintenance.h
Normal file
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
class WebApiMaintenanceClass {
|
||||
public:
|
||||
void init(AsyncWebServer* server);
|
||||
void loop();
|
||||
|
||||
private:
|
||||
void onRebootPost(AsyncWebServerRequest* request);
|
||||
|
||||
AsyncWebServer* _server;
|
||||
};
|
||||
@ -9,8 +9,8 @@ public:
|
||||
void loop();
|
||||
|
||||
private:
|
||||
void onPasswordGet(AsyncWebServerRequest* request);
|
||||
void onPasswordPost(AsyncWebServerRequest* request);
|
||||
void onSecurityGet(AsyncWebServerRequest* request);
|
||||
void onSecurityPost(AsyncWebServerRequest* request);
|
||||
|
||||
void onAuthenticateGet(AsyncWebServerRequest* request);
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#define ACCESS_POINT_NAME "OpenDTU-"
|
||||
#define ACCESS_POINT_PASSWORD "openDTU42"
|
||||
#define AUTH_USERNAME "admin"
|
||||
#define SECURITY_ALLOW_READONLY true
|
||||
|
||||
#define ADMIN_TIMEOUT 180
|
||||
#define WIFI_RECONNECT_TIMEOUT 15
|
||||
|
||||
@ -77,6 +77,7 @@ bool ConfigurationClass::write()
|
||||
|
||||
JsonObject security = doc.createNestedObject("security");
|
||||
security["password"] = config.Security_Password;
|
||||
security["allow_readonly"] = config.Security_AllowReadonly;
|
||||
|
||||
JsonArray inverters = doc.createNestedArray("inverters");
|
||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||
@ -202,6 +203,7 @@ bool ConfigurationClass::read()
|
||||
|
||||
JsonObject security = doc["security"];
|
||||
strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password));
|
||||
config.Security_AllowReadonly = security["allow_readonly"] | SECURITY_ALLOW_READONLY;
|
||||
|
||||
JsonArray inverters = doc["inverters"];
|
||||
for (uint8_t i = 0; i < INV_MAX_COUNT; i++) {
|
||||
|
||||
@ -25,6 +25,7 @@ void WebApiClass::init()
|
||||
_webApiFirmware.init(&_server);
|
||||
_webApiInverter.init(&_server);
|
||||
_webApiLimit.init(&_server);
|
||||
_webApiMaintenance.init(&_server);
|
||||
_webApiMqtt.init(&_server);
|
||||
_webApiNetwork.init(&_server);
|
||||
_webApiNtp.init(&_server);
|
||||
@ -49,6 +50,7 @@ void WebApiClass::loop()
|
||||
_webApiFirmware.loop();
|
||||
_webApiInverter.loop();
|
||||
_webApiLimit.loop();
|
||||
_webApiMaintenance.loop();
|
||||
_webApiMqtt.loop();
|
||||
_webApiNetwork.loop();
|
||||
_webApiNtp.loop();
|
||||
@ -79,4 +81,14 @@ bool WebApiClass::checkCredentials(AsyncWebServerRequest* request)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WebApiClass::checkCredentialsReadonly(AsyncWebServerRequest* request)
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
if (config.Security_AllowReadonly) {
|
||||
return true;
|
||||
} else {
|
||||
return checkCredentials(request);
|
||||
}
|
||||
}
|
||||
|
||||
WebApiClass WebApi;
|
||||
@ -6,6 +6,7 @@
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Hoymiles.h"
|
||||
#include "WebApi.h"
|
||||
#include <ctime>
|
||||
|
||||
void WebApiDevInfoClass::init(AsyncWebServer* server)
|
||||
@ -23,6 +24,10 @@ void WebApiDevInfoClass::loop()
|
||||
|
||||
void WebApiDevInfoClass::onDevInfoStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Hoymiles.h"
|
||||
#include "WebApi.h"
|
||||
|
||||
void WebApiEventlogClass::init(AsyncWebServer* server)
|
||||
{
|
||||
@ -22,6 +23,10 @@ void WebApiEventlogClass::loop()
|
||||
|
||||
void WebApiEventlogClass::onEventlogStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 2048);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
|
||||
@ -24,6 +24,10 @@ void WebApiLimitClass::loop()
|
||||
|
||||
void WebApiLimitClass::onLimitStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
|
||||
82
src/WebApi_maintenance.cpp
Normal file
82
src/WebApi_maintenance.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
|
||||
#include "WebApi_maintenance.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "WebApi.h"
|
||||
|
||||
void WebApiMaintenanceClass::init(AsyncWebServer* server)
|
||||
{
|
||||
using std::placeholders::_1;
|
||||
|
||||
_server = server;
|
||||
|
||||
_server->on("/api/maintenance/reboot", HTTP_POST, std::bind(&WebApiMaintenanceClass::onRebootPost, this, _1));
|
||||
}
|
||||
|
||||
void WebApiMaintenanceClass::loop()
|
||||
{
|
||||
}
|
||||
|
||||
void WebApiMaintenanceClass::onRebootPost(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentials(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||
JsonObject retMsg = response->getRoot();
|
||||
retMsg[F("type")] = F("warning");
|
||||
|
||||
if (!request->hasParam("data", true)) {
|
||||
retMsg[F("message")] = F("No values found!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
String json = request->getParam("data", true)->value();
|
||||
|
||||
if (json.length() > MQTT_JSON_DOC_SIZE) {
|
||||
retMsg[F("message")] = F("Data too large!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
DynamicJsonDocument root(MQTT_JSON_DOC_SIZE);
|
||||
DeserializationError error = deserializeJson(root, json);
|
||||
|
||||
if (error) {
|
||||
retMsg[F("message")] = F("Failed to parse data!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(root.containsKey("reboot"))) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (root[F("reboot")].as<bool>()) {
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Reboot triggered!");
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
yield();
|
||||
delay(1000);
|
||||
yield();
|
||||
ESP.restart();
|
||||
} else {
|
||||
retMsg[F("message")] = F("Reboot cancled!");
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
}
|
||||
@ -28,6 +28,10 @@ void WebApiMqttClass::loop()
|
||||
|
||||
void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, MQTT_JSON_DOC_SIZE);
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
@ -27,6 +27,10 @@ void WebApiNetworkClass::loop()
|
||||
|
||||
void WebApiNetworkClass::onNetworkStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
|
||||
@ -29,6 +29,10 @@ void WebApiNtpClass::loop()
|
||||
|
||||
void WebApiNtpClass::onNtpStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
@ -24,6 +24,10 @@ void WebApiPowerClass::loop()
|
||||
|
||||
void WebApiPowerClass::onPowerStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
|
||||
@ -15,8 +15,8 @@ void WebApiSecurityClass::init(AsyncWebServer* server)
|
||||
|
||||
_server = server;
|
||||
|
||||
_server->on("/api/security/password", HTTP_GET, std::bind(&WebApiSecurityClass::onPasswordGet, this, _1));
|
||||
_server->on("/api/security/password", HTTP_POST, std::bind(&WebApiSecurityClass::onPasswordPost, this, _1));
|
||||
_server->on("/api/security/config", HTTP_GET, std::bind(&WebApiSecurityClass::onSecurityGet, this, _1));
|
||||
_server->on("/api/security/config", HTTP_POST, std::bind(&WebApiSecurityClass::onSecurityPost, this, _1));
|
||||
_server->on("/api/security/authenticate", HTTP_GET, std::bind(&WebApiSecurityClass::onAuthenticateGet, this, _1));
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ void WebApiSecurityClass::loop()
|
||||
{
|
||||
}
|
||||
|
||||
void WebApiSecurityClass::onPasswordGet(AsyncWebServerRequest* request)
|
||||
void WebApiSecurityClass::onSecurityGet(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentials(request)) {
|
||||
return;
|
||||
@ -35,12 +35,13 @@ void WebApiSecurityClass::onPasswordGet(AsyncWebServerRequest* request)
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("password")] = config.Security_Password;
|
||||
root[F("allow_readonly")] = config.Security_AllowReadonly;
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request)
|
||||
void WebApiSecurityClass::onSecurityPost(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentials(request)) {
|
||||
return;
|
||||
@ -76,7 +77,8 @@ void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!root.containsKey("password")) {
|
||||
if (!root.containsKey("password")
|
||||
&& root.containsKey("allow_readonly")) {
|
||||
retMsg[F("message")] = F("Values are missing!");
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
@ -92,6 +94,7 @@ void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request)
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
strlcpy(config.Security_Password, root[F("password")].as<String>().c_str(), sizeof(config.Security_Password));
|
||||
config.Security_AllowReadonly = root[F("allow_readonly")].as<bool>();
|
||||
Configuration.write();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "WebApi.h"
|
||||
#include <Hoymiles.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ResetReason.h>
|
||||
@ -30,6 +31,10 @@ void WebApiSysstatusClass::loop()
|
||||
|
||||
void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
|
||||
@ -27,6 +27,10 @@ void WebApiVedirectClass::loop()
|
||||
|
||||
void WebApiVedirectClass::onVedirectStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||
JsonObject root = response->getRoot();
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "WebApi_ws_live.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "WebApi.h"
|
||||
#include "defaults.h"
|
||||
|
||||
WebApiWsLiveClass::WebApiWsLiveClass()
|
||||
@ -65,6 +66,13 @@ void WebApiWsLiveClass::loop()
|
||||
String buffer;
|
||||
if (buffer) {
|
||||
serializeJson(root, buffer);
|
||||
|
||||
if (Configuration.get().Security_AllowReadonly) {
|
||||
_ws.setAuthentication("", "");
|
||||
} else {
|
||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security_Password);
|
||||
}
|
||||
|
||||
_ws.textAll(buffer);
|
||||
}
|
||||
|
||||
@ -200,6 +208,10 @@ void WebApiWsLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketC
|
||||
|
||||
void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 40960U);
|
||||
JsonVariant root = response->getRoot();
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "WebApi_ws_vedirect_live.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
#include "WebApi.h"
|
||||
|
||||
WebApiWsVedirectLiveClass::WebApiWsVedirectLiveClass()
|
||||
: _ws("/vedirectlivedata")
|
||||
@ -60,6 +61,13 @@ void WebApiWsVedirectLiveClass::loop()
|
||||
String buffer;
|
||||
if (buffer) {
|
||||
serializeJson(root, buffer);
|
||||
|
||||
if (Configuration.get().Security_AllowReadonly) {
|
||||
_ws.setAuthentication("", "");
|
||||
} else {
|
||||
_ws.setAuthentication(AUTH_USERNAME, Configuration.get().Security_Password);
|
||||
}
|
||||
|
||||
_ws.textAll(buffer);
|
||||
}
|
||||
|
||||
@ -125,6 +133,9 @@ void WebApiWsVedirectLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWe
|
||||
|
||||
void WebApiWsVedirectLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
||||
{
|
||||
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||
return;
|
||||
}
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 1024U);
|
||||
JsonVariant root = response->getRoot().as<JsonVariant>();
|
||||
generateJsonResponse(root);
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
# opendtu
|
||||
# OpenDTU web frontend
|
||||
|
||||
You can run the webapp locally with `yarn dev`. If you enter the IP of your ESP in the `vite.config.ts` beforehand, all api requests will even be proxied to the real ESP. Then you can develop the webapp as if it were running directly on the ESP. The `yarn dev` also supports hot reload, i.e. as soon as you save a vue file, it is automatically reloaded in the browser.
|
||||
|
||||
## Project Setup
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"bootstrap": "^5.2.2",
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap-icons-vue": "^1.8.1",
|
||||
"mitt": "^3.0.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
|
||||
@ -49,6 +49,9 @@
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/firmware/upgrade">Firmware Upgrade</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link @click="onClick" class="dropdown-item" to="/maintenance/reboot">Device Reboot</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
|
||||
@ -16,6 +16,7 @@ import VedirectAdminView from '@/views/VedirectAdminView.vue'
|
||||
import VedirectInfoView from '@/views/VedirectInfoView.vue'
|
||||
import SecurityAdminView from '@/views/SecurityAdminView.vue'
|
||||
import LoginView from '@/views/LoginView.vue'
|
||||
import MaintenanceRebootView from '@/views/MaintenanceRebootView.vue';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@ -105,24 +106,13 @@ const router = createRouter({
|
||||
path: '/settings/security',
|
||||
name: 'Security',
|
||||
component: SecurityAdminView
|
||||
},
|
||||
{
|
||||
path: '/maintenance/reboot',
|
||||
name: 'Device Reboot',
|
||||
component: MaintenanceRebootView
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
// redirect to login page if not logged in and trying to access a restricted page
|
||||
const publicPages = ['/', '/login', '/about', '/info/network', '/info/system', '/info/ntp', '/info/mqtt', '/info/vedirect', ];
|
||||
const authRequired = !publicPages.includes(to.path);
|
||||
const loggedIn = localStorage.getItem('user');
|
||||
|
||||
if (authRequired && !loggedIn) {
|
||||
return next({
|
||||
path: '/login',
|
||||
query: { returnUrl: to.path }
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
||||
@ -1,3 +1,4 @@
|
||||
export interface SecurityConfig {
|
||||
password: string;
|
||||
allow_readonly: boolean;
|
||||
}
|
||||
@ -1,17 +1,31 @@
|
||||
import type { Emitter, EventType } from "mitt";
|
||||
import type { Router } from "vue-router";
|
||||
|
||||
export function authHeader(): Headers {
|
||||
// return authorization header with basic auth credentials
|
||||
let user = JSON.parse(localStorage.getItem('user') || "");
|
||||
let user = null;
|
||||
try {
|
||||
user = JSON.parse(localStorage.getItem('user') || "");
|
||||
} catch { }
|
||||
|
||||
const headers = new Headers();
|
||||
headers.append('X-Requested-With', 'XMLHttpRequest');
|
||||
if (user && user.authdata) {
|
||||
headers.append('Authorization', 'Basic ' + user.authdata);
|
||||
}
|
||||
return new Headers(headers);
|
||||
}
|
||||
|
||||
export function authUrl(): string {
|
||||
let user = null;
|
||||
try {
|
||||
user = JSON.parse(localStorage.getItem('user') || "");
|
||||
} catch { }
|
||||
|
||||
if (user && user.authdata) {
|
||||
const headers = new Headers();
|
||||
headers.append('Authorization', 'Basic ' + user.authdata);
|
||||
headers.append('X-Requested-With', 'XMLHttpRequest')
|
||||
return new Headers(headers);
|
||||
} else {
|
||||
return new Headers();
|
||||
return encodeURIComponent(atob(user.authdata)).replace("%3A", ":") + '@';
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
@ -47,7 +61,7 @@ export function login(username: String, password: String) {
|
||||
});
|
||||
}
|
||||
|
||||
export function handleResponse(response: Response, emitter: Emitter<Record<EventType, unknown>>) {
|
||||
export function handleResponse(response: Response, emitter: Emitter<Record<EventType, unknown>>, router: Router) {
|
||||
return response.text().then(text => {
|
||||
const data = text && JSON.parse(text);
|
||||
if (!response.ok) {
|
||||
@ -55,7 +69,7 @@ export function handleResponse(response: Response, emitter: Emitter<Record<Event
|
||||
// auto logout if 401 response returned from api
|
||||
logout();
|
||||
emitter.emit("logged-out");
|
||||
location.reload();
|
||||
router.push({path: "/login", query: { returnUrl: router.currentRoute.value.fullPath }});
|
||||
}
|
||||
|
||||
const error = (data && data.message) || response.statusText;
|
||||
|
||||
@ -112,7 +112,7 @@ import {
|
||||
} from 'bootstrap-icons-vue';
|
||||
import * as bootstrap from 'bootstrap';
|
||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
||||
import { handleResponse, authHeader } from '@/utils/authentication';
|
||||
import { handleResponse, authHeader, isLoggedIn } from '@/utils/authentication';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@ -137,6 +137,9 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (!isLoggedIn()) {
|
||||
this.$router.push({ path: "/login", query: { returnUrl: this.$router.currentRoute.value.fullPath } });
|
||||
}
|
||||
this.modalFactoryReset = new bootstrap.Modal('#factoryReset');
|
||||
this.loading = false;
|
||||
},
|
||||
@ -156,7 +159,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
|
||||
@ -79,7 +79,7 @@ export default defineComponent({
|
||||
getDtuConfig() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/dtu/config", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(data) => {
|
||||
this.dtuConfigList = data;
|
||||
@ -98,7 +98,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
|
||||
@ -77,7 +77,7 @@ import {
|
||||
BIconArrowRepeat,
|
||||
BIconCheckCircle
|
||||
} from 'bootstrap-icons-vue';
|
||||
import { authHeader } from '@/utils/authentication';
|
||||
import { authHeader, isLoggedIn } from '@/utils/authentication';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@ -184,6 +184,9 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (!isLoggedIn()) {
|
||||
this.$router.push({ path: "/login", query: { returnUrl: this.$router.currentRoute.value.fullPath } });
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
});
|
||||
|
||||
@ -345,7 +345,7 @@ import type { EventlogItems } from '@/types/EventlogStatus';
|
||||
import type { LiveData, Inverter } from '@/types/LiveDataStatus';
|
||||
import type { LimitStatus } from '@/types/LimitStatus';
|
||||
import type { LimitConfig } from '@/types/LimitConfig';
|
||||
import { isLoggedIn, handleResponse, authHeader } from '@/utils/authentication';
|
||||
import { isLoggedIn, handleResponse, authHeader, authUrl } from '@/utils/authentication';
|
||||
import { formatNumber } from '@/utils';
|
||||
|
||||
export default defineComponent({
|
||||
@ -467,8 +467,8 @@ export default defineComponent({
|
||||
isLoggedIn,
|
||||
getInitialData() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/livedata/status")
|
||||
.then((response) => response.json())
|
||||
fetch("/api/livedata/status", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.liveData = data;
|
||||
this.dataLoading = false;
|
||||
@ -478,8 +478,9 @@ export default defineComponent({
|
||||
console.log("Starting connection to WebSocket Server");
|
||||
|
||||
const { protocol, host } = location;
|
||||
const authString = authUrl();
|
||||
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
||||
}://${host}/livedata`;
|
||||
}://${authString}${host}/livedata`;
|
||||
|
||||
this.socket = new WebSocket(webSocketUrl);
|
||||
|
||||
@ -502,9 +503,11 @@ export default defineComponent({
|
||||
},
|
||||
initDataAgeing() {
|
||||
this.dataAgeInterval = setInterval(() => {
|
||||
this.inverterData.forEach(element => {
|
||||
element.data_age++;
|
||||
});
|
||||
if (this.inverterData) {
|
||||
this.inverterData.forEach(element => {
|
||||
element.data_age++;
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
// Send heartbeat packets regularly * 59s Send a heartbeat
|
||||
@ -530,8 +533,8 @@ export default defineComponent({
|
||||
},
|
||||
onShowEventlog(serial: number) {
|
||||
this.eventLogLoading = true;
|
||||
fetch("/api/eventlog/status?inv=" + serial)
|
||||
.then((response) => response.json())
|
||||
fetch("/api/eventlog/status?inv=" + serial, { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.eventLogList = data[serial];
|
||||
this.eventLogLoading = false;
|
||||
@ -544,8 +547,8 @@ export default defineComponent({
|
||||
},
|
||||
onShowDevInfo(serial: number) {
|
||||
this.devInfoLoading = true;
|
||||
fetch("/api/devinfo/status")
|
||||
.then((response) => response.json())
|
||||
fetch("/api/devinfo/status", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.devInfoList = data[serial][0];
|
||||
this.devInfoLoading = false;
|
||||
@ -563,8 +566,8 @@ export default defineComponent({
|
||||
this.targetLimitTypeText = "Relative (%)";
|
||||
|
||||
this.limitSettingLoading = true;
|
||||
fetch("/api/limit/status")
|
||||
.then((response) => response.json())
|
||||
fetch("/api/limit/status", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.currentLimitList = data[serial];
|
||||
this.targetLimitList.serial = serial;
|
||||
@ -587,7 +590,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
if (response.type == "success") {
|
||||
@ -618,8 +621,8 @@ export default defineComponent({
|
||||
|
||||
onShowPowerSettings(serial: number) {
|
||||
this.powerSettingLoading = true;
|
||||
fetch("/api/power/status")
|
||||
.then((response) => response.json())
|
||||
fetch("/api/power/status", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.successCommandPower = data[serial].power_set_status;
|
||||
this.powerSettingSerial = serial;
|
||||
@ -657,7 +660,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
if (response.type == "success") {
|
||||
|
||||
@ -207,7 +207,7 @@ export default defineComponent({
|
||||
getInverters() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/inverter/list", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.inverters = data.inverter;
|
||||
this.dataLoading = false;
|
||||
@ -222,7 +222,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.getInverters();
|
||||
this.alert = data;
|
||||
|
||||
101
webapp/src/views/MaintenanceRebootView.vue
Normal file
101
webapp/src/views/MaintenanceRebootView.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<BasePage :title="'Device Reboot'" :isLoading="dataLoading">
|
||||
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
|
||||
{{ alertMessage }}
|
||||
</BootstrapAlert>
|
||||
|
||||
<div class="card mt-5">
|
||||
<div class="card-header text-bg-primary">Perform Reboot</div>
|
||||
<div class="card-body text-center">
|
||||
|
||||
<button class="btn btn-danger" @click="onOpenModal(performReboot)">Reboot!
|
||||
</button>
|
||||
|
||||
<div class="alert alert-danger mt-3" role="alert">
|
||||
<b>Note:</b> A manual reboot does not normally have to be performed. OpenDTU performs any required
|
||||
reboot (e.g. after a firmware update) automatically. Settings are also adopted without rebooting. If
|
||||
you need to reboot due to an error, please consider reporting it at <a
|
||||
href="https://github.com/tbnobody/OpenDTU/issues" class="alert-link"
|
||||
target="_blank">https://github.com/tbnobody/OpenDTU/issues</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasePage>
|
||||
|
||||
<div class="modal" id="performReboot" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Reboot OpenDTU</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Do you really want to reboot the device?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="onCloseModal(performReboot)"
|
||||
data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger" @click="onReboot">Reboot</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import * as bootstrap from 'bootstrap';
|
||||
import BasePage from '@/components/BasePage.vue';
|
||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
||||
import { handleResponse, authHeader, isLoggedIn } from '@/utils/authentication';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BasePage,
|
||||
BootstrapAlert,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
performReboot: {} as bootstrap.Modal,
|
||||
|
||||
dataLoading: false,
|
||||
|
||||
alertMessage: "",
|
||||
alertType: "info",
|
||||
showAlert: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (!isLoggedIn()) {
|
||||
this.$router.push({ path: "/login", query: { returnUrl: this.$router.currentRoute.value.fullPath } });
|
||||
}
|
||||
|
||||
this.performReboot = new bootstrap.Modal('#performReboot');
|
||||
},
|
||||
methods: {
|
||||
onReboot() {
|
||||
const formData = new FormData();
|
||||
formData.append("data", JSON.stringify({ reboot: true }));
|
||||
|
||||
fetch("/api/maintenance/reboot", {
|
||||
method: "POST",
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.alertMessage = data.message;
|
||||
this.alertType = data.type;
|
||||
this.showAlert = true;
|
||||
});
|
||||
this.onCloseModal(this.performReboot);
|
||||
},
|
||||
onOpenModal(modal: bootstrap.Modal) {
|
||||
modal.show();
|
||||
},
|
||||
onCloseModal(modal: bootstrap.Modal) {
|
||||
modal.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -242,7 +242,7 @@ export default defineComponent({
|
||||
getMqttConfig() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/mqtt/config", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.mqttConfigList = data;
|
||||
this.dataLoading = false;
|
||||
@ -259,7 +259,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
|
||||
@ -149,6 +149,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { handleResponse, authHeader } from '@/utils/authentication';
|
||||
import BasePage from '@/components/BasePage.vue';
|
||||
import type { MqttStatus } from '@/types/MqttStatus';
|
||||
|
||||
@ -168,8 +169,8 @@ export default defineComponent({
|
||||
methods: {
|
||||
getMqttInfo() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/mqtt/status")
|
||||
.then((response) => response.json())
|
||||
fetch("/api/mqtt/status", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.mqttDataList = data;
|
||||
this.dataLoading = false;
|
||||
|
||||
@ -128,7 +128,7 @@ export default defineComponent({
|
||||
getNetworkConfig() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/network/config", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.networkConfigList = data;
|
||||
this.dataLoading = false;
|
||||
@ -145,7 +145,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { handleResponse, authHeader } from '@/utils/authentication';
|
||||
import BasePage from '@/components/BasePage.vue';
|
||||
import WifiStationInfo from "@/components/WifiStationInfo.vue";
|
||||
import WifiApInfo from "@/components/WifiApInfo.vue";
|
||||
@ -40,8 +41,8 @@ export default defineComponent({
|
||||
methods: {
|
||||
getNetworkInfo() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/network/status")
|
||||
.then((response) => response.json())
|
||||
fetch("/api/network/status", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.networkDataList = data;
|
||||
this.dataLoading = false;
|
||||
|
||||
@ -129,7 +129,7 @@ export default defineComponent({
|
||||
getNtpConfig() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/ntp/config", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(data) => {
|
||||
this.ntpConfigList = data;
|
||||
@ -144,7 +144,7 @@ export default defineComponent({
|
||||
getCurrentTime() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/ntp/time", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(data) => {
|
||||
this.mcuTime = new Date(
|
||||
@ -172,7 +172,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
@ -195,7 +195,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
|
||||
@ -54,6 +54,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { handleResponse, authHeader } from '@/utils/authentication';
|
||||
import BasePage from '@/components/BasePage.vue';
|
||||
import type { NtpStatus } from "@/types/NtpStatus";
|
||||
|
||||
@ -73,8 +74,8 @@ export default defineComponent({
|
||||
methods: {
|
||||
getNtpInfo() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/ntp/status")
|
||||
.then((response) => response.json())
|
||||
fetch("/api/ntp/status", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.ntpDataList = data;
|
||||
this.dataLoading = false;
|
||||
|
||||
@ -32,6 +32,22 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-5">
|
||||
<div class="card-header text-bg-primary">Permissions</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-6 form-check-label" for="inputReadonly">Allow readonly access to web interface</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="inputReadonly"
|
||||
v-model="securityConfigList.allow_readonly" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mb-3">Save</button>
|
||||
</form>
|
||||
</BasePage>
|
||||
@ -66,8 +82,8 @@ export default defineComponent({
|
||||
methods: {
|
||||
getPasswordConfig() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/security/password", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
fetch("/api/security/config", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(data) => {
|
||||
this.securityConfigList = data;
|
||||
@ -89,12 +105,12 @@ export default defineComponent({
|
||||
const formData = new FormData();
|
||||
formData.append("data", JSON.stringify(this.securityConfigList));
|
||||
|
||||
fetch("/api/security/password", {
|
||||
fetch("/api/security/config", {
|
||||
method: "POST",
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { handleResponse, authHeader } from '@/utils/authentication';
|
||||
import BasePage from '@/components/BasePage.vue';
|
||||
import HardwareInfo from "@/components/HardwareInfo.vue";
|
||||
import FirmwareInfo from "@/components/FirmwareInfo.vue";
|
||||
@ -40,8 +41,8 @@ export default defineComponent({
|
||||
methods: {
|
||||
getSystemInfo() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/system/status")
|
||||
.then((response) => response.json())
|
||||
fetch("/api/system/status", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.systemDataList = data;
|
||||
this.dataLoading = false;
|
||||
|
||||
@ -75,7 +75,7 @@ export default defineComponent({
|
||||
getVedirectConfig() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/vedirect/config", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.vedirectConfigList = data;
|
||||
this.dataLoading = false;
|
||||
@ -92,7 +92,7 @@ export default defineComponent({
|
||||
headers: authHeader(),
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => handleResponse(response, this.$emitter))
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then(
|
||||
(response) => {
|
||||
this.alertMessage = response.message;
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { handleResponse, authHeader } from '@/utils/authentication';
|
||||
import BasePage from '@/components/BasePage.vue';
|
||||
import type { VedirectStatus } from "@/types/VedirectStatus";
|
||||
|
||||
@ -55,8 +56,8 @@ export default defineComponent({
|
||||
methods: {
|
||||
getVedirectInfo() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/vedirect/status")
|
||||
.then((response) => response.json())
|
||||
fetch("/api/vedirect/status", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.vedirectDataList = data;
|
||||
this.dataLoading = false;
|
||||
|
||||
@ -177,6 +177,8 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import type { Vedirect } from '@/types/VedirectLiveDataStatus';
|
||||
import { isLoggedIn, handleResponse, authHeader, authUrl } from '@/utils/authentication';
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@ -202,8 +204,8 @@ export default defineComponent({
|
||||
methods: {
|
||||
getInitialData() {
|
||||
this.dataLoading = true;
|
||||
fetch("/api/vedirectlivedata/status")
|
||||
.then((response) => response.json())
|
||||
fetch("/api/vedirectlivedata/status", { headers: authHeader() })
|
||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||
.then((data) => {
|
||||
this.vedirectData = data;
|
||||
this.dataLoading = false;
|
||||
@ -213,8 +215,9 @@ export default defineComponent({
|
||||
console.log("Starting connection to WebSocket Server");
|
||||
|
||||
const { protocol, host } = location;
|
||||
const authString = authUrl();
|
||||
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
||||
}://${host}/vedirectlivedata`;
|
||||
}://${authString}${host}/vedirectlivedata`;
|
||||
|
||||
this.socket = new WebSocket(webSocketUrl);
|
||||
|
||||
@ -237,7 +240,9 @@ export default defineComponent({
|
||||
},
|
||||
initDataAgeing() {
|
||||
this.dataAgeInterval = setInterval(() => {
|
||||
this.vedirectData.data_age++;
|
||||
if (this.vedirectData) {
|
||||
this.vedirectData.data_age++;
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
// Send heartbeat packets regularly * 59s Send a heartbeat
|
||||
|
||||
@ -524,10 +524,10 @@ bootstrap-icons-vue@^1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/bootstrap-icons-vue/-/bootstrap-icons-vue-1.8.1.tgz#ce4a0c1f6efe41dabcc1341f2cb191d307fbaf50"
|
||||
integrity sha512-uItRULwQz0epETi9x/RBEqfjHmTAmkIIczpH1R6L9T6yyaaijk0826PzTWnWNm15tw66JT/8GNuXjB0HI5PHLw==
|
||||
|
||||
bootstrap@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.2.tgz#834e053eed584a65e244d8aa112a6959f56e27a0"
|
||||
integrity sha512-dEtzMTV71n6Fhmbg4fYJzQsw1N29hJKO1js5ackCgIpDcGid2ETMGC6zwSYw09v05Y+oRdQ9loC54zB1La3hHQ==
|
||||
bootstrap@^5.2.3:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.3.tgz#54739f4414de121b9785c5da3c87b37ff008322b"
|
||||
integrity sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user