Commit 9d40ae4f by Csók Tamás

Merge remote-tracking branch 'origin/master' into issue-218

Conflicts:
	requirements/test.txt
parents 82870d16 082389aa
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Level'
db.create_table(u'acl_level', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=50)),
('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])),
('codename', self.gf('django.db.models.fields.CharField')(max_length=100)),
('weight', self.gf('django.db.models.fields.IntegerField')(null=True)),
))
db.send_create_signal(u'acl', ['Level'])
# Adding unique constraint on 'Level', fields ['content_type', 'codename']
db.create_unique(u'acl_level', ['content_type_id', 'codename'])
# Adding model 'ObjectLevel'
db.create_table(u'acl_objectlevel', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('level', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['acl.Level'])),
('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])),
('object_id', self.gf('django.db.models.fields.CharField')(max_length=255)),
))
db.send_create_signal(u'acl', ['ObjectLevel'])
# Adding unique constraint on 'ObjectLevel', fields ['content_type', 'object_id', 'level']
db.create_unique(u'acl_objectlevel', ['content_type_id', 'object_id', 'level_id'])
# Adding M2M table for field users on 'ObjectLevel'
m2m_table_name = db.shorten_name(u'acl_objectlevel_users')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('objectlevel', models.ForeignKey(orm[u'acl.objectlevel'], null=False)),
('user', models.ForeignKey(orm[u'auth.user'], null=False))
))
db.create_unique(m2m_table_name, ['objectlevel_id', 'user_id'])
# Adding M2M table for field groups on 'ObjectLevel'
m2m_table_name = db.shorten_name(u'acl_objectlevel_groups')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('objectlevel', models.ForeignKey(orm[u'acl.objectlevel'], null=False)),
('group', models.ForeignKey(orm[u'auth.group'], null=False))
))
db.create_unique(m2m_table_name, ['objectlevel_id', 'group_id'])
def backwards(self, orm):
# Removing unique constraint on 'ObjectLevel', fields ['content_type', 'object_id', 'level']
db.delete_unique(u'acl_objectlevel', ['content_type_id', 'object_id', 'level_id'])
# Removing unique constraint on 'Level', fields ['content_type', 'codename']
db.delete_unique(u'acl_level', ['content_type_id', 'codename'])
# Deleting model 'Level'
db.delete_table(u'acl_level')
# Deleting model 'ObjectLevel'
db.delete_table(u'acl_objectlevel')
# Removing M2M table for field users on 'ObjectLevel'
db.delete_table(db.shorten_name(u'acl_objectlevel_users'))
# Removing M2M table for field groups on 'ObjectLevel'
db.delete_table(db.shorten_name(u'acl_objectlevel_groups'))
models = {
u'acl.level': {
'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Level'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'weight': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
},
u'acl.objectlevel': {
'Meta': {'unique_together': "(('content_type', 'object_id', 'level'),)", 'object_name': 'ObjectLevel'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['acl.Level']"}),
'object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False'})
},
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['acl']
\ No newline at end of file
...@@ -15,29 +15,4 @@ ...@@ -15,29 +15,4 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import TextField, ForeignKey from .test_acl import TestModel, Test2Model # noqa
from django.contrib.auth.models import User
from ..models import AclBase
class TestModel(AclBase):
normal_field = TextField()
ACL_LEVELS = (
('alfa', 'Alfa'),
('bravo', 'Bravo'),
('charlie', 'Charlie'),
)
class Test2Model(AclBase):
normal2_field = TextField()
owner = ForeignKey(User, null=True)
ACL_LEVELS = (
('one', 'One'),
('two', 'Two'),
('three', 'Three'),
('owner', 'owner'),
)
...@@ -17,9 +17,31 @@ ...@@ -17,9 +17,31 @@
from django.test import TestCase from django.test import TestCase
from django.contrib.auth.models import User, Group, AnonymousUser from django.contrib.auth.models import User, Group, AnonymousUser
from django.db.models import TextField, ForeignKey
from ..models import ObjectLevel from ..models import ObjectLevel, AclBase
from .models import TestModel, Test2Model
class TestModel(AclBase):
normal_field = TextField()
ACL_LEVELS = (
('alfa', 'Alfa'),
('bravo', 'Bravo'),
('charlie', 'Charlie'),
)
class Test2Model(AclBase):
normal2_field = TextField()
owner = ForeignKey(User, null=True)
ACL_LEVELS = (
('one', 'One'),
('two', 'Two'),
('three', 'Three'),
('owner', 'owner'),
)
class AclUserTest(TestCase): class AclUserTest(TestCase):
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
"jquery-knob": "~1.2.9", "jquery-knob": "~1.2.9",
"jquery-simple-slider": "https://github.com/BME-IK/jquery-simple-slider.git", "jquery-simple-slider": "https://github.com/BME-IK/jquery-simple-slider.git",
"bootbox": "~4.3.0", "bootbox": "~4.3.0",
"intro.js": "0.9.0" "intro.js": "0.9.0",
"favico.js": "~0.3.5",
"datatables": "~1.10.4"
} }
} }
...@@ -50,20 +50,20 @@ def get_env_variable(var_name, default=None): ...@@ -50,20 +50,20 @@ def get_env_variable(var_name, default=None):
########## PATH CONFIGURATION ########## PATH CONFIGURATION
# Absolute filesystem path to the Django project directory: # Absolute filesystem path to the Django project directory:
DJANGO_ROOT = dirname(dirname(abspath(__file__))) BASE_DIR = dirname(dirname(abspath(__file__)))
# Absolute filesystem path to the top-level project folder: # Absolute filesystem path to the top-level project folder:
SITE_ROOT = dirname(DJANGO_ROOT) SITE_ROOT = dirname(BASE_DIR)
# Site name: # Site name:
SITE_NAME = basename(DJANGO_ROOT) SITE_NAME = basename(BASE_DIR)
# Url to site: (e.g. http://localhost:8080/) # Url to site: (e.g. http://localhost:8080/)
DJANGO_URL = get_env_variable('DJANGO_URL', '/') DJANGO_URL = get_env_variable('DJANGO_URL', '/')
# Add our project to our pythonpath, this way we don't need to type our project # Add our project to our pythonpath, this way we don't need to type our project
# name in our dotted import paths: # name in our dotted import paths:
path.append(DJANGO_ROOT) path.append(BASE_DIR)
########## END PATH CONFIGURATION ########## END PATH CONFIGURATION
...@@ -78,14 +78,9 @@ TEMPLATE_DEBUG = DEBUG ...@@ -78,14 +78,9 @@ TEMPLATE_DEBUG = DEBUG
########## MANAGER CONFIGURATION ########## MANAGER CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins # See: https://docs.djangoproject.com/en/dev/ref/settings/#admins
ADMINS = (
('Root', 'root@localhost'),
)
EMAIL_SUBJECT_PREFIX = get_env_variable('DJANGO_SUBJECT_PREFIX', '[CIRCLE] ') EMAIL_SUBJECT_PREFIX = get_env_variable('DJANGO_SUBJECT_PREFIX', '[CIRCLE] ')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#managers
MANAGERS = ADMINS
########## END MANAGER CONFIGURATION ########## END MANAGER CONFIGURATION
...@@ -197,7 +192,10 @@ PIPELINE_JS = { ...@@ -197,7 +192,10 @@ PIPELINE_JS = {
"intro.js/intro.js", "intro.js/intro.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",
"favico.js/favico.js",
"datatables/media/js/jquery.dataTables.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
...@@ -213,6 +211,7 @@ PIPELINE_JS = { ...@@ -213,6 +211,7 @@ PIPELINE_JS = {
"js/host.js", "js/host.js",
"js/network.js", "js/network.js",
"js/switch-port.js", "js/switch-port.js",
"js/host-list.js",
"autocomplete_light/autocomplete.js", "autocomplete_light/autocomplete.js",
"autocomplete_light/widget.js", "autocomplete_light/widget.js",
"autocomplete_light/addanother.js", "autocomplete_light/addanother.js",
...@@ -281,12 +280,6 @@ TEMPLATE_CONTEXT_PROCESSORS = ( ...@@ -281,12 +280,6 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'dashboard.context_processors.extract_settings', 'dashboard.context_processors.extract_settings',
) )
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
TEMPLATE_DIRS = ( TEMPLATE_DIRS = (
normpath(join(SITE_ROOT, '../../site-circle/templates')), normpath(join(SITE_ROOT, '../../site-circle/templates')),
...@@ -335,7 +328,6 @@ DJANGO_APPS = ( ...@@ -335,7 +328,6 @@ DJANGO_APPS = (
) )
THIRD_PARTY_APPS = ( THIRD_PARTY_APPS = (
'south',
'django_tables2', 'django_tables2',
'crispy_forms', 'crispy_forms',
'djcelery', 'djcelery',
...@@ -347,6 +339,11 @@ THIRD_PARTY_APPS = ( ...@@ -347,6 +339,11 @@ THIRD_PARTY_APPS = (
'pipeline', 'pipeline',
) )
import django
if django.get_version() < '1.7':
THIRD_PARTY_APPS += 'south',
# Apps specific for this project go here. # Apps specific for this project go here.
LOCAL_APPS = ( LOCAL_APPS = (
'common', 'common',
...@@ -531,8 +528,14 @@ LOCALE_PATHS = (join(SITE_ROOT, 'locale'), ) ...@@ -531,8 +528,14 @@ LOCALE_PATHS = (join(SITE_ROOT, 'locale'), )
COMPANY_NAME = "BME IK 2014" COMPANY_NAME = "BME IK 2014"
SOUTH_MIGRATION_MODULES = { SOUTH_MIGRATION_MODULES = {
'taggit': 'taggit.south_migrations', 'taggit': 'taggit.south_migrations',
'vm': 'vm.south_migrations',
'firewall': 'firewall.south_migrations',
'acl': 'acl.south_migrations',
'dashboard': 'dashboard.south_migrations',
'storage': 'storage.south_migrations',
} }
graphite_host = environ.get("GRAPHITE_HOST", None) graphite_host = environ.get("GRAPHITE_HOST", None)
graphite_port = environ.get("GRAPHITE_PORT", None) graphite_port = environ.get("GRAPHITE_PORT", None)
if graphite_host and graphite_port: if graphite_host and graphite_port:
......
...@@ -43,7 +43,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' ...@@ -43,7 +43,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# DATABASES = { # DATABASES = {
# 'default': { # 'default': {
# 'ENGINE': 'django.db.backends.sqlite3', # 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': normpath(join(DJANGO_ROOT, 'default.db')), # 'NAME': normpath(join(BASE_DIR, 'default.db')),
# 'USER': '', # 'USER': '',
# 'PASSWORD': '', # 'PASSWORD': '',
# 'HOST': '', # 'HOST': '',
......
...@@ -370,6 +370,12 @@ class HumanSortField(CharField): ...@@ -370,6 +370,12 @@ class HumanSortField(CharField):
def get_monitored_value(self, instance): def get_monitored_value(self, instance):
return getattr(instance, self.monitor) return getattr(instance, self.monitor)
def deconstruct(self):
name, path, args, kwargs = super(HumanSortField, self).deconstruct()
if self.monitor is not None:
kwargs['monitor'] = self.monitor
return name, path, args, kwargs
@staticmethod @staticmethod
def _partition(s, pred): def _partition(s, pred):
"""Partition a deque of chars to a tuple of a """Partition a deque of chars to a tuple of a
......
...@@ -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__))
......
...@@ -31,6 +31,7 @@ from django.db.models import ( ...@@ -31,6 +31,7 @@ from django.db.models import (
) )
from django.db.models.signals import post_save, pre_delete, post_delete from django.db.models.signals import post_save, pre_delete, post_delete
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_sshkey.models import UserKey from django_sshkey.models import UserKey
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
...@@ -53,7 +54,9 @@ from .validators import connect_command_template_validator ...@@ -53,7 +54,9 @@ from .validators import connect_command_template_validator
logger = getLogger(__name__) logger = getLogger(__name__)
pwgen = User.objects.make_random_password
def pwgen():
return User.objects.make_random_password()
class Favourite(Model): class Favourite(Model):
...@@ -87,7 +90,8 @@ class Notification(TimeStampedModel): ...@@ -87,7 +90,8 @@ class Notification(TimeStampedModel):
@property @property
def subject(self): def subject(self):
return HumanReadableObject.from_dict(self.subject_data) return HumanReadableObject.from_dict(
self.escape_dict(self.subject_data))
@subject.setter @subject.setter
def subject(self, value): def subject(self, value):
...@@ -95,7 +99,14 @@ class Notification(TimeStampedModel): ...@@ -95,7 +99,14 @@ class Notification(TimeStampedModel):
@property @property
def message(self): def message(self):
return HumanReadableObject.from_dict(self.message_data) return HumanReadableObject.from_dict(
self.escape_dict(self.message_data))
def escape_dict(self, data):
for k, v in data['params'].items():
if isinstance(v, basestring):
data['params'][k] = escape(v)
return data
@message.setter @message.setter
def message(self, value): def message(self, value):
......
/* 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;
}
}
$('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;
});
/* vm operations */ /* 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']); // jshint ignore:line
$("#vm-details-state span").html(data.human_readable_status.toUpperCase());
}
if(data['status'] == "RUNNING") { // jshint ignore:line
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;
};
...@@ -23,46 +23,23 @@ html { ...@@ -23,46 +23,23 @@ html {
padding-right: 15px; padding-right: 15px;
} }
/* values for 45px tall navbar */ /* --- */
.navbar {
min-height: 45px;
}
.navbar-brand {
height: 45px;
padding: 12.5px 12.5px;
}
.navbar-toggle { #dashboard-menu > li > a {
margin-top: 5.5px; color: white;
margin-bottom: 5.5px; font-size: 10px;
} }
.navbar-form {
margin-top: 5.5px;
margin-bottom: 5.5px;
}
.navbar-btn { #dashboard-menu {
margin-top: 5.5px; margin-right: 15px;
margin-bottom: 5.5px;
} }
.navbar-btn.btn-sm { /* we need this for mobile view */
margin-top: 7.5px; .container > :first-child {
margin-bottom: 7.5px; margin-top: 15px;
}
.navbar-btn.btn-xs {
margin-top: 11.5px;
margin-bottom: 11.5px;
}
.navbar-text {
margin-top: 12.5px;
margin-bottom: 12.5px;
} }
/* --- */
/* Responsive: Portrait tablets and up */ /* Responsive: Portrait tablets and up */
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
/* Let the jumbotron breathe */ /* Let the jumbotron breathe */
...@@ -80,13 +57,17 @@ html { ...@@ -80,13 +57,17 @@ html {
} }
} }
.no-margin { .no-margin {
margin: 0!important; margin: 0!important;
} }
.list-group .list-group-footer { .list-group .list-group-footer {
padding-top: 5px; height: 41px;
padding-bottom: 5px; }
.list-group-footer .text-right {
padding-top: 4px;
} }
.big { .big {
...@@ -194,7 +175,7 @@ html { ...@@ -194,7 +175,7 @@ html {
} }
.dashboard-index .panel { .dashboard-index .panel {
height: 300px; height: 294px;
} }
#vm-details-rename, #vm-details-h1-name, #vm-details-rename , #vm-details-rename, #vm-details-h1-name, #vm-details-rename ,
...@@ -207,11 +188,15 @@ html { ...@@ -207,11 +188,15 @@ html {
display: none; display: none;
} }
.vm-details-home-name { #group-details-rename-form {
display: inline-block;
}
.vm-details-home-name, #group-details-rename-form .input-group {
max-width: 401px; max-width: 401px;
} }
#node-details-rename-name, #group-details-rename-name { #node-details-rename-name {
max-width: 160px; max-width: 160px;
} }
...@@ -397,10 +382,6 @@ a.hover-black { ...@@ -397,10 +382,6 @@ a.hover-black {
font-size: 12px; font-size: 12px;
} }
#notification-button {
margin-right: 15px;
}
#vm-migrate-node-list { #vm-migrate-node-list {
list-style: none; list-style: none;
} }
...@@ -516,15 +497,6 @@ footer a, footer a:hover, footer a:visited { ...@@ -516,15 +497,6 @@ footer a, footer a:hover, footer a:visited {
padding: 5px; /* it's nice this way in the tour */ padding: 5px; /* it's nice this way in the tour */
} }
.index-vm-list-name {
display: inline-block;
max-width: 70%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
float: left;
}
#dashboard-vm-list a small { #dashboard-vm-list a small {
padding-left: 10px; padding-left: 10px;
} }
...@@ -590,8 +562,8 @@ footer a, footer a:hover, footer a:visited { ...@@ -590,8 +562,8 @@ footer a, footer a:hover, footer a:visited {
} }
#dashboard-vm-list, #dashboard-node-list, #dashboard-group-list, #dashboard-vm-list, #dashboard-node-list, #dashboard-group-list,
#dashboard-template-list { #dashboard-template-list, #dashboard-files-toplist, #dashboard-user-list {
min-height: 204px; min-height: 200px;
} }
#group-detail-user-table td:first-child, #group-detail-user-table th:last-child, #group-detail-user-table td:first-child, #group-detail-user-table th:last-child,
...@@ -754,15 +726,14 @@ textarea[name="new_members"] { ...@@ -754,15 +726,14 @@ textarea[name="new_members"] {
} }
#dashboard-files-toplist { #dashboard-files-toplist {
min-height: 204px; div.list-group-item {
}
#dashboard-files-toplist div.list-group-item {
color: #555; color: #555;
} height: 41px;
#dashboard-files-toplist div.list-group-item:hover { &:hover {
background: #eee; background: #eee;
}
}
} }
.store-list-item-name { .store-list-item-name {
...@@ -961,6 +932,11 @@ textarea[name="new_members"] { ...@@ -961,6 +932,11 @@ textarea[name="new_members"] {
#vm-list-search, #vm-mass-ops { #vm-list-search, #vm-mass-ops {
margin-top: 8px; margin-top: 8px;
} }
.list-group-item {
border-bottom: 0px !important;
}
.list-group-item-last { .list-group-item-last {
border-bottom: 1px solid #ddd !important; border-bottom: 1px solid #ddd !important;
} }
...@@ -1105,6 +1081,25 @@ textarea[name="new_members"] { ...@@ -1105,6 +1081,25 @@ textarea[name="new_members"] {
text-align: center; text-align: center;
} }
.vm-create-list-name {
display: inline-block;
max-width: 60%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
float: left;
}
.vm-create-list-system {
display: inline-block;
max-width: 40%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
float: right;
}
/* for introjs /* for introjs
* newer version has this fixed * newer version has this fixed
* but it doesn't work w bootstrap 3.2.0 * but it doesn't work w bootstrap 3.2.0
...@@ -1151,3 +1146,104 @@ textarea[name="new_members"] { ...@@ -1151,3 +1146,104 @@ textarea[name="new_members"] {
background-position: 0 0px; background-position: 0 0px;
} }
} }
#dashboard-vm-list {
.list-group-item {
display: flex;
}
.index-vm-list-name, .index-vm-list-host {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.index-vm-list-name {
max-width: 70%;
}
.index-vm-list-host {
padding-top: 3px;
flex: 1;
}
}
#dashboard-user-list {
.list-group-item {
display: flex;
}
.index-user-list-name, .index-user-list-org {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.index-user-list-name {
max-width: 80%;
}
.index-user-list-org {
padding-left: 5px;
flex: 1;
}
}
.fa-fw-12 {
/* fa-fw is too wide */
width: 12px;
}
.btn-op-form-send {
padding: 6px 12px 6px 8px;
}
@media (max-width: 767px) {
#vm-detail-panel .graph-buttons {
padding-top: 15px;
}
.graph-buttons a {
margin-bottom: 8px;
}
#ops .operation {
margin-bottom: 5px;
}
.vm-details-connection dd {
margin-left: 25px;
}
.vm-details-connection dt {
padding-left: 0px;
}
}
#notifications-upper-pagination {
margin-top: 4px;
}
#notifications-bottom-pagination {
* {
display: inline-block;
}
a {
font-size: 20px;
&:hover {
text-decoration: none;
}
}
.page-numbers {
padding: 25px;
}
}
.pagination {
width: 100%;
}
$(function() {
/* rename */ /* rename */
$("#group-details-h1-name, .group-details-rename-button").click(function() { $("#group-details-h1-name, .group-details-rename-button").click(function() {
$("#group-details-h1-name").hide(); $("#group-details-h1-name span").hide();
$("#group-details-rename").css('display', 'inline'); $("#group-details-rename-form").show().css('display', 'inline-block');
$("#group-details-rename-name").focus(); $("#group-details-rename-name").select();
}); });
/* rename ajax */ /* rename ajax */
$('#group-details-rename-submit').click(function() { $('#group-details-rename-submit').click(function() {
if(!$("#group-details-rename-name")[0].checkValidity()) {
return true;
}
var name = $('#group-details-rename-name').val(); var name = $('#group-details-rename-name').val();
$.ajax({ $.ajax({
method: 'POST', method: 'POST',
url: location.href, url: location.href,
data: {'new_name': name}, data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')}, headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
$("#group-details-h1-name").text(data['new_name']).show(); $("#group-details-h1-name span").text(data.new_name).show();
$('#group-details-rename').hide(); $('#group-details-rename-form').hide();
// addMessage(data['message'], "success");
}, },
error: function(xhr, textStatus, error) { error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger"); addMessage("Error during renaming.", "danger");
} }
}); });
return false; return false;
}); });
});
$(".group-details-help-button").click(function() {
$(".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();
}
...@@ -15,7 +15,7 @@ $(function() { ...@@ -15,7 +15,7 @@ $(function() {
data: {'new_name': name}, data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')}, headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) { success: function(data, textStatus, xhr) {
$("#node-details-h1-name").text(data['new_name']).show(); $("#node-details-h1-name").text(data.new_name).show();
$('#node-details-rename').hide(); $('#node-details-rename').hide();
// addMessage(data.message, "success"); // addMessage(data.message, "success");
}, },
...@@ -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');
}
});
}
$(function() { $(function() {
$(document).ready( function() { $(document).ready( function() {
colortable();
});
// find disabled nodes, set danger (red) on the rows // find disabled nodes, set danger (red) on the rows
function colortable() $('.node-disabled').closest("tr").addClass('danger');
{
$('.false').closest("tr").addClass('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");
...@@ -109,7 +71,7 @@ $(function() { ...@@ -109,7 +71,7 @@ $(function() {
var eye = $(this).children("#vm-details-pw-eye"); var eye = $(this).children("#vm-details-pw-eye");
var span = $(this); var span = $(this);
span.tooltip("destroy") span.tooltip("destroy");
if(eye.hasClass("fa-eye")) { if(eye.hasClass("fa-eye")) {
eye.removeClass("fa-eye").addClass("fa-eye-slash"); eye.removeClass("fa-eye").addClass("fa-eye-slash");
input.prop("type", "text"); input.prop("type", "text");
...@@ -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();
...@@ -244,7 +132,7 @@ $(function() { ...@@ -244,7 +132,7 @@ $(function() {
var tmp = ta.val(); var tmp = ta.val();
ta.val(""); ta.val("");
ta.focus(); ta.focus();
ta.val(tmp) ta.val(tmp);
e.preventDefault(); e.preventDefault();
}); });
...@@ -319,7 +207,7 @@ $(function() { ...@@ -319,7 +207,7 @@ $(function() {
$("#dashboard-tutorial-toggle").click(function() { $("#dashboard-tutorial-toggle").click(function() {
var box = $("#alert-new-template"); var box = $("#alert-new-template");
var list = box.find("ol") var list = box.find("ol");
list.stop().slideToggle(function() { list.stop().slideToggle(function() {
var url = box.find("form").prop("action"); var url = box.find("form").prop("action");
var hidden = list.css("display") === "none"; var hidden = list.css("display") === "none";
...@@ -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;
};
...@@ -82,7 +82,7 @@ $(function() { ...@@ -82,7 +82,7 @@ $(function() {
/* mass operations */ /* mass operations */
$("#vm-mass-ops").on('click', '.mass-operation', function(e) { $("#vm-mass-ops").on('click', '.mass-operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin'); var icon = $(this).children("i").addClass('fa-spinner fa-spin');
params = "?" + selected.map(function(a){return "vm=" + a.vm}).join("&"); params = "?" + selected.map(function(a){return "vm=" + a.vm;}).join("&");
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
...@@ -212,7 +212,7 @@ function updateStatuses(runs) { ...@@ -212,7 +212,7 @@ function updateStatuses(runs) {
if(checkStatusUpdate()) { if(checkStatusUpdate()) {
setTimeout( setTimeout(
function() {updateStatuses(runs + 1)}, function() {updateStatuses(runs + 1);},
1000 + Math.exp(runs * 0.05) 1000 + Math.exp(runs * 0.05)
); );
} }
......
...@@ -82,7 +82,6 @@ html { ...@@ -82,7 +82,6 @@ html {
z-index: 1; z-index: 1;
} }
.nojs-dropdown-toggle:focus + .nojs-dropdown-menu .nojs-dropdown-toggle:focus + .nojs-dropdown-menu
{ {
display: block; display: block;
...@@ -98,32 +97,6 @@ html { ...@@ -98,32 +97,6 @@ html {
display: block; display: block;
} }
.notification-messages {
padding: 10px 8px;
width: 350px;
}
.notification-message {
margin-bottom: 10px;
padding: 0 0 4px 0;
border-bottom: 1px dotted #D3D3D3;
}
.notification-messages .notification-message:last-child {
margin-bottom: 0px;
padding: 0px;
border-bottom: none;
}
.notification-message-text {
padding: 8px 15px;
display: none;
}
.notification-message .notification-message-subject {
cursor: pointer;
}
/* footer */ /* footer */
footer { footer {
position: absolute; position: absolute;
...@@ -148,10 +121,6 @@ footer a, footer a:hover, footer a:visited { ...@@ -148,10 +121,6 @@ footer a, footer a:hover, footer a:visited {
display: none; display: none;
} }
#notifications-button {
margin: 0;
}
/* 2px border bottom for all bootstrap tables */ /* 2px border bottom for all bootstrap tables */
.table thead>tr>th { .table thead>tr>th {
border-bottom: 1px; border-bottom: 1px;
......
...@@ -18,12 +18,16 @@ ...@@ -18,12 +18,16 @@
from __future__ import absolute_import from __future__ import absolute_import
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.utils.translation import ugettext_lazy as _
from django.utils.html import mark_safe
from django_tables2 import Table, A from django_tables2 import Table, A
from django_tables2.columns import TemplateColumn, Column, LinkColumn from django_tables2.columns import (
TemplateColumn, Column, LinkColumn, BooleanColumn
)
from django_sshkey.models import UserKey
from vm.models import Node, InstanceTemplate, Lease from vm.models import Node, InstanceTemplate, Lease
from django.utils.translation import ugettext_lazy as _
from django_sshkey.models import UserKey
from dashboard.models import ConnectCommand from dashboard.models import ConnectCommand
...@@ -67,12 +71,18 @@ class NodeListTable(Table): ...@@ -67,12 +71,18 @@ class NodeListTable(Table):
orderable=False, orderable=False,
) )
minion_online = BooleanColumn(
verbose_name=_("Minion online"),
attrs={'th': {'class': 'node-list-table-thin'}},
orderable=False,
)
class Meta: class Meta:
model = Node model = Node
attrs = {'class': ('table table-bordered table-striped table-hover ' attrs = {'class': ('table table-bordered table-striped table-hover '
'node-list-table')} 'node-list-table')}
fields = ('pk', 'name', 'host', 'get_status_display', 'priority', fields = ('pk', 'name', 'host', 'get_status_display', 'priority',
'overcommit', 'number_of_VMs', ) 'minion_online', 'overcommit', 'number_of_VMs', )
class GroupListTable(Table): class GroupListTable(Table):
...@@ -116,26 +126,30 @@ class GroupListTable(Table): ...@@ -116,26 +126,30 @@ class GroupListTable(Table):
class UserListTable(Table): class UserListTable(Table):
pk = TemplateColumn( username = LinkColumn(
template_name='dashboard/vm-list/column-id.html', 'dashboard.views.profile',
verbose_name="ID", args=[A('username')],
attrs={'th': {'class': 'vm-list-table-thin'}},
) )
profile__org_id = LinkColumn(
username = TemplateColumn( 'dashboard.views.profile',
template_name="dashboard/group-list/column-username.html" accessor='profile.org_id',
args=[A('username')],
verbose_name=_('Organization ID')
) )
class Meta: is_superuser = BooleanColumn(
model = User verbose_name=mark_safe(
attrs = {'class': ('table table-bordered table-striped table-hover ' _('<abbr data-placement="left" title="Superuser status">SU</abbr>')
'vm-list-table')} )
fields = ('pk', 'username', ) )
is_active = BooleanColumn()
class UserListTablex(Table):
class Meta: class Meta:
model = User model = User
template = "django_tables2/with_pagination.html"
attrs = {'class': ('table table-bordered table-striped table-hover')}
fields = ('username', 'last_name', 'first_name', 'profile__org_id',
'email', 'is_active', 'is_superuser')
class TemplateListTable(Table): class TemplateListTable(Table):
......
...@@ -64,7 +64,6 @@ ...@@ -64,7 +64,6 @@
<a href="{% url "info.support" %}">{% trans "Support" %}</a> <a href="{% url "info.support" %}">{% trans "Support" %}</a>
<span class="pull-right">{{ COMPANY_NAME }}</span> <span class="pull-right">{{ COMPANY_NAME }}</span>
</footer> </footer>
</body>
<script src="{% static "jquery/dist/jquery.min.js" %}"></script> <script src="{% static "jquery/dist/jquery.min.js" %}"></script>
<script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script> <script src="{{ STATIC_URL }}jsi18n/{{ LANGUAGE_CODE }}/djangojs.js"></script>
...@@ -78,4 +77,5 @@ ...@@ -78,4 +77,5 @@
{% block extra_etc %} {% block extra_etc %}
{% endblock %} {% endblock %}
</body>
</html> </html>
<img src="{{ STATIC_URL}}dashboard/img/logo.png" style="height: 25px;"/> <img src="{{ STATIC_URL}}dashboard/img/logo.png" alt="circle logo" style="height: 25px;"/>
<img src="{{ STATIC_URL}}local-logo.png" style="padding-left: 2px; height: 25px;"/> <img src="{{ STATIC_URL}}local-logo.png" alt="local logo" style="padding-left: 2px; height: 25px;"/>
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<a href="{{ op.remove_disk.get_url }}?disk={{d.pk}}" <a href="{{ op.remove_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.remove_disk.effect}} pull-right operation disk-remove-btn class="btn btn-xs btn-{{ op.remove_disk.effect}} pull-right operation disk-remove-btn
{% if op.remove_disk.disabled %}disabled{% endif %}"> {% if op.remove_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.remove_disk.icon }}"></i> {% trans "Remove" %} <i class="fa fa-{{ op.remove_disk.icon }} fa-fw-12"></i> {% trans "Remove" %}
</a> </a>
</span> </span>
{% endif %} {% endif %}
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
<a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}" <a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.resize_disk.effect }} pull-right operation disk-resize-btn class="btn btn-xs btn-{{ op.resize_disk.effect }} pull-right operation disk-resize-btn
{% if op.resize_disk.disabled %}disabled{% endif %}"> {% if op.resize_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.resize_disk.icon }}"></i> {% trans "Resize" %} <i class="fa fa-{{ op.resize_disk.icon }} fa-fw-12"></i> {% trans "Resize" %}
</a> </a>
</span> </span>
{% endif %} {% endif %}
......
{% load i18n %} {% load i18n %}
{% load hro %} {% load hro %}
{% for n in notifications %} {% for n in page %}
<li class="notification-message" id="msg-{{n.id}}"> <li class="notification-message" id="msg-{{n.id}}">
<span class="notification-message-subject"> <span class="notification-message-subject">
{% if n.status == "new" %}<i class="fa fa-envelope-o"></i> {% endif %} {% if n.status == "new" %}<i class="fa fa-envelope-o"></i> {% endif %}
......
...@@ -5,8 +5,14 @@ ...@@ -5,8 +5,14 @@
{% for t in templates %} {% for t in templates %}
<div class="vm-create-template"> <div class="vm-create-template">
<div class="vm-create-template-summary"> <div class="vm-create-template-summary">
<span class="vm-create-list-name">
{{ t.name }} {{ t.name }}
<span class="pull-right"><i class="fa fa-{{ t.os_type }}"></i> {{ t.system }}</span> </span>
<span class="vm-create-list-system">
<i class="fa fa-{{ t.os_type }}"></i>
{{ t.system }}
</span>
<div class="clearfix"></div>
</div> </div>
<div class="vm-create-template-details"> <div class="vm-create-template-details">
<ul> <ul>
......
...@@ -23,11 +23,35 @@ Choose a compute node to migrate {{obj}} to. ...@@ -23,11 +23,35 @@ Choose a compute node to migrate {{obj}} to.
<i class="fa {{n.get_status_icon}}"></i> {{n.get_status_display}}</div> <i class="fa {{n.get_status_icon}}"></i> {{n.get_status_display}}</div>
{% if current == n.pk %}<div class="label label-info">{% trans "current" %}</div>{% endif %} {% if current == n.pk %}<div class="label label-info">{% trans "current" %}</div>{% endif %}
{% if recommended == n.pk %}<div class="label label-success">{% trans "recommended" %}</div>{% endif %} {% if recommended == n.pk %}<div class="label label-success">{% trans "recommended" %}</div>{% endif %}
{% if n.pk not in nodes_w_traits %}
<div class="label label-warning">
<i class="fa fa-warning"></i>
{% trans "missing traits" %}</div>
{% endif %}
</label> </label>
<input id="migrate-to-{{n.pk}}" type="radio" name="to_node" value="{{ n.pk }}" style="float: right;" <input id="migrate-to-{{n.pk}}" type="radio" name="to_node" value="{{ n.pk }}" style="float: right;"
{% if current == n.pk %}disabled="disabled"{% endif %} {% if current == n.pk %}disabled="disabled"{% endif %}
{% if recommended == n.pk %}checked="checked"{% endif %} {% if recommended == n.pk and n.pk != current %}checked="checked"{% endif %}
/> />
{% if n.pk not in nodes_w_traits %}
<span class="vm-migrate-node-property">
{% trans "Node traits" %}:
{% if n.traits.all %}
{{ n.traits.all|join:", " }}
{% else %}
-
{% endif %}
</span>
<span class="vm-migrate-node-property">
{% trans "Required traits" %}:
{% if object.req_traits.all %}
{{ object.req_traits.all|join:", " }}
{% else %}
-
{% endif %}
</span>
<hr />
{% endif %}
<span class="vm-migrate-node-property">{% trans "CPU load" %}: {{ n.cpu_usage }}</span> <span class="vm-migrate-node-property">{% trans "CPU load" %}: {{ n.cpu_usage }}</span>
<span class="vm-migrate-node-property"> <span class="vm-migrate-node-property">
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}</span> {% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}</span>
......
...@@ -18,35 +18,53 @@ ...@@ -18,35 +18,53 @@
{% block navbar %} {% block navbar %}
{% if user.is_authenticated and user.pk and not request.token_user %} {% if user.is_authenticated and user.pk and not request.token_user %}
<ul class="nav navbar-nav pull-right"> <ul class="nav navbar-nav navbar-right" id="dashboard-menu">
<li class="dropdown" id="notification-button"> {% if user.is_superuser %}
<a href="{% url "dashboard.views.notifications" %}" <li>
class="dropdown-toggle" data-toggle="dropdown"> <a href="/admin/"><i class="fa fa-cogs"></i> {% trans "Admin" %}</a>
</li>
<li>
<a href="/network/"><i class="fa fa-globe"></i> {% trans "Network" %}</a>
</li>
{% endif %}
<li>
<a href="{% url "dashboard.views.profile-preferences" %}">
<i class="fa fa-user"></i>
{% include "dashboard/_display-name.html" with user=user show_org=True %}
</a>
</li>
<li>
<a href="{% url "logout" %}?next={% url "login" %}">
<i class="fa fa-sign-out"></i> {% trans "Log out" %}
</a>
</li>
<li class="visible-xs">
<a href="{% url "dashboard.views.notifications" %}">
{% trans "Notifications" %} {% trans "Notifications" %}
{% if NEW_NOTIFICATIONS_COUNT > 0 %} {% if NEW_NOTIFICATIONS_COUNT > 0 %}
<span class="badge badge-pulse">{{ NEW_NOTIFICATIONS_COUNT }}</span> <span class="badge badge-pulse">{{ NEW_NOTIFICATIONS_COUNT }}</span>
{% endif %} {% endif %}
</a> </a>
</li>
<li class="dropdown hidden-xs" id="notification-button">
<a href="{% url "dashboard.views.notifications" %}"
class="dropdown-toggle" data-toggle="dropdown"
id="notification_count" data-notifications="{{ NEW_NOTIFICATIONS_COUNT }}">
{% trans "Notifications" %}
{% if NEW_NOTIFICATIONS_COUNT > 0 %}
<span class="badge badge-pulse">
{{ NEW_NOTIFICATIONS_COUNT }}
</span>
{% endif %}
</a>
<ul class="dropdown-menu" id="notification-messages"> <ul class="dropdown-menu" id="notification-messages">
<li>{% trans "Loading..." %}</li> <li>{% trans "Loading..." %}</li>
</ul> </ul>
</li> </li>
</ul> </ul>
<a class="navbar-brand pull-right" href="{% url "logout" %}?next={% url "login" %}" style="color: white; font-size: 10px;">
<i class="fa fa-sign-out"></i> {% trans "Log out" %}
</a>
<a class="navbar-brand pull-right" href="{% url "dashboard.views.profile-preferences" %}" style="color: white; font-size: 10px;">
<i class="fa fa-user"></i>
{% include "dashboard/_display-name.html" with user=user show_org=True %}
</a>
{% if user.is_superuser %}
<a class="navbar-brand pull-right" href="/network/" style="color: white; font-size: 10px;"><i class="fa fa-globe"></i> {% trans "Network" %}</a>
<a class="navbar-brand pull-right" href="/admin/" style="color: white; font-size: 10px;"><i class="fa fa-cogs"></i> {% trans "Admin" %}</a>
{% endif %}
{% else %} {% else %}
<a class="navbar-brand pull-right" href="{% url "login" %}?next={{ request.path }}" style="color: white; font-size: 10px;"><i class="fa fa-sign-in"></i> {% trans "Log in " %}</a> <a class="navbar-brand pull-right" href="{% url "login" %}?next={{ request.path }}"><i class="fa fa-sign-in"></i> {% trans "Log in " %}</a>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
...@@ -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;">
<form action="{{ request.path }}" method="POST">
{% csrf_token %}
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
<button id="confirmation-modal-button" type="button" class="btn btn-danger" <input type="hidden" name="next" value="{{ request.GET.next }}"/>
<button class="btn btn-danger"
{% if disable_submit %}disabled{% endif %} {% if disable_submit %}disabled{% endif %}
>{% trans "Delete" %}</button> >{% 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 %}
{% blocktrans with group=object member=member %}
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>? Are you sure you want to delete <strong>{{ object }}</strong>?
{%endblocktrans%} {% 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 %}
...@@ -9,43 +9,33 @@ ...@@ -9,43 +9,33 @@
<div class="body-content"> <div class="body-content">
<div class="page-header"> <div class="page-header">
<div class="pull-right" style="padding-top: 15px;"> <div class="pull-right" style="padding-top: 15px;">
<a title="{% trans "Rename" %}" href="#" class="btn btn-default btn-xs group-details-rename-button"> <a title="{% trans "Rename" %}" class="btn btn-default btn-xs group-details-rename-button">
<i class="fa fa-pencil"></i> <i class="fa fa-pencil"></i>
</a> </a>
<a title="{% trans "Delete" %}" data-group-pk="{{ group.pk }}" class="btn btn-default btn-xs real-link group-delete" href="{% url "dashboard.views.delete-group" pk=group.pk %}"> <a title="{% trans "Delete" %}" data-group-pk="{{ group.pk }}" class="btn btn-default btn-xs real-link group-delete" href="{% url "dashboard.views.delete-group" pk=group.pk %}">
<i class="fa fa-trash-o"></i> <i class="fa fa-trash-o"></i>
</a> </a>
<a title="{% trans "Help" %}" href="#" class="btn btn-default btn-xs group-details-help-button">
<i class="fa fa-question"></i>
</a>
</div> </div>
<h1> <h1>
<div id="group-details-rename"> <form action="" method="POST" id="group-details-rename-form" class="js-hidden">
<form action="" method="POST" id="group-details-rename-form">
{% csrf_token %} {% csrf_token %}
<input id="group-details-rename-name" class="form-control" name="new_name" type="text" value="{{ group.name }}"/> <div class="input-group">
<button type="submit" id="group-details-rename-submit" class="btn">{% trans "Rename" %}</button> <input id="group-details-rename-name" class="form-control" name="new_name"
</form> type="text" value="{{ group.name }}" required />
<span class="input-group-btn">
<button type="submit" id="group-details-rename-submit" class="btn">
{% trans "Rename" %}
</button>
</span>
</div> </div>
</form>
<div id="group-details-h1-name"> <div id="group-details-h1-name">
{{ group.name }} <span class="no-js-hidden">{{ group.name }}</span>
{% if group.groupprofile.org_id %} {% if group.groupprofile.org_id %}
<small>{{group.groupprofile.org_id}}</small> <small>{{group.groupprofile.org_id}}</small>
{% endif %} {% endif %}
</div> </div>
</h1> </h1>
<div class="group-details-help js-hidden">
<ul style="list-style: none;">
<li>
<strong>{% trans "Rename" %}:</strong>
{% trans "Change the name of the group." %}
</li>
<li>
<strong>{% trans "Delete" %}:</strong>
{% trans "Delete group." %}
</li>
</ul>
</div>
</div><!-- .page-header --> </div><!-- .page-header -->
<div class="row"> <div class="row">
<div class="col-md-12" id="group-detail-pane"> <div class="col-md-12" id="group-detail-pane">
...@@ -88,7 +78,7 @@ ...@@ -88,7 +78,7 @@
<h3> <h3>
{% trans "User list" %} {% trans "User list" %}
{% if perms.auth.add_user %} {% if perms.auth.add_user %}
<a href="{% url "dashboard.views.create-user" group.pk %}" class="btn btn-success pull-right"> <a href="{% url "dashboard.views.user-create" %}?group_pk={{ group.pk}}" class="btn btn-success pull-right">
{% trans "Create user" %} {% trans "Create user" %}
</a> </a>
{% endif %} {% endif %}
......
...@@ -16,9 +16,11 @@ ...@@ -16,9 +16,11 @@
<div class="panel-body"> <div class="panel-body">
<div id="table_container"> <div id="table_container">
<div id="rendered_table" class="panel-body"> <div id="rendered_table" class="panel-body">
<div class="table-responsive">
{% render_table table %} {% render_table table %}
</div> </div>
</div> </div>
</div>
</div><!-- .panel-body --> </div><!-- .panel-body -->
</div> </div>
</div> </div>
......
...@@ -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>
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
</div> </div>
<h3 class="no-margin"><i class="fa fa-group"></i> {% trans "Groups" %}</h3> <h3 class="no-margin"><i class="fa fa-group"></i> {% trans "Groups" %}</h3>
</div> </div>
<div class="list-group" id="vm-list-view"> <div class="list-group" id="group-list-view">
<div id="dashboard-group-list"> <div id="dashboard-group-list">
{% for i in groups %} {% for i in groups %}
<a href="{% url "dashboard.views.group-detail" pk=i.pk %}" class="list-group-item real-link <a href="{% url "dashboard.views.group-detail" pk=i.pk %}" class="list-group-item real-link
...@@ -15,14 +15,14 @@ ...@@ -15,14 +15,14 @@
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
<div href="#" class="list-group-item list-group-footer text-right"> <div class="list-group-item list-group-footer text-right">
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-6">
<form action="{% url "dashboard.views.group-list" %}" method="GET" id="dashboard-group-search-form"> <form action="{% url "dashboard.views.group-list" %}" method="GET" id="dashboard-group-search-form">
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<input id="dashboard-group-search-input" name="s" type="text" class="form-control" placeholder="{% trans "Search..." %}" /> <input id="dashboard-group-search-input" name="s" type="text" class="form-control" placeholder="{% trans "Search..." %}" />
<div class="input-group-btn"> <div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary"><i class="fa fa-search"></i></button> <button type="submit" class="btn btn-primary"><i class="fa fa-search"></i></button>
</div> </div>
</div> </div>
</form> </form>
......
...@@ -29,39 +29,15 @@ ...@@ -29,39 +29,15 @@
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
</div><!-- #node-list-view -->
<div class="panel-body" id="node-graph-view" style="display: none; min-height: 204px;">
<p class="pull-right">
<input class="knob" data-fgColor="chartreuse"
data-thickness=".4" data-width="60" data-height="60" data-readOnly="true"
value="{% widthratio node_num.running sum_node_num 100 %}">
</p>
<p>
<span class="big">
<big>{{ node_num.running }}</big> running
</span>
+ <big>{{ node_num.missing }}</big>
missing + <br><big>{{ node_num.disabled }}</big> disabled + <big>{{ node_num.offline }}</big> offline
</p>
<ul class="list-inline" id="dashboard-node-taglist">
{% for i in nodes %}
<a href="{{ i.get_absolute_url }}" class="label {{i.get_status_label}}" >
<i class="fa {{ i.get_status_icon }}" title="{{ i.get_status_display }}"></i> {{ i.name }}</a>
{% endfor %}
</ul>
<div class="clearfix"></div>
</div>
<div href="#" class="list-group-item list-group-footer"> <div class="list-group-item list-group-footer">
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-6">
<form action="{% url "dashboard.views.node-list" %}" method="GET" <form action="{% url "dashboard.views.node-list" %}" method="GET"
id="dashboard-node-search-form"> id="dashboard-node-search-form">
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<input id="dashboard-node-search-input" type="text" class="form-control" <input id="dashboard-node-search-input" type="text" class="form-control"
placeholder="{% trans "Search..." %}" /> name="s" placeholder="{% trans "Search..." %}" />
<div class="input-group-btn"> <div class="input-group-btn">
<button type="submit" class="btn btn-primary" title="{% trans "Search" %}" data-container="body"> <button type="submit" class="btn btn-primary" title="{% trans "Search" %}" data-container="body">
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
...@@ -87,4 +63,31 @@ ...@@ -87,4 +63,31 @@
</div> </div>
</div> </div>
</div> </div>
</div><!-- #node-list-view -->
<div class="panel-body" id="node-graph-view" style="display: none">
<p class="pull-right">
<input class="knob" data-fgColor="chartreuse"
data-thickness=".4" data-width="60" data-height="60" data-readOnly="true"
value="{% widthratio node_num.running sum_node_num 100 %}">
</p>
<p>
<span class="big">
<big>{{ node_num.running }}</big> running
</span>
+ <big>{{ node_num.missing }}</big>
missing + <br><big>{{ node_num.disabled }}</big> disabled + <big>{{ node_num.offline }}</big> offline
</p>
<ul class="list-inline" id="dashboard-node-taglist">
{% for i in nodes %}
<li>
<a href="{{ i.get_absolute_url }}" class="label {{i.get_status_label}}" >
<i class="fa {{ i.get_status_icon }}" title="{{ i.get_status_display }}"></i> {{ i.name }}</a>
</li>
{% endfor %}
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<h3 class="no-margin"><i class="fa fa-puzzle-piece"></i> {% trans "Templates" %} <h3 class="no-margin"><i class="fa fa-puzzle-piece"></i> {% trans "Templates" %}
</h3> </h3>
</div> </div>
<div class="list-group" id="dashboard-template-list"> <div class="list-group" id="template-list-view">
<div id="dashboard-template-list"> <div id="dashboard-template-list">
{% for t in templates %} {% for t in templates %}
<a href="{% url "dashboard.views.template-detail" pk=t.pk %}" class="list-group-item <a href="{% url "dashboard.views.template-detail" pk=t.pk %}" class="list-group-item
...@@ -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>
...@@ -32,15 +32,27 @@ ...@@ -32,15 +32,27 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div href="#" class="list-group-item list-group-footer text-right"> <div class="list-group-item list-group-footer">
<p> <div class="row">
<div class="col-xs-6">
<form action="{% url "dashboard.views.template-list" %}" method="GET" id="dashboard-template-search-form">
<div class="input-group input-group-sm">
<input id="dashboard-group-search-input" name="s" type="text" class="form-control" placeholder="{% trans "Search..." %}" />
<div class="input-group-btn">
<button type="submit" class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</form>
</div>
<div class="col-xs-6 text-right">
<a href="{% url "dashboard.views.template-list" %}" class="btn btn-primary btn-xs"> <a href="{% url "dashboard.views.template-list" %}" class="btn btn-primary btn-xs">
<i class="fa fa-chevron-circle-right"></i> {% trans "show all" %} <i class="fa fa-chevron-circle-right"></i> {% trans "show all" %}
</a> </a>
<a href="{% url "dashboard.views.template-choose" %}" class="btn btn-success btn-xs template-choose"> <a href="{% url "dashboard.views.template-choose" %}" class="btn btn-success btn-xs template-choose">
<i class="fa fa-plus-circle"></i> {% trans "new" %} <i class="fa fa-plus-circle"></i> {% trans "new" %}
</a> </a>
</p> </div>
</div>
</div> </div>
</div> </div>
</div> </div>
{% load i18n %}
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right toolbar">
<span class="btn btn-default btn-xs infobtn" data-container="body" title="{% trans "List of CIRCLE users." %}"><i class="fa fa-info-circle"></i></span>
</div>
<h3 class="no-margin"><i class="fa fa-users"></i> {% trans "Users" %}</h3>
</div>
<div class="list-group" id="user-list-view">
<div id="dashboard-user-list">
{% for i in users %}
<a href="{% url "dashboard.views.profile" username=i.username %}" class="list-group-item real-link
{% if forloop.last and users|length < 5 %} list-group-item-last{% endif %}">
<span class="index-user-list-name">
<i class="fa fa-user"></i> {% firstof i.get_full_name|safe i.username|safe %}
</span>
<span class="index-user-list-org">
<small class="text-muted"> {{ i.profile.org_id|default:"" }}</small>
</span>
</a>
{% endfor %}
</div>
<div class="list-group-item list-group-footer text-right">
<div class="row">
<div class="col-xs-6">
<form action="{% url "dashboard.views.user-list" %}" method="GET" id="dashboard-user-search-form">
<div class="input-group input-group-sm">
<input id="dashboard-group-search-input" name="s" type="text" class="form-control" placeholder="{% trans "Search..." %}" />
<div class="input-group-btn">
<button type="submit" class="btn btn-primary"><i class="fa fa-search"></i></button>
</div>
</div>
</form>
</div>
<div class="col-xs-6 text-right">
<a class="btn btn-primary btn-xs" href="{% url "dashboard.views.user-list" %}">
<i class="fa fa-chevron-circle-right"></i>
{% if more_users > 0 %}
{% blocktrans count more=more_users %}
<strong>{{ more }}</strong> more
{% plural %}
<strong>{{ more }}</strong> more
{% endblocktrans %}
{% else %}
{% trans "list" %}
{% endif %}
</a>
<a class="btn btn-success btn-xs user-create" href="{% url "dashboard.views.user-create" %}"><i class="fa fa-plus-circle"></i> {% trans "new" %} </a>
</div>
</div>
</div>
</div>
</div>
...@@ -13,7 +13,14 @@ ...@@ -13,7 +13,14 @@
<span class="btn btn-default btn-xs infobtn" data-container="body" title="{% trans "List of your current virtual machines. Favourited ones are ahead of others." %}"><i class="fa fa-info-circle"></i></span> <span class="btn btn-default btn-xs infobtn" data-container="body" title="{% trans "List of your current virtual machines. Favourited ones are ahead of others." %}"><i class="fa fa-info-circle"></i></span>
</div> </div>
<h3 class="no-margin"> <h3 class="no-margin">
<i class="fa fa-desktop"></i> {% trans "Virtual machines" %} <span class="visible-xs">
<i class="fa fa-desktop"></i>
{% trans "VMs" %}
</span>
<span class="hidden-xs">
<i class="fa fa-desktop"></i>
{% trans "Virtual machines" %}
</span>
</h3> </h3>
</div> </div>
<div class="list-group" id="vm-list-view"> <div class="list-group" id="vm-list-view">
...@@ -25,7 +32,7 @@ ...@@ -25,7 +32,7 @@
<i class="fa {{ i.get_status_icon }}" title="{{ i.get_status_display }}"></i> <i class="fa {{ i.get_status_icon }}" title="{{ i.get_status_display }}"></i>
{{ i.name }} {{ i.name }}
</span> </span>
<small class="text-muted"> <small class="text-muted index-vm-list-host">
{% if i.owner == request.user %}{{ i.short_hostname }} {% if i.owner == request.user %}{{ i.short_hostname }}
{% else %}{{i.owner.profile.get_display_name}}{% endif %} {% else %}{{i.owner.profile.get_display_name}}{% endif %}
</small> </small>
...@@ -44,7 +51,7 @@ ...@@ -44,7 +51,7 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div href="#" class="list-group-item list-group-footer"> <div class="list-group-item list-group-footer">
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-6">
<form action="{% url "dashboard.views.vm-list" %}" method="GET" id="dashboard-vm-search-form"> <form action="{% url "dashboard.views.vm-list" %}" method="GET" id="dashboard-vm-search-form">
...@@ -52,7 +59,7 @@ ...@@ -52,7 +59,7 @@
<input id="dashboard-vm-search-input" type="text" class="form-control" name="s" <input id="dashboard-vm-search-input" type="text" class="form-control" name="s"
placeholder="{% trans "Search..." %}" /> placeholder="{% trans "Search..." %}" />
<div class="input-group-btn"> <div class="input-group-btn">
<button type="submit" class="form-control btn btn-primary"><i class="fa fa-search"></i></button> <button type="submit" class="btn btn-primary"><i class="fa fa-search"></i></button>
</div> </div>
</div> </div>
</form> </form>
...@@ -79,7 +86,7 @@ ...@@ -79,7 +86,7 @@
<p class="pull-right"> <p class="pull-right">
<input class="knob" data-fgColor="chartreuse" data-thickness=".4" data-max="{{ request.user.profile.instance_limit }}" data-width="100" data-height="100" data-readOnly="true" value="{{ instances|length|add:more_instances }}"> <input class="knob" data-fgColor="chartreuse" data-thickness=".4" data-max="{{ request.user.profile.instance_limit }}" data-width="100" data-height="100" data-readOnly="true" value="{{ instances|length|add:more_instances }}">
</p> </p>
<p><span class="bigbig">{% blocktrans with count=running_vm_num %}<big>{{ count }}</big> running{% endblocktrans %}</span> <span class="bigbig">{% blocktrans with count=running_vm_num %}<big>{{ count }}</big> running{% endblocktrans %}</span>
<ul class="list-inline" style="max-height: 95px; overflow: hidden;"> <ul class="list-inline" style="max-height: 95px; overflow: hidden;">
{% for vm in running_vms %} {% for vm in running_vms %}
<li style="display: inline-block; padding: 2px;"> <li style="display: inline-block; padding: 2px;">
...@@ -89,7 +96,6 @@ ...@@ -89,7 +96,6 @@
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</p>
<div class="clearfix"></div> <div class="clearfix"></div>
<div> <div>
......
...@@ -48,6 +48,12 @@ ...@@ -48,6 +48,12 @@
{% include "dashboard/index-nodes.html" %} {% include "dashboard/index-nodes.html" %}
</div> </div>
{% endif %} {% endif %}
{% if perms.auth.change_user %}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-users.html" %}
</div>
{% endif %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
<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>
...@@ -73,14 +73,15 @@ ...@@ -73,14 +73,15 @@
</a> </a>
</li> </li>
<li> <li>
<a href="{% url "dashboard.views.vm-list" %}?s=node:{{ node.name }}" <a href="{% url "dashboard.views.vm-list" %}?s=node_exact:{{ node.name }}"
target="blank" class="text-center"> target="blank" class="text-center">
<i class="fa fa-desktop fa-2x"></i><br> <i class="fa fa-desktop fa-2x"></i><br>
{% trans "Virtual Machines" %} {% trans "Virtual Machines" %}
</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 }}
</span>
&ndash; &ndash;
{% if s.finished %} {% if s.finished %}
{{ s.finished|time:"H:i:s" }} {{ s.finished|time:"H:i:s" }}
...@@ -25,7 +26,7 @@ ...@@ -25,7 +26,7 @@
<i class="fa fa-refresh fa-spin" class="sub-activity-loading-icon"></i> <i class="fa fa-refresh fa-spin" class="sub-activity-loading-icon"></i>
{% endif %} {% endif %}
{% if s.has_failed %} {% if s.has_failed %}
<div title="{{ s.result.get_admin_text }}" class="label label-danger">{% trans "failed" %}</div> <div class="label label-danger">{% trans "failed" %}</div>
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
......
...@@ -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>
...@@ -7,8 +7,9 @@ ...@@ -7,8 +7,9 @@
<dt>{% trans "RAM size" %}:</dt> <dd>{% widthratio node.info.ram_size 1048576 1 %} MiB</dd> <dt>{% trans "RAM size" %}:</dt> <dd>{% widthratio node.info.ram_size 1048576 1 %} MiB</dd>
<dt>{% trans "Architecture" %}:</dt><dd>{{ node.info.architecture }}</dd> <dt>{% trans "Architecture" %}:</dt><dd>{{ node.info.architecture }}</dd>
<dt>{% trans "Host IP" %}:</dt><dd>{{ node.host.ipv4 }}</dd> <dt>{% trans "Host IP" %}:</dt><dd>{{ node.host.ipv4 }}</dd>
<dt>{% trans "Enabled" %}:</dt><dd>{{ node.enabled }}</dd> <dt>{% trans "Enabled" %}:</dt><dd>{{ node.enabled|yesno }}</dd>
<dt>{% trans "Host online" %}:</dt><dd> {{ node.online }}</dd> <dt>{% trans "Host online" %}:</dt><dd> {{ node.online|yesno }}</dd>
<dt>{% trans "Minion online" %}:</dt><dd> {{ node.minion_online|yesno }}</dd>
<dt>{% trans "Priority" %}:</dt><dd>{{ node.priority }}</dd> <dt>{% trans "Priority" %}:</dt><dd>{{ node.priority }}</dd>
<dt>{% trans "Driver Version:" %}</dt> <dt>{% trans "Driver Version:" %}</dt>
<dd> <dd>
......
...@@ -11,15 +11,17 @@ ...@@ -11,15 +11,17 @@
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="no-margin"><i class="fa fa-desktop"></i> {% trans "Compute nodes" %}</h3> <h3 class="no-margin"><i class="fa fa-sitemap"></i> {% trans "Compute nodes" %}</h3>
</div> </div>
<div id="table_container"> <div id="table_container">
<div id="rendered_table" class="panel-body"> <div id="rendered_table" class="panel-body">
<div class="table-responsive">
{% render_table table %} {% render_table table %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
<div class="row"> <div class="row">
......
{% load sizefieldtags %} {% load sizefieldtags %}
{% load i18n %} {% load i18n %}
<span class="{% if not record.enabled %}node-disabled{% endif %}"></span>
<i class="fa fa-gears"></i> {% trans "CPU" %} <i class="fa fa-gears"></i> {% trans "CPU" %}
<div class="progress pull-right"> <div class="progress pull-right">
<div class="progress-bar progress-bar-success" role="progressbar" <div class="progress-bar progress-bar-success" role="progressbar"
......
...@@ -6,6 +6,18 @@ ...@@ -6,6 +6,18 @@
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div id="notifications-upper-pagination" class="pull-right">
{% if page.has_previous %}
<a href="?page={{ page.previous_page_number }}">
<i class="fa fa-chevron-left"></i></a>
</a>
{% endif %}
{{ page.number }} / {{ paginator.num_pages }}
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}"><i class="fa fa-chevron-right"></i></a>
{% endif %}
</div>
<h3 class="no-margin"><i class="fa fa-desktop"></i> {% trans "Notifications" %}</h3> <h3 class="no-margin"><i class="fa fa-desktop"></i> {% trans "Notifications" %}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
...@@ -13,6 +25,29 @@ ...@@ -13,6 +25,29 @@
{% include "dashboard/_notifications-timeline.html" %} {% include "dashboard/_notifications-timeline.html" %}
</ul> </ul>
</div> </div>
<div class="panel-body text-center" id="notifications-bottom-pagination">
{% if page.has_previous %}
<a href="?page=1">
<i class="fa fa-angle-double-left"></i>
</a>
<a href="{% if page.has_previous %}?page={{ page.previous_page_number}}{% else %}#{% endif %}">
<i class="fa fa-angle-left"></i>
</a>
{% endif %}
<div class="page-numbers">
{{ page.number }} / {{ paginator.num_pages }}
</div>
{% if page.has_next %}
<a href="{% if page.has_next %}?page={{ page.next_page_number}}{% else %}#{% endif %}">
<i class="fa fa-angle-right"></i>
</a>
<a href="?page={{ paginator.num_pages }}">
<i class="fa fa-angle-double-right"></i>
</a>
{% endif %}
</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -19,8 +19,8 @@ Do you want to perform the following operation on ...@@ -19,8 +19,8 @@ Do you want to perform the following operation on
<div class="pull-right"> <div class="pull-right">
<a class="btn btn-default" href="{{object.get_absolute_url}}" <a class="btn btn-default" href="{{object.get_absolute_url}}"
data-dismiss="modal">{% trans "Cancel" %}</a> data-dismiss="modal">{% trans "Cancel" %}</a>
<button class="btn btn-{{ opview.effect }}" type="submit" id="op-form-send"> <button class="btn btn-{{ opview.effect }} btn-op-form-send" type="submit" id="op-form-send">
{% if opview.icon %}<i class="fa fa-{{opview.icon}}"></i> {% endif %}{{ op|capfirst }} {% if opview.icon %}<i class="fa fa-fw fa-{{opview.icon}}"></i> {% endif %}{{ op.name|capfirst }}
</button> </button>
</div> </div>
</form> </form>
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-{% if perms.auth.change_user %}8{% else %}12{% endif %}">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
title="{% trans "Log in as this user. Recommended to open in an incognito window." %}"> title="{% trans "Log in as this user. Recommended to open in an incognito window." %}">
{% trans "Login as this user" %}</a> {% trans "Login as this user" %}</a>
{% endif %} {% endif %}
<a class="pull-right btn btn-default btn-xs" href="{% url "dashboard.index" %}">{% trans "Back" %}</a> <a class="pull-right btn btn-default btn-xs" href="{% url "dashboard.views.user-list" %}">{% trans "Back" %}</a>
<h3 class="no-margin"> <h3 class="no-margin">
<i class="fa fa-user"></i> <i class="fa fa-user"></i>
{% include "dashboard/_display-name.html" with user=profile show_org=True %} {% include "dashboard/_display-name.html" with user=profile show_org=True %}
...@@ -109,6 +109,23 @@ ...@@ -109,6 +109,23 @@
</div> </div>
</div> </div>
</div> </div>
{% if perms.auth.change_user %}
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
<i class="fa fa-user"></i>
{% trans "Edit user" %}
</h3>
</div>
<div class="panel-body">
{% crispy form %}
</div>
</div>
</div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}
...@@ -54,7 +54,8 @@ ...@@ -54,7 +54,8 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<div class="list-group-item text-right no-hover"> <div class="list-group-item list-group-footer">
<div class="text-right">
<form class="pull-left" method="POST" action="{% url "dashboard.views.store-refresh-toplist" %}"> <form class="pull-left" method="POST" action="{% url "dashboard.views.store-refresh-toplist" %}">
{% csrf_token %} {% csrf_token %}
<button class="btn btn-success btn-xs" type="submit" title="{% trans "Refresh" %}"/> <button class="btn btn-success btn-xs" type="submit" title="{% trans "Refresh" %}"/>
...@@ -69,4 +70,5 @@ ...@@ -69,4 +70,5 @@
</a> </a>
</div> </div>
</div> </div>
</div>
</div> </div>
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<a href="{% url "dashboard.views.template-delete" pk=object.pk %}" <a href="{% url "dashboard.views.template-delete" pk=object.pk %}"
class="btn btn-xs btn-danger pull-right"> class="btn btn-xs btn-danger pull-right template-delete">
{% trans "Delete" %} {% trans "Delete" %}
</a> </a>
<h4 class="no-margin"><i class="fa fa-times"></i> {% trans "Delete template" %}</h4> <h4 class="no-margin"><i class="fa fa-times"></i> {% trans "Delete template" %}</h4>
......
...@@ -34,10 +34,12 @@ ...@@ -34,10 +34,12 @@
</div> </div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="table-responsive">
{% render_table table %} {% render_table table %}
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
{% if show_lease_table %} {% if show_lease_table %}
......
{% extends "dashboard/base.html" %}
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block content %} {% block content %}
{% crispy form %} {% crispy form %}
{% endblock %} {% endblock %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block title-page %}{% trans "Users" %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<a href="{% url "dashboard.views.user-create" %}" class="pull-right btn btn-success btn-xs">
<i class="fa fa-plus"></i> {% trans "new user" %}
</a>
<h3 class="no-margin"><i class="fa fa-user"></i> {% trans "Users" %}</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-offset-8 col-md-4" id="user-list-search">
<form action="" method="GET">
<div class="input-group">
{{ search_form.s }}
<div class="input-group-btn">
{{ search_form.stype }}
<button type="submit" class="btn btn-primary input-tags">
<i class="fa fa-search"></i>
</button>
</div>
</div><!-- .input-group -->
</form>
</div><!-- .col-md-4 #user-list-search -->
</div>
</div>
<div class="panel-body">
<div class="table-responsive">
{% render_table table %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
...@@ -207,19 +207,25 @@ ...@@ -207,19 +207,25 @@
{% 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>
</ul> </ul>
<div class="tab-content panel-body"> <div class="tab-content panel-body">
<div class="tab-pane active" id="_home">{% include "dashboard/vm-detail/home.html" %}</div> <div class="not-tab-pane active" id="_home">{% include "dashboard/vm-detail/home.html" %}</div>
<div class="tab-pane" id="_resources">{% include "dashboard/vm-detail/resources.html" %}</div> <hr class="js-hidden"/>
<div class="not-tab-pane" id="_resources">{% include "dashboard/vm-detail/resources.html" %}</div>
<div class="tab-pane" id="_console">{% include "dashboard/vm-detail/console.html" %}</div> <div class="tab-pane" id="_console">{% include "dashboard/vm-detail/console.html" %}</div>
<div class="tab-pane" id="_access">{% include "dashboard/vm-detail/access.html" %} </div> <hr class="js-hidden"/>
<div class="tab-pane" id="_network">{% include "dashboard/vm-detail/network.html" %}</div> <div class="not-tab-pane" id="_access">{% include "dashboard/vm-detail/access.html" %} </div>
<div class="tab-pane" id="_activity">{% include "dashboard/vm-detail/activity.html" %}</div> <hr class="js-hidden"/>
<div class="not-tab-pane" id="_network">{% include "dashboard/vm-detail/network.html" %}</div>
<hr class="js-hidden"/>
<div class="not-tab-pane" id="_activity">{% include "dashboard/vm-detail/activity.html" %}</div>
<hr class="js-hidden"/>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
{% if op.is_disk_operation %} {% if op.is_disk_operation %}
<a href="{{op.get_url}}" class="btn btn-success btn-xs <a href="{{op.get_url}}" class="btn btn-success btn-xs
operation operation-{{op.op}}"> operation operation-{{op.op}}">
<i class="fa fa-{{op.icon}}"></i> <i class="fa fa-{{op.icon}} fa-fw-12"></i>
{{op.name}} </a> {{op.name}} </a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
...@@ -13,7 +13,10 @@ ...@@ -13,7 +13,10 @@
<select class="form-control" name="proto" style="width: 70px;"><option>tcp</option><option>udp</option></select> <select class="form-control" name="proto" style="width: 70px;"><option>tcp</option><option>udp</option></select>
<div class="input-group-btn"> <div class="input-group-btn">
<button type="submit" class="btn btn-success btn-sm <button type="submit" class="btn btn-success btn-sm
{% if not is_operator %}disabled{% endif %}">{% trans "Add" %}</button> {% if not is_operator %}disabled{% endif %}">
<span class="hidden-xs">{% trans "Add" %}</span>
<span class="visible-xs"><i class="fa fa-plus-circle"></i></span>
</button>
</div> </div>
</div> </div>
</form> </form>
......
...@@ -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 %}
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
</div> </div>
</div> </div>
</h3> </h3>
<div class="clearfix"></div>
{% if not instance.disks.all %} {% if not instance.disks.all %}
{% trans "No disks are added." %} {% trans "No disks are added." %}
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
</div><!-- .row --> </div><!-- .row -->
</div><!-- .panel-body --> </div><!-- .panel-body -->
<div class="panel-body"> <div class="panel-body">
<div class="table-responsive">
<table class="table table-bordered table-striped table-hover vm-list-table" <table class="table table-bordered table-striped table-hover vm-list-table"
id="vm-list-table"> id="vm-list-table">
<thead><tr> <thead><tr>
...@@ -140,6 +141,7 @@ ...@@ -140,6 +141,7 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div><!-- .table-responsive -->
</div> </div>
</div> </div>
</div> </div>
......
{% extends "django_tables2/table.html" %}
{% load i18n %}
{% block pagination.cardinality %}{% endblock %}
{% block pagination.current %}
<li></li>
<li class="pull-right">
<a>
{% blocktrans with table.page.number as current and table.paginator.num_pages as total %}
{{ current }}/{{ total }}
{% endblocktrans %}
</a>
</li>
<li></li>
{% endblock %}
...@@ -389,6 +389,7 @@ class RenewViewTest(unittest.TestCase): ...@@ -389,6 +389,7 @@ class RenewViewTest(unittest.TestCase):
inst = MagicMock(spec=Instance) inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance" inst._meta.object_name = "Instance"
inst.name = 'foo' inst.name = 'foo'
inst.lease = MagicMock(pk=99)
inst.renew = Instance._ops['renew'](inst) inst.renew = Instance._ops['renew'](inst)
inst.has_level.return_value = True inst.has_level.return_value = True
go.return_value = inst go.return_value = inst
...@@ -403,6 +404,7 @@ class RenewViewTest(unittest.TestCase): ...@@ -403,6 +404,7 @@ class RenewViewTest(unittest.TestCase):
patch('dashboard.views.util.messages') as msg: patch('dashboard.views.util.messages') as msg:
inst = MagicMock(spec=Instance) inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance" inst._meta.object_name = "Instance"
inst.lease = MagicMock(pk=99)
inst.renew = Instance._ops['renew'](inst) inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock() inst.renew.async = MagicMock()
inst.has_level.return_value = True inst.has_level.return_value = True
...@@ -421,6 +423,7 @@ class RenewViewTest(unittest.TestCase): ...@@ -421,6 +423,7 @@ class RenewViewTest(unittest.TestCase):
patch('dashboard.views.util.messages') as msg: patch('dashboard.views.util.messages') as msg:
inst = MagicMock(spec=Instance) inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance" inst._meta.object_name = "Instance"
inst.lease = MagicMock(pk=99)
inst.renew = Instance._ops['renew'](inst) inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock() inst.renew.async = MagicMock()
inst.has_level.return_value = True inst.has_level.return_value = True
...@@ -463,6 +466,7 @@ class RenewViewTest(unittest.TestCase): ...@@ -463,6 +466,7 @@ class RenewViewTest(unittest.TestCase):
with patch.object(view, 'get_object') as go: with patch.object(view, 'get_object') as go:
inst = MagicMock(spec=Instance, pk=11) inst = MagicMock(spec=Instance, pk=11)
inst._meta.object_name = "Instance" inst._meta.object_name = "Instance"
inst.lease = MagicMock(pk=99)
inst.renew = Instance._ops['renew'](inst) inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock() inst.renew.async = MagicMock()
inst.has_level.return_value = False inst.has_level.return_value = False
......
...@@ -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()
...@@ -1335,24 +1293,26 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1335,24 +1293,26 @@ class GroupDetailTest(LoginMixin, TestCase):
self.login(c, 'user1') self.login(c, 'user1')
self.u1.user_permissions.add(Permission.objects.get( self.u1.user_permissions.add(Permission.objects.get(
name='Can add user')) name='Can add user'))
response = c.post('/dashboard/group/%d/create/' % self.g1.pk, response = c.post('/dashboard/profile/create/',
{'username': 'userx1', {'username': 'userx1',
'groups': self.g1.pk,
'password1': 'test123', 'password1': 'test123',
'password2': 'test123'}) 'password2': 'test123'})
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 200)
self.assertEqual(user_count, self.g1.user_set.count()) self.assertEqual(user_count, self.g1.user_set.count())
def test_permitted_user_add_wo_can_add_user_perm(self): def test_permitted_user_add_wo_can_add_user_perm(self):
user_count = self.g1.user_set.count() user_count = self.g1.user_set.count()
c = Client() c = Client()
self.login(c, 'user0') self.login(c, 'user0')
response = c.post('/dashboard/group/%d/create/' % self.g1.pk, response = c.post('/dashboard/profile/create/',
{'username': 'userx2', {'username': 'userx2',
'groups': self.g1.pk,
'password1': 'test123', 'password1': 'test123',
'password2': 'test123'}) 'password2': 'test123'})
self.assertRedirects( self.assertRedirects(
response, response,
'/accounts/login/?next=/dashboard/group/%d/create/' % self.g1.pk) '/accounts/login/?next=/dashboard/profile/create/')
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(user_count, self.g1.user_set.count()) self.assertEqual(user_count, self.g1.user_set.count())
...@@ -1362,11 +1322,12 @@ class GroupDetailTest(LoginMixin, TestCase): ...@@ -1362,11 +1322,12 @@ class GroupDetailTest(LoginMixin, TestCase):
name='Can add user')) name='Can add user'))
c = Client() c = Client()
self.login(c, 'user0') self.login(c, 'user0')
response = c.post('/dashboard/group/%d/create/' % self.g1.pk, response = c.post('/dashboard/profile/create/',
{'username': 'userx2', {'username': 'userx2',
'groups': self.g1.pk,
'password1': 'test123', 'password1': 'test123',
'password2': 'test123'}) 'password2': 'test123'})
self.assertRedirects(response, '/dashboard/group/%d/' % self.g1.pk) self.assertRedirects(response, '/dashboard/profile/userx2/')
self.assertEqual(user_count + 1, self.g1.user_set.count()) self.assertEqual(user_count + 1, self.g1.user_set.count())
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
...@@ -1484,38 +1445,6 @@ class TransferOwnershipViewTest(LoginMixin, TestCase): ...@@ -1484,38 +1445,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,8 @@ from .views import ( ...@@ -51,6 +51,8 @@ from .views import (
TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView, TransferInstanceOwnershipView, TransferInstanceOwnershipConfirmView,
TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView, TransferTemplateOwnershipView, TransferTemplateOwnershipConfirmView,
OpenSearchDescriptionView, OpenSearchDescriptionView,
NodeActivityView,
UserList,
) )
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
...@@ -60,6 +62,11 @@ autocomplete_light.autodiscover() ...@@ -60,6 +62,11 @@ autocomplete_light.autodiscover()
urlpatterns = patterns( urlpatterns = patterns(
'', '',
url(r'^$', IndexView.as_view(), name="dashboard.index"), url(r'^$', IndexView.as_view(), name="dashboard.index"),
url(r"^profile/list/$", UserList.as_view(),
name="dashboard.views.user-list"),
url(r'^profile/create/$',
UserCreationView.as_view(),
name="dashboard.views.user-create"),
url(r'^lease/(?P<pk>\d+)/$', LeaseDetail.as_view(), url(r'^lease/(?P<pk>\d+)/$', LeaseDetail.as_view(),
name="dashboard.views.lease-detail"), name="dashboard.views.lease-detail"),
url(r'^lease/create/$', LeaseCreate.as_view(), url(r'^lease/create/$', LeaseCreate.as_view(),
...@@ -94,7 +101,8 @@ urlpatterns = patterns( ...@@ -94,7 +101,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 +127,8 @@ urlpatterns = patterns( ...@@ -119,8 +127,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 +164,6 @@ urlpatterns = patterns( ...@@ -156,9 +164,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(),
...@@ -175,9 +180,6 @@ urlpatterns = patterns( ...@@ -175,9 +180,6 @@ urlpatterns = patterns(
name="dashboard.views.remove-future-user"), name="dashboard.views.remove-future-user"),
url(r'^group/create/$', GroupCreate.as_view(), url(r'^group/create/$', GroupCreate.as_view(),
name='dashboard.views.group-create'), name='dashboard.views.group-create'),
url(r'^group/(?P<group_pk>\d+)/create/$',
UserCreationView.as_view(),
name="dashboard.views.create-user"),
url(r'^group/(?P<group_pk>\d+)/permissions/$', url(r'^group/(?P<group_pk>\d+)/permissions/$',
GroupPermissionsView.as_view(), GroupPermissionsView.as_view(),
name="dashboard.views.group-permissions"), name="dashboard.views.group-permissions"),
......
...@@ -62,7 +62,7 @@ class GraphViewBase(LoginRequiredMixin, View): ...@@ -62,7 +62,7 @@ class GraphViewBase(LoginRequiredMixin, View):
metric = self.create_class(metric)(instance) metric = self.create_class(metric)(instance)
return HttpResponse(metric.get_graph(graphite_url, time), return HttpResponse(metric.get_graph(graphite_url, time),
mimetype="image/png") content_type="image/png")
def get_object(self, request, pk): def get_object(self, request, pk):
instance = self.model.objects.get(id=pk) instance = self.model.objects.get(id=pk)
......
...@@ -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,10 +247,6 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView): ...@@ -243,10 +247,6 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView):
return context return context
def get_success_url(self): def get_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy("dashboard.views.group-detail", return reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.get_object().pk}) kwargs={'pk': self.get_object().pk})
...@@ -254,39 +254,17 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView): ...@@ -254,39 +254,17 @@ class GroupRemoveUserView(CheckedDetailView, DeleteView):
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(CheckedDetailView, DeleteView):
"""This stuff deletes the group. class GroupDelete(DeleteViewBase):
"""
model = Group model = Group
template_name = "dashboard/confirm/base-delete.html"
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 delete(self, request, *args, **kwargs):
object = self.get_object()
if not object.profile.has_level(request.user, 'owner'):
raise PermissionDenied()
object.delete()
success_url = self.get_success_url()
success_message = _("Group successfully deleted.") success_message = _("Group successfully deleted.")
if request.is_ajax(): def check_auth(self):
if request.POST.get('redirect').lower() == "true": if not self.get_object().profile.has_level(self.request.user, 'owner'):
messages.success(request, success_message) raise PermissionDenied()
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']
......
...@@ -21,7 +21,7 @@ import logging ...@@ -21,7 +21,7 @@ import logging
from django.core.cache import get_cache from django.core.cache import get_cache
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group from django.contrib.auth.models import Group, User
from django.views.generic import TemplateView from django.views.generic import TemplateView
from braces.views import LoginRequiredMixin from braces.views import LoginRequiredMixin
...@@ -86,6 +86,14 @@ class IndexView(LoginRequiredMixin, TemplateView): ...@@ -86,6 +86,14 @@ class IndexView(LoginRequiredMixin, TemplateView):
'more_groups': groups.count() - len(groups[:5]), 'more_groups': groups.count() - len(groups[:5]),
}) })
# users
if user.has_module_perms('auth.change_user'):
users = User.objects.all()
context.update({
'users': users[:5],
'more_users': users.count() - len(users[:5]),
})
# template # template
if user.has_perm('vm.create_template'): if user.has_perm('vm.create_template'):
context['templates'] = InstanceTemplate.get_objects_with_level( context['templates'] = InstanceTemplate.get_objects_with_level(
......
This source diff could not be displayed because it is too large. You can view the blob instead.
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