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;
}
}
/* vm operations */ $('a[href="#activity"]').click(function(){
$('a[href="#activity"] i').addClass('fa-spin');
if(!in_progress) {
checkNewActivity(1);
in_progress = true;
}
});
$("#activity-refresh").on("click", "#show-all-activities", function() {
$(this).find("i").addClass("fa-spinner fa-spin");
show_all = !show_all;
$('a[href="#activity"]').trigger("click");
return false;
});
/* operations */
$('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface, .operation-wrapper').on('click', '.operation', function(e) { $('#ops, #vm-details-resources-disk, #vm-details-renew-op, #vm-details-pw-reset, #vm-details-add-interface, .operation-wrapper').on('click', '.operation', function(e) {
var icon = $(this).children("i").addClass('fa-spinner fa-spin'); var icon = $(this).children("i").addClass('fa-spinner fa-spin');
...@@ -23,7 +48,7 @@ $(function() { ...@@ -23,7 +48,7 @@ $(function() {
}); });
/* if the operation fails show the modal again */ /* if the operation fails show the modal again */
$("body").on("click", "#op-form-send", function() { $("body").on("click", "#confirmation-modal #op-form-send", function() {
var url = $(this).closest("form").prop("action"); var url = $(this).closest("form").prop("action");
$.ajax({ $.ajax({
...@@ -77,4 +102,91 @@ $(function() { ...@@ -77,4 +102,91 @@ $(function() {
return false; return false;
}); });
function decideActivityRefresh() {
var check = false;
/* if something is still spinning */
if($('.timeline .activity i').hasClass('fa-spin'))
check = true;
return check;
}
function checkNewActivity(runs) {
$.ajax({
type: 'GET',
url: $('a[href="#activity"]').attr('data-activity-url'),
data: {'show_all': show_all},
success: function(data) {
var new_activity_hash = (data.activities + "").hashCode();
if(new_activity_hash != activity_hash) {
$("#activity-refresh").html(data.activities);
}
activity_hash = new_activity_hash;
$("#ops").html(data.ops);
$("#disk-ops").html(data.disk_ops);
$("[title]").tooltip();
/* changing the status text */
var icon = $("#vm-details-state i");
if(data.is_new_state) {
if(!icon.hasClass("fa-spin"))
icon.prop("class", "fa fa-spinner fa-spin");
} else {
icon.prop("class", "fa " + data.icon);
}
var vm_state = $("#vm-details-state");
if (vm_state.length) {
vm_state.data("status", data['status']); // 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 */