Sure, but please tell me about the specific part o anything. It’s too much code the entire view.
Would be really interested in seeing how you coded the left hand column, and also one of the bar charts e.g. for HDD
Thanks!
Thanks for the reply, I’m very interested. I spent hours this week trying every command under the sun I could find to try and achieve this… Help!
Here is the guidance.
1.First create file on HA host machine where HA is installed:
/usr/local/bin/updatePackagesStatus.pl
With this code:
use File::Which;
use strict;
if(which('aptitude')) {
open ( FILE, 'aptitude -F%p --disable-columns search ~U |') or die "$!\n";
} elsif (which('checkupdates')) {
open ( FILE, 'checkupdates |' ) or die "$!\n";
} else {
die "Error: neither checkupdates nor aptitude seem to be available\n";
}
my $pkgnbr = 0;
my $pkglist = "";
while (<FILE>){
chomp;
$pkglist = "$pkglist $_";+
$pkgnbr++;
}
close (FILE);
open ( FILE, '> /home/amilino/updatestatus.log' ) or die "$!\n";
print FILE "$pkglist $pkgnbr upgradable(s)\n";
close (FILE);
This script is checking for updates and storing in /home/amilino/updatestatus.log
information that you will later on use in HA. Of course you can change location and name of your log file.
2.Then create file (as well on host machine).
/etc/apt/apt.conf.d/99monitor
With this code:
# Update packages status
APT::Update::Post-Invoke {
"echo 'Update Packages Status' && /usr/local/bin/updatePackagesStatus.pl";
};
Basically it will execute our script every time sudo apt-get update
is executed. You can as well test it by running sudo apt-get update
and see if you have any text created in /home/amilino/updatestatus.log
file.
3.I also added in /etc/crontab
this:
*/10 ** * * root apt-get update &
So that every 10 minutes apt-get update is executed automatically.
4.Now that your are done with HA host machine, you need to install PythonScriptsPro in HA.
5.You will also need generic script that can execute command and return output from any machine on the network via SSH.
I stored script here (be careful, this is inside HA docker, best to use File editor addon for adding this):
/config/scripts/remote_ssh_command.py
With this code:
from paramiko import SSHClient, AutoAddPolicy
host = self.config.get("host")
port = self.config.get("port")
username = self.config.get("user")
password = self.config.get("pass")
command = self.config.get("cmd")
client = SSHClient()
client.set_missing_host_key_policy(AutoAddPolicy())
client.connect(host, port, username, password)
stdin, stdout, stderr = client.exec_command(command)
resp = stdout.read()
stderr.read()
client.close()
self.state = f"\n{ resp.decode()[:250] }"
self._attr_extra_state_attributes = { "response": resp.decode() }
6.Add sensor.
- platform: python_script
name: NUC Updates
unique_id: "nuc_updates"
icon: mdi:package-up
scan_interval: 120
file: scripts/remote_ssh_command.py
host: !secret nuc_host
port: !secret nuc_port
user: "amilino"
pass: !secret nuc_amilino_password
cmd: >
cat /home/amilino/updatestatus.log
7.If you want to place sensitive data into secrets file, you can have this in your /config/secrets.yaml
:
nuc_host: 192.168.188.203
nuc_port: 20541
nuc_amilino_password: your_password
If not, then just replace !secret nuc_amilino_password
with real password in previous step (same for other properties !secret nuc_host
and !secret nuc_port
).
8.And finally add card.
type: vertical-stack
cards:
- type: entities
entities:
- entity: sensor.nuc_updates
type: custom:template-entity-row
name: Package(s)
state: '{{ state_attr("sensor.nuc_updates", "response").split(" ")[1] }}'
secondary: null
- type: markdown
content: |
{{ state_attr("sensor.nuc_updates", "response").split(" ")[0] }}
title: null
style: |
ha-card {
border: 0px;
{% if '0 upgradable(s)' in states('sensor.nuc_updates') -%}
display: none
{% endif %}
}
I think that is all, hope I haven’t missed anything. Good thing is that you can use this setup to get any information from any server having SSH (OpenSSH on Windows is also working, so you can get system information about any windows machine), but you need to know how to construct command that is returning you wanted data, in this example cat
command is used just to return content of log file. If you need some basic examples for linux and windows I can write it here as well, you can see from my example what I am returning and displaying in my cards.
This is for example if you want to get Distribution of your host machine:
- platform: python_script
name: NUC Distribution
unique_id: "nuc_distribution"
icon: mdi:monitor
scan_interval: 63072000 #2 Years in seconds
file: scripts/remote_ssh_command.py
host: !secret nuc_host
port: !secret nuc_port
user: "amilino"
pass: !secret nuc_amilino_password
cmd: >
grep -Po "(?<=^PRETTY_NAME=).+" /etc/os-release | sed 's/"//g'
For this sensor I added very big interval, because this information is static and you don’t need to update your sensor every few seconds.
Let me know if you have any questions.
For the bar-char look for bar-card on HACS
type: custom:bar-card
name: HDD
positions:
icon: outside
name: inside
color: '#17A589'
entities:
- entity: sensor.disk_use_percent_home
And this is the code for the router cards.
type: vertical-stack
cards:
- type: horizontal-stack
cards:
- show_name: true
show_icon: false
type: custom:button-card
tap_action:
action: none
name: ANTARES
color_type: card
color: rgb(50,78,78)
hold_action:
action: none
styles:
card:
- height: 10px
- type: entities
entities:
- type: custom:button-card
entity: binary_sensor.velop_mesh_wan_status
show_name: false
icon: hass:web
size: 75px
tap_action:
action: none
custom_fields:
attr_dns_servers: '[[[ return entity.attributes.dns ]]]'
attr_public_ip: '[[[ return entity.attributes.ip ]]]'
attr_speedtest_latest: |
[[[
var entity_speedtest = states['sensor.velop_mesh_speedtest_latest']
var d = new Date(entity_speedtest.state)
return d.toLocaleString()
]]]
attr_speedtest_details: |
[[[
var round2 = (num) => Math.round(num * 100) / 100
var spacing_internal = 5
var spacing_external = 30
var icon_size = 22
var entity_speedtest = states['sensor.velop_mesh_speedtest_latest']
var latency = entity_speedtest.attributes.latency
var download_bandwidth = round2(entity_speedtest.attributes.download_bandwidth / 1000)
var upload_bandwidth = round2(entity_speedtest.attributes.upload_bandwidth / 1000)
return `<span style="margin-right: ${spacing_external}px;">
<ha-icon icon="hass:swap-horizontal" style="width: ${icon_size}px;"></ha-icon>
<span>${latency}ms</span>
</span>
<span style="margin-right: ${spacing_external}px;">
<ha-icon icon="hass:cloud-download-outline" style="width: ${icon_size}px;"></ha-icon>
<span>${download_bandwidth} Mbps</span>
</span>
<span>
<ha-icon icon="hass:cloud-upload-outline" style="width: ${icon_size}px;"></ha-icon>
<span>${upload_bandwidth} Mbps</span>
</span>
`
]]]
state:
- value: 'on'
color: darkcyan
- value: 'off'
color: darkred
styles:
card:
- box-shadow: none
- padding: 16px 8px
grid:
- grid-template-areas: >-
"attr_dns_servers . attr_public_ip" "i i i"
"attr_speedtest_details attr_speedtest_details
attr_speedtest_details" "attr_speedtest_latest
attr_speedtest_latest attr_speedtest_latest"
- grid-template-rows: 5% 1fr 15% 5%
- grid-template-columns: 1fr min-content 1fr
custom_fields:
attr_dns_servers:
- justify-self: self-start
attr_public_ip:
- justify-self: self-end
extra_styles: >
div[id^="attr_"] { font-size: smaller; color:
var(--disabled-text-color);
}
div[id^="attr_speedtest_"] { margin-top: 10px; }
#attr_speedtest_latest::before { content: 'As at:' }
#attr_public_ip::before { content: 'Public IP: ' }
#attr_dns_servers::before { content: 'DNS: ' }
- type: conditional
conditions:
- entity: binary_sensor.velop_mesh_speedtest_status
state: 'on'
row:
type: section
- type: custom:template-entity-row
condition: '{{ is_state(''binary_sensor.velop_mesh_speedtest_status'', ''on'') }}'
name: >-
{{ state_attr('binary_sensor.velop_mesh_speedtest_status', 'status')
}}
tap_action:
action: none
card_mod:
style: >
state-badge { display: none; } state-badge + div.info { margin-left:
8px !important; margin-right: 8px; text-align: center; }
- type: section
- type: custom:auto-entities
card:
type: horizontal-stack
card_param: cards
sort:
method: friendly_name
filter:
include:
- entity_id: /^(button|switch)\.velop_mesh_/
options:
type: custom:button-card
hold_action:
action: more-info
tap_action:
action: >-
[[[ return (entity.entity_id.startsWith("button")) ?
"call-service" : "toggle" ]]]
service: >-
[[[ return (entity.entity_id.startsWith("button")) ?
"button.press" : undefined ]]]
service_data:
entity_id: entity
name: |-
[[[
var friendly_name = entity.attributes.friendly_name.replace("Velop Mesh:", "").trim()
var idx = friendly_name.lastIndexOf(" ");
var ret = friendly_name.substring(0, idx) + "<br />" + friendly_name.substring(idx + 1)
return ret
]]]
styles:
card:
- box-shadow: none
- margin-bottom: 3px
icon:
- animation: |-
[[[
var ret
if (entity.entity_id == "button.velop_mesh_start_speedtest" && states["binary_sensor.velop_mesh_speedtest_status"].state == "on") {
ret = "rotating 2s linear infinite"
}
return ret
]]]
- color: |-
[[[
var ret
var col_on = "darkcyan"
var col_off = "var(--primary-text-color)"
ret = (entity.state == "on") ? col_on : col_off
if (entity.entity_id == "button.velop_mesh_start_speedtest") {
ret = (states["binary_sensor.velop_mesh_speedtest_status"].state == "on") ? col_on : col_off
}
return ret
]]]
name:
- font-size: smaller
- color: |-
[[[
var ret
var col_on = "darkcyan"
var col_off = "var(--primary-text-color)"
ret = (entity.state == "on") ? col_on : col_off
if (entity.entity_id == "button.velop_mesh_start_speedtest") {
ret = (states["binary_sensor.velop_mesh_speedtest_status"].state == "on") ? col_on : col_off
}
return ret
]]]
- type: section
- type: custom:fold-entity-row
padding: 0
head:
type: custom:template-entity-row
entity: sensor.velop_mesh_online_devices
tap_action:
action: fire-dom-event
fold_row: true
name: >-
{% set friendly_name = state_attr(config.entity, 'friendly_name') %}
{% if friendly_name %}
{{ friendly_name.split(':')[1].strip() }}
{% endif %}
card_mod:
style: |
.info.pointer { font-weight: 500; }
.state { margin-right: 10px; }
entities:
- type: custom:hui-element
card_type: markdown
card_mod:
style:
.: |
ha-card { border-radius: 0px; box-shadow: none; }
ha-markdown { padding: 16px 0px 0px !important; }
ha-markdown$: >
table { width: 100%; border-collapse: separate;
border-spacing: 0px; }
tbody tr:nth-child(2n+1) { background-color:
var(--table-row-background-color); }
thead tr th, tbody tr td { padding: 4px 10px; }
content: >
{% set devices = state_attr('sensor.velop_mesh_online_devices',
'devices') %} | # | Name | IP | Type |
|:---:|:---|---:|:---:| {%- for device in devices -%}
{% set idx = loop.index %}
{%- for device_name, device_details in device.items() -%}
{%- set device_ip = device_details.ip -%}
{%- set connection_type = device_details.connection | lower -%}
{%- set guest_network = device_details.guest_network -%}
{%- if connection_type == "wired" -%}
{%- set connection_icon = "ethernet" -%}
{% elif connection_type == "wireless" -%}
{%- set connection_icon = "wifi" -%}
{% elif connection_type == "unknown" -%}
{%- set connection_icon = "help" -%}
{% else -%}
{%- set connection_icon = "" -%}
{%- endif %}
{{ "| {} | {}{} | {} | {} |".format(idx, device_name,
' <ha-icon icon="hass:account-multiple"></ha-icon>' if
guest_network else '', device_ip, '<ha-icon icon="hass:' ~
connection_icon ~ '"></ha-icon>') }}
{%- endfor %}
{%- endfor %}
- type: custom:fold-entity-row
padding: 0
head:
type: custom:template-entity-row
entity: sensor.velop_mesh_offline_devices
tap_action:
action: fire-dom-event
fold_row: true
name: >-
{% set friendly_name = state_attr(config.entity, 'friendly_name') %}
{% if friendly_name %}
{{ friendly_name.split(':')[1].strip() }}
{% endif %}
card_mod:
style: |
.info.pointer { font-weight: 500; }
.state { margin-right: 10px; }
entities:
- type: custom:hui-element
card_type: markdown
card_mod:
style:
.: |
ha-card { border-radius: 0px; box-shadow: none; }
ha-markdown { padding: 16px 0px 0px !important; }
ha-markdown$: >
table { width: 100%; border-collapse: separate;
border-spacing: 0px; }
tbody tr:nth-child(2n+1) { background-color:
var(--table-row-background-color); }
thead tr th, tbody tr td { padding: 4px 10px; }
content: >
{% set devices = state_attr('sensor.velop_mesh_offline_devices',
'devices') %}
| # | Name |
|:---:|:---|
{% for device in devices %} {{ "| {} | {} |".format(loop.index,
device) }}
{% endfor %}
- type: conditional
conditions:
- entity: sensor.velop_mesh_available_storage
state_not: '0'
- entity: sensor.velop_mesh_available_storage
state_not: unavailable
row:
type: custom:fold-entity-row
padding: 0
head:
type: section
label: Storage
entities:
- type: custom:hui-element
card_type: markdown
card_mod:
style:
.: |
ha-card { border-radius: 0px; box-shadow: none; }
ha-markdown { padding: 16px 0px 0px !important; }
ha-markdown$: >
table { width: 100%; border-collapse: separate;
border-spacing: 0px; }
tbody tr:nth-child(2n+1) { background-color:
var(--table-row-background-color); }
thead tr th, tbody tr td { padding: 4px 10px; }
content: >
{% set partitions =
state_attr('sensor.velop_mesh_available_storage', 'partitions')
%}
| Host | Label | %age used
|:---:|:---:|:---:|
{% for partition in partitions %} {{ "| {} | {} | {}
|".format(partition.ip, partition.label, partition.used_percent)
}}
{% endfor %}
state_color: false
card_mod:
style:
.: |
#states { padding-top: 0px; }
fold-entity-row:
$:
template-entity-row:
$: |
state-badge { display: none; }
state-badge + div { margin-left: 8px !important; }
.info.pointer { font-weight: 500; }
.state { margin-right: 10px; }
- type: custom:auto-entities
card:
type: vertical-stack
card_param: cards
sort:
method: friendly_name
reverse: false
filter:
include:
- entity_id: /^binary_sensor\.velop_(?!(mesh)).*_status/
options:
type: custom:config-template-card
variables:
BUTTONS: |
() => {
var ret = []
var entity_prefix = "button." + "this.entity_id".split(".")[1].split("_").slice(0, -1).join("_")
for (var entity_id in states) {
if (entity_id.startsWith(entity_prefix)) {
var entity_action = states[entity_id].attributes.friendly_name.split(':')[1].trim()
var entity_name = states[entity_id].attributes.friendly_name.split(':')[0].replace('Velop', '').trim()
ret.push({
'entity': entity_id,
'name': entity_action,
'tap_action': {
'action': 'call-service',
'service': 'linksys_velop.' + entity_action.toLowerCase() + '_node',
'service_data': {
'node_name': entity_name,
},
'confirmation': {
'text': 'Are you sure you want to reboot the ' + entity_name + ' node?'
}
}
})
}
}
return ret
}
ID_CONNECTED_DEVICES: >
"sensor." + "this.entity_id".split(".")[1].split("_").slice(0,
-1).join("_") + "_connected_devices"
ID_ENTITY_PICTURE: >
"sensor." + "this.entity_id".split(".")[1].split("_").slice(0,
-1).join("_") + "_image"
ID_LAST_UPDATE_CHECK: >
"sensor." + "this.entity_id".split(".")[1].split("_").slice(0,
-1).join("_") + "_last_update_check"
ID_MODEL: >
"sensor." +
"this.entity_id".split(".")[1].split("_").slice(0,-1).join("_")
+ "_model"
ID_PARENT: >
"sensor." +
"this.entity_id".split(".")[1].split("_").slice(0,-1).join("_")
+ "_parent"
ID_SERIAL: >
"sensor." +
"this.entity_id".split(".")[1].split("_").slice(0,-1).join("_")
+ "_serial"
ID_UPDATE_AVAILABLE: >
"update." +
"this.entity_id".split(".")[1].split("_").slice(0,-1).join("_")
+ "_update"
CONNECTED_DEVICES_TEXT: |
(entity_id) => {
var ret = `
| # | Name | IP | Type |
|:---:|:---|---:|:---:|
`
if (states[entity_id].attributes.devices) {
states[entity_id].attributes.devices.forEach((device, idx) => {
var connection_icon
switch (device.type.toLowerCase()) {
case "wireless":
connection_icon = "wifi"
break
case "wired":
connection_icon = "ethernet"
break
case "unknown":
connection_icon = "help"
break
}
ret += "| " + (idx + 1) + " | " + device.name + ((device.guest_network) ? " <ha-icon icon='hass:account-multiple'></ha-icon>" : "") + " | " + device.ip + " | <ha-icon icon='hass:" + connection_icon + "'></ha-icon> |\n"
})
}
return ret
}
getEntityPicture: |
() => {
if (states[vars['ID_ENTITY_PICTURE']]) {
return states[vars['ID_ENTITY_PICTURE']].state
} else {
return '/local/velop_nodes/' + states[vars['ID_MODEL']].state + '.png'
}
}
entities:
- this.entity_id
- ${ID_CONNECTED_DEVICES}
- ${ID_LAST_UPDATE_CHECK}
- ${ID_MODEL}
- ${ID_PARENT}
- ${ID_SERIAL}
- ${ID_UPDATE_AVAILABLE}
card:
type: entities
card_mod:
style:
.: |
#states { padding-top: 0px; }
fold-entity-row:
$:
template-entity-row:
$: |
state-badge { display: none; }
state-badge + div { margin-left: 8px !important; }
.info.pointer { font-weight: 500; }
.state { margin-right: 10px; }
entities:
- type: custom:button-card
entity: this.entity_id
size: 100%
show_entity_picture: true
show_last_changed: true
show_state: true
tap_action:
action: none
entity_picture: ${ getEntityPicture() }
name: |
[[[
var ret = entity.attributes.friendly_name
if (ret) {
ret = ret.replace("Velop", "").split(":")[0].trim()
}
return ret || "N/A"
]]]
state_display: |
[[[
return `<ha-icon
icon="hass:checkbox-blank-circle"
style="width: 24px; height: 24px;">
</ha-icon>`
]]]
custom_fields:
attr_label_model: Model
attr_model: ${states[ID_MODEL].state}
attr_label_serial: Serial
attr_serial: ${states[ID_SERIAL].state}
attr_parent: >-
${(states[ID_PARENT].state && states[ID_PARENT].state !=
'unknown') ? 'Connected to ' + states[ID_PARENT].state :
'N/A'}
attr_label_ip: IP Address
attr_ip: '[[[ return entity.attributes.ip || ''N/A'' ]]]'
attr_update: |
[[[
var ret
var entity_update = 'update.' + entity.entity_id.split('.')[1].split('_').slice(0, -1).join('_') + '_update'
var update_available = states[entity_update].state
if (update_available == 'on') {
ret = `<ha-icon
icon="hass:package-up"
style="width: 24px; height: 20px;"
>
</ha-icon>`
}
return ret
]]]
extra_styles: >
div[id^="attr_"] { justify-self: start; }
div[id^="attr_label_"] { justify-self: start; margin-left:
20px; } #label, #attr_parent { padding-top: 25px; font-size:
smaller; }
styles:
card:
- box-shadow: none
- padding: 10px 8px
grid:
- grid-template-areas: >-
"n n attr_update s" "i attr_label_model
attr_label_model attr_model" "i attr_label_serial
attr_label_serial attr_serial" "i attr_label_ip
attr_label_ip attr_ip" "l l l attr_parent "
- grid-template-columns: 30% 1fr 30px 150px
name:
- font-size: large
- justify-self: start
- padding-bottom: 20px
label:
- justify-self: start
custom_fields:
attr_label_last_update_check:
- margin-left: 0px
attr_last_update_check:
- justify-self: end
attr_parent:
- justify-self: end
attr_update:
- color: darkred
- justify-self: end
- padding-bottom: 25px
attr_label_model:
- color: white
- font-size: 16px
- justify-self: start
attr_label_serial:
- color: white
- font-size: 16px
- justify-self: start
attr_label_ip:
- color: white
- font-size: 16px
- justify-self: start
attr_model:
- color: darkwhite
- font-size: 12px
- justify-self: end
attr_serial:
- color: darkwhite
- font-size: 12px
- justify-self: end
attr_ip:
- color: darkwhite
- font-size: 12px
- justify-self: end
state:
- justify-self: end
- padding-bottom: 20px
- color: |-
[[[
return (entity.state == 'on' ? 'darkcyan' : 'darkred')
]]]
- type: custom:auto-entities
show_empty: true
card:
type: custom:fold-entity-row
head:
type: section
label: Additional Information
padding: 0
filter:
include:
- entity_id: ${ID_LAST_UPDATE_CHECK}
options:
name: Last update check
- entity_id: ${ID_PARENT}
not:
state: unknown
options:
type: custom:template-entity-row
name: Backhaul
state: >-
{% set backhaul_info = state_attr(config.entity,
'backhaul') %} {% set backhaul_speed =
backhaul_info.speed_mbps | round(2) %} {% if
(backhaul_speed | string).split('.')[1] == '0' %}
{% set backhaul_speed = backhaul_speed | int %}
{% endif %} {{ backhaul_info.connection }} ({{
backhaul_speed }} Mbps)
- type: custom:auto-entities
show_empty: false
filter:
include:
- domain: button
entity_id: >-
${"/" +
"this.entity_id".split(".")[1].split("_").slice(0,-1).join("_")
+ "/"}
card:
type: custom:fold-entity-row
padding: 0
head:
type: section
label: Actions
entities:
- type: buttons
entities: ${BUTTONS()}
- type: section
- type: custom:fold-entity-row
padding: 0
head:
type: custom:template-entity-row
tap_action:
action: fire-dom-event
fold_row: true
entity: ${ID_CONNECTED_DEVICES}
name: >-
{% set name = state_attr(config.entity, 'friendly_name')
%} {% if name %}
{{ name.split(':')[1].strip() }}
{% endif %}
entities:
- type: custom:hui-element
card_type: markdown
content: ${CONNECTED_DEVICES_TEXT(ID_CONNECTED_DEVICES)}
card_mod:
style:
.: |
ha-card { border-radius: 0px; box-shadow: none; }
ha-markdown { padding: 16px 0px 0px !important; }
ha-markdown$: >
table { width: 100%; border-collapse: collapse; }
tbody tr:nth-child(2n+1) { background-color:
var(--table-row-background-color); }
thead tr th, tbody tr td { padding: 4px 10px; }
This setup for showing multiple temperature looks great. I was not able to find it on the repository. Is it available? Or even a sample code. I am using the card already for my server vitals and love it
Anyone knowing how to get disk throughput (read / write local disk) as a sensor? Like this which is from a virtual environment (outside HA):
Setup: Raspberry Pi with HA OS. Does the supervisor has this information?
Thanks all for the inspiration, now I just need to create a dashboard for everything else
type: custom:stack-in-card
keep:
border_radius: true
margin: true
cards:
- type: custom:button-card
name: Network Server
entity_picture: /local/images/raspberry-pi.png
show_name: true
show_entity_picture: true
styles:
card:
- padding: 5px
- border: 0px
grid:
- grid-template-areas: '"n i"'
- grid-template-columns: 3fr 1fr
name:
- justify-self: start
- padding-left: 20px
- font-size: 30px
- font-weight: 300
- type: horizontal-stack
cards:
- type: custom:mini-graph-card
entities:
- sensor.networkserver_cpu_usage
hours_to_show: 24
points_per_hour: 2
hour24: true
animate: true
height: 150
show:
extrema: true
icon: false
name: false
color_thresholds:
- value: 100
color: '#d32f2f'
- value: 90
color: '#ffa000'
- value: 75
color: '#388e3c'
card_mod:
style: |
ha-card .states.flex{
padding-bottom: 0px;
}
ha-card .graph{
margin-top: 0px !important;
}
ha-card .graph .graph__legend{
padding-bottom: 0px !important;
}
ha-card .info.flex{
padding-bottom: 0px !important;
}
- type: custom:mini-graph-card
entities:
- sensor.networkserver_temperature
hours_to_show: 24
points_per_hour: 2
hour24: true
animate: true
height: 150
show:
extrema: true
icon: false
name: false
color_thresholds:
- value: 100
color: '#d32f2f'
- value: 85
color: '#ffa000'
- value: 60
color: '#388e3c'
card_mod:
style: |
ha-card .states.flex{
padding-bottom: 0px;
}
ha-card .graph{
margin-top: 0px !important;
}
ha-card .graph .graph__legend{
padding-bottom: 0px !important;
}
ha-card .info.flex{
padding-bottom: 0px !important;
}
- type: horizontal-stack
cards:
- type: custom:bar-card
entities:
- sensor.networkserver_cpu_usage
name: CPU Usage
positions:
icon: 'off'
name: inside
value: inside
indicator: inside
severity:
- color: '#d32f2f'
from: 90
to: 100
- color: '#ffa000'
from: 75
to: 90
- color: '#388e3c'
from: 0
to: 75
card_mod:
style: |
bar-card-backgroundbar {
border-radius: 8px;
}
bar-card-currentbar {
border-radius: 8px;
}
- type: custom:bar-card
entities:
- sensor.networkserver_temperature
name: CPU Temp
positions:
icon: 'off'
name: inside
value: inside
indicator: inside
severity:
- color: '#d32f2f'
from: 85
to: 100
- color: '#ffa000'
from: 60
to: 85
- color: '#388e3c'
from: 0
to: 60
card_mod:
style: |
bar-card-backgroundbar {
border-radius: 8px;
}
bar-card-currentbar {
border-radius: 8px;
}
- type: horizontal-stack
cards:
- type: custom:bar-card
entities:
- entity: sensor.networkserver_load_1m
name: Load 1m
- entity: sensor.networkserver_load_5m
name: Load 5m
- entity: sensor.networkserver_load_15m
name: Load 15m
stack: horizontal
max: 4
severity:
- color: '#d32f2f'
from: 2
to: 4
- color: '#ffa000'
from: 1
to: 2
- color: '#388e3c'
from: 0
to: 1
positions:
icon: 'off'
name: inside
value: inside
indicator: inside
card_mod:
style: |
bar-card-card{
margin-right: 20px
}
bar-card-backgroundbar {
border-radius: 8px;
}
bar-card-currentbar {
border-radius: 8px;
}
- type: custom:bar-card
entities:
- sensor.networkserver_memory_use
name: Memory Usage
width: 70%
positions:
icon: 'off'
name: inside
value: inside
indicator: inside
severity:
- color: '#d32f2f'
from: 90
to: 100
- color: '#ffa000'
from: 75
to: 90
- color: '#388e3c'
from: 0
to: 75
card_mod:
style: |
bar-card-backgroundbar {
border-radius: 8px;
}
bar-card-currentbar {
border-radius: 8px;
}
- type: custom:bar-card
entities:
- sensor.networkserver_disk_use
name: Disk Usage
width: 70%
positions:
icon: 'off'
name: inside
value: inside
indicator: inside
severity:
- color: '#d32f2f'
from: 90
to: 100
- color: '#ffa000'
from: 75
to: 90
- color: '#388e3c'
from: 0
to: 75
card_mod:
style: |
bar-card-backgroundbar {
border-radius: 8px;
}
bar-card-currentbar {
border-radius: 8px;
}
- type: custom:mini-graph-card
entities:
- entity: sensor.networkserver_network_download
color: '#91C0F8'
name: Download
- entity: sensor.networkserver_network_upload
color: '#ffa000'
name: Upload
show_state: true
hours_to_show: 24
points_per_hour: 2
height: 75
animate: true
line_width: 2.5
show:
name: false
icon: false
card_mod:
style: |
ha-card .graph{
margin-top: -15px !important;
}
ha-card .graph .graph__legend{
padding-bottom: 0px !important;
}
- type: entities
entities:
- entity: sensor.networkserver_uptime
name: Uptime
- entity: sensor.networkserver_updates
name: Packages
- type: custom:fold-entity-row
head:
type: section
label: Server Details
card_mod:
style:
hui-sensor-entity-row:
$ hui-generic-entity-row $: |
.pointer{
color: #91C0F8;
}
.text-content{
color: #e1e1e1;
}
hui-simple-entity-row:
$ hui-generic-entity-row $: |
.pointer{
color: #91C0F8;
}
.text-content{
color: #e1e1e1;
}
entities:
- entity: sensor.networkserver_hostname
name: Hostname
- entity: sensor.networkserver_host_ip
name: IP Address
- entity: sensor.networkserver_host_os
name: OS
- entity: sensor.networkserver_host_kernel
name: Kernel
- entity: sensor.networkserver_host_platform
name: Platform
- entity: sensor.networkserver_host_architecture
name: Architecture
- entity: binary_sensor.networkserver_under_voltage
name: Power
- entity: sensor.networkserver_last_message
name: Last Refresh
card_mod:
style:
hui-sensor-entity-row:
$ hui-generic-entity-row $: |
.pointer{
color: #91C0F8;
}
.text-content{
color: #e1e1e1;
}
Great looking dashboard, kudos for sharing the code.
me too, would love to see the code plz
You can grab the above code copy+paste it all for the Network Server. Then copy paste for your other 3 servers and change the entities. That’ll make the total of 4.
type: custom:stack-in-card
keep:
border_radius: true
margin: true
cards:
- type: custom:button-card
name: **Network Server**
entity_picture: /local/images/raspberry-pi.png
show_name: true
show_entity_picture: true
styles:
etc
etc
etc
Hope to be of some help. Good luck!
I know this old, but can you share how you use the pythonscripts for this?
want to do it with windows and linux systems.
thanks for your help
Sure for the setup you can check this post.
To get the actual sensors, I isolated things in separate configuration file stored in /config/sensors_sys_monitoring.yaml
# 1NUC (Linux machine)
- platform: systemmonitor
resources:
- type: disk_use_percent
arg: /
- type: disk_use
arg: /
- type: disk_free
arg: /
- type: memory_use_percent
- type: memory_use
- type: memory_free
- type: swap_use_percent
- type: swap_use
- type: swap_free
- type: processor_use
- type: processor_temperature
- type: last_boot
- platform: python_script
name: 1NUC Updates
unique_id: "1nuc_updates"
icon: mdi:package-up
scan_interval: 120
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
cat /home/amilino/updatestatus.log
- platform: python_script
name: 1NUC Model
unique_id: "1nuc_model"
icon: mdi:desktop-tower
scan_interval: 63072000 #2 Years in seconds
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
sudo dmidecode -s system-product-name
- platform: python_script
name: 1NUC Distribution
unique_id: "1nuc_distribution"
icon: mdi:monitor
scan_interval: 63072000 #2 Years in seconds
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
grep -Po "(?<=^PRETTY_NAME=).+" /etc/os-release | sed 's/"//g'
- platform: python_script
name: 1NUC Kernel
unique_id: "1nuc_kernel"
icon: mdi:developer-board
scan_interval: 63072000 #2 Years in seconds
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
uname -msr
- platform: python_script
name: 1NUC Processor
unique_id: "1nuc_processor"
icon: mdi:chip
scan_interval: 63072000 #2 Years in seconds
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
grep -m 1 "model name" /proc/cpuinfo | sed -e "s/^.*: //"
- platform: python_script
name: 1NUC Processor speed
unique_id: "1nuc_processor_speed"
unit_of_measurement: MHz
icon: mdi:rotate-360
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
lscpu | grep "CPU MHz" | sed -e "s/^.*: //" | awk '{printf("%.f \n",$1)}'
- platform: python_script
name: 1NUC Processor voltage
unique_id: "1nuc_processor_voltage"
unit_of_measurement: V
icon: mdi:lightning-bolt-outline
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
sudo dmidecode --type processor | grep "Voltage" | grep -Eo "[0-9]+\.[0-9]+"
- platform: python_script
name: 1NUC Processor mem x86_64
unique_id: "1nuc_processor_mem_x86_64"
unit_of_measurement: MiB
icon: mdi:memory
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
helper_functions -kb2mborgb $(grep "Slab" /proc/meminfo | grep -Eo "[0-9]+") | sed 's/M//' | awk '{printf("%.f \n",$1)}'
- platform: python_script
name: 1NUC Processor mem gpu
unique_id: "1nuc_processor_mem_gpu"
unit_of_measurement: MiB
icon: mdi:expansion-card
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
lspci -v -s 00:02.0 | grep " prefetchable" | grep -Eo "[0-9]+[0-9]+[0-9]M" | sed 's/M//'
- platform: python_script
name: 1NUC Processor scaling governor
unique_id: "1nuc_processor_scaling_governor"
icon: mdi:scale-balance
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
- platform: python_script
name: 1NUC Top Processes cpu usage
unique_id: "1nuc_processes_cpu_usage"
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
top -b -n 1 | grep -ve "jq" -ve "top" | awk 'FNR>=6 && FNR<=10{print $12, $9}' | jq -nR '{"return": [inputs | split(" ") | { "proc": .[0], "usage": .[1] }]}'
- platform: python_script
name: 1NUC Top Processes memory usage
unique_id: "1nuc_processes_memory_usage"
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
top -o %MEM -b -n 1 | grep -ve "jq" -ve "top" | awk 'FNR>=6 && FNR<=10{print $12, $10}' | jq -nR '{"return": [inputs | split(" ") | { "proc": .[0], "usage": .[1] }]}'
- platform: python_script
name: 1NUC Processes
unique_id: "1nuc_processes"
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
sudo ps -N --format comm,etime,cmd | grep -v grep | grep -w "/usr/bin/kodi\|webgrabplus" | awk '{print $1, $2}' | jq -nR '{"return": [inputs | split(" ") | { "proc": .[0], "etime": .[1] }]}'
- platform: python_script
name: 1NUC Services
unique_id: "1nuc_services"
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 1nuc_host
port: !secret 1nuc_port
user: "amilino"
pass: !secret 1nuc_amilino_password
cmd: >
for i in "smbd" "ssh" "xrdp"; do printf "$i\t"; sudo service "$i" status | grep "Active: " | sed -e "s/^.*; //"; done | jq -nR '{"return": [inputs | split("\t") | { "service": .[0], "etime": .[1] }]}'
# 2NUC (Windows machine)
- platform: python_script
name: 2NUC Processor
unique_id: "2nuc_processor"
icon: mdi:chip
scan_interval: 63072000 #2 Years in seconds
file: scripts/remote_ssh_command.py
host: !secret 2nuc_host
port: !secret 2nuc_port
user: "amilino"
pass: !secret 2nuc_amilino_password
cmd: >
wmic CPU get NAME | more +1
- platform: python_script
name: 2NUC Model
unique_id: "2nuc_model"
icon: mdi:desktop-tower
scan_interval: 63072000 #2 Years in seconds
file: scripts/remote_ssh_command.py
host: !secret 2nuc_host
port: !secret 2nuc_port
user: "amilino"
pass: !secret 2nuc_amilino_password
cmd: >
powershell (Get-WmiObject -Class:Win32_ComputerSystem).Model
- platform: python_script
name: 2NUC Distribution
unique_id: "2nuc_distribution"
icon: mdi:monitor
scan_interval: 63072000 #2 Years in seconds
file: scripts/remote_ssh_command.py
host: !secret 2nuc_host
port: !secret 2nuc_port
user: "amilino"
pass: !secret 2nuc_amilino_password
cmd: >
powershell (Get-WmiObject -class Win32_OperatingSystem).Caption
- platform: python_script
name: 2NUC Processor use
unique_id: "2nuc_processor_use"
icon: mdi:cpu-64-bit
unit_of_measurement: '%'
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 2nuc_host
port: !secret 2nuc_port
user: "amilino"
pass: !secret 2nuc_amilino_password
cmd: >
powershell c:\\SystemMonitoring\\processor_use.ps1
- platform: python_script
name: 2NUC Processor temperature
unique_id: "2nuc_processor_temperature"
icon: mdi:thermometer
unit_of_measurement: 'ºC'
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 2nuc_host
port: !secret 2nuc_port
user: "amilino"
pass: !secret 2nuc_amilino_password
cmd: >
powershell c:\\SystemMonitoring\\processor_temperature.ps1
- platform: python_script
name: 2NUC Memory use percent
unique_id: "2nuc_memory_use_percent"
icon: mdi:memory
unit_of_measurement: '%'
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 2nuc_host
port: !secret 2nuc_port
user: "amilino"
pass: !secret 2nuc_amilino_password
cmd: >
powershell c:\\SystemMonitoring\\memory_use_percent.ps1
- platform: python_script
name: 2NUC Memory use
unique_id: "2nuc_memory_use"
icon: mdi:memory
unit_of_measurement: 'MiB'
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 2nuc_host
port: !secret 2nuc_port
user: "amilino"
pass: !secret 2nuc_amilino_password
cmd: >
powershell c:\\SystemMonitoring\\memory_use.ps1
- platform: python_script
name: 2NUC Memory free
unique_id: "2nuc_memory_free"
icon: mdi:memory
unit_of_measurement: 'MiB'
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 2nuc_host
port: !secret 2nuc_port
user: "amilino"
pass: !secret 2nuc_amilino_password
cmd: >
powershell c:\\SystemMonitoring\\memory_free.ps1
- platform: python_script
name: 2NUC Last boot
unique_id: "2nuc_last_boot"
icon: mdi:clock
scan_interval: 15
file: scripts/remote_ssh_command.py
host: !secret 2nuc_host
port: !secret 2nuc_port
user: "amilino"
pass: !secret 2nuc_amilino_password
cmd: >
powershell c:\\SystemMonitoring\\last_boot.ps1
After you created file you need to add this into /config/configuration.yaml
sensor sys monitoring: !include sensors_sys_monitoring.yaml
For windows machine I created powershell files on windows machine itself (you also need OpenHardwareMonitor running in the background).
processor_use.ps1
# Check if Open Hardware Monitor is running.
if ((Get-Process -Name OpenHardwareMonitor -ErrorAction SilentlyContinue) -eq $null) {
write-host 'OpenHardwareMonitor.exe not running!'
exit 3
}
# Get the temperatures from all the found sensors and check them
$temperatures = Get-WmiObject -Namespace "Root\OpenHardwareMonitor" -Query "SELECT value FROM Sensor WHERE Name LIKE '%CPU Total%' AND Sensortype='Load'" | sort-object Identifier | select -First 1
$temperature_string = foreach ($result in $temperatures){
'{0}' -f [math]::Round($result.Value)
}
write-host "$temperature_string"
exit 00
processor_temperature.ps1
# Check if Open Hardware Monitor is running.
if ((Get-Process -Name OpenHardwareMonitor -ErrorAction SilentlyContinue) -eq $null) {
write-host 'OpenHardwareMonitor.exe not running!'
exit 3
}
# Get the temperatures from all the found sensors and check them
$temperatures = Get-WmiObject -Namespace "Root\OpenHardwareMonitor" -Query "SELECT value FROM Sensor WHERE Name LIKE '%CPU Core%' AND Sensortype='Temperature'" | sort-object Identifier | select -First 1
$temperature_string = foreach ($result in $temperatures){
'{0}' -f $result.Value
}
write-host "$temperature_string"
exit 0
memory_use_percent.ps1
# Check if Open Hardware Monitor is running.
if ((Get-Process -Name OpenHardwareMonitor -ErrorAction SilentlyContinue) -eq $null) {
write-host 'OpenHardwareMonitor.exe not running!'
exit 3
}
# Get the temperatures from all the found sensors and check them
$values= Get-WmiObject -Namespace "Root\OpenHardwareMonitor" -Query "SELECT value FROM Sensor WHERE Name = 'Memory' AND Sensortype='Load'" | sort-object Identifier | select -First 1
$value_string = foreach ($result in $values){
'{0}' -f [math]::Round($result.Value,1) -Replace ',','.'
}
write-host "$value_string"
exit 0
memory_use.ps1
# Check if Open Hardware Monitor is running.
if ((Get-Process -Name OpenHardwareMonitor -ErrorAction SilentlyContinue) -eq $null) {
write-host 'OpenHardwareMonitor.exe not running!'
exit 3
}
# Get the temperatures from all the found sensors and check them
$values= Get-WmiObject -Namespace "Root\OpenHardwareMonitor" -Query "SELECT value FROM Sensor WHERE Name = 'Used Memory' AND Sensortype='Data'" | sort-object Identifier | select -First 1
$value_string = foreach ($result in $values){
'{0}' -f [math]::Round($result.Value*1024,1) -Replace ',','.'
}
write-host "$value_string"
exit 0
memory_free.ps1
# Check if Open Hardware Monitor is running.
if ((Get-Process -Name OpenHardwareMonitor -ErrorAction SilentlyContinue) -eq $null) {
write-host 'OpenHardwareMonitor.exe not running!'
exit 3
}
# Get the temperatures from all the found sensors and check them
$values= Get-WmiObject -Namespace "Root\OpenHardwareMonitor" -Query "SELECT value FROM Sensor WHERE Name = 'Available Memory' AND Sensortype='Data'" | sort-object Identifier | select -First 1
$value_string = foreach ($result in $values){
'{0}' -f [math]::Round($result.Value*1024,1) -Replace ',','.'
}
write-host "$value_string"
exit 0
last_boot.ps1
$os = Get-WmiObject -Class win32_operatingsystem
$os.ConvertToDateTime($os.LastBootUpTime).ToString('yyyy-MM-ddTHH:mm:ss+00:00')
Let me know if you need as well code for the lovelace card.
I have been using similar to your code for several years. They always worked well until 2023.4.x (not sure which release caused it to fail. Does yours still work?
I’m afraid I can’t say. I don’t use that anymore and I haven’t for a long, long time!
If have been using the following code for a couple of years with no issues until core 2023.4x was released. Not sure which of the updates cause it to fail.
- type: markdown
title: Are Updates Available?
card_mod:
style: |
:host {
--card-mod-icon-color: #42a5f5;
font-size: 1.1em;
}
content: |
<ha-icon icon="mdi:home-assistant"></ha-icon> Add-ons needing update: {{ states('sensor.supervisor_updates') | default }}
> {% for addon in state_attr('sensor.supervisor_updates', 'addons') %}
> {{ addon.name }} {{ addon.version }} -> {{ addon.version_latest }}
> {% endfor %}
<ha-icon icon="hacs:hacs"></ha-icon> HACS updates available: {{ states('sensor.hacs') | default }}
> {% for repo in state_attr('sensor.hacs', 'repositories') %}
> {{ repo.display_name }} {{ repo["installed_version"] }} -> {{ repo["available_version"] }}
> {% endfor %}
| | Current | | Latest |
|--- |:---: |:---: |:---: |
| **Supervisor** | {{ state_attr('sensor.supervisor_updates', 'current_version') }} | | {{ state_attr('sensor.supervisor_updates', 'newest_version') }} |
| **HassOS** | {{ states('sensor.home_assistant_operating_system_version') }} | | {{ states('sensor.home_assistant_operating_system_newest_version') }} |
| **CORE** | {{ states.sensor.updater_core.attributes.current_version }} | | {{ states.sensor.updater_core.attributes.newest_version }} |
| **Audio** | {{ states.sensor.updater_audio.attributes.current_version }} | | {{ states.sensor.updater_audio.attributes.newest_version }} |
| **CLI** | {{ states.sensor.updater_cli.attributes.current_version }} | | {{ states.sensor.updater_cli.attributes.newest_version }} |
| **DNS** | {{ states.sensor.updater_dns.attributes.current_version }} | | {{ states.sensor.updater_dns.attributes.newest_version }} |
| **Multicast** | {{ states.sensor.updater_multicast.attributes.current_version }} | | {{ states.sensor.updater_multicast.attributes.newest_version }} |
| **Observer** | {{ states.sensor.updater_observer.attributes.current_version }} | | {{ states.sensor.updater_observer.attributes.newest_version }} |
As I used many of the examples in this and other forums to build this card, I am not sure what is wrong with the code now. There are no errors in the logs. Just noting but the title showing on my screen. Any ideas on how to fix it?
Thanks.
what does it show in dev templates ?
Personally, I did away with most of these templates, and just added an auto-entities on domain update being on…
the more info HA integrations provide out of the box, the less we need to write our own investigator/monitor.