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:
6
server/src/tests/fixtures/user_interface.py
vendored
6
server/src/tests/fixtures/user_interface.py
vendored
@@ -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',
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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']))
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
74
server/src/uds/core/util/dateutils.py
Normal file
74
server/src/uds/core/util/dateutils.py
Normal 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)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user