From 2015047eb15650ff0c5cfee4f536c89c9389d4f3 Mon Sep 17 00:00:00 2001 From: Stream Date: Wed, 15 Apr 2026 15:20:01 +0300 Subject: [PATCH] auto-sync: 2026-04-15 15:20:01 --- .../dashboard/availability.yaml | 231 +++--------------- 1 file changed, 36 insertions(+), 195 deletions(-) diff --git a/tasks/ha-availability-dashboard/dashboard/availability.yaml b/tasks/ha-availability-dashboard/dashboard/availability.yaml index 4fc37b9..0d2a9c2 100644 --- a/tasks/ha-availability-dashboard/dashboard/availability.yaml +++ b/tasks/ha-availability-dashboard/dashboard/availability.yaml @@ -8,34 +8,17 @@ views: entities: - entity: input_select.avail_period name: Период - card_mod: - style: | - ha-card { - padding: 8px 16px !important; - margin-bottom: 4px !important; - } + style: + card: + padding: 8px 16px # ═══ Кнопка обновить + статус ═══ - type: entities entities: - entity: sensor.avail_calc_progress - name: Статус расчёта - - type: button - name: Принудительный пересчёт - action_name: Обновить - tap_action: - action: call-service - service: input_select.select_option - target: - entity_id: input_select.avail_period - data: - option: "7d" - card_mod: - style: | - ha-card { - padding: 8px 16px !important; - margin-bottom: 4px !important; - } + name: Статус + - entity: input_select.avail_period + name: Период (для обновления) # ═══ Сводная карточка ═══ - type: custom:button-card @@ -60,8 +43,6 @@ views: const avgClr = avg < 90 ? 'var(--error-color)' : avg < 95 ? 'var(--warning-color)' : avg < 99 ? '#f0c040' : 'var(--success-color)'; let h = `
`; - - // Средний % h += `
Средняя доступность
`; h += `
`; h += `
`; @@ -70,21 +51,19 @@ views: h += `
${avg}%
`; h += `
`; - // Проблемные if (problems.length) { - h += `
🔴 Проблемных (<95%): ${problems.length}
`; - problems.forEach(p => { - const ic = {switch:'🔌',light:'💡',binary_sensor:'🔘',sensor:'🌡️',climate:'❄️',cover:'🪟',lock:'🔒',media_player:'📺',fan:'🌀',vacuum:'🤖',device_tracker:'📍',humidifier:'💧',water_heater:'🚿',siren:'🚨',button:'🔔'}[p.attrs.entity_id?.split('.')[0]] || '📟'; + h += `
🔴 Проблемных (<95%): ${problems.length}
`; + problems.slice(0, 5).forEach(p => { + const ic = {switch:'🔌',light:'💡',binary_sensor:'️',sensor:'️',climate:'️',cover:'',lock:'🔒',media_player:'🔊',device_tracker:'📍',vacuum:'🤖',fan:'🌀',humidifier:'💨',water_heater:'🚿',siren:'🚨',button:'🔘'}[p.attrs.entity_id?.split('.')[0]] || '📡'; const pc = p.pct < 90 ? 'var(--error-color)' : 'var(--warning-color)'; const ti = p.attrs.trend === 'down' ? '📉' : p.attrs.trend === 'up' ? '📈' : '➡️'; h += `
`; - h += `${ic}${p.attrs.friendly_name || p.id}`; + h += `${ic}${p.attrs.friendly_name || p.id.split('.')[1].replace(/_/g,' ')}`; h += `${p.pct}%${ti}`; h += `
`; }); } - // Тренды h += `
`; h += `📉 Хуже: ${worse}📈 Лучше: ${better}`; h += `
`; @@ -95,176 +74,38 @@ views: card: - padding: 0 - background: var(--card-background-color) - custom_fields: - card: - - padding: 0 - - margin: 0 - - # ═══ Группы по комнатам ═══ - # Каждая комната — отдельная collapsible секция с auto-entities - # Фильтр по атрибуту area. Сортировка: проблемные комнаты первые. + # ═══ Список всех устройств (автоматический) ═══ - type: custom:auto-entities card: - type: vertical-stack + type: entities + title: Устройства filter: include: + - entity_id: "sensor.avail_*" + exclude: - entity_id: "sensor.avail_area_*" - exclude: [] + - entity_id: "sensor.avail_calc_*" sort: method: state numeric: true - card_param: cards - card_template: - type: conditional - conditions: &area_cond - - entity: "[[entity.entity_id]]" - state_not: "0" # always show - row: {} # placeholder - # We can't easily do nested auto-entities per area in Lovelace. - # Instead, render each area as a section header + its devices. - - # ═══ Альтернативный подход: все устройства одной auto-entities ═══ - # Группировка по area — визуальная через JavaScript в button-card - - - type: custom:auto-entities - card: - type: vertical-stack - filter: - include: - - entity_id: "sensor.avail_area_*" - options: - type: custom:button-card - entity: "[[entity.entity_id]]" - show_name: false - show_state: false - show_icon: false - tap_action: - action: none - custom_fields: - area: | - [[[ - const pct = parseFloat(entity.state); - const a = entity.attributes; - const clr = pct < 90 ? 'var(--error-color)' : pct < 95 ? 'var(--warning-color)' : pct < 99 ? '#f0c040' : 'var(--success-color)'; - const iconMap = {'спальня':'🛏️','кухня':'🍳','ванная':'🚿','гостиная':'🛋️','прихожая':'🚪','дом':'🏠','улица':'🌳','гараж':'🚗','подвал':'🏚️','балкон':'🌅','кабинет':'💼','детская':'🧸'}; - const name = a.friendly_name || ''; - const ic = iconMap[name.toLowerCase()] || '📍'; - let h = `
`; - h += `${ic}`; - h += `${name}`; - h += `${pct}%`; - h += `${a.device_count||'?'} устр.`; - if (a.problem_count > 0) { - h += `${a.problem_count}`; - } - h += `
`; - h += `
`; - return h; - ]]] - styles: - card: - - padding: 4px 16px - - background: transparent - - box-shadow: none - custom_fields: - area: - - padding: 0 - - entity_id: "sensor.avail_*" - options: - type: custom:button-card - entity: "[[entity.entity_id]]" - show_name: false - show_state: false - show_icon: false - tap_action: - action: more-info - custom_fields: - dev: | - [[[ - const pct = parseFloat(entity.state); - const a = entity.attributes; - const clr = pct < 90 ? 'var(--error-color)' : pct < 95 ? 'var(--warning-color)' : pct < 99 ? '#f0c040' : 'var(--success-color)'; - const ic = {switch:'🔌',light:'💡',binary_sensor:'🔘',sensor:'🌡️',climate:'❄️',cover:'🪟',lock:'🔒',media_player:'📺',fan:'🌀',vacuum:'🤖',device_tracker:'📍',humidifier:'💧',water_heater:'🚿',siren:'🚨',button:'🔔'}[a.entity_id?.split('.')[0]] || '📟'; - const ti = a.trend === 'down' ? '📉' : a.trend === 'up' ? '📈' : '➡️'; - - let downInfo = ''; - if (a.down_count > 0) { - const mh = Math.floor((a.max_downtime_minutes||0)/60); - const mm = (a.max_downtime_minutes||0) % 60; - downInfo = `${a.down_count} пад., макс ${mh > 0 ? mh+'ч ':''}${mm}мин`; - } - - // Sparkline SVG - let spark = ''; - if (a.sparkline && a.sparkline.length > 1) { - const pts = a.sparkline; - const mn = Math.min(...pts), mx = Math.max(...pts), rng = mx-mn||1; - const w=120, h=20; - const svgPts = pts.map((v,i) => `${(i/(pts.length-1))*w},${h-((v-mn)/rng)*h}`).join(' '); - spark = ``; - } - - // Desktop - let o = `
`; - o += `
`; - o += `${ic}`; - o += `${a.friendly_name||a.entity_id}`; - o += `${pct}%`; - o += `${ti}`; - o += `
`; - o += `
`; - o += `
`; - o += `
`; - o += `
`; - o += `
`; - if (downInfo || spark) { - o += `
`; - o += `${downInfo}`; - o += spark; - o += `
`; - } - o += `
`; - - // Mobile - o += ``; - - return o; - ]]] - styles: - card: - - padding: 6px 16px - - background: transparent - - box-shadow: none - custom_fields: - dev: - - padding: 0 - card_mod: - style: | - ha-card { padding: 6px 16px !important; box-shadow: none !important; background: transparent !important; } - .spark-d { display: block; } - .av-desk { display: block; } - .av-mob { display: none !important; } - @media (max-width: 768px) { - .spark-d { display: none !important; } - .av-desk { display: none !important; } - .av-mob { display: block !important; } - } - exclude: - - entity_id: "sensor.avail_calc_*" - sort: - method: attribute - attribute: area - secondary_sort: - method: state - numeric: true - reverse: true - card: - type: vertical-stack - show_empty: true + reverse: true + card_mod: + style: | + ha-card { + margin-top: 8px; + } + ha-card .entity-row { + display: grid; + grid-template-columns: 40px 1fr 80px 40px; + align-items: center; + gap: 8px; + } + @media (max-width: 600px) { + ha-card .entity-row { + grid-template-columns: 30px 1fr 60px; + } + ha-card .entity-row .state { + display: none; + } + }