1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-11 05:17:55 +03:00

Advancing on servers management

This commit is contained in:
Adolfo Gómez García 2023-08-10 22:05:30 +02:00
parent 9879eaf8d6
commit 5a420e0967
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
8 changed files with 210 additions and 150 deletions

View File

@ -32,11 +32,16 @@
import logging
import typing
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from uds import models
from uds.core import types
from uds.core.types import permissions
from uds.core.types import permissions as permtypes
from uds.core.types import rest, servers
from uds.core.ui import gui
from uds.core.util import permissions
from uds.core.util.model import processUuid
from uds.REST.exceptions import NotFound, RequestError
from uds.REST.model import OK, ModelHandler
@ -85,7 +90,7 @@ class ServersTokens(ModelHandler):
raise RequestError('Delete need one and only one argument')
self.ensureAccess(
self.model(), permissions.PermissionType.ALL, root=True
self.model(), permtypes.PermissionType.ALL, root=True
) # Must have write permissions to delete
try:
@ -102,30 +107,68 @@ class ServersGroups(ModelHandler):
model_filter = {
'type__in': [
types.servers.ServerType.SERVER,
types.servers.ServerType.LEGACY,
types.servers.ServerType.UNMANAGED,
]
}
path = 'servers'
name = 'groups'
save_fields = ['name', 'comments', 'type', 'subtype']
table_title = _('Servers Groups')
table_fields = [
{'stamp': {'title': _('Date'), 'type': 'datetime'}},
{'name': {'title': _('Name')}},
{'comments': {'title': _('Comments')}},
{'type': {'title': _('Type')}},
{'ip': {'title': _('IP')}},
{'subtype': {'title': _('Subtype')}},
]
def item_as_dict(self, item: models.RegisteredServer) -> typing.Dict[str, typing.Any]:
def getTypes(self, *args, **kwargs) -> typing.Generator[typing.Dict[str, typing.Any], None, None]:
for i in servers.ServerSubType.manager().enum():
v = rest.TypeInfo(name=i.description, type=f'{i.type.name}@{i.subtype}', description='', icon='').asDict(
group=gettext('Managed') if i.managed else gettext('Unmanaged')
)
yield v
def getGui(self, type_: str) -> typing.List[typing.Any]:
strServerType, serverSubType = type_.split('@')
serverType = types.servers.ServerType[strServerType]
serverSubType = serverSubType.lower()
logger.info('Server type: %s', serverType)
return self.addField(
self.addDefaultFields(
[],
['name', 'comments', 'tags'],
),
[
{
'name': 'type',
'value': serverType, # As int
'type': gui.InputField.Types.HIDDEN,
},
{
'name': 'subtype',
'value': serverSubType, # As str
'type': gui.InputField.Types.HIDDEN,
},
],
)
def beforeSave(self, fields: typing.Dict[str, typing.Any]) -> None:
return super().beforeSave(fields)
def item_as_dict(self, item: 'models.RegisteredServerGroup') -> typing.Dict[str, typing.Any]:
return {
'id': item.uuid,
'name': str(_('Token isued by {} from {}')).format(item.username, item.ip),
'stamp': item.stamp,
'username': item.username,
'ip': item.ip,
'hostname': item.hostname,
'token': item.token,
'type': types.servers.ServerType(item.type).as_str(),
'os': item.os_type,
'name': item.name,
'comments': item.comments,
'host': item.host,
'port': item.port,
'tags': [tag.tag for tag in item.tags.all()],
'transports_count': item.transports.count(),
'servers_count': item.servers.count(),
'permission': permissions.getEffectivePermission(self._user, item),
}
def delete(self) -> str:
@ -140,7 +183,7 @@ class ServersGroups(ModelHandler):
) # Must have write permissions to delete
try:
self.model.objects.get(token=self._args[0]).delete()
self.model.objects.get(uuid=processUuid(self._args[0])).delete()
except self.model.DoesNotExist:
raise NotFound('Element do not exists') from None

View File

