Auto-fill group IDs from names in web UI
This commit is contained in:
@@ -1,5 +1,43 @@
|
|||||||
const SESSION_KEY_NAME = "ignis_session_key";
|
const SESSION_KEY_NAME = "ignis_session_key";
|
||||||
const DEFAULT_STATS_DAYS = 7;
|
const DEFAULT_STATS_DAYS = 7;
|
||||||
|
const GROUP_ID_SANITIZE_PATTERN = /[^a-z0-9_-]+/g;
|
||||||
|
const GROUP_ID_DASH_COLLAPSE_PATTERN = /[-_]{2,}/g;
|
||||||
|
const GROUP_ID_EDGE_TRIM_PATTERN = /^[-_]+|[-_]+$/g;
|
||||||
|
const GROUP_ID_TRANSLITERATION_MAP = {
|
||||||
|
а: "a",
|
||||||
|
б: "b",
|
||||||
|
в: "v",
|
||||||
|
г: "g",
|
||||||
|
д: "d",
|
||||||
|
е: "e",
|
||||||
|
ё: "e",
|
||||||
|
ж: "zh",
|
||||||
|
з: "z",
|
||||||
|
и: "i",
|
||||||
|
й: "y",
|
||||||
|
к: "k",
|
||||||
|
л: "l",
|
||||||
|
м: "m",
|
||||||
|
н: "n",
|
||||||
|
о: "o",
|
||||||
|
п: "p",
|
||||||
|
р: "r",
|
||||||
|
с: "s",
|
||||||
|
т: "t",
|
||||||
|
у: "u",
|
||||||
|
ф: "f",
|
||||||
|
х: "h",
|
||||||
|
ц: "ts",
|
||||||
|
ч: "ch",
|
||||||
|
ш: "sh",
|
||||||
|
щ: "sch",
|
||||||
|
ъ: "",
|
||||||
|
ы: "y",
|
||||||
|
ь: "",
|
||||||
|
э: "e",
|
||||||
|
ю: "yu",
|
||||||
|
я: "ya",
|
||||||
|
};
|
||||||
|
|
||||||
function summaryInt(summary, key) {
|
function summaryInt(summary, key) {
|
||||||
const value = summary?.[key];
|
const value = summary?.[key];
|
||||||
@@ -51,6 +89,8 @@ createApp({
|
|||||||
devices: [],
|
devices: [],
|
||||||
sliders: {},
|
sliders: {},
|
||||||
newGroup: { id: "", name: "", macs: [] },
|
newGroup: { id: "", name: "", macs: [] },
|
||||||
|
isSyncingNewGroupId: false,
|
||||||
|
isNewGroupIdEditedManually: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isLoadingStatus: false,
|
isLoadingStatus: false,
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
@@ -297,6 +337,44 @@ createApp({
|
|||||||
}
|
}
|
||||||
return room;
|
return room;
|
||||||
},
|
},
|
||||||
|
slugifyGroupId(input) {
|
||||||
|
const lower = String(input || "").trim().toLowerCase();
|
||||||
|
let value = "";
|
||||||
|
for (const char of lower) {
|
||||||
|
value += GROUP_ID_TRANSLITERATION_MAP[char] ?? char;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = value
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
|
.replace(GROUP_ID_SANITIZE_PATTERN, "-")
|
||||||
|
.replace(GROUP_ID_DASH_COLLAPSE_PATTERN, "-")
|
||||||
|
.replace(GROUP_ID_EDGE_TRIM_PATTERN, "");
|
||||||
|
|
||||||
|
if (value.length > 32) {
|
||||||
|
value = value.slice(0, 32).replace(GROUP_ID_EDGE_TRIM_PATTERN, "");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
syncNewGroupIdFromName() {
|
||||||
|
if (this.isNewGroupIdEditedManually) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const slug = this.slugifyGroupId(this.newGroup.name);
|
||||||
|
this.isSyncingNewGroupId = true;
|
||||||
|
this.newGroup.id = slug;
|
||||||
|
this.isSyncingNewGroupId = false;
|
||||||
|
},
|
||||||
|
handleNewGroupNameInput() {
|
||||||
|
this.syncNewGroupIdFromName();
|
||||||
|
},
|
||||||
|
handleNewGroupIdInput() {
|
||||||
|
if (this.isSyncingNewGroupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const suggested = this.slugifyGroupId(this.newGroup.name);
|
||||||
|
const current = String(this.newGroup.id || "").trim();
|
||||||
|
this.isNewGroupIdEditedManually = current.length > 0 && current !== suggested;
|
||||||
|
},
|
||||||
deviceLocationLabel(device) {
|
deviceLocationLabel(device) {
|
||||||
const room = this.formatRoomName(device?.room);
|
const room = this.formatRoomName(device?.room);
|
||||||
if (room !== "Без комнаты") {
|
if (room !== "Без комнаты") {
|
||||||
@@ -689,6 +767,8 @@ createApp({
|
|||||||
|
|
||||||
this.toast(`Группа "${this.newGroup.name}" создана`, "success");
|
this.toast(`Группа "${this.newGroup.name}" создана`, "success");
|
||||||
this.newGroup = { id: "", name: "", macs: [] };
|
this.newGroup = { id: "", name: "", macs: [] };
|
||||||
|
this.isSyncingNewGroupId = false;
|
||||||
|
this.isNewGroupIdEditedManually = false;
|
||||||
await this.fetchData();
|
await this.fetchData();
|
||||||
},
|
},
|
||||||
async deleteGroup(id) {
|
async deleteGroup(id) {
|
||||||
|
|||||||
@@ -233,8 +233,8 @@
|
|||||||
<div class="section-kicker">Новая группа</div>
|
<div class="section-kicker">Новая группа</div>
|
||||||
<h2 class="text-xl font-black tracking-tight mb-5">Собрать комнату из найденных ламп</h2>
|
<h2 class="text-xl font-black tracking-tight mb-5">Собрать комнату из найденных ламп</h2>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-5">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 mb-5">
|
||||||
<input v-model="newGroup.id" placeholder="ID (bedroom)" class="bg-black/30 border border-slate-700/50 p-3 rounded-xl focus:border-orange-500 outline-none text-sm mono">
|
<input v-model="newGroup.name" @input="handleNewGroupNameInput" placeholder="Название (Спальня)" class="bg-black/30 border border-slate-700/50 p-3 rounded-xl focus:border-orange-500 outline-none text-sm">
|
||||||
<input v-model="newGroup.name" placeholder="Название (Спальня)" class="bg-black/30 border border-slate-700/50 p-3 rounded-xl focus:border-orange-500 outline-none text-sm">
|
<input v-model="newGroup.id" @input="handleNewGroupIdInput" placeholder="ID (bedroom)" class="bg-black/30 border border-slate-700/50 p-3 rounded-xl focus:border-orange-500 outline-none text-sm mono">
|
||||||
<button @click="createGroup" :disabled="!newGroup.id || !newGroup.name || !newGroup.macs.length" class="accent-button disabled:opacity-30 disabled:cursor-not-allowed">СОЗДАТЬ ГРУППУ</button>
|
<button @click="createGroup" :disabled="!newGroup.id || !newGroup.name || !newGroup.macs.length" class="accent-button disabled:opacity-30 disabled:cursor-not-allowed">СОЗДАТЬ ГРУППУ</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
|
|||||||
@@ -55,9 +55,15 @@ class UiSecurityTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.assertIn("sessionStorage", app_js)
|
self.assertIn("sessionStorage", app_js)
|
||||||
self.assertIn("/system/info", app_js)
|
self.assertIn("/system/info", app_js)
|
||||||
self.assertIn("serverInfo", app_js)
|
self.assertIn("serverInfo", app_js)
|
||||||
|
self.assertIn("slugifyGroupId", app_js)
|
||||||
|
self.assertIn("GROUP_ID_TRANSLITERATION_MAP", app_js)
|
||||||
self.assertIn("Комнаты, сцены и свет", index_html)
|
self.assertIn("Комнаты, сцены и свет", index_html)
|
||||||
self.assertIn("Устройства и группы", index_html)
|
self.assertIn("Устройства и группы", index_html)
|
||||||
self.assertIn("Собрать комнату из найденных ламп", index_html)
|
self.assertIn("Собрать комнату из найденных ламп", index_html)
|
||||||
|
self.assertLess(
|
||||||
|
index_html.index('placeholder="Название (Спальня)"'),
|
||||||
|
index_html.index('placeholder="ID (bedroom)"'),
|
||||||
|
)
|
||||||
self.assertIn("Повторяющееся расписание", index_html)
|
self.assertIn("Повторяющееся расписание", index_html)
|
||||||
self.assertIn("Гостевые и админ-ключи", index_html)
|
self.assertIn("Гостевые и админ-ключи", index_html)
|
||||||
self.assertIn("О сервере", index_html)
|
self.assertIn("О сервере", index_html)
|
||||||
|
|||||||
Reference in New Issue
Block a user