Enhance Home Assistant dashboard configuration by adding new layout rules for sections, including mandatory full-width containers and improved alert row templates. Consolidate layout to eliminate dead space and ensure responsive design. Remove obsolete views for MariaDB and Activity Feed. Update README to reflect new infrastructure observability package. #1561

pull/1581/head
Carlo Costanzo 1 month ago
parent d75f8c32b0
commit a6105a34d4

@ -78,6 +78,8 @@ Tier 2 (fallback, must justify in comments):
- No freeform positioning
- No layout logic embedded in `card-mod`
- For dense tile lists in `type: sections` views, keep the outer panel full-width (`column_span: 4`) and make the inner tile grid responsive using `custom:layout-card` (`4` desktop / `2` mobile unless the user asks otherwise).
- In `type: sections` views, the first section immediately below top chips/badges must be a full-width container (`column_span: 4`) with a single wrapper card that also sets `grid_options.columns: full` to prevent skinny-column whitespace.
- Consolidate upward after removals/moves: do not leave dead space, empty placeholders, or intentionally sparse rows unless the user explicitly asks for whitespace.
Note: If the repo/view uses Home Assistant `type: sections`, treat `sections` as the top-level structure and enforce the same container rules inside each section (sections should contain only `grid`/`vertical-stack` cards and their children).
@ -138,14 +140,50 @@ Card types limited to: custom:button-card, card-mod, custom:flex-horseshoe-card,
Max layout nesting depth: 2. No horizontal-stack inside grid cells.
```
## Optional Stitch Handoff (Light Bridge)
Use this only when the user explicitly asks for visual ideation, layout exploration, or variants.
- Trigger Stitch path:
- User asks for redesign inspiration, variants, or multiple visual directions.
- Skip Stitch path:
- User asks for direct dashboard refactor/update and no ideation is needed.
- Preconditions:
- Stitch is used for hierarchy/spacing/grouping ideation only.
- Stitch output must never be copied as implementation code.
- Final implementation must still obey all allowed card/layout rules in this skill.
Handoff artifact (summary only, no code export):
```yaml
stitch_intent:
layout_pattern: "<grid and grouping pattern to translate>"
section_hierarchy: "<header/panel/section ordering>"
density_target: "<compact|balanced|spacious>"
cards_to_translate:
- "<visual card concept mapped to allowed Lovelace card types>"
```
Translation requirement:
- Convert `stitch_intent` into approved Lovelace structures only (`grid`, `vertical-stack`, Tier 1 cards first).
- Re-map any unsupported Stitch concepts to compliant cards/containers and explain fallback choices inline when Tier 2 is required.
Fallback behavior when Stitch is unavailable:
- Continue with manual ideation using this skill's design system and rules.
- State degraded mode clearly ("Stitch unavailable; proceeding with manual hierarchy/density planning").
- Do not block dashboard work on Stitch availability.
## Workflow (Do This In Order)
1. Read the target dashboard/view/partials/templates to understand existing patterns and avoid drift.
2. Determine intent from the user's request and existing dashboard context: `infra` (NOC), `home`, `energy`, or `environment`. Keep one intent per view.
3. Validate entities and services before editing:
- Prefer the Home Assistant MCP for live entity/service validation (required when available).
- Record the MCP validation step in the work notes before writing YAML.
- If MCP is not available, ask the user to confirm entity IDs and services (do not guess).
4. Draft layout with constraints: a top-level `grid` and optional `vertical-stack` groups.
- If using Stitch, first summarize `stitch_intent` and treat it as advisory input to this step.
- After removals, reflow cards/sections upward to collapse gaps and reduce empty rows.
5. Implement using Tier 1 cards first; reuse existing templates; avoid one-off styles.
6. If fallback cards are necessary, add an inline comment explaining why Tier 1 cannot satisfy the requirement.
7. Validate:
@ -155,6 +193,9 @@ Max layout nesting depth: 2. No horizontal-stack inside grid cells.
- If requirements can't be met: state the violated rule and propose a compliant alternative.
- If validation fails: stop, surface the error output, and propose corrected YAML. Do not leave invalid config applied.
Example flow:
- User asks for redesign inspiration -> Stitch ideation with required constraints -> summarize `stitch_intent` -> translate to Lovelace-safe YAML -> run validation checks.
## References
- Read `references/dashboard_rules.md` when you need the full constraint set and repo-specific mapping notes.

@ -25,9 +25,12 @@ Tier 2 (fallback, only when Tier 1 cannot satisfy the requirement; justify inlin
- No freeform positioning
- No layout logic embedded in `card-mod`
- For dense status/container lists in `type: sections` views, keep the panel full-width (`column_span: 4`) and use a responsive inner grid (`4` desktop / `2` mobile by default).
- In `type: sections` views, always place a full-width wrapper section directly below top chips/badges and set the wrapper card to `grid_options.columns: full`.
- Consolidate layout vertically after any move/remove; dead space is disallowed unless explicitly requested.
Sections-mode note:
- If a view uses `type: sections`, treat `sections` as the top-level structure and enforce the same container rules inside each section.
- Prefer reducing section count and regrouping cards into earlier rows rather than leaving sparse trailing space.
## Template Architecture (Required)
@ -111,6 +114,37 @@ Card types limited to: custom:button-card, card-mod, custom:flex-horseshoe-card,
Max layout nesting depth: 2. No horizontal-stack inside grid cells.
```
## Optional Stitch Handoff (Light Bridge)
Decision rule:
- Use Stitch only when the user requests visual ideation, variants, or exploration.
- For normal refactor/update requests, skip Stitch and implement directly with this skill's rules.
Handoff artifact (advisory summary, not implementation code):
```yaml
stitch_intent:
layout_pattern: "<grid and grouping pattern to translate>"
section_hierarchy: "<header/panel/section ordering>"
density_target: "<compact|balanced|spacious>"
cards_to_translate:
- "<visual card concept mapped to allowed Lovelace card types>"
```
Required translation back to Lovelace:
- Treat `stitch_intent` as input to layout planning only.
- Implement using approved containers/cards and template architecture from this document.
- For any unsupported concept, re-map to compliant cards and justify Tier 2 fallback inline.
Degraded mode:
- If Stitch or Stitch skills are unavailable, continue with manual hierarchy/spacing ideation.
- Do not block dashboard work on Stitch availability.
Anti-drift checklist:
- No HTML/CSS export artifacts from Stitch output.
- No nesting-depth violations (max 2).
- No card-type drift outside approved Tier 1/Tier 2 lists.
## Repo-Specific Dashboard YAML Rules (config/dashboards/**)
If working in this repo's `config/dashboards/` tree:

