diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/NetworkInterface.vue b/platypush/backend/http/webapp/src/components/panels/Entities/NetworkInterface.vue index 4fa3ae6a..b6a48abd 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/NetworkInterface.vue +++ b/platypush/backend/http/webapp/src/components/panels/Entities/NetworkInterface.vue @@ -91,12 +91,39 @@ -
-
Addresses
-
- +
+
+
Speed
+
+
+
+
+
+ +
+
+
MTU
+
+
+
+
+
+ +
+
+
Flags
+
+
+
+
+
+ +
+
Addresses
+
+
diff --git a/platypush/entities/system.py b/platypush/entities/system.py index 8afa6658..5932ea4e 100644 --- a/platypush/entities/system.py +++ b/platypush/entities/system.py @@ -3,6 +3,7 @@ from sqlalchemy import Column, Float, ForeignKey, Integer, JSON, String from platypush.common.db import Base from . import Entity +from .devices import Device if 'cpu' not in Base.metadata: @@ -179,7 +180,7 @@ if 'disk' not in Base.metadata: if 'network_interface' not in Base.metadata: - class NetworkInterface(Entity): + class NetworkInterface(Device): """ ``NetworkInterface`` ORM model. """ @@ -187,7 +188,7 @@ if 'network_interface' not in Base.metadata: __tablename__ = 'network_interface' id = Column( - Integer, ForeignKey(Entity.id, ondelete='CASCADE'), primary_key=True + Integer, ForeignKey(Device.id, ondelete='CASCADE'), primary_key=True ) bytes_sent = Column(Integer) @@ -199,6 +200,10 @@ if 'network_interface' not in Base.metadata: drop_in = Column(Integer) drop_out = Column(Integer) addresses = Column(JSON) + speed = Column(Integer) + mtu = Column(Integer) + duplex = Column(String) + flags = Column(JSON) __mapper_args__ = { 'polymorphic_identity': __tablename__, diff --git a/platypush/message/response/system/__init__.py b/platypush/message/response/system/__init__.py index 2078187e..a4269af0 100644 --- a/platypush/message/response/system/__init__.py +++ b/platypush/message/response/system/__init__.py @@ -28,23 +28,6 @@ class SensorResponse(SystemResponse): pass -class NetworkInterfaceStatsResponse(NetworkResponse): - def __init__( - self, nic: str, is_up: bool, duplex: str, speed: int, mtu: int, *args, **kwargs - ): - super().__init__( - *args, - output={ - 'nic': nic, - 'is_up': is_up, - 'duplex': duplex, - 'speed': speed, - 'mtu': mtu, - }, - **kwargs - ) - - class SensorTemperatureResponse(SensorResponse): def __init__( self, @@ -184,11 +167,6 @@ class SystemResponseList(SystemResponse): super().__init__(output=[r.output for r in responses], *args, **kwargs) -class NetworkResponseList(NetworkResponse, SystemResponseList): - def __init__(self, responses: List[NetworkResponse], *args, **kwargs): - super().__init__(responses=responses, *args, **kwargs) - - class SensorResponseList(SensorResponse, SystemResponseList): def __init__(self, responses: List[SensorResponse], *args, **kwargs): super().__init__(responses=responses, *args, **kwargs) diff --git a/platypush/plugins/system/__init__.py b/platypush/plugins/system/__init__.py index f686f2a1..053435b2 100644 --- a/platypush/plugins/system/__init__.py +++ b/platypush/plugins/system/__init__.py @@ -21,8 +21,6 @@ from platypush.entities.system import ( SwapStats as SwapStatsModel, ) from platypush.message.response.system import ( - NetworkResponseList, - NetworkInterfaceStatsResponse, SensorTemperatureResponse, SensorResponseList, SensorFanResponse, @@ -256,15 +254,18 @@ class SystemPlugin(SensorPlugin, EntityManager): def _network_info(self) -> List[NetworkInterface]: addrs = psutil.net_if_addrs() + stats = psutil.net_if_stats() + return NetworkInterfaceSchema().load( # type: ignore [ { 'interface': interface, 'addresses': addrs.get(interface, []), - **stats._asdict(), + **(stats[interface]._asdict() if stats.get(interface) else {}), + **info._asdict(), } - for interface, stats in psutil.net_io_counters(pernic=True).items() - if any(bool(val) for val in stats._asdict().values()) + for interface, info in psutil.net_io_counters(pernic=True).items() + if any(bool(val) for val in info._asdict().values()) ], many=True, ) @@ -331,37 +332,6 @@ class SystemPlugin(SensorPlugin, EntityManager): many=True, ) - @action - def net_stats( - self, nic: Optional[str] = None - ) -> Union[NetworkInterfaceStatsResponse, NetworkResponseList]: - """ - Get stats about the network interfaces. - - :param nic: Select the stats for a specific network device (e.g. 'eth0'). Default: get stats for all NICs. - :return: :class:`platypush.message.response.system.NetworkInterfaceStatsResponse` or list of - :class:`platypush.message.response.system.NetworkInterfaceStatsResponse`. - """ - stats = psutil.net_if_stats() - - def _expand_stats(_nic, _stats): - return NetworkInterfaceStatsResponse( - nic=_nic, - is_up=_stats.isup, - duplex=_stats.duplex.name, - speed=_stats.speed, - mtu=_stats.mtu, - ) - - if nic: - stats = [addr for name, addr in stats.items() if name == nic] - assert stats, 'No such network interface: {}'.format(nic) - return _expand_stats(nic, stats[0]) - - return NetworkResponseList( - [_expand_stats(nic, addr) for nic, addr in stats.items()] - ) - @action def sensors_temperature( self, sensor: Optional[str] = None, fahrenheit: bool = False @@ -706,6 +676,7 @@ class SystemPlugin(SensorPlugin, EntityManager): NetworkInterfaceModel( id=f'system:network_interface:{nic["interface"]}', name=nic.pop('interface'), + reachable=nic.pop('is_up'), **nic, ) for nic in entities['network'] diff --git a/platypush/schemas/system/_network/_base.py b/platypush/schemas/system/_network/_base.py index 2fca5fc0..97f3affc 100644 --- a/platypush/schemas/system/_network/_base.py +++ b/platypush/schemas/system/_network/_base.py @@ -1,3 +1,4 @@ +from enum import Enum from socket import AddressFamily from marshmallow import pre_load @@ -20,6 +21,7 @@ class NetworkInterfaceBaseSchema(SystemBaseSchema): 'errout': 'errors_out', 'dropin': 'drop_in', 'dropout': 'drop_out', + 'isup': 'is_up', }.items(): if in_attr in data: data[out_attr] = data.pop(in_attr) @@ -32,4 +34,10 @@ class NetworkInterfaceBaseSchema(SystemBaseSchema): addr['family'] = addr['family'].name data['addresses'][i] = addr + if isinstance(data.get('duplex'), Enum): + data['duplex'] = data['duplex'].name.split('_')[-1] + + # Split the flags string + data['flags'] = data.get('flags', '').split(',') + return data diff --git a/platypush/schemas/system/_network/_model.py b/platypush/schemas/system/_network/_model.py index c9bd7cb5..68b1f03e 100644 --- a/platypush/schemas/system/_network/_model.py +++ b/platypush/schemas/system/_network/_model.py @@ -2,6 +2,8 @@ from dataclasses import dataclass, field from socket import AddressFamily from typing import List, Optional +from marshmallow.validate import OneOf + @dataclass class NetworkInterface: @@ -82,6 +84,55 @@ class NetworkInterface: } ) + is_up: bool = field( + metadata={ + 'metadata': { + 'description': 'Whether the interface is active', + 'example': True, + }, + } + ) + + speed: int = field( + metadata={ + 'metadata': { + 'description': 'Interface reported speed in Mbps', + 'example': 10000, + }, + } + ) + + mtu: int = field( + metadata={ + 'metadata': { + 'description': 'Interface maximum transmission unit expressed ' + 'in bytes', + 'example': 65535, + }, + } + ) + + duplex: str = field( + metadata={ + 'validate': OneOf(['FULL', 'HALF', 'UNKNOWN']), + 'metadata': { + 'description': 'Interface duplex configuration. Can be FULL, ' + 'HALF or UNKNOWN', + 'example': 'FULL', + }, + } + ) + + flags: List[str] = field( + default_factory=list, + metadata={ + 'metadata': { + 'description': 'List of flags associated to the interface', + 'example': ['up', 'broadcast', 'running'], + } + }, + ) + addresses: List['NetworkInterfaceAddress'] = field( default_factory=list, metadata={