Commit c10c1a49 by Bach Dániel

dashboard: add broadcast message support

parent ff91b0ff
......@@ -282,6 +282,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
'dashboard.context_processors.notifications',
'dashboard.context_processors.extract_settings',
'dashboard.context_processors.broadcast_messages',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
......
......@@ -21,7 +21,7 @@ from django import contrib
from django.contrib.auth.admin import UserAdmin, GroupAdmin
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):
......@@ -43,3 +43,5 @@ contrib.admin.site.unregister(User)
contrib.admin.site.register(User, UserAdmin)
contrib.admin.site.unregister(Group)
contrib.admin.site.register(Group, GroupAdmin)
contrib.admin.site.register(Message)
......@@ -16,6 +16,10 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.conf import settings
from django.db.models import Q
from django.utils import timezone
from .models import Message
def notifications(request):
......@@ -31,3 +35,10 @@ def extract_settings(request):
'COMPANY_NAME': getattr(settings, "COMPANY_NAME", None),
'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 (
from storage.models import DataStore, Disk
from django.contrib.admin.widgets import FilteredSelectMultiple
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 django.utils.translation import string_concat
......@@ -1624,3 +1624,15 @@ class DiskForm(ModelForm):
model = Disk
fields = ("name", "filename", "datastore", "type", "bus", "size",
"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():
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):
instance = ForeignKey("vm.Instance")
user = ForeignKey(User)
......
......@@ -527,3 +527,22 @@ function replaceTag(tag) {
function safe_tags_replace(str) {
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"] {
.little-margin-bottom {
margin-bottom: 5px;
}
.broadcast-message {
margin-bottom: 5px;
padding-top: 5px;
padding-bottom: 5px;
}
......@@ -29,7 +29,7 @@ from django_sshkey.models import UserKey
from storage.models import Disk
from vm.models import Node, InstanceTemplate, Lease
from dashboard.models import ConnectCommand
from dashboard.models import ConnectCommand, Message
class FileSizeColumn(Column):
......@@ -354,3 +354,18 @@ class DiskListTable(Table):
order_by = ("-pk", )
per_page = 15
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 staticfiles %}
{% load cache %}
{% load compressed %}
<!DOCTYPE html>
<html lang="{{lang}}">
......@@ -40,6 +41,22 @@
</div><!-- navbar navbar-inverse navbar-fixed-top -->
<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 %}
<div class="messagelist">
{% if messages %}
......
......@@ -27,6 +27,12 @@
<span class="hidden-sm">{% trans "Admin" %}</span>
</a>
</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 %}
<li>
<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 (
NodeActivityView,
UserList,
StorageDetail, DiskDetail,
MessageList, MessageDetail, MessageCreate, MessageDelete,
)
from .views.vm import vm_ops, vm_mass_ops
from .views.node import node_ops
......@@ -232,6 +233,15 @@ urlpatterns = patterns(
name="dashboard.views.storage"),
url(r'^disk/(?P<pk>\d+)/$', DiskDetail.as_view(),
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(
......
......@@ -14,3 +14,4 @@ from vm import *
from graph import *
from storage 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