Commit c10c1a49 by Bach Dániel

dashboard: add broadcast message support

parent ff91b0ff
...@@ -282,6 +282,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( ...@@ -282,6 +282,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request', 'django.core.context_processors.request',
'dashboard.context_processors.notifications', 'dashboard.context_processors.notifications',
'dashboard.context_processors.extract_settings', 'dashboard.context_processors.extract_settings',
'dashboard.context_processors.broadcast_messages',
) )
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
......
...@@ -21,7 +21,7 @@ from django import contrib ...@@ -21,7 +21,7 @@ from django import contrib
from django.contrib.auth.admin import UserAdmin, GroupAdmin from django.contrib.auth.admin import UserAdmin, GroupAdmin
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from dashboard.models import Profile, GroupProfile, ConnectCommand from dashboard.models import Profile, GroupProfile, ConnectCommand, Message
class ProfileInline(contrib.admin.TabularInline): class ProfileInline(contrib.admin.TabularInline):
...@@ -43,3 +43,5 @@ contrib.admin.site.unregister(User) ...@@ -43,3 +43,5 @@ contrib.admin.site.unregister(User)
contrib.admin.site.register(User, UserAdmin) contrib.admin.site.register(User, UserAdmin)
contrib.admin.site.unregister(Group) contrib.admin.site.unregister(Group)
contrib.admin.site.register(Group, GroupAdmin) contrib.admin.site.register(Group, GroupAdmin)
contrib.admin.site.register(Message)
...@@ -16,6 +16,10 @@ ...@@ -16,6 +16,10 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.conf import settings from django.conf import settings
from django.db.models import Q
from django.utils import timezone
from .models import Message
def notifications(request): def notifications(request):
...@@ -31,3 +35,10 @@ def extract_settings(request): ...@@ -31,3 +35,10 @@ def extract_settings(request):
'COMPANY_NAME': getattr(settings, "COMPANY_NAME", None), 'COMPANY_NAME': getattr(settings, "COMPANY_NAME", None),
'ADMIN_ENABLED': getattr(settings, "ADMIN_ENABLED", False), 'ADMIN_ENABLED': getattr(settings, "ADMIN_ENABLED", False),
} }
def broadcast_messages(request):
now = timezone.now()
messages = Message.objects.filter(enabled=True).exclude(
Q(starts_at__gt=now) | Q(ends_at__lt=now))
return {'broadcast_messages': messages}
...@@ -57,7 +57,7 @@ from vm.models import ( ...@@ -57,7 +57,7 @@ from vm.models import (
from storage.models import DataStore, Disk from storage.models import DataStore, Disk
from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from .models import Profile, GroupProfile from .models import Profile, GroupProfile, Message
from circle.settings.base import LANGUAGES, MAX_NODE_RAM from circle.settings.base import LANGUAGES, MAX_NODE_RAM
from django.utils.translation import string_concat from django.utils.translation import string_concat
...@@ -1624,3 +1624,15 @@ class DiskForm(ModelForm): ...@@ -1624,3 +1624,15 @@ class DiskForm(ModelForm):
model = Disk model = Disk
fields = ("name", "filename", "datastore", "type", "bus", "size", fields = ("name", "filename", "datastore", "type", "bus", "size",
"base", "dev_num", "destroyed", "is_ready", ) "base", "dev_num", "destroyed", "is_ready", )
class MessageForm(ModelForm):
class Meta:
model = Message
fields = ("message", "enabled", "effect", "starts_at", "ends_at")
@property
def helper(self):
helper = FormHelper()
helper.add_input(Submit("submit", _("Save")))
return helper
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
import model_utils.fields
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0002_auto_20150318_1317'),
]
operations = [
migrations.CreateModel(
name='Message',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('message', models.CharField(max_length=500, verbose_name='message')),
('starts_at', models.DateTimeField(null=True, verbose_name='starts at', blank=True)),
('ends_at', models.DateTimeField(null=True, verbose_name='ends at', blank=True)),
('effect', models.CharField(default=b'info', max_length=10, verbose_name='effect', choices=[(b'success', 'success'), (b'info', 'info'), (b'warning', 'warning'), (b'danger', 'danger')])),
('enabled', models.BooleanField(default=False, verbose_name='enabled')),
],
options={
'ordering': ['-ends_at'],
},
bases=(models.Model,),
),
]
...@@ -59,6 +59,31 @@ def pwgen(): ...@@ -59,6 +59,31 @@ def pwgen():
return User.objects.make_random_password() return User.objects.make_random_password()
class Message(TimeStampedModel):
message = CharField(max_length=500, verbose_name=_('message'))
starts_at = DateTimeField(
null=True, blank=True, verbose_name=_('starts at'))
ends_at = DateTimeField(
null=True, blank=True, verbose_name=_('ends at'))
effect = CharField(
default='info', max_length=10, verbose_name=_('effect'),
choices=(('success', _('success')), ('info', _('info')),
('warning', _('warning')), ('danger', _('danger'))))
enabled = BooleanField(default=False, verbose_name=_('enabled'))
class Meta:
ordering = ["id"]
verbose_name = _('message')
verbose_name_plural = _('messages')
def __unicode__(self):
return self.message
@permalink
def get_absolute_url(self):
return ('dashboard.views.message-detail', None, {'pk': self.pk})
class Favourite(Model): class Favourite(Model):
instance = ForeignKey("vm.Instance") instance = ForeignKey("vm.Instance")
user = ForeignKey(User) user = ForeignKey(User)
......
...@@ -527,3 +527,22 @@ function replaceTag(tag) { ...@@ -527,3 +527,22 @@ function replaceTag(tag) {
function safe_tags_replace(str) { function safe_tags_replace(str) {
return str.replace(/[&<>]/g, replaceTag); return str.replace(/[&<>]/g, replaceTag);
} }
$(function () {
var closed = JSON.parse(getCookie('broadcast-messages'));
$('.broadcast-message').each(function() {
var id = $(this).data('id');
if (closed && closed.indexOf(id) != -1) {
$(this).remove()
}
});
$('.broadcast-message').on('closed.bs.alert', function () {
var closed = JSON.parse(getCookie('broadcast-messages'));
if (!closed) {
closed = [];
}
closed.push($(this).data('id'));
setCookie('broadcast-messages', JSON.stringify(closed), 7 * 24 * 60 * 60 * 1000, "/");
});
});
...@@ -1315,3 +1315,9 @@ textarea[name="new_members"] { ...@@ -1315,3 +1315,9 @@ textarea[name="new_members"] {
.little-margin-bottom { .little-margin-bottom {
margin-bottom: 5px; margin-bottom: 5px;
} }
.broadcast-message {
margin-bottom: 5px;
padding-top: 5px;
padding-bottom: 5px;
}
...@@ -29,7 +29,7 @@ from django_sshkey.models import UserKey ...@@ -29,7 +29,7 @@ from django_sshkey.models import UserKey
from storage.models import Disk from storage.models import Disk
from vm.models import Node, InstanceTemplate, Lease from vm.models import Node, InstanceTemplate, Lease
from dashboard.models import ConnectCommand from dashboard.models import ConnectCommand, Message
class FileSizeColumn(Column): class FileSizeColumn(Column):
...@@ -354,3 +354,18 @@ class DiskListTable(Table): ...@@ -354,3 +354,18 @@ class DiskListTable(Table):
order_by = ("-pk", ) order_by = ("-pk", )
per_page = 15 per_page = 15
empty_text = _("No disk found.") empty_text = _("No disk found.")
class MessageListTable(Table):
message = LinkColumn(
'dashboard.views.message-detail',
args=[A('pk')],
attrs={'th': {'data-sort': "string"}}
)
class Meta:
model = Message
attrs = {'class': "table table-bordered table-striped table-hover",
'id': "disk-list-table"}
order_by = ("-pk", )
fields = ('pk', 'message', 'enabled', 'effect')
{% load i18n %} {% load i18n %}
{% load staticfiles %} {% load staticfiles %}
{% load cache %}
{% load compressed %} {% load compressed %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{lang}}"> <html lang="{{lang}}">
...@@ -40,6 +41,22 @@ ...@@ -40,6 +41,22 @@
</div><!-- navbar navbar-inverse navbar-fixed-top --> </div><!-- navbar navbar-inverse navbar-fixed-top -->
<div class="container"> <div class="container">
{% block broadcast_messages %}
{% cache 1 broadcast_messages %}
<div id="broadcast-messages">
{% for message in broadcast_messages %}
<div data-id={{ message.id }} class="alert alert-{{ message.effect }}
text-center broadcast-message">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
{{ message.message|safe }}
</div>
{% endfor %}
</div>
{% endcache %}
{% endblock broadcast_messages %}
{% block messages %} {% block messages %}
<div class="messagelist"> <div class="messagelist">
{% if messages %} {% if messages %}
......
...@@ -27,6 +27,12 @@ ...@@ -27,6 +27,12 @@
<span class="hidden-sm">{% trans "Admin" %}</span> <span class="hidden-sm">{% trans "Admin" %}</span>
</a> </a>
</li> </li>
<li>
<a href="{% url "dashboard.views.message-list" %}">
<i class="fa fa-bullhorn"></i>
<span class="hidden-sm">{% trans "Messages" %}</span>
</a>
</li>
{% endif %} {% endif %}
<li> <li>
<a href="{% url "dashboard.views.storage" %}"> <a href="{% url "dashboard.views.storage" %}">
......
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Broadcast Messages" %}{% 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.message-list" %}" class="btn btn-default btn-xs pull-right">
{% trans "Back" %}
</a>
<h3 class="no-margin">
<i class="fa fa-bullhorn"></i>
{% trans "New message" %}
</h3>
</div>
<div class="panel-body">
{% crispy form %}
</div><!-- .panel-body -->
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Broadcast Messages" %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
<a href="{% url "dashboard.views.message-list" %}"
class="btn btn-default btn-xs">
{% trans "Back" %}
</a>
<a href="{% url "dashboard.views.message-delete" pk=object.pk %}"
class="btn btn-danger btn-xs">
{% trans "Delete" %}
</a>
</div>
<h3 class="no-margin">
<i class="fa fa-bullhorn"></i>
{% trans "Edit message" %}
</h3>
</div>
<div class="panel-body">
{% crispy form %}
</div><!-- .panel-body -->
</div>
</div>
</div>
{% endblock %}
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% block title-page %}{% trans "Broadcast Messages" %}{% 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.message-create" %}" class="pull-right btn btn-success btn-xs">
<i class="fa fa-plus"></i> {% trans "new message" %}
</a>
<h3 class="no-margin"><i class="fa fa-bullhorn"></i> {% trans "Broadcast Messages" %}</h3>
</div>
<div class="panel-body">
<div class="table-responsive">
{% render_table table %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
...@@ -54,6 +54,7 @@ from .views import ( ...@@ -54,6 +54,7 @@ from .views import (
NodeActivityView, NodeActivityView,
UserList, UserList,
StorageDetail, DiskDetail, StorageDetail, DiskDetail,
MessageList, MessageDetail, MessageCreate, MessageDelete,
) )
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
...@@ -232,6 +233,15 @@ urlpatterns = patterns( ...@@ -232,6 +233,15 @@ urlpatterns = patterns(
name="dashboard.views.storage"), name="dashboard.views.storage"),
url(r'^disk/(?P<pk>\d+)/$', DiskDetail.as_view(), url(r'^disk/(?P<pk>\d+)/$', DiskDetail.as_view(),
name="dashboard.views.disk-detail"), name="dashboard.views.disk-detail"),
url(r'^message/list/$', MessageList.as_view(),
name="dashboard.views.message-list"),
url(r'^message/(?P<pk>\d+)/$', MessageDetail.as_view(),
name="dashboard.views.message-detail"),
url(r'^message/create/$', MessageCreate.as_view(),
name="dashboard.views.message-create"),
url(r'^message/delete/(?P<pk>\d+)/$', MessageDelete.as_view(),
name="dashboard.views.message-delete"),
) )
urlpatterns += patterns( urlpatterns += patterns(
......
...@@ -14,3 +14,4 @@ from vm import * ...@@ -14,3 +14,4 @@ from vm import *
from graph import * from graph import *
from storage import * from storage import *
from request import * from request import *
from message import *
from django.contrib.messages.views import SuccessMessageMixin
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.views.generic import CreateView, DeleteView, UpdateView
from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from django_tables2 import SingleTableView
from ..forms import MessageForm
from ..models import Message
from ..tables import MessageListTable
class MessageList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
template_name = "dashboard/message-list.html"
model = Message
table_class = MessageListTable
class MessageDetail(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, UpdateView):
model = Message
template_name = "dashboard/message-edit.html"
form_class = MessageForm
success_message = _("Broadcast message successfully updated.")
class MessageCreate(LoginRequiredMixin, SuperuserRequiredMixin,
SuccessMessageMixin, CreateView):
model = Message
template_name = "dashboard/message-create.html"
form_class = MessageForm
success_message = _("New broadcast message successfully created.")
class MessageDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
model = Message
template_name = "dashboard/confirm/base-delete.html"
def get_success_url(self):
return reverse("dashboard.views.message-list")
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