mirror of
https://github.com/dkmstr/openuds.git
synced 2024-12-22 13:34:04 +03:00
Added support for importing unmanaged servers using CSV
This commit is contained in:
parent
628f43a2e7
commit
eb86784c62
@ -32,6 +32,7 @@
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@ -108,7 +109,7 @@ class ServersTokens(ModelHandler):
|
||||
|
||||
# REST API For servers (except tunnel servers nor actors)
|
||||
class ServersServers(DetailHandler):
|
||||
custom_methods = ['maintenance']
|
||||
custom_methods = ['maintenance', 'importcsv']
|
||||
|
||||
def get_items(self, parent: 'Model', item: typing.Optional[str]) -> types.rest.ManyItemsDictType:
|
||||
parent = typing.cast('models.ServerGroup', parent) # We will receive for sure
|
||||
@ -332,6 +333,74 @@ class ServersServers(DetailHandler):
|
||||
item.save()
|
||||
return 'ok'
|
||||
|
||||
def importcsv(self, parent: 'Model') -> typing.Any:
|
||||
"""
|
||||
We receive a json with string[][] format with the data.
|
||||
Has no header, only the data.
|
||||
"""
|
||||
parent = ensure.is_instance(parent, models.ServerGroup)
|
||||
data: list[list[str]] = self._params.get('data', [])
|
||||
logger.debug('Data received: %s', data)
|
||||
# String lines can have 1, 2 or 3 fields.
|
||||
# if 1, it's a IP
|
||||
# if 2, it's a IP and a hostname. Hostame can be empty, in this case, it will be the same as IP
|
||||
# if 3, it's a IP, a hostname and a MAC. MAC can be empty, in this case, it will be UNKNOWN
|
||||
# if ip is empty and has a hostname, it will be kept, but if it has no hostname, it will be skipped
|
||||
# If the IP is invalid and has no hostname, it will be skipped
|
||||
import_errors: list[str] = []
|
||||
for line_number, row in enumerate(data, 1):
|
||||
if len(row) == 0:
|
||||
continue
|
||||
ip = row[0].strip()
|
||||
hostname = ip
|
||||
mac = consts.MAC_UNKNOWN
|
||||
if len(row) > 1:
|
||||
hostname = row[1].strip()
|
||||
if len(row) > 2:
|
||||
mac = row[2].strip().upper().strip() or consts.MAC_UNKNOWN
|
||||
if mac and not net.is_valid_mac(mac):
|
||||
import_errors.append(f'Line {line_number}: MAC {mac} is invalid, skipping')
|
||||
continue # skip invalid macs
|
||||
if ip and not net.is_valid_ip(ip):
|
||||
import_errors.append(f'Line {line_number}: IP {ip} is invalid, skipping')
|
||||
continue # skip invalid ips if not empty
|
||||
# Must have at least a valid ip or a valid hostname
|
||||
if not ip and not hostname:
|
||||
import_errors.append(f'Line {line_number}: No IP or hostname, skipping')
|
||||
continue
|
||||
|
||||
if hostname != ip and not net.is_valid_host(hostname):
|
||||
# Log it has been skipped
|
||||
import_errors.append(f'Line {line_number}: Hostname {hostname} is invalid, skipping')
|
||||
continue # skip invalid hostnames
|
||||
|
||||
# Seems valid, create server if not exists already (by ip OR hostname)
|
||||
logger.debug('Creating server with ip %s, hostname %s and mac %s', ip, hostname, mac)
|
||||
try:
|
||||
if parent.servers.filter(Q(ip=ip) | Q(hostname=hostname)).count() == 0:
|
||||
server = models.Server.objects.create(
|
||||
register_username=self._user.name,
|
||||
register_ip=self._request.ip,
|
||||
ip=ip,
|
||||
hostname=hostname,
|
||||
listen_port=0,
|
||||
mac=mac,
|
||||
type=parent.type,
|
||||
subtype=parent.subtype,
|
||||
stamp=sql_now(),
|
||||
)
|
||||
parent.servers.add(server) # And register it on group
|
||||
else:
|
||||
# Log it has been skipped
|
||||
import_errors.append(
|
||||
f'Line {line_number}: duplicated server, skipping'
|
||||
)
|
||||
except Exception as e:
|
||||
import_errors.append(f'Error creating server on line {line_number}: {str(e)}')
|
||||
logger.exception('Error creating server on line %s', line_number)
|
||||
|
||||
return import_errors
|
||||
|
||||
|
||||
class ServersGroups(ModelHandler):
|
||||
model = models.ServerGroup
|
||||
|
@ -101,6 +101,7 @@ class DetailHandler(BaseModelHandler):
|
||||
"""
|
||||
# Parent init not invoked because their methos are not used on detail handlers (only on parent handlers..)
|
||||
self._parent = parent_handler
|
||||
self._request = parent_handler._request
|
||||
self._path = path
|
||||
self._params = params
|
||||
self._args = list(args)
|
||||
@ -123,7 +124,7 @@ class DetailHandler(BaseModelHandler):
|
||||
return operation(parent)
|
||||
return operation(parent, arg)
|
||||
|
||||
return None
|
||||
return consts.rest.NOT_FOUND
|
||||
|
||||
# pylint: disable=too-many-branches,too-many-return-statements
|
||||
def get(self) -> typing.Any:
|
||||
@ -141,7 +142,7 @@ class DetailHandler(BaseModelHandler):
|
||||
|
||||
# if has custom methods, look for if this request matches any of them
|
||||
r = self._check_is_custom_method(self._args[0], parent)
|
||||
if r is not None:
|
||||
if r is not consts.rest.NOT_FOUND:
|
||||
return r
|
||||
|
||||
if nArgs == 1:
|
||||
@ -195,6 +196,12 @@ class DetailHandler(BaseModelHandler):
|
||||
|
||||
parent: models.Model = self._kwargs['parent']
|
||||
|
||||
# if has custom methods, look for if this request matches any of them
|
||||
if len(self._args) > 0:
|
||||
r = self._check_is_custom_method(self._args[1], parent)
|
||||
if r is not consts.rest.NOT_FOUND:
|
||||
return r
|
||||
|
||||
# Create new item unless 1 param received (the id of the item to modify)
|
||||
item = None
|
||||
if len(self._args) == 1:
|
||||
|
@ -159,7 +159,11 @@ class MarshallerProcessor(ContentProcessor):
|
||||
|
||||
res = self.marshaller.loads(self._request.body.decode('utf8'))
|
||||
logger.debug('Unmarshalled content: %s', res)
|
||||
return res
|
||||
|
||||
if not isinstance(res, dict):
|
||||
raise ParametersException('Invalid content')
|
||||
|
||||
return typing.cast(dict[str, typing.Any], res)
|
||||
except Exception as e:
|
||||
logger.exception('parsing %s: %s', self.mime_type, self._request.body.decode('utf8'))
|
||||
raise ParametersException(str(e))
|
||||
|
@ -40,3 +40,10 @@ GUI: typing.Final[str] = 'gui'
|
||||
LOG: typing.Final[str] = 'log'
|
||||
|
||||
SYSTEM: typing.Final[str] = 'system' # Defined on system class, here for reference
|
||||
|
||||
|
||||
class _NotFound:
|
||||
pass
|
||||
|
||||
|
||||
NOT_FOUND: typing.Final[_NotFound] = _NotFound()
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -163,9 +163,11 @@ gettext("Active");
|
||||
gettext("Maintenance");
|
||||
gettext("Exit maintenance mode");
|
||||
gettext("Enter maintenance mode");
|
||||
gettext("Import CSV");
|
||||
gettext("Exit maintenance mode?");
|
||||
gettext("Enter maintenance mode?");
|
||||
gettext("Maintenance mode for");
|
||||
gettext("Import servers");
|
||||
gettext("New server");
|
||||
gettext("Edit server");
|
||||
gettext("Remove server from server group");
|
||||
@ -497,7 +499,7 @@ gettext("New");
|
||||
gettext("New");
|
||||
gettext("Edit");
|
||||
gettext("Permissions");
|
||||
gettext("Export");
|
||||
gettext("Export CSV");
|
||||
gettext("Delete");
|
||||
gettext("Filter");
|
||||
gettext("Selected items");
|
||||
@ -520,6 +522,18 @@ gettext("Users");
|
||||
gettext("Groups");
|
||||
gettext("New permission...");
|
||||
gettext("Ok");
|
||||
gettext("CVS Import options for");
|
||||
gettext("Header");
|
||||
gettext("CSV contains header line");
|
||||
gettext("CSV DOES NOT contains header line");
|
||||
gettext("Separator");
|
||||
gettext("Use semicolon");
|
||||
gettext("Use comma");
|
||||
gettext("Use pipe");
|
||||
gettext("Use tab");
|
||||
gettext("File");
|
||||
gettext("Ok");
|
||||
gettext("Cancel");
|
||||
gettext("Remove all");
|
||||
gettext("Cancel");
|
||||
gettext("Ok");
|
||||
|
@ -102,6 +102,6 @@
|
||||
</svg>
|
||||
</div>
|
||||
</uds-root>
|
||||
<script src="/uds/res/admin/runtime.js?stamp=1713376179" type="module"></script><script src="/uds/res/admin/polyfills.js?stamp=1713376179" type="module"></script><script src="/uds/res/admin/main.js?stamp=1713376179" type="module"></script></body>
|
||||
<script src="/uds/res/admin/runtime.js?stamp=1714323481" type="module"></script><script src="/uds/res/admin/polyfills.js?stamp=1714323481" type="module"></script><script src="/uds/res/admin/main.js?stamp=1714323481" type="module"></script></body>
|
||||
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user