1
0
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:
Adolfo Gómez García
2025-07-26 02:07:58 +02:00
parent 19f7cda26c
commit ce9110b7ca
19 changed files with 173 additions and 118 deletions

View File

@@ -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:
"""

View File

@@ -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)

View File

@@ -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'])

View File

@@ -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': '',

View File

@@ -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 [
{

View File

@@ -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,
{

View File

@@ -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': '',

View File

@@ -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 [
{

View File

@@ -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'],
)

View File

@@ -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]:

View File

@@ -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'],
),

View File

@@ -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,
{

View File

@@ -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 [
{

View File

@@ -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 [
{

View File

@@ -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(

View File

@@ -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'],
),

View File

@@ -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

View File

@@ -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

View File

@@ -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[