@ -40,9 +40,10 @@ import typing
from django.db import IntegrityError, models
from django.utils.translation import gettext as _
from uds.core.consts import OK
import uds.core.types.permissions
import uds.core.types.rest
from uds.core import exceptions as g_exceptions
from uds.core.consts import OK
from uds.core.module import Module
from uds.core.ui import gui as uiGui
from uds.core.util import log, permissions
@ -68,6 +69,7 @@ LOG: typing.Final[str] = 'log'
FieldType = typing.Mapping[str, typing.Any]
# pylint: disable=unused-argument
class BaseModelHandler(Handler):
"""
@ -110,11 +112,8 @@ class BaseModelHandler(Handler):
v['gui']['tab'] = _(str(field['tab']))
gui.append(v)
return gui
def addDefaultFields(
self, gui: typing.List[typing.Any], flds: typing.List[str]
) -> typing.List[typing.Any]:
def addDefaultFields(self, gui: typing.List[typing.Any], flds: typing.List[str]) -> typing.List[typing.Any]:
"""
Adds default fields (based in a list) to a "gui" description
:param gui: Gui list where the "default" fielsds will be added
@ -176,9 +175,7 @@ class BaseModelHandler(Handler):
'name': 'priority',
'type': 'numeric',
'label': _('Priority'),
'tooltip': _(
'Selects the priority of this element (lower number means higher priority)'
),
'tooltip': _('Selects the priority of this element (lower number means higher priority)'),
'required': True,
'value': 1,
'length': 4,
@ -228,9 +225,7 @@ class BaseModelHandler(Handler):
key=lambda x: x['text'].lower(),
),
'label': _('Networks'),
'tooltip': _(
'Networks associated. If No network selected, will mean "all networks"'
),
'tooltip': _('Networks associated. If No network selected, will mean "all networks"'),
'type': 'multichoice',
'order': 101,
'tab': uiGui.Tab.ADVANCED,
@ -264,15 +259,12 @@ class BaseModelHandler(Handler):
"""
Returns a dictionary describing the type (the name, the icon, description, etc...)
"""
res = self.typeInfo(type_)
res.update(
{
'name': _(type_.name()),
'type': type_.type(),
'description': _(type_.description()),
'icon': type_.icon64().replace('\n', ''),
}
)
res = uds.core.types.rest.TypeInfo(
name=_(type_.name()),
type=type_.type(),
description=_(type_.description()),
icon=type_.icon64().replace('\n', ''),
).asDict(**self.typeInfo(type_))
if hasattr(type_, 'group'):
res['group'] = _(type_.group) # Add group info is it is contained
return res
@ -294,9 +286,7 @@ class BaseModelHandler(Handler):
'subtitle': subtitle or '',
}
def readFieldsFromParams(
self, fldList: typing.List[str]
) -> typing.Dict[str, typing.Any]:
def readFieldsFromParams(self, fldList: typing.List[str]) -> typing.Dict[str, typing.Any]:
"""
Reads the indicated fields from the parameters received, and if
:param fldList: List of required fields
@ -345,9 +335,7 @@ class BaseModelHandler(Handler):
return res
# Exceptions
def invalidRequestException(
self, message: typing.Optional[str] = None
) -> exceptions.HandlerError:
def invalidRequestException(self, message: typing.Optional[str] = None) -> exceptions.HandlerError:
"""
Raises an invalid request error with a default translated string
:param message: Custom message to add to exception. If it is None, "Invalid Request" is used
@ -355,9 +343,7 @@ class BaseModelHandler(Handler):
message = message or _('Invalid Request')
return exceptions.RequestError(f'{message} {self.__class__}: {self._args}')
def invalidResponseException(
self, message: typing.Optional[str] = None
) -> exceptions.HandlerError:
def invalidResponseException(self, message: typing.Optional[str] = None) -> exceptions.HandlerError:
message = 'Invalid response' if message is None else message
return exceptions.ResponseError(message)
@ -365,13 +351,9 @@ class BaseModelHandler(Handler):
"""
Raises a NotFound exception with translated "Method not found" string to current locale
"""
return exceptions.RequestError(
_('Method not found in {}: {}').format(self.__class__, self._args)
)
return exceptions.RequestError(_('Method not found in {}: {}').format(self.__class__, self._args))
def invalidItemException(
self, message: typing.Optional[str] = None
) -> exceptions.HandlerError:
def invalidItemException(self, message: typing.Optional[str] = None) -> exceptions.HandlerError:
"""
Raises a NotFound exception, with location info
"""
@ -379,14 +361,10 @@ class BaseModelHandler(Handler):
return exceptions.NotFound(message)
# raise NotFound('{} {}: {}'.format(message, self.__class__, self._args))
def accessDenied(
self, message: typing.Optional[str] = None
) -> exceptions.HandlerError:
def accessDenied(self, message: typing.Optional[str] = None) -> exceptions.HandlerError:
return exceptions.AccessDenied(message or _('Access denied'))
def notSupported(
self, message: typing.Optional[str] = None
) -> exceptions.HandlerError:
def notSupported(self, message: typing.Optional[str] = None) -> exceptions.HandlerError:
return exceptions.NotSupportedError(message or _('Operation not supported'))
# Success methods
@ -401,9 +379,7 @@ class BaseModelHandler(Handler):
"""
Invokes a test for an item
"""
logger.debug(
'Called base test for %s --> %s', self.__class__.__name__, self._params
)
logger.debug('Called base test for %s --> %s', self.__class__.__name__, self._params)
raise self.invalidMethodException()
@ -460,9 +436,7 @@ class DetailHandler(BaseModelHandler):
self._kwargs = kwargs
self._user = kwargs.get('user', None)
def __checkCustom(
self, check: str, parent: models.Model, arg: typing.Any = None
) -> typing.Any:
def __checkCustom(self, check: str, parent: models.Model, arg: typing.Any = None) -> typing.Any:
"""
checks curron methods
:param check: Method to check
@ -590,7 +564,9 @@ class DetailHandler(BaseModelHandler):
# Override this to provide functionality
# Default (as sample) getItems
def getItems(self, parent: models.Model, item: typing.Optional[str]) -> typing.Union[typing.List[typing.Dict], typing.Dict]:
def getItems(
self, parent: models.Model, item: typing.Optional[str]
) -> typing.Union[typing.List[typing.Dict], typing.Dict]:
"""
This MUST be overridden by derived classes
Excepts to return a list of dictionaries or a single dictionary, depending on "item" param
@ -600,9 +576,7 @@ class DetailHandler(BaseModelHandler):
# if item is None: # Returns ALL detail items
# return []
# return {} # Returns one item
raise NotImplementedError(
f'Must provide an getItems method for {self.__class__} class'
)
raise NotImplementedError(f'Must provide an getItems method for {self.__class__} class')
# Default save
def saveItem(self, parent: models.Model, item: typing.Optional[str]) -> None:
@ -768,7 +742,7 @@ class ModelHandler(BaseModelHandler):
"""
return []
def getTypes(self, *args, **kwargs):
def getTypes(self, *args, **kwargs) -> typing.Generator[typing.Dict[str, typing.Any], None, None]:
for type_ in self.enum_types():
yield self.typeAsDict(type_)
@ -865,16 +839,12 @@ class ModelHandler(BaseModelHandler):
except Exception as e:
logger.exception('Exception:')
logger.info('Filtering expression %s is invalid!', self.fltr)
raise exceptions.RequestError(
f'Filtering expression {self.fltr} is invalid'
) from e
raise exceptions.RequestError(f'Filtering expression {self.fltr} is invalid') from e
# Helper to process detail
# Details can be managed (writen) by any user that has MANAGEMENT permission over parent
def processDetail(self) -> typing.Any:
logger.debug(
'Processing detail %s for with params %s', self._path, self._params
)
logger.debug('Processing detail %s for with params %s', self._path, self._params)
try:
item: models.Model = self.model.objects.filter(uuid=self._args[0])[0] # type: ignore # Slicing is not supported by pylance right now
# If we do not have access to parent to, at least, read...
@ -899,9 +869,7 @@ class ModelHandler(BaseModelHandler):
detailCls = self.detail[self._args[1]]
args = list(self._args[2:])
path = self._path + '/' + '/'.join(args[:2])
detail = detailCls(
self, path, self._params, *args, parent=item, user=self._user
)
detail = detailCls(self, path, self._params, *args, parent=item, user=self._user)
method = getattr(detail, self._operation)
return method()
@ -915,9 +883,7 @@ class ModelHandler(BaseModelHandler):
logger.error('Exception processing detail: %s', e)
raise self.invalidRequestException() from e
def getItems(
self, *args, **kwargs
) -> typing.Generator[typing.MutableMapping[str, typing.Any], None, None]:
def getItems(self, *args, **kwargs) -> typing.Generator[typing.MutableMapping[str, typing.Any], None, None]:
if 'overview' in kwargs:
overview = kwargs['overview']
del kwargs['overview']
@ -932,14 +898,12 @@ class ModelHandler(BaseModelHandler):
prefetch = []
if 'query' in kwargs:
query = kwargs['query']
query = kwargs['query'] # We are using a prebuilt query on args
logger.debug('Got query: %s', query)
del kwargs['query']
else:
logger.debug('Args: %s, kwargs: %s', args, kwargs)
query = self.model.objects.filter(*args, **kwargs).prefetch_related(
*prefetch
)
query = self.model.objects.filter(*args, **kwargs).prefetch_related(*prefetch)
if self.model_filter is not None:
query = query.filter(**self.model_filter)
@ -1133,16 +1097,8 @@ class ModelHandler(BaseModelHandler):
if isinstance(item, TaggingMixin):
if tags:
logger.debug('Updating tags: %s', tags)
item.tags.set(
[
Tag.objects.get_or_create(tag=val)[0]
for val in tags
if val != ''
]
)
elif isinstance(
tags, list
): # Present, but list is empty (will be proccesed on "if" else)
item.tags.set([Tag.objects.get_or_create(tag=val)[0] for val in tags if val != ''])
elif isinstance(tags, list): # Present, but list is empty (will be proccesed on "if" else)
item.tags.clear()
if not deleteOnError:
@ -1153,9 +1109,7 @@ class ModelHandler(BaseModelHandler):
# Store associated object if requested (data_type)
try:
if isinstance(item, ManagedObjectModel):
data_type: typing.Optional[str] = self._params.get(
'data_type', self._params.get('type')
)
data_type: typing.Optional[str] = self._params.get('data_type', self._params.get('type'))
if data_type:
item.data_type = data_type
item.data = item.getInstance(self._params).serialize()
@ -1177,9 +1131,7 @@ class ModelHandler(BaseModelHandler):
except self.model.DoesNotExist:
raise exceptions.NotFound('Item not found') from None
except IntegrityError: # Duplicate key probably
raise exceptions.RequestError(
'Element already exists (duplicate key error)'
) from None
raise exceptions.RequestError('Element already exists (duplicate key error)') from None
except (exceptions.SaveException, g_exceptions.ValidationError) as e:
raise exceptions.RequestError(str(e)) from e
except (exceptions.RequestError, exceptions.ResponseError):

View File

@ -77,6 +77,10 @@ class CryptoManager(metaclass=singleton.Singleton):
)
self._namespace = uuid.UUID('627a37a5-e8db-431a-b783-73f7d20b4934')
@staticmethod
def manager() -> 'CryptoManager':
return CryptoManager() # Singleton pattern will return always the same instance
@staticmethod
def AESKey(key: typing.Union[str, bytes], length: int) -> bytes:
if isinstance(key, str):
@ -94,10 +98,6 @@ class CryptoManager(metaclass=singleton.Singleton):
return bytes(kl)
@staticmethod
def manager() -> 'CryptoManager':
return CryptoManager() # Singleton pattern will return always the same instance
def encrypt(self, value: str) -> str:
return codecs.encode(
self._rsa.public_key().encrypt(
@ -139,9 +139,7 @@ class CryptoManager(metaclass=singleton.Singleton):
)
rndStr = secrets.token_bytes(16) # Same as block size of CBC (that is 16 here)
paddedLength = ((len(text) + 4 + 15) // 16) * 16
toEncode = (
struct.pack('>i', len(text)) + text + rndStr[: paddedLength - len(text) - 4]
)
toEncode = struct.pack('>i', len(text)) + text + rndStr[: paddedLength - len(text) - 4]
encryptor = cipher.encryptor()
encoded = encryptor.update(toEncode) + encryptor.finalize()
@ -172,9 +170,7 @@ class CryptoManager(metaclass=singleton.Singleton):
def fastDecrypt(self, data: bytes) -> bytes:
return self.AESDecrypt(data, UDSK)
def xor(
self, value: typing.Union[str, bytes], key: typing.Union[str, bytes]
) -> bytes:
def xor(self, value: typing.Union[str, bytes], key: typing.Union[str, bytes]) -> bytes:
if not key:
return b'' # Protect against division by cero
@ -184,17 +180,11 @@ class CryptoManager(metaclass=singleton.Singleton):
key = key.encode('utf-8')
mult = len(value) // len(key) + 1
value_array = array.array('B', value)
key_array = array.array(
'B', key * mult
) # Ensure key array is at least as long as value_array
key_array = array.array('B', key * mult) # Ensure key array is at least as long as value_array
# We must return binary in xor, because result is in fact binary
return array.array(
'B', (value_array[i] ^ key_array[i] for i in range(len(value_array)))
).tobytes()
return array.array('B', (value_array[i] ^ key_array[i] for i in range(len(value_array)))).tobytes()
def symCrypt(
self, text: typing.Union[str, bytes], key: typing.Union[str, bytes]
) -> bytes:
def symCrypt(self, text: typing.Union[str, bytes], key: typing.Union[str, bytes]) -> bytes:
if isinstance(text, str):
text = text.encode()
if isinstance(key, str):
@ -202,9 +192,7 @@ class CryptoManager(metaclass=singleton.Singleton):
return self.AESCrypt(text, key)
def symDecrpyt(
self, cryptText: typing.Union[str, bytes], key: typing.Union[str, bytes]
) -> str:
def symDecrpyt(self, cryptText: typing.Union[str, bytes], key: typing.Union[str, bytes]) -> str:
if isinstance(cryptText, str):
cryptText = cryptText.encode()
@ -221,19 +209,13 @@ class CryptoManager(metaclass=singleton.Singleton):
def loadPrivateKey(
self, rsaKey: str
) -> typing.Union[
'RSAPrivateKey', 'DSAPrivateKey', 'DHPrivateKey', 'EllipticCurvePrivateKey'
]:
) -> typing.Union['RSAPrivateKey', 'DSAPrivateKey', 'DHPrivateKey', 'EllipticCurvePrivateKey']:
try:
return serialization.load_pem_private_key(
rsaKey.encode(), password=None, backend=default_backend()
)
return serialization.load_pem_private_key(rsaKey.encode(), password=None, backend=default_backend())
except Exception as e:
raise e
def loadCertificate(
self, certificate: typing.Union[str, bytes]
) -> x509.Certificate:
def loadCertificate(self, certificate: typing.Union[str, bytes]) -> x509.Certificate:
if isinstance(certificate, str):
certificate = certificate.encode()
@ -274,16 +256,12 @@ class CryptoManager(metaclass=singleton.Singleton):
return not hashValue
if hashValue[:8] == '{SHA256}':
return secrets.compare_digest(
hashlib.sha3_256(value).hexdigest(), hashValue[8:]
)
return secrets.compare_digest(hashlib.sha3_256(value).hexdigest(), hashValue[8:])
if hashValue[:12] == '{SHA256SALT}':
# Extract 16 chars salt and hash
salt = hashValue[12:28].encode()
value = salt + value
return secrets.compare_digest(
hashlib.sha3_256(value).hexdigest(), hashValue[28:]
)
return secrets.compare_digest(hashlib.sha3_256(value).hexdigest(), hashValue[28:])
# Argon2
if hashValue[:8] == '{ARGON2}':
ph = PasswordHasher()
@ -295,11 +273,16 @@ class CryptoManager(metaclass=singleton.Singleton):
# Old sha1
return secrets.compare_digest(
hashValue, str(hashlib.sha1(value).hexdigest()) # nosec: Old compatibility SHA1, not used anymore but need to be supported
) # nosec: Old compatibility SHA1, not used anymore but need to be supported
hashValue,
str(
hashlib.sha1(
value
).hexdigest() # nosec: Old SHA1 password, not used anymore but need to be supported
),
)
def uuid(self, obj: typing.Any = None) -> str:
""" Generates an uuid from obj. (lower case)
"""Generates an uuid from obj. (lower case)
If obj is None, returns an uuid based on a random string
"""
if obj is None:
@ -319,8 +302,5 @@ class CryptoManager(metaclass=singleton.Singleton):
def unique(self) -> str:
return hashlib.sha3_256(
(
self.randomString(24, True)
+ datetime.datetime.now().strftime('%H%M%S%f')
).encode()
(self.randomString(24, True) + datetime.datetime.now().strftime('%H%M%S%f')).encode()
).hexdigest()

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2023 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import typing
class TypeInfo(typing.NamedTuple):
name: str
type: str
description: str
icon: str
def asDict(self, **extra) -> typing.Dict[str, typing.Any]:
return {
'name': self.name,
'type': self.type,
'description': self.description,
'icon': self.icon,
**extra
}

