Merge branch 'tbnobody:master' into master
This commit is contained in:
commit
ceef11a038
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -84,3 +84,5 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have updated the title field above with a concise description.
|
- label: I have updated the title field above with a concise description.
|
||||||
required: true
|
required: true
|
||||||
|
- label: I have double checked that my inverter does not contain a W in the model name (like HMS-xxxW) as they are not supported
|
||||||
|
required: true
|
||||||
|
|||||||
@ -22,6 +22,34 @@
|
|||||||
"clk_mode": 0
|
"clk_mode": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "WT32-ETH01 with SH1106",
|
||||||
|
"links": [
|
||||||
|
{"name": "Datasheet", "url": "http://www.wireless-tag.com/portfolio/wt32-eth01/"}
|
||||||
|
],
|
||||||
|
"nrf24": {
|
||||||
|
"miso": 4,
|
||||||
|
"mosi": 2,
|
||||||
|
"clk": 32,
|
||||||
|
"irq": 33,
|
||||||
|
"en": 14,
|
||||||
|
"cs": 15
|
||||||
|
},
|
||||||
|
"eth": {
|
||||||
|
"enabled": true,
|
||||||
|
"phy_addr": 1,
|
||||||
|
"power": 16,
|
||||||
|
"mdc": 23,
|
||||||
|
"mdio": 18,
|
||||||
|
"type": 0,
|
||||||
|
"clk_mode": 0
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"type": 3,
|
||||||
|
"data": 5,
|
||||||
|
"clk": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "WT32-ETH01 with SSD1306",
|
"name": "WT32-ETH01 with SSD1306",
|
||||||
"links": [
|
"links": [
|
||||||
|
|||||||
@ -63,7 +63,7 @@ private:
|
|||||||
void publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic = "");
|
void publishDtuBinarySensor(const char* name, const char* device_class, const char* category, const char* payload_on, const char* payload_off, const char* subTopic = "");
|
||||||
void publishInverterField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false);
|
void publishInverterField(std::shared_ptr<InverterAbstract> inv, const ChannelType_t type, const ChannelNum_t channel, const byteAssign_fieldDeviceClass_t fieldType, const bool clear = false);
|
||||||
void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload);
|
void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload);
|
||||||
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100);
|
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, const int16_t min = 1, const int16_t max = 100, float step = 1.0);
|
||||||
void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);
|
void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);
|
||||||
|
|
||||||
static void createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
|
static void createInverterInfo(JsonDocument& doc, std::shared_ptr<InverterAbstract> inv);
|
||||||
|
|||||||
@ -85,7 +85,7 @@ bool ActivePowerControlCommand::handleResponse(const fragment_t fragment[], cons
|
|||||||
|
|
||||||
float ActivePowerControlCommand::getLimit() const
|
float ActivePowerControlCommand::getLimit() const
|
||||||
{
|
{
|
||||||
const uint16_t l = (((uint16_t)_payload[12] << 8) | _payload[13]);
|
const float l = (((uint16_t)_payload[12] << 8) | _payload[13]);
|
||||||
return l / 10;
|
return l / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,7 @@ bool HMS_2CH::isValidSerial(const uint64_t serial)
|
|||||||
{
|
{
|
||||||
// serial >= 0x114400000000 && serial <= 0x1144ffffffff
|
// serial >= 0x114400000000 && serial <= 0x1144ffffffff
|
||||||
uint16_t preSerial = (serial >> 32) & 0xffff;
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
return preSerial == 0x1144;
|
return preSerial == 0x1144 || preSerial == 0x1143;
|
||||||
}
|
}
|
||||||
|
|
||||||
String HMS_2CH::typeName() const
|
String HMS_2CH::typeName() const
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
| HM_4CH | HM-1000/1200/1500-4T | 1161 |
|
| HM_4CH | HM-1000/1200/1500-4T | 1161 |
|
||||||
| HMS_1CH | HMS-300/350/400/450/500-1T | 1124 |
|
| HMS_1CH | HMS-300/350/400/450/500-1T | 1124 |
|
||||||
| HMS_1CHv2 | HMS-500-1T v2 | 1125 |
|
| HMS_1CHv2 | HMS-500-1T v2 | 1125 |
|
||||||
| HMS_2CH | HMS-600/700/800/900/1000-2T | 1144 |
|
| HMS_2CH | HMS-600/700/800/900/1000-2T | 1143, 1144 |
|
||||||
| HMS_4CH | HMS-1600/1800/2000-4T | 1164 |
|
| HMS_4CH | HMS-1600/1800/2000-4T | 1164 |
|
||||||
| HMT_4CH | HMT-1600/1800/2000-4T | 1361 |
|
| HMT_4CH | HMT-1600/1800/2000-4T | 1361 |
|
||||||
| HMT_6CH | HMT-1800/2250-6T | 1382 |
|
| HMT_6CH | HMT-1800/2250-6T | 1382 |
|
||||||
|
|||||||
@ -28,15 +28,15 @@ ID Source Addr Target Addr Idx ? wcode ? Start End ?
|
|||||||
|
|
||||||
const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> AlarmLogParser::_alarmMessages = { {
|
const std::array<const AlarmMessage_t, ALARM_MSG_COUNT> AlarmLogParser::_alarmMessages = { {
|
||||||
{ AlarmMessageType_t::ALL, 1, "Inverter start", "Wechselrichter gestartet", "L'onduleur a démarré" },
|
{ AlarmMessageType_t::ALL, 1, "Inverter start", "Wechselrichter gestartet", "L'onduleur a démarré" },
|
||||||
{ AlarmMessageType_t::ALL, 2, "Time calibration", "", "" },
|
{ AlarmMessageType_t::ALL, 2, "Time calibration", "Zeitabgleich", "" },
|
||||||
{ AlarmMessageType_t::ALL, 3, "EEPROM reading and writing error during operation", "", "" },
|
{ AlarmMessageType_t::ALL, 3, "EEPROM reading and writing error during operation", "", "" },
|
||||||
{ AlarmMessageType_t::ALL, 4, "Offline", "Offline", "Non connecté" },
|
{ AlarmMessageType_t::ALL, 4, "Offline", "Offline", "Non connecté" },
|
||||||
|
|
||||||
{ AlarmMessageType_t::ALL, 11, "Grid voltage surge", "", "" },
|
{ AlarmMessageType_t::ALL, 11, "Grid voltage surge", "Netz: Überspannungsimpuls", "" },
|
||||||
{ AlarmMessageType_t::ALL, 12, "Grid voltage sharp drop", "", "" },
|
{ AlarmMessageType_t::ALL, 12, "Grid voltage sharp drop", "Netz: Spannungseinbruch", "" },
|
||||||
{ AlarmMessageType_t::ALL, 13, "Grid frequency mutation", "", "" },
|
{ AlarmMessageType_t::ALL, 13, "Grid frequency mutation", "Netz: Frequenzänderung", "" },
|
||||||
{ AlarmMessageType_t::ALL, 14, "Grid phase mutation", "", "" },
|
{ AlarmMessageType_t::ALL, 14, "Grid phase mutation", "Netz: Phasenänderung", "" },
|
||||||
{ AlarmMessageType_t::ALL, 15, "Grid transient fluctuation", "", "" },
|
{ AlarmMessageType_t::ALL, 15, "Grid transient fluctuation", "Netz: vorübergehende Schwankung", "" },
|
||||||
|
|
||||||
{ AlarmMessageType_t::ALL, 36, "INV overvoltage or overcurrent", "", "" },
|
{ AlarmMessageType_t::ALL, 36, "INV overvoltage or overcurrent", "", "" },
|
||||||
|
|
||||||
|
|||||||
@ -62,6 +62,7 @@ const devInfo_t devInfo[] = {
|
|||||||
{ { 0x10, 0x20, 0x71, ALL }, 500, "HMS-500-1T v2" }, // 02
|
{ { 0x10, 0x20, 0x71, ALL }, 500, "HMS-500-1T v2" }, // 02
|
||||||
{ { 0x10, 0x21, 0x11, ALL }, 600, "HMS-600-2T" }, // 01
|
{ { 0x10, 0x21, 0x11, ALL }, 600, "HMS-600-2T" }, // 01
|
||||||
{ { 0x10, 0x21, 0x41, ALL }, 800, "HMS-800-2T" }, // 00
|
{ { 0x10, 0x21, 0x41, ALL }, 800, "HMS-800-2T" }, // 00
|
||||||
|
{ { 0x10, 0x11, 0x41, ALL }, 800, "HMS-800-2T-LV" }, // 00
|
||||||
{ { 0x10, 0x11, 0x51, ALL }, 900, "HMS-900-2T" }, // 01
|
{ { 0x10, 0x11, 0x51, ALL }, 900, "HMS-900-2T" }, // 01
|
||||||
{ { 0x10, 0x21, 0x51, ALL }, 900, "HMS-900-2T" }, // 03
|
{ { 0x10, 0x21, 0x51, ALL }, 900, "HMS-900-2T" }, // 03
|
||||||
{ { 0x10, 0x21, 0x71, ALL }, 1000, "HMS-1000-2T" }, // 05
|
{ { 0x10, 0x21, 0x71, ALL }, 1000, "HMS-1000-2T" }, // 05
|
||||||
|
|||||||
@ -19,7 +19,7 @@ extra_configs =
|
|||||||
custom_ci_action = generic,generic_esp32,generic_esp32s3,generic_esp32s3_usb
|
custom_ci_action = generic,generic_esp32,generic_esp32s3,generic_esp32s3_usb
|
||||||
|
|
||||||
framework = arduino
|
framework = arduino
|
||||||
platform = espressif32@6.7.0
|
platform = espressif32@6.8.1
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-DPIOENV=\"$PIOENV\"
|
-DPIOENV=\"$PIOENV\"
|
||||||
@ -27,6 +27,7 @@ build_flags =
|
|||||||
-D_TASK_THREAD_SAFE=1
|
-D_TASK_THREAD_SAFE=1
|
||||||
-DCONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE=128
|
-DCONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE=128
|
||||||
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
|
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
|
||||||
|
-DEMC_TASK_STACK_SIZE=6400
|
||||||
-Wall -Wextra -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference
|
-Wall -Wextra -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference
|
||||||
; Have to remove -Werror because of
|
; Have to remove -Werror because of
|
||||||
; https://github.com/espressif/arduino-esp32/issues/9044 and
|
; https://github.com/espressif/arduino-esp32/issues/9044 and
|
||||||
@ -38,10 +39,10 @@ build_unflags =
|
|||||||
-std=gnu++11
|
-std=gnu++11
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
mathieucarbou/ESP Async WebServer @ 2.10.8
|
mathieucarbou/ESPAsyncWebServer @ 3.1.2
|
||||||
bblanchon/ArduinoJson @ 7.0.4
|
bblanchon/ArduinoJson @ 7.1.0
|
||||||
https://github.com/bertmelis/espMqttClient.git#v1.7.0
|
https://github.com/bertmelis/espMqttClient.git#v1.7.0
|
||||||
nrf24/RF24 @ 1.4.8
|
nrf24/RF24 @ 1.4.9
|
||||||
olikraus/U8g2 @ 2.35.19
|
olikraus/U8g2 @ 2.35.19
|
||||||
buelowp/sunset @ 1.1.7
|
buelowp/sunset @ 1.1.7
|
||||||
https://github.com/arkhipenko/TaskScheduler#testing
|
https://github.com/arkhipenko/TaskScheduler#testing
|
||||||
|
|||||||
@ -73,8 +73,8 @@ void MqttHandleHassClass::publishConfig()
|
|||||||
publishInverterButton(inv, "Turn Inverter On", "mdi:power-plug", "config", "", "cmd/power", "1");
|
publishInverterButton(inv, "Turn Inverter On", "mdi:power-plug", "config", "", "cmd/power", "1");
|
||||||
publishInverterButton(inv, "Restart Inverter", "", "config", "restart", "cmd/restart", "1");
|
publishInverterButton(inv, "Restart Inverter", "", "config", "restart", "cmd/restart", "1");
|
||||||
|
|
||||||
publishInverterNumber(inv, "Limit NonPersistent Relative", "mdi:speedometer", "config", "cmd/limit_nonpersistent_relative", "status/limit_relative", "%", 0, 100);
|
publishInverterNumber(inv, "Limit NonPersistent Relative", "mdi:speedometer", "config", "cmd/limit_nonpersistent_relative", "status/limit_relative", "%", 0, 100, 0.1);
|
||||||
publishInverterNumber(inv, "Limit Persistent Relative", "mdi:speedometer", "config", "cmd/limit_persistent_relative", "status/limit_relative", "%", 0, 100);
|
publishInverterNumber(inv, "Limit Persistent Relative", "mdi:speedometer", "config", "cmd/limit_persistent_relative", "status/limit_relative", "%", 0, 100, 0.1);
|
||||||
|
|
||||||
publishInverterNumber(inv, "Limit NonPersistent Absolute", "mdi:speedometer", "config", "cmd/limit_nonpersistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT);
|
publishInverterNumber(inv, "Limit NonPersistent Absolute", "mdi:speedometer", "config", "cmd/limit_nonpersistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT);
|
||||||
publishInverterNumber(inv, "Limit Persistent Absolute", "mdi:speedometer", "config", "cmd/limit_persistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT);
|
publishInverterNumber(inv, "Limit Persistent Absolute", "mdi:speedometer", "config", "cmd/limit_persistent_absolute", "status/limit_absolute", "W", 0, MAX_INVERTER_LIMIT);
|
||||||
@ -215,7 +215,7 @@ void MqttHandleHassClass::publishInverterButton(std::shared_ptr<InverterAbstract
|
|||||||
void MqttHandleHassClass::publishInverterNumber(
|
void MqttHandleHassClass::publishInverterNumber(
|
||||||
std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category,
|
std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category,
|
||||||
const char* commandTopic, const char* stateTopic, const char* unitOfMeasure,
|
const char* commandTopic, const char* stateTopic, const char* unitOfMeasure,
|
||||||
const int16_t min, const int16_t max)
|
const int16_t min, const int16_t max, float step)
|
||||||
{
|
{
|
||||||
const String serial = inv->serialString();
|
const String serial = inv->serialString();
|
||||||
|
|
||||||
@ -243,6 +243,7 @@ void MqttHandleHassClass::publishInverterNumber(
|
|||||||
root["unit_of_meas"] = unitOfMeasure;
|
root["unit_of_meas"] = unitOfMeasure;
|
||||||
root["min"] = min;
|
root["min"] = min;
|
||||||
root["max"] = max;
|
root["max"] = max;
|
||||||
|
root["step"] = step;
|
||||||
|
|
||||||
createInverterInfo(root, inv);
|
createInverterInfo(root, inv);
|
||||||
|
|
||||||
|
|||||||
@ -183,7 +183,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
char* strlimit = new char[len + 1];
|
char* strlimit = new char[len + 1];
|
||||||
memcpy(strlimit, payload, len);
|
memcpy(strlimit, payload, len);
|
||||||
strlimit[len] = '\0';
|
strlimit[len] = '\0';
|
||||||
const int32_t payload_val = strtol(strlimit, NULL, 10);
|
const float payload_val = strtof(strlimit, NULL);
|
||||||
delete[] strlimit;
|
delete[] strlimit;
|
||||||
|
|
||||||
if (payload_val < 0) {
|
if (payload_val < 0) {
|
||||||
@ -193,17 +193,17 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
|
|
||||||
if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
|
if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_RELATIVE)) {
|
||||||
// Set inverter limit relative persistent
|
// Set inverter limit relative persistent
|
||||||
MessageOutput.printf("Limit Persistent: %d %%\r\n", payload_val);
|
MessageOutput.printf("Limit Persistent: %.1f %%\r\n", payload_val);
|
||||||
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativPersistent);
|
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativPersistent);
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
|
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_PERSISTENT_ABSOLUTE)) {
|
||||||
// Set inverter limit absolute persistent
|
// Set inverter limit absolute persistent
|
||||||
MessageOutput.printf("Limit Persistent: %d W\r\n", payload_val);
|
MessageOutput.printf("Limit Persistent: %.1f W\r\n", payload_val);
|
||||||
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutPersistent);
|
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutPersistent);
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
|
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_RELATIVE)) {
|
||||||
// Set inverter limit relative non persistent
|
// Set inverter limit relative non persistent
|
||||||
MessageOutput.printf("Limit Non-Persistent: %d %%\r\n", payload_val);
|
MessageOutput.printf("Limit Non-Persistent: %.1f %%\r\n", payload_val);
|
||||||
if (!properties.retain) {
|
if (!properties.retain) {
|
||||||
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativNonPersistent);
|
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::RelativNonPersistent);
|
||||||
} else {
|
} else {
|
||||||
@ -212,7 +212,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE)) {
|
} else if (!strcmp(setting, TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE)) {
|
||||||
// Set inverter limit absolute non persistent
|
// Set inverter limit absolute non persistent
|
||||||
MessageOutput.printf("Limit Non-Persistent: %d W\r\n", payload_val);
|
MessageOutput.printf("Limit Non-Persistent: %.1f W\r\n", payload_val);
|
||||||
if (!properties.retain) {
|
if (!properties.retain) {
|
||||||
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutNonPersistent);
|
inv->sendActivePowerControlRequest(payload_val, PowerLimitControlType::AbsolutNonPersistent);
|
||||||
} else {
|
} else {
|
||||||
@ -221,8 +221,8 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
|
} else if (!strcmp(setting, TOPIC_SUB_POWER)) {
|
||||||
// Turn inverter on or off
|
// Turn inverter on or off
|
||||||
MessageOutput.printf("Set inverter power to: %d\r\n", payload_val);
|
MessageOutput.printf("Set inverter power to: %d\r\n", static_cast<int32_t>(payload_val));
|
||||||
inv->sendPowerControlRequest(payload_val > 0);
|
inv->sendPowerControlRequest(static_cast<int32_t>(payload_val) > 0);
|
||||||
|
|
||||||
} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
|
} else if (!strcmp(setting, TOPIC_SUB_RESTART)) {
|
||||||
// Restart inverter
|
// Restart inverter
|
||||||
@ -230,7 +230,7 @@ void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessagePro
|
|||||||
if (!properties.retain && payload_val == 1) {
|
if (!properties.retain && payload_val == 1) {
|
||||||
inv->sendRestartControlRequest();
|
inv->sendRestartControlRequest();
|
||||||
} else {
|
} else {
|
||||||
MessageOutput.println("Ignored because retained");
|
MessageOutput.println("Ignored because retained or numeric value not '1'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,8 @@ void NetworkSettingsClass::init(Scheduler& scheduler)
|
|||||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||||
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
||||||
|
|
||||||
|
WiFi.disconnect(true, true);
|
||||||
|
|
||||||
WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1));
|
WiFi.onEvent(std::bind(&NetworkSettingsClass::NetworkEvent, this, _1));
|
||||||
setupMode();
|
setupMode();
|
||||||
|
|
||||||
@ -77,7 +79,8 @@ void NetworkSettingsClass::NetworkEvent(const WiFiEvent_t event)
|
|||||||
MessageOutput.println("WiFi disconnected");
|
MessageOutput.println("WiFi disconnected");
|
||||||
if (_networkMode == network_mode::WiFi) {
|
if (_networkMode == network_mode::WiFi) {
|
||||||
MessageOutput.println("Try reconnecting");
|
MessageOutput.println("Try reconnecting");
|
||||||
WiFi.reconnect();
|
WiFi.disconnect(true, false);
|
||||||
|
WiFi.begin();
|
||||||
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
raiseEvent(network_event::NETWORK_DISCONNECTED);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -83,7 +83,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["limit_value"].as<uint16_t>() > MAX_INVERTER_LIMIT) {
|
if (root["limit_value"].as<float>() > MAX_INVERTER_LIMIT) {
|
||||||
retMsg["message"] = "Limit must between 0 and " STR(MAX_INVERTER_LIMIT) "!";
|
retMsg["message"] = "Limit must between 0 and " STR(MAX_INVERTER_LIMIT) "!";
|
||||||
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
retMsg["code"] = WebApiError::LimitInvalidLimit;
|
||||||
retMsg["param"]["max"] = MAX_INVERTER_LIMIT;
|
retMsg["param"]["max"] = MAX_INVERTER_LIMIT;
|
||||||
@ -102,7 +102,7 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t limit = root["limit_value"].as<uint16_t>();
|
float limit = root["limit_value"].as<float>();
|
||||||
PowerLimitControlType type = root["limit_type"].as<PowerLimitControlType>();
|
PowerLimitControlType type = root["limit_type"].as<PowerLimitControlType>();
|
||||||
|
|
||||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
|
|||||||
@ -42,7 +42,7 @@ void WebApiWebappClass::responseBinaryDataWithETagCache(AsyncWebServerRequest *r
|
|||||||
if (eTagMatch) {
|
if (eTagMatch) {
|
||||||
response = request->beginResponse(304);
|
response = request->beginResponse(304);
|
||||||
} else {
|
} else {
|
||||||
response = request->beginResponse_P(200, contentType, content, len);
|
response = request->beginResponse(200, contentType, content, len);
|
||||||
if (contentEncoding.length() > 0) {
|
if (contentEncoding.length() > 0) {
|
||||||
response->addHeader("Content-Encoding", contentEncoding);
|
response->addHeader("Content-Encoding", contentEncoding);
|
||||||
}
|
}
|
||||||
|
|||||||
8
webapp/.prettierrc.json
Normal file
8
webapp/.prettierrc.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": true,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 120,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# OpenDTU web frontend
|
# 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.
|
You can run the webapp locally with `yarn dev`. If you enter the IP of your ESP in the `vite.user.ts` beforehand (template can be found in `vite.config.ts`), 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
|
## Project Setup
|
||||||
|
|
||||||
|
|||||||
8
webapp/env.d.ts
vendored
8
webapp/env.d.ts
vendored
@ -1 +1,9 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
import { Router, Route } from 'vue-router'
|
||||||
|
declare module '@vue/runtime-core' {
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
$router: Router
|
||||||
|
$route: Route
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -9,7 +9,8 @@
|
|||||||
"preview": "vite preview --port 4173",
|
"preview": "vite preview --port 4173",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "vue-tsc --noEmit",
|
"type-check": "vue-tsc --noEmit",
|
||||||
"lint": "eslint ."
|
"lint": "eslint .",
|
||||||
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
@ -18,31 +19,32 @@
|
|||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"sortablejs": "^1.15.2",
|
"sortablejs": "^1.15.2",
|
||||||
"spark-md5": "^3.0.2",
|
"spark-md5": "^3.0.2",
|
||||||
"vue": "^3.4.31",
|
"vue": "^3.4.35",
|
||||||
"vue-i18n": "^9.13.1",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-router": "^4.4.0"
|
"vue-router": "^4.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||||
"@tsconfig/node18": "^18.2.4",
|
"@tsconfig/node18": "^18.2.4",
|
||||||
"@types/bootstrap": "^5.2.10",
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/node": "^20.14.9",
|
"@types/node": "^22.1.0",
|
||||||
"@types/pulltorefreshjs": "^0.1.7",
|
"@types/pulltorefreshjs": "^0.1.7",
|
||||||
"@types/sortablejs": "^1.15.8",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@types/spark-md5": "^3.0.4",
|
"@types/spark-md5": "^3.0.4",
|
||||||
"@vitejs/plugin-vue": "^5.0.5",
|
"@vitejs/plugin-vue": "^5.1.2",
|
||||||
"@vue/eslint-config-typescript": "^13.0.0",
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
"@vue/tsconfig": "^0.5.1",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"eslint": "^9.6.0",
|
"eslint": "^9.8.0",
|
||||||
"eslint-plugin-vue": "^9.26.0",
|
"eslint-plugin-vue": "^9.27.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
"pulltorefreshjs": "^0.1.22",
|
"pulltorefreshjs": "^0.1.22",
|
||||||
"sass": "^1.77.6",
|
"sass": "^1.77.6",
|
||||||
"terser": "^5.31.1",
|
"terser": "^5.31.3",
|
||||||
"typescript": "^5.5.2",
|
"typescript": "^5.5.4",
|
||||||
"vite": "^5.3.2",
|
"vite": "^5.3.5",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-css-injected-by-js": "^3.5.1",
|
"vite-plugin-css-injected-by-js": "^3.5.1",
|
||||||
"vue-tsc": "^2.0.22"
|
"vue-tsc": "^2.0.29"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import NavBar from "./components/NavBar.vue";
|
import NavBar from './components/NavBar.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "App",
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
NavBar,
|
NavBar,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,19 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="{'container-xxl': !isWideScreen,
|
<div :class="{ 'container-xxl': !isWideScreen, 'container-fluid': isWideScreen }" role="main">
|
||||||
'container-fluid': isWideScreen}" role="main">
|
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-11">
|
<div class="col-sm-11">
|
||||||
<h1>{{ title }}
|
<h1>
|
||||||
<span v-if="showWebSocket" :class="{
|
{{ title }}
|
||||||
'onlineMarker': isWebsocketConnected,
|
<span
|
||||||
'offlineMarker': !isWebsocketConnected,
|
v-if="showWebSocket"
|
||||||
}"></span>
|
:class="{
|
||||||
|
onlineMarker: isWebsocketConnected,
|
||||||
|
offlineMarker: !isWebsocketConnected,
|
||||||
|
}"
|
||||||
|
></span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-1" v-if="showReload">
|
<div class="col-sm-1" v-if="showReload">
|
||||||
<button type="button" class="float-end btn btn-outline-primary"
|
<button
|
||||||
@click="$emit('reload')" v-tooltip :title="$t('base.Reload')" ><BIconArrowClockwise /></button>
|
type="button"
|
||||||
|
class="float-end btn btn-outline-primary"
|
||||||
|
@click="$emit('reload')"
|
||||||
|
v-tooltip
|
||||||
|
:title="$t('base.Reload')"
|
||||||
|
>
|
||||||
|
<BIconArrowClockwise />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -48,7 +58,7 @@ export default defineComponent({
|
|||||||
showReload: { type: Boolean, required: false, default: false },
|
showReload: { type: Boolean, required: false, default: false },
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
console.log("init");
|
console.log('init');
|
||||||
PullToRefresh.init({
|
PullToRefresh.init({
|
||||||
mainElement: 'body', // above which element?
|
mainElement: 'body', // above which element?
|
||||||
instructionsPullToRefresh: this.$t('base.Pull'),
|
instructionsPullToRefresh: this.$t('base.Pull'),
|
||||||
@ -56,11 +66,11 @@ export default defineComponent({
|
|||||||
instructionsRefreshing: this.$t('base.Refreshing'),
|
instructionsRefreshing: this.$t('base.Refreshing'),
|
||||||
onRefresh: () => {
|
onRefresh: () => {
|
||||||
this.$emit('reload');
|
this.$emit('reload');
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
console.log("destroy");
|
console.log('destroy');
|
||||||
PullToRefresh.destroyAll();
|
PullToRefresh.destroyAll();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -100,13 +110,15 @@ export default defineComponent({
|
|||||||
margin: -12px 0 0 -12px;
|
margin: -12px 0 0 -12px;
|
||||||
border: 1px solid #00bb00;
|
border: 1px solid #00bb00;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
box-shadow: 0 0 4px #00bb00, inset 0 0 4px rgb(56, 111, 169);
|
box-shadow:
|
||||||
|
0 0 4px #00bb00,
|
||||||
|
inset 0 0 4px rgb(56, 111, 169);
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
animation: online 2.5s ease-in-out infinite;
|
animation: online 2.5s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
@keyframes online {
|
@keyframes online {
|
||||||
0% {
|
0% {
|
||||||
transform: scale(.1);
|
transform: scale(0.1);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,44 +1,50 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="isAlertVisible" ref="element" class="alert" role="alert" :class="classes">
|
<div v-if="isAlertVisible" ref="element" class="alert" role="alert" :class="classes">
|
||||||
<slot />
|
<slot />
|
||||||
<button v-if="dismissible" type="button" class="btn-close" data-bs-dismiss="alert" :aria-label="dismissLabel"
|
<button
|
||||||
@click="dismissClicked" />
|
v-if="dismissible"
|
||||||
|
type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="alert"
|
||||||
|
:aria-label="dismissLabel"
|
||||||
|
@click="dismissClicked"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Alert from "bootstrap/js/dist/alert";
|
import Alert from 'bootstrap/js/dist/alert';
|
||||||
import { computed, defineComponent, onBeforeUnmount, ref, watch } from "vue";
|
import { computed, defineComponent, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
|
|
||||||
export const toInteger = (value: number, defaultValue = NaN) => {
|
export const toInteger = (value: number, defaultValue = NaN) => {
|
||||||
return Number.isInteger(value) ? value : defaultValue;
|
return Number.isInteger(value) ? value : defaultValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "BootstrapAlert",
|
name: 'BootstrapAlert',
|
||||||
props: {
|
props: {
|
||||||
dismissLabel: { type: String, default: "Close" },
|
dismissLabel: { type: String, default: 'Close' },
|
||||||
dismissible: { type: Boolean, default: false },
|
dismissible: { type: Boolean, default: false },
|
||||||
fade: { type: Boolean, default: false },
|
fade: { type: Boolean, default: false },
|
||||||
modelValue: { type: [Boolean, Number], default: false },
|
modelValue: { type: [Boolean, Number], default: false },
|
||||||
show: { type: Boolean, default: false },
|
show: { type: Boolean, default: false },
|
||||||
variant: { type: String, default: "info" },
|
variant: { type: String, default: 'info' },
|
||||||
},
|
},
|
||||||
emits: ["dismissed", "dismiss-count-down", "update:modelValue"],
|
emits: ['dismissed', 'dismiss-count-down', 'update:modelValue'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const element = ref<HTMLElement>();
|
const element = ref<HTMLElement>();
|
||||||
const instance = ref<Alert>();
|
const instance = ref<Alert>();
|
||||||
const classes = computed(() => ({
|
const classes = computed(() => ({
|
||||||
[`alert-${props.variant}`]: props.variant,
|
[`alert-${props.variant}`]: props.variant,
|
||||||
show: props.modelValue,
|
show: props.modelValue,
|
||||||
"alert-dismissible": props.dismissible,
|
'alert-dismissible': props.dismissible,
|
||||||
fade: props.modelValue,
|
fade: props.modelValue,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let _countDownTimeout: number | undefined = 0;
|
let _countDownTimeout: number | undefined = 0;
|
||||||
|
|
||||||
const parseCountDown = (value: boolean | number) => {
|
const parseCountDown = (value: boolean | number) => {
|
||||||
if (typeof value === "boolean") {
|
if (typeof value === 'boolean') {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,9 +59,12 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const countDown = ref();
|
const countDown = ref();
|
||||||
watch(() => props.modelValue, () => {
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
() => {
|
||||||
countDown.value = parseCountDown(props.modelValue);
|
countDown.value = parseCountDown(props.modelValue);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const isAlertVisible = computed(() => props.modelValue || props.show);
|
const isAlertVisible = computed(() => props.modelValue || props.show);
|
||||||
|
|
||||||
@ -85,12 +94,12 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const dismissClicked = () => {
|
const dismissClicked = () => {
|
||||||
if (typeof props.modelValue === "boolean") {
|
if (typeof props.modelValue === 'boolean') {
|
||||||
emit("update:modelValue", false);
|
emit('update:modelValue', false);
|
||||||
} else {
|
} else {
|
||||||
emit("update:modelValue", 0);
|
emit('update:modelValue', 0);
|
||||||
}
|
}
|
||||||
emit("dismissed");
|
emit('dismissed');
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(() => props.modelValue, handleShowAndModelChanged);
|
watch(() => props.modelValue, handleShowAndModelChanged);
|
||||||
@ -98,10 +107,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
watch(countDown, (newValue) => {
|
watch(countDown, (newValue) => {
|
||||||
clearCountDownInterval();
|
clearCountDownInterval();
|
||||||
if (typeof props.modelValue === "boolean") return;
|
if (typeof props.modelValue === 'boolean') return;
|
||||||
emit("dismiss-count-down", newValue);
|
emit('dismiss-count-down', newValue);
|
||||||
if (newValue === 0 && props.modelValue > 0) emit("dismissed");
|
if (newValue === 0 && props.modelValue > 0) emit('dismissed');
|
||||||
if (props.modelValue !== newValue) emit("update:modelValue", newValue);
|
if (props.modelValue !== newValue) emit('update:modelValue', newValue);
|
||||||
if (newValue > 0) {
|
if (newValue > 0) {
|
||||||
_countDownTimeout = setTimeout(() => {
|
_countDownTimeout = setTimeout(() => {
|
||||||
countDown.value--;
|
countDown.value--;
|
||||||
|
|||||||
@ -12,10 +12,10 @@ import { defineComponent } from 'vue';
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
'text': String,
|
text: String,
|
||||||
'textVariant': String,
|
textVariant: String,
|
||||||
'addSpace': Boolean,
|
addSpace: Boolean,
|
||||||
'centerContent': Boolean,
|
centerContent: Boolean,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<BootstrapAlert :show="!devInfoList.valid_data">
|
<BootstrapAlert :show="!devInfoList.valid_data">
|
||||||
<h4 class="alert-heading">
|
<h4 class="alert-heading"><BIconInfoSquare class="fs-2" /> {{ $t('devinfo.NoInfo') }}</h4>
|
||||||
<BIconInfoSquare class="fs-2" /> {{ $t('devinfo.NoInfo') }}
|
{{ $t('devinfo.NoInfoLong') }}
|
||||||
</h4>{{ $t('devinfo.NoInfoLong') }}
|
|
||||||
</BootstrapAlert>
|
</BootstrapAlert>
|
||||||
<table v-if="devInfoList.valid_data" class="table table-hover">
|
<table v-if="devInfoList.valid_data" class="table table-hover">
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -53,7 +52,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import type { DevInfoStatus } from "@/types/DevInfoStatus";
|
import type { DevInfoStatus } from '@/types/DevInfoStatus';
|
||||||
import { BIconInfoSquare } from 'bootstrap-icons-vue';
|
import { BIconInfoSquare } from 'bootstrap-icons-vue';
|
||||||
import { defineComponent, type PropType } from 'vue';
|
import { defineComponent, type PropType } from 'vue';
|
||||||
|
|
||||||
@ -70,20 +69,20 @@ export default defineComponent({
|
|||||||
return (value: number) => {
|
return (value: number) => {
|
||||||
const version_major = Math.floor(value / 10000);
|
const version_major = Math.floor(value / 10000);
|
||||||
const version_minor = Math.floor((value - version_major * 10000) / 100);
|
const version_minor = Math.floor((value - version_major * 10000) / 100);
|
||||||
const version_patch = Math.floor((value - version_major * 10000 - version_minor * 100));
|
const version_patch = Math.floor(value - version_major * 10000 - version_minor * 100);
|
||||||
return version_major + "." + version_minor + "." + version_patch;
|
return version_major + '.' + version_minor + '.' + version_patch;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
productionYear() {
|
productionYear() {
|
||||||
return () => {
|
return () => {
|
||||||
return ((parseInt(this.devInfoList.serial, 16) >> (7 * 4)) & 0xF) + 2014;
|
return ((parseInt(this.devInfoList.serial, 16) >> (7 * 4)) & 0xf) + 2014;
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
productionWeek() {
|
productionWeek() {
|
||||||
return () => {
|
return () => {
|
||||||
return ((parseInt(this.devInfoList.serial, 16) >> (5 * 4)) & 0xFF).toString(16);
|
return ((parseInt(this.devInfoList.serial, 16) >> (5 * 4)) & 0xff).toString(16);
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -17,10 +17,16 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('firmwareinfo.FirmwareVersion') }}</th>
|
<th>{{ $t('firmwareinfo.FirmwareVersion') }}</th>
|
||||||
<td><a :href="versionInfoUrl"
|
<td>
|
||||||
target="_blank" v-tooltip :title="$t('firmwareinfo.FirmwareVersionHint')">
|
<a
|
||||||
|
:href="versionInfoUrl"
|
||||||
|
target="_blank"
|
||||||
|
v-tooltip
|
||||||
|
:title="$t('firmwareinfo.FirmwareVersionHint')"
|
||||||
|
>
|
||||||
{{ systemStatus.git_hash }}
|
{{ systemStatus.git_hash }}
|
||||||
</a></td>
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('firmwareinfo.PioEnv') }}</th>
|
<th>{{ $t('firmwareinfo.PioEnv') }}</th>
|
||||||
@ -30,15 +36,31 @@
|
|||||||
<th>{{ $t('firmwareinfo.FirmwareUpdate') }}</th>
|
<th>{{ $t('firmwareinfo.FirmwareUpdate') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<div class="form-check form-check-inline form-switch">
|
<div class="form-check form-check-inline form-switch">
|
||||||
<input v-model="modelAllowVersionInfo" class="form-check-input" type="checkbox" role="switch" v-tooltip :title="$t('firmwareinfo.FrmwareUpdateAllow')" />
|
<input
|
||||||
|
v-model="modelAllowVersionInfo"
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
role="switch"
|
||||||
|
v-tooltip
|
||||||
|
:title="$t('firmwareinfo.FrmwareUpdateAllow')"
|
||||||
|
/>
|
||||||
<label class="form-check-label">
|
<label class="form-check-label">
|
||||||
<a v-if="modelAllowVersionInfo && systemStatus.update_url !== undefined" :href="systemStatus.update_url" target="_blank" v-tooltip
|
<a
|
||||||
:title="$t('firmwareinfo.FirmwareUpdateHint')">
|
v-if="modelAllowVersionInfo && systemStatus.update_url !== undefined"
|
||||||
|
:href="systemStatus.update_url"
|
||||||
|
target="_blank"
|
||||||
|
v-tooltip
|
||||||
|
:title="$t('firmwareinfo.FirmwareUpdateHint')"
|
||||||
|
>
|
||||||
<span class="badge" :class="systemStatus.update_status">
|
<span class="badge" :class="systemStatus.update_status">
|
||||||
{{ systemStatus.update_text }}
|
{{ systemStatus.update_text }}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<span v-else-if="modelAllowVersionInfo" class="badge" :class="systemStatus.update_status">
|
<span
|
||||||
|
v-else-if="modelAllowVersionInfo"
|
||||||
|
class="badge"
|
||||||
|
:class="systemStatus.update_status"
|
||||||
|
>
|
||||||
{{ systemStatus.update_text }}
|
{{ systemStatus.update_text }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@ -59,7 +81,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('firmwareinfo.Uptime') }}</th>
|
<th>{{ $t('firmwareinfo.Uptime') }}</th>
|
||||||
<td>{{ $t('firmwareinfo.UptimeValue', timeInHours(systemStatus.uptime)) }}</td>
|
<td>
|
||||||
|
{{ $t('firmwareinfo.UptimeValue', timeInHours(systemStatus.uptime)) }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -101,7 +125,7 @@ export default defineComponent({
|
|||||||
return 'https://github.com/tbnobody/OpenDTU/commits/' + this.systemStatus.git_hash;
|
return 'https://github.com/tbnobody/OpenDTU/commits/' + this.systemStatus.git_hash;
|
||||||
}
|
}
|
||||||
return 'https://github.com/tbnobody/OpenDTU/releases/tag/' + this.systemStatus.git_hash;
|
return 'https://github.com/tbnobody/OpenDTU/releases/tag/' + this.systemStatus.git_hash;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<hr class="border border-3 opacity-75">
|
<hr class="border border-3 opacity-75" />
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||||
<button type="button" class="btn btn-secondary" @click="$emit('reload')">{{ $t('base.Cancel') }}</button>
|
<button type="button" class="btn btn-secondary" @click="$emit('reload')">
|
||||||
|
{{ $t('base.Cancel') }}
|
||||||
|
</button>
|
||||||
<button type="submit" class="btn btn-primary">{{ $t('base.Save') }}</button>
|
<button type="submit" class="btn btn-primary">{{ $t('base.Save') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -3,8 +3,14 @@
|
|||||||
<th>{{ name }}</th>
|
<th>{{ name }}</th>
|
||||||
<td>
|
<td>
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<div class="progress-bar" role="progressbar" :style="{ width: getPercent() + '%' }"
|
<div
|
||||||
v-bind:aria-valuenow="getPercent()" aria-valuemin="0" aria-valuemax="100">
|
class="progress-bar"
|
||||||
|
role="progressbar"
|
||||||
|
:style="{ width: getPercent() + '%' }"
|
||||||
|
v-bind:aria-valuenow="getPercent()"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
>
|
||||||
{{ $n(getPercent() / 100, 'percent') }}
|
{{ $n(getPercent() / 100, 'percent') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<BootstrapAlert :show="!hasValidData">
|
<BootstrapAlert :show="!hasValidData">
|
||||||
<h4 class="alert-heading">
|
<h4 class="alert-heading"><BIconInfoSquare class="fs-2" /> {{ $t('gridprofile.NoInfo') }}</h4>
|
||||||
<BIconInfoSquare class="fs-2" /> {{ $t('gridprofile.NoInfo') }}
|
{{ $t('gridprofile.NoInfoLong') }}
|
||||||
</h4>{{ $t('gridprofile.NoInfoLong') }}
|
|
||||||
</BootstrapAlert>
|
</BootstrapAlert>
|
||||||
|
|
||||||
<template v-if="hasValidData">
|
<template v-if="hasValidData">
|
||||||
@ -22,7 +21,14 @@
|
|||||||
<div class="accordion" id="accordionProfile">
|
<div class="accordion" id="accordionProfile">
|
||||||
<div class="accordion-item" v-for="(section, index) in gridProfileList.sections" :key="index">
|
<div class="accordion-item" v-for="(section, index) in gridProfileList.sections" :key="index">
|
||||||
<h2 class="accordion-header">
|
<h2 class="accordion-header">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" :data-bs-target="`#collapse${index}`" aria-expanded="true" :aria-controls="`collapse${index}`">
|
<button
|
||||||
|
class="accordion-button collapsed"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
:data-bs-target="`#collapse${index}`"
|
||||||
|
aria-expanded="true"
|
||||||
|
:aria-controls="`collapse${index}`"
|
||||||
|
>
|
||||||
{{ section.name }}
|
{{ section.name }}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
@ -37,7 +43,11 @@
|
|||||||
{{ $n(value.v, 'decimal') }} {{ value.u }}
|
{{ $n(value.v, 'decimal') }} {{ value.u }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<StatusBadge :status="value.v==1" true_text="gridprofile.Enabled" false_text="gridprofile.Disabled"/>
|
<StatusBadge
|
||||||
|
:status="value.v == 1"
|
||||||
|
true_text="gridprofile.Enabled"
|
||||||
|
false_text="gridprofile.Disabled"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -53,7 +63,14 @@
|
|||||||
<div class="accordion" id="accordionDev">
|
<div class="accordion" id="accordionDev">
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header">
|
<h2 class="accordion-header">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDev" aria-expanded="true" aria-controls="collapseDev">
|
<button
|
||||||
|
class="accordion-button collapsed"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseDev"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="collapseDev"
|
||||||
|
>
|
||||||
{{ $t('gridprofile.GridprofileSupport') }}
|
{{ $t('gridprofile.GridprofileSupport') }}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
@ -62,7 +79,8 @@
|
|||||||
<BootstrapAlert :show="true" variant="danger">
|
<BootstrapAlert :show="true" variant="danger">
|
||||||
<h4 class="info-heading">
|
<h4 class="info-heading">
|
||||||
<BIconInfoSquare class="fs-2" /> {{ $t('gridprofile.GridprofileSupport') }}
|
<BIconInfoSquare class="fs-2" /> {{ $t('gridprofile.GridprofileSupport') }}
|
||||||
</h4><div v-html="$t('gridprofile.GridprofileSupportLong')"></div>
|
</h4>
|
||||||
|
<div v-html="$t('gridprofile.GridprofileSupportLong')"></div>
|
||||||
</BootstrapAlert>
|
</BootstrapAlert>
|
||||||
<samp>
|
<samp>
|
||||||
{{ rawContent() }}
|
{{ rawContent() }}
|
||||||
@ -71,15 +89,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import type { GridProfileRawdata } from '@/types/GridProfileRawdata';
|
import type { GridProfileRawdata } from '@/types/GridProfileRawdata';
|
||||||
import type { GridProfileStatus } from "@/types/GridProfileStatus";
|
import type { GridProfileStatus } from '@/types/GridProfileStatus';
|
||||||
import { BIconInfoSquare } from 'bootstrap-icons-vue';
|
import { BIconInfoSquare } from 'bootstrap-icons-vue';
|
||||||
import { defineComponent, type PropType } from 'vue';
|
import { defineComponent, type PropType } from 'vue';
|
||||||
import StatusBadge from './StatusBadge.vue';
|
import StatusBadge from './StatusBadge.vue';
|
||||||
@ -97,12 +113,14 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
rawContent() {
|
rawContent() {
|
||||||
return () => {
|
return () => {
|
||||||
return this.gridProfileRawList.raw.map(function (x) {
|
return this.gridProfileRawList.raw
|
||||||
|
.map(function (x) {
|
||||||
let y = x.toString(16); // to hex
|
let y = x.toString(16); // to hex
|
||||||
y = ("00" + y).substr(-2); // zero-pad to 2-digits
|
y = ('00' + y).substr(-2); // zero-pad to 2-digits
|
||||||
return y
|
return y;
|
||||||
}).join(' ');
|
})
|
||||||
}
|
.join(' ');
|
||||||
|
};
|
||||||
},
|
},
|
||||||
hasValidData() {
|
hasValidData() {
|
||||||
return this.gridProfileRawList.raw.reduce((sum, x) => sum + x, 0) > 0;
|
return this.gridProfileRawList.raw.reduce((sum, x) => sum + x, 0) > 0;
|
||||||
|
|||||||
@ -9,7 +9,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('heapdetails.LargestFreeBlock') }}</th>
|
<th>{{ $t('heapdetails.LargestFreeBlock') }}</th>
|
||||||
<td>{{ $n(Math.round(systemStatus.heap_max_block / 1024), 'kilobyte') }}</td>
|
<td>
|
||||||
|
{{ $n(Math.round(systemStatus.heap_max_block / 1024), 'kilobyte') }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('heapdetails.Fragmentation') }}</th>
|
<th>{{ $t('heapdetails.Fragmentation') }}</th>
|
||||||
@ -17,7 +19,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('heapdetails.MaxUsage') }}</th>
|
<th>{{ $t('heapdetails.MaxUsage') }}</th>
|
||||||
<td>{{ $n(Math.round(getMaxUsageAbs() / 1024), 'kilobyte') }} ({{ $n(getMaxUsageRel(), 'percent') }})</td>
|
<td>
|
||||||
|
{{ $n(Math.round(getMaxUsageAbs() / 1024), 'kilobyte') }} ({{
|
||||||
|
$n(getMaxUsageRel(), 'percent')
|
||||||
|
}})
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -48,7 +54,7 @@ export default defineComponent({
|
|||||||
return this.getMaxUsageAbs() / this.systemStatus.heap_total;
|
return this.getMaxUsageAbs() / this.systemStatus.heap_total;
|
||||||
},
|
},
|
||||||
getFragmentation() {
|
getFragmentation() {
|
||||||
return 1 - (this.systemStatus.heap_max_block / this.getFreeHeap());
|
return 1 - this.systemStatus.heap_max_block / this.getFreeHeap();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -17,11 +17,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import type { Hints } from '@/types/LiveDataStatus';
|
import type { Hints } from '@/types/LiveDataStatus';
|
||||||
import {
|
import { BIconBroadcast, BIconClock, BIconExclamationCircle } from 'bootstrap-icons-vue';
|
||||||
BIconBroadcast,
|
|
||||||
BIconClock,
|
|
||||||
BIconExclamationCircle
|
|
||||||
} from 'bootstrap-icons-vue';
|
|
||||||
import { defineComponent, type PropType } from 'vue';
|
import { defineComponent, type PropType } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -36,11 +32,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
gotoTimeSettings() {
|
gotoTimeSettings() {
|
||||||
this.$router.push("/settings/ntp");
|
this.$router.push('/settings/ntp');
|
||||||
},
|
},
|
||||||
gotoPasswordSettings() {
|
gotoPasswordSettings() {
|
||||||
this.$router.push("/settings/security");
|
this.$router.push('/settings/security');
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -8,14 +8,8 @@
|
|||||||
<BIconInfoCircle v-if="tooltip !== undefined" v-tooltip :title="tooltip" />
|
<BIconInfoCircle v-if="tooltip !== undefined" v-tooltip :title="tooltip" />
|
||||||
</label>
|
</label>
|
||||||
<div :class="[wide ? 'col-sm-8' : 'col-sm-10']">
|
<div :class="[wide ? 'col-sm-8' : 'col-sm-10']">
|
||||||
<div v-if="!isTextarea"
|
<div v-if="!isTextarea" :class="{ 'form-check form-switch': isCheckbox, 'input-group': postfix || prefix }">
|
||||||
:class="{'form-check form-switch': isCheckbox,
|
<span v-if="prefix" class="input-group-text" :id="descriptionId">
|
||||||
'input-group': postfix || prefix }"
|
|
||||||
>
|
|
||||||
<span v-if="prefix"
|
|
||||||
class="input-group-text"
|
|
||||||
:id="descriptionId"
|
|
||||||
>
|
|
||||||
{{ prefix }}
|
{{ prefix }}
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
@ -31,10 +25,7 @@
|
|||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:aria-describedby="descriptionId"
|
:aria-describedby="descriptionId"
|
||||||
/>
|
/>
|
||||||
<span v-if="postfix"
|
<span v-if="postfix" class="input-group-text" :id="descriptionId">
|
||||||
class="input-group-text"
|
|
||||||
:id="descriptionId"
|
|
||||||
>
|
|
||||||
{{ postfix }}
|
{{ postfix }}
|
||||||
</span>
|
</span>
|
||||||
<slot />
|
<slot />
|
||||||
@ -63,20 +54,20 @@ export default defineComponent({
|
|||||||
BIconInfoCircle,
|
BIconInfoCircle,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
'modelValue': [String, Number, Boolean, Date],
|
modelValue: [String, Number, Boolean, Date],
|
||||||
'label': String,
|
label: String,
|
||||||
'placeholder': String,
|
placeholder: String,
|
||||||
'type': String,
|
type: String,
|
||||||
'maxlength': String,
|
maxlength: String,
|
||||||
'min': String,
|
min: String,
|
||||||
'max': String,
|
max: String,
|
||||||
'step': String,
|
step: String,
|
||||||
'rows': String,
|
rows: String,
|
||||||
'disabled': Boolean,
|
disabled: Boolean,
|
||||||
'postfix': String,
|
postfix: String,
|
||||||
'prefix': String,
|
prefix: String,
|
||||||
'wide': Boolean,
|
wide: Boolean,
|
||||||
'tooltip': String,
|
tooltip: String,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {};
|
||||||
@ -97,11 +88,13 @@ export default defineComponent({
|
|||||||
// normally, the label is sufficient to build a unique id
|
// normally, the label is sufficient to build a unique id
|
||||||
// if two inputs with the same label text on one page is required,
|
// if two inputs with the same label text on one page is required,
|
||||||
// use a unique placeholder even if it is a checkbox
|
// use a unique placeholder even if it is a checkbox
|
||||||
return this.label?.replace(/[^A-Za-z0-9]/g, '') +
|
return (
|
||||||
(this.placeholder ? this.placeholder.replace(/[^A-Za-z0-9]/g, '') : '');
|
this.label?.replace(/[^A-Za-z0-9]/g, '') +
|
||||||
|
(this.placeholder ? this.placeholder.replace(/[^A-Za-z0-9]/g, '') : '')
|
||||||
|
);
|
||||||
},
|
},
|
||||||
inputId() {
|
inputId() {
|
||||||
return 'input' + this.uniqueLabel
|
return 'input' + this.uniqueLabel;
|
||||||
},
|
},
|
||||||
descriptionId() {
|
descriptionId() {
|
||||||
return 'desc' + this.uniqueLabel;
|
return 'desc' + this.uniqueLabel;
|
||||||
@ -111,7 +104,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
isCheckbox() {
|
isCheckbox() {
|
||||||
return this.type === 'checkbox';
|
return this.type === 'checkbox';
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -14,16 +14,16 @@ export default defineComponent({
|
|||||||
BootstrapAlert,
|
BootstrapAlert,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
'modelValue': { type: [String, Number], required: true },
|
modelValue: { type: [String, Number], required: true },
|
||||||
'id': String,
|
id: String,
|
||||||
'inputClass': String,
|
inputClass: String,
|
||||||
'required': Boolean,
|
required: Boolean,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
inputSerial: "",
|
inputSerial: '',
|
||||||
formatHint: "",
|
formatHint: '',
|
||||||
formatShow: "info",
|
formatShow: 'info',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -45,13 +45,13 @@ export default defineComponent({
|
|||||||
inputSerial: function (val) {
|
inputSerial: function (val) {
|
||||||
const serial = val.toString().toUpperCase(); // Convert to lowercase for case-insensitivity
|
const serial = val.toString().toUpperCase(); // Convert to lowercase for case-insensitivity
|
||||||
|
|
||||||
if (serial == "") {
|
if (serial == '') {
|
||||||
this.formatHint = "";
|
this.formatHint = '';
|
||||||
this.model = "";
|
this.model = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.formatShow = "info";
|
this.formatShow = 'info';
|
||||||
|
|
||||||
// Contains only numbers
|
// Contains only numbers
|
||||||
if (/^1{1}[\dA-F]{11}$/.test(serial)) {
|
if (/^1{1}[\dA-F]{11}$/.test(serial)) {
|
||||||
@ -70,30 +70,31 @@ export default defineComponent({
|
|||||||
if (this.checkHerfChecksum(serial)) {
|
if (this.checkHerfChecksum(serial)) {
|
||||||
this.model = this.convertHerfToHoy(serial);
|
this.model = this.convertHerfToHoy(serial);
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.formatHint = this.$t('inputserial.format_herf_valid', { serial: this.model });
|
this.formatHint = this.$t('inputserial.format_herf_valid', {
|
||||||
|
serial: this.model,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.formatHint = this.$t('inputserial.format_herf_invalid');
|
this.formatHint = this.$t('inputserial.format_herf_invalid');
|
||||||
this.formatShow = "danger";
|
this.formatShow = 'danger';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any other format
|
// Any other format
|
||||||
} else {
|
} else {
|
||||||
this.formatHint = this.$t('inputserial.format_unknown');
|
this.formatHint = this.$t('inputserial.format_unknown');
|
||||||
this.formatShow = "danger";
|
this.formatShow = 'danger';
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
checkHerfChecksum(sn: string) {
|
checkHerfChecksum(sn: string) {
|
||||||
const chars64 = 'HMFLGW5XC301234567899Z67YRT2S8ABCDEFGHJKDVEJ4KQPUALMNPRSTUVWXYNB';
|
const chars64 = 'HMFLGW5XC301234567899Z67YRT2S8ABCDEFGHJKDVEJ4KQPUALMNPRSTUVWXYNB';
|
||||||
|
|
||||||
const checksum = sn.substring(sn.indexOf("-") + 1);
|
const checksum = sn.substring(sn.indexOf('-') + 1);
|
||||||
const serial = sn.substring(0, sn.indexOf("-"));
|
const serial = sn.substring(0, sn.indexOf('-'));
|
||||||
|
|
||||||
const first_char = '1';
|
const first_char = '1';
|
||||||
const i = chars32.indexOf(first_char)
|
const i = chars32.indexOf(first_char);
|
||||||
const sum1: number = Array.from(serial).reduce((sum, c) => sum + c.charCodeAt(0), 0) & 31;
|
const sum1: number = Array.from(serial).reduce((sum, c) => sum + c.charCodeAt(0), 0) & 31;
|
||||||
const sum2: number = Array.from(serial).reduce((sum, c) => sum + chars32.indexOf(c), 0) & 31;
|
const sum2: number = Array.from(serial).reduce((sum, c) => sum + chars32.indexOf(c), 0) & 31;
|
||||||
const ext = first_char + chars64[sum1 + i] + chars64[sum2 + i];
|
const ext = first_char + chars64[sum1 + i] + chars64[sum2 + i];
|
||||||
@ -106,11 +107,11 @@ export default defineComponent({
|
|||||||
for (let i = 0; i < 9; i++) {
|
for (let i = 0; i < 9; i++) {
|
||||||
const pos: bigint = BigInt(chars32.indexOf(sn[i].toUpperCase()));
|
const pos: bigint = BigInt(chars32.indexOf(sn[i].toUpperCase()));
|
||||||
const shift: bigint = BigInt(42 - 5 * i - (i <= 2 ? 0 : 2));
|
const shift: bigint = BigInt(42 - 5 * i - (i <= 2 ? 0 : 2));
|
||||||
sn_int |= (pos << shift);
|
sn_int |= pos << shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sn_int.toString(16);
|
return sn_int.toString(16);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<CardElement :text="$t('interfacenetworkinfo.NetworkInterface', { iface: networkStatus.network_mode })"
|
<CardElement
|
||||||
|
:text="$t('interfacenetworkinfo.NetworkInterface', { iface: networkStatus.network_mode })"
|
||||||
textVariant="text-bg-primary"
|
textVariant="text-bg-primary"
|
||||||
>
|
>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="card" :class="{
|
<div
|
||||||
|
class="card"
|
||||||
|
:class="{
|
||||||
'border-info': channelType == 'AC',
|
'border-info': channelType == 'AC',
|
||||||
'border-secondary': channelType == 'INV'
|
'border-secondary': channelType == 'INV',
|
||||||
}" style="overflow: hidden">
|
}"
|
||||||
|
style="overflow: hidden"
|
||||||
|
>
|
||||||
<div v-if="channelType == 'INV'" class="card-header text-bg-secondary">
|
<div v-if="channelType == 'INV'" class="card-header text-bg-secondary">
|
||||||
{{ $t('inverterchannelinfo.General') }}
|
{{ $t('inverterchannelinfo.General') }}
|
||||||
</div>
|
</div>
|
||||||
@ -22,10 +26,11 @@
|
|||||||
<tr v-for="(property, key) in channelData" :key="`prop-${key}`">
|
<tr v-for="(property, key) in channelData" :key="`prop-${key}`">
|
||||||
<template v-if="key != 'name' && property">
|
<template v-if="key != 'name' && property">
|
||||||
<th scope="row">{{ $t('inverterchannelproperty.' + key) }}</th>
|
<th scope="row">{{ $t('inverterchannelproperty.' + key) }}</th>
|
||||||
<td style="text-align: right; padding-right: 0;">
|
<td style="text-align: right; padding-right: 0">
|
||||||
{{ $n(property.v, 'decimal', {
|
{{
|
||||||
|
$n(property.v, 'decimal', {
|
||||||
minimumFractionDigits: property.d,
|
minimumFractionDigits: property.d,
|
||||||
maximumFractionDigits: property.d
|
maximumFractionDigits: property.d,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -3,10 +3,12 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalYieldTotal')">
|
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalYieldTotal')">
|
||||||
<h2>
|
<h2>
|
||||||
{{ $n(totalData.YieldTotal.v, 'decimal', {
|
{{
|
||||||
|
$n(totalData.YieldTotal.v, 'decimal', {
|
||||||
minimumFractionDigits: totalData.YieldTotal.d,
|
minimumFractionDigits: totalData.YieldTotal.d,
|
||||||
maximumFractionDigits: totalData.YieldTotal.d
|
maximumFractionDigits: totalData.YieldTotal.d,
|
||||||
}) }}
|
})
|
||||||
|
}}
|
||||||
<small class="text-muted">{{ totalData.YieldTotal.u }}</small>
|
<small class="text-muted">{{ totalData.YieldTotal.u }}</small>
|
||||||
</h2>
|
</h2>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
@ -14,10 +16,12 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalYieldDay')">
|
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalYieldDay')">
|
||||||
<h2>
|
<h2>
|
||||||
{{ $n(totalData.YieldDay.v, 'decimal', {
|
{{
|
||||||
|
$n(totalData.YieldDay.v, 'decimal', {
|
||||||
minimumFractionDigits: totalData.YieldDay.d,
|
minimumFractionDigits: totalData.YieldDay.d,
|
||||||
maximumFractionDigits: totalData.YieldDay.d
|
maximumFractionDigits: totalData.YieldDay.d,
|
||||||
}) }}
|
})
|
||||||
|
}}
|
||||||
<small class="text-muted">{{ totalData.YieldDay.u }}</small>
|
<small class="text-muted">{{ totalData.YieldDay.u }}</small>
|
||||||
</h2>
|
</h2>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
@ -25,10 +29,12 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalPower')">
|
<CardElement centerContent textVariant="text-bg-success" :text="$t('invertertotalinfo.TotalPower')">
|
||||||
<h2>
|
<h2>
|
||||||
{{ $n(totalData.Power.v, 'decimal', {
|
{{
|
||||||
|
$n(totalData.Power.v, 'decimal', {
|
||||||
minimumFractionDigits: totalData.Power.d,
|
minimumFractionDigits: totalData.Power.d,
|
||||||
maximumFractionDigits: totalData.Power.d
|
maximumFractionDigits: totalData.Power.d,
|
||||||
}) }}
|
})
|
||||||
|
}}
|
||||||
<small class="text-muted">{{ totalData.Power.u }}</small>
|
<small class="text-muted">{{ totalData.Power.u }}</small>
|
||||||
</h2>
|
</h2>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|||||||
@ -11,20 +11,20 @@ import { defineComponent } from 'vue';
|
|||||||
import { LOCALES } from '@/locales';
|
import { LOCALES } from '@/locales';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "LocaleSwitcher",
|
name: 'LocaleSwitcher',
|
||||||
methods: {
|
methods: {
|
||||||
updateLanguage() {
|
updateLanguage() {
|
||||||
localStorage.setItem("locale", this.$i18n.locale);
|
localStorage.setItem('locale', this.$i18n.locale);
|
||||||
},
|
},
|
||||||
getLocaleName(locale: string): string {
|
getLocaleName(locale: string): string {
|
||||||
return LOCALES.find(i => i.value === locale)?.caption || "";
|
return LOCALES.find((i) => i.value === locale)?.caption || '';
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (localStorage.getItem("locale")) {
|
if (localStorage.getItem('locale')) {
|
||||||
this.$i18n.locale = localStorage.getItem("locale") || "en";
|
this.$i18n.locale = localStorage.getItem('locale') || 'en';
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem("locale", this.$i18n.locale);
|
localStorage.setItem('locale', this.$i18n.locale);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,14 +12,26 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<FsInfo :name="$t('memoryinfo.Heap')" :total="systemStatus.heap_total"
|
<FsInfo
|
||||||
:used="systemStatus.heap_used" />
|
:name="$t('memoryinfo.Heap')"
|
||||||
<FsInfo :name="$t('memoryinfo.PsRam')" :total="systemStatus.psram_total"
|
:total="systemStatus.heap_total"
|
||||||
:used="systemStatus.psram_used" />
|
:used="systemStatus.heap_used"
|
||||||
<FsInfo :name="$t('memoryinfo.LittleFs')" :total="systemStatus.littlefs_total"
|
/>
|
||||||
:used="systemStatus.littlefs_used" />
|
<FsInfo
|
||||||
<FsInfo :name="$t('memoryinfo.Sketch')" :total="systemStatus.sketch_total"
|
:name="$t('memoryinfo.PsRam')"
|
||||||
:used="systemStatus.sketch_used" />
|
:total="systemStatus.psram_total"
|
||||||
|
:used="systemStatus.psram_used"
|
||||||
|
/>
|
||||||
|
<FsInfo
|
||||||
|
:name="$t('memoryinfo.LittleFs')"
|
||||||
|
:total="systemStatus.littlefs_total"
|
||||||
|
:used="systemStatus.littlefs_used"
|
||||||
|
/>
|
||||||
|
<FsInfo
|
||||||
|
:name="$t('memoryinfo.Sketch')"
|
||||||
|
:total="systemStatus.sketch_total"
|
||||||
|
:used="systemStatus.sketch_used"
|
||||||
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -28,7 +40,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import FsInfo from "@/components/FsInfo.vue";
|
import FsInfo from '@/components/FsInfo.vue';
|
||||||
import type { SystemStatus } from '@/types/SystemStatus';
|
import type { SystemStatus } from '@/types/SystemStatus';
|
||||||
import { defineComponent, type PropType } from 'vue';
|
import { defineComponent, type PropType } from 'vue';
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,13 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">{{ title }}</h5>
|
<h5 class="modal-title">{{ title }}</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="getCloseText"
|
<button
|
||||||
@click="close"></button>
|
type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
:aria-label="getCloseText"
|
||||||
|
@click="close"
|
||||||
|
></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="text-center" v-if="loading">
|
<div class="text-center" v-if="loading">
|
||||||
@ -13,14 +18,13 @@
|
|||||||
<span class="visually-hidden">{{ $t('home.Loading') }}</span>
|
<span class="visually-hidden">{{ $t('home.Loading') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<slot v-else>
|
<slot v-else> </slot>
|
||||||
</slot>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<slot name="footer">
|
<slot name="footer"> </slot>
|
||||||
</slot>
|
<button type="button" class="btn btn-secondary" @click="close" data-bs-dismiss="modal">
|
||||||
<button type="button" class="btn btn-secondary" @click="close" data-bs-dismiss="modal">{{
|
{{ getCloseText }}
|
||||||
getCloseText }}</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -32,16 +36,16 @@ import { defineComponent } from 'vue';
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
'modalId': { type: String, required: true },
|
modalId: { type: String, required: true },
|
||||||
'title': { type: String, required: true },
|
title: { type: String, required: true },
|
||||||
'closeText': { type: String, required: false, default: '' },
|
closeText: { type: String, required: false, default: '' },
|
||||||
'small': Boolean,
|
small: Boolean,
|
||||||
'loading': Boolean,
|
loading: Boolean,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
getCloseText() {
|
getCloseText() {
|
||||||
return this.closeText == '' ? this.$t('base.Close') : this.closeText;
|
return this.closeText == '' ? this.$t('base.Close') : this.closeText;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
|
|||||||
@ -1,19 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="navbar navbar-expand-md fixed-top bg-body-tertiary" data-bs-theme="dark">
|
<nav class="navbar navbar-expand-md fixed-top bg-body-tertiary" data-bs-theme="dark">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<router-link @click="onClick" class="navbar-brand" to="/" style="display: flex; height: 30px; padding: 0;">
|
<router-link @click="onClick" class="navbar-brand" to="/" style="display: flex; height: 30px; padding: 0">
|
||||||
<BIconTree v-if="isXmas" width="30" height="30" class="d-inline-block align-text-top text-success" />
|
<BIconTree v-if="isXmas" width="30" height="30" class="d-inline-block align-text-top text-success" />
|
||||||
|
|
||||||
<BIconEgg v-else-if="isEaster" width="30" height="30" class="d-inline-block align-text-top text-info" />
|
<BIconEgg v-else-if="isEaster" width="30" height="30" class="d-inline-block align-text-top text-info" />
|
||||||
|
|
||||||
<BIconSun v-else width="30" height="30" class="d-inline-block align-text-top text-warning" />
|
<BIconSun v-else width="30" height="30" class="d-inline-block align-text-top text-warning" />
|
||||||
|
|
||||||
<span style="margin-left: .5rem">
|
<span style="margin-left: 0.5rem"> OpenDTU </span>
|
||||||
OpenDTU
|
|
||||||
</span>
|
|
||||||
</router-link>
|
</router-link>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup"
|
<button
|
||||||
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
|
class="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarNavAltMarkup"
|
||||||
|
aria-controls="navbarNavAltMarkup"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" ref="navbarCollapse" id="navbarNavAltMarkup">
|
<div class="collapse navbar-collapse" ref="navbarCollapse" id="navbarNavAltMarkup">
|
||||||
@ -22,71 +27,111 @@
|
|||||||
<router-link @click="onClick" class="nav-link" to="/">{{ $t('menu.LiveView') }}</router-link>
|
<router-link @click="onClick" class="nav-link" to="/">{{ $t('menu.LiveView') }}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button"
|
<a
|
||||||
data-bs-toggle="dropdown" aria-expanded="false">
|
class="nav-link dropdown-toggle"
|
||||||
|
href="#"
|
||||||
|
id="navbarScrollingDropdown"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
{{ $t('menu.Settings') }}
|
{{ $t('menu.Settings') }}
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
|
<ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/settings/network">{{ $t('menu.NetworkSettings') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/settings/network">{{
|
||||||
|
$t('menu.NetworkSettings')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/settings/ntp">{{ $t('menu.NTPSettings') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/settings/ntp">{{
|
||||||
|
$t('menu.NTPSettings')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/settings/mqtt">{{ $t('menu.MQTTSettings') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/settings/mqtt">{{
|
||||||
|
$t('menu.MQTTSettings')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/settings/inverter">{{ $t('menu.InverterSettings') }}
|
<router-link @click="onClick" class="dropdown-item" to="/settings/inverter"
|
||||||
|
>{{ $t('menu.InverterSettings') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/settings/security">{{ $t('menu.SecuritySettings') }}
|
<router-link @click="onClick" class="dropdown-item" to="/settings/security"
|
||||||
|
>{{ $t('menu.SecuritySettings') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/settings/dtu">{{ $t('menu.DTUSettings') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/settings/dtu">{{
|
||||||
|
$t('menu.DTUSettings')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/settings/device">{{ $t('menu.DeviceManager') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/settings/device">{{
|
||||||
|
$t('menu.DeviceManager')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<hr class="dropdown-divider" />
|
<hr class="dropdown-divider" />
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/settings/config">{{ $t('menu.ConfigManagement') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/settings/config">{{
|
||||||
|
$t('menu.ConfigManagement')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/firmware/upgrade">{{ $t('menu.FirmwareUpgrade') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/firmware/upgrade">{{
|
||||||
|
$t('menu.FirmwareUpgrade')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/maintenance/reboot">{{ $t('menu.DeviceReboot') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/maintenance/reboot">{{
|
||||||
|
$t('menu.DeviceReboot')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button"
|
<a
|
||||||
data-bs-toggle="dropdown" aria-expanded="false">
|
class="nav-link dropdown-toggle"
|
||||||
|
href="#"
|
||||||
|
id="navbarScrollingDropdown"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
{{ $t('menu.Info') }}
|
{{ $t('menu.Info') }}
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
|
<ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/info/system">{{ $t('menu.System') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/info/system">{{
|
||||||
|
$t('menu.System')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/info/network">{{ $t('menu.Network') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/info/network">{{
|
||||||
|
$t('menu.Network')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/info/ntp">{{ $t('menu.NTP') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/info/ntp">{{
|
||||||
|
$t('menu.NTP')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/info/mqtt">{{ $t('menu.MQTT') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/info/mqtt">{{
|
||||||
|
$t('menu.MQTT')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<hr class="dropdown-divider" />
|
<hr class="dropdown-divider" />
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link @click="onClick" class="dropdown-item" to="/info/console">{{ $t('menu.Console') }}</router-link>
|
<router-link @click="onClick" class="dropdown-item" to="/info/console">{{
|
||||||
|
$t('menu.Console')
|
||||||
|
}}</router-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@ -97,8 +142,12 @@
|
|||||||
<ThemeSwitcher class="me-2" />
|
<ThemeSwitcher class="me-2" />
|
||||||
<form class="d-flex" role="search">
|
<form class="d-flex" role="search">
|
||||||
<LocaleSwitcher class="me-2" />
|
<LocaleSwitcher class="me-2" />
|
||||||
<button v-if="isLogged" class="btn btn-outline-danger" @click="signout">{{ $t('menu.Logout') }}</button>
|
<button v-if="isLogged" class="btn btn-outline-danger" @click="signout">
|
||||||
<button v-if="!isLogged" class="btn btn-outline-success" @click="signin">{{ $t('menu.Login') }}</button>
|
{{ $t('menu.Logout') }}
|
||||||
|
</button>
|
||||||
|
<button v-if="!isLogged" class="btn btn-outline-success" @click="signin">
|
||||||
|
{{ $t('menu.Login') }}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -125,24 +174,24 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
isLogged: this.isLoggedIn(),
|
isLogged: this.isLoggedIn(),
|
||||||
now: {} as Date,
|
now: {} as Date,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$emitter.on("logged-in", () => {
|
this.$emitter.on('logged-in', () => {
|
||||||
this.isLogged = this.isLoggedIn();
|
this.isLogged = this.isLoggedIn();
|
||||||
});
|
});
|
||||||
this.$emitter.on("logged-out", () => {
|
this.$emitter.on('logged-out', () => {
|
||||||
this.isLogged = this.isLoggedIn();
|
this.isLogged = this.isLoggedIn();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.now = new Date();
|
this.now = new Date();
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.now = new Date();
|
this.now = new Date();
|
||||||
}, 10000)
|
}, 10000);
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isXmas() {
|
isXmas() {
|
||||||
return (this.now.getMonth() + 1 == 12 && (this.now.getDate() >= 24 && this.now.getDate() <= 26));
|
return this.now.getMonth() + 1 == 12 && this.now.getDate() >= 24 && this.now.getDate() <= 26;
|
||||||
},
|
},
|
||||||
isEaster() {
|
isEaster() {
|
||||||
const easter = this.getEasterSunday(this.now.getFullYear());
|
const easter = this.getEasterSunday(this.now.getFullYear());
|
||||||
@ -163,11 +212,11 @@ export default defineComponent({
|
|||||||
signout(e: Event) {
|
signout(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.logout();
|
this.logout();
|
||||||
this.$emitter.emit("logged-out");
|
this.$emitter.emit('logged-out');
|
||||||
this.$router.push('/');
|
this.$router.push('/');
|
||||||
},
|
},
|
||||||
onClick() {
|
onClick() {
|
||||||
this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove("show");
|
this.$refs.navbarCollapse && (this.$refs.navbarCollapse as HTMLElement).classList.remove('show');
|
||||||
},
|
},
|
||||||
getEasterSunday(year: number): Date {
|
getEasterSunday(year: number): Date {
|
||||||
const f = Math.floor;
|
const f = Math.floor;
|
||||||
@ -181,7 +230,7 @@ export default defineComponent({
|
|||||||
const day = L + 28 - 31 * f(month / 4);
|
const day = L + 28 - 31 * f(month / 4);
|
||||||
|
|
||||||
return new Date(year, month - 1, day);
|
return new Date(year, month - 1, day);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -11,18 +11,23 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<template v-for="(category) in categories" :key="category">
|
<template v-for="category in categories" :key="category">
|
||||||
<tr v-for="(prop, prop_idx) in properties(category)" :key="prop">
|
<tr v-for="(prop, prop_idx) in properties(category)" :key="prop">
|
||||||
<td v-if="prop_idx == 0" :rowspan="properties(category).length">
|
<td v-if="prop_idx == 0" :rowspan="properties(category).length">
|
||||||
{{ capitalizeFirstLetter(category) }}</td>
|
{{ capitalizeFirstLetter(category) }}
|
||||||
<td :class="{ 'table-danger': !isEqual(category, prop) }">{{ prop }}</td>
|
</td>
|
||||||
|
<td :class="{ 'table-danger': !isEqual(category, prop) }">
|
||||||
|
{{ prop }}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<template v-if="selectedPinAssignment && category in selectedPinAssignment">
|
<template v-if="selectedPinAssignment && category in selectedPinAssignment">
|
||||||
{{ (selectedPinAssignment as any)[category][prop] }}</template>
|
{{ (selectedPinAssignment as any)[category][prop] }}</template
|
||||||
|
>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<template v-if="currentPinAssignment && category in currentPinAssignment">
|
<template v-if="currentPinAssignment && category in currentPinAssignment">
|
||||||
{{ (currentPinAssignment as any)[category][prop] }}</template>
|
{{ (currentPinAssignment as any)[category][prop] }}</template
|
||||||
|
>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
@ -59,7 +64,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
let total: Array<string> = [];
|
let total: Array<string> = [];
|
||||||
total = total.concat(curArray, selArray);
|
total = total.concat(curArray, selArray);
|
||||||
return Array.from(new Set(total)).filter(cat => cat != 'name' && cat != 'links').sort();
|
return Array.from(new Set(total))
|
||||||
|
.filter((cat) => cat != 'name' && cat != 'links')
|
||||||
|
.sort();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -105,6 +112,6 @@ export default defineComponent({
|
|||||||
capitalizeFirstLetter(value: string): string {
|
capitalizeFirstLetter(value: string): string {
|
||||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -4,58 +4,83 @@
|
|||||||
<table class="table table-hover table-condensed">
|
<table class="table table-hover table-condensed">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('radioinfo.Status', { module: "nRF24" }) }}</th>
|
<th>{{ $t('radioinfo.Status', { module: 'nRF24' }) }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="systemStatus.nrf_configured" true_text="radioinfo.Configured" false_text="radioinfo.NotConfigured" false_class="text-bg-secondary" />
|
<StatusBadge
|
||||||
|
:status="systemStatus.nrf_configured"
|
||||||
|
true_text="radioinfo.Configured"
|
||||||
|
false_text="radioinfo.NotConfigured"
|
||||||
|
false_class="text-bg-secondary"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('radioinfo.ChipStatus', { module: "nRF24" }) }}</th>
|
<th>{{ $t('radioinfo.ChipStatus', { module: 'nRF24' }) }}</th>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge" :class="{
|
<span
|
||||||
|
class="badge"
|
||||||
|
:class="{
|
||||||
'text-bg-danger': systemStatus.nrf_configured && !systemStatus.nrf_connected,
|
'text-bg-danger': systemStatus.nrf_configured && !systemStatus.nrf_connected,
|
||||||
'text-bg-success': systemStatus.nrf_configured && systemStatus.nrf_connected,
|
'text-bg-success': systemStatus.nrf_configured && systemStatus.nrf_connected,
|
||||||
}">
|
}"
|
||||||
<template
|
>
|
||||||
v-if="systemStatus.nrf_configured && systemStatus.nrf_connected">{{ $t('radioinfo.Connected') }}</template>
|
<template v-if="systemStatus.nrf_configured && systemStatus.nrf_connected">{{
|
||||||
<template
|
$t('radioinfo.Connected')
|
||||||
v-else-if="systemStatus.nrf_configured && !systemStatus.nrf_connected">{{ $t('radioinfo.NotConnected') }}</template>
|
}}</template>
|
||||||
|
<template v-else-if="systemStatus.nrf_configured && !systemStatus.nrf_connected">{{
|
||||||
|
$t('radioinfo.NotConnected')
|
||||||
|
}}</template>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('radioinfo.ChipType', { module: "nRF24" }) }}</th>
|
<th>{{ $t('radioinfo.ChipType', { module: 'nRF24' }) }}</th>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge" :class="{
|
<span
|
||||||
|
class="badge"
|
||||||
|
:class="{
|
||||||
'text-bg-danger': systemStatus.nrf_connected && !systemStatus.nrf_pvariant,
|
'text-bg-danger': systemStatus.nrf_connected && !systemStatus.nrf_pvariant,
|
||||||
'text-bg-success': systemStatus.nrf_connected && systemStatus.nrf_pvariant,
|
'text-bg-success': systemStatus.nrf_connected && systemStatus.nrf_pvariant,
|
||||||
'text-bg-secondary': !systemStatus.nrf_connected,
|
'text-bg-secondary': !systemStatus.nrf_connected,
|
||||||
}">
|
}"
|
||||||
<template
|
>
|
||||||
v-if="systemStatus.nrf_connected && systemStatus.nrf_pvariant">nRF24L01+</template>
|
<template v-if="systemStatus.nrf_connected && systemStatus.nrf_pvariant"
|
||||||
<template
|
>nRF24L01+</template
|
||||||
v-else-if="systemStatus.nrf_connected && !systemStatus.nrf_pvariant">nRF24L01</template>
|
>
|
||||||
|
<template v-else-if="systemStatus.nrf_connected && !systemStatus.nrf_pvariant"
|
||||||
|
>nRF24L01</template
|
||||||
|
>
|
||||||
<template v-else>{{ $t('radioinfo.Unknown') }}</template>
|
<template v-else>{{ $t('radioinfo.Unknown') }}</template>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('radioinfo.Status', { module: "CMT2300A" }) }}</th>
|
<th>{{ $t('radioinfo.Status', { module: 'CMT2300A' }) }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="systemStatus.cmt_configured" true_text="radioinfo.Configured" false_text="radioinfo.NotConfigured" false_class="text-bg-secondary" />
|
<StatusBadge
|
||||||
|
:status="systemStatus.cmt_configured"
|
||||||
|
true_text="radioinfo.Configured"
|
||||||
|
false_text="radioinfo.NotConfigured"
|
||||||
|
false_class="text-bg-secondary"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('radioinfo.ChipStatus', { module: "CMT2300A" }) }}</th>
|
<th>{{ $t('radioinfo.ChipStatus', { module: 'CMT2300A' }) }}</th>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge" :class="{
|
<span
|
||||||
|
class="badge"
|
||||||
|
:class="{
|
||||||
'text-bg-danger': systemStatus.cmt_configured && !systemStatus.cmt_connected,
|
'text-bg-danger': systemStatus.cmt_configured && !systemStatus.cmt_connected,
|
||||||
'text-bg-success': systemStatus.cmt_configured && systemStatus.cmt_connected,
|
'text-bg-success': systemStatus.cmt_configured && systemStatus.cmt_connected,
|
||||||
}">
|
}"
|
||||||
<template
|
>
|
||||||
v-if="systemStatus.cmt_configured && systemStatus.cmt_connected">{{ $t('radioinfo.Connected') }}</template>
|
<template v-if="systemStatus.cmt_configured && systemStatus.cmt_connected">{{
|
||||||
<template
|
$t('radioinfo.Connected')
|
||||||
v-else-if="systemStatus.cmt_configured && !systemStatus.cmt_connected">{{ $t('radioinfo.NotConnected') }}</template>
|
}}</template>
|
||||||
|
<template v-else-if="systemStatus.cmt_configured && !systemStatus.cmt_connected">{{
|
||||||
|
$t('radioinfo.NotConnected')
|
||||||
|
}}</template>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -10,23 +10,23 @@ import { defineComponent } from 'vue';
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
'status': Boolean,
|
status: Boolean,
|
||||||
'true_text': {
|
true_text: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
'false_text': {
|
false_text: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
'true_class': {
|
true_class: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'text-bg-success'
|
default: 'text-bg-success',
|
||||||
},
|
},
|
||||||
'false_class': {
|
false_class: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'text-bg-danger'
|
default: 'text-bg-danger',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -1,28 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<button class="btn btn-link nav-link py-2 px-0 px-lg-2 dropdown-toggle d-flex align-items-center" id="bd-theme"
|
<button
|
||||||
type="button" aria-expanded="false" data-bs-toggle="dropdown" data-bs-display="static"
|
class="btn btn-link nav-link py-2 px-0 px-lg-2 dropdown-toggle d-flex align-items-center"
|
||||||
aria-label="Toggle theme (auto)">
|
id="bd-theme"
|
||||||
|
type="button"
|
||||||
|
aria-expanded="false"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
data-bs-display="static"
|
||||||
|
aria-label="Toggle theme (auto)"
|
||||||
|
>
|
||||||
<BIconCircleHalf class="bi my-1 theme-icon-active" />
|
<BIconCircleHalf class="bi my-1 theme-icon-active" />
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li>
|
<li>
|
||||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light"
|
<button
|
||||||
aria-pressed="false">
|
type="button"
|
||||||
|
class="dropdown-item d-flex align-items-center"
|
||||||
|
data-bs-theme-value="light"
|
||||||
|
aria-pressed="false"
|
||||||
|
>
|
||||||
<BIconSunFill class="bi me-2 opacity-50 theme-icon" />
|
<BIconSunFill class="bi me-2 opacity-50 theme-icon" />
|
||||||
{{ $t('localeswitcher.Light') }}
|
{{ $t('localeswitcher.Light') }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark"
|
<button
|
||||||
aria-pressed="false">
|
type="button"
|
||||||
|
class="dropdown-item d-flex align-items-center"
|
||||||
|
data-bs-theme-value="dark"
|
||||||
|
aria-pressed="false"
|
||||||
|
>
|
||||||
<BIconMoonStarsFill class="bi me-2 opacity-50 theme-icon" />
|
<BIconMoonStarsFill class="bi me-2 opacity-50 theme-icon" />
|
||||||
{{ $t('localeswitcher.Dark') }}
|
{{ $t('localeswitcher.Dark') }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto"
|
<button
|
||||||
aria-pressed="true">
|
type="button"
|
||||||
|
class="dropdown-item d-flex align-items-center active"
|
||||||
|
data-bs-theme-value="auto"
|
||||||
|
aria-pressed="true"
|
||||||
|
>
|
||||||
<BIconCircleHalf class="bi me-2 opacity-50 theme-icon" />
|
<BIconCircleHalf class="bi me-2 opacity-50 theme-icon" />
|
||||||
{{ $t('localeswitcher.Auto') }}
|
{{ $t('localeswitcher.Auto') }}
|
||||||
</button>
|
</button>
|
||||||
@ -33,14 +51,10 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import {
|
import { BIconCircleHalf, BIconSunFill, BIconMoonStarsFill } from 'bootstrap-icons-vue';
|
||||||
BIconCircleHalf,
|
|
||||||
BIconSunFill,
|
|
||||||
BIconMoonStarsFill,
|
|
||||||
} from 'bootstrap-icons-vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "ThemeSwitcher",
|
name: 'ThemeSwitcher',
|
||||||
components: {
|
components: {
|
||||||
BIconCircleHalf,
|
BIconCircleHalf,
|
||||||
BIconSunFill,
|
BIconSunFill,
|
||||||
@ -70,9 +84,9 @@ export default defineComponent({
|
|||||||
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
|
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
|
||||||
const svgOfActiveBtn = btnToActive?.querySelector('.theme-icon');
|
const svgOfActiveBtn = btnToActive?.querySelector('.theme-icon');
|
||||||
|
|
||||||
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
|
document.querySelectorAll('[data-bs-theme-value]').forEach((element) => {
|
||||||
element.classList.remove('active');
|
element.classList.remove('active');
|
||||||
})
|
});
|
||||||
|
|
||||||
btnToActive?.classList.add('active');
|
btnToActive?.classList.add('active');
|
||||||
|
|
||||||
@ -92,14 +106,13 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('[data-bs-theme-value]')
|
document.querySelectorAll('[data-bs-theme-value]').forEach((toggle) => {
|
||||||
.forEach(toggle => {
|
|
||||||
toggle.addEventListener('click', () => {
|
toggle.addEventListener('click', () => {
|
||||||
const theme = toggle.getAttribute('data-bs-theme-value') || 'auto';
|
const theme = toggle.getAttribute('data-bs-theme-value') || 'auto';
|
||||||
localStorage.setItem('theme', theme);
|
localStorage.setItem('theme', theme);
|
||||||
this.setTheme(theme);
|
this.setTheme(theme);
|
||||||
this.showActiveTheme(theme);
|
this.showActiveTheme(theme);
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,7 +6,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('wifiapinfo.Status') }}</th>
|
<th>{{ $t('wifiapinfo.Status') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="networkStatus.ap_status" true_text="wifiapinfo.Enabled" false_text="wifiapinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="networkStatus.ap_status"
|
||||||
|
true_text="wifiapinfo.Enabled"
|
||||||
|
false_text="wifiapinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@ -6,7 +6,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('wifistationinfo.Status') }}</th>
|
<th>{{ $t('wifistationinfo.Status') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="networkStatus.sta_status" true_text="wifistationinfo.Enabled" false_text="wifistationinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="networkStatus.sta_status"
|
||||||
|
true_text="wifistationinfo.Enabled"
|
||||||
|
false_text="wifistationinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -40,7 +44,7 @@ import { defineComponent, type PropType } from 'vue';
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
CardElement,
|
CardElement,
|
||||||
StatusBadge
|
StatusBadge,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
networkStatus: { type: Object as PropType<NetworkStatus>, required: true },
|
networkStatus: { type: Object as PropType<NetworkStatus>, required: true },
|
||||||
|
|||||||
2
webapp/src/emitter.d.ts
vendored
2
webapp/src/emitter.d.ts
vendored
@ -4,4 +4,4 @@ declare module '@vue/runtime-core' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { } // Important! See note.
|
export {}; // Important! See note.
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { I18nOptions } from "vue-i18n";
|
import type { I18nOptions } from 'vue-i18n';
|
||||||
|
|
||||||
export enum Locales {
|
export enum Locales {
|
||||||
EN = 'en',
|
EN = 'en',
|
||||||
@ -10,22 +10,22 @@ export const LOCALES = [
|
|||||||
{ value: Locales.EN, caption: 'English' },
|
{ value: Locales.EN, caption: 'English' },
|
||||||
{ value: Locales.DE, caption: 'Deutsch' },
|
{ value: Locales.DE, caption: 'Deutsch' },
|
||||||
{ value: Locales.FR, caption: 'Français' },
|
{ value: Locales.FR, caption: 'Français' },
|
||||||
]
|
];
|
||||||
|
|
||||||
export const dateTimeFormats: I18nOptions["datetimeFormats"] = {};
|
export const dateTimeFormats: I18nOptions['datetimeFormats'] = {};
|
||||||
export const numberFormats: I18nOptions["numberFormats"] = {};
|
export const numberFormats: I18nOptions['numberFormats'] = {};
|
||||||
|
|
||||||
LOCALES.forEach((locale) => {
|
LOCALES.forEach((locale) => {
|
||||||
dateTimeFormats[locale.value] = {
|
dateTimeFormats[locale.value] = {
|
||||||
'datetime': {
|
datetime: {
|
||||||
hour: 'numeric',
|
hour: 'numeric',
|
||||||
minute: 'numeric',
|
minute: 'numeric',
|
||||||
second: 'numeric',
|
second: 'numeric',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'numeric',
|
month: 'numeric',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
hour12: false
|
hour12: false,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
numberFormats[locale.value] = {
|
numberFormats[locale.value] = {
|
||||||
@ -33,25 +33,44 @@ LOCALES.forEach((locale) => {
|
|||||||
style: 'decimal',
|
style: 'decimal',
|
||||||
},
|
},
|
||||||
decimalNoDigits: {
|
decimalNoDigits: {
|
||||||
style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 0
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
},
|
||||||
|
decimalOneDigit: {
|
||||||
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1,
|
||||||
},
|
},
|
||||||
decimalTwoDigits: {
|
decimalTwoDigits: {
|
||||||
style: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
},
|
},
|
||||||
percent: {
|
percent: {
|
||||||
style: 'percent',
|
style: 'percent',
|
||||||
},
|
},
|
||||||
|
percentOneDigit: {
|
||||||
|
style: 'percent',
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
},
|
||||||
byte: {
|
byte: {
|
||||||
style: 'unit', unit: 'byte',
|
style: 'unit',
|
||||||
|
unit: 'byte',
|
||||||
},
|
},
|
||||||
kilobyte: {
|
kilobyte: {
|
||||||
style: 'unit', unit: 'kilobyte',
|
style: 'unit',
|
||||||
|
unit: 'kilobyte',
|
||||||
},
|
},
|
||||||
megabyte: {
|
megabyte: {
|
||||||
style: 'unit', unit: 'megabyte',
|
style: 'unit',
|
||||||
|
unit: 'megabyte',
|
||||||
},
|
},
|
||||||
celsius: {
|
celsius: {
|
||||||
style: 'unit', unit: 'celsius', maximumFractionDigits: 1,
|
style: 'unit',
|
||||||
|
unit: 'celsius',
|
||||||
|
maximumFractionDigits: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
import messages from '@intlify/unplugin-vue-i18n/messages'
|
import messages from '@intlify/unplugin-vue-i18n/messages';
|
||||||
import mitt from 'mitt'
|
import mitt from 'mitt';
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue';
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n';
|
||||||
import App from './App.vue'
|
import App from './App.vue';
|
||||||
import { dateTimeFormats, defaultLocale, numberFormats } from './locales'
|
import { dateTimeFormats, defaultLocale, numberFormats } from './locales';
|
||||||
import { tooltip } from './plugins/bootstrap'
|
import { tooltip } from './plugins/bootstrap';
|
||||||
import router from './router'
|
import router from './router';
|
||||||
|
|
||||||
import "bootstrap"
|
import 'bootstrap';
|
||||||
import './scss/styles.scss'
|
import './scss/styles.scss';
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App);
|
||||||
|
|
||||||
const emitter = mitt();
|
const emitter = mitt();
|
||||||
app.config.globalProperties.$emitter = emitter;
|
app.config.globalProperties.$emitter = emitter;
|
||||||
|
|
||||||
app.directive('tooltip', tooltip)
|
app.directive('tooltip', tooltip);
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
@ -24,10 +24,10 @@ const i18n = createI18n({
|
|||||||
fallbackLocale: defaultLocale,
|
fallbackLocale: defaultLocale,
|
||||||
messages,
|
messages,
|
||||||
datetimeFormats: dateTimeFormats,
|
datetimeFormats: dateTimeFormats,
|
||||||
numberFormats: numberFormats
|
numberFormats: numberFormats,
|
||||||
})
|
});
|
||||||
|
|
||||||
app.use(router)
|
app.use(router);
|
||||||
app.use(i18n)
|
app.use(i18n);
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app');
|
||||||
|
|||||||
@ -3,5 +3,5 @@ import { Tooltip } from 'bootstrap';
|
|||||||
export const tooltip = {
|
export const tooltip = {
|
||||||
mounted(el: HTMLElement) {
|
mounted(el: HTMLElement) {
|
||||||
new Tooltip(el);
|
new Tooltip(el);
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import AboutView from '@/views/AboutView.vue';
|
import AboutView from '@/views/AboutView.vue';
|
||||||
import ConfigAdminView from '@/views/ConfigAdminView.vue';
|
import ConfigAdminView from '@/views/ConfigAdminView.vue';
|
||||||
import ConsoleInfoView from '@/views/ConsoleInfoView.vue';
|
import ConsoleInfoView from '@/views/ConsoleInfoView.vue';
|
||||||
import DeviceAdminView from '@/views/DeviceAdminView.vue'
|
import DeviceAdminView from '@/views/DeviceAdminView.vue';
|
||||||
import DtuAdminView from '@/views/DtuAdminView.vue';
|
import DtuAdminView from '@/views/DtuAdminView.vue';
|
||||||
import ErrorView from '@/views/ErrorView.vue';
|
import ErrorView from '@/views/ErrorView.vue';
|
||||||
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue';
|
import FirmwareUpgradeView from '@/views/FirmwareUpgradeView.vue';
|
||||||
@ -21,104 +21,104 @@ import { createRouter, createWebHistory } from 'vue-router';
|
|||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
linkActiveClass: "active",
|
linkActiveClass: 'active',
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: HomeView
|
component: HomeView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
component: LoginView
|
component: LoginView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/error?status=:status&message=:message',
|
path: '/error?status=:status&message=:message',
|
||||||
name: 'Error',
|
name: 'Error',
|
||||||
component: ErrorView
|
component: ErrorView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
name: 'About',
|
name: 'About',
|
||||||
component: AboutView
|
component: AboutView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/info/network',
|
path: '/info/network',
|
||||||
name: 'Network',
|
name: 'Network',
|
||||||
component: NetworkInfoView
|
component: NetworkInfoView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/info/system',
|
path: '/info/system',
|
||||||
name: 'System',
|
name: 'System',
|
||||||
component: SystemInfoView
|
component: SystemInfoView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/info/ntp',
|
path: '/info/ntp',
|
||||||
name: 'NTP',
|
name: 'NTP',
|
||||||
component: NtpInfoView
|
component: NtpInfoView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/info/mqtt',
|
path: '/info/mqtt',
|
||||||
name: 'MqTT',
|
name: 'MqTT',
|
||||||
component: MqttInfoView
|
component: MqttInfoView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/info/console',
|
path: '/info/console',
|
||||||
name: 'Web Console',
|
name: 'Web Console',
|
||||||
component: ConsoleInfoView
|
component: ConsoleInfoView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/network',
|
path: '/settings/network',
|
||||||
name: 'Network Settings',
|
name: 'Network Settings',
|
||||||
component: NetworkAdminView
|
component: NetworkAdminView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/ntp',
|
path: '/settings/ntp',
|
||||||
name: 'NTP Settings',
|
name: 'NTP Settings',
|
||||||
component: NtpAdminView
|
component: NtpAdminView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/mqtt',
|
path: '/settings/mqtt',
|
||||||
name: 'MqTT Settings',
|
name: 'MqTT Settings',
|
||||||
component: MqttAdminView
|
component: MqttAdminView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/inverter',
|
path: '/settings/inverter',
|
||||||
name: 'Inverter Settings',
|
name: 'Inverter Settings',
|
||||||
component: InverterAdminView
|
component: InverterAdminView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/dtu',
|
path: '/settings/dtu',
|
||||||
name: 'DTU Settings',
|
name: 'DTU Settings',
|
||||||
component: DtuAdminView
|
component: DtuAdminView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/device',
|
path: '/settings/device',
|
||||||
name: 'Device Manager',
|
name: 'Device Manager',
|
||||||
component: DeviceAdminView
|
component: DeviceAdminView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/firmware/upgrade',
|
path: '/firmware/upgrade',
|
||||||
name: 'Firmware Upgrade',
|
name: 'Firmware Upgrade',
|
||||||
component: FirmwareUpgradeView
|
component: FirmwareUpgradeView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/config',
|
path: '/settings/config',
|
||||||
name: 'Config Management',
|
name: 'Config Management',
|
||||||
component: ConfigAdminView
|
component: ConfigAdminView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/security',
|
path: '/settings/security',
|
||||||
name: 'Security',
|
name: 'Security',
|
||||||
component: SecurityAdminView
|
component: SecurityAdminView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/maintenance/reboot',
|
path: '/maintenance/reboot',
|
||||||
name: 'Device Reboot',
|
name: 'Device Reboot',
|
||||||
component: MaintenanceRebootView
|
component: MaintenanceRebootView,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Import all of Bootstrap's CSS
|
// Import all of Bootstrap's CSS
|
||||||
@import "~bootstrap/scss/bootstrap";
|
@import '~bootstrap/scss/bootstrap';
|
||||||
|
|
||||||
.container-fluid .row {
|
.container-fluid .row {
|
||||||
font-feature-settings: "tnum";
|
font-feature-settings: 'tnum';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Device } from "./PinMapping";
|
import type { Device } from './PinMapping';
|
||||||
|
|
||||||
export interface Display {
|
export interface Display {
|
||||||
rotation: number;
|
rotation: number;
|
||||||
|
|||||||
@ -6,11 +6,11 @@ export interface ValueObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface InverterStatistics {
|
export interface InverterStatistics {
|
||||||
name: ValueObject,
|
name: ValueObject;
|
||||||
Power?: ValueObject;
|
Power?: ValueObject;
|
||||||
Voltage?: ValueObject;
|
Voltage?: ValueObject;
|
||||||
Current?: ValueObject;
|
Current?: ValueObject;
|
||||||
"Power DC"?: ValueObject;
|
'Power DC'?: ValueObject;
|
||||||
YieldDay?: ValueObject;
|
YieldDay?: ValueObject;
|
||||||
YieldTotal?: ValueObject;
|
YieldTotal?: ValueObject;
|
||||||
Frequency?: ValueObject;
|
Frequency?: ValueObject;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
export interface NtpStatus {
|
export interface NtpStatus {
|
||||||
ntp_server: string;
|
ntp_server: string;
|
||||||
ntp_timezone: string;
|
ntp_timezone: string;
|
||||||
ntp_timezone_descr: string
|
ntp_timezone_descr: string;
|
||||||
ntp_status: boolean;
|
ntp_status: boolean;
|
||||||
ntp_localtime: string;
|
ntp_localtime: string;
|
||||||
sun_risetime: string;
|
sun_risetime: string;
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import type { Emitter, EventType } from "mitt";
|
import type { Emitter, EventType } from 'mitt';
|
||||||
import type { Router } from "vue-router";
|
import type { Router } from 'vue-router';
|
||||||
|
|
||||||
export function authHeader(): Headers {
|
export function authHeader(): Headers {
|
||||||
// return authorization header with basic auth credentials
|
// return authorization header with basic auth credentials
|
||||||
let user = null;
|
let user = null;
|
||||||
try {
|
try {
|
||||||
user = JSON.parse(localStorage.getItem('user') || "");
|
user = JSON.parse(localStorage.getItem('user') || '');
|
||||||
} catch {
|
} catch {
|
||||||
// continue regardless of error
|
// continue regardless of error
|
||||||
}
|
}
|
||||||
@ -21,15 +21,15 @@ export function authHeader(): Headers {
|
|||||||
export function authUrl(): string {
|
export function authUrl(): string {
|
||||||
let user = null;
|
let user = null;
|
||||||
try {
|
try {
|
||||||
user = JSON.parse(localStorage.getItem('user') || "");
|
user = JSON.parse(localStorage.getItem('user') || '');
|
||||||
} catch {
|
} catch {
|
||||||
// continue regardless of error
|
// continue regardless of error
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user && user.authdata) {
|
if (user && user.authdata) {
|
||||||
return encodeURIComponent(atob(user.authdata)).replace("%3A", ":") + '@';
|
return encodeURIComponent(atob(user.authdata)).replace('%3A', ':') + '@';
|
||||||
}
|
}
|
||||||
return "";
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
@ -38,7 +38,7 @@ export function logout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isLoggedIn(): boolean {
|
export function isLoggedIn(): boolean {
|
||||||
return (localStorage.getItem('user') != null);
|
return localStorage.getItem('user') != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function login(username: string, password: string) {
|
export function login(username: string, password: string) {
|
||||||
@ -46,13 +46,13 @@ export function login(username: string, password: string) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
'Authorization': 'Basic ' + btoa(unescape(encodeURIComponent(username + ':' + password))),
|
Authorization: 'Basic ' + btoa(unescape(encodeURIComponent(username + ':' + password))),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return fetch('/api/security/authenticate', requestOptions)
|
return fetch('/api/security/authenticate', requestOptions)
|
||||||
.then(handleAuthResponse)
|
.then(handleAuthResponse)
|
||||||
.then(retVal => {
|
.then((retVal) => {
|
||||||
// login successful if there's a user in the response
|
// login successful if there's a user in the response
|
||||||
if (retVal) {
|
if (retVal) {
|
||||||
// store user details and basic auth credentials in local storage
|
// store user details and basic auth credentials in local storage
|
||||||
@ -65,21 +65,32 @@ export function login(username: string, password: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleResponse(response: Response, emitter: Emitter<Record<EventType, unknown>>, router: Router, ignore_error: boolean = false) {
|
export function handleResponse(
|
||||||
return response.text().then(text => {
|
response: Response,
|
||||||
|
emitter: Emitter<Record<EventType, unknown>>,
|
||||||
|
router: Router,
|
||||||
|
ignore_error: boolean = false
|
||||||
|
) {
|
||||||
|
return response.text().then((text) => {
|
||||||
const data = text && JSON.parse(text);
|
const data = text && JSON.parse(text);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
// auto logout if 401 response returned from api
|
// auto logout if 401 response returned from api
|
||||||
logout();
|
logout();
|
||||||
emitter.emit("logged-out");
|
emitter.emit('logged-out');
|
||||||
router.push({ path: "/login", query: { returnUrl: router.currentRoute.value.fullPath } });
|
router.push({
|
||||||
|
path: '/login',
|
||||||
|
query: { returnUrl: router.currentRoute.value.fullPath },
|
||||||
|
});
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = { message: (data && data.message) || response.statusText, status: response.status || 0 };
|
const error = {
|
||||||
|
message: (data && data.message) || response.statusText,
|
||||||
|
status: response.status || 0,
|
||||||
|
};
|
||||||
if (!ignore_error) {
|
if (!ignore_error) {
|
||||||
router.push({ name: "Error", params: error });
|
router.push({ name: 'Error', params: error });
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
@ -89,7 +100,7 @@ export function handleResponse(response: Response, emitter: Emitter<Record<Event
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleAuthResponse(response: Response) {
|
function handleAuthResponse(response: Response) {
|
||||||
return response.text().then(text => {
|
return response.text().then((text) => {
|
||||||
const data = text && JSON.parse(text);
|
const data = text && JSON.parse(text);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
@ -97,7 +108,7 @@ function handleAuthResponse(response: Response) {
|
|||||||
logout();
|
logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = "Invalid credentials";
|
const error = 'Invalid credentials';
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
import { isLoggedIn, login, logout } from './authentication';
|
import { isLoggedIn, login, logout } from './authentication';
|
||||||
import { timestampToString } from './time';
|
import { timestampToString } from './time';
|
||||||
|
|
||||||
export {
|
export { timestampToString, login, logout, isLoggedIn };
|
||||||
timestampToString,
|
|
||||||
login,
|
|
||||||
logout,
|
|
||||||
isLoggedIn,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
timestampToString,
|
timestampToString,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
}
|
};
|
||||||
|
|||||||
@ -1,7 +1,14 @@
|
|||||||
export function timestampToString(locale: string, timestampSeconds: number, includeDays: true): [number, string];
|
export function timestampToString(locale: string, timestampSeconds: number, includeDays: true): [number, string];
|
||||||
export function timestampToString(locale: string, timestampSeconds: number, includeDays?: false): [string];
|
export function timestampToString(locale: string, timestampSeconds: number, includeDays?: false): [string];
|
||||||
export function timestampToString(locale: string, timestampSeconds: number, includeDays = false): [number, string] | [string] {
|
export function timestampToString(
|
||||||
const timeString = new Date(timestampSeconds * 1000).toLocaleTimeString(locale, { timeZone: "UTC", hour12: false });
|
locale: string,
|
||||||
|
timestampSeconds: number,
|
||||||
|
includeDays = false
|
||||||
|
): [number, string] | [string] {
|
||||||
|
const timeString = new Date(timestampSeconds * 1000).toLocaleTimeString(locale, {
|
||||||
|
timeZone: 'UTC',
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
if (!includeDays) return [timeString];
|
if (!includeDays) return [timeString];
|
||||||
|
|
||||||
const secondsPerDay = 60 * 60 * 24;
|
const secondsPerDay = 60 * 60 * 24;
|
||||||
|
|||||||
@ -3,15 +3,25 @@
|
|||||||
<div class="accordion" id="accordionExample">
|
<div class="accordion" id="accordionExample">
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingDocumentation">
|
<h2 class="accordion-header" id="headingDocumentation">
|
||||||
<button class="accordion-button" type="button" data-bs-toggle="collapse"
|
<button
|
||||||
data-bs-target="#collapseDocumentation" aria-expanded="true" aria-controls="collapseOne">
|
class="accordion-button"
|
||||||
<span class="badge text-bg-secondary">
|
type="button"
|
||||||
<BIconInfoCircle class="fs-4" />
|
data-bs-toggle="collapse"
|
||||||
</span> {{ $t('about.Documentation') }}
|
data-bs-target="#collapseDocumentation"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="collapseOne"
|
||||||
|
>
|
||||||
|
<span class="badge text-bg-secondary"> <BIconInfoCircle class="fs-4" /> </span> {{
|
||||||
|
$t('about.Documentation')
|
||||||
|
}}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="collapseDocumentation" class="accordion-collapse collapse show" aria-labelledby="headingDocumentation"
|
<div
|
||||||
data-bs-parent="#accordionExample">
|
id="collapseDocumentation"
|
||||||
|
class="accordion-collapse collapse show"
|
||||||
|
aria-labelledby="headingDocumentation"
|
||||||
|
data-bs-parent="#accordionExample"
|
||||||
|
>
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<p class="fw-normal" v-html="$t('about.DocumentationBody')"></p>
|
<p class="fw-normal" v-html="$t('about.DocumentationBody')"></p>
|
||||||
</div>
|
</div>
|
||||||
@ -19,15 +29,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingOne">
|
<h2 class="accordion-header" id="headingOne">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
<button
|
||||||
data-bs-target="#collapseOne" aria-expanded="false" aria-controls="collapseOne">
|
class="accordion-button collapsed"
|
||||||
<span class="badge text-bg-secondary">
|
type="button"
|
||||||
<BIconInfoCircle class="fs-4" />
|
data-bs-toggle="collapse"
|
||||||
</span> {{ $t('about.ProjectOrigin') }}
|
data-bs-target="#collapseOne"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="collapseOne"
|
||||||
|
>
|
||||||
|
<span class="badge text-bg-secondary"> <BIconInfoCircle class="fs-4" /> </span> {{
|
||||||
|
$t('about.ProjectOrigin')
|
||||||
|
}}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="collapseOne" class="accordion-collapse collapse" aria-labelledby="headingOne"
|
<div
|
||||||
data-bs-parent="#accordionExample">
|
id="collapseOne"
|
||||||
|
class="accordion-collapse collapse"
|
||||||
|
aria-labelledby="headingOne"
|
||||||
|
data-bs-parent="#accordionExample"
|
||||||
|
>
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<p class="fw-normal" v-html="$t('about.ProjectOriginBody1')"></p>
|
<p class="fw-normal" v-html="$t('about.ProjectOriginBody1')"></p>
|
||||||
<p class="fw-normal" v-html="$t('about.ProjectOriginBody2')"></p>
|
<p class="fw-normal" v-html="$t('about.ProjectOriginBody2')"></p>
|
||||||
@ -38,61 +58,83 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingTwo">
|
<h2 class="accordion-header" id="headingTwo">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
<button
|
||||||
data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
class="accordion-button collapsed"
|
||||||
<span class="badge text-bg-secondary">
|
type="button"
|
||||||
<BIconActivity class="fs-4" />
|
data-bs-toggle="collapse"
|
||||||
</span> {{ $t('about.NewsUpdates') }}
|
data-bs-target="#collapseTwo"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="collapseTwo"
|
||||||
|
>
|
||||||
|
<span class="badge text-bg-secondary"> <BIconActivity class="fs-4" /> </span> {{
|
||||||
|
$t('about.NewsUpdates')
|
||||||
|
}}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo"
|
<div
|
||||||
data-bs-parent="#accordionExample">
|
id="collapseTwo"
|
||||||
|
class="accordion-collapse collapse"
|
||||||
|
aria-labelledby="headingTwo"
|
||||||
|
data-bs-parent="#accordionExample"
|
||||||
|
>
|
||||||
<div class="accordion-body" v-html="$t('about.NewsUpdatesBody')"></div>
|
<div class="accordion-body" v-html="$t('about.NewsUpdatesBody')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingThree">
|
<h2 class="accordion-header" id="headingThree">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
<button
|
||||||
data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
class="accordion-button collapsed"
|
||||||
<span class="badge text-bg-secondary">
|
type="button"
|
||||||
<BIconBug class="fs-4" />
|
data-bs-toggle="collapse"
|
||||||
</span> {{ $t('about.ErrorReporting') }}
|
data-bs-target="#collapseThree"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="collapseThree"
|
||||||
|
>
|
||||||
|
<span class="badge text-bg-secondary"> <BIconBug class="fs-4" /> </span> {{
|
||||||
|
$t('about.ErrorReporting')
|
||||||
|
}}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree"
|
<div
|
||||||
data-bs-parent="#accordionExample">
|
id="collapseThree"
|
||||||
|
class="accordion-collapse collapse"
|
||||||
|
aria-labelledby="headingThree"
|
||||||
|
data-bs-parent="#accordionExample"
|
||||||
|
>
|
||||||
<div class="accordion-body" v-html="$t('about.ErrorReportingBody')"></div>
|
<div class="accordion-body" v-html="$t('about.ErrorReportingBody')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingFour">
|
<h2 class="accordion-header" id="headingFour">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
<button
|
||||||
data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
|
class="accordion-button collapsed"
|
||||||
<span class="badge text-bg-secondary">
|
type="button"
|
||||||
<BIconChat class="fs-4" />
|
data-bs-toggle="collapse"
|
||||||
</span> {{ $t('about.Discussion') }}
|
data-bs-target="#collapseFour"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="collapseFour"
|
||||||
|
>
|
||||||
|
<span class="badge text-bg-secondary"> <BIconChat class="fs-4" /> </span> {{
|
||||||
|
$t('about.Discussion')
|
||||||
|
}}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingFour"
|
<div
|
||||||
data-bs-parent="#accordionExample">
|
id="collapseFour"
|
||||||
|
class="accordion-collapse collapse"
|
||||||
|
aria-labelledby="headingFour"
|
||||||
|
data-bs-parent="#accordionExample"
|
||||||
|
>
|
||||||
<div class="accordion-body" v-html="$t('about.DiscussionBody')"></div>
|
<div class="accordion-body" v-html="$t('about.DiscussionBody')"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</BasePage>
|
</BasePage>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import {
|
import { BIconActivity, BIconBug, BIconChat, BIconInfoCircle } from 'bootstrap-icons-vue';
|
||||||
BIconActivity,
|
|
||||||
BIconBug,
|
|
||||||
BIconChat,
|
|
||||||
BIconInfoCircle
|
|
||||||
} from 'bootstrap-icons-vue';
|
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -104,5 +146,4 @@ export default defineComponent({
|
|||||||
BIconInfoCircle,
|
BIconInfoCircle,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -11,13 +11,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<select class="form-select" v-model="backupFileSelect">
|
<select class="form-select" v-model="backupFileSelect">
|
||||||
<option v-for="(file) in fileList.configs" :key="file.name" :value="file.name">
|
<option v-for="file in fileList.configs" :key="file.name" :value="file.name">
|
||||||
{{ file.name }}
|
{{ file.name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<button class="btn btn-primary" @click="downloadConfig">{{ $t('configadmin.Backup') }}
|
<button class="btn btn-primary" @click="downloadConfig">
|
||||||
|
{{ $t('configadmin.Backup') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -33,9 +34,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<button class="btn btn-light" @click="clear">
|
<button class="btn btn-light" @click="clear"><BIconArrowLeft /> {{ $t('configadmin.Back') }}</button>
|
||||||
<BIconArrowLeft /> {{ $t('configadmin.Back') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="!uploading && UploadSuccess">
|
<div v-else-if="!uploading && UploadSuccess">
|
||||||
@ -45,9 +44,7 @@
|
|||||||
<span> {{ $t('configadmin.UploadSuccess') }} </span>
|
<span> {{ $t('configadmin.UploadSuccess') }} </span>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<button class="btn btn-primary" @click="clear">
|
<button class="btn btn-primary" @click="clear"><BIconArrowLeft /> {{ $t('configadmin.Back') }}</button>
|
||||||
<BIconArrowLeft /> {{ $t('configadmin.Back') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="!uploading">
|
<div v-else-if="!uploading">
|
||||||
@ -62,7 +59,8 @@
|
|||||||
<input class="form-control" type="file" ref="file" accept=".json" />
|
<input class="form-control" type="file" ref="file" accept=".json" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<button class="btn btn-primary" @click="uploadConfig">{{ $t('configadmin.Restore') }}
|
<button class="btn btn-primary" @click="uploadConfig">
|
||||||
|
{{ $t('configadmin.Restore') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -70,8 +68,14 @@
|
|||||||
|
|
||||||
<div v-else-if="uploading">
|
<div v-else-if="uploading">
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<div class="progress-bar" role="progressbar" :style="{ width: progress + '%' }"
|
<div
|
||||||
v-bind:aria-valuenow="progress" aria-valuemin="0" aria-valuemax="100">
|
class="progress-bar"
|
||||||
|
role="progressbar"
|
||||||
|
:style="{ width: progress + '%' }"
|
||||||
|
v-bind:aria-valuenow="progress"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
>
|
||||||
{{ progress }}%
|
{{ progress }}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -81,14 +85,20 @@
|
|||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('configadmin.ResetHeader')" textVariant="text-bg-primary" center-content add-space>
|
<CardElement :text="$t('configadmin.ResetHeader')" textVariant="text-bg-primary" center-content add-space>
|
||||||
<button class="btn btn-danger" @click="onFactoryResetModal">{{ $t('configadmin.FactoryResetButton') }}
|
<button class="btn btn-danger" @click="onFactoryResetModal">
|
||||||
|
{{ $t('configadmin.FactoryResetButton') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="alert alert-danger mt-3" role="alert" v-html="$t('configadmin.ResetHint')"></div>
|
<div class="alert alert-danger mt-3" role="alert" v-html="$t('configadmin.ResetHint')"></div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
|
|
||||||
<ModalDialog modalId="factoryReset" small :title="$t('configadmin.FactoryReset')" :closeText="$t('configadmin.Cancel')">
|
<ModalDialog
|
||||||
|
modalId="factoryReset"
|
||||||
|
small
|
||||||
|
:title="$t('configadmin.FactoryReset')"
|
||||||
|
:closeText="$t('configadmin.Cancel')"
|
||||||
|
>
|
||||||
{{ $t('configadmin.ResetMsg') }}
|
{{ $t('configadmin.ResetMsg') }}
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<button type="button" class="btn btn-danger" @click="onFactoryResetPerform">
|
<button type="button" class="btn btn-danger" @click="onFactoryResetPerform">
|
||||||
@ -100,17 +110,13 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import ModalDialog from '@/components/ModalDialog.vue';
|
import ModalDialog from '@/components/ModalDialog.vue';
|
||||||
import type { ConfigFileList } from '@/types/Config';
|
import type { ConfigFileList } from '@/types/Config';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import * as bootstrap from 'bootstrap';
|
import * as bootstrap from 'bootstrap';
|
||||||
import {
|
import { BIconArrowLeft, BIconCheckCircle, BIconExclamationCircleFill } from 'bootstrap-icons-vue';
|
||||||
BIconArrowLeft,
|
|
||||||
BIconCheckCircle,
|
|
||||||
BIconExclamationCircleFill
|
|
||||||
} from 'bootstrap-icons-vue';
|
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -126,18 +132,18 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
modalFactoryReset: {} as bootstrap.Modal,
|
modalFactoryReset: {} as bootstrap.Modal,
|
||||||
alertMessage: "",
|
alertMessage: '',
|
||||||
alertType: "info",
|
alertType: 'info',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
UploadError: "",
|
UploadError: '',
|
||||||
UploadSuccess: false,
|
UploadSuccess: false,
|
||||||
file: {} as Blob,
|
file: {} as Blob,
|
||||||
fileList: {} as ConfigFileList,
|
fileList: {} as ConfigFileList,
|
||||||
backupFileSelect: "",
|
backupFileSelect: '',
|
||||||
restoreFileSelect: "config.json",
|
restoreFileSelect: 'config.json',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -155,26 +161,24 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onFactoryResetPerform() {
|
onFactoryResetPerform() {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify({ delete: true }));
|
formData.append('data', JSON.stringify({ delete: true }));
|
||||||
|
|
||||||
fetch("/api/config/delete", {
|
fetch('/api/config/delete', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
|
||||||
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertType = response.type;
|
this.alertType = response.type;
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
});
|
||||||
)
|
|
||||||
this.modalFactoryReset.hide();
|
this.modalFactoryReset.hide();
|
||||||
},
|
},
|
||||||
getFileList() {
|
getFileList() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
fetch("/api/config/list", { headers: authHeader() })
|
fetch('/api/config/list', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.fileList = data;
|
this.fileList = data;
|
||||||
@ -185,9 +189,9 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
downloadConfig() {
|
downloadConfig() {
|
||||||
fetch("/api/config/get?file=" + this.backupFileSelect, { headers: authHeader() })
|
fetch('/api/config/get?file=' + this.backupFileSelect, { headers: authHeader() })
|
||||||
.then(res => res.blob())
|
.then((res) => res.blob())
|
||||||
.then(blob => {
|
.then((blob) => {
|
||||||
const file = window.URL.createObjectURL(blob);
|
const file = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = file;
|
a.href = file;
|
||||||
@ -204,13 +208,13 @@ export default defineComponent({
|
|||||||
if (target.files !== null && target.files?.length > 0) {
|
if (target.files !== null && target.files?.length > 0) {
|
||||||
this.file = target.files[0];
|
this.file = target.files[0];
|
||||||
} else {
|
} else {
|
||||||
this.UploadError = this.$t("configadmin.NoFileSelected");
|
this.UploadError = this.$t('configadmin.NoFileSelected');
|
||||||
this.uploading = false;
|
this.uploading = false;
|
||||||
this.progress = 0;
|
this.progress = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
request.addEventListener("load", () => {
|
request.addEventListener('load', () => {
|
||||||
// request.response will hold the response from the server
|
// request.response will hold the response from the server
|
||||||
if (request.status === 200) {
|
if (request.status === 200) {
|
||||||
this.UploadSuccess = true;
|
this.UploadSuccess = true;
|
||||||
@ -223,20 +227,20 @@ export default defineComponent({
|
|||||||
this.progress = 0;
|
this.progress = 0;
|
||||||
});
|
});
|
||||||
// Upload progress
|
// Upload progress
|
||||||
request.upload.addEventListener("progress", (e) => {
|
request.upload.addEventListener('progress', (e) => {
|
||||||
this.progress = Math.trunc((e.loaded / e.total) * 100);
|
this.progress = Math.trunc((e.loaded / e.total) * 100);
|
||||||
});
|
});
|
||||||
request.withCredentials = true;
|
request.withCredentials = true;
|
||||||
|
|
||||||
formData.append("config", this.file, "config");
|
formData.append('config', this.file, 'config');
|
||||||
request.open("post", "/api/config/upload?file=" + this.restoreFileSelect);
|
request.open('post', '/api/config/upload?file=' + this.restoreFileSelect);
|
||||||
authHeader().forEach((value, key) => {
|
authHeader().forEach((value, key) => {
|
||||||
request.setRequestHeader(key, value);
|
request.setRequestHeader(key, value);
|
||||||
});
|
});
|
||||||
request.send(formData);
|
request.send(formData);
|
||||||
},
|
},
|
||||||
clear() {
|
clear() {
|
||||||
this.UploadError = "";
|
this.UploadError = '';
|
||||||
this.UploadSuccess = false;
|
this.UploadSuccess = false;
|
||||||
this.getFileList();
|
this.getFileList();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,8 +4,13 @@
|
|||||||
<div class="row g-3 align-items-center">
|
<div class="row g-3 align-items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="autoScroll"
|
<input
|
||||||
v-model="isAutoScroll">
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
role="switch"
|
||||||
|
id="autoScroll"
|
||||||
|
v-model="isAutoScroll"
|
||||||
|
/>
|
||||||
<label class="form-check-label" for="autoScroll">
|
<label class="form-check-label" for="autoScroll">
|
||||||
{{ $t('console.EnableAutoScroll') }}
|
{{ $t('console.EnableAutoScroll') }}
|
||||||
</label>
|
</label>
|
||||||
@ -14,9 +19,11 @@
|
|||||||
<div class="col text-end">
|
<div class="col text-end">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button type="button" class="btn btn-primary" :onClick="clearConsole">
|
<button type="button" class="btn btn-primary" :onClick="clearConsole">
|
||||||
{{ $t('console.ClearConsole') }}</button>
|
{{ $t('console.ClearConsole') }}
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-secondary" :onClick="copyConsole">
|
<button type="button" class="btn btn-secondary" :onClick="copyConsole">
|
||||||
{{ $t('console.CopyToClipboard') }}</button>
|
{{ $t('console.CopyToClipboard') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -41,7 +48,7 @@ export default defineComponent({
|
|||||||
socket: {} as WebSocket,
|
socket: {} as WebSocket,
|
||||||
heartInterval: 0,
|
heartInterval: 0,
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
consoleBuffer: "",
|
consoleBuffer: '',
|
||||||
isAutoScroll: true,
|
isAutoScroll: true,
|
||||||
endWithNewline: false,
|
endWithNewline: false,
|
||||||
};
|
};
|
||||||
@ -56,21 +63,20 @@ export default defineComponent({
|
|||||||
watch: {
|
watch: {
|
||||||
consoleBuffer() {
|
consoleBuffer() {
|
||||||
if (this.isAutoScroll) {
|
if (this.isAutoScroll) {
|
||||||
const textarea = this.$el.querySelector("#console");
|
const textarea = this.$el.querySelector('#console');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
textarea.scrollTop = textarea.scrollHeight;
|
textarea.scrollTop = textarea.scrollHeight;
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initSocket() {
|
initSocket() {
|
||||||
console.log("Starting connection to WebSocket Server");
|
console.log('Starting connection to WebSocket Server');
|
||||||
|
|
||||||
const { protocol, host } = location;
|
const { protocol, host } = location;
|
||||||
const authString = authUrl();
|
const authString = authUrl();
|
||||||
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
const webSocketUrl = `${protocol === 'https:' ? 'wss' : 'ws'}://${authString}${host}/console`;
|
||||||
}://${authString}${host}/console`;
|
|
||||||
|
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
this.socket = new WebSocket(webSocketUrl);
|
this.socket = new WebSocket(webSocketUrl);
|
||||||
@ -84,14 +90,15 @@ export default defineComponent({
|
|||||||
outstr = outstr.substring(0, outstr.length - 1);
|
outstr = outstr.substring(0, outstr.length - 1);
|
||||||
removedNewline = true;
|
removedNewline = true;
|
||||||
}
|
}
|
||||||
this.consoleBuffer += (this.endWithNewline ? this.getOutDate() : '') + outstr.replaceAll("\n", "\n" + this.getOutDate());
|
this.consoleBuffer +=
|
||||||
|
(this.endWithNewline ? this.getOutDate() : '') + outstr.replaceAll('\n', '\n' + this.getOutDate());
|
||||||
this.endWithNewline = removedNewline;
|
this.endWithNewline = removedNewline;
|
||||||
this.heartCheck(); // Reset heartbeat detection
|
this.heartCheck(); // Reset heartbeat detection
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onopen = function (event) {
|
this.socket.onopen = function (event) {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
console.log("Successfully connected to the echo websocket server...");
|
console.log('Successfully connected to the echo websocket server...');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
||||||
@ -105,7 +112,7 @@ export default defineComponent({
|
|||||||
this.heartInterval = setInterval(() => {
|
this.heartInterval = setInterval(() => {
|
||||||
if (this.socket.readyState === 1) {
|
if (this.socket.readyState === 1) {
|
||||||
// Connection status
|
// Connection status
|
||||||
this.socket.send("ping");
|
this.socket.send('ping');
|
||||||
} else {
|
} else {
|
||||||
this.initSocket(); // Breakpoint reconnection 5 Time
|
this.initSocket(); // Breakpoint reconnection 5 Time
|
||||||
}
|
}
|
||||||
@ -123,13 +130,19 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
getOutDate(): string {
|
getOutDate(): string {
|
||||||
const u = new Date();
|
const u = new Date();
|
||||||
return ('0' + u.getHours()).slice(-2) + ':' +
|
return (
|
||||||
('0' + u.getMinutes()).slice(-2) + ':' +
|
('0' + u.getHours()).slice(-2) +
|
||||||
('0' + u.getSeconds()).slice(-2) + '.' +
|
':' +
|
||||||
(u.getMilliseconds() / 1000).toFixed(3).slice(2, 5) + ' > ';
|
('0' + u.getMinutes()).slice(-2) +
|
||||||
|
':' +
|
||||||
|
('0' + u.getSeconds()).slice(-2) +
|
||||||
|
'.' +
|
||||||
|
(u.getMilliseconds() / 1000).toFixed(3).slice(2, 5) +
|
||||||
|
' > '
|
||||||
|
);
|
||||||
},
|
},
|
||||||
clearConsole() {
|
clearConsole() {
|
||||||
this.consoleBuffer = "";
|
this.consoleBuffer = '';
|
||||||
},
|
},
|
||||||
copyConsole() {
|
copyConsole() {
|
||||||
const input = document.createElement('textarea');
|
const input = document.createElement('textarea');
|
||||||
@ -138,17 +151,17 @@ export default defineComponent({
|
|||||||
input.select();
|
input.select();
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
document.body.removeChild(input);
|
document.body.removeChild(input);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#console {
|
#console {
|
||||||
background-color: #0C0C0C;
|
background-color: #0c0c0c;
|
||||||
color: #CCCCCC;
|
color: #cccccc;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
font-family: courier new;
|
font-family: courier new;
|
||||||
font-size: .875em;
|
font-size: 0.875em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -7,19 +7,50 @@
|
|||||||
<form @submit="savePinConfig">
|
<form @submit="savePinConfig">
|
||||||
<nav>
|
<nav>
|
||||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||||
<button class="nav-link active" id="nav-pin-tab" data-bs-toggle="tab" data-bs-target="#nav-pin"
|
<button
|
||||||
type="button" role="tab" aria-controls="nav-pin" aria-selected="true">{{
|
class="nav-link active"
|
||||||
$t('deviceadmin.PinAssignment')
|
id="nav-pin-tab"
|
||||||
}}</button>
|
data-bs-toggle="tab"
|
||||||
<button class="nav-link" id="nav-display-tab" data-bs-toggle="tab" data-bs-target="#nav-display"
|
data-bs-target="#nav-pin"
|
||||||
type="button" role="tab" aria-controls="nav-display">{{ $t('deviceadmin.Display') }}</button>
|
type="button"
|
||||||
<button class="nav-link" id="nav-leds-tab" data-bs-toggle="tab" data-bs-target="#nav-leds"
|
role="tab"
|
||||||
type="button" role="tab" aria-controls="nav-leds">{{ $t('deviceadmin.Leds') }}</button>
|
aria-controls="nav-pin"
|
||||||
|
aria-selected="true"
|
||||||
|
>
|
||||||
|
{{ $t('deviceadmin.PinAssignment') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="nav-link"
|
||||||
|
id="nav-display-tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-bs-target="#nav-display"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="nav-display"
|
||||||
|
>
|
||||||
|
{{ $t('deviceadmin.Display') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="nav-link"
|
||||||
|
id="nav-leds-tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-bs-target="#nav-leds"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="nav-leds"
|
||||||
|
>
|
||||||
|
{{ $t('deviceadmin.Leds') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="tab-content" id="nav-tabContent">
|
<div class="tab-content" id="nav-tabContent">
|
||||||
<div class="tab-pane fade show active" id="nav-pin" role="tabpanel" aria-labelledby="nav-pin-tab"
|
<div
|
||||||
tabindex="0">
|
class="tab-pane fade show active"
|
||||||
|
id="nav-pin"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="nav-pin-tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
@ -27,10 +58,21 @@
|
|||||||
$t('deviceadmin.SelectedProfile')
|
$t('deviceadmin.SelectedProfile')
|
||||||
}}</label>
|
}}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select class="form-select" id="inputPinProfile"
|
<select
|
||||||
v-model="deviceConfigList.curPin.name">
|
class="form-select"
|
||||||
<option v-for="device in pinMappingList" :value="device.name" :key="device.name">
|
id="inputPinProfile"
|
||||||
{{ device.name === "Default" ? $t('deviceadmin.DefaultProfile') : device.name }}
|
v-model="deviceConfigList.curPin.name"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="device in pinMappingList"
|
||||||
|
:value="device.name"
|
||||||
|
:key="device.name"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
device.name === 'Default'
|
||||||
|
? $t('deviceadmin.DefaultProfile')
|
||||||
|
: device.name
|
||||||
|
}}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -39,33 +81,56 @@
|
|||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-sm-2"></div>
|
<div class="col-sm-2"></div>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="btn-group mb-2 me-2" v-for="(doc, index) in pinMappingList.find(i => i.name === deviceConfigList.curPin.name)?.links" :key="index">
|
<div
|
||||||
|
class="btn-group mb-2 me-2"
|
||||||
|
v-for="(doc, index) in pinMappingList.find(
|
||||||
|
(i) => i.name === deviceConfigList.curPin.name
|
||||||
|
)?.links"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
<a :href="doc.url" class="btn btn-primary" target="_blank">{{ doc.name }}</a>
|
<a :href="doc.url" class="btn btn-primary" target="_blank">{{ doc.name }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alert alert-danger mt-3" role="alert" v-html="$t('deviceadmin.ProfileHint')">
|
<div
|
||||||
</div>
|
class="alert alert-danger mt-3"
|
||||||
|
role="alert"
|
||||||
|
v-html="$t('deviceadmin.ProfileHint')"
|
||||||
|
></div>
|
||||||
|
|
||||||
<PinInfo
|
<PinInfo
|
||||||
:selectedPinAssignment="pinMappingList.find(i => i.name === deviceConfigList.curPin.name)"
|
:selectedPinAssignment="
|
||||||
:currentPinAssignment="deviceConfigList.curPin" />
|
pinMappingList.find((i) => i.name === deviceConfigList.curPin.name)
|
||||||
|
"
|
||||||
|
:currentPinAssignment="deviceConfigList.curPin"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade show" id="nav-display" role="tabpanel" aria-labelledby="nav-display-tab"
|
<div
|
||||||
tabindex="0">
|
class="tab-pane fade show"
|
||||||
|
id="nav-display"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="nav-display-tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<InputElement :label="$t('deviceadmin.PowerSafe')"
|
<InputElement
|
||||||
v-model="deviceConfigList.display.power_safe" type="checkbox"
|
:label="$t('deviceadmin.PowerSafe')"
|
||||||
:tooltip="$t('deviceadmin.PowerSafeHint')" />
|
v-model="deviceConfigList.display.power_safe"
|
||||||
|
type="checkbox"
|
||||||
|
:tooltip="$t('deviceadmin.PowerSafeHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('deviceadmin.Screensaver')"
|
<InputElement
|
||||||
v-model="deviceConfigList.display.screensaver" type="checkbox"
|
:label="$t('deviceadmin.Screensaver')"
|
||||||
:tooltip="$t('deviceadmin.ScreensaverHint')" />
|
v-model="deviceConfigList.display.screensaver"
|
||||||
|
type="checkbox"
|
||||||
|
:tooltip="$t('deviceadmin.ScreensaverHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-2 col-form-label">
|
<label class="col-sm-2 col-form-label">
|
||||||
@ -80,10 +145,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InputElement :label="$t('deviceadmin.DiagramDuration')"
|
<InputElement
|
||||||
v-model="deviceConfigList.display.diagramduration" type="number"
|
:label="$t('deviceadmin.DiagramDuration')"
|
||||||
min=600 max=86400
|
v-model="deviceConfigList.display.diagramduration"
|
||||||
:tooltip="$t('deviceadmin.DiagramDurationHint')" :postfix="$t('deviceadmin.Seconds')" />
|
type="number"
|
||||||
|
min="600"
|
||||||
|
max="86400"
|
||||||
|
:tooltip="$t('deviceadmin.DiagramDurationHint')"
|
||||||
|
:postfix="$t('deviceadmin.Seconds')"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-2 col-form-label">
|
<label class="col-sm-2 col-form-label">
|
||||||
@ -91,7 +161,11 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select class="form-select" v-model="deviceConfigList.display.language">
|
<select class="form-select" v-model="deviceConfigList.display.language">
|
||||||
<option v-for="language in displayLanguageList" :key="language.key" :value="language.key">
|
<option
|
||||||
|
v-for="language in displayLanguageList"
|
||||||
|
:key="language.key"
|
||||||
|
:value="language.key"
|
||||||
|
>
|
||||||
{{ $t(`deviceadmin.` + language.value) }}
|
{{ $t(`deviceadmin.` + language.value) }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
@ -104,7 +178,11 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select class="form-select" v-model="deviceConfigList.display.rotation">
|
<select class="form-select" v-model="deviceConfigList.display.rotation">
|
||||||
<option v-for="rotation in displayRotationList" :key="rotation.key" :value="rotation.key">
|
<option
|
||||||
|
v-for="rotation in displayRotationList"
|
||||||
|
:key="rotation.key"
|
||||||
|
:value="rotation.key"
|
||||||
|
>
|
||||||
{{ $t(`deviceadmin.` + rotation.value) }}
|
{{ $t(`deviceadmin.` + rotation.value) }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
@ -113,36 +191,57 @@
|
|||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label for="inputDisplayContrast" class="col-sm-2 col-form-label">{{
|
<label for="inputDisplayContrast" class="col-sm-2 col-form-label">{{
|
||||||
$t('deviceadmin.Contrast', { contrast: $n(deviceConfigList.display.contrast / 100,
|
$t('deviceadmin.Contrast', {
|
||||||
'percent')
|
contrast: $n(deviceConfigList.display.contrast / 100, 'percent'),
|
||||||
}) }}</label>
|
})
|
||||||
|
}}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="range" class="form-range" min="0" max="100" id="inputDisplayContrast"
|
<input
|
||||||
v-model="deviceConfigList.display.contrast" />
|
type="range"
|
||||||
|
class="form-range"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
id="inputDisplayContrast"
|
||||||
|
v-model="deviceConfigList.display.contrast"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<div
|
||||||
</div>
|
class="tab-pane fade show"
|
||||||
</div>
|
id="nav-leds"
|
||||||
|
role="tabpanel"
|
||||||
<div class="tab-pane fade show" id="nav-leds" role="tabpanel" aria-labelledby="nav-leds-tab" tabindex="0">
|
aria-labelledby="nav-leds-tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<InputElement :label="$t('deviceadmin.EqualBrightness')"
|
<InputElement
|
||||||
v-model="equalBrightnessCheckVal" type="checkbox" />
|
:label="$t('deviceadmin.EqualBrightness')"
|
||||||
|
v-model="equalBrightnessCheckVal"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="row mb-3" v-for="(ledSetting, index) in deviceConfigList.led" :key="index">
|
<div class="row mb-3" v-for="(ledSetting, index) in deviceConfigList.led" :key="index">
|
||||||
<label :for="getLedIdFromNumber(index)" class="col-sm-2 col-form-label">{{
|
<label :for="getLedIdFromNumber(index)" class="col-sm-2 col-form-label">{{
|
||||||
$t('deviceadmin.LedBrightness', {
|
$t('deviceadmin.LedBrightness', {
|
||||||
led: index,
|
led: index,
|
||||||
brightness: $n(ledSetting.brightness / 100,
|
brightness: $n(ledSetting.brightness / 100, 'percent'),
|
||||||
'percent')
|
|
||||||
})
|
})
|
||||||
}}</label>
|
}}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="range" class="form-range" min="0" max="100" :id="getLedIdFromNumber(index)"
|
<input
|
||||||
v-model="ledSetting.brightness" @change="syncSliders" />
|
type="range"
|
||||||
|
class="form-range"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
:id="getLedIdFromNumber(index)"
|
||||||
|
v-model="ledSetting.brightness"
|
||||||
|
@change="syncSliders"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -151,19 +250,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormFooter @reload="getDeviceConfig" />
|
<FormFooter @reload="getDeviceConfig" />
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import FormFooter from '@/components/FormFooter.vue';
|
import FormFooter from '@/components/FormFooter.vue';
|
||||||
import InputElement from '@/components/InputElement.vue';
|
import InputElement from '@/components/InputElement.vue';
|
||||||
import PinInfo from '@/components/PinInfo.vue';
|
import PinInfo from '@/components/PinInfo.vue';
|
||||||
import type { DeviceConfig, Led } from "@/types/DeviceConfig";
|
import type { DeviceConfig, Led } from '@/types/DeviceConfig';
|
||||||
import type { PinMapping, Device } from "@/types/PinMapping";
|
import type { PinMapping, Device } from '@/types/PinMapping';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
@ -181,8 +279,8 @@ export default defineComponent({
|
|||||||
pinMappingLoading: true,
|
pinMappingLoading: true,
|
||||||
deviceConfigList: {} as DeviceConfig,
|
deviceConfigList: {} as DeviceConfig,
|
||||||
pinMappingList: {} as PinMapping,
|
pinMappingList: {} as PinMapping,
|
||||||
alertMessage: "",
|
alertMessage: '',
|
||||||
alertType: "info",
|
alertType: 'info',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
equalBrightnessCheckVal: false,
|
equalBrightnessCheckVal: false,
|
||||||
displayRotationList: [
|
displayRotationList: [
|
||||||
@ -192,16 +290,16 @@ export default defineComponent({
|
|||||||
{ key: 3, value: 'rot270' },
|
{ key: 3, value: 'rot270' },
|
||||||
],
|
],
|
||||||
displayLanguageList: [
|
displayLanguageList: [
|
||||||
{ key: 0, value: "en" },
|
{ key: 0, value: 'en' },
|
||||||
{ key: 1, value: "de" },
|
{ key: 1, value: 'de' },
|
||||||
{ key: 2, value: "fr" },
|
{ key: 2, value: 'fr' },
|
||||||
],
|
],
|
||||||
diagramModeList: [
|
diagramModeList: [
|
||||||
{ key: 0, value: "off" },
|
{ key: 0, value: 'off' },
|
||||||
{ key: 1, value: "small" },
|
{ key: 1, value: 'small' },
|
||||||
{ key: 2, value: "fullscreen" },
|
{ key: 2, value: 'fullscreen' },
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getDeviceConfig();
|
this.getDeviceConfig();
|
||||||
@ -212,46 +310,44 @@ export default defineComponent({
|
|||||||
if (!val) {
|
if (!val) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.deviceConfigList.led.every(v => v.brightness = this.deviceConfigList.led[0].brightness);
|
this.deviceConfigList.led.every((v) => (v.brightness = this.deviceConfigList.led[0].brightness));
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getPinMappingList() {
|
getPinMappingList() {
|
||||||
this.pinMappingLoading = true;
|
this.pinMappingLoading = true;
|
||||||
fetch("/api/config/get?file=pin_mapping.json", { headers: authHeader() })
|
fetch('/api/config/get?file=pin_mapping.json', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router, true))
|
.then((response) => handleResponse(response, this.$emitter, this.$router, true))
|
||||||
.then(
|
.then((data) => {
|
||||||
(data) => {
|
|
||||||
this.pinMappingList = data;
|
this.pinMappingList = data;
|
||||||
}
|
})
|
||||||
)
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (error.status != 404) {
|
if (error.status != 404) {
|
||||||
this.alertMessage = this.$t('deviceadmin.ParseError', { error: error.message });
|
this.alertMessage = this.$t('deviceadmin.ParseError', {
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
this.alertType = 'danger';
|
this.alertType = 'danger';
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
}
|
||||||
this.pinMappingList = Array<Device>();
|
this.pinMappingList = Array<Device>();
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.pinMappingList.sort((a, b) => (a.name < b.name) ? -1 : 1);
|
this.pinMappingList.sort((a, b) => (a.name < b.name ? -1 : 1));
|
||||||
this.pinMappingList.splice(0, 0, { "name": "Default" } as Device);
|
this.pinMappingList.splice(0, 0, { name: 'Default' } as Device);
|
||||||
this.pinMappingLoading = false;
|
this.pinMappingLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getDeviceConfig() {
|
getDeviceConfig() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/device/config", { headers: authHeader() })
|
fetch('/api/device/config', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((data) => {
|
||||||
(data) => {
|
|
||||||
this.deviceConfigList = data;
|
this.deviceConfigList = data;
|
||||||
if (this.deviceConfigList.curPin.name === "") {
|
if (this.deviceConfigList.curPin.name === '') {
|
||||||
this.deviceConfigList.curPin.name = "Default";
|
this.deviceConfigList.curPin.name = 'Default';
|
||||||
}
|
}
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
}
|
})
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.equalBrightnessCheckVal = this.isEqualBrightness();
|
this.equalBrightnessCheckVal = this.isEqualBrightness();
|
||||||
});
|
});
|
||||||
@ -260,30 +356,28 @@ export default defineComponent({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(this.deviceConfigList));
|
formData.append('data', JSON.stringify(this.deviceConfigList));
|
||||||
|
|
||||||
fetch("/api/device/config", {
|
fetch('/api/device/config', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
|
||||||
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertType = response.type;
|
this.alertType = response.type;
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getLedIdFromNumber(ledNo: number): string {
|
getLedIdFromNumber(ledNo: number): string {
|
||||||
return 'inputLED' + ledNo + 'Brightness';
|
return 'inputLED' + ledNo + 'Brightness';
|
||||||
},
|
},
|
||||||
getNumberFromLedId(id: string): number {
|
getNumberFromLedId(id: string): number {
|
||||||
return parseInt(id.replace("inputLED", "").replace("Brightness", ""));
|
return parseInt(id.replace('inputLED', '').replace('Brightness', ''));
|
||||||
},
|
},
|
||||||
isEqualBrightness(): boolean {
|
isEqualBrightness(): boolean {
|
||||||
const allEqual = (arr : Led[]) => arr.every(v => v.brightness === arr[0].brightness);
|
const allEqual = (arr: Led[]) => arr.every((v) => v.brightness === arr[0].brightness);
|
||||||
return allEqual(this.deviceConfigList.led);
|
return allEqual(this.deviceConfigList.led);
|
||||||
},
|
},
|
||||||
syncSliders(event: Event) {
|
syncSliders(event: Event) {
|
||||||
@ -291,8 +385,8 @@ export default defineComponent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const srcId = this.getNumberFromLedId((event.target as Element).id);
|
const srcId = this.getNumberFromLedId((event.target as Element).id);
|
||||||
this.deviceConfigList.led.every(v => v.brightness = this.deviceConfigList.led[srcId].brightness);
|
this.deviceConfigList.led.every((v) => (v.brightness = this.deviceConfigList.led[srcId].brightness));
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -6,15 +6,23 @@
|
|||||||
|
|
||||||
<form @submit="saveDtuConfig">
|
<form @submit="saveDtuConfig">
|
||||||
<CardElement :text="$t('dtuadmin.DtuConfiguration')" textVariant="text-bg-primary">
|
<CardElement :text="$t('dtuadmin.DtuConfiguration')" textVariant="text-bg-primary">
|
||||||
<InputElement :label="$t('dtuadmin.Serial')"
|
<InputElement
|
||||||
|
:label="$t('dtuadmin.Serial')"
|
||||||
v-model="dtuConfigList.serial"
|
v-model="dtuConfigList.serial"
|
||||||
type="number" min="1" max="199999999999"
|
type="number"
|
||||||
:tooltip="$t('dtuadmin.SerialHint')"/>
|
min="1"
|
||||||
|
max="199999999999"
|
||||||
|
:tooltip="$t('dtuadmin.SerialHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('dtuadmin.PollInterval')"
|
<InputElement
|
||||||
|
:label="$t('dtuadmin.PollInterval')"
|
||||||
v-model="dtuConfigList.pollinterval"
|
v-model="dtuConfigList.pollinterval"
|
||||||
type="number" min="1" max="86400"
|
type="number"
|
||||||
:postfix="$t('dtuadmin.Seconds')"/>
|
min="1"
|
||||||
|
max="86400"
|
||||||
|
:postfix="$t('dtuadmin.Seconds')"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="row mb-3" v-if="dtuConfigList.nrf_enabled">
|
<div class="row mb-3" v-if="dtuConfigList.nrf_enabled">
|
||||||
<label for="inputNrfPaLevel" class="col-sm-2 col-form-label">
|
<label for="inputNrfPaLevel" class="col-sm-2 col-form-label">
|
||||||
@ -37,11 +45,16 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input type="range" class="form-control form-range"
|
<input
|
||||||
|
type="range"
|
||||||
|
class="form-control form-range"
|
||||||
v-model="dtuConfigList.cmt_palevel"
|
v-model="dtuConfigList.cmt_palevel"
|
||||||
min="-10" max="20"
|
min="-10"
|
||||||
id="inputCmtPaLevel" aria-describedby="basic-addon1"
|
max="20"
|
||||||
style="height: unset;" />
|
id="inputCmtPaLevel"
|
||||||
|
aria-describedby="basic-addon1"
|
||||||
|
style="height: unset"
|
||||||
|
/>
|
||||||
<span class="input-group-text" id="basic-addon1">{{ cmtPaLevelText }}</span>
|
<span class="input-group-text" id="basic-addon1">{{ cmtPaLevelText }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -55,7 +68,12 @@
|
|||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select id="inputCmtCountry" class="form-select" v-model="dtuConfigList.cmt_country">
|
<select id="inputCmtCountry" class="form-select" v-model="dtuConfigList.cmt_country">
|
||||||
<option v-for="(country, index) in dtuConfigList.country_def" :key="index" :value="index">
|
<option v-for="(country, index) in dtuConfigList.country_def" :key="index" :value="index">
|
||||||
{{ $t(`dtuadmin.country_` + index, {min: country.freq_min / 1e6, max: country.freq_max / 1e6}) }}
|
{{
|
||||||
|
$t(`dtuadmin.country_` + index, {
|
||||||
|
min: country.freq_min / 1e6,
|
||||||
|
max: country.freq_max / 1e6,
|
||||||
|
})
|
||||||
|
}}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -68,17 +86,27 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input type="range" class="form-control form-range"
|
<input
|
||||||
|
type="range"
|
||||||
|
class="form-control form-range"
|
||||||
v-model="dtuConfigList.cmt_frequency"
|
v-model="dtuConfigList.cmt_frequency"
|
||||||
:min="cmtMinFrequency" :max="cmtMaxFrequency" :step="dtuConfigList.cmt_chan_width"
|
:min="cmtMinFrequency"
|
||||||
id="cmtFrequency" aria-describedby="basic-addon2"
|
:max="cmtMaxFrequency"
|
||||||
style="height: unset;" />
|
:step="dtuConfigList.cmt_chan_width"
|
||||||
|
id="cmtFrequency"
|
||||||
|
aria-describedby="basic-addon2"
|
||||||
|
style="height: unset"
|
||||||
|
/>
|
||||||
<span class="input-group-text" id="basic-addon2">{{ cmtFrequencyText }}</span>
|
<span class="input-group-text" id="basic-addon2">{{ cmtFrequencyText }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-danger" role="alert" v-html="$t('dtuadmin.CmtFrequencyWarning')" v-if="cmtIsOutOfLegalRange"></div>
|
<div
|
||||||
|
class="alert alert-danger"
|
||||||
|
role="alert"
|
||||||
|
v-html="$t('dtuadmin.CmtFrequencyWarning')"
|
||||||
|
v-if="cmtIsOutOfLegalRange"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</CardElement>
|
</CardElement>
|
||||||
<FormFooter @reload="getDtuConfig" />
|
<FormFooter @reload="getDtuConfig" />
|
||||||
</form>
|
</form>
|
||||||
@ -87,11 +115,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import FormFooter from '@/components/FormFooter.vue';
|
import FormFooter from '@/components/FormFooter.vue';
|
||||||
import InputElement from '@/components/InputElement.vue';
|
import InputElement from '@/components/InputElement.vue';
|
||||||
import type { DtuConfig } from "@/types/DtuConfig";
|
import type { DtuConfig } from '@/types/DtuConfig';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import { BIconInfoCircle } from 'bootstrap-icons-vue';
|
import { BIconInfoCircle } from 'bootstrap-icons-vue';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
@ -110,13 +138,13 @@ export default defineComponent({
|
|||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
dtuConfigList: {} as DtuConfig,
|
dtuConfigList: {} as DtuConfig,
|
||||||
nrfpalevelList: [
|
nrfpalevelList: [
|
||||||
{ key: 0, value: 'Min', db: "-18" },
|
{ key: 0, value: 'Min', db: '-18' },
|
||||||
{ key: 1, value: 'Low', db: "-12" },
|
{ key: 1, value: 'Low', db: '-12' },
|
||||||
{ key: 2, value: 'High', db: "-6" },
|
{ key: 2, value: 'High', db: '-6' },
|
||||||
{ key: 3, value: 'Max', db: "0" },
|
{ key: 3, value: 'Max', db: '0' },
|
||||||
],
|
],
|
||||||
alertMessage: "",
|
alertMessage: '',
|
||||||
alertType: "info",
|
alertType: 'info',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -125,10 +153,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
cmtFrequencyText() {
|
cmtFrequencyText() {
|
||||||
return this.$t("dtuadmin.MHz", { mhz: this.$n(this.dtuConfigList.cmt_frequency / 1000000, "decimalTwoDigits") });
|
return this.$t('dtuadmin.MHz', {
|
||||||
|
mhz: this.$n(this.dtuConfigList.cmt_frequency / 1000000, 'decimalTwoDigits'),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
cmtPaLevelText() {
|
cmtPaLevelText() {
|
||||||
return this.$t("dtuadmin.dBm", { dbm: this.$n(this.dtuConfigList.cmt_palevel * 1) });
|
return this.$t('dtuadmin.dBm', { dbm: this.$n(this.dtuConfigList.cmt_palevel * 1) });
|
||||||
},
|
},
|
||||||
cmtMinFrequency() {
|
cmtMinFrequency() {
|
||||||
return this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_min;
|
return this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_min;
|
||||||
@ -137,9 +167,13 @@ export default defineComponent({
|
|||||||
return this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_max;
|
return this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_max;
|
||||||
},
|
},
|
||||||
cmtIsOutOfLegalRange() {
|
cmtIsOutOfLegalRange() {
|
||||||
return this.dtuConfigList.cmt_frequency < this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_legal_min
|
return (
|
||||||
|| this.dtuConfigList.cmt_frequency > this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_legal_max;
|
this.dtuConfigList.cmt_frequency <
|
||||||
}
|
this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_legal_min ||
|
||||||
|
this.dtuConfigList.cmt_frequency >
|
||||||
|
this.dtuConfigList.country_def[this.dtuConfigList.cmt_country].freq_legal_max
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'dtuConfigList.cmt_country'(newValue, oldValue) {
|
'dtuConfigList.cmt_country'(newValue, oldValue) {
|
||||||
@ -149,39 +183,35 @@ export default defineComponent({
|
|||||||
this.dtuConfigList.cmt_frequency = this.dtuConfigList.country_def[newValue].freq_default;
|
this.dtuConfigList.cmt_frequency = this.dtuConfigList.country_def[newValue].freq_default;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getDtuConfig() {
|
getDtuConfig() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/dtu/config", { headers: authHeader() })
|
fetch('/api/dtu/config', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((data) => {
|
||||||
(data) => {
|
|
||||||
this.dtuConfigList = data;
|
this.dtuConfigList = data;
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
saveDtuConfig(e: Event) {
|
saveDtuConfig(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(this.dtuConfigList));
|
formData.append('data', JSON.stringify(this.dtuConfigList));
|
||||||
|
|
||||||
fetch("/api/dtu/config", {
|
fetch('/api/dtu/config', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
|
||||||
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertType = response.type;
|
this.alertType = response.type;
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,7 +8,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CardElement :text="$t('firmwareupgrade.OtaError')" textVariant="text-bg-danger" center-content
|
<CardElement
|
||||||
|
:text="$t('firmwareupgrade.OtaError')"
|
||||||
|
textVariant="text-bg-danger"
|
||||||
|
center-content
|
||||||
v-if="!loading && !uploading && OTAError != ''"
|
v-if="!loading && !uploading && OTAError != ''"
|
||||||
>
|
>
|
||||||
<p class="h1 mb-2">
|
<p class="h1 mb-2">
|
||||||
@ -20,15 +23,16 @@
|
|||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<button class="btn btn-light" @click="clear">
|
<button class="btn btn-light" @click="clear"><BIconArrowLeft /> {{ $t('firmwareupgrade.Back') }}</button>
|
||||||
<BIconArrowLeft /> {{ $t('firmwareupgrade.Back') }}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-primary" @click="retryOTA">
|
<button class="btn btn-primary" @click="retryOTA">
|
||||||
<BIconArrowRepeat /> {{ $t('firmwareupgrade.Retry') }}
|
<BIconArrowRepeat /> {{ $t('firmwareupgrade.Retry') }}
|
||||||
</button>
|
</button>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('firmwareupgrade.OtaStatus')" textVariant="text-bg-success" center-content
|
<CardElement
|
||||||
|
:text="$t('firmwareupgrade.OtaStatus')"
|
||||||
|
textVariant="text-bg-success"
|
||||||
|
center-content
|
||||||
v-else-if="!loading && !uploading && OTASuccess"
|
v-else-if="!loading && !uploading && OTASuccess"
|
||||||
>
|
>
|
||||||
<span class="h1 mb-2">
|
<span class="h1 mb-2">
|
||||||
@ -44,7 +48,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('firmwareupgrade.FirmwareUpload')" textVariant="text-bg-primary" center-content
|
<CardElement
|
||||||
|
:text="$t('firmwareupgrade.FirmwareUpload')"
|
||||||
|
textVariant="text-bg-primary"
|
||||||
|
center-content
|
||||||
v-else-if="!loading && !uploading"
|
v-else-if="!loading && !uploading"
|
||||||
>
|
>
|
||||||
<div class="form-group pt-2 mt-3">
|
<div class="form-group pt-2 mt-3">
|
||||||
@ -52,17 +59,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('firmwareupgrade.UploadProgress')" textVariant="text-bg-primary" center-content
|
<CardElement
|
||||||
|
:text="$t('firmwareupgrade.UploadProgress')"
|
||||||
|
textVariant="text-bg-primary"
|
||||||
|
center-content
|
||||||
v-else-if="!loading && uploading"
|
v-else-if="!loading && uploading"
|
||||||
>
|
>
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<div class="progress-bar" role="progressbar" :style="{ width: progress + '%' }"
|
<div
|
||||||
v-bind:aria-valuenow="progress" aria-valuemin="0" aria-valuemax="100">
|
class="progress-bar"
|
||||||
|
role="progressbar"
|
||||||
|
:style="{ width: progress + '%' }"
|
||||||
|
v-bind:aria-valuenow="progress"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
>
|
||||||
{{ progress }}%
|
{{ progress }}%
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
</BasePage>
|
</BasePage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -70,13 +85,8 @@
|
|||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import { authHeader, isLoggedIn } from '@/utils/authentication';
|
import { authHeader, isLoggedIn } from '@/utils/authentication';
|
||||||
import {
|
import { BIconArrowLeft, BIconArrowRepeat, BIconCheckCircle, BIconExclamationCircleFill } from 'bootstrap-icons-vue';
|
||||||
BIconArrowLeft,
|
import SparkMD5 from 'spark-md5';
|
||||||
BIconArrowRepeat,
|
|
||||||
BIconCheckCircle,
|
|
||||||
BIconExclamationCircleFill
|
|
||||||
} from 'bootstrap-icons-vue';
|
|
||||||
import SparkMD5 from "spark-md5";
|
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -93,11 +103,11 @@ export default defineComponent({
|
|||||||
loading: true,
|
loading: true,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
OTAError: "",
|
OTAError: '',
|
||||||
OTASuccess: false,
|
OTASuccess: false,
|
||||||
type: "firmware",
|
type: 'firmware',
|
||||||
file: {} as Blob,
|
file: {} as Blob,
|
||||||
hostCheckInterval: 0
|
hostCheckInterval: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -124,8 +134,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
const loadNext = () => {
|
const loadNext = () => {
|
||||||
const start = currentChunk * chunkSize;
|
const start = currentChunk * chunkSize;
|
||||||
const end =
|
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
|
||||||
start + chunkSize >= file.size ? file.size : start + chunkSize;
|
|
||||||
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
|
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
|
||||||
};
|
};
|
||||||
loadNext();
|
loadNext();
|
||||||
@ -141,7 +150,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
request.addEventListener("load", () => {
|
request.addEventListener('load', () => {
|
||||||
// request.response will hold the response from the server
|
// request.response will hold the response from the server
|
||||||
if (request.status === 200) {
|
if (request.status === 200) {
|
||||||
this.OTASuccess = true;
|
this.OTASuccess = true;
|
||||||
@ -155,56 +164,55 @@ export default defineComponent({
|
|||||||
this.progress = 0;
|
this.progress = 0;
|
||||||
});
|
});
|
||||||
// Upload progress
|
// Upload progress
|
||||||
request.upload.addEventListener("progress", (e) => {
|
request.upload.addEventListener('progress', (e) => {
|
||||||
this.progress = Math.trunc((e.loaded / e.total) * 100);
|
this.progress = Math.trunc((e.loaded / e.total) * 100);
|
||||||
});
|
});
|
||||||
request.withCredentials = true;
|
request.withCredentials = true;
|
||||||
this.fileMD5(this.file)
|
this.fileMD5(this.file)
|
||||||
.then((md5) => {
|
.then((md5) => {
|
||||||
formData.append("MD5", (md5 as string));
|
formData.append('MD5', md5 as string);
|
||||||
formData.append("firmware", this.file, "firmware");
|
formData.append('firmware', this.file, 'firmware');
|
||||||
request.open("post", "/api/firmware/update");
|
request.open('post', '/api/firmware/update');
|
||||||
authHeader().forEach((value, key) => {
|
authHeader().forEach((value, key) => {
|
||||||
request.setRequestHeader(key, value);
|
request.setRequestHeader(key, value);
|
||||||
});
|
});
|
||||||
request.send(formData);
|
request.send(formData);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.OTAError =
|
this.OTAError = 'Unknown error while upload, check the console for details.';
|
||||||
"Unknown error while upload, check the console for details.";
|
|
||||||
this.uploading = false;
|
this.uploading = false;
|
||||||
this.progress = 0;
|
this.progress = 0;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
retryOTA() {
|
retryOTA() {
|
||||||
this.OTAError = "";
|
this.OTAError = '';
|
||||||
this.OTASuccess = false;
|
this.OTASuccess = false;
|
||||||
this.uploadOTA(null);
|
this.uploadOTA(null);
|
||||||
},
|
},
|
||||||
clear() {
|
clear() {
|
||||||
this.OTAError = "";
|
this.OTAError = '';
|
||||||
this.OTASuccess = false;
|
this.OTASuccess = false;
|
||||||
},
|
},
|
||||||
checkRemoteHostAndReload(): void {
|
checkRemoteHostAndReload(): void {
|
||||||
// Check if the browser is online
|
// Check if the browser is online
|
||||||
if (navigator.onLine) {
|
if (navigator.onLine) {
|
||||||
const remoteHostUrl = "/api/system/status";
|
const remoteHostUrl = '/api/system/status';
|
||||||
|
|
||||||
// Use a simple fetch request to check if the remote host is reachable
|
// Use a simple fetch request to check if the remote host is reachable
|
||||||
fetch(remoteHostUrl, { method: 'GET' })
|
fetch(remoteHostUrl, { method: 'GET' })
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
// Check if the response status is OK (200-299 range)
|
// Check if the response status is OK (200-299 range)
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
console.log('Remote host is available. Reloading page...');
|
console.log('Remote host is available. Reloading page...');
|
||||||
clearInterval(this.hostCheckInterval);
|
clearInterval(this.hostCheckInterval);
|
||||||
this.hostCheckInterval = 0;
|
this.hostCheckInterval = 0;
|
||||||
// Perform a page reload
|
// Perform a page reload
|
||||||
window.location.replace("/");
|
window.location.replace('/');
|
||||||
} else {
|
} else {
|
||||||
console.log('Remote host is not reachable. Do something else if needed.');
|
console.log('Remote host is not reachable. Do something else if needed.');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error('Error checking remote host:', error);
|
console.error('Error checking remote host:', error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -214,12 +222,15 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (!isLoggedIn()) {
|
if (!isLoggedIn()) {
|
||||||
this.$router.push({ path: "/login", query: { returnUrl: this.$router.currentRoute.value.fullPath } });
|
this.$router.push({
|
||||||
|
path: '/login',
|
||||||
|
query: { returnUrl: this.$router.currentRoute.value.fullPath },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
clearInterval(this.hostCheckInterval);
|
clearInterval(this.hostCheckInterval);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,18 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasePage :title="$t('home.LiveData')" :isLoading="dataLoading" :isWideScreen="true" :showWebSocket="true" :isWebsocketConnected="isWebsocketConnected" @reload="reloadData">
|
<BasePage
|
||||||
|
:title="$t('home.LiveData')"
|
||||||
|
:isLoading="dataLoading"
|
||||||
|
:isWideScreen="true"
|
||||||
|
:showWebSocket="true"
|
||||||
|
:isWebsocketConnected="isWebsocketConnected"
|
||||||
|
@reload="reloadData"
|
||||||
|
>
|
||||||
<HintView :hints="liveData.hints" />
|
<HintView :hints="liveData.hints" />
|
||||||
<InverterTotalInfo :totalData="liveData.total" /><br />
|
<InverterTotalInfo :totalData="liveData.total" /><br />
|
||||||
<div class="row gy-3">
|
<div class="row gy-3">
|
||||||
<div class="col-sm-3 col-md-2" :style="[inverterData.length == 1 ? { 'display': 'none' } : {}]">
|
<div class="col-sm-3 col-md-2" :style="[inverterData.length == 1 ? { display: 'none' } : {}]">
|
||||||
<div class="nav nav-pills row-cols-sm-1" id="v-pills-tab" role="tablist" aria-orientation="vertical">
|
<div class="nav nav-pills row-cols-sm-1" id="v-pills-tab" role="tablist" aria-orientation="vertical">
|
||||||
<button v-for="inverter in inverterData" :key="inverter.serial" class="nav-link border border-primary text-break"
|
<button
|
||||||
:id="'v-pills-' + inverter.serial + '-tab'" data-bs-toggle="pill"
|
v-for="inverter in inverterData"
|
||||||
:data-bs-target="'#v-pills-' + inverter.serial" type="button" role="tab"
|
:key="inverter.serial"
|
||||||
aria-controls="'v-pills-' + inverter.serial" aria-selected="true">
|
class="nav-link border border-primary text-break"
|
||||||
|
:id="'v-pills-' + inverter.serial + '-tab'"
|
||||||
|
data-bs-toggle="pill"
|
||||||
|
:data-bs-target="'#v-pills-' + inverter.serial"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="'v-pills-' + inverter.serial"
|
||||||
|
aria-selected="true"
|
||||||
|
>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto col-sm-2">
|
<div class="col-auto col-sm-2">
|
||||||
<BIconXCircleFill class="fs-4" v-if="!inverter.reachable" />
|
<BIconXCircleFill class="fs-4" v-if="!inverter.reachable" />
|
||||||
<BIconExclamationCircleFill class="fs-4" v-if="inverter.reachable && !inverter.producing" />
|
<BIconExclamationCircleFill
|
||||||
|
class="fs-4"
|
||||||
|
v-if="inverter.reachable && !inverter.producing"
|
||||||
|
/>
|
||||||
<BIconCheckCircleFill class="fs-4" v-if="inverter.reachable && inverter.producing" />
|
<BIconCheckCircleFill class="fs-4" v-if="inverter.reachable && inverter.producing" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@ -23,36 +41,50 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-content" id="v-pills-tabContent" :class="{
|
<div
|
||||||
|
class="tab-content"
|
||||||
|
id="v-pills-tabContent"
|
||||||
|
:class="{
|
||||||
'col-sm-9 col-md-10': inverterData.length > 1,
|
'col-sm-9 col-md-10': inverterData.length > 1,
|
||||||
'col-sm-12 col-md-12': inverterData.length == 1
|
'col-sm-12 col-md-12': inverterData.length == 1,
|
||||||
}">
|
}"
|
||||||
<div v-for="inverter in inverterData" :key="inverter.serial" class="tab-pane fade show"
|
>
|
||||||
:id="'v-pills-' + inverter.serial" role="tabpanel"
|
<div
|
||||||
:aria-labelledby="'v-pills-' + inverter.serial + '-tab'" tabindex="0">
|
v-for="inverter in inverterData"
|
||||||
|
:key="inverter.serial"
|
||||||
|
class="tab-pane fade show"
|
||||||
|
:id="'v-pills-' + inverter.serial"
|
||||||
|
role="tabpanel"
|
||||||
|
:aria-labelledby="'v-pills-' + inverter.serial + '-tab'"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center"
|
<div
|
||||||
|
class="card-header d-flex justify-content-between align-items-center"
|
||||||
:class="{
|
:class="{
|
||||||
'text-bg-tertiary': !inverter.poll_enabled,
|
'text-bg-tertiary': !inverter.poll_enabled,
|
||||||
'text-bg-danger': inverter.poll_enabled && !inverter.reachable,
|
'text-bg-danger': inverter.poll_enabled && !inverter.reachable,
|
||||||
'text-bg-warning': inverter.poll_enabled && inverter.reachable && !inverter.producing,
|
'text-bg-warning': inverter.poll_enabled && inverter.reachable && !inverter.producing,
|
||||||
'text-bg-primary': inverter.poll_enabled && inverter.reachable && inverter.producing,
|
'text-bg-primary': inverter.poll_enabled && inverter.reachable && inverter.producing,
|
||||||
}">
|
}"
|
||||||
|
>
|
||||||
<div class="p-1 flex-grow-1">
|
<div class="p-1 flex-grow-1">
|
||||||
<div class="d-flex flex-wrap">
|
<div class="d-flex flex-wrap">
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em">
|
||||||
{{ inverter.name }}
|
{{ inverter.name }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em">
|
||||||
{{ $t('home.SerialNumber') }}{{ inverter.serial }}
|
{{ $t('home.SerialNumber') }}{{ inverter.serial }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em">
|
||||||
{{ $t('home.CurrentLimit') }}<template v-if="inverter.limit_absolute > -1"> {{
|
{{ $t('home.CurrentLimit')
|
||||||
$n(inverter.limit_absolute, 'decimalNoDigits')
|
}}<template v-if="inverter.limit_absolute > -1">
|
||||||
}} W | </template>{{ $n(inverter.limit_relative / 100, 'percent') }}
|
{{ $n(inverter.limit_absolute, 'decimalNoDigits') }} W | </template
|
||||||
|
>{{ $n(inverter.limit_relative / 100, 'percentOneDigit') }}
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-right: 2em;">
|
<div style="padding-right: 2em">
|
||||||
{{ $t('home.DataAge') }} {{ $t('home.Seconds', {'val': $n(inverter.data_age) }) }}
|
{{ $t('home.DataAge') }}
|
||||||
|
{{ $t('home.Seconds', { val: $n(inverter.data_age) }) }}
|
||||||
<template v-if="inverter.data_age > 300">
|
<template v-if="inverter.data_age > 300">
|
||||||
/ {{ calculateAbsoluteTime(inverter.data_age) }}
|
/ {{ calculateAbsoluteTime(inverter.data_age) }}
|
||||||
</template>
|
</template>
|
||||||
@ -61,44 +93,68 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="btn-toolbar p-2" role="toolbar">
|
<div class="btn-toolbar p-2" role="toolbar">
|
||||||
<div class="btn-group me-2" role="group">
|
<div class="btn-group me-2" role="group">
|
||||||
<button :disabled="!isLogged" type="button" class="btn btn-sm btn-danger"
|
<button
|
||||||
@click="onShowLimitSettings(inverter.serial)" v-tooltip :title="$t('home.ShowSetInverterLimit')">
|
:disabled="!isLogged"
|
||||||
<BIconSpeedometer style="font-size:24px;" />
|
type="button"
|
||||||
|
class="btn btn-sm btn-danger"
|
||||||
|
@click="onShowLimitSettings(inverter.serial)"
|
||||||
|
v-tooltip
|
||||||
|
:title="$t('home.ShowSetInverterLimit')"
|
||||||
|
>
|
||||||
|
<BIconSpeedometer style="font-size: 24px" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group me-2" role="group">
|
<div class="btn-group me-2" role="group">
|
||||||
<button :disabled="!isLogged" type="button" class="btn btn-sm btn-danger"
|
<button
|
||||||
@click="onShowPowerSettings(inverter.serial)" v-tooltip :title="$t('home.TurnOnOff')">
|
:disabled="!isLogged"
|
||||||
<BIconPower style="font-size:24px;" />
|
type="button"
|
||||||
|
class="btn btn-sm btn-danger"
|
||||||
|
@click="onShowPowerSettings(inverter.serial)"
|
||||||
|
v-tooltip
|
||||||
|
:title="$t('home.TurnOnOff')"
|
||||||
|
>
|
||||||
|
<BIconPower style="font-size: 24px" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group me-2" role="group">
|
<div class="btn-group me-2" role="group">
|
||||||
<button type="button" class="btn btn-sm btn-info"
|
<button
|
||||||
@click="onShowDevInfo(inverter.serial)" v-tooltip :title="$t('home.ShowInverterInfo')">
|
type="button"
|
||||||
<BIconCpu style="font-size:24px;" />
|
class="btn btn-sm btn-info"
|
||||||
|
@click="onShowDevInfo(inverter.serial)"
|
||||||
|
v-tooltip
|
||||||
|
:title="$t('home.ShowInverterInfo')"
|
||||||
|
>
|
||||||
|
<BIconCpu style="font-size: 24px" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group me-2" role="group">
|
<div class="btn-group me-2" role="group">
|
||||||
<button type="button" class="btn btn-sm btn-info"
|
<button
|
||||||
@click="onShowGridProfile(inverter.serial)" v-tooltip :title="$t('home.ShowGridProfile')">
|
type="button"
|
||||||
<BIconOutlet style="font-size:24px;" />
|
class="btn btn-sm btn-info"
|
||||||
|
@click="onShowGridProfile(inverter.serial)"
|
||||||
|
v-tooltip
|
||||||
|
:title="$t('home.ShowGridProfile')"
|
||||||
|
>
|
||||||
|
<BIconOutlet style="font-size: 24px" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button v-if="inverter.events >= 0" type="button"
|
<button
|
||||||
|
v-if="inverter.events >= 0"
|
||||||
|
type="button"
|
||||||
class="btn btn-sm btn-secondary position-relative"
|
class="btn btn-sm btn-secondary position-relative"
|
||||||
@click="onShowEventlog(inverter.serial)" v-tooltip :title="$t('home.ShowEventlog')">
|
@click="onShowEventlog(inverter.serial)"
|
||||||
<BIconJournalText style="font-size:24px;" />
|
v-tooltip
|
||||||
|
:title="$t('home.ShowEventlog')"
|
||||||
|
>
|
||||||
|
<BIconJournalText style="font-size: 24px" />
|
||||||
<span
|
<span
|
||||||
class="position-absolute top-0 start-100 translate-middle badge rounded-pill text-bg-danger">
|
class="position-absolute top-0 start-100 translate-middle badge rounded-pill text-bg-danger"
|
||||||
|
>
|
||||||
{{ inverter.events }}
|
{{ inverter.events }}
|
||||||
<span class="visually-hidden">{{ $t('home.UnreadMessages') }}</span>
|
<span class="visually-hidden">{{ $t('home.UnreadMessages') }}</span>
|
||||||
</span>
|
</span>
|
||||||
@ -108,17 +164,37 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row flex-row-reverse flex-wrap-reverse g-3">
|
<div class="row flex-row-reverse flex-wrap-reverse g-3">
|
||||||
<template v-for="chanType in [{obj: inverter.INV, name: 'INV'}, {obj: inverter.AC, name: 'AC'}, {obj: inverter.DC, name: 'DC'}].reverse()">
|
<template
|
||||||
|
v-for="chanType in [
|
||||||
|
{ obj: inverter.INV, name: 'INV' },
|
||||||
|
{ obj: inverter.AC, name: 'AC' },
|
||||||
|
{ obj: inverter.DC, name: 'DC' },
|
||||||
|
].reverse()"
|
||||||
|
>
|
||||||
<template v-if="chanType.obj != null">
|
<template v-if="chanType.obj != null">
|
||||||
<template v-for="channel in Object.keys(chanType.obj).sort().reverse().map(x=>+x)" :key="channel">
|
<template
|
||||||
<template v-if="(chanType.name != 'DC') ||
|
v-for="channel in Object.keys(chanType.obj)
|
||||||
|
.sort()
|
||||||
|
.reverse()
|
||||||
|
.map((x) => +x)"
|
||||||
|
:key="channel"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="
|
||||||
|
chanType.name != 'DC' ||
|
||||||
(chanType.name == 'DC' && getSumIrridiation(inverter) == 0) ||
|
(chanType.name == 'DC' && getSumIrridiation(inverter) == 0) ||
|
||||||
(chanType.name == 'DC' && getSumIrridiation(inverter) > 0 && chanType.obj[channel].Irradiation?.max || 0 > 0)
|
(chanType.name == 'DC' &&
|
||||||
">
|
getSumIrridiation(inverter) > 0 &&
|
||||||
|
chanType.obj[channel].Irradiation?.max) ||
|
||||||
|
0 > 0
|
||||||
|
"
|
||||||
|
>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<InverterChannelInfo :channelData="chanType.obj[channel]"
|
<InverterChannelInfo
|
||||||
|
:channelData="chanType.obj[channel]"
|
||||||
:channelType="chanType.name"
|
:channelType="chanType.name"
|
||||||
:channelNumber="channel" />
|
:channelNumber="channel"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@ -158,20 +234,31 @@
|
|||||||
</BootstrapAlert>
|
</BootstrapAlert>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label for="inputCurrentLimit" class="col-sm-3 col-form-label">{{ $t('home.CurrentLimit') }}
|
<label for="inputCurrentLimit" class="col-sm-3 col-form-label">{{ $t('home.CurrentLimit') }} </label>
|
||||||
</label>
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" id="inputCurrentLimit" aria-describedby="currentLimitType"
|
<input
|
||||||
v-model="currentLimitRelative" disabled />
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="inputCurrentLimit"
|
||||||
|
aria-describedby="currentLimitType"
|
||||||
|
v-model="currentLimitRelative"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
<span class="input-group-text" id="currentLimitType">%</span>
|
<span class="input-group-text" id="currentLimitType">%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-4" v-if="currentLimitList.max_power > 0">
|
<div class="col-sm-4" v-if="currentLimitList.max_power > 0">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" id="inputCurrentLimitAbsolute"
|
<input
|
||||||
aria-describedby="currentLimitTypeAbsolute" v-model="currentLimitAbsolute" disabled />
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="inputCurrentLimitAbsolute"
|
||||||
|
aria-describedby="currentLimitTypeAbsolute"
|
||||||
|
v-model="currentLimitAbsolute"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
<span class="input-group-text" id="currentLimitTypeAbsolute">W</span>
|
<span class="input-group-text" id="currentLimitTypeAbsolute">W</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -182,45 +269,67 @@
|
|||||||
{{ $t('home.LastLimitSetStatus') }}
|
{{ $t('home.LastLimitSetStatus') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<span class="badge" :class="{
|
<span
|
||||||
|
class="badge"
|
||||||
|
:class="{
|
||||||
'text-bg-danger': currentLimitList.limit_set_status == 'Failure',
|
'text-bg-danger': currentLimitList.limit_set_status == 'Failure',
|
||||||
'text-bg-warning': currentLimitList.limit_set_status == 'Pending',
|
'text-bg-warning': currentLimitList.limit_set_status == 'Pending',
|
||||||
'text-bg-success': currentLimitList.limit_set_status == 'Ok',
|
'text-bg-success': currentLimitList.limit_set_status == 'Ok',
|
||||||
'text-bg-secondary': currentLimitList.limit_set_status == 'Unknown',
|
'text-bg-secondary': currentLimitList.limit_set_status == 'Unknown',
|
||||||
}">
|
}"
|
||||||
|
>
|
||||||
{{ $t('home.' + currentLimitList.limit_set_status) }}
|
{{ $t('home.' + currentLimitList.limit_set_status) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label for="inputTargetLimit" class="col-sm-3 col-form-label">{{ $t('home.SetLimit')
|
<label for="inputTargetLimit" class="col-sm-3 col-form-label">{{ $t('home.SetLimit') }}</label>
|
||||||
}}</label>
|
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="number" name="inputTargetLimit" class="form-control" id="inputTargetLimit"
|
<input
|
||||||
:min="targetLimitMin" :max="targetLimitMax" v-model="targetLimitList.limit_value">
|
type="number"
|
||||||
<button class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
name="inputTargetLimit"
|
||||||
aria-expanded="false">{{ targetLimitTypeText
|
class="form-control"
|
||||||
}}</button>
|
id="inputTargetLimit"
|
||||||
|
:min="targetLimitMin"
|
||||||
|
:max="targetLimitMax"
|
||||||
|
v-model="targetLimitList.limit_value"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary dropdown-toggle"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
{{ targetLimitTypeText }}
|
||||||
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<li><a class="dropdown-item" @click="onSelectType(1)" href="#">{{
|
<li>
|
||||||
$t('home.Relative') }}</a></li>
|
<a class="dropdown-item" @click="onSelectType(1)" href="#">{{ $t('home.Relative') }}</a>
|
||||||
<li><a class="dropdown-item" @click="onSelectType(0)" href="#">{{
|
</li>
|
||||||
$t('home.Absolute') }}</a></li>
|
<li>
|
||||||
|
<a class="dropdown-item" @click="onSelectType(0)" href="#">{{ $t('home.Absolute') }}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="targetLimitType == 0" class="alert alert-secondary mt-3" role="alert"
|
<div
|
||||||
v-html="$t('home.LimitHint')"></div>
|
v-if="targetLimitType == 0"
|
||||||
|
class="alert alert-secondary mt-3"
|
||||||
|
role="alert"
|
||||||
|
v-html="$t('home.LimitHint')"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<button type="button" class="btn btn-danger" @click="onSetLimitSettings(true)">{{
|
<button type="button" class="btn btn-danger" @click="onSetLimitSettings(true)">
|
||||||
$t('home.SetPersistent') }}</button>
|
{{ $t('home.SetPersistent') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
<button type="button" class="btn btn-danger" @click="onSetLimitSettings(false)">{{
|
<button type="button" class="btn btn-danger" @click="onSetLimitSettings(false)">
|
||||||
$t('home.SetNonPersistent') }}</button>
|
{{ $t('home.SetNonPersistent') }}
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
|
|
||||||
@ -230,15 +339,17 @@
|
|||||||
</BootstrapAlert>
|
</BootstrapAlert>
|
||||||
|
|
||||||
<div class="row mb-3 align-items-center">
|
<div class="row mb-3 align-items-center">
|
||||||
<label for="inputLastPowerSet" class="col col-form-label">{{ $t('home.LastPowerSetStatus')
|
<label for="inputLastPowerSet" class="col col-form-label">{{ $t('home.LastPowerSetStatus') }}</label>
|
||||||
}}</label>
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<span class="badge" :class="{
|
<span
|
||||||
|
class="badge"
|
||||||
|
:class="{
|
||||||
'text-bg-danger': successCommandPower == 'Failure',
|
'text-bg-danger': successCommandPower == 'Failure',
|
||||||
'text-bg-warning': successCommandPower == 'Pending',
|
'text-bg-warning': successCommandPower == 'Pending',
|
||||||
'text-bg-success': successCommandPower == 'Ok',
|
'text-bg-success': successCommandPower == 'Ok',
|
||||||
'text-bg-secondary': successCommandPower == 'Unknown',
|
'text-bg-secondary': successCommandPower == 'Unknown',
|
||||||
}">
|
}"
|
||||||
|
>
|
||||||
{{ $t('home.' + successCommandPower) }}
|
{{ $t('home.' + successCommandPower) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -265,7 +376,7 @@ import DevInfo from '@/components/DevInfo.vue';
|
|||||||
import EventLog from '@/components/EventLog.vue';
|
import EventLog from '@/components/EventLog.vue';
|
||||||
import GridProfile from '@/components/GridProfile.vue';
|
import GridProfile from '@/components/GridProfile.vue';
|
||||||
import HintView from '@/components/HintView.vue';
|
import HintView from '@/components/HintView.vue';
|
||||||
import InverterChannelInfo from "@/components/InverterChannelInfo.vue";
|
import InverterChannelInfo from '@/components/InverterChannelInfo.vue';
|
||||||
import InverterTotalInfo from '@/components/InverterTotalInfo.vue';
|
import InverterTotalInfo from '@/components/InverterTotalInfo.vue';
|
||||||
import ModalDialog from '@/components/ModalDialog.vue';
|
import ModalDialog from '@/components/ModalDialog.vue';
|
||||||
import type { DevInfoStatus } from '@/types/DevInfoStatus';
|
import type { DevInfoStatus } from '@/types/DevInfoStatus';
|
||||||
@ -288,7 +399,7 @@ import {
|
|||||||
BIconSpeedometer,
|
BIconSpeedometer,
|
||||||
BIconToggleOff,
|
BIconToggleOff,
|
||||||
BIconToggleOn,
|
BIconToggleOn,
|
||||||
BIconXCircleFill
|
BIconXCircleFill,
|
||||||
} from 'bootstrap-icons-vue';
|
} from 'bootstrap-icons-vue';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
@ -347,17 +458,17 @@ export default defineComponent({
|
|||||||
targetLimitTypeText: this.$t('home.Relative'),
|
targetLimitTypeText: this.$t('home.Relative'),
|
||||||
targetLimitType: 1,
|
targetLimitType: 1,
|
||||||
|
|
||||||
alertMessageLimit: "",
|
alertMessageLimit: '',
|
||||||
alertTypeLimit: "info",
|
alertTypeLimit: 'info',
|
||||||
showAlertLimit: false,
|
showAlertLimit: false,
|
||||||
|
|
||||||
powerSettingView: {} as bootstrap.Modal,
|
powerSettingView: {} as bootstrap.Modal,
|
||||||
powerSettingSerial: "",
|
powerSettingSerial: '',
|
||||||
powerSettingLoading: true,
|
powerSettingLoading: true,
|
||||||
alertMessagePower: "",
|
alertMessagePower: '',
|
||||||
alertTypePower: "info",
|
alertTypePower: 'info',
|
||||||
showAlertPower: false,
|
showAlertPower: false,
|
||||||
successCommandPower: "",
|
successCommandPower: '',
|
||||||
|
|
||||||
isWebsocketConnected: false,
|
isWebsocketConnected: false,
|
||||||
};
|
};
|
||||||
@ -366,10 +477,10 @@ export default defineComponent({
|
|||||||
this.getInitialData();
|
this.getInitialData();
|
||||||
this.initSocket();
|
this.initSocket();
|
||||||
this.initDataAgeing();
|
this.initDataAgeing();
|
||||||
this.$emitter.on("logged-in", () => {
|
this.$emitter.on('logged-in', () => {
|
||||||
this.isLogged = this.isLoggedIn();
|
this.isLogged = this.isLoggedIn();
|
||||||
});
|
});
|
||||||
this.$emitter.on("logged-out", () => {
|
this.$emitter.on('logged-out', () => {
|
||||||
this.isLogged = this.isLoggedIn();
|
this.isLogged = this.isLoggedIn();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -384,19 +495,17 @@ export default defineComponent({
|
|||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
console.log("Updated");
|
console.log('Updated');
|
||||||
// Select first tab
|
// Select first tab
|
||||||
if (this.isFirstFetchAfterConnect) {
|
if (this.isFirstFetchAfterConnect) {
|
||||||
console.log("isFirstFetchAfterConnect");
|
console.log('isFirstFetchAfterConnect');
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
console.log("nextTick");
|
console.log('nextTick');
|
||||||
const firstTabEl = document.querySelector(
|
const firstTabEl = document.querySelector('#v-pills-tab:first-child button');
|
||||||
"#v-pills-tab:first-child button"
|
|
||||||
);
|
|
||||||
if (firstTabEl != null) {
|
if (firstTabEl != null) {
|
||||||
this.isFirstFetchAfterConnect = false;
|
this.isFirstFetchAfterConnect = false;
|
||||||
console.log("Show");
|
console.log('Show');
|
||||||
const firstTab = new bootstrap.Tab(firstTabEl);
|
const firstTab = new bootstrap.Tab(firstTabEl);
|
||||||
firstTab.show();
|
firstTab.show();
|
||||||
}
|
}
|
||||||
@ -406,20 +515,21 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
currentLimitAbsolute(): string {
|
currentLimitAbsolute(): string {
|
||||||
if (this.currentLimitList.max_power > 0) {
|
if (this.currentLimitList.max_power > 0) {
|
||||||
return this.$n(this.currentLimitList.limit_relative * this.currentLimitList.max_power / 100,
|
return this.$n(
|
||||||
'decimalTwoDigits');
|
(this.currentLimitList.limit_relative * this.currentLimitList.max_power) / 100,
|
||||||
|
'decimalNoDigits'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return "0";
|
return '0';
|
||||||
},
|
},
|
||||||
currentLimitRelative(): string {
|
currentLimitRelative(): string {
|
||||||
return this.$n(this.currentLimitList.limit_relative,
|
return this.$n(this.currentLimitList.limit_relative, 'decimalOneDigit');
|
||||||
'decimalTwoDigits');
|
|
||||||
},
|
},
|
||||||
inverterData(): Inverter[] {
|
inverterData(): Inverter[] {
|
||||||
return this.liveData.inverters.slice().sort((a: Inverter, b: Inverter) => {
|
return this.liveData.inverters.slice().sort((a: Inverter, b: Inverter) => {
|
||||||
return a.order - b.order;
|
return a.order - b.order;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
@ -427,7 +537,7 @@ export default defineComponent({
|
|||||||
if (triggerLoading) {
|
if (triggerLoading) {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
}
|
}
|
||||||
fetch("/api/livedata/status", { headers: authHeader() })
|
fetch('/api/livedata/status', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.liveData = data;
|
this.liveData = data;
|
||||||
@ -445,23 +555,24 @@ export default defineComponent({
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
},
|
},
|
||||||
initSocket() {
|
initSocket() {
|
||||||
console.log("Starting connection to WebSocket Server");
|
console.log('Starting connection to WebSocket Server');
|
||||||
|
|
||||||
const { protocol, host } = location;
|
const { protocol, host } = location;
|
||||||
const authString = authUrl();
|
const authString = authUrl();
|
||||||
const webSocketUrl = `${protocol === "https:" ? "wss" : "ws"
|
const webSocketUrl = `${protocol === 'https:' ? 'wss' : 'ws'}://${authString}${host}/livedata`;
|
||||||
}://${authString}${host}/livedata`;
|
|
||||||
|
|
||||||
this.socket = new WebSocket(webSocketUrl);
|
this.socket = new WebSocket(webSocketUrl);
|
||||||
|
|
||||||
this.socket.onmessage = (event) => {
|
this.socket.onmessage = (event) => {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
if (event.data != "{}") {
|
if (event.data != '{}') {
|
||||||
const newData = JSON.parse(event.data);
|
const newData = JSON.parse(event.data);
|
||||||
Object.assign(this.liveData.total, newData.total);
|
Object.assign(this.liveData.total, newData.total);
|
||||||
Object.assign(this.liveData.hints, newData.hints);
|
Object.assign(this.liveData.hints, newData.hints);
|
||||||
|
|
||||||
const foundIdx = this.liveData.inverters.findIndex((element) => element.serial == newData.inverters[0].serial);
|
const foundIdx = this.liveData.inverters.findIndex(
|
||||||
|
(element) => element.serial == newData.inverters[0].serial
|
||||||
|
);
|
||||||
if (foundIdx == -1) {
|
if (foundIdx == -1) {
|
||||||
Object.assign(this.liveData.inverters, newData.inverters);
|
Object.assign(this.liveData.inverters, newData.inverters);
|
||||||
} else {
|
} else {
|
||||||
@ -478,14 +589,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
this.socket.onopen = (event) => {
|
this.socket.onopen = (event) => {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
console.log("Successfully connected to the echo websocket server...");
|
console.log('Successfully connected to the echo websocket server...');
|
||||||
this.isWebsocketConnected = true;
|
this.isWebsocketConnected = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onclose = () => {
|
this.socket.onclose = () => {
|
||||||
console.log("Connection to websocket closed...")
|
console.log('Connection to websocket closed...');
|
||||||
this.isWebsocketConnected = false;
|
this.isWebsocketConnected = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
|
||||||
window.onbeforeunload = () => {
|
window.onbeforeunload = () => {
|
||||||
@ -495,7 +606,7 @@ export default defineComponent({
|
|||||||
initDataAgeing() {
|
initDataAgeing() {
|
||||||
this.dataAgeInterval = setInterval(() => {
|
this.dataAgeInterval = setInterval(() => {
|
||||||
if (this.inverterData) {
|
if (this.inverterData) {
|
||||||
this.inverterData.forEach(element => {
|
this.inverterData.forEach((element) => {
|
||||||
element.data_age++;
|
element.data_age++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -507,7 +618,7 @@ export default defineComponent({
|
|||||||
this.heartInterval = setInterval(() => {
|
this.heartInterval = setInterval(() => {
|
||||||
if (this.socket.readyState === 1) {
|
if (this.socket.readyState === 1) {
|
||||||
// Connection status
|
// Connection status
|
||||||
this.socket.send("ping");
|
this.socket.send('ping');
|
||||||
} else {
|
} else {
|
||||||
this.initSocket(); // Breakpoint reconnection 5 Time
|
this.initSocket(); // Breakpoint reconnection 5 Time
|
||||||
}
|
}
|
||||||
@ -521,7 +632,9 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onShowEventlog(serial: string) {
|
onShowEventlog(serial: string) {
|
||||||
this.eventLogLoading = true;
|
this.eventLogLoading = true;
|
||||||
fetch("/api/eventlog/status?inv=" + serial + "&locale=" + this.$i18n.locale, { headers: authHeader() })
|
fetch('/api/eventlog/status?inv=' + serial + '&locale=' + this.$i18n.locale, {
|
||||||
|
headers: authHeader(),
|
||||||
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.eventLogList = data;
|
this.eventLogList = data;
|
||||||
@ -532,7 +645,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onShowDevInfo(serial: string) {
|
onShowDevInfo(serial: string) {
|
||||||
this.devInfoLoading = true;
|
this.devInfoLoading = true;
|
||||||
fetch("/api/devinfo/status?inv=" + serial, { headers: authHeader() })
|
fetch('/api/devinfo/status?inv=' + serial, { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.devInfoList = data;
|
this.devInfoList = data;
|
||||||
@ -544,30 +657,30 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onShowGridProfile(serial: string) {
|
onShowGridProfile(serial: string) {
|
||||||
this.gridProfileLoading = true;
|
this.gridProfileLoading = true;
|
||||||
fetch("/api/gridprofile/status?inv=" + serial, { headers: authHeader() })
|
fetch('/api/gridprofile/status?inv=' + serial, { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.gridProfileList = data;
|
this.gridProfileList = data;
|
||||||
|
|
||||||
fetch("/api/gridprofile/rawdata?inv=" + serial, { headers: authHeader() })
|
fetch('/api/gridprofile/rawdata?inv=' + serial, { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.gridProfileRawList = data;
|
this.gridProfileRawList = data;
|
||||||
this.gridProfileLoading = false;
|
this.gridProfileLoading = false;
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.gridProfileView.show();
|
this.gridProfileView.show();
|
||||||
},
|
},
|
||||||
onShowLimitSettings(serial: string) {
|
onShowLimitSettings(serial: string) {
|
||||||
this.showAlertLimit = false;
|
this.showAlertLimit = false;
|
||||||
this.targetLimitList.serial = "";
|
this.targetLimitList.serial = '';
|
||||||
this.targetLimitList.limit_value = 0;
|
this.targetLimitList.limit_value = 0;
|
||||||
this.targetLimitType = 1;
|
this.targetLimitType = 1;
|
||||||
this.targetLimitTypeText = this.$t('home.Relative');
|
this.targetLimitTypeText = this.$t('home.Relative');
|
||||||
|
|
||||||
this.limitSettingLoading = true;
|
this.limitSettingLoading = true;
|
||||||
fetch("/api/limit/status", { headers: authHeader() })
|
fetch('/api/limit/status', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.currentLimitList = data[serial];
|
this.currentLimitList = data[serial];
|
||||||
@ -578,29 +691,27 @@ export default defineComponent({
|
|||||||
this.limitSettingView.show();
|
this.limitSettingView.show();
|
||||||
},
|
},
|
||||||
onSetLimitSettings(setPersistent: boolean) {
|
onSetLimitSettings(setPersistent: boolean) {
|
||||||
this.targetLimitList.limit_type = (setPersistent ? 256 : 0) + this.targetLimitType
|
this.targetLimitList.limit_type = (setPersistent ? 256 : 0) + this.targetLimitType;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(this.targetLimitList));
|
formData.append('data', JSON.stringify(this.targetLimitList));
|
||||||
|
|
||||||
console.log(this.targetLimitList);
|
console.log(this.targetLimitList);
|
||||||
|
|
||||||
fetch("/api/limit/config", {
|
fetch('/api/limit/config', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
if (response.type == 'success') {
|
||||||
if (response.type == "success") {
|
|
||||||
this.limitSettingView.hide();
|
this.limitSettingView.hide();
|
||||||
} else {
|
} else {
|
||||||
this.alertMessageLimit = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessageLimit = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertTypeLimit = response.type;
|
this.alertTypeLimit = response.type;
|
||||||
this.showAlertLimit = true;
|
this.showAlertLimit = true;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
)
|
|
||||||
},
|
},
|
||||||
onSelectType(type: number) {
|
onSelectType(type: number) {
|
||||||
if (type == 1) {
|
if (type == 1) {
|
||||||
@ -610,16 +721,16 @@ export default defineComponent({
|
|||||||
} else {
|
} else {
|
||||||
this.targetLimitTypeText = this.$t('home.Absolute');
|
this.targetLimitTypeText = this.$t('home.Absolute');
|
||||||
this.targetLimitMin = 0;
|
this.targetLimitMin = 0;
|
||||||
this.targetLimitMax = (this.currentLimitList.max_power > 0 ? this.currentLimitList.max_power : 2250);
|
this.targetLimitMax = this.currentLimitList.max_power > 0 ? this.currentLimitList.max_power : 2250;
|
||||||
}
|
}
|
||||||
this.targetLimitType = type;
|
this.targetLimitType = type;
|
||||||
},
|
},
|
||||||
|
|
||||||
onShowPowerSettings(serial: string) {
|
onShowPowerSettings(serial: string) {
|
||||||
this.showAlertPower = false;
|
this.showAlertPower = false;
|
||||||
this.powerSettingSerial = "";
|
this.powerSettingSerial = '';
|
||||||
this.powerSettingLoading = true;
|
this.powerSettingLoading = true;
|
||||||
fetch("/api/power/status", { headers: authHeader() })
|
fetch('/api/power/status', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.successCommandPower = data[serial].power_set_status;
|
this.successCommandPower = data[serial].power_set_status;
|
||||||
@ -644,27 +755,25 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(data));
|
formData.append('data', JSON.stringify(data));
|
||||||
|
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
fetch("/api/power/config", {
|
fetch('/api/power/config', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
if (response.type == 'success') {
|
||||||
if (response.type == "success") {
|
|
||||||
this.powerSettingView.hide();
|
this.powerSettingView.hide();
|
||||||
} else {
|
} else {
|
||||||
this.alertMessagePower = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessagePower = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertTypePower = response.type;
|
this.alertTypePower = response.type;
|
||||||
this.showAlertPower = true;
|
this.showAlertPower = true;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
)
|
|
||||||
},
|
},
|
||||||
calculateAbsoluteTime(lastTime: number): string {
|
calculateAbsoluteTime(lastTime: number): string {
|
||||||
const date = new Date(Date.now() - lastTime * 1000);
|
const date = new Date(Date.now() - lastTime * 1000);
|
||||||
@ -676,7 +785,7 @@ export default defineComponent({
|
|||||||
total += inv.DC[key as unknown as number].Irradiation?.max || 0;
|
total += inv.DC[key as unknown as number].Irradiation?.max || 0;
|
||||||
});
|
});
|
||||||
return total;
|
return total;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -12,11 +12,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('inverteradmin.Name') }}</label>
|
<label>{{ $t('inverteradmin.Name') }}</label>
|
||||||
<input v-model="newInverterData.name" type="text" class="form-control ml-sm-2 mr-sm-4 my-2"
|
<input
|
||||||
maxlength="31" required />
|
v-model="newInverterData.name"
|
||||||
|
type="text"
|
||||||
|
class="form-control ml-sm-2 mr-sm-4 my-2"
|
||||||
|
maxlength="31"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-auto text-right">
|
<div class="ml-auto text-right">
|
||||||
<button type="submit" class="btn btn-primary my-2">{{ $t('inverteradmin.Add') }}</button>
|
<button type="submit" class="btn btn-primary my-2">
|
||||||
|
{{ $t('inverteradmin.Add') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-secondary" role="alert" v-html="$t('inverteradmin.AddHint')"></div>
|
<div class="alert alert-secondary" role="alert" v-html="$t('inverteradmin.AddHint')"></div>
|
||||||
</form>
|
</form>
|
||||||
@ -39,23 +46,33 @@
|
|||||||
<tr v-for="inverter in inverters" v-bind:key="inverter.id" :data-id="inverter.id">
|
<tr v-for="inverter in inverters" v-bind:key="inverter.id" :data-id="inverter.id">
|
||||||
<td><BIconGripHorizontal class="drag-handle" /></td>
|
<td><BIconGripHorizontal class="drag-handle" /></td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge" :title="$t('inverteradmin.Receive')" :class="{
|
<span
|
||||||
|
class="badge"
|
||||||
|
:title="$t('inverteradmin.Receive')"
|
||||||
|
:class="{
|
||||||
'text-bg-warning': !inverter.poll_enable_night,
|
'text-bg-warning': !inverter.poll_enable_night,
|
||||||
'text-bg-dark': inverter.poll_enable_night,}"
|
'text-bg-dark': inverter.poll_enable_night,
|
||||||
><BIconArrowDown v-if="inverter.poll_enable" /></span>
|
}"
|
||||||
|
><BIconArrowDown v-if="inverter.poll_enable"
|
||||||
|
/></span>
|
||||||
|
|
||||||
<span class="badge" :title="$t('inverteradmin.Send')" :class="{
|
<span
|
||||||
|
class="badge"
|
||||||
|
:title="$t('inverteradmin.Send')"
|
||||||
|
:class="{
|
||||||
'text-bg-warning': !inverter.command_enable_night,
|
'text-bg-warning': !inverter.command_enable_night,
|
||||||
'text-bg-dark': inverter.command_enable_night,}"
|
'text-bg-dark': inverter.command_enable_night,
|
||||||
><BIconArrowUp v-if="inverter.command_enable" /></span>
|
}"
|
||||||
|
><BIconArrowUp v-if="inverter.command_enable"
|
||||||
|
/></span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ inverter.serial }}</td>
|
<td>{{ inverter.serial }}</td>
|
||||||
<td>{{ inverter.name }}</td>
|
<td>{{ inverter.name }}</td>
|
||||||
<td>{{ inverter.type }}</td>
|
<td>{{ inverter.type }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="icon text-danger" :title="$t('inverteradmin.DeleteInverter')">
|
<a href="#" class="icon text-danger" :title="$t('inverteradmin.DeleteInverter')">
|
||||||
<BIconTrash v-on:click="onOpenModal(modalDelete, inverter)" />
|
<BIconTrash v-on:click="onOpenModal(modalDelete, inverter)" /> </a
|
||||||
</a>
|
>
|
||||||
<a href="#" class="icon" :title="$t('inverteradmin.EditInverter')">
|
<a href="#" class="icon" :title="$t('inverteradmin.EditInverter')">
|
||||||
<BIconPencil v-on:click="onOpenModal(modal, inverter)" />
|
<BIconPencil v-on:click="onOpenModal(modal, inverter)" />
|
||||||
</a>
|
</a>
|
||||||
@ -65,54 +82,122 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-auto text-right">
|
<div class="ml-auto text-right">
|
||||||
<button class="btn btn-primary my-2" @click="onSaveOrder()">{{ $t('inverteradmin.SaveOrder') }}</button>
|
<button class="btn btn-primary my-2" @click="onSaveOrder()">
|
||||||
|
{{ $t('inverteradmin.SaveOrder') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
|
|
||||||
<ModalDialog modalId="inverterEdit" :title="$t('inverteradmin.EditInverter')" :closeText="$t('inverteradmin.Cancel')">
|
<ModalDialog
|
||||||
|
modalId="inverterEdit"
|
||||||
|
:title="$t('inverteradmin.EditInverter')"
|
||||||
|
:closeText="$t('inverteradmin.Cancel')"
|
||||||
|
>
|
||||||
<nav>
|
<nav>
|
||||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||||
<button class="nav-link active" id="nav-general-tab" data-bs-toggle="tab" data-bs-target="#nav-general"
|
<button
|
||||||
type="button" role="tab" aria-controls="nav-general" aria-selected="true">{{
|
class="nav-link active"
|
||||||
$t('inverteradmin.General')
|
id="nav-general-tab"
|
||||||
}}</button>
|
data-bs-toggle="tab"
|
||||||
<button class="nav-link" id="nav-string-tab" data-bs-toggle="tab" data-bs-target="#nav-string" type="button"
|
data-bs-target="#nav-general"
|
||||||
role="tab" aria-controls="nav-string">{{ $t('inverteradmin.String') }}</button>
|
type="button"
|
||||||
<button class="nav-link" id="nav-advanced-tab" data-bs-toggle="tab" data-bs-target="#nav-advanced"
|
role="tab"
|
||||||
type="button" role="tab" aria-controls="nav-advanced">{{ $t('inverteradmin.Advanced') }}</button>
|
aria-controls="nav-general"
|
||||||
|
aria-selected="true"
|
||||||
|
>
|
||||||
|
{{ $t('inverteradmin.General') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="nav-link"
|
||||||
|
id="nav-string-tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-bs-target="#nav-string"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="nav-string"
|
||||||
|
>
|
||||||
|
{{ $t('inverteradmin.String') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="nav-link"
|
||||||
|
id="nav-advanced-tab"
|
||||||
|
data-bs-toggle="tab"
|
||||||
|
data-bs-target="#nav-advanced"
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="nav-advanced"
|
||||||
|
>
|
||||||
|
{{ $t('inverteradmin.Advanced') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="tab-content" id="nav-tabContent">
|
<div class="tab-content" id="nav-tabContent">
|
||||||
<div class="tab-pane fade show active" id="nav-general" role="tabpanel" aria-labelledby="nav-general-tab"
|
<div
|
||||||
tabindex="0">
|
class="tab-pane fade show active"
|
||||||
|
id="nav-general"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="nav-general-tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="inverter-serial" class="col-form-label">
|
<label for="inverter-serial" class="col-form-label">
|
||||||
{{ $t('inverteradmin.InverterSerial') }}
|
{{ $t('inverteradmin.InverterSerial') }}
|
||||||
</label>
|
</label>
|
||||||
<InputSerial v-model="selectedInverterData.serial" id="inverter-serial" />
|
<InputSerial v-model="selectedInverterData.serial" id="inverter-serial" />
|
||||||
<label for="inverter-name" class="col-form-label">{{ $t('inverteradmin.InverterName') }}
|
<label for="inverter-name" class="col-form-label"
|
||||||
|
>{{ $t('inverteradmin.InverterName') }}
|
||||||
<BIconInfoCircle v-tooltip :title="$t('inverteradmin.InverterNameHint')" />
|
<BIconInfoCircle v-tooltip :title="$t('inverteradmin.InverterNameHint')" />
|
||||||
</label>
|
</label>
|
||||||
<input v-model="selectedInverterData.name" type="text" id="inverter-name" class="form-control"
|
<input
|
||||||
maxlength="31" />
|
v-model="selectedInverterData.name"
|
||||||
|
type="text"
|
||||||
|
id="inverter-name"
|
||||||
|
class="form-control"
|
||||||
|
maxlength="31"
|
||||||
|
/>
|
||||||
|
|
||||||
<CardElement :text="$t('inverteradmin.InverterStatus')" addSpace>
|
<CardElement :text="$t('inverteradmin.InverterStatus')" addSpace>
|
||||||
<InputElement :label="$t('inverteradmin.PollEnable')" v-model="selectedInverterData.poll_enable"
|
<InputElement
|
||||||
type="checkbox" wide />
|
:label="$t('inverteradmin.PollEnable')"
|
||||||
<InputElement :label="$t('inverteradmin.PollEnableNight')"
|
v-model="selectedInverterData.poll_enable"
|
||||||
v-model="selectedInverterData.poll_enable_night" type="checkbox" wide />
|
type="checkbox"
|
||||||
<InputElement :label="$t('inverteradmin.CommandEnable')"
|
wide
|
||||||
v-model="selectedInverterData.command_enable" type="checkbox" wide />
|
/>
|
||||||
<InputElement :label="$t('inverteradmin.CommandEnableNight')"
|
<InputElement
|
||||||
v-model="selectedInverterData.command_enable_night" type="checkbox" wide />
|
:label="$t('inverteradmin.PollEnableNight')"
|
||||||
<div class="alert alert-secondary mt-3" role="alert" v-html="$t('inverteradmin.StatusHint')">
|
v-model="selectedInverterData.poll_enable_night"
|
||||||
</div>
|
type="checkbox"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
|
<InputElement
|
||||||
|
:label="$t('inverteradmin.CommandEnable')"
|
||||||
|
v-model="selectedInverterData.command_enable"
|
||||||
|
type="checkbox"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
|
<InputElement
|
||||||
|
:label="$t('inverteradmin.CommandEnableNight')"
|
||||||
|
v-model="selectedInverterData.command_enable_night"
|
||||||
|
type="checkbox"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="alert alert-secondary mt-3"
|
||||||
|
role="alert"
|
||||||
|
v-html="$t('inverteradmin.StatusHint')"
|
||||||
|
></div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade show" id="nav-string" role="tabpanel" aria-labelledby="nav-string-tab" tabindex="0">
|
<div
|
||||||
|
class="tab-pane fade show"
|
||||||
|
id="nav-string"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="nav-string-tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
<div v-for="(ch, index) in selectedInverterData.channel" :key="`${index}`">
|
<div v-for="(ch, index) in selectedInverterData.channel" :key="`${index}`">
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
@ -122,8 +207,13 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="d-flex mb-2">
|
<div class="d-flex mb-2">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" :id="`inverter-name_${index}`" maxlength="31"
|
<input
|
||||||
v-model="ch.name" />
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
:id="`inverter-name_${index}`"
|
||||||
|
maxlength="31"
|
||||||
|
v-model="ch.name"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -136,11 +226,17 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="d-flex mb-2">
|
<div class="d-flex mb-2">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="number" class="form-control" :id="`inverter-max_${index}`" min="0"
|
<input
|
||||||
|
type="number"
|
||||||
|
class="form-control"
|
||||||
|
:id="`inverter-max_${index}`"
|
||||||
|
min="0"
|
||||||
v-model="ch.max_power"
|
v-model="ch.max_power"
|
||||||
:aria-describedby="`inverter-maxDescription_${index} inverter-customizer`" />
|
:aria-describedby="`inverter-maxDescription_${index} inverter-customizer`"
|
||||||
<span class="input-group-text"
|
/>
|
||||||
:id="`inverter-maxDescription_${index}`">W<sub>p</sub><sup>*</sup></span>
|
<span class="input-group-text" :id="`inverter-maxDescription_${index}`"
|
||||||
|
>W<sub>p</sub><sup>*</sup></span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -151,61 +247,104 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="d-flex mb-2">
|
<div class="d-flex mb-2">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="number" class="form-control" :id="`inverter-ytoffset_${index}`" min="0"
|
<input
|
||||||
|
type="number"
|
||||||
|
class="form-control"
|
||||||
|
:id="`inverter-ytoffset_${index}`"
|
||||||
|
min="0"
|
||||||
v-model="ch.yield_total_offset"
|
v-model="ch.yield_total_offset"
|
||||||
:aria-describedby="`inverter-ytoffsetDescription_${index} inverter-customizer`" />
|
:aria-describedby="`inverter-ytoffsetDescription_${index} inverter-customizer`"
|
||||||
<span class="input-group-text" :id="`inverter-ytoffsetDescription_${index}`">kWh</span>
|
/>
|
||||||
|
<span class="input-group-text" :id="`inverter-ytoffsetDescription_${index}`"
|
||||||
|
>kWh</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :id="`inverter-customizer`" class="form-text" v-html="$t('inverteradmin.InverterHint')">
|
<div :id="`inverter-customizer`" class="form-text" v-html="$t('inverteradmin.InverterHint')"></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade show" id="nav-advanced" role="tabpanel" aria-labelledby="nav-advanced-tab"
|
<div
|
||||||
tabindex="0">
|
class="tab-pane fade show"
|
||||||
<InputElement :label="$t('inverteradmin.ReachableThreshold')"
|
id="nav-advanced"
|
||||||
v-model="selectedInverterData.reachable_threshold" type="number" min="1" max="100"
|
role="tabpanel"
|
||||||
:tooltip="$t('inverteradmin.ReachableThresholdHint')" wide />
|
aria-labelledby="nav-advanced-tab"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<InputElement
|
||||||
|
:label="$t('inverteradmin.ReachableThreshold')"
|
||||||
|
v-model="selectedInverterData.reachable_threshold"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="100"
|
||||||
|
:tooltip="$t('inverteradmin.ReachableThresholdHint')"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('inverteradmin.ZeroRuntime')" v-model="selectedInverterData.zero_runtime"
|
<InputElement
|
||||||
type="checkbox" :tooltip="$t('inverteradmin.ZeroRuntimeHint')" wide />
|
:label="$t('inverteradmin.ZeroRuntime')"
|
||||||
|
v-model="selectedInverterData.zero_runtime"
|
||||||
|
type="checkbox"
|
||||||
|
:tooltip="$t('inverteradmin.ZeroRuntimeHint')"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('inverteradmin.ZeroDay')" v-model="selectedInverterData.zero_day" type="checkbox"
|
<InputElement
|
||||||
:tooltip="$t('inverteradmin.ZeroDayHint')" wide />
|
:label="$t('inverteradmin.ZeroDay')"
|
||||||
|
v-model="selectedInverterData.zero_day"
|
||||||
|
type="checkbox"
|
||||||
|
:tooltip="$t('inverteradmin.ZeroDayHint')"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('inverteradmin.ClearEventlog')" v-model="selectedInverterData.clear_eventlog" type="checkbox" wide />
|
<InputElement
|
||||||
|
:label="$t('inverteradmin.ClearEventlog')"
|
||||||
|
v-model="selectedInverterData.clear_eventlog"
|
||||||
|
type="checkbox"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('inverteradmin.YieldDayCorrection')"
|
<InputElement
|
||||||
v-model="selectedInverterData.yieldday_correction" type="checkbox"
|
:label="$t('inverteradmin.YieldDayCorrection')"
|
||||||
:tooltip="$t('inverteradmin.YieldDayCorrectionHint')" wide />
|
v-model="selectedInverterData.yieldday_correction"
|
||||||
|
type="checkbox"
|
||||||
|
:tooltip="$t('inverteradmin.YieldDayCorrectionHint')"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<button type="button" class="btn btn-primary" @click="onEditSubmit">
|
<button type="button" class="btn btn-primary" @click="onEditSubmit">
|
||||||
{{ $t('inverteradmin.Save') }}</button>
|
{{ $t('inverteradmin.Save') }}
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
|
|
||||||
<ModalDialog modalId="inverterDelete" small :title="$t('inverteradmin.DeleteInverter')"
|
<ModalDialog
|
||||||
:closeText="$t('inverteradmin.Cancel')">
|
modalId="inverterDelete"
|
||||||
{{ $t('inverteradmin.DeleteMsg', {
|
small
|
||||||
|
:title="$t('inverteradmin.DeleteInverter')"
|
||||||
|
:closeText="$t('inverteradmin.Cancel')"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
$t('inverteradmin.DeleteMsg', {
|
||||||
name: selectedInverterData.name,
|
name: selectedInverterData.name,
|
||||||
serial: selectedInverterData.serial
|
serial: selectedInverterData.serial,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<button type="button" class="btn btn-danger" @click="onDelete">
|
<button type="button" class="btn btn-danger" @click="onDelete">
|
||||||
{{ $t('inverteradmin.Delete') }}</button>
|
{{ $t('inverteradmin.Delete') }}
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import InputElement from '@/components/InputElement.vue';
|
import InputElement from '@/components/InputElement.vue';
|
||||||
import InputSerial from '@/components/InputSerial.vue';
|
import InputSerial from '@/components/InputSerial.vue';
|
||||||
@ -268,7 +407,7 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
getInverters() {
|
getInverters() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/inverter/list", { headers: authHeader() })
|
fetch('/api/inverter/list', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.inverters = data.inverter.slice().sort((a: Inverter, b: Inverter) => {
|
this.inverters = data.inverter.slice().sort((a: Inverter, b: Inverter) => {
|
||||||
@ -290,10 +429,10 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
callInverterApiEndpoint(endpoint: string, jsonData: string) {
|
callInverterApiEndpoint(endpoint: string, jsonData: string) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", jsonData);
|
formData.append('data', jsonData);
|
||||||
|
|
||||||
fetch("/api/inverter/" + endpoint, {
|
fetch('/api/inverter/' + endpoint, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
@ -306,15 +445,15 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.callInverterApiEndpoint("add", JSON.stringify(this.newInverterData));
|
this.callInverterApiEndpoint('add', JSON.stringify(this.newInverterData));
|
||||||
this.newInverterData = {} as Inverter;
|
this.newInverterData = {} as Inverter;
|
||||||
},
|
},
|
||||||
onDelete() {
|
onDelete() {
|
||||||
this.callInverterApiEndpoint("del", JSON.stringify({ id: this.selectedInverterData.id }));
|
this.callInverterApiEndpoint('del', JSON.stringify({ id: this.selectedInverterData.id }));
|
||||||
this.onCloseModal(this.modalDelete);
|
this.onCloseModal(this.modalDelete);
|
||||||
},
|
},
|
||||||
onEditSubmit() {
|
onEditSubmit() {
|
||||||
this.callInverterApiEndpoint("edit", JSON.stringify(this.selectedInverterData));
|
this.callInverterApiEndpoint('edit', JSON.stringify(this.selectedInverterData));
|
||||||
this.onCloseModal(this.modal);
|
this.onCloseModal(this.modal);
|
||||||
},
|
},
|
||||||
onOpenModal(modal: bootstrap.Modal, inverter: Inverter) {
|
onOpenModal(modal: bootstrap.Modal, inverter: Inverter) {
|
||||||
@ -326,7 +465,7 @@ export default defineComponent({
|
|||||||
modal.hide();
|
modal.hide();
|
||||||
},
|
},
|
||||||
onSaveOrder() {
|
onSaveOrder() {
|
||||||
this.callInverterApiEndpoint("order", JSON.stringify({ order: this.sortable.toArray() }));
|
this.callInverterApiEndpoint('order', JSON.stringify({ order: this.sortable.toArray() }));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,20 +8,35 @@
|
|||||||
<form @submit.prevent="handleSubmit">
|
<form @submit.prevent="handleSubmit">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username">{{ $t('login.Username') }}</label>
|
<label for="username">{{ $t('login.Username') }}</label>
|
||||||
<input type="text" v-model="username" name="username" class="form-control"
|
<input
|
||||||
:class="{ 'is-invalid': submitted && !username }" @keydown.space.prevent />
|
type="text"
|
||||||
<div v-show="submitted && !username" class="invalid-feedback">{{ $t('login.UsernameRequired') }}
|
v-model="username"
|
||||||
|
name="username"
|
||||||
|
class="form-control"
|
||||||
|
:class="{ 'is-invalid': submitted && !username }"
|
||||||
|
@keydown.space.prevent
|
||||||
|
/>
|
||||||
|
<div v-show="submitted && !username" class="invalid-feedback">
|
||||||
|
{{ $t('login.UsernameRequired') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label htmlFor="password">{{ $t('login.Password') }}</label>
|
<label htmlFor="password">{{ $t('login.Password') }}</label>
|
||||||
<input type="password" v-model="password" name="password" class="form-control"
|
<input
|
||||||
:class="{ 'is-invalid': submitted && !password }" />
|
type="password"
|
||||||
|
v-model="password"
|
||||||
|
name="password"
|
||||||
|
class="form-control"
|
||||||
|
:class="{ 'is-invalid': submitted && !password }"
|
||||||
|
/>
|
||||||
<div v-show="submitted && !password" class="invalid-feedback">
|
<div v-show="submitted && !password" class="invalid-feedback">
|
||||||
{{ $t('login.PasswordRequired') }}</div>
|
{{ $t('login.PasswordRequired') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<button class="btn btn-primary" :disabled="dataLoading">{{ $t('login.LoginButton') }}</button>
|
<button class="btn btn-primary" :disabled="dataLoading">
|
||||||
|
{{ $t('login.LoginButton') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
@ -30,7 +45,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import { login } from '@/utils';
|
import { login } from '@/utils';
|
||||||
@ -45,8 +60,8 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
dataLoading: false,
|
dataLoading: false,
|
||||||
alertMessage: "",
|
alertMessage: '',
|
||||||
alertType: "info",
|
alertType: 'info',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
returnUrl: '',
|
returnUrl: '',
|
||||||
username: '',
|
username: '',
|
||||||
@ -69,21 +84,20 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
login(username, password)
|
login(username, password).then(
|
||||||
.then(
|
|
||||||
() => {
|
() => {
|
||||||
this.$emitter.emit("logged-in");
|
this.$emitter.emit('logged-in');
|
||||||
router.push(this.returnUrl);
|
router.push(this.returnUrl);
|
||||||
},
|
},
|
||||||
error => {
|
(error) => {
|
||||||
this.$emitter.emit("logged-out");
|
this.$emitter.emit('logged-out');
|
||||||
this.alertMessage = error;
|
this.alertMessage = error;
|
||||||
this.alertType = 'danger';
|
this.alertType = 'danger';
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -5,25 +5,32 @@
|
|||||||
</BootstrapAlert>
|
</BootstrapAlert>
|
||||||
|
|
||||||
<CardElement :text="$t('maintenancereboot.PerformReboot')" textVariant="text-bg-primary" center-content>
|
<CardElement :text="$t('maintenancereboot.PerformReboot')" textVariant="text-bg-primary" center-content>
|
||||||
<button class="btn btn-danger" @click="onOpenModal(performReboot)">{{ $t('maintenancereboot.Reboot') }}
|
<button class="btn btn-danger" @click="onOpenModal(performReboot)">
|
||||||
|
{{ $t('maintenancereboot.Reboot') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="alert alert-danger mt-3" role="alert" v-html="$t('maintenancereboot.RebootHint')"></div>
|
<div class="alert alert-danger mt-3" role="alert" v-html="$t('maintenancereboot.RebootHint')"></div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
|
|
||||||
<ModalDialog modalId="performReboot" small :title="$t('maintenancereboot.RebootOpenDTU')" :closeText="$t('maintenancereboot.Cancel')">
|
<ModalDialog
|
||||||
|
modalId="performReboot"
|
||||||
|
small
|
||||||
|
:title="$t('maintenancereboot.RebootOpenDTU')"
|
||||||
|
:closeText="$t('maintenancereboot.Cancel')"
|
||||||
|
>
|
||||||
{{ $t('maintenancereboot.RebootQuestion') }}
|
{{ $t('maintenancereboot.RebootQuestion') }}
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<button type="button" class="btn btn-danger" @click="onReboot">
|
<button type="button" class="btn btn-danger" @click="onReboot">
|
||||||
{{ $t('maintenancereboot.Reboot') }}</button>
|
{{ $t('maintenancereboot.Reboot') }}
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import ModalDialog from '@/components/ModalDialog.vue';
|
import ModalDialog from '@/components/ModalDialog.vue';
|
||||||
import { authHeader, handleResponse, isLoggedIn } from '@/utils/authentication';
|
import { authHeader, handleResponse, isLoggedIn } from '@/utils/authentication';
|
||||||
@ -43,14 +50,17 @@ export default defineComponent({
|
|||||||
|
|
||||||
dataLoading: false,
|
dataLoading: false,
|
||||||
|
|
||||||
alertMessage: "",
|
alertMessage: '',
|
||||||
alertType: "info",
|
alertType: 'info',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (!isLoggedIn()) {
|
if (!isLoggedIn()) {
|
||||||
this.$router.push({ path: "/login", query: { returnUrl: this.$router.currentRoute.value.fullPath } });
|
this.$router.push({
|
||||||
|
path: '/login',
|
||||||
|
query: { returnUrl: this.$router.currentRoute.value.fullPath },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.performReboot = new bootstrap.Modal('#performReboot');
|
this.performReboot = new bootstrap.Modal('#performReboot');
|
||||||
@ -58,10 +68,10 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
onReboot() {
|
onReboot() {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify({ reboot: true }));
|
formData.append('data', JSON.stringify({ reboot: true }));
|
||||||
|
|
||||||
fetch("/api/maintenance/reboot", {
|
fetch('/api/maintenance/reboot', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
@ -78,7 +88,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onCloseModal(modal: bootstrap.Modal) {
|
onCloseModal(modal: bootstrap.Modal) {
|
||||||
modal.hide();
|
modal.hide();
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -6,102 +6,163 @@
|
|||||||
|
|
||||||
<form @submit="saveMqttConfig">
|
<form @submit="saveMqttConfig">
|
||||||
<CardElement :text="$t('mqttadmin.MqttConfiguration')" textVariant="text-bg-primary">
|
<CardElement :text="$t('mqttadmin.MqttConfiguration')" textVariant="text-bg-primary">
|
||||||
<InputElement :label="$t('mqttadmin.EnableMqtt')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.EnableMqtt')"
|
||||||
v-model="mqttConfigList.mqtt_enabled"
|
v-model="mqttConfigList.mqtt_enabled"
|
||||||
type="checkbox" wide/>
|
type="checkbox"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement v-show="mqttConfigList.mqtt_enabled"
|
<InputElement
|
||||||
|
v-show="mqttConfigList.mqtt_enabled"
|
||||||
:label="$t('mqttadmin.EnableHass')"
|
:label="$t('mqttadmin.EnableHass')"
|
||||||
v-model="mqttConfigList.mqtt_hass_enabled"
|
v-model="mqttConfigList.mqtt_hass_enabled"
|
||||||
type="checkbox" wide/>
|
type="checkbox"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('mqttadmin.MqttBrokerParameter')" textVariant="text-bg-primary" add-space
|
<CardElement
|
||||||
|
:text="$t('mqttadmin.MqttBrokerParameter')"
|
||||||
|
textVariant="text-bg-primary"
|
||||||
|
add-space
|
||||||
v-show="mqttConfigList.mqtt_enabled"
|
v-show="mqttConfigList.mqtt_enabled"
|
||||||
>
|
>
|
||||||
<InputElement :label="$t('mqttadmin.Hostname')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.Hostname')"
|
||||||
v-model="mqttConfigList.mqtt_hostname"
|
v-model="mqttConfigList.mqtt_hostname"
|
||||||
type="text" maxlength="128"
|
type="text"
|
||||||
:placeholder="$t('mqttadmin.HostnameHint')"/>
|
maxlength="128"
|
||||||
|
:placeholder="$t('mqttadmin.HostnameHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.Port')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.Port')"
|
||||||
v-model="mqttConfigList.mqtt_port"
|
v-model="mqttConfigList.mqtt_port"
|
||||||
type="number" min="1" max="65535"/>
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.ClientId')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.ClientId')"
|
||||||
v-model="mqttConfigList.mqtt_clientid"
|
v-model="mqttConfigList.mqtt_clientid"
|
||||||
type="text" maxlength="64"/>
|
type="text"
|
||||||
|
maxlength="64"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.Username')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.Username')"
|
||||||
v-model="mqttConfigList.mqtt_username"
|
v-model="mqttConfigList.mqtt_username"
|
||||||
type="text" maxlength="64"
|
type="text"
|
||||||
:placeholder="$t('mqttadmin.UsernameHint')"/>
|
maxlength="64"
|
||||||
|
:placeholder="$t('mqttadmin.UsernameHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.Password')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.Password')"
|
||||||
v-model="mqttConfigList.mqtt_password"
|
v-model="mqttConfigList.mqtt_password"
|
||||||
type="password" maxlength="64"
|
type="password"
|
||||||
:placeholder="$t('mqttadmin.PasswordHint')"/>
|
maxlength="64"
|
||||||
|
:placeholder="$t('mqttadmin.PasswordHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.BaseTopic')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.BaseTopic')"
|
||||||
v-model="mqttConfigList.mqtt_topic"
|
v-model="mqttConfigList.mqtt_topic"
|
||||||
type="text" maxlength="32"
|
type="text"
|
||||||
:placeholder="$t('mqttadmin.BaseTopicHint')"/>
|
maxlength="32"
|
||||||
|
:placeholder="$t('mqttadmin.BaseTopicHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.PublishInterval')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.PublishInterval')"
|
||||||
v-model="mqttConfigList.mqtt_publish_interval"
|
v-model="mqttConfigList.mqtt_publish_interval"
|
||||||
type="number" min="5" max="86400"
|
type="number"
|
||||||
:postfix="$t('mqttadmin.Seconds')"/>
|
min="5"
|
||||||
|
max="86400"
|
||||||
|
:postfix="$t('mqttadmin.Seconds')"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.CleanSession')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.CleanSession')"
|
||||||
v-model="mqttConfigList.mqtt_clean_session"
|
v-model="mqttConfigList.mqtt_clean_session"
|
||||||
type="checkbox"/>
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.EnableRetain')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.EnableRetain')"
|
||||||
v-model="mqttConfigList.mqtt_retain"
|
v-model="mqttConfigList.mqtt_retain"
|
||||||
type="checkbox"/>
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.EnableTls')"
|
<InputElement :label="$t('mqttadmin.EnableTls')" v-model="mqttConfigList.mqtt_tls" type="checkbox" />
|
||||||
v-model="mqttConfigList.mqtt_tls"
|
|
||||||
type="checkbox"/>
|
|
||||||
|
|
||||||
<InputElement v-show="mqttConfigList.mqtt_tls"
|
<InputElement
|
||||||
|
v-show="mqttConfigList.mqtt_tls"
|
||||||
:label="$t('mqttadmin.RootCa')"
|
:label="$t('mqttadmin.RootCa')"
|
||||||
v-model="mqttConfigList.mqtt_root_ca_cert"
|
v-model="mqttConfigList.mqtt_root_ca_cert"
|
||||||
type="textarea" maxlength="2560" rows="10"/>
|
type="textarea"
|
||||||
|
maxlength="2560"
|
||||||
|
rows="10"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement v-show="mqttConfigList.mqtt_tls"
|
<InputElement
|
||||||
|
v-show="mqttConfigList.mqtt_tls"
|
||||||
:label="$t('mqttadmin.TlsCertLoginEnable')"
|
:label="$t('mqttadmin.TlsCertLoginEnable')"
|
||||||
v-model="mqttConfigList.mqtt_tls_cert_login"
|
v-model="mqttConfigList.mqtt_tls_cert_login"
|
||||||
type="checkbox"/>
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement v-show="mqttConfigList.mqtt_tls_cert_login"
|
<InputElement
|
||||||
|
v-show="mqttConfigList.mqtt_tls_cert_login"
|
||||||
:label="$t('mqttadmin.ClientCert')"
|
:label="$t('mqttadmin.ClientCert')"
|
||||||
v-model="mqttConfigList.mqtt_client_cert"
|
v-model="mqttConfigList.mqtt_client_cert"
|
||||||
type="textarea" maxlength="2560" rows="10"/>
|
type="textarea"
|
||||||
|
maxlength="2560"
|
||||||
|
rows="10"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement v-show="mqttConfigList.mqtt_tls_cert_login"
|
<InputElement
|
||||||
|
v-show="mqttConfigList.mqtt_tls_cert_login"
|
||||||
:label="$t('mqttadmin.ClientKey')"
|
:label="$t('mqttadmin.ClientKey')"
|
||||||
v-model="mqttConfigList.mqtt_client_key"
|
v-model="mqttConfigList.mqtt_client_key"
|
||||||
type="textarea" maxlength="2560" rows="10"/>
|
type="textarea"
|
||||||
|
maxlength="2560"
|
||||||
|
rows="10"
|
||||||
|
/>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('mqttadmin.LwtParameters')" textVariant="text-bg-primary" add-space
|
<CardElement
|
||||||
|
:text="$t('mqttadmin.LwtParameters')"
|
||||||
|
textVariant="text-bg-primary"
|
||||||
|
add-space
|
||||||
v-show="mqttConfigList.mqtt_enabled"
|
v-show="mqttConfigList.mqtt_enabled"
|
||||||
>
|
>
|
||||||
<InputElement :label="$t('mqttadmin.LwtTopic')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.LwtTopic')"
|
||||||
v-model="mqttConfigList.mqtt_lwt_topic"
|
v-model="mqttConfigList.mqtt_lwt_topic"
|
||||||
type="text" maxlength="32" :prefix="mqttConfigList.mqtt_topic"
|
type="text"
|
||||||
:placeholder="$t('mqttadmin.LwtTopicHint')"/>
|
maxlength="32"
|
||||||
|
:prefix="mqttConfigList.mqtt_topic"
|
||||||
|
:placeholder="$t('mqttadmin.LwtTopicHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.LwtOnline')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.LwtOnline')"
|
||||||
v-model="mqttConfigList.mqtt_lwt_online"
|
v-model="mqttConfigList.mqtt_lwt_online"
|
||||||
type="text" maxlength="20"
|
type="text"
|
||||||
:placeholder="$t('mqttadmin.LwtOnlineHint')"/>
|
maxlength="20"
|
||||||
|
:placeholder="$t('mqttadmin.LwtOnlineHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.LwtOffline')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.LwtOffline')"
|
||||||
v-model="mqttConfigList.mqtt_lwt_offline"
|
v-model="mqttConfigList.mqtt_lwt_offline"
|
||||||
type="text" maxlength="20"
|
type="text"
|
||||||
:placeholder="$t('mqttadmin.LwtOfflineHint')"/>
|
maxlength="20"
|
||||||
|
:placeholder="$t('mqttadmin.LwtOfflineHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-2 col-form-label">
|
<label class="col-sm-2 col-form-label">
|
||||||
@ -117,25 +178,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('mqttadmin.HassParameters')" textVariant="text-bg-primary" add-space
|
<CardElement
|
||||||
|
:text="$t('mqttadmin.HassParameters')"
|
||||||
|
textVariant="text-bg-primary"
|
||||||
|
add-space
|
||||||
v-show="mqttConfigList.mqtt_enabled && mqttConfigList.mqtt_hass_enabled"
|
v-show="mqttConfigList.mqtt_enabled && mqttConfigList.mqtt_hass_enabled"
|
||||||
>
|
>
|
||||||
<InputElement :label="$t('mqttadmin.HassPrefixTopic')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.HassPrefixTopic')"
|
||||||
v-model="mqttConfigList.mqtt_hass_topic"
|
v-model="mqttConfigList.mqtt_hass_topic"
|
||||||
type="text" maxlength="32"
|
type="text"
|
||||||
:placeholder="$t('mqttadmin.HassPrefixTopicHint')"/>
|
maxlength="32"
|
||||||
|
:placeholder="$t('mqttadmin.HassPrefixTopicHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.HassRetain')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.HassRetain')"
|
||||||
v-model="mqttConfigList.mqtt_hass_retain"
|
v-model="mqttConfigList.mqtt_hass_retain"
|
||||||
type="checkbox"/>
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.HassExpire')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.HassExpire')"
|
||||||
v-model="mqttConfigList.mqtt_hass_expire"
|
v-model="mqttConfigList.mqtt_hass_expire"
|
||||||
type="checkbox"/>
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('mqttadmin.HassIndividual')"
|
<InputElement
|
||||||
|
:label="$t('mqttadmin.HassIndividual')"
|
||||||
v-model="mqttConfigList.mqtt_hass_individualpanels"
|
v-model="mqttConfigList.mqtt_hass_individualpanels"
|
||||||
type="checkbox"/>
|
type="checkbox"
|
||||||
|
/>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<FormFooter @reload="getMqttConfig" />
|
<FormFooter @reload="getMqttConfig" />
|
||||||
@ -145,11 +218,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import FormFooter from '@/components/FormFooter.vue';
|
import FormFooter from '@/components/FormFooter.vue';
|
||||||
import InputElement from '@/components/InputElement.vue';
|
import InputElement from '@/components/InputElement.vue';
|
||||||
import type { MqttConfig } from "@/types/MqttConfig";
|
import type { MqttConfig } from '@/types/MqttConfig';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
@ -165,8 +238,8 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
mqttConfigList: {} as MqttConfig,
|
mqttConfigList: {} as MqttConfig,
|
||||||
alertMessage: "",
|
alertMessage: '',
|
||||||
alertType: "info",
|
alertType: 'info',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
qosTypeList: [
|
qosTypeList: [
|
||||||
{ key: 0, value: 'QOS0' },
|
{ key: 0, value: 'QOS0' },
|
||||||
@ -181,7 +254,7 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
getMqttConfig() {
|
getMqttConfig() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/mqtt/config", { headers: authHeader() })
|
fetch('/api/mqtt/config', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.mqttConfigList = data;
|
this.mqttConfigList = data;
|
||||||
@ -192,21 +265,19 @@ export default defineComponent({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(this.mqttConfigList));
|
formData.append('data', JSON.stringify(this.mqttConfigList));
|
||||||
|
|
||||||
fetch("/api/mqtt/config", {
|
fetch('/api/mqtt/config', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
|
||||||
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertType = response.type;
|
this.alertType = response.type;
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasePage :title="$t('mqttinfo.MqttInformation')" :isLoading="dataLoading" :show-reload="true" @reload="getMqttInfo">
|
<BasePage
|
||||||
|
:title="$t('mqttinfo.MqttInformation')"
|
||||||
|
:isLoading="dataLoading"
|
||||||
|
:show-reload="true"
|
||||||
|
@reload="getMqttInfo"
|
||||||
|
>
|
||||||
<CardElement :text="$t('mqttinfo.ConfigurationSummary')" textVariant="text-bg-primary">
|
<CardElement :text="$t('mqttinfo.ConfigurationSummary')" textVariant="text-bg-primary">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover table-condensed">
|
<table class="table table-hover table-condensed">
|
||||||
@ -7,7 +12,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.Status') }}</th>
|
<th>{{ $t('mqttinfo.Status') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="mqttDataList.mqtt_enabled" true_text="mqttinfo.Enabled" false_text="mqttinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="mqttDataList.mqtt_enabled"
|
||||||
|
true_text="mqttinfo.Enabled"
|
||||||
|
false_text="mqttinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -32,24 +41,42 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.PublishInterval') }}</th>
|
<th>{{ $t('mqttinfo.PublishInterval') }}</th>
|
||||||
<td>{{ $t('mqttinfo.Seconds', { sec: mqttDataList.mqtt_publish_interval }) }}</td>
|
<td>
|
||||||
|
{{
|
||||||
|
$t('mqttinfo.Seconds', {
|
||||||
|
sec: mqttDataList.mqtt_publish_interval,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.CleanSession') }}</th>
|
<th>{{ $t('mqttinfo.CleanSession') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="mqttDataList.mqtt_clean_session" true_text="mqttinfo.Enabled" false_text="mqttinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="mqttDataList.mqtt_clean_session"
|
||||||
|
true_text="mqttinfo.Enabled"
|
||||||
|
false_text="mqttinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.Retain') }}</th>
|
<th>{{ $t('mqttinfo.Retain') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="mqttDataList.mqtt_retain" true_text="mqttinfo.Enabled" false_text="mqttinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="mqttDataList.mqtt_retain"
|
||||||
|
true_text="mqttinfo.Enabled"
|
||||||
|
false_text="mqttinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.Tls') }}</th>
|
<th>{{ $t('mqttinfo.Tls') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="mqttDataList.mqtt_tls" true_text="mqttinfo.Enabled" false_text="mqttinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="mqttDataList.mqtt_tls"
|
||||||
|
true_text="mqttinfo.Enabled"
|
||||||
|
false_text="mqttinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-show="mqttDataList.mqtt_tls">
|
<tr v-show="mqttDataList.mqtt_tls">
|
||||||
@ -59,7 +86,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.TlsCertLogin') }}</th>
|
<th>{{ $t('mqttinfo.TlsCertLogin') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="mqttDataList.mqtt_tls_cert_login" true_text="mqttinfo.Enabled" false_text="mqttinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="mqttDataList.mqtt_tls_cert_login"
|
||||||
|
true_text="mqttinfo.Enabled"
|
||||||
|
false_text="mqttinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-show="mqttDataList.mqtt_tls_cert_login">
|
<tr v-show="mqttDataList.mqtt_tls_cert_login">
|
||||||
@ -78,7 +109,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.Status') }}</th>
|
<th>{{ $t('mqttinfo.Status') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="mqttDataList.mqtt_hass_enabled" true_text="mqttinfo.Enabled" false_text="mqttinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="mqttDataList.mqtt_hass_enabled"
|
||||||
|
true_text="mqttinfo.Enabled"
|
||||||
|
false_text="mqttinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -88,19 +123,31 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.Retain') }}</th>
|
<th>{{ $t('mqttinfo.Retain') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="mqttDataList.mqtt_hass_retain" true_text="mqttinfo.Enabled" false_text="mqttinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="mqttDataList.mqtt_hass_retain"
|
||||||
|
true_text="mqttinfo.Enabled"
|
||||||
|
false_text="mqttinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.Expire') }}</th>
|
<th>{{ $t('mqttinfo.Expire') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="mqttDataList.mqtt_hass_expire" true_text="mqttinfo.Enabled" false_text="mqttinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="mqttDataList.mqtt_hass_expire"
|
||||||
|
true_text="mqttinfo.Enabled"
|
||||||
|
false_text="mqttinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.IndividualPanels') }}</th>
|
<th>{{ $t('mqttinfo.IndividualPanels') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="mqttDataList.mqtt_hass_individualpanels" true_text="mqttinfo.Enabled" false_text="mqttinfo.Disabled" />
|
<StatusBadge
|
||||||
|
:status="mqttDataList.mqtt_hass_individualpanels"
|
||||||
|
true_text="mqttinfo.Enabled"
|
||||||
|
false_text="mqttinfo.Disabled"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -115,7 +162,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('mqttinfo.ConnectionStatus') }}</th>
|
<th>{{ $t('mqttinfo.ConnectionStatus') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="mqttDataList.mqtt_connected" true_text="mqttinfo.Connected" false_text="mqttinfo.Disconnected" />
|
<StatusBadge
|
||||||
|
:status="mqttDataList.mqtt_connected"
|
||||||
|
true_text="mqttinfo.Connected"
|
||||||
|
false_text="mqttinfo.Disconnected"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -137,7 +188,7 @@ export default defineComponent({
|
|||||||
components: {
|
components: {
|
||||||
BasePage,
|
BasePage,
|
||||||
CardElement,
|
CardElement,
|
||||||
StatusBadge
|
StatusBadge,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -151,7 +202,7 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
getMqttInfo() {
|
getMqttInfo() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/mqtt/status", { headers: authHeader() })
|
fetch('/api/mqtt/status', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.mqttDataList = data;
|
this.mqttDataList = data;
|
||||||
|
|||||||
@ -6,62 +6,92 @@
|
|||||||
|
|
||||||
<form @submit="saveNetworkConfig">
|
<form @submit="saveNetworkConfig">
|
||||||
<CardElement :text="$t('networkadmin.WifiConfiguration')" textVariant="text-bg-primary">
|
<CardElement :text="$t('networkadmin.WifiConfiguration')" textVariant="text-bg-primary">
|
||||||
<InputElement :label="$t('networkadmin.WifiSsid')"
|
<InputElement
|
||||||
|
:label="$t('networkadmin.WifiSsid')"
|
||||||
v-model="networkConfigList.ssid"
|
v-model="networkConfigList.ssid"
|
||||||
type="text" maxlength="32"/>
|
type="text"
|
||||||
|
maxlength="32"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('networkadmin.WifiPassword')"
|
<InputElement
|
||||||
|
:label="$t('networkadmin.WifiPassword')"
|
||||||
v-model="networkConfigList.password"
|
v-model="networkConfigList.password"
|
||||||
type="password" maxlength="64"/>
|
type="password"
|
||||||
|
maxlength="64"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('networkadmin.Hostname')"
|
<InputElement
|
||||||
|
:label="$t('networkadmin.Hostname')"
|
||||||
v-model="networkConfigList.hostname"
|
v-model="networkConfigList.hostname"
|
||||||
type="text" maxlength="32"
|
type="text"
|
||||||
|
maxlength="32"
|
||||||
>
|
>
|
||||||
<div class="alert alert-secondary" role="alert" v-html="$t('networkadmin.HostnameHint')"></div>
|
<div class="alert alert-secondary" role="alert" v-html="$t('networkadmin.HostnameHint')"></div>
|
||||||
</InputElement>
|
</InputElement>
|
||||||
|
|
||||||
<InputElement :label="$t('networkadmin.EnableDhcp')"
|
<InputElement :label="$t('networkadmin.EnableDhcp')" v-model="networkConfigList.dhcp" type="checkbox" />
|
||||||
v-model="networkConfigList.dhcp"
|
|
||||||
type="checkbox"/>
|
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('networkadmin.StaticIpConfiguration')" textVariant="text-bg-primary" add-space
|
<CardElement
|
||||||
|
:text="$t('networkadmin.StaticIpConfiguration')"
|
||||||
|
textVariant="text-bg-primary"
|
||||||
|
add-space
|
||||||
v-show="!networkConfigList.dhcp"
|
v-show="!networkConfigList.dhcp"
|
||||||
>
|
>
|
||||||
<InputElement :label="$t('networkadmin.IpAddress')"
|
<InputElement
|
||||||
|
:label="$t('networkadmin.IpAddress')"
|
||||||
v-model="networkConfigList.ipaddress"
|
v-model="networkConfigList.ipaddress"
|
||||||
type="text" maxlength="32"/>
|
type="text"
|
||||||
|
maxlength="32"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('networkadmin.Netmask')"
|
<InputElement
|
||||||
|
:label="$t('networkadmin.Netmask')"
|
||||||
v-model="networkConfigList.netmask"
|
v-model="networkConfigList.netmask"
|
||||||
type="text" maxlength="32"/>
|
type="text"
|
||||||
|
maxlength="32"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('networkadmin.DefaultGateway')"
|
<InputElement
|
||||||
|
:label="$t('networkadmin.DefaultGateway')"
|
||||||
v-model="networkConfigList.gateway"
|
v-model="networkConfigList.gateway"
|
||||||
type="text" maxlength="32"/>
|
type="text"
|
||||||
|
maxlength="32"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('networkadmin.Dns', { num: 1 })"
|
<InputElement
|
||||||
|
:label="$t('networkadmin.Dns', { num: 1 })"
|
||||||
v-model="networkConfigList.dns1"
|
v-model="networkConfigList.dns1"
|
||||||
type="text" maxlength="32"/>
|
type="text"
|
||||||
|
maxlength="32"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('networkadmin.Dns', { num: 2 })"
|
<InputElement
|
||||||
|
:label="$t('networkadmin.Dns', { num: 2 })"
|
||||||
v-model="networkConfigList.dns2"
|
v-model="networkConfigList.dns2"
|
||||||
type="text" maxlength="32"/>
|
type="text"
|
||||||
|
maxlength="32"
|
||||||
|
/>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('networkadmin.MdnsSettings')" textVariant="text-bg-primary" add-space>
|
<CardElement :text="$t('networkadmin.MdnsSettings')" textVariant="text-bg-primary" add-space>
|
||||||
<InputElement :label="$t('networkadmin.EnableMdns')"
|
<InputElement
|
||||||
|
:label="$t('networkadmin.EnableMdns')"
|
||||||
v-model="networkConfigList.mdnsenabled"
|
v-model="networkConfigList.mdnsenabled"
|
||||||
type="checkbox"/>
|
type="checkbox"
|
||||||
|
/>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('networkadmin.AdminAp')" textVariant="text-bg-primary" add-space>
|
<CardElement :text="$t('networkadmin.AdminAp')" textVariant="text-bg-primary" add-space>
|
||||||
<InputElement :label="$t('networkadmin.ApTimeout')"
|
<InputElement
|
||||||
|
:label="$t('networkadmin.ApTimeout')"
|
||||||
v-model="networkConfigList.aptimeout"
|
v-model="networkConfigList.aptimeout"
|
||||||
type="number" min="0" max="99999"
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="99999"
|
||||||
:postfix="$t('networkadmin.Minutes')"
|
:postfix="$t('networkadmin.Minutes')"
|
||||||
:tooltip="$t('networkadmin.ApTimeoutHint')"/>
|
:tooltip="$t('networkadmin.ApTimeoutHint')"
|
||||||
|
/>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
<FormFooter @reload="getNetworkConfig" />
|
<FormFooter @reload="getNetworkConfig" />
|
||||||
</form>
|
</form>
|
||||||
@ -70,11 +100,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import FormFooter from '@/components/FormFooter.vue';
|
import FormFooter from '@/components/FormFooter.vue';
|
||||||
import InputElement from '@/components/InputElement.vue';
|
import InputElement from '@/components/InputElement.vue';
|
||||||
import type { NetworkConfig } from "@/types/NetworkConfig";
|
import type { NetworkConfig } from '@/types/NetworkConfig';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
@ -90,8 +120,8 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
networkConfigList: {} as NetworkConfig,
|
networkConfigList: {} as NetworkConfig,
|
||||||
alertMessage: "",
|
alertMessage: '',
|
||||||
alertType: "info",
|
alertType: 'info',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -101,7 +131,7 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
getNetworkConfig() {
|
getNetworkConfig() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/network/config", { headers: authHeader() })
|
fetch('/api/network/config', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.networkConfigList = data;
|
this.networkConfigList = data;
|
||||||
@ -112,21 +142,19 @@ export default defineComponent({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(this.networkConfigList));
|
formData.append('data', JSON.stringify(this.networkConfigList));
|
||||||
|
|
||||||
fetch("/api/network/config", {
|
fetch('/api/network/config', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
|
||||||
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertType = response.type;
|
this.alertType = response.type;
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<BasePage :title="$t('networkinfo.NetworkInformation')" :isLoading="dataLoading" :show-reload="true" @reload="getNetworkInfo">
|
<BasePage
|
||||||
|
:title="$t('networkinfo.NetworkInformation')"
|
||||||
|
:isLoading="dataLoading"
|
||||||
|
:show-reload="true"
|
||||||
|
@reload="getNetworkInfo"
|
||||||
|
>
|
||||||
<WifiStationInfo :networkStatus="networkDataList" />
|
<WifiStationInfo :networkStatus="networkDataList" />
|
||||||
<div class="mt-5"></div>
|
<div class="mt-5"></div>
|
||||||
<WifiApInfo :networkStatus="networkDataList" />
|
<WifiApInfo :networkStatus="networkDataList" />
|
||||||
@ -13,10 +18,10 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import InterfaceApInfo from "@/components/InterfaceApInfo.vue";
|
import InterfaceApInfo from '@/components/InterfaceApInfo.vue';
|
||||||
import InterfaceNetworkInfo from "@/components/InterfaceNetworkInfo.vue";
|
import InterfaceNetworkInfo from '@/components/InterfaceNetworkInfo.vue';
|
||||||
import WifiApInfo from "@/components/WifiApInfo.vue";
|
import WifiApInfo from '@/components/WifiApInfo.vue';
|
||||||
import WifiStationInfo from "@/components/WifiStationInfo.vue";
|
import WifiStationInfo from '@/components/WifiStationInfo.vue';
|
||||||
import type { NetworkStatus } from '@/types/NetworkStatus';
|
import type { NetworkStatus } from '@/types/NetworkStatus';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
@ -33,7 +38,7 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
networkDataList: {} as NetworkStatus,
|
networkDataList: {} as NetworkStatus,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getNetworkInfo();
|
this.getNetworkInfo();
|
||||||
@ -41,7 +46,7 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
getNetworkInfo() {
|
getNetworkInfo() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/network/status", { headers: authHeader() })
|
fetch('/api/network/status', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.networkDataList = data;
|
this.networkDataList = data;
|
||||||
|
|||||||
@ -6,37 +6,56 @@
|
|||||||
|
|
||||||
<form @submit="saveNtpConfig">
|
<form @submit="saveNtpConfig">
|
||||||
<CardElement :text="$t('ntpadmin.NtpConfiguration')" textVariant="text-bg-primary">
|
<CardElement :text="$t('ntpadmin.NtpConfiguration')" textVariant="text-bg-primary">
|
||||||
<InputElement :label="$t('ntpadmin.TimeServer')"
|
<InputElement
|
||||||
|
:label="$t('ntpadmin.TimeServer')"
|
||||||
v-model="ntpConfigList.ntp_server"
|
v-model="ntpConfigList.ntp_server"
|
||||||
type="text" maxlength="32"
|
type="text"
|
||||||
:tooltip="$t('ntpadmin.TimeServerHint')"/>
|
maxlength="32"
|
||||||
|
:tooltip="$t('ntpadmin.TimeServerHint')"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label for="inputTimezone" class="col-sm-2 col-form-label">{{ $t('ntpadmin.Timezone') }}</label>
|
<label for="inputTimezone" class="col-sm-2 col-form-label">{{ $t('ntpadmin.Timezone') }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<select class="form-select" v-model="timezoneSelect">
|
<select class="form-select" v-model="timezoneSelect">
|
||||||
<option v-for="(config, name) in timezoneList" :key="name + '---' + config"
|
<option
|
||||||
:value="name + '---' + config">
|
v-for="(config, name) in timezoneList"
|
||||||
|
:key="name + '---' + config"
|
||||||
|
:value="name + '---' + config"
|
||||||
|
>
|
||||||
{{ name }}
|
{{ name }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InputElement :label="$t('ntpadmin.TimezoneConfig')"
|
<InputElement
|
||||||
|
:label="$t('ntpadmin.TimezoneConfig')"
|
||||||
v-model="ntpConfigList.ntp_timezone"
|
v-model="ntpConfigList.ntp_timezone"
|
||||||
type="text" maxlength="32" disabled/>
|
type="text"
|
||||||
|
maxlength="32"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('ntpadmin.LocationConfiguration')" textVariant="text-bg-primary" add-space>
|
<CardElement :text="$t('ntpadmin.LocationConfiguration')" textVariant="text-bg-primary" add-space>
|
||||||
<InputElement :label="$t('ntpadmin.Latitude')"
|
<InputElement
|
||||||
|
:label="$t('ntpadmin.Latitude')"
|
||||||
v-model="ntpConfigList.latitude"
|
v-model="ntpConfigList.latitude"
|
||||||
type="number" min="-90" max="90" step="any"/>
|
type="number"
|
||||||
|
min="-90"
|
||||||
|
max="90"
|
||||||
|
step="any"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('ntpadmin.Longitude')"
|
<InputElement
|
||||||
|
:label="$t('ntpadmin.Longitude')"
|
||||||
v-model="ntpConfigList.longitude"
|
v-model="ntpConfigList.longitude"
|
||||||
type="number" min="-180" max="180" step="any"/>
|
type="number"
|
||||||
|
min="-180"
|
||||||
|
max="180"
|
||||||
|
step="any"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-2 col-form-label">
|
<label class="col-sm-2 col-form-label">
|
||||||
@ -56,13 +75,9 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<CardElement :text="$t('ntpadmin.ManualTimeSynchronization')" textVariant="text-bg-primary" add-space>
|
<CardElement :text="$t('ntpadmin.ManualTimeSynchronization')" textVariant="text-bg-primary" add-space>
|
||||||
<InputElement :label="$t('ntpadmin.CurrentOpenDtuTime')"
|
<InputElement :label="$t('ntpadmin.CurrentOpenDtuTime')" v-model="mcuTime" type="text" disabled />
|
||||||
v-model="mcuTime"
|
|
||||||
type="text" disabled/>
|
|
||||||
|
|
||||||
<InputElement :label="$t('ntpadmin.CurrentLocalTime')"
|
<InputElement :label="$t('ntpadmin.CurrentLocalTime')" v-model="localTime" type="text" disabled />
|
||||||
v-model="localTime"
|
|
||||||
type="text" disabled/>
|
|
||||||
|
|
||||||
<div class="text-center mb-3">
|
<div class="text-center mb-3">
|
||||||
<button type="button" class="btn btn-danger" @click="setCurrentTime()">
|
<button type="button" class="btn btn-danger" @click="setCurrentTime()">
|
||||||
@ -76,11 +91,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import InputElement from '@/components/InputElement.vue';
|
import InputElement from '@/components/InputElement.vue';
|
||||||
import FormFooter from '@/components/FormFooter.vue';
|
import FormFooter from '@/components/FormFooter.vue';
|
||||||
import type { NtpConfig } from "@/types/NtpConfig";
|
import type { NtpConfig } from '@/types/NtpConfig';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { BIconInfoCircle } from 'bootstrap-icons-vue';
|
import { BIconInfoCircle } from 'bootstrap-icons-vue';
|
||||||
@ -100,12 +115,12 @@ export default defineComponent({
|
|||||||
timezoneLoading: true,
|
timezoneLoading: true,
|
||||||
ntpConfigList: {} as NtpConfig,
|
ntpConfigList: {} as NtpConfig,
|
||||||
timezoneList: {},
|
timezoneList: {},
|
||||||
timezoneSelect: "",
|
timezoneSelect: '',
|
||||||
mcuTime: new Date(),
|
mcuTime: new Date(),
|
||||||
localTime: new Date(),
|
localTime: new Date(),
|
||||||
dataAgeInterval: 0,
|
dataAgeInterval: 0,
|
||||||
alertMessage: "",
|
alertMessage: '',
|
||||||
alertType: "info",
|
alertType: 'info',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
sunsetTypeList: [
|
sunsetTypeList: [
|
||||||
{ key: 0, value: 'OFFICIAL' },
|
{ key: 0, value: 'OFFICIAL' },
|
||||||
@ -117,8 +132,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
timezoneSelect: function (newValue) {
|
timezoneSelect: function (newValue) {
|
||||||
this.ntpConfigList.ntp_timezone = newValue.split("---")[1];
|
this.ntpConfigList.ntp_timezone = newValue.split('---')[1];
|
||||||
this.ntpConfigList.ntp_timezone_descr = newValue.split("---")[0];
|
this.ntpConfigList.ntp_timezone_descr = newValue.split('---')[0];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -136,7 +151,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
getTimezoneList() {
|
getTimezoneList() {
|
||||||
this.timezoneLoading = true;
|
this.timezoneLoading = true;
|
||||||
fetch("/zones.json")
|
fetch('/zones.json')
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.timezoneList = data;
|
this.timezoneList = data;
|
||||||
@ -145,31 +160,23 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
getNtpConfig() {
|
getNtpConfig() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/ntp/config", { headers: authHeader() })
|
fetch('/api/ntp/config', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((data) => {
|
||||||
(data) => {
|
|
||||||
this.ntpConfigList = data;
|
this.ntpConfigList = data;
|
||||||
this.timezoneSelect =
|
this.timezoneSelect =
|
||||||
this.ntpConfigList.ntp_timezone_descr +
|
this.ntpConfigList.ntp_timezone_descr + '---' + this.ntpConfigList.ntp_timezone;
|
||||||
"---" +
|
|
||||||
this.ntpConfigList.ntp_timezone;
|
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getCurrentTime() {
|
getCurrentTime() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/ntp/time", { headers: authHeader() })
|
fetch('/api/ntp/time', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((data) => {
|
||||||
(data) => {
|
this.mcuTime = new Date(data.year, data.month - 1, data.day, data.hour, data.minute, data.second);
|
||||||
this.mcuTime = new Date(
|
|
||||||
data.year, data.month - 1, data.day,
|
|
||||||
data.hour, data.minute, data.second);
|
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
setCurrentTime() {
|
setCurrentTime() {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@ -182,21 +189,19 @@ export default defineComponent({
|
|||||||
second: this.localTime.getSeconds(),
|
second: this.localTime.getSeconds(),
|
||||||
};
|
};
|
||||||
console.log(time);
|
console.log(time);
|
||||||
formData.append("data", JSON.stringify(time));
|
formData.append('data', JSON.stringify(time));
|
||||||
|
|
||||||
fetch("/api/ntp/time", {
|
fetch('/api/ntp/time', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
|
||||||
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertType = response.type;
|
this.alertType = response.type;
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
})
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.getCurrentTime();
|
this.getCurrentTime();
|
||||||
});
|
});
|
||||||
@ -205,21 +210,19 @@ export default defineComponent({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(this.ntpConfigList));
|
formData.append('data', JSON.stringify(this.ntpConfigList));
|
||||||
|
|
||||||
fetch("/api/ntp/config", {
|
fetch('/api/ntp/config', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
|
||||||
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertType = response.type;
|
this.alertType = response.type;
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -28,7 +28,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('ntpinfo.Status') }}</th>
|
<th>{{ $t('ntpinfo.Status') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="ntpDataList.ntp_status" true_text="ntpinfo.Synced" false_text="ntpinfo.NotSynced" />
|
<StatusBadge
|
||||||
|
:status="ntpDataList.ntp_status"
|
||||||
|
true_text="ntpinfo.Synced"
|
||||||
|
false_text="ntpinfo.NotSynced"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -38,20 +42,28 @@
|
|||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('ntpinfo.Sunrise') }}</th>
|
<th>{{ $t('ntpinfo.Sunrise') }}</th>
|
||||||
<td v-if="ntpDataList.sun_isSunsetAvailable">{{ ntpDataList.sun_risetime }}</td>
|
<td v-if="ntpDataList.sun_isSunsetAvailable">
|
||||||
|
{{ ntpDataList.sun_risetime }}
|
||||||
|
</td>
|
||||||
<td v-else>{{ $t('ntpinfo.NotAvailable') }}</td>
|
<td v-else>{{ $t('ntpinfo.NotAvailable') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('ntpinfo.Sunset') }}</th>
|
<th>{{ $t('ntpinfo.Sunset') }}</th>
|
||||||
<td v-if="ntpDataList.sun_isSunsetAvailable">{{ ntpDataList.sun_settime }}</td>
|
<td v-if="ntpDataList.sun_isSunsetAvailable">
|
||||||
|
{{ ntpDataList.sun_settime }}
|
||||||
|
</td>
|
||||||
<td v-else>{{ $t('ntpinfo.NotAvailable') }}</td>
|
<td v-else>{{ $t('ntpinfo.NotAvailable') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('ntpinfo.Mode') }}</th>
|
<th>{{ $t('ntpinfo.Mode') }}</th>
|
||||||
<td>
|
<td>
|
||||||
<StatusBadge :status="ntpDataList.sun_isDayPeriod"
|
<StatusBadge
|
||||||
true_text="ntpinfo.Day" true_class="text-bg-warning"
|
:status="ntpDataList.sun_isDayPeriod"
|
||||||
false_text="ntpinfo.Night" false_class="text-bg-dark" />
|
true_text="ntpinfo.Day"
|
||||||
|
true_class="text-bg-warning"
|
||||||
|
false_text="ntpinfo.Night"
|
||||||
|
false_class="text-bg-dark"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -65,7 +77,7 @@
|
|||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import StatusBadge from '@/components/StatusBadge.vue';
|
import StatusBadge from '@/components/StatusBadge.vue';
|
||||||
import type { NtpStatus } from "@/types/NtpStatus";
|
import type { NtpStatus } from '@/types/NtpStatus';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
@ -87,7 +99,7 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
getNtpInfo() {
|
getNtpInfo() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/ntp/status", { headers: authHeader() })
|
fetch('/api/ntp/status', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.ntpDataList = data;
|
this.ntpDataList = data;
|
||||||
|
|||||||
@ -6,21 +6,30 @@
|
|||||||
|
|
||||||
<form @submit="savePasswordConfig">
|
<form @submit="savePasswordConfig">
|
||||||
<CardElement :text="$t('securityadmin.AdminPassword')" textVariant="text-bg-primary">
|
<CardElement :text="$t('securityadmin.AdminPassword')" textVariant="text-bg-primary">
|
||||||
<InputElement :label="$t('securityadmin.Password')"
|
<InputElement
|
||||||
|
:label="$t('securityadmin.Password')"
|
||||||
v-model="securityConfigList.password"
|
v-model="securityConfigList.password"
|
||||||
type="password" maxlength="64"/>
|
type="password"
|
||||||
|
maxlength="64"
|
||||||
|
/>
|
||||||
|
|
||||||
<InputElement :label="$t('securityadmin.RepeatPassword')"
|
<InputElement
|
||||||
|
:label="$t('securityadmin.RepeatPassword')"
|
||||||
v-model="passwordRepeat"
|
v-model="passwordRepeat"
|
||||||
type="password" maxlength="64"/>
|
type="password"
|
||||||
|
maxlength="64"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="alert alert-secondary" role="alert" v-html="$t('securityadmin.PasswordHint')"></div>
|
<div class="alert alert-secondary" role="alert" v-html="$t('securityadmin.PasswordHint')"></div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<CardElement :text="$t('securityadmin.Permissions')" textVariant="text-bg-primary" add-space>
|
<CardElement :text="$t('securityadmin.Permissions')" textVariant="text-bg-primary" add-space>
|
||||||
<InputElement :label="$t('securityadmin.ReadOnly')"
|
<InputElement
|
||||||
|
:label="$t('securityadmin.ReadOnly')"
|
||||||
v-model="securityConfigList.allow_readonly"
|
v-model="securityConfigList.allow_readonly"
|
||||||
type="checkbox" wide/>
|
type="checkbox"
|
||||||
|
wide
|
||||||
|
/>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
|
|
||||||
<FormFooter @reload="getPasswordConfig" />
|
<FormFooter @reload="getPasswordConfig" />
|
||||||
@ -30,7 +39,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import FormFooter from '@/components/FormFooter.vue';
|
import FormFooter from '@/components/FormFooter.vue';
|
||||||
import InputElement from '@/components/InputElement.vue';
|
import InputElement from '@/components/InputElement.vue';
|
||||||
@ -49,12 +58,12 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
alertMessage: "",
|
alertMessage: '',
|
||||||
alertType: "info",
|
alertType: 'info',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
|
|
||||||
securityConfigList: {} as SecurityConfig,
|
securityConfigList: {} as SecurityConfig,
|
||||||
passwordRepeat: "",
|
passwordRepeat: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -63,42 +72,38 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
getPasswordConfig() {
|
getPasswordConfig() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/security/config", { headers: authHeader() })
|
fetch('/api/security/config', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((data) => {
|
||||||
(data) => {
|
|
||||||
this.securityConfigList = data;
|
this.securityConfigList = data;
|
||||||
this.passwordRepeat = this.securityConfigList.password;
|
this.passwordRepeat = this.securityConfigList.password;
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
savePasswordConfig(e: Event) {
|
savePasswordConfig(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (this.securityConfigList.password != this.passwordRepeat) {
|
if (this.securityConfigList.password != this.passwordRepeat) {
|
||||||
this.alertMessage = "Passwords are not equal";
|
this.alertMessage = 'Passwords are not equal';
|
||||||
this.alertType = "warning";
|
this.alertType = 'warning';
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(this.securityConfigList));
|
formData.append('data', JSON.stringify(this.securityConfigList));
|
||||||
|
|
||||||
fetch("/api/security/config", {
|
fetch('/api/security/config', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then(
|
.then((response) => {
|
||||||
(response) => {
|
|
||||||
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
this.alertMessage = this.$t('apiresponse.' + response.code, response.param);
|
||||||
this.alertType = response.type;
|
this.alertType = response.type;
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -15,11 +15,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BasePage from '@/components/BasePage.vue';
|
import BasePage from '@/components/BasePage.vue';
|
||||||
import FirmwareInfo from "@/components/FirmwareInfo.vue";
|
import FirmwareInfo from '@/components/FirmwareInfo.vue';
|
||||||
import HardwareInfo from "@/components/HardwareInfo.vue";
|
import HardwareInfo from '@/components/HardwareInfo.vue';
|
||||||
import MemoryInfo from "@/components/MemoryInfo.vue";
|
import MemoryInfo from '@/components/MemoryInfo.vue';
|
||||||
import HeapDetails from "@/components/HeapDetails.vue";
|
import HeapDetails from '@/components/HeapDetails.vue';
|
||||||
import RadioInfo from "@/components/RadioInfo.vue";
|
import RadioInfo from '@/components/RadioInfo.vue';
|
||||||
import type { SystemStatus } from '@/types/SystemStatus';
|
import type { SystemStatus } from '@/types/SystemStatus';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
@ -38,16 +38,16 @@ export default defineComponent({
|
|||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
systemDataList: {} as SystemStatus,
|
systemDataList: {} as SystemStatus,
|
||||||
allowVersionInfo: false,
|
allowVersionInfo: false,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.allowVersionInfo = (localStorage.getItem("allowVersionInfo") || "0") == "1";
|
this.allowVersionInfo = (localStorage.getItem('allowVersionInfo') || '0') == '1';
|
||||||
this.getSystemInfo();
|
this.getSystemInfo();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getSystemInfo() {
|
getSystemInfo() {
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
fetch("/api/system/status", { headers: authHeader() })
|
fetch('/api/system/status', { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.systemDataList = data;
|
this.systemDataList = data;
|
||||||
@ -55,7 +55,7 @@ export default defineComponent({
|
|||||||
if (this.allowVersionInfo) {
|
if (this.allowVersionInfo) {
|
||||||
this.getUpdateInfo();
|
this.getUpdateInfo();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
getUpdateInfo() {
|
getUpdateInfo() {
|
||||||
if (this.systemDataList.git_hash === undefined) {
|
if (this.systemDataList.git_hash === undefined) {
|
||||||
@ -64,47 +64,51 @@ export default defineComponent({
|
|||||||
|
|
||||||
// If the left char is a "g" the value is the git hash (remove the "g")
|
// If the left char is a "g" the value is the git hash (remove the "g")
|
||||||
this.systemDataList.git_is_hash = this.systemDataList.git_hash?.substring(0, 1) == 'g';
|
this.systemDataList.git_is_hash = this.systemDataList.git_hash?.substring(0, 1) == 'g';
|
||||||
this.systemDataList.git_hash = this.systemDataList.git_is_hash ? this.systemDataList.git_hash?.substring(1) : this.systemDataList.git_hash;
|
this.systemDataList.git_hash = this.systemDataList.git_is_hash
|
||||||
|
? this.systemDataList.git_hash?.substring(1)
|
||||||
|
: this.systemDataList.git_hash;
|
||||||
|
|
||||||
// Handle format "v0.1-5-gabcdefh"
|
// Handle format "v0.1-5-gabcdefh"
|
||||||
if (this.systemDataList.git_hash?.lastIndexOf("-") >= 0) {
|
if (this.systemDataList.git_hash?.lastIndexOf('-') >= 0) {
|
||||||
this.systemDataList.git_hash = this.systemDataList.git_hash.substring(this.systemDataList.git_hash.lastIndexOf("-") + 2)
|
this.systemDataList.git_hash = this.systemDataList.git_hash.substring(
|
||||||
|
this.systemDataList.git_hash.lastIndexOf('-') + 2
|
||||||
|
);
|
||||||
this.systemDataList.git_is_hash = true;
|
this.systemDataList.git_is_hash = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchUrl = "https://api.github.com/repos/tbnobody/OpenDTU/compare/"
|
const fetchUrl =
|
||||||
+ this.systemDataList.git_hash + "...HEAD";
|
'https://api.github.com/repos/tbnobody/OpenDTU/compare/' + this.systemDataList.git_hash + '...HEAD';
|
||||||
|
|
||||||
fetch(fetchUrl)
|
fetch(fetchUrl)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return response.json()
|
return response.json();
|
||||||
}
|
}
|
||||||
throw new Error(this.$t("systeminfo.VersionError"));
|
throw new Error(this.$t('systeminfo.VersionError'));
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.total_commits > 0) {
|
if (data.total_commits > 0) {
|
||||||
this.systemDataList.update_text = this.$t("systeminfo.VersionNew");
|
this.systemDataList.update_text = this.$t('systeminfo.VersionNew');
|
||||||
this.systemDataList.update_status = "text-bg-danger";
|
this.systemDataList.update_status = 'text-bg-danger';
|
||||||
this.systemDataList.update_url = data.html_url;
|
this.systemDataList.update_url = data.html_url;
|
||||||
} else {
|
} else {
|
||||||
this.systemDataList.update_text = this.$t("systeminfo.VersionOk");
|
this.systemDataList.update_text = this.$t('systeminfo.VersionOk');
|
||||||
this.systemDataList.update_status = "text-bg-success";
|
this.systemDataList.update_status = 'text-bg-success';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error: Error) => {
|
.catch((error: Error) => {
|
||||||
this.systemDataList.update_text = error.message;
|
this.systemDataList.update_text = error.message;
|
||||||
this.systemDataList.update_status = "text-bg-secondary";
|
this.systemDataList.update_status = 'text-bg-secondary';
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
allowVersionInfo(allow: boolean) {
|
allowVersionInfo(allow: boolean) {
|
||||||
localStorage.setItem("allowVersionInfo", allow ? "1" : "0");
|
localStorage.setItem('allowVersionInfo', allow ? '1' : '0');
|
||||||
if (allow) {
|
if (allow) {
|
||||||
this.getUpdateInfo();
|
this.getUpdateInfo();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
361
webapp/yarn.lock
361
webapp/yarn.lock
@ -151,20 +151,20 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
|
"@eslint-community/regexpp@^4.11.0":
|
||||||
|
version "4.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae"
|
||||||
|
integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==
|
||||||
|
|
||||||
"@eslint-community/regexpp@^4.5.1":
|
"@eslint-community/regexpp@^4.5.1":
|
||||||
version "4.8.1"
|
version "4.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.1.tgz#8c4bb756cc2aa7eaf13cfa5e69c83afb3260c20c"
|
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.1.tgz#8c4bb756cc2aa7eaf13cfa5e69c83afb3260c20c"
|
||||||
integrity sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==
|
integrity sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==
|
||||||
|
|
||||||
"@eslint-community/regexpp@^4.6.1":
|
"@eslint/config-array@^0.17.1":
|
||||||
version "4.6.2"
|
version "0.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8"
|
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.17.1.tgz#d9b8b8b6b946f47388f32bedfd3adf29ca8f8910"
|
||||||
integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==
|
integrity sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==
|
||||||
|
|
||||||
"@eslint/config-array@^0.17.0":
|
|
||||||
version "0.17.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.17.0.tgz#ff305e1ee618a00e6e5d0485454c8d92d94a860d"
|
|
||||||
integrity sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/object-schema" "^2.1.4"
|
"@eslint/object-schema" "^2.1.4"
|
||||||
debug "^4.3.1"
|
debug "^4.3.1"
|
||||||
@ -185,10 +185,10 @@
|
|||||||
minimatch "^3.1.2"
|
minimatch "^3.1.2"
|
||||||
strip-json-comments "^3.1.1"
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
"@eslint/js@9.6.0":
|
"@eslint/js@9.8.0":
|
||||||
version "9.6.0"
|
version "9.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.6.0.tgz#5b0cb058cc13d9c92d4e561d3538807fa5127c95"
|
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.8.0.tgz#ae9bc14bb839713c5056f5018bcefa955556d3a4"
|
||||||
integrity sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==
|
integrity sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==
|
||||||
|
|
||||||
"@eslint/object-schema@^2.1.4":
|
"@eslint/object-schema@^2.1.4":
|
||||||
version "2.1.4"
|
version "2.1.4"
|
||||||
@ -449,12 +449,12 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
||||||
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
||||||
|
|
||||||
"@types/node@^20.14.9":
|
"@types/node@^22.1.0":
|
||||||
version "20.14.9"
|
version "22.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.1.0.tgz#6d6adc648b5e03f0e83c78dc788c2b037d0ad94b"
|
||||||
integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==
|
integrity sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~5.26.4"
|
undici-types "~6.13.0"
|
||||||
|
|
||||||
"@types/pulltorefreshjs@^0.1.7":
|
"@types/pulltorefreshjs@^0.1.7":
|
||||||
version "0.1.7"
|
version "0.1.7"
|
||||||
@ -562,29 +562,29 @@
|
|||||||
"@typescript-eslint/types" "7.2.0"
|
"@typescript-eslint/types" "7.2.0"
|
||||||
eslint-visitor-keys "^3.4.1"
|
eslint-visitor-keys "^3.4.1"
|
||||||
|
|
||||||
"@vitejs/plugin-vue@^5.0.5":
|
"@vitejs/plugin-vue@^5.1.2":
|
||||||
version "5.0.5"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz#e3dc11e427d4b818b7e3202766ad156e3d5e2eaa"
|
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.1.2.tgz#f11091e0130eca6c1ca8cfb85ee71ea53b255d31"
|
||||||
integrity sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==
|
integrity sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==
|
||||||
|
|
||||||
"@volar/language-core@2.3.4", "@volar/language-core@~2.3.1":
|
"@volar/language-core@2.4.0-alpha.18", "@volar/language-core@~2.4.0-alpha.18":
|
||||||
version "2.3.4"
|
version "2.4.0-alpha.18"
|
||||||
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.3.4.tgz#51de0263039a567a12a1eea90e02e59cdbf5de3b"
|
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.4.0-alpha.18.tgz#dafffd68ac07c26d69de16741187fd4c06bfa345"
|
||||||
integrity sha512-wXBhY11qG6pCDAqDnbBRFIDSIwbqkWI7no+lj5+L7IlA7HRIjRP7YQLGzT0LF4lS6eHkMSsclXqy9DwYJasZTQ==
|
integrity sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@volar/source-map" "2.3.4"
|
"@volar/source-map" "2.4.0-alpha.18"
|
||||||
|
|
||||||
"@volar/source-map@2.3.4":
|
"@volar/source-map@2.4.0-alpha.18":
|
||||||
version "2.3.4"
|
version "2.4.0-alpha.18"
|
||||||
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.3.4.tgz#1d285610134fe565ca59a54e5a99c12befc70c93"
|
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.4.0-alpha.18.tgz#a2413932ff6b1821ae8efcbd9249d4da3f99f223"
|
||||||
integrity sha512-C+t63nwcblqLIVTYXaVi/+gC8NukDaDIQI72J3R7aXGvtgaVB16c+J8Iz7/VfOy7kjYv7lf5GhBny6ACw9fTGQ==
|
integrity sha512-MTeCV9MUwwsH0sNFiZwKtFrrVZUK6p8ioZs3xFzHc2cvDXHWlYN3bChdQtwKX+FY2HG6H3CfAu1pKijolzIQ8g==
|
||||||
|
|
||||||
"@volar/typescript@~2.3.1":
|
"@volar/typescript@~2.4.0-alpha.18":
|
||||||
version "2.3.4"
|
version "2.4.0-alpha.18"
|
||||||
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.3.4.tgz#bfa2834c79bd0b9a38cdfdf220fea0afa8ed64b0"
|
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.4.0-alpha.18.tgz#806aca9ce1bd7c48dc5fcd0fcf7f33bdd04e5b35"
|
||||||
integrity sha512-acCvt7dZECyKcvO5geNybmrqOsu9u8n5XP1rfiYsOLYGPxvHRav9BVmEdRyZ3vvY6mNyQ1wLL5Hday4IShe17w==
|
integrity sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@volar/language-core" "2.3.4"
|
"@volar/language-core" "2.4.0-alpha.18"
|
||||||
path-browserify "^1.0.1"
|
path-browserify "^1.0.1"
|
||||||
vscode-uri "^3.0.8"
|
vscode-uri "^3.0.8"
|
||||||
|
|
||||||
@ -609,13 +609,13 @@
|
|||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
"@vue/compiler-core@3.4.31":
|
"@vue/compiler-core@3.4.35":
|
||||||
version "3.4.31"
|
version "3.4.35"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.31.tgz#b51a76f1b30e9b5eba0553264dff0f171aedb7c6"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.35.tgz#421922a75ecabf1aabc6b7a2ce98b5acb2fc2d65"
|
||||||
integrity sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==
|
integrity sha512-gKp0zGoLnMYtw4uS/SJRRO7rsVggLjvot3mcctlMXunYNsX+aRJDqqw/lV5/gHK91nvaAAlWFgdVl020AW1Prg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser" "^7.24.7"
|
"@babel/parser" "^7.24.7"
|
||||||
"@vue/shared" "3.4.31"
|
"@vue/shared" "3.4.35"
|
||||||
entities "^4.5.0"
|
entities "^4.5.0"
|
||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
source-map-js "^1.2.0"
|
source-map-js "^1.2.0"
|
||||||
@ -628,13 +628,13 @@
|
|||||||
"@vue/compiler-core" "3.2.47"
|
"@vue/compiler-core" "3.2.47"
|
||||||
"@vue/shared" "3.2.47"
|
"@vue/shared" "3.2.47"
|
||||||
|
|
||||||
"@vue/compiler-dom@3.4.31":
|
"@vue/compiler-dom@3.4.35":
|
||||||
version "3.4.31"
|
version "3.4.35"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz#30961ca847f5d6ad18ffa26236c219f61b195f6b"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.35.tgz#cd0881f1b4ed655cd96367bce4845f87023a5a2d"
|
||||||
integrity sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==
|
integrity sha512-pWIZRL76/oE/VMhdv/ovZfmuooEni6JPG1BFe7oLk5DZRo/ImydXijoZl/4kh2406boRQ7lxTYzbZEEXEhj9NQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-core" "3.4.31"
|
"@vue/compiler-core" "3.4.35"
|
||||||
"@vue/shared" "3.4.31"
|
"@vue/shared" "3.4.35"
|
||||||
|
|
||||||
"@vue/compiler-dom@^3.4.0":
|
"@vue/compiler-dom@^3.4.0":
|
||||||
version "3.4.21"
|
version "3.4.21"
|
||||||
@ -644,19 +644,19 @@
|
|||||||
"@vue/compiler-core" "3.4.21"
|
"@vue/compiler-core" "3.4.21"
|
||||||
"@vue/shared" "3.4.21"
|
"@vue/shared" "3.4.21"
|
||||||
|
|
||||||
"@vue/compiler-sfc@3.4.31":
|
"@vue/compiler-sfc@3.4.35":
|
||||||
version "3.4.31"
|
version "3.4.35"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz#cc6bfccda17df8268cc5440842277f61623c591f"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.35.tgz#16f87dd3bdab64cef14d3a6fcf53f8673e404071"
|
||||||
integrity sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==
|
integrity sha512-xacnRS/h/FCsjsMfxBkzjoNxyxEyKyZfBch/P4vkLRvYJwe5ChXmZZrj8Dsed/752H2Q3JE8kYu9Uyha9J6PgA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser" "^7.24.7"
|
"@babel/parser" "^7.24.7"
|
||||||
"@vue/compiler-core" "3.4.31"
|
"@vue/compiler-core" "3.4.35"
|
||||||
"@vue/compiler-dom" "3.4.31"
|
"@vue/compiler-dom" "3.4.35"
|
||||||
"@vue/compiler-ssr" "3.4.31"
|
"@vue/compiler-ssr" "3.4.35"
|
||||||
"@vue/shared" "3.4.31"
|
"@vue/shared" "3.4.35"
|
||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
magic-string "^0.30.10"
|
magic-string "^0.30.10"
|
||||||
postcss "^8.4.38"
|
postcss "^8.4.40"
|
||||||
source-map-js "^1.2.0"
|
source-map-js "^1.2.0"
|
||||||
|
|
||||||
"@vue/compiler-sfc@^3.2.47":
|
"@vue/compiler-sfc@^3.2.47":
|
||||||
@ -683,23 +683,31 @@
|
|||||||
"@vue/compiler-dom" "3.2.47"
|
"@vue/compiler-dom" "3.2.47"
|
||||||
"@vue/shared" "3.2.47"
|
"@vue/shared" "3.2.47"
|
||||||
|
|
||||||
"@vue/compiler-ssr@3.4.31":
|
"@vue/compiler-ssr@3.4.35":
|
||||||
version "3.4.31"
|
version "3.4.35"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz#f62ffecdf15bacb883d0099780cf9a1e3654bfc4"
|
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.4.35.tgz#0774c9a0afed74d71615209904b38f3fa9711adb"
|
||||||
integrity sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==
|
integrity sha512-7iynB+0KB1AAJKk/biENTV5cRGHRdbdaD7Mx3nWcm1W8bVD6QmnH3B4AHhQQ1qZHhqFwzEzMwiytXm3PX1e60A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-dom" "3.4.31"
|
"@vue/compiler-dom" "3.4.35"
|
||||||
"@vue/shared" "3.4.31"
|
"@vue/shared" "3.4.35"
|
||||||
|
|
||||||
|
"@vue/compiler-vue2@^2.7.16":
|
||||||
|
version "2.7.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz#2ba837cbd3f1b33c2bc865fbe1a3b53fb611e249"
|
||||||
|
integrity sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==
|
||||||
|
dependencies:
|
||||||
|
de-indent "^1.0.2"
|
||||||
|
he "^1.2.0"
|
||||||
|
|
||||||
"@vue/devtools-api@^6.5.0":
|
"@vue/devtools-api@^6.5.0":
|
||||||
version "6.5.0"
|
version "6.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
|
||||||
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
|
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
|
||||||
|
|
||||||
"@vue/devtools-api@^6.5.1":
|
"@vue/devtools-api@^6.6.3":
|
||||||
version "6.6.1"
|
version "6.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.1.tgz#7c14346383751d9f6ad4bea0963245b30220ef83"
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.3.tgz#b23a588154cba8986bba82b6e1d0248bde3fd1a0"
|
||||||
integrity sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==
|
integrity sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==
|
||||||
|
|
||||||
"@vue/eslint-config-typescript@^13.0.0":
|
"@vue/eslint-config-typescript@^13.0.0":
|
||||||
version "13.0.0"
|
version "13.0.0"
|
||||||
@ -710,19 +718,19 @@
|
|||||||
"@typescript-eslint/parser" "^7.1.1"
|
"@typescript-eslint/parser" "^7.1.1"
|
||||||
vue-eslint-parser "^9.3.1"
|
vue-eslint-parser "^9.3.1"
|
||||||
|
|
||||||
"@vue/language-core@2.0.22":
|
"@vue/language-core@2.0.29":
|
||||||
version "2.0.22"
|
version "2.0.29"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.0.22.tgz#2f8164ecc83f85f27301521d0a6ce37cc59bb23a"
|
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.0.29.tgz#19462d786cd7a1c21dbe575b46970a57094e0357"
|
||||||
integrity sha512-dNTAAtEOuMiz7N1s5tKpypnVVCtawxVSF5BukD0ELcYSw+DSbrSlYYSw8GuwvurodCeYFSHsmslE+c2sYDNoiA==
|
integrity sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@volar/language-core" "~2.3.1"
|
"@volar/language-core" "~2.4.0-alpha.18"
|
||||||
"@vue/compiler-dom" "^3.4.0"
|
"@vue/compiler-dom" "^3.4.0"
|
||||||
|
"@vue/compiler-vue2" "^2.7.16"
|
||||||
"@vue/shared" "^3.4.0"
|
"@vue/shared" "^3.4.0"
|
||||||
computeds "^0.0.1"
|
computeds "^0.0.1"
|
||||||
minimatch "^9.0.3"
|
minimatch "^9.0.3"
|
||||||
muggle-string "^0.4.1"
|
muggle-string "^0.4.1"
|
||||||
path-browserify "^1.0.1"
|
path-browserify "^1.0.1"
|
||||||
vue-template-compiler "^2.7.14"
|
|
||||||
|
|
||||||
"@vue/reactivity-transform@3.2.47":
|
"@vue/reactivity-transform@3.2.47":
|
||||||
version "3.2.47"
|
version "3.2.47"
|
||||||
@ -735,38 +743,38 @@
|
|||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
magic-string "^0.25.7"
|
magic-string "^0.25.7"
|
||||||
|
|
||||||
"@vue/reactivity@3.4.31":
|
"@vue/reactivity@3.4.35":
|
||||||
version "3.4.31"
|
version "3.4.35"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.31.tgz#eda80e90c4f9d7659efe1f5ed99c2dfdc9e93d77"
|
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.35.tgz#dfbb4f5371da1290ac86e3313d0e9a68bb0ab38d"
|
||||||
integrity sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==
|
integrity sha512-Ggtz7ZZHakriKioveJtPlStYardwQH6VCs9V13/4qjHSQb/teE30LVJNrbBVs4+aoYGtTQKJbTe4CWGxVZrvEw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/shared" "3.4.31"
|
"@vue/shared" "3.4.35"
|
||||||
|
|
||||||
"@vue/runtime-core@3.4.31":
|
"@vue/runtime-core@3.4.35":
|
||||||
version "3.4.31"
|
version "3.4.35"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.31.tgz#ad3a41ad76385c0429e3e4dbefb81918494e10cf"
|
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.4.35.tgz#c036013a7b1bbe0d14a6b76eb4355dae6690d2e6"
|
||||||
integrity sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==
|
integrity sha512-D+BAjFoWwT5wtITpSxwqfWZiBClhBbR+bm0VQlWYFOadUUXFo+5wbe9ErXhLvwguPiLZdEF13QAWi2vP3ZD5tA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/reactivity" "3.4.31"
|
"@vue/reactivity" "3.4.35"
|
||||||
"@vue/shared" "3.4.31"
|
"@vue/shared" "3.4.35"
|
||||||
|
|
||||||
"@vue/runtime-dom@3.4.31":
|
"@vue/runtime-dom@3.4.35":
|
||||||
version "3.4.31"
|
version "3.4.35"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz#bae7ad844f944af33699c73581bc36125bab96ce"
|
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.4.35.tgz#74254c7c327163d692e1d7d2b6d9e92463744e90"
|
||||||
integrity sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==
|
integrity sha512-yGOlbos+MVhlS5NWBF2HDNgblG8e2MY3+GigHEyR/dREAluvI5tuUUgie3/9XeqhPE4LF0i2wjlduh5thnfOqw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/reactivity" "3.4.31"
|
"@vue/reactivity" "3.4.35"
|
||||||
"@vue/runtime-core" "3.4.31"
|
"@vue/runtime-core" "3.4.35"
|
||||||
"@vue/shared" "3.4.31"
|
"@vue/shared" "3.4.35"
|
||||||
csstype "^3.1.3"
|
csstype "^3.1.3"
|
||||||
|
|
||||||
"@vue/server-renderer@3.4.31":
|
"@vue/server-renderer@3.4.35":
|
||||||
version "3.4.31"
|
version "3.4.35"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.31.tgz#bbe990f793c36d62d05bdbbaf142511d53e159fd"
|
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.4.35.tgz#188e94e82d8e729ba7b40dd91d10678b85f77c6b"
|
||||||
integrity sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==
|
integrity sha512-iZ0e/u9mRE4T8tNhlo0tbA+gzVkgv8r5BX6s1kRbOZqfpq14qoIvCZ5gIgraOmYkMYrSEZgkkojFPr+Nyq/Mnw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-ssr" "3.4.31"
|
"@vue/compiler-ssr" "3.4.35"
|
||||||
"@vue/shared" "3.4.31"
|
"@vue/shared" "3.4.35"
|
||||||
|
|
||||||
"@vue/shared@3.2.47":
|
"@vue/shared@3.2.47":
|
||||||
version "3.2.47"
|
version "3.2.47"
|
||||||
@ -778,10 +786,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.21.tgz#de526a9059d0a599f0b429af7037cd0c3ed7d5a1"
|
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.21.tgz#de526a9059d0a599f0b429af7037cd0c3ed7d5a1"
|
||||||
integrity sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==
|
integrity sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==
|
||||||
|
|
||||||
"@vue/shared@3.4.31":
|
"@vue/shared@3.4.35":
|
||||||
version "3.4.31"
|
version "3.4.35"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.31.tgz#af9981f57def2c3f080c14bf219314fc0dc808a0"
|
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.35.tgz#5432f4b1c79e763fcf78cc830faf59ff01248968"
|
||||||
integrity sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==
|
integrity sha512-hvuhBYYDe+b1G8KHxsQ0diDqDMA8D9laxWZhNAjE83VZb5UDaXl9Xnz7cGdDSyiHM90qqI/CyGMcpBpiDy6VVQ==
|
||||||
|
|
||||||
"@vue/tsconfig@^0.5.1":
|
"@vue/tsconfig@^0.5.1":
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
@ -1164,10 +1172,10 @@ escodegen@^2.1.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
eslint-plugin-vue@^9.26.0:
|
eslint-plugin-vue@^9.27.0:
|
||||||
version "9.26.0"
|
version "9.27.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.26.0.tgz#bf7f5cce62c8f878059b91edae44d22974133af5"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.27.0.tgz#c22dae704a03d9ecefa81364ff89f60ce0481f94"
|
||||||
integrity sha512-eTvlxXgd4ijE1cdur850G6KalZqk65k1JKoOI2d1kT3hr8sPD07j1q98FRFdNnpxBELGPWxZmInxeHGF/GxtqQ==
|
integrity sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils" "^4.4.0"
|
"@eslint-community/eslint-utils" "^4.4.0"
|
||||||
globals "^13.24.0"
|
globals "^13.24.0"
|
||||||
@ -1175,7 +1183,7 @@ eslint-plugin-vue@^9.26.0:
|
|||||||
nth-check "^2.1.1"
|
nth-check "^2.1.1"
|
||||||
postcss-selector-parser "^6.0.15"
|
postcss-selector-parser "^6.0.15"
|
||||||
semver "^7.6.0"
|
semver "^7.6.0"
|
||||||
vue-eslint-parser "^9.4.2"
|
vue-eslint-parser "^9.4.3"
|
||||||
xml-name-validator "^4.0.0"
|
xml-name-validator "^4.0.0"
|
||||||
|
|
||||||
eslint-scope@^7.1.1:
|
eslint-scope@^7.1.1:
|
||||||
@ -1186,10 +1194,10 @@ eslint-scope@^7.1.1:
|
|||||||
esrecurse "^4.3.0"
|
esrecurse "^4.3.0"
|
||||||
estraverse "^5.2.0"
|
estraverse "^5.2.0"
|
||||||
|
|
||||||
eslint-scope@^8.0.1:
|
eslint-scope@^8.0.2:
|
||||||
version "8.0.1"
|
version "8.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.1.tgz#a9601e4b81a0b9171657c343fb13111688963cfc"
|
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.2.tgz#5cbb33d4384c9136083a71190d548158fe128f94"
|
||||||
integrity sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==
|
integrity sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==
|
||||||
dependencies:
|
dependencies:
|
||||||
esrecurse "^4.3.0"
|
esrecurse "^4.3.0"
|
||||||
estraverse "^5.2.0"
|
estraverse "^5.2.0"
|
||||||
@ -1214,16 +1222,16 @@ eslint-visitor-keys@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb"
|
||||||
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
|
integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==
|
||||||
|
|
||||||
eslint@^9.6.0:
|
eslint@^9.8.0:
|
||||||
version "9.6.0"
|
version "9.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.6.0.tgz#9f54373afa15e1ba356656a8d96233182027fb49"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.8.0.tgz#a4f4a090c8ea2d10864d89a6603e02ce9f649f0f"
|
||||||
integrity sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==
|
integrity sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils" "^4.2.0"
|
"@eslint-community/eslint-utils" "^4.2.0"
|
||||||
"@eslint-community/regexpp" "^4.6.1"
|
"@eslint-community/regexpp" "^4.11.0"
|
||||||
"@eslint/config-array" "^0.17.0"
|
"@eslint/config-array" "^0.17.1"
|
||||||
"@eslint/eslintrc" "^3.1.0"
|
"@eslint/eslintrc" "^3.1.0"
|
||||||
"@eslint/js" "9.6.0"
|
"@eslint/js" "9.8.0"
|
||||||
"@humanwhocodes/module-importer" "^1.0.1"
|
"@humanwhocodes/module-importer" "^1.0.1"
|
||||||
"@humanwhocodes/retry" "^0.3.0"
|
"@humanwhocodes/retry" "^0.3.0"
|
||||||
"@nodelib/fs.walk" "^1.2.8"
|
"@nodelib/fs.walk" "^1.2.8"
|
||||||
@ -1232,7 +1240,7 @@ eslint@^9.6.0:
|
|||||||
cross-spawn "^7.0.2"
|
cross-spawn "^7.0.2"
|
||||||
debug "^4.3.2"
|
debug "^4.3.2"
|
||||||
escape-string-regexp "^4.0.0"
|
escape-string-regexp "^4.0.0"
|
||||||
eslint-scope "^8.0.1"
|
eslint-scope "^8.0.2"
|
||||||
eslint-visitor-keys "^4.0.0"
|
eslint-visitor-keys "^4.0.0"
|
||||||
espree "^10.1.0"
|
espree "^10.1.0"
|
||||||
esquery "^1.5.0"
|
esquery "^1.5.0"
|
||||||
@ -2064,6 +2072,11 @@ picocolors@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||||
|
|
||||||
|
picocolors@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
|
||||||
|
integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
|
||||||
|
|
||||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||||
@ -2105,13 +2118,22 @@ postcss@^8.1.10:
|
|||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
postcss@^8.4.38:
|
postcss@^8.4.39:
|
||||||
version "8.4.38"
|
version "8.4.39"
|
||||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.39.tgz#aa3c94998b61d3a9c259efa51db4b392e1bde0e3"
|
||||||
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
|
integrity sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid "^3.3.7"
|
nanoid "^3.3.7"
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.1"
|
||||||
|
source-map-js "^1.2.0"
|
||||||
|
|
||||||
|
postcss@^8.4.40:
|
||||||
|
version "8.4.40"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8"
|
||||||
|
integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^3.3.7"
|
||||||
|
picocolors "^1.0.1"
|
||||||
source-map-js "^1.2.0"
|
source-map-js "^1.2.0"
|
||||||
|
|
||||||
prelude-ls@^1.2.1:
|
prelude-ls@^1.2.1:
|
||||||
@ -2119,6 +2141,11 @@ prelude-ls@^1.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||||
|
|
||||||
|
prettier@^3.3.3:
|
||||||
|
version "3.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"
|
||||||
|
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==
|
||||||
|
|
||||||
pulltorefreshjs@^0.1.22:
|
pulltorefreshjs@^0.1.22:
|
||||||
version "0.1.22"
|
version "0.1.22"
|
||||||
resolved "https://registry.yarnpkg.com/pulltorefreshjs/-/pulltorefreshjs-0.1.22.tgz#ddb5e3feee0b2a49fd46e1b18e84fffef2c47ac0"
|
resolved "https://registry.yarnpkg.com/pulltorefreshjs/-/pulltorefreshjs-0.1.22.tgz#ddb5e3feee0b2a49fd46e1b18e84fffef2c47ac0"
|
||||||
@ -2421,10 +2448,10 @@ supports-preserve-symlinks-flag@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||||
|
|
||||||
terser@^5.31.1:
|
terser@^5.31.3:
|
||||||
version "5.31.1"
|
version "5.31.3"
|
||||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.1.tgz#735de3c987dd671e95190e6b98cfe2f07f3cf0d4"
|
resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.3.tgz#b24b7beb46062f4653f049eea4f0cd165d0f0c38"
|
||||||
integrity sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==
|
integrity sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@jridgewell/source-map" "^0.3.3"
|
"@jridgewell/source-map" "^0.3.3"
|
||||||
acorn "^8.8.2"
|
acorn "^8.8.2"
|
||||||
@ -2460,10 +2487,10 @@ type-fest@^0.20.2:
|
|||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
||||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||||
|
|
||||||
typescript@^5.5.2:
|
typescript@^5.5.4:
|
||||||
version "5.5.2"
|
version "5.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.2.tgz#c26f023cb0054e657ce04f72583ea2d85f8d0507"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
|
||||||
integrity sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==
|
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
|
||||||
|
|
||||||
ufo@^1.1.2:
|
ufo@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
@ -2480,10 +2507,10 @@ unbox-primitive@^1.0.2:
|
|||||||
has-symbols "^1.0.3"
|
has-symbols "^1.0.3"
|
||||||
which-boxed-primitive "^1.0.2"
|
which-boxed-primitive "^1.0.2"
|
||||||
|
|
||||||
undici-types@~5.26.4:
|
undici-types@~6.13.0:
|
||||||
version "5.26.5"
|
version "6.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.13.0.tgz#e3e79220ab8c81ed1496b5812471afd7cf075ea5"
|
||||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
integrity sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==
|
||||||
|
|
||||||
universalify@^2.0.0:
|
universalify@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@ -2534,13 +2561,13 @@ vite-plugin-css-injected-by-js@^3.5.1:
|
|||||||
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.1.tgz#b9c568c21b131d08e31aa6d368ee39c9d6c1b6c1"
|
resolved "https://registry.yarnpkg.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.1.tgz#b9c568c21b131d08e31aa6d368ee39c9d6c1b6c1"
|
||||||
integrity sha512-9ioqwDuEBxW55gNoWFEDhfLTrVKXEEZgl5adhWmmqa88EQGKfTmexy4v1Rh0pAS6RhKQs2bUYQArprB32JpUZQ==
|
integrity sha512-9ioqwDuEBxW55gNoWFEDhfLTrVKXEEZgl5adhWmmqa88EQGKfTmexy4v1Rh0pAS6RhKQs2bUYQArprB32JpUZQ==
|
||||||
|
|
||||||
vite@^5.3.2:
|
vite@^5.3.5:
|
||||||
version "5.3.2"
|
version "5.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.2.tgz#2f0a8531c71060467ed3e0a205a203f269b6d9c8"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.5.tgz#b847f846fb2b6cb6f6f4ed50a830186138cb83d8"
|
||||||
integrity sha512-6lA7OBHBlXUxiJxbO5aAY2fsHHzDr1q7DvXYnyZycRs2Dz+dXBWuhpWHvmljTRTpQC2uvGmUFFkSHF2vGo90MA==
|
integrity sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild "^0.21.3"
|
esbuild "^0.21.3"
|
||||||
postcss "^8.4.38"
|
postcss "^8.4.39"
|
||||||
rollup "^4.13.0"
|
rollup "^4.13.0"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.3"
|
fsevents "~2.3.3"
|
||||||
@ -2563,10 +2590,10 @@ vue-eslint-parser@^9.3.1:
|
|||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
semver "^7.3.6"
|
semver "^7.3.6"
|
||||||
|
|
||||||
vue-eslint-parser@^9.4.2:
|
vue-eslint-parser@^9.4.3:
|
||||||
version "9.4.2"
|
version "9.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz#02ffcce82042b082292f2d1672514615f0d95b6d"
|
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz#9b04b22c71401f1e8bca9be7c3e3416a4bde76a8"
|
||||||
integrity sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==
|
integrity sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
eslint-scope "^7.1.1"
|
eslint-scope "^7.1.1"
|
||||||
@ -2585,40 +2612,32 @@ vue-i18n@^9.13.1:
|
|||||||
"@intlify/shared" "9.13.1"
|
"@intlify/shared" "9.13.1"
|
||||||
"@vue/devtools-api" "^6.5.0"
|
"@vue/devtools-api" "^6.5.0"
|
||||||
|
|
||||||
vue-router@^4.4.0:
|
vue-router@^4.4.2:
|
||||||
version "4.4.0"
|
version "4.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.4.0.tgz#128e3fc0c84421035a9bd26027245e6bd68f69ab"
|
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.4.2.tgz#bc7bf27f108fc15e5cc2a30b314a662275e2b2bb"
|
||||||
integrity sha512-HB+t2p611aIZraV2aPSRNXf0Z/oLZFrlygJm+sZbdJaW6lcFqEDQwnzUBXn+DApw+/QzDU/I9TeWx9izEjTmsA==
|
integrity sha512-1qNybkn2L7QsLzaXs8nvlQmRKp8XF8DCxZys/Jr1JpQcHsKUxTKzTxCVA1G7NfBfwRIBgCJPoujOG5lHCCNUxw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/devtools-api" "^6.5.1"
|
"@vue/devtools-api" "^6.6.3"
|
||||||
|
|
||||||
vue-template-compiler@^2.7.14:
|
vue-tsc@^2.0.29:
|
||||||
version "2.7.14"
|
version "2.0.29"
|
||||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz#4545b7dfb88090744c1577ae5ac3f964e61634b1"
|
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.0.29.tgz#bf7e9605af9fadec7fd6037d242217f5c6ad2c3b"
|
||||||
integrity sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==
|
integrity sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
de-indent "^1.0.2"
|
"@volar/typescript" "~2.4.0-alpha.18"
|
||||||
he "^1.2.0"
|
"@vue/language-core" "2.0.29"
|
||||||
|
|
||||||
vue-tsc@^2.0.22:
|
|
||||||
version "2.0.22"
|
|
||||||
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-2.0.22.tgz#ddfef6b3a016d1b986008a3b8702f7e667db128c"
|
|
||||||
integrity sha512-lMBIwPBO0sxCcmvu45yt1b035AaQ8/XSXQDk8m75y4j0jSXY/y/XzfEtssQ9JMS47lDaR10O3/926oCs8OeGUw==
|
|
||||||
dependencies:
|
|
||||||
"@volar/typescript" "~2.3.1"
|
|
||||||
"@vue/language-core" "2.0.22"
|
|
||||||
semver "^7.5.4"
|
semver "^7.5.4"
|
||||||
|
|
||||||
vue@^3.4.31:
|
vue@^3.4.35:
|
||||||
version "3.4.31"
|
version "3.4.35"
|
||||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.31.tgz#83a3c4dab8302b0e974b0d4b92a2f6a6378ae797"
|
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.35.tgz#9ad23525919eece40153fdf8675d07ddd879eb33"
|
||||||
integrity sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==
|
integrity sha512-+fl/GLmI4GPileHftVlCdB7fUL4aziPcqTudpTGXCT8s+iZWuOCeNEB5haX6Uz2IpRrbEXOgIFbe+XciCuGbNQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/compiler-dom" "3.4.31"
|
"@vue/compiler-dom" "3.4.35"
|
||||||
"@vue/compiler-sfc" "3.4.31"
|
"@vue/compiler-sfc" "3.4.35"
|
||||||
"@vue/runtime-dom" "3.4.31"
|
"@vue/runtime-dom" "3.4.35"
|
||||||
"@vue/server-renderer" "3.4.31"
|
"@vue/server-renderer" "3.4.35"
|
||||||
"@vue/shared" "3.4.31"
|
"@vue/shared" "3.4.35"
|
||||||
|
|
||||||
webpack-sources@^3.2.3:
|
webpack-sources@^3.2.3:
|
||||||
version "3.2.3"
|
version "3.2.3"
|
||||||
|
|||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user