Commit 90122b75 by Őry Máté

Merge branch 'feature-template-list' into 'master'

Template list upgrade and show deleted vms

Closes #234
Closes #258
parents 0963fa06 986782b5
...@@ -1199,7 +1199,12 @@ class VmListSearchForm(forms.Form): ...@@ -1199,7 +1199,12 @@ class VmListSearchForm(forms.Form):
})) }))
stype = forms.ChoiceField(vm_search_choices, widget=forms.Select(attrs={ stype = forms.ChoiceField(vm_search_choices, widget=forms.Select(attrs={
'class': "btn btn-default input-tags", 'class': "btn btn-default form-control input-tags",
'style': "min-width: 80px;",
}))
include_deleted = forms.BooleanField(widget=forms.CheckboxInput(attrs={
'id': "vm-list-search-checkbox",
})) }))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
...@@ -1209,3 +1214,22 @@ class VmListSearchForm(forms.Form): ...@@ -1209,3 +1214,22 @@ class VmListSearchForm(forms.Form):
data = self.data.copy() data = self.data.copy()
data['stype'] = "all" data['stype'] = "all"
self.data = data self.data = data
class TemplateListSearchForm(forms.Form):
s = forms.CharField(widget=forms.TextInput(attrs={
'class': "form-control input-tags",
'placeholder': _("Search...")
}))
stype = forms.ChoiceField(vm_search_choices, widget=forms.Select(attrs={
'class': "btn btn-default input-tags",
}))
def __init__(self, *args, **kwargs):
super(TemplateListSearchForm, self).__init__(*args, **kwargs)
# set initial value, otherwise it would be overwritten by request.GET
if not self.data.get("stype"):
data = self.data.copy()
data['stype'] = "owned"
self.data = data
...@@ -868,10 +868,10 @@ textarea[name="list-new-namelist"] { ...@@ -868,10 +868,10 @@ textarea[name="list-new-namelist"] {
padding: 5px 0px; padding: 5px 0px;
} }
#profile-key-list-table td:last-child, #profile-key-list-table th:last-child, #profile-key-list-table td:last-child, #profile-key-list-table th:last-child,
#profile-command-list-table td:last-child, #profile-command-list-table th:last-child, #profile-command-list-table td:last-child, #profile-command-list-table th:last-child,
#profile-command-list-table td:nth-child(2), #profile-command-list-table th:nth-child(2) { #profile-command-list-table td:nth-child(2), #profile-command-list-table th:nth-child(2) {
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
} }
...@@ -946,3 +946,13 @@ textarea[name="list-new-namelist"] { ...@@ -946,3 +946,13 @@ textarea[name="list-new-namelist"] {
#vm-list-search, #vm-mass-ops { #vm-list-search, #vm-mass-ops {
margin-top: 8px; margin-top: 8px;
} }
#vm-list-search-checkbox {
margin-top: -1px;
display: inline-block;
vertical-align: middle;
}
#vm-list-search-checkbox-span {
cursor: pointer
}
...@@ -617,3 +617,11 @@ function noJS() { ...@@ -617,3 +617,11 @@ function noJS() {
$('.no-js-hidden').show(); $('.no-js-hidden').show();
$('.js-hidden').hide(); $('.js-hidden').hide();
} }
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
...@@ -39,6 +39,7 @@ $(function() { ...@@ -39,6 +39,7 @@ $(function() {
$(".template-list-table thead th").css("cursor", "pointer"); $(".template-list-table thead th").css("cursor", "pointer");
$(".template-list-table th a").on("click", function(event) { $(".template-list-table th a").on("click", function(event) {
if(!$(this).closest("th").data("sort")) return true;
event.preventDefault(); event.preventDefault();
}); });
}); });
......
...@@ -163,9 +163,10 @@ $(function() { ...@@ -163,9 +163,10 @@ $(function() {
$(this).find('input[type="radio"]').prop("checked", true); $(this).find('input[type="radio"]').prop("checked", true);
}); });
if(checkStatusUpdate()) { if(checkStatusUpdate() || $("#vm-list-table tbody tr").length >= 100) {
updateStatuses(1); updateStatuses(1);
} }
}); });
...@@ -178,6 +179,7 @@ function checkStatusUpdate() { ...@@ -178,6 +179,7 @@ function checkStatusUpdate() {
function updateStatuses(runs) { function updateStatuses(runs) {
var include_deleted = getParameterByName("include_deleted");
$.get("/dashboard/vm/list/?compact", function(result) { $.get("/dashboard/vm/list/?compact", function(result) {
$("#vm-list-table tbody tr").each(function() { $("#vm-list-table tbody tr").each(function() {
vm = $(this).data("vm-pk"); vm = $(this).data("vm-pk");
...@@ -203,7 +205,8 @@ function updateStatuses(runs) { ...@@ -203,7 +205,8 @@ function updateStatuses(runs) {
$(this).find(".node").text(result[vm].node); $(this).find(".node").text(result[vm].node);
} }
} else { } else {
$(this).remove(); if(!include_deleted)
$(this).remove();
} }
}); });
......
...@@ -147,13 +147,11 @@ class TemplateListTable(Table): ...@@ -147,13 +147,11 @@ class TemplateListTable(Table):
template_name="dashboard/template-list/column-template-name.html", template_name="dashboard/template-list/column-template-name.html",
attrs={'th': {'data-sort': "string"}} attrs={'th': {'data-sort': "string"}}
) )
num_cores = Column( resources = TemplateColumn(
verbose_name=_("Cores"), template_name="dashboard/template-list/column-template-resources.html",
attrs={'th': {'data-sort': "int"}} verbose_name=_("Resources"),
)
ram_size = TemplateColumn(
"{{ record.ram_size }} MiB",
attrs={'th': {'data-sort': "int"}}, attrs={'th': {'data-sort': "int"}},
order_by=("ram_size"),
) )
lease = TemplateColumn( lease = TemplateColumn(
"{{ record.lease.name }}", "{{ record.lease.name }}",
...@@ -171,11 +169,14 @@ class TemplateListTable(Table): ...@@ -171,11 +169,14 @@ class TemplateListTable(Table):
verbose_name=_("Owner"), verbose_name=_("Owner"),
attrs={'th': {'data-sort': "string"}} attrs={'th': {'data-sort': "string"}}
) )
created = TemplateColumn(
template_name="dashboard/template-list/column-template-created.html",
verbose_name=_("Created at"),
)
running = TemplateColumn( running = TemplateColumn(
template_name="dashboard/template-list/column-template-running.html", template_name="dashboard/template-list/column-template-running.html",
verbose_name=_("Running"), verbose_name=_("Running"),
attrs={'th': {'data-sort': "int"}}, attrs={'th': {'data-sort': "int"}},
orderable=False,
) )
actions = TemplateColumn( actions = TemplateColumn(
verbose_name=_("Actions"), verbose_name=_("Actions"),
...@@ -188,8 +189,8 @@ class TemplateListTable(Table): ...@@ -188,8 +189,8 @@ class TemplateListTable(Table):
model = InstanceTemplate model = InstanceTemplate
attrs = {'class': ('table table-bordered table-striped table-hover' attrs = {'class': ('table table-bordered table-striped table-hover'
' template-list-table')} ' template-list-table')}
fields = ('name', 'num_cores', 'ram_size', 'system', fields = ('name', 'resources', 'system', 'access_method', 'lease',
'access_method', 'lease', 'owner', 'running', 'actions', ) 'owner', 'created', 'running', 'actions', )
prefix = "template-" prefix = "template-"
......
{% load i18n %} {% load i18n %}
{% if user and user.pk %} {% if user and user.pk %}
{% if user.get_full_name %} {% if user.get_full_name %}{{ user.get_full_name }}{% else %}{{ user.username }}{% endif %}{% if new_line %}<br />{% endif %}
{{ user.get_full_name }}
{% else %}
{{ user.username }}
{% endif %}
{% if show_org %} {% if show_org %}
{% if user.profile and user.profile.org_id %} {% if user.profile and user.profile.org_id %}
......
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<a href="{% url "dashboard.views.connect-command-create" %}" <a href="{% url "dashboard.views.connect-command-create" %}"
class="pull-right btn btn-success btn-xs" style="margin-right: 10px;"> class="pull-right btn btn-success btn-xs" style="margin-right: 10px;">
<i class="fa fa-plus"></i> {% trans "add command template" %} <i class="fa fa-plus"></i> {% trans "add command template" %}
</a> </a>
...@@ -82,4 +82,4 @@ ...@@ -82,4 +82,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
...@@ -16,6 +16,23 @@ ...@@ -16,6 +16,23 @@
<h3 class="no-margin"><i class="fa fa-puzzle-piece"></i> {% trans "Templates" %}</h3> <h3 class="no-margin"><i class="fa fa-puzzle-piece"></i> {% trans "Templates" %}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="row">
<div class="col-md-offset-8 col-md-4" id="template-list-search">
<form action="" method="GET">
<div class="input-group">
{{ search_form.s }}
<div class="input-group-btn">
{{ search_form.stype }}
<button type="submit" class="btn btn-primary input-tags">
<i class="fa fa-search"></i>
</button>
</div>
</div><!-- .input-group -->
</form>
</div><!-- .col-md-4 #template-list-search -->
</div>
</div>
<div class="panel-body">
{% render_table table %} {% render_table table %}
</div> </div>
</div> </div>
......
{% load i18n %} {% load i18n %}
<a href="{% url "dashboard.views.vm-create" %}?template={{ record.pk }}"
class="btn btn-success btn-xs customize-vm" title="{% trans "Start" %}">
<i class="fa fa-play"></i>
</a>
<a href="{% url "dashboard.views.template-detail" pk=record.pk%}" id="template-list-edit-button" class="btn btn-default btn-xs" title="{% trans "Edit" %}"> <a href="{% url "dashboard.views.template-detail" pk=record.pk%}" id="template-list-edit-button" class="btn btn-default btn-xs" title="{% trans "Edit" %}">
<i class="fa fa-edit"></i> <i class="fa fa-edit"></i>
</a> </a>
......
{{ record.created|date }}
<br />
{{ record.created|time }}
{% include "dashboard/_display-name.html" with user=record.owner show_org=True %} {% include "dashboard/_display-name.html" with user=record.owner show_org=True new_line=True %}
{% load i18n %}
{{ record.ram_size }}MiB RAM
<br />
{% blocktrans with num_cores=record.num_cores count count=record.num_cores %}
{{ num_cores }} CPU core
{% plural %}
{{ num_cores }} CPU cores
{% endblocktrans %}
<a href="{% url "dashboard.views.vm-list" %}?s=template:{{ record.pk }}%20status:running"> <a href="{% url "dashboard.views.vm-list" %}?s=template:{{ record.pk }}%20status:running">
{{ record.get_running_instances.count }} {{ record.running }}
</a> </a>
...@@ -33,6 +33,12 @@ ...@@ -33,6 +33,12 @@
{{ search_form.s }} {{ search_form.s }}
<div class="input-group-btn"> <div class="input-group-btn">
{{ search_form.stype }} {{ search_form.stype }}
</div>
<label class="input-group-addon input-tags" title="{% trans "Include deleted VMs" %}"
id="vm-list-search-checkbox-span" data-container="body">
{{ search_form.include_deleted }}
</label>
<div class="input-group-btn">
<button type="submit" class="btn btn-primary input-tags"> <button type="submit" class="btn btn-primary input-tags">
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
</button> </button>
...@@ -62,10 +68,20 @@ ...@@ -62,10 +68,20 @@
{% trans "Owner" as t %} {% trans "Owner" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="owner" %} {% include "dashboard/vm-list/header-link.html" with name=t sort="owner" %}
</th> </th>
{% if user.is_superuser %}<th data-sort="string" class="orderable sortable"> <th data-sort="string" class="orderable sortable">
{% trans "Node" as t %} {% trans "Lease" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="node" %} {% include "dashboard/vm-list/header-link.html" with name=t sort="lease" %}
</th>{% endif %} </th>
{% if user.is_superuser %}
<th data-sort="string" class="orderable sortable">
{% trans "IP address" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="ip_addr" %}
</th>
<th data-sort="string" class="orderable sortable">
{% trans "Node" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="node" %}
</th>
{% endif %}
</tr></thead><tbody> </tr></thead><tbody>
{% for i in object_list %} {% for i in object_list %}
<tr class="{% cycle 'odd' 'even' %}" data-vm-pk="{{ i.pk }}"> <tr class="{% cycle 'odd' 'even' %}" data-vm-pk="{{ i.pk }}">
...@@ -73,16 +89,27 @@ ...@@ -73,16 +89,27 @@
<td class="name"><a class="real-link" href="{% url "dashboard.views.detail" i.pk %}">{{ i.name }}</a> </td> <td class="name"><a class="real-link" href="{% url "dashboard.views.detail" i.pk %}">{{ i.name }}</a> </td>
<td class="state"> <td class="state">
<i class="fa fa-fw <i class="fa fa-fw
{% if i.is_in_status_change %} {% if show_acts_in_progress and i.is_in_status_change %}
fa-spin fa-spinner fa-spin fa-spinner
{% else %} {% else %}
{{ i.get_status_icon }}{% endif %}"></i> {{ i.get_status_icon }}{% endif %}"></i>
<span>{{ i.get_status_display }}</span> <span>{{ i.get_status_display }}</span>
</td> </td>
<td> <td>
{% include "dashboard/_display-name.html" with user=i.owner show_org=True %} {% if i.owner.profile %}
{{ i.owner.profile.get_display_name }}
{% else %}
{{ i.owner.username }}
{% endif %}
{# include "dashboard/_display-name.html" with user=i.owner show_org=True #}
</td>
<td class="lease "data-sort-value="{{ i.lease.name }}">
{{ i.lease.name }}
</td> </td>
{% if user.is_superuser %} {% if user.is_superuser %}
<td class="ip_addr "data-sort-value="{{ i.ipv4 }}">
{{ i.ipv4|default:"-" }}
</td>
<td class="node "data-sort-value="{{ i.node.normalized_name }}"> <td class="node "data-sort-value="{{ i.node.normalized_name }}">
{{ i.node.name|default:"-" }} {{ i.node.name|default:"-" }}
</td> </td>
...@@ -90,7 +117,7 @@ ...@@ -90,7 +117,7 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="5"> <td colspan="7">
{% if request.GET.s %} {% if request.GET.s %}
<strong>{% trans "No result." %}</strong> <strong>{% trans "No result." %}</strong>
{% else %} {% else %}
......
...@@ -70,7 +70,8 @@ from .forms import ( ...@@ -70,7 +70,8 @@ from .forms import (
VmSaveForm, UserKeyForm, VmRenewForm, VmStateChangeForm, VmSaveForm, UserKeyForm, VmRenewForm, VmStateChangeForm,
CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm, CirclePasswordChangeForm, VmCreateDiskForm, VmDownloadDiskForm,
TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm, TraitsForm, RawDataForm, GroupPermissionForm, AclUserAddForm,
VmResourcesForm, VmAddInterfaceForm, VmListSearchForm, ConnectCommandForm VmResourcesForm, VmAddInterfaceForm, VmListSearchForm,
TemplateListSearchForm, ConnectCommandForm
) )
from .tables import ( from .tables import (
...@@ -173,6 +174,65 @@ class FilterMixin(object): ...@@ -173,6 +174,65 @@ class FilterMixin(object):
return super(FilterMixin, return super(FilterMixin,
self).get_queryset().filter(**self.get_queryset_filters()) self).get_queryset().filter(**self.get_queryset_filters())
def create_fake_get(self):
self.request.GET = self._parse_get(self.request.GET)
def _parse_get(self, GET_dict):
"""
Returns a new dict from request's GET dict to filter the vm list
For example: "name:xy node:1" updates the GET dict
to resemble this URL ?name=xy&node=1
"name:xy node:1".split(":") becomes ["name", "xy node", "1"]
we pop the the first element and use it as the first dict key
then we iterate over the rest of the list and split by the last
whitespace, the first part of this list will be the previous key's
value, then last part of the list will be the next key.
The final dict looks like this: {'name': xy, 'node':1}
>>> f = FilterMixin()
>>> o = f._parse_get({'s': "hello"}).items()
>>> sorted(o) # doctest: +ELLIPSIS
[(u'name', u'hello'), (...)]
>>> o = f._parse_get({'s': "name:hello owner:test"}).items()
>>> sorted(o) # doctest: +ELLIPSIS
[(u'name', u'hello'), (u'owner', u'test'), (...)]
>>> o = f._parse_get({'s': "name:hello ws node:node 3 oh"}).items()
>>> sorted(o) # doctest: +ELLIPSIS
[(u'name', u'hello ws'), (u'node', u'node 3 oh'), (...)]
"""
s = GET_dict.get("s")
fake = GET_dict.copy()
if s:
s = s.split(":")
if len(s) < 2: # if there is no ':' in the string, filter by name
got = {'name': s[0]}
else:
latest = s.pop(0)
got = {'%s' % latest: None}
for i in s[:-1]:
new = i.rsplit(" ", 1)
got[latest] = new[0]
latest = new[1] if len(new) > 1 else None
got[latest] = s[-1]
# generate a new GET request, that is kinda fake
for k, v in got.iteritems():
fake[k] = v
return fake
def create_acl_queryset(self, model):
cleaned_data = self.search_form.cleaned_data
stype = cleaned_data.get('stype', "all")
superuser = stype == "all"
shared = stype == "shared"
level = "owner" if stype == "owned" else "user"
queryset = model.get_objects_with_level(
level, self.request.user,
group_also=shared, disregard_superuser=not superuser,
)
return queryset
class IndexView(LoginRequiredMixin, TemplateView): class IndexView(LoginRequiredMixin, TemplateView):
template_name = "dashboard/index.html" template_name = "dashboard/index.html"
...@@ -1641,23 +1701,60 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -1641,23 +1701,60 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
return kwargs return kwargs
class TemplateList(LoginRequiredMixin, SingleTableView): class TemplateList(LoginRequiredMixin, FilterMixin, SingleTableView):
template_name = "dashboard/template-list.html" template_name = "dashboard/template-list.html"
model = InstanceTemplate model = InstanceTemplate
table_class = TemplateListTable table_class = TemplateListTable
table_pagination = False table_pagination = False
allowed_filters = {
'name': "name__icontains",
'tags[]': "tags__name__in",
'tags': "tags__name__in", # for search string
'owner': "owner__username",
'ram': "ram_size",
'ram_size': "ram_size",
'cores': "num_cores",
'num_cores': "num_cores",
'access_method': "access_method__iexact",
}
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
context = super(TemplateList, self).get_context_data(*args, **kwargs) context = super(TemplateList, self).get_context_data(*args, **kwargs)
context['lease_table'] = LeaseListTable(Lease.objects.all(), context['lease_table'] = LeaseListTable(
request=self.request) Lease.get_objects_with_level("user", self.request.user),
request=self.request)
context['search_form'] = self.search_form
return context return context
def get(self, *args, **kwargs):
self.search_form = TemplateListSearchForm(self.request.GET)
self.search_form.full_clean()
return super(TemplateList, self).get(*args, **kwargs)
def create_acl_queryset(self, model):
queryset = super(TemplateList, self).create_acl_queryset(model)
sql = ("SELECT count(*) FROM vm_instance WHERE "
"vm_instance.template_id = vm_instancetemplate.id and "
"vm_instance.destroyed_at is null and "
"vm_instance.status = 'RUNNING'")
queryset = queryset.extra(select={'running': sql})
return queryset
def get_queryset(self): def get_queryset(self):
logger.debug('TemplateList.get_queryset() called. User: %s', logger.debug('TemplateList.get_queryset() called. User: %s',
unicode(self.request.user)) unicode(self.request.user))
return InstanceTemplate.get_objects_with_level( qs = self.create_acl_queryset(InstanceTemplate)
'user', self.request.user).all() self.create_fake_get()
try:
qs = qs.filter(**self.get_queryset_filters()).distinct()
except ValueError:
messages.error(self.request, _("Error during filtering."))
return qs.select_related("lease", "owner", "owner__profile")
class TemplateDelete(LoginRequiredMixin, DeleteView): class TemplateDelete(LoginRequiredMixin, DeleteView):
...@@ -1715,6 +1812,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView): ...@@ -1715,6 +1812,7 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
else: else:
context['ops'].append(v) context['ops'].append(v)
context['search_form'] = self.search_form context['search_form'] = self.search_form
context['show_acts_in_progress'] = self.object_list.count() < 100
return context return context
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
...@@ -1759,10 +1857,16 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView): ...@@ -1759,10 +1857,16 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
content_type="application/json", content_type="application/json",
) )
def create_acl_queryset(self, model):
queryset = super(VmList, self).create_acl_queryset(model)
if not self.search_form.cleaned_data.get("include_deleted"):
queryset = queryset.filter(destroyed_at=None)
return queryset
def get_queryset(self): def get_queryset(self):
logger.debug('VmList.get_queryset() called. User: %s', logger.debug('VmList.get_queryset() called. User: %s',
unicode(self.request.user)) unicode(self.request.user))
queryset = self.create_default_queryset() queryset = self.create_acl_queryset(Instance)
self.create_fake_get() self.create_fake_get()
sort = self.request.GET.get("sort") sort = self.request.GET.get("sort")
...@@ -1774,54 +1878,9 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView): ...@@ -1774,54 +1878,9 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
queryset = queryset.order_by(sort) queryset = queryset.order_by(sort)
return queryset.filter( return queryset.filter(
**self.get_queryset_filters()).select_related('owner', 'node' **self.get_queryset_filters()).prefetch_related(
).distinct() "owner", "node", "owner__profile", "interface_set", "lease",
"interface_set__host").distinct()
def create_default_queryset(self):
cleaned_data = self.search_form.cleaned_data
stype = cleaned_data.get('stype', "all")
superuser = stype == "all"
shared = stype == "shared"
level = "owner" if stype == "owned" else "user"
queryset = Instance.get_objects_with_level(
level, self.request.user,
group_also=shared, disregard_superuser=not superuser,
).filter(destroyed_at=None)
return queryset
def create_fake_get(self):
"""
Updates the request's GET dict to filter the vm list
For example: "name:xy node:1" updates the GET dict
to resemble this URL ?name=xy&node=1
"name:xy node:1".split(":") becomes ["name", "xy node", "1"]
we pop the the first element and use it as the first dict key
then we iterate over the rest of the list and split by the last
whitespace, the first part of this list will be the previous key's
value, then last part of the list will be the next key.
The final dict looks like this: {'name': xy, 'node':1}
"""
s = self.request.GET.get("s")
if s:
s = s.split(":")
if len(s) < 2: # if there is no ':' in the string, filter by name
got = {'name': s[0]}
else:
latest = s.pop(0)
got = {'%s' % latest: None}
for i in s[:-1]:
new = i.rsplit(" ", 1)
got[latest] = new[0]
latest = new[1] if len(new) > 1 else None
got[latest] = s[-1]
# generate a new GET request, that is kinda fake
fake = self.request.GET.copy()
for k, v in got.iteritems():
fake[k] = v
self.request.GET = fake
class NodeList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView): class NodeList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
......
...@@ -513,7 +513,11 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -513,7 +513,11 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
def ipv4(self): def ipv4(self):
"""Primary IPv4 address of the instance. """Primary IPv4 address of the instance.
""" """
return self.primary_host.ipv4 if self.primary_host else None # return self.primary_host.ipv4 if self.primary_host else None
for i in self.interface_set.all():
if i.host:
return i.host.ipv4
return None
@property @property
def ipv6(self): def ipv6(self):
......
...@@ -14,4 +14,3 @@ post-stop script ...@@ -14,4 +14,3 @@ post-stop script
stop mancelery stop mancelery
stop slowcelery stop slowcelery
end script end script
...@@ -12,4 +12,3 @@ script ...@@ -12,4 +12,3 @@ script
. /home/cloud/.virtualenvs/circle/bin/postactivate . /home/cloud/.virtualenvs/circle/bin/postactivate
exec ./manage.py celery --app=manager.mancelery worker --autoreload --loglevel=info --hostname=mancelery -B -c 10 exec ./manage.py celery --app=manager.mancelery worker --autoreload --loglevel=info --hostname=mancelery -B -c 10
end script end script
...@@ -12,4 +12,3 @@ script ...@@ -12,4 +12,3 @@ script
. /home/cloud/.virtualenvs/circle/bin/postactivate . /home/cloud/.virtualenvs/circle/bin/postactivate
exec ./manage.py celery --app=manager.moncelery worker --autoreload --loglevel=info --hostname=moncelery -B -c 3 exec ./manage.py celery --app=manager.moncelery worker --autoreload --loglevel=info --hostname=moncelery -B -c 3
end script end script
...@@ -12,4 +12,3 @@ script ...@@ -12,4 +12,3 @@ script
. /home/cloud/.virtualenvs/circle/bin/postactivate . /home/cloud/.virtualenvs/circle/bin/postactivate
exec /home/cloud/.virtualenvs/circle/bin/uwsgi --chdir=/home/cloud/circle/circle -H /home/cloud/.virtualenvs/circle --socket /tmp/uwsgi.sock --wsgi-file circle/wsgi.py --chmod-socket=666 exec /home/cloud/.virtualenvs/circle/bin/uwsgi --chdir=/home/cloud/circle/circle -H /home/cloud/.virtualenvs/circle --socket /tmp/uwsgi.sock --wsgi-file circle/wsgi.py --chmod-socket=666
end script end script
...@@ -14,4 +14,3 @@ script ...@@ -14,4 +14,3 @@ script
. /home/cloud/.virtualenvs/circle/bin/postactivate . /home/cloud/.virtualenvs/circle/bin/postactivate
exec ./manage.py runserver '[::]:8080' exec ./manage.py runserver '[::]:8080'
end script end script
...@@ -12,4 +12,3 @@ script ...@@ -12,4 +12,3 @@ script
. /home/cloud/.virtualenvs/circle/bin/postactivate . /home/cloud/.virtualenvs/circle/bin/postactivate
exec ./manage.py celery --app=manager.slowcelery worker --autoreload --loglevel=info --hostname=slowcelery -B -c 5 exec ./manage.py celery --app=manager.slowcelery worker --autoreload --loglevel=info --hostname=slowcelery -B -c 5
end script end script
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment