You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Home-AssistantConfig/config/dashboards/infrastructure/templates/button_card_templates.yaml

819 lines
30 KiB

######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Infrastructure Templates - custom:button-card
# Related Issue: 1560
# Shared `button_card_templates` used across Infrastructure views.
# -------------------------------------------------------------------
# Notes: Keep templates generic; view-specific tuning stays in the view partials.
######################################################################
bearstone_infra_base:
show_icon: true
show_name: true
show_state: true
tap_action:
action: more-info
styles:
card:
- border-radius: 18px
- padding: 14px
- box-shadow: none
- background: var(--ha-card-background, var(--card-background-color))
- border: 1px solid var(--divider-color)
- overflow: hidden
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 40px 1fr
- grid-template-rows: min-content min-content
icon:
- width: 22px
- color: var(--primary-text-color)
name:
- font-weight: 700
- font-size: 14px
- justify-self: start
- padding-bottom: 2px
state:
- font-size: 12px
- opacity: 0.75
- justify-self: start
bearstone_infra_chip:
template: bearstone_infra_base
show_state: true
styles:
card:
- border-radius: 999px
- padding: 10px 12px
- border: 1px solid var(--divider-color)
- background: var(--ha-card-background, var(--card-background-color))
grid:
- grid-template-areas: "\"i n\" \"i s\""
- grid-template-columns: 28px 1fr
icon:
- width: 18px
name:
- font-weight: 700
- font-size: 12px
state:
- font-size: 11px
- opacity: 0.7
bearstone_infra_chip_running:
template: bearstone_infra_chip
state:
- value: 'on'
styles:
card:
- border-color: rgba(67,160,71,0.45)
- background: rgba(232,245,233,0.85)
icon:
- color: rgba(46,125,50,1)
- value: 'off'
styles:
card:
- border-color: rgba(229,57,53,0.35)
- background: rgba(255,235,238,0.85)
icon:
- color: rgba(198,40,40,1)
- value: unavailable
styles:
card:
- border-color: rgba(229,57,53,0.35)
- background: rgba(255,235,238,0.85)
icon:
- color: rgba(198,40,40,1)
bearstone_infra_tile:
template: bearstone_infra_base
show_label: true
label: '[[[ return variables.label ? variables.label : "" ]]]'
styles:
grid:
- grid-template-areas: "\"i n\" \"i l\""
- grid-template-rows: min-content min-content
label:
- font-size: 12px
- opacity: 0.8
- justify-self: start
state:
- value: unavailable
styles:
card:
- border-color: rgba(229,57,53,0.35)
- background: rgba(255,235,238,0.85)
bearstone_infra_reboot:
template: bearstone_infra_tile
show_state: false
icon: mdi:restart
label: '[[[ return variables.subtitle ? variables.subtitle : "Hold to reboot" ]]]'
hold_action:
action: call-service
service: button.press
service_data:
entity_id: '[[[ return variables.button_entity ]]]'
confirmation:
text: '[[[ return "Reboot " + (variables.name ? variables.name : "device") + "?" ]]]'
styles:
icon:
- color: var(--secondary-text-color)
bearstone_infra_device_tile:
template: bearstone_infra_tile
show_state: true
label: '[[[ return variables.subtitle ? variables.subtitle : "" ]]]'
hold_action:
action: call-service
service: button.press
service_data:
entity_id: '[[[ return variables.button_entity ]]]'
confirmation:
text: '[[[ return "Restart " + (variables.name ? variables.name : "device") + "?" ]]]'
styles:
state:
- font-weight: 700
bearstone_infra_prune_tile:
template: bearstone_infra_tile
show_state: true
icon: mdi:image-sync
label: >-
[[[
return variables.subtitle ? variables.subtitle : "Hold to prune unused images";
]]]
hold_action:
action: call-service
service: button.press
service_data:
entity_id: '[[[ return variables.prune_button ]]]'
confirmation:
text: '[[[ return "Prune unused images on " + (variables.name ? variables.name : "host") + "?" ]]]'
styles:
icon:
- color: var(--secondary-text-color)
state:
- font-weight: 700
bearstone_infra_apt_prune_tile:
template: bearstone_infra_prune_tile
show_state: true
icon: mdi:server
label: >-
[[[
const lastSensor = variables.last_update_sensor ? variables.last_update_sensor : '';
const lastValue = lastSensor ? (states[lastSensor]?.state ?? 'unknown') : 'unknown';
return "Last update: " + lastValue + " | Hold to prune";
]]]
bearstone_infra_container_row:
template: bearstone_infra_list_row
show_label: false
show_state: false
tap_action:
action: none
hold_action:
action: call-service
service: button.press
service_data:
entity_id: >
[[[
if (variables && variables.restart_button) return variables.restart_button;
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
let key = '';
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
} else if (ent.startsWith('binary_sensor.') && ent.endsWith('_status_2')) {
key = ent.replace('binary_sensor.', '').replace(/_status_2$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container')) {
key = ent.replace('switch.', '').replace(/_container$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container_2')) {
key = ent.replace('switch.', '').replace(/_container_2$/, '');
}
if (!key) return '';
const restartCandidates = [
`button.${key}_restart_container`,
`button.${key}_restart_container_2`,
];
for (const candidate of restartCandidates) {
if (states[candidate]) return candidate;
}
return restartCandidates[0];
]]]
confirmation:
text: >
[[[
const friendly =
(entity && entity.attributes && entity.attributes.friendly_name)
? String(entity.attributes.friendly_name)
: ((entity && entity.entity_id) ? String(entity.entity_id) : 'container');
return "Restart container " + friendly + "?";
]]]
icon: mdi:docker
name: >
[[[
if (variables && variables.name) return variables.name;
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
const friendly = (entity && entity.attributes && entity.attributes.friendly_name)
? String(entity.attributes.friendly_name)
: '';
let key = '';
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
} else if (ent.startsWith('binary_sensor.') && ent.endsWith('_status_2')) {
key = ent.replace('binary_sensor.', '').replace(/_status_2$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container')) {
key = ent.replace('switch.', '').replace(/_container$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container_2')) {
key = ent.replace('switch.', '').replace(/_container_2$/, '');
}
if (friendly && friendly !== 'Container') {
return friendly.replace(/\s+Container$/, '');
}
return key || friendly || ent;
]]]
custom_fields:
image: >
[[[
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
const telemetryDegraded = states['binary_sensor.docker_container_telemetry_degraded']?.state === 'on';
let key = '';
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
} else if (ent.startsWith('binary_sensor.') && ent.endsWith('_status_2')) {
key = ent.replace('binary_sensor.', '').replace(/_status_2$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container')) {
key = ent.replace('switch.', '').replace(/_container$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container_2')) {
key = ent.replace('switch.', '').replace(/_container_2$/, '');
}
const stateCandidates = key ? [
`binary_sensor.${key}_status`,
`binary_sensor.${key}_status_2`,
`sensor.${key}_state`,
`sensor.${key}_state_2`,
`switch.${key}_container`,
`switch.${key}_container_2`,
] : [];
const isUnknownLike = (v) => !v || ['unknown', 'unavailable', 'none', ''].includes(String(v).toLowerCase());
let resolvedState = String(entity && entity.state !== undefined ? entity.state : '').toLowerCase();
for (const candidate of stateCandidates) {
const candidateState = states[candidate]?.state;
if (!isUnknownLike(candidateState)) {
resolvedState = String(candidateState).toLowerCase();
break;
}
}
if (isUnknownLike(resolvedState)) {
for (const candidate of stateCandidates) {
const candidateState = states[candidate]?.state;
if (candidateState !== undefined) {
resolvedState = String(candidateState).toLowerCase();
break;
}
}
}
const imageCandidates = variables.image_sensor
? [variables.image_sensor]
: (key ? [`sensor.${key}_image`, `sensor.${key}_image_2`] : []);
let imageValue;
for (const candidate of imageCandidates) {
const candidateState = states[candidate]?.state;
if (!isUnknownLike(candidateState)) {
imageValue = candidateState;
break;
}
}
if (imageValue === undefined) {
for (const candidate of imageCandidates) {
const candidateState = states[candidate]?.state;
if (candidateState !== undefined) {
imageValue = candidateState;
break;
}
}
}
if (isUnknownLike(imageValue)) {
if (telemetryDegraded && ['unknown', 'unavailable', ''].includes(resolvedState)) {
return 'telemetry: delayed';
}
return 'image: n/a';
}
return imageValue;
]]]
status: >
[[[
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
let key = '';
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
} else if (ent.startsWith('binary_sensor.') && ent.endsWith('_status_2')) {
key = ent.replace('binary_sensor.', '').replace(/_status_2$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container')) {
key = ent.replace('switch.', '').replace(/_container$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container_2')) {
key = ent.replace('switch.', '').replace(/_container_2$/, '');
}
const candidates = key ? [
`binary_sensor.${key}_status`,
`binary_sensor.${key}_status_2`,
`sensor.${key}_state`,
`sensor.${key}_state_2`,
`switch.${key}_container`,
`switch.${key}_container_2`,
] : [];
const isUnknownLike = (v) => !v || ['unknown', 'unavailable', ''].includes(String(v).toLowerCase());
let s = String(entity && entity.state !== undefined ? entity.state : '').toLowerCase();
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (!isUnknownLike(candidateState)) {
s = String(candidateState).toLowerCase();
break;
}
}
if (isUnknownLike(s)) {
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (candidateState !== undefined) {
s = String(candidateState).toLowerCase();
break;
}
}
}
const telemetryDegraded = states['binary_sensor.docker_container_telemetry_degraded']?.state === 'on';
if (s === 'on' || s === 'running') return 'RUNNING';
if (s === 'off' || s === 'stopped' || s === 'exited' || s === 'dead') return 'STOPPED';
if (s === 'unavailable') return telemetryDegraded ? 'STALE' : 'OFFLINE';
if (s === 'unknown' || s === '') return telemetryDegraded ? 'STALE' : 'UNKNOWN';
return String(s).toUpperCase();
]]]
styles:
grid:
- grid-template-areas: "\"i n status\" \"i image status\""
- grid-template-columns: 24px 1fr auto
- grid-template-rows: min-content min-content
- align-items: center
- column-gap: 12px
name:
- min-width: 0
- overflow: hidden
- text-overflow: ellipsis
- white-space: nowrap
icon:
- color: >
[[[
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
let key = '';
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
} else if (ent.startsWith('binary_sensor.') && ent.endsWith('_status_2')) {
key = ent.replace('binary_sensor.', '').replace(/_status_2$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container')) {
key = ent.replace('switch.', '').replace(/_container$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container_2')) {
key = ent.replace('switch.', '').replace(/_container_2$/, '');
}
const candidates = key ? [
`binary_sensor.${key}_status`,
`binary_sensor.${key}_status_2`,
`sensor.${key}_state`,
`sensor.${key}_state_2`,
`switch.${key}_container`,
`switch.${key}_container_2`,
] : [];
const isUnknownLike = (v) => !v || ['unknown', 'unavailable', ''].includes(String(v).toLowerCase());
let s = String(entity && entity.state !== undefined ? entity.state : '').toLowerCase();
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (!isUnknownLike(candidateState)) {
s = String(candidateState).toLowerCase();
break;
}
}
if (isUnknownLike(s)) {
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (candidateState !== undefined) {
s = String(candidateState).toLowerCase();
break;
}
}
}
if (s === 'on' || s === 'running') return 'rgba(46,125,50,1)';
if (s === 'off' || s === 'stopped' || s === 'exited' || s === 'dead') return 'rgba(198,40,40,1)';
return 'rgba(230,81,0,1)';
]]]
custom_fields:
image:
- grid-area: image
- justify-self: start
- align-self: start
- font-size: 11px
- opacity: 0.72
- line-height: 1.1
- white-space: nowrap
- overflow: hidden
- text-overflow: ellipsis
- max-width: 100%
status:
- justify-self: end
- align-self: center
- font-weight: 900
- font-size: 12px
- letter-spacing: 0.04em
- padding: 4px 10px
- border-radius: 999px
- background: >
[[[
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
let key = '';
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
} else if (ent.startsWith('binary_sensor.') && ent.endsWith('_status_2')) {
key = ent.replace('binary_sensor.', '').replace(/_status_2$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container')) {
key = ent.replace('switch.', '').replace(/_container$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container_2')) {
key = ent.replace('switch.', '').replace(/_container_2$/, '');
}
const candidates = key ? [
`binary_sensor.${key}_status`,
`binary_sensor.${key}_status_2`,
`sensor.${key}_state`,
`sensor.${key}_state_2`,
`switch.${key}_container`,
`switch.${key}_container_2`,
] : [];
const isUnknownLike = (v) => !v || ['unknown', 'unavailable', ''].includes(String(v).toLowerCase());
let s = String(entity && entity.state !== undefined ? entity.state : '').toLowerCase();
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (!isUnknownLike(candidateState)) {
s = String(candidateState).toLowerCase();
break;
}
}
if (isUnknownLike(s)) {
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (candidateState !== undefined) {
s = String(candidateState).toLowerCase();
break;
}
}
}
if (s === 'on' || s === 'running') return 'rgba(46,125,50,0.12)';
if (s === 'off' || s === 'stopped' || s === 'exited' || s === 'dead') return 'rgba(198,40,40,0.10)';
return 'rgba(230,81,0,0.12)';
]]]
- color: >
[[[
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
let key = '';
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
} else if (ent.startsWith('binary_sensor.') && ent.endsWith('_status_2')) {
key = ent.replace('binary_sensor.', '').replace(/_status_2$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container')) {
key = ent.replace('switch.', '').replace(/_container$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container_2')) {
key = ent.replace('switch.', '').replace(/_container_2$/, '');
}
const candidates = key ? [
`binary_sensor.${key}_status`,
`binary_sensor.${key}_status_2`,
`sensor.${key}_state`,
`sensor.${key}_state_2`,
`switch.${key}_container`,
`switch.${key}_container_2`,
] : [];
const isUnknownLike = (v) => !v || ['unknown', 'unavailable', ''].includes(String(v).toLowerCase());
let s = String(entity && entity.state !== undefined ? entity.state : '').toLowerCase();
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (!isUnknownLike(candidateState)) {
s = String(candidateState).toLowerCase();
break;
}
}
if (isUnknownLike(s)) {
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (candidateState !== undefined) {
s = String(candidateState).toLowerCase();
break;
}
}
}
if (s === 'on' || s === 'running') return 'rgba(46,125,50,1)';
if (s === 'off' || s === 'stopped' || s === 'exited' || s === 'dead') return 'rgba(198,40,40,1)';
return 'rgba(230,81,0,1)';
]]]
card:
- border-color: >
[[[
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
let key = '';
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
} else if (ent.startsWith('binary_sensor.') && ent.endsWith('_status_2')) {
key = ent.replace('binary_sensor.', '').replace(/_status_2$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container')) {
key = ent.replace('switch.', '').replace(/_container$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container_2')) {
key = ent.replace('switch.', '').replace(/_container_2$/, '');
}
const candidates = key ? [
`binary_sensor.${key}_status`,
`binary_sensor.${key}_status_2`,
`sensor.${key}_state`,
`sensor.${key}_state_2`,
`switch.${key}_container`,
`switch.${key}_container_2`,
] : [];
const isUnknownLike = (v) => !v || ['unknown', 'unavailable', ''].includes(String(v).toLowerCase());
let s = String(entity && entity.state !== undefined ? entity.state : '').toLowerCase();
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (!isUnknownLike(candidateState)) {
s = String(candidateState).toLowerCase();
break;
}
}
if (isUnknownLike(s)) {
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (candidateState !== undefined) {
s = String(candidateState).toLowerCase();
break;
}
}
}
if (s === 'on' || s === 'running') return 'rgba(67,160,71,0.45)';
if (s === 'off' || s === 'stopped' || s === 'exited' || s === 'dead') return 'rgba(229,57,53,0.35)';
return 'rgba(245,124,0,0.35)';
]]]
- background: >
[[[
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
let key = '';
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
} else if (ent.startsWith('binary_sensor.') && ent.endsWith('_status_2')) {
key = ent.replace('binary_sensor.', '').replace(/_status_2$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container')) {
key = ent.replace('switch.', '').replace(/_container$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container_2')) {
key = ent.replace('switch.', '').replace(/_container_2$/, '');
}
const candidates = key ? [
`binary_sensor.${key}_status`,
`binary_sensor.${key}_status_2`,
`sensor.${key}_state`,
`sensor.${key}_state_2`,
`switch.${key}_container`,
`switch.${key}_container_2`,
] : [];
const isUnknownLike = (v) => !v || ['unknown', 'unavailable', ''].includes(String(v).toLowerCase());
let s = String(entity && entity.state !== undefined ? entity.state : '').toLowerCase();
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (!isUnknownLike(candidateState)) {
s = String(candidateState).toLowerCase();
break;
}
}
if (isUnknownLike(s)) {
for (const candidate of candidates) {
const candidateState = states[candidate]?.state;
if (candidateState !== undefined) {
s = String(candidateState).toLowerCase();
break;
}
}
}
if (s === 'on' || s === 'running') return 'rgba(232,245,233,0.85)';
if (s === 'off' || s === 'stopped' || s === 'exited' || s === 'dead') return 'rgba(255,235,238,0.85)';
return 'rgba(255,243,224,0.85)';
]]]
- display: >
[[[
const ent = (entity && entity.entity_id) ? String(entity.entity_id) : '';
let key = '';
if (ent.startsWith('binary_sensor.') && ent.endsWith('_status')) {
key = ent.replace('binary_sensor.', '').replace(/_status$/, '');
} else if (ent.startsWith('binary_sensor.') && ent.endsWith('_status_2')) {
key = ent.replace('binary_sensor.', '').replace(/_status_2$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container')) {
key = ent.replace('switch.', '').replace(/_container$/, '');
} else if (ent.startsWith('switch.') && ent.endsWith('_container_2')) {
key = ent.replace('switch.', '').replace(/_container_2$/, '');
}
const switchEntity = key ? `switch.${key}_container` : '';
const switchEntityAlt = key ? `switch.${key}_container_2` : '';
const monitored = states['switch.docker_monitored_containers']?.attributes?.entity_id || [];
const restartCandidates = key ? [
`button.${key}_restart_container`,
`button.${key}_restart_container_2`,
] : [];
const hasRestart = restartCandidates.some((candidate) => states[candidate]);
const isMonitored = monitored.includes(switchEntity) || monitored.includes(switchEntityAlt);
return (hasRestart && isMonitored) ? 'block' : 'none';
]]]
bearstone_infra_panel_header:
show_icon: false
show_state: false
show_label: false
tap_action:
action: none
styles:
card:
- background: transparent
- border: none
- box-shadow: none
- padding: 6px 2px 10px 2px
name:
- font-size: 18px
- font-weight: 800
- letter-spacing: 0.04em
- text-transform: uppercase
- justify-self: start
bearstone_infra_list_row:
show_icon: true
show_name: true
show_state: true
show_label: false
tap_action:
action: more-info
styles:
card:
- border-radius: 14px
- padding: 12px 12px
- box-shadow: none
- border: 1px solid rgba(0,0,0,0.03)
- background: rgba(0,0,0,0.02)
- width: 100%
- box-sizing: border-box
grid:
- grid-template-areas: "\"i n s\""
- grid-template-columns: 24px 1fr auto
- align-items: center
- column-gap: 10px
icon:
- width: 18px
- color: var(--primary-color)
name:
- font-weight: 700
- font-size: 14px
- justify-self: start
- opacity: 0.95
- min-width: 0
- overflow: hidden
- text-overflow: ellipsis
- white-space: nowrap
state:
- justify-self: end
- font-weight: 700
- font-size: 13px
- opacity: 0.8
- 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:
- value: 'on'
styles:
card:
- border-color: rgba(67,160,71,0.45)
- background: rgba(232,245,233,0.85)
icon:
- color: rgba(46,125,50,1)
state:
- color: rgba(46,125,50,1)
- opacity: 1
- value: 'off'
styles:
card:
- border-color: rgba(229,57,53,0.35)
- background: rgba(255,235,238,0.85)
icon:
- color: rgba(198,40,40,1)
state:
- color: rgba(198,40,40,1)
- opacity: 1
- value: Running
styles:
icon:
- color: rgba(46,125,50,1)
state:
- color: rgba(46,125,50,1)
- opacity: 1
- value: running
styles:
icon:
- color: rgba(46,125,50,1)
state:
- color: rgba(46,125,50,1)
- opacity: 1
- value: Stopped
styles:
icon:
- color: rgba(198,40,40,1)
state:
- color: rgba(198,40,40,1)
- opacity: 1
- value: stopped
styles:
icon:
- color: rgba(198,40,40,1)
state:
- color: rgba(198,40,40,1)
- opacity: 1
- value: unavailable
styles:
icon:
- color: rgba(198,40,40,1)
state:
- color: rgba(198,40,40,1)
- opacity: 1
bearstone_infra_kpi:
show_icon: true
show_name: true
show_state: true
show_label: false
tap_action:
action: more-info
styles:
card:
- border-radius: 14px
- padding: 12px
- box-shadow: none
- border: 1px solid rgba(0,0,0,0.03)
- background: rgba(0,0,0,0.02)
grid:
- grid-template-areas: "\"i\" \"n\" \"s\""
- grid-template-rows: 22px min-content min-content
- row-gap: 6px
- justify-items: center
icon:
- width: 18px
- color: var(--primary-color)
name:
- font-size: 11px
- font-weight: 800
- opacity: 0.65
- text-transform: uppercase
- text-align: center
- letter-spacing: 0.6px
state:
- font-size: 16px
- font-weight: 800
- opacity: 0.9

Powered by TurnKey Linux.