mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
fix: HA panel — table layout, filter unavailable, proper toggles
- 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)
This commit is contained in:
+67
-43
@@ -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 += `<div style="font-family:var(--font-display);font-size:0.55rem;letter-spacing:2px;color:var(--text-dim);margin:8px 0 4px">${icon} ${domain.toUpperCase()}</div>`;
|
||||
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
|
||||
? `<button class="ha-toggle scene" onclick="toggleHA('${e.entity_id}','${domain}','${e.state}')" title="Activate scene" style="cursor:pointer;background:rgba(0,212,255,0.1);border:1px solid var(--cyan);border-radius:4px;color:var(--cyan);font-size:0.6rem;padding:1px 5px;font-family:var(--font-mono)">▶</button>`
|
||||
: `<label class="ha-toggle${unavail?' disabled':''}"><input type="checkbox"${isOn?' checked':''}${unavail?' disabled':''} onchange="toggleHA('${e.entity_id}','${domain}','${e.state}')"><span class="ha-slider"></span></label>`;
|
||||
return `<div class="ha-entity">
|
||||
<span class="ha-name" onclick="selectContext('${ctxKey}')">${e.name}</span>
|
||||
<button class="ha-ask-btn" onclick="selectContext('${ctxKey}')" data-ctx-key="${ctxKey}" title="Ask JARVIS">ASK</button>
|
||||
<span class="ha-state ${stateClass}">${stateLabel}</span>
|
||||
${toggleEl}
|
||||
</div>`;
|
||||
}).join('');
|
||||
const stateLabel = isScene ? '—' : (isOn ? 'ON' : 'OFF');
|
||||
const stateClass = isOn ? 'on' : 'off';
|
||||
const eid = e.entity_id.replace(/'/g,"\\'");
|
||||
const ctrl = isScene
|
||||
? `<button class="ha-scene-btn" onclick="toggleHA('${eid}','${domain}','${e.state}')">▶ RUN</button>`
|
||||
: `<label class="ha-toggle"><input type="checkbox"${isOn?' checked':''} onchange="toggleHA('${eid}','${domain}','${e.state}')"><span class="ha-slider"></span></label>`;
|
||||
rows += `<tr class="ha-row">
|
||||
<td class="ha-col-domain" title="${domain}">${icon}</td>
|
||||
<td class="ha-col-name" title="${e.name}">${e.name}</td>
|
||||
<td class="ha-col-state ${stateClass}">${stateLabel}</td>
|
||||
<td class="ha-col-ctrl">${ctrl}</td>
|
||||
</tr>`;
|
||||
});
|
||||
}
|
||||
el.innerHTML = html;
|
||||
if (!totalShown) {
|
||||
el.innerHTML = '<div class="text-dim" style="font-size:0.75rem;margin-top:8px">No available entities.</div>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = `<table class="ha-table"><thead class="ha-thead"><tr>
|
||||
<th></th><th>DEVICE</th><th>STATE</th><th>CTRL</th>
|
||||
</tr></thead><tbody>${rows}</tbody></table>`;
|
||||
}
|
||||
|
||||
async function toggleHA(entityId, domain, currentState) {
|
||||
|
||||
Reference in New Issue
Block a user