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__ = 10 |
|
|
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: |
|
|
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