View File

@ -30,13 +30,17 @@
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import enum
import typing
from uds.core.util import singleton
class ServerType(enum.IntEnum):
TUNNEL = 1
ACTOR = 2
SERVER = 3
LEGACY = 100
UNMANAGED = 100
def as_str(self) -> str:
return self.name.lower() # type: ignore
@ -46,5 +50,38 @@ class ServerType(enum.IntEnum):
ServerType.TUNNEL: 'tunnel',
ServerType.ACTOR: 'actor',
ServerType.SERVER: 'server',
ServerType.LEGACY: '', # Legacy has no path, does not listen to anything
ServerType.UNMANAGED: '', # Unmanaged has no path, does not listen to anything
}[self]
class ServerSubType(metaclass=singleton.Singleton):
class Info(typing.NamedTuple):
type: ServerType
subtype: str
description: str
managed: bool
registered: typing.Dict[typing.Tuple[ServerType, str], Info]
def __init__(self) -> None:
self.registered = {}
@staticmethod
def manager() -> 'ServerSubType':
return ServerSubType()
def register(self, type: ServerType, subtype: str, description: str, managed: bool) -> None:
self.registered[(type, subtype)] = ServerSubType.Info(
type=type, subtype=subtype, description=description, managed=managed
)
def enum(self) -> typing.Iterable[Info]:
return self.registered.values()
def get(self, type: ServerType, subtype: str) -> typing.Optional[Info]:
return self.registered.get((type, subtype))
# Registering default subtypes (basically, ip unmanaged is the "global" one), any other will be registered by the providers
# I.e. "linuxapp" will be registered by the Linux Applications Provider
ServerSubType.manager().register(ServerType.UNMANAGED, 'ip', 'Unmanaged IP Server', False)

