Add Webmin filesystem sensors (#112660)

* Add Webmin filesystem sensors

* fix names

* update snapshots

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Sid 2024-05-16 09:03:35 +02:00 committed by GitHub
parent 465e3d421e
commit 6ce1d97e7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 3097 additions and 21 deletions

View file

@ -51,4 +51,6 @@ class WebminUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
} }
async def _async_update_data(self) -> dict[str, Any]: async def _async_update_data(self) -> dict[str, Any]:
return await self.instance.update() data = await self.instance.update()
data["disk_fs"] = {item["dir"]: item for item in data["disk_fs"]}
return data

View file

@ -21,6 +21,39 @@
}, },
"swap_free": { "swap_free": {
"default": "mdi:memory" "default": "mdi:memory"
},
"disk_total": {
"default": "mdi:harddisk"
},
"disk_used": {
"default": "mdi:harddisk"
},
"disk_free": {
"default": "mdi:harddisk"
},
"disk_fs_total": {
"default": "mdi:harddisk"
},
"disk_fs_used": {
"default": "mdi:harddisk"
},
"disk_fs_free": {
"default": "mdi:harddisk"
},
"disk_fs_itotal": {
"default": "mdi:harddisk"
},
"disk_fs_iused": {
"default": "mdi:harddisk"
},
"disk_fs_ifree": {
"default": "mdi:harddisk"
},
"disk_fs_used_percent": {
"default": "mdi:harddisk"
},
"disk_fs_iused_percent": {
"default": "mdi:harddisk"
} }
} }
} }

View file

