##// END OF EJS Templates
search: allow quick search for path globally
dan -
r3542:40ac848d default
parent child Browse files
Show More
@@ -1,607 +1,659 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import re
22 22 import logging
23 23 import collections
24 24
25 25 from pyramid.view import view_config
26 26
27 27 from rhodecode.apps._base import BaseAppView
28 28 from rhodecode.lib import helpers as h
29 29 from rhodecode.lib.auth import (
30 30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
31 31 CSRFRequired)
32 32 from rhodecode.lib.index import searcher_from_config
33 33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.model.db import (
36 36 func, true, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
37 37 from rhodecode.model.repo import RepoModel
38 38 from rhodecode.model.repo_group import RepoGroupModel
39 39 from rhodecode.model.scm import RepoGroupList, RepoList
40 40 from rhodecode.model.user import UserModel
41 41 from rhodecode.model.user_group import UserGroupModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class HomeView(BaseAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context()
50 50 c.user = c.auth_user.get_instance()
51 51
52 52 return c
53 53
54 54 @LoginRequired()
55 55 @view_config(
56 56 route_name='user_autocomplete_data', request_method='GET',
57 57 renderer='json_ext', xhr=True)
58 58 def user_autocomplete_data(self):
59 59 self.load_default_context()
60 60 query = self.request.GET.get('query')
61 61 active = str2bool(self.request.GET.get('active') or True)
62 62 include_groups = str2bool(self.request.GET.get('user_groups'))
63 63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
64 64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
65 65
66 66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
67 67 query, active, include_groups)
68 68
69 69 _users = UserModel().get_users(
70 70 name_contains=query, only_active=active)
71 71
72 72 def maybe_skip_default_user(usr):
73 73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
74 74 return False
75 75 return True
76 76 _users = filter(maybe_skip_default_user, _users)
77 77
78 78 if include_groups:
79 79 # extend with user groups
80 80 _user_groups = UserGroupModel().get_user_groups(
81 81 name_contains=query, only_active=active,
82 82 expand_groups=expand_groups)
83 83 _users = _users + _user_groups
84 84
85 85 return {'suggestions': _users}
86 86
87 87 @LoginRequired()
88 88 @NotAnonymous()
89 89 @view_config(
90 90 route_name='user_group_autocomplete_data', request_method='GET',
91 91 renderer='json_ext', xhr=True)
92 92 def user_group_autocomplete_data(self):
93 93 self.load_default_context()
94 94 query = self.request.GET.get('query')
95 95 active = str2bool(self.request.GET.get('active') or True)
96 96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
97 97
98 98 log.debug('generating user group list, query:%s, active:%s',
99 99 query, active)
100 100
101 101 _user_groups = UserGroupModel().get_user_groups(
102 102 name_contains=query, only_active=active,
103 103 expand_groups=expand_groups)
104 104 _user_groups = _user_groups
105 105
106 106 return {'suggestions': _user_groups}
107 107
108 108 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
109 109 org_query = name_contains
110 110 allowed_ids = self._rhodecode_user.repo_acl_ids(
111 111 ['repository.read', 'repository.write', 'repository.admin'],
112 112 cache=False, name_filter=name_contains) or [-1]
113 113
114 114 query = Repository.query()\
115 115 .order_by(func.length(Repository.repo_name))\
116 116 .order_by(Repository.repo_name)\
117 117 .filter(Repository.archived.isnot(true()))\
118 118 .filter(or_(
119 119 # generate multiple IN to fix limitation problems
120 120 *in_filter_generator(Repository.repo_id, allowed_ids)
121 121 ))
122 122
123 123 if repo_type:
124 124 query = query.filter(Repository.repo_type == repo_type)
125 125
126 126 if name_contains:
127 127 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
128 128 query = query.filter(
129 129 Repository.repo_name.ilike(ilike_expression))
130 130 query = query.limit(limit)
131 131
132 132 acl_iter = query
133 133
134 134 return [
135 135 {
136 136 'id': obj.repo_name,
137 137 'value': org_query,
138 138 'value_display': obj.repo_name,
139 139 'text': obj.repo_name,
140 140 'type': 'repo',
141 141 'repo_id': obj.repo_id,
142 142 'repo_type': obj.repo_type,
143 143 'private': obj.private,
144 144 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
145 145 }
146 146 for obj in acl_iter]
147 147
148 148 def _get_repo_group_list(self, name_contains=None, limit=20):
149 149 org_query = name_contains
150 150 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
151 151 ['group.read', 'group.write', 'group.admin'],
152 152 cache=False, name_filter=name_contains) or [-1]
153 153
154 154 query = RepoGroup.query()\
155 155 .order_by(func.length(RepoGroup.group_name))\
156 156 .order_by(RepoGroup.group_name) \
157 157 .filter(or_(
158 158 # generate multiple IN to fix limitation problems
159 159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 160 ))
161 161
162 162 if name_contains:
163 163 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
164 164 query = query.filter(
165 165 RepoGroup.group_name.ilike(ilike_expression))
166 166 query = query.limit(limit)
167 167
168 168 acl_iter = query
169 169
170 170 return [
171 171 {
172 172 'id': obj.group_name,
173 173 'value': org_query,
174 174 'value_display': obj.group_name,
175 175 'text': obj.group_name,
176 176 'type': 'repo_group',
177 177 'repo_group_id': obj.group_id,
178 178 'url': h.route_path(
179 179 'repo_group_home', repo_group_name=obj.group_name)
180 180 }
181 181 for obj in acl_iter]
182 182
183 183 def _get_user_list(self, name_contains=None, limit=20):
184 184 org_query = name_contains
185 185 if not name_contains:
186 186 return []
187 187
188 188 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
189 189 if len(name_contains) != 1:
190 190 return []
191 191 name_contains = name_contains[0]
192 192
193 193 query = User.query()\
194 194 .order_by(func.length(User.username))\
195 195 .order_by(User.username) \
196 196 .filter(User.username != User.DEFAULT_USER)
197 197
198 198 if name_contains:
199 199 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
200 200 query = query.filter(
201 201 User.username.ilike(ilike_expression))
202 202 query = query.limit(limit)
203 203
204 204 acl_iter = query
205 205
206 206 return [
207 207 {
208 208 'id': obj.user_id,
209 209 'value': org_query,
210 210 'value_display': obj.username,
211 211 'type': 'user',
212 212 'icon_link': h.gravatar_url(obj.email, 30),
213 213 'url': h.route_path(
214 214 'user_profile', username=obj.username)
215 215 }
216 216 for obj in acl_iter]
217 217
218 218 def _get_user_groups_list(self, name_contains=None, limit=20):
219 219 org_query = name_contains
220 220 if not name_contains:
221 221 return []
222 222
223 223 name_contains = re.compile('(?:user_group:)(.+)').findall(name_contains)
224 224 if len(name_contains) != 1:
225 225 return []
226 226 name_contains = name_contains[0]
227 227
228 228 query = UserGroup.query()\
229 229 .order_by(func.length(UserGroup.users_group_name))\
230 230 .order_by(UserGroup.users_group_name)
231 231
232 232 if name_contains:
233 233 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
234 234 query = query.filter(
235 235 UserGroup.users_group_name.ilike(ilike_expression))
236 236 query = query.limit(limit)
237 237
238 238 acl_iter = query
239 239
240 240 return [
241 241 {
242 242 'id': obj.users_group_id,
243 243 'value': org_query,
244 244 'value_display': obj.users_group_name,
245 245 'type': 'user_group',
246 246 'url': h.route_path(
247 247 'user_group_profile', user_group_name=obj.users_group_name)
248 248 }
249 249 for obj in acl_iter]
250 250
251 251 def _get_hash_commit_list(self, auth_user, searcher, query):
252 252 org_query = query
253 253 if not query or len(query) < 3 or not searcher:
254 254 return []
255 255
256 256 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
257 257
258 258 if len(commit_hashes) != 1:
259 259 return []
260 260 commit_hash = commit_hashes[0]
261 261
262 262 result = searcher.search(
263 263 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
264 264 raise_on_exc=False)
265 265
266 266 commits = []
267 267 for entry in result['results']:
268 268 repo_data = {
269 269 'repository_id': entry.get('repository_id'),
270 270 'repository_type': entry.get('repo_type'),
271 271 'repository_name': entry.get('repository'),
272 272 }
273 273
274 274 commit_entry = {
275 275 'id': entry['commit_id'],
276 276 'value': org_query,
277 277 'value_display': '`{}` commit: {}'.format(
278 278 entry['repository'], entry['commit_id']),
279 279 'type': 'commit',
280 280 'repo': entry['repository'],
281 281 'repo_data': repo_data,
282 282
283 283 'url': h.route_path(
284 284 'repo_commit',
285 285 repo_name=entry['repository'], commit_id=entry['commit_id'])
286 286 }
287 287
288 288 commits.append(commit_entry)
289 289 return commits
290 290
291 def _get_path_list(self, auth_user, searcher, query):
292 org_query = query
293 if not query or len(query) < 3 or not searcher:
294 return []
295
296 paths_re = re.compile('(?:file:)(.{1,})').findall(query)
297 if len(paths_re) != 1:
298 return []
299 file_path = paths_re[0]
300
301 search_path = searcher.escape_specials(file_path)
302 result = searcher.search(
303 'file.raw:*{}*'.format(search_path), 'path', auth_user,
304 raise_on_exc=False)
305
306 files = []
307 for entry in result['results']:
308 repo_data = {
309 'repository_id': entry.get('repository_id'),
310 'repository_type': entry.get('repo_type'),
311 'repository_name': entry.get('repository'),
312 }
313
314 file_entry = {
315 'id': entry['commit_id'],
316 'value': org_query,
317 'value_display': '`{}` file: {}'.format(
318 entry['repository'], entry['file']),
319 'type': 'file',
320 'repo': entry['repository'],
321 'repo_data': repo_data,
322
323 'url': h.route_path(
324 'repo_files',
325 repo_name=entry['repository'], commit_id=entry['commit_id'],
326 f_path=entry['file'])
327 }
328
329 files.append(file_entry)
330 return files
331
291 332 @LoginRequired()
292 333 @view_config(
293 334 route_name='repo_list_data', request_method='GET',
294 335 renderer='json_ext', xhr=True)
295 336 def repo_list_data(self):
296 337 _ = self.request.translate
297 338 self.load_default_context()
298 339
299 340 query = self.request.GET.get('query')
300 341 repo_type = self.request.GET.get('repo_type')
301 342 log.debug('generating repo list, query:%s, repo_type:%s',
302 343 query, repo_type)
303 344
304 345 res = []
305 346 repos = self._get_repo_list(query, repo_type=repo_type)
306 347 if repos:
307 348 res.append({
308 349 'text': _('Repositories'),
309 350 'children': repos
310 351 })
311 352
312 353 data = {
313 354 'more': False,
314 355 'results': res
315 356 }
316 357 return data
317 358
318 359 @LoginRequired()
319 360 @view_config(
320 361 route_name='repo_group_list_data', request_method='GET',
321 362 renderer='json_ext', xhr=True)
322 363 def repo_group_list_data(self):
323 364 _ = self.request.translate
324 365 self.load_default_context()
325 366
326 367 query = self.request.GET.get('query')
327 368
328 369 log.debug('generating repo group list, query:%s',
329 370 query)
330 371
331 372 res = []
332 373 repo_groups = self._get_repo_group_list(query)
333 374 if repo_groups:
334 375 res.append({
335 376 'text': _('Repository Groups'),
336 377 'children': repo_groups
337 378 })
338 379
339 380 data = {
340 381 'more': False,
341 382 'results': res
342 383 }
343 384 return data
344 385
345 386 def _get_default_search_queries(self, search_context, searcher, query):
346 387 if not searcher:
347 388 return []
348 389
349 390 is_es_6 = searcher.is_es_6
350 391
351 392 queries = []
352 393 repo_group_name, repo_name, repo_context = None, None, None
353 394
354 395 # repo group context
355 396 if search_context.get('search_context[repo_group_name]'):
356 397 repo_group_name = search_context.get('search_context[repo_group_name]')
357 398 if search_context.get('search_context[repo_name]'):
358 399 repo_name = search_context.get('search_context[repo_name]')
359 400 repo_context = search_context.get('search_context[repo_view_type]')
360 401
361 402 if is_es_6 and repo_name:
362 403 # files
363 404 def query_modifier():
364 405 qry = query
365 406 return {'q': qry, 'type': 'content'}
366 407 label = u'File search for `{}` in this repository.'.format(query)
367 408 queries.append(
368 409 {
369 410 'id': -10,
370 411 'value': query,
371 412 'value_display': label,
372 413 'type': 'search',
373 414 'url': h.route_path('search_repo',
374 415 repo_name=repo_name,
375 416 _query=query_modifier())
376 417 }
377 418 )
378 419
379 420 # commits
380 421 def query_modifier():
381 422 qry = query
382 423 return {'q': qry, 'type': 'commit'}
383 424
384 425 label = u'Commit search for `{}` in this repository.'.format(query)
385 426 queries.append(
386 427 {
387 428 'id': -20,
388 429 'value': query,
389 430 'value_display': label,
390 431 'type': 'search',
391 432 'url': h.route_path('search_repo',
392 433 repo_name=repo_name,
393 434 _query=query_modifier())
394 435 }
395 436 )
396 437
397 438 elif is_es_6 and repo_group_name:
398 439 # files
399 440 def query_modifier():
400 441 qry = query
401 442 return {'q': qry, 'type': 'content'}
402 443
403 444 label = u'File search for `{}` in this repository group'.format(query)
404 445 queries.append(
405 446 {
406 447 'id': -30,
407 448 'value': query,
408 449 'value_display': label,
409 450 'type': 'search',
410 451 'url': h.route_path('search_repo_group',
411 452 repo_group_name=repo_group_name,
412 453 _query=query_modifier())
413 454 }
414 455 )
415 456
416 457 # commits
417 458 def query_modifier():
418 459 qry = query
419 460 return {'q': qry, 'type': 'commit'}
420 461
421 462 label = u'Commit search for `{}` in this repository group'.format(query)
422 463 queries.append(
423 464 {
424 465 'id': -40,
425 466 'value': query,
426 467 'value_display': label,
427 468 'type': 'search',
428 469 'url': h.route_path('search_repo_group',
429 470 repo_group_name=repo_group_name,
430 471 _query=query_modifier())
431 472 }
432 473 )
433 474
434 475 if not queries:
435 476 queries.append(
436 477 {
437 478 'id': -1,
438 479 'value': query,
439 480 'value_display': u'File search for: `{}`'.format(query),
440 481 'type': 'search',
441 482 'url': h.route_path('search',
442 483 _query={'q': query, 'type': 'content'})
443 484 })
444 485 queries.append(
445 486 {
446 487 'id': -2,
447 488 'value': query,
448 489 'value_display': u'Commit search for: `{}`'.format(query),
449 490 'type': 'search',
450 491 'url': h.route_path('search',
451 492 _query={'q': query, 'type': 'commit'})
452 493 })
453 494
454 495 return queries
455 496
456 497 @LoginRequired()
457 498 @view_config(
458 499 route_name='goto_switcher_data', request_method='GET',
459 500 renderer='json_ext', xhr=True)
460 501 def goto_switcher_data(self):
461 502 c = self.load_default_context()
462 503
463 504 _ = self.request.translate
464 505
465 506 query = self.request.GET.get('query')
466 507 log.debug('generating main filter data, query %s', query)
467 508
468 509 res = []
469 510 if not query:
470 511 return {'suggestions': res}
471 512
472 513 searcher = searcher_from_config(self.request.registry.settings)
473 514 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
474 515 res.append(_q)
475 516
476 517 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
477 518 if repo_group_id:
478 519 repo_group = RepoGroup.get(repo_group_id)
479 520 composed_hint = '{}/{}'.format(repo_group.group_name, query)
480 521 show_hint = not query.startswith(repo_group.group_name)
481 522 if repo_group and show_hint:
482 523 hint = u'Repository search inside: `{}`'.format(composed_hint)
483 524 res.append({
484 525 'id': -1,
485 526 'value': composed_hint,
486 527 'value_display': hint,
487 528 'type': 'hint',
488 529 'url': ""
489 530 })
490 531
491 532 repo_groups = self._get_repo_group_list(query)
492 533 for serialized_repo_group in repo_groups:
493 534 res.append(serialized_repo_group)
494 535
495 536 repos = self._get_repo_list(query)
496 537 for serialized_repo in repos:
497 538 res.append(serialized_repo)
498 539
499 540 # TODO(marcink): should all logged in users be allowed to search others?
500 541 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
501 542 if allowed_user_search:
502 543 users = self._get_user_list(query)
503 544 for serialized_user in users:
504 545 res.append(serialized_user)
505 546
506 547 user_groups = self._get_user_groups_list(query)
507 548 for serialized_user_group in user_groups:
508 549 res.append(serialized_user_group)
509 550
510 551 commits = self._get_hash_commit_list(c.auth_user, searcher, query)
511 552 if commits:
512 553 unique_repos = collections.OrderedDict()
513 554 for commit in commits:
514 555 repo_name = commit['repo']
515 556 unique_repos.setdefault(repo_name, []).append(commit)
516 557
517 558 for repo, commits in unique_repos.items():
518 559 for commit in commits:
519 560 res.append(commit)
520 561
562 paths = self._get_path_list(c.auth_user, searcher, query)
563 if paths:
564 unique_repos = collections.OrderedDict()
565 for path in paths:
566 repo_name = path['repo']
567 unique_repos.setdefault(repo_name, []).append(path)
568
569 for repo, paths in unique_repos.items():
570 for path in paths:
571 res.append(path)
572
521 573 return {'suggestions': res}
522 574
523 575 def _get_groups_and_repos(self, repo_group_id=None):
524 576 # repo groups groups
525 577 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
526 578 _perms = ['group.read', 'group.write', 'group.admin']
527 579 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
528 580 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
529 581 repo_group_list=repo_group_list_acl, admin=False)
530 582
531 583 # repositories
532 584 repo_list = Repository.get_all_repos(group_id=repo_group_id)
533 585 _perms = ['repository.read', 'repository.write', 'repository.admin']
534 586 repo_list_acl = RepoList(repo_list, perm_set=_perms)
535 587 repo_data = RepoModel().get_repos_as_dict(
536 588 repo_list=repo_list_acl, admin=False)
537 589
538 590 return repo_data, repo_group_data
539 591
540 592 @LoginRequired()
541 593 @view_config(
542 594 route_name='home', request_method='GET',
543 595 renderer='rhodecode:templates/index.mako')
544 596 def main_page(self):
545 597 c = self.load_default_context()
546 598 c.repo_group = None
547 599
548 600 repo_data, repo_group_data = self._get_groups_and_repos()
549 601 # json used to render the grids
550 602 c.repos_data = json.dumps(repo_data)
551 603 c.repo_groups_data = json.dumps(repo_group_data)
552 604
553 605 return self._get_template_context(c)
554 606
555 607 @LoginRequired()
556 608 @HasRepoGroupPermissionAnyDecorator(
557 609 'group.read', 'group.write', 'group.admin')
558 610 @view_config(
559 611 route_name='repo_group_home', request_method='GET',
560 612 renderer='rhodecode:templates/index_repo_group.mako')
561 613 @view_config(
562 614 route_name='repo_group_home_slash', request_method='GET',
563 615 renderer='rhodecode:templates/index_repo_group.mako')
564 616 def repo_group_main_page(self):
565 617 c = self.load_default_context()
566 618 c.repo_group = self.request.db_repo_group
567 619 repo_data, repo_group_data = self._get_groups_and_repos(
568 620 c.repo_group.group_id)
569 621
570 622 # json used to render the grids
571 623 c.repos_data = json.dumps(repo_data)
572 624 c.repo_groups_data = json.dumps(repo_group_data)
573 625
574 626 return self._get_template_context(c)
575 627
576 628 @LoginRequired()
577 629 @CSRFRequired()
578 630 @view_config(
579 631 route_name='markup_preview', request_method='POST',
580 632 renderer='string', xhr=True)
581 633 def markup_preview(self):
582 634 # Technically a CSRF token is not needed as no state changes with this
583 635 # call. However, as this is a POST is better to have it, so automated
584 636 # tools don't flag it as potential CSRF.
585 637 # Post is required because the payload could be bigger than the maximum
586 638 # allowed by GET.
587 639
588 640 text = self.request.POST.get('text')
589 641 renderer = self.request.POST.get('renderer') or 'rst'
590 642 if text:
591 643 return h.render(text, renderer=renderer, mentions=True)
592 644 return ''
593 645
594 646 @LoginRequired()
595 647 @CSRFRequired()
596 648 @view_config(
597 649 route_name='store_user_session_value', request_method='POST',
598 650 renderer='string', xhr=True)
599 651 def store_user_session_attr(self):
600 652 key = self.request.POST.get('key')
601 653 val = self.request.POST.get('val')
602 654
603 655 existing_value = self.request.session.get(key)
604 656 if existing_value != val:
605 657 self.request.session[key] = val
606 658
607 659 return 'stored:{}'.format(key)
@@ -1,103 +1,110 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Index schema for RhodeCode
23 23 """
24 24
25 25 import importlib
26 26 import logging
27 27
28 28 from rhodecode.lib.index.search_utils import normalize_text_for_matching
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32 # leave defaults for backward compat
33 33 default_searcher = 'rhodecode.lib.index.whoosh'
34 34 default_location = '%(here)s/data/index'
35 35
36 36 ES_VERSION_2 = '2'
37 37 ES_VERSION_6 = '6'
38 38 # for legacy reasons we keep 2 compat as default
39 39 DEFAULT_ES_VERSION = ES_VERSION_2
40 40
41 41 from rhodecode_tools.lib.fts_index.elasticsearch_engine_6 import \
42 42 ES_CONFIG # pragma: no cover
43 43
44 44
45 45 class BaseSearcher(object):
46 46 query_lang_doc = ''
47 47 es_version = None
48 48 name = None
49 49
50 50 def __init__(self):
51 51 pass
52 52
53 53 def cleanup(self):
54 54 pass
55 55
56 56 def search(self, query, document_type, search_user,
57 57 repo_name=None, repo_group_name=None,
58 58 raise_on_exc=True):
59 59 raise Exception('NotImplemented')
60 60
61 61 @staticmethod
62 62 def query_to_mark(query, default_field=None):
63 63 """
64 64 Formats the query to mark token for jquery.mark.js highlighting. ES could
65 65 have a different format optionally.
66 66
67 67 :param default_field:
68 68 :param query:
69 69 """
70 70 return ' '.join(normalize_text_for_matching(query).split())
71 71
72 72 @property
73 73 def is_es_6(self):
74 74 return self.es_version == ES_VERSION_6
75 75
76 76 def get_handlers(self):
77 77 return {}
78 78
79 79 @staticmethod
80 80 def extract_search_tags(query):
81 81 return []
82 82
83 @staticmethod
84 def escape_specials(val):
85 """
86 Handle and escape reserved chars for search
87 """
88 return val
89
83 90
84 91 def search_config(config, prefix='search.'):
85 92 _config = {}
86 93 for key in config.keys():
87 94 if key.startswith(prefix):
88 95 _config[key[len(prefix):]] = config[key]
89 96 return _config
90 97
91 98
92 99 def searcher_from_config(config, prefix='search.'):
93 100 _config = search_config(config, prefix)
94 101
95 102 if 'location' not in _config:
96 103 _config['location'] = default_location
97 104 if 'es_version' not in _config:
98 105 # use old legacy ES version set to 2
99 106 _config['es_version'] = '2'
100 107
101 108 imported = importlib.import_module(_config.get('module', default_searcher))
102 109 searcher = imported.Searcher(config=_config)
103 110 return searcher
@@ -1,818 +1,830 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <%include file="/ejs_templates/templates.html"/>
5 5
6 6 <div class="outerwrapper">
7 7 <!-- HEADER -->
8 8 <div class="header">
9 9 <div id="header-inner" class="wrapper">
10 10 <div id="logo">
11 11 <div class="logo-wrapper">
12 12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 13 </div>
14 14 %if c.rhodecode_name:
15 15 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
16 16 %endif
17 17 </div>
18 18 <!-- MENU BAR NAV -->
19 19 ${self.menu_bar_nav()}
20 20 <!-- END MENU BAR NAV -->
21 21 </div>
22 22 </div>
23 23 ${self.menu_bar_subnav()}
24 24 <!-- END HEADER -->
25 25
26 26 <!-- CONTENT -->
27 27 <div id="content" class="wrapper">
28 28
29 29 <rhodecode-toast id="notifications"></rhodecode-toast>
30 30
31 31 <div class="main">
32 32 ${next.main()}
33 33 </div>
34 34 </div>
35 35 <!-- END CONTENT -->
36 36
37 37 </div>
38 38 <!-- FOOTER -->
39 39 <div id="footer">
40 40 <div id="footer-inner" class="title wrapper">
41 41 <div>
42 42 <p class="footer-link-right">
43 43 % if c.visual.show_version:
44 44 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
45 45 % endif
46 46 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
47 47 % if c.visual.rhodecode_support_url:
48 48 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
49 49 % endif
50 50 </p>
51 51 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
52 52 <p class="server-instance" style="display:${sid}">
53 53 ## display hidden instance ID if specially defined
54 54 % if c.rhodecode_instanceid:
55 55 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
56 56 % endif
57 57 </p>
58 58 </div>
59 59 </div>
60 60 </div>
61 61
62 62 <!-- END FOOTER -->
63 63
64 64 ### MAKO DEFS ###
65 65
66 66 <%def name="menu_bar_subnav()">
67 67 </%def>
68 68
69 69 <%def name="breadcrumbs(class_='breadcrumbs')">
70 70 <div class="${class_}">
71 71 ${self.breadcrumbs_links()}
72 72 </div>
73 73 </%def>
74 74
75 75 <%def name="admin_menu()">
76 76 <ul class="admin_menu submenu">
77 77 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
78 78 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
79 79 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
80 80 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
81 81 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
82 82 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
83 83 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
84 84 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
85 85 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 86 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
87 87 </ul>
88 88 </%def>
89 89
90 90
91 91 <%def name="dt_info_panel(elements)">
92 92 <dl class="dl-horizontal">
93 93 %for dt, dd, title, show_items in elements:
94 94 <dt>${dt}:</dt>
95 95 <dd title="${h.tooltip(title)}">
96 96 %if callable(dd):
97 97 ## allow lazy evaluation of elements
98 98 ${dd()}
99 99 %else:
100 100 ${dd}
101 101 %endif
102 102 %if show_items:
103 103 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
104 104 %endif
105 105 </dd>
106 106
107 107 %if show_items:
108 108 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
109 109 %for item in show_items:
110 110 <dt></dt>
111 111 <dd>${item}</dd>
112 112 %endfor
113 113 </div>
114 114 %endif
115 115
116 116 %endfor
117 117 </dl>
118 118 </%def>
119 119
120 120
121 121 <%def name="gravatar(email, size=16)">
122 122 <%
123 123 if (size > 16):
124 124 gravatar_class = 'gravatar gravatar-large'
125 125 else:
126 126 gravatar_class = 'gravatar'
127 127 %>
128 128 <%doc>
129 129 TODO: johbo: For now we serve double size images to make it smooth
130 130 for retina. This is how it worked until now. Should be replaced
131 131 with a better solution at some point.
132 132 </%doc>
133 133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
134 134 </%def>
135 135
136 136
137 137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
138 138 <% email = h.email_or_none(contact) %>
139 139 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
140 140 ${self.gravatar(email, size)}
141 141 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
142 142 </div>
143 143 </%def>
144 144
145 145
146 146 ## admin menu used for people that have some admin resources
147 147 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
148 148 <ul class="submenu">
149 149 %if repositories:
150 150 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
151 151 %endif
152 152 %if repository_groups:
153 153 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
154 154 %endif
155 155 %if user_groups:
156 156 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
157 157 %endif
158 158 </ul>
159 159 </%def>
160 160
161 161 <%def name="repo_page_title(repo_instance)">
162 162 <div class="title-content">
163 163 <div class="title-main">
164 164 ## SVN/HG/GIT icons
165 165 %if h.is_hg(repo_instance):
166 166 <i class="icon-hg"></i>
167 167 %endif
168 168 %if h.is_git(repo_instance):
169 169 <i class="icon-git"></i>
170 170 %endif
171 171 %if h.is_svn(repo_instance):
172 172 <i class="icon-svn"></i>
173 173 %endif
174 174
175 175 ## public/private
176 176 %if repo_instance.private:
177 177 <i class="icon-repo-private"></i>
178 178 %else:
179 179 <i class="icon-repo-public"></i>
180 180 %endif
181 181
182 182 ## repo name with group name
183 183 ${h.breadcrumb_repo_link(repo_instance)}
184 184
185 185 </div>
186 186
187 187 ## FORKED
188 188 %if repo_instance.fork:
189 189 <p>
190 190 <i class="icon-code-fork"></i> ${_('Fork of')}
191 191 ${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))}
192 192 </p>
193 193 %endif
194 194
195 195 ## IMPORTED FROM REMOTE
196 196 %if repo_instance.clone_uri:
197 197 <p>
198 198 <i class="icon-code-fork"></i> ${_('Clone from')}
199 199 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
200 200 </p>
201 201 %endif
202 202
203 203 ## LOCKING STATUS
204 204 %if repo_instance.locked[0]:
205 205 <p class="locking_locked">
206 206 <i class="icon-repo-lock"></i>
207 207 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
208 208 </p>
209 209 %elif repo_instance.enable_locking:
210 210 <p class="locking_unlocked">
211 211 <i class="icon-repo-unlock"></i>
212 212 ${_('Repository not locked. Pull repository to lock it.')}
213 213 </p>
214 214 %endif
215 215
216 216 </div>
217 217 </%def>
218 218
219 219 <%def name="repo_menu(active=None)">
220 220 <%
221 221 def is_active(selected):
222 222 if selected == active:
223 223 return "active"
224 224 %>
225 225
226 226 <!--- CONTEXT BAR -->
227 227 <div id="context-bar">
228 228 <div class="wrapper">
229 229 <ul id="context-pages" class="navigation horizontal-list">
230 230 <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>
231 231 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
232 232 <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>
233 233 <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>
234 234 <li class="${is_active('search')}"><a class="menulink" href="${h.route_path('search_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Search')}</div></a></li>
235 235
236 236 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
237 237 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
238 238 <li class="${is_active('showpullrequest')}">
239 239 <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)}">
240 240 %if c.repository_pull_requests:
241 241 <span class="pr_notifications">${c.repository_pull_requests}</span>
242 242 %endif
243 243 <div class="menulabel">${_('Pull Requests')}</div>
244 244 </a>
245 245 </li>
246 246 %endif
247 247
248 248 <li class="${is_active('options')}">
249 249 <a class="menulink dropdown">
250 250 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
251 251 </a>
252 252 <ul class="submenu">
253 253 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
254 254 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Repository Settings')}</a></li>
255 255 %endif
256 256 %if c.rhodecode_db_repo.fork:
257 257 <li>
258 258 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
259 259 href="${h.route_path('repo_compare',
260 260 repo_name=c.rhodecode_db_repo.fork.repo_name,
261 261 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
262 262 source_ref=c.rhodecode_db_repo.landing_rev[1],
263 263 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
264 264 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
265 265 _query=dict(merge=1))}"
266 266 >
267 267 ${_('Compare fork')}
268 268 </a>
269 269 </li>
270 270 %endif
271 271
272 272 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
273 273 %if c.rhodecode_db_repo.locked[0]:
274 274 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
275 275 %else:
276 276 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
277 277 %endif
278 278 %endif
279 279 %if c.rhodecode_user.username != h.DEFAULT_USER:
280 280 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
281 281 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
282 282 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
283 283 %endif
284 284 %endif
285 285 </ul>
286 286 </li>
287 287 </ul>
288 288 </div>
289 289 <div class="clear"></div>
290 290 </div>
291 291 % if c.rhodecode_db_repo.archived:
292 292 <div class="alert alert-warning text-center">
293 293 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
294 294 </div>
295 295 % endif
296 296 <!--- END CONTEXT BAR -->
297 297
298 298 </%def>
299 299
300 300 <%def name="repo_group_page_title(repo_group_instance)">
301 301 <div class="title-content">
302 302 <div class="title-main">
303 303 ## Repository Group icon
304 304 <i class="icon-folder-close"></i>
305 305
306 306 ## repo name with group name
307 307 ${h.breadcrumb_repo_group_link(repo_group_instance)}
308 308 </div>
309 309
310 310 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
311 311 <div class="repo-group-desc">
312 312 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
313 313 </div>
314 314
315 315 </div>
316 316 </%def>
317 317
318 318 <%def name="repo_group_menu(active=None)">
319 319 <%
320 320 def is_active(selected):
321 321 if selected == active:
322 322 return "active"
323 323
324 324 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
325 325
326 326 gr_name = c.repo_group.group_name if c.repo_group else None
327 327 # create repositories with write permission on group is set to true
328 328 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
329 329 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
330 330 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
331 331
332 332 %>
333 333
334 334 <!--- CONTEXT BAR -->
335 335 <div id="context-bar">
336 336 <div class="wrapper">
337 337 <ul id="context-pages" class="navigation horizontal-list">
338 338 <li class="${is_active('home')}"><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></li>
339 339 <li class="${is_active('search')}"><a class="menulink" href="${h.route_path('search_repo_group', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Search')}</div></a></li>
340 340
341 341 <li class="${is_active('options')}">
342 342 <a class="menulink dropdown">
343 343 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
344 344 </a>
345 345 <ul class="submenu">
346 346 %if is_admin or group_admin:
347 347 <li><a 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')}">${_('Group Settings')}</a></li>
348 348 %endif
349 349 %if is_admin or group_admin or (group_write and create_on_write):
350 350 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
351 351 %endif
352 352 %if is_admin or group_admin:
353 353 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
354 354 %endif
355 355 </ul>
356 356 </li>
357 357 </ul>
358 358 </div>
359 359 <div class="clear"></div>
360 360 </div>
361 361
362 362 <!--- END CONTEXT BAR -->
363 363
364 364 </%def>
365 365
366 366
367 367 <%def name="usermenu(active=False)">
368 368 ## USER MENU
369 369 <li id="quick_login_li" class="${'active' if active else ''}">
370 370 % if c.rhodecode_user.username == h.DEFAULT_USER:
371 371 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
372 372 ${gravatar(c.rhodecode_user.email, 20)}
373 373 <span class="user">
374 374 <span>${_('Sign in')}</span>
375 375 </span>
376 376 </a>
377 377 % else:
378 378 ## logged in user
379 379 <a id="quick_login_link" class="menulink childs">
380 380 ${gravatar(c.rhodecode_user.email, 20)}
381 381 <span class="user">
382 382 <span class="menu_link_user">${c.rhodecode_user.username}</span>
383 383 <div class="show_more"></div>
384 384 </span>
385 385 </a>
386 386 ## subnav with menu for logged in user
387 387 <div class="user-menu submenu">
388 388 <div id="quick_login">
389 389 %if c.rhodecode_user.username != h.DEFAULT_USER:
390 390 <div class="">
391 391 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
392 392 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
393 393 <div class="email">${c.rhodecode_user.email}</div>
394 394 </div>
395 395 <div class="">
396 396 <ol class="links">
397 397 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
398 398 % if c.rhodecode_user.personal_repo_group:
399 399 <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>
400 400 % endif
401 401 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
402 402 ## bookmark-items
403 403 <li class="bookmark-items">
404 404 ${_('Bookmarks')}
405 405 <div class="pull-right">
406 406 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
407 407 </div>
408 408 </li>
409 409 % if not c.bookmark_items:
410 410 <li>
411 411 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
412 412 </li>
413 413 % endif
414 414 % for item in c.bookmark_items:
415 415 <li>
416 416 % if item.repository:
417 417 <div>
418 418 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
419 419 <code>${item.position}</code>
420 420 % if item.repository.repo_type == 'hg':
421 421 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
422 422 % elif item.repository.repo_type == 'git':
423 423 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
424 424 % elif item.repository.repo_type == 'svn':
425 425 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
426 426 % endif
427 427 ${(item.title or h.shorter(item.repository.repo_name, 30))}
428 428 </a>
429 429 </div>
430 430 % elif item.repository_group:
431 431 <div>
432 432 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
433 433 <code>${item.position}</code>
434 434 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
435 435 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
436 436 </a>
437 437 </div>
438 438 % else:
439 439 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
440 440 <code>${item.position}</code>
441 441 ${item.title}
442 442 </a>
443 443 % endif
444 444 </li>
445 445 % endfor
446 446
447 447 <li class="logout">
448 448 ${h.secure_form(h.route_path('logout'), request=request)}
449 449 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
450 450 ${h.end_form()}
451 451 </li>
452 452 </ol>
453 453 </div>
454 454 %endif
455 455 </div>
456 456 </div>
457 457 ## unread counter
458 458 <div class="pill_container">
459 459 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
460 460 </div>
461 461 % endif
462 462 </li>
463 463 </%def>
464 464
465 465 <%def name="menu_items(active=None)">
466 466 <%
467 467 def is_active(selected):
468 468 if selected == active:
469 469 return "active"
470 470 return ""
471 471 %>
472 472
473 473 <ul id="quick" class="main_nav navigation horizontal-list">
474 474 ## notice box for important system messages
475 475 <li style="display: none">
476 476 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
477 477 <div class="menulabel-notice" >
478 478 0
479 479 </div>
480 480 </a>
481 481 </li>
482 482
483 483 ## Main filter
484 484 <li>
485 485 <div class="menulabel main_filter_box">
486 486 <div class="main_filter_input_box">
487 487 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/>
488 488 </div>
489 489 <div class="main_filter_help_box">
490 490 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
491 491 </div>
492 492 </div>
493 493
494 494 <div id="main_filter_help" style="display: none">
495 495 - Use '/' key to quickly access this field.
496 496
497 497 - Enter a name of repository, or repository group for quick search.
498 498
499 499 - Prefix query to allow special search:
500 500
501 501 user:admin, to search for usernames
502 502
503 503 user_group:devops, to search for user groups
504 504
505 505 commit:efced4, to search for commits
506
507 file:models.py, to search for file paths
506 508 </div>
507 509 </li>
508 510
509 511 ## ROOT MENU
510 512 <li class="${is_active('home')}">
511 513 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
512 514 <div class="menulabel">${_('Home')}</div>
513 515 </a>
514 516 </li>
515 517
516 518 %if c.rhodecode_user.username != h.DEFAULT_USER:
517 519 <li class="${is_active('journal')}">
518 520 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
519 521 <div class="menulabel">${_('Journal')}</div>
520 522 </a>
521 523 </li>
522 524 %else:
523 525 <li class="${is_active('journal')}">
524 526 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
525 527 <div class="menulabel">${_('Public journal')}</div>
526 528 </a>
527 529 </li>
528 530 %endif
529 531
530 532 <li class="${is_active('gists')}">
531 533 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
532 534 <div class="menulabel">${_('Gists')}</div>
533 535 </a>
534 536 </li>
535 537
536 538 % if h.HasPermissionAll('hg.admin')('access admin main page'):
537 539 <li class="${is_active('admin')}">
538 540 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
539 541 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
540 542 </a>
541 543 ${admin_menu()}
542 544 </li>
543 545 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
544 546 <li class="${is_active('admin')}">
545 547 <a class="menulink childs" title="${_('Delegated Admin settings')}">
546 548 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
547 549 </a>
548 550 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
549 551 c.rhodecode_user.repository_groups_admin,
550 552 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
551 553 </li>
552 554 % endif
553 555 ## render extra user menu
554 556 ${usermenu(active=(active=='my_account'))}
555 557
556 558 % if c.debug_style:
557 559 <li>
558 560 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
559 561 <div class="menulabel">${_('[Style]')}</div>
560 562 </a>
561 563 </li>
562 564 % endif
563 565 </ul>
564 566
565 567 <script type="text/javascript">
566 568 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
567 569
568 570 var formatRepoResult = function(result, container, query, escapeMarkup) {
569 571 return function(data, escapeMarkup) {
570 572 if (!data.repo_id){
571 573 return data.text; // optgroup text Repositories
572 574 }
573 575
574 576 var tmpl = '';
575 577 var repoType = data['repo_type'];
576 578 var repoName = data['text'];
577 579
578 580 if(data && data.type == 'repo'){
579 581 if(repoType === 'hg'){
580 582 tmpl += '<i class="icon-hg"></i> ';
581 583 }
582 584 else if(repoType === 'git'){
583 585 tmpl += '<i class="icon-git"></i> ';
584 586 }
585 587 else if(repoType === 'svn'){
586 588 tmpl += '<i class="icon-svn"></i> ';
587 589 }
588 590 if(data['private']){
589 591 tmpl += '<i class="icon-lock" ></i> ';
590 592 }
591 593 else if(visualShowPublicIcon){
592 594 tmpl += '<i class="icon-unlock-alt"></i> ';
593 595 }
594 596 }
595 597 tmpl += escapeMarkup(repoName);
596 598 return tmpl;
597 599
598 600 }(result, escapeMarkup);
599 601 };
600 602
601 603 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
602 604 return function(data, escapeMarkup) {
603 605 if (!data.repo_group_id){
604 606 return data.text; // optgroup text Repositories
605 607 }
606 608
607 609 var tmpl = '';
608 610 var repoGroupName = data['text'];
609 611
610 612 if(data){
611 613
612 614 tmpl += '<i class="icon-folder-close"></i> ';
613 615
614 616 }
615 617 tmpl += escapeMarkup(repoGroupName);
616 618 return tmpl;
617 619
618 620 }(result, escapeMarkup);
619 621 };
620 622
621 623
622 624 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
623 625
624 626 if (value.split(':').length === 2) {
625 627 value = value.split(':')[1]
626 628 }
627 629
628 630 var searchType = data['type'];
629 631 var valueDisplay = data['value_display'];
630 632
631 633 var escapeRegExChars = function (value) {
632 634 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
633 635 };
634 636 var pattern = '(' + escapeRegExChars(value) + ')';
635 637
636 638 var getRepoIcon = function(repo_type) {
637 639 if (repo_type === 'hg') {
638 640 return '<i class="icon-hg"></i> ';
639 641 }
640 642 else if (repo_type === 'git') {
641 643 return '<i class="icon-git"></i> ';
642 644 }
643 645 else if (repo_type === 'svn') {
644 646 return '<i class="icon-svn"></i> ';
645 647 }
646 648 return ''
647 649 };
648 650
649 651 // highlight match
650 652 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
651 653 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
652 654
653 655 var icon = '';
654 656
655 657 if (searchType === 'hint') {
656 658 icon += '<i class="icon-folder-close"></i> ';
657 659 }
658 660 // full text search
659 661 else if (searchType === 'search') {
660 662 icon += '<i class="icon-more"></i> ';
661 663 }
662 664 // repository
663 665 else if (searchType === 'repo') {
664 666
665 667 var repoIcon = getRepoIcon(data['repo_type']);
666 668 icon += repoIcon;
667 669
668 670 if (data['private']) {
669 671 icon += '<i class="icon-lock" ></i> ';
670 672 }
671 673 else if (visualShowPublicIcon) {
672 674 icon += '<i class="icon-unlock-alt"></i> ';
673 675 }
674 676 }
675 677 // repository groups
676 678 else if (searchType === 'repo_group') {
677 679 icon += '<i class="icon-folder-close"></i> ';
678 680 }
679 681 // user group
680 682 else if (searchType === 'user_group') {
681 683 icon += '<i class="icon-group"></i> ';
682 684 }
683 685 else if (searchType === 'user') {
684 686 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
685 687 }
686 688 // commit
687 689 else if (searchType === 'commit') {
688 690 var repo_data = data['repo_data'];
689 691 var repoIcon = getRepoIcon(repo_data['repository_type']);
690 692 if (repoIcon) {
691 693 icon += repoIcon;
692 694 } else {
693 695 icon += '<i class="icon-tag"></i>';
694 696 }
695 697 }
698 // file
699 else if (searchType === 'file') {
700 var repo_data = data['repo_data'];
701 var repoIcon = getRepoIcon(repo_data['repository_type']);
702 if (repoIcon) {
703 icon += repoIcon;
704 } else {
705 icon += '<i class="icon-tag"></i>';
706 }
707 }
696 708
697 709 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
698 710 return tmpl.format(icon, valueDisplay);
699 711 };
700 712
701 713 var handleSelect = function(element, suggestion) {
702 714 if (suggestion.type === "hint") {
703 715 // we skip action
704 716 $('#main_filter').focus();
705 717 } else {
706 718 window.location = suggestion['url'];
707 719 }
708 720 };
709 721 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
710 722 if (queryLowerCase.split(':').length === 2) {
711 723 queryLowerCase = queryLowerCase.split(':')[1]
712 724 }
713 725 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
714 726 };
715 727
716 728 $('#main_filter').autocomplete({
717 729 serviceUrl: pyroutes.url('goto_switcher_data'),
718 730 params: {"search_context": templateContext.search_context},
719 731 minChars:2,
720 732 maxHeight:400,
721 733 deferRequestBy: 300, //miliseconds
722 734 tabDisabled: true,
723 735 autoSelectFirst: true,
724 736 formatResult: autocompleteMainFilterFormatResult,
725 737 lookupFilter: autocompleteMainFilterResult,
726 738 onSelect: function (element, suggestion) {
727 739 handleSelect(element, suggestion);
728 740 return false;
729 741 },
730 742 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
731 743 if (jqXHR !== 'abort') {
732 744 alert("Error during search.\nError code: {0}".format(textStatus));
733 745 window.location = '';
734 746 }
735 747 }
736 748 });
737 749
738 750 showMainFilterBox = function () {
739 751 $('#main_filter_help').toggle();
740 752 };
741 753
742 754 </script>
743 755 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
744 756 </%def>
745 757
746 758 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
747 759 <div class="modal-dialog">
748 760 <div class="modal-content">
749 761 <div class="modal-header">
750 762 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
751 763 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
752 764 </div>
753 765 <div class="modal-body">
754 766 <div class="block-left">
755 767 <table class="keyboard-mappings">
756 768 <tbody>
757 769 <tr>
758 770 <th></th>
759 771 <th>${_('Site-wide shortcuts')}</th>
760 772 </tr>
761 773 <%
762 774 elems = [
763 775 ('/', 'Use quick search box'),
764 776 ('g h', 'Goto home page'),
765 777 ('g g', 'Goto my private gists page'),
766 778 ('g G', 'Goto my public gists page'),
767 779 ('g 0-9', 'Goto bookmarked items from 0-9'),
768 780 ('n r', 'New repository page'),
769 781 ('n g', 'New gist page'),
770 782 ]
771 783 %>
772 784 %for key, desc in elems:
773 785 <tr>
774 786 <td class="keys">
775 787 <span class="key tag">${key}</span>
776 788 </td>
777 789 <td>${desc}</td>
778 790 </tr>
779 791 %endfor
780 792 </tbody>
781 793 </table>
782 794 </div>
783 795 <div class="block-left">
784 796 <table class="keyboard-mappings">
785 797 <tbody>
786 798 <tr>
787 799 <th></th>
788 800 <th>${_('Repositories')}</th>
789 801 </tr>
790 802 <%
791 803 elems = [
792 804 ('g s', 'Goto summary page'),
793 805 ('g c', 'Goto changelog page'),
794 806 ('g f', 'Goto files page'),
795 807 ('g F', 'Goto files page with file search activated'),
796 808 ('g p', 'Goto pull requests page'),
797 809 ('g o', 'Goto repository settings'),
798 810 ('g O', 'Goto repository permissions settings'),
799 811 ]
800 812 %>
801 813 %for key, desc in elems:
802 814 <tr>
803 815 <td class="keys">
804 816 <span class="key tag">${key}</span>
805 817 </td>
806 818 <td>${desc}</td>
807 819 </tr>
808 820 %endfor
809 821 </tbody>
810 822 </table>
811 823 </div>
812 824 </div>
813 825 <div class="modal-footer">
814 826 </div>
815 827 </div><!-- /.modal-content -->
816 828 </div><!-- /.modal-dialog -->
817 829 </div><!-- /.modal -->
818 830
General Comments 0
You need to be logged in to leave comments. Login now