View File

@ -80,7 +80,7 @@ def getTunnelFromField(fld: ui.gui.ChoiceField) -> models.RegisteredServerGroup:
# Server group field
def serverGroupField(
kind: types.servers.ServerType = types.servers.ServerType.LEGACY, subkind: typing.Optional[str] = None
kind: types.servers.ServerType = types.servers.ServerType.UNMANAGED, subkind: typing.Optional[str] = None
) -> ui.gui.ChoiceField:
"""Returns a field to select a server group
@ -102,7 +102,7 @@ def serverGroupField(
def getServerGroupFromField(
fld: ui.gui.ChoiceField, type_: types.servers.ServerType = types.servers.ServerType.LEGACY
fld: ui.gui.ChoiceField, type_: types.servers.ServerType = types.servers.ServerType.UNMANAGED
) -> models.RegisteredServerGroup:
"""Returns a server group from a field
@ -114,7 +114,7 @@ def getServerGroupFromField(
def getServersFromServerGroupField(
fld: ui.gui.ChoiceField, type_: types.servers.ServerType = types.servers.ServerType.LEGACY
fld: ui.gui.ChoiceField, type_: types.servers.ServerType = types.servers.ServerType.UNMANAGED
) -> typing.List[models.RegisteredServer]:
"""Returns a list of servers from a server group field

View File

@ -115,7 +115,7 @@ class Migration(migrations.Migration):
),
("name", models.CharField(max_length=64, unique=True)),
("comments", models.CharField(default="", max_length=255)),
("type", models.IntegerField(default=uds.core.types.servers.ServerType["LEGACY"], db_index=True)),
("type", models.IntegerField(default=uds.core.types.servers.ServerType["UNMANAGED"], db_index=True)),
("subtype", models.CharField(db_index=True, default="", max_length=32)),
("host", models.CharField(default="", max_length=255)),
("port", models.IntegerField(default=0)),

View File

@ -68,7 +68,7 @@ class RegisteredServerGroup(UUIDModel, TaggingMixin):
# These are not the servers ports itself, and it depends on the kind of server
# For example, for tunnel server groups, has an internet address and port that will be used
# But for APP Servers, host and port are ununsed
type = models.IntegerField(default=types.servers.ServerType.LEGACY, db_index=True)
type = models.IntegerField(default=types.servers.ServerType.UNMANAGED, db_index=True)
subtype = models.CharField(
max_length=32, default='', db_index=True
) # Subkind of server, if any (I.E. LinuxDocker, RDS, etc..)