1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-12-01 08:24:15 +03:00

Fixed rest of UserInterface classes and improved multichoice

This commit is contained in:
Adolfo Gómez García
2023-09-01 03:42:13 +02:00
parent f24b801f04
commit 33c263d6a8
38 changed files with 446 additions and 337 deletions

View File

@@ -33,7 +33,7 @@ import datetime
from uds.core.ui.user_interface import UserInterface, gui from uds.core.ui.user_interface import UserInterface, gui
DEFAULTS: typing.Dict[str, typing.Union[str, int, typing.List[str]]] = { DEFAULTS: typing.Dict[str, typing.Union[str, int, typing.List[str], datetime.date]] = {
'str_field': 'Default value text', 'str_field': 'Default value text',
'str_auto_field': 'Default value auto', 'str_auto_field': 'Default value auto',
'num_field': 50, 'num_field': 50,
@@ -45,7 +45,7 @@ DEFAULTS: typing.Dict[str, typing.Union[str, int, typing.List[str]]] = {
'checkbox_field': True, 'checkbox_field': True,
'image_choice_field': 'Default value image choice', 'image_choice_field': 'Default value image choice',
'image_field': 'Default value image', 'image_field': 'Default value image',
'date_field': '2009-12-09', 'date_field': datetime.date(2009, 12, 9),
'info_field': 'Default value info', 'info_field': 'Default value info',
} }
@@ -129,7 +129,7 @@ class TestingUserInterface(UserInterface):
order=10, order=10,
tooltip='This is a date field', tooltip='This is a date field',
required=True, required=True,
default=DEFAULTS['date_field'], default=typing.cast(datetime.date, DEFAULTS['date_field']),
) )
info_field = gui.InfoField( info_field = gui.InfoField(
label='title', label='title',

View File

@@ -186,6 +186,8 @@ class Dispatcher(View):
for k, val in handler.headers().items(): for k, val in handler.headers().items():
response[k] = val response[k] = val
# Log de operation on the audit log for admin
# Exceptiol will also be logged, but with ERROR level
log.logOperation(handler, response.status_code, log.LogLevel.INFO) log.logOperation(handler, response.status_code, log.LogLevel.INFO)
return response return response
except RequestError as e: except RequestError as e:

View File

@@ -93,7 +93,7 @@ def logOperation(
doLog( doLog(
None, None,
level=level, level=level,
message=f'{handler.request.ip}[{username}]: [{handler.request.method}/{response_code}] {path}'[ message=f'{handler.request.ip} [{username}]: [{handler.request.method}/{response_code}] {path}'[
:4096 :4096
], ],
source=LogSource.REST, source=LogSource.REST,

View File

@@ -34,10 +34,12 @@ import typing
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from uds.models.meta_pool import MetaPool, MetaPoolMember from uds import models
from uds.models.service_pool import ServicePool
from uds.models.user_service import UserService # from uds.models.meta_pool import MetaPool, MetaPoolMember
from uds.models.user import User # from uds.models.service_pool import ServicePool
# from uds.models.user_service import UserService
# from uds.models.user import User
from uds.core.util.state import State from uds.core.util.state import State
from uds.core.util.model import processUuid from uds.core.util.model import processUuid
@@ -54,7 +56,7 @@ class MetaServicesPool(DetailHandler):
""" """
@staticmethod @staticmethod
def as_dict(item: MetaPoolMember): def as_dict(item: models.MetaPoolMember):
return { return {
'id': item.uuid, 'id': item.uuid,
'pool_id': item.pool.uuid, 'pool_id': item.pool.uuid,
@@ -62,15 +64,11 @@ class MetaServicesPool(DetailHandler):
'comments': item.pool.comments, 'comments': item.pool.comments,
'priority': item.priority, 'priority': item.priority,
'enabled': item.enabled, 'enabled': item.enabled,
'user_services_count': item.pool.userServices.exclude( 'user_services_count': item.pool.userServices.exclude(state__in=State.INFO_STATES).count(),
state__in=State.INFO_STATES 'user_services_in_preparation': item.pool.userServices.filter(state=State.PREPARING).count(),
).count(),
'user_services_in_preparation': item.pool.userServices.filter(
state=State.PREPARING
).count(),
} }
def getItems(self, parent: MetaPool, item: typing.Optional[str]): def getItems(self, parent: models.MetaPool, item: typing.Optional[str]):
try: try:
if not item: if not item:
return [MetaServicesPool.as_dict(i) for i in parent.members.all()] return [MetaServicesPool.as_dict(i) for i in parent.members.all()]
@@ -80,21 +78,21 @@ class MetaServicesPool(DetailHandler):
logger.exception('err: %s', item) logger.exception('err: %s', item)
raise self.invalidItemException() raise self.invalidItemException()
def getTitle(self, parent: MetaPool) -> str: def getTitle(self, parent: models.MetaPool) -> str:
return _('Service pools') return _('Service pools')
def getFields(self, parent: MetaPool) -> typing.List[typing.Any]: def getFields(self, parent: models.MetaPool) -> typing.List[typing.Any]:
return [ return [
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}}, {'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}},
{'name': {'title': _('Service Pool name')}}, {'name': {'title': _('Service Pool name')}},
{'enabled': {'title': _('Enabled')}}, {'enabled': {'title': _('Enabled')}},
] ]
def saveItem(self, parent: MetaPool, item: typing.Optional[str]): def saveItem(self, parent: models.MetaPool, item: typing.Optional[str]):
# If already exists # If already exists
uuid = processUuid(item) if item else None uuid = processUuid(item) if item else None
pool = ServicePool.objects.get(uuid=processUuid(self._params['pool_id'])) pool = models.ServicePool.objects.get(uuid=processUuid(self._params['pool_id']))
enabled = self._params['enabled'] not in ('false', False, '0', 0) enabled = self._params['enabled'] not in ('false', False, '0', 0)
priority = int(self._params['priority']) priority = int(self._params['priority'])
priority = priority if priority >= 0 else 0 priority = priority if priority >= 0 else 0
@@ -112,19 +110,15 @@ class MetaServicesPool(DetailHandler):
parent, parent,
log.LogLevel.INFO, log.LogLevel.INFO,
("Added" if uuid is None else "Modified") ("Added" if uuid is None else "Modified")
+ " meta pool member {}/{}/{} by {}".format( + " meta pool member {}/{}/{} by {}".format(pool.name, priority, enabled, self._user.pretty_name),
pool.name, priority, enabled, self._user.pretty_name
),
log.LogSource.ADMIN, log.LogSource.ADMIN,
) )
return self.success() return self.success()
def deleteItem(self, parent: MetaPool, item: str): def deleteItem(self, parent: models.MetaPool, item: str):
member = parent.members.get(uuid=processUuid(self._args[0])) member = parent.members.get(uuid=processUuid(self._args[0]))
logStr = "Removed meta pool member {} by {}".format( logStr = "Removed meta pool member {} by {}".format(member.pool.name, self._user.pretty_name)
member.pool.name, self._user.pretty_name
)
member.delete() member.delete()
@@ -137,21 +131,23 @@ class MetaAssignedService(DetailHandler):
""" """
@staticmethod @staticmethod
def itemToDict(metaPool, item): def itemToDict(
element = AssignedService.itemToDict(item, False) metaPool: 'models.MetaPool',
item: 'models.UserService',
props: typing.Optional[typing.Dict[str, typing.Any]],
) -> typing.Dict[str, typing.Any]:
element = AssignedService.itemToDict(item, props, False)
element['pool_id'] = item.deployed_service.uuid element['pool_id'] = item.deployed_service.uuid
element['pool_name'] = item.deployed_service.name element['pool_name'] = item.deployed_service.name
return element return element
def _getAssignedService( def _getAssignedService(self, metaPool: models.MetaPool, userServiceId: str) -> models.UserService:
self, metaPool: MetaPool, userServiceId: str
) -> UserService:
""" """
Gets an assigned service and checks that it belongs to this metapool Gets an assigned service and checks that it belongs to this metapool
If not found, raises InvalidItemException If not found, raises InvalidItemException
""" """
try: try:
return UserService.objects.filter( return models.UserService.objects.filter(
uuid=processUuid(userServiceId), uuid=processUuid(userServiceId),
cache_level=0, cache_level=0,
deployed_service__in=[i.pool for i in metaPool.members.all()], deployed_service__in=[i.pool for i in metaPool.members.all()],
@@ -159,34 +155,53 @@ class MetaAssignedService(DetailHandler):
except Exception: except Exception:
raise self.invalidItemException() raise self.invalidItemException()
def getItems(self, parent: MetaPool, item: typing.Optional[str]): def getItems(self, parent: models.MetaPool, item: typing.Optional[str]):
def assignedUserServicesForPools(): def assignedUserServicesForPools() -> (
typing.Generator[
typing.Tuple[models.UserService, typing.Optional[typing.Dict[str, typing.Any]]], None, None
]
):
for m in parent.members.filter(enabled=True): for m in parent.members.filter(enabled=True):
properties: typing.Dict[str, typing.Any] = {
k: v
for k, v in models.Properties.objects.filter(
owner_type='userservice',
owner_id__in=m.pool.assignedUserServices().values_list('uuid', flat=True),
).values_list('key', 'value')
}
for u in ( for u in (
m.pool.assignedUserServices() m.pool.assignedUserServices()
.filter(state__in=State.VALID_STATES) .filter(state__in=State.VALID_STATES)
.prefetch_related('deployed_service', 'publication') .prefetch_related('deployed_service', 'publication')
): ):
yield u yield u, properties.get(u.uuid, {})
try: try:
if not item: # All items if not item: # All items
result = {} result = {}
for k in assignedUserServicesForPools():
result[k.uuid] = MetaAssignedService.itemToDict(parent, k) for k, props in assignedUserServicesForPools():
result[k.uuid] = MetaAssignedService.itemToDict(parent, k, props)
return list(result.values()) return list(result.values())
return MetaAssignedService.itemToDict( return MetaAssignedService.itemToDict(
parent, self._getAssignedService(parent, item) parent,
self._getAssignedService(parent, item),
props={
k: v
for k, v in models.Properties.objects.filter(
owner_type='userservice', owner_id=processUuid(item)
).values_list('key', 'value')
},
) )
except Exception: except Exception:
logger.exception('getItems') logger.exception('getItems')
raise self.invalidItemException() raise self.invalidItemException()
def getTitle(self, parent: MetaPool) -> str: def getTitle(self, parent: 'models.MetaPool') -> str:
return _('Assigned services') return _('Assigned services')
def getFields(self, parent: MetaPool) -> typing.List[typing.Any]: def getFields(self, parent: 'models.MetaPool') -> typing.List[typing.Any]:
return [ return [
{'creation_date': {'title': _('Creation date'), 'type': 'datetime'}}, {'creation_date': {'title': _('Creation date'), 'type': 'datetime'}},
{'pool_name': {'title': _('Pool')}}, {'pool_name': {'title': _('Pool')}},
@@ -207,10 +222,10 @@ class MetaAssignedService(DetailHandler):
{'actor_version': {'title': _('Actor version')}}, {'actor_version': {'title': _('Actor version')}},
] ]
def getRowStyle(self, parent: MetaPool) -> typing.Dict[str, typing.Any]: def getRowStyle(self, parent: 'models.MetaPool') -> typing.Dict[str, typing.Any]:
return {'field': 'state', 'prefix': 'row-state-'} return {'field': 'state', 'prefix': 'row-state-'}
def getLogs(self, parent: MetaPool, item: str) -> typing.List[typing.Any]: def getLogs(self, parent: 'models.MetaPool', item: str) -> typing.List[typing.Any]:
try: try:
asignedService = self._getAssignedService(parent, item) asignedService = self._getAssignedService(parent, item)
logger.debug('Getting logs for %s', asignedService) logger.debug('Getting logs for %s', asignedService)
@@ -218,7 +233,7 @@ class MetaAssignedService(DetailHandler):
except Exception: except Exception:
raise self.invalidItemException() raise self.invalidItemException()
def deleteItem(self, parent: MetaPool, item: str) -> None: def deleteItem(self, parent: 'models.MetaPool', item: str) -> None:
userService = self._getAssignedService(parent, item) userService = self._getAssignedService(parent, item)
if userService.user: if userService.user:
@@ -228,9 +243,7 @@ class MetaAssignedService(DetailHandler):
self._user.pretty_name, self._user.pretty_name,
) )
else: else:
logStr = 'Deleted cached service {} by {}'.format( logStr = 'Deleted cached service {} by {}'.format(userService.friendly_name, self._user.pretty_name)
userService.friendly_name, self._user.pretty_name
)
if userService.state in (State.USABLE, State.REMOVING): if userService.state in (State.USABLE, State.REMOVING):
userService.remove() userService.remove()
@@ -244,13 +257,13 @@ class MetaAssignedService(DetailHandler):
log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN) log.doLog(parent, log.LogLevel.INFO, logStr, log.LogSource.ADMIN)
# Only owner is allowed to change right now # Only owner is allowed to change right now
def saveItem(self, parent: MetaPool, item: typing.Optional[str]): def saveItem(self, parent: 'models.MetaPool', item: typing.Optional[str]):
if item is None: if item is None:
raise self.invalidItemException() raise self.invalidItemException()
fields = self.readFieldsFromParams(['auth_id', 'user_id']) fields = self.readFieldsFromParams(['auth_id', 'user_id'])
service = self._getAssignedService(parent, item) service = self._getAssignedService(parent, item)
user: User = User.objects.get(uuid=processUuid(fields['user_id'])) user = models.User.objects.get(uuid=processUuid(fields['user_id']))
logStr = 'Changing ownership of service from {} to {} by {}'.format( logStr = 'Changing ownership of service from {} to {} by {}'.format(
service.user.pretty_name if service.user else 'unknown', user.pretty_name, self._user.pretty_name service.user.pretty_name if service.user else 'unknown', user.pretty_name, self._user.pretty_name
@@ -265,9 +278,7 @@ class MetaAssignedService(DetailHandler):
> 0 > 0
): ):
raise self.invalidResponseException( raise self.invalidResponseException(
'There is already another user service assigned to {}'.format( 'There is already another user service assigned to {}'.format(user.pretty_name)
user.pretty_name
)
) )
service.user = user # type: ignore service.user = user # type: ignore

View File

@@ -40,7 +40,7 @@ from uds.REST import RequestError
from uds import models from uds import models
from uds.core.managers.crypto import CryptoManager from uds.core.managers.crypto import CryptoManager
from uds.core.util.model import processUuid from uds.core.util.model import processUuid
from uds.core.util import tools from uds.core.util import utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -177,7 +177,7 @@ class Tickets(Handler):
# Some machines needs password, depending on configuration # Some machines needs password, depending on configuration
groupIds: typing.List[str] = [] groupIds: typing.List[str] = []
for groupName in tools.as_list(self.getParam('groups')): for groupName in utils.as_list(self.getParam('groups')):
try: try:
groupIds.append(auth.groups.get(name=groupName).uuid or '') groupIds.append(auth.groups.get(name=groupName).uuid or '')
except Exception: except Exception:

View File

@@ -121,9 +121,9 @@ class AssignedService(DetailHandler):
if not item: if not item:
# First, fetch all properties for all assigned services on this pool # First, fetch all properties for all assigned services on this pool
# We can cache them, because they are going to be readed anyway... # We can cache them, because they are going to be readed anyway...
properties = { properties: typing.Dict[str, typing.Any] = {
k: v k: v
for k,v in models.Properties.objects.filter( for k, v in models.Properties.objects.filter(
owner_type='userservice', owner_type='userservice',
owner_id__in=parent.assignedUserServices().values_list('uuid', flat=True), owner_id__in=parent.assignedUserServices().values_list('uuid', flat=True),
).values_list('key', 'value') ).values_list('key', 'value')
@@ -135,7 +135,13 @@ class AssignedService(DetailHandler):
.prefetch_related('deployed_service', 'publication', 'user') .prefetch_related('deployed_service', 'publication', 'user')
] ]
return AssignedService.itemToDict( return AssignedService.itemToDict(
parent.assignedUserServices().get(processUuid(uuid=processUuid(item))) parent.assignedUserServices().get(processUuid(uuid=processUuid(item))),
props={
k: v
for k, v in models.Properties.objects.filter(
owner_type='userservice', owner_id=processUuid(item)
).values_list('key', 'value')
},
) )
except Exception as e: except Exception as e:
logger.exception('getItems') logger.exception('getItems')

View File

@@ -45,7 +45,6 @@ from django.utils.translation import gettext as _
from uds.core import consts, types from uds.core import consts, types
from uds.core import exceptions as udsExceptions from uds.core import exceptions as udsExceptions
from uds.core.module import Module from uds.core.module import Module
from uds.core.ui import gui as uiGui
from uds.core.util import log, permissions from uds.core.util import log, permissions
from uds.core.util.model import processUuid from uds.core.util.model import processUuid
from uds.models import ManagedObjectModel, Network, Tag, TaggingMixin from uds.models import ManagedObjectModel, Network, Tag, TaggingMixin
@@ -101,23 +100,16 @@ class BaseModelHandler(Handler):
choices = field['values'] choices = field['values']
else: else:
choices = field.get('choices', []) choices = field.get('choices', [])
# Build gui with non empty values
guiDesc: typing.Dict[str, typing.Any] = {}
for fld in ('required', 'default', 'minValue', 'maxValue', 'label', 'length', 'multiline', 'tooltip', 'readonly', 'type', 'order', 'choices'):
if fld in field and field[fld] is not None:
guiDesc[fld] = field[fld]
v = { v = {
'name': field.get('name', ''), 'name': field.get('name', ''),
'value': '', 'value': '',
'gui': { 'gui': guiDesc,
'required': field.get('required', False),
'default': field.get('value', ''),
'minValue': field.get('minValue', '987654321'),
'maxValue': field.get('maxValue', '123456789'),
'label': field.get('label', ''),
'length': field.get('length', 128),
'multiline': field.get('multiline', 0),
'tooltip': field.get('tooltip', ''),
'readonly': field.get('readonly', False),
'type': str(field.get('type', types.ui.FieldType.TEXT)),
'order': field.get('order', 0),
'choices': choices,
},
} }
if field.get('tab', None): if field.get('tab', None):
v['gui']['tab'] = _(str(field['tab'])) v['gui']['tab'] = _(str(field['tab']))

View File

@@ -93,7 +93,7 @@ class RegexLdap(auths.Authenticator):
tab=types.ui.Tab.CREDENTIALS, tab=types.ui.Tab.CREDENTIALS,
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=32, length=32,
label=_('Password'), label=_('Password'),
order=5, order=5,
tooltip=_('Password of the ldap user'), tooltip=_('Password of the ldap user'),

View File

@@ -82,7 +82,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
tab=types.ui.Tab.CREDENTIALS, tab=types.ui.Tab.CREDENTIALS,
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=32, length=32,
label=_('Password'), label=_('Password'),
order=5, order=5,
tooltip=_('Password of the ldap user'), tooltip=_('Password of the ldap user'),

View File

@@ -33,7 +33,7 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com
import logging import logging
import typing import typing
from uds.core.util import tools from uds.core.util import utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -45,9 +45,9 @@ CSS = 'report.css'
def getStockImagePath(stockImg: typing.Optional[str] = None) -> str: def getStockImagePath(stockImg: typing.Optional[str] = None) -> str:
stockImg = stockImg or LOGO stockImg = stockImg or LOGO
return tools.package_relative_file(__name__, 'stock_images/' + stockImg) return utils.package_relative_file(__name__, 'stock_images/' + stockImg)
def getStockCssPath(css: typing.Optional[str] = None) -> str: def getStockCssPath(css: typing.Optional[str] = None) -> str:
css = css or CSS css = css or CSS
return tools.package_relative_file(__name__, 'css/' + css) return utils.package_relative_file(__name__, 'css/' + css)

View File

@@ -119,14 +119,14 @@ ChoicesType = typing.Union[typing.Callable[[], typing.Iterable[ChoiceType]], typ
@dataclasses.dataclass @dataclasses.dataclass
class FieldInfoType: class FieldInfoType:
required: bool
label: str label: str
default: typing.Optional[typing.Union[typing.Callable[[], str], str]]
readonly: bool
order: int
tooltip: str tooltip: str
value: typing.Union[typing.Callable[[], typing.Any], typing.Any] order: int
type: FieldType type: FieldType
readonly: typing.Optional[bool] = None
value: typing.Union[typing.Callable[[], typing.Any], typing.Any] = None
default: typing.Optional[typing.Union[typing.Callable[[], str], str]] = None
required: typing.Optional[bool] = None
length: typing.Optional[int] = None length: typing.Optional[int] = None
multiline: typing.Optional[int] = None multiline: typing.Optional[int] = None
pattern: typing.Union[FieldPatternType, 're.Pattern'] = FieldPatternType.NONE pattern: typing.Union[FieldPatternType, 're.Pattern'] = FieldPatternType.NONE

View File

@@ -30,10 +30,10 @@
Author: Adolfo Gómez, dkmaster at dkmon dot com Author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
# pylint: disable=too-many-lines # pylint: disable=too-many-lines
import calendar
import codecs import codecs
import copy import copy
import datetime import datetime
import enum
import inspect import inspect
import logging import logging
import pickle # nosec: safe usage import pickle # nosec: safe usage
@@ -108,36 +108,16 @@ class gui:
], ],
] ]
# : For backward compatibility, will be removed in future versions # Static Callbacks simple registry
# For now, will log a warning if used # Note that this works fine, even on several servers
@deprecatedClassValue('types.ui.Tab.ADVANCED') # cause the callbacks are registered on field creation, that is done
def ADVANCED_TAB(cls) -> str: # pylint: disable=no-self-argument # on clases at server startup, so all servers will have the same
return str(types.ui.Tab.ADVANCED) # callbacks registered
callbacks: typing.ClassVar[
@deprecatedClassValue('types.ui.Tab.PARAMETERS') typing.Dict[
def PARAMETERS_TAB(cls) -> str: # pylint: disable=no-self-argument str,
return str(types.ui.Tab.PARAMETERS) typing.Callable[[typing.Dict[str, str]], typing.List[typing.Dict[str, str]]],
]
@deprecatedClassValue('types.ui.Tab.CREDENTIALS')
def CREDENTIALS_TAB(cls) -> str: # pylint: disable=no-self-argument
return str(types.ui.Tab.CREDENTIALS)
@deprecatedClassValue('types.ui.Tab.TUNNEL')
def TUNNEL_TAB(cls) -> str: # pylint: disable=no-self-argument
return str(types.ui.Tab.TUNNEL)
@deprecatedClassValue('types.ui.Tab.DISPLAY')
def DISPLAY_TAB(cls) -> str: # pylint: disable=no-self-argument
return str(types.ui.Tab.DISPLAY)
@deprecatedClassValue('types.ui.Tab.MFA')
def MFA_TAB(cls) -> str: # pylint: disable=no-self-argument
return str(types.ui.Tab.MFA)
# : Static Callbacks simple registry
callbacks: typing.Dict[
str,
typing.Callable[[typing.Dict[str, str]], typing.List[typing.Dict[str, str]]],
] = {} ] = {}
@staticmethod @staticmethod
@@ -285,10 +265,9 @@ class gui:
if you use "value", you will get the default value if not set) if you use "value", you will get the default value if not set)
""" """
_data: types.ui.FieldInfoType _fieldsInfo: types.ui.FieldInfoType
def __init__(self, type: types.ui.FieldType, **kwargs) -> None: def __init__(self, label: str, type: types.ui.FieldType, **kwargs) -> None:
label = kwargs.get('label') or ''
# if defvalue or defaultValue or defValue in kwargs, emit a warning # if defvalue or defaultValue or defValue in kwargs, emit a warning
# with the new name (that is "default"), but use the old one # with the new name (that is "default"), but use the old one
for new_name, old_names in ( for new_name, old_names in (
@@ -316,22 +295,22 @@ class gui:
default = kwargs.get('default') default = kwargs.get('default')
# Length is not used on some kinds of fields, but present in all anyway # Length is not used on some kinds of fields, but present in all anyway
# This property only affects in "modify" operations # This property only affects in "modify" operations
self._data = types.ui.FieldInfoType( self._fieldsInfo = types.ui.FieldInfoType(
length=kwargs.get('length'),
required=kwargs.get('required') or False,
label=label,
default=default if not callable(default) else default,
readonly=kwargs.get('readonly') or False,
order=kwargs.get('order') or 0, order=kwargs.get('order') or 0,
label=label,
tooltip=kwargs.get('tooltip') or '', tooltip=kwargs.get('tooltip') or '',
value=kwargs.get('value') or default,
type=type, type=type,
length=kwargs.get('length'),
required=kwargs.get('required'),
default=default if not callable(default) else default,
readonly=kwargs.get('readonly'),
value=kwargs.get('value') if kwargs.get('value') is not None else default,
tab=types.ui.Tab.fromStr(kwargs.get('tab')), tab=types.ui.Tab.fromStr(kwargs.get('tab')),
) )
@property @property
def type(self) -> 'types.ui.FieldType': def type(self) -> 'types.ui.FieldType':
return types.ui.FieldType(self._data.type) return types.ui.FieldType(self._fieldsInfo.type)
@type.setter @type.setter
def type(self, type_: 'types.ui.FieldType') -> None: def type(self, type_: 'types.ui.FieldType') -> None:
@@ -341,13 +320,13 @@ class gui:
Args: Args:
type: Type to set (from constants of this class) type: Type to set (from constants of this class)
""" """
self._data.type = type_ self._fieldsInfo.type = type_
def isType(self, type_: str) -> bool: def isType(self, *type_: types.ui.FieldType) -> bool:
""" """
Returns true if this field is of specified type Returns true if this field is of specified type
""" """
return self._data.type == type_ return self._fieldsInfo.type in type_
def isSerializable(self) -> bool: def isSerializable(self) -> bool:
return True return True
@@ -372,9 +351,9 @@ class gui:
returns default value instead. returns default value instead.
This is mainly used for hidden fields, so we have correctly initialized This is mainly used for hidden fields, so we have correctly initialized
""" """
if callable(self._data.value): if callable(self._fieldsInfo.value):
return self._data.value() return self._fieldsInfo.value()
return self._data.value if self._data.value is not None else self.default return self._fieldsInfo.value if self._fieldsInfo.value is not None else self.default
@value.setter @value.setter
def value(self, value: typing.Any) -> None: def value(self, value: typing.Any) -> None:
@@ -385,9 +364,9 @@ class gui:
def _setValue(self, value: typing.Any) -> None: def _setValue(self, value: typing.Any) -> None:
""" """
So we can override value setting at descendants So we can override value setter at descendants
""" """
self._data.value = value self._fieldsInfo.value = value
def guiDescription(self) -> typing.Dict[str, typing.Any]: def guiDescription(self) -> typing.Dict[str, typing.Any]:
""" """
@@ -396,7 +375,7 @@ class gui:
and don't want to and don't want to
alter original values. alter original values.
""" """
data = typing.cast(dict, self._data.asDict()) data = typing.cast(dict, self._fieldsInfo.asDict())
if 'value' in data: if 'value' in data:
del data['value'] # We don't want to send value on guiDescription del data['value'] # We don't want to send value on guiDescription
data['label'] = _(data['label']) if data['label'] else '' data['label'] = _(data['label']) if data['label'] else ''
@@ -411,7 +390,7 @@ class gui:
""" """
Returns the default value for this field Returns the default value for this field
""" """
defValue = self._data.default defValue = self._fieldsInfo.default
return defValue() if callable(defValue) else defValue return defValue() if callable(defValue) else defValue
@default.setter @default.setter
@@ -425,27 +404,29 @@ class gui:
Args: Args:
value: Default value (string) value: Default value (string)
""" """
self._data.default = value self._fieldsInfo.default = value
@property @property
def label(self) -> str: def label(self) -> str:
return self._data.label return self._fieldsInfo.label
def serialize(self) -> str: @label.setter
"""Serialize value to an string""" def label(self, value: str) -> None:
return str(self.value) self._fieldsInfo.label = value
def deserialize(self, value) -> None:
"""Unserialize value from an string"""
self.value = value
@property @property
def required(self) -> bool: def required(self) -> bool:
return self._data.required return self._fieldsInfo.required or False
@required.setter
def required(self, value: bool) -> None:
self._fieldsInfo.required = value
def validate(self) -> bool: def validate(self) -> bool:
""" """
Validates the value of this field. Validates the value of this field.
Intended to be overriden by descendants
""" """
return True return True
@@ -453,7 +434,7 @@ class gui:
return str(self.value) return str(self.value)
def __repr__(self): def __repr__(self):
return f'{self.__class__.__name__}: {repr(self._data)}' return f'{self.__class__.__name__}: {repr(self._fieldsInfo)}'
class TextField(InputField): class TextField(InputField):
""" """
@@ -496,8 +477,8 @@ class gui:
tooltip: str = '', tooltip: str = '',
required: bool = False, required: bool = False,
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None, tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
default: typing.Union[str, typing.Iterable[str]] = '', default: typing.Union[typing.Callable[[], str], str] = '',
value: str = '', value: typing.Optional[str] = None,
pattern: typing.Union[str, types.ui.FieldPatternType] = types.ui.FieldPatternType.NONE, pattern: typing.Union[str, types.ui.FieldPatternType] = types.ui.FieldPatternType.NONE,
multiline: int = 0, multiline: int = 0,
) -> None: ) -> None:
@@ -513,7 +494,7 @@ class gui:
value=value, value=value,
type=types.ui.FieldType.TEXT, type=types.ui.FieldType.TEXT,
) )
self._data.multiline = min(max(int(multiline), 0), 8) self._fieldsInfo.multiline = min(max(int(multiline), 0), 8)
# Pattern to validate the value # Pattern to validate the value
# Can contain an regex or PatternType # Can contain an regex or PatternType
# - 'ipv4' # IPv4 address # - 'ipv4' # IPv4 address
@@ -529,7 +510,7 @@ class gui:
# Note: # Note:
# Checks are performed on admin side, so they are not 100% reliable. # Checks are performed on admin side, so they are not 100% reliable.
if pattern: if pattern:
self._data.pattern = ( self._fieldsInfo.pattern = (
pattern pattern
if isinstance(pattern, types.ui.FieldPatternType) if isinstance(pattern, types.ui.FieldPatternType)
else types.ui.FieldPatternType(pattern) else types.ui.FieldPatternType(pattern)
@@ -542,7 +523,7 @@ class gui:
return super().validate() and self._validatePattern() return super().validate() and self._validatePattern()
def _validatePattern(self) -> bool: def _validatePattern(self) -> bool:
pattern = self._data.pattern pattern = self._fieldsInfo.pattern
if isinstance(pattern, types.ui.FieldPatternType): if isinstance(pattern, types.ui.FieldPatternType):
try: try:
if pattern == types.ui.FieldPatternType.IPV4: if pattern == types.ui.FieldPatternType.IPV4:
@@ -594,8 +575,8 @@ class gui:
tooltip: str = '', tooltip: str = '',
required: bool = False, required: bool = False,
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None, tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
default: typing.Union[str, typing.Iterable[str]] = '', default: typing.Union[typing.Callable[[], str], str] = '',
value: str = '', value: typing.Optional[str] = None,
choices: typing.Union[ choices: typing.Union[
typing.Callable[[], typing.List['types.ui.ChoiceType']], typing.Callable[[], typing.List['types.ui.ChoiceType']],
typing.Iterable[typing.Union[str, types.ui.ChoiceType]], typing.Iterable[typing.Union[str, types.ui.ChoiceType]],
@@ -616,13 +597,13 @@ class gui:
) )
# Update parent type # Update parent type
self.type = types.ui.FieldType.TEXT_AUTOCOMPLETE self.type = types.ui.FieldType.TEXT_AUTOCOMPLETE
self._data.choices = gui.convertToChoices(choices or []) self._fieldsInfo.choices = gui.convertToChoices(choices or [])
def setChoices(self, values: typing.Iterable[typing.Union[str, types.ui.ChoiceType]]): def setChoices(self, values: typing.Iterable[typing.Union[str, types.ui.ChoiceType]]):
""" """
Set the values for this choice field Set the values for this choice field
""" """
self._data.choices = gui.convertToChoices(values) self._fieldsInfo.choices = gui.convertToChoices(values)
class NumericField(InputField): class NumericField(InputField):
""" """
@@ -655,10 +636,10 @@ class gui:
tooltip: str = '', tooltip: str = '',
required: bool = False, required: bool = False,
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None, tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
default: int = 0, default: typing.Union[typing.Callable[[], int], int] = 0,
value: int = 0, value: int = 0,
minValue: int = 0, minValue: typing.Optional[int] = None,
maxValue: int = 987654321, maxValue: typing.Optional[int] = None,
) -> None: ) -> None:
super().__init__( super().__init__(
label=label, label=label,
@@ -672,8 +653,8 @@ class gui:
value=value, value=value,
type=types.ui.FieldType.NUMERIC, type=types.ui.FieldType.NUMERIC,
) )
self._data.minValue = minValue self._fieldsInfo.minValue = minValue
self._data.maxValue = maxValue self._fieldsInfo.maxValue = maxValue
def _setValue(self, value: typing.Any): def _setValue(self, value: typing.Any):
# Internally stores an string # Internally stores an string
@@ -699,44 +680,68 @@ class gui:
The values of parameres are inherited from :py:class:`InputField` The values of parameres are inherited from :py:class:`InputField`
""" """
def __init__(self, **options): def __init__(
super().__init__(**options, type=types.ui.FieldType.DATE) self,
label: str = '',
length: typing.Optional[int] = None,
readonly: bool = False,
order: int = 0,
tooltip: str = '',
required: bool = False,
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
default: typing.Optional[typing.Union[typing.Callable[[], datetime.date], datetime.date]] = None,
value: typing.Optional[typing.Union[str, datetime.date]] = None,
) -> None:
super().__init__(
label=label,
length=length,
readonly=readonly,
order=order,
tooltip=tooltip,
required=required,
tab=tab,
default=default,
value=value,
type=types.ui.FieldType.DATE,
)
def date(self, useMin: bool = True) -> datetime.date: def as_date(self) -> datetime.date:
""" """Alias for "value" property"""
Returns the date this object represents return typing.cast(datetime.date, self.value)
Args: def as_datetime(self) -> datetime.datetime:
min (bool, optional): If true, in case of invalid date will return "min" date, else "max". Defaults to True. """Alias for "value" property, but as datetime.datetime"""
# Convert date to datetime
Returns: return datetime.datetime.combine(
datetime.date: the date that this object holds, or "min" | "max" on error typing.cast(datetime.date, self.value), datetime.datetime.min.time()
""" )
try:
return datetime.datetime.strptime(self.value, '%Y-%m-%d').date() # ISO Format
except Exception:
return datetime.date.min if useMin else datetime.date.max
def datetime(self, useMin: bool = True) -> datetime.datetime:
"""
Returns the date this object represents
Args:
min (bool, optional): If true, in case of invalid date will return "min" date, else "max". Defaults to True.
Returns:
datetime.date: the date that this object holds, or "min" | "max" on error
"""
try:
return datetime.datetime.strptime(self.value, '%Y-%m-%d') # ISO Format
except Exception:
return datetime.datetime.min if useMin else datetime.datetime.max
def stamp(self) -> int: def stamp(self) -> int:
"""Alias for "value" property, but as timestamp"""
return int(time.mktime(datetime.datetime.strptime(self.value, '%Y-%m-%d').timetuple())) return int(time.mktime(datetime.datetime.strptime(self.value, '%Y-%m-%d').timetuple()))
# Override value setter, so we can convert from datetime.datetime or str to datetime.date
def _setValue(self, value: typing.Any) -> None:
if isinstance(value, datetime.datetime):
value = value.date()
elif isinstance(value, datetime.date):
value = value
elif isinstance(value, str): # YYYY-MM-DD
value = datetime.datetime.strptime(value, '%Y-%m-%d').date()
else:
raise ValueError(f'Invalid value for date: {value}')
super()._setValue(value)
def guiDescription(self) -> typing.Dict[str, typing.Any]:
theGui = super().guiDescription()
# Convert if needed value and default to string (YYYY-MM-DD)
if 'default' in theGui:
theGui['default'] = str(theGui['default'])
return theGui
def __str__(self): def __str__(self):
return str(self.datetime()) return str(f'Datetime: {self.value}')
class PasswordField(InputField): class PasswordField(InputField):
""" """
@@ -754,14 +759,36 @@ class gui:
# tooltip "Password of the user", that is required, # tooltip "Password of the user", that is required,
# with max length of 32 chars and order = 2, and is # with max length of 32 chars and order = 2, and is
# editable after creation. # editable after creation.
passw = gui.PasswordField(lenth=32, label = _('Password'), passw = gui.PasswordField(length=32, label = _('Password'),
order = 4, tooltip = _('Password of the user'), order = 4, tooltip = _('Password of the user'),
required = True) required = True)
""" """
def __init__(self, **options): def __init__(
super().__init__(**options, type=types.ui.FieldType.PASSWORD) self,
label: str = '',
length: int = consts.DEFAULT_TEXT_LENGTH,
readonly: bool = False,
order: int = 0,
tooltip: str = '',
required: bool = False,
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
default: typing.Union[typing.Callable[[], str], str] = '',
value: typing.Optional[str] = None,
):
super().__init__(
label=label,
length=length,
readonly=readonly,
order=order,
tooltip=tooltip,
required=required,
tab=tab,
default=default,
value=value,
type=types.ui.FieldType.PASSWORD,
)
def cleanStr(self): def cleanStr(self):
return str(self.value).strip() return str(self.value).strip()
@@ -807,7 +834,7 @@ class gui:
self, self,
label: str = '', # label is optional on hidden fields label: str = '', # label is optional on hidden fields
order: int = 0, order: int = 0,
default: typing.Any = None, default: typing.Any = None, # May be also callable
value: typing.Any = None, value: typing.Any = None,
serializable: bool = False, serializable: bool = False,
) -> None: ) -> None:
@@ -850,7 +877,7 @@ class gui:
tooltip: str = '', tooltip: str = '',
required: bool = False, required: bool = False,
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None, tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
default: bool = False, default: typing.Union[typing.Callable[[], bool], bool] = False,
value: bool = False, value: bool = False,
): ):
super().__init__( super().__init__(
@@ -869,7 +896,7 @@ class gui:
""" """
Override to set value to True or False (bool) Override to set value to True or False (bool)
""" """
self._data.value = gui.toBool(value) self._fieldsInfo.value = gui.toBool(value)
def isTrue(self): def isTrue(self):
""" """
@@ -995,7 +1022,7 @@ class gui:
] = None, ] = None,
fills: typing.Optional[types.ui.FillerType] = None, fills: typing.Optional[types.ui.FillerType] = None,
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None, tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
default: typing.Optional[str] = None, default: typing.Union[typing.Callable[[], str], str, None] = None,
value: typing.Optional[str] = None, value: typing.Optional[str] = None,
) -> None: ) -> None:
super().__init__( super().__init__(
@@ -1010,14 +1037,14 @@ class gui:
type=types.ui.FieldType.CHOICE, type=types.ui.FieldType.CHOICE,
) )
self._data.choices = gui.convertToChoices(choices or []) self._fieldsInfo.choices = gui.convertToChoices(choices or [])
# if has fillers, set them # if has fillers, set them
if fills: if fills:
if 'function' not in fills or 'callbackName' not in fills: if 'function' not in fills or 'callbackName' not in fills:
raise ValueError('Invalid fills parameters') raise ValueError('Invalid fills parameters')
fnc = fills['function'] fnc = fills['function']
fills.pop('function') fills.pop('function')
self._data.fills = fills self._fieldsInfo.fills = fills
# Store it only if not already present # Store it only if not already present
if fills['callbackName'] not in gui.callbacks: if fills['callbackName'] not in gui.callbacks:
gui.callbacks[fills['callbackName']] = fnc gui.callbacks[fills['callbackName']] = fnc
@@ -1026,7 +1053,7 @@ class gui:
""" """
Set the values for this choice field Set the values for this choice field
""" """
self._data.choices = gui.convertToChoices(values) self._fieldsInfo.choices = gui.convertToChoices(values)
class ImageChoiceField(InputField): class ImageChoiceField(InputField):
def __init__( def __init__(
@@ -1037,12 +1064,13 @@ class gui:
tooltip: str = '', tooltip: str = '',
required: bool = False, required: bool = False,
choices: typing.Union[ choices: typing.Union[
typing.Callable[[], typing.List['types.ui.ChoiceType']],
typing.Iterable[typing.Union[str, types.ui.ChoiceType]], typing.Iterable[typing.Union[str, types.ui.ChoiceType]],
typing.Dict[str, str], typing.Dict[str, str],
None, None,
] = None, ] = None,
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None, tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
default: typing.Optional[str] = None, default: typing.Union[typing.Callable[[], str], str, None] = None,
value: typing.Optional[str] = None, value: typing.Optional[str] = None,
): ):
super().__init__( super().__init__(
@@ -1057,13 +1085,13 @@ class gui:
type=types.ui.FieldType.IMAGECHOICE, type=types.ui.FieldType.IMAGECHOICE,
) )
self._data.choices = gui.convertToChoices(choices or []) self._fieldsInfo.choices = gui.convertToChoices(choices or [])
def setChoices(self, values: typing.Iterable[typing.Union[str, types.ui.ChoiceType]]): def setChoices(self, values: typing.Iterable[typing.Union[str, types.ui.ChoiceType]]):
""" """
Set the values for this choice field Set the values for this choice field
""" """
self._data.choices = gui.convertToChoices(values) self._fieldsInfo.choices = gui.convertToChoices(values)
class MultiChoiceField(InputField): class MultiChoiceField(InputField):
""" """
@@ -1108,12 +1136,13 @@ class gui:
tooltip: str = '', tooltip: str = '',
required: bool = False, required: bool = False,
choices: typing.Union[ choices: typing.Union[
typing.Callable[[], typing.List['types.ui.ChoiceType']],
typing.Iterable[typing.Union[str, types.ui.ChoiceType]], typing.Iterable[typing.Union[str, types.ui.ChoiceType]],
typing.Dict[str, str], typing.Dict[str, str],
None, None,
] = None, ] = None,
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None, tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
default: typing.Optional[typing.Iterable[str]] = None, default: typing.Union[typing.Callable[[], str], str, None] = None,
value: typing.Optional[typing.Iterable[str]] = None, value: typing.Optional[typing.Iterable[str]] = None,
): ):
super().__init__( super().__init__(
@@ -1128,14 +1157,14 @@ class gui:
value=value, value=value,
) )
self._data.rows = rows self._fieldsInfo.rows = rows
self._data.choices = gui.convertToChoices(choices or []) self._fieldsInfo.choices = gui.convertToChoices(choices or [])
def setChoices(self, values: typing.Iterable[typing.Union[str, types.ui.ChoiceType]]): def setChoices(self, choices: typing.Iterable[typing.Union[str, types.ui.ChoiceType]]):
""" """
Set the values for this choice field Set the values for this choice field
""" """
self._data.choices = gui.convertToChoices(values) self._fieldsInfo.choices = gui.convertToChoices(choices)
class EditableListField(InputField): class EditableListField(InputField):
""" """
@@ -1172,7 +1201,7 @@ class gui:
tooltip: str = '', tooltip: str = '',
required: bool = False, required: bool = False,
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None, tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
default: typing.Optional[typing.Iterable[str]] = None, default: typing.Union[typing.Callable[[], str], str, None] = None,
value: typing.Optional[typing.Iterable[str]] = None, value: typing.Optional[typing.Iterable[str]] = None,
) -> None: ) -> None:
super().__init__( super().__init__(
@@ -1227,7 +1256,7 @@ class UserInterfaceType(type):
for attrName, attr in namespace.items(): for attrName, attr in namespace.items():
if isinstance(attr, gui.InputField): if isinstance(attr, gui.InputField):
# Ensure we have a copy of the data, so we can modify it without affecting others # Ensure we have a copy of the data, so we can modify it without affecting others
attr._data = copy.deepcopy(attr._data) attr._fieldsInfo = copy.deepcopy(attr._fieldsInfo)
_gui[attrName] = attr _gui[attrName] = attr
newClassDict[attrName] = attr newClassDict[attrName] = attr
@@ -1278,12 +1307,11 @@ class UserInterface(metaclass=UserInterfaceType):
setattr(self, key, val) # Reference to self._gui[key] setattr(self, key, val) # Reference to self._gui[key]
# Check for "callable" fields and update them if needed # Check for "callable" fields and update them if needed
for field in ['choices', 'value', 'default']: # ['value', 'default']: for field in ['choices', 'default']: # Update references to self for callable fields
logger.debug('Checking field %s', field) attr = getattr(val._fieldsInfo, field, None)
attr = getattr(val._data, field, None)
if attr and callable(attr): if attr and callable(attr):
# val is an InputField derived instance, so it is a reference to self._gui[key] # val is an InputField derived instance, so it is a reference to self._gui[key]
setattr(val._data, field, attr()) setattr(val._fieldsInfo, field, attr())
if values is not None: if values is not None:
for k, v in self._gui.items(): for k, v in self._gui.items():
@@ -1342,10 +1370,8 @@ class UserInterface(metaclass=UserInterfaceType):
""" """
dic: gui.ValuesDictType = {} dic: gui.ValuesDictType = {}
for k, v in self._gui.items(): for k, v in self._gui.items():
if v.isType(types.ui.FieldType.EDITABLELIST): if v.isType(types.ui.FieldType.EDITABLELIST, types.ui.FieldType.MULTICHOICE):
dic[k] = ensure.is_list(v.value) dic[k] = ensure.is_list(v.value)
elif v.isType(types.ui.FieldType.MULTICHOICE):
dic[k] = gui.convertToChoices(v.value)
else: else:
dic[k] = v.value dic[k] = v.value
logger.debug('Values Dict: %s', dic) logger.debug('Values Dict: %s', dic)

View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 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 datetime
import calendar
# Some helpers
def start_of_year() -> datetime.date:
return datetime.date(datetime.date.today().year, 1, 1)
def end_of_year() -> datetime.date:
return datetime.date(datetime.date.today().year, 12, 31)
def start_of_month() -> datetime.date:
return datetime.date(datetime.date.today().year, datetime.date.today().month, 1)
def end_of_month() -> datetime.date:
return datetime.date(
datetime.date.today().year,
datetime.date.today().month,
calendar.monthrange(datetime.date.today().year, datetime.date.today().month)[1],
)
def start_of_week() -> datetime.date:
return datetime.date.today() - datetime.timedelta(days=datetime.date.today().weekday())
def end_of_week() -> datetime.date:
return datetime.date.today() + datetime.timedelta(days=6 - datetime.date.today().weekday())
def yesterday() -> datetime.date:
return datetime.date.today() - datetime.timedelta(days=1)
def today() -> datetime.date:
return datetime.date.today()
def tomorrow() -> datetime.date:
return datetime.date.today() + datetime.timedelta(days=1)

View File

@@ -46,7 +46,7 @@ from ldap import (
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.conf import settings from django.conf import settings
from uds.core.util import tools from uds.core.util import utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -207,9 +207,9 @@ def getAsDict(
# Convert back attritutes to test_type ONLY on python2 # Convert back attritutes to test_type ONLY on python2
dct = ( dct = (
tools.CaseInsensitiveDict((k, ['']) for k in attrList) utils.CaseInsensitiveDict((k, ['']) for k in attrList)
if attrList is not None if attrList is not None
else tools.CaseInsensitiveDict() else utils.CaseInsensitiveDict()
) )
# Convert back result fields to str # Convert back result fields to str

View File

@@ -91,7 +91,7 @@ class EmailMFA(mfas.MFA):
tab=_('SMTP Server'), tab=_('SMTP Server'),
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=128, length=128,
label=_('Password'), label=_('Password'),
order=10, order=10,
tooltip=_('Password of the user with access to SMTP server'), tooltip=_('Password of the user with access to SMTP server'),

View File

@@ -125,6 +125,10 @@ class RadiusOTP(mfas.MFA):
order=32, order=32,
tooltip=_('Networks for Radius OTP authentication'), tooltip=_('Networks for Radius OTP authentication'),
required=False, required=False,
choices=lambda: [
gui.choiceItem(v.uuid, v.name) # type: ignore
for v in models.Network.objects.all().order_by('name')
],
tab=_('Config'), tab=_('Config'),
) )
@@ -141,15 +145,6 @@ class RadiusOTP(mfas.MFA):
def initialize(self, values: 'Module.ValuesType') -> None: def initialize(self, values: 'Module.ValuesType') -> None:
return super().initialize(values) return super().initialize(values)
def initGui(self) -> None:
# Populate the networks list
self.networks.setChoices(
[
gui.choiceItem(v.uuid, v.name) # type: ignore
for v in models.Network.objects.all().order_by('name')
]
)
def radiusClient(self) -> client.RadiusClient: def radiusClient(self) -> client.RadiusClient:
"""Return a new radius client .""" """Return a new radius client ."""
return client.RadiusClient( return client.RadiusClient(

View File

@@ -33,7 +33,7 @@ import logging
from django.db import models from django.db import models
from uds.core.util.tools import secondsToTimeString from uds.core.util.utils import secondsToTimeString
from .uuid_model import UUIDModel from .uuid_model import UUIDModel
from .account import Account from .account import Account

View File

@@ -101,7 +101,7 @@ class EmailNotifier(messaging.Notifier):
tab=_('SMTP Server'), tab=_('SMTP Server'),
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=128, length=128,
label=_('Password'), label=_('Password'),
order=10, order=10,
tooltip=_('Password of the user with access to SMTP server'), tooltip=_('Password of the user with access to SMTP server'),

View File

@@ -41,7 +41,7 @@ from django.utils.translation import gettext_noop as _
from uds.core import messaging, exceptions from uds.core import messaging, exceptions
from uds.core.ui import gui from uds.core.ui import gui
from uds.core.util.model import getSqlDatetime from uds.core.util.model import getSqlDatetime
from uds.core.util.tools import ignoreExceptions from uds.core.util.utils import ignoreExceptions
from . import telegram from . import telegram

View File

@@ -73,8 +73,7 @@ class ReportAutoType(UserInterfaceType):
if attrs.get('dates') == 'single': if attrs.get('dates') == 'single':
attrs['date_start'] = fields.single_date_field(order) attrs['date_start'] = fields.single_date_field(order)
order += 1 order += 1
elif attrs.get('dates') == 'range':
if attrs.get('dates') == 'range':
attrs['date_start'] = fields.start_date_field(order) attrs['date_start'] = fields.start_date_field(order)
order += 1 order += 1
attrs['date_end'] = fields.end_date_field(order) attrs['date_end'] = fields.end_date_field(order)
@@ -178,7 +177,7 @@ class ReportAuto(Report, metaclass=ReportAutoType):
return d.strftime('%Y-%b-%d %H:%M:%S') return d.strftime('%Y-%b-%d %H:%M:%S')
def startingDate(self) -> datetime.date: def startingDate(self) -> datetime.date:
return self.adjustDate(self.date_start.date(), False) return self.adjustDate(self.date_start.as_date(), False)
def endingDate(self) -> datetime.date: def endingDate(self) -> datetime.date:
return self.adjustDate(self.date_end.date(), True) return self.adjustDate(self.date_end.as_date(), True)

View File

@@ -36,7 +36,7 @@ import typing
from django.utils.translation import gettext_noop as _ from django.utils.translation import gettext_noop as _
from uds.core import types from uds.core import types
from uds.core.util import dateutils
from uds.core.ui import gui from uds.core.ui import gui
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -47,7 +47,7 @@ def start_date_field(order: int) -> gui.DateField:
order=order, order=order,
label=_('Starting date'), label=_('Starting date'),
tooltip=_('Starting date for report'), tooltip=_('Starting date for report'),
default=lambda: datetime.date.today().replace(day=1, month=1), default=dateutils.start_of_month,
required=True, required=True,
) )
@@ -57,7 +57,7 @@ def single_date_field(order: int) -> gui.DateField:
order=order, order=order,
label=_('Date'), label=_('Date'),
tooltip=_('Date for report'), tooltip=_('Date for report'),
default=datetime.date.today, default=dateutils.today,
required=True, required=True,
) )
@@ -67,7 +67,7 @@ def end_date_field(order: int) -> gui.DateField:
order=order, order=order,
label=_('Ending date'), label=_('Ending date'),
tooltip=_('ending date for report'), tooltip=_('ending date for report'),
default=lambda: datetime.date.today() + datetime.timedelta(days=1), default=dateutils.tomorrow,
required=True, required=True,
) )