@ -4,216 +4,626 @@
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Infrastructure Partial - Home sections
# Rebuilt homepage layout (Stitch-inspired).
# Desktop-first infra overview: full-width hero, exceptions-first alerts.
# -------------------------------------------------------------------
# Notes: Standardized on `custom:button-card` + `custom:mini-graph-card` with `card_mod` polish.
# Notes: Default/light theme only; no dark-mode specific styling.
# Notes: Always keep a full-width container directly below chips.
######################################################################
- !include /config/dashboards/infrastructure/partials/infra_top_chips_section.yaml
- !include /config/dashboards/infrastructure/partials/infra_top_chips_home_section.yaml
# -------------------------------------------------------------------
# Home hero (mandatory full-width container under chips)
# -------------------------------------------------------------------
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:button-card
template: bearstone_infra_kpi
entity: sensor.total_wifi_clients
name: Wi-Fi Clients
icon: mdi:wifi
- type: grid
column_span: 3
columns: 3
square: false
cards:
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
- type: custom:layout-card
grid_options:
columns: full
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(2, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 900px)":
grid-template-columns: repeat(1, minmax(0, 1fr))
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Proxmox01
- type: custom:mini-graph-card
name: CPU / Memory
icon: mdi:server
hours_to_show: 24
points_per_hour: 2
line_width: 2
animate: true
show:
fill: true
legend: false
icon: true
name: true
state: true
entities:
- entity: sensor.node_proxmox1_cpu_used
name: CPU
- entity: sensor.node_proxmox1_memory_used_percentage
name: Memory
- type: grid
columns: 3
square: false
- type: custom:vertical-stack-in-card
grid_options:
columns: full
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_device_tile
entity: sensor.qemu_docker69_169_status
name: Docker69
icon: mdi:docker
label: >
[[[ return "Last boot: " + (states['sensor.qemu_docker69_169_last_boot']?.state ?? 'unknown'); ]]]
template: bearstone_infra_panel_header
name: Alerts
- type: custom:button-card
template: bearstone_infra_list_row
name: Network
icon: mdi:access-point-network
show_state: false
tap_action:
action: none
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.unifi_ap_office_clients
name: Office AP has 0 clients
icon: mdi:wifi-alert
variables:
button_entity: button.qemu_docker69_169_reboot
name: Docker69
- type: custom:button-card
template: bearstone_infra_device_tile
entity: sensor.qemu_carlo_hass_105_status
name: HASS
icon: mdi:home-assistant
label: >
[[[ return "Last boot: " + (states['sensor.qemu_carlo_hass_105_last_boot']?.state ?? 'unknown'); ]]]
alert_kind: ap_zero
min_age_s: 300
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.unifi_ap_study_clients
name: Study AP has 0 clients
icon: mdi:wifi-alert
variables:
button_entity: button.qemu_carlo_hass_105_reboot
name: HASS
- type: custom:button-card
template: bearstone_infra_device_tile
entity: sensor.qemu_wireguard_104_status
name: WireGuard
icon: mdi:vpn
label: >
[[[ return "Last boot: " + (states['sensor.qemu_wireguard_104_last_boot']?.state ?? 'unknown'); ]]]
alert_kind: ap_zero
min_age_s: 300
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.unifi_ap_garage_clients
name: Garage AP has 0 clients
icon: mdi:wifi-alert
variables:
button_entity: button.qemu_wireguard_104_reboot
name: WireGuard
alert_kind: ap_zero
min_age_s: 300
- 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: Proxmox02
- type: custom:mini-graph-card
name: CPU / Memory
icon: mdi:server
hours_to_show: 24
points_per_hour: 2
line_width: 2
animate: true
show:
fill: true
legend: false
icon: true
name: true
state: true
entities:
- entity: sensor.node_proxmox02_cpu_used
name: CPU
- entity: sensor.node_proxmox02_memory_used_percentage
name: Memory
- type: grid
columns: 1
square: false
cards:
- type: custom:button-card
template: bearstone_infra_device_tile
entity: sensor.qemu_docker2_101_status
name: Frigate
icon: mdi:video
label: >
[[[ return "Last boot: " + (states['sensor.qemu_docker2_101_last_boot']?.state ?? 'unknown'); ]]]
template: bearstone_infra_alert_row
entity: sensor.speedtest_download
name: Internet speed is slow
icon: mdi:speedometer-slow
variables:
button_entity: button.qemu_docker2_101_reboot
name: Frigate
alert_kind: speedtest_slow
threshold: 300
state_display: >
[[[
const dl = parseFloat(states['sensor.speedtest_download']?.state);
const ul = parseFloat(states['sensor.speedtest_upload']?.state);
const dlText = Number.isFinite(dl) ? (dl.toFixed(0) + ' Mbps') : 'n/a';
const ulText = Number.isFinite(ul) ? (ul.toFixed(0) + ' Mbps') : 'n/a';
return `DL ${dlText} | UL ${ulText}`;
]]]
- 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: Pi-hole
- type: custom:pi-hole
device_id: d69637da16f7d7f3626070582be59808
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.speedtest_ping
name: WAN latency high
icon: mdi:wan
variables:
alert_kind: disk_high
threshold: 80
state_display: >
[[[ return `${entity.state} ms`; ]]]
- type: custom:button-card
template: bearstone_infra_alert_row
entity: switch.pi_hole
name: DNS Pi-hole disabled
icon: mdi:dns-outline
variables:
alert_kind: binary_off
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/pihole
state_display: >
[[[ return `Switch ${entity.state}`; ]]]
- type: custom:button-card
template: bearstone_infra_alert_row
entity: binary_sensor.pihole_status
name: DNS Pi-hole service down
icon: mdi:dns
variables:
alert_kind: binary_off
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/pihole
- type: custom:button-card
template: bearstone_infra_alert_row
entity: binary_sensor.vcloudinfo_com
name: vcloudinfo.com is down
icon: mdi:web-cancel
variables:
alert_kind: binary_off
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/website-health
- type: custom:button-card
template: bearstone_infra_alert_row
entity: binary_sensor.www_kingcrafthomes_com
name: kingcrafthomes.com is down
icon: mdi:web-cancel
variables:
alert_kind: binary_off
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/website-health
- type: custom:button-card
template: bearstone_infra_list_row
name: Domain expiration critical
icon: mdi:domain-off
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/website-health
state_display: >
[[[
const ids = [
'sensor.vcloudinfo_com_days_until_expiration',
'sensor.kingcrafthomes_com_days_until_expiration'
];
let min = null;
ids.forEach((id) => {
const raw = states[id]?.state;
const n = Number(raw);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min === null) ? 'No data' : `${Math.round(min)} days remaining`;
]]]
styles:
card:
- display: >
[[[
const ids = [
'sensor.vcloudinfo_com_days_until_expiration',
'sensor.kingcrafthomes_com_days_until_expiration'
];
let min = null;
ids.forEach((id) => {
const n = Number(states[id]?.state);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min !== null && min < 14) ? 'block' : 'none';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: Domain expiration warning
icon: mdi:domain
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/website-health
state_display: >
[[[
const ids = [
'sensor.vcloudinfo_com_days_until_expiration',
'sensor.kingcrafthomes_com_days_until_expiration'
];
let min = null;
ids.forEach((id) => {
const raw = states[id]?.state;
const n = Number(raw);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min === null) ? 'No data' : `${Math.round(min)} days remaining`;
]]]
styles:
card:
- display: >
[[[
const ids = [
'sensor.vcloudinfo_com_days_until_expiration',
'sensor.kingcrafthomes_com_days_until_expiration'
];
let min = null;
ids.forEach((id) => {
const n = Number(states[id]?.state);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min !== null && min >= 14 && min < 30) ? 'block' : 'none';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: Certificate expiration critical
icon: mdi:certificate-outline
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/website-health
state_display: >
[[[
const keys = Object.keys(states).filter((k) =>
k.startsWith('sensor.') &&
/(vcloudinfo|kingcrafthomes)/.test(k) &&
/(cert|ssl|tls)/.test(k)
);
let min = null;
keys.forEach((k) => {
const n = Number(states[k]?.state);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min === null) ? 'Not available' : `${Math.round(min)} days remaining`;
]]]
styles:
card:
- display: >
[[[
const keys = Object.keys(states).filter((k) =>
k.startsWith('sensor.') &&
/(vcloudinfo|kingcrafthomes)/.test(k) &&
/(cert|ssl|tls)/.test(k)
);
let min = null;
keys.forEach((k) => {
const n = Number(states[k]?.state);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min !== null && min < 14) ? 'block' : 'none';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: Certificate expiration warning
icon: mdi:certificate
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/website-health
state_display: >
[[[
const keys = Object.keys(states).filter((k) =>
k.startsWith('sensor.') &&
/(vcloudinfo|kingcrafthomes)/.test(k) &&
/(cert|ssl|tls)/.test(k)
);
let min = null;
keys.forEach((k) => {
const n = Number(states[k]?.state);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min === null) ? 'Not available' : `${Math.round(min)} days remaining`;
]]]
styles:
card:
- display: >
[[[
const keys = Object.keys(states).filter((k) =>
k.startsWith('sensor.') &&
/(vcloudinfo|kingcrafthomes)/.test(k) &&
/(cert|ssl|tls)/.test(k)
);
let min = null;
keys.forEach((k) => {
const n = Number(states[k]?.state);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min !== null && min >= 14 && min < 30) ? 'block' : 'none';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: System
icon: mdi:alert-circle-outline
show_state: false
tap_action:
action: none
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.disk_use_percent
name: Home Assistant disk usage high
icon: mdi:harddisk
variables:
alert_kind: disk_high
threshold: 80
state_display: >
[[[ return `${entity.state}% used`; ]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: Backup stale or failed
icon: mdi:backup-restore
state_display: >
[[[
const s = states['sensor.dockerconfigs_backup_status']?.state ?? 'unknown';
const e = states['sensor.dockerconfigs_backup_error_message']?.state ?? '';
const d = states['sensor.dockerconfigs_backup_date']?.state;
let ageText = 'n/a';
if (d && !['unknown','unavailable','none',''].includes(String(d).toLowerCase())) {
const dt = new Date(d);
if (!Number.isNaN(dt.getTime())) {
const h = (Date.now() - dt.getTime()) / 3600000;
ageText = `${h.toFixed(1)}h`;
}
}
return `Age ${ageText} | ${s}${e ? ' | error' : ''}`;
]]]
styles:
card:
- display: >
[[[
const status = String(states['sensor.dockerconfigs_backup_status']?.state ?? '').toLowerCase();
const err = String(states['sensor.dockerconfigs_backup_error_message']?.state ?? '').toLowerCase();
const d = states['sensor.dockerconfigs_backup_date']?.state;
let stale = false;
if (d && !['unknown','unavailable','none',''].includes(String(d).toLowerCase())) {
const dt = new Date(d);
if (!Number.isNaN(dt.getTime())) stale = ((Date.now() - dt.getTime()) / 3600000) > 24;
}
const failed = status.includes('fail') || status.includes('error') || err.length > 0;
return (stale || failed) ? 'block' : 'none';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: Services
icon: mdi:docker
show_state: false
tap_action:
action: none
- type: custom:button-card
template: bearstone_infra_alert_row
entity: binary_sensor.docker_10_apt_reboot_required
name: docker_10 needs reboot
icon: mdi:restart-alert
variables:
alert_kind: binary_on
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/docker
state_display: >
[[[ return 'REBOOT REQUIRED'; ]]]
- type: custom:button-card
template: bearstone_infra_alert_row
entity: binary_sensor.docker_14_apt_reboot_required
name: docker_14 needs reboot
icon: mdi:restart-alert
variables:
alert_kind: binary_on
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/docker
state_display: >
[[[ return 'REBOOT REQUIRED'; ]]]
- type: custom:button-card
template: bearstone_infra_alert_row
entity: binary_sensor.docker_69_apt_reboot_required
name: docker_69 needs reboot
icon: mdi:restart-alert
variables:
alert_kind: binary_on
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/docker
state_display: >
[[[ return 'REBOOT REQUIRED'; ]]]
- type: custom:auto-entities
show_empty: false
card:
type: custom:layout-card
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(2, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 900px)":
grid-template-columns: repeat(1, minmax(0, 1fr))
card_param: cards
filter:
include: !include /config/dashboards/infrastructure/partials/docker_container_rows_include.yaml
exclude:
- state: 'on'
- type: custom:vertical-stack-in-card
grid_options:
columns: full
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Datacenter
- type: custom:mini-graph-card
name: Garage Temp
icon: mdi:thermometer
hours_to_show: 96
points_per_hour: 1
line_width: 2
smoothing: true
show:
legend: false
labels: false
name: true
icon: true
state: true
color_thresholds:
- value: 50
color: '#f39c12'
- value: 120
color: '#d35400'
- value: 145
color: '#c0392b'
entities:
- entity: sensor.proxmox_garage_average_temperature
name: Garage
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_card.yaml
- type: custom:layout-card
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(2, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 900px)":
grid-template-columns: repeat(1, minmax(0, 1fr))
cards:
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.garage_ups_status
name: UPS Status
icon: mdi:battery-heart-variant
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.garage_ups_battery_charge
name: UPS Battery
icon: mdi:battery-70
state_display: >
[[[
const v = Number(entity?.state);
return Number.isFinite(v) ? `${v.toFixed(0)}%` : (entity?.state ?? 'unknown');
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.garage_ups_battery_runtime
name: Runtime Remaining
icon: mdi:timer-outline
state_display: >
[[[
const secs = parseInt(entity?.state, 10);
if (!Number.isFinite(secs)) return entity?.state ?? 'unknown';
const hours = Math.floor(secs / 3600);
const mins = Math.floor((secs % 3600) / 60);
return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.garage_ups_status
name: On Battery
icon: mdi:battery-alert-variant-outline
state_display: >
[[[
const s = String(entity?.state || '').toUpperCase();
return s.includes('OB') ? 'Yes' : 'No';
]]]
# -------------------------------------------------------------------
# Overviews (desktop: 2 columns, mobile: stack)
# -------------------------------------------------------------------
- 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(2, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 900px)":
grid-template-columns: repeat(1, 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: Wi-Fi Overview
- type: custom:mini-graph-card
name: Clients
icon: mdi:wifi
hours_to_show: 24
line_width: 2
points_per_hour: 1
smoothing: true
show:
graph: line
legend: false
labels: false
name: true
icon: true
state: true
entities:
- entity: sensor.total_wifi_clients
name: Total
show_state: true
show_graph: false
show_line: false
show_points: false
show_fill: false
show_legend: false
- entity: sensor.unifi_ap_office_clients
name: Office AP
show_state: true
- entity: sensor.unifi_ap_study_clients
name: Study AP
show_state: true
- entity: sensor.unifi_ap_garage_clients
name: Garage AP
show_state: true
- 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: Internet Trend
- type: custom:mini-graph-card
name: Speedtest
icon: mdi:speedometer
hours_to_show: 24
points_per_hour: 1
line_width: 2
smoothing: true
show:
fill: false
legend: false
labels: false
name: true
icon: true
state: true
entities:
- entity: sensor.speedtest_download
name: Download
- entity: sensor.speedtest_upload
name: Upload
# -------------------------------------------------------------------
# Activity highlights (compact)
# -------------------------------------------------------------------
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:vertical-stack-in-card
grid_options:
columns: full
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Wi-Fi Overview
- type: custom:mini-graph-card
name: Clients
icon: mdi:wifi
name: Activity Highlights
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/activity
- type: logbook
target:
entity_id:
- sensor.activity_feed
hours_to_show: 24
line_width: 2
points_per_hour: 1
smoothing: true
show:
graph: line
legend: false
labels: false
name: true
icon: true
state: true
entities:
- entity: sensor.total_wifi_clients
name: Total
show_state: true
show_graph: false
show_line: false
show_points: false
show_fill: false
show_legend: false
- entity: sensor.unifi_ap_office_clients
name: Office AP
show_state: true
- entity: sensor.unifi_ap_study_clients
name: Study AP
show_state: true
- entity: sensor.unifi_ap_garage_clients
name: Garage AP
show_state: true
- type: grid
columns: 3
square: false
cards:
- type: custom:button-card
template: bearstone_infra_device_tile
name: Garage AP
icon: mdi:access-point
entity: sensor.unifi_ap_garage_clients
label: >
[[[ return "Uptime: " + (states['sensor.unifi_ap_garage_uptime']?.state ?? 'unknown'); ]]]
variables:
button_entity: button.unifi_ap_garage_restart
name: Garage AP
- type: custom:button-card
template: bearstone_infra_device_tile
name: Office AP
icon: mdi:access-point
entity: sensor.unifi_ap_office_clients
label: >
[[[ return "Uptime: " + (states['sensor.unifi_ap_office_uptime']?.state ?? 'unknown'); ]]]
variables:
button_entity: button.unifi_ap_office_restart
name: Office AP
- type: custom:button-card
template: bearstone_infra_device_tile
name: Study AP
icon: mdi:access-point
entity: sensor.unifi_ap_study_clients
label: >
[[[ return "Uptime: " + (states['sensor.unifi_ap_study_uptime']?.state ?? 'unknown'); ]]]
variables:
button_entity: button.unifi_ap_study_restart
name: Study AP

@ -0,0 +1,35 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Infrastructure Partial - Top status chips (Home)
# Home-only chips row (no Pi-hole control; DNS health is alert-only on Home).
# -------------------------------------------------------------------
# Notes: Keep this lightweight; it should render fast on the Home view.
######################################################################
type: grid
column_span: 4
columns: 3
square: false
cards:
- type: custom:button-card
template: bearstone_infra_chip
entity: binary_sensor.node_proxmox1_updates_packages
name: Proxmox01
icon: mdi:server
state_display: >
[[[ return entity.state === 'on' ? 'Updates pending' : 'Up to date'; ]]]
- type: custom:button-card
template: bearstone_infra_chip
entity: binary_sensor.node_proxmox02_updates_packages
name: Proxmox02
icon: mdi:server
state_display: >
[[[ return entity.state === 'on' ? 'Updates pending' : 'Up to date'; ]]]
- type: custom:button-card
template: bearstone_infra_chip
entity: sensor.garage_ups_status
name: Garage UPS
icon: mdi:transmission-tower

@ -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
# -------------------------------------------------------------------
# Infrastructure Partial - Pi-hole sections
# Dedicated full-width Pi-hole panel.
# -------------------------------------------------------------------
# Notes: Uses `custom:pi-hole` with existing device_id.
######################################################################
- !include /config/dashboards/infrastructure/partials/infra_top_chips_section.yaml
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:vertical-stack-in-card
grid_options:
columns: full
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:pi-hole
device_id: d69637da16f7d7f3626070582be59808
grid_options:
columns: full

@ -39,7 +39,7 @@
- type: grid
column_span: 4
columns: 2
columns: 3
square: false
cards:
- type: custom:vertical-stack-in-card
@ -93,40 +93,6 @@
name: WireGuard CPU
- entity: sensor.qemu_wireguard_104_memory_used_percentage
name: WireGuard MEM
- type: grid
columns: 3
square: false
cards:
- type: custom:button-card
template: bearstone_infra_device_tile
entity: sensor.qemu_docker69_169_status
name: Docker69
icon: mdi:docker
label: >
[[[ return "Last boot: " + (states['sensor.qemu_docker69_169_last_boot']?.state ?? 'unknown'); ]]]
variables:
button_entity: button.qemu_docker69_169_reboot
name: Docker69
- type: custom:button-card
template: bearstone_infra_device_tile
entity: sensor.qemu_carlo_hass_105_status
name: HASS
icon: mdi:home-assistant
label: >
[[[ return "Last boot: " + (states['sensor.qemu_carlo_hass_105_last_boot']?.state ?? 'unknown'); ]]]
variables:
button_entity: button.qemu_carlo_hass_105_reboot
name: HASS
- type: custom:button-card
template: bearstone_infra_device_tile
entity: sensor.qemu_wireguard_104_status
name: WireGuard
icon: mdi:vpn
label: >
[[[ return "Last boot: " + (states['sensor.qemu_wireguard_104_last_boot']?.state ?? 'unknown'); ]]]
variables:
button_entity: button.qemu_wireguard_104_reboot
name: WireGuard
- type: custom:vertical-stack-in-card
card_mod:
@ -171,40 +137,28 @@
name: CPU
- entity: sensor.qemu_docker2_101_memory_used_percentage
name: MEM
- type: grid
columns: 3
square: false
cards:
- type: custom:button-card
template: bearstone_infra_device_tile
entity: sensor.qemu_docker2_101_status
name: Frigate
icon: mdi:video
label: >
[[[ return "Last boot: " + (states['sensor.qemu_docker2_101_last_boot']?.state ?? 'unknown'); ]]]
variables:
button_entity: button.qemu_docker2_101_reboot
name: Frigate
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:mini-graph-card
entities:
- entity: sensor.proxmox_garage_average_temperature
name: Garage
- entity: sensor.pirateweather_temperature
name: Outside
name: Garage Temp
hours_to_show: 96
color_thresholds:
- value: 50
color: '#f39c12'
- value: 120
color: '#d35400'
- value: 145
color: '#c0392b'
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_card.yaml
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Garage Temp
- type: custom:mini-graph-card
entities:
- entity: sensor.proxmox_garage_average_temperature
name: Garage
- entity: sensor.pirateweather_temperature
name: Outside
name: Garage Temp
hours_to_show: 96
color_thresholds:
- value: 50
color: '#f39c12'
- value: 120
color: '#d35400'
- value: 145
color: '#c0392b'
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_card.yaml

@ -0,0 +1,236 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Infrastructure Partial - Website health sections
# Website uptime + domain expiry + certificate telemetry detail view.
# -------------------------------------------------------------------
# Notes: Keep a full-width container directly below chips to avoid whitespace.
######################################################################
- !include /config/dashboards/infrastructure/partials/infra_top_chips_section.yaml
# -------------------------------------------------------------------
# Website hero (mandatory full-width container under chips)
# -------------------------------------------------------------------
- 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(2, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 900px)":
grid-template-columns: repeat(1, minmax(0, 1fr))
cards:
- type: custom:vertical-stack-in-card
grid_options:
columns: full
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Website Uptime
- type: custom:layout-card
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(2, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 900px)":
grid-template-columns: repeat(1, minmax(0, 1fr))
cards:
- type: custom:button-card
template: bearstone_infra_list_row_running
entity: binary_sensor.vcloudinfo_com
name: vcloudinfo.com
icon: mdi:web
state_display: >
[[[ return entity.state === 'on' ? 'UP' : (entity.state || 'unknown').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row_running
entity: binary_sensor.www_kingcrafthomes_com
name: kingcrafthomes.com
icon: mdi:web
state_display: >
[[[ return entity.state === 'on' ? 'UP' : (entity.state || 'unknown').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row_running
entity: binary_sensor.bear_stone
name: Bear Stone
icon: mdi:web-check
state_display: >
[[[ return entity.state === 'on' ? 'UP' : (entity.state || 'unknown').toUpperCase(); ]]]
- type: custom:vertical-stack-in-card
grid_options:
columns: full
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Domain Expiry
- type: custom:layout-card
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(2, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 900px)":
grid-template-columns: repeat(1, minmax(0, 1fr))
cards:
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.vcloudinfo_com_days_until_expiration
name: vcloudinfo.com
icon: mdi:calendar-clock
state_display: >
[[[
const days = states['sensor.vcloudinfo_com_days_until_expiration']?.state ?? 'n/a';
const expRaw = states['sensor.vcloudinfo_com_expires']?.state;
let exp = 'n/a';
const s = String(expRaw ?? '');
if (s && !['unknown','unavailable','none',''].includes(s.toLowerCase())) {
if (/^\d{4}-\d{2}-\d{2}/.test(s)) {
exp = s.slice(0, 10);
} else {
const dt = new Date(s);
if (!Number.isNaN(dt.getTime())) {
const y = dt.getFullYear();
const m = String(dt.getMonth() + 1).padStart(2, '0');
const d = String(dt.getDate()).padStart(2, '0');
exp = `${y}-${m}-${d}`;
}
}
}
return `${days}d | ${exp}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.kingcrafthomes_com_days_until_expiration
name: kingcrafthomes.com
icon: mdi:calendar-clock
state_display: >
[[[
const days = states['sensor.kingcrafthomes_com_days_until_expiration']?.state ?? 'n/a';
const expRaw = states['sensor.kingcrafthomes_com_expires']?.state;
let exp = 'n/a';
const s = String(expRaw ?? '');
if (s && !['unknown','unavailable','none',''].includes(s.toLowerCase())) {
if (/^\d{4}-\d{2}-\d{2}/.test(s)) {
exp = s.slice(0, 10);
} else {
const dt = new Date(s);
if (!Number.isNaN(dt.getTime())) {
const y = dt.getFullYear();
const m = String(dt.getMonth() + 1).padStart(2, '0');
const d = String(dt.getDate()).padStart(2, '0');
exp = `${y}-${m}-${d}`;
}
}
}
return `${days}d | ${exp}`;
]]]
# -------------------------------------------------------------------
# Certificate health
# -------------------------------------------------------------------
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:vertical-stack-in-card
grid_options:
columns: full
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Certificate Health
- type: custom:button-card
template: bearstone_infra_list_row
name: Cert telemetry sensors
icon: mdi:certificate-outline
state_display: >
[[[
const keys = Object.keys(states).filter((k) =>
k.startsWith('sensor.') &&
/(vcloudinfo|kingcrafthomes)/.test(k) &&
/(cert|ssl|tls)/.test(k)
);
return keys.length === 0 ? 'Missing (domain expiry only)' : `${keys.length} sensor(s)`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: Minimum cert days remaining
icon: mdi:calendar-alert
state_display: >
[[[
const keys = Object.keys(states).filter((k) =>
k.startsWith('sensor.') &&
/(vcloudinfo|kingcrafthomes)/.test(k) &&
/(cert|ssl|tls)/.test(k)
);
let min = null;
keys.forEach((k) => {
const n = Number(states[k]?.state);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min === null) ? 'Not available' : `${Math.round(min)} days`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: Cert warning (< 30d)
icon: mdi:alert-outline
state_display: >
[[[
const keys = Object.keys(states).filter((k) =>
k.startsWith('sensor.') &&
/(vcloudinfo|kingcrafthomes)/.test(k) &&
/(cert|ssl|tls)/.test(k)
);
let min = null;
keys.forEach((k) => {
const n = Number(states[k]?.state);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min !== null && min < 30 && min >= 14) ? 'ALERT' : 'OK';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: Cert critical (< 14d)
icon: mdi:alert-circle
state_display: >
[[[
const keys = Object.keys(states).filter((k) =>
k.startsWith('sensor.') &&
/(vcloudinfo|kingcrafthomes)/.test(k) &&
/(cert|ssl|tls)/.test(k)
);
let min = null;
keys.forEach((k) => {
const n = Number(states[k]?.state);
if (Number.isFinite(n)) min = (min === null) ? n : Math.min(min, n);
});
return (min !== null && min < 14) ? 'ALERT' : 'OK';
]]]

@ -381,6 +381,46 @@ bearstone_infra_list_row:
- padding-left: 8px
- white-space: nowrap
bearstone_infra_alert_row:
template: bearstone_infra_list_row
# Home view only. Uses JS visibility checks to keep Home "exceptions-only".
styles:
card:
- border-color: rgba(229,57,53,0.25)
- background: rgba(255,235,238,0.65)
- display: >
[[[
const kind = (variables && variables.alert_kind) ? String(variables.alert_kind) : '';
const minAge = (variables && variables.min_age_s !== undefined) ? Number(variables.min_age_s) : 0;
const thr = (variables && variables.threshold !== undefined) ? Number(variables.threshold) : null;
const stateStr = String(entity && entity.state !== undefined ? entity.state : '');
const lastChanged = entity && entity.last_changed ? new Date(entity.last_changed) : null;
// If we can't determine age, do not surface time-gated alerts.
const ageS = lastChanged ? ((Date.now() - lastChanged.getTime()) / 1000) : 0;
const num = (entId) => {
const v = parseFloat(states[entId] ? states[entId].state : '');
return Number.isFinite(v) ? v : null;
};
let show = false;
if (kind === 'binary_on') show = (stateStr === 'on');
else if (kind === 'binary_off') show = (stateStr === 'off');
else if (kind === 'ap_zero') show = (stateStr === '0' && ageS >= minAge);
else if (kind === 'disk_high') {
const v = parseFloat(stateStr);
show = Number.isFinite(v) && (thr !== null ? (v > thr) : (v > 80));
} else if (kind === 'speedtest_slow') {
const dl = num('sensor.speedtest_download');
const ul = num('sensor.speedtest_upload');
const t = (thr !== null) ? thr : 300;
show = (dl !== null && dl < t) || (ul !== null && ul < t);
}
return show ? 'block' : 'none';
]]]
bearstone_infra_list_row_running:
template: bearstone_infra_list_row
state:

@ -0,0 +1,19 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Infrastructure View - Pi-hole
# Dedicated Pi-hole view after Docker.
# -------------------------------------------------------------------
# Notes: Full-width sections layout for Pi-hole telemetry/control card.
######################################################################
title: Pi-hole
path: pihole
type: sections
icon: mdi:pi-hole
theme: default
badges: []
sections: !include /config/dashboards/infrastructure/partials/pihole_sections.yaml
max_columns: 4

@ -0,0 +1,20 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Infrastructure View - Website Health
# Website uptime + domain expiry + certificate telemetry details.
# -------------------------------------------------------------------
# Notes: Home view surfaces only exceptions; this view carries detail.
######################################################################
title: Website Health
path: website-health
type: sections
icon: mdi:web-check
theme: default
badges: []
sections: !include /config/dashboards/infrastructure/partials/website_health_sections.yaml
max_columns: 4
cards: []

@ -46,6 +46,7 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
| [logbook_activity_feed.yaml](logbook_activity_feed.yaml) | Dummy `sensor.activity_feed` + helper to write clean Activity entries (Issue #1550). | `sensor.activity_feed`, `script.send_to_logbook` |
| [mariadb_monitoring.yaml](mariadb_monitoring.yaml) | MariaDB health sensors and Lovelace dashboard snippet for recorder stats. | `sensor.mariadb_status`, `sensor.database_size` |
| [docker_infrastructure.yaml](docker_infrastructure.yaml) | Docker host patching + staggered auto-reboot flow + container-down Repairs alerts. | `sensor.docker_*_apt_status`, `repairs.create`, `repairs.remove` |
| [infrastructure_observability.yaml](infrastructure_observability.yaml) | Normalized WAN/DNS/backup/domain/cert health sensors used by the Infrastructure Home + Website Health dashboards. | `binary_sensor.infra_*`, `sensor.infra_*`, `script.send_to_logbook` |
| [mariadb.yaml](mariadb.yaml) | MariaDB recorder health and capacity SQL sensors. | `sensor.mariadb_status`, `sensor.database_size` |
| [tugtainer_updates.yaml](tugtainer_updates.yaml) | Tugtainer container update notifications via webhook + persistent alerts. | `persistent_notification.create`, `input_datetime.tugtainer_last_update` |
| [phynplus.yaml](phynplus.yaml) | Phyn shutoff automations with push + Activity feed + Repairs issues for leak events. | `valve.phyn_shutoff_valve`, `binary_sensor.phyn_leak_test_running`, `repairs.create` |

@ -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 Observability - Normalized infra monitoring signals
# WAN/DNS/backup/website/domain/cert state normalized for dashboards.
# -------------------------------------------------------------------
# Notes: Home dashboard consumes `infra_*` entities for exceptions-only alerts.
# Notes: Domain warning threshold is <30 days; critical threshold is <14 days.
######################################################################
command_line:
- sensor:
name: Infra WAN Packet Loss
unique_id: infra_wan_packet_loss
command: >-
ping -q -c 10 -W 1 1.1.1.1 2>/dev/null |
awk -F',' '/packet loss/ {gsub(/%| /, "", $3); print $3; found=1}
END {if (!found) print "unknown"}'
scan_interval: 300
unit_of_measurement: "%"
- sensor:
name: Infra WAN Latency Ms
unique_id: infra_wan_latency_ms
command: >-
ping -q -c 10 -W 1 1.1.1.1 2>/dev/null |
awk -F'/' '/^rtt|^round-trip/ {print $5; found=1}
END {if (!found) print "unknown"}'
scan_interval: 300
unit_of_measurement: "ms"
- sensor:
name: Infra External IP Fallback
unique_id: infra_external_ip_fallback
command: "curl -fsS https://api.ipify.org || echo unknown"
scan_interval: 900
template:
- sensor:
- name: "Infra External IP"
unique_id: infra_external_ip
state: >-
{% set primary = states('sensor.external_ip') | trim %}
{% set fallback = states('sensor.infra_external_ip_fallback') | trim %}
{% if primary not in ['unknown', 'unavailable', 'none', ''] %}
{{ primary }}
{% else %}
{{ fallback }}
{% endif %}
- name: "Infra Backup Age Hours"
unique_id: infra_backup_age_hours
unit_of_measurement: "h"
state: >-
{% set stamp = states('sensor.dockerconfigs_backup_date') %}
{% set ts = as_datetime(stamp) %}
{% if ts is not none %}
{{ ((now() - ts).total_seconds() / 3600) | round(1) }}
{% else %}
unknown
{% endif %}
- name: "Infra Domain Expiry Min Days"
unique_id: infra_domain_expiry_min_days
unit_of_measurement: "d"
state: >-
{% set ids = [
'sensor.vcloudinfo_com_days_until_expiration',
'sensor.ipmer_com_days_until_expiration',
'sensor.fordst_com_days_until_expiration',
'sensor.kingcrafthomes_com_days_until_expiration'
] %}
{% set ns = namespace(min=9999, any=false) %}
{% for id in ids %}
{% if expand(id) | count > 0 %}
{% set raw = states(id) %}
{% if raw not in ['unknown', 'unavailable', 'none', ''] %}
{% set ns.any = true %}
{% set val = raw | float(9999) %}
{% if val < ns.min %}
{% set ns.min = val %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% if ns.any %}
{{ ns.min | round(0) }}
{% else %}
unknown
{% endif %}
- name: "Infra Cert Expiry Min Days"
unique_id: infra_cert_expiry_min_days
unit_of_measurement: "d"
state: >-
{% set ns = namespace(min=9999, any=false) %}
{% for item in states.sensor %}
{% if item.entity_id is search('(vcloudinfo|ipmer|fordst|kingcrafthomes).*(cert|ssl|tls)') %}
{% set raw = item.state %}
{% if raw not in ['unknown', 'unavailable', 'none', ''] %}
{% set value = raw | float(9999) %}
{% if value != 9999 %}
{% set ns.any = true %}
{% if value < ns.min %}
{% set ns.min = value %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% if ns.any %}
{{ ns.min | round(0) }}
{% else %}
unknown
{% endif %}
- name: "Infra Cert Telemetry Count"
unique_id: infra_cert_telemetry_count
icon: mdi:counter
state: >-
{% set ns = namespace(count=0) %}
{% for item in states.sensor %}
{% if item.entity_id is search('(vcloudinfo|ipmer|fordst|kingcrafthomes).*(cert|ssl|tls)') %}
{% set ns.count = ns.count + 1 %}
{% endif %}
{% endfor %}
{{ ns.count }}
- name: "Infra Website Down Count"
unique_id: infra_website_down_count
icon: mdi:counter
state: >-
{% set ids = [
'binary_sensor.vcloudinfo_com',
'binary_sensor.ipmer_com',
'binary_sensor.fordst_com',
'binary_sensor.www_kingcrafthomes_com'
] %}
{% set ns = namespace(count=0) %}
{% for id in ids %}
{% if expand(id) | count > 0 %}
{% set st = states(id) %}
{% if st in ['off', 'unknown', 'unavailable'] %}
{% set ns.count = ns.count + 1 %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.count }}
- binary_sensor:
- name: "Infra WAN Quality Degraded"
unique_id: infra_wan_quality_degraded
device_class: problem
state: >-
{% set loss_raw = states('sensor.infra_wan_packet_loss') %}
{% set lat_raw = states('sensor.infra_wan_latency_ms') %}
{% set invalid = loss_raw in ['unknown', 'unavailable', 'none', ''] or
lat_raw in ['unknown', 'unavailable', 'none', ''] %}
{% set loss = loss_raw | float(0) %}
{% set lat = lat_raw | float(0) %}
{{ invalid or loss > 5 or lat > 80 }}
- name: "Infra Backup Stale Or Failed"
unique_id: infra_backup_stale_or_failed
device_class: problem
state: >-
{% set status = states('sensor.dockerconfigs_backup_status') | lower %}
{% set err = states('sensor.dockerconfigs_backup_error_message') | lower %}
{% set age = states('sensor.infra_backup_age_hours') | float(9999) %}
{% set failed = status in ['failed', 'failure', 'error', 'fatal'] or
'fail' in status or
'error' in status or
err not in ['unknown', 'unavailable', 'none', ''] %}
{{ failed or age > 24 }}
- name: "Infra DNS Pihole Degraded"
unique_id: infra_dns_pihole_degraded
device_class: problem
state: >-
{% set switch_state = states('switch.pi_hole') %}
{% set service_state = states('binary_sensor.pihole_status') %}
{{ switch_state != 'on' or service_state in ['off', 'unavailable', 'unknown'] }}
- name: "Infra UPS On Battery"
unique_id: infra_ups_on_battery
device_class: problem
state: >-
{% set status = states('sensor.garage_ups_status') | upper %}
{{ 'OB' in status }}
- name: "Infra Website Degraded"
unique_id: infra_website_degraded
device_class: problem
state: >-
{{ states('sensor.infra_website_down_count') | int(0) > 0 }}
- name: "Infra Domain Expiry Critical"
unique_id: infra_domain_expiry_critical
device_class: problem
state: >-
{% set d = states('sensor.infra_domain_expiry_min_days') %}
{% if d in ['unknown', 'unavailable', 'none', ''] %}
false
{% else %}
{{ d | float(9999) < 14 }}
{% endif %}
- name: "Infra Domain Expiry Warning"
unique_id: infra_domain_expiry_warning
device_class: problem
state: >-
{% set d = states('sensor.infra_domain_expiry_min_days') %}
{% if d in ['unknown', 'unavailable', 'none', ''] %}
false
{% else %}
{% set days = d | float(9999) %}
{{ days < 30 and days >= 14 }}
{% endif %}
- name: "Infra Cert Expiry Critical"
unique_id: infra_cert_expiry_critical
device_class: problem
state: >-
{% set d = states('sensor.infra_cert_expiry_min_days') %}
{% if d in ['unknown', 'unavailable', 'none', ''] %}
false
{% else %}
{{ d | float(9999) < 14 }}
{% endif %}
- name: "Infra Cert Expiry Warning"
unique_id: infra_cert_expiry_warning
device_class: problem
state: >-
{% set d = states('sensor.infra_cert_expiry_min_days') %}
{% if d in ['unknown', 'unavailable', 'none', ''] %}
false
{% else %}
{% set days = d | float(9999) %}
{{ days < 30 and days >= 14 }}
{% endif %}
automation:
- alias: "Infrastructure - External IP Change Logbook"
id: infra_external_ip_change_logbook
description: "Log external IP changes into the Activity feed."
mode: queued
trigger:
- platform: state
entity_id: sensor.infra_external_ip
condition:
- condition: template
value_template: "{{ trigger.from_state is not none }}"
- condition: template
value_template: >-
{{ trigger.from_state.state not in ['unknown', 'unavailable', 'none', ''] and
trigger.to_state.state not in ['unknown', 'unavailable', 'none', ''] and
trigger.from_state.state != trigger.to_state.state }}
action:
- service: script.send_to_logbook
data:
topic: "NETWORK"
message: >-
External IP changed from {{ trigger.from_state.state }} to {{ trigger.to_state.state }}.
Loading…
Cancel
Save

Powered by TurnKey Linux.