Commit 1d1ab1ea by Kálmán Viktor

dashboard: mass migration

parent 79ddd6fb
......@@ -114,7 +114,7 @@ $(function() {
$("body").on("click", "#op-form-send", function() {
var url = $(this).closest("form").prop("action");
$(this).find("i").prop("class", "fa fa-spinner fa-spin");
$(this).find("i").prop("class", "fa fa-fw fa-spinner fa-spin");
$.ajax({
url: url,
......@@ -177,7 +177,8 @@ $(function() {
function checkStatusUpdate() {
if($("#vm-list-table tbody td.state i").hasClass("fa-spin")) {
icons = $("#vm-list-table tbody td.state i");
if(icons.hasClass("fa-spin") || icons.hasClass("migrating-icon")) {
return true;
}
}
......@@ -194,12 +195,20 @@ function updateStatuses(runs) {
if(vm in result) {
if(result[vm].in_status_change) {
if(!status_icon.hasClass("fa-spin")) {
status_icon.prop("class", "fa fa-spinner fa-spin");
status_icon.prop("class", "fa fa-fw fa-spinner fa-spin");
}
}
else if(result[vm].status == "MIGRATING") {
if(!status_icon.hasClass("migrating-icon")) {
status_icon.prop("class", "fa fa-fw " + result[vm].icon + " migrating-icon");
}
} else {
status_icon.prop("class", "fa " + result[vm].icon);
status_icon.prop("class", "fa fa-fw " + result[vm].icon);
}
status_text.text(result[vm].status);
if("node" in result[vm]) {
$(this).find(".node").text(result[vm].node);
}
} else {
$(this).remove();
}
......
{% extends "dashboard/mass-operate.html" %}
{% load i18n %}
{% load sizefieldtags %}
{% block question %}
<p>
{% blocktrans with op=op.name %}
Choose a compute node to migrate the selected VMs to.
{% endblocktrans %}
</p>
<p class="text-info">{{op.name}}: {{op.description}}</p>
{% endblock %}
{% block formfields %}
<ul id="vm-migrate-node-list" class="list-unstyled">
{% for n in nodes %}
<li class="panel panel-default"><div class="panel-body">
<label for="migrate-to-{{n.pk}}">
<strong>{{ n }}</strong>
</label>
<input id="migrate-to-{{n.pk}}" type="radio" name="node" value="{{ n.pk }}" style="float: right;" checked="checked">
<span class="vm-migrate-node-property">{% trans "CPU load" %}: {{ n.cpu_usage }}</span>
<span class="vm-migrate-node-property">{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}</span>
<div style="clear: both;"></div>
</div></li>
{% endfor %}
</ul>
<hr />
{% endblock %}
......@@ -10,6 +10,7 @@ Do you want to perform the following operation: <strong>{{op}}</strong>?
<p class="text-info">{{op.description}}</p>
{% endblock %}
<form method="POST" action="{{url}}">{% csrf_token %}
{% block formfields %}{% endblock %}
{% for i in instances %}
<div class="panel panel-default mass-op-panel">
<i class="fa {{ i.get_status_icon }} fa-fw"></i>
......@@ -28,7 +29,7 @@ Do you want to perform the following operation: <strong>{{op}}</strong>?
<a class="btn btn-default" href="{% url "dashboard.views.vm-list" %}"
data-dismiss="modal">{% trans "Cancel" %}</a>
<button class="btn btn-{{ opview.effect }}" type="submit" id="op-form-send">
{% if opview.icon %}<i class="fa fa-{{opview.icon}}"></i> {% endif %}{{ opview.name|capfirst }}
{% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ opview.name|capfirst }}
</button>
</div>
</form>
......
......@@ -66,7 +66,7 @@
<td class="pk"><div id="vm-{{i.pk}}">{{i.pk}}</div> </td>
<td class="name"><a class="real-link" href="{% url "dashboard.views.detail" i.pk %}">{{ i.name }}</a> </td>
<td class="state">
<i class="fa
<i class="fa fa-fw
{% if i.is_in_status_change %}
fa-spin fa-spinner
{% else %}
......@@ -77,7 +77,9 @@
{% include "dashboard/_display-name.html" with user=i.owner show_org=True %}
</td>
{% if user.is_superuser %}
<td data-sort-value="{{ i.node.normalized_name }}">{{ i.node.name|default:"-" }}</td>
<td class="node "data-sort-value="{{ i.node.normalized_name }}">
{{ i.node.name|default:"-" }}
</td>
{% endif %}
</tr>
{% empty %}
......@@ -109,4 +111,53 @@
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/vm-list.js"></script>
<script src="{{ STATIC_URL}}dashboard/js/stupidtable.min.js"></script>
<style>
#vm-list-table .migrating-icon {
-webkit-animation: passing 2s linear infinite;
animation: passing 2s linear infinite;
}
@-webkit-keyframes passing {
0% {
-webkit-transform: translateX(50%);
transform: translateX(50%);
opacity: 0;
}
50% {
-webkit-transform: translateX(0%);
transform: translateX(0%);
opacity: 1;
}
100% {
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
opacity: 0;
}
}
@keyframes passing {
0% {
-webkit-transform: translateX(50%);
-ms-transform: translateX(50%);
transform: translateX(50%);
opacity: 0;
}
50% {
-webkit-transform: translateX(0%);
-ms-transform: translateX(0%);
transform: translateX(0%);
opacity: 1;
}
100% {
-webkit-transform: translateX(-50%);
-ms-transform: translateX(-50%);
transform: translateX(-50%);
opacity: 0;
}
}
</style>
{% endblock %}
......@@ -1034,14 +1034,16 @@ class MassOperationView(OperationView):
vms.append(i)
return vms
def post(self, request, *args, **kwargs):
def post(self, request, extra=None, *args, **kwargs):
if extra is None:
extra = {}
user = self.request.user
vms = request.POST.getlist("vm")
instances = Instance.objects.filter(pk__in=vms)
for i in instances:
try:
op = self._op_checks(i, user)
op.async(user=user)
op.async(user=user, **extra)
except HumanReadableException as e:
e.send_message(request)
except Exception as e:
......@@ -1067,6 +1069,28 @@ class MassOperationView(OperationView):
return op
class MassMigrationView(MassOperationView):
template_name = 'dashboard/_vm-mass-migrate.html'
icon = "info"
op = "migrate"
icon = "truck"
def get_context_data(self, **kwargs):
ctx = super(MassMigrationView, self).get_context_data(**kwargs)
ctx['nodes'] = [n for n in Node.objects.filter(enabled=True)
if n.state == "ONLINE"]
return ctx
def post(self, request, extra=None, *args, **kwargs):
if extra is None:
extra = {}
node = self.request.POST.get("node")
if node:
node = get_object_or_404(Node, pk=node)
extra["to_node"] = node
return super(MassMigrationView, self).post(request, extra, *args,
**kwargs)
vm_mass_ops = OrderedDict([
('deploy', MassOperationView.factory(
op='deploy', icon='play', effect='success')),
......@@ -1074,6 +1098,7 @@ vm_mass_ops = OrderedDict([
op='wake_up', icon='sun-o', effect='success')),
('sleep', MassOperationView.factory(
op='sleep', icon='moon-o', effect='info')),
('migrate', MassMigrationView),
('destroy', MassOperationView.factory(
op='destroy', icon='times', effect='danger')),
])
......@@ -1671,6 +1696,8 @@ class VmList(LoginRequiredMixin, FilterMixin, ListView):
'icon': i.get_status_icon(),
'in_status_change': i.is_in_status_change(),
}
if self.request.user.is_superuser:
statuses[i.pk]['node'] = i.node.name if i.node else "-"
return HttpResponse(json.dumps(statuses),
content_type="application/json")
else:
......
......@@ -938,7 +938,8 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
'ERROR': 'fa-warning',
'PENDING': 'fa-rocket',
'DESTROYED': 'fa-trash-o',
'MIGRATING': 'fa-truck'}.get(self.status, 'fa-question')
'MIGRATING': 'fa-truck migrating-icon'
}.get(self.status, 'fa-question')
def get_activities(self, user=None):
acts = (self.activity_log.filter(parent=None).
......
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