@ -2,13 +2,15 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
SensorStateClass, SensorStateClass,
) )
from homeassistant.const import UnitOfInformation from homeassistant.const import PERCENTAGE, UnitOfInformation
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -16,6 +18,14 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import WebminConfigEntry from . import WebminConfigEntry
from .coordinator import WebminUpdateCoordinator from .coordinator import WebminUpdateCoordinator
@dataclass(frozen=True, kw_only=True)
class WebminFSSensorDescription(SensorEntityDescription):
"""Represents a filesystem sensor description."""
mountpoint: str
SENSOR_TYPES: list[SensorEntityDescription] = [ SENSOR_TYPES: list[SensorEntityDescription] = [
SensorEntityDescription( SensorEntityDescription(
key="load_1m", key="load_1m",
@ -75,9 +85,118 @@ SENSOR_TYPES: list[SensorEntityDescription] = [
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
), ),
SensorEntityDescription(
key="disk_total",
translation_key="disk_total",
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
device_class=SensorDeviceClass.DATA_SIZE,
suggested_display_precision=1,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="disk_free",
translation_key="disk_free",
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
device_class=SensorDeviceClass.DATA_SIZE,
suggested_display_precision=1,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="disk_used",
translation_key="disk_used",
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
device_class=SensorDeviceClass.DATA_SIZE,
suggested_display_precision=1,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
] ]
def generate_filesystem_sensor_description(
mountpoint: str,
) -> list[WebminFSSensorDescription]:
"""Return all sensor descriptions for a mount point."""
return [
WebminFSSensorDescription(
mountpoint=mountpoint,
key="total",
translation_key="disk_fs_total",
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
device_class=SensorDeviceClass.DATA_SIZE,
suggested_display_precision=1,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
WebminFSSensorDescription(
mountpoint=mountpoint,
key="used",
translation_key="disk_fs_used",
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
device_class=SensorDeviceClass.DATA_SIZE,
suggested_display_precision=1,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
WebminFSSensorDescription(
mountpoint=mountpoint,
key="free",
translation_key="disk_fs_free",
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
device_class=SensorDeviceClass.DATA_SIZE,
suggested_display_precision=1,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
WebminFSSensorDescription(
mountpoint=mountpoint,
key="itotal",
translation_key="disk_fs_itotal",
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
WebminFSSensorDescription(
mountpoint=mountpoint,
key="iused",
translation_key="disk_fs_iused",
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
WebminFSSensorDescription(
mountpoint=mountpoint,
key="ifree",
translation_key="disk_fs_ifree",
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
WebminFSSensorDescription(
mountpoint=mountpoint,
key="used_percent",
translation_key="disk_fs_used_percent",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
WebminFSSensorDescription(
mountpoint=mountpoint,
key="iused_percent",
translation_key="disk_fs_iused_percent",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
]
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: WebminConfigEntry, entry: WebminConfigEntry,
@ -85,11 +204,21 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up Webmin sensors based on a config entry.""" """Set up Webmin sensors based on a config entry."""
coordinator = entry.runtime_data coordinator = entry.runtime_data
async_add_entities(
entities: list[WebminSensor | WebminFSSensor] = [
WebminSensor(coordinator, description) WebminSensor(coordinator, description)
for description in SENSOR_TYPES for description in SENSOR_TYPES
if description.key in coordinator.data if description.key in coordinator.data
) ]
for fs, values in coordinator.data["disk_fs"].items():
entities += [
WebminFSSensor(coordinator, description)
for description in generate_filesystem_sensor_description(fs)
if description.key in values
]
async_add_entities(entities)
class WebminSensor(CoordinatorEntity[WebminUpdateCoordinator], SensorEntity): class WebminSensor(CoordinatorEntity[WebminUpdateCoordinator], SensorEntity):
@ -112,3 +241,32 @@ class WebminSensor(CoordinatorEntity[WebminUpdateCoordinator], SensorEntity):
def native_value(self) -> int | float: def native_value(self) -> int | float:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self.coordinator.data[self.entity_description.key] return self.coordinator.data[self.entity_description.key]
class WebminFSSensor(CoordinatorEntity[WebminUpdateCoordinator], SensorEntity):
"""Represents a Webmin filesystem sensor."""
entity_description: WebminFSSensorDescription
_attr_has_entity_name = True
def __init__(
self,
coordinator: WebminUpdateCoordinator,
description: WebminFSSensorDescription,
) -> None:
"""Initialize a Webmin filesystem sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_device_info = coordinator.device_info
self._attr_translation_placeholders = {"mountpoint": description.mountpoint}
self._attr_unique_id = (
f"{coordinator.mac_address}_{description.mountpoint}_{description.key}"
)
@property
def native_value(self) -> int | float:
"""Return the state of the sensor."""
return self.coordinator.data["disk_fs"][self.entity_description.mountpoint][
self.entity_description.key
]

View file

@ -48,6 +48,39 @@
}, },
"swap_free": { "swap_free": {
"name": "Swap free" "name": "Swap free"
},
"disk_total": {
"name": "Disks total space"
},
"disk_used": {
"name": "Disks used space"
},
"disk_free": {
"name": "Disks free space"
},
"disk_fs_total": {
"name": "Disk total space {mountpoint}"
},
"disk_fs_used": {
"name": "Disk used space {mountpoint}"
},
"disk_fs_free": {
"name": "Disk free space {mountpoint}"
},
"disk_fs_itotal": {
"name": "Disk total inodes {mountpoint}"
},
"disk_fs_iused": {
"name": "Disk used inodes {mountpoint}"
},
"disk_fs_ifree": {
"name": "Disk free inodes {mountpoint}"
},
"disk_fs_used_percent": {
"name": "Disk usage {mountpoint}"
},
"disk_fs_iused_percent": {
"name": "Disk inode usage {mountpoint}"
} }
} }
} }

View file

@ -111,8 +111,8 @@
}), }),
]), ]),
'disk_free': 7749321486336, 'disk_free': 7749321486336,
'disk_fs': list([ 'disk_fs': dict({
dict({ '/': dict({
'device': '**REDACTED**', 'device': '**REDACTED**',
'dir': '**REDACTED**', 'dir': '**REDACTED**',
'free': 49060442112, 'free': 49060442112,
@ -125,20 +125,7 @@
'used': 186676502528, 'used': 186676502528,
'used_percent': 80, 'used_percent': 80,
}), }),
dict({ '/media/disk1': dict({
'device': '**REDACTED**',
'dir': '**REDACTED**',
'free': 7028764823552,
'ifree': 362656466,
'itotal': 366198784,
'iused': 3542318,
'iused_percent': 1,
'total': 11903838912512,
'type': 'ext4',
'used': 4275077644288,
'used_percent': 38,
}),
dict({
'device': '**REDACTED**', 'device': '**REDACTED**',
'dir': '**REDACTED**', 'dir': '**REDACTED**',
'free': 671496220672, 'free': 671496220672,
@ -151,7 +138,20 @@
'used': 4981066997760, 'used': 4981066997760,
'used_percent': 89, 'used_percent': 89,
}), }),
]), '/media/disk2': dict({
'device': '**REDACTED**',
'dir': '**REDACTED**',
'free': 7028764823552,
'ifree': 362656466,
'itotal': 366198784,
'iused': 3542318,
'iused_percent': 1,
'total': 11903838912512,
'type': 'ext4',
'used': 4275077644288,
'used_percent': 38,
}),
}),
'disk_total': 18104905818112, 'disk_total': 18104905818112,
'disk_used': 9442821144576, 'disk_used': 9442821144576,
'drivetemps': list([ 'drivetemps': list([

File diff suppressed because it is too large Load diff