View File

@@ -30,23 +30,23 @@
""" """
Author: Adolfo Gómez, dkmaster at dkmon dot com Author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import re
import io
import typing
import csv import csv
import datetime import datetime
import io
import logging import logging
import re
import typing
from django.utils.translation import gettext, gettext_lazy as _ from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from uds.core.ui import gui
from uds.core.util import log
from uds.core.managers.log.objects import LogObjectType from uds.core.managers.log.objects import LogObjectType
from uds.core.ui import gui
from uds.core.util import dateutils, log
from uds.models import Log from uds.models import Log
from .base import ListReport from .base import ListReport
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -62,7 +62,7 @@ class ListReportAuditCSV(ListReport):
order=2, order=2,
label=_('Starting date'), label=_('Starting date'),
tooltip=_('starting date for report'), tooltip=_('starting date for report'),
default=datetime.date.min, default=dateutils.start_of_month,
required=True, required=True,
) )
@@ -70,7 +70,7 @@ class ListReportAuditCSV(ListReport):
order=3, order=3,
label=_('Finish date'), label=_('Finish date'),
tooltip=_('finish date for report'), tooltip=_('finish date for report'),
default=datetime.date.max, default=dateutils.tomorrow,
required=True, required=True,
) )
@@ -81,11 +81,11 @@ class ListReportAuditCSV(ListReport):
# Xtract user method, response_code and request from data # Xtract user method, response_code and request from data
# the format is "user: [method/response_code] request" # the format is "user: [method/response_code] request"
rx = re.compile( rx = re.compile(
r'(?P<ip>[^ ]*) (?P<user>.*?): \[(?P<method>[^/]*)/(?P<response_code>[^\]]*)\] (?P<request>.*)' r'(?P<ip>[^\[ ]*) *(?P<user>.*?): \[(?P<method>[^/]*)/(?P<response_code>[^\]]*)\] (?P<request>.*)'
) )
start = self.startDate.datetime().replace(hour=0, minute=0, second=0, microsecond=0) start = self.startDate.as_datetime().replace(hour=0, minute=0, second=0, microsecond=0)
end = self.endDate.datetime().replace(hour=23, minute=59, second=59, microsecond=999999) end = self.endDate.as_datetime().replace(hour=23, minute=59, second=59, microsecond=999999)
for i in Log.objects.filter( for i in Log.objects.filter(
created__gte=start, created__gte=start,
created__lte=end, created__lte=end,
@@ -94,10 +94,15 @@ class ListReportAuditCSV(ListReport):
).order_by('-created'): ).order_by('-created'):
# extract user, method, response_code and request from data field # extract user, method, response_code and request from data field
m = rx.match(i.data) m = rx.match(i.data)
if m is not None: if m:
# Convert response code to an string if 200, else, to an error code: str = m.group('response_code')
response_code = { try:
code_grp = int(code) // 100
except Exception:
code_grp = 500
response_code = code + '/' + {
'200': 'OK', '200': 'OK',
'400': 'Bad Request', '400': 'Bad Request',
'401': 'Unauthorized', '401': 'Unauthorized',
@@ -106,10 +111,17 @@ class ListReportAuditCSV(ListReport):
'405': 'Method Not Allowed', '405': 'Method Not Allowed',
'500': 'Internal Server Error', '500': 'Internal Server Error',
'501': 'Not Implemented', '501': 'Not Implemented',
}.get(m.group('response_code'), 'Code: ' + m.group('response_code')) }.get(code, {
1: 'Informational',
2: 'Success',
3: 'Redirection',
4: 'Client Error',
5: 'Server Error',
}.get(code_grp, 'Unknown')
)
yield ( yield (
i.created, i.created,
i.level_str,
m.group('ip'), m.group('ip'),
m.group('user'), m.group('user'),
m.group('method'), m.group('method'),
@@ -124,7 +136,6 @@ class ListReportAuditCSV(ListReport):
writer.writerow( writer.writerow(
[ [
gettext('Date'), gettext('Date'),
gettext('Level'),
gettext('IP'), gettext('IP'),
gettext('User'), gettext('User'),
gettext('Method'), gettext('Method'),

View File

@@ -29,17 +29,19 @@
""" """
Author: Adolfo Gómez, dkmaster at dkmon dot com Author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import io
import csv import csv
import datetime import datetime
import io
import logging import logging
import typing import typing
from django.utils.translation import gettext, gettext_lazy as _ from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from uds.core.ui import gui
from uds.core.util.stats import events
from uds.core.managers.stats import StatsManager from uds.core.managers.stats import StatsManager
from uds.core.ui import gui
from uds.core.util import dateutils
from uds.core.util.stats import events
from uds.models import ServicePool from uds.models import ServicePool
from .base import StatsReport from .base import StatsReport
@@ -64,7 +66,7 @@ class UsageSummaryByUsersPool(StatsReport):
order=2, order=2,
label=_('Starting date'), label=_('Starting date'),
tooltip=_('starting date for report'), tooltip=_('starting date for report'),
default=datetime.date.min, default=dateutils.start_of_month,
required=True, required=True,
) )
@@ -72,7 +74,7 @@ class UsageSummaryByUsersPool(StatsReport):
order=3, order=3,
label=_('Finish date'), label=_('Finish date'),
tooltip=_('finish date for report'), tooltip=_('finish date for report'),
default=datetime.date.max, default=dateutils.tomorrow,
required=True, required=True,
) )
@@ -147,8 +149,8 @@ class UsageSummaryByUsersPool(StatsReport):
dct={ dct={
'data': items, 'data': items,
'pool': poolName, 'pool': poolName,
'beginning': self.startDate.date(), 'beginning': self.startDate.as_date(),
'ending': self.endDate.date(), 'ending': self.endDate.as_date(),
}, },
header=gettext('Users usage list for {}').format(poolName), header=gettext('Users usage list for {}').format(poolName),
water=gettext('UDS Report of users in {}').format(poolName), water=gettext('UDS Report of users in {}').format(poolName),

View File

@@ -30,21 +30,22 @@
""" """
Author: Adolfo Gómez, dkmaster at dkmon dot com Author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import io
import csv import csv
import datetime import datetime
import io
import logging import logging
import typing import typing
from django.utils.translation import gettext, gettext_lazy as _
from django.db.models import Count
import django.template.defaultfilters as filters import django.template.defaultfilters as filters
from django.db.models import Count
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from uds.core.ui import gui
from uds.core.util.stats import events
from uds.core.util import tools
from uds.core.managers.stats import StatsManager from uds.core.managers.stats import StatsManager
from uds.core.reports import graphs from uds.core.reports import graphs
from uds.core.ui import gui
from uds.core.util import dateutils, utils
from uds.core.util.stats import events
from uds.models import ServicePool from uds.models import ServicePool
from .base import StatsReport from .base import StatsReport
@@ -71,7 +72,7 @@ class PoolPerformanceReport(StatsReport):
order=2, order=2,
label=_('Starting date'), label=_('Starting date'),
tooltip=_('starting date for report'), tooltip=_('starting date for report'),
default=datetime.date.min, default=dateutils.start_of_month,
required=True, required=True,
) )
@@ -79,7 +80,7 @@ class PoolPerformanceReport(StatsReport):
order=3, order=3,
label=_('Finish date'), label=_('Finish date'),
tooltip=_('finish date for report'), tooltip=_('finish date for report'),
default=datetime.date.max, default=dateutils.tomorrow,
required=True, required=True,
) )
@@ -160,9 +161,9 @@ class PoolPerformanceReport(StatsReport):
reportData.append( reportData.append(
{ {
'name': p[1], 'name': p[1],
'date': tools.timestampAsStr(interval[0], 'SHORT_DATETIME_FORMAT') 'date': utils.timestampAsStr(interval[0], 'SHORT_DATETIME_FORMAT')
+ ' - ' + ' - '
+ tools.timestampAsStr(interval[1], 'SHORT_DATETIME_FORMAT'), + utils.timestampAsStr(interval[1], 'SHORT_DATETIME_FORMAT'),
'users': len(q), 'users': len(q),
'accesses': accesses, 'accesses': accesses,
} }
@@ -234,8 +235,8 @@ class PoolPerformanceReport(StatsReport):
dct={ dct={
'data': reportData, 'data': reportData,
'pools': [i[1] for i in self.getPools()], 'pools': [i[1] for i in self.getPools()],
'beginning': self.startDate.date(), 'beginning': self.startDate.as_date(),
'ending': self.endDate.date(), 'ending': self.endDate.as_date(),
'intervals': self.samplingPoints.num(), 'intervals': self.samplingPoints.num(),
}, },
header=gettext('UDS Pools Performance Report'), header=gettext('UDS Pools Performance Report'),

View File

@@ -40,6 +40,7 @@ from django.utils.translation import gettext, gettext_lazy as _
from uds.core.ui import gui from uds.core.ui import gui
from uds.core.util.stats import counters from uds.core.util.stats import counters
from uds.core.reports import graphs from uds.core.reports import graphs
from uds.core.util import dateutils
from uds.models import ServicePool from uds.models import ServicePool
@@ -63,28 +64,23 @@ class CountersPoolAssigned(StatsReport):
order=2, order=2,
label=_('Date'), label=_('Date'),
tooltip=_('Date for report'), tooltip=_('Date for report'),
default='', default=dateutils.start_of_month,
required=True, required=True,
) )
pools = gui.MultiChoiceField( pools = gui.MultiChoiceField(order=1, label=_('Pools'), tooltip=_('Pools for report'), required=True)
order=1, label=_('Pools'), tooltip=_('Pools for report'), required=True
)
def initialize(self, values): def initialize(self, values):
pass pass
def initGui(self): def initGui(self):
logger.debug('Initializing gui') logger.debug('Initializing gui')
vals = [ vals = [gui.choiceItem(v.uuid, v.name) for v in ServicePool.objects.all().order_by('name')]
gui.choiceItem(v.uuid, v.name)
for v in ServicePool.objects.all().order_by('name')
]
self.pools.setChoices(vals) self.pools.setChoices(vals)
def getData(self) -> typing.List[typing.Dict[str, typing.Any]]: def getData(self) -> typing.List[typing.Dict[str, typing.Any]]:
# Generate the sampling intervals and get dataUsers from db # Generate the sampling intervals and get dataUsers from db
start = self.startDate.date() start = self.startDate.as_date()
data = [] data = []
@@ -101,7 +97,7 @@ class CountersPoolAssigned(StatsReport):
pool, pool,
counters.CT_ASSIGNED, counters.CT_ASSIGNED,
since=start, since=start,
to=start+datetime.timedelta(days=1), to=start + datetime.timedelta(days=1),
intervals=3600, intervals=3600,
use_max=True, use_max=True,
all=False, all=False,
@@ -127,9 +123,7 @@ class CountersPoolAssigned(StatsReport):
'x': X, 'x': X,
'xtickFnc': '{:02d}'.format, # Two digits 'xtickFnc': '{:02d}'.format, # Two digits
'xlabel': _('Hour'), 'xlabel': _('Hour'),
'y': [ 'y': [{'label': i['name'], 'data': [i['hours'][v] for v in X]} for i in items],
{'label': i['name'], 'data': [i['hours'][v] for v in X]} for i in items
],
'ylabel': 'Services', 'ylabel': 'Services',
} }
@@ -139,11 +133,8 @@ class CountersPoolAssigned(StatsReport):
'uds/reports/stats/pools-usage-day.html', 'uds/reports/stats/pools-usage-day.html',
dct={ dct={
'data': items, 'data': items,
'pools': [ 'pools': [v.name for v in ServicePool.objects.filter(uuid__in=self.pools.value)],
v.name 'beginning': self.startDate.as_date(),
for v in ServicePool.objects.filter(uuid__in=self.pools.value)
],
'beginning': self.startDate.date(),
}, },
header=gettext('Services usage report for a day'), header=gettext('Services usage report for a day'),
water=gettext('Service usage report'), water=gettext('Service usage report'),

