mirror of
https://github.com/dkmstr/openuds.git
synced 2025-10-05 07:33:41 +03:00
Refactor GUI field handling to replace add_default_fields with default_fields for consistency
This commit is contained in:
@@ -94,7 +94,7 @@ class Accounts(ModelHandler[AccountItem]):
|
||||
}
|
||||
|
||||
def get_gui(self, type_: str) -> list[typing.Any]:
|
||||
return self.add_default_fields([], ['name', 'comments', 'tags'])
|
||||
return self.default_fields([], ['name', 'comments', 'tags'])
|
||||
|
||||
def timemark(self, item: 'Model') -> typing.Any:
|
||||
"""
|
||||
|
@@ -121,19 +121,20 @@ class Authenticators(ModelHandler[AuthenticatorItem]):
|
||||
# Not of my type
|
||||
return None
|
||||
|
||||
def get_gui(self, type_: str) -> list[typing.Any]:
|
||||
def get_gui(self, type_: str) -> list[types.ui.GuiElement]:
|
||||
try:
|
||||
auth_type = auths.factory().lookup(type_)
|
||||
if auth_type:
|
||||
# Create a new instance of the authenticator to access to its GUI
|
||||
with Environment.temporary_environment() as env:
|
||||
auth_instance = auth_type(env, None)
|
||||
field = self.add_default_fields(
|
||||
fields = self.default_fields(
|
||||
auth_instance.gui_description(),
|
||||
['name', 'comments', 'tags', 'priority', 'small_name', 'networks'],
|
||||
)
|
||||
|
||||
self.add_field(
|
||||
field,
|
||||
fields,
|
||||
{
|
||||
'name': 'state',
|
||||
'value': consts.auth.VISIBLE,
|
||||
@@ -154,7 +155,7 @@ class Authenticators(ModelHandler[AuthenticatorItem]):
|
||||
# If supports mfa, add MFA provider selector field
|
||||
if auth_type.provides_mfa():
|
||||
self.add_field(
|
||||
field,
|
||||
fields,
|
||||
{
|
||||
'name': 'mfa_id',
|
||||
'choices': [gui.choice_item('', str(_('None')))]
|
||||
@@ -168,7 +169,7 @@ class Authenticators(ModelHandler[AuthenticatorItem]):
|
||||
'tab': types.ui.Tab.MFA,
|
||||
},
|
||||
)
|
||||
return field
|
||||
return fields
|
||||
raise Exception() # Not found
|
||||
except Exception as e:
|
||||
logger.info('Type not found: %s', e)
|
||||
|
@@ -103,4 +103,4 @@ class Calendars(ModelHandler[CalendarItem]):
|
||||
}
|
||||
|
||||
def get_gui(self, type_: str) -> list[typing.Any]:
|
||||
return self.add_default_fields([], ['name', 'comments', 'tags'])
|
||||
return self.default_fields([], ['name', 'comments', 'tags'])
|
||||
|
@@ -93,7 +93,7 @@ class Images(ModelHandler[ImageItem]):
|
||||
|
||||
def get_gui(self, type_: str) -> list[typing.Any]:
|
||||
return self.add_field(
|
||||
self.add_default_fields([], ['name']),
|
||||
self.default_fields([], ['name']),
|
||||
{
|
||||
'name': 'data',
|
||||
'value': '',
|
||||
|
@@ -180,7 +180,7 @@ class MetaPools(ModelHandler[MetaPoolItem]):
|
||||
|
||||
# Gui related
|
||||
def get_gui(self, type_: str) -> list[typing.Any]:
|
||||
local_gui = self.add_default_fields([], ['name', 'comments', 'tags'])
|
||||
local_gui = self.default_fields([], ['name', 'comments', 'tags'])
|
||||
|
||||
for field in [
|
||||
{
|
||||
|
@@ -89,7 +89,7 @@ class MFA(ModelHandler[MFAItem]):
|
||||
with Environment.temporary_environment() as env:
|
||||
mfa = mfa_type(env, None)
|
||||
|
||||
local_gui = self.add_default_fields(mfa.gui_description(), ['name', 'comments', 'tags'])
|
||||
local_gui = self.default_fields(mfa.gui_description(), ['name', 'comments', 'tags'])
|
||||
self.add_field(
|
||||
local_gui,
|
||||
{
|
||||
|
@@ -97,7 +97,7 @@ class Networks(ModelHandler[NetworkItem]):
|
||||
|
||||
def get_gui(self, type_: str) -> list[typing.Any]:
|
||||
return self.add_field(
|
||||
self.add_default_fields([], ['name', 'tags']),
|
||||
self.default_fields([], ['name', 'tags']),
|
||||
{
|
||||
'name': 'net_string',
|
||||
'value': '',
|
||||
|
@@ -99,7 +99,7 @@ class Notifiers(ModelHandler[NotifierItem]):
|
||||
with Environment.temporary_environment() as env:
|
||||
notifier = notifier_type(env, None)
|
||||
|
||||
local_gui = self.add_default_fields(notifier.gui_description(), ['name', 'comments', 'tags'])
|
||||
local_gui = self.default_fields(notifier.gui_description(), ['name', 'comments', 'tags'])
|
||||
|
||||
for field in [
|
||||
{
|
||||
|
@@ -117,7 +117,7 @@ class OsManagers(ModelHandler[OsManagerItem]):
|
||||
with Environment.temporary_environment() as env:
|
||||
osmanager = osmanager_type(env, None)
|
||||
|
||||
return self.add_default_fields(
|
||||
return self.default_fields(
|
||||
osmanager.gui_description(),
|
||||
['name', 'comments', 'tags'],
|
||||
)
|
||||
|
@@ -149,7 +149,7 @@ class Providers(ModelHandler[ProviderItem]):
|
||||
if provider_type:
|
||||
with Environment.temporary_environment() as env:
|
||||
provider = provider_type(env, None)
|
||||
return self.add_default_fields(provider.gui_description(), ['name', 'comments', 'tags'])
|
||||
return self.default_fields(provider.gui_description(), ['name', 'comments', 'tags'])
|
||||
raise exceptions.rest.NotFound('Type not found!')
|
||||
|
||||
def allservices(self) -> typing.Generator[types.rest.ItemDictType, None, None]:
|
||||
|
@@ -498,7 +498,7 @@ class ServersGroups(ModelHandler[GroupItem ]):
|
||||
kind = _('Unmanaged')
|
||||
title = _('of type') + f' {subkind.upper()} {kind}'
|
||||
return self.add_field(
|
||||
self.add_default_fields(
|
||||
self.default_fields(
|
||||
[],
|
||||
['name', 'comments', 'tags'],
|
||||
),
|
||||
|
@@ -337,7 +337,7 @@ class Services(DetailHandler[ServiceItem]): # pylint: disable=too-many-public-m
|
||||
service = service_type(
|
||||
env, parent_instance
|
||||
) # Instantiate it so it has the opportunity to alter gui description based on parent
|
||||
local_gui = self.add_default_fields(service.gui_description(), ['name', 'comments', 'tags'])
|
||||
local_gui = self.default_fields(service.gui_description(), ['name', 'comments', 'tags'])
|
||||
self.add_field(
|
||||
local_gui,
|
||||
{
|
||||
|
@@ -96,7 +96,7 @@ class ServicesPoolGroups(ModelHandler[ServicePoolGroupItem]):
|
||||
|
||||
# Gui related
|
||||
def get_gui(self, type_: str) -> list[typing.Any]:
|
||||
local_gui = self.add_default_fields([], ['name', 'comments', 'priority'])
|
||||
local_gui = self.default_fields([], ['name', 'comments', 'priority'])
|
||||
|
||||
for field in [
|
||||
{
|
||||
|
@@ -322,7 +322,7 @@ class ServicesPools(ModelHandler[ServicePoolItem]):
|
||||
gettext('Create at least a service before creating a new service pool')
|
||||
)
|
||||
|
||||
g = self.add_default_fields([], ['name', 'comments', 'tags'])
|
||||
g = self.default_fields([], ['name', 'comments', 'tags'])
|
||||
|
||||
for f in [
|
||||
{
|
||||
|
@@ -110,7 +110,7 @@ class Transports(ModelHandler[TransportItem]):
|
||||
with Environment.temporary_environment() as env:
|
||||
transport = transport_type(env, None)
|
||||
|
||||
field = self.add_default_fields(
|
||||
field = self.default_fields(
|
||||
transport.gui_description(), ['name', 'comments', 'tags', 'priority', 'networks']
|
||||
)
|
||||
field = self.add_field(
|
||||
|
@@ -180,7 +180,7 @@ class Tunnels(ModelHandler):
|
||||
|
||||
def get_gui(self, type_: str) -> list[typing.Any]:
|
||||
return self.add_field(
|
||||
self.add_default_fields(
|
||||
self.default_fields(
|
||||
[],
|
||||
['name', 'comments', 'tags'],
|
||||
),
|
||||
|
@@ -133,113 +133,136 @@ class BaseModelHandler(Handler, typing.Generic[types.rest.T_Item]):
|
||||
gui.append(v)
|
||||
return gui
|
||||
|
||||
def add_default_fields(self, gui: list[typing.Any], flds: list[str]) -> list[typing.Any]:
|
||||
def append_field(
|
||||
self, gui_list: list[types.ui.GuiElement], field: types.ui.GuiElement
|
||||
) -> list[types.ui.GuiElement]:
|
||||
"""
|
||||
Appends a field to the gui description
|
||||
|
||||
Args:
|
||||
gui_list: List of GuiElement to append the field to
|
||||
field: Field to append
|
||||
|
||||
Returns:
|
||||
The updated gui list with the new field appended
|
||||
"""
|
||||
gui_list.append(field)
|
||||
return gui_list
|
||||
|
||||
def default_fields(self, gui: list[types.ui.GuiElement], flds: list[str]) -> list[types.ui.GuiElement]:
|
||||
"""
|
||||
Adds default fields (based in a list) to a "gui" description
|
||||
:param gui: Gui list where the "default" fielsds will be added
|
||||
:param flds: List of fields names requested to be added. Valid values are 'name', 'comments',
|
||||
'priority' and 'small_name', 'short_name', 'tags'
|
||||
"""
|
||||
if 'tags' in flds:
|
||||
self.add_field(
|
||||
gui,
|
||||
TRANS_FLDS: dict[str, list[types.ui.GuiElement]] = {
|
||||
'tags': [
|
||||
{
|
||||
'name': 'tags',
|
||||
'label': _('Tags'),
|
||||
'type': 'taglist',
|
||||
'tooltip': _('Tags for this element'),
|
||||
'order': 0 - 105,
|
||||
},
|
||||
)
|
||||
if 'name' in flds:
|
||||
self.add_field(
|
||||
gui,
|
||||
'gui': {
|
||||
'label': _('Tags'),
|
||||
'type': 'taglist',
|
||||
'tooltip': _('Tags for this element'),
|
||||
'order': 0 - 105,
|
||||
},
|
||||
}
|
||||
],
|
||||
'name': [
|
||||
{
|
||||
'name': 'name',
|
||||
'type': 'text',
|
||||
'required': True,
|
||||
'label': _('Name'),
|
||||
'length': 128,
|
||||
'tooltip': _('Name of this element'),
|
||||
'order': 0 - 100,
|
||||
},
|
||||
)
|
||||
if 'comments' in flds:
|
||||
self.add_field(
|
||||
gui,
|
||||
'gui': {
|
||||
'type': 'text',
|
||||
'required': True,
|
||||
'label': _('Name'),
|
||||
'length': 128,
|
||||
'tooltip': _('Name of this element'),
|
||||
'order': 0 - 100,
|
||||
},
|
||||
}
|
||||
],
|
||||
'comments': [
|
||||
{
|
||||
'name': 'comments',
|
||||
'label': _('Comments'),
|
||||
'type': 'text',
|
||||
'lines': 3,
|
||||
'tooltip': _('Comments for this element'),
|
||||
'length': 256,
|
||||
'order': 0 - 90,
|
||||
},
|
||||
)
|
||||
if 'priority' in flds:
|
||||
self.add_field(
|
||||
gui,
|
||||
'gui': {
|
||||
'label': _('Comments'),
|
||||
'type': 'text',
|
||||
'lines': 3,
|
||||
'tooltip': _('Comments for this element'),
|
||||
'length': 256,
|
||||
'order': 0 - 90,
|
||||
},
|
||||
}
|
||||
],
|
||||
'priority': [
|
||||
{
|
||||
'name': 'priority',
|
||||
'type': 'numeric',
|
||||
'label': _('Priority'),
|
||||
'tooltip': _('Selects the priority of this element (lower number means higher priority)'),
|
||||
'required': True,
|
||||
'value': 1,
|
||||
'length': 4,
|
||||
'order': 0 - 85,
|
||||
},
|
||||
)
|
||||
if 'small_name' in flds:
|
||||
self.add_field(
|
||||
gui,
|
||||
'gui': {
|
||||
'label': _('Priority'),
|
||||
'type': 'numeric',
|
||||
'required': True,
|
||||
'default': 1,
|
||||
'length': 4,
|
||||
'tooltip': _(
|
||||
'Selects the priority of this element (lower number means higher priority)'
|
||||
),
|
||||
'order': 0 - 85,
|
||||
},
|
||||
}
|
||||
],
|
||||
'small_name': [
|
||||
{
|
||||
'name': 'small_name',
|
||||
'type': 'text',
|
||||
'label': _('Label'),
|
||||
'tooltip': _('Label for this element'),
|
||||
'required': True,
|
||||
'length': 128,
|
||||
'order': 0 - 80,
|
||||
},
|
||||
)
|
||||
if 'networks' in flds:
|
||||
self.add_field(
|
||||
gui,
|
||||
{
|
||||
'name': 'net_filtering',
|
||||
'value': 'n',
|
||||
'choices': [
|
||||
{'id': 'n', 'text': _('No filtering')},
|
||||
{'id': 'a', 'text': _('Allow selected networks')},
|
||||
{'id': 'd', 'text': _('Deny selected networks')},
|
||||
],
|
||||
'label': _('Network Filtering'),
|
||||
'tooltip': _(
|
||||
'Type of network filtering. Use "Disabled" to disable origin check, "Allow" to only enable for selected networks or "Deny" to deny from selected networks'
|
||||
),
|
||||
'type': 'choice',
|
||||
'order': 100, # At end
|
||||
'tab': types.ui.Tab.ADVANCED,
|
||||
},
|
||||
)
|
||||
self.add_field(
|
||||
gui,
|
||||
'gui': {
|
||||
'label': _('Label'),
|
||||
'type': 'text',
|
||||
'required': True,
|
||||
'length': 128,
|
||||
'tooltip': _('Label for this element'),
|
||||
'order': 0 - 80,
|
||||
},
|
||||
}
|
||||
],
|
||||
'networks': [
|
||||
{
|
||||
'name': 'networks',
|
||||
'value': [],
|
||||
'choices': sorted(
|
||||
[{'id': x.uuid, 'text': x.name} for x in Network.objects.all()],
|
||||
key=lambda x: x['text'].lower(),
|
||||
),
|
||||
'label': _('Networks'),
|
||||
'tooltip': _('Networks associated. If No network selected, will mean "all networks"'),
|
||||
'type': 'multichoice',
|
||||
'order': 101,
|
||||
'tab': types.ui.Tab.ADVANCED,
|
||||
'gui': {
|
||||
'label': _('Networks'),
|
||||
'type': 'multichoice',
|
||||
'tooltip': _('Networks associated. If No network selected, will mean "all networks"'),
|
||||
'choices': sorted(
|
||||
[{'id': x.uuid, 'text': x.name} for x in Network.objects.all()],
|
||||
key=lambda x: x['text'].lower(),
|
||||
),
|
||||
'order': 101,
|
||||
'tab': types.ui.Tab.ADVANCED,
|
||||
},
|
||||
},
|
||||
)
|
||||
{
|
||||
'name': 'net_filtering',
|
||||
'gui': {
|
||||
'label': _('Network Filtering'),
|
||||
'type': 'choice', # Type of network filtering
|
||||
'default': 'n',
|
||||
'choices': [
|
||||
{'id': 'n', 'text': _('No filtering')},
|
||||
{'id': 'a', 'text': _('Allow selected networks')},
|
||||
{'id': 'd', 'text': _('Deny selected networks')},
|
||||
],
|
||||
'tooltip': _(
|
||||
'Type of network filtering. Use "Disabled" to disable origin check, "Allow" to only enable for selected networks or "Deny" to deny from selected networks'
|
||||
),
|
||||
'order': 100, # At end
|
||||
'tab': types.ui.Tab.ADVANCED,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
for i in flds:
|
||||
if i in TRANS_FLDS:
|
||||
for field in TRANS_FLDS[i]:
|
||||
gui = self.append_field(gui, field)
|
||||
|
||||
return gui
|
||||
|
||||
|
@@ -167,11 +167,33 @@ class FieldInfo:
|
||||
"""Returns a dict with all fields that are not None"""
|
||||
return {k: v for k, v in dataclasses.asdict(self).items() if v is not None}
|
||||
|
||||
class GuiDescription(typing.TypedDict):
|
||||
"""
|
||||
GuiDescription is a dictionary that describes a GUI element.
|
||||
It contains the name of the element, the GUI description, and the value.
|
||||
"""
|
||||
label: str
|
||||
tooltip: str
|
||||
order: int
|
||||
type: str
|
||||
readonly: typing.NotRequired[bool]
|
||||
default: typing.NotRequired[str|int|float|bool]
|
||||
required: typing.NotRequired[bool]
|
||||
length: typing.NotRequired[int]
|
||||
lines: typing.NotRequired[int]
|
||||
pattern: typing.NotRequired[str]
|
||||
tab: typing.NotRequired[str]
|
||||
choices: typing.NotRequired[list[ChoiceItem]]
|
||||
min_value: typing.NotRequired[int]
|
||||
max_value: typing.NotRequired[int]
|
||||
fills: typing.NotRequired[Filler]
|
||||
rows: typing.NotRequired[int]
|
||||
|
||||
|
||||
class GuiElement(typing.TypedDict):
|
||||
name: str
|
||||
gui: dict[str, list[dict[str, typing.Any]]]
|
||||
value: typing.Any
|
||||
value: typing.NotRequired[typing.Any]
|
||||
gui: GuiDescription
|
||||
|
||||
|
||||
# Row styles
|
||||
|
@@ -325,7 +325,7 @@ class gui:
|
||||
value=value,
|
||||
tab=tab,
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def field_name(self) -> str:
|
||||
"""
|
||||
@@ -389,7 +389,7 @@ class gui:
|
||||
"""
|
||||
self._field_info.value = value
|
||||
|
||||
def gui_description(self) -> dict[str, typing.Any]:
|
||||
def gui_description(self) -> types.ui.GuiDescription:
|
||||
"""
|
||||
Returns the dictionary with the description of this item.
|
||||
We copy it, cause we need to translate the label and tooltip fields
|
||||
@@ -400,12 +400,17 @@ class gui:
|
||||
for i in ('value', 'old_field_name'):
|
||||
if i in data:
|
||||
del data[i] # We don't want to send some values on gui_description
|
||||
# Translate label and tooltip
|
||||
data['label'] = gettext(data['label']) if data['label'] else ''
|
||||
data['tooltip'] = gettext(data['tooltip']) if data['tooltip'] else ''
|
||||
|
||||
# And, if tab is set, translate it too
|
||||
if 'tab' in data:
|
||||
data['tab'] = gettext(data['tab']) # Translates tab name
|
||||
data['default'] = self.default # We need to translate default value
|
||||
return data
|
||||
|
||||
data['default'] = self.default
|
||||
|
||||
return typing.cast(types.ui.GuiDescription, data)
|
||||
|
||||
@property
|
||||
def default(self) -> typing.Any:
|
||||
@@ -799,7 +804,7 @@ class gui:
|
||||
def value(self, value: datetime.date | str) -> None:
|
||||
self._set_value(value)
|
||||
|
||||
def gui_description(self) -> dict[str, typing.Any]:
|
||||
def gui_description(self) -> types.ui.GuiDescription:
|
||||
fldgui = super().gui_description()
|
||||
# Convert if needed value and default to string (YYYY-MM-DD)
|
||||
if 'default' in fldgui:
|
||||
@@ -1653,10 +1658,13 @@ class UserInterface(metaclass=UserInterfaceType):
|
||||
if internal_field_type not in FIELD_DECODERS:
|
||||
logger.warning('Field %s has no decoder', field_name)
|
||||
continue
|
||||
|
||||
|
||||
if field_type != internal_field_type.name:
|
||||
# Especial case for text fields converted to password fields
|
||||
if not (internal_field_type == types.ui.FieldType.PASSWORD and field_type == types.ui.FieldType.TEXT.name):
|
||||
if not (
|
||||
internal_field_type == types.ui.FieldType.PASSWORD
|
||||
and field_type == types.ui.FieldType.TEXT.name
|
||||
):
|
||||
logger.warning(
|
||||
'Field %s has different type than expected: %s != %s',
|
||||
field_name,
|
||||
@@ -1791,12 +1799,13 @@ def password_compat_field_decoder(value: str) -> str:
|
||||
"""
|
||||
Compatibility function to decode text fields converted to password fields
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
value = CryptoManager.manager().aes_decrypt(value.encode('utf8'), UDSK, True).decode()
|
||||
except Exception:
|
||||
pass
|
||||
return value
|
||||
|
||||
|
||||
# Dictionaries used to encode/decode fields to be stored on database
|
||||
FIELDS_ENCODERS: typing.Final[
|
||||
collections.abc.Mapping[
|
||||
|
Reference in New Issue
Block a user