112 lines
5.2 KiB
YAML
112 lines
5.2 KiB
YAML
title: Доступность устройств
|
||
views:
|
||
- title: Доступность устройств
|
||
path: availability
|
||
cards:
|
||
# ═══ Переключатель периода ═══
|
||
- type: entities
|
||
entities:
|
||
- entity: input_select.avail_period
|
||
name: Период
|
||
style:
|
||
card:
|
||
padding: 8px 16px
|
||
|
||
# ═══ Кнопка обновить + статус ═══
|
||
- type: entities
|
||
entities:
|
||
- entity: sensor.avail_calc_progress
|
||
name: Статус
|
||
- entity: input_select.avail_period
|
||
name: Период (для обновления)
|
||
|
||
# ═══ Сводная карточка ═══
|
||
- type: custom:button-card
|
||
entity: sensor.avail_calc_progress
|
||
show_name: false
|
||
show_state: false
|
||
show_icon: false
|
||
custom_fields:
|
||
card: |
|
||
[[[
|
||
const devs = Object.entries(hass.states)
|
||
.filter(([k]) => k.startsWith('sensor.avail_') && !k.startsWith('sensor.avail_area_') && !k.startsWith('sensor.avail_calc_'))
|
||
.map(([k,v]) => ({ id: k, pct: parseFloat(v.state), attrs: v.attributes }));
|
||
|
||
if (!devs.length) return '<div style="padding:16px;color:var(--secondary-text-color);">Нет данных</div>';
|
||
|
||
const avg = (devs.reduce((s,d) => s + d.pct, 0) / devs.length).toFixed(1);
|
||
const problems = devs.filter(d => d.pct < 95).sort((a,b) => a.pct - b.pct);
|
||
const worse = devs.filter(d => d.attrs.trend === 'down').length;
|
||
const better = devs.filter(d => d.attrs.trend === 'up').length;
|
||
|
||
const avgClr = avg < 90 ? 'var(--error-color)' : avg < 95 ? 'var(--warning-color)' : avg < 99 ? '#f0c040' : 'var(--success-color)';
|
||
|
||
let h = `<div style="padding:16px;">`;
|
||
h += `<div style="font-size:13px;color:var(--secondary-text-color);margin-bottom:6px;">Средняя доступность</div>`;
|
||
h += `<div style="display:flex;align-items:center;gap:12px;margin-bottom:14px;">`;
|
||
h += `<div style="flex:1;position:relative;height:20px;background:var(--divider-color);border-radius:10px;overflow:hidden;">`;
|
||
h += `<div style="width:${avg}%;height:100%;background:${avgClr};border-radius:10px;transition:width .5s;"></div>`;
|
||
h += `</div>`;
|
||
h += `<div style="font-size:26px;font-weight:700;color:${avgClr};min-width:70px;text-align:right;">${avg}%</div>`;
|
||
h += `</div>`;
|
||
|
||
if (problems.length) {
|
||
h += `<div style="font-size:13px;font-weight:600;color:var(--error-color);margin-bottom:6px;">🔴 Проблемных (<95%): ${problems.length}</div>`;
|
||
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 += `<div style="display:flex;align-items:center;gap:6px;padding:2px 0;font-size:13px;">`;
|
||
h += `<span>${ic}</span><span style="flex:1;">${p.attrs.friendly_name || p.id.split('.')[1].replace(/_/g,' ')}</span>`;
|
||
h += `<span style="color:${pc};font-weight:600;">${p.pct}%</span><span>${ti}</span>`;
|
||
h += `</div>`;
|
||
});
|
||
}
|
||
|
||
h += `<div style="display:flex;gap:16px;margin-top:10px;font-size:12px;color:var(--secondary-text-color);">`;
|
||
h += `<span>📉 Хуже: ${worse}</span><span>📈 Лучше: ${better}</span>`;
|
||
h += `</div>`;
|
||
h += `</div>`;
|
||
return h;
|
||
]]]
|
||
styles:
|
||
card:
|
||
- padding: 0
|
||
- background: var(--card-background-color)
|
||
|
||
# ═══ Список всех устройств (автоматический) ═══
|
||
- type: custom:auto-entities
|
||
card:
|
||
type: entities
|
||
title: Устройства
|
||
filter:
|
||
include:
|
||
- entity_id: "sensor.avail_*"
|
||
exclude:
|
||
- entity_id: "sensor.avail_area_*"
|
||
- entity_id: "sensor.avail_calc_*"
|
||
sort:
|
||
method: state
|
||
numeric: 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;
|
||
}
|
||
}
|