diff --git a/config/dashboards/infrastructure/partials/home_sections.yaml b/config/dashboards/infrastructure/partials/home_sections.yaml
index 08945b2d..45229283 100644
--- a/config/dashboards/infrastructure/partials/home_sections.yaml
+++ b/config/dashboards/infrastructure/partials/home_sections.yaml
@@ -12,10 +12,11 @@
# Notes: Network trend panels live on Network so Overview stays focused on broad house status.
# Notes: Power & Solar shows realtime flow; accumulated history remains in Energy.
# Notes: Scheduled Proxmox, Docker host, and HVAC maintenance plus sustained WAN quality/reset warnings render amber; critical conditions remain red.
-# Notes: Active Issue rows enumerate faults, scheduled maintenance, and reset urgency so drill-down is optional for initial triage.
-# Notes: BearClaw scheduled-job warnings/errors roll up under Joanna and appear as an exceptions-only Active Issue row.
+# Notes: Active Issue rows enumerate actionable faults and maintenance; optional notices stay in the blue banner.
+# Notes: Scheduled-job warnings/errors appear individually, using the same rows as Joanna and the Systems count.
# Notes: Water and Systems surface normalized Rheem faults; telemetry-only loss renders amber.
# Notes: Back to Overview is the first control in the landing-view navigation strip.
+# Notes: The landing strip omits the redundant Systems tile and keeps twelve category tiles.
# Notes: UPS health covers both Garage and Office NUT servers and drills into the shared UPS view.
# Notes: UPS alarms and telemetry loss roll up alongside utility outages; other submenu faults and Repairs gate Systems all-clear.
# Notes: Systems color follows the shared highest severity, with warnings amber and any critical issue red.
@@ -45,6 +46,7 @@
"(max-width: 900px)":
grid-template-columns: repeat(2, minmax(0, 1fr))
cards:
+ - !include /config/dashboards/infrastructure/partials/informational_notices.yaml
- type: custom:button-card
template: bearstone_nav_back
name: Back to Overview
@@ -55,18 +57,6 @@
action: navigate
navigation_path: /lovelace/home
- - type: custom:button-card
- template: bearstone_infra_status_kpi
- entity: sensor.infra_dashboard_active_issue_count
- name: Systems
- icon: mdi:shield-check-outline
- variables:
- ok_state: '0'
- healthy_text: ALL CLEAR
- warning_text: '[[[ return `${entity.state} ACTIVE`; ]]]'
- raw_suffix: ACTIVE
- severity_attribute: severity
-
- type: custom:button-card
template: bearstone_infra_status_kpi
entity: binary_sensor.home_water_problem
@@ -281,9 +271,9 @@
if (app === 'on') return 'MINI APP ERROR';
if (entity?.state === 'error') return 'JOB ERROR';
const security = states['sensor.joanna_security_updates'];
- if (security?.attributes?.severity === 'critical') return 'CRITICAL REVIEW';
+ if (security?.attributes?.severity === 'critical') return 'URGENT CODEX';
if (security?.state === 'waiting_vendor_patch') return 'VENDOR PATCHES';
- if (security?.state !== 'review') return security?.state === 'needs_attention' ? 'SECURITY REVIEW' : 'SECURITY COVERAGE';
+ if (security?.state !== 'review') return security?.state === 'needs_attention' ? 'NEEDS CODEX' : 'CODEX SCAN CHECK';
if (app !== 'off') return 'MINI APP UNKNOWN';
return ({ ok: 'HEALTHY', warning: 'JOB WARNING', error: 'JOB ERROR' })[entity?.state] || 'UNKNOWN';
]]]
@@ -319,12 +309,12 @@
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
- name: Active Issue
+ name: Active Issues
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.joanna_security_updates
- name: Security & Updates review
+ name: Security update work for Codex
icon: mdi:shield-search
variables:
alert_kind: state_not
@@ -332,10 +322,10 @@
alert_severity: '[[[ return entity?.attributes?.severity === "critical" ? "critical" : "warning"; ]]]'
exception_detail: >
[[[ return entity?.state === 'needs_attention'
- ? `${entity?.attributes?.services_need_review ?? 0} SERVICES TO REVIEW · OPEN JOANNA FOR DETAILS`
- : 'SCAN COVERAGE NEEDS REVIEW · OPEN JOANNA FOR DETAILS'; ]]]
+ ? `${entity?.attributes?.services_need_review ?? 0} SERVICES NEED CODEX | OPEN JOANNA TO COPY PROMPT`
+ : 'SCAN COVERAGE NEEDS CODEX | OPEN JOANNA TO COPY PROMPT'; ]]]
state_display: >
- [[[ return entity?.attributes?.severity === 'critical' ? 'CRITICAL REVIEW' : 'REVIEW'; ]]]
+ [[[ return entity?.attributes?.severity === 'critical' ? 'URGENT CODEX' : 'NEEDS CODEX'; ]]]
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/joanna
@@ -563,30 +553,7 @@
action: navigate
navigation_path: /dashboard-infrastructure/kiosks
- - type: custom:button-card
- template: bearstone_infra_alert_row
- entity: sensor.bearclaw_scheduled_job_health
- name: >
- [[[
- const issues = entity?.attributes?.issues;
- return Array.isArray(issues) && issues.length === 1
- ? issues[0].label : 'BearClaw scheduled jobs need attention';
- ]]]
- icon: mdi:calendar-alert
- variables:
- alert_kind: state_not
- ok_state: ok
- alert_severity: '[[[ return entity?.state === "warning" ? "warning" : "critical"; ]]]'
- exception_detail: >
- [[[
- const issues = entity?.attributes?.issues;
- const detail = Array.isArray(issues) && issues.length === 1 ? issues[0].detail : null;
- return String(detail || entity?.attributes?.summary || 'SCHEDULED JOB STATUS REQUIRES REVIEW');
- ]]]
- state_display: '[[[ return entity?.state === "warning" ? "WARNING" : "ERROR"; ]]]'
- tap_action:
- action: navigate
- navigation_path: /dashboard-infrastructure/joanna
+ - !include /config/dashboards/infrastructure/partials/scheduled_job_issues.yaml
- type: custom:button-card
template: bearstone_infra_alert_row
@@ -927,7 +894,8 @@
icon: mdi:notebook-alert-outline
variables:
alert_kind: state_not
- ok_state: ok
+ ok_states: [ok, indexing, recovering]
+ exception_detail: '[[[ return entity?.attributes?.detail || "OneNote index needs operator review"; ]]]'
alert_severity: '[[[ return entity?.state === "warning" ? "warning" : "critical"; ]]]'
tap_action:
action: navigate
diff --git a/config/dashboards/infrastructure/partials/informational_notices.yaml b/config/dashboards/infrastructure/partials/informational_notices.yaml
new file mode 100644
index 00000000..92e913f6
--- /dev/null
+++ b/config/dashboards/infrastructure/partials/informational_notices.yaml
@@ -0,0 +1,28 @@
+######################################################################
+# @CCOSTAN - Follow Me on X
+# For more info visit https://www.vcloudinfo.com/click-here
+# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
+# -------------------------------------------------------------------
+# Dashboard notices - optional appliance information
+# A quiet full-width banner, separate from actionable health warnings.
+# -------------------------------------------------------------------
+# Notes: Native Markdown supplies HA's info alert and server-side expiry
+# handling; button-card templates remain the status-card renderer.
+# Notes: Reuses live-only telemetry; no extra sensor, history, or dispatch.
+######################################################################
+
+type: markdown
+grid_options:
+ columns: full
+view_layout:
+ grid-column: 1 / -1
+text_only: true
+show_empty: false
+entity_id: sensor.bearclaw_status_telemetry
+content: >-
+ {%- set health = state_attr('sensor.bearclaw_status_telemetry', 'scheduledJobsHealth') | default({}, true) -%}
+ {%- if is_state('sensor.bearclaw_status_telemetry', 'ok') -%}
+ {%- for notice in health.get('notices', []) if notice.get('severity') == 'info' and as_timestamp(notice.get('expiresAt'), 0) > as_timestamp(now()) -%}
+ {{ notice.get('label', '') | e }}
{{ notice.get('detail', '') | e }}
+ {%- endfor -%}
+ {%- endif -%}
diff --git a/config/dashboards/infrastructure/partials/joanna_sections.yaml b/config/dashboards/infrastructure/partials/joanna_sections.yaml
index e5761e8b..d93920df 100644
--- a/config/dashboards/infrastructure/partials/joanna_sections.yaml
+++ b/config/dashboards/infrastructure/partials/joanna_sections.yaml
@@ -115,23 +115,50 @@
ok_state: review
healthy_text: CLEAR
severity_attribute: severity
- warning_text: NEEDS REVIEW
- critical_text: CRITICAL REVIEW
+ warning_text: NEEDS CODEX
+ critical_text: URGENT CODEX
state_display: >
- [[[ return entity?.attributes?.severity === 'critical' ? 'CRITICAL REVIEW' : entity?.state === 'needs_attention' ? 'NEEDS REVIEW' : entity?.state === 'review' ? 'CLEAR' : String(entity?.state ?? 'unknown').replaceAll('_', ' ').toUpperCase(); ]]]
+ [[[ return entity?.attributes?.severity === 'critical' ? 'URGENT CODEX' : entity?.state === 'needs_attention' ? 'NEEDS CODEX' : entity?.state === 'review' ? 'CLEAR' : String(entity?.state ?? 'unknown').replaceAll('_', ' ').toUpperCase(); ]]]
tap_action:
action: url
url_path: !secret bearclaw_security_admin_url
- type: custom:button-card
template: bearstone_infra_kpi
entity: sensor.joanna_security_updates
- name: Services need review
- icon: mdi:shield-alert-outline
+ name: Copy Codex prompt
+ icon: mdi:content-copy
+ state_display: >
+ [[[ return entity?.attributes?.codex_prompt ? `${entity?.attributes?.services_need_review ?? 0} SERVICES` : 'NOT NEEDED'; ]]]
tap_action:
+ action: javascript
+ javascript: >
+ [[[
+ const text = entity?.attributes?.codex_prompt;
+ const toast = (message) => this.dispatchEvent(new CustomEvent('hass-notification', {
+ bubbles: true, composed: true, detail: { message }
+ }));
+ if (['unknown', 'unavailable'].includes(entity?.state) || typeof text !== 'string' || !text.trim()) {
+ toast('No current Codex prompt is available.');
+ return;
+ }
+ const manual = () => window.prompt('Copy this prompt into Codex:', text);
+ const copied = () => toast('Copied. Paste into Codex to start the review.');
+ if (window.isSecureContext && navigator.clipboard?.writeText) {
+ navigator.clipboard.writeText(text).then(copied).catch(manual);
+ } else {
+ const area = document.createElement('textarea');
+ area.value = text;
+ document.body.appendChild(area);
+ area.focus(); area.select();
+ let ok = false;
+ try { ok = document.execCommand('copy'); } catch {}
+ area.remove();
+ if (ok) copied(); else manual();
+ }
+ ]]]
+ hold_action:
action: url
url_path: !secret bearclaw_security_admin_url
- state_display: >
- [[[ return entity?.attributes?.services_need_review ?? '—'; ]]]
- type: custom:button-card
template: bearstone_infra_kpi
entity: sensor.joanna_security_updates
@@ -300,27 +327,7 @@
return parts.join(' · ') || 'STATUS UNKNOWN';
]]]
- - type: custom:button-card
- template: bearstone_infra_alert_row
- entity: sensor.bearclaw_scheduled_job_health
- name: >
- [[[
- const issue = Array.isArray(entity?.attributes?.issues) ? entity.attributes.issues[0] : null;
- return issue?.label || 'Scheduled job needs attention';
- ]]]
- icon: mdi:alert-circle-outline
- variables:
- alert_kind: state_not
- ok_state: ok
- alert_severity: >
- [[[ return entity?.state === 'warning' ? 'warning' : 'critical'; ]]]
- exception_detail: >
- [[[
- const issue = Array.isArray(entity?.attributes?.issues) ? entity.attributes.issues[0] : null;
- return String(issue?.detail || entity?.attributes?.summary || 'SCHEDULED JOB STATUS REQUIRES REVIEW');
- ]]]
- state_display: >
- [[[ return entity?.state === 'warning' ? 'WARNING' : 'ERROR'; ]]]
+ - !include /config/dashboards/infrastructure/partials/scheduled_job_issues.yaml
- type: custom:button-card
template: bearstone_infra_status_kpi
@@ -343,12 +350,17 @@
entity: sensor.joanna_onenote_kb_health
name: OneNote account indexes
icon: mdi:notebook-multiple
+ show_label: true
+ label: '[[[ return entity?.attributes?.detail || ""; ]]]'
variables:
- ok_state: ok
- warning_state: warning
- healthy_text: CURRENT
- warning_text: SYNC STALE
- critical_text: INDEX ERROR
+ show_detail: true
+ status_severity: >
+ [[[
+ const status = String(entity?.state ?? 'unknown');
+ if (['indexing', 'recovering'].includes(status)) return 'info';
+ if (status === 'ok') return 'healthy';
+ return status === 'warning' ? 'warning' : 'critical';
+ ]]]
state_display: >
[[[
const docs = Number(entity?.attributes?.docs ?? 0).toLocaleString();
diff --git a/config/dashboards/infrastructure/partials/kiosk_sections.yaml b/config/dashboards/infrastructure/partials/kiosk_sections.yaml
index 52b5e6f5..ed655382 100644
--- a/config/dashboards/infrastructure/partials/kiosk_sections.yaml
+++ b/config/dashboards/infrastructure/partials/kiosk_sections.yaml
@@ -4,9 +4,13 @@
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Infrastructure Partial - kiosks
+# Related Issue: 2030
# Exception-first health, device telemetry, and live dashboard previews.
# -------------------------------------------------------------------
# Notes: Included by config/dashboards/infrastructure/views/05z_kiosks.yaml.
+# Notes: Living Room TV is an on-demand kiosk, outside fleet health checks.
+# Notes: Raven app status replaces the retired Samsung Haven browser preview.
+# Notes: Kitchen Show power uses its charging sensor; battery telemetry is absent.
######################################################################
# -------------------------------------------------------------------
@@ -131,63 +135,7 @@
[[[ return String(entity?.attributes?.status ?? entity?.state ?? 'unknown').replaceAll('_', ' ').toUpperCase(); ]]]
# -------------------------------------------------------------------
-# Latest device screenshots
-# -------------------------------------------------------------------
-# Native picture cards provide live device screenshots; button-card cannot render camera images.
-- type: grid
- column_span: 4
- columns: 4
- square: false
- cards:
- - camera_view: auto
- type: picture-glance
- title: Bedroom Tablet
- image: https://demo.home-assistant.io/stub_config/kitchen.png
- entities:
- - entity: binary_sensor.bedroom_kiosk_needs_attention
- image_entity: image.alarm_panel_1_screenshot
- tap_action:
- action: navigate
- navigation_path: /dashboard-bedroom/home
-
- - camera_view: auto
- type: picture-glance
- title: Kitchen Show
- image: https://demo.home-assistant.io/stub_config/kitchen.png
- entities:
- - entity: binary_sensor.kitchen_kiosk_needs_attention
- - entity: sensor.kitchen_show_last_screenshot
- camera_image: camera.kitchen_show_screenshot
- tap_action:
- action: navigate
- navigation_path: /dashboard-kiosk-satellite-staging/home
-
- - camera_view: auto
- type: picture-glance
- title: Upstairs Show
- image: https://demo.home-assistant.io/stub_config/kitchen.png
- entities:
- - entity: binary_sensor.upstairs_kiosk_needs_attention
- - entity: sensor.upstairs_show_last_screenshot
- camera_image: camera.upstairs_show_screenshot
- tap_action:
- action: navigate
- navigation_path: /dashboard-kiosk-satellite-staging/echo-show5
-
- - camera_view: auto
- type: picture-glance
- title: Office Show
- image: https://demo.home-assistant.io/stub_config/kitchen.png
- entities:
- - entity: binary_sensor.office_kiosk_needs_attention
- - entity: sensor.office_show_last_screenshot
- camera_image: camera.office_show_screenshot
- tap_action:
- action: navigate
- navigation_path: /dashboard-kiosk-satellite-staging/echo-show5
-
-# -------------------------------------------------------------------
-# Living Room Nest - actual circular framebuffer preview
+# Screens - device captures and native Roku Raven status
# -------------------------------------------------------------------
- type: grid
column_span: 4
@@ -199,13 +147,62 @@
columns: full
layout_type: custom:grid-layout
layout:
- grid-template-columns: minmax(0, 1fr) minmax(0, 2fr)
+ grid-template-columns: repeat(3, minmax(0, 1fr))
grid-gap: 12px
margin: 0
mediaquery:
- "(max-width: 900px)":
+ "(max-width: 700px)":
grid-template-columns: minmax(0, 1fr)
+ "(max-width: 1200px)":
+ grid-template-columns: repeat(2, minmax(0, 1fr))
cards:
+ - camera_view: auto
+ type: picture-glance
+ title: Bedroom Tablet
+ image: https://demo.home-assistant.io/stub_config/kitchen.png
+ entities:
+ - entity: binary_sensor.bedroom_kiosk_needs_attention
+ image_entity: image.alarm_panel_1_screenshot
+ tap_action:
+ action: navigate
+ navigation_path: /dashboard-bedroom/home
+
+ - camera_view: auto
+ type: picture-glance
+ title: Kitchen Show
+ image: https://demo.home-assistant.io/stub_config/kitchen.png
+ entities:
+ - entity: binary_sensor.kitchen_kiosk_needs_attention
+ - entity: sensor.kitchen_show_last_screenshot
+ camera_image: camera.kitchen_show_screenshot
+ tap_action:
+ action: navigate
+ navigation_path: /dashboard-kiosk-satellite-staging/home
+
+ - camera_view: auto
+ type: picture-glance
+ title: Upstairs Show
+ image: https://demo.home-assistant.io/stub_config/kitchen.png
+ entities:
+ - entity: binary_sensor.upstairs_kiosk_needs_attention
+ - entity: sensor.upstairs_show_last_screenshot
+ camera_image: camera.upstairs_show_screenshot
+ tap_action:
+ action: navigate
+ navigation_path: /dashboard-kiosk-satellite-staging/echo-show5
+
+ - camera_view: auto
+ type: picture-glance
+ title: Office Show
+ image: https://demo.home-assistant.io/stub_config/kitchen.png
+ entities:
+ - entity: binary_sensor.office_kiosk_needs_attention
+ - entity: sensor.office_show_last_screenshot
+ camera_image: camera.office_show_screenshot
+ tap_action:
+ action: navigate
+ navigation_path: /dashboard-kiosk-satellite-staging/echo-show5
+
- type: custom:button-card
template: bearstone_infra_round_display
entity: image.living_room_nest_screen
@@ -219,6 +216,368 @@
timestamp_entity: sensor.living_room_nest_last_screenshot
capture_entity: button.living_room_nest_capture_screen
+ - type: custom:vertical-stack-in-card
+ card_mod:
+ style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
+ cards:
+ - type: custom:button-card
+ template: bearstone_infra_panel_header
+ name: Raven - Living Room Roku
+ icon: mdi:television
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: sensor.living_room_ultra_active_app_id
+ name: Open Raven
+ show_entity_picture: true
+ entity_picture: /local/custom_ui/floorplan/images/branding/vCloudInfoCircle.png
+ triggers_update:
+ - media_player.samsung_q70_series_65
+ variables:
+ status_severity: >
+ [[[
+ const tv = states['media_player.samsung_q70_series_65']?.state;
+ if (tv === 'off') return 'active';
+ if (['unknown', 'unavailable', undefined].includes(entity?.state)) return 'warning';
+ return tv === 'on' && entity?.state === 'dev' ? 'healthy' : 'active';
+ ]]]
+ state_display: >
+ [[[
+ const tv = states['media_player.samsung_q70_series_65']?.state;
+ if (tv === 'off') return 'TV OFF';
+ if (['unknown', 'unavailable', undefined].includes(entity?.state)) return 'ROKU UNAVAILABLE';
+ return entity?.state === 'dev' ? 'RAVEN RUNNING' : 'TAP TO OPEN';
+ ]]]
+ tap_action:
+ action: perform-action
+ perform_action: rest_command.samsung_living_room_launch_haven
+ hold_action:
+ action: navigate
+ navigation_path: /dashboard-infrastructure/roku-remote
+ double_tap_action:
+ action: none
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.living_room_ultra_active_app
+ name: Current Roku app
+ icon: mdi:apps
+ triggers_update:
+ - sensor.living_room_ultra_active_app_id
+ state_display: >
+ [[[ return states['sensor.living_room_ultra_active_app_id']?.state === '562859' ? 'Roku Home' : entity?.state ?? 'Unknown'; ]]]
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ name: Roku Remote
+ icon: mdi:remote
+ show_state: false
+ tap_action:
+ action: navigate
+ navigation_path: /dashboard-infrastructure/roku-remote
+
+# -------------------------------------------------------------------
+# Device details - status and occasional controls
+# -------------------------------------------------------------------
+- type: grid
+ column_span: 4
+ columns: 1
+ square: false
+ cards:
+ - type: custom:layout-card
+ grid_options:
+ columns: full
+ layout_type: custom:grid-layout
+ layout:
+ grid-template-columns: repeat(3, minmax(0, 1fr))
+ grid-gap: 12px
+ margin: 0
+ mediaquery:
+ "(max-width: 700px)":
+ grid-template-columns: minmax(0, 1fr)
+ "(max-width: 1200px)":
+ grid-template-columns: repeat(2, minmax(0, 1fr))
+ cards:
+ - type: custom:vertical-stack-in-card
+ card_mod:
+ style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
+ cards:
+ - type: custom:button-card
+ template: bearstone_infra_panel_header
+ name: Bedroom Tablet
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.alarm_panel_1_battery
+ name: Battery
+ icon: mdi:battery
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: binary_sensor.alarm_panel_1_plugged_in
+ name: Power
+ icon: mdi:power-plug
+ state_display: >
+ [[[ return entity?.state === 'on' ? 'PLUGGED IN' : 'ON BATTERY'; ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.alarm_panel_1_current_page
+ name: Dashboard
+ icon: mdi:web
+ state_display: >
+ [[[
+ const page = String(entity?.state ?? '');
+ return page.includes('/local/bedroom-kiosk.html') ? 'PAGE OK' : 'CHECK PAGE';
+ ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.alarm_panel_1_foreground_app
+ name: Foreground App
+ icon: mdi:application-outline
+ state_display: >
+ [[[ return entity?.state === 'de.ozerov.fully' ? 'FULLY KIOSK' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
+
+ - type: custom:vertical-stack-in-card
+ card_mod:
+ style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
+ cards:
+ - type: custom:button-card
+ template: bearstone_infra_panel_header
+ name: Kitchen Show
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: binary_sensor.kitchen_show_charging
+ name: Power
+ icon: mdi:power-plug
+ state_display: >
+ [[[ return entity?.state === 'on' ? 'PLUGGED IN' : entity?.state === 'off' ? 'ON BATTERY' : 'UNKNOWN'; ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.kitchen_show_last_seen
+ name: Last Seen
+ icon: mdi:clock-check-outline
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.kitchen_show_current_page
+ name: Dashboard
+ icon: mdi:web
+ state_display: >
+ [[[
+ const page = String(entity?.state ?? '');
+ return page.includes('/dashboard-kiosk-satellite-staging/home') ? 'PAGE OK' : 'CHECK PAGE';
+ ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: update.kitchen_show_update
+ name: Kiosk Satellite
+ icon: mdi:update
+ state_display: >
+ [[[
+ const installed = entity?.attributes?.installed_version ?? 'unknown';
+ const latest = entity?.attributes?.latest_version ?? installed;
+ return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
+ ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.kitchen_show_remote_admin
+ name: Remote Admin
+ icon: mdi:remote-desktop
+ state_display: AVAILABLE
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.kitchen_show_voice_satellite
+ name: Voice Assistant
+ icon: mdi:microphone
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: select.kitchen_show_assistant
+ name: Assist Pipeline
+ icon: mdi:account-voice
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: select.kitchen_show_wake_word
+ name: Wake Word
+ icon: mdi:ear-hearing
+
+ - type: custom:vertical-stack-in-card
+ card_mod:
+ style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
+ cards:
+ - type: custom:button-card
+ template: bearstone_infra_panel_header
+ name: Upstairs Show
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: binary_sensor.upstairs_show_charging
+ name: Power
+ icon: mdi:power-plug
+ state_display: >
+ [[[ return states["binary_sensor.upstairs_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.upstairs_show_last_seen
+ name: Last Seen
+ icon: mdi:clock-check-outline
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.upstairs_show_current_page
+ name: Dashboard
+ icon: mdi:web
+ state_display: >
+ [[[
+ const page = String(entity?.state ?? '');
+ return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE';
+ ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.upstairs_show_foreground_app
+ name: Foreground App
+ icon: mdi:application-outline
+ state_display: >
+ [[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: light.upstairs_show_screen
+ name: Screen
+ icon: mdi:monitor
+ state_display: >
+ [[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: update.upstairs_show_update
+ name: Kiosk Satellite
+ icon: mdi:update
+ state_display: >
+ [[[
+ const installed = entity?.attributes?.installed_version ?? 'unknown';
+ const latest = entity?.attributes?.latest_version ?? installed;
+ return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
+ ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.upstairs_show_remote_admin
+ name: Remote Admin
+ icon: mdi:remote-desktop
+ state_display: AVAILABLE
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.upstairs_show_voice_satellite
+ name: Voice Assistant
+ icon: mdi:microphone
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: select.upstairs_show_assistant
+ name: Assist Pipeline
+ icon: mdi:account-voice
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: select.upstairs_show_wake_word
+ name: Wake Word
+ icon: mdi:ear-hearing
+
+ - type: custom:vertical-stack-in-card
+ card_mod:
+ style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
+ cards:
+ - type: custom:button-card
+ template: bearstone_infra_panel_header
+ name: Office Show
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: binary_sensor.office_show_charging
+ name: Power
+ icon: mdi:power-plug
+ state_display: >
+ [[[ return states["binary_sensor.office_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.office_show_last_seen
+ name: Last Seen
+ icon: mdi:clock-check-outline
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.office_show_current_page
+ name: Dashboard
+ icon: mdi:web
+ state_display: >
+ [[[
+ const page = String(entity?.state ?? '');
+ return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE';
+ ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.office_show_foreground_app
+ name: Foreground App
+ icon: mdi:application-outline
+ state_display: >
+ [[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: light.office_show_screen
+ name: Screen
+ icon: mdi:monitor
+ state_display: >
+ [[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: update.office_show_update
+ name: Kiosk Satellite
+ icon: mdi:update
+ state_display: >
+ [[[
+ const installed = entity?.attributes?.installed_version ?? 'unknown';
+ const latest = entity?.attributes?.latest_version ?? installed;
+ return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
+ ]]]
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.office_show_remote_admin
+ name: Remote Admin
+ icon: mdi:remote-desktop
+ state_display: AVAILABLE
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: sensor.office_show_voice_satellite
+ name: Voice Assistant
+ icon: mdi:microphone
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: select.office_show_assistant
+ name: Assist Pipeline
+ icon: mdi:account-voice
+
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: select.office_show_wake_word
+ name: Wake Word
+ icon: mdi:ear-hearing
+
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
@@ -289,291 +648,70 @@
target:
entity_id: button.living_room_nest_restart_display
-# -------------------------------------------------------------------
-# Device telemetry
-# -------------------------------------------------------------------
-- type: grid
- column_span: 4
- columns: 4
- square: false
- cards:
- - type: custom:vertical-stack-in-card
- card_mod:
- style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
- cards:
- - type: custom:button-card
- template: bearstone_infra_panel_header
- name: Bedroom Tablet
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.alarm_panel_1_battery
- name: Battery
- icon: mdi:battery
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: binary_sensor.alarm_panel_1_plugged_in
- name: Power
- icon: mdi:power-plug
- state_display: >
- [[[ return entity?.state === 'on' ? 'PLUGGED IN' : 'ON BATTERY'; ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.alarm_panel_1_current_page
- name: Dashboard
- icon: mdi:web
- state_display: >
- [[[
- const page = String(entity?.state ?? '');
- return page.includes('/local/bedroom-kiosk.html') ? 'PAGE OK' : 'CHECK PAGE';
- ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.alarm_panel_1_foreground_app
- name: Foreground App
- icon: mdi:application-outline
- state_display: >
- [[[ return entity?.state === 'de.ozerov.fully' ? 'FULLY KIOSK' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
-
- - type: custom:vertical-stack-in-card
- card_mod:
- style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
- cards:
- - type: custom:button-card
- template: bearstone_infra_panel_header
- name: Kitchen Show
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.kitchen_show_battery
- name: Battery
- icon: mdi:battery
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.kitchen_show_last_seen
- name: Last Seen
- icon: mdi:clock-check-outline
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.kitchen_show_current_page
- name: Dashboard
- icon: mdi:web
- state_display: >
- [[[
- const page = String(entity?.state ?? '');
- return page.includes('/dashboard-kiosk-satellite-staging/home') ? 'PAGE OK' : 'CHECK PAGE';
- ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: update.kitchen_show_update
- name: Kiosk Satellite
- icon: mdi:update
- state_display: >
- [[[
- const installed = entity?.attributes?.installed_version ?? 'unknown';
- const latest = entity?.attributes?.latest_version ?? installed;
- return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
- ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.kitchen_show_remote_admin
- name: Remote Admin
- icon: mdi:remote-desktop
- state_display: AVAILABLE
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.kitchen_show_voice_satellite
- name: Voice Assistant
- icon: mdi:microphone
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: select.kitchen_show_assistant
- name: Assist Pipeline
- icon: mdi:account-voice
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: select.kitchen_show_wake_word
- name: Wake Word
- icon: mdi:ear-hearing
-
- - type: custom:vertical-stack-in-card
- card_mod:
- style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
- cards:
- - type: custom:button-card
- template: bearstone_infra_panel_header
- name: Upstairs Show
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: binary_sensor.upstairs_show_charging
- name: Power
- icon: mdi:power-plug
- state_display: >
- [[[ return states["binary_sensor.upstairs_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.upstairs_show_last_seen
- name: Last Seen
- icon: mdi:clock-check-outline
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.upstairs_show_current_page
- name: Dashboard
- icon: mdi:web
- state_display: >
- [[[
- const page = String(entity?.state ?? '');
- return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE';
- ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.upstairs_show_foreground_app
- name: Foreground App
- icon: mdi:application-outline
- state_display: >
- [[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: light.upstairs_show_screen
- name: Screen
- icon: mdi:monitor
- state_display: >
- [[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: update.upstairs_show_update
- name: Kiosk Satellite
- icon: mdi:update
- state_display: >
- [[[
- const installed = entity?.attributes?.installed_version ?? 'unknown';
- const latest = entity?.attributes?.latest_version ?? installed;
- return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
- ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.upstairs_show_remote_admin
- name: Remote Admin
- icon: mdi:remote-desktop
- state_display: AVAILABLE
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.upstairs_show_voice_satellite
- name: Voice Assistant
- icon: mdi:microphone
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: select.upstairs_show_assistant
- name: Assist Pipeline
- icon: mdi:account-voice
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: select.upstairs_show_wake_word
- name: Wake Word
- icon: mdi:ear-hearing
-
- - type: custom:vertical-stack-in-card
- card_mod:
- style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
- cards:
- - type: custom:button-card
- template: bearstone_infra_panel_header
- name: Office Show
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: binary_sensor.office_show_charging
- name: Power
- icon: mdi:power-plug
- state_display: >
- [[[ return states["binary_sensor.office_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.office_show_last_seen
- name: Last Seen
- icon: mdi:clock-check-outline
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.office_show_current_page
- name: Dashboard
- icon: mdi:web
- state_display: >
- [[[
- const page = String(entity?.state ?? '');
- return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE';
- ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.office_show_foreground_app
- name: Foreground App
- icon: mdi:application-outline
- state_display: >
- [[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: light.office_show_screen
- name: Screen
- icon: mdi:monitor
- state_display: >
- [[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: update.office_show_update
- name: Kiosk Satellite
- icon: mdi:update
- state_display: >
- [[[
- const installed = entity?.attributes?.installed_version ?? 'unknown';
- const latest = entity?.attributes?.latest_version ?? installed;
- return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
- ]]]
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.office_show_remote_admin
- name: Remote Admin
- icon: mdi:remote-desktop
- state_display: AVAILABLE
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: sensor.office_show_voice_satellite
- name: Voice Assistant
- icon: mdi:microphone
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: select.office_show_assistant
- name: Assist Pipeline
- icon: mdi:account-voice
-
- - type: custom:button-card
- template: bearstone_infra_list_row
- entity: select.office_show_wake_word
- name: Wake Word
- icon: mdi:ear-hearing
+ - type: custom:vertical-stack-in-card
+ card_mod:
+ style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
+ cards:
+ - type: custom:button-card
+ template: bearstone_infra_panel_header
+ name: Raven - Living Room TV
+ icon: mdi:television
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: media_player.samsung_q70_series_65
+ name: TV power
+ icon: mdi:power
+ state_display: >
+ [[[ return entity?.state === 'off' ? 'OFF (NORMAL)' : String(entity?.state ?? 'unknown').toUpperCase(); ]]]
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: timer.roku_haven_manual_exit
+ name: Manual-exit pause
+ icon: mdi:pause-circle-outline
+ state_display: >
+ [[[ return entity?.state === 'active' ? 'PAUSED AFTER EXIT' : entity?.state === 'idle' ? 'READY' : 'UNKNOWN'; ]]]
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: input_boolean.haven
+ name: Automatic Raven launch
+ icon: mdi:play-circle-outline
+ state_display: >
+ [[[ return entity?.state === 'on' ? 'ENABLED' : entity?.state === 'off' ? 'PAUSED UNTIL 3 AM' : 'UNKNOWN'; ]]]
+ tap_action:
+ action: toggle
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ entity: automation.living_room_tv_haven_on_roku_screensaver
+ name: Last automatic launch
+ icon: mdi:history
+ state_display: >
+ [[[
+ const last = entity?.attributes?.last_triggered;
+ if (!last) return 'NOT YET';
+ const date = new Date(last);
+ return Number.isNaN(date.getTime()) ? 'UNKNOWN' : date.toLocaleString(undefined, {month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit'});
+ ]]]
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ name: Open Raven
+ icon: mdi:view-dashboard-outline
+ show_state: false
+ tap_action:
+ action: perform-action
+ perform_action: rest_command.samsung_living_room_launch_haven
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ name: Roku Remote
+ icon: mdi:remote
+ show_state: false
+ tap_action:
+ action: navigate
+ navigation_path: /dashboard-infrastructure/roku-remote
+ - type: custom:button-card
+ template: bearstone_infra_list_row
+ name: TV Remote
+ icon: mdi:remote-tv
+ show_state: false
+ tap_action:
+ action: navigate
+ navigation_path: /dashboard-infrastructure/tv-remote
diff --git a/config/dashboards/infrastructure/partials/scheduled_job_issues.yaml b/config/dashboards/infrastructure/partials/scheduled_job_issues.yaml
new file mode 100644
index 00000000..efebb529
--- /dev/null
+++ b/config/dashboards/infrastructure/partials/scheduled_job_issues.yaml
@@ -0,0 +1,44 @@
+######################################################################
+# @CCOSTAN - Follow Me on X
+# For more info visit https://www.vcloudinfo.com/click-here
+# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
+# -------------------------------------------------------------------
+# Scheduled Job Issues - shared exceptions for Systems and Joanna
+# Notes: Existing alert-row styling preserves each job's own severity.
+# Notes: Auto-entities is needed to render a changing number of job rows.
+######################################################################
+
+type: custom:auto-entities
+show_empty: false
+card:
+ type: grid
+ columns: 1
+ square: false
+card_param: cards
+filter:
+ template: >-
+ {% from 'infrastructure_health.jinja' import scheduled_job_issues %}
+ {% set issues = (scheduled_job_issues | as_function)() %}
+ {% set ns = namespace(cards=[]) %}
+ {% for issue in issues %}
+ {% set next_run = as_datetime(issue.get('nextRunAt'), none) %}
+ {% set detail = issue.get('detail') or 'Open BearClaw Admin > Activity > Scheduled Jobs to review this warning.' %}
+ {% if next_run is not none %}
+ {% set detail = detail ~ ' Next check: ' ~ as_local(next_run).strftime('%b %d, %I:%M %p') ~ '.' %}
+ {% endif %}
+ {% set warning = issue.get('severity') == 'warning' %}
+ {% set ns.cards = ns.cards + [{
+ 'type': 'custom:button-card', 'template': 'bearstone_infra_alert_row',
+ 'entity': 'sensor.bearclaw_scheduled_job_health',
+ 'name': issue.get('label') or 'Scheduled job needs attention',
+ 'icon': 'mdi:calendar-alert',
+ 'variables': {
+ 'alert_kind': 'state_not', 'ok_state': 'ok',
+ 'alert_severity': 'warning' if warning else 'critical',
+ 'exception_detail': detail
+ },
+ 'state_display': 'WARNING' if warning else 'ERROR',
+ 'tap_action': {'action': 'more-info'}
+ }] %}
+ {% endfor %}
+ {{ ns.cards | to_json }}
diff --git a/config/dashboards/infrastructure/templates/button_card_templates.yaml b/config/dashboards/infrastructure/templates/button_card_templates.yaml
index dd42ffd9..657fcd98 100644
--- a/config/dashboards/infrastructure/templates/button_card_templates.yaml
+++ b/config/dashboards/infrastructure/templates/button_card_templates.yaml
@@ -5,6 +5,7 @@
# -------------------------------------------------------------------
# Infrastructure Templates - custom:button-card
# Related Issue: 1560
+# Related Issue: 2029
# Shared `button_card_templates` used across Infrastructure and Overview views.
# -------------------------------------------------------------------
# Notes: Keep templates generic; view-specific tuning stays in the view partials.
@@ -749,7 +750,12 @@ bearstone_infra_container_row:
return (hasRestart && isMonitored) ? 'block' : 'none';
]]]
+# Seasonal paint stays inside the existing header; dimensions and content are unchanged.
bearstone_infra_panel_header:
+ variables:
+ seasonal: '[[[ return window.BearStoneKioskAmbient?.seasonal?.(states) || {mode: "standard", wash: "none", emblem: ""}; ]]]'
+ triggers_update:
+ - sensor.holiday_lighting_mode
show_icon: false
show_state: false
show_label: false
@@ -758,6 +764,7 @@ bearstone_infra_panel_header:
styles:
card:
- background: transparent
+ - background-image: '[[[ return variables.seasonal.wash; ]]]'
- border: none
- box-shadow: none
- padding: 6px 2px 10px 2px
@@ -770,6 +777,8 @@ bearstone_infra_panel_header:
bearstone_infra_panel_header_icon:
template: bearstone_infra_panel_header
+ show_entity_picture: '[[[ return Boolean(variables.seasonal.emblem); ]]]'
+ entity_picture: '[[[ return variables.seasonal.emblem || ""; ]]]'
show_icon: true
show_name: true
icon: mdi:view-dashboard-outline
@@ -917,7 +926,7 @@ bearstone_infra_alert_row:
else if (kind === 'binary_off') show = (stateStr === 'off');
else if (kind === 'state_not') {
const okState = String(variables?.ok_state ?? '').toLowerCase();
- show = stateStr.toLowerCase() !== okState;
+ show = !(Array.isArray(variables?.ok_states) ? variables.ok_states.map(value => String(value).toLowerCase()) : [okState]).includes(stateStr.toLowerCase());
}
else if (kind === 'state_is') {
const alertState = String(variables?.alert_state ?? '').toLowerCase();
@@ -1126,10 +1135,21 @@ bearstone_infra_status_kpi:
return 'ATTENTION';
]]]
styles:
+ grid:
+ - grid-template-areas: '[[[ return variables?.show_detail ? `"i n" "s s" "l l"` : `"i n" "s s"`; ]]]'
+ - grid-template-rows: '[[[ return variables?.show_detail ? "20px min-content min-content" : "20px min-content"; ]]]'
+ label:
+ - grid-area: l
+ - white-space: normal
+ - text-align: center
+ - font-size: 12px
+ - line-height: 16px
+ - opacity: 0.8
card:
- border-color: >
[[[
const severity = String(variables?.status_severity ?? '');
+ if (severity === 'info') return 'rgba(30,136,229,0.40)';
if (severity === 'healthy') return 'rgba(67,160,71,0.45)';
if (severity === 'critical') return 'rgba(229,57,53,0.40)';
return 'rgba(245,124,0,0.45)';
@@ -1137,6 +1157,7 @@ bearstone_infra_status_kpi:
- background: >
[[[
const severity = String(variables?.status_severity ?? '');
+ if (severity === 'info') return 'rgba(227,242,253,0.85)';
if (severity === 'healthy') return 'rgba(232,245,233,0.85)';
if (severity === 'critical') return 'rgba(255,235,238,0.85)';
return 'rgba(255,243,224,0.85)';
@@ -1145,6 +1166,7 @@ bearstone_infra_status_kpi:
- color: >
[[[
const severity = String(variables?.status_severity ?? '');
+ if (severity === 'info') return 'rgba(21,101,192,1)';
if (severity === 'healthy') return 'rgba(46,125,50,1)';
if (severity === 'critical') return 'rgba(198,40,40,1)';
return 'rgba(230,81,0,1)';
@@ -1153,6 +1175,7 @@ bearstone_infra_status_kpi:
- color: >
[[[
const severity = String(variables?.status_severity ?? '');
+ if (severity === 'info') return 'rgba(21,101,192,1)';
if (severity === 'healthy') return 'rgba(46,125,50,1)';
if (severity === 'critical') return 'rgba(198,40,40,1)';
return 'rgba(230,81,0,1)';
diff --git a/config/dashboards/infrastructure/views/05za_tv_remote.yaml b/config/dashboards/infrastructure/views/05za_tv_remote.yaml
new file mode 100644
index 00000000..8846036b
--- /dev/null
+++ b/config/dashboards/infrastructure/views/05za_tv_remote.yaml
@@ -0,0 +1,268 @@
+######################################################################
+# @CCOSTAN - Follow Me on X
+# For more info visit https://www.vcloudinfo.com/click-here
+# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
+# -------------------------------------------------------------------
+# Infrastructure Subview - Living Room TV Remote
+# Related Issue: 2030
+# Large navigation, input/settings menus, and the Raven app launcher.
+# -------------------------------------------------------------------
+# Notes: Open from Living Room TV on the Kiosks page.
+# Notes: Each tap sends one key; Back and Exit control the TV menu.
+# Notes: TV Home exposes Sources; Settings opens TV sound and Anynet+ options.
+# Notes: KEY_SOURCE did not open this TV's input menu; KEY_HOME is verified.
+# Notes: Reuses the 112px mobile controls without automatic key presses.
+# Notes: Raven launches the native Roku app through the display bridge.
+######################################################################
+
+path: tv-remote
+title: TV Remote
+icon: mdi:remote-tv
+subview: true
+type: sections
+max_columns: 4
+badges: []
+sections:
+- type: grid
+ column_span: 4
+ columns: 1
+ square: false
+ cards:
+ - type: custom:layout-card
+ grid_options:
+ columns: full
+ layout_type: custom:grid-layout
+ layout:
+ grid-template-columns: repeat(6, minmax(0, 1fr))
+ grid-auto-flow: row
+ grid-auto-rows: min-content
+ align-items: stretch
+ grid-gap: 10px
+ margin: 0
+ cards:
+ - type: custom:button-card
+ template: bearstone_mobile_header
+ name: Living Room TV
+ icon: mdi:television
+ view_layout:
+ grid-column: 1 / -1
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: media_player.living_room_ultra
+ name: Raven
+ icon: mdi:view-dashboard-outline
+ show_state: false
+ variables:
+ status_severity: healthy
+ view_layout:
+ grid-column: 1 / -1
+ tap_action:
+ action: perform-action
+ perform_action: rest_command.samsung_living_room_launch_haven
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ # Samsung menus stay accessible without a physical TV remote.
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.samsung_q70_series_65
+ name: TV Home
+ icon: mdi:home-outline
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 3
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.samsung_q70_series_65
+ data:
+ command: KEY_HOME
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.samsung_q70_series_65
+ name: Settings
+ icon: mdi:cog
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 3
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.samsung_q70_series_65
+ data:
+ command: KEY_MENU
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ # Full-width Up and Down keep every touch target large without spacers.
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.samsung_q70_series_65
+ name: Up
+ icon: mdi:arrow-up-bold
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: 1 / -1
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.samsung_q70_series_65
+ data:
+ command: KEY_UP
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.samsung_q70_series_65
+ name: Left
+ icon: mdi:arrow-left-bold
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 2
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.samsung_q70_series_65
+ data:
+ command: KEY_LEFT
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.samsung_q70_series_65
+ name: OK
+ icon: mdi:check-bold
+ show_state: false
+ variables:
+ status_severity: healthy
+ view_layout:
+ grid-column: span 2
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.samsung_q70_series_65
+ data:
+ command: KEY_ENTER
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.samsung_q70_series_65
+ name: Right
+ icon: mdi:arrow-right-bold
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 2
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.samsung_q70_series_65
+ data:
+ command: KEY_RIGHT
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.samsung_q70_series_65
+ name: Down
+ icon: mdi:arrow-down-bold
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: 1 / -1
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.samsung_q70_series_65
+ data:
+ command: KEY_DOWN
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ # These buttons navigate the TV, rather than the Home Assistant screen.
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.samsung_q70_series_65
+ name: Back
+ icon: mdi:undo
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 3
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.samsung_q70_series_65
+ data:
+ command: KEY_RETURN
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.samsung_q70_series_65
+ name: Exit
+ icon: mdi:close-circle-outline
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 3
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.samsung_q70_series_65
+ data:
+ command: KEY_EXIT
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+cards: []
diff --git a/config/dashboards/infrastructure/views/05zb_roku_remote.yaml b/config/dashboards/infrastructure/views/05zb_roku_remote.yaml
new file mode 100644
index 00000000..9ec69dde
--- /dev/null
+++ b/config/dashboards/infrastructure/views/05zb_roku_remote.yaml
@@ -0,0 +1,266 @@
+######################################################################
+# @CCOSTAN - Follow Me on X
+# For more info visit https://www.vcloudinfo.com/click-here
+# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
+# -------------------------------------------------------------------
+# Infrastructure Subview - Living Room Roku Remote
+# Related Issue: 2030
+# Native Roku navigation, playback, and the Raven app launcher.
+# -------------------------------------------------------------------
+# Notes: Open from the Raven card or device panel on Systems > Kiosks.
+# Notes: Each tap sends one Roku command; Back/Home exit Raven natively.
+# Notes: TV menus remain on the separate Samsung TV Remote subview.
+# Notes: Reuses the existing large mobile controls and launch action.
+######################################################################
+
+path: roku-remote
+title: Roku Remote
+icon: mdi:remote-tv
+subview: true
+type: sections
+max_columns: 4
+badges: []
+sections:
+- type: grid
+ column_span: 4
+ columns: 1
+ square: false
+ cards:
+ - type: custom:layout-card
+ grid_options:
+ columns: full
+ layout_type: custom:grid-layout
+ layout:
+ grid-template-columns: repeat(6, minmax(0, 1fr))
+ grid-auto-flow: row
+ grid-auto-rows: min-content
+ align-items: stretch
+ grid-gap: 10px
+ margin: 0
+ cards:
+ - type: custom:button-card
+ template: bearstone_mobile_header
+ name: Living Room Roku
+ icon: mdi:television
+ view_layout:
+ grid-column: 1 / -1
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: media_player.living_room_ultra
+ name: Raven
+ icon: mdi:view-dashboard-outline
+ show_state: false
+ variables:
+ status_severity: healthy
+ view_layout:
+ grid-column: 1 / -1
+ tap_action:
+ action: perform-action
+ perform_action: rest_command.samsung_living_room_launch_haven
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ # Roku controls never send Samsung input or menu commands.
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.living_room_ultra
+ name: Roku Home
+ icon: mdi:home-outline
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 3
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.living_room_ultra
+ data:
+ command: home
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.living_room_ultra
+ name: Options
+ icon: mdi:dots-horizontal
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 3
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.living_room_ultra
+ data:
+ command: info
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ # Full-width Up and Down keep every touch target large without spacers.
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.living_room_ultra
+ name: Up
+ icon: mdi:arrow-up-bold
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: 1 / -1
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.living_room_ultra
+ data:
+ command: up
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.living_room_ultra
+ name: Left
+ icon: mdi:arrow-left-bold
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 2
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.living_room_ultra
+ data:
+ command: left
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.living_room_ultra
+ name: OK
+ icon: mdi:check-bold
+ show_state: false
+ variables:
+ status_severity: healthy
+ view_layout:
+ grid-column: span 2
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.living_room_ultra
+ data:
+ command: select
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.living_room_ultra
+ name: Right
+ icon: mdi:arrow-right-bold
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 2
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.living_room_ultra
+ data:
+ command: right
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.living_room_ultra
+ name: Down
+ icon: mdi:arrow-down-bold
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: 1 / -1
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.living_room_ultra
+ data:
+ command: down
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ # Back exits Raven; playback controls act on the current Roku app.
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.living_room_ultra
+ name: Back
+ icon: mdi:undo
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 3
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.living_room_ultra
+ data:
+ command: back
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+
+ - type: custom:button-card
+ template: bearstone_mobile_control
+ entity: remote.living_room_ultra
+ name: Play / Pause
+ icon: mdi:play-pause
+ show_state: false
+ variables:
+ status_severity: active
+ view_layout:
+ grid-column: span 3
+ tap_action:
+ action: perform-action
+ perform_action: remote.send_command
+ target:
+ entity_id: remote.living_room_ultra
+ data:
+ command: play
+ hold_action:
+ action: none
+ double_tap_action:
+ action: none
+cards: []
diff --git a/config/dashboards/kiosk_satellite_staging/templates.yaml b/config/dashboards/kiosk_satellite_staging/templates.yaml
index 8ef59abd..7c4bc765 100644
--- a/config/dashboards/kiosk_satellite_staging/templates.yaml
+++ b/config/dashboards/kiosk_satellite_staging/templates.yaml
@@ -4,12 +4,14 @@
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Kiosk Satellite Templates - Shared alerts and compact Echo Show 5
+# Related Issue: 2029
# One household alert renderer with opt-in small-screen presentation.
# -------------------------------------------------------------------
# Notes: Kitchen keeps its existing dimensions and interactive details.
# Notes: Echo Show 5 uses compact interactive alerts and minute-updated time.
-# Notes: Shared phone notifications join the ambient rotation with sent times and ten-minute expiry.
+# Notes: Shared phone notifications retain the source feed's expiry and sent times.
# Notes: Direct touch release handles kiosk taps before button-card suppresses synthetic clicks.
+# Notes: Shared holiday accents decorate ambient panels only; alert colors retain their meaning.
######################################################################
kiosk_ambient_alerts:
@@ -25,75 +27,18 @@ kiosk_ambient_alerts:
action: none
double_tap_action:
action: none
- triggers_update:
- - binary_sensor.garage_phyn_battery_alert
- - binary_sensor.garage_phyn_freeze_warning_alert
- - binary_sensor.garage_phyn_high_pressure_alert
- - binary_sensor.garage_phyn_leak_alert
- - binary_sensor.garage_phyn_offline_leak_shutoff_alert
- - binary_sensor.garage_phyn_pinhole_leak_alert
- - binary_sensor.garage_phyn_recurring_flow_alert
- - binary_sensor.garage_phyn_temperature_alert
- - valve.phyn_shutoff_valve
- - sensor.phyn_current_water_pressure
- - sensor.phyn_average_water_flow_rate
- - sensor.nws_alerts_alerts
- - group.garage_doors
- - group.entry_points
- - group.locks
- - binary_sensor.front_door_packages_present
- - sensor.rheem_wh_available_hot_water
- - sensor.rheem_wh_alert_count
- - sensor.rheem_wh_diagnostic_status
- - binary_sensor.rheem_wh_problem
- - binary_sensor.rheem_wh_active_alert
- - binary_sensor.rheem_wh_telemetry_stale
- - binary_sensor.rheem_wh_running
- - sensor.rheem_wh_tank_health
- - sensor.rheem_wh_compressor_health
- - water_heater.rheem_wh
- - light.sink
- - input_boolean.garbage_cans_out
- - input_datetime.water_delivery_date
- - sensor.water_softener_salt_days_since_last_add
- - sensor.water_softener_salt_average_days_between_refills
- - sensor.spaceship_battery_level
- - switch.spaceship_charge
- - input_datetime.tesla_model_y_last_garage_close
- - input_datetime.tesla_model_y_plug_in_snooze_until
- - sensor.next_booked_cruise_countdown
- - sensor.bearclaw_status_telemetry
- - sensor.kiosk_recent_personal_records
- - sensor.kiosk_recent_phone_notifications
- - person.carlo
- - person.stacey
- - person.justin
- - person.paige
- - sensor.carlo_location_display
- - sensor.stacey_location_display
- - sensor.justin_location_display
- - sensor.paige_location_display
- - binary_sensor.kiosk_rain_radar_weather_event
- - binary_sensor.kiosk_rain_radar_active
- - timer.kitchen_kiosk_radar_display
- - input_text.kitchen_kiosk_detail_view
- - timer.kitchen_kiosk_detail_display
- - sensor.kiosk_active_camera_view
- - camera.frontdoorbell
+ # Model dependencies may grow; a bounded refresh keeps every Show current.
+ update_timer: 5s
+ triggers_update: update_timer
custom_fields:
ambient: >
[[[
- const state = (entityId) => states[entityId]?.state;
- const valid = (value) => !['unknown', 'unavailable', 'none', '', undefined].includes(value);
- const pretty = (value) => String(value || 'Unknown').replaceAll('_', ' ');
- const dateKey = (date) => {
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
- return `${year}-${month}-${day}`;
- };
- const items = [];
const now = new Date();
+ const model = window.BearStoneKioskAmbient;
+ if (!model) return html`
Loading household notifications...
`;
+ const feed = model.build(states, now);
+ const seasonal = model.seasonal ? model.seasonal(states) : {mode: 'standard'};
+ const {items, state, valid, pretty, dateKey, cleanAlertText, alertSection, limitAlertText, uniqueWeatherAlerts, phynAlerts, rheemProblem} = feed;
const detailValue = valid(state('input_text.kitchen_kiosk_detail_view'))
? state('input_text.kitchen_kiosk_detail_view')
: 'none';
@@ -111,434 +56,6 @@ kiosk_ambient_alerts:
});
};
- const radarWeatherEvent = states['binary_sensor.kiosk_rain_radar_weather_event'];
- const radarDisplayTimer = states['timer.kitchen_kiosk_radar_display'];
- const radarReplayReady =
- radarWeatherEvent?.state === 'on' &&
- state('binary_sensor.kiosk_rain_radar_active') !== 'on' &&
- radarDisplayTimer?.state === 'idle' &&
- new Date(radarDisplayTimer.last_changed).getTime() >
- new Date(radarWeatherEvent.last_changed).getTime();
- if (radarReplayReady) {
- items.push({
- kicker: 'WEATHER RADAR',
- title: 'Storms Approaching',
- detail: 'Tap for radar',
- icon: 'mdi:radar',
- accent: '#0288d1',
- action: {
- service: 'kitchen_kiosk_show_event',
- data: {event_type: 'radar'}
- }
- });
- }
-
- const phynAlerts = [
- 'binary_sensor.garage_phyn_battery_alert',
- 'binary_sensor.garage_phyn_freeze_warning_alert',
- 'binary_sensor.garage_phyn_high_pressure_alert',
- 'binary_sensor.garage_phyn_leak_alert',
- 'binary_sensor.garage_phyn_offline_leak_shutoff_alert',
- 'binary_sensor.garage_phyn_pinhole_leak_alert',
- 'binary_sensor.garage_phyn_recurring_flow_alert',
- 'binary_sensor.garage_phyn_temperature_alert'
- ].filter((entityId) => state(entityId) === 'on');
- const phynValveState = state('valve.phyn_shutoff_valve');
- if (phynAlerts.length > 0 || (valid(phynValveState) && phynValveState !== 'open')) {
- const alertNames = phynAlerts.map((entityId) =>
- states[entityId]?.attributes?.friendly_name || pretty(entityId.split('.')[1])
- );
- items.push({
- kicker: 'WATER SYSTEM',
- title: phynValveState === 'closed' ? 'Main water is off' : 'Phyn needs attention',
- detail: `${alertNames.join(' · ') || `Valve is ${pretty(phynValveState)}`} · Tap for water details`,
- icon: 'mdi:water-alert-outline',
- accent: '#c62828',
- action: {
- service: 'kitchen_kiosk_show_detail',
- data: {detail_type: 'phyn'}
- }
- });
- }
-
- const weatherAlertEntity = states['sensor.nws_alerts_alerts'];
- const weatherAlertCount = Number.parseInt(weatherAlertEntity?.state, 10);
- const weatherAlerts = Array.isArray(weatherAlertEntity?.attributes?.Alerts)
- ? weatherAlertEntity.attributes.Alerts
- : [];
- const cleanAlertText = (value) => String(value || '').replace(/\s+/g, ' ').trim();
- const alertSection = (description, label) => {
- const pattern = new RegExp(
- `\\*\\s*${label}\\.{3}\\s*([\\s\\S]*?)(?=\\n\\s*\\n\\s*\\*\\s*[A-Z ]+\\.{3}|$)`,
- 'i'
- );
- return cleanAlertText(String(description || '').match(pattern)?.[1]);
- };
- const limitAlertText = (value, maxLength = 240) => {
- const text = cleanAlertText(value);
- if (text.length <= maxLength) return text;
- return `${text.slice(0, maxLength - 3).replace(/\s+\S*$/, '')}...`;
- };
- const severityOrder = { Extreme: 0, Severe: 1, Moderate: 2, Minor: 3, Unknown: 4 };
- const severityColors = {
- Extreme: '#b71c1c',
- Severe: '#c62828',
- Moderate: '#d84315',
- Minor: '#f9a825',
- Unknown: '#546e7a'
- };
- const uniqueWeatherAlerts = weatherAlerts
- .filter((alert, index, alerts) => {
- const alertKey = alert.ID || alert.URL || `${alert.Event}|${alert.Expires}`;
- return alerts.findIndex((candidate) =>
- (candidate.ID || candidate.URL || `${candidate.Event}|${candidate.Expires}`) === alertKey
- ) === index;
- })
- .sort((left, right) =>
- (severityOrder[left.Severity] ?? severityOrder.Unknown)
- - (severityOrder[right.Severity] ?? severityOrder.Unknown)
- );
-
- if (uniqueWeatherAlerts.length > 0) {
- uniqueWeatherAlerts.forEach((alert, index) => {
- const severity = severityColors[alert.Severity] ? alert.Severity : 'Unknown';
- const detailParts = [
- alertSection(alert.Description, 'WHAT'),
- alertSection(alert.Description, 'WHERE'),
- alertSection(alert.Description, 'WHEN')
- ].filter(Boolean);
- const detail = detailParts.length > 0
- ? detailParts.join(' · ')
- : alert.Headline || alert.Description || 'Active National Weather Service alert';
- const position = uniqueWeatherAlerts.length > 1
- ? ` ${index + 1}/${uniqueWeatherAlerts.length}`
- : '';
- items.push({
- kicker: `WEATHER ALERT${position} · ${severity.toUpperCase()}`,
- title: alert.Event || `${weatherAlertCount} active alerts`,
- detail: limitAlertText(detail),
- icon: 'mdi:weather-lightning-rainy',
- accent: severityColors[severity],
- action: {
- service: 'kitchen_kiosk_show_detail',
- data: {
- detail_type: 'weather',
- detail_subject: String(alert.ID || index)
- }
- }
- });
- });
- } else if (Number.isFinite(weatherAlertCount) && weatherAlertCount > 0) {
- items.push({
- kicker: 'WEATHER ALERT',
- title: `${weatherAlertCount} active ${weatherAlertCount === 1 ? 'alert' : 'alerts'}`,
- detail: 'Alert details are temporarily unavailable',
- icon: 'mdi:weather-lightning-rainy',
- accent: severityColors.Unknown,
- action: {
- service: 'kitchen_kiosk_show_detail',
- data: {detail_type: 'weather', detail_subject: '0'}
- }
- });
- }
-
- if (valid(state('group.garage_doors')) && !['closed', 'off'].includes(state('group.garage_doors'))) {
- const openGarages = ['cover.large_garage_door', 'cover.small_garage_door']
- .filter((entityId) => valid(state(entityId)) && state(entityId) !== 'closed')
- .map((entityId) => states[entityId]?.attributes?.friendly_name || pretty(entityId.split('.')[1]));
- items.push({
- kicker: 'GARAGE',
- title: 'Garage door open',
- detail: openGarages.join(' · ') || 'Check both garage doors',
- icon: 'mdi:garage-open-variant',
- accent: '#ef6c00'
- });
- }
-
- if (state('group.entry_points') === 'on') {
- const openEntries = (states['group.entry_points']?.attributes?.entity_id || [])
- .filter((entityId) => state(entityId) === 'on')
- .map((entityId) => states[entityId]?.attributes?.friendly_name || pretty(entityId.split('.')[1]));
- const extraCount = Math.max(0, openEntries.length - 3);
- const entryDetail = openEntries.slice(0, 3).join(' · ') + (extraCount ? ` · +${extraCount} more` : '');
- items.push({
- kicker: 'ENTRY POINT',
- title: openEntries.length === 1 ? 'Door or window open' : `${openEntries.length} openings detected`,
- detail: entryDetail || 'Check the exterior doors and windows',
- icon: 'mdi:door-open',
- accent: '#ef6c00',
- action: {
- service: 'kitchen_kiosk_show_detail',
- data: {detail_type: 'entry_points'}
- }
- });
- }
-
- if (valid(state('group.locks')) && state('group.locks') !== 'locked') {
- items.push({
- kicker: 'SECURITY',
- title: 'Check the front door lock',
- detail: `Lock group is ${pretty(state('group.locks'))}`,
- icon: 'mdi:lock-alert-outline',
- accent: '#c62828'
- });
- }
-
- if (state('binary_sensor.front_door_packages_present') === 'on') {
- items.push({
- kicker: 'FRONT DOOR',
- title: 'Package waiting',
- detail: 'Tap for the front door view',
- icon: 'mdi:package-variant-closed',
- accent: '#8e5a2b',
- action: {
- service: 'kitchen_kiosk_show_detail',
- data: {detail_type: 'package'}
- }
- });
- }
-
- const rheemAlertCount = Number.parseInt(state('sensor.rheem_wh_alert_count'), 10);
- const rheemStatus = state('sensor.rheem_wh_diagnostic_status');
- const rheemProblem = state('binary_sensor.rheem_wh_problem') === 'on';
- if (rheemProblem) {
- const activeAlert = state('binary_sensor.rheem_wh_active_alert') === 'on';
- const telemetryStale = state('binary_sensor.rheem_wh_telemetry_stale') === 'on';
- items.push({
- kicker: telemetryStale && !activeAlert ? 'WATER HEATER TELEMETRY' : 'WATER HEATER ALERT',
- title: activeAlert && Number.isFinite(rheemAlertCount)
- ? `${rheemAlertCount} active ${rheemAlertCount === 1 ? 'alert' : 'alerts'}`
- : `Rheem status: ${pretty(rheemStatus)}`,
- detail: telemetryStale
- ? 'EcoNet data has been unavailable for 10 minutes · Tap for diagnostics'
- : 'Home Assistant Repairs has the diagnostic snapshot · Tap for details',
- icon: 'mdi:water-boiler-alert',
- accent: telemetryStale && !activeAlert ? '#ef6c00' : '#c62828',
- action: {
- service: 'kitchen_kiosk_show_detail',
- data: {detail_type: 'hot_water'}
- }
- });
- }
-
- const hotWater = Number.parseFloat(state('sensor.rheem_wh_available_hot_water'));
- if (!rheemProblem && Number.isFinite(hotWater) && hotWater < 30) {
- items.push({
- kicker: 'HOT WATER',
- title: 'Hot water is running low',
- detail: `${Math.round(hotWater)}% available · Tap for recovery details`,
- icon: 'mdi:water-thermometer-outline',
- accent: '#0277bd',
- action: {
- service: 'kitchen_kiosk_show_detail',
- data: {detail_type: 'hot_water'}
- }
- });
- }
-
- const weekday = now.toLocaleDateString('en-US', {weekday: 'short'});
- if (['Wed', 'Sun'].includes(weekday) && state('input_boolean.garbage_cans_out') !== 'on') {
- items.push({
- kicker: 'TODAY',
- title: weekday === 'Wed' ? 'Garbage + recycling day' : 'Garbage day',
- detail: 'Put the cans at the curb',
- icon: 'mdi:trash-can-outline',
- accent: '#2e7d32'
- });
- }
-
- const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
- if (state('input_datetime.water_delivery_date') === dateKey(tomorrow)) {
- items.push({
- kicker: 'WATER DELIVERY TOMORROW',
- title: 'Put the bottles out',
- detail: 'Stage them before leaving the house',
- icon: 'mdi:bottle-soda-classic-outline',
- accent: '#0277bd'
- });
- }
-
- const battery = Number.parseFloat(state('sensor.spaceship_battery_level'));
- const lastClose = new Date(String(state('input_datetime.tesla_model_y_last_garage_close')).replace(' ', 'T'));
- const snoozeUntil = new Date(String(state('input_datetime.tesla_model_y_plug_in_snooze_until')).replace(' ', 'T'));
- const recentArrival = Number.isFinite(lastClose.getTime()) && (now.getTime() - lastClose.getTime()) < 43200000;
- const snoozeExpired = !Number.isFinite(snoozeUntil.getTime()) || snoozeUntil <= now;
- if (
- Number.isFinite(battery) && battery < 50 && recentArrival && snoozeExpired &&
- state('switch.spaceship_charge') === 'off' &&
- state('person.carlo') === 'home' && state('person.stacey') === 'home'
- ) {
- items.push({
- kicker: 'MODEL Y',
- title: 'Plug in the Tesla',
- detail: `${Math.round(battery)}% battery after the latest garage arrival`,
- icon: 'mdi:ev-plug-tesla',
- accent: '#c62828'
- });
- }
-
- if (state('light.sink') === 'unavailable') {
- items.push({
- kicker: 'CHECK WALL SWITCH',
- title: 'Kitchen sink light',
- detail: 'Please turn its physical switch back on.',
- icon: 'mdi:lightbulb-alert-outline',
- accent: '#ef6c00'
- });
- }
-
- const saltDays = Number.parseFloat(state('sensor.water_softener_salt_days_since_last_add'));
- const saltAverage = Number.parseFloat(state('sensor.water_softener_salt_average_days_between_refills'));
- if (
- Number.isFinite(saltDays) && Number.isFinite(saltAverage) && saltAverage > 0 &&
- saltDays >= saltAverage * 0.9
- ) {
- items.push({
- kicker: 'WATER SOFTENER',
- title: 'Salt refill coming due',
- detail: `${Math.round(saltDays)} days since the last refill`,
- icon: 'mdi:shaker-outline',
- accent: '#00838f'
- });
- }
-
- const bowlingHighlights = states['sensor.bearclaw_status_telemetry']?.attributes?.bowlingHighlights;
- if (bowlingHighlights?.active && Array.isArray(bowlingHighlights.items)) {
- bowlingHighlights.items.forEach((highlight) => {
- const expiresAt = new Date(highlight?.expiresAt || '');
- const games = (Array.isArray(highlight?.games) ? highlight.games : [])
- .map((score) => Number.parseInt(score, 10))
- .filter(Number.isFinite);
- const highGame = Number.parseInt(highlight?.highGame, 10);
- const seriesTotal = Number.parseInt(highlight?.seriesTotal, 10);
- if (!Number.isFinite(expiresAt.getTime()) || expiresAt <= now || !games.length || !Number.isFinite(highGame)) return;
- const records = new Set(Array.isArray(highlight?.personalBestIds) ? highlight.personalBestIds : []);
- const highGameRecord = records.has('bowling_high_game');
- const highSeriesRecord = records.has('bowling_high_series') && Number.isFinite(seriesTotal);
- const title = highGameRecord
- ? `${highlight.displayName}'s new high game: ${highGame}`
- : (highSeriesRecord
- ? `${highlight.displayName}'s new high series: ${seriesTotal}`
- : `${highlight.displayName}'s high game: ${highGame}`);
- const detail = [
- games.join(' / '),
- Number.isFinite(seriesTotal) ? `${seriesTotal} series` : '',
- Number.isFinite(Number(highlight?.weekNum)) ? `Week ${highlight.weekNum}` : ''
- ].filter(Boolean).join(' · ');
- items.push({
- kicker: highGameRecord || highSeriesRecord ? 'BOWLING PERSONAL BEST' : 'TUESDAY BOWLING',
- title,
- detail,
- icon: 'mdi:bowling',
- accent: '#7e57c2'
- });
- });
- }
-
- const phoneFeed = states['sensor.kiosk_recent_phone_notifications'];
- if (Array.isArray(phoneFeed?.attributes?.items)) {
- phoneFeed.attributes.items.forEach((notification) => {
- if (Number(notification.expires_at) * 1000 <= now.getTime()) return;
- items.push({
- kicker: `PHONE NOTIFICATION · ${notification.sent_time}`,
- title: notification.title,
- detail: notification.message,
- icon: notification.icon,
- accent: notification.color
- });
- });
- }
-
- const recordFeed = states['sensor.kiosk_recent_personal_records'];
- const personalRecords = recordFeed?.state === 'ready' ? recordFeed.attributes?.items : null;
- if (Array.isArray(personalRecords)) {
- personalRecords.forEach((record) => {
- const occurredAt = new Date(record?.occurredAt || '');
- if (!Number.isFinite(occurredAt.getTime()) ||
- occurredAt > now || now - occurredAt > 7 * 24 * 60 * 60 * 1000 ||
- !record?.person || !record?.label || !record?.displayValue) return;
- items.push({
- kicker: 'NEW PERSONAL BEST',
- title: `${record.person}: ${record.label}`,
- detail: [record.displayValue, record.activityType, record.dayKey].filter(Boolean).join(' · '),
- icon: 'mdi:trophy-outline',
- accent: '#2e7d32'
- });
- });
- }
-
- const cruiseDays = Number.parseInt(state('sensor.next_booked_cruise_countdown'), 10);
- const cruiseEntity = states['sensor.next_booked_cruise_countdown'];
- const cruiseShip = String(cruiseEntity?.attributes?.ship || '').trim();
- const cruiseDate = String(cruiseEntity?.attributes?.sail_date || '').trim();
- if (Number.isInteger(cruiseDays) && cruiseDays >= 0 && cruiseShip && cruiseDate) {
- const sailDate = new Date(`${cruiseDate}T12:00:00`);
- const sailLabel = Number.isFinite(sailDate.getTime())
- ? sailDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
- : cruiseDate;
- const cruiseNights = Number.parseInt(cruiseEntity?.attributes?.nights, 10);
- const cruisePort = String(cruiseEntity?.attributes?.port || '').trim();
- const detail = [
- `Departs ${sailLabel}`,
- Number.isInteger(cruiseNights) && cruiseNights > 0
- ? `${cruiseNights} ${cruiseNights === 1 ? 'night' : 'nights'}`
- : '',
- cruisePort
- ].filter(Boolean).join(' · ');
- items.push({
- kicker: 'NEXT BOOKED CRUISE',
- title: cruiseDays === 0
- ? `${cruiseShip} sails today`
- : `${cruiseDays} ${cruiseDays === 1 ? 'day' : 'days'} until ${cruiseShip}`,
- detail,
- icon: 'mdi:ferry',
- accent: '#00838f'
- });
- }
-
- [
- ['Carlo', 'person.carlo', 'sensor.carlo_location_display'],
- ['Stacey', 'person.stacey', 'sensor.stacey_location_display'],
- ['Justin', 'person.justin', 'sensor.justin_location_display'],
- ['Paige', 'person.paige', 'sensor.paige_location_display']
- ].forEach(([name, personId, locationId]) => {
- const personState = state(personId);
- if (valid(personState) && personState !== 'home') {
- const locationEntity = states[locationId];
- const rawLocation = pretty(valid(state(locationId)) ? state(locationId) : personState);
- const withoutName = rawLocation.replace(new RegExp(`^${name}\\s+`, 'i'), '');
- const sinceMatch = withoutName.match(/\s*\(since\s+([^)]+)\)\s*$/i);
- const location = withoutName.replace(/\s*\(since\s+[^)]+\)\s*$/i, '').trim();
- const changedAt = new Date(locationEntity?.last_changed || states[personId]?.last_changed);
- const since = sinceMatch?.[1] || (
- Number.isFinite(changedAt.getTime())
- ? changedAt.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })
- : 'recently'
- );
- items.push({
- kicker: 'FAMILY LOCATION',
- title: `${name} - ${location || pretty(personState)}`,
- detail: `Since ${since}`,
- icon: 'mdi:map-marker-account-outline',
- accent: '#6a1b9a',
- action: {
- service: 'kitchen_kiosk_show_detail',
- data: {detail_type: 'family', detail_subject: personId}
- }
- });
- }
- });
-
- if (items.length === 0) {
- items.push({
- kicker: 'HOUSEHOLD',
- title: 'Everything is handled',
- detail: 'Everyone is home and there are no active reminders',
- icon: 'mdi:check-circle-outline',
- accent: '#2e7d32'
- });
- }
-
const formatSince = (entity) => {
const changed = new Date(entity?.last_changed);
if (!Number.isFinite(changed.getTime())) return 'recently';
@@ -800,6 +317,8 @@ kiosk_ambient_alerts:
line-height: 1.1;
letter-spacing: 0.1em;
}
+ .kitchen-ambient-rotator.seasonal .kitchen-ambient-kicker { padding-right: 92px; }
+ .kitchen-ambient-rotator.compact.seasonal .kitchen-ambient-kicker { padding-right: 56px; }
.kitchen-ambient-title {
margin-top: 6px;
color: var(--primary-text-color);
@@ -1074,7 +593,7 @@ kiosk_ambient_alerts:
.kitchen-detail-overlay.compact .kitchen-detail-hot-percent { font-size: 90px; }
.kitchen-detail-overlay.compact .kitchen-detail-hot-percent span { font-size: 44px; }
-
+
${items.map((item, index) => html`
- [[[ return states['timer.kitchen_kiosk_detail_display']?.state === 'active' ? 'transparent'
- : 'linear-gradient(120deg, rgba(33, 150, 243, 0.08), rgba(103, 58, 183, 0.08))'; ]]]
+ [[[
+ if (states['timer.kitchen_kiosk_detail_display']?.state === 'active') return 'transparent';
+ const seasonal = window.BearStoneKioskAmbient?.seasonal?.(states);
+ return seasonal && seasonal.wash !== 'none' ? (variables.compact ? seasonal.compactBackground : seasonal.background)
+ : 'linear-gradient(120deg, rgba(33, 150, 243, 0.08), rgba(103, 58, 183, 0.08))';
+ ]]]
+ - box-shadow: >
+ [[[ return states['timer.kitchen_kiosk_detail_display']?.state === 'active' ? 'none'
+ : window.BearStoneKioskAmbient?.seasonal?.(states)?.edge || 'none'; ]]]
grid:
- grid-template-areas: '"ambient"'
- grid-template-columns: 1fr
diff --git a/config/dashboards/overview/sections/00_summary.yaml b/config/dashboards/overview/sections/00_summary.yaml
index 52134a73..fd2ebc0d 100644
--- a/config/dashboards/overview/sections/00_summary.yaml
+++ b/config/dashboards/overview/sections/00_summary.yaml
@@ -34,6 +34,7 @@ cards:
grid-template-columns: repeat(4, minmax(0, 1fr))
grid-gap: 4px
cards:
+ - !include /config/dashboards/infrastructure/partials/informational_notices.yaml
- type: custom:button-card
template: bearstone_overview_ribbon_control
entity: sensor.infra_dashboard_active_issue_count
diff --git a/config/dashboards/overview/views/02_mobile.yaml b/config/dashboards/overview/views/02_mobile.yaml
index b5664cf7..a7d7a0f8 100644
--- a/config/dashboards/overview/views/02_mobile.yaml
+++ b/config/dashboards/overview/views/02_mobile.yaml
@@ -8,6 +8,7 @@
# -------------------------------------------------------------------
# Notes: Visible second Overview tab, after Home and before Camera.
# Notes: Lighting menu and All Lights Off share the first quick-control row.
+# Notes: The top navigation tab uses the phone icon without a label.
# Notes: Tap access and climate tiles for Home Assistant's native controls.
# Notes: Two-column 112px tiles and large text prioritize use without glasses.
# Notes: Odd final tiles span both columns to avoid small orphan controls.
@@ -20,7 +21,7 @@
path: mobile
title: Mobile
icon: mdi:cellphone
-show_icon_and_title: true
+show_icon_and_title: false
type: sections
max_columns: 4
dense_section_placement: true
@@ -47,6 +48,7 @@ sections:
grid-gap: 12px
margin: 0
cards:
+ - !include /config/dashboards/infrastructure/partials/informational_notices.yaml
- type: custom:button-card
template: bearstone_nav_back
name: Back to Home