Feature: Allow reordering of the inverters in the live view
Reordering can be done in the inverter settings via drag&drop.
This commit is contained in:
parent
e0027d951b
commit
1c8bd8091b
@ -40,6 +40,7 @@ struct CHANNEL_CONFIG_T {
|
|||||||
struct INVERTER_CONFIG_T {
|
struct INVERTER_CONFIG_T {
|
||||||
uint64_t Serial;
|
uint64_t Serial;
|
||||||
char Name[INV_MAX_NAME_STRLEN + 1];
|
char Name[INV_MAX_NAME_STRLEN + 1];
|
||||||
|
uint8_t Order;
|
||||||
bool Poll_Enable;
|
bool Poll_Enable;
|
||||||
bool Poll_Enable_Night;
|
bool Poll_Enable_Night;
|
||||||
bool Command_Enable;
|
bool Command_Enable;
|
||||||
|
|||||||
@ -28,6 +28,7 @@ enum WebApiError {
|
|||||||
InverterInvalidMaxChannel,
|
InverterInvalidMaxChannel,
|
||||||
InverterChanged,
|
InverterChanged,
|
||||||
InverterDeleted,
|
InverterDeleted,
|
||||||
|
InverterOrdered,
|
||||||
|
|
||||||
LimitBase = 5000,
|
LimitBase = 5000,
|
||||||
LimitSerialZero,
|
LimitSerialZero,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ private:
|
|||||||
void onInverterAdd(AsyncWebServerRequest* request);
|
void onInverterAdd(AsyncWebServerRequest* request);
|
||||||
void onInverterEdit(AsyncWebServerRequest* request);
|
void onInverterEdit(AsyncWebServerRequest* request);
|
||||||
void onInverterDelete(AsyncWebServerRequest* request);
|
void onInverterDelete(AsyncWebServerRequest* request);
|
||||||
|
void onInverterOrder(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
};
|
};
|
||||||
@ -103,6 +103,7 @@ bool ConfigurationClass::write()
|
|||||||
JsonObject inv = inverters.createNestedObject();
|
JsonObject inv = inverters.createNestedObject();
|
||||||
inv["serial"] = config.Inverter[i].Serial;
|
inv["serial"] = config.Inverter[i].Serial;
|
||||||
inv["name"] = config.Inverter[i].Name;
|
inv["name"] = config.Inverter[i].Name;
|
||||||
|
inv["order"] = config.Inverter[i].Order;
|
||||||
inv["poll_enable"] = config.Inverter[i].Poll_Enable;
|
inv["poll_enable"] = config.Inverter[i].Poll_Enable;
|
||||||
inv["poll_enable_night"] = config.Inverter[i].Poll_Enable_Night;
|
inv["poll_enable_night"] = config.Inverter[i].Poll_Enable_Night;
|
||||||
inv["command_enable"] = config.Inverter[i].Command_Enable;
|
inv["command_enable"] = config.Inverter[i].Command_Enable;
|
||||||
@ -247,6 +248,7 @@ bool ConfigurationClass::read()
|
|||||||
JsonObject inv = inverters[i].as<JsonObject>();
|
JsonObject inv = inverters[i].as<JsonObject>();
|
||||||
config.Inverter[i].Serial = inv["serial"] | 0ULL;
|
config.Inverter[i].Serial = inv["serial"] | 0ULL;
|
||||||
strlcpy(config.Inverter[i].Name, inv["name"] | "", sizeof(config.Inverter[i].Name));
|
strlcpy(config.Inverter[i].Name, inv["name"] | "", sizeof(config.Inverter[i].Name));
|
||||||
|
config.Inverter[i].Order = inv["order"] | 0;
|
||||||
|
|
||||||
config.Inverter[i].Poll_Enable = inv["poll_enable"] | true;
|
config.Inverter[i].Poll_Enable = inv["poll_enable"] | true;
|
||||||
config.Inverter[i].Poll_Enable_Night = inv["poll_enable_night"] | true;
|
config.Inverter[i].Poll_Enable_Night = inv["poll_enable_night"] | true;
|
||||||
|
|||||||
@ -21,6 +21,7 @@ void WebApiInverterClass::init(AsyncWebServer* server)
|
|||||||
_server->on("/api/inverter/add", HTTP_POST, std::bind(&WebApiInverterClass::onInverterAdd, this, _1));
|
_server->on("/api/inverter/add", HTTP_POST, std::bind(&WebApiInverterClass::onInverterAdd, this, _1));
|
||||||
_server->on("/api/inverter/edit", HTTP_POST, std::bind(&WebApiInverterClass::onInverterEdit, this, _1));
|
_server->on("/api/inverter/edit", HTTP_POST, std::bind(&WebApiInverterClass::onInverterEdit, this, _1));
|
||||||
_server->on("/api/inverter/del", HTTP_POST, std::bind(&WebApiInverterClass::onInverterDelete, this, _1));
|
_server->on("/api/inverter/del", HTTP_POST, std::bind(&WebApiInverterClass::onInverterDelete, this, _1));
|
||||||
|
_server->on("/api/inverter/order", HTTP_POST, std::bind(&WebApiInverterClass::onInverterOrder, this, _1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiInverterClass::loop()
|
void WebApiInverterClass::loop()
|
||||||
@ -44,6 +45,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
|||||||
JsonObject obj = data.createNestedObject();
|
JsonObject obj = data.createNestedObject();
|
||||||
obj["id"] = i;
|
obj["id"] = i;
|
||||||
obj["name"] = String(config.Inverter[i].Name);
|
obj["name"] = String(config.Inverter[i].Name);
|
||||||
|
obj["order"] = config.Inverter[i].Order;
|
||||||
|
|
||||||
// Inverter Serial is read as HEX
|
// Inverter Serial is read as HEX
|
||||||
char buffer[sizeof(uint64_t) * 8 + 1];
|
char buffer[sizeof(uint64_t) * 8 + 1];
|
||||||
@ -390,3 +392,72 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
MqttHandleHass.forceUpdate();
|
MqttHandleHass.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebApiInverterClass::onInverterOrder(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
if (!WebApi.checkCredentials(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonObject retMsg = response->getRoot();
|
||||||
|
retMsg["type"] = "warning";
|
||||||
|
|
||||||
|
if (!request->hasParam("data", true)) {
|
||||||
|
retMsg["message"] = "No values found!";
|
||||||
|
retMsg["code"] = WebApiError::GenericNoValueFound;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = request->getParam("data", true)->value();
|
||||||
|
|
||||||
|
if (json.length() > 1024) {
|
||||||
|
retMsg["message"] = "Data too large!";
|
||||||
|
retMsg["code"] = WebApiError::GenericDataTooLarge;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicJsonDocument root(1024);
|
||||||
|
DeserializationError error = deserializeJson(root, json);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
retMsg["message"] = "Failed to parse data!";
|
||||||
|
retMsg["code"] = WebApiError::GenericParseError;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(root.containsKey("order"))) {
|
||||||
|
retMsg["message"] = "Values are missing!";
|
||||||
|
retMsg["code"] = WebApiError::GenericValueMissing;
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The order array contains list or id in the right order
|
||||||
|
JsonArray orderArray = root["order"].as<JsonArray>();
|
||||||
|
uint8_t order = 0;
|
||||||
|
for(JsonVariant id : orderArray) {
|
||||||
|
uint8_t inverter_id = id.as<uint8_t>();
|
||||||
|
if (inverter_id < INV_MAX_COUNT) {
|
||||||
|
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[inverter_id];
|
||||||
|
inverter.Order = order;
|
||||||
|
}
|
||||||
|
order++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration.write();
|
||||||
|
|
||||||
|
retMsg["type"] = "success";
|
||||||
|
retMsg["message"] = "Inverter order saved!";
|
||||||
|
retMsg["code"] = WebApiError::InverterOrdered;
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
@ -99,9 +99,14 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
}
|
}
|
||||||
|
|
||||||
JsonObject invObject = invArray.createNestedObject();
|
JsonObject invObject = invArray.createNestedObject();
|
||||||
|
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
|
||||||
|
if (inv_cfg == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
invObject["serial"] = inv->serialString();
|
invObject["serial"] = inv->serialString();
|
||||||
invObject["name"] = inv->name();
|
invObject["name"] = inv->name();
|
||||||
|
invObject["order"] = inv_cfg->Order;
|
||||||
invObject["data_age"] = (millis() - inv->Statistics()->getLastUpdate()) / 1000;
|
invObject["data_age"] = (millis() - inv->Statistics()->getLastUpdate()) / 1000;
|
||||||
invObject["poll_enabled"] = inv->getEnablePolling();
|
invObject["poll_enabled"] = inv->getEnablePolling();
|
||||||
invObject["reachable"] = inv->isReachable();
|
invObject["reachable"] = inv->isReachable();
|
||||||
@ -118,10 +123,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
JsonObject chanTypeObj = invObject.createNestedObject(inv->Statistics()->getChannelTypeName(t));
|
JsonObject chanTypeObj = invObject.createNestedObject(inv->Statistics()->getChannelTypeName(t));
|
||||||
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
|
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
|
||||||
if (t == TYPE_DC) {
|
if (t == TYPE_DC) {
|
||||||
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
|
chanTypeObj[String(static_cast<uint8_t>(c))]["name"]["u"] = inv_cfg->channel[c].Name;
|
||||||
if (inv_cfg != nullptr) {
|
|
||||||
chanTypeObj[String(static_cast<uint8_t>(c))]["name"]["u"] = inv_cfg->channel[c].Name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
addField(chanTypeObj, i, inv, t, c, FLD_PAC);
|
addField(chanTypeObj, i, inv, t, c, FLD_PAC);
|
||||||
addField(chanTypeObj, i, inv, t, c, FLD_UAC);
|
addField(chanTypeObj, i, inv, t, c, FLD_UAC);
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
"bootstrap": "^5.3.0-alpha3",
|
"bootstrap": "^5.3.0-alpha3",
|
||||||
"bootstrap-icons-vue": "^1.10.3",
|
"bootstrap-icons-vue": "^1.10.3",
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.0",
|
||||||
|
"sortablejs": "^1.15.0",
|
||||||
"spark-md5": "^3.0.2",
|
"spark-md5": "^3.0.2",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-i18n": "^9.2.2",
|
"vue-i18n": "^9.2.2",
|
||||||
@ -26,6 +27,7 @@
|
|||||||
"@tsconfig/node18": "^2.0.1",
|
"@tsconfig/node18": "^2.0.1",
|
||||||
"@types/bootstrap": "^5.2.6",
|
"@types/bootstrap": "^5.2.6",
|
||||||
"@types/node": "^20.2.3",
|
"@types/node": "^20.2.3",
|
||||||
|
"@types/sortablejs": "^1.15.1",
|
||||||
"@types/spark-md5": "^3.0.2",
|
"@types/spark-md5": "^3.0.2",
|
||||||
"@vitejs/plugin-vue": "^4.2.3",
|
"@vitejs/plugin-vue": "^4.2.3",
|
||||||
"@vue/eslint-config-typescript": "^11.0.3",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
|
|||||||
@ -51,6 +51,7 @@
|
|||||||
"4006": "Ungültige Anzahl an Kanalwerten übergeben!",
|
"4006": "Ungültige Anzahl an Kanalwerten übergeben!",
|
||||||
"4007": "Wechselrichter geändert!",
|
"4007": "Wechselrichter geändert!",
|
||||||
"4008": "Wechselrichter gelöscht!",
|
"4008": "Wechselrichter gelöscht!",
|
||||||
|
"4009": "Wechselrichter Reihenfolge gespeichert!",
|
||||||
"5001": "@:apiresponse.2001",
|
"5001": "@:apiresponse.2001",
|
||||||
"5002": "Das Limit muss zwischen 1 und {max} sein!",
|
"5002": "Das Limit muss zwischen 1 und {max} sein!",
|
||||||
"5003": "Ungültiten Typ angegeben!",
|
"5003": "Ungültiten Typ angegeben!",
|
||||||
@ -439,6 +440,7 @@
|
|||||||
"StatusHint": "<b>Hinweis:</b> Der Wechselrichter wird über seinen DC-Eingang mit Strom versorgt. Wenn keine Sonne scheint, ist der Wechselrichter aus. Es können trotzdem Anfragen gesendet werden.",
|
"StatusHint": "<b>Hinweis:</b> Der Wechselrichter wird über seinen DC-Eingang mit Strom versorgt. Wenn keine Sonne scheint, ist der Wechselrichter aus. Es können trotzdem Anfragen gesendet werden.",
|
||||||
"Type": "Typ",
|
"Type": "Typ",
|
||||||
"Action": "Aktion",
|
"Action": "Aktion",
|
||||||
|
"SaveOrder": "Reihenfolge speichern",
|
||||||
"DeleteInverter": "Wechselrichter löschen",
|
"DeleteInverter": "Wechselrichter löschen",
|
||||||
"EditInverter": "Wechselrichter bearbeiten",
|
"EditInverter": "Wechselrichter bearbeiten",
|
||||||
"InverterSerial": "Wechselrichter Seriennummer:",
|
"InverterSerial": "Wechselrichter Seriennummer:",
|
||||||
|
|||||||
@ -51,6 +51,7 @@
|
|||||||
"4006": "Invalid amount of max channel setting given!",
|
"4006": "Invalid amount of max channel setting given!",
|
||||||
"4007": "Inverter changed!",
|
"4007": "Inverter changed!",
|
||||||
"4008": "Inverter deleted!",
|
"4008": "Inverter deleted!",
|
||||||
|
"4009": "Inverter order saved!",
|
||||||
"5001": "@:apiresponse.2001",
|
"5001": "@:apiresponse.2001",
|
||||||
"5002": "Limit must between 1 and {max}!",
|
"5002": "Limit must between 1 and {max}!",
|
||||||
"5003": "Invalid type specified!",
|
"5003": "Invalid type specified!",
|
||||||
@ -439,6 +440,7 @@
|
|||||||
"StatusHint": "<b>Hint:</b> The inverter is powered by its DC input. If there is no sun, the inverter is off. Requests can still be sent.",
|
"StatusHint": "<b>Hint:</b> The inverter is powered by its DC input. If there is no sun, the inverter is off. Requests can still be sent.",
|
||||||
"Type": "Type",
|
"Type": "Type",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
|
"SaveOrder": "Save order",
|
||||||
"DeleteInverter": "Delete inverter",
|
"DeleteInverter": "Delete inverter",
|
||||||
"EditInverter": "Edit inverter",
|
"EditInverter": "Edit inverter",
|
||||||
"InverterSerial": "Inverter Serial:",
|
"InverterSerial": "Inverter Serial:",
|
||||||
|
|||||||
@ -51,6 +51,7 @@
|
|||||||
"4006": "Réglage du montant maximal de canaux invalide !",
|
"4006": "Réglage du montant maximal de canaux invalide !",
|
||||||
"4007": "Onduleur modifié !",
|
"4007": "Onduleur modifié !",
|
||||||
"4008": "Onduleur supprimé !",
|
"4008": "Onduleur supprimé !",
|
||||||
|
"4009": "Inverter order saved!",
|
||||||
"5001": "@:apiresponse.2001",
|
"5001": "@:apiresponse.2001",
|
||||||
"5002": "La limite doit être comprise entre 1 et {max} !",
|
"5002": "La limite doit être comprise entre 1 et {max} !",
|
||||||
"5003": "Type spécifié invalide !",
|
"5003": "Type spécifié invalide !",
|
||||||
@ -439,6 +440,7 @@
|
|||||||
"StatusHint": "<b>Astuce :</b> L'onduleur est alimenté par son entrée courant continu. S'il n'y a pas de soleil, l'onduleur est éteint, mais les requêtes peuvent toujours être envoyées.",
|
"StatusHint": "<b>Astuce :</b> L'onduleur est alimenté par son entrée courant continu. S'il n'y a pas de soleil, l'onduleur est éteint, mais les requêtes peuvent toujours être envoyées.",
|
||||||
"Type": "Type",
|
"Type": "Type",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
|
"SaveOrder": "Save order",
|
||||||
"DeleteInverter": "Supprimer l'onduleur",
|
"DeleteInverter": "Supprimer l'onduleur",
|
||||||
"EditInverter": "Modifier l'onduleur",
|
"EditInverter": "Modifier l'onduleur",
|
||||||
"InverterSerial": "Numéro de série de l'onduleur",
|
"InverterSerial": "Numéro de série de l'onduleur",
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export interface InverterStatistics {
|
|||||||
export interface Inverter {
|
export interface Inverter {
|
||||||
serial: number;
|
serial: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
order: number;
|
||||||
data_age: number;
|
data_age: number;
|
||||||
poll_enabled: boolean;
|
poll_enabled: boolean;
|
||||||
reachable: boolean;
|
reachable: boolean;
|
||||||
|
|||||||
@ -457,7 +457,9 @@ export default defineComponent({
|
|||||||
'decimalTwoDigits');
|
'decimalTwoDigits');
|
||||||
},
|
},
|
||||||
inverterData(): Inverter[] {
|
inverterData(): Inverter[] {
|
||||||
return this.liveData.inverters;
|
return this.liveData.inverters.slice().sort((a : Inverter, b: Inverter) => {
|
||||||
|
return a.order - b.order;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
<th scope="col">{{ $t('inverteradmin.Status') }}</th>
|
<th scope="col">{{ $t('inverteradmin.Status') }}</th>
|
||||||
<th>{{ $t('inverteradmin.Serial') }}</th>
|
<th>{{ $t('inverteradmin.Serial') }}</th>
|
||||||
<th>{{ $t('inverteradmin.Name') }}</th>
|
<th>{{ $t('inverteradmin.Name') }}</th>
|
||||||
@ -35,8 +36,9 @@
|
|||||||
<th>{{ $t('inverteradmin.Action') }}</th>
|
<th>{{ $t('inverteradmin.Action') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody ref="invList">
|
||||||
<tr v-for="inverter in sortedInverters" v-bind:key="inverter.id">
|
<tr v-for="inverter in inverters" v-bind:key="inverter.id" :data-id="inverter.id">
|
||||||
|
<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,
|
||||||
@ -63,6 +65,9 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ml-auto text-right">
|
||||||
|
<button class="btn btn-primary my-2" @click="onSaveOrder()">{{ $t('inverteradmin.SaveOrder') }}</button>
|
||||||
|
</div>
|
||||||
</CardElement>
|
</CardElement>
|
||||||
</BasePage>
|
</BasePage>
|
||||||
|
|
||||||
@ -197,6 +202,7 @@ 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 Sortable from 'sortablejs';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
import * as bootstrap from 'bootstrap';
|
import * as bootstrap from 'bootstrap';
|
||||||
import {
|
import {
|
||||||
@ -205,6 +211,7 @@ import {
|
|||||||
BIconTrash,
|
BIconTrash,
|
||||||
BIconArrowDown,
|
BIconArrowDown,
|
||||||
BIconArrowUp,
|
BIconArrowUp,
|
||||||
|
BIconGripHorizontal,
|
||||||
} from 'bootstrap-icons-vue';
|
} from 'bootstrap-icons-vue';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
@ -219,6 +226,7 @@ declare interface Inverter {
|
|||||||
serial: number;
|
serial: number;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
order: number;
|
||||||
poll_enable: boolean;
|
poll_enable: boolean;
|
||||||
poll_enable_night: boolean;
|
poll_enable_night: boolean;
|
||||||
command_enable: boolean;
|
command_enable: boolean;
|
||||||
@ -244,6 +252,7 @@ export default defineComponent({
|
|||||||
BIconTrash,
|
BIconTrash,
|
||||||
BIconArrowDown,
|
BIconArrowDown,
|
||||||
BIconArrowUp,
|
BIconArrowUp,
|
||||||
|
BIconGripHorizontal,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -253,7 +262,8 @@ export default defineComponent({
|
|||||||
selectedInverterData: {} as Inverter,
|
selectedInverterData: {} as Inverter,
|
||||||
inverters: [] as Inverter[],
|
inverters: [] as Inverter[],
|
||||||
dataLoading: true,
|
dataLoading: true,
|
||||||
alert: {} as AlertResponse
|
alert: {} as AlertResponse,
|
||||||
|
sortable: {} as Sortable,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -263,21 +273,27 @@ export default defineComponent({
|
|||||||
created() {
|
created() {
|
||||||
this.getInverters();
|
this.getInverters();
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
sortedInverters(): Inverter[] {
|
|
||||||
return this.inverters.slice().sort((a, b) => {
|
|
||||||
return a.serial - b.serial;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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;
|
this.inverters = data.inverter.slice().sort((a : Inverter, b: Inverter) => {
|
||||||
|
return a.order - b.order;
|
||||||
|
});
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const table = this.$refs.invList as HTMLElement;
|
||||||
|
|
||||||
|
this.sortable = Sortable.create(table, {
|
||||||
|
sort: true,
|
||||||
|
handle: '.drag-handle',
|
||||||
|
animation: 150,
|
||||||
|
draggable: 'tr',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
callInverterApiEndpoint(endpoint: string, jsonData: string) {
|
callInverterApiEndpoint(endpoint: string, jsonData: string) {
|
||||||
@ -316,7 +332,16 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onCloseModal(modal: bootstrap.Modal) {
|
onCloseModal(modal: bootstrap.Modal) {
|
||||||
modal.hide();
|
modal.hide();
|
||||||
}
|
},
|
||||||
|
onSaveOrder() {
|
||||||
|
this.callInverterApiEndpoint("order", JSON.stringify({ order: this.sortable.toArray() }));
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.drag-handle {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -381,6 +381,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
|
||||||
integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
|
integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
|
||||||
|
|
||||||
|
"@types/sortablejs@^1.15.1":
|
||||||
|
version "1.15.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/sortablejs/-/sortablejs-1.15.1.tgz#123abafbe936f754fee5eb5b49009ce1f1075aa5"
|
||||||
|
integrity sha512-g/JwBNToh6oCTAwNS8UGVmjO7NLDKsejVhvE4x1eWiPTC3uCuNsa/TD4ssvX3du+MLiM+SHPNDuijp8y76JzLQ==
|
||||||
|
|
||||||
"@types/spark-md5@^3.0.2":
|
"@types/spark-md5@^3.0.2":
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/spark-md5/-/spark-md5-3.0.2.tgz#da2e8a778a20335fc4f40b6471c4b0d86b70da55"
|
resolved "https://registry.yarnpkg.com/@types/spark-md5/-/spark-md5-3.0.2.tgz#da2e8a778a20335fc4f40b6471c4b0d86b70da55"
|
||||||
@ -2266,6 +2271,11 @@ slash@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||||
|
|
||||||
|
sortablejs@^1.15.0:
|
||||||
|
version "1.15.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.0.tgz#53230b8aa3502bb77a29e2005808ffdb4a5f7e2a"
|
||||||
|
integrity sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==
|
||||||
|
|
||||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
|
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user