##// END OF EJS Templates
core: added user-notice logic to push notice messages....
ergo -
r4300:8f93504d default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -0,0 +1,35 b''
1 # -*- coding: utf-8 -*-
2
3 import logging
4 from sqlalchemy import *
5
6 from alembic.migration import MigrationContext
7 from alembic.operations import Operations
8 from sqlalchemy import BigInteger
9
10 from rhodecode.lib.dbmigrate.versions import _reset_base
11 from rhodecode.model import init_model_encryption
12
13
14 log = logging.getLogger(__name__)
15
16
17 def upgrade(migrate_engine):
18 """
19 Upgrade operations go here.
20 Don't create your own engine; bind migrate_engine to your metadata
21 """
22 _reset_base(migrate_engine)
23 from rhodecode.lib.dbmigrate.schema import db_4_19_0_0 as db
24
25 init_model_encryption(db)
26 db.UserNotice().__table__.create()
27
28
29 def downgrade(migrate_engine):
30 meta = MetaData()
31 meta.bind = migrate_engine
32
33
34 def fixups(models, _SESSION):
35 pass
@@ -45,7 +45,7 b' PYRAMID_SETTINGS = {}'
45 45 EXTENSIONS = {}
46 46
47 47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
48 __dbversion__ = 104 # defines current db version for migrations
48 __dbversion__ = 105 # defines current db version for migrations
49 49 __platform__ = platform.system()
50 50 __license__ = 'AGPLv3, and Commercial License'
51 51 __author__ = 'RhodeCode GmbH'
@@ -26,7 +26,6 b' def admin_routes(config):'
26 26 """
27 27 Admin prefixed routes
28 28 """
29
30 29 config.add_route(
31 30 name='admin_audit_logs',
32 31 pattern='/audit_logs')
@@ -291,6 +290,12 b' def admin_routes(config):'
291 290 pattern='/users/{user_id:\d+}/create_repo_group',
292 291 user_route=True)
293 292
293 # user notice
294 config.add_route(
295 name='user_notice_dismiss',
296 pattern='/users/{user_id:\d+}/notice_dismiss',
297 user_route=True)
298
294 299 # user auth tokens
295 300 config.add_route(
296 301 name='edit_user_auth_tokens',
@@ -34,7 +34,7 b' from rhodecode.apps.ssh_support import S'
34 34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
35 35 from rhodecode.authentication.plugins import auth_rhodecode
36 36 from rhodecode.events import trigger
37 from rhodecode.model.db import true
37 from rhodecode.model.db import true, UserNotice
38 38
39 39 from rhodecode.lib import audit_logger, rc_cache
40 40 from rhodecode.lib.exceptions import (
@@ -705,6 +705,32 b' class UsersView(UserAppView):'
705 705 @HasPermissionAllDecorator('hg.admin')
706 706 @CSRFRequired()
707 707 @view_config(
708 route_name='user_notice_dismiss', request_method='POST',
709 renderer='json_ext', xhr=True)
710 def user_notice_dismiss(self):
711 _ = self.request.translate
712 c = self.load_default_context()
713
714 user_id = self.db_user_id
715 c.user = self.db_user
716 user_notice_id = safe_int(self.request.POST.get('notice_id'))
717 notice = UserNotice().query()\
718 .filter(UserNotice.user_id == user_id)\
719 .filter(UserNotice.user_notice_id == user_notice_id)\
720 .scalar()
721 read = False
722 if notice:
723 notice.notice_read = True
724 Session().add(notice)
725 Session().commit()
726 read = True
727
728 return {'notice': user_notice_id, 'read': read}
729
730 @LoginRequired()
731 @HasPermissionAllDecorator('hg.admin')
732 @CSRFRequired()
733 @view_config(
708 734 route_name='user_create_personal_repo_group', request_method='POST',
709 735 renderer='rhodecode:templates/admin/users/user_edit.mako')
710 736 def user_create_personal_repo_group(self):
@@ -23,6 +23,8 b' authentication and permission libraries'
23 23 """
24 24
25 25 import os
26
27 import colander
26 28 import time
27 29 import collections
28 30 import fnmatch
@@ -45,15 +47,14 b' from rhodecode.model import meta'
45 47 from rhodecode.model.meta import Session
46 48 from rhodecode.model.user import UserModel
47 49 from rhodecode.model.db import (
48 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
49 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
50 false, User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
51 UserIpMap, UserApiKeys, RepoGroup, UserGroup, UserNotice)
50 52 from rhodecode.lib import rc_cache
51 53 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
52 54 from rhodecode.lib.utils import (
53 55 get_repo_slug, get_repo_group_slug, get_user_group_slug)
54 56 from rhodecode.lib.caching_query import FromCache
55 57
56
57 58 if rhodecode.is_unix:
58 59 import bcrypt
59 60
@@ -1455,6 +1456,38 b' class AuthUser(object):'
1455 1456
1456 1457 return rule, default_perm
1457 1458
1459 def get_notice_messages(self):
1460
1461 notice_level = 'notice-error'
1462 notice_messages = []
1463 if self.is_default:
1464 return [], notice_level
1465
1466 notices = UserNotice.query()\
1467 .filter(UserNotice.user_id == self.user_id)\
1468 .filter(UserNotice.notice_read == false())\
1469 .all()
1470
1471 try:
1472 for entry in notices:
1473
1474 msg = {
1475 'msg_id': entry.user_notice_id,
1476 'level': entry.notification_level,
1477 'subject': entry.notice_subject,
1478 'body': entry.notice_body,
1479 }
1480 notice_messages.append(msg)
1481
1482 log.debug('Got user %s %s messages', self, len(notice_messages))
1483
1484 levels = [x['level'] for x in notice_messages]
1485 notice_level = 'notice-error' if 'error' in levels else 'notice-warning'
1486 except Exception:
1487 pass
1488
1489 return notice_messages, notice_level
1490
1458 1491 def __repr__(self):
1459 1492 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1460 1493 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
@@ -4517,6 +4517,65 b' class UserNotification(Base, BaseModel):'
4517 4517 Session().add(self)
4518 4518
4519 4519
4520 class UserNotice(Base, BaseModel):
4521 __tablename__ = 'user_notices'
4522 __table_args__ = (
4523 base_table_args
4524 )
4525
4526 NOTIFICATION_TYPE_MESSAGE = 'message'
4527 NOTIFICATION_TYPE_NOTICE = 'notice'
4528
4529 NOTIFICATION_LEVEL_INFO = 'info'
4530 NOTIFICATION_LEVEL_WARNING = 'warning'
4531 NOTIFICATION_LEVEL_ERROR = 'error'
4532
4533 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4534
4535 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4536 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4537
4538 notice_read = Column('notice_read', Boolean, default=False)
4539
4540 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4541 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4542
4543 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4544 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4545
4546 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4547 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4548
4549 @classmethod
4550 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4551
4552 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4553 cls.NOTIFICATION_LEVEL_WARNING,
4554 cls.NOTIFICATION_LEVEL_INFO]:
4555 return
4556
4557 from rhodecode.model.user import UserModel
4558 user = UserModel().get_user(user)
4559
4560 new_notice = UserNotice()
4561 if not allow_duplicate:
4562 existing_msg = UserNotice().query() \
4563 .filter(UserNotice.user == user) \
4564 .filter(UserNotice.notice_body == body) \
4565 .filter(UserNotice.notice_read == false()) \
4566 .scalar()
4567 if existing_msg:
4568 log.warning('Ignoring duplicate notice for user %s', user)
4569 return
4570
4571 new_notice.user = user
4572 new_notice.notice_subject = subject
4573 new_notice.notice_body = body
4574 new_notice.notification_level = notice_level
4575 Session().add(new_notice)
4576 Session().commit()
4577
4578
4520 4579 class Gist(Base, BaseModel):
4521 4580 __tablename__ = 'gists'
4522 4581 __table_args__ = (
@@ -2101,6 +2101,12 b' BIN_FILENODE = 7'
2101 2101 }
2102 2102 }
2103 2103
2104 .notice-messages {
2105 .markdown-block,
2106 .rst-block {
2107 padding: 0;
2108 }
2109 }
2104 2110
2105 2111 .notifications_buttons{
2106 2112 float: right;
@@ -820,7 +820,53 b' input {'
820 820 }
821 821
822 822 .menulabel-notice {
823
824 padding:7px 10px;
825
826 &.notice-warning {
827 border: 1px solid @color3;
828 .notice-color-warning
829 }
830 &.notice-error {
823 831 border: 1px solid @color5;
824 padding:7px 10px;
832 .notice-color-error
833 }
834 &.notice-info {
835 border: 1px solid @color1;
836 .notice-color-info
837 }
838 }
839
840 .notice-messages-container {
841 position: absolute;
842 top: 45px;
843 }
844
845 .notice-messages {
846 display: block;
847 position: relative;
848 z-index: 300;
849 min-width: 500px;
850 max-width: 500px;
851 min-height: 100px;
852 margin-top: 4px;
853 margin-bottom: 24px;
854 font-size: 14px;
855 font-weight: 400;
856 padding: 8px 0;
857 background-color: #fff;
858 border: 1px solid @grey4;
859 box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.07);
860 }
861
862 .notice-color-warning {
863 color: @color3;
864 }
865
866 .notice-color-error {
825 867 color: @color5;
826 868 }
869
870 .notice-color-info {
871 color: @color1;
872 }
@@ -269,6 +269,7 b''
269 269 .icon-expand-linked { cursor: pointer; color: @grey3; font-size: 14px }
270 270 .icon-more-linked { cursor: pointer; color: @grey3 }
271 271 .icon-flag-filled-red { color: @color5 !important; }
272 .icon-filled-red { color: @color5 !important; }
272 273
273 274 .repo-switcher-dropdown .select2-result-label {
274 275 .icon-git:before {
@@ -110,6 +110,7 b' function registerRCRoutes() {'
110 110 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
111 111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
112 112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
113 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
113 114 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
114 115 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
115 116 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
@@ -688,17 +688,50 b''
688 688 </%def>
689 689
690 690 <%def name="menu_items(active=None)">
691 <%
692 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
693 notice_display = 'none' if len(notice_messages) == 0 else ''
694 %>
695 <style>
696
697 </style>
691 698
692 699 <ul id="quick" class="main_nav navigation horizontal-list">
693 700 ## notice box for important system messages
694 <li style="display: none">
695 <a class="notice-box" href="#openNotice" onclick="return false">
696 <div class="menulabel-notice" >
697 0
701 <li style="display: ${notice_display}">
702 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
703 <div class="menulabel-notice ${notice_level}" >
704 ${len(notice_messages)}
698 705 </div>
699 706 </a>
700 707 </li>
708 <div class="notice-messages-container" style="display: none">
709 <div class="notice-messages">
710 <table class="rctable">
711 % for notice in notice_messages:
712 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
713 <td style="vertical-align: text-top; width: 20px">
714 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
715 </td>
716 <td>
717 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
718 ${notice['subject']}
701 719
720 <div id="notice-${notice['msg_id']}" style="display: none">
721 ${h.render(notice['body'], renderer='markdown')}
722 </div>
723 </td>
724 <td style="vertical-align: text-top; width: 35px;">
725 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
726 <i class="icon-remove icon-filled-red"></i>
727 </a>
728 </td>
729 </tr>
730
731 % endfor
732 </table>
733 </div>
734 </div>
702 735 ## Main filter
703 736 <li>
704 737 <div class="menulabel main_filter_box">
@@ -1058,6 +1091,26 b''
1058 1091 }
1059 1092 });
1060 1093
1094 var dismissNotice = function(noticeId) {
1095
1096 var url = pyroutes.url('user_notice_dismiss',
1097 {"user_id": templateContext.rhodecode_user.user_id});
1098
1099 var postData = {
1100 'csrf_token': CSRF_TOKEN,
1101 'notice_id': noticeId,
1102 };
1103
1104 var success = function(response) {
1105 $('#notice-message-' + noticeId).remove();
1106 return false;
1107 };
1108 var failure = function(data, textStatus, xhr) {
1109 alert("error processing request: " + textStatus);
1110 return false;
1111 };
1112 ajaxPOST(url, postData, success, failure);
1113 }
1061 1114 </script>
1062 1115 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1063 1116 </%def>
@@ -15,6 +15,7 b" if getattr(c, 'repo_group', None):"
15 15 c.template_context['repo_group_name'] = c.repo_group.group_name
16 16
17 17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
18 c.template_context['rhodecode_user']['user_id'] = c.rhodecode_user.user_id
18 19 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
19 20 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
20 21 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
General Comments 0
You need to be logged in to leave comments. Login now