1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-10-05 07:33:41 +03:00

Refactor table representation in REST methods

- Updated various REST methods to replace `table_title` and `table_fields` with a unified `table_info` structure.
- Enhanced the `table_info` to encapsulate title, fields, and row styles in a single object, improving code clarity and maintainability.
- Adjusted the `DetailHandler` and `ModelHandler` classes to accommodate the new `table_info` structure.
- Removed deprecated methods related to table title and fields, streamlining the codebase.
- Ensured backward compatibility by maintaining the existing functionality while transitioning to the new structure.
This commit is contained in:
Adolfo Gómez García
2025-07-30 01:38:14 +02:00
parent 088ab0cdc1
commit 968fca260f
23 changed files with 218 additions and 407 deletions

View File

@@ -73,8 +73,7 @@ class ActorTokens(ModelHandler[ActorTokenItem]):
model = Server
model_filter = {'type': types.servers.ServerType.ACTOR}
table_title = _('Actor tokens')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Actor tokens'))
.datetime('stamp', _('Date'))
.string('username', _('Issued by'))
@@ -89,6 +88,7 @@ class ActorTokens(ModelHandler[ActorTokenItem]):
.build()
)
# table_title = _('Actor tokens')
# xtable_fields = [
# # {'token': {'title': _('Token')}},
# {'stamp': {'title': _('Date'), 'type': 'datetime'}},

View File

@@ -83,8 +83,7 @@ class Authenticators(ModelHandler[AuthenticatorItem]):
detail = {'users': Users, 'groups': Groups}
save_fields = ['name', 'comments', 'tags', 'priority', 'small_name', 'mfa_id:_', 'state']
table_title = _('Authenticators')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Authenticators'))
.number(name='numeric_id', title=_('Id'), visible=True, width='1rem')
.icon(name='name', title=_('Name'), visible=True)
@@ -95,9 +94,11 @@ class Authenticators(ModelHandler[AuthenticatorItem]):
.number(name='users_count', title=_('Users'), width='1rem')
.string(name='mfa_name', title=_('MFA'))
.string(name='tags', title=_('tags'), visible=False)
.row_style(prefix='row-state-', field='state')
.build()
)
# table_title = _('Authenticators')
# xtable_fields = [
# {'numeric_id': {'title': _('Id'), 'visible': True}},
# {'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},

View File

@@ -65,8 +65,7 @@ class Images(ModelHandler[ImageItem]):
model = Image
save_fields = ['name', 'data']
table_title = _('Image Gallery')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Image Gallery'))
.image('thumb', _('Image'), width='96px')
.string('name', _('Name'))
@@ -74,6 +73,7 @@ class Images(ModelHandler[ImageItem]):
.build()
)
# table_title = _('Image Gallery')
# xtable_fields = [
# {
# 'thumb': {

View File

