From 132ac748eb35498f0c0cba237423ad4d75907806 Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Sun, 31 May 2026 05:14:31 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20HA=20panel=20=E2=80=94=20table=20layout,?= =?UTF-8?q?=20filter=20unavailable,=20proper=20toggles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Redesign HA list as 4-column table: icon | device name | state | control - Filter out unavailable/unknown entities; they reappear automatically on next cache refresh - Replace cluttered flex row with proper grid table — toggle no longer hidden - Remove ASK button from rows (was eating width and hiding toggle) - Scene rows get a ▶ RUN button instead of a toggle - Domain shown as icon column only (no entity_id displayed) --- public_html/index.html | 110 +++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 43 deletions(-) diff --git a/public_html/index.html b/public_html/index.html index 796a750..3d696a0 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -492,37 +492,50 @@ body::after{ .vm-metric span{color:var(--text)} /* HA DEVICES ────────────────────────────────────────────────────────── */ -.ha-entity{ - display:flex;align-items:center;gap:6px; - padding:4px 2px;border-bottom:1px solid rgba(0,212,255,0.06); - font-family:var(--font-mono);font-size:0.72rem; - transition:background 0.15s;border-radius:4px; +.ha-table{width:100%;border-collapse:collapse} +.ha-thead th{ + font-family:var(--font-display);font-size:0.5rem;letter-spacing:2px; + color:var(--text-dim);padding:4px 2px 6px;border-bottom:1px solid rgba(0,212,255,0.15); + text-align:left; } -.ha-entity:hover{background:rgba(0,212,255,0.06)} -.ha-name{color:var(--text);flex:1;cursor:pointer} -.ha-state{font-weight:600;font-size:0.65rem;min-width:32px;text-align:right} -.ha-state.on{color:var(--green)} -.ha-state.off{color:var(--text-dim)} -.ha-state.unavailable{color:var(--text-dim);opacity:0.4} +.ha-thead th:nth-child(3){text-align:center} +.ha-thead th:nth-child(4){text-align:center} +.ha-row{transition:background 0.12s} +.ha-row:hover{background:rgba(0,212,255,0.05)} +.ha-row td{ + padding:4px 2px;border-bottom:1px solid rgba(0,212,255,0.05); + font-family:var(--font-mono);font-size:0.70rem;vertical-align:middle; +} +.ha-col-domain{font-size:0.85rem;text-align:center;width:20px;padding-right:4px!important} +.ha-col-name{color:var(--text);max-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.ha-col-state{text-align:center;width:30px;font-size:0.62rem;font-weight:700;white-space:nowrap} +.ha-col-state.on{color:var(--green)} +.ha-col-state.off{color:var(--text-dim)} +.ha-col-ctrl{text-align:center;width:36px} /* toggle switch */ .ha-toggle{ - position:relative;width:32px;height:16px;flex-shrink:0;cursor:pointer; + position:relative;display:inline-block;width:30px;height:15px;cursor:pointer; } .ha-toggle input{opacity:0;width:0;height:0;position:absolute} .ha-slider{ position:absolute;inset:0;border-radius:8px; - background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.15); - transition:background 0.2s,border-color 0.2s; + background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.14); + transition:background 0.18s,border-color 0.18s; } .ha-slider::before{ content:'';position:absolute;left:2px;top:2px; - width:10px;height:10px;border-radius:50%; - background:var(--text-dim);transition:transform 0.2s,background 0.2s; + width:9px;height:9px;border-radius:50%; + background:var(--text-dim);transition:transform 0.18s,background 0.18s; } -.ha-toggle input:checked + .ha-slider{background:rgba(0,255,100,0.25);border-color:var(--green)} -.ha-toggle input:checked + .ha-slider::before{transform:translateX(16px);background:var(--green)} -.ha-toggle.scene .ha-slider{background:rgba(0,212,255,0.15);border-color:var(--cyan)} -.ha-toggle.scene .ha-slider::before{background:var(--cyan)} +.ha-toggle input:checked + .ha-slider{background:rgba(0,255,100,0.22);border-color:var(--green)} +.ha-toggle input:checked + .ha-slider::before{transform:translateX(15px);background:var(--green)} +/* scene activate button */ +.ha-scene-btn{ + background:transparent;border:1px solid var(--cyan);border-radius:3px; + color:var(--cyan);font-size:0.58rem;padding:1px 4px;cursor:pointer; + font-family:var(--font-mono);transition:background 0.15s; +} +.ha-scene-btn:hover{background:rgba(0,212,255,0.15)} /* ALERTS BADGE ─────────────────────────────────────────────────────── */ .alert-item{ @@ -1416,35 +1429,46 @@ async function loadHA() { return; } - const domainIcon = {light:'💡',switch:'🔌',scene:'🎬',media_player:'📺', - alarm_control_panel:'🔒',lawn_mower:'🌿',water_heater:'🌡',fan:'💨', - lock:'🔑',cover:'🪟',climate:'❄',input_boolean:'⚙'}; - let html = ''; + const domainIcon = { + light:'\u{1F4A1}', switch:'\u{1F50C}', scene:'\u{1F3AC}', + media_player:'\u{1F4FA}', alarm_control_panel:'\u{1F512}', + lawn_mower:'\u{1F33F}', water_heater:'\u{1F321}', fan:'\u{1F4A8}', + lock:'\u{1F511}', cover:'\u{1FA9F}', climate:'☃', input_boolean:'⚙' + }; + let rows = ''; + let totalShown = 0; for (const [domain, items] of Object.entries(entities)) { - if (!items.length) continue; const icon = domainIcon[domain] || '•'; - html += `
${icon} ${domain.toUpperCase()}
`; - html += items.map(e => { - const isOn = ['on','home','open','locked','playing','mowing','armed_home','armed_away','armed_night'].includes(e.state); + const available = items.filter(e => e.state !== 'unavailable' && e.state !== 'unknown'); + if (!available.length) continue; + available.forEach(e => { + totalShown++; + const isOn = ['on','home','open','locked','playing','mowing','armed_home','armed_away','armed_night'].includes(e.state); const isScene = domain === 'scene'; - const unavail = e.state === 'unavailable'; - const ctxKey = 'ha_' + e.entity_id.replace(/[^a-z0-9]/gi,'_'); + const ctxKey = 'ha_' + e.entity_id.replace(/[^a-z0-9]/gi,'_'); _panelCtx[ctxKey] = {type:'ha', label:e.name, entity_id:e.entity_id, name:e.name, state:e.state, domain:domain}; - const stateLabel = unavail ? 'N/A' : (isScene ? 'RUN' : e.state.toUpperCase()); - const stateClass = unavail ? 'unavailable' : (isOn ? 'on' : 'off'); - const toggleEl = isScene - ? `` - : ``; - return `
- ${e.name} - - ${stateLabel} - ${toggleEl} -
`; - }).join(''); + const stateLabel = isScene ? '—' : (isOn ? 'ON' : 'OFF'); + const stateClass = isOn ? 'on' : 'off'; + const eid = e.entity_id.replace(/'/g,"\\'"); + const ctrl = isScene + ? `` + : ``; + rows += ` + ${icon} + ${e.name} + ${stateLabel} + ${ctrl} + `; + }); } - el.innerHTML = html; + if (!totalShown) { + el.innerHTML = '
No available entities.
'; + return; + } + el.innerHTML = ` + + ${rows}
DEVICESTATECTRL
`; } async function toggleHA(entityId, domain, currentState) {