forked from shaba/openuds
* Fixed Actions Calendars migrations
* Started support for actions calendars edition on dashboard
This commit is contained in:
parent
b0a6807ea4
commit
b5387d4922
@ -34,14 +34,14 @@ from __future__ import unicode_literals
|
||||
# On centos, old six release does not includes byte2int, nor six.PY2
|
||||
import six
|
||||
|
||||
VERSION = '1.9.0'
|
||||
VERSION = '2.0.0'
|
||||
|
||||
__title__ = 'udsactor'
|
||||
__version__ = VERSION
|
||||
__build__ = 0x010750
|
||||
__author__ = 'Adolfo Gómez'
|
||||
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
|
||||
__license__ = "BSD 3-clause"
|
||||
__copyright__ = "Copyright 2014-2015 VirtualCable S.L.U."
|
||||
__copyright__ = "Copyright 2014-2016 VirtualCable S.L.U."
|
||||
|
||||
|
||||
if not hasattr(six, 'byte2int'):
|
||||
|
@ -42,7 +42,7 @@ from uds.models import User
|
||||
from uds.web import errors
|
||||
from uds.core.managers import cryptoManager, userServiceManager
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
from uds.core.services.Exceptions import ServiceNotReadyError
|
||||
from uds.core.services.Exceptions import ServiceNotReadyError, ServiceAccessDeniedByCalendar, ServiceInMaintenanceMode
|
||||
from uds.core import VERSION as UDS_VERSION
|
||||
|
||||
import six
|
||||
@ -147,6 +147,10 @@ class Client(Handler):
|
||||
# Refresh ticket and make this retrayable
|
||||
TicketStore.revalidate(ticket, 20) # Retry will be in at most 5 seconds
|
||||
return Client.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True)
|
||||
except ServiceAccessDeniedByCalendar:
|
||||
return Client.result(error=errors.SERVICE_CALENDAR_DENIED)
|
||||
except ServiceInMaintenanceMode:
|
||||
return Client.result(error=errors.SERVICE_IN_MAINTENANCE)
|
||||
except Exception as e:
|
||||
logger.exception("Exception")
|
||||
return Client.result(error=six.text_type(e))
|
||||
|
@ -38,7 +38,7 @@ from __future__ import unicode_literals
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
from uds.models import CalendarAccess, Calendar
|
||||
from uds.models import CalendarAccess, CalendarAction, Calendar
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.core.util import log
|
||||
@ -113,3 +113,68 @@ class AccessCalendars(DetailHandler):
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
CalendarAccess.objects.get(uuid=processUuid(self._args[0])).delete()
|
||||
|
||||
|
||||
|
||||
class ActionsCalendars(DetailHandler):
|
||||
'''
|
||||
Processes the transports detail requests of a Service Pool
|
||||
'''
|
||||
@staticmethod
|
||||
def as_dict(item):
|
||||
return {
|
||||
'id': item.uuid,
|
||||
'calendarId': item.calendar.uuid,
|
||||
'name': item.calendar.name,
|
||||
'action': item.action,
|
||||
'atStart': item.atStart,
|
||||
'offset': item.eventOffset,
|
||||
'params': item.params
|
||||
}
|
||||
|
||||
def getItems(self, parent, item):
|
||||
try:
|
||||
if item is None:
|
||||
return [ActionsCalendars.as_dict(i) for i in parent.calendaraction_set.all()]
|
||||
else:
|
||||
i = CalendarAction.objects.get(uuid=processUuid(item))
|
||||
return ActionsCalendars.as_dict(i)
|
||||
except Exception:
|
||||
self.invalidItemException()
|
||||
|
||||
|
||||
def getTitle(self, parent):
|
||||
return _('Scheduled actions')
|
||||
|
||||
def getFields(self, parent):
|
||||
return [
|
||||
{'name': {'title': _('Name')}},
|
||||
{'action': {'title': _('Action')}},
|
||||
{'atStart': {'title': _('Referer')}},
|
||||
{'offset': {'title': _('Time offset')}},
|
||||
]
|
||||
|
||||
def saveItem(self, parent, item):
|
||||
# If already exists
|
||||
uuid = processUuid(self._params['id']) if 'id' in self._params else None
|
||||
|
||||
calendar = Calendar.objects.get(uuid=processUuid(self._params['calendarId']))
|
||||
action = self._params['action'].upper()
|
||||
eventOffset = int(self._params['eventOffset'])
|
||||
atStart = (self._params['atStart'] == 'true')
|
||||
|
||||
if uuid is not None:
|
||||
calAction = CalendarAction.objects.get(uuid=uuid)
|
||||
calAction.calendar = calendar
|
||||
calAction.servicePool = parent
|
||||
calAction.action = action
|
||||
calAction.atStart = atStart
|
||||
calAction.eventOffset = eventOffset
|
||||
calAction.save()
|
||||
else:
|
||||
CalendarAction.objects.create(calendar=calendar, servicePool=parent, action=action, atStart=atStart, eventOffset=eventOffset)
|
||||
|
||||
return self.success()
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
CalendarAction.objects.get(uuid=processUuid(self._args[0])).delete()
|
||||
|
@ -43,7 +43,7 @@ from uds.REST.model import ModelHandler
|
||||
from uds.REST import RequestError, ResponseError
|
||||
from uds.core.ui.UserInterface import gui
|
||||
from .user_services import AssignedService, CachedService, Groups, Transports, Publications, Changelog
|
||||
from .services_pool_calendars import AccessCalendars
|
||||
from .services_pool_calendars import AccessCalendars, ActionsCalendars
|
||||
from .services import Services
|
||||
|
||||
import logging
|
||||
@ -64,6 +64,7 @@ class ServicesPools(ModelHandler):
|
||||
'publications': Publications,
|
||||
'changelog': Changelog,
|
||||
'access': AccessCalendars,
|
||||
'actions': ActionsCalendars
|
||||
}
|
||||
|
||||
save_fields = ['name', 'comments', 'tags', 'service_id', 'osmanager_id', 'image_id', 'servicesPoolGroup_id', 'initial_srvs', 'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports']
|
||||
|
@ -39,7 +39,7 @@ from uds.core.services.Exceptions import OperationException
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util import log
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
from uds.core.services.Exceptions import MaxServicesReachedError, ServiceInMaintenanceMode, InvalidServiceException, ServiceNotReadyError
|
||||
from uds.core.services.Exceptions import MaxServicesReachedError, ServiceInMaintenanceMode, InvalidServiceException, ServiceNotReadyError, ServiceAccessDeniedByCalendar
|
||||
from uds.models import ServicePool, UserService, getSqlDatetime, Transport
|
||||
from uds.core import services
|
||||
from uds.core.services import Service
|
||||
@ -51,7 +51,7 @@ import requests
|
||||
import json
|
||||
import logging
|
||||
|
||||
__updated__ = '2016-03-14'
|
||||
__updated__ = '2016-03-16'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -470,10 +470,13 @@ class UserServiceManager(object):
|
||||
# Now we have to locate an instance of the service, so we can assign it to user.
|
||||
userService = self.getAssignationForUser(ds, user)
|
||||
|
||||
logger.debug('Found service: {0}'.format(userService))
|
||||
|
||||
if userService.isInMaintenance() is True:
|
||||
raise ServiceInMaintenanceMode()
|
||||
|
||||
logger.debug('Found service: {0}'.format(userService))
|
||||
if userService.deployed_service.isAccessAllowed() is False:
|
||||
raise ServiceAccessDeniedByCalendar()
|
||||
|
||||
if idTransport is None or idTransport == '': # Find a suitable transport
|
||||
for v in userService.deployed_service.transports.order_by('priority'):
|
||||
|
@ -32,7 +32,7 @@
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__updated__ = '2015-11-06'
|
||||
__updated__ = '2016-03-16'
|
||||
|
||||
|
||||
class ServiceException(Exception):
|
||||
@ -95,6 +95,12 @@ class ServiceInMaintenanceMode(ServiceException):
|
||||
'''
|
||||
pass
|
||||
|
||||
class ServiceAccessDeniedByCalendar(ServiceException):
|
||||
'''
|
||||
This service can't be accessed right now, probably due to date-time restrictions
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class ServiceNotReadyError(ServiceException):
|
||||
'''
|
||||
|
@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.2 on 2016-02-19 07:26
|
||||
# Generated by Django 1.9.2 on 2016-03-16 08:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
@ -32,8 +32,10 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('uuid', models.CharField(default=None, max_length=50, null=True, unique=True)),
|
||||
('action', models.CharField(max_length=64)),
|
||||
('params', models.CharField(max_length=1024)),
|
||||
('action', models.CharField(default='', max_length=64)),
|
||||
('atStart', models.BooleanField(default=False)),
|
||||
('eventsOffset', models.IntegerField(default=0)),
|
||||
('params', models.CharField(default='', max_length=1024)),
|
||||
('calendar', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uds.Calendar')),
|
||||
],
|
||||
options={
|
@ -34,7 +34,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__updated__ = '2016-02-18'
|
||||
__updated__ = '2016-03-16'
|
||||
|
||||
from django.db import models
|
||||
from uds.models.Calendar import Calendar
|
||||
@ -47,12 +47,20 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Current posible actions
|
||||
ACTION_PUBLISH = 'PUBLISH'
|
||||
ACTION_CACHE_L1 = 'CACHEL1'
|
||||
ACTION_CACHE_L2 = 'CACHEL2'
|
||||
ACTION_INITIAL = 'INITIAL'
|
||||
ACTION_MAX = 'MAX'
|
||||
|
||||
class CalendarAction(UUIDModel):
|
||||
calendar = models.ForeignKey(Calendar, on_delete=models.CASCADE)
|
||||
servicePool = models.ForeignKey(ServicePool, on_delete=models.CASCADE)
|
||||
action = models.CharField(max_length=64)
|
||||
params = models.CharField(max_length=1024)
|
||||
action = models.CharField(max_length=64, default='')
|
||||
atStart = models.BooleanField(default=False) # If false, action is done at end of event
|
||||
eventsOffset = models.IntegerField(default=0) # In minutes
|
||||
params = models.CharField(max_length=1024, default='')
|
||||
|
||||
class Meta:
|
||||
'''
|
||||
|
@ -60,7 +60,7 @@ from uds.core.util.calendar import CalendarChecker
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
__updated__ = '2016-02-26'
|
||||
__updated__ = '2016-03-16'
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -203,6 +203,7 @@ class DeployedService(UUIDModel, TaggingMixin):
|
||||
for ac in self.calendaraccess_set.all():
|
||||
if CalendarChecker(ac.calendar).check(chkDateTime) is True:
|
||||
access = ac.access
|
||||
break # Stops on first rule match found
|
||||
|
||||
return access == states.action.ALLOW
|
||||
|
||||
|
109
server/src/uds/static/adm/js/gui-d-servicespools-actions.coffee
Normal file
109
server/src/uds/static/adm/js/gui-d-servicespools-actions.coffee
Normal file
@ -0,0 +1,109 @@
|
||||
gui.servicesPools.actionsCalendars = (servPool, info) ->
|
||||
accessCalendars = new GuiElement(api.servicesPools.detail(servPool.id, "actions", { permission: servPool.permission }), "actions")
|
||||
accessCalendarsTable = accessCalendars.table(
|
||||
doNotLoadData: true
|
||||
icon: 'assigned'
|
||||
container: "actions-placeholder"
|
||||
rowSelect: "multi"
|
||||
buttons: [
|
||||
"new"
|
||||
"edit"
|
||||
"delete"
|
||||
"xls"
|
||||
]
|
||||
|
||||
onCheck: (action, selected) ->
|
||||
if action == 'edit'
|
||||
return true
|
||||
for v in selected
|
||||
if v.id == -1
|
||||
return false # No action allowed on DEFAULT
|
||||
|
||||
return true
|
||||
|
||||
onNew: (value, table, refreshFnc) ->
|
||||
api.templates.get "pool_add_access", (tmpl) ->
|
||||
api.calendars.overview (data) ->
|
||||
modalId = gui.launchModal(gettext("Add access calendar"), api.templates.evaluate(tmpl,
|
||||
calendars: data
|
||||
priority: 1
|
||||
calendarId: ''
|
||||
accessList: accessList
|
||||
access: 'ALLOW'
|
||||
))
|
||||
$(modalId + " .button-accept").on "click", (event) ->
|
||||
priority = $(modalId + " #id_priority").val()
|
||||
calendar = $(modalId + " #id_calendar_select").val()
|
||||
access = $(modalId + " #id_access_select").val()
|
||||
accessCalendars.rest.create
|
||||
calendarId: calendar
|
||||
access: access
|
||||
priority: priority
|
||||
, (data) ->
|
||||
$(modalId).modal "hide"
|
||||
refreshFnc()
|
||||
return
|
||||
|
||||
return
|
||||
|
||||
|
||||
# Makes form "beautyfull" :-)
|
||||
gui.tools.applyCustoms modalId
|
||||
return
|
||||
|
||||
return
|
||||
|
||||
return
|
||||
|
||||
onEdit: (value, event, table, refreshFnc) ->
|
||||
if value.id == -1
|
||||
api.templates.get "pool_access_default", (tmpl) ->
|
||||
modalId = gui.launchModal(gettext("Default fallback access"), api.templates.evaluate(tmpl,
|
||||
accessList: accessList
|
||||
access: servPool.fallbackAccess
|
||||
))
|
||||
$(modalId + " .button-accept").on "click", (event) ->
|
||||
access = $(modalId + " #id_access_select").val()
|
||||
servPool.fallbackAccess = access
|
||||
gui.servicesPools.rest.setFallbackAccess servPool.id, access, (data) ->
|
||||
$(modalId).modal "hide"
|
||||
refreshFnc()
|
||||
return
|
||||
# Makes form "beautyfull" :-)
|
||||
gui.tools.applyCustoms modalId
|
||||
return
|
||||
api.templates.get "pool_add_access", (tmpl) ->
|
||||
accessCalendars.rest.item value.id, (item) ->
|
||||
api.calendars.overview (data) ->
|
||||
gui.doLog "Item: ", item
|
||||
modalId = gui.launchModal(gettext("Edit access calendar"), api.templates.evaluate(tmpl,
|
||||
calendars: data
|
||||
priority: item.priority
|
||||
calendarId: item.calendarId
|
||||
accessList: accessList
|
||||
access: item.access
|
||||
))
|
||||
$(modalId + " .button-accept").on "click", (event) ->
|
||||
priority = $(modalId + " #id_priority").val()
|
||||
calendar = $(modalId + " #id_calendar_select").val()
|
||||
access = $(modalId + " #id_access_select").val()
|
||||
accessCalendars.rest.save
|
||||
id: item.id
|
||||
calendarId: calendar
|
||||
access: access
|
||||
priority: priority
|
||||
, (data) ->
|
||||
$(modalId).modal "hide"
|
||||
refreshFnc()
|
||||
return
|
||||
return
|
||||
# Makes form "beautyfull" :-)
|
||||
gui.tools.applyCustoms modalId
|
||||
return
|
||||
return
|
||||
return
|
||||
|
||||
onDelete: gui.methods.del(accessCalendars, gettext("Remove access calendar"), gettext("Access calendar removal error"))
|
||||
)
|
||||
|
||||
return [accessCalendarsTable]
|
@ -126,7 +126,7 @@ gui.servicesPools.link = (event) ->
|
||||
transports: "transports-placeholder"
|
||||
publications: "publications-placeholder"
|
||||
changelog: "changelog-placeholder"
|
||||
scheduling: "scheduling-placeholder"
|
||||
actions: "actions-placeholder"
|
||||
access: "access-placeholder"
|
||||
logs: "logs-placeholder"
|
||||
)
|
||||
@ -381,6 +381,10 @@ gui.servicesPools.link = (event) ->
|
||||
else
|
||||
$("#publications-placeholder_tab").addClass "hidden"
|
||||
|
||||
# Actions calendars
|
||||
for v in gui.servicesPools.actionsCalendars(servPool, info)
|
||||
prevTables.push v
|
||||
|
||||
#
|
||||
# Access calendars
|
||||
#
|
||||
|
@ -121,6 +121,7 @@
|
||||
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-connectivity.coffee"></script>
|
||||
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-servicespools.coffee"></script>
|
||||
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-servicespools-calendars.coffee"></script>
|
||||
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-servicespools-actions.coffee"></script>
|
||||
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-servicespools-publications.coffee"></script>
|
||||
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-servicespools-transports.coffee"></script>
|
||||
<script type="text/coffeescript" charset="utf-8" src="{% get_static_prefix %}adm/js/gui-d-config.coffee"></script>
|
||||
|
@ -14,9 +14,9 @@
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-danger alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
{{ message }}
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -33,10 +33,10 @@
|
||||
<li><a href="#{{ groups }}" data-toggle="tab">{% endverbatim %}{% trans 'Groups' %}{% verbatim %}</a></li>
|
||||
<li><a href="#{{ transports }}" data-toggle="tab">{% endverbatim %}{% trans 'Transports' %}{% verbatim %}</a></li>
|
||||
<li><a id="{{ publications }}_tab" href="#{{ publications }}" data-toggle="tab">{% endverbatim %}{% trans 'Publications' %}{% verbatim %}</a></li>
|
||||
<li><a id="{{ scheduling }}_tab" href="#{{ scheduling }}" data-toggle="tab">{% endverbatim %}{% trans 'Scheduled actions' %}{% verbatim %}</a></li>
|
||||
<li><a id="{{ actions }}_tab" href="#{{ actions }}" data-toggle="tab">{% endverbatim %}{% trans 'Scheduled actions' %}{% verbatim %}</a></li>
|
||||
<li><a id="{{ access }}_tab" href="#{{ access }}" data-toggle="tab">{% endverbatim %}{% trans 'Access Calendars' %}{% verbatim %}</a></li>
|
||||
<li><a href="#{{ logs }}" data-toggle="tab">{% endverbatim %}{% trans 'Logs' %}{% verbatim %}</a></li>
|
||||
</ul>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade in active" id="{{ pool_info }}">
|
||||
<div class="row">
|
||||
@ -44,7 +44,7 @@
|
||||
<div class="well">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="{{ assigned_services }}">
|
||||
<div class="row">
|
||||
@ -72,12 +72,12 @@
|
||||
<div id="{{ publications }}">
|
||||
</div>
|
||||
<div id="{{ changelog }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="{{ scheduling }}">...</div>
|
||||
<div class="tab-pane fade" id="{{ actions }}">...</div>
|
||||
<div class="tab-pane fade" id="{{ access }}">...</div>
|
||||
<div class="tab-pane fade" id="{{ logs }}">...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endverbatim %}
|
||||
{% endverbatim %}
|
||||
|
@ -65,6 +65,7 @@ BROWSER_NOT_SUPPORTED = 11,
|
||||
SERVICE_IN_MAINTENANCE = 12
|
||||
SERVICE_NOT_READY = 13
|
||||
SERVICE_IN_PREPARATION = 14
|
||||
SERVICE_CALENDAR_DENIED = 15
|
||||
|
||||
|
||||
strings = [
|
||||
@ -82,7 +83,8 @@ strings = [
|
||||
_('Your browser is not supported. Please, upgrade it to a modern HTML5 browser like Firefox or Chrome'),
|
||||
_('The requested service is in maintenance mode'),
|
||||
_('The service is not ready.\nPlease, try again in a few moments.'),
|
||||
_('Preparing service')
|
||||
_('Preparing service'),
|
||||
_('Service access denied by calendars')
|
||||
]
|
||||
|
||||
|
||||
|
@ -44,16 +44,17 @@ from uds.core.ui import theme
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.models import Transport, Image
|
||||
from uds.core.util import html
|
||||
from uds.core.services.Exceptions import ServiceNotReadyError, MaxServicesReachedError
|
||||
from uds.core.services.Exceptions import ServiceNotReadyError, MaxServicesReachedError, ServiceAccessDeniedByCalendar
|
||||
|
||||
import uds.web.errors as errors
|
||||
|
||||
import six
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__updated__ = '2015-10-09'
|
||||
__updated__ = '2016-03-16'
|
||||
|
||||
|
||||
@webLoginRequired(admin=False)
|
||||
@ -136,11 +137,18 @@ def clientEnabler(request, idService, idTransport):
|
||||
except MaxServicesReachedError:
|
||||
logger.info('Number of service reached MAX for service pool "{}"'.format(idService))
|
||||
error = errors.errorString(errors.MAX_SERVICES_REACHED)
|
||||
except ServiceAccessDeniedByCalendar:
|
||||
logger.info('Access tried to a calendar limited access pool "{}"'.format(idService))
|
||||
error = errors.errorString(errors.SERVICE_CALENDAR_DENIED)
|
||||
except Exception as e:
|
||||
logger.exception('Error')
|
||||
error = six.text_type(e)
|
||||
|
||||
|
||||
return HttpResponse(
|
||||
'{{ "url": "{}", "error": "{}" }}'.format(url, error),
|
||||
json.dumps({
|
||||
'url': six.text_type(url),
|
||||
'error': six.text_type(error)
|
||||
}),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user