@@ -104,8 +104,7 @@ class MetaPools(ModelHandler[MetaPoolItem]):
'transport_grouping',
]
table_title = _('Meta Pools')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Meta Pools'))
.string(name='name', title=_('Name'))
.string(name='comments', title=_('Comments'))
@@ -121,13 +120,14 @@ class MetaPools(ModelHandler[MetaPoolItem]):
)
.number(name='user_services_count', title=_('User services'))
.number(name='user_services_in_preparation', title=_('In Preparation'))
.callback(name='visible', title=_('Visible'))
.string(name='pool_group_name', title=_('Pool Group'))
.boolean(name='visible', title=_('Visible'))
.string(name='pool_group_name', title=_('Pool Group'), width='16em')
.string(name='short_name', title=_('Label'))
.string(name='tags', title=_('tags'), visible=False)
.build()
)
# table_title = _('Meta Pools')
# xtable_fields = [
# {'name': {'title': _('Name')}},
# {'comments': {'title': _('Comments')}},
@@ -259,7 +259,7 @@ class MetaPools(ModelHandler[MetaPoolItem]):
label=gettext('Calendar access denied text'),
tooltip=gettext('Custom message to be shown to users if access is limited by calendar rules.'),
)
.add_multichoice(
.add_choice(
name='transport_grouping', # Transport Selection
label=gettext('Transport Selection'),
choices=[

View File

@@ -42,9 +42,10 @@ from uds.core import types
# from uds.models.user_service import UserService
# from uds.models.user import User
from uds.core.types.rest import TableInfo
from uds.core.types.states import State
from uds.core.util.model import process_uuid
from uds.core.util import log, ensure
from uds.core.util import log, ensure, ui as ui_utils
from uds.REST.model import DetailHandler
from .user_services import AssignedUserService, UserServiceItem
@@ -53,10 +54,12 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
class MetaItem(types.rest.BaseRestItem):
"""
Item type for a Meta Pool Member
"""
id: str
pool_id: str
pool_name: typing.NotRequired[str] # Optional, as it can be not present
@@ -67,11 +70,11 @@ class MetaItem(types.rest.BaseRestItem):
user_services_count: int
user_services_in_preparation: int
class MetaServicesPool(DetailHandler[MetaItem]):
"""
Processes the transports detail requests of a Service Pool
"""
@staticmethod
def as_dict(item: models.MetaPoolMember) -> 'MetaItem':
@@ -97,15 +100,16 @@ class MetaServicesPool(DetailHandler[MetaItem]):
logger.exception('err: %s', item)
raise self.invalid_item_response()
def get_title(self, parent: 'Model') -> str:
return _('Service pools')
def get_fields(self, parent: 'Model') -> list[typing.Any]:
return [
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}},
{'name': {'title': _('Service Pool name')}},
{'enabled': {'title': _('Enabled')}},
]
def get_table_info(self, parent: 'Model') -> types.rest.TableInfo:
parent = ensure.is_instance(parent, models.MetaPool)
return (
ui_utils.TableBuilder(_('Members of {0}').format(parent.name))
.string(name='name', title=_('Name'))
.string(name='comments', title=_('Comments'))
.number(name='priority', title=_('Priority'))
.string(name='enabled', title=_('Enabled'))
.build()
)
def save_item(self, parent: 'Model', item: typing.Optional[str]) -> typing.Any:
parent = ensure.is_instance(parent, models.MetaPool)
@@ -119,13 +123,13 @@ class MetaServicesPool(DetailHandler[MetaItem]):
if uuid is not None:
member = parent.members.get(uuid=uuid)
member.pool = pool
member.pool = pool
member.enabled = enabled
member.priority = priority
member.save()
else:
member = parent.members.create(pool=pool, priority=priority, enabled=enabled)
log.log(
parent,
types.log.LogLevel.INFO,
@@ -136,7 +140,6 @@ class MetaServicesPool(DetailHandler[MetaItem]):
return {'id': member.uuid}
def delete_item(self, parent: 'Model', item: str) -> None:
parent = ensure.is_instance(parent, models.MetaPool)
member = parent.members.get(uuid=process_uuid(self._args[0]))
@@ -179,10 +182,9 @@ class MetaAssignedService(DetailHandler[UserServiceItem]):
def get_items(self, parent: 'Model', item: typing.Optional[str]) -> types.rest.ItemsResult[UserServiceItem]:
parent = ensure.is_instance(parent, models.MetaPool)
def _assigned_userservices_for_pools() -> (
typing.Generator[
tuple[models.UserService, typing.Optional[dict[str, typing.Any]]], None, None
]
typing.Generator[tuple[models.UserService, typing.Optional[dict[str, typing.Any]]], None, None]
):
for m in parent.members.filter(enabled=True):
properties: dict[str, typing.Any] = {
@@ -221,35 +223,25 @@ class MetaAssignedService(DetailHandler[UserServiceItem]):
logger.exception('get_items')
raise self.invalid_item_response()
def get_title(self, parent: 'Model') -> str:
def get_table_info(self, parent: 'Model') -> TableInfo:
parent = ensure.is_instance(parent, models.MetaPool)
return _('Assigned services')
return (
ui_utils.TableBuilder(_('Assigned services to {0}').format(parent.name))
.datetime(name='creation_date', title=_('Creation date'))
.string(name='pool_name', title=_('Pool'))
.string(name='unique_id', title='Unique ID')
.string(name='ip', title=_('IP'))
.string(name='friendly_name', title=_('Friendly name'))
.dictionary(name='state', title=_('status'), dct=State.literals_dict())
.string(name='in_use', title=_('In Use'))
.string(name='source_host', title=_('Src Host'))
.string(name='source_ip', title=_('Src Ip'))
.string(name='owner', title=_('Owner'))
.string(name='actor_version', title=_('Actor version'))
.row_style(prefix='row-state-', field='state')
.build()
)
def get_fields(self, parent: 'Model') -> list[typing.Any]:
parent = ensure.is_instance(parent, models.MetaPool)
return [
{'creation_date': {'title': _('Creation date'), 'type': 'datetime'}},
{'pool_name': {'title': _('Pool')}},
{'unique_id': {'title': 'Unique ID'}},
{'ip': {'title': _('IP')}},
{'friendly_name': {'title': _('Friendly name')}},
{
'state': {
'title': _('status'),
'type': 'dict',
'dict': State.literals_dict(),
}
},
{'in_use': {'title': _('In Use')}},
{'source_host': {'title': _('Src Host')}},
{'source_ip': {'title': _('Src Ip')}},
{'owner': {'title': _('Owner')}},
{'actor_version': {'title': _('Actor version')}},
]
def get_row_style(self, parent: 'Model') -> types.ui.RowStyleInfo:
return types.ui.RowStyleInfo(prefix='row-state-', field='state')
def get_logs(self, parent: 'Model', item: str) -> list[typing.Any]:
parent = ensure.is_instance(parent, models.MetaPool)
try:
@@ -270,7 +262,9 @@ class MetaAssignedService(DetailHandler[UserServiceItem]):
self._user.pretty_name,
)
else:
log_str = 'Deleted cached service {} by {}'.format(userservice.friendly_name, self._user.pretty_name)
log_str = 'Deleted cached service {} by {}'.format(
userservice.friendly_name, self._user.pretty_name
)
if userservice.state in (State.USABLE, State.REMOVING):
userservice.release()
@@ -294,7 +288,9 @@ class MetaAssignedService(DetailHandler[UserServiceItem]):
user = models.User.objects.get(uuid=process_uuid(fields['user_id']))
log_str = 'Changing ownership of service from {} to {} by {}'.format(
userservice.user.pretty_name if userservice.user else 'unknown', user.pretty_name, self._user.pretty_name
userservice.user.pretty_name if userservice.user else 'unknown',
user.pretty_name,
self._user.pretty_name,
)
# If there is another service that has this same owner, raise an exception
@@ -314,5 +310,5 @@ class MetaAssignedService(DetailHandler[UserServiceItem]):
# Log change
log.log(parent, types.log.LogLevel.INFO, log_str, types.log.LogSource.ADMIN)
return {'id': userservice.uuid}

View File

@@ -68,9 +68,7 @@ class MFA(ModelHandler[MFAItem]):
model = models.MFA
save_fields = ['name', 'comments', 'tags', 'remember_device', 'validity']
table_title = _('Multi Factor Authentication')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Multi Factor Authentication'))
.icon(name='name', title=_('Name'), visible=True)
.string(name='type_name', title=_('Type'))
@@ -78,7 +76,8 @@ class MFA(ModelHandler[MFAItem]):
.string(name='tags', title=_('tags'), visible=False)
.build()
)
# table_title = _('Multi Factor Authentication')
# xtable_fields = [
# {'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},
# {'type_name': {'title': _('Type')}},

View File

@@ -68,8 +68,7 @@ class Networks(ModelHandler[NetworkItem]):
model = Network
save_fields = ['name', 'net_string', 'tags']
table_title = _('Networks')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Networks'))
.string('name', _('Name'))
.string('net_string', _('Range'))
@@ -79,6 +78,7 @@ class Networks(ModelHandler[NetworkItem]):
.build()
)
# table_title = _('Networks')
# xtable_fields = [
# {
# 'name': {

