Skip to content

Commit 025b91f

Browse files
committed
Add Docker telemetry monitoring and degraded state handling
- Introduced a new binary sensor for Docker container telemetry degradation. - Updated dashboard configurations to display telemetry status and alerts. - Enhanced button card templates to reflect telemetry state in UI. - Modified Docker infrastructure package to include new telemetry sensors and logic for handling degraded telemetry scenarios. - Updated README to describe new features and improvements related to Docker monitoring.
1 parent b527dce commit 025b91f

5 files changed

Lines changed: 117 additions & 15 deletions

File tree

config/dashboards/infrastructure/partials/docker_containers_sections.yaml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# Sections layout for the Docker containers view.
99
# -------------------------------------------------------------------
1010
# Notes: Auto-discovers Portainer container entities from `switch.*_container`.
11+
# Notes: Keeps cards visible when Portainer telemetry is unavailable (degraded mode).
1112
######################################################################
1213

1314
- type: grid
@@ -80,6 +81,20 @@
8081
card_mod:
8182
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
8283
cards:
84+
- type: custom:button-card
85+
template: bearstone_infra_alert_row
86+
entity: binary_sensor.docker_container_telemetry_degraded
87+
name: Docker telemetry degraded
88+
icon: mdi:lan-disconnect
89+
variables:
90+
alert_kind: binary_on
91+
state_display: >-
92+
[[[
93+
const unavailable = states['sensor.docker_monitored_unavailable_count']?.state ?? '0';
94+
const total = states['sensor.docker_monitored_container_count']?.state ?? '0';
95+
return `${unavailable}/${total} unavailable`;
96+
]]]
97+
8398
- type: custom:auto-entities
8499
show_empty: true
85100
grid_options:
@@ -104,7 +119,5 @@
104119
type: custom:button-card
105120
template: bearstone_infra_container_row
106121
icon: mdi:docker
107-
exclude:
108-
- state: unavailable
109122
sort:
110123
method: name

config/dashboards/infrastructure/partials/home_sections.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,23 @@
400400
tap_action:
401401
action: none
402402

403+
- type: custom:button-card
404+
template: bearstone_infra_alert_row
405+
entity: binary_sensor.docker_container_telemetry_degraded
406+
name: Docker telemetry degraded
407+
icon: mdi:lan-disconnect
408+
variables:
409+
alert_kind: binary_on
410+
tap_action:
411+
action: navigate
412+
navigation_path: /dashboard-infrastructure/docker
413+
state_display: >-
414+
[[[
415+
const unavailable = states['sensor.docker_monitored_unavailable_count']?.state ?? '0';
416+
const total = states['sensor.docker_monitored_container_count']?.state ?? '0';
417+
return `${unavailable}/${total} unavailable`;
418+
]]]
419+
403420
- type: custom:auto-entities
404421
show_empty: false
405422
card:
@@ -424,6 +441,8 @@
424441
icon: mdi:docker
425442
exclude:
426443
- state: 'on'
444+
- state: unavailable
445+
- state: unknown
427446

428447
- type: custom:vertical-stack-in-card
429448
grid_options:

config/dashboards/infrastructure/templates/button_card_templates.yaml

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ bearstone_infra_container_row:
216216
image: >
217217
[[[
218218
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
219+
const stateNow = String(entity && entity.state !== undefined ? entity.state : '').toLowerCase();
220+
const telemetryDegraded = states['binary_sensor.docker_container_telemetry_degraded']?.state === 'on';
219221
let key = '';
220222
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
221223
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
@@ -227,17 +229,21 @@ bearstone_infra_container_row:
227229
: (key ? `sensor.${key}_image` : '');
228230
const imageValue = states[imageEntity]?.state;
229231
if (!imageValue || ['unknown', 'unavailable', 'none', ''].includes(String(imageValue).toLowerCase())) {
232+
if (telemetryDegraded && ['unknown', 'unavailable', ''].includes(stateNow)) {
233+
return 'telemetry: delayed';
234+
}
230235
return 'image: n/a';
231236
}
232237
return imageValue;
233238
]]]
234239
status: >
235240
[[[
236241
const s = String(entity.state || '').toLowerCase();
242+
const telemetryDegraded = states['binary_sensor.docker_container_telemetry_degraded']?.state === 'on';
237243
if (s === 'on' || s === 'running') return 'RUNNING';
238244
if (s === 'off' || s === 'stopped') return 'STOPPED';
239-
if (s === 'unavailable') return 'OFFLINE';
240-
if (s === 'unknown' || s === '') return 'UNKNOWN';
245+
if (s === 'unavailable') return telemetryDegraded ? 'STALE' : 'OFFLINE';
246+
if (s === 'unknown' || s === '') return telemetryDegraded ? 'STALE' : 'UNKNOWN';
241247
return String(entity.state).toUpperCase();
242248
]]]
243249
styles:
@@ -359,14 +365,25 @@ bearstone_infra_container_row:
359365
- value: unavailable
360366
styles:
361367
card:
362-
- border-color: rgba(229,57,53,0.35)
363-
- background: rgba(255,235,238,0.85)
368+
- border-color: rgba(245,124,0,0.35)
369+
- background: rgba(255,243,224,0.85)
364370
icon:
365-
- color: rgba(198,40,40,1)
371+
- color: rgba(230,81,0,1)
366372
custom_fields:
367373
status:
368-
- background: rgba(198,40,40,0.10)
369-
- color: rgba(198,40,40,1)
374+
- background: rgba(230,81,0,0.12)
375+
- color: rgba(230,81,0,1)
376+
- value: unknown
377+
styles:
378+
card:
379+
- border-color: rgba(245,124,0,0.35)
380+
- background: rgba(255,243,224,0.85)
381+
icon:
382+
- color: rgba(230,81,0,1)
383+
custom_fields:
384+
status:
385+
- background: rgba(230,81,0,0.12)
386+
- color: rgba(230,81,0,1)
370387