View File

@@ -29,22 +29,22 @@
""" """
Author: Adolfo Gómez, dkmaster at dkmon dot com Author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import io
import csv import csv
import datetime import datetime
import io
import logging import logging
import typing import typing
from django.utils.translation import gettext, gettext_lazy as _ from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from uds.core.ui import gui
from uds.core.util.stats import events
from uds.core.managers.stats import StatsManager from uds.core.managers.stats import StatsManager
from uds.core.ui import gui
from uds.core.util import dateutils, stats
from uds.models import ServicePool from uds.models import ServicePool
from .base import StatsReport from .base import StatsReport
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -63,7 +63,7 @@ class UsageByPool(StatsReport):
order=2, order=2,
label=_('Starting date'), label=_('Starting date'),
tooltip=_('starting date for report'), tooltip=_('starting date for report'),
default=datetime.date.min, default=dateutils.start_of_month,
required=True, required=True,
) )
@@ -71,7 +71,7 @@ class UsageByPool(StatsReport):
order=3, order=3,
label=_('Finish date'), label=_('Finish date'),
tooltip=_('finish date for report'), tooltip=_('finish date for report'),
default=datetime.date.max, default=dateutils.end_of_month,
required=True, required=True,
) )
@@ -101,8 +101,8 @@ class UsageByPool(StatsReport):
items = ( items = (
StatsManager.manager() StatsManager.manager()
.getEvents( .getEvents(
events.OT_SERVICEPOOL, stats.events.OT_SERVICEPOOL,
(events.ET_LOGIN, events.ET_LOGOUT), (stats.events.ET_LOGIN, stats.events.ET_LOGOUT),
owner_id=pool.id, owner_id=pool.id,
since=start, since=start,
to=end, to=end,
@@ -115,7 +115,7 @@ class UsageByPool(StatsReport):
# if '\\' in i.fld1: # if '\\' in i.fld1:
# continue # continue
if i.event_type == events.ET_LOGIN: if i.event_type == stats.events.ET_LOGIN:
logins[i.fld4] = i.stamp logins[i.fld4] = i.stamp
else: else:
if i.fld4 in logins: if i.fld4 in logins:

View File

@@ -30,23 +30,22 @@
Author: Adolfo Gómez, dkmaster at dkmon dot com Author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import csv import csv
import io
import datetime import datetime
import io
import logging import logging
import typing import typing
from django.utils.translation import gettext, gettext_lazy as _
import django.template.defaultfilters as filters import django.template.defaultfilters as filters
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from uds.core.ui import gui
from uds.core.util.stats import events
from uds.core.util import tools
from uds.core.managers.stats import StatsManager from uds.core.managers.stats import StatsManager
from uds.core.reports import graphs from uds.core.reports import graphs
from uds.core.ui import gui
from uds.core.util import dateutils, stats, utils
from .base import StatsReport from .base import StatsReport
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# several constants as Width height # several constants as Width height
@@ -65,7 +64,7 @@ class StatsReportLogin(StatsReport):
order=1, order=1,
label=_('Starting date'), label=_('Starting date'),
tooltip=_('starting date for report'), tooltip=_('starting date for report'),
default=datetime.date.min, default=dateutils.start_of_month,
required=True, required=True,
) )
@@ -73,7 +72,7 @@ class StatsReportLogin(StatsReport):
order=2, order=2,
label=_('Finish date'), label=_('Finish date'),
tooltip=_('finish date for report'), tooltip=_('finish date for report'),
default=datetime.date.max, default=dateutils.tomorrow,
required=True, required=True,
) )
@@ -112,7 +111,9 @@ class StatsReportLogin(StatsReport):
samplingIntervals: typing.List[typing.Tuple[int, int]] = [] samplingIntervals: typing.List[typing.Tuple[int, int]] = []
samplingIntervalSeconds = (end - start) / samplingPoints samplingIntervalSeconds = (end - start) / samplingPoints
for i in range(samplingPoints): for i in range(samplingPoints):
samplingIntervals.append((int(start + i * samplingIntervalSeconds), int(start + (i + 1) * samplingIntervalSeconds))) samplingIntervals.append(
(int(start + i * samplingIntervalSeconds), int(start + (i + 1) * samplingIntervalSeconds))
)
data = [] data = []
reportData = [] reportData = []
@@ -121,8 +122,8 @@ class StatsReportLogin(StatsReport):
val = ( val = (
StatsManager.manager() StatsManager.manager()
.getEvents( .getEvents(
events.OT_AUTHENTICATOR, stats.events.OT_AUTHENTICATOR,
events.ET_LOGIN, stats.events.ET_LOGIN,
since=interval[0], since=interval[0],
to=interval[1], to=interval[1],
) )
@@ -131,9 +132,9 @@ class StatsReportLogin(StatsReport):
data.append((key, val)) data.append((key, val))
reportData.append( reportData.append(
{ {
'date': tools.timestampAsStr(interval[0], 'SHORT_DATETIME_FORMAT') 'date': utils.timestampAsStr(interval[0], 'SHORT_DATETIME_FORMAT')
+ ' - ' + ' - '
+ tools.timestampAsStr(interval[1], 'SHORT_DATETIME_FORMAT'), + utils.timestampAsStr(interval[1], 'SHORT_DATETIME_FORMAT'),
'users': val, 'users': val,
} }
) )
@@ -148,7 +149,7 @@ class StatsReportLogin(StatsReport):
dataHour = [0] * 24 dataHour = [0] * 24
dataWeekHour = [[0] * 24 for _ in range(7)] dataWeekHour = [[0] * 24 for _ in range(7)]
for val in StatsManager.manager().getEvents( for val in StatsManager.manager().getEvents(
events.OT_AUTHENTICATOR, events.ET_LOGIN, since=start, to=end stats.events.OT_AUTHENTICATOR, stats.events.ET_LOGIN, since=start, to=end
): ):
s = datetime.datetime.fromtimestamp(val.stamp) s = datetime.datetime.fromtimestamp(val.stamp)
dataWeek[s.weekday()] += 1 dataWeek[s.weekday()] += 1
@@ -170,9 +171,7 @@ class StatsReportLogin(StatsReport):
d = { d = {
'title': _('Users Access (global)'), 'title': _('Users Access (global)'),
'x': X, 'x': X,
'xtickFnc': lambda l: filters.date( 'xtickFnc': lambda l: filters.date(datetime.datetime.fromtimestamp(l), xLabelFormat),
datetime.datetime.fromtimestamp(l), xLabelFormat
),
'xlabel': _('Date'), 'xlabel': _('Date'),
'y': [{'label': 'Users', 'data': [v[1] for v in data]}], 'y': [{'label': 'Users', 'data': [v[1] for v in data]}],
'ylabel': 'Users', 'ylabel': 'Users',
@@ -245,8 +244,8 @@ class StatsReportLogin(StatsReport):
'uds/reports/stats/user-access.html', 'uds/reports/stats/user-access.html',
dct={ dct={
'data': reportData, 'data': reportData,
'beginning': self.startDate.date(), 'beginning': self.startDate.as_date(),
'ending': self.endDate.date(), 'ending': self.endDate.as_date(),
'intervals': self.samplingPoints.num(), 'intervals': self.samplingPoints.num(),
}, },
header=gettext('Users access to UDS'), header=gettext('Users access to UDS'),

View File

@@ -126,7 +126,7 @@ class OVirtProvider(
default='admin@internal', default='admin@internal',
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=32, length=32,
label=_('Password'), label=_('Password'),
order=4, order=4,
tooltip=_('Password of the user of oVirt'), tooltip=_('Password of the user of oVirt'),

View File

@@ -125,7 +125,7 @@ class OGProvider(ServiceProvider):
required=True, required=True,
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=32, length=32,
label=_('Password'), label=_('Password'),
order=5, order=5,
tooltip=_('Password of the user of OpenGnsys'), tooltip=_('Password of the user of OpenGnsys'),

View File

@@ -105,7 +105,7 @@ class OpenNebulaProvider(ServiceProvider): # pylint: disable=too-many-public-me
default='oneadmin', default='oneadmin',
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=32, length=32,
label=_('Password'), label=_('Password'),
order=5, order=5,
tooltip=_('Password of the user of OpenNebula'), tooltip=_('Password of the user of OpenNebula'),

View File

@@ -135,7 +135,7 @@ class OpenStackProvider(ServiceProvider):
default='admin', default='admin',
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=32, length=32,
label=_('Password'), label=_('Password'),
order=10, order=10,
tooltip=_('Password of the user of OpenStack'), tooltip=_('Password of the user of OpenStack'),

View File

@@ -151,7 +151,7 @@ class ProviderLegacy(ServiceProvider):
default='admin', default='admin',
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=32, length=32,
label=_('Password'), label=_('Password'),
order=10, order=10,
tooltip=_('Password of the user of OpenStack'), tooltip=_('Password of the user of OpenStack'),

View File

@@ -37,7 +37,7 @@ from django.utils.translation import gettext_noop as _
from uds.core import services, types from uds.core import services, types
from uds.core.transports import protocols from uds.core.transports import protocols
from uds.core.util import tools, validators from uds.core.util import utils, validators
from uds.core.ui import gui from uds.core.ui import gui
from .publication import LivePublication from .publication import LivePublication

View File

@@ -90,7 +90,7 @@ class ProxmoxProvider(
default='root@pam', default='root@pam',
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=32, length=32,
label=_('Password'), label=_('Password'),
order=4, order=4,
tooltip=_('Password of the user of Proxmox'), tooltip=_('Password of the user of Proxmox'),

View File

@@ -116,7 +116,7 @@ class XenProvider(ServiceProvider): # pylint: disable=too-many-public-methods
default='root', default='root',
) )
password = gui.PasswordField( password = gui.PasswordField(
lenth=32, length=32,
label=_('Password'), label=_('Password'),
order=3, order=3,
tooltip=_('Password of the user of XenServer'), tooltip=_('Password of the user of XenServer'),

File diff suppressed because one or more lines are too long

View File

@@ -102,6 +102,6 @@
</svg> </svg>
</div> </div>
</uds-root> </uds-root>
<script src="/uds/res/admin/runtime.js?stamp=1693502627" type="module"></script><script src="/uds/res/admin/polyfills.js?stamp=1693502627" type="module"></script><script src="/uds/res/admin/main.js?stamp=1693502627" type="module"></script></body> <script src="/uds/res/admin/runtime.js?stamp=1693532187" type="module"></script><script src="/uds/res/admin/polyfills.js?stamp=1693532187" type="module"></script><script src="/uds/res/admin/main.js?stamp=1693532187" type="module"></script></body>
</html> </html>