Introduced management of webservices and added the corresponding menu items in the board panel, moved fleet panel, updated the api and fixed a path in README.rst
Change-Id: Ia99967e57fa88053692237709161cafc1fcdc2c2
This commit is contained in:
parent
b660cbfb5e
commit
748db492e9
@ -43,7 +43,7 @@ If you want to enable logs for a better debug follow the following steps or just
|
|||||||
|
|
||||||
mkdir /var/log/horizon
|
mkdir /var/log/horizon
|
||||||
touch /var/log/horizon/horizon.log
|
touch /var/log/horizon/horizon.log
|
||||||
chown -R horizon:horizon horizon
|
chown -R horizon:horizon /var/log/horizon
|
||||||
|
|
||||||
vim /etc/openstack-dashboard/local_settings.py
|
vim /etc/openstack-dashboard/local_settings.py
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
from iotronicclient import client as iotronic_client
|
from iotronicclient import client as iotronic_client
|
||||||
# from django.conf import settings
|
# from django.conf import settings
|
||||||
# from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
# from horizon import exceptions
|
# from horizon import exceptions
|
||||||
from horizon.utils.memoized import memoized # noqa
|
from horizon.utils.memoized import memoized # noqa
|
||||||
@ -260,3 +260,96 @@ def fleet_get_boards(request, fleet_id):
|
|||||||
"""Get fleet boards."""
|
"""Get fleet boards."""
|
||||||
return iotronicclient(request).fleet.boards_in_fleet(fleet=fleet_id)
|
return iotronicclient(request).fleet.boards_in_fleet(fleet=fleet_id)
|
||||||
|
|
||||||
|
|
||||||
|
# WEBSERVICES MANAGEMENT
|
||||||
|
def webservice_list(request, detail=None):
|
||||||
|
"""Get web services list."""
|
||||||
|
return iotronicclient(request).webservice.list()
|
||||||
|
|
||||||
|
|
||||||
|
def webservice_enabled_list(request):
|
||||||
|
"""Get enabled web services list."""
|
||||||
|
return iotronicclient(request).enabledwebservice.list()
|
||||||
|
|
||||||
|
|
||||||
|
def webservice_get_enabled_info(request, board_id, detail=None):
|
||||||
|
"""Get the information of the enabled webservices."""
|
||||||
|
ws_info = []
|
||||||
|
|
||||||
|
ws_enabled = iotronicclient(request).enabledwebservice.list()
|
||||||
|
|
||||||
|
for ws in ws_enabled:
|
||||||
|
if ws.board_uuid == board_id:
|
||||||
|
ws_info = ws
|
||||||
|
break
|
||||||
|
|
||||||
|
return ws_info
|
||||||
|
|
||||||
|
|
||||||
|
def webservices_on_board(request, board_id, fields=None):
|
||||||
|
"""Get web services on board list."""
|
||||||
|
webservices = iotronicclient(request).webserviceonboard.list(board_id,
|
||||||
|
fields)
|
||||||
|
|
||||||
|
detailed_webservices = []
|
||||||
|
# fields = {"name", "port", "uuid"}
|
||||||
|
|
||||||
|
for ws in webservices:
|
||||||
|
detailed_webservices.append({"name": ws._info["name"],
|
||||||
|
"port": ws._info["port"],
|
||||||
|
"uuid": ws._info["uuid"]})
|
||||||
|
|
||||||
|
return detailed_webservices
|
||||||
|
|
||||||
|
|
||||||
|
def webservice_get(request, webservice_id, fields):
|
||||||
|
"""Get web service info."""
|
||||||
|
return iotronicclient(request).webservice.get(webservice_id, fields)
|
||||||
|
|
||||||
|
|
||||||
|
def webservice_expose(request, board_id, name, port, secure):
|
||||||
|
"""Expose a web service."""
|
||||||
|
return iotronicclient(request).webserviceonboard.expose(board_id,
|
||||||
|
name,
|
||||||
|
port,
|
||||||
|
secure)
|
||||||
|
|
||||||
|
|
||||||
|
def webservice_unexpose(request, webservice_id):
|
||||||
|
"""Unexpose a web service from a board."""
|
||||||
|
return iotronicclient(request).webservice.delete(webservice_id)
|
||||||
|
|
||||||
|
|
||||||
|
def webservice_enable(request, board, dns, zone, email):
|
||||||
|
"""Enable web service."""
|
||||||
|
return iotronicclient(request).webserviceonboard.enable_webservice(board,
|
||||||
|
dns,
|
||||||
|
zone,
|
||||||
|
email)
|
||||||
|
|
||||||
|
|
||||||
|
def webservice_disable(request, board):
|
||||||
|
"""Disable web service."""
|
||||||
|
return iotronicclient(request).webserviceonboard.disable_webservice(board)
|
||||||
|
|
||||||
|
|
||||||
|
def boards_no_webservice(request):
|
||||||
|
"""Get all the boards that have not webservice enabled."""
|
||||||
|
|
||||||
|
boards_no_ws_enabled = []
|
||||||
|
|
||||||
|
board_list = iotronicclient(request).board.list()
|
||||||
|
board_list.sort(key=lambda b: b.name)
|
||||||
|
|
||||||
|
board_ws_list = iotronicclient(request).enabledwebservice.list()
|
||||||
|
|
||||||
|
for board in board_list:
|
||||||
|
for i in range(len(board_ws_list)):
|
||||||
|
if board.uuid == board_ws_list[i].board_uuid:
|
||||||
|
break
|
||||||
|
elif ((board.uuid != board_ws_list[i].board_uuid) and
|
||||||
|
(i==len(board_ws_list)-1)):
|
||||||
|
boards_no_ws_enabled.append((board.uuid, _(board.name)))
|
||||||
|
|
||||||
|
# LOG.debug('COMPLEMENTARY %s', boards_no_ws_enabled)
|
||||||
|
return boards_no_ws_enabled
|
||||||
|
23
iotronic_ui/enabled/_6040_iot_webservices_panel.py
Normal file
23
iotronic_ui/enabled/_6040_iot_webservices_panel.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||||
|
PANEL = 'webservices'
|
||||||
|
# The slug of the dashboard the PANEL associated with. Required.
|
||||||
|
PANEL_DASHBOARD = 'iot'
|
||||||
|
# The slug of the panel group the PANEL is associated with.
|
||||||
|
PANEL_GROUP = 'iot'
|
||||||
|
# If set, it will update the default panel of the PANEL_DASHBOARD.
|
||||||
|
DEFAULT_PANEL = ''
|
||||||
|
|
||||||
|
# Python panel class of the PANEL to be added.
|
||||||
|
ADD_PANEL = 'iotronic_ui.iot.webservices.panel.Webservices'
|
@ -34,7 +34,8 @@ class CreateBoardForm(forms.SelfHandlingForm):
|
|||||||
# MODIFY ---> options: yun, server
|
# MODIFY ---> options: yun, server
|
||||||
type = forms.ChoiceField(
|
type = forms.ChoiceField(
|
||||||
label=_("Type"),
|
label=_("Type"),
|
||||||
choices=[('yun', _('YUN')), ('server', _('Server'))],
|
# choices=[('yun', _('YUN')), ('server', _('Server'))],
|
||||||
|
choices=[('gateway', _('Gateway')), ('server', _('Server'))],
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
attrs={'class': 'switchable', 'data-slug': 'slug-type'},
|
attrs={'class': 'switchable', 'data-slug': 'slug-type'},
|
||||||
)
|
)
|
||||||
@ -175,7 +176,7 @@ class UpdateBoardForm(forms.SelfHandlingForm):
|
|||||||
|
|
||||||
class EnableServiceForm(forms.SelfHandlingForm):
|
class EnableServiceForm(forms.SelfHandlingForm):
|
||||||
|
|
||||||
uuid = forms.CharField(label=_("Plugin ID"), widget=forms.HiddenInput)
|
uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput)
|
||||||
|
|
||||||
name = forms.CharField(
|
name = forms.CharField(
|
||||||
label=_('Board Name'),
|
label=_('Board Name'),
|
||||||
@ -216,9 +217,10 @@ class EnableServiceForm(forms.SelfHandlingForm):
|
|||||||
exceptions.handle(request, _(message_text))
|
exceptions.handle(request, _(message_text))
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
class DisableServiceForm(forms.SelfHandlingForm):
|
class DisableServiceForm(forms.SelfHandlingForm):
|
||||||
|
|
||||||
uuid = forms.CharField(label=_("Plugin ID"), widget=forms.HiddenInput)
|
uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput)
|
||||||
|
|
||||||
name = forms.CharField(
|
name = forms.CharField(
|
||||||
label=_('Board Name'),
|
label=_('Board Name'),
|
||||||
@ -257,6 +259,7 @@ class DisableServiceForm(forms.SelfHandlingForm):
|
|||||||
except Exception:
|
except Exception:
|
||||||
message_text = "Unable to disable service."
|
message_text = "Unable to disable service."
|
||||||
exceptions.handle(request, _(message_text))
|
exceptions.handle(request, _(message_text))
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class AttachPortForm(forms.SelfHandlingForm):
|
class AttachPortForm(forms.SelfHandlingForm):
|
||||||
@ -352,6 +355,64 @@ class DetachPortForm(forms.SelfHandlingForm):
|
|||||||
exceptions.handle(request, _(message_text))
|
exceptions.handle(request, _(message_text))
|
||||||
|
|
||||||
|
|
||||||
|
class EnableWebServiceForm(forms.SelfHandlingForm):
|
||||||
|
|
||||||
|
uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput)
|
||||||
|
|
||||||
|
name = forms.CharField(
|
||||||
|
label=_('Board Name'),
|
||||||
|
widget=forms.TextInput(attrs={'readonly': 'readonly'})
|
||||||
|
)
|
||||||
|
|
||||||
|
dns = forms.CharField(label=_("Domain Name Server"))
|
||||||
|
zone = forms.CharField(label=_("Zone"))
|
||||||
|
email = forms.CharField(label=_("Email"))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(EnableWebServiceForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
|
||||||
|
try:
|
||||||
|
iotronic.webservice_enable(request, data["uuid"],
|
||||||
|
data["dns"], data["zone"],
|
||||||
|
data["email"])
|
||||||
|
|
||||||
|
messages.success(request, _("Web Service enabled on board " +
|
||||||
|
str(data["name"]) + "."))
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
message_text = "Unable to enable web service."
|
||||||
|
exceptions.handle(request, _(message_text))
|
||||||
|
|
||||||
|
|
||||||
|
class DisableWebServiceForm(forms.SelfHandlingForm):
|
||||||
|
|
||||||
|
uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput)
|
||||||
|
|
||||||
|
name = forms.CharField(
|
||||||
|
label=_('Board Name'),
|
||||||
|
widget=forms.TextInput(attrs={'readonly': 'readonly'})
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(DisableWebServiceForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
|
||||||
|
try:
|
||||||
|
iotronic.webservice_disable(request, data["uuid"])
|
||||||
|
|
||||||
|
messages.success(request, _("Web Service disabled on board " +
|
||||||
|
str(data["name"]) + "."))
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
message_text = "Unable to disable web service."
|
||||||
|
exceptions.handle(request, _(message_text))
|
||||||
|
|
||||||
|
|
||||||
class RemovePluginsForm(forms.SelfHandlingForm):
|
class RemovePluginsForm(forms.SelfHandlingForm):
|
||||||
|
|
||||||
uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput)
|
uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput)
|
||||||
|
@ -73,6 +73,7 @@ class EnableServiceLink(tables.LinkAction):
|
|||||||
# policy_rules = (("iot", "iot:service_action"),)
|
# policy_rules = (("iot", "iot:service_action"),)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
class DisableServiceLink(tables.LinkAction):
|
class DisableServiceLink(tables.LinkAction):
|
||||||
name = "disableservice"
|
name = "disableservice"
|
||||||
verbose_name = _("Disable Service(s)")
|
verbose_name = _("Disable Service(s)")
|
||||||
@ -80,6 +81,7 @@ class DisableServiceLink(tables.LinkAction):
|
|||||||
classes = ("ajax-modal",)
|
classes = ("ajax-modal",)
|
||||||
# icon = "plus"
|
# icon = "plus"
|
||||||
# policy_rules = (("iot", "iot:service_action"),)
|
# policy_rules = (("iot", "iot:service_action"),)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class RemovePluginsLink(tables.LinkAction):
|
class RemovePluginsLink(tables.LinkAction):
|
||||||
@ -116,6 +118,22 @@ class DetachPortLink(tables.LinkAction):
|
|||||||
icon = "plus"
|
icon = "plus"
|
||||||
|
|
||||||
|
|
||||||
|
class EnableWebServiceLink(tables.LinkAction):
|
||||||
|
name = "enablewebservice"
|
||||||
|
verbose_name = _("Enable Web Services")
|
||||||
|
url = "horizon:iot:boards:enablewebservice"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
icon = "plus"
|
||||||
|
|
||||||
|
|
||||||
|
class DisableWebServiceLink(tables.LinkAction):
|
||||||
|
name = "disablewebservice"
|
||||||
|
verbose_name = _("Disable Web Services")
|
||||||
|
url = "horizon:iot:boards:disablewebservice"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
icon = "plus"
|
||||||
|
|
||||||
|
|
||||||
class DeleteBoardsAction(tables.DeleteAction):
|
class DeleteBoardsAction(tables.DeleteAction):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def action_present(count):
|
def action_present(count):
|
||||||
@ -191,8 +209,10 @@ class BoardsTable(tables.DataTable):
|
|||||||
class Meta(object):
|
class Meta(object):
|
||||||
name = "boards"
|
name = "boards"
|
||||||
verbose_name = _("boards")
|
verbose_name = _("boards")
|
||||||
row_actions = (EditBoardLink, EnableServiceLink, DisableServiceLink,
|
# row_actions = (EditBoardLink, EnableServiceLink, DisableServiceLink,
|
||||||
|
row_actions = (EditBoardLink, EnableServiceLink,
|
||||||
RestoreServices, AttachPortLink, DetachPortLink,
|
RestoreServices, AttachPortLink, DetachPortLink,
|
||||||
|
EnableWebServiceLink, DisableWebServiceLink,
|
||||||
RemovePluginsLink, RemoveServicesLink,
|
RemovePluginsLink, RemoveServicesLink,
|
||||||
DeleteBoardsAction)
|
DeleteBoardsAction)
|
||||||
table_actions = (BoardFilterAction, CreateBoardLink,
|
table_actions = (BoardFilterAction, CreateBoardLink,
|
||||||
|
@ -36,11 +36,13 @@ class OverviewTab(tabs.Tab):
|
|||||||
coordinates = self.tab_group.kwargs['board'].__dict__['location'][0]
|
coordinates = self.tab_group.kwargs['board'].__dict__['location'][0]
|
||||||
ports = self.tab_group.kwargs['board']._info['ports']
|
ports = self.tab_group.kwargs['board']._info['ports']
|
||||||
services = self.tab_group.kwargs['board']._info['services']
|
services = self.tab_group.kwargs['board']._info['services']
|
||||||
|
webservices = self.tab_group.kwargs['board']._info['webservices']
|
||||||
plugins = self.tab_group.kwargs['board']._info['plugins']
|
plugins = self.tab_group.kwargs['board']._info['plugins']
|
||||||
|
|
||||||
return {"board": self.tab_group.kwargs['board'],
|
return {"board": self.tab_group.kwargs['board'],
|
||||||
"coordinates": coordinates,
|
"coordinates": coordinates,
|
||||||
"services": services,
|
"services": services,
|
||||||
|
"webservices": webservices,
|
||||||
"ports": ports,
|
"ports": ports,
|
||||||
"plugins": plugins,
|
"plugins": plugins,
|
||||||
"is_superuser": request.user.is_superuser}
|
"is_superuser": request.user.is_superuser}
|
||||||
|
@ -57,6 +57,19 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
|
<h4>{% trans "Web Services" %}</h4>
|
||||||
|
<hr class="header_rule">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
{% if webservices %}
|
||||||
|
{% for ws in webservices %}
|
||||||
|
<dt>{{ ws.name }} [{{ ws.port }}]</dt>
|
||||||
|
<dd>{{ ws.uuid }}</dd>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<dd>--</dd>
|
||||||
|
{% endif %}
|
||||||
|
</dl>
|
||||||
|
|
||||||
<h4>{% trans "Plugins" %}</h4>
|
<h4>{% trans "Plugins" %}</h4>
|
||||||
<hr class="header_rule">
|
<hr class="header_rule">
|
||||||
<dl class="dl-horizontal">
|
<dl class="dl-horizontal">
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-body-right %}
|
||||||
|
<h3>{% trans "Description:" %}</h3>
|
||||||
|
<p>{% trans "Disable web service(s) on the selected board." %}</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,8 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-body-right %}
|
||||||
|
<h3>{% trans "Description:" %}</h3>
|
||||||
|
<p>{% trans "Enable web service(s) on the selected board." %}</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Disable" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include 'iot/boards/_disablewebservice.html' %}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Enable" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include 'iot/boards/_enablewebservice.html' %}
|
||||||
|
{% endblock %}
|
@ -26,9 +26,13 @@ urlpatterns = [
|
|||||||
views.RemoveServicesView.as_view(), name='removeservices'),
|
views.RemoveServicesView.as_view(), name='removeservices'),
|
||||||
url(r'^(?P<board_id>[^/]+)/enableservice/$',
|
url(r'^(?P<board_id>[^/]+)/enableservice/$',
|
||||||
views.EnableServiceView.as_view(), name='enableservice'),
|
views.EnableServiceView.as_view(), name='enableservice'),
|
||||||
url(r'^(?P<board_id>[^/]+)/disableservice/$',
|
# url(r'^(?P<board_id>[^/]+)/disableservice/$',
|
||||||
views.DisableServiceView.as_view(), name='disableservice'),
|
# views.DisableServiceView.as_view(), name='disableservice'),
|
||||||
url(r'^(?P<board_id>[^/]+)/attachport/$',
|
url(r'^(?P<board_id>[^/]+)/attachport/$',
|
||||||
|
views.EnableWebServiceView.as_view(), name='enablewebservice'),
|
||||||
|
url(r'^(?P<board_id>[^/]+)/enablewebservice/$',
|
||||||
|
views.DisableWebServiceView.as_view(), name='disablewebservice'),
|
||||||
|
url(r'^(?P<board_id>[^/]+)/disablewebservice/$',
|
||||||
views.AttachPortView.as_view(), name='attachport'),
|
views.AttachPortView.as_view(), name='attachport'),
|
||||||
url(r'^(?P<board_id>[^/]+)/detachport/$',
|
url(r'^(?P<board_id>[^/]+)/detachport/$',
|
||||||
views.DetachPortView.as_view(), name='detachport'),
|
views.DetachPortView.as_view(), name='detachport'),
|
||||||
|
@ -75,6 +75,18 @@ class IndexView(tables.DataTableView):
|
|||||||
board.uuid,
|
board.uuid,
|
||||||
True)
|
True)
|
||||||
|
|
||||||
|
# TO BE REMOVED
|
||||||
|
# We are filtering the services that starts with "webservice"
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
filter_ws = []
|
||||||
|
for service in board_services:
|
||||||
|
if ((service["name"] != "webservice") and
|
||||||
|
(service["name"] != "webservice_ssl")):
|
||||||
|
filter_ws.append(service)
|
||||||
|
|
||||||
|
board_services = filter_ws
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
# board.__dict__.update(dict(services=board_services))
|
# board.__dict__.update(dict(services=board_services))
|
||||||
board._info.update(dict(services=board_services))
|
board._info.update(dict(services=board_services))
|
||||||
|
|
||||||
@ -205,6 +217,7 @@ class EnableServiceView(forms.ModalFormView):
|
|||||||
'service_list': service_list}
|
'service_list': service_list}
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
class DisableServiceView(forms.ModalFormView):
|
class DisableServiceView(forms.ModalFormView):
|
||||||
template_name = 'iot/boards/disableservice.html'
|
template_name = 'iot/boards/disableservice.html'
|
||||||
modal_header = _("Disable Service(s)")
|
modal_header = _("Disable Service(s)")
|
||||||
@ -246,15 +259,31 @@ class DisableServiceView(forms.ModalFormView):
|
|||||||
|
|
||||||
service_list = []
|
service_list = []
|
||||||
|
|
||||||
|
|
||||||
|
# BEFORE filtering necessity
|
||||||
|
# for cloud_service in cloud_services:
|
||||||
|
# for board_service in board_services:
|
||||||
|
# if board_service["uuid"] == cloud_service._info["uuid"]:
|
||||||
|
# service_list.append((cloud_service._info["uuid"],
|
||||||
|
# _(cloud_service._info["name"])))
|
||||||
|
|
||||||
|
# AFTER filtering necessity
|
||||||
|
# We are filtering the services that starts with "webservice"
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
for cloud_service in cloud_services:
|
for cloud_service in cloud_services:
|
||||||
for board_service in board_services:
|
for board_service in board_services:
|
||||||
if board_service["uuid"] == cloud_service._info["uuid"]:
|
if ((board_service["uuid"] == cloud_service._info["uuid"]) and
|
||||||
|
((board_service["name"] != "webservice") and
|
||||||
|
(board_service["name"] != "webservice_ssl"))):
|
||||||
service_list.append((cloud_service._info["uuid"],
|
service_list.append((cloud_service._info["uuid"],
|
||||||
_(cloud_service._info["name"])))
|
_(cloud_service._info["name"])))
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
return {'uuid': board.uuid,
|
return {'uuid': board.uuid,
|
||||||
'name': board.name,
|
'name': board.name,
|
||||||
'service_list': service_list}
|
'service_list': service_list}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class AttachPortView(forms.ModalFormView):
|
class AttachPortView(forms.ModalFormView):
|
||||||
@ -357,6 +386,80 @@ class DetachPortView(forms.ModalFormView):
|
|||||||
'ports': ports}
|
'ports': ports}
|
||||||
|
|
||||||
|
|
||||||
|
class EnableWebServiceView(forms.ModalFormView):
|
||||||
|
template_name = 'iot/boards/enablewebservice.html'
|
||||||
|
modal_header = _("Enable Web Service(s)")
|
||||||
|
form_id = "webservice_enable_form"
|
||||||
|
form_class = project_forms.EnableWebServiceForm
|
||||||
|
submit_label = _("Enable")
|
||||||
|
# submit_url = reverse_lazy("horizon:iot:boards:enablewebservice")
|
||||||
|
submit_url = "horizon:iot:boards:enablewebservice"
|
||||||
|
success_url = reverse_lazy('horizon:iot:boards:index')
|
||||||
|
page_title = _("Enable")
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_object(self):
|
||||||
|
try:
|
||||||
|
return api.iotronic.board_get(self.request,
|
||||||
|
self.kwargs['board_id'],
|
||||||
|
None)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
redirect = reverse("horizon:iot:boards:index")
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to get board information.'),
|
||||||
|
redirect=redirect)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(EnableWebServiceView, self).get_context_data(**kwargs)
|
||||||
|
args = (self.get_object().uuid,)
|
||||||
|
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
board = self.get_object()
|
||||||
|
|
||||||
|
return {'uuid': board.uuid,
|
||||||
|
'name': board.name}
|
||||||
|
|
||||||
|
|
||||||
|
class DisableWebServiceView(forms.ModalFormView):
|
||||||
|
template_name = 'iot/boards/disablewebservice.html'
|
||||||
|
modal_header = _("Disable Web Service(s)")
|
||||||
|
form_id = "webservice_disable_form"
|
||||||
|
form_class = project_forms.DisableWebServiceForm
|
||||||
|
submit_label = _("Disable")
|
||||||
|
# submit_url = reverse_lazy("horizon:iot:boards:disablewebservice")
|
||||||
|
submit_url = "horizon:iot:boards:disablewebservice"
|
||||||
|
success_url = reverse_lazy('horizon:iot:boards:index')
|
||||||
|
page_title = _("Disable")
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_object(self):
|
||||||
|
try:
|
||||||
|
return api.iotronic.board_get(self.request,
|
||||||
|
self.kwargs['board_id'],
|
||||||
|
None)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
redirect = reverse("horizon:iot:boards:index")
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to get board information.'),
|
||||||
|
redirect=redirect)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(DisableWebServiceView, self).get_context_data(**kwargs)
|
||||||
|
args = (self.get_object().uuid,)
|
||||||
|
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
board = self.get_object()
|
||||||
|
|
||||||
|
return {'uuid': board.uuid,
|
||||||
|
'name': board.name}
|
||||||
|
|
||||||
|
|
||||||
class RemovePluginsView(forms.ModalFormView):
|
class RemovePluginsView(forms.ModalFormView):
|
||||||
template_name = 'iot/boards/removeplugins.html'
|
template_name = 'iot/boards/removeplugins.html'
|
||||||
modal_header = _("Remove Plugins from board")
|
modal_header = _("Remove Plugins from board")
|
||||||
@ -445,7 +548,15 @@ class RemoveServicesView(forms.ModalFormView):
|
|||||||
|
|
||||||
service_list = []
|
service_list = []
|
||||||
for service in services:
|
for service in services:
|
||||||
service_list.append((service["uuid"], _(service["name"])))
|
# service_list.append((service["uuid"], _(service["name"])))
|
||||||
|
|
||||||
|
# TO BE REMOVED
|
||||||
|
# ###########################################################
|
||||||
|
# We are filtering the services that starts with "webservice"
|
||||||
|
if ((service["name"] != "webservice") and
|
||||||
|
(service["name"] != "webservice_ssl")):
|
||||||
|
service_list.append((service["uuid"], _(service["name"])))
|
||||||
|
# ###########################################################
|
||||||
|
|
||||||
return {'uuid': board.uuid,
|
return {'uuid': board.uuid,
|
||||||
'name': board.name,
|
'name': board.name,
|
||||||
@ -501,6 +612,10 @@ class DetailView(tabs.TabView):
|
|||||||
board_id)
|
board_id)
|
||||||
board._info.update(dict(plugins=board_plugins))
|
board._info.update(dict(plugins=board_plugins))
|
||||||
|
|
||||||
|
board_webservices = api.iotronic.webservices_on_board(self.request,
|
||||||
|
board_id)
|
||||||
|
board._info.update(dict(webservices=board_webservices))
|
||||||
|
|
||||||
# Adding fleet name
|
# Adding fleet name
|
||||||
if board.fleet != None:
|
if board.fleet != None:
|
||||||
fleet_info = api.iotronic.fleet_get(self.request,
|
fleet_info = api.iotronic.fleet_get(self.request,
|
||||||
|
@ -18,7 +18,8 @@ import horizon
|
|||||||
class Iot(horizon.Dashboard):
|
class Iot(horizon.Dashboard):
|
||||||
name = _("IoT")
|
name = _("IoT")
|
||||||
slug = "iot"
|
slug = "iot"
|
||||||
panels = ('boards', 'plugins', 'services', 'fleets') # Add your panels here.
|
panels = ('boards', 'plugins', 'services',
|
||||||
|
'webservices', 'fleets') # Add your panels here.
|
||||||
|
|
||||||
# Specify the slug of the dashboard's default panel.
|
# Specify the slug of the dashboard's default panel.
|
||||||
default_panel = 'boards'
|
default_panel = 'boards'
|
||||||
|
@ -88,64 +88,3 @@ class UpdateFleetForm(forms.SelfHandlingForm):
|
|||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
exceptions.handle(request, _('Unable to update fleet.'))
|
exceptions.handle(request, _('Unable to update fleet.'))
|
||||||
|
|
||||||
|
|
||||||
class FleetActionForm(forms.SelfHandlingForm):
|
|
||||||
|
|
||||||
uuid = forms.CharField(label=_("Plugin ID"), widget=forms.HiddenInput)
|
|
||||||
|
|
||||||
name = forms.CharField(
|
|
||||||
label=_('Fleet Name'),
|
|
||||||
widget=forms.TextInput(attrs={'readonly': 'readonly'})
|
|
||||||
)
|
|
||||||
|
|
||||||
board_list = forms.MultipleChoiceField(
|
|
||||||
label=_("Boards List"),
|
|
||||||
widget=forms.SelectMultiple(
|
|
||||||
attrs={'class': 'switchable', 'data-slug': 'slug-select-boards'}),
|
|
||||||
help_text=_("Select boards in this pool")
|
|
||||||
)
|
|
||||||
|
|
||||||
action = forms.ChoiceField(
|
|
||||||
label=_("Action"),
|
|
||||||
choices=[('FleetEnable', _('Enable')),
|
|
||||||
('FleetDisable', _('Disable')),
|
|
||||||
('FleetRestore', _('Restore'))],
|
|
||||||
widget=forms.Select(
|
|
||||||
attrs={'class': 'switchable', 'data-slug': 'slug-action'},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
|
|
||||||
super(FleetActionForm, self).__init__(*args, **kwargs)
|
|
||||||
# input=kwargs.get('initial',{})
|
|
||||||
|
|
||||||
self.fields["board_list"].choices = kwargs["initial"]["board_list"]
|
|
||||||
|
|
||||||
def handle(self, request, data):
|
|
||||||
|
|
||||||
counter = 0
|
|
||||||
|
|
||||||
for board in data["board_list"]:
|
|
||||||
for key, value in self.fields["board_list"].choices:
|
|
||||||
if key == board:
|
|
||||||
|
|
||||||
try:
|
|
||||||
action = iotronic.fleet_action(request, key,
|
|
||||||
data["uuid"],
|
|
||||||
data["action"])
|
|
||||||
message_text = action
|
|
||||||
messages.success(request, _(message_text))
|
|
||||||
|
|
||||||
if counter != len(data["board_list"]) - 1:
|
|
||||||
counter += 1
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
message_text = "Unable to execute action on board " \
|
|
||||||
+ str(value) + "."
|
|
||||||
exceptions.handle(request, _(message_text))
|
|
||||||
|
|
||||||
break
|
|
||||||
|
@ -40,15 +40,6 @@ class EditFleetLink(tables.LinkAction):
|
|||||||
# policy_rules = (("iot", "iot:update_fleet"),)
|
# policy_rules = (("iot", "iot:update_fleet"),)
|
||||||
|
|
||||||
|
|
||||||
class ActionFleetLink(tables.LinkAction):
|
|
||||||
name = "action"
|
|
||||||
verbose_name = _("Fleet Action")
|
|
||||||
url = "horizon:iot:fleets:action"
|
|
||||||
classes = ("ajax-modal",)
|
|
||||||
# icon = "plus"
|
|
||||||
# policy_rules = (("iot", "iot:fleet_action"),)
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteFleetsAction(tables.DeleteAction):
|
class DeleteFleetsAction(tables.DeleteAction):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def action_present(count):
|
def action_present(count):
|
||||||
@ -93,8 +84,6 @@ class FleetsTable(tables.DataTable):
|
|||||||
class Meta(object):
|
class Meta(object):
|
||||||
name = "fleets"
|
name = "fleets"
|
||||||
verbose_name = _("fleets")
|
verbose_name = _("fleets")
|
||||||
# row_actions = (EditFleetLink, ActionFleetLink,
|
|
||||||
# DeleteFleetsAction)
|
|
||||||
row_actions = (EditFleetLink, DeleteFleetsAction)
|
row_actions = (EditFleetLink, DeleteFleetsAction)
|
||||||
table_actions = (FleetFilterAction, CreateFleetLink,
|
table_actions = (FleetFilterAction, CreateFleetLink,
|
||||||
DeleteFleetsAction)
|
DeleteFleetsAction)
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block title %}{% trans "Execute Action" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block main %}
|
|
||||||
{% include 'iot/fleets/_action.html' %}
|
|
||||||
{% endblock %}
|
|
@ -20,8 +20,6 @@ urlpatterns = [
|
|||||||
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||||
url(r'^(?P<fleet_id>[^/]+)/update/$', views.UpdateView.as_view(),
|
url(r'^(?P<fleet_id>[^/]+)/update/$', views.UpdateView.as_view(),
|
||||||
name='update'),
|
name='update'),
|
||||||
url(r'^(?P<fleet_id>[^/]+)/action/$', views.ActionView.as_view(),
|
|
||||||
name='action'),
|
|
||||||
url(r'^(?P<fleet_id>[^/]+)/detail/$', views.FleetDetailView.as_view(),
|
url(r'^(?P<fleet_id>[^/]+)/detail/$', views.FleetDetailView.as_view(),
|
||||||
name='detail'),
|
name='detail'),
|
||||||
]
|
]
|
||||||
|
@ -120,51 +120,6 @@ class UpdateView(forms.ModalFormView):
|
|||||||
'description': fleet.description}
|
'description': fleet.description}
|
||||||
|
|
||||||
|
|
||||||
class ActionView(forms.ModalFormView):
|
|
||||||
template_name = 'iot/fleets/action.html'
|
|
||||||
modal_header = _("Fleet Action")
|
|
||||||
form_id = "fleet_action_form"
|
|
||||||
form_class = project_forms.FleetActionForm
|
|
||||||
submit_label = _("Fleet Action")
|
|
||||||
# submit_url = reverse_lazy("horizon:iot:fleets:action")
|
|
||||||
submit_url = "horizon:iot:fleets:action"
|
|
||||||
success_url = reverse_lazy('horizon:iot:fleets:index')
|
|
||||||
page_title = _("Fleet Action")
|
|
||||||
|
|
||||||
@memoized.memoized_method
|
|
||||||
def get_object(self):
|
|
||||||
try:
|
|
||||||
return iotronic.fleet_get(self.request,
|
|
||||||
self.kwargs['fleet_id'],
|
|
||||||
None)
|
|
||||||
except Exception:
|
|
||||||
redirect = reverse("horizon:iot:fleets:index")
|
|
||||||
exceptions.handle(self.request,
|
|
||||||
_('Unable to get fleet information.'),
|
|
||||||
redirect=redirect)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(ActionView, self).get_context_data(**kwargs)
|
|
||||||
args = (self.get_object().uuid,)
|
|
||||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
fleet = self.get_object()
|
|
||||||
|
|
||||||
# Populate boards
|
|
||||||
boards = iotronic.board_list(self.request, "online", None, None)
|
|
||||||
boards.sort(key=lambda b: b.name)
|
|
||||||
|
|
||||||
board_list = []
|
|
||||||
for board in boards:
|
|
||||||
board_list.append((board.uuid, _(board.name)))
|
|
||||||
|
|
||||||
return {'uuid': fleet.uuid,
|
|
||||||
'name': fleet.name,
|
|
||||||
'board_list': board_list}
|
|
||||||
|
|
||||||
|
|
||||||
class DetailView(tabs.TabView):
|
class DetailView(tabs.TabView):
|
||||||
tab_group_class = project_tabs.FleetDetailTabs
|
tab_group_class = project_tabs.FleetDetailTabs
|
||||||
template_name = 'horizon/common/_detail.html'
|
template_name = 'horizon/common/_detail.html'
|
||||||
|
0
iotronic_ui/iot/webservices/__init__.py
Normal file
0
iotronic_ui/iot/webservices/__init__.py
Normal file
103
iotronic_ui/iot/webservices/forms.py
Normal file
103
iotronic_ui/iot/webservices/forms.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import messages
|
||||||
|
|
||||||
|
from openstack_dashboard.api import iotronic
|
||||||
|
from openstack_dashboard import policy
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExposeWebserviceForm(forms.SelfHandlingForm):
|
||||||
|
|
||||||
|
uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput)
|
||||||
|
|
||||||
|
name = forms.CharField(
|
||||||
|
label=_('Board Name'),
|
||||||
|
widget=forms.TextInput(attrs={'readonly': 'readonly'})
|
||||||
|
)
|
||||||
|
|
||||||
|
ws_name = forms.CharField(label=_("Web Service Name"))
|
||||||
|
|
||||||
|
port = forms.IntegerField(
|
||||||
|
label=_("Port"),
|
||||||
|
help_text=_("The port used by the service")
|
||||||
|
)
|
||||||
|
|
||||||
|
secure = forms.BooleanField(label=_("Secure"), initial=True)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ExposeWebserviceForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
try:
|
||||||
|
iotronic.webservice_expose(request, data["uuid"],
|
||||||
|
data["ws_name"], data["port"],
|
||||||
|
data["secure"])
|
||||||
|
|
||||||
|
messages.success(request, _("Web Service " + str(data["name"]) +
|
||||||
|
" exposed successfully on port " +
|
||||||
|
str(data["port"]) + "."))
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(request, _('Unable to expose web service.'))
|
||||||
|
|
||||||
|
|
||||||
|
class UnexposeWebserviceForm(forms.SelfHandlingForm):
|
||||||
|
|
||||||
|
uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput)
|
||||||
|
|
||||||
|
name = forms.CharField(
|
||||||
|
label=_('Board Name'),
|
||||||
|
widget=forms.TextInput(attrs={'readonly': 'readonly'})
|
||||||
|
)
|
||||||
|
|
||||||
|
ws_onboard = forms.MultipleChoiceField(
|
||||||
|
label=_("Web Services on board"),
|
||||||
|
widget=forms.SelectMultiple(
|
||||||
|
attrs={'class': 'switchable',
|
||||||
|
'data-slug': 'slug-select-webservices'}),
|
||||||
|
help_text=_("Select a webservice from the list")
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
super(UnexposeWebserviceForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields["ws_onboard"].choices = kwargs["initial"]["ws_onboard"]
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
for ws in data["ws_onboard"]:
|
||||||
|
try:
|
||||||
|
iotronic.webservice_unexpose(request, ws)
|
||||||
|
|
||||||
|
message_text = "Web Service(s) unexposed successfully."
|
||||||
|
messages.success(request, _(message_text))
|
||||||
|
|
||||||
|
if counter != len(data["ws_onboard"]) - 1:
|
||||||
|
counter += 1
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
LOG.debug("HERE")
|
||||||
|
message_text = "Unable to unexpose web service."
|
||||||
|
exceptions.handle(request, _(message_text))
|
28
iotronic_ui/iot/webservices/panel.py
Normal file
28
iotronic_ui/iot/webservices/panel.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
# from openstack_dashboard.api import keystone
|
||||||
|
from iotronic_ui.iot import dashboard
|
||||||
|
|
||||||
|
|
||||||
|
class Webservices(horizon.Panel):
|
||||||
|
name = _("Web Services")
|
||||||
|
slug = "webservices"
|
||||||
|
# permissions = ('openstack.webservices.iot', )
|
||||||
|
# policy_rules = (("iot", "iot:list_all_webservices"),)
|
||||||
|
|
||||||
|
|
||||||
|
dashboard.Iot.register(Webservices)
|
121
iotronic_ui/iot/webservices/tables.py
Normal file
121
iotronic_ui/iot/webservices/tables.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.translation import ungettext_lazy
|
||||||
|
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExposeWebserviceLink(tables.LinkAction):
|
||||||
|
name = "expose"
|
||||||
|
verbose_name = _("Expose")
|
||||||
|
url = "horizon:iot:webservices:expose"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
icon = "plus"
|
||||||
|
# policy_rules = (("iot", "iot:expose_webservice"),)
|
||||||
|
|
||||||
|
|
||||||
|
class UnexposeWebserviceLink(tables.LinkAction):
|
||||||
|
name = "unexpose"
|
||||||
|
verbose_name = _("Unexpose")
|
||||||
|
url = "horizon:iot:webservices:unexpose"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
icon = "plus"
|
||||||
|
# policy_rules = (("iot", "iot:unexpose_webservice"),)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
class DisableWebservicesAction(tables.DeleteAction):
|
||||||
|
@staticmethod
|
||||||
|
def action_present(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Disable",
|
||||||
|
u"Disable Web Services",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_past(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Disabled Web Service",
|
||||||
|
u"Disabled Web Services",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
# policy_rules = (("iot", "iot:disable_webservice"),)
|
||||||
|
|
||||||
|
def delete(self, request, board_id):
|
||||||
|
api.iotronic.webservice_disable(request, board_id)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class WebserviceFilterAction(tables.FilterAction):
|
||||||
|
|
||||||
|
def filter(self, table, webservices, filter_string):
|
||||||
|
# Naive case-insensitive search.
|
||||||
|
q = filter_string.lower()
|
||||||
|
return [webservice for webservice in webservices
|
||||||
|
if q in webservice.name.lower()]
|
||||||
|
|
||||||
|
|
||||||
|
def show_webservices(board_info):
|
||||||
|
template_name = 'iot/webservices/_cell_webservices.html'
|
||||||
|
context = board_info._info
|
||||||
|
# LOG.debug("CONTEXT: %s", context)
|
||||||
|
return template.loader.render_to_string(template_name,
|
||||||
|
context)
|
||||||
|
|
||||||
|
|
||||||
|
class WebservicesTable(tables.DataTable):
|
||||||
|
|
||||||
|
"""
|
||||||
|
uuid = tables.WrappingColumn('uuid',
|
||||||
|
link="horizon:iot:webservices:detail",
|
||||||
|
verbose_name=_('UUID'))
|
||||||
|
"""
|
||||||
|
board = tables.Column('name', verbose_name=_('Board Name'))
|
||||||
|
|
||||||
|
board_uuid = tables.Column('board_uuid',
|
||||||
|
verbose_name=_('Board UUID'),
|
||||||
|
hidden=True)
|
||||||
|
|
||||||
|
webservices = tables.Column(show_webservices,
|
||||||
|
verbose_name=_('Web Services'))
|
||||||
|
|
||||||
|
http = tables.Column('http_port', verbose_name=_('HTTP'))
|
||||||
|
https = tables.Column('https_port', verbose_name=_('HTTPS'))
|
||||||
|
|
||||||
|
# Overriding get_object_id method because in IoT webservice the "id" is
|
||||||
|
# identified by the field UUID
|
||||||
|
def get_object_id(self, datum):
|
||||||
|
# LOG.debug('SELF: %s', self)
|
||||||
|
return datum.board_uuid
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = "webservices"
|
||||||
|
verbose_name = _("Web Services")
|
||||||
|
"""
|
||||||
|
row_actions = (ExposeWebserviceLink,
|
||||||
|
UnexposeWebserviceLink,
|
||||||
|
DisableWebservicesAction)
|
||||||
|
table_actions = (WebserviceFilterAction, DisableWebservicesAction)
|
||||||
|
"""
|
||||||
|
row_actions = (ExposeWebserviceLink,
|
||||||
|
UnexposeWebserviceLink)
|
||||||
|
table_actions = (WebserviceFilterAction,)
|
47
iotronic_ui/iot/webservices/tabs.py
Normal file
47
iotronic_ui/iot/webservices/tabs.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# from django.core.urlresolvers import reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import tabs
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
class OverviewTab(tabs.Tab):
|
||||||
|
name = _("Overview")
|
||||||
|
slug = "overview"
|
||||||
|
template_name = ("iot/webservices/_detail_overview.html")
|
||||||
|
|
||||||
|
def get_context_data(self, request):
|
||||||
|
# coordinates = self.tab_group.kwargs['board'].__dict__["location"][0]
|
||||||
|
# LOG.debug('IOT INFO: %s', coordinates)
|
||||||
|
|
||||||
|
board_name = self.tab_group.kwargs['webservice']._info['board_name']
|
||||||
|
board_uuid = self.tab_group.kwargs['webservice']._info['board_uuid']
|
||||||
|
|
||||||
|
return {"webservice": self.tab_group.kwargs['webservice'],
|
||||||
|
"board_uuid": board_uuid,
|
||||||
|
"board_name": board_name,
|
||||||
|
"is_superuser": request.user.is_superuser}
|
||||||
|
|
||||||
|
|
||||||
|
class WebserviceDetailTabs(tabs.TabGroup):
|
||||||
|
slug = "webservice_details"
|
||||||
|
# tabs = (OverviewTab, LogTab, ConsoleTab, AuditTab)
|
||||||
|
tabs = (OverviewTab,)
|
||||||
|
sticky = True
|
||||||
|
"""
|
@ -0,0 +1,8 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% if webservices %}
|
||||||
|
{% for ws in webservices %}
|
||||||
|
<dd><a target="_blank" href="{{ ws.service_url}}">{{ ws.service_url}}</a></dd>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<dd>--</dd>
|
||||||
|
{% endif %}
|
@ -0,0 +1,27 @@
|
|||||||
|
{% load i18n sizeformat %}
|
||||||
|
|
||||||
|
<div class="detail">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>{% trans "Name" %}</dt>
|
||||||
|
<dd>{{ webservice.name }}</dd>
|
||||||
|
<dt>{% trans "Port" %}</dt>
|
||||||
|
<dd>{{ webservice.port }}</dd>
|
||||||
|
<dt>{% trans "ID" %}</dt>
|
||||||
|
<dd>{{ webservice.uuid }}</dd>
|
||||||
|
<dt>{% trans "Created At" %}</dt>
|
||||||
|
<dd>{{ webservice.created_at }}</dd>
|
||||||
|
<dt>{% trans "extra" %}</dt>
|
||||||
|
<dd>{{ webservice.extra }}</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h4>{% trans "Board" %}</h4>
|
||||||
|
<hr class="header_rule">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
{% if board_uuid %}
|
||||||
|
<dt>{{ board_name }}</dt>
|
||||||
|
<dd>{{ board_uuid }}</dd>
|
||||||
|
{% else %}
|
||||||
|
<dd>--</dd>
|
||||||
|
{% endif %}
|
||||||
|
</dl>
|
||||||
|
</div>
|
@ -3,5 +3,6 @@
|
|||||||
|
|
||||||
{% block modal-body-right %}
|
{% block modal-body-right %}
|
||||||
<h3>{% trans "Description:" %}</h3>
|
<h3>{% trans "Description:" %}</h3>
|
||||||
<p>{% trans "Execute action on board(s)." %}</p>
|
<p>{% trans "Expose a new Web Service." %}</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,8 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-body-right %}
|
||||||
|
<h3>{% trans "Description:" %}</h3>
|
||||||
|
<p>{% trans "Unexpose Web Service(s)." %}</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Insert Web Service" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include 'iot/webservices/_expose.html' %}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Web Services" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{{ table.render }}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Unexpose Web Service" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include 'iot/webservices/_unexpose.html' %}
|
||||||
|
{% endblock %}
|
19
iotronic_ui/iot/webservices/tests.py
Normal file
19
iotronic_ui/iot/webservices/tests.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from horizon.test import helpers as test
|
||||||
|
|
||||||
|
|
||||||
|
class FleetsTests(test.TestCase):
|
||||||
|
# Unit tests for boards.
|
||||||
|
def test_me(self):
|
||||||
|
self.assertTrue(1 + 1 == 2)
|
26
iotronic_ui/iot/webservices/urls.py
Normal file
26
iotronic_ui/iot/webservices/urls.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from iotronic_ui.iot.webservices import views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||||
|
url(r'^(?P<board_id>[^/]+)/expose/$',
|
||||||
|
views.ExposeView.as_view(), name='expose'),
|
||||||
|
url(r'^(?P<board_id>[^/]+)/unexpose/$',
|
||||||
|
views.UnexposeView.as_view(), name='unexpose'),
|
||||||
|
# url(r'^(?P<webservice_id>[^/]+)/detail/$', views.WebserviceDetailView.as_view(),
|
||||||
|
# name='detail'),
|
||||||
|
]
|
234
iotronic_ui/iot/webservices/views.py
Normal file
234
iotronic_ui/iot/webservices/views.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.core.urlresolvers import reverse_lazy
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
# from horizon import messages
|
||||||
|
from horizon import tables
|
||||||
|
from horizon import tabs
|
||||||
|
from horizon.utils import memoized
|
||||||
|
|
||||||
|
from openstack_dashboard.api import iotronic
|
||||||
|
from openstack_dashboard import policy
|
||||||
|
|
||||||
|
from iotronic_ui.iot.webservices import forms as project_forms
|
||||||
|
from iotronic_ui.iot.webservices import tables as project_tables
|
||||||
|
from iotronic_ui.iot.webservices import tabs as project_tabs
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(tables.DataTableView):
|
||||||
|
table_class = project_tables.WebservicesTable
|
||||||
|
template_name = 'iot/webservices/index.html'
|
||||||
|
page_title = _("Web Services")
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
webservices = []
|
||||||
|
en_webservices = []
|
||||||
|
|
||||||
|
# Admin
|
||||||
|
if policy.check((("iot", "iot:list_all_webservices"),), self.request):
|
||||||
|
try:
|
||||||
|
webservices = iotronic.webservice_list(self.request,
|
||||||
|
None)
|
||||||
|
en_webservices = iotronic.webservice_enabled_list(self.request)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to retrieve webservices list.'))
|
||||||
|
|
||||||
|
# Admin_iot_project
|
||||||
|
elif policy.check((("iot", "iot:list_project_webservices"),),
|
||||||
|
self.request):
|
||||||
|
try:
|
||||||
|
webservices = iotronic.webservice_list(self.request,
|
||||||
|
None)
|
||||||
|
en_webservices = iotronic.webservice_enabled_list(self.request)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to retrieve webservices list.'))
|
||||||
|
|
||||||
|
# Other users
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
webservices = iotronic.webservice_list(self.request,
|
||||||
|
None)
|
||||||
|
en_webservices = iotronic.webservice_enabled_list(self.request)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to retrieve webservices list.'))
|
||||||
|
|
||||||
|
# Append some information to the webservice
|
||||||
|
# LOG.debug('WSS: %s', webservices)
|
||||||
|
for ws_en in en_webservices:
|
||||||
|
|
||||||
|
ws_list = []
|
||||||
|
|
||||||
|
for ws in webservices:
|
||||||
|
if ws_en.board_uuid == ws.board_uuid:
|
||||||
|
|
||||||
|
service_url = "https://" + ws.name + "." + ws_en.dns + "." + ws_en.zone
|
||||||
|
ws_list.append({"service_url": service_url})
|
||||||
|
|
||||||
|
ws_en.uuid = ws.uuid
|
||||||
|
|
||||||
|
board = iotronic.board_get(self.request, ws_en.board_uuid, None)
|
||||||
|
ws_en.name = board.name
|
||||||
|
ws_en._info.update(dict(webservices=ws_list))
|
||||||
|
|
||||||
|
# LOG.debug('WS: %s', en_webservices)
|
||||||
|
return en_webservices
|
||||||
|
|
||||||
|
|
||||||
|
class ExposeView(forms.ModalFormView):
|
||||||
|
template_name = 'iot/webservices/expose.html'
|
||||||
|
modal_header = _("Expose Web Service")
|
||||||
|
form_id = "expose_webservice_form"
|
||||||
|
form_class = project_forms.ExposeWebserviceForm
|
||||||
|
submit_label = _("Expose")
|
||||||
|
submit_url = "horizon:iot:webservices:expose"
|
||||||
|
success_url = reverse_lazy('horizon:iot:webservices:index')
|
||||||
|
page_title = _("Expose Web Service")
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_object(self):
|
||||||
|
try:
|
||||||
|
return iotronic.board_get(self.request,
|
||||||
|
self.kwargs['board_id'],
|
||||||
|
None)
|
||||||
|
except Exception:
|
||||||
|
redirect = reverse("horizon:iot:webservices:index")
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to get webservice information.'),
|
||||||
|
redirect=redirect)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(ExposeView, self).get_context_data(**kwargs)
|
||||||
|
args = (self.get_object().uuid,)
|
||||||
|
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
board = self.get_object()
|
||||||
|
|
||||||
|
return {'uuid': board.uuid, 'name': board.name}
|
||||||
|
|
||||||
|
|
||||||
|
class UnexposeView(forms.ModalFormView):
|
||||||
|
template_name = 'iot/webservices/unexpose.html'
|
||||||
|
modal_header = _("Unexpose Web Service")
|
||||||
|
form_id = "unexpose_webservice_form"
|
||||||
|
form_class = project_forms.UnexposeWebserviceForm
|
||||||
|
submit_label = _("Unexpose")
|
||||||
|
submit_url = "horizon:iot:webservices:unexpose"
|
||||||
|
success_url = reverse_lazy('horizon:iot:webservices:index')
|
||||||
|
page_title = _("Unexpose Web Service")
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_object(self):
|
||||||
|
try:
|
||||||
|
return iotronic.board_get(self.request,
|
||||||
|
self.kwargs['board_id'],
|
||||||
|
None)
|
||||||
|
except Exception:
|
||||||
|
redirect = reverse("horizon:iot:webservices:index")
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to get webservice information.'),
|
||||||
|
redirect=redirect)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(UnexposeView, self).get_context_data(**kwargs)
|
||||||
|
args = (self.get_object().uuid,)
|
||||||
|
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
board = self.get_object()
|
||||||
|
|
||||||
|
# Populate web services on board
|
||||||
|
ws_onboard = iotronic.webservices_on_board(self.request, board.uuid)
|
||||||
|
ws_onboard.sort(key=lambda b: b["name"])
|
||||||
|
|
||||||
|
ws_onboard_list = []
|
||||||
|
for ws in ws_onboard:
|
||||||
|
ws_onboard_list.append((ws["uuid"], _(ws["name"])))
|
||||||
|
|
||||||
|
return {'uuid': board.uuid,
|
||||||
|
'name': board.name,
|
||||||
|
'ws_onboard': ws_onboard_list}
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
class DetailView(tabs.TabView):
|
||||||
|
tab_group_class = project_tabs.WebserviceDetailTabs
|
||||||
|
template_name = 'horizon/common/_detail.html'
|
||||||
|
page_title = "{{ webservice.name|default:webservice.uuid }}"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(DetailView, self).get_context_data(**kwargs)
|
||||||
|
webservice = self.get_data()
|
||||||
|
context["webservice"] = webservice
|
||||||
|
context["url"] = reverse(self.redirect_url)
|
||||||
|
context["actions"] = self._get_actions(webservice)
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def _get_actions(self, webservice):
|
||||||
|
table = project_tables.WebservicesTable(self.request)
|
||||||
|
return table.render_row_actions(webservice)
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_data(self):
|
||||||
|
webservice = []
|
||||||
|
|
||||||
|
webservice_id = self.kwargs['webservice_id']
|
||||||
|
try:
|
||||||
|
webservice = iotronic.webservice_get(self.request,
|
||||||
|
webservice_id,
|
||||||
|
None)
|
||||||
|
board = iotronic.board_get(self.request,
|
||||||
|
webservice.board_uuid,
|
||||||
|
None)
|
||||||
|
|
||||||
|
webservice._info.update({u'board_name': board.name})
|
||||||
|
webservice.board_name = board.name
|
||||||
|
# LOG.debug('WS: %s', webservice)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
s = webservice.name
|
||||||
|
msg = ('Unable to retrieve webservice %s information') % {'name': s}
|
||||||
|
exceptions.handle(self.request, msg, ignore=True)
|
||||||
|
|
||||||
|
return webservice
|
||||||
|
|
||||||
|
def get_tabs(self, request, *args, **kwargs):
|
||||||
|
webservice = self.get_data()
|
||||||
|
return self.tab_group_class(request, webservice=webservice, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class WebserviceDetailView(DetailView):
|
||||||
|
redirect_url = 'horizon:iot:webservices:index'
|
||||||
|
|
||||||
|
def _get_actions(self, webservice):
|
||||||
|
table = project_tables.WebservicesTable(self.request)
|
||||||
|
return table.render_row_actions(webservice)
|
||||||
|
"""
|
Loading…
Reference in New Issue
Block a user