View File

@@ -77,17 +77,17 @@ class Notifiers(ModelHandler[NotifierItem]):
'enabled',
]
table_title = _('Notifiers')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Notifiers'))
.icon(name='name', title=_('Name'))
.string(name='type_name', title=_('Type'))
.string(name='level', title=_('Level'))
.callback(name='enabled', title=_('Enabled'))
.boolean(name='enabled', title=_('Enabled'))
.string(name='comments', title=_('Comments'))
.string(name='tags', title=_('Tags'), visible=False)
).build()
# table_title = _('Notifiers')
# xtable_fields = [
# {'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},
# {'type_name': {'title': _('Type')}},

View File

@@ -65,8 +65,7 @@ class OsManagers(ModelHandler[OsManagerItem]):
model = OSManager
save_fields = ['name', 'comments', 'tags']
table_title = _('OS Managers')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('OS Managers'))
.icon(name='name', title=_('Name'))
.string(name='type_name', title=_('Type'))
@@ -76,6 +75,7 @@ class OsManagers(ModelHandler[OsManagerItem]):
.build()
)
# table_title = _('OS Managers')
# xtable_fields = [
# {'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},
# {'type_name': {'title': _('Type')}},

View File

@@ -87,18 +87,18 @@ class Providers(ModelHandler[ProviderItem]):
save_fields = ['name', 'comments', 'tags']
table_title = _('Service providers')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Service providers'))
.icon(name='name', title=_('Name'))
.string(name='type_name', title=_('Type'))
.string(name='comments', title=_('Comments'))
.number(name='services_count', title=_('Services'), width='6em')
.number(name='user_services_count', title=_('User Services'), width='6em')
.number(name='services_count', title=_('Services'))
.number(name='user_services_count', title=_('User Services'))
.string(name='tags', title=_('Tags'), visible=False)
.row_style(prefix='row-maintenance-', field='maintenance_mode')
).build()
# table_title = _('Service providers')
# Table info fields
# xtable_fields = [
# {'name': {'title': _('Name'), 'type': 'iconType'}},
@@ -110,7 +110,7 @@ class Providers(ModelHandler[ProviderItem]):
# {'tags': {'title': _('tags'), 'visible': False}},
# ]
# Field from where to get "class" and prefix for that class, so this will generate "row-state-A, row-state-X, ....
table_row_style = types.ui.RowStyleInfo(prefix='row-maintenance-', field='maintenance_mode')
# table_row_style = types.rest.RowStyleInfo(prefix='row-maintenance-', field='maintenance_mode')
def item_as_dict(self, item: 'Model') -> ProviderItem:
item = ensure.is_instance(item, Provider)

View File

@@ -76,16 +76,17 @@ class Reports(model.BaseModelHandler[ReportItem]):
min_access_role = consts.UserRole.ADMIN
table_title = _('Available reports')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Available reports'))
.string(name='group', title=_('Group'), visible=True)
.string(name='name', title=_('Name'), visible=True)
.string(name='description', title=_('Description'), visible=True)
.string(name='mime_type', title=_('Generates'), visible=True)
.row_style(prefix='row-state-', field='state')
.build()
)
# table_title = _('Available reports')
# xtable_fields = [
# {'group': {'title': _('Group')}},
# {'name': {'title': _('Name')}},
@@ -93,7 +94,6 @@ class Reports(model.BaseModelHandler[ReportItem]):
# {'mime_type': {'title': _('Generates')}},
# ]
# Field from where to get "class" and prefix for that class, so this will generate "row-state-A, row-state-X, ....
table_row_style = types.ui.RowStyleInfo(prefix='row-state-', field='state')
def _locate_report(
self, uuid: str, values: typing.Optional[typing.Dict[str, typing.Any]] = None
@@ -126,9 +126,7 @@ class Reports(model.BaseModelHandler[ReportItem]):
((consts.rest.OVERVIEW,), lambda: list(self.get_items())),
(
(consts.rest.TABLEINFO,),
lambda: self.table_info(
str(self.table_title), self.table_fields, self.table_row_style
).as_dict(),
lambda: self.table_info.as_dict(),
),
((consts.rest.GUI, '<report>'), report_gui),
)

