Upversioning platform horizon to master
This commit is to enable the stx-gui plugin to work with the vanilla horizon master branch. It also moves whatever of our customizations that we are keeping from horizon into the plugin. Whatever was not moved is being dropped as a feature. Major changes: - Moving the get_value template tag to the plugin - Removing server groups panels - Removing the custom JS related to autorefresh and the alarm banner - Implementing the upstream feature to add the alarm banner - Moving ceph API to the plugin, removing nova - Django no longer supports a TIME_ZONE of None to automatically capture the system time, so it is now done in our setting snippet - Move scss additions to plugin - Removing neutron service dependancy from datanet panels and removing create seg range button as it isn't supported any more from platform Known issues: - Branding and branding SDK need to be updated - Timezone changes now require a horizon restart to take effect, will need to update documentation to highlight this (if we do document that) - Port forwarding tab, launch instance enhancements, and controller services tab still need to be moved over, tracked by different stories Change-Id: Ic158d3483bb33513b64a0f8c8b5bb5a2a5e48ec5 Story: 2004765 Task: 28883 Depends-On: https://review.openstack.org/#/c/642797 Signed-off-by: Tyler Smith <tyler.smith@windriver.com>
This commit is contained in:
parent
a02dfe050c
commit
3858e68b1f
|
@ -11,14 +11,14 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# Copyright (c) 2017-2018 Wind River Systems, Inc.
|
||||
# Copyright (c) 2017-2019 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
from starlingx_dashboard.api import base
|
||||
from starlingx_dashboard.api import ceph
|
||||
from starlingx_dashboard.api import dc_manager
|
||||
from starlingx_dashboard.api import fm
|
||||
from starlingx_dashboard.api import neutron
|
||||
from starlingx_dashboard.api import nova
|
||||
from starlingx_dashboard.api import patch
|
||||
from starlingx_dashboard.api import sysinv
|
||||
from starlingx_dashboard.api import vim
|
||||
|
@ -26,11 +26,11 @@ from starlingx_dashboard.api import vim
|
|||
|
||||
__all__ = [
|
||||
"base",
|
||||
"ceph",
|
||||
"dc_manager",
|
||||
"neutron",
|
||||
"nova",
|
||||
"patch",
|
||||
"fm",
|
||||
"neutron",
|
||||
"patch",
|
||||
"sysinv",
|
||||
"vim",
|
||||
]
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2019 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from cephclient import wrapper
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def cephwrapper():
|
||||
return wrapper.CephWrapper()
|
||||
|
||||
|
||||
class Monitor(base.APIDictWrapper):
|
||||
__attrs = ['host', 'rank']
|
||||
|
||||
def __init__(self, apidict):
|
||||
super(Monitor, self).__init__(apidict)
|
||||
|
||||
|
||||
class OSD(base.APIDictWrapper):
|
||||
__attrs = ['id', 'name', 'host', 'status']
|
||||
|
||||
def __init__(self, apidict):
|
||||
super(OSD, self).__init__(apidict)
|
||||
|
||||
|
||||
class Cluster(base.APIDictWrapper):
|
||||
_attrs = ['fsid', 'status', 'health', 'detail']
|
||||
|
||||
def __init__(self, apidict):
|
||||
super(Cluster, self).__init__(apidict)
|
||||
|
||||
|
||||
class Storage(base.APIDictWrapper):
|
||||
_attrs = ['total', 'used', 'available',
|
||||
'writes_per_sec', 'operations_per_sec']
|
||||
|
||||
def __init__(self, apidict):
|
||||
super(Storage, self).__init__(apidict)
|
||||
|
||||
|
||||
def _Bytes_to_MiB(value_B):
|
||||
return (value_B / (1024 * 1024))
|
||||
|
||||
|
||||
def _Bytes_to_GiB(value_B):
|
||||
return (value_B / (1024 * 1024 * 1024))
|
||||
|
||||
|
||||
def cluster_get():
|
||||
# the json response doesn't give all the information
|
||||
response, text_body = cephwrapper().health(body='text')
|
||||
# ceph is not up, raise exception
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
health_info = text_body.split(' ', 1)
|
||||
|
||||
# if health is ok, there will be no details so just show HEALTH_OK
|
||||
if len(health_info) > 1:
|
||||
detail = health_info[1]
|
||||
else:
|
||||
detail = health_info[0]
|
||||
|
||||
response, cluster_uuid = cephwrapper().fsid(body='text')
|
||||
if not response.ok:
|
||||
cluster_uuid = None
|
||||
|
||||
cluster = {
|
||||
'fsid': cluster_uuid,
|
||||
'health': health_info[0],
|
||||
'detail': detail,
|
||||
}
|
||||
|
||||
return Cluster(cluster)
|
||||
|
||||
|
||||
def storage_get():
|
||||
# # Space info
|
||||
response, body = cephwrapper().df(body='json')
|
||||
# return no space info
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
stats = body['output']['stats']
|
||||
space = {
|
||||
'total': _Bytes_to_GiB(stats['total_bytes']),
|
||||
'used': _Bytes_to_MiB(stats['total_used_bytes']),
|
||||
'available': _Bytes_to_GiB(stats['total_avail_bytes']),
|
||||
}
|
||||
|
||||
# # I/O info
|
||||
response, body = cephwrapper().osd_pool_stats(body='json',
|
||||
name='cinder-volumes')
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
stats = body['output'][0]['client_io_rate']
|
||||
# not showing reads/sec at the moment
|
||||
# reads_per_sec = stats['read_bytes_sec'] if (
|
||||
# 'read_bytes_sec' in stats) else 0
|
||||
writes_per_sec = stats['write_bytes_sec'] if (
|
||||
'write_bytes_sec' in stats) else 0
|
||||
operations_per_sec = stats['op_per_sec'] if ('op_per_sec' in stats) else 0
|
||||
io = {
|
||||
'writes_per_sec': writes_per_sec / 1024,
|
||||
'operations_per_sec': operations_per_sec
|
||||
}
|
||||
|
||||
storage = {}
|
||||
storage.update(space)
|
||||
storage.update(io)
|
||||
|
||||
return Storage(storage)
|
||||
|
||||
|
||||
def _get_quorum_status(mon, quorums):
|
||||
if mon['rank'] in quorums:
|
||||
status = 'up'
|
||||
else:
|
||||
status = 'down'
|
||||
return status
|
||||
|
||||
|
||||
def monitor_list():
|
||||
response, body = cephwrapper().mon_dump(body='json')
|
||||
# return no monitors info
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
|
||||
quorums = body['output']['quorum']
|
||||
|
||||
mons = []
|
||||
for mon in body['output']['mons']:
|
||||
status = _get_quorum_status(mon, quorums)
|
||||
mons.append(
|
||||
{'host': mon['name'], 'rank': mon['rank'], 'status': status})
|
||||
return [Monitor(m) for m in mons]
|
||||
|
||||
|
||||
def osd_list():
|
||||
# would use osd_find, but it doesn't give osd's name
|
||||
response, tree = cephwrapper().osd_tree(body='json')
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
|
||||
osds = []
|
||||
for node in tree['output']['nodes']:
|
||||
# found osd
|
||||
if node['type'] == 'osd':
|
||||
osd = {}
|
||||
osd['id'] = node['id']
|
||||
osd['name'] = node['name']
|
||||
osd['status'] = node['status']
|
||||
|
||||
# check if osd belongs to host
|
||||
response, body = cephwrapper().osd_find(body='json', id=osd['id'])
|
||||
if response.ok and 'host' in body['output']['crush_location']:
|
||||
osd['host'] = body['output']['crush_location']['host']
|
||||
# else dont set hostname
|
||||
|
||||
osds.append(osd)
|
||||
|
||||
return [OSD(o) for o in osds]
|
|
@ -15,6 +15,7 @@ from django.conf import settings
|
|||
|
||||
from openstack_dashboard.api import base
|
||||
|
||||
from starlingx_dashboard.api import base as stx_base
|
||||
|
||||
# Fault management values
|
||||
FM_ALL = 'ALL'
|
||||
|
@ -118,7 +119,7 @@ def alarm_list(request, search_opts=None):
|
|||
marker = search_opts.get('marker', None)
|
||||
sort_key = search_opts.get('sort_key', None)
|
||||
sort_dir = search_opts.get('sort_dir', None)
|
||||
page_size = base.get_request_page_size(request, limit)
|
||||
page_size = stx_base.get_request_page_size(request, limit)
|
||||
|
||||
if "suppression" in search_opts:
|
||||
suppression = search_opts.pop('suppression')
|
||||
|
@ -195,7 +196,7 @@ def event_log_list(request, search_opts=None):
|
|||
|
||||
limit = search_opts.get('limit', None)
|
||||
marker = search_opts.get('marker', None)
|
||||
page_size = base.get_request_page_size(request, limit)
|
||||
page_size = stx_base.get_request_page_size(request, limit)
|
||||
|
||||
if 'paginate' in search_opts:
|
||||
paginate = search_opts.pop('paginate')
|
||||
|
|
|
@ -1,18 +1,36 @@
|
|||
#
|
||||
# Copyright (c) 2018 Intel Corporation
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.api.neutron import NeutronAPIDictWrapper
|
||||
from openstack_dashboard.api.neutron import neutronclient
|
||||
from openstack_dashboard.api.neutron import PortForwardingRule
|
||||
from openstack_dashboard.api.neutron import ProviderNetwork
|
||||
from openstack_dashboard.api.neutron import ProviderNetworkRange
|
||||
from openstack_dashboard.api.neutron import ProviderNetworkType
|
||||
from openstack_dashboard.api.neutron import ProviderTenantNetwork
|
||||
from openstack_dashboard.api.neutron import QoSPolicy
|
||||
|
||||
|
||||
class PortForwardingRule(base.APIDictWrapper):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderNetworkType(NeutronAPIDictWrapper):
|
||||
"""Wrapper for neutron Provider Network Types."""
|
||||
|
||||
|
||||
class ProviderNetworkRange(NeutronAPIDictWrapper):
|
||||
"""Wrapper for neutron Provider Networks Id Ranges."""
|
||||
|
||||
|
||||
class ProviderNetwork(NeutronAPIDictWrapper):
|
||||
"""Wrapper for neutron Provider Networks."""
|
||||
|
||||
|
||||
class ProviderTenantNetwork(NeutronAPIDictWrapper):
|
||||
"""Wrapper for neutron Provider Tenant Networks."""
|
||||
|
||||
|
||||
def provider_network_type_list(request, **params):
|
||||
providernet_types = neutronclient(request).list_providernet_types(
|
||||
**params).get(
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
#
|
||||
# Copyright (c) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from openstack_dashboard.api.nova import novaclient
|
||||
|
||||
|
||||
def server_group_list(request, all_projects=False):
|
||||
return novaclient(request).server_groups.list(all_projects)
|
||||
|
||||
|
||||
def server_group_get(request, server_group_id):
|
||||
return novaclient(request).server_groups.get(server_group_id)
|
||||
|
||||
|
||||
def server_group_create(request, name, project_id, metadata, policies):
|
||||
return novaclient(request).server_groups.create(
|
||||
name, project_id, metadata, policies)
|
||||
|
||||
|
||||
def server_group_delete(request, server_group_id):
|
||||
return novaclient(request).server_groups.delete(server_group_id)
|
|
@ -0,0 +1,33 @@
|
|||
#
|
||||
# Copyright (c) 2015-2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
from six.moves import configparser
|
||||
|
||||
|
||||
# Configuration Global used by other modules to get access to the configuration
|
||||
# specified in the config file.
|
||||
CONFSS = dict()
|
||||
|
||||
|
||||
class ConfigSS(configparser.ConfigParser):
|
||||
"""Override ConfigParser class to add dictionary functionality. """
|
||||
def as_dict(self):
|
||||
d = dict(self._sections)
|
||||
for key in d:
|
||||
d[key] = dict(self._defaults, **d[key])
|
||||
d[key].pop('__name__', None)
|
||||
return d
|
||||
|
||||
|
||||
def load(config_file):
|
||||
"""Load the configuration file into a global CONFSS variable. """
|
||||
global CONFSS # noqa pylint: disable=global-statement
|
||||
|
||||
config = ConfigSS()
|
||||
config.read(config_file)
|
||||
if not CONFSS:
|
||||
CONFSS = config.as_dict()
|
||||
else:
|
||||
CONFSS.update(config.as_dict())
|
|
@ -0,0 +1,39 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% if dc_admin %}
|
||||
<span class="alarmbanner">
|
||||
Clouds:
|
||||
<strong>Critical:</strong>
|
||||
<span class="badge{% if critical != 0 %} badge-danger{% endif %}">{{ critical }}</span>
|
||||
<strong> Degraded:</strong>
|
||||
<span class="badge{% if degraded != 0 %} badge-warning{% endif %}">{{ degraded }}</span>
|
||||
<strong> OK:</strong>
|
||||
<span class="badge{% if OK != 0 %} badge-success{% endif %}">{{ OK }}</span>
|
||||
{% if disabled != 0 %}
|
||||
<strong> Disabled:</strong>
|
||||
<span class="badge badge-danger">{{ disabled }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="alarmbanner" onclick="bannerclick()" style="cursor:pointer">
|
||||
<strong>C:</strong>
|
||||
<span class="badge{% if summary.critical != 0 %} badge-danger{% endif %}">{{ summary.critical }}</span>
|
||||
<strong> M:</strong>
|
||||
<span class="badge{% if summary.major != 0 %} badge-danger{% endif %}">{{ summary.major }}</span>
|
||||
<strong> m:</strong>
|
||||
<span class="badge{% if summary.minor != 0 %} badge-warning{% endif %}">{{ summary.minor }}</span>
|
||||
<strong> W:</strong>
|
||||
<span class="badge{% if summary.warnings != 0 %} badge-success{% endif %}">{{ summary.warnings }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
function bannerclick() {
|
||||
var $fm = "/admin/active_alarms";
|
||||
var $dc = "/dc_admin/";
|
||||
var $cur = document.location.href;
|
||||
var $path = document.location.pathname;
|
||||
if ($path.match($fm) === null && $path.match($dc) === null) {
|
||||
document.location.href = $cur.replace($path, $fm);
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -18,9 +18,9 @@
|
|||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import views
|
||||
from openstack_dashboard.api.base import is_service_enabled
|
||||
from starlingx_dashboard.api import dc_manager
|
||||
from starlingx_dashboard.api import fm
|
||||
|
@ -29,8 +29,8 @@ from starlingx_dashboard.api import fm
|
|||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BannerView(TemplateView):
|
||||
template_name = 'header/_alarm_banner.html'
|
||||
class BannerView(views.HorizonTemplateView):
|
||||
template_name = 'admin/active_alarms/_alarm_banner.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
|
@ -39,7 +39,7 @@ class BannerView(TemplateView):
|
|||
if not self.request.is_ajax():
|
||||
raise exceptions.NotFound()
|
||||
|
||||
if (not self.request.user.is_authenticated() or
|
||||
if (not self.request.user.is_authenticated or
|
||||
not self.request.user.is_superuser):
|
||||
context["alarmbanner"] = False
|
||||
elif 'dc_admin' in self.request.META.get('HTTP_REFERER'):
|
||||
|
|
|
@ -77,19 +77,6 @@ class EditDataNetwork(tables.LinkAction):
|
|||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
|
||||
class AddDataNetworkRange(tables.LinkAction):
|
||||
name = "addrange"
|
||||
verbose_name = _("Create Segmentation Range")
|
||||
url = "horizon:admin:datanets:datanets:addrange"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def allowed(self, request, datanet):
|
||||
if datanet:
|
||||
return datanet.network_type not in ('flat')
|
||||
return super(AddDataNetworkRange, self).allowed(request,
|
||||
datanet)
|
||||
|
||||
|
||||
class DataNetworksFilterAction(tables.FilterAction):
|
||||
def filter(self, table, datanets, filter_string):
|
||||
"""Naive case-insensitive search."""
|
||||
|
@ -119,8 +106,7 @@ class DataNetworksTable(tables.DataTable):
|
|||
table_actions = (CreateDataNetwork, DeleteDataNetwork,
|
||||
DataNetworksFilterAction)
|
||||
row_actions = (EditDataNetwork,
|
||||
DeleteDataNetwork,
|
||||
AddDataNetworkRange)
|
||||
DeleteDataNetwork)
|
||||
|
||||
|
||||
def _get_link_url(datum):
|
||||
|
|
|
@ -33,8 +33,6 @@ VIEW_MOD = 'starlingx_dashboard.dashboards.admin.datanets.datanets.' \
|
|||
urlpatterns = [
|
||||
url(r'^create/$', views.CreateView.as_view(),
|
||||
name='create'),
|
||||
url(PROVIDERNETS % 'addrange',
|
||||
views.CreateRangeView.as_view(), name='addrange'),
|
||||
url(PROVIDERNETS % 'update', views.UpdateView.as_view(),
|
||||
name='update'),
|
||||
url(PROVIDERNETS % 'detail', views.DetailView.as_view(),
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
from collections import OrderedDict
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse # noqa
|
||||
from django.core.urlresolvers import reverse_lazy # noqa
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
|
@ -34,8 +33,6 @@ from starlingx_dashboard.dashboards.admin.datanets.datanets import \
|
|||
forms as datanet_forms
|
||||
from starlingx_dashboard.dashboards.admin.datanets.datanets.ranges \
|
||||
import tables as range_tables
|
||||
from starlingx_dashboard.dashboards.admin.datanets.datanets.ranges \
|
||||
import views as range_views
|
||||
from starlingx_dashboard.dashboards.admin.datanets.datanets import \
|
||||
tables as providernet_tables
|
||||
|
||||
|
@ -69,7 +66,7 @@ class DetailView(tables.MultiTableView):
|
|||
|
||||
def get_tenant_networks_data(self):
|
||||
try:
|
||||
# TODO(datanetworks): get tenant networks when in upstream neutron
|
||||
# TODO(datanets): get tenant networks when in upstream neutron
|
||||
networks = []
|
||||
except Exception:
|
||||
networks = []
|
||||
|
@ -151,15 +148,3 @@ class UpdateView(forms.ModalFormView):
|
|||
'mtu': datanet.mtu,
|
||||
'description': datanet.description,
|
||||
}
|
||||
|
||||
|
||||
class CreateRangeView(range_views.CreateView):
|
||||
template_name = 'admin/datanets/datanets/add_range.html'
|
||||
success_url = 'horizon:admin:datanets:index'
|
||||
failure_url = 'horizon:admin:datanets:index'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url)
|
||||
|
||||
def get_failure_url(self):
|
||||
return reverse(self.failure_url)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2015 Wind River Systems, Inc.
|
||||
# Copyright (c) 2015-2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -14,7 +14,7 @@ from openstack_dashboard.dashboards.admin import dashboard
|
|||
class Datanets(horizon.Panel):
|
||||
name = _("Data Networks")
|
||||
slug = 'datanets'
|
||||
permissions = ('openstack.services.platform', 'openstack.services.network')
|
||||
permissions = ('openstack.services.platform', )
|
||||
|
||||
def allowed(self, context):
|
||||
if context['request'].user.services_region == 'SystemController':
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}add_provider_network_range_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:datanets:datanets:addrange' providernet_id %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Create Segmentation Range" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% trans "You can create a segmentation range for the provider network. The range must not overlap with any other segmentation range."%}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a class="btn btn-default cancel" data-dismiss="modal">{% trans "Cancel" %}</a>
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Segmentation Range" %}"/>
|
||||
{% endblock %}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Segmentation Range" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Segmentation Range") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "admin/datanets/datanets/_add_range.html" %}
|
||||
{% endblock %}
|
|
@ -16,7 +16,7 @@ from openstack_dashboard.dashboards.admin import dashboard
|
|||
class HostTopology(horizon.Panel):
|
||||
name = _("Data Network Topology")
|
||||
slug = 'host_topology'
|
||||
permissions = ('openstack.services.platform', 'openstack.services.network')
|
||||
permissions = ('openstack.services.platform', )
|
||||
|
||||
def allowed(self, context):
|
||||
if context['request'].user.services_region == 'SystemController':
|
||||
|
|
|
@ -9,6 +9,7 @@ import json
|
|||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.http import HttpResponse # noqa
|
||||
|
@ -217,5 +218,6 @@ class JSONView(View):
|
|||
data = {'hosts': self._get_hosts(request),
|
||||
'networks': self._get_dnets(request),
|
||||
'alarms': self._get_alarms(request), }
|
||||
json_string = json.dumps(data, ensure_ascii=False)
|
||||
json_string = json.dumps(data, ensure_ascii=False,
|
||||
cls=DjangoJSONEncoder)
|
||||
return HttpResponse(json_string, content_type='text/json')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2013-2015 Wind River Systems, Inc.
|
||||
# Copyright (c) 2013-2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -18,6 +18,7 @@ from horizon import forms
|
|||
from horizon import messages
|
||||
|
||||
from starlingx_dashboard.api import sysinv
|
||||
from starlingx_dashboard.horizon.forms.fields import DynamicIntegerField
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -35,19 +36,19 @@ class UpdateCpuFunctions(forms.SelfHandlingForm):
|
|||
required=False,
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
|
||||
platform_processor0 = forms.DynamicIntegerField(
|
||||
platform_processor0 = DynamicIntegerField(
|
||||
label=_("# of Platform Physical Cores on Processor 0:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
platform_processor1 = forms.DynamicIntegerField(
|
||||
platform_processor1 = DynamicIntegerField(
|
||||
label=_("# of Platform Physical Cores on Processor 1:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
platform_processor2 = forms.DynamicIntegerField(
|
||||
platform_processor2 = DynamicIntegerField(
|
||||
label=_("# of Platform Physical Cores on Processor 2:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
platform_processor3 = forms.DynamicIntegerField(
|
||||
platform_processor3 = DynamicIntegerField(
|
||||
label=_("# of Platform Physical Cores on Processor 3:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
|
@ -56,19 +57,19 @@ class UpdateCpuFunctions(forms.SelfHandlingForm):
|
|||
label=_("------------------------ Function ------------------------"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
num_cores_on_processor0 = forms.DynamicIntegerField(
|
||||
num_cores_on_processor0 = DynamicIntegerField(
|
||||
label=_("# of vSwitch Physical Cores on Processor 0:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_cores_on_processor1 = forms.DynamicIntegerField(
|
||||
num_cores_on_processor1 = DynamicIntegerField(
|
||||
label=_("# of vSwitch Physical Cores on Processor 1:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_cores_on_processor2 = forms.DynamicIntegerField(
|
||||
num_cores_on_processor2 = DynamicIntegerField(
|
||||
label=_("# of vSwitch Physical Cores on Processor 2:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_cores_on_processor3 = forms.DynamicIntegerField(
|
||||
num_cores_on_processor3 = DynamicIntegerField(
|
||||
label=_("# of vSwitch Physical Cores on Processor 3:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
|
@ -77,19 +78,19 @@ class UpdateCpuFunctions(forms.SelfHandlingForm):
|
|||
label=_("------------------------ Function ------------------------"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
num_shared_on_processor0 = forms.DynamicIntegerField(
|
||||
num_shared_on_processor0 = DynamicIntegerField(
|
||||
label=_("# of Shared Physical Cores on Processor 0:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_shared_on_processor1 = forms.DynamicIntegerField(
|
||||
num_shared_on_processor1 = DynamicIntegerField(
|
||||
label=_("# of Shared Physical Cores on Processor 1:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_shared_on_processor2 = forms.DynamicIntegerField(
|
||||
num_shared_on_processor2 = DynamicIntegerField(
|
||||
label=_("# of Shared Physical Cores on Processor 2:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
num_shared_on_processor3 = forms.DynamicIntegerField(
|
||||
num_shared_on_processor3 = DynamicIntegerField(
|
||||
label=_("# of Shared Physical Cores on Processor 3:"),
|
||||
min_value=0, max_value=99,
|
||||
required=False)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% load horizon i18n %}
|
||||
{% load horizon i18n getvalue %}
|
||||
|
||||
{% for cpufunc in cpuProfile.core_assignment %}
|
||||
<dt>{{ cpuFormats|get_value:cpufunc.allocated_function }}</dt>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% load horizon i18n sizeformat %}
|
||||
{% load horizon i18n sizeformat getvalue %}
|
||||
|
||||
{% block main %}
|
||||
{% autoescape off %}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load horizon i18n %}
|
||||
{% load horizon i18n getvalue %}
|
||||
|
||||
{% block form_id %}add_cpu_profile_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:inventory:addcpuprofile' host_id %}{% endblock %}
|
||||
|
|
|
@ -75,25 +75,6 @@
|
|||
});
|
||||
}
|
||||
}); // horizon.addInitFunction
|
||||
|
||||
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $sold_status = $('#active-sensor-stats');
|
||||
var $snew_status = $(html).find('#active-sensor-stats');
|
||||
if ($snew_status.html() != $sold_status.html()) {
|
||||
$sold_status.replaceWith($snew_status);
|
||||
}
|
||||
|
||||
var $old_table = $('#sensorgroups');
|
||||
var $new_table = $(html).find('#sensorgroups');
|
||||
if ($old_table.length && $new_table.length) {
|
||||
var $new_table_actions = $new_table.find('.table_actions');
|
||||
var $old_table_actions = $old_table.find('.table_actions');
|
||||
if ($new_table_actions.html() != $old_table_actions.html())
|
||||
$old_table_actions.replaceWith($new_table_actions);
|
||||
}
|
||||
}); // horizon.addRefreshFunction
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -12,53 +12,4 @@
|
|||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $old_status = $('#patching-status');
|
||||
var $new_status = $(html).find('#patching-status');
|
||||
if ($new_status.html() != $old_status.html()) {
|
||||
$old_status.replaceWith($new_status);
|
||||
}
|
||||
});
|
||||
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $old_status = $('#hosts-stats');
|
||||
var $new_status = $(html).find('#hosts-stats');
|
||||
if ($new_status.html() != $old_status.html()) {
|
||||
$old_status.replaceWith($new_status);
|
||||
}
|
||||
});
|
||||
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $old_table = $('#unprovisioned');
|
||||
var $new_table = $(html).find('#unprovisioned');
|
||||
if (($old_table.children().length == 0 && $new_table.children().length> 0) ||
|
||||
($old_table.children().length > 0 && $new_table.children().length == 0)) {
|
||||
$old_table.replaceWith($new_table);
|
||||
}
|
||||
});
|
||||
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $old_table = $('#storages');
|
||||
var $new_table = $(html).find('#storages');
|
||||
if (($old_table.children().length == 0 && $new_table.children().length> 0) ||
|
||||
($old_table.children().length > 0 && $new_table.children().length == 0)) {
|
||||
$old_table.replaceWith($new_table);
|
||||
}
|
||||
});
|
||||
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $old_table = $('#workers');
|
||||
var $new_table = $(html).find('#workers');
|
||||
if (($old_table.children().length == 0 && $new_table.children().length> 0) ||
|
||||
($old_table.children().length > 0 && $new_table.children().length == 0)) {
|
||||
$old_table.replaceWith($new_table);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
|
@ -1,195 +0,0 @@
|
|||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
# All rights reserved.
|
||||
|
||||
"""
|
||||
Views for managing volumes.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.forms import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import cinder
|
||||
from openstack_dashboard.dashboards.project.instances import tables
|
||||
|
||||
from starlingx_dashboard.api import nova as stx_nova
|
||||
|
||||
|
||||
class CreateForm(forms.SelfHandlingForm):
|
||||
tenantP = forms.ChoiceField(label=_("Project"), required=True)
|
||||
name = forms.CharField(max_length="255", label=_("Server Group Name"))
|
||||
policy = forms.ChoiceField(label=_("Policy"),
|
||||
required=False,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'policy_ht'}))
|
||||
|
||||
is_best_effort = forms.BooleanField(label=_("Best Effort"), required=False)
|
||||
|
||||
group_size = forms.IntegerField(
|
||||
min_value=1,
|
||||
label=_("Max Group Size (Instances)"),
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'class': 'switchable switched',
|
||||
'data-switch-on': 'policy_ht',
|
||||
'data-policy_ht-anti-affinity': 'Max Group Size (Instances)',
|
||||
'data-policy_ht-affinity': 'Max Group Size (Instances)'}))
|
||||
|
||||
group_size_ht = forms.IntegerField(
|
||||
label=_("Max Group Size (Instances)"),
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'readonly': 'readonly',
|
||||
'class': 'switchable switched',
|
||||
'data-switch-on': 'policy_ht',
|
||||
'data-policy_ht-affinity-hyperthread':
|
||||
'Max Group Size (Instances)'}))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(CreateForm, self).__init__(request, *args, **kwargs)
|
||||
self.fields['policy'].choices = [("anti-affinity", "anti-affinity"),
|
||||
("affinity", "affinity")]
|
||||
|
||||
# Populate available project_id/name choices
|
||||
all_projects = []
|
||||
try:
|
||||
# Get list of available projects.
|
||||
all_projects, has_more = api.keystone.tenant_list(request) # noqa pylint: disable=unused-variable
|
||||
|
||||
projects_list = [(project.id, project.name)
|
||||
for project in all_projects]
|
||||
|
||||
except Exception:
|
||||
projects_list = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve list of tenants.'))
|
||||
|
||||
self.fields['tenantP'].choices = projects_list
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
policy = data['policy']
|
||||
policies = []
|
||||
if policy:
|
||||
policies.append(policy)
|
||||
metadata = {}
|
||||
if data['is_best_effort']:
|
||||
metadata['wrs-sg:best_effort'] = "true"
|
||||
group_size = data['group_size']
|
||||
group_size_ht = data['group_size_ht']
|
||||
if group_size:
|
||||
metadata['wrs-sg:group_size'] = str(group_size)
|
||||
elif group_size_ht:
|
||||
metadata['wrs-sg:group_size'] = str(group_size_ht)
|
||||
|
||||
project_id = None
|
||||
if data['tenantP']:
|
||||
project_id = data['tenantP']
|
||||
|
||||
server_group = stx_nova.server_group_create(
|
||||
request, data['name'], project_id, metadata, policies)
|
||||
return server_group
|
||||
|
||||
except ValidationError as e:
|
||||
self.api_error(e.messages[0])
|
||||
return False
|
||||
except Exception as e:
|
||||
exceptions.handle(request, ignore=True)
|
||||
self.api_error(_("Unable to create server group."))
|
||||
return False
|
||||
|
||||
|
||||
class AttachForm(forms.SelfHandlingForm):
|
||||
instance = forms.ChoiceField(label=_("Attach to Server Group"),
|
||||
help_text=_("Select an server group to "
|
||||
"attach to."))
|
||||
device = forms.CharField(label=_("Device Name"))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AttachForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Hide the device field if the hypervisor doesn't support it.
|
||||
hypervisor_features = getattr(settings,
|
||||
"OPENSTACK_HYPERVISOR_FEATURES", {})
|
||||
can_set_mount_point = hypervisor_features.get("can_set_mount_point",
|
||||
True)
|
||||
if not can_set_mount_point:
|
||||
self.fields['device'].widget = forms.widgets.HiddenInput()
|
||||
self.fields['device'].required = False
|
||||
|
||||
# populate volume_id
|
||||
volume = kwargs.get('initial', {}).get("volume", None)
|
||||
if volume:
|
||||
volume_id = volume.id
|
||||
else:
|
||||
volume_id = None
|
||||
self.fields['volume_id'] = forms.CharField(widget=forms.HiddenInput(),
|
||||
initial=volume_id)
|
||||
|
||||
# Populate instance choices
|
||||
instance_list = kwargs.get('initial', {}).get('instances', [])
|
||||
instances = []
|
||||
for instance in instance_list:
|
||||
if instance.status in tables.ACTIVE_STATES and \
|
||||
not any(instance.id == att["server_id"]
|
||||
for att in volume.attachments):
|
||||
instances.append((instance.id, '%s (%s)' % (instance.name,
|
||||
instance.id)))
|
||||
if instances:
|
||||
instances.insert(0, ("", _("Select an instance")))
|
||||
else:
|
||||
instances = (("", _("No instances available")),)
|
||||
self.fields['instance'].choices = instances
|
||||
|
||||
def handle(self, request, data):
|
||||
instance_choices = dict(self.fields['instance'].choices)
|
||||
instance_name = instance_choices.get(data['instance'],
|
||||
_("Unknown instance (None)"))
|
||||
# The name of the instance in the choices list has the ID appended to
|
||||
# it, so let's slice that off...
|
||||
instance_name = instance_name.rsplit(" (")[0]
|
||||
try:
|
||||
attach = api.nova.instance_volume_attach(request,
|
||||
data['volume_id'],
|
||||
data['instance'],
|
||||
data.get('device', ''))
|
||||
volume = cinder.volume_get(request, data['volume_id'])
|
||||
if not volume.display_name:
|
||||
volume_name = volume.id
|
||||
else:
|
||||
volume_name = volume.display_name
|
||||
message = _('Attaching volume %(vol)s to instance '
|
||||
'%(inst)s on %(dev)s.') % {"vol": volume_name,
|
||||
"inst": instance_name,
|
||||
"dev": attach.device}
|
||||
messages.info(request, message)
|
||||
return True
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:volumes:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to attach volume.'),
|
||||
redirect=redirect)
|
|
@ -1,40 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
|
||||
class ServerGroups(horizon.Panel):
|
||||
name = _("Server Groups")
|
||||
slug = 'server_groups'
|
||||
# Server groups are wrs-specific
|
||||
permissions = ('openstack.services.compute',)
|
||||
policy_rules = (("compute", "context_is_admin"),)
|
||||
|
||||
def allowed(self, context):
|
||||
if context['request'].user.services_region == 'SystemController':
|
||||
return False
|
||||
else:
|
||||
return super(ServerGroups, self).allowed(context)
|
||||
|
||||
def nav(self, context):
|
||||
if context['request'].user.services_region == 'SystemController':
|
||||
return False
|
||||
else:
|
||||
return True
|
|
@ -1,305 +0,0 @@
|
|||
# Copyright 2014 Wind River, Inc.
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
from django.core.urlresolvers import NoReverseMatch
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils import html
|
||||
from django.utils import safestring
|
||||
from django.utils.translation import string_concat
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.volumes.tables \
|
||||
import get_attachment_name
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
from starlingx_dashboard.api import nova as stx_nova
|
||||
|
||||
DELETABLE_STATES = ("available", "error")
|
||||
|
||||
|
||||
class DeleteServerGroup(tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
"Delete Server Group",
|
||||
"Delete Server Groups",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
"Deleted Server Group",
|
||||
"Deleted Server Groups",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
obj = self.table.get_object_by_id(obj_id)
|
||||
name = self.table.get_object_display(obj)
|
||||
|
||||
try:
|
||||
stx_nova.server_group_delete(request, obj_id)
|
||||
except Exception:
|
||||
msg = _('Unable to delete group "%s" because it is not empty. '
|
||||
'Either delete the member instances'
|
||||
' or remove them from the group.')
|
||||
exceptions.check_message(["group", "not", "empty."], msg % name)
|
||||
raise
|
||||
|
||||
# maybe do a precheck to see if the group is empty first?
|
||||
def allowed(self, request, server_group=None):
|
||||
return True
|
||||
|
||||
|
||||
class CreateServerGroup(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Server Group")
|
||||
url = "horizon:admin:server_groups:create"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
icon = "plus"
|
||||
|
||||
def allowed(self, request, volume=None):
|
||||
usages = quotas.tenant_quota_usages(request)
|
||||
if usages['server_groups']['available'] <= 0:
|
||||
if "disabled" not in self.classes:
|
||||
self.classes = [c for c in self.classes] + ['disabled']
|
||||
self.verbose_name = string_concat(self.verbose_name, ' ',
|
||||
_("(Quota exceeded)"))
|
||||
else:
|
||||
self.verbose_name = _("Create Server Group")
|
||||
classes = [c for c in self.classes if c != "disabled"]
|
||||
self.classes = classes
|
||||
return True
|
||||
|
||||
|
||||
class EditAttachments(tables.LinkAction):
|
||||
name = "attachments"
|
||||
verbose_name = _("Edit Attachments")
|
||||
url = "horizon:admin:server_groups:attach"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def allowed(self, request, server_group=None):
|
||||
return True # volume.status in ("available", "in-use")
|
||||
|
||||
|
||||
class CreateSnapshot(tables.LinkAction):
|
||||
name = "snapshots"
|
||||
verbose_name = _("Create Snapshot")
|
||||
url = "horizon:admin:server_groups:create_snapshot"
|
||||
classes = ("ajax-modal", "btn-camera")
|
||||
|
||||
def allowed(self, request, server_group=None):
|
||||
return True # server_group.status == "available"
|
||||
|
||||
|
||||
class UpdateRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, server_group_id):
|
||||
server_group = stx_nova.server_group_get(request, server_group_id)
|
||||
if not server_group.name:
|
||||
server_group.name = server_group_id
|
||||
|
||||
return server_group
|
||||
|
||||
|
||||
def get_policies(server_group):
|
||||
policies = ', '.join(server_group.policies)
|
||||
return policies
|
||||
|
||||
|
||||
def get_metadata(server_group):
|
||||
metadata_items = ['{}:{}'.format(x, y) for x, y in
|
||||
server_group.metadata.items()]
|
||||
metadata = ', '.join(metadata_items)
|
||||
return metadata
|
||||
|
||||
|
||||
def get_member_name(request, server_id):
|
||||
try:
|
||||
server = api.nova.server_get(request, server_id)
|
||||
name = server.name
|
||||
except Exception:
|
||||
name = None
|
||||
exceptions.handle(request, _("Unable to retrieve "
|
||||
"member information."))
|
||||
# try and get a URL
|
||||
try:
|
||||
url = reverse("horizon:admin:instances:detail", args=(server_id,))
|
||||
instance = '<a href="%s">%s</a>' % (url, html.escape(name))
|
||||
except NoReverseMatch:
|
||||
instance = name
|
||||
return instance
|
||||
|
||||
|
||||
class ProjectNameColumn(tables.Column):
|
||||
"""Customized column class
|
||||
|
||||
Customized column class that does complex processing on the
|
||||
server group.
|
||||
"""
|
||||
|
||||
def get_raw_data(self, server_group):
|
||||
request = self.table.request
|
||||
project_id = getattr(server_group, 'project_id', None)
|
||||
try:
|
||||
tenant = api.keystone.tenant_get(request,
|
||||
project_id,
|
||||
admin=True)
|
||||
|
||||
project_name = getattr(tenant, "name", None)
|
||||
except Exception:
|
||||
project_name = "(not found)"
|
||||
|
||||
return project_name
|
||||
|
||||
|
||||
class MemberColumn(tables.Column):
|
||||
"""Customized column class
|
||||
|
||||
Customized column class that does complex processing on the instances
|
||||
in a server group. This was substantially copied
|
||||
from the volume equivalent.
|
||||
"""
|
||||
|
||||
def get_raw_data(self, server_group):
|
||||
request = self.table.request
|
||||
link = _('%(name)s (%(id)s)')
|
||||
members = []
|
||||
for member in server_group.members:
|
||||
member_id = member
|
||||
name = get_member_name(request, member)
|
||||
vals = {"name": name, "id": member_id}
|
||||
members.append(link % vals)
|
||||
return safestring.mark_safe(", ".join(members))
|
||||
|
||||
|
||||
def get_server_group_type(server_group):
|
||||
return server_group.volume_type if server_group.volume_type != "None" \
|
||||
else None
|
||||
|
||||
|
||||
class ServerGroupsFilterAction(tables.FilterAction):
|
||||
def filter(self, table, server_groups, filter_string):
|
||||
"""Naive case-insensitive search."""
|
||||
q = filter_string.lower()
|
||||
return [group for group in server_groups
|
||||
if q in group.display_name.lower()]
|
||||
|
||||
|
||||
class ServerGroupsTable(tables.DataTable):
|
||||
projectname = ProjectNameColumn("project_name",
|
||||
verbose_name=_("Project"))
|
||||
name = tables.Column("name",
|
||||
verbose_name=_("Group Name"),
|
||||
link="horizon:admin:server_groups:detail")
|
||||
policies = tables.Column(get_policies,
|
||||
verbose_name=_("Policies"))
|
||||
members = MemberColumn("members",
|
||||
verbose_name=_("Members"))
|
||||
metadata = tables.Column(get_metadata,
|
||||
verbose_name=_("Metadata"))
|
||||
|
||||
class Meta(object):
|
||||
name = "server_groups"
|
||||
verbose_name = _("Server Groups")
|
||||
row_class = UpdateRow
|
||||
table_actions = (
|
||||
CreateServerGroup, DeleteServerGroup, ServerGroupsFilterAction)
|
||||
row_actions = (DeleteServerGroup,)
|
||||
|
||||
def get_object_display(self, obj):
|
||||
return obj.name
|
||||
|
||||
|
||||
class DetachServerGroup(tables.BatchAction):
|
||||
name = "detach"
|
||||
action_type = 'danger'
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
"Detach Server Group",
|
||||
"Detached Server Groups",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
"Detaching Server Group",
|
||||
"Detaching Server Groups",
|
||||
count
|
||||
)
|
||||
|
||||
def action(self, request, obj_id):
|
||||
attachment = self.table.get_object_by_id(obj_id)
|
||||
api.nova.instance_server_group_detach(request,
|
||||
attachment.get('server_id',
|
||||
None),
|
||||
obj_id)
|
||||
|
||||
def get_success_url(self, request):
|
||||
return reverse('horizon:admin:server_groups:index')
|
||||
|
||||
|
||||
class AttachedInstanceColumn(tables.Column):
|
||||
"""Customized column class
|
||||
|
||||
Customized column class that does complex processing on the attachments
|
||||
for a server group.
|
||||
"""
|
||||
|
||||
def get_raw_data(self, attachment):
|
||||
request = self.table.request
|
||||
return safestring.mark_safe(get_attachment_name(request, attachment,
|
||||
True))
|
||||
|
||||
|
||||
class AttachmentsTable(tables.DataTable):
|
||||
instance = AttachedInstanceColumn(get_member_name,
|
||||
verbose_name=_("Instance"))
|
||||
device = tables.Column("device",
|
||||
verbose_name=_("Device"))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj['id']
|
||||
|
||||
def get_object_display(self, attachment):
|
||||
instance_name = get_attachment_name(self.request, attachment, True)
|
||||
vals = {"dev": attachment['device'],
|
||||
"instance_name": html.strip_tags(instance_name)}
|
||||
return _("%(dev)s on instance %(instance_name)s") % vals
|
||||
|
||||
def get_object_by_id(self, obj_id):
|
||||
for obj in self.data:
|
||||
if self.get_object_id(obj) == obj_id:
|
||||
return obj
|
||||
raise ValueError('No match found for the id "%s".' % obj_id)
|
||||
|
||||
class Meta(object):
|
||||
name = "attachments"
|
||||
verbose_name = _("Attachments")
|
||||
table_actions = (DetachServerGroup,)
|
||||
row_actions = (DetachServerGroup,)
|
|
@ -1,53 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard.api import nova
|
||||
|
||||
from starlingx_dashboard.api import nova as stx_nova
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = ("admin/server_groups/"
|
||||
"_detail_overview.html")
|
||||
|
||||
def get_context_data(self, request):
|
||||
server_group_id = self.tab_group.kwargs['server_group_id']
|
||||
try:
|
||||
server_group = stx_nova.server_group_get(request, server_group_id)
|
||||
server_group.members_display = []
|
||||
for member in server_group.members:
|
||||
server_group.members_display.append(
|
||||
dict(id=member, instance=nova.server_get(request, member)))
|
||||
except Exception:
|
||||
redirect = reverse('horizon:admin:server_groups:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve server group details.'),
|
||||
redirect=redirect)
|
||||
return {'server_group': server_group}
|
||||
|
||||
|
||||
class ServerGroupDetailTabs(tabs.TabGroup):
|
||||
slug = "server_group_details"
|
||||
tabs = (OverviewTab,)
|
|
@ -1,25 +0,0 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}attach_volume_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:volumes:attach' volume.id %}{% endblock %}
|
||||
{% block form_class %}{{ block.super }} horizontal {% if show_attach %}split_half{% else %} no_split{% endif %}{% endblock %}
|
||||
|
||||
{% block modal_id %}attach_volume_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Manage Volume Attachments" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
{% if show_attach %}
|
||||
<h3>{% trans "Attach To Instance" %}</h3>
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a href="{% url 'horizon:project:volumes:index' %}" class="btn btn-default cancel">{% trans "Cancel" %}</a>
|
||||
{% if show_attach %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Attach Volume" %}" />
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,26 +0,0 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:server_groups:create' %}?{{ request.GET.urlencode }}{% endblock %}
|
||||
|
||||
{% block modal_id %}create_server_group_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Create Server Group" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% trans "From here you can create a new server group" %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a class="btn btn-default cancel" data-dismiss="modal">{% trans "Cancel" %}</a>
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Server Group" %}" />
|
||||
{% endblock %}
|
|
@ -1,52 +0,0 @@
|
|||
{% load i18n sizeformat parse_date %}
|
||||
|
||||
<h3>{% trans "Server Group Overview" %}: {{server_group.name }}</h3>
|
||||
|
||||
<div class="info row-fluid detail">
|
||||
<h4>{% trans "Info" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ server_group.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ server_group.id }}</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ server_group.status|capfirst }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="members row-fluid detail">
|
||||
<h4>{% trans "Members" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
{% for member in server_group.members_display %}
|
||||
<dd>
|
||||
{% url 'horizon:admin:instances:detail' member.id as instance_url%}
|
||||
<a href="{{ instance_url }}">{{ member.instance.name }} ({{ member.id }})</a>
|
||||
</dd>
|
||||
{% empty %}
|
||||
<dd><em>{% trans "No members" %}</em></dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="policies row-fluid detail">
|
||||
<h4>{% trans "Policies" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
{% for policy in server_group.policies %}
|
||||
<dd>{{ policy }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="status row-fluid detail">
|
||||
<h4>{% trans "Metadata" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
{% for key, value in server_group.metadata.items %}
|
||||
<dt>{{ key }}</dt>
|
||||
<dd>{{ value }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Manage Volume Attachments" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Manage Volume Attachments") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/volumes/_attach.html' %}
|
||||
{% endblock %}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Server Group" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create a Server Group") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/server_groups/_create.html' %}
|
||||
{% endblock %}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% block title %}{% trans "Server Group Details" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Server Groups" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Server Groups") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
|
@ -1,32 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from starlingx_dashboard.dashboards.admin.server_groups import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||
url(r'^(?P<server_group_id>[^/]+)/attach/$',
|
||||
views.EditAttachmentsView.as_view(),
|
||||
name='attach'),
|
||||
url(r'^(?P<server_group_id>[^/]+)/$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail')
|
||||
]
|
|
@ -1,148 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
"""
|
||||
Views for managing server groups.
|
||||
"""
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy # noqa
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
from starlingx_dashboard import api as stx_api
|
||||
from starlingx_dashboard.dashboards.admin.server_groups \
|
||||
import forms as admin_forms
|
||||
|
||||
from starlingx_dashboard.dashboards.admin.server_groups \
|
||||
import tables as admin_tables
|
||||
from starlingx_dashboard.dashboards.admin.server_groups \
|
||||
import tabs as admin_tabs
|
||||
|
||||
|
||||
# server groups don't currently support pagination
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = admin_tables.ServerGroupsTable
|
||||
template_name = 'admin/server_groups/index.html'
|
||||
page_title = _("Server Groups")
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
server_groups = stx_api.nova.server_group_list(
|
||||
self.request, all_projects=True)
|
||||
except Exception:
|
||||
server_groups = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve server groups.'))
|
||||
return server_groups
|
||||
|
||||
|
||||
class DetailView(tabs.TabView):
|
||||
tab_group_class = admin_tabs.ServerGroupDetailTabs
|
||||
template_name = 'admin/server_groups/detail.html'
|
||||
page_title = 'Server Group Details'
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = admin_forms.CreateForm
|
||||
template_name = 'admin/server_groups/create.html'
|
||||
success_url = reverse_lazy("horizon:admin:server_groups:index")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
try:
|
||||
context['usages'] = quotas.tenant_limit_usages(self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request)
|
||||
return context
|
||||
|
||||
|
||||
class EditAttachmentsView(tables.DataTableView, forms.ModalFormView):
|
||||
table_class = admin_tables.AttachmentsTable
|
||||
form_class = admin_forms.AttachForm
|
||||
template_name = 'admin/server_groups/attach.html'
|
||||
success_url = reverse_lazy("horizon:admin:server_groups:index")
|
||||
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
volume_id = self.kwargs['volume_id']
|
||||
try:
|
||||
self._object = api.cinder.volume_get(self.request, volume_id)
|
||||
except Exception:
|
||||
self._object = None
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve volume information.'))
|
||||
return self._object
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
volumes = self.get_object()
|
||||
attachments = [att for att in volumes.attachments if att]
|
||||
except Exception:
|
||||
attachments = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve volume information.'))
|
||||
return attachments
|
||||
|
||||
def get_initial(self):
|
||||
try:
|
||||
instances, has_more = api.nova.server_list(self.request) # noqa pylint: disable=unused-variable
|
||||
except Exception:
|
||||
instances = []
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve attachment information."))
|
||||
return {'volume': self.get_object(),
|
||||
'instances': instances}
|
||||
|
||||
def get_form(self):
|
||||
if not hasattr(self, "_form"):
|
||||
form_class = self.get_form_class()
|
||||
self._form = super(EditAttachmentsView, self).get_form(form_class)
|
||||
return self._form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EditAttachmentsView, self).get_context_data(**kwargs)
|
||||
context['form'] = self.get_form()
|
||||
volume = self.get_object()
|
||||
if volume and volume.status == 'available':
|
||||
context['show_attach'] = True
|
||||
else:
|
||||
context['show_attach'] = False
|
||||
context['volume'] = volume
|
||||
if self.request.is_ajax():
|
||||
context['hide'] = True
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Table action handling
|
||||
handled = self.construct_tables()
|
||||
if handled:
|
||||
return handled
|
||||
return self.render_to_response(self.get_context_data(**kwargs))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.get(request, *args, **kwargs)
|
|
@ -42,16 +42,3 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $old_stage_detail = $('#stage-details');
|
||||
var $new_stage_detail = $(html).find('#stage-details');
|
||||
if ($new_stage_detail.html() != $old_stage_detail.html()) {
|
||||
$old_stage_detail.replaceWith($new_stage_detail);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -13,33 +13,3 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $old_strategy = $('#patch-strategy-detail');
|
||||
var $new_strategy = $(html).find('#patch-strategy-detail');
|
||||
if ($new_strategy.html() != $old_strategy.html()) {
|
||||
$old_strategy.replaceWith($new_strategy);
|
||||
}
|
||||
});
|
||||
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $old_strategy = $('#upgrade-strategy-detail');
|
||||
var $new_strategy = $(html).find('#upgrade-strategy-detail');
|
||||
if ($new_strategy.html() != $old_strategy.html()) {
|
||||
$old_strategy.replaceWith($new_strategy);
|
||||
}
|
||||
});
|
||||
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $old_status = $('#patching-status');
|
||||
var $new_status = $(html).find('#patching-status');
|
||||
if ($new_status.html() != $old_status.html()) {
|
||||
$old_status.replaceWith($new_status);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2016 Wind River Systems, Inc.
|
||||
# Copyright (c) 2016-2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
@ -11,9 +11,8 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard.api import ceph
|
||||
|
||||
from starlingx_dashboard.api import base as stx_base
|
||||
from starlingx_dashboard.api import ceph
|
||||
from starlingx_dashboard.api import sysinv
|
||||
from starlingx_dashboard.dashboards.admin.storage_overview import constants
|
||||
from starlingx_dashboard.dashboards.admin.storage_overview import tables
|
||||
|
|
|
@ -13,16 +13,3 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
horizon.refresh.addRefreshFunction(function (html) {
|
||||
var $old_strategy = $('#cloud-patch-strategy-detail');
|
||||
var $new_strategy = $(html).find('#cloud-patch-strategy-detail');
|
||||
if ($new_strategy.html() != $old_strategy.html()) {
|
||||
$old_strategy.replaceWith($new_strategy);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
# All rights reserved.
|
||||
|
||||
"""
|
||||
Views for managing volumes.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.forms import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api import cinder
|
||||
from openstack_dashboard.dashboards.project.instances import tables
|
||||
|
||||
from starlingx_dashboard.api import nova as stx_nova
|
||||
|
||||
|
||||
class CreateForm(forms.SelfHandlingForm):
|
||||
name = forms.CharField(max_length="255", label=_("Server Group Name"))
|
||||
policy = forms.ChoiceField(label=_("Policy"),
|
||||
required=False,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'policy_ht'}))
|
||||
|
||||
is_best_effort = forms.BooleanField(label=_("Best Effort"), required=False)
|
||||
|
||||
group_size = forms.IntegerField(
|
||||
min_value=1,
|
||||
label=_("Max Group Size (Instances)"),
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'class': 'switchable switched',
|
||||
'data-switch-on': 'policy_ht',
|
||||
'data-policy_ht-anti-affinity': 'Max Group Size (Instances)',
|
||||
'data-policy_ht-affinity': 'Max Group Size (Instances)'}))
|
||||
|
||||
group_size_ht = forms.IntegerField(
|
||||
label=_("Max Group Size (Instances)"),
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'readonly': 'readonly',
|
||||
'class': 'switchable switched',
|
||||
'data-switch-on': 'policy_ht',
|
||||
'data-policy_ht-affinity-hyperthread':
|
||||
'Max Group Size (Instances)'}))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(CreateForm, self).__init__(request, *args, **kwargs)
|
||||
self.fields['policy'].choices = [("anti-affinity", "anti-affinity"),
|
||||
("affinity", "affinity")]
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
project_id = self.request.user.tenant_id
|
||||
policy = data['policy']
|
||||
policies = []
|
||||
if policy:
|
||||
policies.append(policy)
|
||||
metadata = {}
|
||||
if data['is_best_effort']:
|
||||
metadata['wrs-sg:best_effort'] = "true"
|
||||
group_size = data['group_size']
|
||||
group_size_ht = data['group_size_ht']
|
||||
if group_size:
|
||||
metadata['wrs-sg:group_size'] = str(group_size)
|
||||
elif group_size_ht:
|
||||
metadata['wrs-sg:group_size'] = str(group_size_ht)
|
||||
|
||||
kwargs = {'name': data['name'],
|
||||
'policies': policies,
|
||||
'metadata': metadata,
|
||||
'project_id': project_id}
|
||||
|
||||
server_group = stx_nova.server_group_create(request, **kwargs)
|
||||
return server_group
|
||||
|
||||
except ValidationError as e:
|
||||
self.api_error(e.messages[0])
|
||||
return False
|
||||
except Exception:
|
||||
exceptions.handle(request, ignore=True)
|
||||
self.api_error(_("Unable to create server group."))
|
||||
return False
|
||||
|
||||
|
||||
class AttachForm(forms.SelfHandlingForm):
|
||||
instance = forms.ChoiceField(label=_("Attach to Server Group"),
|
||||
help_text=_("Select an server group to "
|
||||
"attach to."))
|
||||
device = forms.CharField(label=_("Device Name"))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AttachForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Hide the device field if the hypervisor doesn't support it.
|
||||
hypervisor_features = getattr(settings,
|
||||
"OPENSTACK_HYPERVISOR_FEATURES",
|
||||
{})
|
||||
can_set_mount_point = hypervisor_features.get("can_set_mount_point",
|
||||
True)
|
||||
if not can_set_mount_point:
|
||||
self.fields['device'].widget = forms.widgets.HiddenInput()
|
||||
self.fields['device'].required = False
|
||||
|
||||
# populate volume_id
|
||||
volume = kwargs.get('initial', {}).get("volume", None)
|
||||
if volume:
|
||||
volume_id = volume.id
|
||||
else:
|
||||
volume_id = None
|
||||
self.fields['volume_id'] = forms.CharField(widget=forms.HiddenInput(),
|
||||
initial=volume_id)
|
||||
|
||||
# Populate instance choices
|
||||
instance_list = kwargs.get('initial', {}).get('instances', [])
|
||||
instances = []
|
||||
for instance in instance_list:
|
||||
if instance.status in tables.ACTIVE_STATES and \
|
||||
not any(instance.id == att["server_id"]
|
||||
for att in volume.attachments):
|
||||
instances.append((instance.id, '%s (%s)' % (instance.name,
|
||||
instance.id)))
|
||||
if instances:
|
||||
instances.insert(0, ("", _("Select an instance")))
|
||||
else:
|
||||
instances = (("", _("No instances available")),)
|
||||
self.fields['instance'].choices = instances
|
||||
|
||||
def handle(self, request, data):
|
||||
instance_choices = dict(self.fields['instance'].choices)
|
||||
instance_name = instance_choices.get(data['instance'],
|
||||
_("Unknown instance (None)"))
|
||||
# The name of the instance in the choices list has the ID appended to
|
||||
# it, so let's slice that off...
|
||||
instance_name = instance_name.rsplit(" (")[0]
|
||||
try:
|
||||
attach = api.nova.instance_volume_attach(request,
|
||||
data['volume_id'],
|
||||
data['instance'],
|
||||
data.get('device', ''))
|
||||
volume = cinder.volume_get(request, data['volume_id'])
|
||||
if not volume.display_name:
|
||||
volume_name = volume.id
|
||||
else:
|
||||
volume_name = volume.display_name
|
||||
message = _('Attaching volume %(vol)s to instance '
|
||||
'%(inst)s on %(dev)s.') % {"vol": volume_name,
|
||||
"inst": instance_name,
|
||||
"dev": attach.device}
|
||||
messages.info(request, message)
|
||||
return True
|
||||
except Exception:
|
||||
redirect = reverse("horizon:project:volumes:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to attach volume.'),
|
||||
redirect=redirect)
|
|
@ -1,45 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
import horizon
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.dashboards.project import dashboard
|
||||
|
||||
|
||||
class ServerGroups(horizon.Panel):
|
||||
name = _("Server Groups")
|
||||
slug = 'server_groups'
|
||||
# Server groups are wrs-specific
|
||||
permissions = ('openstack.services.platform',)
|
||||
|
||||
def allowed(self, context):
|
||||
if not base.is_service_enabled(context['request'], 'compute'):
|
||||
return False
|
||||
else:
|
||||
return super(ServerGroups, self).allowed(context)
|
||||
|
||||
def nav(self, context):
|
||||
if not base.is_service_enabled(context['request'], 'compute'):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
dashboard.Project.register(ServerGroups)
|
|
@ -1,283 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
from django.core.urlresolvers import NoReverseMatch
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils import html
|
||||
from django.utils import safestring
|
||||
from django.utils.translation import string_concat # noqa
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.volumes.tables \
|
||||
import get_attachment_name
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
from starlingx_dashboard.api import nova as stx_nova
|
||||
|
||||
DELETABLE_STATES = ("available", "error")
|
||||
|
||||
|
||||
class DeleteServerGroup(tables.DeleteAction):
|
||||
data_type_singular = _("Server Group")
|
||||
data_type_plural = _("Server Groups")
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
"Delete Server Group",
|
||||
"Delete Server Groups",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
"Deleted Server Group",
|
||||
"Deleted Server Groups",
|
||||
count
|
||||
)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
obj = self.table.get_object_by_id(obj_id)
|
||||
name = self.table.get_object_display(obj)
|
||||
|
||||
try:
|
||||
stx_nova.server_group_delete(request, obj_id)
|
||||
except Exception:
|
||||
msg = _('Unable to delete group "%s" because it is not empty. '
|
||||
'Either delete the member '
|
||||
'instances or remove them from the group.')
|
||||
exceptions.check_message(["group", "not", "empty."], msg % name)
|
||||
raise
|
||||
|
||||
# maybe do a precheck to see if the group is empty first?
|
||||
def allowed(self, request, server_group=None):
|
||||
return True
|
||||
|
||||
|
||||
class CreateServerGroup(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Server Group")
|
||||
url = "horizon:project:server_groups:create"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
icon = "plus"
|
||||
|
||||
def allowed(self, request, volume=None):
|
||||
usages = quotas.tenant_quota_usages(request)
|
||||
if usages['server_groups']['available'] <= 0:
|
||||
if "disabled" not in self.classes:
|
||||
self.classes = [c for c in self.classes] + ['disabled']
|
||||
self.verbose_name = string_concat(self.verbose_name, ' ',
|
||||
_("(Quota exceeded)"))
|
||||
else:
|
||||
self.verbose_name = _("Create Server Group")
|
||||
classes = [c for c in self.classes if c != "disabled"]
|
||||
self.classes = classes
|
||||
return True
|
||||
|
||||
|
||||
class EditAttachments(tables.LinkAction):
|
||||
name = "attachments"
|
||||
verbose_name = _("Edit Attachments")
|
||||
url = "horizon:project:server_groups:attach"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def allowed(self, request, server_group=None):
|
||||
return True # volume.status in ("available", "in-use")
|
||||
|
||||
|
||||
class CreateSnapshot(tables.LinkAction):
|
||||
name = "snapshots"
|
||||
verbose_name = _("Create Snapshot")
|
||||
url = "horizon:project:server_groups:create_snapshot"
|
||||
classes = ("ajax-modal", "btn-camera")
|
||||
|
||||
def allowed(self, request, server_group=None):
|
||||
return True # server_group.status == "available"
|
||||
|
||||
|
||||
class UpdateRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, server_group_id):
|
||||
server_group = stx_nova.server_group_get(request, server_group_id)
|
||||
if not server_group.name:
|
||||
server_group.name = server_group_id
|
||||
return server_group
|
||||
|
||||
|
||||
def get_policies(server_group):
|
||||
policies = ', '.join(server_group.policies)
|
||||
return policies
|
||||
|
||||
|
||||
def get_metadata(server_group):
|
||||
metadata_items = ['{}:{}'.format(x, y) for x, y in
|
||||
server_group.metadata.items()]
|
||||
metadata = ', '.join(metadata_items)
|
||||
return metadata
|
||||
|
||||
|
||||
def get_member_name(request, server_id):
|
||||
try:
|
||||
server = api.nova.server_get(request, server_id)
|
||||
name = server.name
|
||||
except Exception:
|
||||
name = None
|
||||
exceptions.handle(request, _("Unable to retrieve "
|
||||
"member information."))
|
||||
# try and get a URL
|
||||
try:
|
||||
url = reverse("horizon:project:instances:detail", args=(server_id,))
|
||||
instance = '<a href="%s">%s</a>' % (url, html.escape(name))
|
||||
except NoReverseMatch:
|
||||
instance = name
|
||||
return instance
|
||||
|
||||
|
||||
class MemberColumn(tables.Column):
|
||||
"""Customized column class
|
||||
|
||||
Customized column class that does complex processing on the instances
|
||||
in a server group. This was substantially copied
|
||||
from the volume equivalent.
|
||||
"""
|
||||
|
||||
def get_raw_data(self, server_group):
|
||||
request = self.table.request
|
||||
link = _('%(name)s (%(id)s)')
|
||||
members = []
|
||||
for member in server_group.members:
|
||||
member_id = member
|
||||
name = get_member_name(request, member)
|
||||
vals = {"name": name, "id": member_id}
|
||||
members.append(link % vals)
|
||||
return safestring.mark_safe(", ".join(members))
|
||||
|
||||
|
||||
def get_server_group_type(server_group):
|
||||
return server_group.volume_type if server_group.volume_type != "None" \
|
||||
else None
|
||||
|
||||
|
||||
class ServerGroupsFilterAction(tables.FilterAction):
|
||||
def filter(self, table, server_groups, filter_string):
|
||||
"""Naive case-insensitive search."""
|
||||
q = filter_string.lower()
|
||||
return [group for group in server_groups
|
||||
if q in group.display_name.lower()]
|
||||
|
||||
|
||||
class ServerGroupsTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
verbose_name=_("Group Name"),
|
||||
link="horizon:project:server_groups:detail")
|
||||
policies = tables.Column(get_policies,
|
||||
verbose_name=_("Policies"))
|
||||
members = MemberColumn("members",
|
||||
verbose_name=_("Members"))
|
||||
metadata = tables.Column(get_metadata,
|
||||
verbose_name=_("Metadata"))
|
||||
|
||||
class Meta(object):
|
||||
name = "server_groups"
|
||||
verbose_name = _("Server Groups")
|
||||
row_class = UpdateRow
|
||||
table_actions = (
|
||||
CreateServerGroup, DeleteServerGroup, ServerGroupsFilterAction)
|
||||
row_actions = (DeleteServerGroup,)
|
||||
|
||||
def get_object_display(self, obj):
|
||||
return obj.name
|
||||
|
||||
|
||||
class DetachServerGroup(tables.BatchAction):
|
||||
name = "detach"
|
||||
data_type_singular = _("Server Group")
|
||||
data_type_plural = _("Server Groups")
|
||||
classes = ('btn-danger', 'btn-detach')
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
"Server Group",
|
||||
"Server Groups",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
"Deleted Group",
|
||||
"Deleted Groups",
|
||||
count
|
||||
)
|
||||
|
||||
def action(self, request, obj_id):
|
||||
attachment = self.table.get_object_by_id(obj_id)
|
||||
api.nova.instance_server_group_detach(
|
||||
request,
|
||||
attachment.get('server_id', None),
|
||||
obj_id)
|
||||
|
||||
def get_success_url(self, request):
|
||||
return reverse('horizon:project:server_groups:index')
|
||||
|
||||
|
||||
class AttachedInstanceColumn(tables.Column):
|
||||
"""Customized column class
|
||||
|
||||
Customized column class that does complex processing on the attachments
|
||||
for a server group.
|
||||
"""
|
||||
|
||||
def get_raw_data(self, attachment):
|
||||
request = self.table.request
|
||||
return safestring.mark_safe(get_attachment_name(request, attachment))
|
||||
|
||||
|
||||
class AttachmentsTable(tables.DataTable):
|
||||
instance = AttachedInstanceColumn(get_member_name,
|
||||
verbose_name=_("Instance"))
|
||||
device = tables.Column("device",
|
||||
verbose_name=_("Device"))
|
||||
|
||||
def get_object_id(self, obj):
|
||||
return obj['id']
|
||||
|
||||
def get_object_display(self, attachment):
|
||||
instance_name = get_attachment_name(self.request, attachment)
|
||||
vals = {"dev": attachment['device'],
|
||||
"instance_name": html.strip_tags(instance_name)}
|
||||
return _("%(dev)s on instance %(instance_name)s") % vals
|
||||
|
||||
def get_object_by_id(self, obj_id):
|
||||
for obj in self.data:
|
||||
if self.get_object_id(obj) == obj_id:
|
||||
return obj
|
||||
raise ValueError('No match found for the id "%s".' % obj_id)
|
||||
|
||||
class Meta(object):
|
||||
name = "attachments"
|
||||
verbose_name = _("Attachments")
|
||||
table_actions = (DetachServerGroup,)
|
||||
row_actions = (DetachServerGroup,)
|
|
@ -1,53 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard.api import nova
|
||||
|
||||
from starlingx_dashboard.api import nova as stx_nova
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = ("project/server_groups/"
|
||||
"_detail_overview.html")
|
||||
|
||||
def get_context_data(self, request):
|
||||
server_group_id = self.tab_group.kwargs['server_group_id']
|
||||
try:
|
||||
server_group = stx_nova.server_group_get(request, server_group_id)
|
||||
server_group.members_display = []
|
||||
for member in server_group.members:
|
||||
server_group.members_display.append(
|
||||
dict(id=member, instance=nova.server_get(request, member)))
|
||||
except Exception:
|
||||
redirect = reverse('horizon:project:server_groups:index')
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve server group details.'),
|
||||
redirect=redirect)
|
||||
return {'server_group': server_group}
|
||||
|
||||
|
||||
class ServerGroupDetailTabs(tabs.TabGroup):
|
||||
slug = "server_group_details"
|
||||
tabs = (OverviewTab,)
|
|
@ -1,25 +0,0 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}attach_volume_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:volumes:attach' volume.id %}{% endblock %}
|
||||
{% block form_class %}{{ block.super }} horizontal {% if show_attach %}split_half{% else %} no_split{% endif %}{% endblock %}
|
||||
|
||||
{% block modal_id %}attach_volume_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Manage Volume Attachments" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
{% if show_attach %}
|
||||
<h3>{% trans "Attach To Instance" %}</h3>
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a class="btn btn-default cancel" data-dismiss="modal">Cancel</a>
|
||||
{% if show_attach %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Attach Volume" %}" />
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,26 +0,0 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:server_groups:create' %}?{{ request.GET.urlencode }}{% endblock %}
|
||||
|
||||
{% block modal_id %}create_server_group_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Create Server Group" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% trans "From here you can create a new server group" %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a class="btn btn-default cancel" data-dismiss="modal">{% trans "Cancel" %}</a>
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Server Group" %}" />
|
||||
{% endblock %}
|
|
@ -1,52 +0,0 @@
|
|||
{% load i18n sizeformat parse_date %}
|
||||
|
||||
<h3>{% trans "Server Group Overview" %}: {{server_group.name }}</h3>
|
||||
|
||||
<div class="info row-fluid detail">
|
||||
<h4>{% trans "Info" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ server_group.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ server_group.id }}</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ server_group.status|capfirst }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="members row-fluid detail">
|
||||
<h4>{% trans "Members" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
{% for member in server_group.members_display %}
|
||||
<dd>
|
||||
{% url 'horizon:project:instances:detail' member.id as instance_url%}
|
||||
<a href="{{ instance_url }}">{{ member.instance.name }} ({{ member.id }})</a>
|
||||
</dd>
|
||||
{% empty %}
|
||||
<dd><em>{% trans "No members" %}</em></dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="policies row-fluid detail">
|
||||
<h4>{% trans "Policies" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
{% for policy in server_group.policies %}
|
||||
<dd>{{ policy }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="status row-fluid detail">
|
||||
<h4>{% trans "Metadata" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
{% for key, value in server_group.metadata.items %}
|
||||
<dt>{{ key }}</dt>
|
||||
<dd>{{ value }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Manage Volume Attachments" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Manage Volume Attachments") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/volumes/_attach.html' %}
|
||||
{% endblock %}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Server Group" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create an Server Group") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/server_groups/_create.html' %}
|
||||
{% endblock %}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% block title %}{% trans "Server Group Details" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Server Groups" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Server Groups") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
|
@ -1,32 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from starlingx_dashboard.dashboards.project.server_groups import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||
url(r'^(?P<server_group_id>[^/]+)/attach/$',
|
||||
views.EditAttachmentsView.as_view(),
|
||||
name='attach'),
|
||||
url(r'^(?P<server_group_id>[^/]+)/$',
|
||||
views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
]
|
|
@ -1,148 +0,0 @@
|
|||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Copyright (c) 2013-2018 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
"""
|
||||
Views for managing server groups.
|
||||
"""
|
||||
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy # noqa
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
from starlingx_dashboard import api as stx_api
|
||||
from starlingx_dashboard.dashboards.project.server_groups \
|
||||
import forms as project_forms
|
||||
from starlingx_dashboard.dashboards.project.server_groups \
|
||||
import tables as project_tables
|
||||
from starlingx_dashboard.dashboards.project.server_groups \
|
||||
import tabs as project_tabs
|
||||
|
||||
|
||||
# server groups don't currently support pagination
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = project_tables.ServerGroupsTable
|
||||
template_name = 'project/server_groups/index.html'
|
||||
page_title = _("Server Groups")
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
server_groups = stx_api.nova.server_group_list(
|
||||
self.request)
|
||||
except Exception:
|
||||
server_groups = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve server groups.'))
|
||||
return server_groups
|
||||
|
||||
|
||||
class DetailView(tabs.TabView):
|
||||
tab_group_class = project_tabs.ServerGroupDetailTabs
|
||||
template_name = 'project/server_groups/detail.html'
|
||||
page_title = 'Server Group Details'
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = project_forms.CreateForm
|
||||
template_name = 'project/server_groups/create.html'
|
||||
success_url = reverse_lazy("horizon:project:server_groups:index")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
try:
|
||||
context['usages'] = quotas.tenant_limit_usages(self.request)
|
||||
except Exception:
|
||||
exceptions.handle(self.request)
|
||||
return context
|
||||
|
||||
|
||||
class EditAttachmentsView(tables.DataTableView, forms.ModalFormView):
|
||||
table_class = project_tables.AttachmentsTable
|
||||
form_class = project_forms.AttachForm
|
||||
template_name = 'project/server_groups/attach.html'
|
||||
success_url = reverse_lazy("horizon:project:server_groups:index")
|
||||
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
volume_id = self.kwargs['volume_id']
|
||||
try:
|
||||
self._object = api.cinder.volume_get(self.request, volume_id)
|
||||
except Exception:
|
||||
self._object = None
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve volume information.'))
|
||||
return self._object
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
volumes = self.get_object()
|
||||
attachments = [att for att in volumes.attachments if att]
|
||||
except Exception:
|
||||
attachments = []
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve volume information.'))
|
||||
return attachments
|
||||
|
||||
def get_initial(self):
|
||||
try:
|
||||
instances, has_more = api.nova.server_list(self.request) # noqa pylint: disable=unused-variable
|
||||
except Exception:
|
||||
instances = []
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve attachment information."))
|
||||
return {'volume': self.get_object(),
|
||||
'instances': instances}
|
||||
|
||||
def get_form(self):
|
||||
if not hasattr(self, "_form"):
|
||||
form_class = self.get_form_class()
|
||||
self._form = super(EditAttachmentsView, self).get_form(form_class)
|
||||
return self._form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EditAttachmentsView, self).get_context_data(**kwargs)
|
||||
context['form'] = self.get_form()
|
||||
volume = self.get_object()
|
||||
if volume and volume.status == 'available':
|
||||
context['show_attach'] = True
|
||||
else:
|
||||
context['show_attach'] = False
|
||||
context['volume'] = volume
|
||||
if self.request.is_ajax():
|
||||
context['hide'] = True
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Table action handling
|
||||
handled = self.construct_tables()
|
||||
if handled:
|
||||
return handled
|
||||
return self.render_to_response(self.get_context_data(**kwargs))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.get(request, *args, **kwargs)
|
|
@ -3,4 +3,10 @@ ADD_INSTALLED_APPS = ['starlingx_dashboard']
|
|||
|
||||
FEATURE = ['starlingx_dashboard']
|
||||
|
||||
ADD_HEADER_SECTIONS = \
|
||||
['starlingx_dashboard.dashboards.admin.active_alarms.views.BannerView', ]
|
||||
|
||||
ADD_SCSS_FILES = ['dashboard/scss/styles.scss',
|
||||
'dashboard/scss/_host_topology.scss']
|
||||
|
||||
AUTO_DISCOVER_STATIC_FILES = True
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'server_groups'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'project'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'compute'
|
||||
# A list of applications to be added to INSTALLED_APPS.
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = \
|
||||
'starlingx_dashboard.dashboards.project.server_groups.panel.ServerGroups'
|
|
@ -1,10 +0,0 @@
|
|||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'server_groups'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'admin'
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
PANEL_GROUP = 'compute'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = \
|
||||
'starlingx_dashboard.dashboards.admin.server_groups.panel.ServerGroups'
|
|
@ -0,0 +1,4 @@
|
|||
PANEL_DASHBOARD = 'admin'
|
||||
PANEL_GROUP = 'system'
|
||||
PANEL = 'defaults'
|
||||
REMOVE_PANEL = True
|
|
@ -0,0 +1 @@
|
|||
from starlingx_dashboard.horizon.forms.fields import DynamicIntegerField # noqa
|
|
@ -0,0 +1,38 @@
|
|||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from django.core import validators
|
||||
|
||||
from horizon.forms import IntegerField
|
||||
|
||||
|
||||
class DynamicIntegerField(IntegerField):
|
||||
"""A subclass of ``IntegerField``.
|
||||
|
||||
A subclass of ``IntegerField`` with additional properties that make
|
||||
dynamically updating its range easier.
|
||||
"""
|
||||
def set_max_value(self, max_value):
|
||||
if max_value is not None:
|
||||
for v in self.validators:
|
||||
if isinstance(v, validators.MaxValueValidator):
|
||||
self.validators.remove(v)
|
||||
|
||||
self.max_value = max_value
|
||||
self.validators.append(validators.MaxValueValidator(max_value))
|
||||
self.widget_attrs(self.widget)
|
||||
self.widget.attrs['max'] = self.max_value
|
||||
|
||||
def set_min_value(self, min_value):
|
||||
if min_value is not None:
|
||||
for v in self.validators:
|
||||
if isinstance(v, validators.MinValueValidator):
|
||||
self.validators.remove(v)
|
||||
|
||||
self.min_value = min_value
|
||||
self.validators.append(validators.MinValueValidator(min_value))
|
||||
self.widget_attrs(self.widget)
|
||||
self.widget.attrs['min'] = self.min_value
|
|
@ -1,7 +1,13 @@
|
|||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from openstack_dashboard.local import configss
|
||||
from openstack_dashboard.settings import HORIZON_CONFIG
|
||||
from starlingx_dashboard import configss
|
||||
from tsconfig.tsconfig import distributed_cloud_role
|
||||
|
||||
|
||||
|
@ -312,7 +318,9 @@ LOGGING = {
|
|||
},
|
||||
}
|
||||
|
||||
# Session timeout overrides
|
||||
# Session overrides
|
||||
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
|
||||
SESSION_FILE_PATH = '/var/tmp'
|
||||
|
||||
# SESSION_TIMEOUT is a method to supersede the token timeout with a shorter
|
||||
# horizon session timeout (in seconds). So if your token expires in 60
|
||||
|
@ -325,8 +333,11 @@ SESSION_TIMEOUT = 3000
|
|||
# value of 600 will log users out after 50 minutes.
|
||||
TOKEN_TIMEOUT_MARGIN = 600
|
||||
|
||||
# The timezone of the server. This should correspond with the timezone
|
||||
# of your entire OpenStack installation, and hopefully be in UTC.
|
||||
# In this case, we set the value to None so that the interface uses the system
|
||||
# timezone by default
|
||||
TIME_ZONE = None
|
||||
|
||||
# Retrieve the current system timezone
|
||||
USE_TZ = True
|
||||
try:
|
||||
tz = os.path.realpath('/etc/localtime')
|
||||
TIME_ZONE = tz.split('zoneinfo/')[1]
|
||||
except Exception:
|
||||
TIME_ZONE = 'UTC'
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
#hostTopologyContainer {
|
||||
|
||||
#topologyCanvasContainer {
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
cursor: auto;
|
||||
padding-bottom: 25px;
|
||||
padding-left: 10px;
|
||||
padding-right: 20px;
|
||||
padding-top: 5px;
|
||||
background: #ffffff;
|
||||
min-height: 400px;
|
||||
|
||||
div.nodata {
|
||||
font-size: 150%;
|
||||
text-align: center;
|
||||
padding-top: 150px;
|
||||
display: none;
|
||||
}
|
||||
&.noinfo {
|
||||
div.nodata {
|
||||
display: block;
|
||||
}
|
||||
#topology_canvas {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#canvas_container {
|
||||
font-family: sans-serif;
|
||||
cursor: grab;
|
||||
cursor: -webkit-grab;
|
||||
|
||||
display: table-cell;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
#topology_canvas {
|
||||
border: 1px;
|
||||
border-color: grey;
|
||||
border-style: solid;
|
||||
|
||||
.network-name {
|
||||
fill: black;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#top_row {
|
||||
display: table;
|
||||
height: 550px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
#lists_container {
|
||||
display: inline-block;
|
||||
min-width: 200px;
|
||||
max-width: 300px;
|
||||
margin-right: 20px;
|
||||
vertical-align: bottom;
|
||||
|
||||
div {
|
||||
min-height: 200px;
|
||||
max-height: initial;
|
||||
height: 200px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
a.list-group-item {
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 32px;
|
||||
}
|
||||
a.related {
|
||||
color: #555;
|
||||
background-color: #8eb9df;
|
||||
}
|
||||
svg.list_alarm {
|
||||
overflow: visible;
|
||||
width: 26px;
|
||||
height: 22px;
|
||||
vertical-align: middle;
|
||||
margin-top: -4px;
|
||||
margin-right: -29px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#detail_view {
|
||||
.spinner {
|
||||
margin-top: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#hostTopologyNavi {
|
||||
overflow: hidden;
|
||||
margin-top: 10px;
|
||||
padding-left: 10px;
|
||||
#toggleLabels {
|
||||
float: left;
|
||||
span.glyphicon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Data List for sysinv
|
||||
.dl-horizontal-wide {
|
||||
@extend .dl-horizontal;
|
||||
dt {
|
||||
width: 22em;
|
||||
}
|
||||
dd {
|
||||
margin-left: 23em;
|
||||
}
|
||||
}
|
||||
|
||||
// Fault management summary formatting
|
||||
#active-alarm-stats {
|
||||
margin: 10px 5px;
|
||||
& > span {
|
||||
margin: 0px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
// System patch summary formatting
|
||||
#patching-status {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
// Hosts summary formatting
|
||||
#hosts-stats {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
// Sensor summary formatting
|
||||
#active-sensor-stats {
|
||||
margin-top: 10px;
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2019 Wind River Systems, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
/* Core functionality related to alam-banner. */
|
||||
horizon.alarmbanner = {
|
||||
|
||||
enabled: false,
|
||||
refresh: function(data) {
|
||||
|
||||
var $old = $(location).attr('pathname');
|
||||
var $url = $(location).attr('href');
|
||||
$url = $url.replace($old, "/admin/active_alarms/banner");
|
||||
|
||||
horizon.ajax.queue({
|
||||
url: $url,
|
||||
success: function(data, textStatus, jqXHR) {
|
||||
var $new = $(data);
|
||||
var $old = $('.alarmbanner');
|
||||
if ($new.html() !== $old.html()) {
|
||||
$old.html($new.html());
|
||||
}
|
||||
|
||||
// start periodic refresh
|
||||
if (horizon.alarmbanner.enabled === false) {
|
||||
horizon.refresh.addRefreshFunction(
|
||||
horizon.alarmbanner.refresh);
|
||||
horizon.alarmbanner.enabled = true;
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, error) {
|
||||
if (jqXHR.status !== 401 && jqXHR.status !== 403) {
|
||||
// error is raised with status of 0 when ajax query is cancelled
|
||||
// due to new page request
|
||||
if (jqXHR.status !== 0) {
|
||||
horizon.clearInfoMessages();
|
||||
horizon.clearErrorMessages();
|
||||
horizon.alert("info", gettext("Failed to refresh alarm banner, retrying on next interval."));
|
||||
}
|
||||
|
||||
// start periodic refresh
|
||||
if (horizon.alarmbanner.enabled === false) {
|
||||
horizon.refresh.addRefreshFunction(
|
||||
horizon.alarmbanner.refresh);
|
||||
horizon.alarmbanner.enabled = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
complete: function(jqXHR, textStatus) {
|
||||
}
|
||||
});
|
||||
return true;
|
||||
},
|
||||
|
||||
onclick: function() {
|
||||
var $fm = "/admin/active_alarms";
|
||||
var $dc = "/dc_admin/";
|
||||
var $cur = document.location.href;
|
||||
var $path = document.location.pathname;
|
||||
if ($path.match($fm) === null && $path.match($dc) === null) {
|
||||
document.location.href = $cur.replace($path, $fm);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
horizon.addInitFunction(function() {
|
||||
// trigger alarm banner refresh on page load and enable
|
||||
// periodic refresh after the first one
|
||||
horizon.alarmbanner.refresh(null);
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
/* Functionality related to detail panes */
|
||||
horizon.details = {
|
||||
};
|
||||
|
||||
horizon.details.refresh = function(html) {
|
||||
var $new_details = $(html).find('.detail > dl').parent();
|
||||
var $old_details = $('.detail > dl').parent();
|
||||
|
||||
if ($new_details.length != $old_details.length) {
|
||||
// Page structure has changed, abort refresh
|
||||
return false;
|
||||
}
|
||||
|
||||
$new_details.each(function(index, elm) {
|
||||
var $new_detail = $(this);
|
||||
var $old_detail = $($old_details.get(index));
|
||||
if ($new_detail.html() != $old_detail.html()) {
|
||||
$old_detail.replaceWith($new_detail);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
horizon.addInitFunction(function() {
|
||||
if ($('.detail > dl').length > 0) {
|
||||
// Register callback handler to update the detail panels on page refresh
|
||||
horizon.refresh.addRefreshFunction(horizon.details.refresh);
|
||||
}
|
||||
});
|
|
@ -1,135 +0,0 @@
|
|||
/* Core functionality related to page auto refresh. */
|
||||
horizon.refresh = {
|
||||
refresh_interval: 5000,
|
||||
_refresh_functions: [],
|
||||
timeout: null,
|
||||
|
||||
init: function() {
|
||||
// Add page refresh time to page header
|
||||
horizon.refresh.datetime();
|
||||
|
||||
$('#navbar-collapse > div.context_selection > ul > li > ul li a ').click(function (){
|
||||
clearTimeout(horizon.refresh.timeout);
|
||||
clearTimeout(horizon.datatables.timeout);
|
||||
});
|
||||
|
||||
// Setup next refresh interval
|
||||
horizon.refresh.timeout = setTimeout(horizon.refresh.update, horizon.refresh.refresh_interval);
|
||||
},
|
||||
|
||||
update: function() {
|
||||
if (!horizon.refresh._refresh_functions.length) {
|
||||
// No refresh handlers registered
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore any tabs which react badly to auto-refresh
|
||||
var ignore = ["instance_details__console"];
|
||||
|
||||
// Figure out which tabs have been loaded
|
||||
loaded = "";
|
||||
$(".nav-tabs a[data-target]").each(function(index){
|
||||
var slug = $(this).attr('data-target').replace('#','');
|
||||
if ($(this).attr('data-loaded') == 'true' && ignore.indexOf(slug) == -1){
|
||||
if (loaded){loaded+=','}
|
||||
loaded+=slug;
|
||||
}
|
||||
});
|
||||
|
||||
// Grab current href (for refresh) but remove the tab parameter ( if exists )
|
||||
var currentHREF = $(location).attr('href');
|
||||
var qryStrObj = jQuery.query.load( currentHREF );
|
||||
var oldQryStr = qryStrObj.toString();
|
||||
var newQryStr = qryStrObj.SET("tab", null).SET("loaded", loaded).COMPACT().toString();
|
||||
if (oldQryStr){
|
||||
var $href=currentHREF.replace(oldQryStr, newQryStr);
|
||||
}else {
|
||||
var $href=currentHREF += newQryStr;
|
||||
}
|
||||
|
||||
horizon.ajax.queue({
|
||||
url: $href,
|
||||
success: function(data, textStatus, jqXHR) {
|
||||
var refreshed = true;
|
||||
if (jqXHR.responseText.length > 0) {
|
||||
$(horizon.refresh._refresh_functions).each(function (index, f) {
|
||||
refreshed = f(data);
|
||||
return refreshed;
|
||||
});
|
||||
if (refreshed) {
|
||||
horizon.refresh.datetime();
|
||||
}
|
||||
} else {
|
||||
// assume that the entity has been deleted
|
||||
location.href = $('#sidebar-accordion a.openstack-panel.active').attr('href');
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, error) {
|
||||
// Zero-status usually seen when user navigates away
|
||||
if (jqXHR.status != 0) {
|
||||
horizon.refresh.alert();
|
||||
}
|
||||
},
|
||||
complete: function(jqXHR, textStatus) {
|
||||
|
||||
// Check for redirect condition
|
||||
var redirect = false
|
||||
switch (jqXHR.status) {
|
||||
case 401:
|
||||
// Authorization error, likely redirect to login page
|
||||
redirect = jqXHR.getResponseHeader("X-Horizon-Location");
|
||||
break;
|
||||
case 404:
|
||||
// The object seems to be gone, redirect back to main nav
|
||||
redirect = $('#sidebar-accordion a.openstack-panel.active').attr('href');
|
||||
break;
|
||||
case 302:
|
||||
// Object is likely gone and are being redirect to index page
|
||||
redirect = jqXHR.getResponseHeader("Location");
|
||||
break;
|
||||
}
|
||||
if (redirect) {
|
||||
location.href = redirect;
|
||||
}
|
||||
|
||||
horizon.autoDismissAlerts();
|
||||
|
||||
// Check for error condition
|
||||
var messages = $.parseJSON(horizon.ajax.get_messages(jqXHR));
|
||||
var errors = $(messages).filter(function (index, item) {
|
||||
return item[0] == 'error';
|
||||
});
|
||||
if (errors.length > 0) {
|
||||
horizon.refresh.alert();
|
||||
}
|
||||
|
||||
// Reset for next refresh interval
|
||||
horizon.refresh.timeout = setTimeout(horizon.refresh.update, horizon.refresh.refresh_interval);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
alert: function() {
|
||||
horizon.clearInfoMessages();
|
||||
horizon.clearErrorMessages();
|
||||
horizon.clearWarningMessages();
|
||||
horizon.alert("info", gettext("Failed to auto refresh page, retrying on next interval."));
|
||||
},
|
||||
|
||||
datetime: function() {
|
||||
// Update page refresh time
|
||||
var location = $('.datetime');
|
||||
if (location !== null) {
|
||||
var datetime = new Date();
|
||||
location.html(datetime.toLocaleString());
|
||||
}
|
||||
},
|
||||
|
||||
addRefreshFunction: function(f) {
|
||||
horizon.refresh._refresh_functions.push(f);
|
||||
}
|
||||
};
|
||||
|
||||
horizon.addInitFunction(function() {
|
||||
horizon.refresh.init();
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
#
|
||||
# Copyright (c) 2019 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name="get_value")
|
||||
def get_value(dictionary, key):
|
||||
return dictionary.get(key)
|
Loading…
Reference in New Issue