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.
819 lines
30 KiB
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
|