Show More
@@ -1,800 +1,802 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2016-2019 RhodeCode GmbH |
|
3 | # Copyright (C) 2016-2019 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import time |
|
21 | import time | |
22 | import logging |
|
22 | import logging | |
23 | import operator |
|
23 | import operator | |
24 |
|
24 | |||
25 | from pyramid import compat |
|
25 | from pyramid import compat | |
26 | from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest |
|
26 | from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest | |
27 |
|
27 | |||
28 | from rhodecode.lib import helpers as h, diffs, rc_cache |
|
28 | from rhodecode.lib import helpers as h, diffs, rc_cache | |
29 | from rhodecode.lib.utils2 import ( |
|
29 | from rhodecode.lib.utils2 import ( | |
30 | StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode) |
|
30 | StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode) | |
31 | from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links |
|
31 | from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links | |
32 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
32 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |
33 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError |
|
33 | from rhodecode.lib.vcs.exceptions import RepositoryRequirementError | |
34 | from rhodecode.model import repo |
|
34 | from rhodecode.model import repo | |
35 | from rhodecode.model import repo_group |
|
35 | from rhodecode.model import repo_group | |
36 | from rhodecode.model import user_group |
|
36 | from rhodecode.model import user_group | |
37 | from rhodecode.model import user |
|
37 | from rhodecode.model import user | |
38 | from rhodecode.model.db import User |
|
38 | from rhodecode.model.db import User | |
39 | from rhodecode.model.scm import ScmModel |
|
39 | from rhodecode.model.scm import ScmModel | |
40 | from rhodecode.model.settings import VcsSettingsModel |
|
40 | from rhodecode.model.settings import VcsSettingsModel | |
41 | from rhodecode.model.repo import ReadmeFinder |
|
41 | from rhodecode.model.repo import ReadmeFinder | |
42 |
|
42 | |||
43 | log = logging.getLogger(__name__) |
|
43 | log = logging.getLogger(__name__) | |
44 |
|
44 | |||
45 |
|
45 | |||
46 | ADMIN_PREFIX = '/_admin' |
|
46 | ADMIN_PREFIX = '/_admin' | |
47 | STATIC_FILE_PREFIX = '/_static' |
|
47 | STATIC_FILE_PREFIX = '/_static' | |
48 |
|
48 | |||
49 | URL_NAME_REQUIREMENTS = { |
|
49 | URL_NAME_REQUIREMENTS = { | |
50 | # group name can have a slash in them, but they must not end with a slash |
|
50 | # group name can have a slash in them, but they must not end with a slash | |
51 | 'group_name': r'.*?[^/]', |
|
51 | 'group_name': r'.*?[^/]', | |
52 | 'repo_group_name': r'.*?[^/]', |
|
52 | 'repo_group_name': r'.*?[^/]', | |
53 | # repo names can have a slash in them, but they must not end with a slash |
|
53 | # repo names can have a slash in them, but they must not end with a slash | |
54 | 'repo_name': r'.*?[^/]', |
|
54 | 'repo_name': r'.*?[^/]', | |
55 | # file path eats up everything at the end |
|
55 | # file path eats up everything at the end | |
56 | 'f_path': r'.*', |
|
56 | 'f_path': r'.*', | |
57 | # reference types |
|
57 | # reference types | |
58 | 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)', |
|
58 | 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)', | |
59 | 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)', |
|
59 | 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)', | |
60 | } |
|
60 | } | |
61 |
|
61 | |||
62 |
|
62 | |||
63 | def add_route_with_slash(config,name, pattern, **kw): |
|
63 | def add_route_with_slash(config,name, pattern, **kw): | |
64 | config.add_route(name, pattern, **kw) |
|
64 | config.add_route(name, pattern, **kw) | |
65 | if not pattern.endswith('/'): |
|
65 | if not pattern.endswith('/'): | |
66 | config.add_route(name + '_slash', pattern + '/', **kw) |
|
66 | config.add_route(name + '_slash', pattern + '/', **kw) | |
67 |
|
67 | |||
68 |
|
68 | |||
69 | def add_route_requirements(route_path, requirements=None): |
|
69 | def add_route_requirements(route_path, requirements=None): | |
70 | """ |
|
70 | """ | |
71 | Adds regex requirements to pyramid routes using a mapping dict |
|
71 | Adds regex requirements to pyramid routes using a mapping dict | |
72 | e.g:: |
|
72 | e.g:: | |
73 | add_route_requirements('{repo_name}/settings') |
|
73 | add_route_requirements('{repo_name}/settings') | |
74 | """ |
|
74 | """ | |
75 | requirements = requirements or URL_NAME_REQUIREMENTS |
|
75 | requirements = requirements or URL_NAME_REQUIREMENTS | |
76 | for key, regex in requirements.items(): |
|
76 | for key, regex in requirements.items(): | |
77 | route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex)) |
|
77 | route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex)) | |
78 | return route_path |
|
78 | return route_path | |
79 |
|
79 | |||
80 |
|
80 | |||
81 | def get_format_ref_id(repo): |
|
81 | def get_format_ref_id(repo): | |
82 | """Returns a `repo` specific reference formatter function""" |
|
82 | """Returns a `repo` specific reference formatter function""" | |
83 | if h.is_svn(repo): |
|
83 | if h.is_svn(repo): | |
84 | return _format_ref_id_svn |
|
84 | return _format_ref_id_svn | |
85 | else: |
|
85 | else: | |
86 | return _format_ref_id |
|
86 | return _format_ref_id | |
87 |
|
87 | |||
88 |
|
88 | |||
89 | def _format_ref_id(name, raw_id): |
|
89 | def _format_ref_id(name, raw_id): | |
90 | """Default formatting of a given reference `name`""" |
|
90 | """Default formatting of a given reference `name`""" | |
91 | return name |
|
91 | return name | |
92 |
|
92 | |||
93 |
|
93 | |||
94 | def _format_ref_id_svn(name, raw_id): |
|
94 | def _format_ref_id_svn(name, raw_id): | |
95 | """Special way of formatting a reference for Subversion including path""" |
|
95 | """Special way of formatting a reference for Subversion including path""" | |
96 | return '%s@%s' % (name, raw_id) |
|
96 | return '%s@%s' % (name, raw_id) | |
97 |
|
97 | |||
98 |
|
98 | |||
99 | class TemplateArgs(StrictAttributeDict): |
|
99 | class TemplateArgs(StrictAttributeDict): | |
100 | pass |
|
100 | pass | |
101 |
|
101 | |||
102 |
|
102 | |||
103 | class BaseAppView(object): |
|
103 | class BaseAppView(object): | |
104 |
|
104 | |||
105 | def __init__(self, context, request): |
|
105 | def __init__(self, context, request): | |
106 | self.request = request |
|
106 | self.request = request | |
107 | self.context = context |
|
107 | self.context = context | |
108 | self.session = request.session |
|
108 | self.session = request.session | |
109 | if not hasattr(request, 'user'): |
|
109 | if not hasattr(request, 'user'): | |
110 | # NOTE(marcink): edge case, we ended up in matched route |
|
110 | # NOTE(marcink): edge case, we ended up in matched route | |
111 | # but probably of web-app context, e.g API CALL/VCS CALL |
|
111 | # but probably of web-app context, e.g API CALL/VCS CALL | |
112 | if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'): |
|
112 | if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'): | |
113 | log.warning('Unable to process request `%s` in this scope', request) |
|
113 | log.warning('Unable to process request `%s` in this scope', request) | |
114 | raise HTTPBadRequest() |
|
114 | raise HTTPBadRequest() | |
115 |
|
115 | |||
116 | self._rhodecode_user = request.user # auth user |
|
116 | self._rhodecode_user = request.user # auth user | |
117 | self._rhodecode_db_user = self._rhodecode_user.get_instance() |
|
117 | self._rhodecode_db_user = self._rhodecode_user.get_instance() | |
118 | self._maybe_needs_password_change( |
|
118 | self._maybe_needs_password_change( | |
119 | request.matched_route.name, self._rhodecode_db_user) |
|
119 | request.matched_route.name, self._rhodecode_db_user) | |
120 |
|
120 | |||
121 | def _maybe_needs_password_change(self, view_name, user_obj): |
|
121 | def _maybe_needs_password_change(self, view_name, user_obj): | |
122 | log.debug('Checking if user %s needs password change on view %s', |
|
122 | log.debug('Checking if user %s needs password change on view %s', | |
123 | user_obj, view_name) |
|
123 | user_obj, view_name) | |
124 | skip_user_views = [ |
|
124 | skip_user_views = [ | |
125 | 'logout', 'login', |
|
125 | 'logout', 'login', | |
126 | 'my_account_password', 'my_account_password_update' |
|
126 | 'my_account_password', 'my_account_password_update' | |
127 | ] |
|
127 | ] | |
128 |
|
128 | |||
129 | if not user_obj: |
|
129 | if not user_obj: | |
130 | return |
|
130 | return | |
131 |
|
131 | |||
132 | if user_obj.username == User.DEFAULT_USER: |
|
132 | if user_obj.username == User.DEFAULT_USER: | |
133 | return |
|
133 | return | |
134 |
|
134 | |||
135 | now = time.time() |
|
135 | now = time.time() | |
136 | should_change = user_obj.user_data.get('force_password_change') |
|
136 | should_change = user_obj.user_data.get('force_password_change') | |
137 | change_after = safe_int(should_change) or 0 |
|
137 | change_after = safe_int(should_change) or 0 | |
138 | if should_change and now > change_after: |
|
138 | if should_change and now > change_after: | |
139 | log.debug('User %s requires password change', user_obj) |
|
139 | log.debug('User %s requires password change', user_obj) | |
140 | h.flash('You are required to change your password', 'warning', |
|
140 | h.flash('You are required to change your password', 'warning', | |
141 | ignore_duplicate=True) |
|
141 | ignore_duplicate=True) | |
142 |
|
142 | |||
143 | if view_name not in skip_user_views: |
|
143 | if view_name not in skip_user_views: | |
144 | raise HTTPFound( |
|
144 | raise HTTPFound( | |
145 | self.request.route_path('my_account_password')) |
|
145 | self.request.route_path('my_account_password')) | |
146 |
|
146 | |||
147 | def _log_creation_exception(self, e, repo_name): |
|
147 | def _log_creation_exception(self, e, repo_name): | |
148 | _ = self.request.translate |
|
148 | _ = self.request.translate | |
149 | reason = None |
|
149 | reason = None | |
150 | if len(e.args) == 2: |
|
150 | if len(e.args) == 2: | |
151 | reason = e.args[1] |
|
151 | reason = e.args[1] | |
152 |
|
152 | |||
153 | if reason == 'INVALID_CERTIFICATE': |
|
153 | if reason == 'INVALID_CERTIFICATE': | |
154 | log.exception( |
|
154 | log.exception( | |
155 | 'Exception creating a repository: invalid certificate') |
|
155 | 'Exception creating a repository: invalid certificate') | |
156 | msg = (_('Error creating repository %s: invalid certificate') |
|
156 | msg = (_('Error creating repository %s: invalid certificate') | |
157 | % repo_name) |
|
157 | % repo_name) | |
158 | else: |
|
158 | else: | |
159 | log.exception("Exception creating a repository") |
|
159 | log.exception("Exception creating a repository") | |
160 | msg = (_('Error creating repository %s') |
|
160 | msg = (_('Error creating repository %s') | |
161 | % repo_name) |
|
161 | % repo_name) | |
162 | return msg |
|
162 | return msg | |
163 |
|
163 | |||
164 | def _get_local_tmpl_context(self, include_app_defaults=True): |
|
164 | def _get_local_tmpl_context(self, include_app_defaults=True): | |
165 | c = TemplateArgs() |
|
165 | c = TemplateArgs() | |
166 | c.auth_user = self.request.user |
|
166 | c.auth_user = self.request.user | |
167 | # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user |
|
167 | # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user | |
168 | c.rhodecode_user = self.request.user |
|
168 | c.rhodecode_user = self.request.user | |
169 |
|
169 | |||
170 | if include_app_defaults: |
|
170 | if include_app_defaults: | |
171 | from rhodecode.lib.base import attach_context_attributes |
|
171 | from rhodecode.lib.base import attach_context_attributes | |
172 | attach_context_attributes(c, self.request, self.request.user.user_id) |
|
172 | attach_context_attributes(c, self.request, self.request.user.user_id) | |
173 |
|
173 | |||
174 | c.is_super_admin = c.auth_user.is_admin |
|
174 | c.is_super_admin = c.auth_user.is_admin | |
175 |
|
175 | |||
176 | c.can_create_repo = c.is_super_admin |
|
176 | c.can_create_repo = c.is_super_admin | |
177 | c.can_create_repo_group = c.is_super_admin |
|
177 | c.can_create_repo_group = c.is_super_admin | |
178 | c.can_create_user_group = c.is_super_admin |
|
178 | c.can_create_user_group = c.is_super_admin | |
179 |
|
179 | |||
180 | c.is_delegated_admin = False |
|
180 | c.is_delegated_admin = False | |
181 |
|
181 | |||
182 | if not c.auth_user.is_default and not c.is_super_admin: |
|
182 | if not c.auth_user.is_default and not c.is_super_admin: | |
183 | c.can_create_repo = h.HasPermissionAny('hg.create.repository')( |
|
183 | c.can_create_repo = h.HasPermissionAny('hg.create.repository')( | |
184 | user=self.request.user) |
|
184 | user=self.request.user) | |
185 | repositories = c.auth_user.repositories_admin or c.can_create_repo |
|
185 | repositories = c.auth_user.repositories_admin or c.can_create_repo | |
186 |
|
186 | |||
187 | c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')( |
|
187 | c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')( | |
188 | user=self.request.user) |
|
188 | user=self.request.user) | |
189 | repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group |
|
189 | repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group | |
190 |
|
190 | |||
191 | c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')( |
|
191 | c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')( | |
192 | user=self.request.user) |
|
192 | user=self.request.user) | |
193 | user_groups = c.auth_user.user_groups_admin or c.can_create_user_group |
|
193 | user_groups = c.auth_user.user_groups_admin or c.can_create_user_group | |
194 | # delegated admin can create, or manage some objects |
|
194 | # delegated admin can create, or manage some objects | |
195 | c.is_delegated_admin = repositories or repository_groups or user_groups |
|
195 | c.is_delegated_admin = repositories or repository_groups or user_groups | |
196 | return c |
|
196 | return c | |
197 |
|
197 | |||
198 | def _get_template_context(self, tmpl_args, **kwargs): |
|
198 | def _get_template_context(self, tmpl_args, **kwargs): | |
199 |
|
199 | |||
200 | local_tmpl_args = { |
|
200 | local_tmpl_args = { | |
201 | 'defaults': {}, |
|
201 | 'defaults': {}, | |
202 | 'errors': {}, |
|
202 | 'errors': {}, | |
203 | 'c': tmpl_args |
|
203 | 'c': tmpl_args | |
204 | } |
|
204 | } | |
205 | local_tmpl_args.update(kwargs) |
|
205 | local_tmpl_args.update(kwargs) | |
206 | return local_tmpl_args |
|
206 | return local_tmpl_args | |
207 |
|
207 | |||
208 | def load_default_context(self): |
|
208 | def load_default_context(self): | |
209 | """ |
|
209 | """ | |
210 | example: |
|
210 | example: | |
211 |
|
211 | |||
212 | def load_default_context(self): |
|
212 | def load_default_context(self): | |
213 | c = self._get_local_tmpl_context() |
|
213 | c = self._get_local_tmpl_context() | |
214 | c.custom_var = 'foobar' |
|
214 | c.custom_var = 'foobar' | |
215 |
|
215 | |||
216 | return c |
|
216 | return c | |
217 | """ |
|
217 | """ | |
218 | raise NotImplementedError('Needs implementation in view class') |
|
218 | raise NotImplementedError('Needs implementation in view class') | |
219 |
|
219 | |||
220 |
|
220 | |||
221 | class RepoAppView(BaseAppView): |
|
221 | class RepoAppView(BaseAppView): | |
222 |
|
222 | |||
223 | def __init__(self, context, request): |
|
223 | def __init__(self, context, request): | |
224 | super(RepoAppView, self).__init__(context, request) |
|
224 | super(RepoAppView, self).__init__(context, request) | |
225 | self.db_repo = request.db_repo |
|
225 | self.db_repo = request.db_repo | |
226 | self.db_repo_name = self.db_repo.repo_name |
|
226 | self.db_repo_name = self.db_repo.repo_name | |
227 | self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo) |
|
227 | self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo) | |
|
228 | self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo) | |||
228 |
|
229 | |||
229 | def _handle_missing_requirements(self, error): |
|
230 | def _handle_missing_requirements(self, error): | |
230 | log.error( |
|
231 | log.error( | |
231 | 'Requirements are missing for repository %s: %s', |
|
232 | 'Requirements are missing for repository %s: %s', | |
232 | self.db_repo_name, safe_unicode(error)) |
|
233 | self.db_repo_name, safe_unicode(error)) | |
233 |
|
234 | |||
234 | def _get_local_tmpl_context(self, include_app_defaults=True): |
|
235 | def _get_local_tmpl_context(self, include_app_defaults=True): | |
235 | _ = self.request.translate |
|
236 | _ = self.request.translate | |
236 | c = super(RepoAppView, self)._get_local_tmpl_context( |
|
237 | c = super(RepoAppView, self)._get_local_tmpl_context( | |
237 | include_app_defaults=include_app_defaults) |
|
238 | include_app_defaults=include_app_defaults) | |
238 |
|
239 | |||
239 | # register common vars for this type of view |
|
240 | # register common vars for this type of view | |
240 | c.rhodecode_db_repo = self.db_repo |
|
241 | c.rhodecode_db_repo = self.db_repo | |
241 | c.repo_name = self.db_repo_name |
|
242 | c.repo_name = self.db_repo_name | |
242 | c.repository_pull_requests = self.db_repo_pull_requests |
|
243 | c.repository_pull_requests = self.db_repo_pull_requests | |
|
244 | c.repository_artifacts = self.db_repo_artifacts | |||
243 | c.repository_is_user_following = ScmModel().is_following_repo( |
|
245 | c.repository_is_user_following = ScmModel().is_following_repo( | |
244 | self.db_repo_name, self._rhodecode_user.user_id) |
|
246 | self.db_repo_name, self._rhodecode_user.user_id) | |
245 | self.path_filter = PathFilter(None) |
|
247 | self.path_filter = PathFilter(None) | |
246 |
|
248 | |||
247 | c.repository_requirements_missing = {} |
|
249 | c.repository_requirements_missing = {} | |
248 | try: |
|
250 | try: | |
249 | self.rhodecode_vcs_repo = self.db_repo.scm_instance() |
|
251 | self.rhodecode_vcs_repo = self.db_repo.scm_instance() | |
250 | # NOTE(marcink): |
|
252 | # NOTE(marcink): | |
251 | # comparison to None since if it's an object __bool__ is expensive to |
|
253 | # comparison to None since if it's an object __bool__ is expensive to | |
252 | # calculate |
|
254 | # calculate | |
253 | if self.rhodecode_vcs_repo is not None: |
|
255 | if self.rhodecode_vcs_repo is not None: | |
254 | path_perms = self.rhodecode_vcs_repo.get_path_permissions( |
|
256 | path_perms = self.rhodecode_vcs_repo.get_path_permissions( | |
255 | c.auth_user.username) |
|
257 | c.auth_user.username) | |
256 | self.path_filter = PathFilter(path_perms) |
|
258 | self.path_filter = PathFilter(path_perms) | |
257 | except RepositoryRequirementError as e: |
|
259 | except RepositoryRequirementError as e: | |
258 | c.repository_requirements_missing = {'error': str(e)} |
|
260 | c.repository_requirements_missing = {'error': str(e)} | |
259 | self._handle_missing_requirements(e) |
|
261 | self._handle_missing_requirements(e) | |
260 | self.rhodecode_vcs_repo = None |
|
262 | self.rhodecode_vcs_repo = None | |
261 |
|
263 | |||
262 | c.path_filter = self.path_filter # used by atom_feed_entry.mako |
|
264 | c.path_filter = self.path_filter # used by atom_feed_entry.mako | |
263 |
|
265 | |||
264 | if self.rhodecode_vcs_repo is None: |
|
266 | if self.rhodecode_vcs_repo is None: | |
265 | # unable to fetch this repo as vcs instance, report back to user |
|
267 | # unable to fetch this repo as vcs instance, report back to user | |
266 | h.flash(_( |
|
268 | h.flash(_( | |
267 | "The repository `%(repo_name)s` cannot be loaded in filesystem. " |
|
269 | "The repository `%(repo_name)s` cannot be loaded in filesystem. " | |
268 | "Please check if it exist, or is not damaged.") % |
|
270 | "Please check if it exist, or is not damaged.") % | |
269 | {'repo_name': c.repo_name}, |
|
271 | {'repo_name': c.repo_name}, | |
270 | category='error', ignore_duplicate=True) |
|
272 | category='error', ignore_duplicate=True) | |
271 | if c.repository_requirements_missing: |
|
273 | if c.repository_requirements_missing: | |
272 | route = self.request.matched_route.name |
|
274 | route = self.request.matched_route.name | |
273 | if route.startswith(('edit_repo', 'repo_summary')): |
|
275 | if route.startswith(('edit_repo', 'repo_summary')): | |
274 | # allow summary and edit repo on missing requirements |
|
276 | # allow summary and edit repo on missing requirements | |
275 | return c |
|
277 | return c | |
276 |
|
278 | |||
277 | raise HTTPFound( |
|
279 | raise HTTPFound( | |
278 | h.route_path('repo_summary', repo_name=self.db_repo_name)) |
|
280 | h.route_path('repo_summary', repo_name=self.db_repo_name)) | |
279 |
|
281 | |||
280 | else: # redirect if we don't show missing requirements |
|
282 | else: # redirect if we don't show missing requirements | |
281 | raise HTTPFound(h.route_path('home')) |
|
283 | raise HTTPFound(h.route_path('home')) | |
282 |
|
284 | |||
283 | c.has_origin_repo_read_perm = False |
|
285 | c.has_origin_repo_read_perm = False | |
284 | if self.db_repo.fork: |
|
286 | if self.db_repo.fork: | |
285 | c.has_origin_repo_read_perm = h.HasRepoPermissionAny( |
|
287 | c.has_origin_repo_read_perm = h.HasRepoPermissionAny( | |
286 | 'repository.write', 'repository.read', 'repository.admin')( |
|
288 | 'repository.write', 'repository.read', 'repository.admin')( | |
287 | self.db_repo.fork.repo_name, 'summary fork link') |
|
289 | self.db_repo.fork.repo_name, 'summary fork link') | |
288 |
|
290 | |||
289 | return c |
|
291 | return c | |
290 |
|
292 | |||
291 | def _get_f_path_unchecked(self, matchdict, default=None): |
|
293 | def _get_f_path_unchecked(self, matchdict, default=None): | |
292 | """ |
|
294 | """ | |
293 | Should only be used by redirects, everything else should call _get_f_path |
|
295 | Should only be used by redirects, everything else should call _get_f_path | |
294 | """ |
|
296 | """ | |
295 | f_path = matchdict.get('f_path') |
|
297 | f_path = matchdict.get('f_path') | |
296 | if f_path: |
|
298 | if f_path: | |
297 | # fix for multiple initial slashes that causes errors for GIT |
|
299 | # fix for multiple initial slashes that causes errors for GIT | |
298 | return f_path.lstrip('/') |
|
300 | return f_path.lstrip('/') | |
299 |
|
301 | |||
300 | return default |
|
302 | return default | |
301 |
|
303 | |||
302 | def _get_f_path(self, matchdict, default=None): |
|
304 | def _get_f_path(self, matchdict, default=None): | |
303 | f_path_match = self._get_f_path_unchecked(matchdict, default) |
|
305 | f_path_match = self._get_f_path_unchecked(matchdict, default) | |
304 | return self.path_filter.assert_path_permissions(f_path_match) |
|
306 | return self.path_filter.assert_path_permissions(f_path_match) | |
305 |
|
307 | |||
306 | def _get_general_setting(self, target_repo, settings_key, default=False): |
|
308 | def _get_general_setting(self, target_repo, settings_key, default=False): | |
307 | settings_model = VcsSettingsModel(repo=target_repo) |
|
309 | settings_model = VcsSettingsModel(repo=target_repo) | |
308 | settings = settings_model.get_general_settings() |
|
310 | settings = settings_model.get_general_settings() | |
309 | return settings.get(settings_key, default) |
|
311 | return settings.get(settings_key, default) | |
310 |
|
312 | |||
311 | def _get_repo_setting(self, target_repo, settings_key, default=False): |
|
313 | def _get_repo_setting(self, target_repo, settings_key, default=False): | |
312 | settings_model = VcsSettingsModel(repo=target_repo) |
|
314 | settings_model = VcsSettingsModel(repo=target_repo) | |
313 | settings = settings_model.get_repo_settings_inherited() |
|
315 | settings = settings_model.get_repo_settings_inherited() | |
314 | return settings.get(settings_key, default) |
|
316 | return settings.get(settings_key, default) | |
315 |
|
317 | |||
316 | def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'): |
|
318 | def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'): | |
317 | log.debug('Looking for README file at path %s', path) |
|
319 | log.debug('Looking for README file at path %s', path) | |
318 | if commit_id: |
|
320 | if commit_id: | |
319 | landing_commit_id = commit_id |
|
321 | landing_commit_id = commit_id | |
320 | else: |
|
322 | else: | |
321 | landing_commit = db_repo.get_landing_commit() |
|
323 | landing_commit = db_repo.get_landing_commit() | |
322 | if isinstance(landing_commit, EmptyCommit): |
|
324 | if isinstance(landing_commit, EmptyCommit): | |
323 | return None, None |
|
325 | return None, None | |
324 | landing_commit_id = landing_commit.raw_id |
|
326 | landing_commit_id = landing_commit.raw_id | |
325 |
|
327 | |||
326 | cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id) |
|
328 | cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id) | |
327 | region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid) |
|
329 | region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid) | |
328 | start = time.time() |
|
330 | start = time.time() | |
329 |
|
331 | |||
330 | @region.conditional_cache_on_arguments(namespace=cache_namespace_uid) |
|
332 | @region.conditional_cache_on_arguments(namespace=cache_namespace_uid) | |
331 | def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type): |
|
333 | def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type): | |
332 | readme_data = None |
|
334 | readme_data = None | |
333 | readme_filename = None |
|
335 | readme_filename = None | |
334 |
|
336 | |||
335 | commit = db_repo.get_commit(_commit_id) |
|
337 | commit = db_repo.get_commit(_commit_id) | |
336 | log.debug("Searching for a README file at commit %s.", _commit_id) |
|
338 | log.debug("Searching for a README file at commit %s.", _commit_id) | |
337 | readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path) |
|
339 | readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path) | |
338 |
|
340 | |||
339 | if readme_node: |
|
341 | if readme_node: | |
340 | log.debug('Found README node: %s', readme_node) |
|
342 | log.debug('Found README node: %s', readme_node) | |
341 | relative_urls = { |
|
343 | relative_urls = { | |
342 | 'raw': h.route_path( |
|
344 | 'raw': h.route_path( | |
343 | 'repo_file_raw', repo_name=_repo_name, |
|
345 | 'repo_file_raw', repo_name=_repo_name, | |
344 | commit_id=commit.raw_id, f_path=readme_node.path), |
|
346 | commit_id=commit.raw_id, f_path=readme_node.path), | |
345 | 'standard': h.route_path( |
|
347 | 'standard': h.route_path( | |
346 | 'repo_files', repo_name=_repo_name, |
|
348 | 'repo_files', repo_name=_repo_name, | |
347 | commit_id=commit.raw_id, f_path=readme_node.path), |
|
349 | commit_id=commit.raw_id, f_path=readme_node.path), | |
348 | } |
|
350 | } | |
349 | readme_data = self._render_readme_or_none(commit, readme_node, relative_urls) |
|
351 | readme_data = self._render_readme_or_none(commit, readme_node, relative_urls) | |
350 | readme_filename = readme_node.unicode_path |
|
352 | readme_filename = readme_node.unicode_path | |
351 |
|
353 | |||
352 | return readme_data, readme_filename |
|
354 | return readme_data, readme_filename | |
353 |
|
355 | |||
354 | readme_data, readme_filename = generate_repo_readme( |
|
356 | readme_data, readme_filename = generate_repo_readme( | |
355 | db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,) |
|
357 | db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,) | |
356 | compute_time = time.time() - start |
|
358 | compute_time = time.time() - start | |
357 | log.debug('Repo README for path %s generated and computed in %.4fs', |
|
359 | log.debug('Repo README for path %s generated and computed in %.4fs', | |
358 | path, compute_time) |
|
360 | path, compute_time) | |
359 | return readme_data, readme_filename |
|
361 | return readme_data, readme_filename | |
360 |
|
362 | |||
361 | def _render_readme_or_none(self, commit, readme_node, relative_urls): |
|
363 | def _render_readme_or_none(self, commit, readme_node, relative_urls): | |
362 | log.debug('Found README file `%s` rendering...', readme_node.path) |
|
364 | log.debug('Found README file `%s` rendering...', readme_node.path) | |
363 | renderer = MarkupRenderer() |
|
365 | renderer = MarkupRenderer() | |
364 | try: |
|
366 | try: | |
365 | html_source = renderer.render( |
|
367 | html_source = renderer.render( | |
366 | readme_node.content, filename=readme_node.path) |
|
368 | readme_node.content, filename=readme_node.path) | |
367 | if relative_urls: |
|
369 | if relative_urls: | |
368 | return relative_links(html_source, relative_urls) |
|
370 | return relative_links(html_source, relative_urls) | |
369 | return html_source |
|
371 | return html_source | |
370 | except Exception: |
|
372 | except Exception: | |
371 | log.exception( |
|
373 | log.exception( | |
372 | "Exception while trying to render the README") |
|
374 | "Exception while trying to render the README") | |
373 |
|
375 | |||
374 | def get_recache_flag(self): |
|
376 | def get_recache_flag(self): | |
375 | for flag_name in ['force_recache', 'force-recache', 'no-cache']: |
|
377 | for flag_name in ['force_recache', 'force-recache', 'no-cache']: | |
376 | flag_val = self.request.GET.get(flag_name) |
|
378 | flag_val = self.request.GET.get(flag_name) | |
377 | if str2bool(flag_val): |
|
379 | if str2bool(flag_val): | |
378 | return True |
|
380 | return True | |
379 | return False |
|
381 | return False | |
380 |
|
382 | |||
381 |
|
383 | |||
382 | class PathFilter(object): |
|
384 | class PathFilter(object): | |
383 |
|
385 | |||
384 | # Expects and instance of BasePathPermissionChecker or None |
|
386 | # Expects and instance of BasePathPermissionChecker or None | |
385 | def __init__(self, permission_checker): |
|
387 | def __init__(self, permission_checker): | |
386 | self.permission_checker = permission_checker |
|
388 | self.permission_checker = permission_checker | |
387 |
|
389 | |||
388 | def assert_path_permissions(self, path): |
|
390 | def assert_path_permissions(self, path): | |
389 | if self.path_access_allowed(path): |
|
391 | if self.path_access_allowed(path): | |
390 | return path |
|
392 | return path | |
391 | raise HTTPForbidden() |
|
393 | raise HTTPForbidden() | |
392 |
|
394 | |||
393 | def path_access_allowed(self, path): |
|
395 | def path_access_allowed(self, path): | |
394 | log.debug('Checking ACL permissions for PathFilter for `%s`', path) |
|
396 | log.debug('Checking ACL permissions for PathFilter for `%s`', path) | |
395 | if self.permission_checker: |
|
397 | if self.permission_checker: | |
396 | return path and self.permission_checker.has_access(path) |
|
398 | return path and self.permission_checker.has_access(path) | |
397 | return True |
|
399 | return True | |
398 |
|
400 | |||
399 | def filter_patchset(self, patchset): |
|
401 | def filter_patchset(self, patchset): | |
400 | if not self.permission_checker or not patchset: |
|
402 | if not self.permission_checker or not patchset: | |
401 | return patchset, False |
|
403 | return patchset, False | |
402 | had_filtered = False |
|
404 | had_filtered = False | |
403 | filtered_patchset = [] |
|
405 | filtered_patchset = [] | |
404 | for patch in patchset: |
|
406 | for patch in patchset: | |
405 | filename = patch.get('filename', None) |
|
407 | filename = patch.get('filename', None) | |
406 | if not filename or self.permission_checker.has_access(filename): |
|
408 | if not filename or self.permission_checker.has_access(filename): | |
407 | filtered_patchset.append(patch) |
|
409 | filtered_patchset.append(patch) | |
408 | else: |
|
410 | else: | |
409 | had_filtered = True |
|
411 | had_filtered = True | |
410 | if had_filtered: |
|
412 | if had_filtered: | |
411 | if isinstance(patchset, diffs.LimitedDiffContainer): |
|
413 | if isinstance(patchset, diffs.LimitedDiffContainer): | |
412 | filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset) |
|
414 | filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset) | |
413 | return filtered_patchset, True |
|
415 | return filtered_patchset, True | |
414 | else: |
|
416 | else: | |
415 | return patchset, False |
|
417 | return patchset, False | |
416 |
|
418 | |||
417 | def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None): |
|
419 | def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None): | |
418 | filtered_patchset, has_hidden_changes = self.filter_patchset(patchset) |
|
420 | filtered_patchset, has_hidden_changes = self.filter_patchset(patchset) | |
419 | result = diffset.render_patchset( |
|
421 | result = diffset.render_patchset( | |
420 | filtered_patchset, source_ref=source_ref, target_ref=target_ref) |
|
422 | filtered_patchset, source_ref=source_ref, target_ref=target_ref) | |
421 | result.has_hidden_changes = has_hidden_changes |
|
423 | result.has_hidden_changes = has_hidden_changes | |
422 | return result |
|
424 | return result | |
423 |
|
425 | |||
424 | def get_raw_patch(self, diff_processor): |
|
426 | def get_raw_patch(self, diff_processor): | |
425 | if self.permission_checker is None: |
|
427 | if self.permission_checker is None: | |
426 | return diff_processor.as_raw() |
|
428 | return diff_processor.as_raw() | |
427 | elif self.permission_checker.has_full_access: |
|
429 | elif self.permission_checker.has_full_access: | |
428 | return diff_processor.as_raw() |
|
430 | return diff_processor.as_raw() | |
429 | else: |
|
431 | else: | |
430 | return '# Repository has user-specific filters, raw patch generation is disabled.' |
|
432 | return '# Repository has user-specific filters, raw patch generation is disabled.' | |
431 |
|
433 | |||
432 | @property |
|
434 | @property | |
433 | def is_enabled(self): |
|
435 | def is_enabled(self): | |
434 | return self.permission_checker is not None |
|
436 | return self.permission_checker is not None | |
435 |
|
437 | |||
436 |
|
438 | |||
437 | class RepoGroupAppView(BaseAppView): |
|
439 | class RepoGroupAppView(BaseAppView): | |
438 | def __init__(self, context, request): |
|
440 | def __init__(self, context, request): | |
439 | super(RepoGroupAppView, self).__init__(context, request) |
|
441 | super(RepoGroupAppView, self).__init__(context, request) | |
440 | self.db_repo_group = request.db_repo_group |
|
442 | self.db_repo_group = request.db_repo_group | |
441 | self.db_repo_group_name = self.db_repo_group.group_name |
|
443 | self.db_repo_group_name = self.db_repo_group.group_name | |
442 |
|
444 | |||
443 | def _get_local_tmpl_context(self, include_app_defaults=True): |
|
445 | def _get_local_tmpl_context(self, include_app_defaults=True): | |
444 | _ = self.request.translate |
|
446 | _ = self.request.translate | |
445 | c = super(RepoGroupAppView, self)._get_local_tmpl_context( |
|
447 | c = super(RepoGroupAppView, self)._get_local_tmpl_context( | |
446 | include_app_defaults=include_app_defaults) |
|
448 | include_app_defaults=include_app_defaults) | |
447 | c.repo_group = self.db_repo_group |
|
449 | c.repo_group = self.db_repo_group | |
448 | return c |
|
450 | return c | |
449 |
|
451 | |||
450 | def _revoke_perms_on_yourself(self, form_result): |
|
452 | def _revoke_perms_on_yourself(self, form_result): | |
451 | _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), |
|
453 | _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), | |
452 | form_result['perm_updates']) |
|
454 | form_result['perm_updates']) | |
453 | _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), |
|
455 | _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), | |
454 | form_result['perm_additions']) |
|
456 | form_result['perm_additions']) | |
455 | _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), |
|
457 | _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]), | |
456 | form_result['perm_deletions']) |
|
458 | form_result['perm_deletions']) | |
457 | admin_perm = 'group.admin' |
|
459 | admin_perm = 'group.admin' | |
458 | if _updates and _updates[0][1] != admin_perm or \ |
|
460 | if _updates and _updates[0][1] != admin_perm or \ | |
459 | _additions and _additions[0][1] != admin_perm or \ |
|
461 | _additions and _additions[0][1] != admin_perm or \ | |
460 | _deletions and _deletions[0][1] != admin_perm: |
|
462 | _deletions and _deletions[0][1] != admin_perm: | |
461 | return True |
|
463 | return True | |
462 | return False |
|
464 | return False | |
463 |
|
465 | |||
464 |
|
466 | |||
465 | class UserGroupAppView(BaseAppView): |
|
467 | class UserGroupAppView(BaseAppView): | |
466 | def __init__(self, context, request): |
|
468 | def __init__(self, context, request): | |
467 | super(UserGroupAppView, self).__init__(context, request) |
|
469 | super(UserGroupAppView, self).__init__(context, request) | |
468 | self.db_user_group = request.db_user_group |
|
470 | self.db_user_group = request.db_user_group | |
469 | self.db_user_group_name = self.db_user_group.users_group_name |
|
471 | self.db_user_group_name = self.db_user_group.users_group_name | |
470 |
|
472 | |||
471 |
|
473 | |||
472 | class UserAppView(BaseAppView): |
|
474 | class UserAppView(BaseAppView): | |
473 | def __init__(self, context, request): |
|
475 | def __init__(self, context, request): | |
474 | super(UserAppView, self).__init__(context, request) |
|
476 | super(UserAppView, self).__init__(context, request) | |
475 | self.db_user = request.db_user |
|
477 | self.db_user = request.db_user | |
476 | self.db_user_id = self.db_user.user_id |
|
478 | self.db_user_id = self.db_user.user_id | |
477 |
|
479 | |||
478 | _ = self.request.translate |
|
480 | _ = self.request.translate | |
479 | if not request.db_user_supports_default: |
|
481 | if not request.db_user_supports_default: | |
480 | if self.db_user.username == User.DEFAULT_USER: |
|
482 | if self.db_user.username == User.DEFAULT_USER: | |
481 | h.flash(_("Editing user `{}` is disabled.".format( |
|
483 | h.flash(_("Editing user `{}` is disabled.".format( | |
482 | User.DEFAULT_USER)), category='warning') |
|
484 | User.DEFAULT_USER)), category='warning') | |
483 | raise HTTPFound(h.route_path('users')) |
|
485 | raise HTTPFound(h.route_path('users')) | |
484 |
|
486 | |||
485 |
|
487 | |||
486 | class DataGridAppView(object): |
|
488 | class DataGridAppView(object): | |
487 | """ |
|
489 | """ | |
488 | Common class to have re-usable grid rendering components |
|
490 | Common class to have re-usable grid rendering components | |
489 | """ |
|
491 | """ | |
490 |
|
492 | |||
491 | def _extract_ordering(self, request, column_map=None): |
|
493 | def _extract_ordering(self, request, column_map=None): | |
492 | column_map = column_map or {} |
|
494 | column_map = column_map or {} | |
493 | column_index = safe_int(request.GET.get('order[0][column]')) |
|
495 | column_index = safe_int(request.GET.get('order[0][column]')) | |
494 | order_dir = request.GET.get( |
|
496 | order_dir = request.GET.get( | |
495 | 'order[0][dir]', 'desc') |
|
497 | 'order[0][dir]', 'desc') | |
496 | order_by = request.GET.get( |
|
498 | order_by = request.GET.get( | |
497 | 'columns[%s][data][sort]' % column_index, 'name_raw') |
|
499 | 'columns[%s][data][sort]' % column_index, 'name_raw') | |
498 |
|
500 | |||
499 | # translate datatable to DB columns |
|
501 | # translate datatable to DB columns | |
500 | order_by = column_map.get(order_by) or order_by |
|
502 | order_by = column_map.get(order_by) or order_by | |
501 |
|
503 | |||
502 | search_q = request.GET.get('search[value]') |
|
504 | search_q = request.GET.get('search[value]') | |
503 | return search_q, order_by, order_dir |
|
505 | return search_q, order_by, order_dir | |
504 |
|
506 | |||
505 | def _extract_chunk(self, request): |
|
507 | def _extract_chunk(self, request): | |
506 | start = safe_int(request.GET.get('start'), 0) |
|
508 | start = safe_int(request.GET.get('start'), 0) | |
507 | length = safe_int(request.GET.get('length'), 25) |
|
509 | length = safe_int(request.GET.get('length'), 25) | |
508 | draw = safe_int(request.GET.get('draw')) |
|
510 | draw = safe_int(request.GET.get('draw')) | |
509 | return draw, start, length |
|
511 | return draw, start, length | |
510 |
|
512 | |||
511 | def _get_order_col(self, order_by, model): |
|
513 | def _get_order_col(self, order_by, model): | |
512 | if isinstance(order_by, compat.string_types): |
|
514 | if isinstance(order_by, compat.string_types): | |
513 | try: |
|
515 | try: | |
514 | return operator.attrgetter(order_by)(model) |
|
516 | return operator.attrgetter(order_by)(model) | |
515 | except AttributeError: |
|
517 | except AttributeError: | |
516 | return None |
|
518 | return None | |
517 | else: |
|
519 | else: | |
518 | return order_by |
|
520 | return order_by | |
519 |
|
521 | |||
520 |
|
522 | |||
521 | class BaseReferencesView(RepoAppView): |
|
523 | class BaseReferencesView(RepoAppView): | |
522 | """ |
|
524 | """ | |
523 | Base for reference view for branches, tags and bookmarks. |
|
525 | Base for reference view for branches, tags and bookmarks. | |
524 | """ |
|
526 | """ | |
525 | def load_default_context(self): |
|
527 | def load_default_context(self): | |
526 | c = self._get_local_tmpl_context() |
|
528 | c = self._get_local_tmpl_context() | |
527 |
|
529 | |||
528 |
|
530 | |||
529 | return c |
|
531 | return c | |
530 |
|
532 | |||
531 | def load_refs_context(self, ref_items, partials_template): |
|
533 | def load_refs_context(self, ref_items, partials_template): | |
532 | _render = self.request.get_partial_renderer(partials_template) |
|
534 | _render = self.request.get_partial_renderer(partials_template) | |
533 | pre_load = ["author", "date", "message", "parents"] |
|
535 | pre_load = ["author", "date", "message", "parents"] | |
534 |
|
536 | |||
535 | is_svn = h.is_svn(self.rhodecode_vcs_repo) |
|
537 | is_svn = h.is_svn(self.rhodecode_vcs_repo) | |
536 | is_hg = h.is_hg(self.rhodecode_vcs_repo) |
|
538 | is_hg = h.is_hg(self.rhodecode_vcs_repo) | |
537 |
|
539 | |||
538 | format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo) |
|
540 | format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo) | |
539 |
|
541 | |||
540 | closed_refs = {} |
|
542 | closed_refs = {} | |
541 | if is_hg: |
|
543 | if is_hg: | |
542 | closed_refs = self.rhodecode_vcs_repo.branches_closed |
|
544 | closed_refs = self.rhodecode_vcs_repo.branches_closed | |
543 |
|
545 | |||
544 | data = [] |
|
546 | data = [] | |
545 | for ref_name, commit_id in ref_items: |
|
547 | for ref_name, commit_id in ref_items: | |
546 | commit = self.rhodecode_vcs_repo.get_commit( |
|
548 | commit = self.rhodecode_vcs_repo.get_commit( | |
547 | commit_id=commit_id, pre_load=pre_load) |
|
549 | commit_id=commit_id, pre_load=pre_load) | |
548 | closed = ref_name in closed_refs |
|
550 | closed = ref_name in closed_refs | |
549 |
|
551 | |||
550 | # TODO: johbo: Unify generation of reference links |
|
552 | # TODO: johbo: Unify generation of reference links | |
551 | use_commit_id = '/' in ref_name or is_svn |
|
553 | use_commit_id = '/' in ref_name or is_svn | |
552 |
|
554 | |||
553 | if use_commit_id: |
|
555 | if use_commit_id: | |
554 | files_url = h.route_path( |
|
556 | files_url = h.route_path( | |
555 | 'repo_files', |
|
557 | 'repo_files', | |
556 | repo_name=self.db_repo_name, |
|
558 | repo_name=self.db_repo_name, | |
557 | f_path=ref_name if is_svn else '', |
|
559 | f_path=ref_name if is_svn else '', | |
558 | commit_id=commit_id) |
|
560 | commit_id=commit_id) | |
559 |
|
561 | |||
560 | else: |
|
562 | else: | |
561 | files_url = h.route_path( |
|
563 | files_url = h.route_path( | |
562 | 'repo_files', |
|
564 | 'repo_files', | |
563 | repo_name=self.db_repo_name, |
|
565 | repo_name=self.db_repo_name, | |
564 | f_path=ref_name if is_svn else '', |
|
566 | f_path=ref_name if is_svn else '', | |
565 | commit_id=ref_name, |
|
567 | commit_id=ref_name, | |
566 | _query=dict(at=ref_name)) |
|
568 | _query=dict(at=ref_name)) | |
567 |
|
569 | |||
568 | data.append({ |
|
570 | data.append({ | |
569 | "name": _render('name', ref_name, files_url, closed), |
|
571 | "name": _render('name', ref_name, files_url, closed), | |
570 | "name_raw": ref_name, |
|
572 | "name_raw": ref_name, | |
571 | "date": _render('date', commit.date), |
|
573 | "date": _render('date', commit.date), | |
572 | "date_raw": datetime_to_time(commit.date), |
|
574 | "date_raw": datetime_to_time(commit.date), | |
573 | "author": _render('author', commit.author), |
|
575 | "author": _render('author', commit.author), | |
574 | "commit": _render( |
|
576 | "commit": _render( | |
575 | 'commit', commit.message, commit.raw_id, commit.idx), |
|
577 | 'commit', commit.message, commit.raw_id, commit.idx), | |
576 | "commit_raw": commit.idx, |
|
578 | "commit_raw": commit.idx, | |
577 | "compare": _render( |
|
579 | "compare": _render( | |
578 | 'compare', format_ref_id(ref_name, commit.raw_id)), |
|
580 | 'compare', format_ref_id(ref_name, commit.raw_id)), | |
579 | }) |
|
581 | }) | |
580 |
|
582 | |||
581 | return data |
|
583 | return data | |
582 |
|
584 | |||
583 |
|
585 | |||
584 | class RepoRoutePredicate(object): |
|
586 | class RepoRoutePredicate(object): | |
585 | def __init__(self, val, config): |
|
587 | def __init__(self, val, config): | |
586 | self.val = val |
|
588 | self.val = val | |
587 |
|
589 | |||
588 | def text(self): |
|
590 | def text(self): | |
589 | return 'repo_route = %s' % self.val |
|
591 | return 'repo_route = %s' % self.val | |
590 |
|
592 | |||
591 | phash = text |
|
593 | phash = text | |
592 |
|
594 | |||
593 | def __call__(self, info, request): |
|
595 | def __call__(self, info, request): | |
594 | if hasattr(request, 'vcs_call'): |
|
596 | if hasattr(request, 'vcs_call'): | |
595 | # skip vcs calls |
|
597 | # skip vcs calls | |
596 | return |
|
598 | return | |
597 |
|
599 | |||
598 | repo_name = info['match']['repo_name'] |
|
600 | repo_name = info['match']['repo_name'] | |
599 | repo_model = repo.RepoModel() |
|
601 | repo_model = repo.RepoModel() | |
600 |
|
602 | |||
601 | by_name_match = repo_model.get_by_repo_name(repo_name, cache=False) |
|
603 | by_name_match = repo_model.get_by_repo_name(repo_name, cache=False) | |
602 |
|
604 | |||
603 | def redirect_if_creating(route_info, db_repo): |
|
605 | def redirect_if_creating(route_info, db_repo): | |
604 | skip_views = ['edit_repo_advanced_delete'] |
|
606 | skip_views = ['edit_repo_advanced_delete'] | |
605 | route = route_info['route'] |
|
607 | route = route_info['route'] | |
606 | # we should skip delete view so we can actually "remove" repositories |
|
608 | # we should skip delete view so we can actually "remove" repositories | |
607 | # if they get stuck in creating state. |
|
609 | # if they get stuck in creating state. | |
608 | if route.name in skip_views: |
|
610 | if route.name in skip_views: | |
609 | return |
|
611 | return | |
610 |
|
612 | |||
611 | if db_repo.repo_state in [repo.Repository.STATE_PENDING]: |
|
613 | if db_repo.repo_state in [repo.Repository.STATE_PENDING]: | |
612 | repo_creating_url = request.route_path( |
|
614 | repo_creating_url = request.route_path( | |
613 | 'repo_creating', repo_name=db_repo.repo_name) |
|
615 | 'repo_creating', repo_name=db_repo.repo_name) | |
614 | raise HTTPFound(repo_creating_url) |
|
616 | raise HTTPFound(repo_creating_url) | |
615 |
|
617 | |||
616 | if by_name_match: |
|
618 | if by_name_match: | |
617 | # register this as request object we can re-use later |
|
619 | # register this as request object we can re-use later | |
618 | request.db_repo = by_name_match |
|
620 | request.db_repo = by_name_match | |
619 | redirect_if_creating(info, by_name_match) |
|
621 | redirect_if_creating(info, by_name_match) | |
620 | return True |
|
622 | return True | |
621 |
|
623 | |||
622 | by_id_match = repo_model.get_repo_by_id(repo_name) |
|
624 | by_id_match = repo_model.get_repo_by_id(repo_name) | |
623 | if by_id_match: |
|
625 | if by_id_match: | |
624 | request.db_repo = by_id_match |
|
626 | request.db_repo = by_id_match | |
625 | redirect_if_creating(info, by_id_match) |
|
627 | redirect_if_creating(info, by_id_match) | |
626 | return True |
|
628 | return True | |
627 |
|
629 | |||
628 | return False |
|
630 | return False | |
629 |
|
631 | |||
630 |
|
632 | |||
631 | class RepoForbidArchivedRoutePredicate(object): |
|
633 | class RepoForbidArchivedRoutePredicate(object): | |
632 | def __init__(self, val, config): |
|
634 | def __init__(self, val, config): | |
633 | self.val = val |
|
635 | self.val = val | |
634 |
|
636 | |||
635 | def text(self): |
|
637 | def text(self): | |
636 | return 'repo_forbid_archived = %s' % self.val |
|
638 | return 'repo_forbid_archived = %s' % self.val | |
637 |
|
639 | |||
638 | phash = text |
|
640 | phash = text | |
639 |
|
641 | |||
640 | def __call__(self, info, request): |
|
642 | def __call__(self, info, request): | |
641 | _ = request.translate |
|
643 | _ = request.translate | |
642 | rhodecode_db_repo = request.db_repo |
|
644 | rhodecode_db_repo = request.db_repo | |
643 |
|
645 | |||
644 | log.debug( |
|
646 | log.debug( | |
645 | '%s checking if archived flag for repo for %s', |
|
647 | '%s checking if archived flag for repo for %s', | |
646 | self.__class__.__name__, rhodecode_db_repo.repo_name) |
|
648 | self.__class__.__name__, rhodecode_db_repo.repo_name) | |
647 |
|
649 | |||
648 | if rhodecode_db_repo.archived: |
|
650 | if rhodecode_db_repo.archived: | |
649 | log.warning('Current view is not supported for archived repo:%s', |
|
651 | log.warning('Current view is not supported for archived repo:%s', | |
650 | rhodecode_db_repo.repo_name) |
|
652 | rhodecode_db_repo.repo_name) | |
651 |
|
653 | |||
652 | h.flash( |
|
654 | h.flash( | |
653 | h.literal(_('Action not supported for archived repository.')), |
|
655 | h.literal(_('Action not supported for archived repository.')), | |
654 | category='warning') |
|
656 | category='warning') | |
655 | summary_url = request.route_path( |
|
657 | summary_url = request.route_path( | |
656 | 'repo_summary', repo_name=rhodecode_db_repo.repo_name) |
|
658 | 'repo_summary', repo_name=rhodecode_db_repo.repo_name) | |
657 | raise HTTPFound(summary_url) |
|
659 | raise HTTPFound(summary_url) | |
658 | return True |
|
660 | return True | |
659 |
|
661 | |||
660 |
|
662 | |||
661 | class RepoTypeRoutePredicate(object): |
|
663 | class RepoTypeRoutePredicate(object): | |
662 | def __init__(self, val, config): |
|
664 | def __init__(self, val, config): | |
663 | self.val = val or ['hg', 'git', 'svn'] |
|
665 | self.val = val or ['hg', 'git', 'svn'] | |
664 |
|
666 | |||
665 | def text(self): |
|
667 | def text(self): | |
666 | return 'repo_accepted_type = %s' % self.val |
|
668 | return 'repo_accepted_type = %s' % self.val | |
667 |
|
669 | |||
668 | phash = text |
|
670 | phash = text | |
669 |
|
671 | |||
670 | def __call__(self, info, request): |
|
672 | def __call__(self, info, request): | |
671 | if hasattr(request, 'vcs_call'): |
|
673 | if hasattr(request, 'vcs_call'): | |
672 | # skip vcs calls |
|
674 | # skip vcs calls | |
673 | return |
|
675 | return | |
674 |
|
676 | |||
675 | rhodecode_db_repo = request.db_repo |
|
677 | rhodecode_db_repo = request.db_repo | |
676 |
|
678 | |||
677 | log.debug( |
|
679 | log.debug( | |
678 | '%s checking repo type for %s in %s', |
|
680 | '%s checking repo type for %s in %s', | |
679 | self.__class__.__name__, rhodecode_db_repo.repo_type, self.val) |
|
681 | self.__class__.__name__, rhodecode_db_repo.repo_type, self.val) | |
680 |
|
682 | |||
681 | if rhodecode_db_repo.repo_type in self.val: |
|
683 | if rhodecode_db_repo.repo_type in self.val: | |
682 | return True |
|
684 | return True | |
683 | else: |
|
685 | else: | |
684 | log.warning('Current view is not supported for repo type:%s', |
|
686 | log.warning('Current view is not supported for repo type:%s', | |
685 | rhodecode_db_repo.repo_type) |
|
687 | rhodecode_db_repo.repo_type) | |
686 | return False |
|
688 | return False | |
687 |
|
689 | |||
688 |
|
690 | |||
689 | class RepoGroupRoutePredicate(object): |
|
691 | class RepoGroupRoutePredicate(object): | |
690 | def __init__(self, val, config): |
|
692 | def __init__(self, val, config): | |
691 | self.val = val |
|
693 | self.val = val | |
692 |
|
694 | |||
693 | def text(self): |
|
695 | def text(self): | |
694 | return 'repo_group_route = %s' % self.val |
|
696 | return 'repo_group_route = %s' % self.val | |
695 |
|
697 | |||
696 | phash = text |
|
698 | phash = text | |
697 |
|
699 | |||
698 | def __call__(self, info, request): |
|
700 | def __call__(self, info, request): | |
699 | if hasattr(request, 'vcs_call'): |
|
701 | if hasattr(request, 'vcs_call'): | |
700 | # skip vcs calls |
|
702 | # skip vcs calls | |
701 | return |
|
703 | return | |
702 |
|
704 | |||
703 | repo_group_name = info['match']['repo_group_name'] |
|
705 | repo_group_name = info['match']['repo_group_name'] | |
704 | repo_group_model = repo_group.RepoGroupModel() |
|
706 | repo_group_model = repo_group.RepoGroupModel() | |
705 | by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False) |
|
707 | by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False) | |
706 |
|
708 | |||
707 | if by_name_match: |
|
709 | if by_name_match: | |
708 | # register this as request object we can re-use later |
|
710 | # register this as request object we can re-use later | |
709 | request.db_repo_group = by_name_match |
|
711 | request.db_repo_group = by_name_match | |
710 | return True |
|
712 | return True | |
711 |
|
713 | |||
712 | return False |
|
714 | return False | |
713 |
|
715 | |||
714 |
|
716 | |||
715 | class UserGroupRoutePredicate(object): |
|
717 | class UserGroupRoutePredicate(object): | |
716 | def __init__(self, val, config): |
|
718 | def __init__(self, val, config): | |
717 | self.val = val |
|
719 | self.val = val | |
718 |
|
720 | |||
719 | def text(self): |
|
721 | def text(self): | |
720 | return 'user_group_route = %s' % self.val |
|
722 | return 'user_group_route = %s' % self.val | |
721 |
|
723 | |||
722 | phash = text |
|
724 | phash = text | |
723 |
|
725 | |||
724 | def __call__(self, info, request): |
|
726 | def __call__(self, info, request): | |
725 | if hasattr(request, 'vcs_call'): |
|
727 | if hasattr(request, 'vcs_call'): | |
726 | # skip vcs calls |
|
728 | # skip vcs calls | |
727 | return |
|
729 | return | |
728 |
|
730 | |||
729 | user_group_id = info['match']['user_group_id'] |
|
731 | user_group_id = info['match']['user_group_id'] | |
730 | user_group_model = user_group.UserGroup() |
|
732 | user_group_model = user_group.UserGroup() | |
731 | by_id_match = user_group_model.get(user_group_id, cache=False) |
|
733 | by_id_match = user_group_model.get(user_group_id, cache=False) | |
732 |
|
734 | |||
733 | if by_id_match: |
|
735 | if by_id_match: | |
734 | # register this as request object we can re-use later |
|
736 | # register this as request object we can re-use later | |
735 | request.db_user_group = by_id_match |
|
737 | request.db_user_group = by_id_match | |
736 | return True |
|
738 | return True | |
737 |
|
739 | |||
738 | return False |
|
740 | return False | |
739 |
|
741 | |||
740 |
|
742 | |||
741 | class UserRoutePredicateBase(object): |
|
743 | class UserRoutePredicateBase(object): | |
742 | supports_default = None |
|
744 | supports_default = None | |
743 |
|
745 | |||
744 | def __init__(self, val, config): |
|
746 | def __init__(self, val, config): | |
745 | self.val = val |
|
747 | self.val = val | |
746 |
|
748 | |||
747 | def text(self): |
|
749 | def text(self): | |
748 | raise NotImplementedError() |
|
750 | raise NotImplementedError() | |
749 |
|
751 | |||
750 | def __call__(self, info, request): |
|
752 | def __call__(self, info, request): | |
751 | if hasattr(request, 'vcs_call'): |
|
753 | if hasattr(request, 'vcs_call'): | |
752 | # skip vcs calls |
|
754 | # skip vcs calls | |
753 | return |
|
755 | return | |
754 |
|
756 | |||
755 | user_id = info['match']['user_id'] |
|
757 | user_id = info['match']['user_id'] | |
756 | user_model = user.User() |
|
758 | user_model = user.User() | |
757 | by_id_match = user_model.get(user_id, cache=False) |
|
759 | by_id_match = user_model.get(user_id, cache=False) | |
758 |
|
760 | |||
759 | if by_id_match: |
|
761 | if by_id_match: | |
760 | # register this as request object we can re-use later |
|
762 | # register this as request object we can re-use later | |
761 | request.db_user = by_id_match |
|
763 | request.db_user = by_id_match | |
762 | request.db_user_supports_default = self.supports_default |
|
764 | request.db_user_supports_default = self.supports_default | |
763 | return True |
|
765 | return True | |
764 |
|
766 | |||
765 | return False |
|
767 | return False | |
766 |
|
768 | |||
767 |
|
769 | |||
768 | class UserRoutePredicate(UserRoutePredicateBase): |
|
770 | class UserRoutePredicate(UserRoutePredicateBase): | |
769 | supports_default = False |
|
771 | supports_default = False | |
770 |
|
772 | |||
771 | def text(self): |
|
773 | def text(self): | |
772 | return 'user_route = %s' % self.val |
|
774 | return 'user_route = %s' % self.val | |
773 |
|
775 | |||
774 | phash = text |
|
776 | phash = text | |
775 |
|
777 | |||
776 |
|
778 | |||
777 | class UserRouteWithDefaultPredicate(UserRoutePredicateBase): |
|
779 | class UserRouteWithDefaultPredicate(UserRoutePredicateBase): | |
778 | supports_default = True |
|
780 | supports_default = True | |
779 |
|
781 | |||
780 | def text(self): |
|
782 | def text(self): | |
781 | return 'user_with_default_route = %s' % self.val |
|
783 | return 'user_with_default_route = %s' % self.val | |
782 |
|
784 | |||
783 | phash = text |
|
785 | phash = text | |
784 |
|
786 | |||
785 |
|
787 | |||
786 | def includeme(config): |
|
788 | def includeme(config): | |
787 | config.add_route_predicate( |
|
789 | config.add_route_predicate( | |
788 | 'repo_route', RepoRoutePredicate) |
|
790 | 'repo_route', RepoRoutePredicate) | |
789 | config.add_route_predicate( |
|
791 | config.add_route_predicate( | |
790 | 'repo_accepted_types', RepoTypeRoutePredicate) |
|
792 | 'repo_accepted_types', RepoTypeRoutePredicate) | |
791 | config.add_route_predicate( |
|
793 | config.add_route_predicate( | |
792 | 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate) |
|
794 | 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate) | |
793 | config.add_route_predicate( |
|
795 | config.add_route_predicate( | |
794 | 'repo_group_route', RepoGroupRoutePredicate) |
|
796 | 'repo_group_route', RepoGroupRoutePredicate) | |
795 | config.add_route_predicate( |
|
797 | config.add_route_predicate( | |
796 | 'user_group_route', UserGroupRoutePredicate) |
|
798 | 'user_group_route', UserGroupRoutePredicate) | |
797 | config.add_route_predicate( |
|
799 | config.add_route_predicate( | |
798 | 'user_route_with_default', UserRouteWithDefaultPredicate) |
|
800 | 'user_route_with_default', UserRouteWithDefaultPredicate) | |
799 | config.add_route_predicate( |
|
801 | config.add_route_predicate( | |
800 | 'user_route', UserRoutePredicate) |
|
802 | 'user_route', UserRoutePredicate) |
@@ -1,464 +1,465 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2012-2019 RhodeCode GmbH |
|
3 | # Copyright (C) 2012-2019 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | import deform |
|
21 | import deform | |
22 | import logging |
|
22 | import logging | |
23 | import peppercorn |
|
23 | import peppercorn | |
24 | import webhelpers.paginate |
|
24 | import webhelpers.paginate | |
25 |
|
25 | |||
26 | from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound |
|
26 | from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound | |
27 |
|
27 | |||
28 | from rhodecode.integrations import integration_type_registry |
|
28 | from rhodecode.integrations import integration_type_registry | |
29 | from rhodecode.apps._base import BaseAppView |
|
29 | from rhodecode.apps._base import BaseAppView | |
30 | from rhodecode.apps._base.navigation import navigation_list |
|
30 | from rhodecode.apps._base.navigation import navigation_list | |
31 | from rhodecode.lib.auth import ( |
|
31 | from rhodecode.lib.auth import ( | |
32 | LoginRequired, CSRFRequired, HasPermissionAnyDecorator, |
|
32 | LoginRequired, CSRFRequired, HasPermissionAnyDecorator, | |
33 | HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator) |
|
33 | HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator) | |
34 | from rhodecode.lib.utils2 import safe_int |
|
34 | from rhodecode.lib.utils2 import safe_int | |
35 | from rhodecode.lib import helpers as h |
|
35 | from rhodecode.lib import helpers as h | |
36 | from rhodecode.model.db import Repository, RepoGroup, Session, Integration |
|
36 | from rhodecode.model.db import Repository, RepoGroup, Session, Integration | |
37 | from rhodecode.model.scm import ScmModel |
|
37 | from rhodecode.model.scm import ScmModel | |
38 | from rhodecode.model.integration import IntegrationModel |
|
38 | from rhodecode.model.integration import IntegrationModel | |
39 | from rhodecode.model.validation_schema.schemas.integration_schema import ( |
|
39 | from rhodecode.model.validation_schema.schemas.integration_schema import ( | |
40 | make_integration_schema, IntegrationScopeType) |
|
40 | make_integration_schema, IntegrationScopeType) | |
41 |
|
41 | |||
42 | log = logging.getLogger(__name__) |
|
42 | log = logging.getLogger(__name__) | |
43 |
|
43 | |||
44 |
|
44 | |||
45 | class IntegrationSettingsViewBase(BaseAppView): |
|
45 | class IntegrationSettingsViewBase(BaseAppView): | |
46 | """ |
|
46 | """ | |
47 | Base Integration settings view used by both repo / global settings |
|
47 | Base Integration settings view used by both repo / global settings | |
48 | """ |
|
48 | """ | |
49 |
|
49 | |||
50 | def __init__(self, context, request): |
|
50 | def __init__(self, context, request): | |
51 | super(IntegrationSettingsViewBase, self).__init__(context, request) |
|
51 | super(IntegrationSettingsViewBase, self).__init__(context, request) | |
52 | self._load_view_context() |
|
52 | self._load_view_context() | |
53 |
|
53 | |||
54 | def _load_view_context(self): |
|
54 | def _load_view_context(self): | |
55 | """ |
|
55 | """ | |
56 | This avoids boilerplate for repo/global+list/edit+views/templates |
|
56 | This avoids boilerplate for repo/global+list/edit+views/templates | |
57 | by doing all possible contexts at the same time however it should |
|
57 | by doing all possible contexts at the same time however it should | |
58 | be split up into separate functions once more "contexts" exist |
|
58 | be split up into separate functions once more "contexts" exist | |
59 | """ |
|
59 | """ | |
60 |
|
60 | |||
61 | self.IntegrationType = None |
|
61 | self.IntegrationType = None | |
62 | self.repo = None |
|
62 | self.repo = None | |
63 | self.repo_group = None |
|
63 | self.repo_group = None | |
64 | self.integration = None |
|
64 | self.integration = None | |
65 | self.integrations = {} |
|
65 | self.integrations = {} | |
66 |
|
66 | |||
67 | request = self.request |
|
67 | request = self.request | |
68 |
|
68 | |||
69 | if 'repo_name' in request.matchdict: # in repo settings context |
|
69 | if 'repo_name' in request.matchdict: # in repo settings context | |
70 | repo_name = request.matchdict['repo_name'] |
|
70 | repo_name = request.matchdict['repo_name'] | |
71 | self.repo = Repository.get_by_repo_name(repo_name) |
|
71 | self.repo = Repository.get_by_repo_name(repo_name) | |
72 |
|
72 | |||
73 | if 'repo_group_name' in request.matchdict: # in group settings context |
|
73 | if 'repo_group_name' in request.matchdict: # in group settings context | |
74 | repo_group_name = request.matchdict['repo_group_name'] |
|
74 | repo_group_name = request.matchdict['repo_group_name'] | |
75 | self.repo_group = RepoGroup.get_by_group_name(repo_group_name) |
|
75 | self.repo_group = RepoGroup.get_by_group_name(repo_group_name) | |
76 |
|
76 | |||
77 | if 'integration' in request.matchdict: # integration type context |
|
77 | if 'integration' in request.matchdict: # integration type context | |
78 | integration_type = request.matchdict['integration'] |
|
78 | integration_type = request.matchdict['integration'] | |
79 | if integration_type not in integration_type_registry: |
|
79 | if integration_type not in integration_type_registry: | |
80 | raise HTTPNotFound() |
|
80 | raise HTTPNotFound() | |
81 |
|
81 | |||
82 | self.IntegrationType = integration_type_registry[integration_type] |
|
82 | self.IntegrationType = integration_type_registry[integration_type] | |
83 | if self.IntegrationType.is_dummy: |
|
83 | if self.IntegrationType.is_dummy: | |
84 | raise HTTPNotFound() |
|
84 | raise HTTPNotFound() | |
85 |
|
85 | |||
86 | if 'integration_id' in request.matchdict: # single integration context |
|
86 | if 'integration_id' in request.matchdict: # single integration context | |
87 | integration_id = request.matchdict['integration_id'] |
|
87 | integration_id = request.matchdict['integration_id'] | |
88 | self.integration = Integration.get(integration_id) |
|
88 | self.integration = Integration.get(integration_id) | |
89 |
|
89 | |||
90 | # extra perms check just in case |
|
90 | # extra perms check just in case | |
91 | if not self._has_perms_for_integration(self.integration): |
|
91 | if not self._has_perms_for_integration(self.integration): | |
92 | raise HTTPForbidden() |
|
92 | raise HTTPForbidden() | |
93 |
|
93 | |||
94 | self.settings = self.integration and self.integration.settings or {} |
|
94 | self.settings = self.integration and self.integration.settings or {} | |
95 | self.admin_view = not (self.repo or self.repo_group) |
|
95 | self.admin_view = not (self.repo or self.repo_group) | |
96 |
|
96 | |||
97 | def _has_perms_for_integration(self, integration): |
|
97 | def _has_perms_for_integration(self, integration): | |
98 | perms = self.request.user.permissions |
|
98 | perms = self.request.user.permissions | |
99 |
|
99 | |||
100 | if 'hg.admin' in perms['global']: |
|
100 | if 'hg.admin' in perms['global']: | |
101 | return True |
|
101 | return True | |
102 |
|
102 | |||
103 | if integration.repo: |
|
103 | if integration.repo: | |
104 | return perms['repositories'].get( |
|
104 | return perms['repositories'].get( | |
105 | integration.repo.repo_name) == 'repository.admin' |
|
105 | integration.repo.repo_name) == 'repository.admin' | |
106 |
|
106 | |||
107 | if integration.repo_group: |
|
107 | if integration.repo_group: | |
108 | return perms['repositories_groups'].get( |
|
108 | return perms['repositories_groups'].get( | |
109 | integration.repo_group.group_name) == 'group.admin' |
|
109 | integration.repo_group.group_name) == 'group.admin' | |
110 |
|
110 | |||
111 | return False |
|
111 | return False | |
112 |
|
112 | |||
113 | def _get_local_tmpl_context(self, include_app_defaults=True): |
|
113 | def _get_local_tmpl_context(self, include_app_defaults=True): | |
114 | _ = self.request.translate |
|
114 | _ = self.request.translate | |
115 | c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context( |
|
115 | c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context( | |
116 | include_app_defaults=include_app_defaults) |
|
116 | include_app_defaults=include_app_defaults) | |
117 | c.active = 'integrations' |
|
117 | c.active = 'integrations' | |
118 |
|
118 | |||
119 | return c |
|
119 | return c | |
120 |
|
120 | |||
121 | def _form_schema(self): |
|
121 | def _form_schema(self): | |
122 | schema = make_integration_schema(IntegrationType=self.IntegrationType, |
|
122 | schema = make_integration_schema(IntegrationType=self.IntegrationType, | |
123 | settings=self.settings) |
|
123 | settings=self.settings) | |
124 |
|
124 | |||
125 | # returns a clone, important if mutating the schema later |
|
125 | # returns a clone, important if mutating the schema later | |
126 | return schema.bind( |
|
126 | return schema.bind( | |
127 | permissions=self.request.user.permissions, |
|
127 | permissions=self.request.user.permissions, | |
128 | no_scope=not self.admin_view) |
|
128 | no_scope=not self.admin_view) | |
129 |
|
129 | |||
130 | def _form_defaults(self): |
|
130 | def _form_defaults(self): | |
131 | _ = self.request.translate |
|
131 | _ = self.request.translate | |
132 | defaults = {} |
|
132 | defaults = {} | |
133 |
|
133 | |||
134 | if self.integration: |
|
134 | if self.integration: | |
135 | defaults['settings'] = self.integration.settings or {} |
|
135 | defaults['settings'] = self.integration.settings or {} | |
136 | defaults['options'] = { |
|
136 | defaults['options'] = { | |
137 | 'name': self.integration.name, |
|
137 | 'name': self.integration.name, | |
138 | 'enabled': self.integration.enabled, |
|
138 | 'enabled': self.integration.enabled, | |
139 | 'scope': { |
|
139 | 'scope': { | |
140 | 'repo': self.integration.repo, |
|
140 | 'repo': self.integration.repo, | |
141 | 'repo_group': self.integration.repo_group, |
|
141 | 'repo_group': self.integration.repo_group, | |
142 | 'child_repos_only': self.integration.child_repos_only, |
|
142 | 'child_repos_only': self.integration.child_repos_only, | |
143 | }, |
|
143 | }, | |
144 | } |
|
144 | } | |
145 | else: |
|
145 | else: | |
146 | if self.repo: |
|
146 | if self.repo: | |
147 | scope = _('{repo_name} repository').format( |
|
147 | scope = _('{repo_name} repository').format( | |
148 | repo_name=self.repo.repo_name) |
|
148 | repo_name=self.repo.repo_name) | |
149 | elif self.repo_group: |
|
149 | elif self.repo_group: | |
150 | scope = _('{repo_group_name} repo group').format( |
|
150 | scope = _('{repo_group_name} repo group').format( | |
151 | repo_group_name=self.repo_group.group_name) |
|
151 | repo_group_name=self.repo_group.group_name) | |
152 | else: |
|
152 | else: | |
153 | scope = _('Global') |
|
153 | scope = _('Global') | |
154 |
|
154 | |||
155 | defaults['options'] = { |
|
155 | defaults['options'] = { | |
156 | 'enabled': True, |
|
156 | 'enabled': True, | |
157 | 'name': _('{name} integration').format( |
|
157 | 'name': _('{name} integration').format( | |
158 | name=self.IntegrationType.display_name), |
|
158 | name=self.IntegrationType.display_name), | |
159 | } |
|
159 | } | |
160 | defaults['options']['scope'] = { |
|
160 | defaults['options']['scope'] = { | |
161 | 'repo': self.repo, |
|
161 | 'repo': self.repo, | |
162 | 'repo_group': self.repo_group, |
|
162 | 'repo_group': self.repo_group, | |
163 | } |
|
163 | } | |
164 |
|
164 | |||
165 | return defaults |
|
165 | return defaults | |
166 |
|
166 | |||
167 | def _delete_integration(self, integration): |
|
167 | def _delete_integration(self, integration): | |
168 | _ = self.request.translate |
|
168 | _ = self.request.translate | |
169 | Session().delete(integration) |
|
169 | Session().delete(integration) | |
170 | Session().commit() |
|
170 | Session().commit() | |
171 | h.flash( |
|
171 | h.flash( | |
172 | _('Integration {integration_name} deleted successfully.').format( |
|
172 | _('Integration {integration_name} deleted successfully.').format( | |
173 | integration_name=integration.name), |
|
173 | integration_name=integration.name), | |
174 | category='success') |
|
174 | category='success') | |
175 |
|
175 | |||
176 | if self.repo: |
|
176 | if self.repo: | |
177 | redirect_to = self.request.route_path( |
|
177 | redirect_to = self.request.route_path( | |
178 | 'repo_integrations_home', repo_name=self.repo.repo_name) |
|
178 | 'repo_integrations_home', repo_name=self.repo.repo_name) | |
179 | elif self.repo_group: |
|
179 | elif self.repo_group: | |
180 | redirect_to = self.request.route_path( |
|
180 | redirect_to = self.request.route_path( | |
181 | 'repo_group_integrations_home', |
|
181 | 'repo_group_integrations_home', | |
182 | repo_group_name=self.repo_group.group_name) |
|
182 | repo_group_name=self.repo_group.group_name) | |
183 | else: |
|
183 | else: | |
184 | redirect_to = self.request.route_path('global_integrations_home') |
|
184 | redirect_to = self.request.route_path('global_integrations_home') | |
185 | raise HTTPFound(redirect_to) |
|
185 | raise HTTPFound(redirect_to) | |
186 |
|
186 | |||
187 | def _integration_list(self): |
|
187 | def _integration_list(self): | |
188 | """ List integrations """ |
|
188 | """ List integrations """ | |
189 |
|
189 | |||
190 | c = self.load_default_context() |
|
190 | c = self.load_default_context() | |
191 | if self.repo: |
|
191 | if self.repo: | |
192 | scope = self.repo |
|
192 | scope = self.repo | |
193 | elif self.repo_group: |
|
193 | elif self.repo_group: | |
194 | scope = self.repo_group |
|
194 | scope = self.repo_group | |
195 | else: |
|
195 | else: | |
196 | scope = 'all' |
|
196 | scope = 'all' | |
197 |
|
197 | |||
198 | integrations = [] |
|
198 | integrations = [] | |
199 |
|
199 | |||
200 | for IntType, integration in IntegrationModel().get_integrations( |
|
200 | for IntType, integration in IntegrationModel().get_integrations( | |
201 | scope=scope, IntegrationType=self.IntegrationType): |
|
201 | scope=scope, IntegrationType=self.IntegrationType): | |
202 |
|
202 | |||
203 | # extra permissions check *just in case* |
|
203 | # extra permissions check *just in case* | |
204 | if not self._has_perms_for_integration(integration): |
|
204 | if not self._has_perms_for_integration(integration): | |
205 | continue |
|
205 | continue | |
206 |
|
206 | |||
207 | integrations.append((IntType, integration)) |
|
207 | integrations.append((IntType, integration)) | |
208 |
|
208 | |||
209 | sort_arg = self.request.GET.get('sort', 'name:asc') |
|
209 | sort_arg = self.request.GET.get('sort', 'name:asc') | |
210 | sort_dir = 'asc' |
|
210 | sort_dir = 'asc' | |
211 | if ':' in sort_arg: |
|
211 | if ':' in sort_arg: | |
212 | sort_field, sort_dir = sort_arg.split(':') |
|
212 | sort_field, sort_dir = sort_arg.split(':') | |
213 | else: |
|
213 | else: | |
214 | sort_field = sort_arg, 'asc' |
|
214 | sort_field = sort_arg, 'asc' | |
215 |
|
215 | |||
216 | assert sort_field in ('name', 'integration_type', 'enabled', 'scope') |
|
216 | assert sort_field in ('name', 'integration_type', 'enabled', 'scope') | |
217 |
|
217 | |||
218 | integrations.sort( |
|
218 | integrations.sort( | |
219 | key=lambda x: getattr(x[1], sort_field), |
|
219 | key=lambda x: getattr(x[1], sort_field), | |
220 | reverse=(sort_dir == 'desc')) |
|
220 | reverse=(sort_dir == 'desc')) | |
221 |
|
221 | |||
222 | page_url = webhelpers.paginate.PageURL( |
|
222 | page_url = webhelpers.paginate.PageURL( | |
223 | self.request.path, self.request.GET) |
|
223 | self.request.path, self.request.GET) | |
224 | page = safe_int(self.request.GET.get('page', 1), 1) |
|
224 | page = safe_int(self.request.GET.get('page', 1), 1) | |
225 |
|
225 | |||
226 | integrations = h.Page( |
|
226 | integrations = h.Page( | |
227 | integrations, page=page, items_per_page=10, url=page_url) |
|
227 | integrations, page=page, items_per_page=10, url=page_url) | |
228 |
|
228 | |||
229 | c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc' |
|
229 | c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc' | |
230 |
|
230 | |||
231 | c.current_IntegrationType = self.IntegrationType |
|
231 | c.current_IntegrationType = self.IntegrationType | |
232 | c.integrations_list = integrations |
|
232 | c.integrations_list = integrations | |
233 | c.available_integrations = integration_type_registry |
|
233 | c.available_integrations = integration_type_registry | |
234 |
|
234 | |||
235 | return self._get_template_context(c) |
|
235 | return self._get_template_context(c) | |
236 |
|
236 | |||
237 | def _settings_get(self, defaults=None, form=None): |
|
237 | def _settings_get(self, defaults=None, form=None): | |
238 | """ |
|
238 | """ | |
239 | View that displays the integration settings as a form. |
|
239 | View that displays the integration settings as a form. | |
240 | """ |
|
240 | """ | |
241 | c = self.load_default_context() |
|
241 | c = self.load_default_context() | |
242 |
|
242 | |||
243 | defaults = defaults or self._form_defaults() |
|
243 | defaults = defaults or self._form_defaults() | |
244 | schema = self._form_schema() |
|
244 | schema = self._form_schema() | |
245 |
|
245 | |||
246 | if self.integration: |
|
246 | if self.integration: | |
247 | buttons = ('submit', 'delete') |
|
247 | buttons = ('submit', 'delete') | |
248 | else: |
|
248 | else: | |
249 | buttons = ('submit',) |
|
249 | buttons = ('submit',) | |
250 |
|
250 | |||
251 | form = form or deform.Form(schema, appstruct=defaults, buttons=buttons) |
|
251 | form = form or deform.Form(schema, appstruct=defaults, buttons=buttons) | |
252 |
|
252 | |||
253 | c.form = form |
|
253 | c.form = form | |
254 | c.current_IntegrationType = self.IntegrationType |
|
254 | c.current_IntegrationType = self.IntegrationType | |
255 | c.integration = self.integration |
|
255 | c.integration = self.integration | |
256 |
|
256 | |||
257 | return self._get_template_context(c) |
|
257 | return self._get_template_context(c) | |
258 |
|
258 | |||
259 | def _settings_post(self): |
|
259 | def _settings_post(self): | |
260 | """ |
|
260 | """ | |
261 | View that validates and stores the integration settings. |
|
261 | View that validates and stores the integration settings. | |
262 | """ |
|
262 | """ | |
263 | _ = self.request.translate |
|
263 | _ = self.request.translate | |
264 |
|
264 | |||
265 | controls = self.request.POST.items() |
|
265 | controls = self.request.POST.items() | |
266 | pstruct = peppercorn.parse(controls) |
|
266 | pstruct = peppercorn.parse(controls) | |
267 |
|
267 | |||
268 | if self.integration and pstruct.get('delete'): |
|
268 | if self.integration and pstruct.get('delete'): | |
269 | return self._delete_integration(self.integration) |
|
269 | return self._delete_integration(self.integration) | |
270 |
|
270 | |||
271 | schema = self._form_schema() |
|
271 | schema = self._form_schema() | |
272 |
|
272 | |||
273 | skip_settings_validation = False |
|
273 | skip_settings_validation = False | |
274 | if self.integration and 'enabled' not in pstruct.get('options', {}): |
|
274 | if self.integration and 'enabled' not in pstruct.get('options', {}): | |
275 | skip_settings_validation = True |
|
275 | skip_settings_validation = True | |
276 | schema['settings'].validator = None |
|
276 | schema['settings'].validator = None | |
277 | for field in schema['settings'].children: |
|
277 | for field in schema['settings'].children: | |
278 | field.validator = None |
|
278 | field.validator = None | |
279 | field.missing = '' |
|
279 | field.missing = '' | |
280 |
|
280 | |||
281 | if self.integration: |
|
281 | if self.integration: | |
282 | buttons = ('submit', 'delete') |
|
282 | buttons = ('submit', 'delete') | |
283 | else: |
|
283 | else: | |
284 | buttons = ('submit',) |
|
284 | buttons = ('submit',) | |
285 |
|
285 | |||
286 | form = deform.Form(schema, buttons=buttons) |
|
286 | form = deform.Form(schema, buttons=buttons) | |
287 |
|
287 | |||
288 | if not self.admin_view: |
|
288 | if not self.admin_view: | |
289 | # scope is read only field in these cases, and has to be added |
|
289 | # scope is read only field in these cases, and has to be added | |
290 | options = pstruct.setdefault('options', {}) |
|
290 | options = pstruct.setdefault('options', {}) | |
291 | if 'scope' not in options: |
|
291 | if 'scope' not in options: | |
292 | options['scope'] = IntegrationScopeType().serialize(None, { |
|
292 | options['scope'] = IntegrationScopeType().serialize(None, { | |
293 | 'repo': self.repo, |
|
293 | 'repo': self.repo, | |
294 | 'repo_group': self.repo_group, |
|
294 | 'repo_group': self.repo_group, | |
295 | }) |
|
295 | }) | |
296 |
|
296 | |||
297 | try: |
|
297 | try: | |
298 | valid_data = form.validate_pstruct(pstruct) |
|
298 | valid_data = form.validate_pstruct(pstruct) | |
299 | except deform.ValidationFailure as e: |
|
299 | except deform.ValidationFailure as e: | |
300 | h.flash( |
|
300 | h.flash( | |
301 | _('Errors exist when saving integration settings. ' |
|
301 | _('Errors exist when saving integration settings. ' | |
302 | 'Please check the form inputs.'), |
|
302 | 'Please check the form inputs.'), | |
303 | category='error') |
|
303 | category='error') | |
304 | return self._settings_get(form=e) |
|
304 | return self._settings_get(form=e) | |
305 |
|
305 | |||
306 | if not self.integration: |
|
306 | if not self.integration: | |
307 | self.integration = Integration() |
|
307 | self.integration = Integration() | |
308 | self.integration.integration_type = self.IntegrationType.key |
|
308 | self.integration.integration_type = self.IntegrationType.key | |
309 | Session().add(self.integration) |
|
309 | Session().add(self.integration) | |
310 |
|
310 | |||
311 | scope = valid_data['options']['scope'] |
|
311 | scope = valid_data['options']['scope'] | |
312 |
|
312 | |||
313 | IntegrationModel().update_integration(self.integration, |
|
313 | IntegrationModel().update_integration(self.integration, | |
314 | name=valid_data['options']['name'], |
|
314 | name=valid_data['options']['name'], | |
315 | enabled=valid_data['options']['enabled'], |
|
315 | enabled=valid_data['options']['enabled'], | |
316 | settings=valid_data['settings'], |
|
316 | settings=valid_data['settings'], | |
317 | repo=scope['repo'], |
|
317 | repo=scope['repo'], | |
318 | repo_group=scope['repo_group'], |
|
318 | repo_group=scope['repo_group'], | |
319 | child_repos_only=scope['child_repos_only'], |
|
319 | child_repos_only=scope['child_repos_only'], | |
320 | ) |
|
320 | ) | |
321 |
|
321 | |||
322 | self.integration.settings = valid_data['settings'] |
|
322 | self.integration.settings = valid_data['settings'] | |
323 | Session().commit() |
|
323 | Session().commit() | |
324 | # Display success message and redirect. |
|
324 | # Display success message and redirect. | |
325 | h.flash( |
|
325 | h.flash( | |
326 | _('Integration {integration_name} updated successfully.').format( |
|
326 | _('Integration {integration_name} updated successfully.').format( | |
327 | integration_name=self.IntegrationType.display_name), |
|
327 | integration_name=self.IntegrationType.display_name), | |
328 | category='success') |
|
328 | category='success') | |
329 |
|
329 | |||
330 | # if integration scope changes, we must redirect to the right place |
|
330 | # if integration scope changes, we must redirect to the right place | |
331 | # keeping in mind if the original view was for /repo/ or /_admin/ |
|
331 | # keeping in mind if the original view was for /repo/ or /_admin/ | |
332 | admin_view = not (self.repo or self.repo_group) |
|
332 | admin_view = not (self.repo or self.repo_group) | |
333 |
|
333 | |||
334 | if self.integration.repo and not admin_view: |
|
334 | if self.integration.repo and not admin_view: | |
335 | redirect_to = self.request.route_path( |
|
335 | redirect_to = self.request.route_path( | |
336 | 'repo_integrations_edit', |
|
336 | 'repo_integrations_edit', | |
337 | repo_name=self.integration.repo.repo_name, |
|
337 | repo_name=self.integration.repo.repo_name, | |
338 | integration=self.integration.integration_type, |
|
338 | integration=self.integration.integration_type, | |
339 | integration_id=self.integration.integration_id) |
|
339 | integration_id=self.integration.integration_id) | |
340 | elif self.integration.repo_group and not admin_view: |
|
340 | elif self.integration.repo_group and not admin_view: | |
341 | redirect_to = self.request.route_path( |
|
341 | redirect_to = self.request.route_path( | |
342 | 'repo_group_integrations_edit', |
|
342 | 'repo_group_integrations_edit', | |
343 | repo_group_name=self.integration.repo_group.group_name, |
|
343 | repo_group_name=self.integration.repo_group.group_name, | |
344 | integration=self.integration.integration_type, |
|
344 | integration=self.integration.integration_type, | |
345 | integration_id=self.integration.integration_id) |
|
345 | integration_id=self.integration.integration_id) | |
346 | else: |
|
346 | else: | |
347 | redirect_to = self.request.route_path( |
|
347 | redirect_to = self.request.route_path( | |
348 | 'global_integrations_edit', |
|
348 | 'global_integrations_edit', | |
349 | integration=self.integration.integration_type, |
|
349 | integration=self.integration.integration_type, | |
350 | integration_id=self.integration.integration_id) |
|
350 | integration_id=self.integration.integration_id) | |
351 |
|
351 | |||
352 | return HTTPFound(redirect_to) |
|
352 | return HTTPFound(redirect_to) | |
353 |
|
353 | |||
354 | def _new_integration(self): |
|
354 | def _new_integration(self): | |
355 | c = self.load_default_context() |
|
355 | c = self.load_default_context() | |
356 | c.available_integrations = integration_type_registry |
|
356 | c.available_integrations = integration_type_registry | |
357 | return self._get_template_context(c) |
|
357 | return self._get_template_context(c) | |
358 |
|
358 | |||
359 | def load_default_context(self): |
|
359 | def load_default_context(self): | |
360 | raise NotImplementedError() |
|
360 | raise NotImplementedError() | |
361 |
|
361 | |||
362 |
|
362 | |||
363 | class GlobalIntegrationsView(IntegrationSettingsViewBase): |
|
363 | class GlobalIntegrationsView(IntegrationSettingsViewBase): | |
364 | def load_default_context(self): |
|
364 | def load_default_context(self): | |
365 | c = self._get_local_tmpl_context() |
|
365 | c = self._get_local_tmpl_context() | |
366 | c.repo = self.repo |
|
366 | c.repo = self.repo | |
367 | c.repo_group = self.repo_group |
|
367 | c.repo_group = self.repo_group | |
368 | c.navlist = navigation_list(self.request) |
|
368 | c.navlist = navigation_list(self.request) | |
369 |
|
369 | |||
370 | return c |
|
370 | return c | |
371 |
|
371 | |||
372 | @LoginRequired() |
|
372 | @LoginRequired() | |
373 | @HasPermissionAnyDecorator('hg.admin') |
|
373 | @HasPermissionAnyDecorator('hg.admin') | |
374 | def integration_list(self): |
|
374 | def integration_list(self): | |
375 | return self._integration_list() |
|
375 | return self._integration_list() | |
376 |
|
376 | |||
377 | @LoginRequired() |
|
377 | @LoginRequired() | |
378 | @HasPermissionAnyDecorator('hg.admin') |
|
378 | @HasPermissionAnyDecorator('hg.admin') | |
379 | def settings_get(self): |
|
379 | def settings_get(self): | |
380 | return self._settings_get() |
|
380 | return self._settings_get() | |
381 |
|
381 | |||
382 | @LoginRequired() |
|
382 | @LoginRequired() | |
383 | @HasPermissionAnyDecorator('hg.admin') |
|
383 | @HasPermissionAnyDecorator('hg.admin') | |
384 | @CSRFRequired() |
|
384 | @CSRFRequired() | |
385 | def settings_post(self): |
|
385 | def settings_post(self): | |
386 | return self._settings_post() |
|
386 | return self._settings_post() | |
387 |
|
387 | |||
388 | @LoginRequired() |
|
388 | @LoginRequired() | |
389 | @HasPermissionAnyDecorator('hg.admin') |
|
389 | @HasPermissionAnyDecorator('hg.admin') | |
390 | def new_integration(self): |
|
390 | def new_integration(self): | |
391 | return self._new_integration() |
|
391 | return self._new_integration() | |
392 |
|
392 | |||
393 |
|
393 | |||
394 | class RepoIntegrationsView(IntegrationSettingsViewBase): |
|
394 | class RepoIntegrationsView(IntegrationSettingsViewBase): | |
395 | def load_default_context(self): |
|
395 | def load_default_context(self): | |
396 | c = self._get_local_tmpl_context() |
|
396 | c = self._get_local_tmpl_context() | |
397 |
|
397 | |||
398 | c.repo = self.repo |
|
398 | c.repo = self.repo | |
399 | c.repo_group = self.repo_group |
|
399 | c.repo_group = self.repo_group | |
400 |
|
400 | |||
401 | self.db_repo = self.repo |
|
401 | self.db_repo = self.repo | |
402 | c.rhodecode_db_repo = self.repo |
|
402 | c.rhodecode_db_repo = self.repo | |
403 | c.repo_name = self.db_repo.repo_name |
|
403 | c.repo_name = self.db_repo.repo_name | |
404 | c.repository_pull_requests = ScmModel().get_pull_requests(self.repo) |
|
404 | c.repository_pull_requests = ScmModel().get_pull_requests(self.repo) | |
|
405 | c.repository_artifacts = ScmModel().get_artifacts(self.repo) | |||
405 | c.repository_is_user_following = ScmModel().is_following_repo( |
|
406 | c.repository_is_user_following = ScmModel().is_following_repo( | |
406 | c.repo_name, self._rhodecode_user.user_id) |
|
407 | c.repo_name, self._rhodecode_user.user_id) | |
407 | c.has_origin_repo_read_perm = False |
|
408 | c.has_origin_repo_read_perm = False | |
408 | if self.db_repo.fork: |
|
409 | if self.db_repo.fork: | |
409 | c.has_origin_repo_read_perm = h.HasRepoPermissionAny( |
|
410 | c.has_origin_repo_read_perm = h.HasRepoPermissionAny( | |
410 | 'repository.write', 'repository.read', 'repository.admin')( |
|
411 | 'repository.write', 'repository.read', 'repository.admin')( | |
411 | self.db_repo.fork.repo_name, 'summary fork link') |
|
412 | self.db_repo.fork.repo_name, 'summary fork link') | |
412 | return c |
|
413 | return c | |
413 |
|
414 | |||
414 | @LoginRequired() |
|
415 | @LoginRequired() | |
415 | @HasRepoPermissionAnyDecorator('repository.admin') |
|
416 | @HasRepoPermissionAnyDecorator('repository.admin') | |
416 | def integration_list(self): |
|
417 | def integration_list(self): | |
417 | return self._integration_list() |
|
418 | return self._integration_list() | |
418 |
|
419 | |||
419 | @LoginRequired() |
|
420 | @LoginRequired() | |
420 | @HasRepoPermissionAnyDecorator('repository.admin') |
|
421 | @HasRepoPermissionAnyDecorator('repository.admin') | |
421 | def settings_get(self): |
|
422 | def settings_get(self): | |
422 | return self._settings_get() |
|
423 | return self._settings_get() | |
423 |
|
424 | |||
424 | @LoginRequired() |
|
425 | @LoginRequired() | |
425 | @HasRepoPermissionAnyDecorator('repository.admin') |
|
426 | @HasRepoPermissionAnyDecorator('repository.admin') | |
426 | @CSRFRequired() |
|
427 | @CSRFRequired() | |
427 | def settings_post(self): |
|
428 | def settings_post(self): | |
428 | return self._settings_post() |
|
429 | return self._settings_post() | |
429 |
|
430 | |||
430 | @LoginRequired() |
|
431 | @LoginRequired() | |
431 | @HasRepoPermissionAnyDecorator('repository.admin') |
|
432 | @HasRepoPermissionAnyDecorator('repository.admin') | |
432 | def new_integration(self): |
|
433 | def new_integration(self): | |
433 | return self._new_integration() |
|
434 | return self._new_integration() | |
434 |
|
435 | |||
435 |
|
436 | |||
436 | class RepoGroupIntegrationsView(IntegrationSettingsViewBase): |
|
437 | class RepoGroupIntegrationsView(IntegrationSettingsViewBase): | |
437 | def load_default_context(self): |
|
438 | def load_default_context(self): | |
438 | c = self._get_local_tmpl_context() |
|
439 | c = self._get_local_tmpl_context() | |
439 | c.repo = self.repo |
|
440 | c.repo = self.repo | |
440 | c.repo_group = self.repo_group |
|
441 | c.repo_group = self.repo_group | |
441 | c.navlist = navigation_list(self.request) |
|
442 | c.navlist = navigation_list(self.request) | |
442 |
|
443 | |||
443 | return c |
|
444 | return c | |
444 |
|
445 | |||
445 | @LoginRequired() |
|
446 | @LoginRequired() | |
446 | @HasRepoGroupPermissionAnyDecorator('group.admin') |
|
447 | @HasRepoGroupPermissionAnyDecorator('group.admin') | |
447 | def integration_list(self): |
|
448 | def integration_list(self): | |
448 | return self._integration_list() |
|
449 | return self._integration_list() | |
449 |
|
450 | |||
450 | @LoginRequired() |
|
451 | @LoginRequired() | |
451 | @HasRepoGroupPermissionAnyDecorator('group.admin') |
|
452 | @HasRepoGroupPermissionAnyDecorator('group.admin') | |
452 | def settings_get(self): |
|
453 | def settings_get(self): | |
453 | return self._settings_get() |
|
454 | return self._settings_get() | |
454 |
|
455 | |||
455 | @LoginRequired() |
|
456 | @LoginRequired() | |
456 | @HasRepoGroupPermissionAnyDecorator('group.admin') |
|
457 | @HasRepoGroupPermissionAnyDecorator('group.admin') | |
457 | @CSRFRequired() |
|
458 | @CSRFRequired() | |
458 | def settings_post(self): |
|
459 | def settings_post(self): | |
459 | return self._settings_post() |
|
460 | return self._settings_post() | |
460 |
|
461 | |||
461 | @LoginRequired() |
|
462 | @LoginRequired() | |
462 | @HasRepoGroupPermissionAnyDecorator('group.admin') |
|
463 | @HasRepoGroupPermissionAnyDecorator('group.admin') | |
463 | def new_integration(self): |
|
464 | def new_integration(self): | |
464 | return self._new_integration() |
|
465 | return self._new_integration() |
@@ -1,1014 +1,1021 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2010-2019 RhodeCode GmbH |
|
3 | # Copyright (C) 2010-2019 RhodeCode GmbH | |
4 | # |
|
4 | # | |
5 | # This program is free software: you can redistribute it and/or modify |
|
5 | # This program is free software: you can redistribute it and/or modify | |
6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
7 | # (only), as published by the Free Software Foundation. |
|
7 | # (only), as published by the Free Software Foundation. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | # |
|
16 | # | |
17 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 | """ |
|
21 | """ | |
22 | Scm model for RhodeCode |
|
22 | Scm model for RhodeCode | |
23 | """ |
|
23 | """ | |
24 |
|
24 | |||
25 | import os.path |
|
25 | import os.path | |
26 | import traceback |
|
26 | import traceback | |
27 | import logging |
|
27 | import logging | |
28 | import cStringIO |
|
28 | import cStringIO | |
29 |
|
29 | |||
30 | from sqlalchemy import func |
|
30 | from sqlalchemy import func | |
31 | from zope.cachedescriptors.property import Lazy as LazyProperty |
|
31 | from zope.cachedescriptors.property import Lazy as LazyProperty | |
32 |
|
32 | |||
33 | import rhodecode |
|
33 | import rhodecode | |
34 | from rhodecode.lib.vcs import get_backend |
|
34 | from rhodecode.lib.vcs import get_backend | |
35 | from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError |
|
35 | from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError | |
36 | from rhodecode.lib.vcs.nodes import FileNode |
|
36 | from rhodecode.lib.vcs.nodes import FileNode | |
37 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
37 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |
38 | from rhodecode.lib import helpers as h, rc_cache |
|
38 | from rhodecode.lib import helpers as h, rc_cache | |
39 | from rhodecode.lib.auth import ( |
|
39 | from rhodecode.lib.auth import ( | |
40 | HasRepoPermissionAny, HasRepoGroupPermissionAny, |
|
40 | HasRepoPermissionAny, HasRepoGroupPermissionAny, | |
41 | HasUserGroupPermissionAny) |
|
41 | HasUserGroupPermissionAny) | |
42 | from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError |
|
42 | from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError | |
43 | from rhodecode.lib import hooks_utils |
|
43 | from rhodecode.lib import hooks_utils | |
44 | from rhodecode.lib.utils import ( |
|
44 | from rhodecode.lib.utils import ( | |
45 | get_filesystem_repos, make_db_config) |
|
45 | get_filesystem_repos, make_db_config) | |
46 | from rhodecode.lib.utils2 import (safe_str, safe_unicode) |
|
46 | from rhodecode.lib.utils2 import (safe_str, safe_unicode) | |
47 | from rhodecode.lib.system_info import get_system_info |
|
47 | from rhodecode.lib.system_info import get_system_info | |
48 | from rhodecode.model import BaseModel |
|
48 | from rhodecode.model import BaseModel | |
49 | from rhodecode.model.db import ( |
|
49 | from rhodecode.model.db import ( | |
|
50 | or_, false, | |||
50 | Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup, |
|
51 | Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup, | |
51 | PullRequest) |
|
52 | PullRequest, FileStore) | |
52 | from rhodecode.model.settings import VcsSettingsModel |
|
53 | from rhodecode.model.settings import VcsSettingsModel | |
53 | from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl |
|
54 | from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl | |
54 |
|
55 | |||
55 | log = logging.getLogger(__name__) |
|
56 | log = logging.getLogger(__name__) | |
56 |
|
57 | |||
57 |
|
58 | |||
58 | class UserTemp(object): |
|
59 | class UserTemp(object): | |
59 | def __init__(self, user_id): |
|
60 | def __init__(self, user_id): | |
60 | self.user_id = user_id |
|
61 | self.user_id = user_id | |
61 |
|
62 | |||
62 | def __repr__(self): |
|
63 | def __repr__(self): | |
63 | return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id) |
|
64 | return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id) | |
64 |
|
65 | |||
65 |
|
66 | |||
66 | class RepoTemp(object): |
|
67 | class RepoTemp(object): | |
67 | def __init__(self, repo_id): |
|
68 | def __init__(self, repo_id): | |
68 | self.repo_id = repo_id |
|
69 | self.repo_id = repo_id | |
69 |
|
70 | |||
70 | def __repr__(self): |
|
71 | def __repr__(self): | |
71 | return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id) |
|
72 | return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id) | |
72 |
|
73 | |||
73 |
|
74 | |||
74 | class SimpleCachedRepoList(object): |
|
75 | class SimpleCachedRepoList(object): | |
75 | """ |
|
76 | """ | |
76 | Lighter version of of iteration of repos without the scm initialisation, |
|
77 | Lighter version of of iteration of repos without the scm initialisation, | |
77 | and with cache usage |
|
78 | and with cache usage | |
78 | """ |
|
79 | """ | |
79 | def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None): |
|
80 | def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None): | |
80 | self.db_repo_list = db_repo_list |
|
81 | self.db_repo_list = db_repo_list | |
81 | self.repos_path = repos_path |
|
82 | self.repos_path = repos_path | |
82 | self.order_by = order_by |
|
83 | self.order_by = order_by | |
83 | self.reversed = (order_by or '').startswith('-') |
|
84 | self.reversed = (order_by or '').startswith('-') | |
84 | if not perm_set: |
|
85 | if not perm_set: | |
85 | perm_set = ['repository.read', 'repository.write', |
|
86 | perm_set = ['repository.read', 'repository.write', | |
86 | 'repository.admin'] |
|
87 | 'repository.admin'] | |
87 | self.perm_set = perm_set |
|
88 | self.perm_set = perm_set | |
88 |
|
89 | |||
89 | def __len__(self): |
|
90 | def __len__(self): | |
90 | return len(self.db_repo_list) |
|
91 | return len(self.db_repo_list) | |
91 |
|
92 | |||
92 | def __repr__(self): |
|
93 | def __repr__(self): | |
93 | return '<%s (%s)>' % (self.__class__.__name__, self.__len__()) |
|
94 | return '<%s (%s)>' % (self.__class__.__name__, self.__len__()) | |
94 |
|
95 | |||
95 | def __iter__(self): |
|
96 | def __iter__(self): | |
96 | for dbr in self.db_repo_list: |
|
97 | for dbr in self.db_repo_list: | |
97 | # check permission at this level |
|
98 | # check permission at this level | |
98 | has_perm = HasRepoPermissionAny(*self.perm_set)( |
|
99 | has_perm = HasRepoPermissionAny(*self.perm_set)( | |
99 | dbr.repo_name, 'SimpleCachedRepoList check') |
|
100 | dbr.repo_name, 'SimpleCachedRepoList check') | |
100 | if not has_perm: |
|
101 | if not has_perm: | |
101 | continue |
|
102 | continue | |
102 |
|
103 | |||
103 | tmp_d = { |
|
104 | tmp_d = { | |
104 | 'name': dbr.repo_name, |
|
105 | 'name': dbr.repo_name, | |
105 | 'dbrepo': dbr.get_dict(), |
|
106 | 'dbrepo': dbr.get_dict(), | |
106 | 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {} |
|
107 | 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {} | |
107 | } |
|
108 | } | |
108 | yield tmp_d |
|
109 | yield tmp_d | |
109 |
|
110 | |||
110 |
|
111 | |||
111 | class _PermCheckIterator(object): |
|
112 | class _PermCheckIterator(object): | |
112 |
|
113 | |||
113 | def __init__( |
|
114 | def __init__( | |
114 | self, obj_list, obj_attr, perm_set, perm_checker, |
|
115 | self, obj_list, obj_attr, perm_set, perm_checker, | |
115 | extra_kwargs=None): |
|
116 | extra_kwargs=None): | |
116 | """ |
|
117 | """ | |
117 | Creates iterator from given list of objects, additionally |
|
118 | Creates iterator from given list of objects, additionally | |
118 | checking permission for them from perm_set var |
|
119 | checking permission for them from perm_set var | |
119 |
|
120 | |||
120 | :param obj_list: list of db objects |
|
121 | :param obj_list: list of db objects | |
121 | :param obj_attr: attribute of object to pass into perm_checker |
|
122 | :param obj_attr: attribute of object to pass into perm_checker | |
122 | :param perm_set: list of permissions to check |
|
123 | :param perm_set: list of permissions to check | |
123 | :param perm_checker: callable to check permissions against |
|
124 | :param perm_checker: callable to check permissions against | |
124 | """ |
|
125 | """ | |
125 | self.obj_list = obj_list |
|
126 | self.obj_list = obj_list | |
126 | self.obj_attr = obj_attr |
|
127 | self.obj_attr = obj_attr | |
127 | self.perm_set = perm_set |
|
128 | self.perm_set = perm_set | |
128 | self.perm_checker = perm_checker |
|
129 | self.perm_checker = perm_checker | |
129 | self.extra_kwargs = extra_kwargs or {} |
|
130 | self.extra_kwargs = extra_kwargs or {} | |
130 |
|
131 | |||
131 | def __len__(self): |
|
132 | def __len__(self): | |
132 | return len(self.obj_list) |
|
133 | return len(self.obj_list) | |
133 |
|
134 | |||
134 | def __repr__(self): |
|
135 | def __repr__(self): | |
135 | return '<%s (%s)>' % (self.__class__.__name__, self.__len__()) |
|
136 | return '<%s (%s)>' % (self.__class__.__name__, self.__len__()) | |
136 |
|
137 | |||
137 | def __iter__(self): |
|
138 | def __iter__(self): | |
138 | checker = self.perm_checker(*self.perm_set) |
|
139 | checker = self.perm_checker(*self.perm_set) | |
139 | for db_obj in self.obj_list: |
|
140 | for db_obj in self.obj_list: | |
140 | # check permission at this level |
|
141 | # check permission at this level | |
141 | name = getattr(db_obj, self.obj_attr, None) |
|
142 | name = getattr(db_obj, self.obj_attr, None) | |
142 | if not checker(name, self.__class__.__name__, **self.extra_kwargs): |
|
143 | if not checker(name, self.__class__.__name__, **self.extra_kwargs): | |
143 | continue |
|
144 | continue | |
144 |
|
145 | |||
145 | yield db_obj |
|
146 | yield db_obj | |
146 |
|
147 | |||
147 |
|
148 | |||
148 | class RepoList(_PermCheckIterator): |
|
149 | class RepoList(_PermCheckIterator): | |
149 |
|
150 | |||
150 | def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None): |
|
151 | def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None): | |
151 | if not perm_set: |
|
152 | if not perm_set: | |
152 | perm_set = [ |
|
153 | perm_set = [ | |
153 | 'repository.read', 'repository.write', 'repository.admin'] |
|
154 | 'repository.read', 'repository.write', 'repository.admin'] | |
154 |
|
155 | |||
155 | super(RepoList, self).__init__( |
|
156 | super(RepoList, self).__init__( | |
156 | obj_list=db_repo_list, |
|
157 | obj_list=db_repo_list, | |
157 | obj_attr='repo_name', perm_set=perm_set, |
|
158 | obj_attr='repo_name', perm_set=perm_set, | |
158 | perm_checker=HasRepoPermissionAny, |
|
159 | perm_checker=HasRepoPermissionAny, | |
159 | extra_kwargs=extra_kwargs) |
|
160 | extra_kwargs=extra_kwargs) | |
160 |
|
161 | |||
161 |
|
162 | |||
162 | class RepoGroupList(_PermCheckIterator): |
|
163 | class RepoGroupList(_PermCheckIterator): | |
163 |
|
164 | |||
164 | def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None): |
|
165 | def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None): | |
165 | if not perm_set: |
|
166 | if not perm_set: | |
166 | perm_set = ['group.read', 'group.write', 'group.admin'] |
|
167 | perm_set = ['group.read', 'group.write', 'group.admin'] | |
167 |
|
168 | |||
168 | super(RepoGroupList, self).__init__( |
|
169 | super(RepoGroupList, self).__init__( | |
169 | obj_list=db_repo_group_list, |
|
170 | obj_list=db_repo_group_list, | |
170 | obj_attr='group_name', perm_set=perm_set, |
|
171 | obj_attr='group_name', perm_set=perm_set, | |
171 | perm_checker=HasRepoGroupPermissionAny, |
|
172 | perm_checker=HasRepoGroupPermissionAny, | |
172 | extra_kwargs=extra_kwargs) |
|
173 | extra_kwargs=extra_kwargs) | |
173 |
|
174 | |||
174 |
|
175 | |||
175 | class UserGroupList(_PermCheckIterator): |
|
176 | class UserGroupList(_PermCheckIterator): | |
176 |
|
177 | |||
177 | def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None): |
|
178 | def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None): | |
178 | if not perm_set: |
|
179 | if not perm_set: | |
179 | perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin'] |
|
180 | perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin'] | |
180 |
|
181 | |||
181 | super(UserGroupList, self).__init__( |
|
182 | super(UserGroupList, self).__init__( | |
182 | obj_list=db_user_group_list, |
|
183 | obj_list=db_user_group_list, | |
183 | obj_attr='users_group_name', perm_set=perm_set, |
|
184 | obj_attr='users_group_name', perm_set=perm_set, | |
184 | perm_checker=HasUserGroupPermissionAny, |
|
185 | perm_checker=HasUserGroupPermissionAny, | |
185 | extra_kwargs=extra_kwargs) |
|
186 | extra_kwargs=extra_kwargs) | |
186 |
|
187 | |||
187 |
|
188 | |||
188 | class ScmModel(BaseModel): |
|
189 | class ScmModel(BaseModel): | |
189 | """ |
|
190 | """ | |
190 | Generic Scm Model |
|
191 | Generic Scm Model | |
191 | """ |
|
192 | """ | |
192 |
|
193 | |||
193 | @LazyProperty |
|
194 | @LazyProperty | |
194 | def repos_path(self): |
|
195 | def repos_path(self): | |
195 | """ |
|
196 | """ | |
196 | Gets the repositories root path from database |
|
197 | Gets the repositories root path from database | |
197 | """ |
|
198 | """ | |
198 |
|
199 | |||
199 | settings_model = VcsSettingsModel(sa=self.sa) |
|
200 | settings_model = VcsSettingsModel(sa=self.sa) | |
200 | return settings_model.get_repos_location() |
|
201 | return settings_model.get_repos_location() | |
201 |
|
202 | |||
202 | def repo_scan(self, repos_path=None): |
|
203 | def repo_scan(self, repos_path=None): | |
203 | """ |
|
204 | """ | |
204 | Listing of repositories in given path. This path should not be a |
|
205 | Listing of repositories in given path. This path should not be a | |
205 | repository itself. Return a dictionary of repository objects |
|
206 | repository itself. Return a dictionary of repository objects | |
206 |
|
207 | |||
207 | :param repos_path: path to directory containing repositories |
|
208 | :param repos_path: path to directory containing repositories | |
208 | """ |
|
209 | """ | |
209 |
|
210 | |||
210 | if repos_path is None: |
|
211 | if repos_path is None: | |
211 | repos_path = self.repos_path |
|
212 | repos_path = self.repos_path | |
212 |
|
213 | |||
213 | log.info('scanning for repositories in %s', repos_path) |
|
214 | log.info('scanning for repositories in %s', repos_path) | |
214 |
|
215 | |||
215 | config = make_db_config() |
|
216 | config = make_db_config() | |
216 | config.set('extensions', 'largefiles', '') |
|
217 | config.set('extensions', 'largefiles', '') | |
217 | repos = {} |
|
218 | repos = {} | |
218 |
|
219 | |||
219 | for name, path in get_filesystem_repos(repos_path, recursive=True): |
|
220 | for name, path in get_filesystem_repos(repos_path, recursive=True): | |
220 | # name need to be decomposed and put back together using the / |
|
221 | # name need to be decomposed and put back together using the / | |
221 | # since this is internal storage separator for rhodecode |
|
222 | # since this is internal storage separator for rhodecode | |
222 | name = Repository.normalize_repo_name(name) |
|
223 | name = Repository.normalize_repo_name(name) | |
223 |
|
224 | |||
224 | try: |
|
225 | try: | |
225 | if name in repos: |
|
226 | if name in repos: | |
226 | raise RepositoryError('Duplicate repository name %s ' |
|
227 | raise RepositoryError('Duplicate repository name %s ' | |
227 | 'found in %s' % (name, path)) |
|
228 | 'found in %s' % (name, path)) | |
228 | elif path[0] in rhodecode.BACKENDS: |
|
229 | elif path[0] in rhodecode.BACKENDS: | |
229 | backend = get_backend(path[0]) |
|
230 | backend = get_backend(path[0]) | |
230 | repos[name] = backend(path[1], config=config, |
|
231 | repos[name] = backend(path[1], config=config, | |
231 | with_wire={"cache": False}) |
|
232 | with_wire={"cache": False}) | |
232 | except OSError: |
|
233 | except OSError: | |
233 | continue |
|
234 | continue | |
234 | log.debug('found %s paths with repositories', len(repos)) |
|
235 | log.debug('found %s paths with repositories', len(repos)) | |
235 | return repos |
|
236 | return repos | |
236 |
|
237 | |||
237 | def get_repos(self, all_repos=None, sort_key=None): |
|
238 | def get_repos(self, all_repos=None, sort_key=None): | |
238 | """ |
|
239 | """ | |
239 | Get all repositories from db and for each repo create it's |
|
240 | Get all repositories from db and for each repo create it's | |
240 | backend instance and fill that backed with information from database |
|
241 | backend instance and fill that backed with information from database | |
241 |
|
242 | |||
242 | :param all_repos: list of repository names as strings |
|
243 | :param all_repos: list of repository names as strings | |
243 | give specific repositories list, good for filtering |
|
244 | give specific repositories list, good for filtering | |
244 |
|
245 | |||
245 | :param sort_key: initial sorting of repositories |
|
246 | :param sort_key: initial sorting of repositories | |
246 | """ |
|
247 | """ | |
247 | if all_repos is None: |
|
248 | if all_repos is None: | |
248 | all_repos = self.sa.query(Repository)\ |
|
249 | all_repos = self.sa.query(Repository)\ | |
249 | .filter(Repository.group_id == None)\ |
|
250 | .filter(Repository.group_id == None)\ | |
250 | .order_by(func.lower(Repository.repo_name)).all() |
|
251 | .order_by(func.lower(Repository.repo_name)).all() | |
251 | repo_iter = SimpleCachedRepoList( |
|
252 | repo_iter = SimpleCachedRepoList( | |
252 | all_repos, repos_path=self.repos_path, order_by=sort_key) |
|
253 | all_repos, repos_path=self.repos_path, order_by=sort_key) | |
253 | return repo_iter |
|
254 | return repo_iter | |
254 |
|
255 | |||
255 | def get_repo_groups(self, all_groups=None): |
|
256 | def get_repo_groups(self, all_groups=None): | |
256 | if all_groups is None: |
|
257 | if all_groups is None: | |
257 | all_groups = RepoGroup.query()\ |
|
258 | all_groups = RepoGroup.query()\ | |
258 | .filter(RepoGroup.group_parent_id == None).all() |
|
259 | .filter(RepoGroup.group_parent_id == None).all() | |
259 | return [x for x in RepoGroupList(all_groups)] |
|
260 | return [x for x in RepoGroupList(all_groups)] | |
260 |
|
261 | |||
261 | def mark_for_invalidation(self, repo_name, delete=False): |
|
262 | def mark_for_invalidation(self, repo_name, delete=False): | |
262 | """ |
|
263 | """ | |
263 | Mark caches of this repo invalid in the database. `delete` flag |
|
264 | Mark caches of this repo invalid in the database. `delete` flag | |
264 | removes the cache entries |
|
265 | removes the cache entries | |
265 |
|
266 | |||
266 | :param repo_name: the repo_name for which caches should be marked |
|
267 | :param repo_name: the repo_name for which caches should be marked | |
267 | invalid, or deleted |
|
268 | invalid, or deleted | |
268 | :param delete: delete the entry keys instead of setting bool |
|
269 | :param delete: delete the entry keys instead of setting bool | |
269 | flag on them, and also purge caches used by the dogpile |
|
270 | flag on them, and also purge caches used by the dogpile | |
270 | """ |
|
271 | """ | |
271 | repo = Repository.get_by_repo_name(repo_name) |
|
272 | repo = Repository.get_by_repo_name(repo_name) | |
272 |
|
273 | |||
273 | if repo: |
|
274 | if repo: | |
274 | invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format( |
|
275 | invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format( | |
275 | repo_id=repo.repo_id) |
|
276 | repo_id=repo.repo_id) | |
276 | CacheKey.set_invalidate(invalidation_namespace, delete=delete) |
|
277 | CacheKey.set_invalidate(invalidation_namespace, delete=delete) | |
277 |
|
278 | |||
278 | repo_id = repo.repo_id |
|
279 | repo_id = repo.repo_id | |
279 | config = repo._config |
|
280 | config = repo._config | |
280 | config.set('extensions', 'largefiles', '') |
|
281 | config.set('extensions', 'largefiles', '') | |
281 | repo.update_commit_cache(config=config, cs_cache=None) |
|
282 | repo.update_commit_cache(config=config, cs_cache=None) | |
282 | if delete: |
|
283 | if delete: | |
283 | cache_namespace_uid = 'cache_repo.{}'.format(repo_id) |
|
284 | cache_namespace_uid = 'cache_repo.{}'.format(repo_id) | |
284 | rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid) |
|
285 | rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid) | |
285 |
|
286 | |||
286 | def toggle_following_repo(self, follow_repo_id, user_id): |
|
287 | def toggle_following_repo(self, follow_repo_id, user_id): | |
287 |
|
288 | |||
288 | f = self.sa.query(UserFollowing)\ |
|
289 | f = self.sa.query(UserFollowing)\ | |
289 | .filter(UserFollowing.follows_repo_id == follow_repo_id)\ |
|
290 | .filter(UserFollowing.follows_repo_id == follow_repo_id)\ | |
290 | .filter(UserFollowing.user_id == user_id).scalar() |
|
291 | .filter(UserFollowing.user_id == user_id).scalar() | |
291 |
|
292 | |||
292 | if f is not None: |
|
293 | if f is not None: | |
293 | try: |
|
294 | try: | |
294 | self.sa.delete(f) |
|
295 | self.sa.delete(f) | |
295 | return |
|
296 | return | |
296 | except Exception: |
|
297 | except Exception: | |
297 | log.error(traceback.format_exc()) |
|
298 | log.error(traceback.format_exc()) | |
298 | raise |
|
299 | raise | |
299 |
|
300 | |||
300 | try: |
|
301 | try: | |
301 | f = UserFollowing() |
|
302 | f = UserFollowing() | |
302 | f.user_id = user_id |
|
303 | f.user_id = user_id | |
303 | f.follows_repo_id = follow_repo_id |
|
304 | f.follows_repo_id = follow_repo_id | |
304 | self.sa.add(f) |
|
305 | self.sa.add(f) | |
305 | except Exception: |
|
306 | except Exception: | |
306 | log.error(traceback.format_exc()) |
|
307 | log.error(traceback.format_exc()) | |
307 | raise |
|
308 | raise | |
308 |
|
309 | |||
309 | def toggle_following_user(self, follow_user_id, user_id): |
|
310 | def toggle_following_user(self, follow_user_id, user_id): | |
310 | f = self.sa.query(UserFollowing)\ |
|
311 | f = self.sa.query(UserFollowing)\ | |
311 | .filter(UserFollowing.follows_user_id == follow_user_id)\ |
|
312 | .filter(UserFollowing.follows_user_id == follow_user_id)\ | |
312 | .filter(UserFollowing.user_id == user_id).scalar() |
|
313 | .filter(UserFollowing.user_id == user_id).scalar() | |
313 |
|
314 | |||
314 | if f is not None: |
|
315 | if f is not None: | |
315 | try: |
|
316 | try: | |
316 | self.sa.delete(f) |
|
317 | self.sa.delete(f) | |
317 | return |
|
318 | return | |
318 | except Exception: |
|
319 | except Exception: | |
319 | log.error(traceback.format_exc()) |
|
320 | log.error(traceback.format_exc()) | |
320 | raise |
|
321 | raise | |
321 |
|
322 | |||
322 | try: |
|
323 | try: | |
323 | f = UserFollowing() |
|
324 | f = UserFollowing() | |
324 | f.user_id = user_id |
|
325 | f.user_id = user_id | |
325 | f.follows_user_id = follow_user_id |
|
326 | f.follows_user_id = follow_user_id | |
326 | self.sa.add(f) |
|
327 | self.sa.add(f) | |
327 | except Exception: |
|
328 | except Exception: | |
328 | log.error(traceback.format_exc()) |
|
329 | log.error(traceback.format_exc()) | |
329 | raise |
|
330 | raise | |
330 |
|
331 | |||
331 | def is_following_repo(self, repo_name, user_id, cache=False): |
|
332 | def is_following_repo(self, repo_name, user_id, cache=False): | |
332 | r = self.sa.query(Repository)\ |
|
333 | r = self.sa.query(Repository)\ | |
333 | .filter(Repository.repo_name == repo_name).scalar() |
|
334 | .filter(Repository.repo_name == repo_name).scalar() | |
334 |
|
335 | |||
335 | f = self.sa.query(UserFollowing)\ |
|
336 | f = self.sa.query(UserFollowing)\ | |
336 | .filter(UserFollowing.follows_repository == r)\ |
|
337 | .filter(UserFollowing.follows_repository == r)\ | |
337 | .filter(UserFollowing.user_id == user_id).scalar() |
|
338 | .filter(UserFollowing.user_id == user_id).scalar() | |
338 |
|
339 | |||
339 | return f is not None |
|
340 | return f is not None | |
340 |
|
341 | |||
341 | def is_following_user(self, username, user_id, cache=False): |
|
342 | def is_following_user(self, username, user_id, cache=False): | |
342 | u = User.get_by_username(username) |
|
343 | u = User.get_by_username(username) | |
343 |
|
344 | |||
344 | f = self.sa.query(UserFollowing)\ |
|
345 | f = self.sa.query(UserFollowing)\ | |
345 | .filter(UserFollowing.follows_user == u)\ |
|
346 | .filter(UserFollowing.follows_user == u)\ | |
346 | .filter(UserFollowing.user_id == user_id).scalar() |
|
347 | .filter(UserFollowing.user_id == user_id).scalar() | |
347 |
|
348 | |||
348 | return f is not None |
|
349 | return f is not None | |
349 |
|
350 | |||
350 | def get_followers(self, repo): |
|
351 | def get_followers(self, repo): | |
351 | repo = self._get_repo(repo) |
|
352 | repo = self._get_repo(repo) | |
352 |
|
353 | |||
353 | return self.sa.query(UserFollowing)\ |
|
354 | return self.sa.query(UserFollowing)\ | |
354 | .filter(UserFollowing.follows_repository == repo).count() |
|
355 | .filter(UserFollowing.follows_repository == repo).count() | |
355 |
|
356 | |||
356 | def get_forks(self, repo): |
|
357 | def get_forks(self, repo): | |
357 | repo = self._get_repo(repo) |
|
358 | repo = self._get_repo(repo) | |
358 | return self.sa.query(Repository)\ |
|
359 | return self.sa.query(Repository)\ | |
359 | .filter(Repository.fork == repo).count() |
|
360 | .filter(Repository.fork == repo).count() | |
360 |
|
361 | |||
361 | def get_pull_requests(self, repo): |
|
362 | def get_pull_requests(self, repo): | |
362 | repo = self._get_repo(repo) |
|
363 | repo = self._get_repo(repo) | |
363 | return self.sa.query(PullRequest)\ |
|
364 | return self.sa.query(PullRequest)\ | |
364 | .filter(PullRequest.target_repo == repo)\ |
|
365 | .filter(PullRequest.target_repo == repo)\ | |
365 | .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count() |
|
366 | .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count() | |
366 |
|
367 | |||
|
368 | def get_artifacts(self, repo): | |||
|
369 | repo = self._get_repo(repo) | |||
|
370 | return self.sa.query(FileStore)\ | |||
|
371 | .filter(FileStore.repo == repo)\ | |||
|
372 | .filter(or_(FileStore.hidden == None, FileStore.hidden == false())).count() | |||
|
373 | ||||
367 | def mark_as_fork(self, repo, fork, user): |
|
374 | def mark_as_fork(self, repo, fork, user): | |
368 | repo = self._get_repo(repo) |
|
375 | repo = self._get_repo(repo) | |
369 | fork = self._get_repo(fork) |
|
376 | fork = self._get_repo(fork) | |
370 | if fork and repo.repo_id == fork.repo_id: |
|
377 | if fork and repo.repo_id == fork.repo_id: | |
371 | raise Exception("Cannot set repository as fork of itself") |
|
378 | raise Exception("Cannot set repository as fork of itself") | |
372 |
|
379 | |||
373 | if fork and repo.repo_type != fork.repo_type: |
|
380 | if fork and repo.repo_type != fork.repo_type: | |
374 | raise RepositoryError( |
|
381 | raise RepositoryError( | |
375 | "Cannot set repository as fork of repository with other type") |
|
382 | "Cannot set repository as fork of repository with other type") | |
376 |
|
383 | |||
377 | repo.fork = fork |
|
384 | repo.fork = fork | |
378 | self.sa.add(repo) |
|
385 | self.sa.add(repo) | |
379 | return repo |
|
386 | return repo | |
380 |
|
387 | |||
381 | def pull_changes(self, repo, username, remote_uri=None, validate_uri=True): |
|
388 | def pull_changes(self, repo, username, remote_uri=None, validate_uri=True): | |
382 | dbrepo = self._get_repo(repo) |
|
389 | dbrepo = self._get_repo(repo) | |
383 | remote_uri = remote_uri or dbrepo.clone_uri |
|
390 | remote_uri = remote_uri or dbrepo.clone_uri | |
384 | if not remote_uri: |
|
391 | if not remote_uri: | |
385 | raise Exception("This repository doesn't have a clone uri") |
|
392 | raise Exception("This repository doesn't have a clone uri") | |
386 |
|
393 | |||
387 | repo = dbrepo.scm_instance(cache=False) |
|
394 | repo = dbrepo.scm_instance(cache=False) | |
388 | repo.config.clear_section('hooks') |
|
395 | repo.config.clear_section('hooks') | |
389 |
|
396 | |||
390 | try: |
|
397 | try: | |
391 | # NOTE(marcink): add extra validation so we skip invalid urls |
|
398 | # NOTE(marcink): add extra validation so we skip invalid urls | |
392 | # this is due this tasks can be executed via scheduler without |
|
399 | # this is due this tasks can be executed via scheduler without | |
393 | # proper validation of remote_uri |
|
400 | # proper validation of remote_uri | |
394 | if validate_uri: |
|
401 | if validate_uri: | |
395 | config = make_db_config(clear_session=False) |
|
402 | config = make_db_config(clear_session=False) | |
396 | url_validator(remote_uri, dbrepo.repo_type, config) |
|
403 | url_validator(remote_uri, dbrepo.repo_type, config) | |
397 | except InvalidCloneUrl: |
|
404 | except InvalidCloneUrl: | |
398 | raise |
|
405 | raise | |
399 |
|
406 | |||
400 | repo_name = dbrepo.repo_name |
|
407 | repo_name = dbrepo.repo_name | |
401 | try: |
|
408 | try: | |
402 | # TODO: we need to make sure those operations call proper hooks ! |
|
409 | # TODO: we need to make sure those operations call proper hooks ! | |
403 | repo.fetch(remote_uri) |
|
410 | repo.fetch(remote_uri) | |
404 |
|
411 | |||
405 | self.mark_for_invalidation(repo_name) |
|
412 | self.mark_for_invalidation(repo_name) | |
406 | except Exception: |
|
413 | except Exception: | |
407 | log.error(traceback.format_exc()) |
|
414 | log.error(traceback.format_exc()) | |
408 | raise |
|
415 | raise | |
409 |
|
416 | |||
410 | def push_changes(self, repo, username, remote_uri=None, validate_uri=True): |
|
417 | def push_changes(self, repo, username, remote_uri=None, validate_uri=True): | |
411 | dbrepo = self._get_repo(repo) |
|
418 | dbrepo = self._get_repo(repo) | |
412 | remote_uri = remote_uri or dbrepo.push_uri |
|
419 | remote_uri = remote_uri or dbrepo.push_uri | |
413 | if not remote_uri: |
|
420 | if not remote_uri: | |
414 | raise Exception("This repository doesn't have a clone uri") |
|
421 | raise Exception("This repository doesn't have a clone uri") | |
415 |
|
422 | |||
416 | repo = dbrepo.scm_instance(cache=False) |
|
423 | repo = dbrepo.scm_instance(cache=False) | |
417 | repo.config.clear_section('hooks') |
|
424 | repo.config.clear_section('hooks') | |
418 |
|
425 | |||
419 | try: |
|
426 | try: | |
420 | # NOTE(marcink): add extra validation so we skip invalid urls |
|
427 | # NOTE(marcink): add extra validation so we skip invalid urls | |
421 | # this is due this tasks can be executed via scheduler without |
|
428 | # this is due this tasks can be executed via scheduler without | |
422 | # proper validation of remote_uri |
|
429 | # proper validation of remote_uri | |
423 | if validate_uri: |
|
430 | if validate_uri: | |
424 | config = make_db_config(clear_session=False) |
|
431 | config = make_db_config(clear_session=False) | |
425 | url_validator(remote_uri, dbrepo.repo_type, config) |
|
432 | url_validator(remote_uri, dbrepo.repo_type, config) | |
426 | except InvalidCloneUrl: |
|
433 | except InvalidCloneUrl: | |
427 | raise |
|
434 | raise | |
428 |
|
435 | |||
429 | try: |
|
436 | try: | |
430 | repo.push(remote_uri) |
|
437 | repo.push(remote_uri) | |
431 | except Exception: |
|
438 | except Exception: | |
432 | log.error(traceback.format_exc()) |
|
439 | log.error(traceback.format_exc()) | |
433 | raise |
|
440 | raise | |
434 |
|
441 | |||
435 | def commit_change(self, repo, repo_name, commit, user, author, message, |
|
442 | def commit_change(self, repo, repo_name, commit, user, author, message, | |
436 | content, f_path): |
|
443 | content, f_path): | |
437 | """ |
|
444 | """ | |
438 | Commits changes |
|
445 | Commits changes | |
439 |
|
446 | |||
440 | :param repo: SCM instance |
|
447 | :param repo: SCM instance | |
441 |
|
448 | |||
442 | """ |
|
449 | """ | |
443 | user = self._get_user(user) |
|
450 | user = self._get_user(user) | |
444 |
|
451 | |||
445 | # decoding here will force that we have proper encoded values |
|
452 | # decoding here will force that we have proper encoded values | |
446 | # in any other case this will throw exceptions and deny commit |
|
453 | # in any other case this will throw exceptions and deny commit | |
447 | content = safe_str(content) |
|
454 | content = safe_str(content) | |
448 | path = safe_str(f_path) |
|
455 | path = safe_str(f_path) | |
449 | # message and author needs to be unicode |
|
456 | # message and author needs to be unicode | |
450 | # proper backend should then translate that into required type |
|
457 | # proper backend should then translate that into required type | |
451 | message = safe_unicode(message) |
|
458 | message = safe_unicode(message) | |
452 | author = safe_unicode(author) |
|
459 | author = safe_unicode(author) | |
453 | imc = repo.in_memory_commit |
|
460 | imc = repo.in_memory_commit | |
454 | imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path))) |
|
461 | imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path))) | |
455 | try: |
|
462 | try: | |
456 | # TODO: handle pre-push action ! |
|
463 | # TODO: handle pre-push action ! | |
457 | tip = imc.commit( |
|
464 | tip = imc.commit( | |
458 | message=message, author=author, parents=[commit], |
|
465 | message=message, author=author, parents=[commit], | |
459 | branch=commit.branch) |
|
466 | branch=commit.branch) | |
460 | except Exception as e: |
|
467 | except Exception as e: | |
461 | log.error(traceback.format_exc()) |
|
468 | log.error(traceback.format_exc()) | |
462 | raise IMCCommitError(str(e)) |
|
469 | raise IMCCommitError(str(e)) | |
463 | finally: |
|
470 | finally: | |
464 | # always clear caches, if commit fails we want fresh object also |
|
471 | # always clear caches, if commit fails we want fresh object also | |
465 | self.mark_for_invalidation(repo_name) |
|
472 | self.mark_for_invalidation(repo_name) | |
466 |
|
473 | |||
467 | # We trigger the post-push action |
|
474 | # We trigger the post-push action | |
468 | hooks_utils.trigger_post_push_hook( |
|
475 | hooks_utils.trigger_post_push_hook( | |
469 | username=user.username, action='push_local', hook_type='post_push', |
|
476 | username=user.username, action='push_local', hook_type='post_push', | |
470 | repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id]) |
|
477 | repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id]) | |
471 | return tip |
|
478 | return tip | |
472 |
|
479 | |||
473 | def _sanitize_path(self, f_path): |
|
480 | def _sanitize_path(self, f_path): | |
474 | if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path: |
|
481 | if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path: | |
475 | raise NonRelativePathError('%s is not an relative path' % f_path) |
|
482 | raise NonRelativePathError('%s is not an relative path' % f_path) | |
476 | if f_path: |
|
483 | if f_path: | |
477 | f_path = os.path.normpath(f_path) |
|
484 | f_path = os.path.normpath(f_path) | |
478 | return f_path |
|
485 | return f_path | |
479 |
|
486 | |||
480 | def get_dirnode_metadata(self, request, commit, dir_node): |
|
487 | def get_dirnode_metadata(self, request, commit, dir_node): | |
481 | if not dir_node.is_dir(): |
|
488 | if not dir_node.is_dir(): | |
482 | return [] |
|
489 | return [] | |
483 |
|
490 | |||
484 | data = [] |
|
491 | data = [] | |
485 | for node in dir_node: |
|
492 | for node in dir_node: | |
486 | if not node.is_file(): |
|
493 | if not node.is_file(): | |
487 | # we skip file-nodes |
|
494 | # we skip file-nodes | |
488 | continue |
|
495 | continue | |
489 |
|
496 | |||
490 | last_commit = node.last_commit |
|
497 | last_commit = node.last_commit | |
491 | last_commit_date = last_commit.date |
|
498 | last_commit_date = last_commit.date | |
492 | data.append({ |
|
499 | data.append({ | |
493 | 'name': node.name, |
|
500 | 'name': node.name, | |
494 | 'size': h.format_byte_size_binary(node.size), |
|
501 | 'size': h.format_byte_size_binary(node.size), | |
495 | 'modified_at': h.format_date(last_commit_date), |
|
502 | 'modified_at': h.format_date(last_commit_date), | |
496 | 'modified_ts': last_commit_date.isoformat(), |
|
503 | 'modified_ts': last_commit_date.isoformat(), | |
497 | 'revision': last_commit.revision, |
|
504 | 'revision': last_commit.revision, | |
498 | 'short_id': last_commit.short_id, |
|
505 | 'short_id': last_commit.short_id, | |
499 | 'message': h.escape(last_commit.message), |
|
506 | 'message': h.escape(last_commit.message), | |
500 | 'author': h.escape(last_commit.author), |
|
507 | 'author': h.escape(last_commit.author), | |
501 | 'user_profile': h.gravatar_with_user( |
|
508 | 'user_profile': h.gravatar_with_user( | |
502 | request, last_commit.author), |
|
509 | request, last_commit.author), | |
503 | }) |
|
510 | }) | |
504 |
|
511 | |||
505 | return data |
|
512 | return data | |
506 |
|
513 | |||
507 | def get_nodes(self, repo_name, commit_id, root_path='/', flat=True, |
|
514 | def get_nodes(self, repo_name, commit_id, root_path='/', flat=True, | |
508 | extended_info=False, content=False, max_file_bytes=None): |
|
515 | extended_info=False, content=False, max_file_bytes=None): | |
509 | """ |
|
516 | """ | |
510 | recursive walk in root dir and return a set of all path in that dir |
|
517 | recursive walk in root dir and return a set of all path in that dir | |
511 | based on repository walk function |
|
518 | based on repository walk function | |
512 |
|
519 | |||
513 | :param repo_name: name of repository |
|
520 | :param repo_name: name of repository | |
514 | :param commit_id: commit id for which to list nodes |
|
521 | :param commit_id: commit id for which to list nodes | |
515 | :param root_path: root path to list |
|
522 | :param root_path: root path to list | |
516 | :param flat: return as a list, if False returns a dict with description |
|
523 | :param flat: return as a list, if False returns a dict with description | |
517 | :param extended_info: show additional info such as md5, binary, size etc |
|
524 | :param extended_info: show additional info such as md5, binary, size etc | |
518 | :param content: add nodes content to the return data |
|
525 | :param content: add nodes content to the return data | |
519 | :param max_file_bytes: will not return file contents over this limit |
|
526 | :param max_file_bytes: will not return file contents over this limit | |
520 |
|
527 | |||
521 | """ |
|
528 | """ | |
522 | _files = list() |
|
529 | _files = list() | |
523 | _dirs = list() |
|
530 | _dirs = list() | |
524 | try: |
|
531 | try: | |
525 | _repo = self._get_repo(repo_name) |
|
532 | _repo = self._get_repo(repo_name) | |
526 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) |
|
533 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) | |
527 | root_path = root_path.lstrip('/') |
|
534 | root_path = root_path.lstrip('/') | |
528 | for __, dirs, files in commit.walk(root_path): |
|
535 | for __, dirs, files in commit.walk(root_path): | |
529 |
|
536 | |||
530 | for f in files: |
|
537 | for f in files: | |
531 | _content = None |
|
538 | _content = None | |
532 | _data = f_name = f.unicode_path |
|
539 | _data = f_name = f.unicode_path | |
533 |
|
540 | |||
534 | if not flat: |
|
541 | if not flat: | |
535 | _data = { |
|
542 | _data = { | |
536 | "name": h.escape(f_name), |
|
543 | "name": h.escape(f_name), | |
537 | "type": "file", |
|
544 | "type": "file", | |
538 | } |
|
545 | } | |
539 | if extended_info: |
|
546 | if extended_info: | |
540 | _data.update({ |
|
547 | _data.update({ | |
541 | "md5": f.md5, |
|
548 | "md5": f.md5, | |
542 | "binary": f.is_binary, |
|
549 | "binary": f.is_binary, | |
543 | "size": f.size, |
|
550 | "size": f.size, | |
544 | "extension": f.extension, |
|
551 | "extension": f.extension, | |
545 | "mimetype": f.mimetype, |
|
552 | "mimetype": f.mimetype, | |
546 | "lines": f.lines()[0] |
|
553 | "lines": f.lines()[0] | |
547 | }) |
|
554 | }) | |
548 |
|
555 | |||
549 | if content: |
|
556 | if content: | |
550 | over_size_limit = (max_file_bytes is not None |
|
557 | over_size_limit = (max_file_bytes is not None | |
551 | and f.size > max_file_bytes) |
|
558 | and f.size > max_file_bytes) | |
552 | full_content = None |
|
559 | full_content = None | |
553 | if not f.is_binary and not over_size_limit: |
|
560 | if not f.is_binary and not over_size_limit: | |
554 | full_content = safe_str(f.content) |
|
561 | full_content = safe_str(f.content) | |
555 |
|
562 | |||
556 | _data.update({ |
|
563 | _data.update({ | |
557 | "content": full_content, |
|
564 | "content": full_content, | |
558 | }) |
|
565 | }) | |
559 | _files.append(_data) |
|
566 | _files.append(_data) | |
560 |
|
567 | |||
561 | for d in dirs: |
|
568 | for d in dirs: | |
562 | _data = d_name = d.unicode_path |
|
569 | _data = d_name = d.unicode_path | |
563 | if not flat: |
|
570 | if not flat: | |
564 | _data = { |
|
571 | _data = { | |
565 | "name": h.escape(d_name), |
|
572 | "name": h.escape(d_name), | |
566 | "type": "dir", |
|
573 | "type": "dir", | |
567 | } |
|
574 | } | |
568 | if extended_info: |
|
575 | if extended_info: | |
569 | _data.update({ |
|
576 | _data.update({ | |
570 | "md5": None, |
|
577 | "md5": None, | |
571 | "binary": None, |
|
578 | "binary": None, | |
572 | "size": None, |
|
579 | "size": None, | |
573 | "extension": None, |
|
580 | "extension": None, | |
574 | }) |
|
581 | }) | |
575 | if content: |
|
582 | if content: | |
576 | _data.update({ |
|
583 | _data.update({ | |
577 | "content": None |
|
584 | "content": None | |
578 | }) |
|
585 | }) | |
579 | _dirs.append(_data) |
|
586 | _dirs.append(_data) | |
580 | except RepositoryError: |
|
587 | except RepositoryError: | |
581 | log.exception("Exception in get_nodes") |
|
588 | log.exception("Exception in get_nodes") | |
582 | raise |
|
589 | raise | |
583 |
|
590 | |||
584 | return _dirs, _files |
|
591 | return _dirs, _files | |
585 |
|
592 | |||
586 | def get_quick_filter_nodes(self, repo_name, commit_id, root_path='/'): |
|
593 | def get_quick_filter_nodes(self, repo_name, commit_id, root_path='/'): | |
587 | """ |
|
594 | """ | |
588 | Generate files for quick filter in files view |
|
595 | Generate files for quick filter in files view | |
589 | """ |
|
596 | """ | |
590 |
|
597 | |||
591 | _files = list() |
|
598 | _files = list() | |
592 | _dirs = list() |
|
599 | _dirs = list() | |
593 | try: |
|
600 | try: | |
594 | _repo = self._get_repo(repo_name) |
|
601 | _repo = self._get_repo(repo_name) | |
595 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) |
|
602 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) | |
596 | root_path = root_path.lstrip('/') |
|
603 | root_path = root_path.lstrip('/') | |
597 | for __, dirs, files in commit.walk(root_path): |
|
604 | for __, dirs, files in commit.walk(root_path): | |
598 |
|
605 | |||
599 | for f in files: |
|
606 | for f in files: | |
600 |
|
607 | |||
601 | _data = { |
|
608 | _data = { | |
602 | "name": h.escape(f.unicode_path), |
|
609 | "name": h.escape(f.unicode_path), | |
603 | "type": "file", |
|
610 | "type": "file", | |
604 | } |
|
611 | } | |
605 |
|
612 | |||
606 | _files.append(_data) |
|
613 | _files.append(_data) | |
607 |
|
614 | |||
608 | for d in dirs: |
|
615 | for d in dirs: | |
609 |
|
616 | |||
610 | _data = { |
|
617 | _data = { | |
611 | "name": h.escape(d.unicode_path), |
|
618 | "name": h.escape(d.unicode_path), | |
612 | "type": "dir", |
|
619 | "type": "dir", | |
613 | } |
|
620 | } | |
614 |
|
621 | |||
615 | _dirs.append(_data) |
|
622 | _dirs.append(_data) | |
616 | except RepositoryError: |
|
623 | except RepositoryError: | |
617 | log.exception("Exception in get_quick_filter_nodes") |
|
624 | log.exception("Exception in get_quick_filter_nodes") | |
618 | raise |
|
625 | raise | |
619 |
|
626 | |||
620 | return _dirs, _files |
|
627 | return _dirs, _files | |
621 |
|
628 | |||
622 | def get_node(self, repo_name, commit_id, file_path, |
|
629 | def get_node(self, repo_name, commit_id, file_path, | |
623 | extended_info=False, content=False, max_file_bytes=None, cache=True): |
|
630 | extended_info=False, content=False, max_file_bytes=None, cache=True): | |
624 | """ |
|
631 | """ | |
625 | retrieve single node from commit |
|
632 | retrieve single node from commit | |
626 | """ |
|
633 | """ | |
627 | try: |
|
634 | try: | |
628 |
|
635 | |||
629 | _repo = self._get_repo(repo_name) |
|
636 | _repo = self._get_repo(repo_name) | |
630 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) |
|
637 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) | |
631 |
|
638 | |||
632 | file_node = commit.get_node(file_path) |
|
639 | file_node = commit.get_node(file_path) | |
633 | if file_node.is_dir(): |
|
640 | if file_node.is_dir(): | |
634 | raise RepositoryError('The given path is a directory') |
|
641 | raise RepositoryError('The given path is a directory') | |
635 |
|
642 | |||
636 | _content = None |
|
643 | _content = None | |
637 | f_name = file_node.unicode_path |
|
644 | f_name = file_node.unicode_path | |
638 |
|
645 | |||
639 | file_data = { |
|
646 | file_data = { | |
640 | "name": h.escape(f_name), |
|
647 | "name": h.escape(f_name), | |
641 | "type": "file", |
|
648 | "type": "file", | |
642 | } |
|
649 | } | |
643 |
|
650 | |||
644 | if extended_info: |
|
651 | if extended_info: | |
645 | file_data.update({ |
|
652 | file_data.update({ | |
646 | "extension": file_node.extension, |
|
653 | "extension": file_node.extension, | |
647 | "mimetype": file_node.mimetype, |
|
654 | "mimetype": file_node.mimetype, | |
648 | }) |
|
655 | }) | |
649 |
|
656 | |||
650 | if cache: |
|
657 | if cache: | |
651 | md5 = file_node.md5 |
|
658 | md5 = file_node.md5 | |
652 | is_binary = file_node.is_binary |
|
659 | is_binary = file_node.is_binary | |
653 | size = file_node.size |
|
660 | size = file_node.size | |
654 | else: |
|
661 | else: | |
655 | is_binary, md5, size, _content = file_node.metadata_uncached() |
|
662 | is_binary, md5, size, _content = file_node.metadata_uncached() | |
656 |
|
663 | |||
657 | file_data.update({ |
|
664 | file_data.update({ | |
658 | "md5": md5, |
|
665 | "md5": md5, | |
659 | "binary": is_binary, |
|
666 | "binary": is_binary, | |
660 | "size": size, |
|
667 | "size": size, | |
661 | }) |
|
668 | }) | |
662 |
|
669 | |||
663 | if content and cache: |
|
670 | if content and cache: | |
664 | # get content + cache |
|
671 | # get content + cache | |
665 | size = file_node.size |
|
672 | size = file_node.size | |
666 | over_size_limit = (max_file_bytes is not None and size > max_file_bytes) |
|
673 | over_size_limit = (max_file_bytes is not None and size > max_file_bytes) | |
667 | full_content = None |
|
674 | full_content = None | |
668 | all_lines = 0 |
|
675 | all_lines = 0 | |
669 | if not file_node.is_binary and not over_size_limit: |
|
676 | if not file_node.is_binary and not over_size_limit: | |
670 | full_content = safe_unicode(file_node.content) |
|
677 | full_content = safe_unicode(file_node.content) | |
671 | all_lines, empty_lines = file_node.count_lines(full_content) |
|
678 | all_lines, empty_lines = file_node.count_lines(full_content) | |
672 |
|
679 | |||
673 | file_data.update({ |
|
680 | file_data.update({ | |
674 | "content": full_content, |
|
681 | "content": full_content, | |
675 | "lines": all_lines |
|
682 | "lines": all_lines | |
676 | }) |
|
683 | }) | |
677 | elif content: |
|
684 | elif content: | |
678 | # get content *without* cache |
|
685 | # get content *without* cache | |
679 | if _content is None: |
|
686 | if _content is None: | |
680 | is_binary, md5, size, _content = file_node.metadata_uncached() |
|
687 | is_binary, md5, size, _content = file_node.metadata_uncached() | |
681 |
|
688 | |||
682 | over_size_limit = (max_file_bytes is not None and size > max_file_bytes) |
|
689 | over_size_limit = (max_file_bytes is not None and size > max_file_bytes) | |
683 | full_content = None |
|
690 | full_content = None | |
684 | all_lines = 0 |
|
691 | all_lines = 0 | |
685 | if not is_binary and not over_size_limit: |
|
692 | if not is_binary and not over_size_limit: | |
686 | full_content = safe_unicode(_content) |
|
693 | full_content = safe_unicode(_content) | |
687 | all_lines, empty_lines = file_node.count_lines(full_content) |
|
694 | all_lines, empty_lines = file_node.count_lines(full_content) | |
688 |
|
695 | |||
689 | file_data.update({ |
|
696 | file_data.update({ | |
690 | "content": full_content, |
|
697 | "content": full_content, | |
691 | "lines": all_lines |
|
698 | "lines": all_lines | |
692 | }) |
|
699 | }) | |
693 |
|
700 | |||
694 | except RepositoryError: |
|
701 | except RepositoryError: | |
695 | log.exception("Exception in get_node") |
|
702 | log.exception("Exception in get_node") | |
696 | raise |
|
703 | raise | |
697 |
|
704 | |||
698 | return file_data |
|
705 | return file_data | |
699 |
|
706 | |||
700 | def get_fts_data(self, repo_name, commit_id, root_path='/'): |
|
707 | def get_fts_data(self, repo_name, commit_id, root_path='/'): | |
701 | """ |
|
708 | """ | |
702 | Fetch node tree for usage in full text search |
|
709 | Fetch node tree for usage in full text search | |
703 | """ |
|
710 | """ | |
704 |
|
711 | |||
705 | tree_info = list() |
|
712 | tree_info = list() | |
706 |
|
713 | |||
707 | try: |
|
714 | try: | |
708 | _repo = self._get_repo(repo_name) |
|
715 | _repo = self._get_repo(repo_name) | |
709 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) |
|
716 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) | |
710 | root_path = root_path.lstrip('/') |
|
717 | root_path = root_path.lstrip('/') | |
711 | for __, dirs, files in commit.walk(root_path): |
|
718 | for __, dirs, files in commit.walk(root_path): | |
712 |
|
719 | |||
713 | for f in files: |
|
720 | for f in files: | |
714 | is_binary, md5, size, _content = f.metadata_uncached() |
|
721 | is_binary, md5, size, _content = f.metadata_uncached() | |
715 | _data = { |
|
722 | _data = { | |
716 | "name": f.unicode_path, |
|
723 | "name": f.unicode_path, | |
717 | "md5": md5, |
|
724 | "md5": md5, | |
718 | "extension": f.extension, |
|
725 | "extension": f.extension, | |
719 | "binary": is_binary, |
|
726 | "binary": is_binary, | |
720 | "size": size |
|
727 | "size": size | |
721 | } |
|
728 | } | |
722 |
|
729 | |||
723 | tree_info.append(_data) |
|
730 | tree_info.append(_data) | |
724 |
|
731 | |||
725 | except RepositoryError: |
|
732 | except RepositoryError: | |
726 | log.exception("Exception in get_nodes") |
|
733 | log.exception("Exception in get_nodes") | |
727 | raise |
|
734 | raise | |
728 |
|
735 | |||
729 | return tree_info |
|
736 | return tree_info | |
730 |
|
737 | |||
731 | def create_nodes(self, user, repo, message, nodes, parent_commit=None, |
|
738 | def create_nodes(self, user, repo, message, nodes, parent_commit=None, | |
732 | author=None, trigger_push_hook=True): |
|
739 | author=None, trigger_push_hook=True): | |
733 | """ |
|
740 | """ | |
734 | Commits given multiple nodes into repo |
|
741 | Commits given multiple nodes into repo | |
735 |
|
742 | |||
736 | :param user: RhodeCode User object or user_id, the commiter |
|
743 | :param user: RhodeCode User object or user_id, the commiter | |
737 | :param repo: RhodeCode Repository object |
|
744 | :param repo: RhodeCode Repository object | |
738 | :param message: commit message |
|
745 | :param message: commit message | |
739 | :param nodes: mapping {filename:{'content':content},...} |
|
746 | :param nodes: mapping {filename:{'content':content},...} | |
740 | :param parent_commit: parent commit, can be empty than it's |
|
747 | :param parent_commit: parent commit, can be empty than it's | |
741 | initial commit |
|
748 | initial commit | |
742 | :param author: author of commit, cna be different that commiter |
|
749 | :param author: author of commit, cna be different that commiter | |
743 | only for git |
|
750 | only for git | |
744 | :param trigger_push_hook: trigger push hooks |
|
751 | :param trigger_push_hook: trigger push hooks | |
745 |
|
752 | |||
746 | :returns: new commited commit |
|
753 | :returns: new commited commit | |
747 | """ |
|
754 | """ | |
748 |
|
755 | |||
749 | user = self._get_user(user) |
|
756 | user = self._get_user(user) | |
750 | scm_instance = repo.scm_instance(cache=False) |
|
757 | scm_instance = repo.scm_instance(cache=False) | |
751 |
|
758 | |||
752 | processed_nodes = [] |
|
759 | processed_nodes = [] | |
753 | for f_path in nodes: |
|
760 | for f_path in nodes: | |
754 | f_path = self._sanitize_path(f_path) |
|
761 | f_path = self._sanitize_path(f_path) | |
755 | content = nodes[f_path]['content'] |
|
762 | content = nodes[f_path]['content'] | |
756 | f_path = safe_str(f_path) |
|
763 | f_path = safe_str(f_path) | |
757 | # decoding here will force that we have proper encoded values |
|
764 | # decoding here will force that we have proper encoded values | |
758 | # in any other case this will throw exceptions and deny commit |
|
765 | # in any other case this will throw exceptions and deny commit | |
759 | if isinstance(content, (basestring,)): |
|
766 | if isinstance(content, (basestring,)): | |
760 | content = safe_str(content) |
|
767 | content = safe_str(content) | |
761 | elif isinstance(content, (file, cStringIO.OutputType,)): |
|
768 | elif isinstance(content, (file, cStringIO.OutputType,)): | |
762 | content = content.read() |
|
769 | content = content.read() | |
763 | else: |
|
770 | else: | |
764 | raise Exception('Content is of unrecognized type %s' % ( |
|
771 | raise Exception('Content is of unrecognized type %s' % ( | |
765 | type(content) |
|
772 | type(content) | |
766 | )) |
|
773 | )) | |
767 | processed_nodes.append((f_path, content)) |
|
774 | processed_nodes.append((f_path, content)) | |
768 |
|
775 | |||
769 | message = safe_unicode(message) |
|
776 | message = safe_unicode(message) | |
770 | commiter = user.full_contact |
|
777 | commiter = user.full_contact | |
771 | author = safe_unicode(author) if author else commiter |
|
778 | author = safe_unicode(author) if author else commiter | |
772 |
|
779 | |||
773 | imc = scm_instance.in_memory_commit |
|
780 | imc = scm_instance.in_memory_commit | |
774 |
|
781 | |||
775 | if not parent_commit: |
|
782 | if not parent_commit: | |
776 | parent_commit = EmptyCommit(alias=scm_instance.alias) |
|
783 | parent_commit = EmptyCommit(alias=scm_instance.alias) | |
777 |
|
784 | |||
778 | if isinstance(parent_commit, EmptyCommit): |
|
785 | if isinstance(parent_commit, EmptyCommit): | |
779 | # EmptyCommit means we we're editing empty repository |
|
786 | # EmptyCommit means we we're editing empty repository | |
780 | parents = None |
|
787 | parents = None | |
781 | else: |
|
788 | else: | |
782 | parents = [parent_commit] |
|
789 | parents = [parent_commit] | |
783 | # add multiple nodes |
|
790 | # add multiple nodes | |
784 | for path, content in processed_nodes: |
|
791 | for path, content in processed_nodes: | |
785 | imc.add(FileNode(path, content=content)) |
|
792 | imc.add(FileNode(path, content=content)) | |
786 | # TODO: handle pre push scenario |
|
793 | # TODO: handle pre push scenario | |
787 | tip = imc.commit(message=message, |
|
794 | tip = imc.commit(message=message, | |
788 | author=author, |
|
795 | author=author, | |
789 | parents=parents, |
|
796 | parents=parents, | |
790 | branch=parent_commit.branch) |
|
797 | branch=parent_commit.branch) | |
791 |
|
798 | |||
792 | self.mark_for_invalidation(repo.repo_name) |
|
799 | self.mark_for_invalidation(repo.repo_name) | |
793 | if trigger_push_hook: |
|
800 | if trigger_push_hook: | |
794 | hooks_utils.trigger_post_push_hook( |
|
801 | hooks_utils.trigger_post_push_hook( | |
795 | username=user.username, action='push_local', |
|
802 | username=user.username, action='push_local', | |
796 | repo_name=repo.repo_name, repo_alias=scm_instance.alias, |
|
803 | repo_name=repo.repo_name, repo_alias=scm_instance.alias, | |
797 | hook_type='post_push', |
|
804 | hook_type='post_push', | |
798 | commit_ids=[tip.raw_id]) |
|
805 | commit_ids=[tip.raw_id]) | |
799 | return tip |
|
806 | return tip | |
800 |
|
807 | |||
801 | def update_nodes(self, user, repo, message, nodes, parent_commit=None, |
|
808 | def update_nodes(self, user, repo, message, nodes, parent_commit=None, | |
802 | author=None, trigger_push_hook=True): |
|
809 | author=None, trigger_push_hook=True): | |
803 | user = self._get_user(user) |
|
810 | user = self._get_user(user) | |
804 | scm_instance = repo.scm_instance(cache=False) |
|
811 | scm_instance = repo.scm_instance(cache=False) | |
805 |
|
812 | |||
806 | message = safe_unicode(message) |
|
813 | message = safe_unicode(message) | |
807 | commiter = user.full_contact |
|
814 | commiter = user.full_contact | |
808 | author = safe_unicode(author) if author else commiter |
|
815 | author = safe_unicode(author) if author else commiter | |
809 |
|
816 | |||
810 | imc = scm_instance.in_memory_commit |
|
817 | imc = scm_instance.in_memory_commit | |
811 |
|
818 | |||
812 | if not parent_commit: |
|
819 | if not parent_commit: | |
813 | parent_commit = EmptyCommit(alias=scm_instance.alias) |
|
820 | parent_commit = EmptyCommit(alias=scm_instance.alias) | |
814 |
|
821 | |||
815 | if isinstance(parent_commit, EmptyCommit): |
|
822 | if isinstance(parent_commit, EmptyCommit): | |
816 | # EmptyCommit means we we're editing empty repository |
|
823 | # EmptyCommit means we we're editing empty repository | |
817 | parents = None |
|
824 | parents = None | |
818 | else: |
|
825 | else: | |
819 | parents = [parent_commit] |
|
826 | parents = [parent_commit] | |
820 |
|
827 | |||
821 | # add multiple nodes |
|
828 | # add multiple nodes | |
822 | for _filename, data in nodes.items(): |
|
829 | for _filename, data in nodes.items(): | |
823 | # new filename, can be renamed from the old one, also sanitaze |
|
830 | # new filename, can be renamed from the old one, also sanitaze | |
824 | # the path for any hack around relative paths like ../../ etc. |
|
831 | # the path for any hack around relative paths like ../../ etc. | |
825 | filename = self._sanitize_path(data['filename']) |
|
832 | filename = self._sanitize_path(data['filename']) | |
826 | old_filename = self._sanitize_path(_filename) |
|
833 | old_filename = self._sanitize_path(_filename) | |
827 | content = data['content'] |
|
834 | content = data['content'] | |
828 | file_mode = data.get('mode') |
|
835 | file_mode = data.get('mode') | |
829 | filenode = FileNode(old_filename, content=content, mode=file_mode) |
|
836 | filenode = FileNode(old_filename, content=content, mode=file_mode) | |
830 | op = data['op'] |
|
837 | op = data['op'] | |
831 | if op == 'add': |
|
838 | if op == 'add': | |
832 | imc.add(filenode) |
|
839 | imc.add(filenode) | |
833 | elif op == 'del': |
|
840 | elif op == 'del': | |
834 | imc.remove(filenode) |
|
841 | imc.remove(filenode) | |
835 | elif op == 'mod': |
|
842 | elif op == 'mod': | |
836 | if filename != old_filename: |
|
843 | if filename != old_filename: | |
837 | # TODO: handle renames more efficient, needs vcs lib changes |
|
844 | # TODO: handle renames more efficient, needs vcs lib changes | |
838 | imc.remove(filenode) |
|
845 | imc.remove(filenode) | |
839 | imc.add(FileNode(filename, content=content, mode=file_mode)) |
|
846 | imc.add(FileNode(filename, content=content, mode=file_mode)) | |
840 | else: |
|
847 | else: | |
841 | imc.change(filenode) |
|
848 | imc.change(filenode) | |
842 |
|
849 | |||
843 | try: |
|
850 | try: | |
844 | # TODO: handle pre push scenario commit changes |
|
851 | # TODO: handle pre push scenario commit changes | |
845 | tip = imc.commit(message=message, |
|
852 | tip = imc.commit(message=message, | |
846 | author=author, |
|
853 | author=author, | |
847 | parents=parents, |
|
854 | parents=parents, | |
848 | branch=parent_commit.branch) |
|
855 | branch=parent_commit.branch) | |
849 | except NodeNotChangedError: |
|
856 | except NodeNotChangedError: | |
850 | raise |
|
857 | raise | |
851 | except Exception as e: |
|
858 | except Exception as e: | |
852 | log.exception("Unexpected exception during call to imc.commit") |
|
859 | log.exception("Unexpected exception during call to imc.commit") | |
853 | raise IMCCommitError(str(e)) |
|
860 | raise IMCCommitError(str(e)) | |
854 | finally: |
|
861 | finally: | |
855 | # always clear caches, if commit fails we want fresh object also |
|
862 | # always clear caches, if commit fails we want fresh object also | |
856 | self.mark_for_invalidation(repo.repo_name) |
|
863 | self.mark_for_invalidation(repo.repo_name) | |
857 |
|
864 | |||
858 | if trigger_push_hook: |
|
865 | if trigger_push_hook: | |
859 | hooks_utils.trigger_post_push_hook( |
|
866 | hooks_utils.trigger_post_push_hook( | |
860 | username=user.username, action='push_local', hook_type='post_push', |
|
867 | username=user.username, action='push_local', hook_type='post_push', | |
861 | repo_name=repo.repo_name, repo_alias=scm_instance.alias, |
|
868 | repo_name=repo.repo_name, repo_alias=scm_instance.alias, | |
862 | commit_ids=[tip.raw_id]) |
|
869 | commit_ids=[tip.raw_id]) | |
863 |
|
870 | |||
864 | return tip |
|
871 | return tip | |
865 |
|
872 | |||
866 | def delete_nodes(self, user, repo, message, nodes, parent_commit=None, |
|
873 | def delete_nodes(self, user, repo, message, nodes, parent_commit=None, | |
867 | author=None, trigger_push_hook=True): |
|
874 | author=None, trigger_push_hook=True): | |
868 | """ |
|
875 | """ | |
869 | Deletes given multiple nodes into `repo` |
|
876 | Deletes given multiple nodes into `repo` | |
870 |
|
877 | |||
871 | :param user: RhodeCode User object or user_id, the committer |
|
878 | :param user: RhodeCode User object or user_id, the committer | |
872 | :param repo: RhodeCode Repository object |
|
879 | :param repo: RhodeCode Repository object | |
873 | :param message: commit message |
|
880 | :param message: commit message | |
874 | :param nodes: mapping {filename:{'content':content},...} |
|
881 | :param nodes: mapping {filename:{'content':content},...} | |
875 | :param parent_commit: parent commit, can be empty than it's initial |
|
882 | :param parent_commit: parent commit, can be empty than it's initial | |
876 | commit |
|
883 | commit | |
877 | :param author: author of commit, cna be different that commiter only |
|
884 | :param author: author of commit, cna be different that commiter only | |
878 | for git |
|
885 | for git | |
879 | :param trigger_push_hook: trigger push hooks |
|
886 | :param trigger_push_hook: trigger push hooks | |
880 |
|
887 | |||
881 | :returns: new commit after deletion |
|
888 | :returns: new commit after deletion | |
882 | """ |
|
889 | """ | |
883 |
|
890 | |||
884 | user = self._get_user(user) |
|
891 | user = self._get_user(user) | |
885 | scm_instance = repo.scm_instance(cache=False) |
|
892 | scm_instance = repo.scm_instance(cache=False) | |
886 |
|
893 | |||
887 | processed_nodes = [] |
|
894 | processed_nodes = [] | |
888 | for f_path in nodes: |
|
895 | for f_path in nodes: | |
889 | f_path = self._sanitize_path(f_path) |
|
896 | f_path = self._sanitize_path(f_path) | |
890 | # content can be empty but for compatabilty it allows same dicts |
|
897 | # content can be empty but for compatabilty it allows same dicts | |
891 | # structure as add_nodes |
|
898 | # structure as add_nodes | |
892 | content = nodes[f_path].get('content') |
|
899 | content = nodes[f_path].get('content') | |
893 | processed_nodes.append((f_path, content)) |
|
900 | processed_nodes.append((f_path, content)) | |
894 |
|
901 | |||
895 | message = safe_unicode(message) |
|
902 | message = safe_unicode(message) | |
896 | commiter = user.full_contact |
|
903 | commiter = user.full_contact | |
897 | author = safe_unicode(author) if author else commiter |
|
904 | author = safe_unicode(author) if author else commiter | |
898 |
|
905 | |||
899 | imc = scm_instance.in_memory_commit |
|
906 | imc = scm_instance.in_memory_commit | |
900 |
|
907 | |||
901 | if not parent_commit: |
|
908 | if not parent_commit: | |
902 | parent_commit = EmptyCommit(alias=scm_instance.alias) |
|
909 | parent_commit = EmptyCommit(alias=scm_instance.alias) | |
903 |
|
910 | |||
904 | if isinstance(parent_commit, EmptyCommit): |
|
911 | if isinstance(parent_commit, EmptyCommit): | |
905 | # EmptyCommit means we we're editing empty repository |
|
912 | # EmptyCommit means we we're editing empty repository | |
906 | parents = None |
|
913 | parents = None | |
907 | else: |
|
914 | else: | |
908 | parents = [parent_commit] |
|
915 | parents = [parent_commit] | |
909 | # add multiple nodes |
|
916 | # add multiple nodes | |
910 | for path, content in processed_nodes: |
|
917 | for path, content in processed_nodes: | |
911 | imc.remove(FileNode(path, content=content)) |
|
918 | imc.remove(FileNode(path, content=content)) | |
912 |
|
919 | |||
913 | # TODO: handle pre push scenario |
|
920 | # TODO: handle pre push scenario | |
914 | tip = imc.commit(message=message, |
|
921 | tip = imc.commit(message=message, | |
915 | author=author, |
|
922 | author=author, | |
916 | parents=parents, |
|
923 | parents=parents, | |
917 | branch=parent_commit.branch) |
|
924 | branch=parent_commit.branch) | |
918 |
|
925 | |||
919 | self.mark_for_invalidation(repo.repo_name) |
|
926 | self.mark_for_invalidation(repo.repo_name) | |
920 | if trigger_push_hook: |
|
927 | if trigger_push_hook: | |
921 | hooks_utils.trigger_post_push_hook( |
|
928 | hooks_utils.trigger_post_push_hook( | |
922 | username=user.username, action='push_local', hook_type='post_push', |
|
929 | username=user.username, action='push_local', hook_type='post_push', | |
923 | repo_name=repo.repo_name, repo_alias=scm_instance.alias, |
|
930 | repo_name=repo.repo_name, repo_alias=scm_instance.alias, | |
924 | commit_ids=[tip.raw_id]) |
|
931 | commit_ids=[tip.raw_id]) | |
925 | return tip |
|
932 | return tip | |
926 |
|
933 | |||
927 | def strip(self, repo, commit_id, branch): |
|
934 | def strip(self, repo, commit_id, branch): | |
928 | scm_instance = repo.scm_instance(cache=False) |
|
935 | scm_instance = repo.scm_instance(cache=False) | |
929 | scm_instance.config.clear_section('hooks') |
|
936 | scm_instance.config.clear_section('hooks') | |
930 | scm_instance.strip(commit_id, branch) |
|
937 | scm_instance.strip(commit_id, branch) | |
931 | self.mark_for_invalidation(repo.repo_name) |
|
938 | self.mark_for_invalidation(repo.repo_name) | |
932 |
|
939 | |||
933 | def get_unread_journal(self): |
|
940 | def get_unread_journal(self): | |
934 | return self.sa.query(UserLog).count() |
|
941 | return self.sa.query(UserLog).count() | |
935 |
|
942 | |||
936 | @classmethod |
|
943 | @classmethod | |
937 | def backend_landing_ref(cls, repo_type): |
|
944 | def backend_landing_ref(cls, repo_type): | |
938 | """ |
|
945 | """ | |
939 | Return a default landing ref based on a repository type. |
|
946 | Return a default landing ref based on a repository type. | |
940 | """ |
|
947 | """ | |
941 |
|
948 | |||
942 | landing_ref = { |
|
949 | landing_ref = { | |
943 | 'hg': ('branch:default', 'default'), |
|
950 | 'hg': ('branch:default', 'default'), | |
944 | 'git': ('branch:master', 'master'), |
|
951 | 'git': ('branch:master', 'master'), | |
945 | 'svn': ('rev:tip', 'latest tip'), |
|
952 | 'svn': ('rev:tip', 'latest tip'), | |
946 | 'default': ('rev:tip', 'latest tip'), |
|
953 | 'default': ('rev:tip', 'latest tip'), | |
947 | } |
|
954 | } | |
948 |
|
955 | |||
949 | return landing_ref.get(repo_type) or landing_ref['default'] |
|
956 | return landing_ref.get(repo_type) or landing_ref['default'] | |
950 |
|
957 | |||
951 | def get_repo_landing_revs(self, translator, repo=None): |
|
958 | def get_repo_landing_revs(self, translator, repo=None): | |
952 | """ |
|
959 | """ | |
953 | Generates select option with tags branches and bookmarks (for hg only) |
|
960 | Generates select option with tags branches and bookmarks (for hg only) | |
954 | grouped by type |
|
961 | grouped by type | |
955 |
|
962 | |||
956 | :param repo: |
|
963 | :param repo: | |
957 | """ |
|
964 | """ | |
958 | _ = translator |
|
965 | _ = translator | |
959 | repo = self._get_repo(repo) |
|
966 | repo = self._get_repo(repo) | |
960 |
|
967 | |||
961 | if repo: |
|
968 | if repo: | |
962 | repo_type = repo.repo_type |
|
969 | repo_type = repo.repo_type | |
963 | else: |
|
970 | else: | |
964 | repo_type = 'default' |
|
971 | repo_type = 'default' | |
965 |
|
972 | |||
966 | default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type) |
|
973 | default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type) | |
967 |
|
974 | |||
968 | default_ref_options = [ |
|
975 | default_ref_options = [ | |
969 | [default_landing_ref, landing_ref_lbl] |
|
976 | [default_landing_ref, landing_ref_lbl] | |
970 | ] |
|
977 | ] | |
971 | default_choices = [ |
|
978 | default_choices = [ | |
972 | default_landing_ref |
|
979 | default_landing_ref | |
973 | ] |
|
980 | ] | |
974 |
|
981 | |||
975 | if not repo: |
|
982 | if not repo: | |
976 | return default_choices, default_ref_options |
|
983 | return default_choices, default_ref_options | |
977 |
|
984 | |||
978 | repo = repo.scm_instance() |
|
985 | repo = repo.scm_instance() | |
979 |
|
986 | |||
980 | ref_options = [('rev:tip', 'latest tip')] |
|
987 | ref_options = [('rev:tip', 'latest tip')] | |
981 | choices = ['rev:tip'] |
|
988 | choices = ['rev:tip'] | |
982 |
|
989 | |||
983 | # branches |
|
990 | # branches | |
984 | branch_group = [(u'branch:%s' % safe_unicode(b), safe_unicode(b)) for b in repo.branches] |
|
991 | branch_group = [(u'branch:%s' % safe_unicode(b), safe_unicode(b)) for b in repo.branches] | |
985 | if not branch_group: |
|
992 | if not branch_group: | |
986 | # new repo, or without maybe a branch? |
|
993 | # new repo, or without maybe a branch? | |
987 | branch_group = default_ref_options |
|
994 | branch_group = default_ref_options | |
988 |
|
995 | |||
989 | branches_group = (branch_group, _("Branches")) |
|
996 | branches_group = (branch_group, _("Branches")) | |
990 | ref_options.append(branches_group) |
|
997 | ref_options.append(branches_group) | |
991 | choices.extend([x[0] for x in branches_group[0]]) |
|
998 | choices.extend([x[0] for x in branches_group[0]]) | |
992 |
|
999 | |||
993 | # bookmarks for HG |
|
1000 | # bookmarks for HG | |
994 | if repo.alias == 'hg': |
|
1001 | if repo.alias == 'hg': | |
995 | bookmarks_group = ( |
|
1002 | bookmarks_group = ( | |
996 | [(u'book:%s' % safe_unicode(b), safe_unicode(b)) |
|
1003 | [(u'book:%s' % safe_unicode(b), safe_unicode(b)) | |
997 | for b in repo.bookmarks], |
|
1004 | for b in repo.bookmarks], | |
998 | _("Bookmarks")) |
|
1005 | _("Bookmarks")) | |
999 | ref_options.append(bookmarks_group) |
|
1006 | ref_options.append(bookmarks_group) | |
1000 | choices.extend([x[0] for x in bookmarks_group[0]]) |
|
1007 | choices.extend([x[0] for x in bookmarks_group[0]]) | |
1001 |
|
1008 | |||
1002 | # tags |
|
1009 | # tags | |
1003 | tags_group = ( |
|
1010 | tags_group = ( | |
1004 | [(u'tag:%s' % safe_unicode(t), safe_unicode(t)) |
|
1011 | [(u'tag:%s' % safe_unicode(t), safe_unicode(t)) | |
1005 | for t in repo.tags], |
|
1012 | for t in repo.tags], | |
1006 | _("Tags")) |
|
1013 | _("Tags")) | |
1007 | ref_options.append(tags_group) |
|
1014 | ref_options.append(tags_group) | |
1008 | choices.extend([x[0] for x in tags_group[0]]) |
|
1015 | choices.extend([x[0] for x in tags_group[0]]) | |
1009 |
|
1016 | |||
1010 | return choices, ref_options |
|
1017 | return choices, ref_options | |
1011 |
|
1018 | |||
1012 | def get_server_info(self, environ=None): |
|
1019 | def get_server_info(self, environ=None): | |
1013 | server_info = get_system_info(environ) |
|
1020 | server_info = get_system_info(environ) | |
1014 | return server_info |
|
1021 | return server_info |
@@ -1,806 +1,816 b'' | |||||
1 | // navigation.less |
|
1 | // navigation.less | |
2 | // For use in RhodeCode applications; |
|
2 | // For use in RhodeCode applications; | |
3 | // see style guide documentation for guidelines. |
|
3 | // see style guide documentation for guidelines. | |
4 |
|
4 | |||
5 | // TOP MAIN DARK NAVIGATION |
|
5 | // TOP MAIN DARK NAVIGATION | |
6 |
|
6 | |||
7 | .header .main_nav.horizontal-list { |
|
7 | .header .main_nav.horizontal-list { | |
8 | float: right; |
|
8 | float: right; | |
9 | color: @grey4; |
|
9 | color: @grey4; | |
10 | > li { |
|
10 | > li { | |
11 | a { |
|
11 | a { | |
12 | color: @grey4; |
|
12 | color: @grey4; | |
13 | } |
|
13 | } | |
14 | } |
|
14 | } | |
15 | } |
|
15 | } | |
16 |
|
16 | |||
17 | // HEADER NAVIGATION |
|
17 | // HEADER NAVIGATION | |
18 |
|
18 | |||
19 | .horizontal-list { |
|
19 | .horizontal-list { | |
20 | display: block; |
|
20 | display: block; | |
21 | margin: 0; |
|
21 | margin: 0; | |
22 | padding: 0; |
|
22 | padding: 0; | |
23 | -webkit-padding-start: 0; |
|
23 | -webkit-padding-start: 0; | |
24 | text-align: left; |
|
24 | text-align: left; | |
25 | font-size: @navigation-fontsize; |
|
25 | font-size: @navigation-fontsize; | |
26 | color: @grey6; |
|
26 | color: @grey6; | |
27 | z-index:10; |
|
27 | z-index:10; | |
28 |
|
28 | |||
29 | li { |
|
29 | li { | |
30 | line-height: 1em; |
|
30 | line-height: 1em; | |
31 | list-style-type: none; |
|
31 | list-style-type: none; | |
32 | margin: 0 20px 0 0; |
|
32 | margin: 0 20px 0 0; | |
33 |
|
33 | |||
34 | a { |
|
34 | a { | |
35 | padding: 0 .5em; |
|
35 | padding: 0 .5em; | |
36 |
|
36 | |||
37 | &.menu_link_notifications { |
|
37 | &.menu_link_notifications { | |
38 | .pill(7px,@rcblue); |
|
38 | .pill(7px,@rcblue); | |
39 | display: inline; |
|
39 | display: inline; | |
40 | margin: 0 7px 0 .7em; |
|
40 | margin: 0 7px 0 .7em; | |
41 | font-size: @basefontsize; |
|
41 | font-size: @basefontsize; | |
42 | color: white; |
|
42 | color: white; | |
43 |
|
43 | |||
44 | &.empty { |
|
44 | &.empty { | |
45 | background-color: @grey4; |
|
45 | background-color: @grey4; | |
46 | } |
|
46 | } | |
47 |
|
47 | |||
48 | &:hover { |
|
48 | &:hover { | |
49 | background-color: @rcdarkblue; |
|
49 | background-color: @rcdarkblue; | |
50 | } |
|
50 | } | |
51 | } |
|
51 | } | |
52 | } |
|
52 | } | |
53 | .pill_container { |
|
53 | .pill_container { | |
54 | margin: 1.25em 0px 0px 0px; |
|
54 | margin: 1.25em 0px 0px 0px; | |
55 | float: right; |
|
55 | float: right; | |
56 | } |
|
56 | } | |
57 |
|
57 | |||
58 | &#quick_login_li { |
|
58 | &#quick_login_li { | |
59 | &:hover { |
|
59 | &:hover { | |
60 | color: @grey5; |
|
60 | color: @grey5; | |
61 | } |
|
61 | } | |
62 |
|
62 | |||
63 | a.menu_link_notifications { |
|
63 | a.menu_link_notifications { | |
64 | color: white; |
|
64 | color: white; | |
65 | } |
|
65 | } | |
66 |
|
66 | |||
67 | .user { |
|
67 | .user { | |
68 | padding-bottom: 10px; |
|
68 | padding-bottom: 10px; | |
69 | } |
|
69 | } | |
70 | } |
|
70 | } | |
71 |
|
71 | |||
72 | &:before { content: none; } |
|
72 | &:before { content: none; } | |
73 |
|
73 | |||
74 | &:last-child { |
|
74 | &:last-child { | |
75 | .menulabel { |
|
75 | .menulabel { | |
76 | padding-right: 0; |
|
76 | padding-right: 0; | |
77 | border-right: none; |
|
77 | border-right: none; | |
78 |
|
78 | |||
79 | .show_more { |
|
79 | .show_more { | |
80 | padding-right: 0; |
|
80 | padding-right: 0; | |
81 | } |
|
81 | } | |
82 | } |
|
82 | } | |
83 |
|
83 | |||
84 | &> a { |
|
84 | &> a { | |
85 | border-bottom: none; |
|
85 | border-bottom: none; | |
86 | } |
|
86 | } | |
87 | } |
|
87 | } | |
88 |
|
88 | |||
89 | &.open { |
|
89 | &.open { | |
90 |
|
90 | |||
91 | a { |
|
91 | a { | |
92 | color: white; |
|
92 | color: white; | |
93 | } |
|
93 | } | |
94 | } |
|
94 | } | |
95 |
|
95 | |||
96 | &:focus { |
|
96 | &:focus { | |
97 | outline: none; |
|
97 | outline: none; | |
98 | } |
|
98 | } | |
99 |
|
99 | |||
100 | ul li { |
|
100 | ul li { | |
101 | display: block; |
|
101 | display: block; | |
102 |
|
102 | |||
103 | &:last-child> a { |
|
103 | &:last-child> a { | |
104 | border-bottom: none; |
|
104 | border-bottom: none; | |
105 | } |
|
105 | } | |
106 |
|
106 | |||
107 | ul li:last-child a { |
|
107 | ul li:last-child a { | |
108 | /* we don't expect more then 3 levels of submenu and the third |
|
108 | /* we don't expect more then 3 levels of submenu and the third | |
109 | level can have different html structure */ |
|
109 | level can have different html structure */ | |
110 | border-bottom: none; |
|
110 | border-bottom: none; | |
111 | } |
|
111 | } | |
112 | } |
|
112 | } | |
113 | } |
|
113 | } | |
114 |
|
114 | |||
115 | > li { |
|
115 | > li { | |
116 | float: left; |
|
116 | float: left; | |
117 | display: block; |
|
117 | display: block; | |
118 | padding: 0; |
|
118 | padding: 0; | |
119 |
|
119 | |||
120 | > a, |
|
120 | > a, | |
121 | &.has_select2 a { |
|
121 | &.has_select2 a { | |
122 | display: block; |
|
122 | display: block; | |
123 | padding: 10px 0; |
|
123 | padding: 10px 0; | |
124 | } |
|
124 | } | |
125 |
|
125 | |||
126 | .menulabel { |
|
126 | .menulabel { | |
127 | line-height: 1em; |
|
127 | line-height: 1em; | |
128 | // for this specifically we do not use a variable |
|
128 | // for this specifically we do not use a variable | |
129 | } |
|
129 | } | |
130 |
|
130 | |||
|
131 | .menulink-counter { | |||
|
132 | border: 1px solid @grey2; | |||
|
133 | border-radius: @border-radius; | |||
|
134 | background: @grey7; | |||
|
135 | display: inline-block; | |||
|
136 | padding: 0px 4px; | |||
|
137 | text-align: center; | |||
|
138 | font-size: 12px; | |||
|
139 | } | |||
|
140 | ||||
131 | .pr_notifications { |
|
141 | .pr_notifications { | |
132 | padding-left: .5em; |
|
142 | padding-left: .5em; | |
133 | } |
|
143 | } | |
134 |
|
144 | |||
135 | .pr_notifications + .menulabel { |
|
145 | .pr_notifications + .menulabel { | |
136 | display:inline; |
|
146 | display:inline; | |
137 | padding-left: 0; |
|
147 | padding-left: 0; | |
138 | } |
|
148 | } | |
139 |
|
149 | |||
140 | &:hover, |
|
150 | &:hover, | |
141 | &.open, |
|
151 | &.open, | |
142 | &.active { |
|
152 | &.active { | |
143 | a { |
|
153 | a { | |
144 | color: @rcblue; |
|
154 | color: @rcblue; | |
145 | } |
|
155 | } | |
146 | } |
|
156 | } | |
147 | } |
|
157 | } | |
148 |
|
158 | |||
149 | pre { |
|
159 | pre { | |
150 | margin: 0; |
|
160 | margin: 0; | |
151 | padding: 0; |
|
161 | padding: 0; | |
152 | } |
|
162 | } | |
153 |
|
163 | |||
154 | .select2-container, |
|
164 | .select2-container, | |
155 | .menulink.childs { |
|
165 | .menulink.childs { | |
156 | position: relative; |
|
166 | position: relative; | |
157 | } |
|
167 | } | |
158 |
|
168 | |||
159 | .menulink { |
|
169 | .menulink { | |
160 | &.disabled { |
|
170 | &.disabled { | |
161 | color: @grey3; |
|
171 | color: @grey3; | |
162 | cursor: default; |
|
172 | cursor: default; | |
163 | opacity: 0.5; |
|
173 | opacity: 0.5; | |
164 | } |
|
174 | } | |
165 | } |
|
175 | } | |
166 |
|
176 | |||
167 | #quick_login { |
|
177 | #quick_login { | |
168 |
|
178 | |||
169 | li a { |
|
179 | li a { | |
170 | padding: .5em 0; |
|
180 | padding: .5em 0; | |
171 | border-bottom: none; |
|
181 | border-bottom: none; | |
172 | color: @grey2; |
|
182 | color: @grey2; | |
173 |
|
183 | |||
174 | &:hover { color: @grey1; } |
|
184 | &:hover { color: @grey1; } | |
175 | } |
|
185 | } | |
176 | } |
|
186 | } | |
177 |
|
187 | |||
178 | #quick_login_link { |
|
188 | #quick_login_link { | |
179 | display: inline-block; |
|
189 | display: inline-block; | |
180 |
|
190 | |||
181 | .gravatar { |
|
191 | .gravatar { | |
182 | border: 1px solid @grey5; |
|
192 | border: 1px solid @grey5; | |
183 | } |
|
193 | } | |
184 |
|
194 | |||
185 | .gravatar-login { |
|
195 | .gravatar-login { | |
186 | height: 20px; |
|
196 | height: 20px; | |
187 | width: 20px; |
|
197 | width: 20px; | |
188 | margin: -8px 0; |
|
198 | margin: -8px 0; | |
189 | padding: 0; |
|
199 | padding: 0; | |
190 | } |
|
200 | } | |
191 |
|
201 | |||
192 | &:hover .user { |
|
202 | &:hover .user { | |
193 | color: @grey6; |
|
203 | color: @grey6; | |
194 | } |
|
204 | } | |
195 | } |
|
205 | } | |
196 | } |
|
206 | } | |
197 | .header .horizontal-list { |
|
207 | .header .horizontal-list { | |
198 |
|
208 | |||
199 | li { |
|
209 | li { | |
200 |
|
210 | |||
201 | &#quick_login_li { |
|
211 | &#quick_login_li { | |
202 | padding-left: .5em; |
|
212 | padding-left: .5em; | |
203 | margin-right: 0px; |
|
213 | margin-right: 0px; | |
204 |
|
214 | |||
205 | &:hover #quick_login_link { |
|
215 | &:hover #quick_login_link { | |
206 | color: inherit; |
|
216 | color: inherit; | |
207 | } |
|
217 | } | |
208 |
|
218 | |||
209 | .menu_link_user { |
|
219 | .menu_link_user { | |
210 | padding: 0 2px; |
|
220 | padding: 0 2px; | |
211 | } |
|
221 | } | |
212 | } |
|
222 | } | |
213 | list-style-type: none; |
|
223 | list-style-type: none; | |
214 | } |
|
224 | } | |
215 |
|
225 | |||
216 | > li { |
|
226 | > li { | |
217 |
|
227 | |||
218 | a { |
|
228 | a { | |
219 | padding: 18px 0 12px 0; |
|
229 | padding: 18px 0 12px 0; | |
220 | color: @nav-grey; |
|
230 | color: @nav-grey; | |
221 |
|
231 | |||
222 | &.menu_link_notifications { |
|
232 | &.menu_link_notifications { | |
223 | padding: 1px 8px; |
|
233 | padding: 1px 8px; | |
224 | } |
|
234 | } | |
225 | } |
|
235 | } | |
226 |
|
236 | |||
227 | &:hover, |
|
237 | &:hover, | |
228 | &.open, |
|
238 | &.open, | |
229 | &.active { |
|
239 | &.active { | |
230 | .pill_container a { |
|
240 | .pill_container a { | |
231 | // don't select text for the pill container, it has it' own |
|
241 | // don't select text for the pill container, it has it' own | |
232 | // hover behaviour |
|
242 | // hover behaviour | |
233 | color: @nav-grey; |
|
243 | color: @nav-grey; | |
234 | } |
|
244 | } | |
235 | } |
|
245 | } | |
236 |
|
246 | |||
237 | &:hover, |
|
247 | &:hover, | |
238 | &.open, |
|
248 | &.open, | |
239 | &.active { |
|
249 | &.active { | |
240 | a { |
|
250 | a { | |
241 | color: @grey6; |
|
251 | color: @grey6; | |
242 | } |
|
252 | } | |
243 | } |
|
253 | } | |
244 |
|
254 | |||
245 | .select2-dropdown-open a { |
|
255 | .select2-dropdown-open a { | |
246 | color: @grey6; |
|
256 | color: @grey6; | |
247 | } |
|
257 | } | |
248 |
|
258 | |||
249 | .repo-switcher { |
|
259 | .repo-switcher { | |
250 | padding-left: 0; |
|
260 | padding-left: 0; | |
251 |
|
261 | |||
252 | .menulabel { |
|
262 | .menulabel { | |
253 | padding-left: 0; |
|
263 | padding-left: 0; | |
254 | } |
|
264 | } | |
255 | } |
|
265 | } | |
256 | } |
|
266 | } | |
257 |
|
267 | |||
258 | li ul li { |
|
268 | li ul li { | |
259 | background-color:@grey2; |
|
269 | background-color:@grey2; | |
260 |
|
270 | |||
261 | a { |
|
271 | a { | |
262 | padding: .5em 0; |
|
272 | padding: .5em 0; | |
263 | border-bottom: @border-thickness solid @border-default-color; |
|
273 | border-bottom: @border-thickness solid @border-default-color; | |
264 | color: @grey6; |
|
274 | color: @grey6; | |
265 | } |
|
275 | } | |
266 |
|
276 | |||
267 | &:last-child a, &.last a{ |
|
277 | &:last-child a, &.last a{ | |
268 | border-bottom: none; |
|
278 | border-bottom: none; | |
269 | } |
|
279 | } | |
270 |
|
280 | |||
271 | &:hover { |
|
281 | &:hover { | |
272 | background-color: @grey3; |
|
282 | background-color: @grey3; | |
273 | } |
|
283 | } | |
274 | } |
|
284 | } | |
275 |
|
285 | |||
276 | .submenu { |
|
286 | .submenu { | |
277 | margin-top: 5px; |
|
287 | margin-top: 5px; | |
278 | } |
|
288 | } | |
279 | } |
|
289 | } | |
280 |
|
290 | |||
281 | // SUBMENUS |
|
291 | // SUBMENUS | |
282 | .navigation .submenu { |
|
292 | .navigation .submenu { | |
283 | display: none; |
|
293 | display: none; | |
284 | } |
|
294 | } | |
285 |
|
295 | |||
286 | .navigation li.open { |
|
296 | .navigation li.open { | |
287 | .submenu { |
|
297 | .submenu { | |
288 | display: block; |
|
298 | display: block; | |
289 | } |
|
299 | } | |
290 | } |
|
300 | } | |
291 |
|
301 | |||
292 | .navigation li:last-child .submenu { |
|
302 | .navigation li:last-child .submenu { | |
293 | right: auto; |
|
303 | right: auto; | |
294 | left: 0; |
|
304 | left: 0; | |
295 | border: 1px solid @grey5; |
|
305 | border: 1px solid @grey5; | |
296 | background: @white; |
|
306 | background: @white; | |
297 | box-shadow: @dropdown-shadow; |
|
307 | box-shadow: @dropdown-shadow; | |
298 | } |
|
308 | } | |
299 |
|
309 | |||
300 | .submenu { |
|
310 | .submenu { | |
301 | position: absolute; |
|
311 | position: absolute; | |
302 | top: 100%; |
|
312 | top: 100%; | |
303 | left: 0; |
|
313 | left: 0; | |
304 | min-width: 180px; |
|
314 | min-width: 180px; | |
305 | margin: 2px 0 0; |
|
315 | margin: 2px 0 0; | |
306 | padding: 0; |
|
316 | padding: 0; | |
307 | text-align: left; |
|
317 | text-align: left; | |
308 | font-family: @text-light; |
|
318 | font-family: @text-light; | |
309 | border-radius: @border-radius; |
|
319 | border-radius: @border-radius; | |
310 | z-index: 20; |
|
320 | z-index: 20; | |
311 |
|
321 | |||
312 | li { |
|
322 | li { | |
313 | display: block; |
|
323 | display: block; | |
314 | margin: 0; |
|
324 | margin: 0; | |
315 | padding: 0 .5em; |
|
325 | padding: 0 .5em; | |
316 | line-height: 1em; |
|
326 | line-height: 1em; | |
317 | color: @grey3; |
|
327 | color: @grey3; | |
318 | background-color: @white; |
|
328 | background-color: @white; | |
319 | list-style-type: none; |
|
329 | list-style-type: none; | |
320 |
|
330 | |||
321 | a { |
|
331 | a { | |
322 | display: block; |
|
332 | display: block; | |
323 | width: 100%; |
|
333 | width: 100%; | |
324 | padding: .5em 0; |
|
334 | padding: .5em 0; | |
325 | border-right: none; |
|
335 | border-right: none; | |
326 | border-bottom: @border-thickness solid white; |
|
336 | border-bottom: @border-thickness solid white; | |
327 | color: @grey3; |
|
337 | color: @grey3; | |
328 | } |
|
338 | } | |
329 |
|
339 | |||
330 | ul { |
|
340 | ul { | |
331 | display: none; |
|
341 | display: none; | |
332 | position: absolute; |
|
342 | position: absolute; | |
333 | top: 0; |
|
343 | top: 0; | |
334 | right: 100%; |
|
344 | right: 100%; | |
335 | padding: 0; |
|
345 | padding: 0; | |
336 | z-index: 30; |
|
346 | z-index: 30; | |
337 | } |
|
347 | } | |
338 | &:hover { |
|
348 | &:hover { | |
339 | background-color: @grey7; |
|
349 | background-color: @grey7; | |
340 | -webkit-transition: background .3s; |
|
350 | -webkit-transition: background .3s; | |
341 | -moz-transition: background .3s; |
|
351 | -moz-transition: background .3s; | |
342 | -o-transition: background .3s; |
|
352 | -o-transition: background .3s; | |
343 | transition: background .3s; |
|
353 | transition: background .3s; | |
344 |
|
354 | |||
345 | ul { |
|
355 | ul { | |
346 | display: block; |
|
356 | display: block; | |
347 | } |
|
357 | } | |
348 | } |
|
358 | } | |
349 | } |
|
359 | } | |
350 |
|
360 | |||
351 | } |
|
361 | } | |
352 |
|
362 | |||
353 |
|
363 | |||
354 |
|
364 | |||
355 |
|
365 | |||
356 | // repo dropdown |
|
366 | // repo dropdown | |
357 | .quick_repo_menu { |
|
367 | .quick_repo_menu { | |
358 | width: 15px; |
|
368 | width: 15px; | |
359 | text-align: center; |
|
369 | text-align: center; | |
360 | position: relative; |
|
370 | position: relative; | |
361 | cursor: pointer; |
|
371 | cursor: pointer; | |
362 |
|
372 | |||
363 | div { |
|
373 | div { | |
364 | overflow: visible !important; |
|
374 | overflow: visible !important; | |
365 | } |
|
375 | } | |
366 |
|
376 | |||
367 | &.sorting { |
|
377 | &.sorting { | |
368 | cursor: auto; |
|
378 | cursor: auto; | |
369 | } |
|
379 | } | |
370 |
|
380 | |||
371 | &:hover { |
|
381 | &:hover { | |
372 | .menu_items_container { |
|
382 | .menu_items_container { | |
373 | position: absolute; |
|
383 | position: absolute; | |
374 | display: block; |
|
384 | display: block; | |
375 | } |
|
385 | } | |
376 | .menu_items { |
|
386 | .menu_items { | |
377 | display: block; |
|
387 | display: block; | |
378 | } |
|
388 | } | |
379 | } |
|
389 | } | |
380 |
|
390 | |||
381 | i { |
|
391 | i { | |
382 | margin: 0; |
|
392 | margin: 0; | |
383 | color: @grey4; |
|
393 | color: @grey4; | |
384 | } |
|
394 | } | |
385 |
|
395 | |||
386 | .menu_items_container { |
|
396 | .menu_items_container { | |
387 | position: absolute; |
|
397 | position: absolute; | |
388 | top: 0; |
|
398 | top: 0; | |
389 | left: 100%; |
|
399 | left: 100%; | |
390 | margin: 0; |
|
400 | margin: 0; | |
391 | padding: 0; |
|
401 | padding: 0; | |
392 | list-style: none; |
|
402 | list-style: none; | |
393 | background-color: @grey6; |
|
403 | background-color: @grey6; | |
394 | z-index: 999; |
|
404 | z-index: 999; | |
395 | text-align: left; |
|
405 | text-align: left; | |
396 |
|
406 | |||
397 | a { |
|
407 | a { | |
398 | color: @grey2; |
|
408 | color: @grey2; | |
399 | } |
|
409 | } | |
400 |
|
410 | |||
401 | ul.menu_items { |
|
411 | ul.menu_items { | |
402 | margin: 0; |
|
412 | margin: 0; | |
403 | padding: 0; |
|
413 | padding: 0; | |
404 | } |
|
414 | } | |
405 |
|
415 | |||
406 | li { |
|
416 | li { | |
407 | margin: 0; |
|
417 | margin: 0; | |
408 | padding: 0; |
|
418 | padding: 0; | |
409 | line-height: 1em; |
|
419 | line-height: 1em; | |
410 | list-style-type: none; |
|
420 | list-style-type: none; | |
411 |
|
421 | |||
412 | a { |
|
422 | a { | |
413 | display: block; |
|
423 | display: block; | |
414 | height: 16px; |
|
424 | height: 16px; | |
415 | padding: 8px; //must add up to td height (28px) |
|
425 | padding: 8px; //must add up to td height (28px) | |
416 | width: 120px; // set width |
|
426 | width: 120px; // set width | |
417 |
|
427 | |||
418 | &:hover { |
|
428 | &:hover { | |
419 | background-color: @grey5; |
|
429 | background-color: @grey5; | |
420 | -webkit-transition: background .3s; |
|
430 | -webkit-transition: background .3s; | |
421 | -moz-transition: background .3s; |
|
431 | -moz-transition: background .3s; | |
422 | -o-transition: background .3s; |
|
432 | -o-transition: background .3s; | |
423 | transition: background .3s; |
|
433 | transition: background .3s; | |
424 | } |
|
434 | } | |
425 | } |
|
435 | } | |
426 | } |
|
436 | } | |
427 | } |
|
437 | } | |
428 | } |
|
438 | } | |
429 |
|
439 | |||
430 |
|
440 | |||
431 | // new objects main action |
|
441 | // new objects main action | |
432 | .action-menu { |
|
442 | .action-menu { | |
433 | left: auto; |
|
443 | left: auto; | |
434 | right: 0; |
|
444 | right: 0; | |
435 | padding: 12px; |
|
445 | padding: 12px; | |
436 | z-index: 999; |
|
446 | z-index: 999; | |
437 | overflow: hidden; |
|
447 | overflow: hidden; | |
438 | background-color: #fff; |
|
448 | background-color: #fff; | |
439 | border: 1px solid @grey5; |
|
449 | border: 1px solid @grey5; | |
440 | color: @grey2; |
|
450 | color: @grey2; | |
441 | box-shadow: @dropdown-shadow; |
|
451 | box-shadow: @dropdown-shadow; | |
442 |
|
452 | |||
443 | .submenu-title { |
|
453 | .submenu-title { | |
444 | font-weight: bold; |
|
454 | font-weight: bold; | |
445 | } |
|
455 | } | |
446 |
|
456 | |||
447 | .submenu-title:not(:first-of-type) { |
|
457 | .submenu-title:not(:first-of-type) { | |
448 | padding-top: 10px; |
|
458 | padding-top: 10px; | |
449 | } |
|
459 | } | |
450 |
|
460 | |||
451 | &.submenu { |
|
461 | &.submenu { | |
452 | min-width: 200px; |
|
462 | min-width: 200px; | |
453 |
|
463 | |||
454 | ol { |
|
464 | ol { | |
455 | padding:0; |
|
465 | padding:0; | |
456 | } |
|
466 | } | |
457 |
|
467 | |||
458 | li { |
|
468 | li { | |
459 | display: block; |
|
469 | display: block; | |
460 | margin: 0; |
|
470 | margin: 0; | |
461 | padding: .2em .5em; |
|
471 | padding: .2em .5em; | |
462 | line-height: 1em; |
|
472 | line-height: 1em; | |
463 |
|
473 | |||
464 | background-color: #fff; |
|
474 | background-color: #fff; | |
465 | list-style-type: none; |
|
475 | list-style-type: none; | |
466 |
|
476 | |||
467 | a { |
|
477 | a { | |
468 | padding: 4px; |
|
478 | padding: 4px; | |
469 | color: @grey4 !important; |
|
479 | color: @grey4 !important; | |
470 | border-bottom: none; |
|
480 | border-bottom: none; | |
471 | } |
|
481 | } | |
472 | } |
|
482 | } | |
473 | li:not(.submenu-title) a:hover{ |
|
483 | li:not(.submenu-title) a:hover{ | |
474 | color: @grey2 !important; |
|
484 | color: @grey2 !important; | |
475 | } |
|
485 | } | |
476 | } |
|
486 | } | |
477 | } |
|
487 | } | |
478 |
|
488 | |||
479 |
|
489 | |||
480 | // Header Repository Switcher |
|
490 | // Header Repository Switcher | |
481 | // Select2 Dropdown |
|
491 | // Select2 Dropdown | |
482 | #select2-drop.select2-drop.repo-switcher-dropdown { |
|
492 | #select2-drop.select2-drop.repo-switcher-dropdown { | |
483 | width: auto !important; |
|
493 | width: auto !important; | |
484 | margin-top: 5px; |
|
494 | margin-top: 5px; | |
485 | padding: 1em 0; |
|
495 | padding: 1em 0; | |
486 | text-align: left; |
|
496 | text-align: left; | |
487 | .border-radius-bottom(@border-radius); |
|
497 | .border-radius-bottom(@border-radius); | |
488 | border-color: transparent; |
|
498 | border-color: transparent; | |
489 | color: @grey6; |
|
499 | color: @grey6; | |
490 | background-color: @grey2; |
|
500 | background-color: @grey2; | |
491 |
|
501 | |||
492 | input { |
|
502 | input { | |
493 | min-width: 90%; |
|
503 | min-width: 90%; | |
494 | } |
|
504 | } | |
495 |
|
505 | |||
496 | ul.select2-result-sub { |
|
506 | ul.select2-result-sub { | |
497 |
|
507 | |||
498 | li { |
|
508 | li { | |
499 | line-height: 1em; |
|
509 | line-height: 1em; | |
500 |
|
510 | |||
501 | &:hover, |
|
511 | &:hover, | |
502 | &.select2-highlighted { |
|
512 | &.select2-highlighted { | |
503 | background-color: @grey3; |
|
513 | background-color: @grey3; | |
504 | } |
|
514 | } | |
505 | } |
|
515 | } | |
506 |
|
516 | |||
507 | &:before { content: none; } |
|
517 | &:before { content: none; } | |
508 | } |
|
518 | } | |
509 |
|
519 | |||
510 | ul.select2-results { |
|
520 | ul.select2-results { | |
511 | min-width: 200px; |
|
521 | min-width: 200px; | |
512 | margin: 0; |
|
522 | margin: 0; | |
513 | padding: 0; |
|
523 | padding: 0; | |
514 | list-style-type: none; |
|
524 | list-style-type: none; | |
515 | overflow-x: visible; |
|
525 | overflow-x: visible; | |
516 | overflow-y: scroll; |
|
526 | overflow-y: scroll; | |
517 |
|
527 | |||
518 | li { |
|
528 | li { | |
519 | padding: 0 8px; |
|
529 | padding: 0 8px; | |
520 | line-height: 1em; |
|
530 | line-height: 1em; | |
521 | color: @grey6; |
|
531 | color: @grey6; | |
522 |
|
532 | |||
523 | &>.select2-result-label { |
|
533 | &>.select2-result-label { | |
524 | padding: 8px 0; |
|
534 | padding: 8px 0; | |
525 | border-bottom: @border-thickness solid @grey3; |
|
535 | border-bottom: @border-thickness solid @grey3; | |
526 | white-space: nowrap; |
|
536 | white-space: nowrap; | |
527 | color: @grey5; |
|
537 | color: @grey5; | |
528 | cursor: pointer; |
|
538 | cursor: pointer; | |
529 | } |
|
539 | } | |
530 |
|
540 | |||
531 | &.select2-result-with-children { |
|
541 | &.select2-result-with-children { | |
532 | margin: 0; |
|
542 | margin: 0; | |
533 | padding: 0; |
|
543 | padding: 0; | |
534 | } |
|
544 | } | |
535 |
|
545 | |||
536 | &.select2-result-unselectable > .select2-result-label { |
|
546 | &.select2-result-unselectable > .select2-result-label { | |
537 | margin: 0 8px; |
|
547 | margin: 0 8px; | |
538 | } |
|
548 | } | |
539 |
|
549 | |||
540 | } |
|
550 | } | |
541 | } |
|
551 | } | |
542 |
|
552 | |||
543 | ul.select2-result-sub { |
|
553 | ul.select2-result-sub { | |
544 | margin: 0; |
|
554 | margin: 0; | |
545 | padding: 0; |
|
555 | padding: 0; | |
546 |
|
556 | |||
547 | li { |
|
557 | li { | |
548 | display: block; |
|
558 | display: block; | |
549 | margin: 0; |
|
559 | margin: 0; | |
550 | border-right: none; |
|
560 | border-right: none; | |
551 | line-height: 1em; |
|
561 | line-height: 1em; | |
552 | font-family: @text-light; |
|
562 | font-family: @text-light; | |
553 | color: @grey2; |
|
563 | color: @grey2; | |
554 | list-style-type: none; |
|
564 | list-style-type: none; | |
555 |
|
565 | |||
556 | &:hover { |
|
566 | &:hover { | |
557 | background-color: @grey3; |
|
567 | background-color: @grey3; | |
558 | } |
|
568 | } | |
559 | } |
|
569 | } | |
560 | } |
|
570 | } | |
561 | } |
|
571 | } | |
562 |
|
572 | |||
563 |
|
573 | |||
564 | #context-bar { |
|
574 | #context-bar { | |
565 | display: block; |
|
575 | display: block; | |
566 | margin: 0 auto 20px 0; |
|
576 | margin: 0 auto 20px 0; | |
567 | padding: 0 @header-padding; |
|
577 | padding: 0 @header-padding; | |
568 | background-color: @grey7; |
|
578 | background-color: @grey7; | |
569 | border-bottom: 1px solid @grey5; |
|
579 | border-bottom: 1px solid @grey5; | |
570 |
|
580 | |||
571 | .clear { |
|
581 | .clear { | |
572 | clear: both; |
|
582 | clear: both; | |
573 | } |
|
583 | } | |
574 | } |
|
584 | } | |
575 |
|
585 | |||
576 | ul#context-pages { |
|
586 | ul#context-pages { | |
577 | li { |
|
587 | li { | |
578 | list-style-type: none; |
|
588 | list-style-type: none; | |
579 |
|
589 | |||
580 | a { |
|
590 | a { | |
581 | color: @grey2; |
|
591 | color: @grey2; | |
582 |
|
592 | |||
583 | &:hover { |
|
593 | &:hover { | |
584 | color: @grey1; |
|
594 | color: @grey1; | |
585 | } |
|
595 | } | |
586 | } |
|
596 | } | |
587 |
|
597 | |||
588 | &.active { |
|
598 | &.active { | |
589 | // special case, non-variable color |
|
599 | // special case, non-variable color | |
590 | border-bottom: 2px solid @rcblue; |
|
600 | border-bottom: 2px solid @rcblue; | |
591 |
|
601 | |||
592 | a { |
|
602 | a { | |
593 | color: @rcblue; |
|
603 | color: @rcblue; | |
594 | } |
|
604 | } | |
595 | } |
|
605 | } | |
596 | } |
|
606 | } | |
597 | } |
|
607 | } | |
598 |
|
608 | |||
599 | // PAGINATION |
|
609 | // PAGINATION | |
600 |
|
610 | |||
601 | .pagination { |
|
611 | .pagination { | |
602 | border: @border-thickness solid @grey5; |
|
612 | border: @border-thickness solid @grey5; | |
603 | color: @grey2; |
|
613 | color: @grey2; | |
604 | box-shadow: @button-shadow; |
|
614 | box-shadow: @button-shadow; | |
605 |
|
615 | |||
606 | .current { |
|
616 | .current { | |
607 | color: @grey4; |
|
617 | color: @grey4; | |
608 | } |
|
618 | } | |
609 | } |
|
619 | } | |
610 |
|
620 | |||
611 | .dataTables_processing { |
|
621 | .dataTables_processing { | |
612 | text-align: center; |
|
622 | text-align: center; | |
613 | font-size: 1.1em; |
|
623 | font-size: 1.1em; | |
614 | position: relative; |
|
624 | position: relative; | |
615 | top: 95px; |
|
625 | top: 95px; | |
616 | } |
|
626 | } | |
617 |
|
627 | |||
618 | .dataTables_paginate, .pagination-wh { |
|
628 | .dataTables_paginate, .pagination-wh { | |
619 | text-align: left; |
|
629 | text-align: left; | |
620 | display: inline-block; |
|
630 | display: inline-block; | |
621 | border-left: 1px solid @grey5; |
|
631 | border-left: 1px solid @grey5; | |
622 | float: none; |
|
632 | float: none; | |
623 | overflow: hidden; |
|
633 | overflow: hidden; | |
624 | box-shadow: @button-shadow; |
|
634 | box-shadow: @button-shadow; | |
625 |
|
635 | |||
626 | .paginate_button, .pager_curpage, |
|
636 | .paginate_button, .pager_curpage, | |
627 | .pager_link, .pg-previous, .pg-next, .pager_dotdot { |
|
637 | .pager_link, .pg-previous, .pg-next, .pager_dotdot { | |
628 | display: inline-block; |
|
638 | display: inline-block; | |
629 | padding: @menupadding/4 @menupadding; |
|
639 | padding: @menupadding/4 @menupadding; | |
630 | border: 1px solid @grey5; |
|
640 | border: 1px solid @grey5; | |
631 | border-left: 0; |
|
641 | border-left: 0; | |
632 | color: @grey2; |
|
642 | color: @grey2; | |
633 | cursor: pointer; |
|
643 | cursor: pointer; | |
634 | float: left; |
|
644 | float: left; | |
635 |
|
645 | |||
636 | &:hover { |
|
646 | &:hover { | |
637 | color: @rcdarkblue; |
|
647 | color: @rcdarkblue; | |
638 | } |
|
648 | } | |
639 | } |
|
649 | } | |
640 |
|
650 | |||
641 | .paginate_button.disabled, |
|
651 | .paginate_button.disabled, | |
642 | .disabled { |
|
652 | .disabled { | |
643 | color: @grey3; |
|
653 | color: @grey3; | |
644 | cursor: default; |
|
654 | cursor: default; | |
645 | opacity: 0.5; |
|
655 | opacity: 0.5; | |
646 | } |
|
656 | } | |
647 |
|
657 | |||
648 | .paginate_button.current, .pager_curpage { |
|
658 | .paginate_button.current, .pager_curpage { | |
649 | background: @rcblue; |
|
659 | background: @rcblue; | |
650 | border-color: @rcblue; |
|
660 | border-color: @rcblue; | |
651 | color: @white; |
|
661 | color: @white; | |
652 | } |
|
662 | } | |
653 |
|
663 | |||
654 | .ellipsis { |
|
664 | .ellipsis { | |
655 | display: inline-block; |
|
665 | display: inline-block; | |
656 | text-align: left; |
|
666 | text-align: left; | |
657 | padding: @menupadding/4 @menupadding; |
|
667 | padding: @menupadding/4 @menupadding; | |
658 | border: 1px solid @grey5; |
|
668 | border: 1px solid @grey5; | |
659 | border-left: 0; |
|
669 | border-left: 0; | |
660 | float: left; |
|
670 | float: left; | |
661 | } |
|
671 | } | |
662 | } |
|
672 | } | |
663 |
|
673 | |||
664 | // SIDEBAR |
|
674 | // SIDEBAR | |
665 |
|
675 | |||
666 | .sidebar { |
|
676 | .sidebar { | |
667 | .block-left; |
|
677 | .block-left; | |
668 | clear: left; |
|
678 | clear: left; | |
669 | max-width: @sidebar-width; |
|
679 | max-width: @sidebar-width; | |
670 | margin-right: @sidebarpadding; |
|
680 | margin-right: @sidebarpadding; | |
671 | padding-right: @sidebarpadding; |
|
681 | padding-right: @sidebarpadding; | |
672 | font-family: @text-regular; |
|
682 | font-family: @text-regular; | |
673 | color: @grey1; |
|
683 | color: @grey1; | |
674 |
|
684 | |||
675 | .nav-pills { |
|
685 | .nav-pills { | |
676 | margin: 0; |
|
686 | margin: 0; | |
677 | } |
|
687 | } | |
678 |
|
688 | |||
679 | .nav { |
|
689 | .nav { | |
680 | list-style: none; |
|
690 | list-style: none; | |
681 | padding: 0; |
|
691 | padding: 0; | |
682 |
|
692 | |||
683 | li { |
|
693 | li { | |
684 | padding-bottom: @menupadding; |
|
694 | padding-bottom: @menupadding; | |
685 | line-height: 1em; |
|
695 | line-height: 1em; | |
686 | color: @grey4; |
|
696 | color: @grey4; | |
687 | list-style-type: none; |
|
697 | list-style-type: none; | |
688 |
|
698 | |||
689 | &.active a { |
|
699 | &.active a { | |
690 | color: @grey2; |
|
700 | color: @grey2; | |
691 | } |
|
701 | } | |
692 |
|
702 | |||
693 | a { |
|
703 | a { | |
694 | color: @grey4; |
|
704 | color: @grey4; | |
695 | } |
|
705 | } | |
696 | } |
|
706 | } | |
697 |
|
707 | |||
698 | } |
|
708 | } | |
699 | } |
|
709 | } | |
700 |
|
710 | |||
701 | .main_filter_help_box { |
|
711 | .main_filter_help_box { | |
702 | padding: 7px 7px; |
|
712 | padding: 7px 7px; | |
703 | display: inline-block; |
|
713 | display: inline-block; | |
704 | vertical-align: top; |
|
714 | vertical-align: top; | |
705 | background: inherit; |
|
715 | background: inherit; | |
706 | position: absolute; |
|
716 | position: absolute; | |
707 | right: 0; |
|
717 | right: 0; | |
708 | top: 9px; |
|
718 | top: 9px; | |
709 | } |
|
719 | } | |
710 |
|
720 | |||
711 | .main_filter_input_box { |
|
721 | .main_filter_input_box { | |
712 | display: inline-block; |
|
722 | display: inline-block; | |
713 |
|
723 | |||
714 | .searchItems { |
|
724 | .searchItems { | |
715 | display:flex; |
|
725 | display:flex; | |
716 | background: @black; |
|
726 | background: @black; | |
717 | padding: 0px; |
|
727 | padding: 0px; | |
718 | border-radius: 3px; |
|
728 | border-radius: 3px; | |
719 | border: 1px solid @black; |
|
729 | border: 1px solid @black; | |
720 |
|
730 | |||
721 | a { |
|
731 | a { | |
722 | border: none !important; |
|
732 | border: none !important; | |
723 | } |
|
733 | } | |
724 | } |
|
734 | } | |
725 |
|
735 | |||
726 | .searchTag { |
|
736 | .searchTag { | |
727 | line-height: 28px; |
|
737 | line-height: 28px; | |
728 | padding: 0 5px; |
|
738 | padding: 0 5px; | |
729 |
|
739 | |||
730 | .tag { |
|
740 | .tag { | |
731 | color: @grey5; |
|
741 | color: @grey5; | |
732 | border-color: @grey2; |
|
742 | border-color: @grey2; | |
733 | background: @grey1; |
|
743 | background: @grey1; | |
734 | } |
|
744 | } | |
735 | } |
|
745 | } | |
736 |
|
746 | |||
737 | .searchTagFilter { |
|
747 | .searchTagFilter { | |
738 | background-color: @black !important; |
|
748 | background-color: @black !important; | |
739 | margin-right: 0; |
|
749 | margin-right: 0; | |
740 | } |
|
750 | } | |
741 |
|
751 | |||
742 | .searchTagHelp { |
|
752 | .searchTagHelp { | |
743 | background-color: @grey1 !important; |
|
753 | background-color: @grey1 !important; | |
744 | margin: 0; |
|
754 | margin: 0; | |
745 | } |
|
755 | } | |
746 | .searchTagHelp:hover { |
|
756 | .searchTagHelp:hover { | |
747 | background-color: @grey1 !important; |
|
757 | background-color: @grey1 !important; | |
748 | } |
|
758 | } | |
749 | .searchTagInput { |
|
759 | .searchTagInput { | |
750 | background-color: @grey1 !important; |
|
760 | background-color: @grey1 !important; | |
751 | margin-right: 0; |
|
761 | margin-right: 0; | |
752 | } |
|
762 | } | |
753 | } |
|
763 | } | |
754 |
|
764 | |||
755 | .main_filter_box { |
|
765 | .main_filter_box { | |
756 | margin: 9px 0 0 0; |
|
766 | margin: 9px 0 0 0; | |
757 | } |
|
767 | } | |
758 |
|
768 | |||
759 | #main_filter_help { |
|
769 | #main_filter_help { | |
760 | background: @grey1; |
|
770 | background: @grey1; | |
761 | border: 1px solid black; |
|
771 | border: 1px solid black; | |
762 | position: absolute; |
|
772 | position: absolute; | |
763 | white-space: pre; |
|
773 | white-space: pre; | |
764 | z-index: 9999; |
|
774 | z-index: 9999; | |
765 | color: @nav-grey; |
|
775 | color: @nav-grey; | |
766 | padding: 0 10px; |
|
776 | padding: 0 10px; | |
767 | } |
|
777 | } | |
768 |
|
778 | |||
769 | input { |
|
779 | input { | |
770 |
|
780 | |||
771 | &.main_filter_input { |
|
781 | &.main_filter_input { | |
772 | padding: 5px 10px; |
|
782 | padding: 5px 10px; | |
773 | min-width: 340px; |
|
783 | min-width: 340px; | |
774 | color: @grey7; |
|
784 | color: @grey7; | |
775 | background: @black; |
|
785 | background: @black; | |
776 | min-height: 18px; |
|
786 | min-height: 18px; | |
777 | border: 0; |
|
787 | border: 0; | |
778 |
|
788 | |||
779 | &:active { |
|
789 | &:active { | |
780 | color: @grey2 !important; |
|
790 | color: @grey2 !important; | |
781 | background: white !important; |
|
791 | background: white !important; | |
782 | } |
|
792 | } | |
783 | &:focus { |
|
793 | &:focus { | |
784 | color: @grey2 !important; |
|
794 | color: @grey2 !important; | |
785 | background: white !important; |
|
795 | background: white !important; | |
786 | } |
|
796 | } | |
787 | } |
|
797 | } | |
788 | } |
|
798 | } | |
789 |
|
799 | |||
790 |
|
800 | |||
791 |
|
801 | |||
792 | .main_filter_input::placeholder { |
|
802 | .main_filter_input::placeholder { | |
793 | color: @nav-grey; |
|
803 | color: @nav-grey; | |
794 | opacity: 1; |
|
804 | opacity: 1; | |
795 | } |
|
805 | } | |
796 |
|
806 | |||
797 | .notice-box { |
|
807 | .notice-box { | |
798 | display:block !important; |
|
808 | display:block !important; | |
799 | padding: 9px 0 !important; |
|
809 | padding: 9px 0 !important; | |
800 | } |
|
810 | } | |
801 |
|
811 | |||
802 | .menulabel-notice { |
|
812 | .menulabel-notice { | |
803 | border: 1px solid @color5; |
|
813 | border: 1px solid @color5; | |
804 | padding:7px 10px; |
|
814 | padding:7px 10px; | |
805 | color: @color5; |
|
815 | color: @color5; | |
806 | } |
|
816 | } |
@@ -1,1061 +1,1063 b'' | |||||
1 | ## -*- coding: utf-8 -*- |
|
1 | ## -*- coding: utf-8 -*- | |
2 | <%inherit file="root.mako"/> |
|
2 | <%inherit file="root.mako"/> | |
3 |
|
3 | |||
4 | <%include file="/ejs_templates/templates.html"/> |
|
4 | <%include file="/ejs_templates/templates.html"/> | |
5 |
|
5 | |||
6 | <div class="outerwrapper"> |
|
6 | <div class="outerwrapper"> | |
7 | <!-- HEADER --> |
|
7 | <!-- HEADER --> | |
8 | <div class="header"> |
|
8 | <div class="header"> | |
9 | <div id="header-inner" class="wrapper"> |
|
9 | <div id="header-inner" class="wrapper"> | |
10 | <div id="logo"> |
|
10 | <div id="logo"> | |
11 | <div class="logo-wrapper"> |
|
11 | <div class="logo-wrapper"> | |
12 | <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a> |
|
12 | <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a> | |
13 | </div> |
|
13 | </div> | |
14 | % if c.rhodecode_name: |
|
14 | % if c.rhodecode_name: | |
15 | <div class="branding"> |
|
15 | <div class="branding"> | |
16 | <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a> |
|
16 | <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a> | |
17 | </div> |
|
17 | </div> | |
18 | % endif |
|
18 | % endif | |
19 | </div> |
|
19 | </div> | |
20 | <!-- MENU BAR NAV --> |
|
20 | <!-- MENU BAR NAV --> | |
21 | ${self.menu_bar_nav()} |
|
21 | ${self.menu_bar_nav()} | |
22 | <!-- END MENU BAR NAV --> |
|
22 | <!-- END MENU BAR NAV --> | |
23 | </div> |
|
23 | </div> | |
24 | </div> |
|
24 | </div> | |
25 | ${self.menu_bar_subnav()} |
|
25 | ${self.menu_bar_subnav()} | |
26 | <!-- END HEADER --> |
|
26 | <!-- END HEADER --> | |
27 |
|
27 | |||
28 | <!-- CONTENT --> |
|
28 | <!-- CONTENT --> | |
29 | <div id="content" class="wrapper"> |
|
29 | <div id="content" class="wrapper"> | |
30 |
|
30 | |||
31 | <rhodecode-toast id="notifications"></rhodecode-toast> |
|
31 | <rhodecode-toast id="notifications"></rhodecode-toast> | |
32 |
|
32 | |||
33 | <div class="main"> |
|
33 | <div class="main"> | |
34 | ${next.main()} |
|
34 | ${next.main()} | |
35 | </div> |
|
35 | </div> | |
36 | </div> |
|
36 | </div> | |
37 | <!-- END CONTENT --> |
|
37 | <!-- END CONTENT --> | |
38 |
|
38 | |||
39 | </div> |
|
39 | </div> | |
40 | <!-- FOOTER --> |
|
40 | <!-- FOOTER --> | |
41 | <div id="footer"> |
|
41 | <div id="footer"> | |
42 | <div id="footer-inner" class="title wrapper"> |
|
42 | <div id="footer-inner" class="title wrapper"> | |
43 | <div> |
|
43 | <div> | |
44 | <p class="footer-link-right"> |
|
44 | <p class="footer-link-right"> | |
45 | % if c.visual.show_version: |
|
45 | % if c.visual.show_version: | |
46 | RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition} |
|
46 | RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition} | |
47 | % endif |
|
47 | % endif | |
48 | © 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved. |
|
48 | © 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved. | |
49 | % if c.visual.rhodecode_support_url: |
|
49 | % if c.visual.rhodecode_support_url: | |
50 | <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> |
|
50 | <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> | |
51 | % endif |
|
51 | % endif | |
52 | </p> |
|
52 | </p> | |
53 | <% sid = 'block' if request.GET.get('showrcid') else 'none' %> |
|
53 | <% sid = 'block' if request.GET.get('showrcid') else 'none' %> | |
54 | <p class="server-instance" style="display:${sid}"> |
|
54 | <p class="server-instance" style="display:${sid}"> | |
55 | ## display hidden instance ID if specially defined |
|
55 | ## display hidden instance ID if specially defined | |
56 | % if c.rhodecode_instanceid: |
|
56 | % if c.rhodecode_instanceid: | |
57 | ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)} |
|
57 | ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)} | |
58 | % endif |
|
58 | % endif | |
59 | </p> |
|
59 | </p> | |
60 | </div> |
|
60 | </div> | |
61 | </div> |
|
61 | </div> | |
62 | </div> |
|
62 | </div> | |
63 |
|
63 | |||
64 | <!-- END FOOTER --> |
|
64 | <!-- END FOOTER --> | |
65 |
|
65 | |||
66 | ### MAKO DEFS ### |
|
66 | ### MAKO DEFS ### | |
67 |
|
67 | |||
68 | <%def name="menu_bar_subnav()"> |
|
68 | <%def name="menu_bar_subnav()"> | |
69 | </%def> |
|
69 | </%def> | |
70 |
|
70 | |||
71 | <%def name="breadcrumbs(class_='breadcrumbs')"> |
|
71 | <%def name="breadcrumbs(class_='breadcrumbs')"> | |
72 | <div class="${class_}"> |
|
72 | <div class="${class_}"> | |
73 | ${self.breadcrumbs_links()} |
|
73 | ${self.breadcrumbs_links()} | |
74 | </div> |
|
74 | </div> | |
75 | </%def> |
|
75 | </%def> | |
76 |
|
76 | |||
77 | <%def name="admin_menu(active=None)"> |
|
77 | <%def name="admin_menu(active=None)"> | |
78 | <% |
|
78 | <% | |
79 | def is_active(selected): |
|
79 | def is_active(selected): | |
80 | if selected == active: |
|
80 | if selected == active: | |
81 | return "active" |
|
81 | return "active" | |
82 | %> |
|
82 | %> | |
83 |
|
83 | |||
84 | <div id="context-bar"> |
|
84 | <div id="context-bar"> | |
85 | <div class="wrapper"> |
|
85 | <div class="wrapper"> | |
86 | <div class="title"> |
|
86 | <div class="title"> | |
87 | <div class="title-content"> |
|
87 | <div class="title-content"> | |
88 | <div class="title-main"> |
|
88 | <div class="title-main"> | |
89 | % if c.is_super_admin: |
|
89 | % if c.is_super_admin: | |
90 | ${_('Super Admin Panel')} |
|
90 | ${_('Super Admin Panel')} | |
91 | % else: |
|
91 | % else: | |
92 | ${_('Delegated Admin Panel')} |
|
92 | ${_('Delegated Admin Panel')} | |
93 | % endif |
|
93 | % endif | |
94 | </div> |
|
94 | </div> | |
95 | </div> |
|
95 | </div> | |
96 | </div> |
|
96 | </div> | |
97 |
|
97 | |||
98 | <ul id="context-pages" class="navigation horizontal-list"> |
|
98 | <ul id="context-pages" class="navigation horizontal-list"> | |
99 |
|
99 | |||
100 | ## super admin case |
|
100 | ## super admin case | |
101 | % if c.is_super_admin: |
|
101 | % if c.is_super_admin: | |
102 | <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li> |
|
102 | <li class="${is_active('audit_logs')}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li> | |
103 | <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li> |
|
103 | <li class="${is_active('repositories')}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li> | |
104 | <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li> |
|
104 | <li class="${is_active('repository_groups')}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li> | |
105 | <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li> |
|
105 | <li class="${is_active('users')}"><a href="${h.route_path('users')}">${_('Users')}</a></li> | |
106 | <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li> |
|
106 | <li class="${is_active('user_groups')}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li> | |
107 | <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li> |
|
107 | <li class="${is_active('permissions')}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li> | |
108 | <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li> |
|
108 | <li class="${is_active('authentication')}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li> | |
109 | <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li> |
|
109 | <li class="${is_active('integrations')}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li> | |
110 | <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li> |
|
110 | <li class="${is_active('defaults')}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li> | |
111 | <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li> |
|
111 | <li class="${is_active('settings')}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li> | |
112 |
|
112 | |||
113 | ## delegated admin |
|
113 | ## delegated admin | |
114 | % elif c.is_delegated_admin: |
|
114 | % elif c.is_delegated_admin: | |
115 | <% |
|
115 | <% | |
116 | repositories=c.auth_user.repositories_admin or c.can_create_repo |
|
116 | repositories=c.auth_user.repositories_admin or c.can_create_repo | |
117 | repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group |
|
117 | repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group | |
118 | user_groups=c.auth_user.user_groups_admin or c.can_create_user_group |
|
118 | user_groups=c.auth_user.user_groups_admin or c.can_create_user_group | |
119 | %> |
|
119 | %> | |
120 |
|
120 | |||
121 | %if repositories: |
|
121 | %if repositories: | |
122 | <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li> |
|
122 | <li class="${is_active('repositories')} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li> | |
123 | %endif |
|
123 | %endif | |
124 | %if repository_groups: |
|
124 | %if repository_groups: | |
125 | <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li> |
|
125 | <li class="${is_active('repository_groups')} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li> | |
126 | %endif |
|
126 | %endif | |
127 | %if user_groups: |
|
127 | %if user_groups: | |
128 | <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li> |
|
128 | <li class="${is_active('user_groups')} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li> | |
129 | %endif |
|
129 | %endif | |
130 | % endif |
|
130 | % endif | |
131 | </ul> |
|
131 | </ul> | |
132 |
|
132 | |||
133 | </div> |
|
133 | </div> | |
134 | <div class="clear"></div> |
|
134 | <div class="clear"></div> | |
135 | </div> |
|
135 | </div> | |
136 | </%def> |
|
136 | </%def> | |
137 |
|
137 | |||
138 | <%def name="dt_info_panel(elements)"> |
|
138 | <%def name="dt_info_panel(elements)"> | |
139 | <dl class="dl-horizontal"> |
|
139 | <dl class="dl-horizontal"> | |
140 | %for dt, dd, title, show_items in elements: |
|
140 | %for dt, dd, title, show_items in elements: | |
141 | <dt>${dt}:</dt> |
|
141 | <dt>${dt}:</dt> | |
142 | <dd title="${h.tooltip(title)}"> |
|
142 | <dd title="${h.tooltip(title)}"> | |
143 | %if callable(dd): |
|
143 | %if callable(dd): | |
144 | ## allow lazy evaluation of elements |
|
144 | ## allow lazy evaluation of elements | |
145 | ${dd()} |
|
145 | ${dd()} | |
146 | %else: |
|
146 | %else: | |
147 | ${dd} |
|
147 | ${dd} | |
148 | %endif |
|
148 | %endif | |
149 | %if show_items: |
|
149 | %if show_items: | |
150 | <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span> |
|
150 | <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span> | |
151 | %endif |
|
151 | %endif | |
152 | </dd> |
|
152 | </dd> | |
153 |
|
153 | |||
154 | %if show_items: |
|
154 | %if show_items: | |
155 | <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none"> |
|
155 | <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none"> | |
156 | %for item in show_items: |
|
156 | %for item in show_items: | |
157 | <dt></dt> |
|
157 | <dt></dt> | |
158 | <dd>${item}</dd> |
|
158 | <dd>${item}</dd> | |
159 | %endfor |
|
159 | %endfor | |
160 | </div> |
|
160 | </div> | |
161 | %endif |
|
161 | %endif | |
162 |
|
162 | |||
163 | %endfor |
|
163 | %endfor | |
164 | </dl> |
|
164 | </dl> | |
165 | </%def> |
|
165 | </%def> | |
166 |
|
166 | |||
167 | <%def name="gravatar(email, size=16)"> |
|
167 | <%def name="gravatar(email, size=16)"> | |
168 | <% |
|
168 | <% | |
169 | if (size > 16): |
|
169 | if (size > 16): | |
170 | gravatar_class = 'gravatar gravatar-large' |
|
170 | gravatar_class = 'gravatar gravatar-large' | |
171 | else: |
|
171 | else: | |
172 | gravatar_class = 'gravatar' |
|
172 | gravatar_class = 'gravatar' | |
173 | %> |
|
173 | %> | |
174 | <%doc> |
|
174 | <%doc> | |
175 | TODO: johbo: For now we serve double size images to make it smooth |
|
175 | TODO: johbo: For now we serve double size images to make it smooth | |
176 | for retina. This is how it worked until now. Should be replaced |
|
176 | for retina. This is how it worked until now. Should be replaced | |
177 | with a better solution at some point. |
|
177 | with a better solution at some point. | |
178 | </%doc> |
|
178 | </%doc> | |
179 | <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}"> |
|
179 | <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}"> | |
180 | </%def> |
|
180 | </%def> | |
181 |
|
181 | |||
182 |
|
182 | |||
183 | <%def name="gravatar_with_user(contact, size=16, show_disabled=False)"> |
|
183 | <%def name="gravatar_with_user(contact, size=16, show_disabled=False)"> | |
184 | <% email = h.email_or_none(contact) %> |
|
184 | <% email = h.email_or_none(contact) %> | |
185 | <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}"> |
|
185 | <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}"> | |
186 | ${self.gravatar(email, size)} |
|
186 | ${self.gravatar(email, size)} | |
187 | <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span> |
|
187 | <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span> | |
188 | </div> |
|
188 | </div> | |
189 | </%def> |
|
189 | </%def> | |
190 |
|
190 | |||
191 |
|
191 | |||
192 | <%def name="repo_page_title(repo_instance)"> |
|
192 | <%def name="repo_page_title(repo_instance)"> | |
193 | <div class="title-content repo-title"> |
|
193 | <div class="title-content repo-title"> | |
194 |
|
194 | |||
195 | <div class="title-main"> |
|
195 | <div class="title-main"> | |
196 | ## SVN/HG/GIT icons |
|
196 | ## SVN/HG/GIT icons | |
197 | %if h.is_hg(repo_instance): |
|
197 | %if h.is_hg(repo_instance): | |
198 | <i class="icon-hg"></i> |
|
198 | <i class="icon-hg"></i> | |
199 | %endif |
|
199 | %endif | |
200 | %if h.is_git(repo_instance): |
|
200 | %if h.is_git(repo_instance): | |
201 | <i class="icon-git"></i> |
|
201 | <i class="icon-git"></i> | |
202 | %endif |
|
202 | %endif | |
203 | %if h.is_svn(repo_instance): |
|
203 | %if h.is_svn(repo_instance): | |
204 | <i class="icon-svn"></i> |
|
204 | <i class="icon-svn"></i> | |
205 | %endif |
|
205 | %endif | |
206 |
|
206 | |||
207 | ## public/private |
|
207 | ## public/private | |
208 | %if repo_instance.private: |
|
208 | %if repo_instance.private: | |
209 | <i class="icon-repo-private"></i> |
|
209 | <i class="icon-repo-private"></i> | |
210 | %else: |
|
210 | %else: | |
211 | <i class="icon-repo-public"></i> |
|
211 | <i class="icon-repo-public"></i> | |
212 | %endif |
|
212 | %endif | |
213 |
|
213 | |||
214 | ## repo name with group name |
|
214 | ## repo name with group name | |
215 | ${h.breadcrumb_repo_link(repo_instance)} |
|
215 | ${h.breadcrumb_repo_link(repo_instance)} | |
216 |
|
216 | |||
217 | ## Context Actions |
|
217 | ## Context Actions | |
218 | <div class="pull-right"> |
|
218 | <div class="pull-right"> | |
219 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
219 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
220 | <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a> |
|
220 | <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a> | |
221 |
|
221 | |||
222 | <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}"> |
|
222 | <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}"> | |
223 | % if c.repository_is_user_following: |
|
223 | % if c.repository_is_user_following: | |
224 | <i class="icon-eye-off"></i>${_('Unwatch')} |
|
224 | <i class="icon-eye-off"></i>${_('Unwatch')} | |
225 | % else: |
|
225 | % else: | |
226 | <i class="icon-eye"></i>${_('Watch')} |
|
226 | <i class="icon-eye"></i>${_('Watch')} | |
227 | % endif |
|
227 | % endif | |
228 |
|
228 | |||
229 | </a> |
|
229 | </a> | |
230 | %else: |
|
230 | %else: | |
231 | <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a> |
|
231 | <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a> | |
232 | %endif |
|
232 | %endif | |
233 | </div> |
|
233 | </div> | |
234 |
|
234 | |||
235 | </div> |
|
235 | </div> | |
236 |
|
236 | |||
237 | ## FORKED |
|
237 | ## FORKED | |
238 | %if repo_instance.fork: |
|
238 | %if repo_instance.fork: | |
239 | <p class="discreet"> |
|
239 | <p class="discreet"> | |
240 | <i class="icon-code-fork"></i> ${_('Fork of')} |
|
240 | <i class="icon-code-fork"></i> ${_('Fork of')} | |
241 | ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))} |
|
241 | ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))} | |
242 | </p> |
|
242 | </p> | |
243 | %endif |
|
243 | %endif | |
244 |
|
244 | |||
245 | ## IMPORTED FROM REMOTE |
|
245 | ## IMPORTED FROM REMOTE | |
246 | %if repo_instance.clone_uri: |
|
246 | %if repo_instance.clone_uri: | |
247 | <p class="discreet"> |
|
247 | <p class="discreet"> | |
248 | <i class="icon-code-fork"></i> ${_('Clone from')} |
|
248 | <i class="icon-code-fork"></i> ${_('Clone from')} | |
249 | <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a> |
|
249 | <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a> | |
250 | </p> |
|
250 | </p> | |
251 | %endif |
|
251 | %endif | |
252 |
|
252 | |||
253 | ## LOCKING STATUS |
|
253 | ## LOCKING STATUS | |
254 | %if repo_instance.locked[0]: |
|
254 | %if repo_instance.locked[0]: | |
255 | <p class="locking_locked discreet"> |
|
255 | <p class="locking_locked discreet"> | |
256 | <i class="icon-repo-lock"></i> |
|
256 | <i class="icon-repo-lock"></i> | |
257 | ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}} |
|
257 | ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}} | |
258 | </p> |
|
258 | </p> | |
259 | %elif repo_instance.enable_locking: |
|
259 | %elif repo_instance.enable_locking: | |
260 | <p class="locking_unlocked discreet"> |
|
260 | <p class="locking_unlocked discreet"> | |
261 | <i class="icon-repo-unlock"></i> |
|
261 | <i class="icon-repo-unlock"></i> | |
262 | ${_('Repository not locked. Pull repository to lock it.')} |
|
262 | ${_('Repository not locked. Pull repository to lock it.')} | |
263 | </p> |
|
263 | </p> | |
264 | %endif |
|
264 | %endif | |
265 |
|
265 | |||
266 | </div> |
|
266 | </div> | |
267 | </%def> |
|
267 | </%def> | |
268 |
|
268 | |||
269 | <%def name="repo_menu(active=None)"> |
|
269 | <%def name="repo_menu(active=None)"> | |
270 | <% |
|
270 | <% | |
271 | def is_active(selected): |
|
271 | def is_active(selected): | |
272 | if selected == active: |
|
272 | if selected == active: | |
273 | return "active" |
|
273 | return "active" | |
274 | ## determine if we have "any" option available |
|
274 | ## determine if we have "any" option available | |
275 | can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking |
|
275 | can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking | |
276 | has_actions = can_lock |
|
276 | has_actions = can_lock | |
277 |
|
277 | |||
278 | %> |
|
278 | %> | |
279 | % if c.rhodecode_db_repo.archived: |
|
279 | % if c.rhodecode_db_repo.archived: | |
280 | <div class="alert alert-warning text-center"> |
|
280 | <div class="alert alert-warning text-center"> | |
281 | <strong>${_('This repository has been archived. It is now read-only.')}</strong> |
|
281 | <strong>${_('This repository has been archived. It is now read-only.')}</strong> | |
282 | </div> |
|
282 | </div> | |
283 | % endif |
|
283 | % endif | |
284 |
|
284 | |||
285 | <!--- REPO CONTEXT BAR --> |
|
285 | <!--- REPO CONTEXT BAR --> | |
286 | <div id="context-bar"> |
|
286 | <div id="context-bar"> | |
287 | <div class="wrapper"> |
|
287 | <div class="wrapper"> | |
288 |
|
288 | |||
289 | <div class="title"> |
|
289 | <div class="title"> | |
290 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
290 | ${self.repo_page_title(c.rhodecode_db_repo)} | |
291 | </div> |
|
291 | </div> | |
292 |
|
292 | |||
293 | <ul id="context-pages" class="navigation horizontal-list"> |
|
293 | <ul id="context-pages" class="navigation horizontal-list"> | |
294 | <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li> |
|
294 | <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li> | |
295 | <li class="${is_active('commits')}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li> |
|
295 | <li class="${is_active('commits')}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li> | |
296 | <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li> |
|
296 | <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li> | |
297 | <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li> |
|
297 | <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li> | |
298 |
|
298 | |||
299 | ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()" |
|
299 | ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()" | |
300 | %if c.rhodecode_db_repo.repo_type in ['git','hg']: |
|
300 | %if c.rhodecode_db_repo.repo_type in ['git','hg']: | |
301 | <li class="${is_active('showpullrequest')}"> |
|
301 | <li class="${is_active('showpullrequest')}"> | |
302 | <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}"> |
|
302 | <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}"> | |
303 | <div class="menulabel"> |
|
303 | <div class="menulabel"> | |
304 | %if c.repository_pull_requests == 1: |
|
304 | ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span> | |
305 | ${_('Pull Request')} ${c.repository_pull_requests} |
|
|||
306 | %else: |
|
|||
307 | ${_('Pull Requests')} ${c.repository_pull_requests} |
|
|||
308 | %endif |
|
|||
309 | </div> |
|
305 | </div> | |
310 | </a> |
|
306 | </a> | |
311 | </li> |
|
307 | </li> | |
312 | %endif |
|
308 | %endif | |
313 |
|
309 | |||
314 | <li class="${is_active('artifacts')}"><a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}"><div class="menulabel">${_('Artifacts')}</div></a></li> |
|
310 | <li class="${is_active('artifacts')}"> | |
|
311 | <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}"> | |||
|
312 | <div class="menulabel"> | |||
|
313 | ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span> | |||
|
314 | </div> | |||
|
315 | </a> | |||
|
316 | </li> | |||
315 |
|
317 | |||
316 | %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): |
|
318 | %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): | |
317 | <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li> |
|
319 | <li class="${is_active('settings')}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li> | |
318 | %endif |
|
320 | %endif | |
319 |
|
321 | |||
320 | <li class="${is_active('options')}"> |
|
322 | <li class="${is_active('options')}"> | |
321 | % if has_actions: |
|
323 | % if has_actions: | |
322 | <a class="menulink dropdown"> |
|
324 | <a class="menulink dropdown"> | |
323 | <div class="menulabel">${_('Options')}<div class="show_more"></div></div> |
|
325 | <div class="menulabel">${_('Options')}<div class="show_more"></div></div> | |
324 | </a> |
|
326 | </a> | |
325 | <ul class="submenu"> |
|
327 | <ul class="submenu"> | |
326 | %if can_lock: |
|
328 | %if can_lock: | |
327 | %if c.rhodecode_db_repo.locked[0]: |
|
329 | %if c.rhodecode_db_repo.locked[0]: | |
328 | <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li> |
|
330 | <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li> | |
329 | %else: |
|
331 | %else: | |
330 | <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li> |
|
332 | <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li> | |
331 | %endif |
|
333 | %endif | |
332 | %endif |
|
334 | %endif | |
333 | </ul> |
|
335 | </ul> | |
334 | % else: |
|
336 | % else: | |
335 | <a class="menulink disabled"> |
|
337 | <a class="menulink disabled"> | |
336 | <div class="menulabel">${_('Options')}<div class="show_more"></div></div> |
|
338 | <div class="menulabel">${_('Options')}<div class="show_more"></div></div> | |
337 | </a> |
|
339 | </a> | |
338 | % endif |
|
340 | % endif | |
339 | </li> |
|
341 | </li> | |
340 |
|
342 | |||
341 | </ul> |
|
343 | </ul> | |
342 | </div> |
|
344 | </div> | |
343 | <div class="clear"></div> |
|
345 | <div class="clear"></div> | |
344 | </div> |
|
346 | </div> | |
345 |
|
347 | |||
346 | <!--- REPO END CONTEXT BAR --> |
|
348 | <!--- REPO END CONTEXT BAR --> | |
347 |
|
349 | |||
348 | </%def> |
|
350 | </%def> | |
349 |
|
351 | |||
350 | <%def name="repo_group_page_title(repo_group_instance)"> |
|
352 | <%def name="repo_group_page_title(repo_group_instance)"> | |
351 | <div class="title-content"> |
|
353 | <div class="title-content"> | |
352 | <div class="title-main"> |
|
354 | <div class="title-main"> | |
353 | ## Repository Group icon |
|
355 | ## Repository Group icon | |
354 | <i class="icon-repo-group"></i> |
|
356 | <i class="icon-repo-group"></i> | |
355 |
|
357 | |||
356 | ## repo name with group name |
|
358 | ## repo name with group name | |
357 | ${h.breadcrumb_repo_group_link(repo_group_instance)} |
|
359 | ${h.breadcrumb_repo_group_link(repo_group_instance)} | |
358 | </div> |
|
360 | </div> | |
359 |
|
361 | |||
360 | <%namespace name="dt" file="/data_table/_dt_elements.mako"/> |
|
362 | <%namespace name="dt" file="/data_table/_dt_elements.mako"/> | |
361 | <div class="repo-group-desc discreet"> |
|
363 | <div class="repo-group-desc discreet"> | |
362 | ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)} |
|
364 | ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)} | |
363 | </div> |
|
365 | </div> | |
364 |
|
366 | |||
365 | </div> |
|
367 | </div> | |
366 | </%def> |
|
368 | </%def> | |
367 |
|
369 | |||
368 |
|
370 | |||
369 | <%def name="repo_group_menu(active=None)"> |
|
371 | <%def name="repo_group_menu(active=None)"> | |
370 | <% |
|
372 | <% | |
371 | def is_active(selected): |
|
373 | def is_active(selected): | |
372 | if selected == active: |
|
374 | if selected == active: | |
373 | return "active" |
|
375 | return "active" | |
374 |
|
376 | |||
375 | gr_name = c.repo_group.group_name if c.repo_group else None |
|
377 | gr_name = c.repo_group.group_name if c.repo_group else None | |
376 | # create repositories with write permission on group is set to true |
|
378 | # create repositories with write permission on group is set to true | |
377 | group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page') |
|
379 | group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page') | |
378 |
|
380 | |||
379 | %> |
|
381 | %> | |
380 |
|
382 | |||
381 |
|
383 | |||
382 | <!--- REPO GROUP CONTEXT BAR --> |
|
384 | <!--- REPO GROUP CONTEXT BAR --> | |
383 | <div id="context-bar"> |
|
385 | <div id="context-bar"> | |
384 | <div class="wrapper"> |
|
386 | <div class="wrapper"> | |
385 | <div class="title"> |
|
387 | <div class="title"> | |
386 | ${self.repo_group_page_title(c.repo_group)} |
|
388 | ${self.repo_group_page_title(c.repo_group)} | |
387 | </div> |
|
389 | </div> | |
388 |
|
390 | |||
389 | <ul id="context-pages" class="navigation horizontal-list"> |
|
391 | <ul id="context-pages" class="navigation horizontal-list"> | |
390 | <li class="${is_active('home')}"> |
|
392 | <li class="${is_active('home')}"> | |
391 | <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a> |
|
393 | <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a> | |
392 | </li> |
|
394 | </li> | |
393 | % if c.is_super_admin or group_admin: |
|
395 | % if c.is_super_admin or group_admin: | |
394 | <li class="${is_active('settings')}"> |
|
396 | <li class="${is_active('settings')}"> | |
395 | <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a> |
|
397 | <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a> | |
396 | </li> |
|
398 | </li> | |
397 | % endif |
|
399 | % endif | |
398 |
|
400 | |||
399 | </ul> |
|
401 | </ul> | |
400 | </div> |
|
402 | </div> | |
401 | <div class="clear"></div> |
|
403 | <div class="clear"></div> | |
402 | </div> |
|
404 | </div> | |
403 |
|
405 | |||
404 | <!--- REPO GROUP CONTEXT BAR --> |
|
406 | <!--- REPO GROUP CONTEXT BAR --> | |
405 |
|
407 | |||
406 | </%def> |
|
408 | </%def> | |
407 |
|
409 | |||
408 |
|
410 | |||
409 | <%def name="usermenu(active=False)"> |
|
411 | <%def name="usermenu(active=False)"> | |
410 | <% |
|
412 | <% | |
411 | not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER |
|
413 | not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER | |
412 |
|
414 | |||
413 | gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None |
|
415 | gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None | |
414 | # create repositories with write permission on group is set to true |
|
416 | # create repositories with write permission on group is set to true | |
415 |
|
417 | |||
416 | can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')() |
|
418 | can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')() | |
417 | create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')() |
|
419 | create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')() | |
418 | group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page') |
|
420 | group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page') | |
419 | group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page') |
|
421 | group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page') | |
420 |
|
422 | |||
421 | can_create_repos = c.is_super_admin or c.can_create_repo |
|
423 | can_create_repos = c.is_super_admin or c.can_create_repo | |
422 | can_create_repo_groups = c.is_super_admin or c.can_create_repo_group |
|
424 | can_create_repo_groups = c.is_super_admin or c.can_create_repo_group | |
423 |
|
425 | |||
424 | can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write) |
|
426 | can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write) | |
425 | can_create_repo_groups_in_group = c.is_super_admin or group_admin |
|
427 | can_create_repo_groups_in_group = c.is_super_admin or group_admin | |
426 | %> |
|
428 | %> | |
427 |
|
429 | |||
428 | % if not_anonymous: |
|
430 | % if not_anonymous: | |
429 | <% |
|
431 | <% | |
430 | default_target_group = dict() |
|
432 | default_target_group = dict() | |
431 | if c.rhodecode_user.personal_repo_group: |
|
433 | if c.rhodecode_user.personal_repo_group: | |
432 | default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id) |
|
434 | default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id) | |
433 | %> |
|
435 | %> | |
434 |
|
436 | |||
435 | ## create action |
|
437 | ## create action | |
436 | <li> |
|
438 | <li> | |
437 | <a href="#create-actions" onclick="return false;" class="menulink childs"> |
|
439 | <a href="#create-actions" onclick="return false;" class="menulink childs"> | |
438 | <i class="icon-plus-circled"></i> |
|
440 | <i class="icon-plus-circled"></i> | |
439 | </a> |
|
441 | </a> | |
440 |
|
442 | |||
441 | <div class="action-menu submenu"> |
|
443 | <div class="action-menu submenu"> | |
442 |
|
444 | |||
443 | <ol> |
|
445 | <ol> | |
444 | ## scope of within a repository |
|
446 | ## scope of within a repository | |
445 | % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo: |
|
447 | % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo: | |
446 | <li class="submenu-title">${_('This Repository')}</li> |
|
448 | <li class="submenu-title">${_('This Repository')}</li> | |
447 | <li> |
|
449 | <li> | |
448 | <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a> |
|
450 | <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a> | |
449 | </li> |
|
451 | </li> | |
450 | % if can_fork: |
|
452 | % if can_fork: | |
451 | <li> |
|
453 | <li> | |
452 | <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a> |
|
454 | <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a> | |
453 | </li> |
|
455 | </li> | |
454 | % endif |
|
456 | % endif | |
455 | % endif |
|
457 | % endif | |
456 |
|
458 | |||
457 | ## scope of within repository groups |
|
459 | ## scope of within repository groups | |
458 | % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group): |
|
460 | % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group): | |
459 | <li class="submenu-title">${_('This Repository Group')}</li> |
|
461 | <li class="submenu-title">${_('This Repository Group')}</li> | |
460 |
|
462 | |||
461 | % if can_create_repos_in_group: |
|
463 | % if can_create_repos_in_group: | |
462 | <li> |
|
464 | <li> | |
463 | <a href="${h.route_path('repo_new',_query=default_target_group)}">${_('New Repository')}</a> |
|
465 | <a href="${h.route_path('repo_new',_query=default_target_group)}">${_('New Repository')}</a> | |
464 | </li> |
|
466 | </li> | |
465 | % endif |
|
467 | % endif | |
466 |
|
468 | |||
467 | % if can_create_repo_groups_in_group: |
|
469 | % if can_create_repo_groups_in_group: | |
468 | <li> |
|
470 | <li> | |
469 | <a href="${h.route_path('repo_group_new',_query=default_target_group)}">${_(u'New Repository Group')}</a> |
|
471 | <a href="${h.route_path('repo_group_new',_query=default_target_group)}">${_(u'New Repository Group')}</a> | |
470 | </li> |
|
472 | </li> | |
471 | % endif |
|
473 | % endif | |
472 | % endif |
|
474 | % endif | |
473 |
|
475 | |||
474 | ## personal group |
|
476 | ## personal group | |
475 | % if c.rhodecode_user.personal_repo_group: |
|
477 | % if c.rhodecode_user.personal_repo_group: | |
476 | <li class="submenu-title">Personal Group</li> |
|
478 | <li class="submenu-title">Personal Group</li> | |
477 |
|
479 | |||
478 | <li> |
|
480 | <li> | |
479 | <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a> |
|
481 | <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a> | |
480 | </li> |
|
482 | </li> | |
481 |
|
483 | |||
482 | <li> |
|
484 | <li> | |
483 | <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a> |
|
485 | <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a> | |
484 | </li> |
|
486 | </li> | |
485 | % endif |
|
487 | % endif | |
486 |
|
488 | |||
487 | ## Global actions |
|
489 | ## Global actions | |
488 | <li class="submenu-title">RhodeCode</li> |
|
490 | <li class="submenu-title">RhodeCode</li> | |
489 | % if can_create_repos: |
|
491 | % if can_create_repos: | |
490 | <li> |
|
492 | <li> | |
491 | <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a> |
|
493 | <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a> | |
492 | </li> |
|
494 | </li> | |
493 | % endif |
|
495 | % endif | |
494 |
|
496 | |||
495 | % if can_create_repo_groups: |
|
497 | % if can_create_repo_groups: | |
496 | <li> |
|
498 | <li> | |
497 | <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a> |
|
499 | <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a> | |
498 | </li> |
|
500 | </li> | |
499 | % endif |
|
501 | % endif | |
500 |
|
502 | |||
501 | <li> |
|
503 | <li> | |
502 | <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a> |
|
504 | <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a> | |
503 | </li> |
|
505 | </li> | |
504 |
|
506 | |||
505 | </ol> |
|
507 | </ol> | |
506 |
|
508 | |||
507 | </div> |
|
509 | </div> | |
508 | </li> |
|
510 | </li> | |
509 |
|
511 | |||
510 | ## notifications |
|
512 | ## notifications | |
511 | <li> |
|
513 | <li> | |
512 | <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}"> |
|
514 | <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}"> | |
513 | ${c.unread_notifications} |
|
515 | ${c.unread_notifications} | |
514 | </a> |
|
516 | </a> | |
515 | </li> |
|
517 | </li> | |
516 | % endif |
|
518 | % endif | |
517 |
|
519 | |||
518 | ## USER MENU |
|
520 | ## USER MENU | |
519 | <li id="quick_login_li" class="${'active' if active else ''}"> |
|
521 | <li id="quick_login_li" class="${'active' if active else ''}"> | |
520 | % if c.rhodecode_user.username == h.DEFAULT_USER: |
|
522 | % if c.rhodecode_user.username == h.DEFAULT_USER: | |
521 | <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}"> |
|
523 | <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}"> | |
522 | ${gravatar(c.rhodecode_user.email, 20)} |
|
524 | ${gravatar(c.rhodecode_user.email, 20)} | |
523 | <span class="user"> |
|
525 | <span class="user"> | |
524 | <span>${_('Sign in')}</span> |
|
526 | <span>${_('Sign in')}</span> | |
525 | </span> |
|
527 | </span> | |
526 | </a> |
|
528 | </a> | |
527 | % else: |
|
529 | % else: | |
528 | ## logged in user |
|
530 | ## logged in user | |
529 | <a id="quick_login_link" class="menulink childs"> |
|
531 | <a id="quick_login_link" class="menulink childs"> | |
530 | ${gravatar(c.rhodecode_user.email, 20)} |
|
532 | ${gravatar(c.rhodecode_user.email, 20)} | |
531 | <span class="user"> |
|
533 | <span class="user"> | |
532 | <span class="menu_link_user">${c.rhodecode_user.username}</span> |
|
534 | <span class="menu_link_user">${c.rhodecode_user.username}</span> | |
533 | <div class="show_more"></div> |
|
535 | <div class="show_more"></div> | |
534 | </span> |
|
536 | </span> | |
535 | </a> |
|
537 | </a> | |
536 | ## subnav with menu for logged in user |
|
538 | ## subnav with menu for logged in user | |
537 | <div class="user-menu submenu"> |
|
539 | <div class="user-menu submenu"> | |
538 | <div id="quick_login"> |
|
540 | <div id="quick_login"> | |
539 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
541 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
540 | <div class=""> |
|
542 | <div class=""> | |
541 | <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div> |
|
543 | <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div> | |
542 | <div class="full_name">${c.rhodecode_user.full_name_or_username}</div> |
|
544 | <div class="full_name">${c.rhodecode_user.full_name_or_username}</div> | |
543 | <div class="email">${c.rhodecode_user.email}</div> |
|
545 | <div class="email">${c.rhodecode_user.email}</div> | |
544 | </div> |
|
546 | </div> | |
545 | <div class=""> |
|
547 | <div class=""> | |
546 | <ol class="links"> |
|
548 | <ol class="links"> | |
547 | <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li> |
|
549 | <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li> | |
548 | % if c.rhodecode_user.personal_repo_group: |
|
550 | % if c.rhodecode_user.personal_repo_group: | |
549 | <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li> |
|
551 | <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li> | |
550 | % endif |
|
552 | % endif | |
551 | <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li> |
|
553 | <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li> | |
552 |
|
554 | |||
553 | % if c.debug_style: |
|
555 | % if c.debug_style: | |
554 | <li> |
|
556 | <li> | |
555 | <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}"> |
|
557 | <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}"> | |
556 | <div class="menulabel">${_('[Style]')}</div> |
|
558 | <div class="menulabel">${_('[Style]')}</div> | |
557 | </a> |
|
559 | </a> | |
558 | </li> |
|
560 | </li> | |
559 | % endif |
|
561 | % endif | |
560 |
|
562 | |||
561 | ## bookmark-items |
|
563 | ## bookmark-items | |
562 | <li class="bookmark-items"> |
|
564 | <li class="bookmark-items"> | |
563 | ${_('Bookmarks')} |
|
565 | ${_('Bookmarks')} | |
564 | <div class="pull-right"> |
|
566 | <div class="pull-right"> | |
565 | <a href="${h.route_path('my_account_bookmarks')}"> |
|
567 | <a href="${h.route_path('my_account_bookmarks')}"> | |
566 |
|
568 | |||
567 | <i class="icon-cog"></i> |
|
569 | <i class="icon-cog"></i> | |
568 | </a> |
|
570 | </a> | |
569 | </div> |
|
571 | </div> | |
570 | </li> |
|
572 | </li> | |
571 | % if not c.bookmark_items: |
|
573 | % if not c.bookmark_items: | |
572 | <li> |
|
574 | <li> | |
573 | <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a> |
|
575 | <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a> | |
574 | </li> |
|
576 | </li> | |
575 | % endif |
|
577 | % endif | |
576 | % for item in c.bookmark_items: |
|
578 | % for item in c.bookmark_items: | |
577 | <li> |
|
579 | <li> | |
578 | % if item.repository: |
|
580 | % if item.repository: | |
579 | <div> |
|
581 | <div> | |
580 | <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}"> |
|
582 | <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}"> | |
581 | <code>${item.position}</code> |
|
583 | <code>${item.position}</code> | |
582 | % if item.repository.repo_type == 'hg': |
|
584 | % if item.repository.repo_type == 'hg': | |
583 | <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i> |
|
585 | <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i> | |
584 | % elif item.repository.repo_type == 'git': |
|
586 | % elif item.repository.repo_type == 'git': | |
585 | <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i> |
|
587 | <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i> | |
586 | % elif item.repository.repo_type == 'svn': |
|
588 | % elif item.repository.repo_type == 'svn': | |
587 | <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i> |
|
589 | <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i> | |
588 | % endif |
|
590 | % endif | |
589 | ${(item.title or h.shorter(item.repository.repo_name, 30))} |
|
591 | ${(item.title or h.shorter(item.repository.repo_name, 30))} | |
590 | </a> |
|
592 | </a> | |
591 | </div> |
|
593 | </div> | |
592 | % elif item.repository_group: |
|
594 | % elif item.repository_group: | |
593 | <div> |
|
595 | <div> | |
594 | <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}"> |
|
596 | <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}"> | |
595 | <code>${item.position}</code> |
|
597 | <code>${item.position}</code> | |
596 | <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i> |
|
598 | <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i> | |
597 | ${(item.title or h.shorter(item.repository_group.group_name, 30))} |
|
599 | ${(item.title or h.shorter(item.repository_group.group_name, 30))} | |
598 | </a> |
|
600 | </a> | |
599 | </div> |
|
601 | </div> | |
600 | % else: |
|
602 | % else: | |
601 | <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}"> |
|
603 | <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}"> | |
602 | <code>${item.position}</code> |
|
604 | <code>${item.position}</code> | |
603 | ${item.title} |
|
605 | ${item.title} | |
604 | </a> |
|
606 | </a> | |
605 | % endif |
|
607 | % endif | |
606 | </li> |
|
608 | </li> | |
607 | % endfor |
|
609 | % endfor | |
608 |
|
610 | |||
609 | <li class="logout"> |
|
611 | <li class="logout"> | |
610 | ${h.secure_form(h.route_path('logout'), request=request)} |
|
612 | ${h.secure_form(h.route_path('logout'), request=request)} | |
611 | ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")} |
|
613 | ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")} | |
612 | ${h.end_form()} |
|
614 | ${h.end_form()} | |
613 | </li> |
|
615 | </li> | |
614 | </ol> |
|
616 | </ol> | |
615 | </div> |
|
617 | </div> | |
616 | %endif |
|
618 | %endif | |
617 | </div> |
|
619 | </div> | |
618 | </div> |
|
620 | </div> | |
619 |
|
621 | |||
620 | % endif |
|
622 | % endif | |
621 | </li> |
|
623 | </li> | |
622 | </%def> |
|
624 | </%def> | |
623 |
|
625 | |||
624 | <%def name="menu_items(active=None)"> |
|
626 | <%def name="menu_items(active=None)"> | |
625 | <% |
|
627 | <% | |
626 | def is_active(selected): |
|
628 | def is_active(selected): | |
627 | if selected == active: |
|
629 | if selected == active: | |
628 | return "active" |
|
630 | return "active" | |
629 | return "" |
|
631 | return "" | |
630 | %> |
|
632 | %> | |
631 |
|
633 | |||
632 | <ul id="quick" class="main_nav navigation horizontal-list"> |
|
634 | <ul id="quick" class="main_nav navigation horizontal-list"> | |
633 | ## notice box for important system messages |
|
635 | ## notice box for important system messages | |
634 | <li style="display: none"> |
|
636 | <li style="display: none"> | |
635 | <a class="notice-box" href="#openNotice" onclick="return false"> |
|
637 | <a class="notice-box" href="#openNotice" onclick="return false"> | |
636 | <div class="menulabel-notice" > |
|
638 | <div class="menulabel-notice" > | |
637 | 0 |
|
639 | 0 | |
638 | </div> |
|
640 | </div> | |
639 | </a> |
|
641 | </a> | |
640 | </li> |
|
642 | </li> | |
641 |
|
643 | |||
642 | ## Main filter |
|
644 | ## Main filter | |
643 | <li> |
|
645 | <li> | |
644 | <div class="menulabel main_filter_box"> |
|
646 | <div class="menulabel main_filter_box"> | |
645 | <div class="main_filter_input_box"> |
|
647 | <div class="main_filter_input_box"> | |
646 | <ul class="searchItems"> |
|
648 | <ul class="searchItems"> | |
647 |
|
649 | |||
648 | % if c.template_context['search_context']['repo_id']: |
|
650 | % if c.template_context['search_context']['repo_id']: | |
649 | <li class="searchTag searchTagFilter searchTagHidable" > |
|
651 | <li class="searchTag searchTagFilter searchTagHidable" > | |
650 | ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}"> |
|
652 | ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}"> | |
651 | <span class="tag"> |
|
653 | <span class="tag"> | |
652 | This repo |
|
654 | This repo | |
653 | <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a> |
|
655 | <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a> | |
654 | </span> |
|
656 | </span> | |
655 | ##</a> |
|
657 | ##</a> | |
656 | </li> |
|
658 | </li> | |
657 | % elif c.template_context['search_context']['repo_group_id']: |
|
659 | % elif c.template_context['search_context']['repo_group_id']: | |
658 | <li class="searchTag searchTagFilter searchTagHidable"> |
|
660 | <li class="searchTag searchTagFilter searchTagHidable"> | |
659 | ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}"> |
|
661 | ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}"> | |
660 | <span class="tag"> |
|
662 | <span class="tag"> | |
661 | This group |
|
663 | This group | |
662 | <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a> |
|
664 | <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a> | |
663 | </span> |
|
665 | </span> | |
664 | ##</a> |
|
666 | ##</a> | |
665 | </li> |
|
667 | </li> | |
666 | % endif |
|
668 | % endif | |
667 |
|
669 | |||
668 | <li class="searchTagInput"> |
|
670 | <li class="searchTagInput"> | |
669 | <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" /> |
|
671 | <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" /> | |
670 | </li> |
|
672 | </li> | |
671 | <li class="searchTag searchTagHelp"> |
|
673 | <li class="searchTag searchTagHelp"> | |
672 | <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a> |
|
674 | <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a> | |
673 | </li> |
|
675 | </li> | |
674 | </ul> |
|
676 | </ul> | |
675 | </div> |
|
677 | </div> | |
676 | </div> |
|
678 | </div> | |
677 |
|
679 | |||
678 | <div id="main_filter_help" style="display: none"> |
|
680 | <div id="main_filter_help" style="display: none"> | |
679 | - Use '/' key to quickly access this field. |
|
681 | - Use '/' key to quickly access this field. | |
680 |
|
682 | |||
681 | - Enter a name of repository, or repository group for quick search. |
|
683 | - Enter a name of repository, or repository group for quick search. | |
682 |
|
684 | |||
683 | - Prefix query to allow special search: |
|
685 | - Prefix query to allow special search: | |
684 |
|
686 | |||
685 | user:admin, to search for usernames, always global |
|
687 | user:admin, to search for usernames, always global | |
686 |
|
688 | |||
687 | user_group:devops, to search for user groups, always global |
|
689 | user_group:devops, to search for user groups, always global | |
688 |
|
690 | |||
689 | commit:efced4, to search for commits, scoped to repositories or groups |
|
691 | commit:efced4, to search for commits, scoped to repositories or groups | |
690 |
|
692 | |||
691 | file:models.py, to search for file paths, scoped to repositories or groups |
|
693 | file:models.py, to search for file paths, scoped to repositories or groups | |
692 |
|
694 | |||
693 | % if c.template_context['search_context']['repo_id']: |
|
695 | % if c.template_context['search_context']['repo_id']: | |
694 | For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a> |
|
696 | For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a> | |
695 | % elif c.template_context['search_context']['repo_group_id']: |
|
697 | % elif c.template_context['search_context']['repo_group_id']: | |
696 | For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a> |
|
698 | For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a> | |
697 | % else: |
|
699 | % else: | |
698 | For advanced full text search visit: <a href="${h.route_path('search')}">global search</a> |
|
700 | For advanced full text search visit: <a href="${h.route_path('search')}">global search</a> | |
699 | % endif |
|
701 | % endif | |
700 | </div> |
|
702 | </div> | |
701 | </li> |
|
703 | </li> | |
702 |
|
704 | |||
703 | ## ROOT MENU |
|
705 | ## ROOT MENU | |
704 | <li class="${is_active('home')}"> |
|
706 | <li class="${is_active('home')}"> | |
705 | <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}"> |
|
707 | <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}"> | |
706 | <div class="menulabel">${_('Home')}</div> |
|
708 | <div class="menulabel">${_('Home')}</div> | |
707 | </a> |
|
709 | </a> | |
708 | </li> |
|
710 | </li> | |
709 |
|
711 | |||
710 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
712 | %if c.rhodecode_user.username != h.DEFAULT_USER: | |
711 | <li class="${is_active('journal')}"> |
|
713 | <li class="${is_active('journal')}"> | |
712 | <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}"> |
|
714 | <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}"> | |
713 | <div class="menulabel">${_('Journal')}</div> |
|
715 | <div class="menulabel">${_('Journal')}</div> | |
714 | </a> |
|
716 | </a> | |
715 | </li> |
|
717 | </li> | |
716 | %else: |
|
718 | %else: | |
717 | <li class="${is_active('journal')}"> |
|
719 | <li class="${is_active('journal')}"> | |
718 | <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}"> |
|
720 | <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}"> | |
719 | <div class="menulabel">${_('Public journal')}</div> |
|
721 | <div class="menulabel">${_('Public journal')}</div> | |
720 | </a> |
|
722 | </a> | |
721 | </li> |
|
723 | </li> | |
722 | %endif |
|
724 | %endif | |
723 |
|
725 | |||
724 | <li class="${is_active('gists')}"> |
|
726 | <li class="${is_active('gists')}"> | |
725 | <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}"> |
|
727 | <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}"> | |
726 | <div class="menulabel">${_('Gists')}</div> |
|
728 | <div class="menulabel">${_('Gists')}</div> | |
727 | </a> |
|
729 | </a> | |
728 | </li> |
|
730 | </li> | |
729 |
|
731 | |||
730 | % if c.is_super_admin or c.is_delegated_admin: |
|
732 | % if c.is_super_admin or c.is_delegated_admin: | |
731 | <li class="${is_active('admin')}"> |
|
733 | <li class="${is_active('admin')}"> | |
732 | <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}"> |
|
734 | <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}"> | |
733 | <div class="menulabel">${_('Admin')} </div> |
|
735 | <div class="menulabel">${_('Admin')} </div> | |
734 | </a> |
|
736 | </a> | |
735 | </li> |
|
737 | </li> | |
736 | % endif |
|
738 | % endif | |
737 |
|
739 | |||
738 | ## render extra user menu |
|
740 | ## render extra user menu | |
739 | ${usermenu(active=(active=='my_account'))} |
|
741 | ${usermenu(active=(active=='my_account'))} | |
740 |
|
742 | |||
741 | </ul> |
|
743 | </ul> | |
742 |
|
744 | |||
743 | <script type="text/javascript"> |
|
745 | <script type="text/javascript"> | |
744 | var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True"; |
|
746 | var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True"; | |
745 |
|
747 | |||
746 | var formatRepoResult = function(result, container, query, escapeMarkup) { |
|
748 | var formatRepoResult = function(result, container, query, escapeMarkup) { | |
747 | return function(data, escapeMarkup) { |
|
749 | return function(data, escapeMarkup) { | |
748 | if (!data.repo_id){ |
|
750 | if (!data.repo_id){ | |
749 | return data.text; // optgroup text Repositories |
|
751 | return data.text; // optgroup text Repositories | |
750 | } |
|
752 | } | |
751 |
|
753 | |||
752 | var tmpl = ''; |
|
754 | var tmpl = ''; | |
753 | var repoType = data['repo_type']; |
|
755 | var repoType = data['repo_type']; | |
754 | var repoName = data['text']; |
|
756 | var repoName = data['text']; | |
755 |
|
757 | |||
756 | if(data && data.type == 'repo'){ |
|
758 | if(data && data.type == 'repo'){ | |
757 | if(repoType === 'hg'){ |
|
759 | if(repoType === 'hg'){ | |
758 | tmpl += '<i class="icon-hg"></i> '; |
|
760 | tmpl += '<i class="icon-hg"></i> '; | |
759 | } |
|
761 | } | |
760 | else if(repoType === 'git'){ |
|
762 | else if(repoType === 'git'){ | |
761 | tmpl += '<i class="icon-git"></i> '; |
|
763 | tmpl += '<i class="icon-git"></i> '; | |
762 | } |
|
764 | } | |
763 | else if(repoType === 'svn'){ |
|
765 | else if(repoType === 'svn'){ | |
764 | tmpl += '<i class="icon-svn"></i> '; |
|
766 | tmpl += '<i class="icon-svn"></i> '; | |
765 | } |
|
767 | } | |
766 | if(data['private']){ |
|
768 | if(data['private']){ | |
767 | tmpl += '<i class="icon-lock" ></i> '; |
|
769 | tmpl += '<i class="icon-lock" ></i> '; | |
768 | } |
|
770 | } | |
769 | else if(visualShowPublicIcon){ |
|
771 | else if(visualShowPublicIcon){ | |
770 | tmpl += '<i class="icon-unlock-alt"></i> '; |
|
772 | tmpl += '<i class="icon-unlock-alt"></i> '; | |
771 | } |
|
773 | } | |
772 | } |
|
774 | } | |
773 | tmpl += escapeMarkup(repoName); |
|
775 | tmpl += escapeMarkup(repoName); | |
774 | return tmpl; |
|
776 | return tmpl; | |
775 |
|
777 | |||
776 | }(result, escapeMarkup); |
|
778 | }(result, escapeMarkup); | |
777 | }; |
|
779 | }; | |
778 |
|
780 | |||
779 | var formatRepoGroupResult = function(result, container, query, escapeMarkup) { |
|
781 | var formatRepoGroupResult = function(result, container, query, escapeMarkup) { | |
780 | return function(data, escapeMarkup) { |
|
782 | return function(data, escapeMarkup) { | |
781 | if (!data.repo_group_id){ |
|
783 | if (!data.repo_group_id){ | |
782 | return data.text; // optgroup text Repositories |
|
784 | return data.text; // optgroup text Repositories | |
783 | } |
|
785 | } | |
784 |
|
786 | |||
785 | var tmpl = ''; |
|
787 | var tmpl = ''; | |
786 | var repoGroupName = data['text']; |
|
788 | var repoGroupName = data['text']; | |
787 |
|
789 | |||
788 | if(data){ |
|
790 | if(data){ | |
789 |
|
791 | |||
790 | tmpl += '<i class="icon-repo-group"></i> '; |
|
792 | tmpl += '<i class="icon-repo-group"></i> '; | |
791 |
|
793 | |||
792 | } |
|
794 | } | |
793 | tmpl += escapeMarkup(repoGroupName); |
|
795 | tmpl += escapeMarkup(repoGroupName); | |
794 | return tmpl; |
|
796 | return tmpl; | |
795 |
|
797 | |||
796 | }(result, escapeMarkup); |
|
798 | }(result, escapeMarkup); | |
797 | }; |
|
799 | }; | |
798 |
|
800 | |||
799 | var escapeRegExChars = function (value) { |
|
801 | var escapeRegExChars = function (value) { | |
800 | return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); |
|
802 | return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); | |
801 | }; |
|
803 | }; | |
802 |
|
804 | |||
803 | var getRepoIcon = function(repo_type) { |
|
805 | var getRepoIcon = function(repo_type) { | |
804 | if (repo_type === 'hg') { |
|
806 | if (repo_type === 'hg') { | |
805 | return '<i class="icon-hg"></i> '; |
|
807 | return '<i class="icon-hg"></i> '; | |
806 | } |
|
808 | } | |
807 | else if (repo_type === 'git') { |
|
809 | else if (repo_type === 'git') { | |
808 | return '<i class="icon-git"></i> '; |
|
810 | return '<i class="icon-git"></i> '; | |
809 | } |
|
811 | } | |
810 | else if (repo_type === 'svn') { |
|
812 | else if (repo_type === 'svn') { | |
811 | return '<i class="icon-svn"></i> '; |
|
813 | return '<i class="icon-svn"></i> '; | |
812 | } |
|
814 | } | |
813 | return '' |
|
815 | return '' | |
814 | }; |
|
816 | }; | |
815 |
|
817 | |||
816 | var autocompleteMainFilterFormatResult = function (data, value, org_formatter) { |
|
818 | var autocompleteMainFilterFormatResult = function (data, value, org_formatter) { | |
817 |
|
819 | |||
818 | if (value.split(':').length === 2) { |
|
820 | if (value.split(':').length === 2) { | |
819 | value = value.split(':')[1] |
|
821 | value = value.split(':')[1] | |
820 | } |
|
822 | } | |
821 |
|
823 | |||
822 | var searchType = data['type']; |
|
824 | var searchType = data['type']; | |
823 | var searchSubType = data['subtype']; |
|
825 | var searchSubType = data['subtype']; | |
824 | var valueDisplay = data['value_display']; |
|
826 | var valueDisplay = data['value_display']; | |
825 |
|
827 | |||
826 | var pattern = '(' + escapeRegExChars(value) + ')'; |
|
828 | var pattern = '(' + escapeRegExChars(value) + ')'; | |
827 |
|
829 | |||
828 | valueDisplay = Select2.util.escapeMarkup(valueDisplay); |
|
830 | valueDisplay = Select2.util.escapeMarkup(valueDisplay); | |
829 |
|
831 | |||
830 | // highlight match |
|
832 | // highlight match | |
831 | if (searchType != 'text') { |
|
833 | if (searchType != 'text') { | |
832 | valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>'); |
|
834 | valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>'); | |
833 | } |
|
835 | } | |
834 |
|
836 | |||
835 | var icon = ''; |
|
837 | var icon = ''; | |
836 |
|
838 | |||
837 | if (searchType === 'hint') { |
|
839 | if (searchType === 'hint') { | |
838 | icon += '<i class="icon-repo-group"></i> '; |
|
840 | icon += '<i class="icon-repo-group"></i> '; | |
839 | } |
|
841 | } | |
840 | // full text search/hints |
|
842 | // full text search/hints | |
841 | else if (searchType === 'search') { |
|
843 | else if (searchType === 'search') { | |
842 | icon += '<i class="icon-more"></i> '; |
|
844 | icon += '<i class="icon-more"></i> '; | |
843 | if (searchSubType !== undefined && searchSubType == 'repo') { |
|
845 | if (searchSubType !== undefined && searchSubType == 'repo') { | |
844 | valueDisplay += '<div class="pull-right tag">repository</div>'; |
|
846 | valueDisplay += '<div class="pull-right tag">repository</div>'; | |
845 | } |
|
847 | } | |
846 | else if (searchSubType !== undefined && searchSubType == 'repo_group') { |
|
848 | else if (searchSubType !== undefined && searchSubType == 'repo_group') { | |
847 | valueDisplay += '<div class="pull-right tag">repo group</div>'; |
|
849 | valueDisplay += '<div class="pull-right tag">repo group</div>'; | |
848 | } |
|
850 | } | |
849 | } |
|
851 | } | |
850 | // repository |
|
852 | // repository | |
851 | else if (searchType === 'repo') { |
|
853 | else if (searchType === 'repo') { | |
852 |
|
854 | |||
853 | var repoIcon = getRepoIcon(data['repo_type']); |
|
855 | var repoIcon = getRepoIcon(data['repo_type']); | |
854 | icon += repoIcon; |
|
856 | icon += repoIcon; | |
855 |
|
857 | |||
856 | if (data['private']) { |
|
858 | if (data['private']) { | |
857 | icon += '<i class="icon-lock" ></i> '; |
|
859 | icon += '<i class="icon-lock" ></i> '; | |
858 | } |
|
860 | } | |
859 | else if (visualShowPublicIcon) { |
|
861 | else if (visualShowPublicIcon) { | |
860 | icon += '<i class="icon-unlock-alt"></i> '; |
|
862 | icon += '<i class="icon-unlock-alt"></i> '; | |
861 | } |
|
863 | } | |
862 | } |
|
864 | } | |
863 | // repository groups |
|
865 | // repository groups | |
864 | else if (searchType === 'repo_group') { |
|
866 | else if (searchType === 'repo_group') { | |
865 | icon += '<i class="icon-repo-group"></i> '; |
|
867 | icon += '<i class="icon-repo-group"></i> '; | |
866 | } |
|
868 | } | |
867 | // user group |
|
869 | // user group | |
868 | else if (searchType === 'user_group') { |
|
870 | else if (searchType === 'user_group') { | |
869 | icon += '<i class="icon-group"></i> '; |
|
871 | icon += '<i class="icon-group"></i> '; | |
870 | } |
|
872 | } | |
871 | // user |
|
873 | // user | |
872 | else if (searchType === 'user') { |
|
874 | else if (searchType === 'user') { | |
873 | icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']); |
|
875 | icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']); | |
874 | } |
|
876 | } | |
875 | // commit |
|
877 | // commit | |
876 | else if (searchType === 'commit') { |
|
878 | else if (searchType === 'commit') { | |
877 | var repo_data = data['repo_data']; |
|
879 | var repo_data = data['repo_data']; | |
878 | var repoIcon = getRepoIcon(repo_data['repository_type']); |
|
880 | var repoIcon = getRepoIcon(repo_data['repository_type']); | |
879 | if (repoIcon) { |
|
881 | if (repoIcon) { | |
880 | icon += repoIcon; |
|
882 | icon += repoIcon; | |
881 | } else { |
|
883 | } else { | |
882 | icon += '<i class="icon-tag"></i>'; |
|
884 | icon += '<i class="icon-tag"></i>'; | |
883 | } |
|
885 | } | |
884 | } |
|
886 | } | |
885 | // file |
|
887 | // file | |
886 | else if (searchType === 'file') { |
|
888 | else if (searchType === 'file') { | |
887 | var repo_data = data['repo_data']; |
|
889 | var repo_data = data['repo_data']; | |
888 | var repoIcon = getRepoIcon(repo_data['repository_type']); |
|
890 | var repoIcon = getRepoIcon(repo_data['repository_type']); | |
889 | if (repoIcon) { |
|
891 | if (repoIcon) { | |
890 | icon += repoIcon; |
|
892 | icon += repoIcon; | |
891 | } else { |
|
893 | } else { | |
892 | icon += '<i class="icon-tag"></i>'; |
|
894 | icon += '<i class="icon-tag"></i>'; | |
893 | } |
|
895 | } | |
894 | } |
|
896 | } | |
895 | // generic text |
|
897 | // generic text | |
896 | else if (searchType === 'text') { |
|
898 | else if (searchType === 'text') { | |
897 | icon = ''; |
|
899 | icon = ''; | |
898 | } |
|
900 | } | |
899 |
|
901 | |||
900 | var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'; |
|
902 | var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'; | |
901 | return tmpl.format(icon, valueDisplay); |
|
903 | return tmpl.format(icon, valueDisplay); | |
902 | }; |
|
904 | }; | |
903 |
|
905 | |||
904 | var handleSelect = function(element, suggestion) { |
|
906 | var handleSelect = function(element, suggestion) { | |
905 | if (suggestion.type === "hint") { |
|
907 | if (suggestion.type === "hint") { | |
906 | // we skip action |
|
908 | // we skip action | |
907 | $('#main_filter').focus(); |
|
909 | $('#main_filter').focus(); | |
908 | } |
|
910 | } | |
909 | else if (suggestion.type === "text") { |
|
911 | else if (suggestion.type === "text") { | |
910 | // we skip action |
|
912 | // we skip action | |
911 | $('#main_filter').focus(); |
|
913 | $('#main_filter').focus(); | |
912 |
|
914 | |||
913 | } else { |
|
915 | } else { | |
914 | window.location = suggestion['url']; |
|
916 | window.location = suggestion['url']; | |
915 | } |
|
917 | } | |
916 | }; |
|
918 | }; | |
917 |
|
919 | |||
918 | var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) { |
|
920 | var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) { | |
919 | if (queryLowerCase.split(':').length === 2) { |
|
921 | if (queryLowerCase.split(':').length === 2) { | |
920 | queryLowerCase = queryLowerCase.split(':')[1] |
|
922 | queryLowerCase = queryLowerCase.split(':')[1] | |
921 | } |
|
923 | } | |
922 | if (suggestion.type === "text") { |
|
924 | if (suggestion.type === "text") { | |
923 | // special case we don't want to "skip" display for |
|
925 | // special case we don't want to "skip" display for | |
924 | return true |
|
926 | return true | |
925 | } |
|
927 | } | |
926 | return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1; |
|
928 | return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1; | |
927 | }; |
|
929 | }; | |
928 |
|
930 | |||
929 | var cleanContext = { |
|
931 | var cleanContext = { | |
930 | repo_view_type: null, |
|
932 | repo_view_type: null, | |
931 |
|
933 | |||
932 | repo_id: null, |
|
934 | repo_id: null, | |
933 | repo_name: "", |
|
935 | repo_name: "", | |
934 |
|
936 | |||
935 | repo_group_id: null, |
|
937 | repo_group_id: null, | |
936 | repo_group_name: null |
|
938 | repo_group_name: null | |
937 | }; |
|
939 | }; | |
938 | var removeGoToFilter = function () { |
|
940 | var removeGoToFilter = function () { | |
939 | $('.searchTagHidable').hide(); |
|
941 | $('.searchTagHidable').hide(); | |
940 | $('#main_filter').autocomplete( |
|
942 | $('#main_filter').autocomplete( | |
941 | 'setOptions', {params:{search_context: cleanContext}}); |
|
943 | 'setOptions', {params:{search_context: cleanContext}}); | |
942 | }; |
|
944 | }; | |
943 |
|
945 | |||
944 | $('#main_filter').autocomplete({ |
|
946 | $('#main_filter').autocomplete({ | |
945 | serviceUrl: pyroutes.url('goto_switcher_data'), |
|
947 | serviceUrl: pyroutes.url('goto_switcher_data'), | |
946 | params: { |
|
948 | params: { | |
947 | "search_context": templateContext.search_context |
|
949 | "search_context": templateContext.search_context | |
948 | }, |
|
950 | }, | |
949 | minChars:2, |
|
951 | minChars:2, | |
950 | maxHeight:400, |
|
952 | maxHeight:400, | |
951 | deferRequestBy: 300, //miliseconds |
|
953 | deferRequestBy: 300, //miliseconds | |
952 | tabDisabled: true, |
|
954 | tabDisabled: true, | |
953 | autoSelectFirst: false, |
|
955 | autoSelectFirst: false, | |
954 | containerClass: 'autocomplete-qfilter-suggestions', |
|
956 | containerClass: 'autocomplete-qfilter-suggestions', | |
955 | formatResult: autocompleteMainFilterFormatResult, |
|
957 | formatResult: autocompleteMainFilterFormatResult, | |
956 | lookupFilter: autocompleteMainFilterResult, |
|
958 | lookupFilter: autocompleteMainFilterResult, | |
957 | onSelect: function (element, suggestion) { |
|
959 | onSelect: function (element, suggestion) { | |
958 | handleSelect(element, suggestion); |
|
960 | handleSelect(element, suggestion); | |
959 | return false; |
|
961 | return false; | |
960 | }, |
|
962 | }, | |
961 | onSearchError: function (element, query, jqXHR, textStatus, errorThrown) { |
|
963 | onSearchError: function (element, query, jqXHR, textStatus, errorThrown) { | |
962 | if (jqXHR !== 'abort') { |
|
964 | if (jqXHR !== 'abort') { | |
963 | alert("Error during search.\nError code: {0}".format(textStatus)); |
|
965 | alert("Error during search.\nError code: {0}".format(textStatus)); | |
964 | window.location = ''; |
|
966 | window.location = ''; | |
965 | } |
|
967 | } | |
966 | } |
|
968 | } | |
967 | }); |
|
969 | }); | |
968 |
|
970 | |||
969 | showMainFilterBox = function () { |
|
971 | showMainFilterBox = function () { | |
970 | $('#main_filter_help').toggle(); |
|
972 | $('#main_filter_help').toggle(); | |
971 | }; |
|
973 | }; | |
972 |
|
974 | |||
973 | $('#main_filter').on('keydown.autocomplete', function (e) { |
|
975 | $('#main_filter').on('keydown.autocomplete', function (e) { | |
974 |
|
976 | |||
975 | var BACKSPACE = 8; |
|
977 | var BACKSPACE = 8; | |
976 | var el = $(e.currentTarget); |
|
978 | var el = $(e.currentTarget); | |
977 | if(e.which === BACKSPACE){ |
|
979 | if(e.which === BACKSPACE){ | |
978 | var inputVal = el.val(); |
|
980 | var inputVal = el.val(); | |
979 | if (inputVal === ""){ |
|
981 | if (inputVal === ""){ | |
980 | removeGoToFilter() |
|
982 | removeGoToFilter() | |
981 | } |
|
983 | } | |
982 | } |
|
984 | } | |
983 | }); |
|
985 | }); | |
984 |
|
986 | |||
985 | </script> |
|
987 | </script> | |
986 | <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script> |
|
988 | <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script> | |
987 | </%def> |
|
989 | </%def> | |
988 |
|
990 | |||
989 | <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> |
|
991 | <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> | |
990 | <div class="modal-dialog"> |
|
992 | <div class="modal-dialog"> | |
991 | <div class="modal-content"> |
|
993 | <div class="modal-content"> | |
992 | <div class="modal-header"> |
|
994 | <div class="modal-header"> | |
993 | <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> |
|
995 | <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> | |
994 | <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4> |
|
996 | <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4> | |
995 | </div> |
|
997 | </div> | |
996 | <div class="modal-body"> |
|
998 | <div class="modal-body"> | |
997 | <div class="block-left"> |
|
999 | <div class="block-left"> | |
998 | <table class="keyboard-mappings"> |
|
1000 | <table class="keyboard-mappings"> | |
999 | <tbody> |
|
1001 | <tbody> | |
1000 | <tr> |
|
1002 | <tr> | |
1001 | <th></th> |
|
1003 | <th></th> | |
1002 | <th>${_('Site-wide shortcuts')}</th> |
|
1004 | <th>${_('Site-wide shortcuts')}</th> | |
1003 | </tr> |
|
1005 | </tr> | |
1004 | <% |
|
1006 | <% | |
1005 | elems = [ |
|
1007 | elems = [ | |
1006 | ('/', 'Use quick search box'), |
|
1008 | ('/', 'Use quick search box'), | |
1007 | ('g h', 'Goto home page'), |
|
1009 | ('g h', 'Goto home page'), | |
1008 | ('g g', 'Goto my private gists page'), |
|
1010 | ('g g', 'Goto my private gists page'), | |
1009 | ('g G', 'Goto my public gists page'), |
|
1011 | ('g G', 'Goto my public gists page'), | |
1010 | ('g 0-9', 'Goto bookmarked items from 0-9'), |
|
1012 | ('g 0-9', 'Goto bookmarked items from 0-9'), | |
1011 | ('n r', 'New repository page'), |
|
1013 | ('n r', 'New repository page'), | |
1012 | ('n g', 'New gist page'), |
|
1014 | ('n g', 'New gist page'), | |
1013 | ] |
|
1015 | ] | |
1014 | %> |
|
1016 | %> | |
1015 | %for key, desc in elems: |
|
1017 | %for key, desc in elems: | |
1016 | <tr> |
|
1018 | <tr> | |
1017 | <td class="keys"> |
|
1019 | <td class="keys"> | |
1018 | <span class="key tag">${key}</span> |
|
1020 | <span class="key tag">${key}</span> | |
1019 | </td> |
|
1021 | </td> | |
1020 | <td>${desc}</td> |
|
1022 | <td>${desc}</td> | |
1021 | </tr> |
|
1023 | </tr> | |
1022 | %endfor |
|
1024 | %endfor | |
1023 | </tbody> |
|
1025 | </tbody> | |
1024 | </table> |
|
1026 | </table> | |
1025 | </div> |
|
1027 | </div> | |
1026 | <div class="block-left"> |
|
1028 | <div class="block-left"> | |
1027 | <table class="keyboard-mappings"> |
|
1029 | <table class="keyboard-mappings"> | |
1028 | <tbody> |
|
1030 | <tbody> | |
1029 | <tr> |
|
1031 | <tr> | |
1030 | <th></th> |
|
1032 | <th></th> | |
1031 | <th>${_('Repositories')}</th> |
|
1033 | <th>${_('Repositories')}</th> | |
1032 | </tr> |
|
1034 | </tr> | |
1033 | <% |
|
1035 | <% | |
1034 | elems = [ |
|
1036 | elems = [ | |
1035 | ('g s', 'Goto summary page'), |
|
1037 | ('g s', 'Goto summary page'), | |
1036 | ('g c', 'Goto changelog page'), |
|
1038 | ('g c', 'Goto changelog page'), | |
1037 | ('g f', 'Goto files page'), |
|
1039 | ('g f', 'Goto files page'), | |
1038 | ('g F', 'Goto files page with file search activated'), |
|
1040 | ('g F', 'Goto files page with file search activated'), | |
1039 | ('g p', 'Goto pull requests page'), |
|
1041 | ('g p', 'Goto pull requests page'), | |
1040 | ('g o', 'Goto repository settings'), |
|
1042 | ('g o', 'Goto repository settings'), | |
1041 | ('g O', 'Goto repository permissions settings'), |
|
1043 | ('g O', 'Goto repository permissions settings'), | |
1042 | ] |
|
1044 | ] | |
1043 | %> |
|
1045 | %> | |
1044 | %for key, desc in elems: |
|
1046 | %for key, desc in elems: | |
1045 | <tr> |
|
1047 | <tr> | |
1046 | <td class="keys"> |
|
1048 | <td class="keys"> | |
1047 | <span class="key tag">${key}</span> |
|
1049 | <span class="key tag">${key}</span> | |
1048 | </td> |
|
1050 | </td> | |
1049 | <td>${desc}</td> |
|
1051 | <td>${desc}</td> | |
1050 | </tr> |
|
1052 | </tr> | |
1051 | %endfor |
|
1053 | %endfor | |
1052 | </tbody> |
|
1054 | </tbody> | |
1053 | </table> |
|
1055 | </table> | |
1054 | </div> |
|
1056 | </div> | |
1055 | </div> |
|
1057 | </div> | |
1056 | <div class="modal-footer"> |
|
1058 | <div class="modal-footer"> | |
1057 | </div> |
|
1059 | </div> | |
1058 | </div><!-- /.modal-content --> |
|
1060 | </div><!-- /.modal-content --> | |
1059 | </div><!-- /.modal-dialog --> |
|
1061 | </div><!-- /.modal-dialog --> | |
1060 | </div><!-- /.modal --> |
|
1062 | </div><!-- /.modal --> | |
1061 |
|
1063 |
General Comments 0
You need to be logged in to leave comments.
Login now