371388
bearstone_infra_panel_header:
372389
show_icon: false

config/packages/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
4545
| [lightning.yaml](lightning.yaml) | Blitzortung lightning counter monitoring with snoozeable push actions. | `sensor.blitzortung_lightning_counter`, `input_boolean.snooze_lightning`, notify engine actions |
4646
| [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` |
4747
| [mariadb_monitoring.yaml](mariadb_monitoring.yaml) | MariaDB health sensors and Lovelace dashboard snippet for recorder stats. | `sensor.mariadb_status`, `sensor.database_size` |
48-
| [docker_infrastructure.yaml](docker_infrastructure.yaml) | Docker host patching telemetry (docker_10/14/17/69) + host-side auto-reboots + container-down Repairs alerts. | `sensor.docker_*_apt_status`, `repairs.create`, `repairs.remove` |
48+
| [docker_infrastructure.yaml](docker_infrastructure.yaml) | Docker host patching telemetry (docker_10/14/17/69) + host-side auto-reboots + container-down Repairs alerts, with degraded-telemetry guardrails when Portainer data drops. | `sensor.docker_*_apt_status`, `binary_sensor.docker_container_telemetry_degraded`, `repairs.create`, `repairs.remove` |
4949
| [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` |
5050
| [onenote_indexer.yaml](onenote_indexer.yaml) | OneNote indexer health/status monitoring for Joanna, failure-repair automation, and a daily duplicate-delete maintenance request. | `sensor.onenote_indexer_last_job_status`, `binary_sensor.onenote_indexer_last_job_successful` |
5151
| [mariadb.yaml](mariadb.yaml) | MariaDB recorder health and capacity SQL sensors. | `sensor.mariadb_status`, `sensor.database_size` |

config/packages/docker_infrastructure.yaml

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,47 @@ template:
200200
{% endif %}
201201
202202
- sensor:
203+
- name: "Docker Monitored Container Count"
204+
unique_id: docker_monitored_container_count
205+
icon: mdi:format-list-numbered
206+
state: >-
207+
{{ state_attr('group.docker_monitored_containers', 'entity_id') | default([], true) | count }}
208+
209+
- name: "Docker Monitored Unavailable Count"
210+
unique_id: docker_monitored_unavailable_count
211+
icon: mdi:lan-disconnect
212+
state: >-
213+
{% set ns = namespace(keys=[], unavailable=0) %}
214+
{% set monitored = state_attr('group.docker_monitored_containers', 'entity_id') | default([], true) %}
215+
{% for switch_entity in monitored %}
216+
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container$', '') %}
217+
{% if key not in ns.keys %}
218+
{% set ns.keys = ns.keys + [key] %}
219+
{% endif %}
220+
{% endfor %}
221+
{% for key in ns.keys %}
222+
{% set status_entity = 'binary_sensor.' ~ key ~ '_status' %}
223+
{% set switch_entity = 'switch.' ~ key ~ '_container' %}
224+
{% if expand(status_entity) | count > 0 %}
225+
{% set effective_state = states(status_entity) | lower %}
226+
{% elif expand(switch_entity) | count > 0 %}
227+
{% set effective_state = states(switch_entity) | lower %}
228+
{% else %}
229+
{% set effective_state = 'unknown' %}
230+
{% endif %}
231+
{% if effective_state == 'unavailable' %}
232+
{% set ns.unavailable = ns.unavailable + 1 %}
233+
{% endif %}
234+
{% endfor %}
235+
{{ ns.unavailable }}
236+
203237
- name: "Docker Containers Down List"
204238
unique_id: docker_containers_down_list
205239
icon: mdi:docker
206240
state: >-
207241
{% set ns = namespace(keys=[], down=[]) %}
208242
{% set monitored = state_attr('group.docker_monitored_containers', 'entity_id') | default([], true) %}
243+
{% set telemetry_degraded = is_state('binary_sensor.docker_container_telemetry_degraded', 'on') %}
209244
{% for switch_entity in monitored %}
210245
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container$', '') %}
211246
{% if key not in ns.keys %}
@@ -222,7 +257,9 @@ template:
222257
{% else %}
223258
{% set effective_state = 'unknown' %}
224259
{% endif %}
225-
{% if effective_state in ['off', 'unknown', 'unavailable'] %}
260+
{% if effective_state in ['off', 'stopped'] %}
261+
{% set ns.down = ns.down + [key] %}
262+
{% elif not telemetry_degraded and effective_state in ['unknown', 'unavailable'] %}
226263
{% set ns.down = ns.down + [key] %}
227264
{% endif %}
228265
{% endfor %}
@@ -241,6 +278,16 @@ template:
241278
{% endif %}
242279
243280
- binary_sensor:
281+
- name: "Docker Container Telemetry Degraded"
282+
unique_id: docker_container_telemetry_degraded
283+
device_class: problem
284+
icon: mdi:lan-disconnect
285+
state: >-
286+
{% set total = states('sensor.docker_monitored_container_count') | int(0) %}
287+
{% set unavailable = states('sensor.docker_monitored_unavailable_count') | int(0) %}
288+
{% set threshold = [3, ((total * 0.6) | round(0, 'ceil') | int(0))] | max %}
289+
{{ total > 0 and unavailable >= threshold }}
290+
244291
- name: "Docker Container Alerts Snoozed"
245292
unique_id: docker_container_alerts_snoozed
246293
device_class: problem
@@ -266,7 +313,7 @@ script:
266313
example: 5
267314
sequence:
268315
- variables:
269-
down_states: ['off', 'unknown', 'unavailable']
316+
down_states: ['off', 'stopped', 'unknown', 'unavailable']
270317
src_entity: "{{ entity_id | default('', true) }}"
271318
op: "{{ operation | default('create', true) | lower }}"
272319
wait_minutes: "{{ delay_minutes | default(0) | int(0) }}"
@@ -304,9 +351,12 @@ script:
304351
minutes: "{{ wait_minutes }}"
305352
- variables:
306353
effective_state: "{{ states(effective_entity) | lower }}"
354+
telemetry_degraded: "{{ is_state('binary_sensor.docker_container_telemetry_degraded', 'on') }}"
307355
container_name: "{{ state_attr(effective_entity, 'friendly_name') | default(container_key, true) }}"
308356
- condition: template
309-
value_template: "{{ effective_state in down_states }}"
357+
value_template: >-
358+
{{ effective_state in down_states and
359+
not (telemetry_degraded and effective_state in ['unknown', 'unavailable']) }}
310360
- condition: state
311361
entity_id: binary_sensor.docker_container_alerts_snoozed
312362
state: "off"
@@ -453,12 +503,15 @@ automation:
453503
value_template: "{{ trigger.event.data.old_state.state != trigger.event.data.new_state.state }}"
454504
action:
455505
- variables:
456-
down_states: ['off', 'unknown', 'unavailable']
506+
down_states: ['off', 'stopped', 'unknown', 'unavailable']
457507
entity_id: "{{ trigger.event.data.entity_id }}"
458508
old_state: "{{ trigger.event.data.old_state.state | lower }}"
459509
new_state: "{{ trigger.event.data.new_state.state | lower }}"
460510
- choose:
461-
- conditions: "{{ new_state in down_states and old_state not in down_states }}"
511+
- conditions: >-
512+
{{ new_state in down_states and old_state not in down_states and
513+
not (is_state('binary_sensor.docker_container_telemetry_degraded', 'on') and
514+
new_state in ['unknown', 'unavailable']) }}
462515
sequence:
463516
- service: script.docker_container_repairs_sync
464517
data:

0 commit comments

Comments
 (0)