##// 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
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 EXTENSIONS = {}
45 EXTENSIONS = {}
46
46
47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
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 __platform__ = platform.system()
49 __platform__ = platform.system()
50 __license__ = 'AGPLv3, and Commercial License'
50 __license__ = 'AGPLv3, and Commercial License'
51 __author__ = 'RhodeCode GmbH'
51 __author__ = 'RhodeCode GmbH'
@@ -26,7 +26,6 b' def admin_routes(config):'
26 """
26 """
27 Admin prefixed routes
27 Admin prefixed routes
28 """
28 """
29
30 config.add_route(
29 config.add_route(
31 name='admin_audit_logs',
30 name='admin_audit_logs',
32 pattern='/audit_logs')
31 pattern='/audit_logs')
@@ -291,6 +290,12 b' def admin_routes(config):'
291 pattern='/users/{user_id:\d+}/create_repo_group',
290 pattern='/users/{user_id:\d+}/create_repo_group',
292 user_route=True)
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 # user auth tokens
299 # user auth tokens
295 config.add_route(
300 config.add_route(
296 name='edit_user_auth_tokens',
301 name='edit_user_auth_tokens',
@@ -34,7 +34,7 b' from rhodecode.apps.ssh_support import S'
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.plugins import auth_rhodecode
35 from rhodecode.authentication.plugins import auth_rhodecode
36 from rhodecode.events import trigger
36 from rhodecode.events import trigger
37 from rhodecode.model.db import true
37 from rhodecode.model.db import true, UserNotice
38
38
39 from rhodecode.lib import audit_logger, rc_cache
39 from rhodecode.lib import audit_logger, rc_cache
40 from rhodecode.lib.exceptions import (
40 from rhodecode.lib.exceptions import (
@@ -705,6 +705,32 b' class UsersView(UserAppView):'
705 @HasPermissionAllDecorator('hg.admin')
705 @HasPermissionAllDecorator('hg.admin')
706 @CSRFRequired()
706 @CSRFRequired()
707 @view_config(
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 route_name='user_create_personal_repo_group', request_method='POST',
734 route_name='user_create_personal_repo_group', request_method='POST',
709 renderer='rhodecode:templates/admin/users/user_edit.mako')
735 renderer='rhodecode:templates/admin/users/user_edit.mako')
710 def user_create_personal_repo_group(self):
736 def user_create_personal_repo_group(self):
@@ -23,6 +23,8 b' authentication and permission libraries'
23 """
23 """
24
24
25 import os
25 import os
26
27 import colander
26 import time
28 import time
27 import collections
29 import collections
28 import fnmatch
30 import fnmatch
@@ -45,15 +47,14 b' from rhodecode.model import meta'
45 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
46 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
47 from rhodecode.model.db import (
49 from rhodecode.model.db import (
48 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 false, User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
49 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 UserIpMap, UserApiKeys, RepoGroup, UserGroup, UserNotice)
50 from rhodecode.lib import rc_cache
52 from rhodecode.lib import rc_cache
51 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
52 from rhodecode.lib.utils import (
54 from rhodecode.lib.utils import (
53 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 get_repo_slug, get_repo_group_slug, get_user_group_slug)
54 from rhodecode.lib.caching_query import FromCache
56 from rhodecode.lib.caching_query import FromCache
55
57
56
57 if rhodecode.is_unix:
58 if rhodecode.is_unix:
58 import bcrypt
59 import bcrypt
59
60
@@ -1455,6 +1456,38 b' class AuthUser(object):'
1455
1456
1456 return rule, default_perm
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 def __repr__(self):
1491 def __repr__(self):
1459 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1492 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1460 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1493 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
@@ -4517,6 +4517,65 b' class UserNotification(Base, BaseModel):'
4517 Session().add(self)
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 class Gist(Base, BaseModel):
4579 class Gist(Base, BaseModel):
4521 __tablename__ = 'gists'
4580 __tablename__ = 'gists'
4522 __table_args__ = (
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 .notifications_buttons{
2111 .notifications_buttons{
2106 float: right;
2112 float: right;
@@ -820,7 +820,53 b' input {'
820 }
820 }
821
821
822 .menulabel-notice {
822 .menulabel-notice {
823 border: 1px solid @color5;
823
824 padding:7px 10px;
824 padding:7px 10px;
825
826 &.notice-warning {
827 border: 1px solid @color3;
828 .notice-color-warning
829 }
830 &.notice-error {
831 border: 1px solid @color5;
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 color: @color5;
867 color: @color5;
826 }
868 }
869
870 .notice-color-info {
871 color: @color1;
872 }
@@ -269,6 +269,7 b''
269 .icon-expand-linked { cursor: pointer; color: @grey3; font-size: 14px }
269 .icon-expand-linked { cursor: pointer; color: @grey3; font-size: 14px }
270 .icon-more-linked { cursor: pointer; color: @grey3 }
270 .icon-more-linked { cursor: pointer; color: @grey3 }
271 .icon-flag-filled-red { color: @color5 !important; }
271 .icon-flag-filled-red { color: @color5 !important; }
272 .icon-filled-red { color: @color5 !important; }
272
273
273 .repo-switcher-dropdown .select2-result-label {
274 .repo-switcher-dropdown .select2-result-label {
274 .icon-git:before {
275 .icon-git:before {
@@ -110,6 +110,7 b' function registerRCRoutes() {'
110 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
110 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
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 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
114 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
114 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
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 </%def>
688 </%def>
689
689
690 <%def name="menu_items(active=None)">
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 <ul id="quick" class="main_nav navigation horizontal-list">
699 <ul id="quick" class="main_nav navigation horizontal-list">
693 ## notice box for important system messages
700 ## notice box for important system messages
694 <li style="display: none">
701 <li style="display: ${notice_display}">
695 <a class="notice-box" href="#openNotice" onclick="return false">
702 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
696 <div class="menulabel-notice" >
703 <div class="menulabel-notice ${notice_level}" >
697 0
704 ${len(notice_messages)}
698 </div>
705 </div>
699 </a>
706 </a>
700 </li>
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 ## Main filter
735 ## Main filter
703 <li>
736 <li>
704 <div class="menulabel main_filter_box">
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 </script>
1114 </script>
1062 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1115 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1063 </%def>
1116 </%def>
@@ -15,6 +15,7 b" if getattr(c, 'repo_group', None):"
15 c.template_context['repo_group_name'] = c.repo_group.group_name
15 c.template_context['repo_group_name'] = c.repo_group.group_name
16
16
17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
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 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
19 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
19 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
20 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
20 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
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