refactor+fix v1.10.0: аудит — гонки записи (asyncio.Lock, атомарный rev), точечный layout/update вместо layout/set (анти last-writer-wins), layout/delete, safeUrl против XSS в link/pdfs, fetchWithAuth, KEY_HASS, стриминговый лимит upload, модульность (styles/types/devices), динамические пространства в GUI-редакторе, чистка мёртвого кода (file/set, GROUP_TITLES)

This commit is contained in:
Matysh
2026-07-05 20:58:33 +03:00
parent 7cd9a56f5a
commit e7cf0416be
16 changed files with 1991 additions and 1874 deletions
+40 -23
View File
@@ -1,30 +1,9 @@
/** Редактор конфигурации карточки (GUI в Lovelace). */
import { LitElement, html, nothing } from 'lit';
const SCHEMA = [
{ name: 'title', selector: { text: {} } },
{
name: 'default_floor',
selector: {
select: {
mode: 'dropdown',
options: [
{ value: 'f1', label: '1 этаж' },
{ value: 'f2', label: '2 этаж' },
{ value: 'yard', label: 'Двор' },
],
},
},
},
{ name: 'icon_size', selector: { number: { min: 1, max: 6, step: 0.1, mode: 'box' } } },
{ name: 'show_temperature', selector: { boolean: {} } },
{ name: 'live_states', selector: { boolean: {} } },
{ name: 'show_signal', selector: { boolean: {} } },
];
const LABELS: Record<string, string> = {
title: 'Заголовок',
default_floor: 'Этаж по умолчанию',
default_floor: 'Пространство по умолчанию',
icon_size: 'Размер иконок, % ширины плана',
show_temperature: 'Показывать температуру',
live_states: 'Живые состояния (вкл/выкл, открыто…)',
@@ -34,22 +13,60 @@ const LABELS: Record<string, string> = {
class HouseplanCardEditor extends LitElement {
public hass?: any;
private _config?: any;
private _spaces: { value: string; label: string }[] | null = null;
private _spacesLoading = false;
static properties = {
hass: { attribute: false },
_config: { state: true },
_spaces: { state: true },
};
public setConfig(config: any): void {
this._config = config;
}
/** Пространства из серверного конфига интеграции — не хардкод. */
private async _loadSpaces(): Promise<void> {
if (this._spaces || this._spacesLoading || !this.hass) return;
this._spacesLoading = true;
try {
const resp = await this.hass.callWS({ type: 'houseplan/config/get' });
this._spaces = (resp?.config?.spaces || []).map((s: any) => ({
value: s.id,
label: s.title || s.id,
}));
} catch {
this._spaces = [];
} finally {
this._spacesLoading = false;
}
}
private get _schema(): any[] {
const spaces = this._spaces || [];
return [
{ name: 'title', selector: { text: {} } },
spaces.length
? {
name: 'default_floor',
selector: { select: { mode: 'dropdown', options: spaces } },
}
: { name: 'default_floor', selector: { text: {} } },
{ name: 'icon_size', selector: { number: { min: 1, max: 6, step: 0.1, mode: 'box' } } },
{ name: 'show_temperature', selector: { boolean: {} } },
{ name: 'live_states', selector: { boolean: {} } },
{ name: 'show_signal', selector: { boolean: {} } },
];
}
protected render() {
if (!this.hass || !this._config) return nothing;
this._loadSpaces();
return html`<ha-form
.hass=${this.hass}
.data=${this._config}
.schema=${SCHEMA}
.schema=${this._schema}
.computeLabel=${(s: any) => LABELS[s.name] || s.name}
@value-changed=${this._valueChanged}
></ha-form>`;