mirror of
https://github.com/HuFlungDu/pylibmeshctrl.git
synced 2026-02-20 13:42:11 +00:00
353 lines
18 KiB
Python
353 lines
18 KiB
Python
from . import constants
|
|
import datetime
|
|
|
|
class Device(object):
|
|
'''
|
|
Object to represent a device. This object is a rough wrapper; it is not guarunteed to be up to date with the state on the server, for instance.
|
|
|
|
Args:
|
|
nodeid (str): id of the device on the server
|
|
session (~meshctrl.session.Session): Parent session used to run commands
|
|
agent (~meshctrl.types.Agent|dict|None): Information about the agent. Meshcentral returns this data in an unreadable way, so if the dict doesn't match :py:class:`~meshctrl.types.Agent`, we will attempt to convert to our format.
|
|
name (str|None): Device name as it is shown on the meshcentral server
|
|
description (str|None): Device description as it is shown on the meshcentral server. Also accepted as desc.
|
|
tags (list[str]|None): tags associated with device.
|
|
users (list[str]|None): latest known usernames which have logged in.
|
|
created_at (datetime.Datetime|int|None): Time at which device mas created. Also accepted as agct.
|
|
computer_name (str|None): Device name as reported from the agent. This may be different from name. Also accepted as rname.
|
|
icon (~meshctrl.constants.Icon): Icon displayed on the website
|
|
mesh (~meshctrl.mesh.Mesh|None): Mesh object under which this device exists. Is None for individual device access.
|
|
meshtype (~meshctrl.constants.MeshType|None): Type of mesh this device is connected to. Also accepted as mtype.
|
|
meshname (str|None): Name of the mesh to which this device is connected. Also accepted as groupname.
|
|
domain (str|None): Domain on server to which device is connected.
|
|
host (str): reachable hostname of device. Not meaningful for agent meshes.
|
|
ip (str): IP from which device connected.
|
|
connected: (bool): Whether the device is currently connected. Also accepted as conn.
|
|
powered_on (bool): Whether the device is currently powered on. Also accepted as pwr.
|
|
os_description (str|None): Description of the underlying OS. Also accepted as osdesc.
|
|
lastaddr (str|None): IP from which the agent most recently connected. This may be set even if ip is not.
|
|
lastconnect (datetime.Datetime|int|None): Last time at which the agent was connected to the server
|
|
links (dict[str, ~meshctrl.types.UserLink]|None): Collection of links for the device,
|
|
details (dict[str, dict]|None): Extra details about the device. These are not well defined, but are filled by calling :py:meth:`~meshctrl.session.Session.list_devices` with `details=True`.
|
|
|
|
Returns:
|
|
:py:class:`Device`: Object representing a device on the meshcentral server.
|
|
|
|
Attributes:
|
|
nodeid (str): id of the device on the server
|
|
agent (~meshctrl.types.Agent|dict|None): Information about the agent. Meshcentral returns this data in an unreadable way, so if the dict doesn't match :py:class:`~meshctrl.types.Agent`, we will attempt to convert to our format.
|
|
name (str|None): Device name as it is shown on the meshcentral server
|
|
description (str|None): Device description as it is shown on the meshcentral server.
|
|
tags (list[str]): tags associated with device.
|
|
users (list[str]): latest known usernames which have logged in.
|
|
computer_name (str|None): Device name as reported from the agent. This may be different from name. Also accepted as rname.
|
|
icon (~meshctrl.constants.Icon): Icon displayed on the website
|
|
mesh (~meshctrl.mesh.Mesh|None): Mesh object under which this device exists. Is None for individual device access.
|
|
meshtype (~meshctrl.constants.MeshType|None): Type of mesh this device is connected to. Also accepted as mtype.
|
|
meshname (str|None): Name of the mesh to which this device is connected. Also accepted as groupname.
|
|
domain (str|None): Domain on server to which device is connected.
|
|
host (str): reachable hostname of device. Not meaningful for agent meshes.
|
|
ip (str): IP from which device connected.
|
|
connected: (bool): Whether the device is currently connected. Also accepted as conn.
|
|
powered_on (bool): Whether the device is currently powered on. Also accepted as pwr.
|
|
os_description (str|None): Description of the underlying OS. Also accepted as osdesc.
|
|
lastaddr (str|None): IP from which the agent most recently connected. This may be set even if ip is not.
|
|
lastconnect (datetime.Datetime|None): Last time at which the agent was connected to the server
|
|
links (dict[str, ~meshctrl.types.UserLink]|None): Collection of links for the device
|
|
details (dict[str, dict]): Extra details about the device. These are not well defined, but are filled by calling :py:meth:`~meshctrl.session.Session.list_devices` with `details=True`.
|
|
'''
|
|
def __init__(self, nodeid, session, agent=None,
|
|
name=None, desc=None, description=None,
|
|
tags=None, users=None,
|
|
agct=None, created_at=None,
|
|
rname=None, computer_name=None, icon=constants.Icon.desktop,
|
|
mesh=None, mtype=None, meshtype=None, groupname=None, meshname=None,
|
|
domain=None, host=None, ip=None, conn=None, connected=None,
|
|
pwr=None, powered_on=None,
|
|
osdesc=None, os_description=None, lastaddr=None, lastconnect=None,
|
|
links=None, details=None, **kwargs):
|
|
self.nodeid = nodeid
|
|
self._session = session
|
|
if links is None:
|
|
links = {}
|
|
self.links = links
|
|
if agent and "ver" in agent:
|
|
agent = {
|
|
"version": agent["ver"],
|
|
"id": agent["id"],
|
|
"capabilities": agent["caps"]
|
|
}
|
|
self.agent = agent
|
|
self.name = name
|
|
self.computer_name = computer_name if computer_name is not None else rname
|
|
self.icon = icon
|
|
self.mesh = mesh
|
|
self.meshtype = meshtype if meshtype is not None else mtype
|
|
self.meshname = meshname if meshname is not None else groupname
|
|
self.domain = domain
|
|
self.host = host
|
|
self.ip = ip
|
|
self.connected = bool(connected if connected is not None else conn)
|
|
self.powered_on = bool(powered_on if powered_on is not None else pwr)
|
|
self.description = description if description is not None else desc
|
|
self.os_description = os_description if os_description is not None else osdesc
|
|
self.tags = tags if tags is not None else []
|
|
self.users = users if users is not None else []
|
|
self.details = details if details is not None else {}
|
|
|
|
created_at = created_at if created_at is not None else agct
|
|
if not isinstance(created_at, datetime.datetime) and created_at is not None:
|
|
try:
|
|
created_at = datetime.datetime.fromtimestamp(created_at)
|
|
except (OSError, ValueError):
|
|
# Meshcentral returns in miliseconds, while fromtimestamp, and most of python, expects the argument in seconds. Try seconds frist, then translate from ms if it fails.
|
|
# This doesn't work for really early timestamps, but I don't expect that to be a problem here.
|
|
created_at = datetime.datetime.fromtimestamp(created_at/1000.0)
|
|
|
|
self.created_at = created_at
|
|
|
|
if not isinstance(lastconnect, datetime.datetime) and lastconnect is not None:
|
|
try:
|
|
lastconnect = datetime.datetime.fromtimestamp(lastconnect)
|
|
except (OSError, ValueError):
|
|
# Meshcentral returns in miliseconds, while fromtimestamp, and most of python, expects the argument in seconds. Try seconds frist, then translate from ms if it fails.
|
|
# This doesn't work for really early timestamps, but I don't expect that to be a problem here.
|
|
lastconnect = datetime.datetime.fromtimestamp(lastconnect/1000.0)
|
|
|
|
self.lastconnect = lastconnect
|
|
self.lastaddr = lastaddr
|
|
|
|
# In case meshcentral gives us props we don't understand, store them here.
|
|
self._extra_props = kwargs
|
|
|
|
async def add_users(self, userids, rights=None, timeout=None):
|
|
'''
|
|
Add a user to an existing node
|
|
|
|
Args:
|
|
userids (str|list[str]): Unique user id(s)
|
|
rights (~meshctrl.constants.DeviceRights): Bitwise mask for the rights on the given device
|
|
timeout (int): duration in milliseconds to wait for a response before throwing an error
|
|
|
|
Returns:
|
|
bool: True on success, raise otherwise
|
|
|
|
Raises:
|
|
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
|
|
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
|
|
asyncio.TimeoutError: Command timed out
|
|
'''
|
|
return await self._session.add_users_to_device(userids, self.nodeid, domain=self.domain, rights=rights, timeout=timeout)
|
|
|
|
async def remove_users(self, userids, timeout=None):
|
|
'''
|
|
Remove users from an this node
|
|
|
|
Args:
|
|
userids (str|list[str]): Unique user id(s)
|
|
timeout (int): duration in seconds to wait for a response before throwing an error
|
|
|
|
Returns:
|
|
bool: True on success, raise otherwise
|
|
|
|
Raises:
|
|
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
|
|
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
|
|
asyncio.TimeoutError: Command timed out
|
|
'''
|
|
return await self._session.remove_users_from_device(self.nodeid, userids, timeout=timeout)
|
|
|
|
async def move_to_device_group(self, meshid, isname=False, timeout=None):
|
|
'''
|
|
Move this device another device group
|
|
|
|
Args:
|
|
meshid (str): Unique mesh id
|
|
isname (bool): treat "meshid" as a name instead of an id
|
|
timeout (int): duration in seconds to wait for a response before throwing an error
|
|
|
|
Returns:
|
|
bool: True on success, raise otherwise
|
|
|
|
Raises:
|
|
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
|
|
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
|
|
asyncio.TimeoutError: Command timed out
|
|
'''
|
|
return await self._session.remove_users_from_device(self.nodeid, meshid, isname=isname, timeout=timeout)
|
|
|
|
async def info(self, timeout=None):
|
|
'''
|
|
Get all info for this device. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
|
|
|
|
Args:
|
|
timeout (int): duration in seconds to wait for a response before throwing an error
|
|
|
|
Returns:
|
|
~meshctrl.device.Device: Object representing the state of the device. This will be a new device, it will not update this device.
|
|
|
|
Raises:
|
|
ValueError: `Invalid device id` if device is not found
|
|
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
|
|
asyncio.TimeoutError: Command timed out
|
|
'''
|
|
return await self._session.device_info(self.nodeid, timeout=timeout)
|
|
|
|
async def edit(self, name=None, description=None, tags=None, icon=None, consent=None, timeout=None):
|
|
'''
|
|
Edit properties of this device
|
|
|
|
Args:
|
|
name (str): New name for device
|
|
description (str): New description for device
|
|
tags (str|list[str]]): New tags for device
|
|
icon (~meshctrl.constants.Icon): New icon for device
|
|
consent (~meshctrl.constants.ConsentFlags): New consent flags for device
|
|
timeout (int): duration in seconds to wait for a response before throwing an error
|
|
|
|
Returns:
|
|
bool: True if successful, raise otherwise
|
|
|
|
Raises:
|
|
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
|
|
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
|
|
asyncio.TimeoutError: Command timed out
|
|
'''
|
|
return await self._session.edit_device(self.nodeid, name=name, description=description, tags=tags, icon=icon, consent=consent, timeout=timeout)
|
|
|
|
async def run_command(self, command, powershell=False, runasuser=False, runasuseronly=False, ignore_output=False, timeout=None):
|
|
'''
|
|
Run a command on this device. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
|
|
|
|
Args:
|
|
command (str): Command to run
|
|
powershell (bool): Use powershell to run command. Only available on Windows.
|
|
runasuser (bool): Attempt to run as a user instead of the root permissions given to the agent. Fall back to root if we cannot.
|
|
ignore_output (bool): Don't bother trying to get the output. Every device will return an empty string for its result.
|
|
runasuseronly (bool): Error if we cannot run the command as the logged in user.
|
|
timeout (int): duration in seconds to wait for a response before throwing an error
|
|
|
|
Returns:
|
|
~meshctrl.types.RunCommandResponse: Output of command
|
|
|
|
Raises:
|
|
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
|
|
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
|
|
ValueError: `Invalid device id` if device is not found
|
|
asyncio.TimeoutError: Command timed out
|
|
'''
|
|
return (await self._session.run_command(self.nodeid, command, powershell=False, runasuser=False, runasuseronly=False, ignore_output=False, timeout=None))[self.nodeid]
|
|
|
|
async def shell(self):
|
|
'''
|
|
Get a terminal shell on this device
|
|
|
|
Returns:
|
|
:py:class:`~meshctrl.shell.Shell`: Newly created :py:class:`~meshctrl.shell.Shell`
|
|
'''
|
|
return await self._session.shell(self.nodeid)
|
|
|
|
|
|
async def smart_shell(self, regex):
|
|
'''
|
|
Get a smart terminal shell on this device
|
|
|
|
Args:
|
|
regex (regex): Regex to watch for to signify that the shell is ready for new input.
|
|
|
|
Returns:
|
|
:py:class:`~meshctrl.shell.SmartShell`: Newly created :py:class:`~meshctrl.shell.SmartShell`
|
|
'''
|
|
return await self._session.smart_shell(self.nodeid, regex)
|
|
|
|
|
|
async def wake(self, timeout=None):
|
|
'''
|
|
Wake up this device
|
|
|
|
Args:
|
|
timeout (int): duration in seconds to wait for a response before throwing an error
|
|
|
|
Returns:
|
|
bool: True if successful
|
|
|
|
Raises:
|
|
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
|
|
asyncio.TimeoutError: Command timed out
|
|
'''
|
|
|
|
return await self._session.wake_devices(self.nodeid, timeout=timeout)
|
|
|
|
async def reset(self, timeout=None):
|
|
'''
|
|
Reset device
|
|
|
|
Args:
|
|
nodeids (str|list[str]): Unique ids of nodes which to reset
|
|
timeout (int): duration in seconds to wait for a response before throwing an error
|
|
|
|
Returns:
|
|
bool: True if successful
|
|
|
|
Raises:
|
|
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
|
|
asyncio.TimeoutError: Command timed out
|
|
'''
|
|
return await self._session.reset_devices(self.nodeid, timeout=timeout)
|
|
|
|
async def sleep(self, timeout=None):
|
|
'''
|
|
Sleep device
|
|
|
|
Args:
|
|
timeout (int): duration in seconds to wait for a response before throwing an error
|
|
|
|
Returns:
|
|
bool: True if successful
|
|
|
|
Raises:
|
|
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
|
|
asyncio.TimeoutError: Command timed out
|
|
'''
|
|
return await self._session.sleep_devices(self.nodeid, timeout=timeout)
|
|
|
|
async def power_off(self, timeout=None):
|
|
''' Power off device
|
|
|
|
Args:
|
|
timeout (int): duration in seconds to wait for a response before throwing an error
|
|
|
|
Returns:
|
|
bool: True if successful
|
|
|
|
Raises:
|
|
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
|
|
asyncio.TimeoutError: Command timed out
|
|
'''
|
|
return await self._session.power_off_devices(self.nodeid, timeout=timeout)
|
|
|
|
@property
|
|
def short_nodeid(self):
|
|
'''
|
|
nodeid without "node/" or the included domain
|
|
'''
|
|
return self.nodeid.split("/")[-1]
|
|
|
|
@property
|
|
def id(self):
|
|
'''
|
|
Alias to "nodeid" to be consistent accross types.
|
|
'''
|
|
return self.nodeid
|
|
|
|
def __str__(self):
|
|
return f"<Device: nodeid={self.nodeid} name={self.name} description={self.description} computer_name={self.computer_name} icon={self.icon} "\
|
|
f"mesh={self.mesh} meshtype={self.meshtype} meshname={self.meshname} domain={self.domain} host={self.host} ip={self.ip} "\
|
|
f"tags={self.tags} users={self.users} details={self.details} created_at={self.created_at} lastaddr={self.lastaddr} lastconnect={self.lastconnect} "\
|
|
f"connected={self.connected} powered_on={self.powered_on} os_description={self.os_description} links={self.links} _extra_props={self._extra_props}>"
|
|
def __repr__(self):
|
|
return f"Device(nodeid={repr(self.nodeid)}, session={repr(self._session)}, name={repr(self.name)}, description={repr(self.description)}, computer_name={repr(self.computer_name)}, icon={repr(self.icon)}, "\
|
|
f"mesh={repr(self.mesh)}, meshtype={repr(self.meshtype)}, meshname={repr(self.meshname)}, domain={repr(self.domain)}, host={repr(self.host)}, ip={repr(self.ip)}, "\
|
|
f"tags={repr(self.tags)}, users={repr(self.users)}, details={repr(self.details)} created_at={repr(self.created_at)} lastaddr={repr(self.lastaddr)} lastconnect={repr(self.lastconnect)} "\
|
|
f"connected={repr(self.connected)}, powered_on={repr(self.powered_on)}, os_description={repr(self.os_description)}, links={repr(self.links)}, **{repr(self._extra_props)})"
|