Commit 7fed7d2c by Bach Dániel

Merge branch 'feature-remove-garbage'

parents 32e83265 f876f926
...@@ -198,6 +198,7 @@ PIPELINE_JS = { ...@@ -198,6 +198,7 @@ PIPELINE_JS = {
"jquery-knob/dist/jquery.knob.min.js", "jquery-knob/dist/jquery.knob.min.js",
"jquery-simple-slider/js/simple-slider.js", "jquery-simple-slider/js/simple-slider.js",
"dashboard/dashboard.js", "dashboard/dashboard.js",
"dashboard/activity.js",
"dashboard/group-details.js", "dashboard/group-details.js",
"dashboard/group-list.js", "dashboard/group-list.js",
"dashboard/js/stupidtable.min.js", # no bower file "dashboard/js/stupidtable.min.js", # no bower file
......
...@@ -282,6 +282,8 @@ def register_operation(op_cls, op_id=None, target_cls=None): ...@@ -282,6 +282,8 @@ def register_operation(op_cls, op_id=None, target_cls=None):
"in the 'target_cls' parameter to this " "in the 'target_cls' parameter to this "
"call.") "call.")
assert not hasattr(target_cls, op_id), (
"target class already has an attribute with this id")
if not issubclass(target_cls, OperatedMixin): if not issubclass(target_cls, OperatedMixin):
raise TypeError("%r is not a subclass of %r" % raise TypeError("%r is not a subclass of %r" %
(target_cls.__name__, OperatedMixin.__name__)) (target_cls.__name__, OperatedMixin.__name__))
......
...@@ -898,7 +898,7 @@ class VmDownloadDiskForm(OperationForm): ...@@ -898,7 +898,7 @@ class VmDownloadDiskForm(OperationForm):
def clean(self): def clean(self):
cleaned_data = super(VmDownloadDiskForm, self).clean() cleaned_data = super(VmDownloadDiskForm, self).clean()
if not cleaned_data['name']: if not cleaned_data['name']:
if cleaned_data['url']: if cleaned_data.get('url'):
cleaned_data['name'] = urlparse( cleaned_data['name'] = urlparse(
cleaned_data['url']).path.split('/')[-1] cleaned_data['url']).path.split('/')[-1]
if not cleaned_data['name']: if not cleaned_data['name']:
...@@ -908,6 +908,36 @@ class VmDownloadDiskForm(OperationForm): ...@@ -908,6 +908,36 @@ class VmDownloadDiskForm(OperationForm):
return cleaned_data return cleaned_data
class VmRemoveInterfaceForm(OperationForm):
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices')
self.interface = kwargs.pop('default')
super(VmRemoveInterfaceForm, self).__init__(*args, **kwargs)
self.fields.insert(0, 'interface', forms.ModelChoiceField(
queryset=choices, initial=self.interface, required=True,
empty_label=None, label=_('Interface')))
if self.interface:
self.fields['interface'].widget = HiddenInput()
@property
def helper(self):
helper = super(VmRemoveInterfaceForm, self).helper
if self.interface:
helper.layout = Layout(
AnyTag(
"div",
HTML(format_html(
_("<label>Vlan:</label> {0}"),
self.interface.vlan)),
css_class="form-group",
),
Field("interface"),
)
return helper
class VmAddInterfaceForm(OperationForm): class VmAddInterfaceForm(OperationForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices') choices = kwargs.pop('choices')
......
/* for functions in both vm list and vm detail */
$(function() { $(function() {
var in_progress = false;
var activity_hash = 5;
var show_all = false;
var reload_vm_detail = false;
/* do we need to check for new activities */
if(decideActivityRefresh()) {
if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
}
/* vm operations */ $('a[href="#activity"]').click(function(){
$('a[href="#activity"] i').addClass('fa-spin');
if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
});
$("#activity-refresh").on("click", "#show-all-activities", function() {
$(this).find("i").addClass("fa-spinner fa-spin");
show_all = !show_all;
$('a[href="#activity"]').trigger("click");
return false;
});
/* operations */
$('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface, .operation-wrapper').on('click', '.operation', function(e) { $('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface, .operation-wrapper').on('click', '.operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin'); var icon = $(this).children("i").addClass('fa-spinner fa-spin');
...@@ -23,7 +48,7 @@ $(function() { ...@@ -23,7 +48,7 @@ $(function() {
}); });
/* if the operation fails show the modal again */ /* if the operation fails show the modal again */
$("body").on("click", "#op-form-send", function() { $("body").on("click", "#confirmation-modal #op-form-send", function() {
var url = $(this).closest("form").prop("action"); var url = $(this).closest("form").prop("action");
$.ajax({ $.ajax({
...@@ -77,4 +102,91 @@ $(function() { ...@@ -77,4 +102,91 @@ $(function() {
return false; return false;
}); });
function decideActivityRefresh() {
var check = false;
/* if something is still spinning */
if($('.timeline .activity i').hasClass('fa-spin'))
check = true;
return check;
}
function checkNewActivity(runs) {
$.ajax({
type: 'GET',
url: $('a[href="#activity"]').attr('data-activity-url'),
data: {'show_all': show_all},
success: function(data) {
var new_activity_hash = (data.activities + "").hashCode();
if(new_activity_hash != activity_hash) {
$("#activity-refresh").html(data.activities);
}
activity_hash = new_activity_hash;
$("#ops").html(data.ops);
$("#disk-ops").html(data.disk_ops);
$("[title]").tooltip();
/* changing the status text */
var icon = $("#vm-details-state i");
if(data.is_new_state) {
if(!icon.hasClass("fa-spin"))
icon.prop("class", "fa fa-spinner fa-spin");
} else {
icon.prop("class", "fa " + data.icon);
}
var vm_state = $("#vm-details-state");
if (vm_state.length) {
vm_state.data("status", data['status']);
$("#vm-details-state span").html(data['human_readable_status'].toUpperCase());
}
if(data['status'] == "RUNNING") {
if(data['connect_uri']) {
$("#dashboard-vm-details-connect-button").removeClass('disabled');
}
$("[data-target=#_console]").attr("data-toggle", "pill").attr("href", "#console").parent("li").removeClass("disabled");
} else {
if(data['connect_uri']) {
$("#dashboard-vm-details-connect-button").addClass('disabled');
}
$("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled");
}
if(data.status == "STOPPED" || data.status == "PENDING") {
$(".change-resources-button").prop("disabled", false);
$(".change-resources-help").hide();
} else {
$(".change-resources-button").prop("disabled", true);
$(".change-resources-help").show();
}
if(runs > 0 && decideActivityRefresh()) {
setTimeout(
function() {checkNewActivity(runs + 1);},
1000 + Math.exp(runs * 0.05)
);
} else {
in_progress = false;
if(reload_vm_detail) location.reload();
}
$('a[href="#activity"] i').removeClass('fa-spin');
},
error: function() {
in_progress = false;
}
});
}
}); });
String.prototype.hashCode = function() {
var hash = 0, i, chr, len;
if (this.length == 0) return hash;
for (i = 0, len = this.length; i < len; i++) {
chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
...@@ -5,63 +5,39 @@ $(function () { ...@@ -5,63 +5,39 @@ $(function () {
var template = $(this).data("template"); var template = $(this).data("template");
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: '/dashboard/vm/create/' + (typeof template === "undefined" ? '' : '?template=' + template), url: $(this).attr('href'),
success: function(data) { success: function(data) {
$('body').append(data); $('body').append(data);
vmCreateLoaded(); vmCreateLoaded();
addSliderMiscs(); addSliderMiscs();
$('#create-modal').modal('show'); var modal = $('#confirmation-modal');
$('#create-modal').on('hidden.bs.modal', function() { modal.modal('show');
$('#create-modal').remove(); modal.on('hidden.bs.modal', function() {
modal.remove();
}); });
} }
}); });
return false; return false;
}); });
$('.node-create').click(function(e) { $('.group-create, .node-create, .tx-tpl-ownership, .group-delete, .node-delete, .disk-remove, .template-delete, .delete-from-group').click(function(e) {
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: '/dashboard/node/create/', url: $(this).prop('href'),
success: function(data) { success: function(data) {
$('body').append(data); $('body').append(data);
nodeCreateLoaded(); var modal = $('#confirmation-modal');
addSliderMiscs(); modal.modal('show');
$('#create-modal').modal('show'); modal.on('hidden.bs.modal', function() {
$('#create-modal').on('hidden.bs.modal', function() { modal.remove();
$('#create-modal').remove();
});
}
});
return false;
});
$('.group-create').click(function(e) {
$.ajax({
type: 'GET',
url: '/dashboard/group/create/',
success: function(data) {
$('body').append(data);
addSliderMiscs();
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
}
});
return false;
});
$('.tx-tpl-ownership').click(function(e) {
$.ajax({
type: 'GET',
url: $('.tx-tpl-ownership').attr('href'),
success: function(data) {
$('body').append(data);
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
}); });
},
error: function(xhr, textStatus, error) {
if(xhr.status === 403) {
addMessage(gettext("Only the owners can delete the selected object."), "warning");
} else {
addMessage(gettext("An error occurred. (") + xhr.status + ")", 'danger')
}
} }
}); });
return false; return false;
...@@ -70,12 +46,13 @@ $(function () { ...@@ -70,12 +46,13 @@ $(function () {
$('.template-choose').click(function(e) { $('.template-choose').click(function(e) {
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: '/dashboard/template/choose/', url: $(this).prop('href'),
success: function(data) { success: function(data) {
$('body').append(data); $('body').append(data);
$('#create-modal').modal('show'); var modal = $('#confirmation-modal');
$('#create-modal').on('hidden.bs.modal', function() { modal.modal('show');
$('#create-modal').remove(); modal.on('hidden.bs.modal', function() {
modal.remove();
}); });
// check if user selected anything // check if user selected anything
$("#template-choose-next-button").click(function() { $("#template-choose-next-button").click(function() {
...@@ -101,6 +78,7 @@ $(function () { ...@@ -101,6 +78,7 @@ $(function () {
e.stopImmediatePropagation(); e.stopImmediatePropagation();
return false; return false;
}); });
$('[href=#index-list-view]').click(function (e) { $('[href=#index-list-view]').click(function (e) {
var box = $(this).data('index-box'); var box = $(this).data('index-box');
$('#' + box + '-graph-view').hide(); $('#' + box + '-graph-view').hide();
...@@ -110,9 +88,10 @@ $(function () { ...@@ -110,9 +88,10 @@ $(function () {
e.stopImmediatePropagation(); e.stopImmediatePropagation();
return false; return false;
}); });
$('body [title]:not(.title-favourite)').tooltip();
$('body .title-favourite').tooltip({'placement': 'right'}); $('body .title-favourite').tooltip({'placement': 'right'});
$('body :input[title]').tooltip({trigger: 'focus', placement: 'auto right'}); $('body :input[title]').tooltip({trigger: 'focus', placement: 'auto right'});
$('body [title]').tooltip();
$(".knob").knob(); $(".knob").knob();
$('[data-toggle="pill"]').click(function() { $('[data-toggle="pill"]').click(function() {
...@@ -167,74 +146,6 @@ $(function () { ...@@ -167,74 +146,6 @@ $(function () {
addSliderMiscs(); addSliderMiscs();
/* for VM removes buttons */
$('.vm-delete').click(function() {
var vm_pk = $(this).data('vm-pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(deleteObject,
{ 'url': '/dashboard/vm/delete/' + vm_pk + '/',
'data': [],
'pk': vm_pk,
'type': "vm",
'redirect': dir});
return false;
});
/* for disk remove buttons */
$('.disk-remove').click(function() {
var disk_pk = $(this).data('disk-pk');
addModalConfirmation(deleteObject,
{ 'url': '/dashboard/disk/' + disk_pk + '/remove/',
'data': [],
'pk': disk_pk,
'type': "disk",
});
return false;
});
/* for Node removes buttons */
$('.node-delete').click(function() {
var node_pk = $(this).data('node-pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(deleteObject,
{ 'url': '/dashboard/node/delete/' + node_pk + '/',
'data': [],
'pk': node_pk,
'type': "node",
'redirect': dir});
return false;
});
/* for Node flush buttons */
$('.node-flush').click(function() {
var node_pk = $(this).data('node-pk');
var postto = $(this).attr('href');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(function(){},
{ 'url': postto,
'data': [],
'pk': node_pk,
'type': "node",
'redirect': dir});
return false;
});
/* for Group removes buttons */
$('.group-delete').click(function() {
var group_pk = $(this).data('group-pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(deleteObject,
{ 'url': '/dashboard/group/delete/' + group_pk + '/',
'data': [],
'type': "group",
'pk': group_pk,
'redirect': dir});
return false;
});
/* search for vms */ /* search for vms */
var my_vms = []; var my_vms = [];
$("#dashboard-vm-search-input").keyup(function(e) { $("#dashboard-vm-search-input").keyup(function(e) {
...@@ -558,62 +469,6 @@ function setDefaultSliderValues() { ...@@ -558,62 +469,6 @@ function setDefaultSliderValues() {
} }
/* deletes the VM with the pk
* if dir is true, then redirect to the dashboard landing page
* else it adds a success message */
function deleteObject(data) {
$.ajax({
type: 'POST',
data: {'redirect': data.redirect},
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
if(!data.redirect) {
selected = [];
addMessage(re.message, 'success');
if(data.type === "disk") {
// no need to remove them from DOM
$('a[data-disk-pk="' + data.pk + '"]').parent("li").fadeOut();
$('a[data-disk-pk="' + data.pk + '"]').parent("h4").fadeOut();
}
else {
$('a[data-'+data.type+'-pk="' + data.pk + '"]').closest('tr').fadeOut(function() {
$(this).remove();
});
}
} else {
window.location.replace('/dashboard');
}
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger');
}
});
}
function massDeleteVm(data) {
f = function() {
selected = [];
// reset group buttons
$('.vm-list-group-control a').attr('disabled', true);
$(this).remove();
};
$.ajax({
traditional: true,
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
type: 'POST',
data: {'vms': data.data.v},
success: function(re, textStatus, xhr) {
for(var i=0; i< data.data.v.length; i++)
$('.vm-list-table tbody tr[data-vm-pk="' + data.data.v[i] + '"]').fadeOut(500, f);
addMessage(re.message, 'success');
},
error: function(xhr, textStatus, error) {
// TODO this
}
});
}
function addMessage(text, type) { function addMessage(text, type) {
...@@ -701,6 +556,7 @@ function csrfSafeMethod(method) { ...@@ -701,6 +556,7 @@ function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection // these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
} }
$.ajaxSetup({ $.ajaxSetup({
beforeSend: function(xhr, settings) { beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
......
...@@ -28,39 +28,3 @@ ...@@ -28,39 +28,3 @@
$(".group-details-help-button").click(function() { $(".group-details-help-button").click(function() {
$(".group-details-help").stop().slideToggle(); $(".group-details-help").stop().slideToggle();
}); });
/* for Node removes buttons */
$('.delete-from-group').click(function() {
var href = $(this).attr('href');
var tr = $(this).closest('tr');
var group = $(this).data('group_pk');
var member = $(this).data('member_pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(removeMember,
{ 'url': href,
'data': [],
'tr': tr,
'group_pk': group,
'member_pk': member,
'type': "user",
'redirect': dir});
return false;
});
function removeMember(data) {
$.ajax({
type: 'POST',
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
data.tr.fadeOut(function() {
$(this).remove();});
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger');
}
});
}
$(function() { $(function() {
/* rename */ /* rename */
$("#group-list-rename-button, .group-details-rename-button").click(function() { $("#group-list-rename-button, .group-details-rename-button").click(function() {
$("#group-list-column-name", $(this).closest("tr")).hide(); $(".group-list-column-name", $(this).closest("tr")).hide();
$("#group-list-rename", $(this).closest("tr")).css('display', 'inline'); $("#group-list-rename", $(this).closest("tr")).css('display', 'inline');
$("#group-list-rename").find("input").select(); $("#group-list-rename").find("input").select();
}); });
...@@ -10,7 +10,7 @@ $(function() { ...@@ -10,7 +10,7 @@ $(function() {
$('.group-list-rename-submit').click(function() { $('.group-list-rename-submit').click(function() {
var row = $(this).closest("tr"); var row = $(this).closest("tr");
var name = $('#group-list-rename-name', row).val(); var name = $('#group-list-rename-name', row).val();
var url = '/dashboard/group/' + row.children("td:first-child").text().replace(" ", "") + '/'; var url = row.find(".group-list-column-name a").prop("href");
$.ajax({ $.ajax({
method: 'POST', method: 'POST',
url: url, url: url,
...@@ -18,7 +18,7 @@ $(function() { ...@@ -18,7 +18,7 @@ $(function() {
headers: {"X-CSRFToken": getCookie('csrftoken')}, headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
$("#group-list-column-name", row).html( $(".group-list-column-name", row).html(
$("<a/>", { $("<a/>", {
'class': "real-link", 'class': "real-link",
href: "/dashboard/group/" + data.group_pk + "/", href: "/dashboard/group/" + data.group_pk + "/",
......
$(function() {
nodeCreateLoaded();
});
function nodeCreateLoaded() {
/* no js compatibility */
$('.no-js-hidden').show();
$('.js-hidden').hide();
}
...@@ -30,20 +30,6 @@ $(function() { ...@@ -30,20 +30,6 @@ $(function() {
$(".node-details-help").stop().slideToggle(); $(".node-details-help").stop().slideToggle();
}); });
/* for Node removes buttons */
$('.node-enable').click(function() {
var node_pk = $(this).data('node-pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(changeNodeStatus,
{ 'url': '/dashboard/node/status/' + node_pk + '/',
'data': [],
'pk': node_pk,
'type': "node",
'redirect': dir});
return false;
});
// remove trait // remove trait
$('.node-details-remove-trait').click(function() { $('.node-details-remove-trait').click(function() {
var to_remove = $(this).data("trait-pk"); var to_remove = $(this).data("trait-pk");
...@@ -69,22 +55,3 @@ $(function() { ...@@ -69,22 +55,3 @@ $(function() {
}); });
}); });
function changeNodeStatus(data) {
$.ajax({
type: 'POST',
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
if(!data.redirect) {
selected = [];
addMessage(re.message, 'success');
} else {
window.location.replace('/dashboard');
}
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger');
}
});
}
...@@ -9,49 +9,4 @@ $(function() { ...@@ -9,49 +9,4 @@ $(function() {
$('.false').closest("tr").addClass('danger'); $('.false').closest("tr").addClass('danger');
$('.true').closest("tr").removeClass('danger'); $('.true').closest("tr").removeClass('danger');
} }
function statuschangeSuccess(tr){
var tspan=tr.children('.enabled').children();
var buttons=tr.children('.actions').children('.btn-group').children('.dropdown-menu').children('li').children('.node-enable');
buttons.each(function(index){
if ($(this).css("display")=="block"){
$(this).css("display","none");
}
else{
$(this).css("display","block");
}
});
if(tspan.hasClass("false")){
tspan.removeClass("false");
tspan.addClass("true");
tspan.text("✔");
}
else{
tspan.removeClass("true");
tspan.addClass("false");
tspan.text("✘");
}
colortable();
}
$('#table_container').on('click','.node-enable',function() {
var tr= $(this).closest("tr");
var pk =$(this).attr('data-node-pk');
var url = $(this).attr('href');
$.ajax({
method: 'POST',
url: url,
data: {'change_status':''},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
statuschangeSuccess(tr);
},
error: function(xhr, textStatus, error) {
addMessage("Error!", "danger");
}
});
return false;
});
}); });
$(function() { $(function() {
/* for template removes buttons */
$('.template-delete').click(function() {
var template_pk = $(this).data('template-pk');
addModalConfirmationOrDisplayMessage(deleteTemplate,
{ 'url': '/dashboard/template/delete/' + template_pk + '/',
'data': [],
'template_pk': template_pk,
});
return false;
});
/* for lease removes buttons */
$('.lease-delete').click(function() {
var lease_pk = $(this).data('lease-pk');
addModalConfirmationOrDisplayMessage(deleteLease,
{ 'url': '/dashboard/lease/delete/' + lease_pk + '/',
'data': [],
'lease_pk': lease_pk,
});
return false;
});
/* template table sort */ /* template table sort */
var ttable = $(".template-list-table").stupidtable(); var ttable = $(".template-list-table").stupidtable();
...@@ -43,67 +21,3 @@ $(function() { ...@@ -43,67 +21,3 @@ $(function() {
event.preventDefault(); event.preventDefault();
}); });
}); });
// send POST request then delete the row in table
function deleteTemplate(data) {
$.ajax({
type: 'POST',
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
addMessage(re.message, 'success');
$('a[data-template-pk="' + data.template_pk + '"]').closest('tr').fadeOut(function() {
$(this).remove();
});
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger');
}
});
}
// send POST request then delete the row in table
function deleteLease(data) {
$.ajax({
type: 'POST',
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
addMessage(re.message, 'success');
$('a[data-lease-pk="' + data.lease_pk + '"]').closest('tr').fadeOut(function() {
$(this).remove();
});
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger');
}
});
}
function addModalConfirmationOrDisplayMessage(func, data) {
$.ajax({
type: 'GET',
url: data['url'],
data: jQuery.param(data['data']),
success: function(result) {
$('body').append(result);
$('#confirmation-modal').modal('show');
$('#confirmation-modal').on('hidden.bs.modal', function() {
$('#confirmation-modal').remove();
});
$('#confirmation-modal-button').click(function() {
func(data);
$('#confirmation-modal').modal('hide');
});
},
error: function(xhr, textStatus, error) {
if(xhr.status === 403) {
addMessage(gettext("Only the owners can delete the selected object."), "warning");
} else {
addMessage(gettext("An error occurred. (") + xhr.status + ")", 'danger')
}
}
});
}
...@@ -20,15 +20,15 @@ function vmCreateLoaded() { ...@@ -20,15 +20,15 @@ function vmCreateLoaded() {
var template = $(this).data("template-pk"); var template = $(this).data("template-pk");
$.get("/dashboard/vm/create/?template=" + template, function(data) { $.get("/dashboard/vm/create/?template=" + template, function(data) {
var r = $('#create-modal'); r.next('div').remove(); r.remove(); var r = $('#confirmation-modal'); r.next('div').remove(); r.remove();
$('body').append(data); $('body').append(data);
vmCreateLoaded(); vmCreateLoaded();
addSliderMiscs(); addSliderMiscs();
$('#create-modal').modal('show'); $('#confirmation-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() { $('#confirmation-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove(); $('#confirmation-modal').remove();
}); });
$("#create-modal").on("shown.bs.modal", function() { $("#confirmation-modal").on("shown.bs.modal", function() {
setDefaultSliderValues(); setDefaultSliderValues();
}); });
}); });
...@@ -48,18 +48,18 @@ function vmCreateLoaded() { ...@@ -48,18 +48,18 @@ function vmCreateLoaded() {
window.location.replace(data.redirect + '#activity'); window.location.replace(data.redirect + '#activity');
} }
else { else {
var r = $('#create-modal'); r.next('div').remove(); r.remove(); var r = $('#confirmation-modal'); r.next('div').remove(); r.remove();
$('body').append(data); $('body').append(data);
vmCreateLoaded(); vmCreateLoaded();
addSliderMiscs(); addSliderMiscs();
$('#create-modal').modal('show'); $('#confirmation-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() { $('#confirmation-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove(); $('#confirmation-modal').remove();
}); });
} }
}, },
error: function(xhr, textStatus, error) { error: function(xhr, textStatus, error) {
var r = $('#create-modal'); r.next('div').remove(); r.remove(); var r = $('#confirmation-modal'); r.next('div').remove(); r.remove();
if (xhr.status == 500) { if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger"); addMessage("500 Internal Server Error", "danger");
...@@ -211,7 +211,7 @@ function vmCustomizeLoaded() { ...@@ -211,7 +211,7 @@ function vmCustomizeLoaded() {
}); });
/* start vm button clicks */ /* start vm button clicks */
$('#vm-create-customized-start').click(function() { $('#confirmation-modal #vm-create-customized-start').click(function() {
var error = false; var error = false;
$(".cpu-count-input, .ram-input, #id_name, #id_amount ").each(function() { $(".cpu-count-input, .ram-input, #id_name, #id_amount ").each(function() {
if(!$(this)[0].checkValidity()) { if(!$(this)[0].checkValidity()) {
...@@ -222,8 +222,6 @@ function vmCustomizeLoaded() { ...@@ -222,8 +222,6 @@ function vmCustomizeLoaded() {
$(this).find("i").prop("class", "fa fa-spinner fa-spin"); $(this).find("i").prop("class", "fa fa-spinner fa-spin");
if($("#create-modal")) return true;
$.ajax({ $.ajax({
url: '/dashboard/vm/create/', url: '/dashboard/vm/create/',
headers: {"X-CSRFToken": getCookie('csrftoken')}, headers: {"X-CSRFToken": getCookie('csrftoken')},
...@@ -238,18 +236,18 @@ function vmCustomizeLoaded() { ...@@ -238,18 +236,18 @@ function vmCustomizeLoaded() {
window.location.href = data.redirect + '#activity'; window.location.href = data.redirect + '#activity';
} }
else { else {
var r = $('#create-modal'); r.next('div').remove(); r.remove(); var r = $('#confirmation-modal'); r.next('div').remove(); r.remove();
$('body').append(data); $('body').append(data);
vmCreateLoaded(); vmCreateLoaded();
addSliderMiscs(); addSliderMiscs();
$('#create-modal').modal('show'); $('#confirmation-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() { $('#confirmation-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove(); $('#confirmation-modal').remove();
}); });
} }
}, },
error: function(xhr, textStatus, error) { error: function(xhr, textStatus, error) {
var r = $('#create-modal'); r.next('div').remove(); r.remove(); var r = $('#confirmation-modal'); r.next('div').remove(); r.remove();
if (xhr.status == 500) { if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger"); addMessage("500 Internal Server Error", "danger");
......
var show_all = false;
var in_progress = false;
var activity_hash = 5;
var Websock_native; // not sure var Websock_native; // not sure
var reload_vm_detail = false;
$(function() { $(function() {
/* do we need to check for new activities */
if(decideActivityRefresh()) {
if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
}
$('a[href="#activity"]').click(function(){
$('a[href="#activity"] i').addClass('fa-spin');
if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
});
$("#activity-refresh").on("click", "#show-all-activities", function() {
$(this).find("i").addClass("fa-spinner fa-spin");
show_all = !show_all;
$('a[href="#activity"]').trigger("click");
return false;
});
/* save resources */ /* save resources */
$('#vm-details-resources-save').click(function(e) { $('#vm-details-resources-save').click(function(e) {
var error = false; var error = false;
...@@ -43,7 +16,7 @@ $(function() { ...@@ -43,7 +16,7 @@ $(function() {
var vm = $(this).data("vm"); var vm = $(this).data("vm");
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: "/dashboard/vm/" + vm + "/op/resources_change/", url: $(this).parent("form").prop('action'),
data: $('#vm-details-resources-form').serialize(), data: $('#vm-details-resources-form').serialize(),
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
if(data.success) { if(data.success) {
...@@ -89,17 +62,6 @@ $(function() { ...@@ -89,17 +62,6 @@ $(function() {
return false; return false;
}); });
/* remove port */
$('.vm-details-remove-port').click(function() {
addModalConfirmation(removePort,
{
'url': $(this).prop("href"),
'data': [],
'rule': $(this).data("rule")
});
return false;
});
/* for js fallback */ /* for js fallback */
$("#vm-details-pw-show").parent("div").children("input").prop("type", "password"); $("#vm-details-pw-show").parent("div").children("input").prop("type", "password");
...@@ -123,80 +85,6 @@ $(function() { ...@@ -123,80 +85,6 @@ $(function() {
span.tooltip(); span.tooltip();
}); });
/* change password confirmation */
$("#vm-details-pw-change").click(function() {
$("#vm-details-pw-confirm").fadeIn();
return false;
});
/* change password */
$(".vm-details-pw-confirm-choice").click(function() {
choice = $(this).data("choice");
if(choice) {
pk = $(this).data("vm");
$.ajax({
type: 'POST',
url: "/dashboard/vm/" + pk + "/",
data: {'change_password': 'true'},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
location.reload();
},
error: function(xhr, textStatus, error) {
if (xhr.status == 500) {
addMessage("Internal Server Error", "danger");
} else {
addMessage(xhr.status + " Unknown Error", "danger");
}
}
});
} else {
$("#vm-details-pw-confirm").fadeOut();
}
return false;
});
/* add network button */
$("#vm-details-network-add").click(function() {
$("#vm-details-network-add-form").toggle();
return false;
});
/* add disk button */
$("#vm-details-disk-add").click(function() {
$("#vm-details-disk-add-for-form").html($("#vm-details-disk-add-form").html());
return false;
});
/* for interface remove buttons */
$('.interface-remove').click(function() {
var interface_pk = $(this).data('interface-pk');
addModalConfirmation(removeInterface,
{ 'url': '/dashboard/interface/' + interface_pk + '/delete/',
'data': [],
'pk': interface_pk,
'type': "interface",
});
return false;
});
/* removing interface post */
function removeInterface(data) {
$.ajax({
type: 'POST',
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
/* remove the html element */
$('a[data-interface-pk="' + data.pk + '"]').closest("div").fadeOut();
location.reload();
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger');
}
});
}
/* rename */ /* rename */
$("#vm-details-h1-name, .vm-details-rename-button").click(function() { $("#vm-details-h1-name, .vm-details-rename-button").click(function() {
$("#vm-details-h1-name").hide(); $("#vm-details-h1-name").hide();
...@@ -336,109 +224,3 @@ $(function() { ...@@ -336,109 +224,3 @@ $(function() {
}); });
}); });
function removePort(data) {
$.ajax({
type: 'POST',
url: data.url,
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
$("a[data-rule=" + data.rule + "]").each(function() {
$(this).closest("tr").fadeOut(500, function() {
$(this).remove();
});
});
addMessage(re.message, "success");
},
error: function(xhr, textStatus, error) {
}
});
}
function decideActivityRefresh() {
var check = false;
/* if something is still spinning */
if($('.timeline .activity i').hasClass('fa-spin'))
check = true;
return check;
}
function checkNewActivity(runs) {
var instance = location.href.split('/'); instance = instance[instance.length - 2];
$.ajax({
type: 'GET',
url: '/dashboard/vm/' + instance + '/activity/',
data: {'show_all': show_all},
success: function(data) {
var new_activity_hash = (data.activities + "").hashCode();
if(new_activity_hash != activity_hash) {
$("#activity-refresh").html(data.activities);
}
activity_hash = new_activity_hash;
$("#ops").html(data.ops);
$("#disk-ops").html(data.disk_ops);
$("[title]").tooltip();
/* changing the status text */
var icon = $("#vm-details-state i");
if(data.is_new_state) {
if(!icon.hasClass("fa-spin"))
icon.prop("class", "fa fa-spinner fa-spin");
} else {
icon.prop("class", "fa " + data.icon);
}
$("#vm-details-state").data("status", data['status']);
$("#vm-details-state span").html(data['human_readable_status'].toUpperCase());
if(data['status'] == "RUNNING") {
if(data['connect_uri']) {
$("#dashboard-vm-details-connect-button").removeClass('disabled');
}
$("[data-target=#_console]").attr("data-toggle", "pill").attr("href", "#console").parent("li").removeClass("disabled");
} else {
if(data['connect_uri']) {
$("#dashboard-vm-details-connect-button").addClass('disabled');
}
$("[data-target=#_console]").attr("data-toggle", "_pill").attr("href", "#").parent("li").addClass("disabled");
}
if(data.status == "STOPPED" || data.status == "PENDING") {
$(".change-resources-button").prop("disabled", false);
$(".change-resources-help").hide();
} else {
$(".change-resources-button").prop("disabled", true);
$(".change-resources-help").show();
}
if(runs > 0 && decideActivityRefresh()) {
setTimeout(
function() {checkNewActivity(runs + 1);},
1000 + Math.exp(runs * 0.05)
);
} else {
in_progress = false;
if(reload_vm_detail) location.reload();
}
$('a[href="#activity"] i').removeClass('fa-spin');
},
error: function() {
in_progress = false;
}
});
}
String.prototype.hashCode = function() {
var hash = 0, i, chr, len;
if (this.length === 0) return hash;
for (i = 0, len = this.length; i < len; i++) {
chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
...@@ -3,19 +3,25 @@ ...@@ -3,19 +3,25 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-body"> <div class="modal-body">
{% if text %} {% if member %}
{{ text|safe }} {% blocktrans with group=object member=member %}
Do you really want to remove <strong>{{ member }}</strong> from {{ group }}?
{% endblocktrans %}
{% else %} {% else %}
{%blocktrans with object=object%} {% blocktrans with object=object %}
Are you sure you want to delete <strong>{{ object }}</strong>? Are you sure you want to delete <strong>{{ object }}</strong>?
{%endblocktrans%} {% endblocktrans %}
{% endif %} {% endif %}
<br /> <br />
<div class="pull-right" style="margin-top: 15px;"> <div class="pull-right" style="margin-top: 15px;">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button> <form action="{{ request.path }}" method="POST">
<button id="confirmation-modal-button" type="button" class="btn btn-danger" {% csrf_token %}
{% if disable_submit %}disabled{% endif %} <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
>{% trans "Delete" %}</button> <input type="hidden" name="next" value="{{ request.GET.next }}"/>
<button class="btn btn-danger"
{% if disable_submit %}disabled{% endif %}
>{% trans "Delete" %}</button>
</form>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
......
{% load i18n %}
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
{% if text %}
{{ text }}
{% else %}
{%blocktrans with object=object%}
Are you sure you want to change <strong>{{ object }}</strong> status?
{%endblocktrans%}
{% endif %}
<div class="pull-right">
<form action="{% url "dashboard.views.status-node" pk=object.pk %}" method="POST">
{% csrf_token %}
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
<input type="hidden" name="change_status" value=""/>
<button class="btn btn-warning">{% blocktrans with status=status %}Yes, {{status}}{% endblocktrans %}</button>
</form>
</div>
<div class="clearfix"></div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% load i18n %}
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
{% if text %}
{{ text }}
{% else %}
{%blocktrans with object=object%}
Are you sure you want to remove <strong>{{ member }}</strong> from <strong>{{ object }}</strong>?
{%endblocktrans%}
{% endif %}
<br />
<div class="pull-right" style="margin-top: 15px;">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button id="confirmation-modal-button" type="button" class="btn btn-warning">Remove</button>
</div>
<div class="clearfix"></div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
...@@ -17,12 +17,18 @@ ...@@ -17,12 +17,18 @@
{% if text %} {% if text %}
{{ text|safe }} {{ text|safe }}
{% else %} {% else %}
{%blocktrans with object=object%} {% if member %}
Are you sure you want to delete <strong>{{ object }}</strong>? {% blocktrans with group=object member=member %}
{%endblocktrans%} Do you really want to remove <strong>{{ member }}</strong> from {{ group }}?
{% endblocktrans %}
{% else %}
{% blocktrans with object=object %}
Are you sure you want to delete <strong>{{ object }}</strong>?
{% endblocktrans %}
{% endif %}
{% endif %} {% endif %}
<div class="pull-right"> <div class="pull-right">
<form action="" method="POST"> <form action="{{ request.path }}" method="POST">
{% csrf_token %} {% csrf_token %}
<a class="btn btn-default">{% trans "Cancel" %}</a> <a class="btn btn-default">{% trans "Cancel" %}</a>
<input type="hidden" name="next" value="{{ request.GET.next }}"/> <input type="hidden" name="next" value="{{ request.GET.next }}"/>
......
{% extends "base.html" %}
{% load i18n %}
{% block title-site %}Dashboard | CIRCLE{% endblock %}
{% block content %}
{% blocktrans with group=object member=member %}
Do you really want to remove {{member}} from {{group}}?
{% endblocktrans %}
<form action="" method="POST">{% csrf_token %}
<input type="submit" value="{% trans "Remove" %}" />
</form>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div class="body-content">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
{%blocktrans with instance=instance.name%}
Renewing <em>{{instance}}</em>
{%endblocktrans%}
</h3>
</div>
<div class="panel-body">
{%blocktrans with object=instance.name%}
Do you want to renew <strong>{{ object }}</strong>?
{%endblocktrans%}
{%blocktrans with suspend=time_of_suspend delete=time_of_delete|default:"n/a" %}
The instance will be suspended at <em>{{suspend}}</em>
and removed at <em>{{delete}}</em> if you renew it now.
{%endblocktrans%}
<div class="pull-right">
<form action="" method="POST">
{% csrf_token %}
<a class="btn btn-default"
href="{{instance.get_absolute_path}}">{% trans "Back" %}</a>
<button class="btn btn-danger">{% trans "Renew" %}</button>
</form>
</div>
</div>
</div>
{% endblock %}
{% load i18n %}
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
{% trans "Are you sure you want to delete the following objects?" %}<br />
{% for o in objects %}
<strong>{{ o }}</strong>{% if not forloop.last %},{% endif %}
{% endfor %}
<div class="pull-right" style="margin-top: 40px;">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
<button id="confirmation-modal-button" type="button" class="btn btn-danger">{% trans "Delete" %}</button>
</div>
<div class="clearfix"></div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div class="body-content">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
{% if title %}
{{ title }}
{% else %}
Flush confirmation
{% endif %}
</h3>
</div>
<div class="panel-body">
{% if text %}
{{ text }}
{% else %}
{%blocktrans with object=object%}
Are you sure you want to flush <strong>{{ object }}</strong>?
{%endblocktrans%}
{% endif %}
<div class="pull-right">
<form action="" method="POST">
{% csrf_token %}
<a class="btn btn-default">{% trans "Back" %}</a>
<input type="hidden" name="flush" value=""/>
<button class="btn btn-warning">{% trans "Yes" %}</button>
</form>
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div class="body-content">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
{% if title %}
{{ title }}
{% else %}
{% trans "Status changing confirmation" %}
{% endif %}
</h3>
</div>
<div class="panel-body">
{% if text %}
{{ text }}
{% else %}
{%blocktrans with object=object%}
Are you sure you want to change <strong>{{ object }}</strong> status?
{%endblocktrans%}
{% endif %}
<div class="pull-right">
<form action="" method="POST">
{% csrf_token %}
<a class="btn btn-default">{% trans "Cancel" %}</a>
<button type="button" class="btn btn-default" data-dismiss="modal"></button>
<input type="hidden" name="change_status" value=""/>
<button class="btn btn-warning">{% blocktrans with status=status %}Yes, {{status}}{% endblocktrans %}</button>
</form>
</div>
</div>
</div>
{% endblock %}
...@@ -7,6 +7,6 @@ ...@@ -7,6 +7,6 @@
<button type="submit" class="group-list-rename-submit btn btn-sm">{% trans "Rename" %}</button> <button type="submit" class="group-list-rename-submit btn btn-sm">{% trans "Rename" %}</button>
</form> </form>
</div> </div>
<div id="group-list-column-name"> <div class="group-list-column-name">
<a class="real-link" href="{% url "dashboard.views.group-detail" pk=record.pk %}">{{ record.name }}</a> <a class="real-link" href="{% url "dashboard.views.group-detail" pk=record.pk %}">{{ record.name }}</a>
</div> </div>
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<i class="fa fa-{{ t.os_type }}"></i> {{ t.name }} <i class="fa fa-{{ t.os_type }}"></i> {{ t.name }}
</span> </span>
<small class="text-muted index-template-list-system">{{ t.system }}</small> <small class="text-muted index-template-list-system">{{ t.system }}</small>
<div class="pull-right vm-create" data-template="{{ t.pk }}"> <div href="{% url "dashboard.views.vm-create" %}?template={{ t.pk }}" class="pull-right vm-create">
<i data-container="body" title="{% trans "Start VM instance" %}" <i data-container="body" title="{% trans "Start VM instance" %}"
class="fa fa-play"></i> class="fa fa-play"></i>
</div> </div>
......
<div class="modal fade" id="create-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
{% if box_title and ajax_title %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">{{ box_title }}</h4>
</div>
{% endif %}
<div class="modal-body">
{% include template %}
</div>
<!--<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>-->
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
...@@ -80,7 +80,8 @@ ...@@ -80,7 +80,8 @@
</a> </a>
</li> </li>
<li> <li>
<a href="#activity" data-toggle="pill" class="text-center"> <a href="#activity" data-toggle="pill" class="text-center"
data-activity-url="{% url "dashboard.views.node-activity-list" node.pk %}">
<i class="fa fa-clock-o fa-2x"></i><br> <i class="fa fa-clock-o fa-2x"></i><br>
{% trans "Activity" %} {% trans "Activity" %}
</a> </a>
......
{% load i18n %} {% load i18n %}
{% load hro %} {% load hro %}
<div id="activity-timeline" class="timeline"> <div id="activity-timeline" class="timeline">
{% for a in activities %} {% for a in activities %}
<div class="activity" data-activity-id="{{ a.pk }}"> <div class="activity" data-activity-id="{{ a.pk }}">
<span class="timeline-icon{% if a.has_failed %} timeline-icon-failed{% endif %}"> <span class="timeline-icon{% if a.has_failed %} timeline-icon-failed{% endif %}">
<i class="fa {% if not a.finished %}fa-refresh fa-spin {% else %}fa-plus{% endif %}"></i> <i class="fa {% if not a.finished %}fa-refresh fa-spin {% else %}fa-plus{% endif %}"></i>
</span> </span>
<strong title="{{ a.result.get_admin_text }}"> <strong title="{{ a.result.get_admin_text }}">
{{ a.readable_name.get_admin_text|capfirst }} {{ a.readable_name.get_admin_text|capfirst }}
</strong> </strong>
{{ a.started|date:"Y-m-d H:i" }}, {{ a.user }} {{ a.started|date:"Y-m-d H:i" }}{% if a.user %}, {{ a.user }}{% endif %}
{% if a.children.count > 0 %} {% if a.children.count > 0 %}
<div class="sub-timeline"> <div class="sub-timeline">
{% for s in a.children.all %} {% for s in a.children.all %}
<div data-activity-id="{{ s.pk }}" <div data-activity-id="{{ s.pk }}"
class="sub-activity{% if s.has_failed %} sub-activity-failed{% endif %}" class="sub-activity{% if s.has_failed %} sub-activity-failed{% endif %}">
> <span title="{{ s.result.get_admin_text }}">
{{ s.readable_name|get_text:user }} {{ s.readable_name|get_text:user }}
&ndash; </span>
{% if s.finished %} &ndash;
{{ s.finished|time:"H:i:s" }} {% if s.finished %}
{% else %} {{ s.finished|time:"H:i:s" }}
<i class="fa fa-refresh fa-spin" class="sub-activity-loading-icon"></i> {% else %}
{% endif %} <i class="fa fa-refresh fa-spin" class="sub-activity-loading-icon"></i>
{% if s.has_failed %} {% endif %}
<div title="{{ s.result.get_admin_text }}" class="label label-danger">{% trans "failed" %}</div> {% if s.has_failed %}
{% endif %} <div class="label label-danger">{% trans "failed" %}</div>
</div> {% endif %}
{% endfor %} </div>
</div> {% endfor %}
{% endif %} </div>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
...@@ -2,6 +2,6 @@ ...@@ -2,6 +2,6 @@
<h3>{% trans "Activity" %}</h3> <h3>{% trans "Activity" %}</h3>
<div id="activity-timeline-wrapper"> <div id="activity-refresh">
{% include "dashboard/node-detail/_activity-timeline.html" %} {% include "dashboard/node-detail/_activity-timeline.html" %}
</div> </div>
...@@ -207,7 +207,8 @@ ...@@ -207,7 +207,8 @@
{% trans "Network" %}</a> {% trans "Network" %}</a>
</li> </li>
<li> <li>
<a href="#activity" data-toggle="pill" data-target="#_activity" class="text-center"> <a href="#activity" data-toggle="pill" data-target="#_activity" class="text-center"
data-activity-url="{% url "dashboard.views.vm-activity-list" instance.pk %}">
<i class="fa fa-clock-o fa-2x"></i><br> <i class="fa fa-clock-o fa-2x"></i><br>
{% trans "Activity" %}</a> {% trans "Activity" %}</a>
</li> </li>
......
...@@ -21,13 +21,14 @@ ...@@ -21,13 +21,14 @@
<a href="{{ i.host.get_absolute_url }}" <a href="{{ i.host.get_absolute_url }}"
class="btn btn-default btn-xs">{% trans "edit" %}</a> class="btn btn-default btn-xs">{% trans "edit" %}</a>
{% endif %} {% endif %}
{% if is_owner %} {% with op=op.remove_interface %}{% if op %}
<a href="{% url "dashboard.views.interface-delete" pk=i.pk %}?next={{ request.path }}" <span class="operation-wrapper">
class="btn btn-danger btn-xs interface-remove" <a href="{{op.get_url}}?interface={{ i.pk }}"
data-interface-pk="{{ i.pk }}"> class="btn btn-{{op.effect}} btn-xs operation interface-remove"
{% trans "remove" %} {% if op.disabled %}disabled{% endif %}>{% trans "remove" %}
</a> </a>
{% endif %} </span>
{% endif %}{% endwith %}
</h3> </h3>
{% if i.host %} {% if i.host %}
<div class="row"> <div class="row">
...@@ -78,7 +79,13 @@ ...@@ -78,7 +79,13 @@
{{ l.private }}/{{ l.proto }} {{ l.private }}/{{ l.proto }}
</td> </td>
<td> <td>
<a href="{{ op.remove_port.get_url }}?rule={{ l.ipv4.pk }}" class="btn btn-link btn-xs vm-details-remove-port" data-rule="{{ l.ipv4.pk }}" title="{% trans "Remove" %}"><i class="fa fa-times"><span class="sr-only">{% trans "Remove" %}</span></i></a> <span class="operation-wrapper">
<a href="{{ op.remove_port.get_url }}?rule={{ l.ipv4.pk }}"
class="btn btn-link btn-xs operation"
title="{% trans "Remove" %}">
<i class="fa fa-times"><span class="sr-only">{% trans "Remove" %}</span></i>
</a>
</span>
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
......
...@@ -27,7 +27,7 @@ from django.contrib.auth import authenticate ...@@ -27,7 +27,7 @@ from django.contrib.auth import authenticate
from dashboard.views import VmAddInterfaceView from dashboard.views import VmAddInterfaceView
from vm.models import Instance, InstanceTemplate, Lease, Node, Trait from vm.models import Instance, InstanceTemplate, Lease, Node, Trait
from vm.operations import (WakeUpOperation, AddInterfaceOperation, from vm.operations import (WakeUpOperation, AddInterfaceOperation,
AddPortOperation) AddPortOperation, RemoveInterfaceOperation)
from ..models import Profile from ..models import Profile
from firewall.models import Vlan, Host, VlanGroup from firewall.models import Vlan, Host, VlanGroup
from mock import Mock, patch from mock import Mock, patch
...@@ -169,26 +169,12 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -169,26 +169,12 @@ class VmDetailTest(LoginMixin, TestCase):
inst.save() inst.save()
iface_count = inst.interface_set.count() iface_count = inst.interface_set.count()
c.post("/dashboard/interface/1/delete/") with patch.object(RemoveInterfaceOperation, 'async') as mock_method:
self.assertEqual(inst.interface_set.count(), iface_count - 1) mock_method.side_effect = inst.remove_interface
response = c.post("/dashboard/vm/1/op/remove_interface/",
def test_permitted_network_delete_w_ajax(self): {'interface': 1})
c = Client() self.assertEqual(response.status_code, 302)
self.login(c, "user1") assert mock_method.called
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
vlan = Vlan.objects.get(pk=1)
inst.add_interface(vlan=vlan, user=self.us)
inst.status = 'RUNNING'
inst.save()
iface_count = inst.interface_set.count()
response = c.post("/dashboard/interface/1/delete/",
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
removed_network = json.loads(response.content)['removed_network']
self.assertEqual(removed_network['vlan'], vlan.name)
self.assertEqual(removed_network['vlan_pk'], vlan.pk)
self.assertEqual(removed_network['managed'], vlan.managed)
self.assertEqual(inst.interface_set.count(), iface_count - 1) self.assertEqual(inst.interface_set.count(), iface_count - 1)
def test_unpermitted_network_delete(self): def test_unpermitted_network_delete(self):
...@@ -199,7 +185,10 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -199,7 +185,10 @@ class VmDetailTest(LoginMixin, TestCase):
inst.add_interface(vlan=Vlan.objects.get(pk=1), user=self.us) inst.add_interface(vlan=Vlan.objects.get(pk=1), user=self.us)
iface_count = inst.interface_set.count() iface_count = inst.interface_set.count()
response = c.post("/dashboard/interface/1/delete/") with patch.object(RemoveInterfaceOperation, 'async') as mock_method:
mock_method.side_effect = inst.remove_interface
response = c.post("/dashboard/vm/1/op/remove_interface/",
{'interface': 1})
self.assertEqual(iface_count, inst.interface_set.count()) self.assertEqual(iface_count, inst.interface_set.count())
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
...@@ -766,42 +755,6 @@ class NodeDetailTest(LoginMixin, TestCase): ...@@ -766,42 +755,6 @@ class NodeDetailTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(len(Node.objects.get(pk=1).traits.all()), trait_count) self.assertEqual(len(Node.objects.get(pk=1).traits.all()), trait_count)
def test_anon_change_node_status(self):
c = Client()
node = Node.objects.get(pk=1)
node_enabled = node.enabled
response = c.post("/dashboard/node/1/", {'change_status': ''})
self.assertEqual(response.status_code, 302)
self.assertEqual(node_enabled, Node.objects.get(pk=1).enabled)
def test_unpermitted_change_node_status(self):
c = Client()
self.login(c, "user2")
node = Node.objects.get(pk=1)
node_enabled = node.enabled
response = c.post("/dashboard/node/status/1/", {'change_status': ''})
self.assertEqual(response.status_code, 302)
self.assertEqual(node_enabled, Node.objects.get(pk=1).enabled)
def test_permitted_change_node_status(self):
c = Client()
self.login(c, "superuser")
node = Node.objects.get(pk=1)
node_enabled = node.enabled
response = c.post("/dashboard/node/status/1/", {'change_status': ''})
self.assertEqual(response.status_code, 302)
self.assertEqual(node_enabled, not Node.objects.get(pk=1).enabled)
def test_permitted_change_node_status_w_ajax(self):
c = Client()
self.login(c, "superuser")
node = Node.objects.get(pk=1)
node_enabled = node.enabled
response = c.post("/dashboard/node/status/1/", {'change_status': ''},
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
self.assertEqual(node_enabled, not Node.objects.get(pk=1).enabled)
class GroupCreateTest(LoginMixin, TestCase): class GroupCreateTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
...@@ -949,21 +902,26 @@ class GroupDeleteTest(LoginMixin, TestCase): ...@@ -949,21 +902,26 @@ class GroupDeleteTest(LoginMixin, TestCase):
def test_permitted_group_page(self): def test_permitted_group_page(self):
c = Client() c = Client()
self.login(c, 'user0') self.login(c, 'user0')
response = c.get('/dashboard/group/delete/' + str(self.g1.pk) + '/') with patch('dashboard.views.util.messages') as msg:
response = c.get('/dashboard/group/delete/%d/' % self.g1.pk)
assert not msg.error.called and not msg.warning.called
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_unpermitted_group_page(self): def test_unpermitted_group_page(self):
c = Client() c = Client()
self.login(c, 'user1') self.login(c, 'user1')
response = c.get('/dashboard/group/delete/' + str(self.g1.pk) + '/') with patch('dashboard.views.util.messages') as msg:
self.assertEqual(response.status_code, 403) response = c.get('/dashboard/group/delete/%d/' % self.g1.pk)
assert msg.error.called or msg.warning.called
self.assertEqual(response.status_code, 302)
def test_anon_group_delete(self): def test_anon_group_delete(self):
c = Client() c = Client()
groupnum = Group.objects.count() response = c.get('/dashboard/group/delete/%d/' % self.g1.pk)
response = c.post('/dashboard/group/delete/' + str(self.g1.pk) + '/') self.assertRedirects(
response, '/accounts/login/?next=/dashboard/group/delete/5/',
status_code=302)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(Group.objects.count(), groupnum)
def test_unpermitted_group_delete(self): def test_unpermitted_group_delete(self):
c = Client() c = Client()
...@@ -1484,38 +1442,6 @@ class TransferOwnershipViewTest(LoginMixin, TestCase): ...@@ -1484,38 +1442,6 @@ class TransferOwnershipViewTest(LoginMixin, TestCase):
response = c.post('/dashboard/vm/1/tx/', {'name': 'user2'}) response = c.post('/dashboard/vm/1/tx/', {'name': 'user2'})
self.assertEqual(self.u2.notification_set.count(), c2 + 1) self.assertEqual(self.u2.notification_set.count(), c2 + 1)
def test_transfer(self):
self.skipTest("How did this ever pass?")
c = Client()
self.login(c, 'user1')
response = c.post('/dashboard/vm/1/tx/', {'name': 'user2'})
url = response.context['token']
c = Client()
self.login(c, 'user2')
response = c.post(url)
self.assertEquals(Instance.objects.get(pk=1).owner.pk, self.u2.pk)
def test_transfer_token_used_by_others(self):
self.skipTest("How did this ever pass?")
c = Client()
self.login(c, 'user1')
response = c.post('/dashboard/vm/1/tx/', {'name': 'user2'})
url = response.context['token']
response = c.post(url) # token is for user2
assert response.status_code == 403
self.assertEquals(Instance.objects.get(pk=1).owner.pk, self.u1.pk)
def test_transfer_by_superuser(self):
self.skipTest("How did this ever pass?")
c = Client()
self.login(c, 'superuser')
response = c.post('/dashboard/vm/1/tx/', {'name': 'user2'})
url = response.context['token']
c = Client()
self.login(c, 'user2')
response = c.post(url)
self.assertEquals(Instance.objects.get(pk=1).owner.pk, self.u2.pk)
class IndexViewTest(LoginMixin, TestCase): class IndexViewTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
......
...@@ -25,12 +25,12 @@ from .views import ( ...@@ -25,12 +25,12 @@ from .views import (
GroupDetailView, GroupList, IndexView, GroupDetailView, GroupList, IndexView,
InstanceActivityDetail, LeaseCreate, LeaseDelete, LeaseDetail, InstanceActivityDetail, LeaseCreate, LeaseDelete, LeaseDetail,
MyPreferencesView, NodeAddTraitView, NodeCreate, NodeDelete, MyPreferencesView, NodeAddTraitView, NodeCreate, NodeDelete,
NodeDetailView, NodeList, NodeStatus, NodeDetailView, NodeList,
NotificationView, TemplateAclUpdateView, TemplateCreate, NotificationView, TemplateAclUpdateView, TemplateCreate,
TemplateDelete, TemplateDetail, TemplateList, TemplateDelete, TemplateDetail, TemplateList,
vm_activity, VmCreate, VmDetailView, vm_activity, VmCreate, VmDetailView,
VmDetailVncTokenView, VmList, VmDetailVncTokenView, VmList,
DiskRemoveView, get_disk_download_status, InterfaceDeleteView, DiskRemoveView, get_disk_download_status,
GroupRemoveUserView, GroupRemoveUserView,
GroupRemoveFutureUserView, GroupRemoveFutureUserView,
GroupCreate, GroupProfileUpdate, GroupCreate, GroupProfileUpdate,
...@@ -51,6 +51,7 @@ from .views import ( ...@@ -51,6 +51,7 @@ from .views import (
TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView, TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView,
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView, OpenSearchDescriptionView,
NodeActivityView,
) )
from .views.vm import vm_ops, vm_mass_ops from .views.vm import vm_ops, vm_mass_ops
from .views.node import node_ops from .views.node import node_ops
...@@ -94,7 +95,8 @@ urlpatterns = patterns( ...@@ -94,7 +95,8 @@ urlpatterns = patterns(
url(r'^vm/list/$', VmList.as_view(), name='dashboard.views.vm-list'), url(r'^vm/list/$', VmList.as_view(), name='dashboard.views.vm-list'),
url(r'^vm/create/$', VmCreate.as_view(), url(r'^vm/create/$', VmCreate.as_view(),
name='dashboard.views.vm-create'), name='dashboard.views.vm-create'),
url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity), url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity,
name='dashboard.views.vm-activity-list'),
url(r'^vm/activity/(?P<pk>\d+)/$', InstanceActivityDetail.as_view(), url(r'^vm/activity/(?P<pk>\d+)/$', InstanceActivityDetail.as_view(),
name='dashboard.views.vm-activity'), name='dashboard.views.vm-activity'),
url(r'^vm/(?P<pk>\d+)/screenshot/$', get_vm_screenshot, url(r'^vm/(?P<pk>\d+)/screenshot/$', get_vm_screenshot,
...@@ -119,8 +121,8 @@ urlpatterns = patterns( ...@@ -119,8 +121,8 @@ urlpatterns = patterns(
name='dashboard.views.template-transfer-ownership-confirm'), name='dashboard.views.template-transfer-ownership-confirm'),
url(r'^node/delete/(?P<pk>\d+)/$', NodeDelete.as_view(), url(r'^node/delete/(?P<pk>\d+)/$', NodeDelete.as_view(),
name="dashboard.views.delete-node"), name="dashboard.views.delete-node"),
url(r'^node/status/(?P<pk>\d+)/$', NodeStatus.as_view(), url(r'^node/(?P<pk>\d+)/activity/$', NodeActivityView.as_view(),
name="dashboard.views.status-node"), name='dashboard.views.node-activity-list'),
url(r'^node/create/$', NodeCreate.as_view(), url(r'^node/create/$', NodeCreate.as_view(),
name='dashboard.views.node-create'), name='dashboard.views.node-create'),
...@@ -156,9 +158,6 @@ urlpatterns = patterns( ...@@ -156,9 +158,6 @@ urlpatterns = patterns(
url(r'^disk/(?P<pk>\d+)/status/$', get_disk_download_status, url(r'^disk/(?P<pk>\d+)/status/$', get_disk_download_status,
name="dashboard.views.disk-status"), name="dashboard.views.disk-status"),
url(r'^interface/(?P<pk>\d+)/delete/$', InterfaceDeleteView.as_view(),
name="dashboard.views.interface-delete"),
url(r'^profile/$', MyPreferencesView.as_view(), url(r'^profile/$', MyPreferencesView.as_view(),
name="dashboard.views.profile-preferences"), name="dashboard.views.profile-preferences"),
url(r'^subscribe/(?P<token>.*)/$', UnsubscribeFormView.as_view(), url(r'^subscribe/(?P<token>.*)/$', UnsubscribeFormView.as_view(),
......
...@@ -29,7 +29,7 @@ from django.core.urlresolvers import reverse, reverse_lazy ...@@ -29,7 +29,7 @@ from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import UpdateView, DeleteView, TemplateView from django.views.generic import UpdateView, TemplateView
from braces.views import SuperuserRequiredMixin, LoginRequiredMixin from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
...@@ -41,7 +41,8 @@ from ..forms import ( ...@@ -41,7 +41,8 @@ from ..forms import (
from ..models import FutureMember, GroupProfile from ..models import FutureMember, GroupProfile
from vm.models import Instance, InstanceTemplate from vm.models import Instance, InstanceTemplate
from ..tables import GroupListTable from ..tables import GroupListTable
from .util import CheckedDetailView, AclUpdateView, search_user, saml_available from .util import (CheckedDetailView, AclUpdateView, search_user,
saml_available, DeleteViewBase)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -224,15 +225,18 @@ class GroupList(LoginRequiredMixin, SingleTableView): ...@@ -224,15 +225,18 @@ class GroupList(LoginRequiredMixin, SingleTableView):
return groups return groups
class GroupRemoveUserView(CheckedDetailView, DeleteView): class GroupRemoveUserView(DeleteViewBase):
model = Group model = Group
slug_field = 'pk' slug_field = 'pk'
slug_url_kwarg = 'group_pk' slug_url_kwarg = 'group_pk'
read_level = 'operator' level = 'operator'
member_key = 'member_pk' member_key = 'member_pk'
success_message = _("Member successfully removed from group.")
def get_has_level(self): def check_auth(self):
return self.object.profile.has_level if not self.get_object().profile.has_level(
self.request.user, self.level):
raise PermissionDenied()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(GroupRemoveUserView, self).get_context_data(**kwargs) context = super(GroupRemoveUserView, self).get_context_data(**kwargs)
...@@ -243,50 +247,24 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView): ...@@ -243,50 +247,24 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView):
return context return context
def get_success_url(self): def get_success_url(self):
next = self.request.POST.get('next') return reverse_lazy("dashboard.views.group-detail",
if next: kwargs={'pk': self.get_object().pk})
return next
else:
return reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.get_object().pk})
def get(self, request, member_pk, *args, **kwargs): def get(self, request, member_pk, *args, **kwargs):
self.member_pk = member_pk self.member_pk = member_pk
return super(GroupRemoveUserView, self).get(request, *args, **kwargs) return super(GroupRemoveUserView, self).get(request, *args, **kwargs)
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-remove.html']
else:
return ['dashboard/confirm/base-remove.html']
def remove_member(self, pk): def remove_member(self, pk):
container = self.get_object() container = self.get_object()
container.user_set.remove(User.objects.get(pk=pk)) container.user_set.remove(User.objects.get(pk=pk))
def get_success_message(self): def delete_obj(self, request, *args, **kwargs):
return _("Member successfully removed from group.")
def delete(self, request, *args, **kwargs):
object = self.get_object()
if not object.profile.has_level(request.user, 'operator'):
raise PermissionDenied()
self.remove_member(kwargs[self.member_key]) self.remove_member(kwargs[self.member_key])
success_url = self.get_success_url()
success_message = self.get_success_message()
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return redirect(success_url)
class GroupRemoveFutureUserView(GroupRemoveUserView): class GroupRemoveFutureUserView(GroupRemoveUserView):
member_key = 'member_org_id' member_key = 'member_org_id'
success_message = _("Future user successfully removed from group.")
def get(self, request, member_org_id, *args, **kwargs): def get(self, request, member_org_id, *args, **kwargs):
self.member_org_id = member_org_id self.member_org_id = member_org_id
...@@ -305,53 +283,17 @@ class GroupRemoveFutureUserView(GroupRemoveUserView): ...@@ -305,53 +283,17 @@ class GroupRemoveFutureUserView(GroupRemoveUserView):
FutureMember.objects.filter(org_id=org_id, FutureMember.objects.filter(org_id=org_id,
group=self.get_object()).delete() group=self.get_object()).delete()
def get_success_message(self):
return _("Future user successfully removed from group.")
class GroupDelete(DeleteViewBase):
class GroupDelete(CheckedDetailView, DeleteView):
"""This stuff deletes the group.
"""
model = Group model = Group
template_name = "dashboard/confirm/base-delete.html" success_message = _("Group successfully deleted.")
read_level = 'operator'
def get_has_level(self):
return self.object.profile.has_level
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
# github.com/django/django/blob/master/django/views/generic/edit.py#L245 def check_auth(self):
def delete(self, request, *args, **kwargs): if not self.get_object().profile.has_level(self.request.user, 'owner'):
object = self.get_object()
if not object.profile.has_level(request.user, 'owner'):
raise PermissionDenied() raise PermissionDenied()
object.delete()
success_url = self.get_success_url()
success_message = _("Group successfully deleted.")
if request.is_ajax():
if request.POST.get('redirect').lower() == "true":
messages.success(request, success_message)
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return redirect(success_url)
def get_success_url(self): def get_success_url(self):
next = self.request.POST.get('next') return reverse_lazy('dashboard.views.group-list')
if next:
return next
else:
return reverse_lazy('dashboard.index')
class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView): class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
...@@ -360,7 +302,7 @@ class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView): ...@@ -360,7 +302,7 @@ class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
def get_template_names(self): def get_template_names(self):
if self.request.is_ajax(): if self.request.is_ajax():
return ['dashboard/modal-wrapper.html'] return ['dashboard/_modal.html']
else: else:
return ['dashboard/nojs-wrapper.html'] return ['dashboard/nojs-wrapper.html']
......
...@@ -27,8 +27,10 @@ from django.db.models import Count ...@@ -27,8 +27,10 @@ from django.db.models import Count
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.template import RequestContext
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic import DetailView, TemplateView, DeleteView from django.views.generic import DetailView, TemplateView, View
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin from braces.views import LoginRequiredMixin, SuperuserRequiredMixin
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
...@@ -38,7 +40,7 @@ from vm.models import Node, NodeActivity, Trait ...@@ -38,7 +40,7 @@ from vm.models import Node, NodeActivity, Trait
from ..forms import TraitForm, HostForm, NodeForm from ..forms import TraitForm, HostForm, NodeForm
from ..tables import NodeListTable from ..tables import NodeListTable
from .util import AjaxOperationMixin, OperationView, GraphMixin from .util import AjaxOperationMixin, OperationView, GraphMixin, DeleteViewBase
def get_operations(instance, user): def get_operations(instance, user):
...@@ -59,6 +61,8 @@ class NodeOperationView(AjaxOperationMixin, OperationView): ...@@ -59,6 +61,8 @@ class NodeOperationView(AjaxOperationMixin, OperationView):
model = Node model = Node
context_object_name = 'node' # much simpler to mock object context_object_name = 'node' # much simpler to mock object
with_reload = True
wait_for_result = 1
node_ops = OrderedDict([ node_ops = OrderedDict([
...@@ -193,7 +197,7 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView): ...@@ -193,7 +197,7 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
def get_template_names(self): def get_template_names(self):
if self.request.is_ajax(): if self.request.is_ajax():
return ['dashboard/modal-wrapper.html'] return ['dashboard/_modal.html']
else: else:
return ['dashboard/nojs-wrapper.html'] return ['dashboard/nojs-wrapper.html']
...@@ -205,7 +209,7 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView): ...@@ -205,7 +209,7 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
context.update({ context.update({
'template': 'dashboard/node-create.html', 'template': 'dashboard/node-create.html',
'box_title': 'Create a Node', 'box_title': _('Create a node'),
'hostform': hostform, 'hostform': hostform,
'formset': formset, 'formset': formset,
...@@ -238,44 +242,16 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView): ...@@ -238,44 +242,16 @@ class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):
return redirect(path) return redirect(path)
class NodeDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView): class NodeDelete(SuperuserRequiredMixin, DeleteViewBase):
"""This stuff deletes the node.
"""
model = Node model = Node
template_name = "dashboard/confirm/base-delete.html" success_message = _("Node successfully deleted.")
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
# github.com/django/django/blob/master/django/views/generic/edit.py#L245
def delete(self, request, *args, **kwargs):
object = self.get_object()
object.delete() def check_auth(self):
success_url = self.get_success_url() # SuperuserRequiredMixin
success_message = _("Node successfully deleted.") pass
if request.is_ajax():
if request.POST.get('redirect').lower() == "true":
messages.success(request, success_message)
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return redirect(success_url)
def get_success_url(self): def get_success_url(self):
next = self.request.POST.get('next') return reverse_lazy('dashboard.views.node-list')
if next:
return next
else:
return reverse_lazy('dashboard.index')
class NodeAddTraitView(SuperuserRequiredMixin, DetailView): class NodeAddTraitView(SuperuserRequiredMixin, DetailView):
...@@ -311,55 +287,20 @@ class NodeAddTraitView(SuperuserRequiredMixin, DetailView): ...@@ -311,55 +287,20 @@ class NodeAddTraitView(SuperuserRequiredMixin, DetailView):
return self.get(self, request, pk, *args, **kwargs) return self.get(self, request, pk, *args, **kwargs)
class NodeStatus(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): class NodeActivityView(LoginRequiredMixin, SuperuserRequiredMixin, View):
template_name = "dashboard/confirm/node-status.html" def get(self, request, pk):
model = Node node = Node.objects.get(pk=pk)
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-node-status.html']
else:
return ['dashboard/confirm/node-status.html']
def get_success_url(self):
next = self.request.GET.get('next')
if next:
return next
else:
return reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(NodeStatus, self).get_context_data(**kwargs)
if self.object.enabled:
context['status'] = "disable"
else:
context['status'] = "enable"
return context
def post(self, request, *args, **kwargs): activities = NodeActivity.objects.filter(
if request.POST.get('change_status') is not None: node=node, parent=None).order_by('-started').select_related()
return self.__set_status(request)
return redirect(reverse_lazy("dashboard.views.node-detail",
kwargs={'pk': self.get_object().pk}))
def __set_status(self, request): response = {
self.object = self.get_object() 'activities': render_to_string(
if not self.object.enabled: "dashboard/node-detail/_activity-timeline.html",
self.object.enable(user=request.user) RequestContext(request, {'activities': activities}))
else: }
self.object.disable(user=request.user)
success_message = _("Node successfully changed status.")
if request.is_ajax(): return HttpResponse(
response = { json.dumps(response),
'message': success_message, content_type="application/json"
'node_pk': self.object.pk )
}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
else:
messages.success(request, success_message)
return redirect(self.get_success_url())
...@@ -28,7 +28,7 @@ from django.http import HttpResponse, HttpResponseRedirect ...@@ -28,7 +28,7 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.utils.translation import ugettext as _, ugettext_noop from django.utils.translation import ugettext as _, ugettext_noop
from django.views.generic import ( from django.views.generic import (
TemplateView, CreateView, DeleteView, UpdateView, TemplateView, CreateView, UpdateView,
) )
from braces.views import ( from braces.views import (
...@@ -47,6 +47,7 @@ from ..tables import TemplateListTable, LeaseListTable ...@@ -47,6 +47,7 @@ from ..tables import TemplateListTable, LeaseListTable
from .util import ( from .util import (
AclUpdateView, FilterMixin, AclUpdateView, FilterMixin,
TransferOwnershipConfirmView, TransferOwnershipView, TransferOwnershipConfirmView, TransferOwnershipView,
DeleteViewBase
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -56,7 +57,7 @@ class TemplateChoose(LoginRequiredMixin, TemplateView): ...@@ -56,7 +57,7 @@ class TemplateChoose(LoginRequiredMixin, TemplateView):
def get_template_names(self): def get_template_names(self):
if self.request.is_ajax(): if self.request.is_ajax():
return ['dashboard/modal-wrapper.html'] return ['dashboard/_modal.html']
else: else:
return ['dashboard/nojs-wrapper.html'] return ['dashboard/nojs-wrapper.html']
...@@ -231,46 +232,17 @@ class TemplateList(LoginRequiredMixin, FilterMixin, SingleTableView): ...@@ -231,46 +232,17 @@ class TemplateList(LoginRequiredMixin, FilterMixin, SingleTableView):
return qs.select_related("lease", "owner", "owner__profile") return qs.select_related("lease", "owner", "owner__profile")
class TemplateDelete(LoginRequiredMixin, DeleteView): class TemplateDelete(DeleteViewBase):
model = InstanceTemplate model = InstanceTemplate
success_message = _("Template successfully deleted.")
def get_success_url(self): def get_success_url(self):
return reverse("dashboard.views.template-list") return reverse("dashboard.views.template-list")
def get_template_names(self): def delete_obj(self, request, *args, **kwargs):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def get(self, request, *args, **kwargs):
if not self.get_object().has_level(request.user, "owner"):
message = _("Only the owners can delete the selected template.")
if request.is_ajax():
raise PermissionDenied()
else:
messages.warning(request, message)
return redirect(self.get_success_url())
return super(TemplateDelete, self).get(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
object = self.get_object() object = self.get_object()
if not object.has_level(request.user, 'owner'):
raise PermissionDenied()
object.destroy_disks() object.destroy_disks()
object.delete() object.delete()
success_url = self.get_success_url()
success_message = _("Template successfully deleted.")
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
...@@ -333,25 +305,24 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -333,25 +305,24 @@ class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
return kwargs return kwargs
class DiskRemoveView(DeleteView): class DiskRemoveView(DeleteViewBase):
model = Disk model = Disk
success_message = _("Disk successfully removed.")
def get_queryset(self): def get_queryset(self):
qs = super(DiskRemoveView, self).get_queryset() qs = super(DiskRemoveView, self).get_queryset()
return qs.exclude(template_set=None) return qs.exclude(template_set=None)
def get_template_names(self): def check_auth(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def get_context_data(self, **kwargs):
context = super(DiskRemoveView, self).get_context_data(**kwargs)
disk = self.get_object() disk = self.get_object()
template = disk.template_set.get() template = disk.template_set.get()
if not template.has_level(self.request.user, 'owner'): if not template.has_level(self.request.user, 'owner'):
raise PermissionDenied() raise PermissionDenied()
def get_context_data(self, **kwargs):
disk = self.get_object()
template = disk.template_set.get()
context = super(DiskRemoveView, self).get_context_data(**kwargs)
context['title'] = _("Disk remove confirmation") context['title'] = _("Disk remove confirmation")
context['text'] = _("Are you sure you want to remove " context['text'] = _("Are you sure you want to remove "
"<strong>%(disk)s</strong> from " "<strong>%(disk)s</strong> from "
...@@ -360,29 +331,12 @@ class DiskRemoveView(DeleteView): ...@@ -360,29 +331,12 @@ class DiskRemoveView(DeleteView):
) )
return context return context
def delete(self, request, *args, **kwargs): def delete_obj(self, request, *args, **kwargs):
disk = self.get_object() disk = self.get_object()
template = disk.template_set.get() template = disk.template_set.get()
template.remove_disk(disk)
if not template.has_level(request.user, 'owner'):
raise PermissionDenied()
template.remove_disk(disk=disk, user=request.user)
disk.destroy() disk.destroy()
next_url = request.POST.get("next")
success_url = next_url if next_url else template.get_absolute_url()
success_message = _("Disk successfully removed.")
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect("%s#resources" % success_url)
class LeaseCreate(LoginRequiredMixin, PermissionRequiredMixin, class LeaseCreate(LoginRequiredMixin, PermissionRequiredMixin,
SuccessMessageMixin, CreateView): SuccessMessageMixin, CreateView):
...@@ -435,18 +389,13 @@ class LeaseDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -435,18 +389,13 @@ class LeaseDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
return super(LeaseDetail, self).post(request, *args, **kwargs) return super(LeaseDetail, self).post(request, *args, **kwargs)
class LeaseDelete(LoginRequiredMixin, DeleteView): class LeaseDelete(DeleteViewBase):
model = Lease model = Lease
success_message = _("Lease successfully deleted.")
def get_success_url(self): def get_success_url(self):
return reverse("dashboard.views.template-list") return reverse("dashboard.views.template-list")
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
c = super(LeaseDelete, self).get_context_data(*args, **kwargs) c = super(LeaseDelete, self).get_context_data(*args, **kwargs)
lease = self.get_object() lease = self.get_object()
...@@ -461,36 +410,11 @@ class LeaseDelete(LoginRequiredMixin, DeleteView): ...@@ -461,36 +410,11 @@ class LeaseDelete(LoginRequiredMixin, DeleteView):
c['disable_submit'] = True c['disable_submit'] = True
return c return c
def get(self, request, *args, **kwargs): def delete_obj(self, request, *args, **kwargs):
if not self.get_object().has_level(request.user, "owner"):
message = _("Only the owners can delete the selected lease.")
if request.is_ajax():
raise PermissionDenied()
else:
messages.warning(request, message)
return redirect(self.get_success_url())
return super(LeaseDelete, self).get(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
object = self.get_object() object = self.get_object()
if not object.has_level(request.user, "owner"):
raise PermissionDenied()
if object.instancetemplate_set.count() > 0: if object.instancetemplate_set.count() > 0:
raise SuspiciousOperation() raise SuspiciousOperation()
object.delete() object.delete()
success_url = self.get_success_url()
success_message = _("Lease successfully deleted.")
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
class TransferTemplateOwnershipConfirmView(TransferOwnershipConfirmView): class TransferTemplateOwnershipConfirmView(TransferOwnershipConfirmView):
......
...@@ -35,7 +35,7 @@ from django.shortcuts import redirect, get_object_or_404 ...@@ -35,7 +35,7 @@ from django.shortcuts import redirect, get_object_or_404
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from django.views.generic import ( from django.views.generic import (
TemplateView, DetailView, View, DeleteView, UpdateView, CreateView, TemplateView, DetailView, View, UpdateView, CreateView,
) )
from django_sshkey.models import UserKey from django_sshkey.models import UserKey
...@@ -50,7 +50,7 @@ from ..forms import ( ...@@ -50,7 +50,7 @@ from ..forms import (
from ..models import Profile, GroupProfile, ConnectCommand, create_profile from ..models import Profile, GroupProfile, ConnectCommand, create_profile
from ..tables import UserKeyListTable, ConnectCommandListTable from ..tables import UserKeyListTable, ConnectCommandListTable
from .util import saml_available from .util import saml_available, DeleteViewBase
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -385,36 +385,17 @@ class UserKeyDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -385,36 +385,17 @@ class UserKeyDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
return super(UserKeyDetail, self).post(self, request, args, kwargs) return super(UserKeyDetail, self).post(self, request, args, kwargs)
class UserKeyDelete(LoginRequiredMixin, DeleteView): class UserKeyDelete(DeleteViewBase):
model = UserKey model = UserKey
success_message = _("SSH key successfully deleted.")
def get_success_url(self): def get_success_url(self):
return reverse("dashboard.views.profile-preferences") return reverse("dashboard.views.profile-preferences")
def get_template_names(self): def check_auth(self):
if self.request.is_ajax(): if self.get_object().user != self.request.user:
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def delete(self, request, *args, **kwargs):
object = self.get_object()
if object.user != request.user:
raise PermissionDenied() raise PermissionDenied()
object.delete()
success_url = self.get_success_url()
success_message = _("SSH key successfully deleted.")
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
class UserKeyCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView): class UserKeyCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = UserKey model = UserKey
...@@ -460,36 +441,17 @@ class ConnectCommandDetail(LoginRequiredMixin, SuccessMessageMixin, ...@@ -460,36 +441,17 @@ class ConnectCommandDetail(LoginRequiredMixin, SuccessMessageMixin,
return kwargs return kwargs
class ConnectCommandDelete(LoginRequiredMixin, DeleteView): class ConnectCommandDelete(DeleteViewBase):
model = ConnectCommand model = ConnectCommand
success_message = _("Command template successfully deleted.")
def get_success_url(self): def get_success_url(self):
return reverse("dashboard.views.profile-preferences") return reverse("dashboard.views.profile-preferences")
def get_template_names(self): def check_auth(self):
if self.request.is_ajax(): if self.get_object().user != self.request.user:
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def delete(self, request, *args, **kwargs):
object = self.get_object()
if object.user != request.user:
raise PermissionDenied() raise PermissionDenied()
object.delete()
success_url = self.get_success_url()
success_message = _("Command template successfully deleted.")
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect(success_url)
class ConnectCommandCreate(LoginRequiredMixin, SuccessMessageMixin, class ConnectCommandCreate(LoginRequiredMixin, SuccessMessageMixin,
CreateView): CreateView):
......
...@@ -33,7 +33,7 @@ from django.db.models import Q ...@@ -33,7 +33,7 @@ from django.db.models import Q
from django.http import HttpResponse, Http404, HttpResponseRedirect from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
from django.views.generic import DetailView, View from django.views.generic import DetailView, View, DeleteView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from braces.views import LoginRequiredMixin from braces.views import LoginRequiredMixin
...@@ -694,3 +694,45 @@ class TransferOwnershipConfirmView(LoginRequiredMixin, View): ...@@ -694,3 +694,45 @@ class TransferOwnershipConfirmView(LoginRequiredMixin, View):
unicode(user), user.pk, new_owner, key) unicode(user), user.pk, new_owner, key)
raise PermissionDenied() raise PermissionDenied()
return (instance, new_owner) return (instance, new_owner)
class DeleteViewBase(LoginRequiredMixin, DeleteView):
level = 'owner'
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def check_auth(self):
if not self.get_object().has_level(self.request.user, self.level):
raise PermissionDenied()
def get(self, request, *args, **kwargs):
try:
self.check_auth()
except PermissionDenied:
message = _("Only the owners can delete the selected object.")
if request.is_ajax():
raise PermissionDenied()
else:
messages.warning(request, message)
return redirect(self.get_success_url())
return super(DeleteViewBase, self).get(request, *args, **kwargs)
def delete_obj(self, request, *args, **kwargs):
self.get_object().delete()
def delete(self, request, *args, **kwargs):
self.check_auth()
self.delete_obj(request, *args, **kwargs)
if request.is_ajax():
return HttpResponse(
json.dumps({'message': self.success_message}),
content_type="application/json",
)
else:
messages.success(request, self.success_message)
return HttpResponseRedirect(self.get_success_url())
...@@ -37,7 +37,7 @@ from django.utils.translation import ( ...@@ -37,7 +37,7 @@ from django.utils.translation import (
) )
from django.views.decorators.http import require_GET from django.views.decorators.http import require_GET
from django.views.generic import ( from django.views.generic import (
UpdateView, ListView, TemplateView, DeleteView UpdateView, ListView, TemplateView
) )
from braces.views import SuperuserRequiredMixin, LoginRequiredMixin from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
...@@ -64,6 +64,7 @@ from ..forms import ( ...@@ -64,6 +64,7 @@ from ..forms import (
VmDiskResizeForm, RedeployForm, VmDiskRemoveForm, VmDiskResizeForm, RedeployForm, VmDiskRemoveForm,
VmMigrateForm, VmDeployForm, VmMigrateForm, VmDeployForm,
VmPortRemoveForm, VmPortAddForm, VmPortRemoveForm, VmPortAddForm,
VmRemoveInterfaceForm,
) )
from ..models import Favourite from ..models import Favourite
...@@ -324,6 +325,32 @@ def get_operations(instance, user): ...@@ -324,6 +325,32 @@ def get_operations(instance, user):
return ops return ops
class VmRemoveInterfaceView(FormOperationMixin, VmOperationView):
op = 'remove_interface'
form_class = VmRemoveInterfaceForm
show_in_toolbar = False
wait_for_result = 0.5
icon = 'times'
effect = "danger"
with_reload = True
def get_form_kwargs(self):
instance = self.get_op().instance
choices = instance.interface_set.all()
interface_pk = self.request.GET.get('interface')
if interface_pk:
try:
default = choices.get(pk=interface_pk)
except (ValueError, Interface.DoesNotExist):
raise Http404()
else:
default = None
val = super(VmRemoveInterfaceView, self).get_form_kwargs()
val.update({'choices': choices, 'default': default})
return val
class VmAddInterfaceView(FormOperationMixin, VmOperationView): class VmAddInterfaceView(FormOperationMixin, VmOperationView):
op = 'add_interface' op = 'add_interface'
...@@ -707,6 +734,7 @@ vm_ops = OrderedDict([ ...@@ -707,6 +734,7 @@ vm_ops = OrderedDict([
op='remove_disk', form_class=VmDiskRemoveForm, op='remove_disk', form_class=VmDiskRemoveForm,
icon='times', effect="danger")), icon='times', effect="danger")),
('add_interface', VmAddInterfaceView), ('add_interface', VmAddInterfaceView),
('remove_interface', VmRemoveInterfaceView),
('remove_port', VmPortRemoveView), ('remove_port', VmPortRemoveView),
('add_port', VmPortAddView), ('add_port', VmPortAddView),
('renew', VmRenewView), ('renew', VmRenewView),
...@@ -951,10 +979,21 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -951,10 +979,21 @@ class VmCreate(LoginRequiredMixin, TemplateView):
def get_template_names(self): def get_template_names(self):
if self.request.is_ajax(): if self.request.is_ajax():
return ['dashboard/modal-wrapper.html'] return ['dashboard/_modal.html']
else: else:
return ['dashboard/nojs-wrapper.html'] return ['dashboard/nojs-wrapper.html']
def get_template(self, request, pk):
try:
template = InstanceTemplate.objects.get(
pk=int(pk))
except (ValueError, InstanceTemplate.DoesNotExist):
raise Http404()
if not template.has_level(request.user, 'user'):
raise PermissionDenied()
return template
def get(self, request, form=None, *args, **kwargs): def get(self, request, form=None, *args, **kwargs):
if not request.user.has_perm('vm.create_vm'): if not request.user.has_perm('vm.create_vm'):
raise PermissionDenied() raise PermissionDenied()
...@@ -965,9 +1004,7 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -965,9 +1004,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
template_pk = form.template.pk template_pk = form.template.pk
if template_pk: if template_pk:
template = get_object_or_404(InstanceTemplate, pk=template_pk) template = self.get_template(request, template_pk)
if not template.has_level(request.user, 'user'):
raise PermissionDenied()
if form is None: if form is None:
form = self.form_class(user=request.user, template=template) form = self.form_class(user=request.user, template=template)
else: else:
...@@ -992,33 +1029,21 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -992,33 +1029,21 @@ class VmCreate(LoginRequiredMixin, TemplateView):
}) })
return self.render_to_response(context) return self.render_to_response(context)
def __create_normal(self, request, *args, **kwargs): def __create_normal(self, request, template, *args, **kwargs):
user = request.user instances = [Instance.create_from_template(
template = InstanceTemplate.objects.get( template=template,
pk=request.POST.get("template")) owner=request.user)]
# permission check
if not template.has_level(request.user, 'user'):
raise PermissionDenied()
args = {"template": template, "owner": user}
instances = [Instance.create_from_template(**args)]
return self.__deploy(request, instances) return self.__deploy(request, instances)
def __create_customized(self, request, *args, **kwargs): def __create_customized(self, request, template, *args, **kwargs):
user = request.user user = request.user
# no form yet, using POST directly: # no form yet, using POST directly:
template = get_object_or_404(InstanceTemplate,
pk=request.POST.get("template"))
form = self.form_class( form = self.form_class(
request.POST, user=request.user, template=template) request.POST, user=request.user, template=template)
if not form.is_valid(): if not form.is_valid():
return self.get(request, form, *args, **kwargs) return self.get(request, form, *args, **kwargs)
post = form.cleaned_data post = form.cleaned_data
if not template.has_level(user, 'user'):
raise PermissionDenied()
ikwargs = { ikwargs = {
'name': post['name'], 'name': post['name'],
'template': template, 'template': template,
...@@ -1071,6 +1096,8 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -1071,6 +1096,8 @@ class VmCreate(LoginRequiredMixin, TemplateView):
if not request.user.has_perm('vm.create_vm'): if not request.user.has_perm('vm.create_vm'):
raise PermissionDenied() raise PermissionDenied()
template = self.get_template(request, request.POST.get("template"))
# limit chekcs # limit chekcs
try: try:
limit = user.profile.instance_limit limit = user.profile.instance_limit
...@@ -1096,7 +1123,7 @@ class VmCreate(LoginRequiredMixin, TemplateView): ...@@ -1096,7 +1123,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
request.POST.get("customized") is None else request.POST.get("customized") is None else
self.__create_customized) self.__create_customized)
return create_func(request, *args, **kwargs) return create_func(request, template, *args, **kwargs)
@require_GET @require_GET
...@@ -1111,56 +1138,6 @@ def get_vm_screenshot(request, pk): ...@@ -1111,56 +1138,6 @@ def get_vm_screenshot(request, pk):
return HttpResponse(image, mimetype="image/png") return HttpResponse(image, mimetype="image/png")
class InterfaceDeleteView(DeleteView):
model = Interface
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def get_context_data(self, **kwargs):
context = super(InterfaceDeleteView, self).get_context_data(**kwargs)
interface = self.get_object()
context['text'] = _("Are you sure you want to remove this interface "
"from <strong>%(vm)s</strong>?" %
{'vm': interface.instance.name})
return context
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
instance = self.object.instance
if not instance.has_level(request.user, "owner"):
raise PermissionDenied()
instance.remove_interface(interface=self.object, user=request.user)
success_url = self.get_success_url()
success_message = _("Interface successfully deleted.")
if request.is_ajax():
return HttpResponse(
json.dumps(
{'message': success_message,
'removed_network': {
'vlan': self.object.vlan.name,
'vlan_pk': self.object.vlan.pk,
'managed': self.object.host is not None,
}}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect("%s#network" % success_url)
def get_success_url(self):
redirect = self.request.POST.get("next")
if redirect:
return redirect
self.object.instance.get_absolute_url()
class InstanceActivityDetail(CheckedDetailView): class InstanceActivityDetail(CheckedDetailView):
model = InstanceActivity model = InstanceActivity
context_object_name = 'instanceactivity' # much simpler to mock object context_object_name = 'instanceactivity' # much simpler to mock object
......
...@@ -354,6 +354,12 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -354,6 +354,12 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
def create(cls, params, disks, networks, req_traits, tags): def create(cls, params, disks, networks, req_traits, tags):
""" Create new Instance object. """ Create new Instance object.
""" """
# permission check
for network in networks:
if not network.vlan.has_level(params['owner'], 'user'):
raise PermissionDenied()
# create instance and do additional setup # create instance and do additional setup
inst = cls(**params) inst = cls(**params)
...@@ -408,10 +414,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -408,10 +414,6 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
networks = (template.interface_set.all() if networks is None networks = (template.interface_set.all() if networks is None
else networks) else networks)
for network in networks:
if not network.vlan.has_level(owner, 'user'):
raise PermissionDenied()
req_traits = (template.req_traits.all() if req_traits is None req_traits = (template.req_traits.all() if req_traits is None
else req_traits) else req_traits)
......
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