View File

@@ -38,6 +38,7 @@ from django.utils.translation import gettext_lazy as _
from uds import models
from uds.core import consts, types
from uds.core.types.rest import TableInfo
from uds.core.util import net, permissions, ensure, ui as ui_utils
from uds.core.util.model import sql_now, process_uuid
from uds.core.exceptions.rest import NotFound, RequestError
@@ -77,8 +78,7 @@ class ServersTokens(ModelHandler[TokenItem]):
path = 'servers'
name = 'tokens'
table_title = _('Registered Servers')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Registered Servers'))
.string(name='hostname', title=_('Hostname'), visible=True)
.string(name='ip', title=_('IP'), visible=True)
@@ -89,7 +89,8 @@ class ServersTokens(ModelHandler[TokenItem]):
.datetime(name='stamp', title=_('Date'), visible=True)
.build()
)
# table_title = _('Registered Servers')
# xtable_fields = [
# {'hostname': {'title': _('Hostname')}},
# {'ip': {'title': _('IP')}},
@@ -181,64 +182,26 @@ class ServersServers(DetailHandler[ServerItem]):
logger.exception('REST servers')
raise self.invalid_item_response() from e
def get_title(self, parent: 'Model') -> str:
def get_table_info(self, parent: 'Model') -> TableInfo:
parent = ensure.is_instance(parent, models.ServerGroup)
try:
return (_('Servers of {0}')).format(parent.name)
except Exception:
return str(_('Servers'))
def get_fields(self, parent: 'Model') -> list[types.rest.TableField]:
parent = ensure.is_instance(parent, models.ServerGroup)
fields = (
ui_utils.TableBuilder(_('Servers'))
table_info = (
ui_utils.TableBuilder(_('Servers of {0}').format(parent.name))
.string(name='hostname', title=_('Hostname'))
.string(name='ip', title=_('Ip'))
.string(name='mac', title=_('Mac'))
)
if parent.is_managed():
fields.string(name='listen_port', title=_('Port'))
return fields.dictionary(
name='maintenance_mode',
title=_('State'),
dct={True: _('Maintenance'), False: _('Normal')},
).build()
table_info.string(name='listen_port', title=_('Port'))
# return (
# [
# {
# 'hostname': {
# 'title': _('Hostname'),
# }
# },
# {'ip': {'title': _('Ip')}},
# ] # If not managed, we can show mac, else listen port (related to UDS Server)
# + (
# [
# {'mac': {'title': _('Mac')}},
# ]
# if not parent.is_managed()
# else [
# {'mac': {'title': _('Mac')}},
# {'listen_port': {'title': _('Port')}},
# ]
# )
# + [
# {
# 'maintenance_mode': {
# 'title': _('State'),
# 'type': 'dict',
# 'dict': {True: _('Maintenance'), False: _('Normal')},
# }
# },
# ]
# )
def get_row_style(self, parent: 'Model') -> types.ui.RowStyleInfo:
return types.ui.RowStyleInfo(prefix='row-maintenance-', field='maintenance_mode')
return (
table_info.dictionary(
name='maintenance_mode',
title=_('State'),
dct={True: _('Maintenance'), False: _('Normal')},
)
.row_style(prefix='row-maintenance-', field='maintenance_mode')
.build()
)
def get_gui(self, parent: 'Model', for_type: str = '') -> list[types.ui.GuiElement]:
parent = ensure.is_instance(parent, models.ServerGroup)
@@ -473,9 +436,8 @@ class ServersGroups(ModelHandler[GroupItem]):
name = 'groups'
save_fields = ['name', 'comments', 'type', 'tags'] # Subtype is appended on pre_save
table_title = _('Servers Groups')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Servers Groups'))
.string(name='name', title=_('Name'), visible=True)
.string(name='comments', title=_('Comments'))
@@ -486,7 +448,8 @@ class ServersGroups(ModelHandler[GroupItem]):
.string(name='tags', title=_('tags'), visible=False)
.build()
)
# table_title = _('Servers Groups')
# xtable_fields = [
# {'name': {'title': _('Name')}},
# {'comments': {'title': _('Comments')}},

View File

@@ -40,6 +40,7 @@ from uds import models
from uds.core import exceptions, types
import uds.core.types.permissions
from uds.core.types.rest import TableInfo
from uds.core.util import log, permissions, ensure, ui as ui_utils
from uds.core.util.model import process_uuid
from uds.core.environment import Environment
@@ -163,9 +164,6 @@ class Services(DetailHandler[ServiceItem]): # pylint: disable=too-many-public-m
logger.error('Error getting services for %s: %s', parent, e)
raise self.invalid_item_response(repr(e)) from e
def get_row_style(self, parent: 'Model') -> types.ui.RowStyleInfo:
return types.ui.RowStyleInfo(prefix='row-maintenance-', field='maintenance_mode')
def _delete_incomplete_service(self, service: models.Service) -> None:
"""
Deletes a service if it is needed to (that is, if it is not None) and silently catch any exception of this operation
@@ -261,34 +259,27 @@ class Services(DetailHandler[ServiceItem]): # pylint: disable=too-many-public-m
raise exceptions.rest.RequestError('Item has associated deployed services')
def get_title(self, parent: 'Model') -> str:
def get_table_info(self, parent: 'Model') -> TableInfo:
parent = ensure.is_instance(parent, models.Provider)
try:
return _('Services of {}').format(parent.name)
except Exception:
return _('Current services')
def get_fields(self, parent: 'Model') -> list[typing.Any]:
return [
{'name': {'title': _('Service name'), 'visible': True, 'type': 'iconType'}},
{'comments': {'title': _('Comments')}},
{'type_name': {'title': _('Type')}},
{
'deployed_services_count': {
'title': _('Services Pools'),
'type': 'numeric',
}
},
{'user_services_count': {'title': _('User services'), 'type': 'numeric'}},
{
'max_services_count_type': {
'title': _('Max services count type'),
'type': 'dict',
'dict': {'0': _('Standard'), '1': _('Conservative')},
return (
ui_utils.TableBuilder(_('Services of {0}').format(parent.name))
.icon(name='name', title=_('Name'))
.string(name='type_name', title=_('Type'))
.string(name='comments', title=_('Comments'))
.number(name='deployed_services_count', title=_('Services Pools'), width='12em')
.number(name='user_services_count', title=_('User Services'), width='12em')
.dictionary(
name='max_services_count_type',
title=_('Counting method'),
dct={
types.services.ServicesCountingType.STANDARD: _('Standard'),
types.services.ServicesCountingType.CONSERVATIVE: _('Conservative'),
},
},
{'tags': {'title': _('tags'), 'visible': False}},
]
)
.string(name='tags', title=_('Tags'), visible=False)
.row_style(prefix='row-maintenance-', field='maintenance_mode')
.build()
)
def get_types(self, parent: 'Model', for_type: typing.Optional[str]) -> list[types.rest.TypeInfo]:

View File

@@ -65,9 +65,8 @@ class ServicesPoolGroups(ModelHandler[ServicePoolGroupItem]):
model = ServicePoolGroup
save_fields = ['name', 'comments', 'image_id', 'priority']
table_title = _('Services Pool Groups')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Services Pool Groups'))
.number(name='priority', title=_('Priority'), width='6em')
.image(name='thumb', title=_('Image'), width='96px')
@@ -76,6 +75,7 @@ class ServicesPoolGroups(ModelHandler[ServicePoolGroupItem]):
.build()
)
# table_title = _('Services Pool Groups')
# xtable_fields = [
# {'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}},
# {

View File

@@ -147,19 +147,19 @@ class ServicesPools(ModelHandler[ServicePoolItem]):
remove_fields = ['osmanager_id', 'service_id']
table_title = _('Service Pools')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Service Pools'))
.string(name='name', title=_('Name'))
.dictionary(name='state', title=_('Status'), dct=State.literals_dict())
.number(name='user_services_count', title=_('User services'))
.number(name='user_services_in_preparation', title=_('In Preparation'))
.string(name='usage', title=_('Usage'))
.callback(name='visible', title=_('Visible'))
.callback(name='show_transports', title=_('Shows transports'))
.boolean(name='visible', title=_('Visible'))
.boolean(name='show_transports', title=_('Shows transports'))
.string(name='pool_group_name', title=_('Pool group'))
.string(name='parent', title=_('Parent service'))
.string(name='tags', title=_('tags'))
.row_style(prefix='row-state-', field='state')
.build()
)
@@ -177,7 +177,6 @@ class ServicesPools(ModelHandler[ServicePoolItem]):
# {'tags': {'title': _('tags'), 'visible': False}},
# ]
# Field from where to get "class" and prefix for that class, so this will generate "row-state-A, row-state-X, ....
table_row_style = types.ui.RowStyleInfo(prefix='row-state-', field='state')
custom_methods = [
types.rest.ModelCustomMethod('set_fallback_access', True),

View File

@@ -42,7 +42,7 @@ from uds.models import UserService, Provider
from uds.core.types.states import State
from uds.core.util.model import process_uuid
from uds.REST.model import DetailHandler
from uds.core.util import ensure
from uds.core.util import ensure, ui as ui_utils
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
@@ -132,25 +132,22 @@ class ServicesUsage(DetailHandler[ServicesUsageItem]):
logger.exception('get_items')
raise self.invalid_item_response()
def get_title(self, parent: 'Model') -> str:
return _('Services Usage')
def get_fields(self, parent: 'Model') -> list[typing.Any]:
return [
# {'creation_date': {'title': _('Creation date'), 'type': 'datetime'}},
{'state_date': {'title': _('Access'), 'type': 'datetime'}},
{'owner': {'title': _('Owner')}},
{'service': {'title': _('Service')}},
{'pool': {'title': _('Pool')}},
{'unique_id': {'title': 'Unique ID'}},
{'ip': {'title': _('IP')}},
{'friendly_name': {'title': _('Friendly name')}},
{'source_ip': {'title': _('Src Ip')}},
{'source_host': {'title': _('Src Host')}},
]
def get_row_style(self, parent: 'Model') -> types.ui.RowStyleInfo:
return types.ui.RowStyleInfo(prefix='row-state-', field='state')
def get_table_info(self, parent: 'Model') -> types.rest.TableInfo:
parent = ensure.is_instance(parent, Provider)
return (
ui_utils.TableBuilder(_('Services Usage'))
.datetime(name='state_date', title=_('Access'))
.string(name='owner', title=_('Owner'))
.string(name='service', title=_('Service'))
.string(name='pool', title=_('Pool'))
.string(name='unique_id', title='Unique ID')
.string(name='ip', title=_('IP'))
.string(name='friendly_name', title=_('Friendly name'))
.string(name='source_ip', title=_('Src Ip'))
.string(name='source_host', title=_('Src Host'))
.row_style(prefix='row-state-', field='state')
.build()
)
def delete_item(self, parent: 'Model', item: str) -> None:
parent = ensure.is_instance(parent, Provider)

View File

@@ -82,8 +82,7 @@ class Transports(ModelHandler[TransportItem]):
'label',
]
table_title = _('Transports')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Transports'))
.number(name='priority', title=_('Priority'), width='6em')
.icon(name='name', title=_('Name'))

View File

@@ -37,6 +37,7 @@ from django.utils.translation import gettext_lazy as _
import uds.core.types.permissions
from uds.core import exceptions, types, consts
from uds.core.types.rest import TableInfo
from uds.core.util import permissions, validators, ensure, ui as ui_utils
from uds.core.util.model import process_uuid
from uds import models
@@ -92,34 +93,20 @@ class TunnelServers(DetailHandler[TunnelServerItem]):
logger.exception('REST groups')
raise self.invalid_item_response() from e
def get_title(self, parent: 'Model') -> str:
def get_table_info(self, parent: 'Model') -> TableInfo:
parent = ensure.is_instance(parent, models.ServerGroup)
try:
return _('Servers of {0}').format(parent.name)
except Exception:
return gettext('Servers')
def get_fields(self, parent: 'Model') -> list[typing.Any]:
parent = ensure.is_instance(parent, models.ServerGroup)
return [
{
'hostname': {
'title': _('Hostname'),
}
},
{'ip': {'title': _('Ip')}},
{'mac': {'title': _('Mac')}},
{
'maintenance_mode': {
'title': _('State'),
'type': 'dict',
'dict': {True: _('Maintenance'), False: _('Normal')},
}
},
]
def get_row_style(self, parent: 'Model') -> types.ui.RowStyleInfo:
return types.ui.RowStyleInfo(prefix='row-maintenance-', field='maintenance_mode')
return (
ui_utils.TableBuilder(_('Servers of {0}').format(parent.name))
.string(name='hostname', title=_('Hostname'))
.string(name='ip', title=_('Ip'))
.string(name='mac', title=_('Mac'))
.dictionary(
name='maintenance',
title=_('State'),
dct={True: _('Maintenance'), False: _('Normal')},
)
.row_style(prefix='row-maintenance-', field='maintenance')
).build()
# Cannot save a tunnel server, it's not editable...
@@ -172,8 +159,7 @@ class Tunnels(ModelHandler[TunnelItem]):
detail = {'servers': TunnelServers}
save_fields = ['name', 'comments', 'host:', 'port:0']
table_title = _('Tunnels')
table_fields = (
table_info = (
ui_utils.TableBuilder(_('Tunnels'))
.icon(name='name', title=_('Name'))
.string(name='comments', title=_('Comments'))
@@ -184,6 +170,7 @@ class Tunnels(ModelHandler[TunnelItem]):
.build()
)
# table_title = _('Tunnels')
# xtable_fields = [
# {'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},
# {'comments': {'title': _('Comments')}},

View File

@@ -41,8 +41,9 @@ import uds.core.types.permissions
from uds import models
from uds.core import exceptions, types
from uds.core.managers.userservice import UserServiceManager
from uds.core.types.rest import TableInfo
from uds.core.types.states import State
from uds.core.util import ensure, log, permissions
from uds.core.util import ensure, log, permissions, ui as ui_utils
from uds.core.util.model import process_uuid
from uds.REST.model import DetailHandler
@@ -181,45 +182,27 @@ class AssignedUserService(DetailHandler[UserServiceItem]):
logger.exception('get_items')
raise self.invalid_item_response() from e
def get_title(self, parent: 'Model') -> str:
return _('Assigned services')
def get_fields(self, parent: 'Model') -> list[typing.Any]:
def get_table_info(self, parent: 'Model') -> types.rest.TableInfo:
parent = ensure.is_instance(parent, models.ServicePool)
# Revision is only shown if publication type is not None
return (
[
{'creation_date': {'title': _('Creation date'), 'type': 'datetime'}},
]
+ (
[
{'revision': {'title': _('Revision')}},
]
if parent.service.get_type().publication_type is not None
else []
)
+ [
{'unique_id': {'title': 'Unique ID'}},
{'ip': {'title': _('IP')}},
{'friendly_name': {'title': _('Friendly name')}},
{
'state': {
'title': _('status'),
'type': 'dict',
'dict': State.literals_dict(),
}
},
{'state_date': {'title': _('Status date'), 'type': 'datetime'}},
{'in_use': {'title': _('In Use')}},
{'source_host': {'title': _('Src Host')}},
{'source_ip': {'title': _('Src Ip')}},
{'owner': {'title': _('Owner')}},
{'actor_version': {'title': _('Actor version')}},
]
table_info = ui_utils.TableBuilder(_('Assigned Services')).datetime(
name='creation_date', title=_('Creation date')
)
if parent.service.get_type().publication_type is not None:
table_info.string(name='revision', title=_('Revision'))
def get_row_style(self, parent: 'Model') -> types.ui.RowStyleInfo:
return types.ui.RowStyleInfo(prefix='row-state-', field='state')
return (
table_info.string(name='unique_id', title='Unique ID')
.string(name='ip', title=_('IP'))
.string(name='friendly_name', title=_('Friendly name'))
.dictionary(name='state', title=_('status'), dct=State.literals_dict())
.datetime(name='state_date', title=_('Status date'))
.string(name='in_use', title=_('In Use'))
.string(name='source_host', title=_('Src Host'))
.string(name='source_ip', title=_('Src Ip'))
.string(name='owner', title=_('Owner'))
.string(name='actor_version', title=_('Actor version'))
.row_style(prefix='row-state-', field='state')
).build()
def get_logs(self, parent: 'Model', item: str) -> list[typing.Any]:
parent = ensure.is_instance(parent, models.ServicePool)
@@ -402,37 +385,16 @@ class Groups(DetailHandler[GroupItem]):
for group in typing.cast(collections.abc.Iterable[models.Group], parent.assignedGroups.all())
]
def get_title(self, parent: 'Model') -> str:
def get_table_info(self, parent: 'Model') -> TableInfo:
parent = typing.cast(typing.Union['models.ServicePool', 'models.MetaPool'], parent)
return _('Assigned groups')
def get_fields(self, parent: 'Model') -> list[typing.Any]:
return [
# Note that this field is "self generated" on client table
{
'group_name': {
'title': _('Name'),
'type': 'alphanumeric',
}
},
{'comments': {'title': _('comments')}},
{
'type': {
'title': _('Type'),
# Alphanumeric, default is alphanumeric
}
},
{
'state': {
'title': _('State'),
'type': 'dict',
'dict': State.literals_dict(),
}
},
]
def get_row_style(self, parent: 'Model') -> types.ui.RowStyleInfo:
return types.ui.RowStyleInfo(prefix='row-state-', field='state')
return (
ui_utils.TableBuilder(_('Assigned groups'))
.string(name='group_name', title=_('Name'))
.string(name='comments', title=_('comments'))
.dictionary(name='state', title=_('State'), dct=State.literals_dict())
.row_style(prefix='row-state-', field='state')
.build()
)
def save_item(self, parent: 'Model', item: typing.Optional[str]) -> typing.Any:
parent = typing.cast(typing.Union['models.ServicePool', 'models.MetaPool'], parent)
@@ -613,26 +575,16 @@ class Publications(DetailHandler[PublicationItem]):
for i in parent.publications.all()
]
def get_title(self, parent: 'Model') -> str:
def get_table_info(self, parent: 'Model') -> TableInfo:
parent = ensure.is_instance(parent, models.ServicePool)
return _('Publications')
def get_fields(self, parent: 'Model') -> list[typing.Any]:
return [
{'revision': {'title': _('Revision'), 'type': 'numeric', 'width': '6em'}},
{'publish_date': {'title': _('Publish date'), 'type': 'datetime'}},
{
'state': {
'title': _('State'),
'type': 'dict',
'dict': State.literals_dict(),
}
},
{'reason': {'title': _('Reason')}},
]
def get_row_style(self, parent: 'Model') -> types.ui.RowStyleInfo:
return types.ui.RowStyleInfo(prefix='row-state-', field='state')
return (
ui_utils.TableBuilder(_('Publications'))
.number(name='revision', title=_('Revision'), width='6em')
.datetime(name='publish_date', title=_('Publish date'))
.dictionary(name='state', title=_('State'), dct=State.literals_dict())
.string(name='reason', title=_('Reason'))
.row_style(prefix='row-state-', field='state')
).build()
class ChangelogItem(types.rest.BaseRestItem):

View File

@@ -41,7 +41,7 @@ from django.core.exceptions import ValidationError
from uds.core.types.states import State
from uds.core.auths.user import User as AUser
from uds.core.util import log, ensure
from uds.core.util import log, ensure, ui as ui_utils
from uds.core.util.rest.tools import as_typed_dict
from uds.core.util.model import process_uuid, sql_stamp_seconds
from uds.models import Authenticator, User, Group, ServicePool
@@ -132,39 +132,20 @@ class Users(DetailHandler[UserItem]):
# User not found
raise self.invalid_item_response() from e
def get_title(self, parent: 'Model') -> str:
try:
return _('Users of {0}').format(
Authenticator.objects.get(uuid=process_uuid(self._kwargs['parent_id'])).name
def get_table_info(self, parent: 'Model') -> types.rest.TableInfo:
parent = ensure.is_instance(parent, Authenticator)
return (
ui_utils.TableBuilder(_('Users of {0}').format(parent.name))
.icon(name='name', title=_('Username'), visible=True)
.string(name='role', title=_('Role'))
.string(name='real_name', title=_('Name'))
.string(name='comments', title=_('Comments'))
.dictionary(
name='state', title=_('Status'), dct={State.ACTIVE: _('Enabled'), State.INACTIVE: _('Disabled')}
)
except Exception:
return _('Current users')
def get_fields(self, parent: 'Model') -> list[typing.Any]:
return [
{
'name': {
'title': _('Username'),
'visible': True,
'type': 'icon',
'icon': 'fa fa-user text-success',
}
},
{'role': {'title': _('Role')}},
{'real_name': {'title': _('Name')}},
{'comments': {'title': _('Comments')}},
{
'state': {
'title': _('state'),
'type': 'dict',
'dict': {State.ACTIVE: _('Enabled'), State.INACTIVE: _('Disabled')},
}
},
{'last_access': {'title': _('Last access'), 'type': 'datetime'}},
]
def get_row_style(self, parent: 'Model') -> types.ui.RowStyleInfo:
return types.ui.RowStyleInfo(prefix='row-state-', field='state')
.datetime(name='last_access', title=_('Last access'))
.row_style(prefix='row-state-', field='state')
).build()
def get_logs(self, parent: 'Model', item: str) -> list[typing.Any]:
parent = ensure.is_instance(parent, Authenticator)

View File

@@ -90,23 +90,6 @@ class BaseModelHandler(Handler, typing.Generic[types.rest.T_Item]):
group=getattr(type_, 'group', None),
)
def table_info(
self,
title: str,
fields: list[types.rest.TableField],
row_style: types.ui.RowStyleInfo,
subtitle: str | None = None,
) -> types.rest.TableInfo:
"""
Returns a dict containing the table fields description
"""
return types.rest.TableInfo(
title=title,
fields=fields,
row_style=row_style,
subtitle=subtitle,
)
def fields_from_params(
self, fields_list: list[str], *, defaults: dict[str, typing.Any] | None = None
) -> dict[str, typing.Any]:

View File

@@ -163,11 +163,7 @@ class DetailHandler(BaseModelHandler[types.rest.T_Item], typing.Generic[types.re
raise self.invalid_request_response()
case consts.rest.TABLEINFO:
if num_args == 1:
return self.table_info(
self.get_title(parent),
self.get_fields(parent),
self.get_row_style(parent),
).as_dict()
return self.get_table_info(parent).as_dict()
raise self.invalid_request_response()
case consts.rest.GUI:
if num_args in (1, 2):
@@ -291,37 +287,13 @@ class DetailHandler(BaseModelHandler[types.rest.T_Item], typing.Generic[types.re
"""
raise self.invalid_request_response()
# A detail handler must also return title & fields for tables
def get_title(self, parent: models.Model) -> str: # pylint: disable=no-self-use
def get_table_info(self, parent: models.Model) -> types.rest.TableInfo:
"""
A "generic" title for a view based on this detail.
If not overridden, defaults to ''
Returns the table info for this detail, that is the title, fields and row style
:param parent: Parent object
:return: Expected to return an string that is the "title".
:return: TableInfo object with title, fields and row style
"""
return ''
def get_fields(self, parent: models.Model) -> list[types.rest.TableField]:
"""
A "generic" list of fields for a view based on this detail.
If not overridden, defaults to emty list
:param parent: Parent object
:return: Expected to return a list of fields
"""
return []
def get_row_style(self, parent: models.Model) -> types.ui.RowStyleInfo:
"""
A "generic" row style based on row field content.
If not overridden, defaults to {}
Args:
parent (models.Model): Parent object
Return:
dict[str, typing.Any]: A dictionary with 'field' and 'prefix' keys
"""
return types.ui.RowStyleInfo.null()
return types.rest.TableInfo.null()
def get_gui(self, parent: models.Model, for_type: str) -> list[types.ui.GuiElement]:
"""

View File

@@ -103,11 +103,9 @@ class ModelHandler(BaseModelHandler[types.rest.T_Item], typing.Generic[types.res
# Put removable fields before updating
remove_fields: typing.ClassVar[list[str]] = []
# Table info needed fields and title
table_fields: typing.ClassVar[list[types.rest.TableField]] = []
table_row_style: typing.ClassVar[types.ui.RowStyleInfo] = types.ui.RowStyleInfo.null()
table_title: typing.ClassVar[str] = ''
table_subtitle: typing.ClassVar[str] = ''
table_info: typing.ClassVar[types.rest.TableInfo] = types.rest.TableInfo.null()
# This methods must be override, depending on what is provided
# Data related
@@ -330,12 +328,7 @@ class ModelHandler(BaseModelHandler[types.rest.T_Item], typing.Generic[types.res
case [consts.rest.OVERVIEW, *_fails]:
raise self.invalid_request_response()
case [consts.rest.TABLEINFO]:
return self.table_info(
self.table_title,
self.table_fields,
self.table_row_style,
self.table_subtitle,
).as_dict()
return self.table_info.as_dict()
case [consts.rest.TABLEINFO, *_fails]:
raise self.invalid_request_response()
case [consts.rest.TYPES]: