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 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) {
|
||||
const value = summary?.[key];
|
||||
@@ -51,6 +89,8 @@ createApp({
|
||||
devices: [],
|
||||
sliders: {},
|
||||
newGroup: { id: "", name: "", macs: [] },
|
||||
isSyncingNewGroupId: false,
|
||||
isNewGroupIdEditedManually: false,
|
||||
isLoading: false,
|
||||
isLoadingStatus: false,
|
||||
isFetching: false,
|
||||
@@ -297,6 +337,44 @@ createApp({
|
||||
}
|
||||
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) {
|
||||
const room = this.formatRoomName(device?.room);
|
||||
if (room !== "Без комнаты") {
|
||||
@@ -689,6 +767,8 @@ createApp({
|
||||
|
||||
this.toast(`Группа "${this.newGroup.name}" создана`, "success");
|
||||
this.newGroup = { id: "", name: "", macs: [] };
|
||||
this.isSyncingNewGroupId = false;
|
||||
this.isNewGroupIdEditedManually = false;
|
||||
await this.fetchData();
|
||||
},
|
||||
async deleteGroup(id) {
|
||||
|
||||
@@ -233,8 +233,8 @@
|
||||
<div class="section-kicker">Новая группа</div>
|
||||
<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">
|
||||
<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" 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" @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.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>
|
||||
</div>
|
||||
<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("/system/info", 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.assertLess(
|
||||
index_html.index('placeholder="Название (Спальня)"'),
|
||||
index_html.index('placeholder="ID (bedroom)"'),
|
||||
)
|
||||
self.assertIn("Повторяющееся расписание", index_html)
|
||||
self.assertIn("Гостевые и админ-ключи", index_html)
|
||||
self.assertIn("О сервере", index_html)
|
||||
|
||||
Reference in New Issue
Block a user