Config UI: persist product name, keep device type (#19808)
This commit is contained in:
parent
3b30b2773a
commit
6a4ec7335e
8 changed files with 90 additions and 29 deletions
|
|
@ -10,9 +10,10 @@
|
|||
<form ref="form" class="container mx-0 px-0">
|
||||
<FormRow id="chargerTemplate" :label="$t('config.charger.template')">
|
||||
<select
|
||||
v-if="isNew"
|
||||
id="chargerTemplate"
|
||||
ref="templateSelect"
|
||||
v-model="templateName"
|
||||
:disabled="!isNew"
|
||||
class="form-select w-100"
|
||||
@change="templateChanged"
|
||||
>
|
||||
|
|
@ -45,6 +46,13 @@
|
|||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input
|
||||
v-else
|
||||
type="text"
|
||||
:value="productName"
|
||||
disabled
|
||||
class="form-control w-100"
|
||||
/>
|
||||
</FormRow>
|
||||
<p v-if="loadingTemplate">Loading ...</p>
|
||||
<SponsorTokenRequired v-if="sponsorTokenRequired" />
|
||||
|
|
@ -250,6 +258,9 @@ export default {
|
|||
description() {
|
||||
return this.template?.Requirements?.Description;
|
||||
},
|
||||
productName() {
|
||||
return this.values.deviceProduct || this.templateName;
|
||||
},
|
||||
sponsorTokenRequired() {
|
||||
const list = this.template?.Requirements?.EVCC || [];
|
||||
return list.includes("sponsorship") && !this.isSponsor;
|
||||
|
|
@ -298,6 +309,10 @@ export default {
|
|||
try {
|
||||
const charger = (await api.get(`config/devices/charger/${this.id}`)).data.result;
|
||||
this.values = charger.config;
|
||||
// convert structure to flat list
|
||||
// TODO: adjust GET response to match POST/PUT formats
|
||||
this.values.type = charger.type;
|
||||
this.values.deviceProduct = charger.deviceProduct;
|
||||
this.applyDefaultsFromTemplate();
|
||||
this.templateName = this.values.template;
|
||||
} catch (e) {
|
||||
|
|
@ -317,6 +332,7 @@ export default {
|
|||
},
|
||||
async loadTemplate() {
|
||||
this.template = null;
|
||||
if (!this.templateName) return;
|
||||
this.loadingTemplate = true;
|
||||
try {
|
||||
const opts = {
|
||||
|
|
@ -342,6 +358,13 @@ export default {
|
|||
});
|
||||
},
|
||||
async create() {
|
||||
// persist selected template product
|
||||
if (this.template) {
|
||||
const select = this.$refs.templateSelect;
|
||||
const name = select.options[select.selectedIndex].text;
|
||||
this.values.deviceProduct = name;
|
||||
}
|
||||
|
||||
if (this.testUnknown) {
|
||||
const success = await this.test(this.testCharger);
|
||||
if (!success) return;
|
||||
|
|
|
|||
|
|
@ -632,7 +632,7 @@ export default {
|
|||
},
|
||||
chargerTitle() {
|
||||
if (!this.charger) return "";
|
||||
const title = this.charger.config?.template || "unknown";
|
||||
const title = this.charger.deviceProduct || this.charger.config?.template || "unknown";
|
||||
return `${title} [${this.values.charger}]`;
|
||||
},
|
||||
chargerStatus() {
|
||||
|
|
@ -651,7 +651,7 @@ export default {
|
|||
const name = this.values.meter;
|
||||
if (!name) return "";
|
||||
const meter = this.meters.find((m) => m.name === name);
|
||||
const title = meter?.config?.template || "unknown";
|
||||
const title = meter?.deviceProduct || meter?.config?.template || "unknown";
|
||||
return `${title} [${name}]`;
|
||||
},
|
||||
isDeletable() {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
<select
|
||||
v-if="isNew"
|
||||
id="meterTemplate"
|
||||
ref="templateSelect"
|
||||
v-model="templateName"
|
||||
class="form-select w-100"
|
||||
@change="templateChanged"
|
||||
|
|
@ -377,6 +378,13 @@ export default {
|
|||
});
|
||||
},
|
||||
async create() {
|
||||
// persist selected template product
|
||||
if (this.template) {
|
||||
const select = this.$refs.templateSelect;
|
||||
const name = select.options[select.selectedIndex].text;
|
||||
this.values.deviceProduct = name;
|
||||
}
|
||||
|
||||
if (this.testUnknown) {
|
||||
const success = await this.test(this.testMeter);
|
||||
if (!success) return;
|
||||
|
|
@ -390,8 +398,7 @@ export default {
|
|||
this.$emit("updated");
|
||||
this.close();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert("create failed");
|
||||
this.handleCreateError(e);
|
||||
}
|
||||
this.saving = false;
|
||||
},
|
||||
|
|
@ -417,8 +424,7 @@ export default {
|
|||
this.$emit("updated");
|
||||
this.close();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert("update failed");
|
||||
this.handleUpdateError(e);
|
||||
}
|
||||
this.saving = false;
|
||||
},
|
||||
|
|
@ -429,8 +435,7 @@ export default {
|
|||
this.$emit("updated");
|
||||
this.close();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert("delete failed");
|
||||
this.handleRemoveError(e);
|
||||
}
|
||||
},
|
||||
open() {
|
||||
|
|
@ -443,11 +448,8 @@ export default {
|
|||
selectType(type) {
|
||||
this.selectedType = type;
|
||||
},
|
||||
templateChanged(event) {
|
||||
templateChanged() {
|
||||
this.reset(true);
|
||||
const select = event.target;
|
||||
const name = select.options[select.selectedIndex].text;
|
||||
this.values.deviceProduct = name;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,14 +9,15 @@
|
|||
<form ref="form" class="container mx-0 px-0">
|
||||
<FormRow id="vehicleTemplate" :label="$t('config.vehicle.template')">
|
||||
<select
|
||||
v-if="isNew"
|
||||
id="vehicleTemplate"
|
||||
ref="templateSelect"
|
||||
v-model="templateName"
|
||||
:disabled="!isNew"
|
||||
class="form-select w-100"
|
||||
@change="templateChanged"
|
||||
>
|
||||
<option value="offline">
|
||||
{{ $t("config.vehicle.offline") }}
|
||||
<option :value="templateOptions.offline.template">
|
||||
{{ templateOptions.offline.name }}
|
||||
</option>
|
||||
<option disabled>----------</option>
|
||||
<optgroup :label="$t('config.vehicle.online')">
|
||||
|
|
@ -47,6 +48,13 @@
|
|||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input
|
||||
v-else
|
||||
type="text"
|
||||
:value="productName"
|
||||
disabled
|
||||
class="form-control w-100"
|
||||
/>
|
||||
</FormRow>
|
||||
<p v-if="loadingTemplate">Loading ...</p>
|
||||
<Markdown v-if="description" :markdown="description" class="my-4" />
|
||||
|
|
@ -273,11 +281,11 @@ export default {
|
|||
isModalVisible: false,
|
||||
templates: [],
|
||||
products: [],
|
||||
saving: false,
|
||||
templateName: null,
|
||||
template: null,
|
||||
values: { ...initialValues },
|
||||
saving: false,
|
||||
loadingTemplate: false,
|
||||
values: { ...initialValues },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -286,6 +294,7 @@ export default {
|
|||
online: this.products.filter((p) => !p.group && p.template !== "offline"),
|
||||
generic: this.products.filter((p) => p.group === "generic"),
|
||||
scooter: this.products.filter((p) => p.group === "scooter"),
|
||||
offline: this.products.find((p) => p.template === "offline") || {},
|
||||
};
|
||||
},
|
||||
templateParams() {
|
||||
|
|
@ -316,6 +325,9 @@ export default {
|
|||
description() {
|
||||
return this.template?.Requirements?.Description;
|
||||
},
|
||||
productName() {
|
||||
return this.values.deviceProduct || this.templateName;
|
||||
},
|
||||
apiData() {
|
||||
const data = {
|
||||
template: this.templateName,
|
||||
|
|
@ -372,6 +384,10 @@ export default {
|
|||
try {
|
||||
const vehicle = (await api.get(`config/devices/vehicle/${this.id}`)).data.result;
|
||||
this.values = vehicle.config;
|
||||
// convert structure to flat list
|
||||
// TODO: adjust GET response to match POST/PUT formats
|
||||
this.values.type = vehicle.type;
|
||||
this.values.deviceProduct = vehicle.deviceProduct;
|
||||
this.applyDefaultsFromTemplate();
|
||||
this.templateName = this.values.template;
|
||||
} catch (e) {
|
||||
|
|
@ -379,6 +395,9 @@ export default {
|
|||
}
|
||||
},
|
||||
async loadProducts() {
|
||||
if (!this.isModalVisible) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const opts = { params: { lang: this.$i18n?.locale } };
|
||||
this.products = (await api.get("config/products/vehicle", opts)).data.result;
|
||||
|
|
@ -388,6 +407,7 @@ export default {
|
|||
},
|
||||
async loadTemplate() {
|
||||
this.template = null;
|
||||
if (!this.templateName) return;
|
||||
this.loadingTemplate = true;
|
||||
try {
|
||||
const opts = {
|
||||
|
|
@ -396,7 +416,8 @@ export default {
|
|||
name: this.templateName,
|
||||
},
|
||||
};
|
||||
this.template = (await api.get("config/templates/vehicle", opts)).data.result;
|
||||
const result = await api.get("config/templates/vehicle", opts);
|
||||
this.template = result.data.result;
|
||||
this.applyDefaultsFromTemplate();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
@ -412,10 +433,17 @@ export default {
|
|||
});
|
||||
},
|
||||
async create() {
|
||||
// persist selected template product
|
||||
if (this.template) {
|
||||
const select = this.$refs.templateSelect;
|
||||
const name = select.options[select.selectedIndex].text;
|
||||
this.values.deviceProduct = name;
|
||||
}
|
||||
|
||||
if (this.testUnknown) {
|
||||
const success = await this.test(this.testVehicle);
|
||||
if (!success) return;
|
||||
await sleep(250);
|
||||
await sleep(100);
|
||||
}
|
||||
this.saving = true;
|
||||
try {
|
||||
|
|
@ -423,8 +451,7 @@ export default {
|
|||
this.$emit("vehicle-changed");
|
||||
this.closed();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert("create failed");
|
||||
this.handleCreateError(e);
|
||||
}
|
||||
this.saving = false;
|
||||
},
|
||||
|
|
@ -450,8 +477,7 @@ export default {
|
|||
this.$emit("vehicle-changed");
|
||||
this.closed();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert("update failed");
|
||||
this.handleUpdateError(e);
|
||||
}
|
||||
this.saving = false;
|
||||
},
|
||||
|
|
@ -461,8 +487,7 @@ export default {
|
|||
this.$emit("vehicle-changed");
|
||||
this.closed();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert("delete failed");
|
||||
this.handleRemoveError(e);
|
||||
}
|
||||
},
|
||||
open() {
|
||||
|
|
|
|||
|
|
@ -357,7 +357,6 @@ validate = "prüfen"
|
|||
cancel = "Abbrechen"
|
||||
delete = "Fahrzeug löschen"
|
||||
generic = "Weitere Integrationen"
|
||||
offline = "Generisches Fahrzeug"
|
||||
online = "Fahrzeuge mit Schnittstelle"
|
||||
save = "Speichern"
|
||||
scooter = "Elektroroller"
|
||||
|
|
|
|||
|
|
@ -356,7 +356,6 @@ validate = "validate"
|
|||
cancel = "Cancel"
|
||||
delete = "Delete"
|
||||
generic = "Other integrations"
|
||||
offline = "Generic vehicle"
|
||||
online = "Vehicles with online API"
|
||||
save = "Save"
|
||||
scooter = "Scooter"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
template: offline
|
||||
products:
|
||||
- description:
|
||||
generic: Offline
|
||||
en: Generic vehicle
|
||||
de: Generisches Fahrzeug
|
||||
group: generic
|
||||
params:
|
||||
- preset: vehicle-common
|
||||
|
|
|
|||
|
|
@ -104,6 +104,16 @@ test.describe("loadpoint", async () => {
|
|||
await page.getByTestId("loadpoint").getByRole("button", { name: "edit" }).click();
|
||||
await expect(lpModal).toBeVisible();
|
||||
await lpModal.getByTestId("chargerPower-22kw").click();
|
||||
|
||||
// update charger mode
|
||||
await expect(lpModal.getByRole("textbox", { name: "Charger" })).toHaveValue(
|
||||
"Demo charger [db:1]"
|
||||
);
|
||||
await lpModal.getByRole("textbox", { name: "Charger" }).click();
|
||||
await chargerModal.getByLabel("Charge status").selectOption("A");
|
||||
await chargerModal.getByRole("button", { name: "Save" }).click();
|
||||
await expect(chargerModal).not.toBeVisible();
|
||||
|
||||
await lpModal.getByRole("button", { name: "Save" }).click();
|
||||
await expect(lpModal).not.toBeVisible();
|
||||
|
||||
|
|
@ -111,6 +121,8 @@ test.describe("loadpoint", async () => {
|
|||
await restart(CONFIG_EMPTY);
|
||||
await page.reload();
|
||||
await expect(page.getByTestId("loadpoint")).toHaveCount(1);
|
||||
await expect(page.getByTestId("loadpoint")).toContainText("not connected");
|
||||
|
||||
await page.getByTestId("loadpoint").getByRole("button", { name: "edit" }).click();
|
||||
await expect(lpModal).toBeVisible();
|
||||
await expect(lpModal.getByTestId("chargerPower-22kw")).toHaveClass(/active/);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue