Making multiple fixes to the cue dashboard

- Renaming column names
- Adding endpoint URL into the table list
- Username and password field in create cue form
- Remove network widget and replace with dropdown
- Add flavor details to create cluster
- Update tooltips

Change-Id: I16f5cde7109931b40c80108fa1dc76cd6597255a
This commit is contained in:
Steve Leon 2015-03-24 15:58:45 -07:00
parent 2d752d0cc6
commit 97604666d6
9 changed files with 103 additions and 132 deletions

2
.gitignore vendored
View File

@ -7,5 +7,5 @@ ChangeLog
cue_dashboard.egg*
build/
.tox/
.egg/
.eggs/
.idea/

View File

@ -67,10 +67,7 @@ def flavor(request, flavor_id):
# This is needed because the cue client returns a dict
# instead of a cluster object.
def _to_cluster_object(cluster_dict):
endpoint = (cluster_dict['end_points'][0] if cluster_dict['end_points']
else None)
if endpoint:
cluster_dict['url'] = "".join((endpoint['type'],
'://',
endpoint['uri']))
endpoints = ["".join((endpoint['type'], '://', endpoint['uri']))
for endpoint in cluster_dict['end_points']]
cluster_dict['url'] = endpoints
return namedtuple('Cluster', cluster_dict)(**cluster_dict)

View File

@ -20,10 +20,6 @@ from django.utils.translation import ungettext_lazy
from django.utils.translation import ugettext as _
from horizon import tables
from cuedashboard import api
import logging
LOG = logging.getLogger(__name__)
class CreateCluster(tables.LinkAction):
@ -66,11 +62,18 @@ class UpdateRow(tables.Row):
return api.cluster_get(request, cluster_id)
def format_endpoints(cluster):
if hasattr(cluster, "url"):
return ', '.join(cluster.url)
return "-"
class ClusterTable(tables.DataTable):
name = tables.Column("name",
verbose_name=_("Name"),
link='horizon:project:queues:detail')
size = tables.Column("size", verbose_name=_("Cluster Size"),)
endpoint = tables.Column(format_endpoints, verbose_name=_("Endpoints"))
status = tables.Column("status", verbose_name=_("Status"))
class Meta:

View File

@ -16,8 +16,6 @@
# Copyright [2014] Hewlett-Packard Development Company, L.P.
# limitations under the License.
import logging
from cuedashboard import api
from cuedashboard.queues.tables import ClusterTable
from cuedashboard.queues.tabs import ClusterDetailTabs
@ -30,9 +28,6 @@ from horizon.utils import memoized
from horizon import workflows
LOG = logging.getLogger(__name__)
class IndexView(tables.DataTableView):
table_class = ClusterTable
template_name = 'queues/index.html'

View File

@ -13,14 +13,16 @@
# under the License.
import logging
import json
from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.forms import ValidationError
from horizon import exceptions
from horizon import forms
from horizon.utils import memoized
from horizon.utils import validators
from horizon import workflows
from openstack_dashboard import api
from cuedashboard.api import cluster_create
@ -32,14 +34,58 @@ from openstack_dashboard.dashboards.project.instances \
LOG = logging.getLogger(__name__)
class PasswordMixin(forms.SelfHandlingForm):
password = forms.RegexField(
label=_("Password"),
widget=forms.PasswordInput(render_value=False),
regex=validators.password_validator(),
error_messages={'invalid': validators.password_validator_msg()})
confirm_password = forms.CharField(
label=_("Confirm Password"),
widget=forms.PasswordInput(render_value=False))
no_autocomplete = True
def clean(self):
'''Check to make sure password fields match.'''
data = super(forms.Form, self).clean()
if 'password' in data:
if data['password'] != data.get('confirm_password', None):
raise ValidationError(_('Passwords do not match.'))
return data
class SetInstanceDetailsAction(workflows.Action):
name = forms.CharField(max_length=80, label=_("Cluster Name"))
flavor = forms.ChoiceField(label=_("Flavor"),
help_text=_("Size of image to launch."))
help_text=_("The amount of RAM and CPU included \
in each node of the cluster."))
size = forms.IntegerField(label=_("Cluster Size"),
min_value=1,
initial=1,
help_text=_("Size of cluster."))
help_text=_("The number of nodes that make up \
the cluster."))
network = forms.ChoiceField(label=_("Network"),
help_text=_("Network to attach to the \
cluster."))
username = forms.CharField(max_length=80, label=_("User Name"),
help_text=_("User name for logging into the \
RabbitMQ Management UI."))
password = forms.RegexField(
label=_("Password"),
widget=forms.PasswordInput(render_value=False),
regex=validators.password_validator(),
error_messages={'invalid': validators.password_validator_msg()})
confirm_password = forms.CharField(
label=_("Confirm Password"),
widget=forms.PasswordInput(render_value=False))
def clean(self):
'''Check to make sure password fields match.'''
data = super(forms.Form, self).clean()
if 'password' in data:
if data['password'] != data.get('confirm_password', None):
raise ValidationError(_('Passwords do not match.'))
return data
class Meta(object):
name = _("Details")
@ -62,41 +108,8 @@ class SetInstanceDetailsAction(workflows.Action):
return instance_utils.sort_flavor_list(request, flavors)
return []
class SetClusterDetails(workflows.Step):
action_class = SetInstanceDetailsAction
contributes = ("name", "flavor", "size")
class SetNetworkAction(workflows.Action):
network = forms.MultipleChoiceField(label=_("Networks"),
widget=forms.CheckboxSelectMultiple(),
error_messages={
'required': _(
"At least one network must"
" be specified.")},
help_text=_("Create cluster with"
" these networks"))
def __init__(self, request, *args, **kwargs):
super(SetNetworkAction, self).__init__(request, *args, **kwargs)
network_list = self.fields["network"].choices
if len(network_list) == 1:
self.fields['network'].initial = [network_list[0][0]]
class Meta(object):
name = _("Networking")
permissions = ('openstack.services.network',)
help_text = _("Select networks for your cluster.")
def clean(self):
# Cue does not currently support attaching multiple networks.
if len(self.data.getlist("network", None)) > 1:
msg = _("You must select only one network.")
self._errors["network"] = self.error_class([msg])
return self.cleaned_data
def populate_network_choices(self, request, context):
@memoized.memoized_method
def networks(self, request):
try:
tenant_id = self.request.user.tenant_id
networks = api.neutron.network_list_for_tenant(request, tenant_id)
@ -108,25 +121,25 @@ class SetNetworkAction(workflows.Action):
_('Unable to retrieve networks.'))
return network_list
def populate_network_choices(self, request, context):
return self.networks(request)
class SetNetwork(workflows.Step):
action_class = SetNetworkAction
template_name = "queues/_launch_networks.html"
contributes = ("network_id",)
def get_help_text(self, extra_context=None):
extra = {} if extra_context is None else dict(extra_context)
flavors = json.dumps([f._info for f in
instance_utils.flavor_list(self.request)])
try:
extra['flavors'] = flavors
def contribute(self, data, context):
if data:
networks = self.workflow.request.POST.getlist("network")
# If no networks are explicitly specified, network list
# contains an empty string, so remove it.
networks = [n for n in networks if n != '']
if networks:
# TODO
# Choosing the first networks until Cue
# supports more than one networks.
context['network_id'] = networks[0]
except Exception:
exceptions.handle(self.request,
_("Unable to retrieve quota information."))
return super(SetInstanceDetailsAction, self).get_help_text(extra)
return context
class SetClusterDetails(workflows.Step):
action_class = SetInstanceDetailsAction
contributes = ("name", "flavor", "size", "network")
class CreateCluster(workflows.Workflow):
@ -136,8 +149,7 @@ class CreateCluster(workflows.Workflow):
success_message = _('Created cluster named "%(name)s".')
failure_message = _('Unable to create cluster named "%(name)s".')
success_url = "horizon:project:queues:index"
default_steps = (SetClusterDetails,
SetNetwork)
default_steps = (SetClusterDetails,)
def __init__(self, request=None, context_seed=None, entry_point=None,
*args, **kwargs):
@ -155,9 +167,9 @@ class CreateCluster(workflows.Workflow):
LOG.info("Launching message queue cluster with parameters "
"{name=%s, flavor=%s, size=%s, nics=%s}",
context['name'], context['flavor'],
context['size'], context['network_id'])
context['size'], context['network'])
cluster_create(request, context['name'], context['network_id'],
cluster_create(request, context['name'], context['network'],
context['flavor'], context['size'])
return True
except Exception:

View File

@ -17,6 +17,6 @@
<dt>{% trans "Cluster Size" %}</dt>
<dd>{{ cluster.size|default:_("-") }}</dd>
<dt>{% trans "Endpoint" %}</dt>
<dd>{{ cluster.url|default:_("-") }}</dd>
<dd>{{ cluster.url|join:", " |default:_("-") }}</dd>
</dl>
</div>

View File

@ -1,3 +1,23 @@
{% load i18n %}
<p>{% blocktrans %}Specify the details for creating a message queue cluster.{% endblocktrans %}</p>
{% block help_message %}
<p>{% blocktrans %}Specify the details for launching an instance.{% endblocktrans %}
{% blocktrans %}The chart below shows the flavor information.{% endblocktrans %}</p>
{% endblock %}
<h4>{% trans "Flavor Details" %}</h4>
<table class="flavor_table table-striped">
<tbody>
<tr><td class="flavor_name">{% trans "Name" %}</td><td><span id="flavor_name"></span></td></tr>
<tr><td class="flavor_name">{% trans "VCPUs" %}</td><td><span id="flavor_vcpus"></span></td></tr>
<tr><td class="flavor_name">{% trans "Root Disk" %}</td><td><span id="flavor_disk"> </span> {% trans "GB" %}</td></tr>
<tr><td class="flavor_name">{% trans "Ephemeral Disk" %}</td><td><span id="flavor_ephemeral"></span> {% trans "GB" %}</td></tr>
<tr><td class="flavor_name">{% trans "Total Disk" %}</td><td><span id="flavor_disk_total"></span> {% trans "GB" %}</td></tr>
<tr><td class="flavor_name">{% trans "RAM" %}</td><td><span id="flavor_ram"></span> {% trans "MB" %}</td></tr>
</tbody>
</table>
<script type="text/javascript" charset="utf-8">
horizon.Quota.initWithFlavors({{ flavors|safe|default:"{}" }});
</script>

View File

@ -1,10 +0,0 @@
{% load i18n %}
<p>
{% blocktrans %}
Move networks from 'Available Networks' to 'Selected Networks' by
clicking the button, or dragging and dropping. You can change the
NIC order by dragging and dropping as well.
{% endblocktrans %}
</p>
<p>{% blocktrans %}<strong>Please note:</strong> You can only choose one network per cluster.{% endblocktrans %}</p>

View File

@ -1,46 +0,0 @@
{% load i18n %}
<noscript><h3>{{ step }}</h3></noscript>
<table class="table-fixed" id="networkListSortContainer">
<tbody>
<tr>
<td class="col-sm-6">
<label id="selected_network_label">{% trans "Selected networks" %}</label>
<ul id="selected_network" class="networklist">
</ul>
<label>{% trans "Available networks" %}</label>
<ul id="available_network" class="networklist">
</ul>
</td>
<td class="col-sm-6">
{% include "queues/_launch_network_help.html" %}
</td>
</tr>
</tbody>
</table>
<table class="table-fixed" id="networkListIdContainer">
<tbody>
<tr>
<td class="actions">
<div id="networkListId">
{% include "horizon/common/_form_fields.html" %}
</div>
</td>
<td class="help_text">
{{ step.get_help_text }}
</td>
</tr>
</tbody>
</table>
<script>
if (typeof $ !== 'undefined') {
horizon.instances.workflow_init($(".workflow"));
} else {
addHorizonLoadEvent(function() {
horizon.instances.workflow_init($(".workflow"));
});
}
</script>