Show More
@@ -1,40 +1,62 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2018 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 | def assert_and_get_content(result): | |
|
22 | def assert_and_get_main_filter_content(result): | |
|
23 | 23 | repos = [] |
|
24 | 24 | groups = [] |
|
25 | 25 | commits = [] |
|
26 | users = [] | |
|
27 | for data_item in result: | |
|
28 | assert data_item['id'] | |
|
29 | assert data_item['value'] | |
|
30 | assert data_item['value_display'] | |
|
31 | assert data_item['url'] | |
|
32 | ||
|
33 | if data_item['type'] == 'search': | |
|
34 | assert data_item['value_display'].startswith('Full text search for:') | |
|
35 | elif data_item['type'] == 'repo': | |
|
36 | repos.append(data_item) | |
|
37 | elif data_item['type'] == 'repo_group': | |
|
38 | groups.append(data_item) | |
|
39 | elif data_item['type'] == 'user': | |
|
40 | users.append(data_item) | |
|
41 | elif data_item['type'] == 'commit': | |
|
42 | commits.append(data_item) | |
|
43 | else: | |
|
44 | raise Exception('invalid type `%s`' % data_item['type']) | |
|
45 | ||
|
46 | return repos, groups, users, commits | |
|
47 | ||
|
48 | ||
|
49 | def assert_and_get_repo_list_content(result): | |
|
50 | repos = [] | |
|
26 | 51 | for data in result: |
|
27 | 52 | for data_item in data['children']: |
|
28 | 53 | assert data_item['id'] |
|
29 | 54 | assert data_item['text'] |
|
30 | 55 | assert data_item['url'] |
|
56 | ||
|
31 | 57 | if data_item['type'] == 'repo': |
|
32 | 58 | repos.append(data_item) |
|
33 | elif data_item['type'] == 'group': | |
|
34 | groups.append(data_item) | |
|
35 | elif data_item['type'] == 'commit': | |
|
36 | commits.append(data_item) | |
|
37 | 59 | else: |
|
38 | 60 | raise Exception('invalid type %s' % data_item['type']) |
|
39 | 61 | |
|
40 |
return repos |
|
|
62 | return repos |
@@ -1,151 +1,180 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2018 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 json |
|
22 | 22 | |
|
23 | 23 | import pytest |
|
24 | 24 | |
|
25 | from . import assert_and_get_content | |
|
25 | from . import assert_and_get_main_filter_content | |
|
26 | 26 | from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN |
|
27 | 27 | from rhodecode.tests.fixture import Fixture |
|
28 | 28 | |
|
29 | 29 | from rhodecode.lib.utils import map_groups |
|
30 | 30 | from rhodecode.model.repo import RepoModel |
|
31 | 31 | from rhodecode.model.repo_group import RepoGroupModel |
|
32 | 32 | from rhodecode.model.db import Session, Repository, RepoGroup |
|
33 | 33 | |
|
34 | 34 | fixture = Fixture() |
|
35 | 35 | |
|
36 | 36 | |
|
37 | 37 | def route_path(name, params=None, **kwargs): |
|
38 | 38 | import urllib |
|
39 | 39 | |
|
40 | 40 | base_url = { |
|
41 | 41 | 'goto_switcher_data': '/_goto_data', |
|
42 | 42 | }[name].format(**kwargs) |
|
43 | 43 | |
|
44 | 44 | if params: |
|
45 | 45 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) |
|
46 | 46 | return base_url |
|
47 | 47 | |
|
48 | 48 | |
|
49 | 49 | class TestGotoSwitcherData(TestController): |
|
50 | 50 | |
|
51 | 51 | required_repos_with_groups = [ |
|
52 | 52 | 'abc', |
|
53 | 53 | 'abc-fork', |
|
54 | 54 | 'forks/abcd', |
|
55 | 55 | 'abcd', |
|
56 | 56 | 'abcde', |
|
57 | 57 | 'a/abc', |
|
58 | 58 | 'aa/abc', |
|
59 | 59 | 'aaa/abc', |
|
60 | 60 | 'aaaa/abc', |
|
61 | 61 | 'repos_abc/aaa/abc', |
|
62 | 62 | 'abc_repos/abc', |
|
63 | 63 | 'abc_repos/abcd', |
|
64 | 64 | 'xxx/xyz', |
|
65 | 65 | 'forked-abc/a/abc' |
|
66 | 66 | ] |
|
67 | 67 | |
|
68 | 68 | @pytest.fixture(autouse=True, scope='class') |
|
69 | 69 | def prepare(self, request, baseapp): |
|
70 | 70 | for repo_and_group in self.required_repos_with_groups: |
|
71 | 71 | # create structure of groups and return the last group |
|
72 | 72 | |
|
73 | 73 | repo_group = map_groups(repo_and_group) |
|
74 | 74 | |
|
75 | 75 | RepoModel()._create_repo( |
|
76 | 76 | repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN, |
|
77 | 77 | repo_group=getattr(repo_group, 'group_id', None)) |
|
78 | 78 | |
|
79 | 79 | Session().commit() |
|
80 | 80 | |
|
81 | 81 | request.addfinalizer(self.cleanup) |
|
82 | 82 | |
|
83 | 83 | def cleanup(self): |
|
84 | 84 | # first delete all repos |
|
85 | 85 | for repo_and_groups in self.required_repos_with_groups: |
|
86 | 86 | repo = Repository.get_by_repo_name(repo_and_groups) |
|
87 | 87 | if repo: |
|
88 | 88 | RepoModel().delete(repo) |
|
89 | 89 | Session().commit() |
|
90 | 90 | |
|
91 | 91 | # then delete all empty groups |
|
92 | 92 | for repo_and_groups in self.required_repos_with_groups: |
|
93 | 93 | if '/' in repo_and_groups: |
|
94 | 94 | r_group = repo_and_groups.rsplit('/', 1)[0] |
|
95 | 95 | repo_group = RepoGroup.get_by_group_name(r_group) |
|
96 | 96 | if not repo_group: |
|
97 | 97 | continue |
|
98 | 98 | parents = repo_group.parents |
|
99 | 99 | RepoGroupModel().delete(repo_group, force_delete=True) |
|
100 | 100 | Session().commit() |
|
101 | 101 | |
|
102 | 102 | for el in reversed(parents): |
|
103 | 103 | RepoGroupModel().delete(el, force_delete=True) |
|
104 | 104 | Session().commit() |
|
105 | 105 | |
|
106 |
def test_ |
|
|
106 | def test_empty_query(self, xhr_header): | |
|
107 | 107 | self.log_user() |
|
108 | 108 | |
|
109 | 109 | response = self.app.get( |
|
110 | 110 | route_path('goto_switcher_data'), |
|
111 | 111 | extra_environ=xhr_header, status=200) |
|
112 |
result = json.loads(response.body)[' |
|
|
113 | ||
|
114 | repos, groups, commits = assert_and_get_content(result) | |
|
112 | result = json.loads(response.body)['suggestions'] | |
|
115 | 113 | |
|
116 | assert len(repos) == len(Repository.get_all()) | |
|
117 | assert len(groups) == len(RepoGroup.get_all()) | |
|
118 | assert len(commits) == 0 | |
|
114 | assert result == [] | |
|
119 | 115 | |
|
120 | 116 | def test_returns_list_of_repos_and_groups_filtered(self, xhr_header): |
|
121 | 117 | self.log_user() |
|
122 | 118 | |
|
123 | 119 | response = self.app.get( |
|
124 | 120 | route_path('goto_switcher_data'), |
|
125 | 121 | params={'query': 'abc'}, |
|
126 | 122 | extra_environ=xhr_header, status=200) |
|
127 |
result = json.loads(response.body)[' |
|
|
123 | result = json.loads(response.body)['suggestions'] | |
|
128 | 124 | |
|
129 | repos, groups, commits = assert_and_get_content(result) | |
|
125 | repos, groups, users, commits = assert_and_get_main_filter_content(result) | |
|
130 | 126 | |
|
131 | 127 | assert len(repos) == 13 |
|
132 | 128 | assert len(groups) == 5 |
|
129 | assert len(users) == 0 | |
|
133 | 130 | assert len(commits) == 0 |
|
134 | 131 | |
|
132 | def test_returns_list_of_users_filtered(self, xhr_header): | |
|
133 | self.log_user() | |
|
134 | ||
|
135 | response = self.app.get( | |
|
136 | route_path('goto_switcher_data'), | |
|
137 | params={'query': 'user:admin'}, | |
|
138 | extra_environ=xhr_header, status=200) | |
|
139 | result = json.loads(response.body)['suggestions'] | |
|
140 | ||
|
141 | repos, groups, users, commits = assert_and_get_main_filter_content(result) | |
|
142 | ||
|
143 | assert len(repos) == 0 | |
|
144 | assert len(groups) == 0 | |
|
145 | assert len(users) == 1 | |
|
146 | assert len(commits) == 0 | |
|
147 | ||
|
148 | def test_returns_list_of_commits_filtered(self, xhr_header): | |
|
149 | self.log_user() | |
|
150 | ||
|
151 | response = self.app.get( | |
|
152 | route_path('goto_switcher_data'), | |
|
153 | params={'query': 'commit:e8'}, | |
|
154 | extra_environ=xhr_header, status=200) | |
|
155 | result = json.loads(response.body)['suggestions'] | |
|
156 | ||
|
157 | repos, groups, users, commits = assert_and_get_main_filter_content(result) | |
|
158 | ||
|
159 | assert len(repos) == 0 | |
|
160 | assert len(groups) == 0 | |
|
161 | assert len(users) == 0 | |
|
162 | assert len(commits) == 5 | |
|
163 | ||
|
135 | 164 | def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header): |
|
136 | 165 | self.log_user() |
|
137 | 166 | |
|
138 | 167 | response = self.app.get( |
|
139 | 168 | route_path('goto_switcher_data'), |
|
140 | 169 | params={'query': 'abc'}, |
|
141 | 170 | extra_environ=xhr_header, status=200) |
|
142 |
result = json.loads(response.body)[' |
|
|
171 | result = json.loads(response.body)['suggestions'] | |
|
143 | 172 | |
|
144 | repos, groups, commits = assert_and_get_content(result) | |
|
173 | repos, groups, users, commits = assert_and_get_main_filter_content(result) | |
|
145 | 174 | |
|
146 |
test_repos = [x[' |
|
|
175 | test_repos = [x['value_display'] for x in repos[:4]] | |
|
147 | 176 | assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos |
|
148 | 177 | |
|
149 |
test_groups = [x[' |
|
|
178 | test_groups = [x['value_display'] for x in groups[:4]] | |
|
150 | 179 | assert ['abc_repos', 'repos_abc', |
|
151 | 180 | 'forked-abc', 'forked-abc/a'] == test_groups |
@@ -1,103 +1,95 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2018 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 json |
|
22 | 22 | |
|
23 | from . import assert_and_get_content | |
|
23 | from . import assert_and_get_repo_list_content | |
|
24 | 24 | from rhodecode.tests import TestController |
|
25 | 25 | from rhodecode.tests.fixture import Fixture |
|
26 | 26 | from rhodecode.model.db import Repository |
|
27 | 27 | |
|
28 | 28 | fixture = Fixture() |
|
29 | 29 | |
|
30 | 30 | |
|
31 | 31 | def route_path(name, params=None, **kwargs): |
|
32 | 32 | import urllib |
|
33 | 33 | |
|
34 | 34 | base_url = { |
|
35 | 35 | 'repo_list_data': '/_repos', |
|
36 | 36 | }[name].format(**kwargs) |
|
37 | 37 | |
|
38 | 38 | if params: |
|
39 | 39 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) |
|
40 | 40 | return base_url |
|
41 | 41 | |
|
42 | 42 | |
|
43 | 43 | class TestRepoListData(TestController): |
|
44 | 44 | |
|
45 | 45 | def test_returns_list_of_repos_and_groups(self, xhr_header): |
|
46 | 46 | self.log_user() |
|
47 | 47 | |
|
48 | 48 | response = self.app.get( |
|
49 | 49 | route_path('repo_list_data'), |
|
50 | 50 | extra_environ=xhr_header, status=200) |
|
51 | 51 | result = json.loads(response.body)['results'] |
|
52 | 52 | |
|
53 |
repos |
|
|
53 | repos = assert_and_get_repo_list_content(result) | |
|
54 | 54 | |
|
55 | 55 | assert len(repos) == len(Repository.get_all()) |
|
56 | assert len(groups) == 0 | |
|
57 | assert len(commits) == 0 | |
|
58 | 56 | |
|
59 | 57 | def test_returns_list_of_repos_and_groups_filtered(self, xhr_header): |
|
60 | 58 | self.log_user() |
|
61 | 59 | |
|
62 | 60 | response = self.app.get( |
|
63 | 61 | route_path('repo_list_data'), |
|
64 | 62 | params={'query': 'vcs_test_git'}, |
|
65 | 63 | extra_environ=xhr_header, status=200) |
|
66 | 64 | result = json.loads(response.body)['results'] |
|
67 | 65 | |
|
68 |
repos |
|
|
66 | repos = assert_and_get_repo_list_content(result) | |
|
69 | 67 | |
|
70 | 68 | assert len(repos) == len(Repository.query().filter( |
|
71 | 69 | Repository.repo_name.ilike('%vcs_test_git%')).all()) |
|
72 | assert len(groups) == 0 | |
|
73 | assert len(commits) == 0 | |
|
74 | 70 | |
|
75 | 71 | def test_returns_list_of_repos_and_groups_filtered_with_type(self, xhr_header): |
|
76 | 72 | self.log_user() |
|
77 | 73 | |
|
78 | 74 | response = self.app.get( |
|
79 | 75 | route_path('repo_list_data'), |
|
80 | 76 | params={'query': 'vcs_test_git', 'repo_type': 'git'}, |
|
81 | 77 | extra_environ=xhr_header, status=200) |
|
82 | 78 | result = json.loads(response.body)['results'] |
|
83 | 79 | |
|
84 |
repos |
|
|
80 | repos = assert_and_get_repo_list_content(result) | |
|
85 | 81 | |
|
86 | 82 | assert len(repos) == len(Repository.query().filter( |
|
87 | 83 | Repository.repo_name.ilike('%vcs_test_git%')).all()) |
|
88 | assert len(groups) == 0 | |
|
89 | assert len(commits) == 0 | |
|
90 | 84 | |
|
91 | 85 | def test_returns_list_of_repos_non_ascii_query(self, xhr_header): |
|
92 | 86 | self.log_user() |
|
93 | 87 | response = self.app.get( |
|
94 | 88 | route_path('repo_list_data'), |
|
95 | 89 | params={'query': 'Δ_vcs_test_Δ ', 'repo_type': 'git'}, |
|
96 | 90 | extra_environ=xhr_header, status=200) |
|
97 | 91 | result = json.loads(response.body)['results'] |
|
98 | 92 | |
|
99 |
repos |
|
|
93 | repos = assert_and_get_repo_list_content(result) | |
|
100 | 94 | |
|
101 | 95 | assert len(repos) == 0 |
|
102 | assert len(groups) == 0 | |
|
103 | assert len(commits) == 0 |
@@ -1,325 +1,374 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2018 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 | import collections | |
|
23 | 24 | |
|
24 | 25 | from pyramid.view import view_config |
|
25 | 26 | |
|
26 | 27 | from rhodecode.apps._base import BaseAppView |
|
27 | 28 | from rhodecode.lib import helpers as h |
|
28 | 29 | from rhodecode.lib.auth import ( |
|
29 | 30 | LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator) |
|
30 | 31 | from rhodecode.lib.index import searcher_from_config |
|
31 | 32 | from rhodecode.lib.utils2 import safe_unicode, str2bool |
|
32 | 33 | from rhodecode.lib.ext_json import json |
|
33 | 34 | from rhodecode.model.db import ( |
|
34 | func, or_, in_filter_generator, Repository, RepoGroup) | |
|
35 | func, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup) | |
|
35 | 36 | from rhodecode.model.repo import RepoModel |
|
36 | 37 | from rhodecode.model.repo_group import RepoGroupModel |
|
37 | 38 | from rhodecode.model.scm import RepoGroupList, RepoList |
|
38 | 39 | from rhodecode.model.user import UserModel |
|
39 | 40 | from rhodecode.model.user_group import UserGroupModel |
|
40 | 41 | |
|
41 | 42 | log = logging.getLogger(__name__) |
|
42 | 43 | |
|
43 | 44 | |
|
44 | 45 | class HomeView(BaseAppView): |
|
45 | 46 | |
|
46 | 47 | def load_default_context(self): |
|
47 | 48 | c = self._get_local_tmpl_context() |
|
48 | 49 | c.user = c.auth_user.get_instance() |
|
49 | 50 | |
|
50 | 51 | return c |
|
51 | 52 | |
|
52 | 53 | @LoginRequired() |
|
53 | 54 | @view_config( |
|
54 | 55 | route_name='user_autocomplete_data', request_method='GET', |
|
55 | 56 | renderer='json_ext', xhr=True) |
|
56 | 57 | def user_autocomplete_data(self): |
|
57 | 58 | self.load_default_context() |
|
58 | 59 | query = self.request.GET.get('query') |
|
59 | 60 | active = str2bool(self.request.GET.get('active') or True) |
|
60 | 61 | include_groups = str2bool(self.request.GET.get('user_groups')) |
|
61 | 62 | expand_groups = str2bool(self.request.GET.get('user_groups_expand')) |
|
62 | 63 | skip_default_user = str2bool(self.request.GET.get('skip_default_user')) |
|
63 | 64 | |
|
64 | 65 | log.debug('generating user list, query:%s, active:%s, with_groups:%s', |
|
65 | 66 | query, active, include_groups) |
|
66 | 67 | |
|
67 | 68 | _users = UserModel().get_users( |
|
68 | 69 | name_contains=query, only_active=active) |
|
69 | 70 | |
|
70 | 71 | def maybe_skip_default_user(usr): |
|
71 | 72 | if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER: |
|
72 | 73 | return False |
|
73 | 74 | return True |
|
74 | 75 | _users = filter(maybe_skip_default_user, _users) |
|
75 | 76 | |
|
76 | 77 | if include_groups: |
|
77 | 78 | # extend with user groups |
|
78 | 79 | _user_groups = UserGroupModel().get_user_groups( |
|
79 | 80 | name_contains=query, only_active=active, |
|
80 | 81 | expand_groups=expand_groups) |
|
81 | 82 | _users = _users + _user_groups |
|
82 | 83 | |
|
83 | 84 | return {'suggestions': _users} |
|
84 | 85 | |
|
85 | 86 | @LoginRequired() |
|
86 | 87 | @NotAnonymous() |
|
87 | 88 | @view_config( |
|
88 | 89 | route_name='user_group_autocomplete_data', request_method='GET', |
|
89 | 90 | renderer='json_ext', xhr=True) |
|
90 | 91 | def user_group_autocomplete_data(self): |
|
91 | 92 | self.load_default_context() |
|
92 | 93 | query = self.request.GET.get('query') |
|
93 | 94 | active = str2bool(self.request.GET.get('active') or True) |
|
94 | 95 | expand_groups = str2bool(self.request.GET.get('user_groups_expand')) |
|
95 | 96 | |
|
96 | 97 | log.debug('generating user group list, query:%s, active:%s', |
|
97 | 98 | query, active) |
|
98 | 99 | |
|
99 | 100 | _user_groups = UserGroupModel().get_user_groups( |
|
100 | 101 | name_contains=query, only_active=active, |
|
101 | 102 | expand_groups=expand_groups) |
|
102 | 103 | _user_groups = _user_groups |
|
103 | 104 | |
|
104 | 105 | return {'suggestions': _user_groups} |
|
105 | 106 | |
|
106 | 107 | def _get_repo_list(self, name_contains=None, repo_type=None, limit=20): |
|
108 | org_query = name_contains | |
|
107 | 109 | allowed_ids = self._rhodecode_user.repo_acl_ids( |
|
108 | 110 | ['repository.read', 'repository.write', 'repository.admin'], |
|
109 | 111 | cache=False, name_filter=name_contains) or [-1] |
|
110 | 112 | |
|
111 | 113 | query = Repository.query()\ |
|
112 | 114 | .order_by(func.length(Repository.repo_name))\ |
|
113 | 115 | .order_by(Repository.repo_name)\ |
|
114 | 116 | .filter(or_( |
|
115 | 117 | # generate multiple IN to fix limitation problems |
|
116 | 118 | *in_filter_generator(Repository.repo_id, allowed_ids) |
|
117 | 119 | )) |
|
118 | 120 | |
|
119 | 121 | if repo_type: |
|
120 | 122 | query = query.filter(Repository.repo_type == repo_type) |
|
121 | 123 | |
|
122 | 124 | if name_contains: |
|
123 | 125 | ilike_expression = u'%{}%'.format(safe_unicode(name_contains)) |
|
124 | 126 | query = query.filter( |
|
125 | 127 | Repository.repo_name.ilike(ilike_expression)) |
|
126 | 128 | query = query.limit(limit) |
|
127 | 129 | |
|
128 |
acl_ |
|
|
130 | acl_iter = query | |
|
129 | 131 | |
|
130 | 132 | return [ |
|
131 | 133 | { |
|
132 | 134 | 'id': obj.repo_name, |
|
135 | 'value': org_query, | |
|
136 | 'value_display': obj.repo_name, | |
|
133 | 137 | 'text': obj.repo_name, |
|
134 | 138 | 'type': 'repo', |
|
135 |
' |
|
|
136 |
|
|
|
139 | 'repo_id': obj.repo_id, | |
|
140 | 'repo_type': obj.repo_type, | |
|
141 | 'private': obj.private, | |
|
137 | 142 | 'url': h.route_path('repo_summary', repo_name=obj.repo_name) |
|
138 | 143 | } |
|
139 |
for obj in acl_ |
|
|
144 | for obj in acl_iter] | |
|
140 | 145 | |
|
141 | 146 | def _get_repo_group_list(self, name_contains=None, limit=20): |
|
147 | org_query = name_contains | |
|
142 | 148 | allowed_ids = self._rhodecode_user.repo_group_acl_ids( |
|
143 | 149 | ['group.read', 'group.write', 'group.admin'], |
|
144 | 150 | cache=False, name_filter=name_contains) or [-1] |
|
145 | 151 | |
|
146 | 152 | query = RepoGroup.query()\ |
|
147 | 153 | .order_by(func.length(RepoGroup.group_name))\ |
|
148 | 154 | .order_by(RepoGroup.group_name) \ |
|
149 | 155 | .filter(or_( |
|
150 | 156 | # generate multiple IN to fix limitation problems |
|
151 | 157 | *in_filter_generator(RepoGroup.group_id, allowed_ids) |
|
152 | 158 | )) |
|
153 | 159 | |
|
154 | 160 | if name_contains: |
|
155 | 161 | ilike_expression = u'%{}%'.format(safe_unicode(name_contains)) |
|
156 | 162 | query = query.filter( |
|
157 | 163 | RepoGroup.group_name.ilike(ilike_expression)) |
|
158 | 164 | query = query.limit(limit) |
|
159 | 165 | |
|
160 |
acl_ |
|
|
166 | acl_iter = query | |
|
161 | 167 | |
|
162 | 168 | return [ |
|
163 | 169 | { |
|
164 | 170 | 'id': obj.group_name, |
|
165 |
' |
|
|
166 |
' |
|
|
167 |
' |
|
|
171 | 'value': org_query, | |
|
172 | 'value_display': obj.group_name, | |
|
173 | 'type': 'repo_group', | |
|
168 | 174 | 'url': h.route_path( |
|
169 | 175 | 'repo_group_home', repo_group_name=obj.group_name) |
|
170 | 176 | } |
|
171 |
for obj in acl_ |
|
|
177 | for obj in acl_iter] | |
|
178 | ||
|
179 | def _get_user_list(self, name_contains=None, limit=20): | |
|
180 | org_query = name_contains | |
|
181 | if not name_contains: | |
|
182 | return [] | |
|
183 | ||
|
184 | name_contains = re.compile('(?:user:)(.+)').findall(name_contains) | |
|
185 | if len(name_contains) != 1: | |
|
186 | return [] | |
|
187 | name_contains = name_contains[0] | |
|
188 | ||
|
189 | query = User.query()\ | |
|
190 | .order_by(func.length(User.username))\ | |
|
191 | .order_by(User.username) \ | |
|
192 | .filter(User.username != User.DEFAULT_USER) | |
|
172 | 193 | |
|
173 | def _get_hash_commit_list(self, auth_user, query=None): | |
|
194 | if name_contains: | |
|
195 | ilike_expression = u'%{}%'.format(safe_unicode(name_contains)) | |
|
196 | query = query.filter( | |
|
197 | User.username.ilike(ilike_expression)) | |
|
198 | query = query.limit(limit) | |
|
199 | ||
|
200 | acl_iter = query | |
|
201 | ||
|
202 | return [ | |
|
203 | { | |
|
204 | 'id': obj.user_id, | |
|
205 | 'value': org_query, | |
|
206 | 'value_display': obj.username, | |
|
207 | 'type': 'user', | |
|
208 | 'icon_link': h.gravatar_url(obj.email, 30), | |
|
209 | 'url': h.route_path( | |
|
210 | 'user_profile', username=obj.username) | |
|
211 | } | |
|
212 | for obj in acl_iter] | |
|
213 | ||
|
214 | def _get_hash_commit_list(self, auth_user, query): | |
|
215 | org_query = query | |
|
174 | 216 | if not query or len(query) < 3: |
|
175 | 217 | return [] |
|
176 | 218 | |
|
177 | 219 | commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query) |
|
178 | 220 | |
|
179 | 221 | if len(commit_hashes) != 1: |
|
180 | 222 | return [] |
|
181 | ||
|
182 | commit_hash_prefix = commit_hashes[0] | |
|
223 | commit_hash = commit_hashes[0] | |
|
183 | 224 | |
|
184 | 225 | searcher = searcher_from_config(self.request.registry.settings) |
|
185 | 226 | result = searcher.search( |
|
186 |
'commit_id:%s*' % commit_hash |
|
|
227 | 'commit_id:%s*' % commit_hash, 'commit', auth_user, | |
|
187 | 228 | raise_on_exc=False) |
|
188 | 229 | |
|
189 | 230 | return [ |
|
190 | 231 | { |
|
191 | 232 | 'id': entry['commit_id'], |
|
192 |
' |
|
|
233 | 'value': org_query, | |
|
234 | 'value_display': 'repo `{}` commit: {}'.format( | |
|
235 | entry['repository'], entry['commit_id']), | |
|
193 | 236 | 'type': 'commit', |
|
194 |
|
|
|
237 | 'repo': entry['repository'], | |
|
195 | 238 | 'url': h.route_path( |
|
196 | 239 | 'repo_commit', |
|
197 | 240 | repo_name=entry['repository'], commit_id=entry['commit_id']) |
|
198 | 241 | } |
|
199 | 242 | for entry in result['results']] |
|
200 | 243 | |
|
201 | 244 | @LoginRequired() |
|
202 | 245 | @view_config( |
|
203 | 246 | route_name='repo_list_data', request_method='GET', |
|
204 | 247 | renderer='json_ext', xhr=True) |
|
205 | 248 | def repo_list_data(self): |
|
206 | 249 | _ = self.request.translate |
|
207 | 250 | self.load_default_context() |
|
208 | 251 | |
|
209 | 252 | query = self.request.GET.get('query') |
|
210 | 253 | repo_type = self.request.GET.get('repo_type') |
|
211 | 254 | log.debug('generating repo list, query:%s, repo_type:%s', |
|
212 | 255 | query, repo_type) |
|
213 | 256 | |
|
214 | 257 | res = [] |
|
215 | 258 | repos = self._get_repo_list(query, repo_type=repo_type) |
|
216 | 259 | if repos: |
|
217 | 260 | res.append({ |
|
218 | 261 | 'text': _('Repositories'), |
|
219 | 262 | 'children': repos |
|
220 | 263 | }) |
|
221 | 264 | |
|
222 | 265 | data = { |
|
223 | 266 | 'more': False, |
|
224 | 267 | 'results': res |
|
225 | 268 | } |
|
226 | 269 | return data |
|
227 | 270 | |
|
228 | 271 | @LoginRequired() |
|
229 | 272 | @view_config( |
|
230 | 273 | route_name='goto_switcher_data', request_method='GET', |
|
231 | 274 | renderer='json_ext', xhr=True) |
|
232 | 275 | def goto_switcher_data(self): |
|
233 | 276 | c = self.load_default_context() |
|
234 | 277 | |
|
235 | 278 | _ = self.request.translate |
|
236 | 279 | |
|
237 | 280 | query = self.request.GET.get('query') |
|
238 |
log.debug('generating |
|
|
281 | log.debug('generating main filter data, query %s', query) | |
|
282 | ||
|
283 | default_search_val = 'Full text search for: `{}`'.format(query) | |
|
284 | res = [] | |
|
285 | if not query: | |
|
286 | return {'suggestions': res} | |
|
239 | 287 | |
|
240 |
res |
|
|
288 | res.append({ | |
|
289 | 'id': -1, | |
|
290 | 'value': query, | |
|
291 | 'value_display': default_search_val, | |
|
292 | 'type': 'search', | |
|
293 | 'url': h.route_path( | |
|
294 | 'search', _query={'q': query}) | |
|
295 | }) | |
|
296 | ||
|
241 | 297 | repo_groups = self._get_repo_group_list(query) |
|
242 |
|
|
|
243 |
res.append( |
|
|
244 | 'text': _('Groups'), | |
|
245 | 'children': repo_groups | |
|
246 | }) | |
|
298 | for serialized_repo_group in repo_groups: | |
|
299 | res.append(serialized_repo_group) | |
|
247 | 300 | |
|
248 | 301 | repos = self._get_repo_list(query) |
|
249 | if repos: | |
|
250 |
res.append( |
|
|
251 | 'text': _('Repositories'), | |
|
252 | 'children': repos | |
|
253 | }) | |
|
302 | for serialized_repo in repos: | |
|
303 | res.append(serialized_repo) | |
|
304 | ||
|
305 | # TODO(marcink): permissions for that ? | |
|
306 | users = self._get_user_list(query) | |
|
307 | for serialized_user in users: | |
|
308 | res.append(serialized_user) | |
|
254 | 309 | |
|
255 | 310 | commits = self._get_hash_commit_list(c.auth_user, query) |
|
256 | 311 | if commits: |
|
257 |
unique_repos = |
|
|
312 | unique_repos = collections.OrderedDict() | |
|
258 | 313 | for commit in commits: |
|
259 |
|
|
|
260 |
|
|
|
314 | repo_name = commit['repo'] | |
|
315 | unique_repos.setdefault(repo_name, []).append(commit) | |
|
261 | 316 | |
|
262 | for repo in unique_repos: | |
|
263 | res.append({ | |
|
264 | 'text': _('Commits in %(repo)s') % {'repo': repo}, | |
|
265 | 'children': unique_repos[repo] | |
|
266 | }) | |
|
317 | for repo, commits in unique_repos.items(): | |
|
318 | for commit in commits: | |
|
319 | res.append(commit) | |
|
267 | 320 | |
|
268 | data = { | |
|
269 | 'more': False, | |
|
270 | 'results': res | |
|
271 | } | |
|
272 | return data | |
|
321 | return {'suggestions': res} | |
|
273 | 322 | |
|
274 | 323 | def _get_groups_and_repos(self, repo_group_id=None): |
|
275 | 324 | # repo groups groups |
|
276 | 325 | repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id) |
|
277 | 326 | _perms = ['group.read', 'group.write', 'group.admin'] |
|
278 | 327 | repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms) |
|
279 | 328 | repo_group_data = RepoGroupModel().get_repo_groups_as_dict( |
|
280 | 329 | repo_group_list=repo_group_list_acl, admin=False) |
|
281 | 330 | |
|
282 | 331 | # repositories |
|
283 | 332 | repo_list = Repository.get_all_repos(group_id=repo_group_id) |
|
284 | 333 | _perms = ['repository.read', 'repository.write', 'repository.admin'] |
|
285 | 334 | repo_list_acl = RepoList(repo_list, perm_set=_perms) |
|
286 | 335 | repo_data = RepoModel().get_repos_as_dict( |
|
287 | 336 | repo_list=repo_list_acl, admin=False) |
|
288 | 337 | |
|
289 | 338 | return repo_data, repo_group_data |
|
290 | 339 | |
|
291 | 340 | @LoginRequired() |
|
292 | 341 | @view_config( |
|
293 | 342 | route_name='home', request_method='GET', |
|
294 | 343 | renderer='rhodecode:templates/index.mako') |
|
295 | 344 | def main_page(self): |
|
296 | 345 | c = self.load_default_context() |
|
297 | 346 | c.repo_group = None |
|
298 | 347 | |
|
299 | 348 | repo_data, repo_group_data = self._get_groups_and_repos() |
|
300 | 349 | # json used to render the grids |
|
301 | 350 | c.repos_data = json.dumps(repo_data) |
|
302 | 351 | c.repo_groups_data = json.dumps(repo_group_data) |
|
303 | 352 | |
|
304 | 353 | return self._get_template_context(c) |
|
305 | 354 | |
|
306 | 355 | @LoginRequired() |
|
307 | 356 | @HasRepoGroupPermissionAnyDecorator( |
|
308 | 357 | 'group.read', 'group.write', 'group.admin') |
|
309 | 358 | @view_config( |
|
310 | 359 | route_name='repo_group_home', request_method='GET', |
|
311 | 360 | renderer='rhodecode:templates/index_repo_group.mako') |
|
312 | 361 | @view_config( |
|
313 | 362 | route_name='repo_group_home_slash', request_method='GET', |
|
314 | 363 | renderer='rhodecode:templates/index_repo_group.mako') |
|
315 | 364 | def repo_group_main_page(self): |
|
316 | 365 | c = self.load_default_context() |
|
317 | 366 | c.repo_group = self.request.db_repo_group |
|
318 | 367 | repo_data, repo_group_data = self._get_groups_and_repos( |
|
319 | 368 | c.repo_group.group_id) |
|
320 | 369 | |
|
321 | 370 | # json used to render the grids |
|
322 | 371 | c.repos_data = json.dumps(repo_data) |
|
323 | 372 | c.repo_groups_data = json.dumps(repo_group_data) |
|
324 | 373 | |
|
325 | 374 | return self._get_template_context(c) |
@@ -1,1298 +1,1301 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2011-2018 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 logging |
|
22 | 22 | import collections |
|
23 | 23 | |
|
24 | 24 | import formencode |
|
25 | 25 | import formencode.htmlfill |
|
26 | 26 | import peppercorn |
|
27 | 27 | from pyramid.httpexceptions import ( |
|
28 | 28 | HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest) |
|
29 | 29 | from pyramid.view import view_config |
|
30 | 30 | from pyramid.renderers import render |
|
31 | 31 | |
|
32 | 32 | from rhodecode import events |
|
33 | 33 | from rhodecode.apps._base import RepoAppView, DataGridAppView |
|
34 | 34 | |
|
35 | 35 | from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream |
|
36 | 36 | from rhodecode.lib.base import vcs_operation_context |
|
37 | 37 | from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist |
|
38 | 38 | from rhodecode.lib.ext_json import json |
|
39 | 39 | from rhodecode.lib.auth import ( |
|
40 | 40 | LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator, |
|
41 | 41 | NotAnonymous, CSRFRequired) |
|
42 | 42 | from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode |
|
43 | 43 | from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason |
|
44 | 44 | from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError, |
|
45 | 45 | RepositoryRequirementError, EmptyRepositoryError) |
|
46 | 46 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
47 | 47 | from rhodecode.model.comment import CommentsModel |
|
48 | 48 | from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion, |
|
49 | 49 | ChangesetComment, ChangesetStatus, Repository) |
|
50 | 50 | from rhodecode.model.forms import PullRequestForm |
|
51 | 51 | from rhodecode.model.meta import Session |
|
52 | 52 | from rhodecode.model.pull_request import PullRequestModel, MergeCheck |
|
53 | 53 | from rhodecode.model.scm import ScmModel |
|
54 | 54 | |
|
55 | 55 | log = logging.getLogger(__name__) |
|
56 | 56 | |
|
57 | 57 | |
|
58 | 58 | class RepoPullRequestsView(RepoAppView, DataGridAppView): |
|
59 | 59 | |
|
60 | 60 | def load_default_context(self): |
|
61 | 61 | c = self._get_local_tmpl_context(include_app_defaults=True) |
|
62 | 62 | c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED |
|
63 | 63 | c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED |
|
64 | 64 | |
|
65 | 65 | return c |
|
66 | 66 | |
|
67 | 67 | def _get_pull_requests_list( |
|
68 | 68 | self, repo_name, source, filter_type, opened_by, statuses): |
|
69 | 69 | |
|
70 | 70 | draw, start, limit = self._extract_chunk(self.request) |
|
71 | 71 | search_q, order_by, order_dir = self._extract_ordering(self.request) |
|
72 | 72 | _render = self.request.get_partial_renderer( |
|
73 | 73 | 'rhodecode:templates/data_table/_dt_elements.mako') |
|
74 | 74 | |
|
75 | 75 | # pagination |
|
76 | 76 | |
|
77 | 77 | if filter_type == 'awaiting_review': |
|
78 | 78 | pull_requests = PullRequestModel().get_awaiting_review( |
|
79 | 79 | repo_name, source=source, opened_by=opened_by, |
|
80 | 80 | statuses=statuses, offset=start, length=limit, |
|
81 | 81 | order_by=order_by, order_dir=order_dir) |
|
82 | 82 | pull_requests_total_count = PullRequestModel().count_awaiting_review( |
|
83 | 83 | repo_name, source=source, statuses=statuses, |
|
84 | 84 | opened_by=opened_by) |
|
85 | 85 | elif filter_type == 'awaiting_my_review': |
|
86 | 86 | pull_requests = PullRequestModel().get_awaiting_my_review( |
|
87 | 87 | repo_name, source=source, opened_by=opened_by, |
|
88 | 88 | user_id=self._rhodecode_user.user_id, statuses=statuses, |
|
89 | 89 | offset=start, length=limit, order_by=order_by, |
|
90 | 90 | order_dir=order_dir) |
|
91 | 91 | pull_requests_total_count = PullRequestModel().count_awaiting_my_review( |
|
92 | 92 | repo_name, source=source, user_id=self._rhodecode_user.user_id, |
|
93 | 93 | statuses=statuses, opened_by=opened_by) |
|
94 | 94 | else: |
|
95 | 95 | pull_requests = PullRequestModel().get_all( |
|
96 | 96 | repo_name, source=source, opened_by=opened_by, |
|
97 | 97 | statuses=statuses, offset=start, length=limit, |
|
98 | 98 | order_by=order_by, order_dir=order_dir) |
|
99 | 99 | pull_requests_total_count = PullRequestModel().count_all( |
|
100 | 100 | repo_name, source=source, statuses=statuses, |
|
101 | 101 | opened_by=opened_by) |
|
102 | 102 | |
|
103 | 103 | data = [] |
|
104 | 104 | comments_model = CommentsModel() |
|
105 | 105 | for pr in pull_requests: |
|
106 | 106 | comments = comments_model.get_all_comments( |
|
107 | 107 | self.db_repo.repo_id, pull_request=pr) |
|
108 | 108 | |
|
109 | 109 | data.append({ |
|
110 | 110 | 'name': _render('pullrequest_name', |
|
111 | 111 | pr.pull_request_id, pr.target_repo.repo_name), |
|
112 | 112 | 'name_raw': pr.pull_request_id, |
|
113 | 113 | 'status': _render('pullrequest_status', |
|
114 | 114 | pr.calculated_review_status()), |
|
115 | 115 | 'title': _render( |
|
116 | 116 | 'pullrequest_title', pr.title, pr.description), |
|
117 | 117 | 'description': h.escape(pr.description), |
|
118 | 118 | 'updated_on': _render('pullrequest_updated_on', |
|
119 | 119 | h.datetime_to_time(pr.updated_on)), |
|
120 | 120 | 'updated_on_raw': h.datetime_to_time(pr.updated_on), |
|
121 | 121 | 'created_on': _render('pullrequest_updated_on', |
|
122 | 122 | h.datetime_to_time(pr.created_on)), |
|
123 | 123 | 'created_on_raw': h.datetime_to_time(pr.created_on), |
|
124 | 124 | 'author': _render('pullrequest_author', |
|
125 | 125 | pr.author.full_contact, ), |
|
126 | 126 | 'author_raw': pr.author.full_name, |
|
127 | 127 | 'comments': _render('pullrequest_comments', len(comments)), |
|
128 | 128 | 'comments_raw': len(comments), |
|
129 | 129 | 'closed': pr.is_closed(), |
|
130 | 130 | }) |
|
131 | 131 | |
|
132 | 132 | data = ({ |
|
133 | 133 | 'draw': draw, |
|
134 | 134 | 'data': data, |
|
135 | 135 | 'recordsTotal': pull_requests_total_count, |
|
136 | 136 | 'recordsFiltered': pull_requests_total_count, |
|
137 | 137 | }) |
|
138 | 138 | return data |
|
139 | 139 | |
|
140 | 140 | @LoginRequired() |
|
141 | 141 | @HasRepoPermissionAnyDecorator( |
|
142 | 142 | 'repository.read', 'repository.write', 'repository.admin') |
|
143 | 143 | @view_config( |
|
144 | 144 | route_name='pullrequest_show_all', request_method='GET', |
|
145 | 145 | renderer='rhodecode:templates/pullrequests/pullrequests.mako') |
|
146 | 146 | def pull_request_list(self): |
|
147 | 147 | c = self.load_default_context() |
|
148 | 148 | |
|
149 | 149 | req_get = self.request.GET |
|
150 | 150 | c.source = str2bool(req_get.get('source')) |
|
151 | 151 | c.closed = str2bool(req_get.get('closed')) |
|
152 | 152 | c.my = str2bool(req_get.get('my')) |
|
153 | 153 | c.awaiting_review = str2bool(req_get.get('awaiting_review')) |
|
154 | 154 | c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review')) |
|
155 | 155 | |
|
156 | 156 | c.active = 'open' |
|
157 | 157 | if c.my: |
|
158 | 158 | c.active = 'my' |
|
159 | 159 | if c.closed: |
|
160 | 160 | c.active = 'closed' |
|
161 | 161 | if c.awaiting_review and not c.source: |
|
162 | 162 | c.active = 'awaiting' |
|
163 | 163 | if c.source and not c.awaiting_review: |
|
164 | 164 | c.active = 'source' |
|
165 | 165 | if c.awaiting_my_review: |
|
166 | 166 | c.active = 'awaiting_my' |
|
167 | 167 | |
|
168 | 168 | return self._get_template_context(c) |
|
169 | 169 | |
|
170 | 170 | @LoginRequired() |
|
171 | 171 | @HasRepoPermissionAnyDecorator( |
|
172 | 172 | 'repository.read', 'repository.write', 'repository.admin') |
|
173 | 173 | @view_config( |
|
174 | 174 | route_name='pullrequest_show_all_data', request_method='GET', |
|
175 | 175 | renderer='json_ext', xhr=True) |
|
176 | 176 | def pull_request_list_data(self): |
|
177 | 177 | self.load_default_context() |
|
178 | 178 | |
|
179 | 179 | # additional filters |
|
180 | 180 | req_get = self.request.GET |
|
181 | 181 | source = str2bool(req_get.get('source')) |
|
182 | 182 | closed = str2bool(req_get.get('closed')) |
|
183 | 183 | my = str2bool(req_get.get('my')) |
|
184 | 184 | awaiting_review = str2bool(req_get.get('awaiting_review')) |
|
185 | 185 | awaiting_my_review = str2bool(req_get.get('awaiting_my_review')) |
|
186 | 186 | |
|
187 | 187 | filter_type = 'awaiting_review' if awaiting_review \ |
|
188 | 188 | else 'awaiting_my_review' if awaiting_my_review \ |
|
189 | 189 | else None |
|
190 | 190 | |
|
191 | 191 | opened_by = None |
|
192 | 192 | if my: |
|
193 | 193 | opened_by = [self._rhodecode_user.user_id] |
|
194 | 194 | |
|
195 | 195 | statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN] |
|
196 | 196 | if closed: |
|
197 | 197 | statuses = [PullRequest.STATUS_CLOSED] |
|
198 | 198 | |
|
199 | 199 | data = self._get_pull_requests_list( |
|
200 | 200 | repo_name=self.db_repo_name, source=source, |
|
201 | 201 | filter_type=filter_type, opened_by=opened_by, statuses=statuses) |
|
202 | 202 | |
|
203 | 203 | return data |
|
204 | 204 | |
|
205 | 205 | def _is_diff_cache_enabled(self, target_repo): |
|
206 | 206 | caching_enabled = self._get_general_setting( |
|
207 | 207 | target_repo, 'rhodecode_diff_cache') |
|
208 | 208 | log.debug('Diff caching enabled: %s', caching_enabled) |
|
209 | 209 | return caching_enabled |
|
210 | 210 | |
|
211 | 211 | def _get_diffset(self, source_repo_name, source_repo, |
|
212 | 212 | source_ref_id, target_ref_id, |
|
213 | 213 | target_commit, source_commit, diff_limit, file_limit, |
|
214 | 214 | fulldiff): |
|
215 | 215 | |
|
216 | 216 | vcs_diff = PullRequestModel().get_diff( |
|
217 | 217 | source_repo, source_ref_id, target_ref_id) |
|
218 | 218 | |
|
219 | 219 | diff_processor = diffs.DiffProcessor( |
|
220 | 220 | vcs_diff, format='newdiff', diff_limit=diff_limit, |
|
221 | 221 | file_limit=file_limit, show_full_diff=fulldiff) |
|
222 | 222 | |
|
223 | 223 | _parsed = diff_processor.prepare() |
|
224 | 224 | |
|
225 | 225 | diffset = codeblocks.DiffSet( |
|
226 | 226 | repo_name=self.db_repo_name, |
|
227 | 227 | source_repo_name=source_repo_name, |
|
228 | 228 | source_node_getter=codeblocks.diffset_node_getter(target_commit), |
|
229 | 229 | target_node_getter=codeblocks.diffset_node_getter(source_commit), |
|
230 | 230 | ) |
|
231 | 231 | diffset = self.path_filter.render_patchset_filtered( |
|
232 | 232 | diffset, _parsed, target_commit.raw_id, source_commit.raw_id) |
|
233 | 233 | |
|
234 | 234 | return diffset |
|
235 | 235 | |
|
236 | 236 | @LoginRequired() |
|
237 | 237 | @HasRepoPermissionAnyDecorator( |
|
238 | 238 | 'repository.read', 'repository.write', 'repository.admin') |
|
239 | 239 | @view_config( |
|
240 | 240 | route_name='pullrequest_show', request_method='GET', |
|
241 | 241 | renderer='rhodecode:templates/pullrequests/pullrequest_show.mako') |
|
242 | 242 | def pull_request_show(self): |
|
243 | 243 | pull_request_id = self.request.matchdict['pull_request_id'] |
|
244 | 244 | |
|
245 | 245 | c = self.load_default_context() |
|
246 | 246 | |
|
247 | 247 | version = self.request.GET.get('version') |
|
248 | 248 | from_version = self.request.GET.get('from_version') or version |
|
249 | 249 | merge_checks = self.request.GET.get('merge_checks') |
|
250 | 250 | c.fulldiff = str2bool(self.request.GET.get('fulldiff')) |
|
251 | 251 | |
|
252 | 252 | (pull_request_latest, |
|
253 | 253 | pull_request_at_ver, |
|
254 | 254 | pull_request_display_obj, |
|
255 | 255 | at_version) = PullRequestModel().get_pr_version( |
|
256 | 256 | pull_request_id, version=version) |
|
257 | 257 | pr_closed = pull_request_latest.is_closed() |
|
258 | 258 | |
|
259 | 259 | if pr_closed and (version or from_version): |
|
260 | 260 | # not allow to browse versions |
|
261 | 261 | raise HTTPFound(h.route_path( |
|
262 | 262 | 'pullrequest_show', repo_name=self.db_repo_name, |
|
263 | 263 | pull_request_id=pull_request_id)) |
|
264 | 264 | |
|
265 | 265 | versions = pull_request_display_obj.versions() |
|
266 | 266 | |
|
267 | 267 | c.at_version = at_version |
|
268 | 268 | c.at_version_num = (at_version |
|
269 | 269 | if at_version and at_version != 'latest' |
|
270 | 270 | else None) |
|
271 | 271 | c.at_version_pos = ChangesetComment.get_index_from_version( |
|
272 | 272 | c.at_version_num, versions) |
|
273 | 273 | |
|
274 | 274 | (prev_pull_request_latest, |
|
275 | 275 | prev_pull_request_at_ver, |
|
276 | 276 | prev_pull_request_display_obj, |
|
277 | 277 | prev_at_version) = PullRequestModel().get_pr_version( |
|
278 | 278 | pull_request_id, version=from_version) |
|
279 | 279 | |
|
280 | 280 | c.from_version = prev_at_version |
|
281 | 281 | c.from_version_num = (prev_at_version |
|
282 | 282 | if prev_at_version and prev_at_version != 'latest' |
|
283 | 283 | else None) |
|
284 | 284 | c.from_version_pos = ChangesetComment.get_index_from_version( |
|
285 | 285 | c.from_version_num, versions) |
|
286 | 286 | |
|
287 | 287 | # define if we're in COMPARE mode or VIEW at version mode |
|
288 | 288 | compare = at_version != prev_at_version |
|
289 | 289 | |
|
290 | 290 | # pull_requests repo_name we opened it against |
|
291 | 291 | # ie. target_repo must match |
|
292 | 292 | if self.db_repo_name != pull_request_at_ver.target_repo.repo_name: |
|
293 | 293 | raise HTTPNotFound() |
|
294 | 294 | |
|
295 | 295 | c.shadow_clone_url = PullRequestModel().get_shadow_clone_url( |
|
296 | 296 | pull_request_at_ver) |
|
297 | 297 | |
|
298 | 298 | c.pull_request = pull_request_display_obj |
|
299 | 299 | c.pull_request_latest = pull_request_latest |
|
300 | 300 | |
|
301 | 301 | if compare or (at_version and not at_version == 'latest'): |
|
302 | 302 | c.allowed_to_change_status = False |
|
303 | 303 | c.allowed_to_update = False |
|
304 | 304 | c.allowed_to_merge = False |
|
305 | 305 | c.allowed_to_delete = False |
|
306 | 306 | c.allowed_to_comment = False |
|
307 | 307 | c.allowed_to_close = False |
|
308 | 308 | else: |
|
309 | 309 | can_change_status = PullRequestModel().check_user_change_status( |
|
310 | 310 | pull_request_at_ver, self._rhodecode_user) |
|
311 | 311 | c.allowed_to_change_status = can_change_status and not pr_closed |
|
312 | 312 | |
|
313 | 313 | c.allowed_to_update = PullRequestModel().check_user_update( |
|
314 | 314 | pull_request_latest, self._rhodecode_user) and not pr_closed |
|
315 | 315 | c.allowed_to_merge = PullRequestModel().check_user_merge( |
|
316 | 316 | pull_request_latest, self._rhodecode_user) and not pr_closed |
|
317 | 317 | c.allowed_to_delete = PullRequestModel().check_user_delete( |
|
318 | 318 | pull_request_latest, self._rhodecode_user) and not pr_closed |
|
319 | 319 | c.allowed_to_comment = not pr_closed |
|
320 | 320 | c.allowed_to_close = c.allowed_to_merge and not pr_closed |
|
321 | 321 | |
|
322 | 322 | c.forbid_adding_reviewers = False |
|
323 | 323 | c.forbid_author_to_review = False |
|
324 | 324 | c.forbid_commit_author_to_review = False |
|
325 | 325 | |
|
326 | 326 | if pull_request_latest.reviewer_data and \ |
|
327 | 327 | 'rules' in pull_request_latest.reviewer_data: |
|
328 | 328 | rules = pull_request_latest.reviewer_data['rules'] or {} |
|
329 | 329 | try: |
|
330 | 330 | c.forbid_adding_reviewers = rules.get( |
|
331 | 331 | 'forbid_adding_reviewers') |
|
332 | 332 | c.forbid_author_to_review = rules.get( |
|
333 | 333 | 'forbid_author_to_review') |
|
334 | 334 | c.forbid_commit_author_to_review = rules.get( |
|
335 | 335 | 'forbid_commit_author_to_review') |
|
336 | 336 | except Exception: |
|
337 | 337 | pass |
|
338 | 338 | |
|
339 | 339 | # check merge capabilities |
|
340 | 340 | _merge_check = MergeCheck.validate( |
|
341 | 341 | pull_request_latest, user=self._rhodecode_user, |
|
342 | 342 | translator=self.request.translate) |
|
343 | 343 | c.pr_merge_errors = _merge_check.error_details |
|
344 | 344 | c.pr_merge_possible = not _merge_check.failed |
|
345 | 345 | c.pr_merge_message = _merge_check.merge_msg |
|
346 | 346 | |
|
347 | 347 | c.pr_merge_info = MergeCheck.get_merge_conditions( |
|
348 | 348 | pull_request_latest, translator=self.request.translate) |
|
349 | 349 | |
|
350 | 350 | c.pull_request_review_status = _merge_check.review_status |
|
351 | 351 | if merge_checks: |
|
352 | 352 | self.request.override_renderer = \ |
|
353 | 353 | 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako' |
|
354 | 354 | return self._get_template_context(c) |
|
355 | 355 | |
|
356 | 356 | comments_model = CommentsModel() |
|
357 | 357 | |
|
358 | 358 | # reviewers and statuses |
|
359 | 359 | c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses() |
|
360 | 360 | allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers] |
|
361 | 361 | |
|
362 | 362 | # GENERAL COMMENTS with versions # |
|
363 | 363 | q = comments_model._all_general_comments_of_pull_request(pull_request_latest) |
|
364 | 364 | q = q.order_by(ChangesetComment.comment_id.asc()) |
|
365 | 365 | general_comments = q |
|
366 | 366 | |
|
367 | 367 | # pick comments we want to render at current version |
|
368 | 368 | c.comment_versions = comments_model.aggregate_comments( |
|
369 | 369 | general_comments, versions, c.at_version_num) |
|
370 | 370 | c.comments = c.comment_versions[c.at_version_num]['until'] |
|
371 | 371 | |
|
372 | 372 | # INLINE COMMENTS with versions # |
|
373 | 373 | q = comments_model._all_inline_comments_of_pull_request(pull_request_latest) |
|
374 | 374 | q = q.order_by(ChangesetComment.comment_id.asc()) |
|
375 | 375 | inline_comments = q |
|
376 | 376 | |
|
377 | 377 | c.inline_versions = comments_model.aggregate_comments( |
|
378 | 378 | inline_comments, versions, c.at_version_num, inline=True) |
|
379 | 379 | |
|
380 | 380 | # inject latest version |
|
381 | 381 | latest_ver = PullRequest.get_pr_display_object( |
|
382 | 382 | pull_request_latest, pull_request_latest) |
|
383 | 383 | |
|
384 | 384 | c.versions = versions + [latest_ver] |
|
385 | 385 | |
|
386 | 386 | # if we use version, then do not show later comments |
|
387 | 387 | # than current version |
|
388 | 388 | display_inline_comments = collections.defaultdict( |
|
389 | 389 | lambda: collections.defaultdict(list)) |
|
390 | 390 | for co in inline_comments: |
|
391 | 391 | if c.at_version_num: |
|
392 | 392 | # pick comments that are at least UPTO given version, so we |
|
393 | 393 | # don't render comments for higher version |
|
394 | 394 | should_render = co.pull_request_version_id and \ |
|
395 | 395 | co.pull_request_version_id <= c.at_version_num |
|
396 | 396 | else: |
|
397 | 397 | # showing all, for 'latest' |
|
398 | 398 | should_render = True |
|
399 | 399 | |
|
400 | 400 | if should_render: |
|
401 | 401 | display_inline_comments[co.f_path][co.line_no].append(co) |
|
402 | 402 | |
|
403 | 403 | # load diff data into template context, if we use compare mode then |
|
404 | 404 | # diff is calculated based on changes between versions of PR |
|
405 | 405 | |
|
406 | 406 | source_repo = pull_request_at_ver.source_repo |
|
407 | 407 | source_ref_id = pull_request_at_ver.source_ref_parts.commit_id |
|
408 | 408 | |
|
409 | 409 | target_repo = pull_request_at_ver.target_repo |
|
410 | 410 | target_ref_id = pull_request_at_ver.target_ref_parts.commit_id |
|
411 | 411 | |
|
412 | 412 | if compare: |
|
413 | 413 | # in compare switch the diff base to latest commit from prev version |
|
414 | 414 | target_ref_id = prev_pull_request_display_obj.revisions[0] |
|
415 | 415 | |
|
416 | 416 | # despite opening commits for bookmarks/branches/tags, we always |
|
417 | 417 | # convert this to rev to prevent changes after bookmark or branch change |
|
418 | 418 | c.source_ref_type = 'rev' |
|
419 | 419 | c.source_ref = source_ref_id |
|
420 | 420 | |
|
421 | 421 | c.target_ref_type = 'rev' |
|
422 | 422 | c.target_ref = target_ref_id |
|
423 | 423 | |
|
424 | 424 | c.source_repo = source_repo |
|
425 | 425 | c.target_repo = target_repo |
|
426 | 426 | |
|
427 | 427 | c.commit_ranges = [] |
|
428 | 428 | source_commit = EmptyCommit() |
|
429 | 429 | target_commit = EmptyCommit() |
|
430 | 430 | c.missing_requirements = False |
|
431 | 431 | |
|
432 | 432 | source_scm = source_repo.scm_instance() |
|
433 | 433 | target_scm = target_repo.scm_instance() |
|
434 | 434 | |
|
435 | 435 | # try first shadow repo, fallback to regular repo |
|
436 | 436 | try: |
|
437 | 437 | commits_source_repo = pull_request_latest.get_shadow_repo() |
|
438 | 438 | except Exception: |
|
439 | 439 | log.debug('Failed to get shadow repo', exc_info=True) |
|
440 | 440 | commits_source_repo = source_scm |
|
441 | 441 | |
|
442 | 442 | c.commits_source_repo = commits_source_repo |
|
443 | 443 | c.ancestor = None # set it to None, to hide it from PR view |
|
444 | 444 | |
|
445 | 445 | # empty version means latest, so we keep this to prevent |
|
446 | 446 | # double caching |
|
447 | 447 | version_normalized = version or 'latest' |
|
448 | 448 | from_version_normalized = from_version or 'latest' |
|
449 | 449 | |
|
450 | 450 | cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path( |
|
451 | 451 | target_repo) |
|
452 | 452 | cache_file_path = diff_cache_exist( |
|
453 | 453 | cache_path, 'pull_request', pull_request_id, version_normalized, |
|
454 | 454 | from_version_normalized, source_ref_id, target_ref_id, c.fulldiff) |
|
455 | 455 | |
|
456 | 456 | caching_enabled = self._is_diff_cache_enabled(c.target_repo) |
|
457 | 457 | force_recache = str2bool(self.request.GET.get('force_recache')) |
|
458 | 458 | |
|
459 | 459 | cached_diff = None |
|
460 | 460 | if caching_enabled: |
|
461 | 461 | cached_diff = load_cached_diff(cache_file_path) |
|
462 | 462 | |
|
463 | 463 | has_proper_commit_cache = ( |
|
464 | 464 | cached_diff and cached_diff.get('commits') |
|
465 | 465 | and len(cached_diff.get('commits', [])) == 5 |
|
466 | 466 | and cached_diff.get('commits')[0] |
|
467 | 467 | and cached_diff.get('commits')[3]) |
|
468 | 468 | if not force_recache and has_proper_commit_cache: |
|
469 | 469 | diff_commit_cache = \ |
|
470 | 470 | (ancestor_commit, commit_cache, missing_requirements, |
|
471 | 471 | source_commit, target_commit) = cached_diff['commits'] |
|
472 | 472 | else: |
|
473 | 473 | diff_commit_cache = \ |
|
474 | 474 | (ancestor_commit, commit_cache, missing_requirements, |
|
475 | 475 | source_commit, target_commit) = self.get_commits( |
|
476 | 476 | commits_source_repo, |
|
477 | 477 | pull_request_at_ver, |
|
478 | 478 | source_commit, |
|
479 | 479 | source_ref_id, |
|
480 | 480 | source_scm, |
|
481 | 481 | target_commit, |
|
482 | 482 | target_ref_id, |
|
483 | 483 | target_scm) |
|
484 | 484 | |
|
485 | 485 | # register our commit range |
|
486 | 486 | for comm in commit_cache.values(): |
|
487 | 487 | c.commit_ranges.append(comm) |
|
488 | 488 | |
|
489 | 489 | c.missing_requirements = missing_requirements |
|
490 | 490 | c.ancestor_commit = ancestor_commit |
|
491 | 491 | c.statuses = source_repo.statuses( |
|
492 | 492 | [x.raw_id for x in c.commit_ranges]) |
|
493 | 493 | |
|
494 | 494 | # auto collapse if we have more than limit |
|
495 | 495 | collapse_limit = diffs.DiffProcessor._collapse_commits_over |
|
496 | 496 | c.collapse_all_commits = len(c.commit_ranges) > collapse_limit |
|
497 | 497 | c.compare_mode = compare |
|
498 | 498 | |
|
499 | 499 | # diff_limit is the old behavior, will cut off the whole diff |
|
500 | 500 | # if the limit is applied otherwise will just hide the |
|
501 | 501 | # big files from the front-end |
|
502 | 502 | diff_limit = c.visual.cut_off_limit_diff |
|
503 | 503 | file_limit = c.visual.cut_off_limit_file |
|
504 | 504 | |
|
505 | 505 | c.missing_commits = False |
|
506 | 506 | if (c.missing_requirements |
|
507 | 507 | or isinstance(source_commit, EmptyCommit) |
|
508 | 508 | or source_commit == target_commit): |
|
509 | 509 | |
|
510 | 510 | c.missing_commits = True |
|
511 | 511 | else: |
|
512 | 512 | c.inline_comments = display_inline_comments |
|
513 | 513 | |
|
514 | 514 | has_proper_diff_cache = cached_diff and cached_diff.get('commits') |
|
515 | 515 | if not force_recache and has_proper_diff_cache: |
|
516 | 516 | c.diffset = cached_diff['diff'] |
|
517 | 517 | (ancestor_commit, commit_cache, missing_requirements, |
|
518 | 518 | source_commit, target_commit) = cached_diff['commits'] |
|
519 | 519 | else: |
|
520 | 520 | c.diffset = self._get_diffset( |
|
521 | 521 | c.source_repo.repo_name, commits_source_repo, |
|
522 | 522 | source_ref_id, target_ref_id, |
|
523 | 523 | target_commit, source_commit, |
|
524 | 524 | diff_limit, file_limit, c.fulldiff) |
|
525 | 525 | |
|
526 | 526 | # save cached diff |
|
527 | 527 | if caching_enabled: |
|
528 | 528 | cache_diff(cache_file_path, c.diffset, diff_commit_cache) |
|
529 | 529 | |
|
530 | 530 | c.limited_diff = c.diffset.limited_diff |
|
531 | 531 | |
|
532 | 532 | # calculate removed files that are bound to comments |
|
533 | 533 | comment_deleted_files = [ |
|
534 | 534 | fname for fname in display_inline_comments |
|
535 | 535 | if fname not in c.diffset.file_stats] |
|
536 | 536 | |
|
537 | 537 | c.deleted_files_comments = collections.defaultdict(dict) |
|
538 | 538 | for fname, per_line_comments in display_inline_comments.items(): |
|
539 | 539 | if fname in comment_deleted_files: |
|
540 | 540 | c.deleted_files_comments[fname]['stats'] = 0 |
|
541 | 541 | c.deleted_files_comments[fname]['comments'] = list() |
|
542 | 542 | for lno, comments in per_line_comments.items(): |
|
543 | 543 | c.deleted_files_comments[fname]['comments'].extend( |
|
544 | 544 | comments) |
|
545 | 545 | |
|
546 | 546 | # this is a hack to properly display links, when creating PR, the |
|
547 | 547 | # compare view and others uses different notation, and |
|
548 | 548 | # compare_commits.mako renders links based on the target_repo. |
|
549 | 549 | # We need to swap that here to generate it properly on the html side |
|
550 | 550 | c.target_repo = c.source_repo |
|
551 | 551 | |
|
552 | 552 | c.commit_statuses = ChangesetStatus.STATUSES |
|
553 | 553 | |
|
554 | 554 | c.show_version_changes = not pr_closed |
|
555 | 555 | if c.show_version_changes: |
|
556 | 556 | cur_obj = pull_request_at_ver |
|
557 | 557 | prev_obj = prev_pull_request_at_ver |
|
558 | 558 | |
|
559 | 559 | old_commit_ids = prev_obj.revisions |
|
560 | 560 | new_commit_ids = cur_obj.revisions |
|
561 | 561 | commit_changes = PullRequestModel()._calculate_commit_id_changes( |
|
562 | 562 | old_commit_ids, new_commit_ids) |
|
563 | 563 | c.commit_changes_summary = commit_changes |
|
564 | 564 | |
|
565 | 565 | # calculate the diff for commits between versions |
|
566 | 566 | c.commit_changes = [] |
|
567 | 567 | mark = lambda cs, fw: list( |
|
568 | 568 | h.itertools.izip_longest([], cs, fillvalue=fw)) |
|
569 | 569 | for c_type, raw_id in mark(commit_changes.added, 'a') \ |
|
570 | 570 | + mark(commit_changes.removed, 'r') \ |
|
571 | 571 | + mark(commit_changes.common, 'c'): |
|
572 | 572 | |
|
573 | 573 | if raw_id in commit_cache: |
|
574 | 574 | commit = commit_cache[raw_id] |
|
575 | 575 | else: |
|
576 | 576 | try: |
|
577 | 577 | commit = commits_source_repo.get_commit(raw_id) |
|
578 | 578 | except CommitDoesNotExistError: |
|
579 | 579 | # in case we fail extracting still use "dummy" commit |
|
580 | 580 | # for display in commit diff |
|
581 | 581 | commit = h.AttributeDict( |
|
582 | 582 | {'raw_id': raw_id, |
|
583 | 583 | 'message': 'EMPTY or MISSING COMMIT'}) |
|
584 | 584 | c.commit_changes.append([c_type, commit]) |
|
585 | 585 | |
|
586 | 586 | # current user review statuses for each version |
|
587 | 587 | c.review_versions = {} |
|
588 | 588 | if self._rhodecode_user.user_id in allowed_reviewers: |
|
589 | 589 | for co in general_comments: |
|
590 | 590 | if co.author.user_id == self._rhodecode_user.user_id: |
|
591 | 591 | status = co.status_change |
|
592 | 592 | if status: |
|
593 | 593 | _ver_pr = status[0].comment.pull_request_version_id |
|
594 | 594 | c.review_versions[_ver_pr] = status[0] |
|
595 | 595 | |
|
596 | 596 | return self._get_template_context(c) |
|
597 | 597 | |
|
598 | 598 | def get_commits( |
|
599 | 599 | self, commits_source_repo, pull_request_at_ver, source_commit, |
|
600 | 600 | source_ref_id, source_scm, target_commit, target_ref_id, target_scm): |
|
601 | 601 | commit_cache = collections.OrderedDict() |
|
602 | 602 | missing_requirements = False |
|
603 | 603 | try: |
|
604 | 604 | pre_load = ["author", "branch", "date", "message"] |
|
605 | 605 | show_revs = pull_request_at_ver.revisions |
|
606 | 606 | for rev in show_revs: |
|
607 | 607 | comm = commits_source_repo.get_commit( |
|
608 | 608 | commit_id=rev, pre_load=pre_load) |
|
609 | 609 | commit_cache[comm.raw_id] = comm |
|
610 | 610 | |
|
611 | 611 | # Order here matters, we first need to get target, and then |
|
612 | 612 | # the source |
|
613 | 613 | target_commit = commits_source_repo.get_commit( |
|
614 | 614 | commit_id=safe_str(target_ref_id)) |
|
615 | 615 | |
|
616 | 616 | source_commit = commits_source_repo.get_commit( |
|
617 | 617 | commit_id=safe_str(source_ref_id)) |
|
618 | 618 | except CommitDoesNotExistError: |
|
619 | 619 | log.warning( |
|
620 | 620 | 'Failed to get commit from `{}` repo'.format( |
|
621 | 621 | commits_source_repo), exc_info=True) |
|
622 | 622 | except RepositoryRequirementError: |
|
623 | 623 | log.warning( |
|
624 | 624 | 'Failed to get all required data from repo', exc_info=True) |
|
625 | 625 | missing_requirements = True |
|
626 | 626 | ancestor_commit = None |
|
627 | 627 | try: |
|
628 | 628 | ancestor_id = source_scm.get_common_ancestor( |
|
629 | 629 | source_commit.raw_id, target_commit.raw_id, target_scm) |
|
630 | 630 | ancestor_commit = source_scm.get_commit(ancestor_id) |
|
631 | 631 | except Exception: |
|
632 | 632 | ancestor_commit = None |
|
633 | 633 | return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit |
|
634 | 634 | |
|
635 | 635 | def assure_not_empty_repo(self): |
|
636 | 636 | _ = self.request.translate |
|
637 | 637 | |
|
638 | 638 | try: |
|
639 | 639 | self.db_repo.scm_instance().get_commit() |
|
640 | 640 | except EmptyRepositoryError: |
|
641 | 641 | h.flash(h.literal(_('There are no commits yet')), |
|
642 | 642 | category='warning') |
|
643 | 643 | raise HTTPFound( |
|
644 | 644 | h.route_path('repo_summary', repo_name=self.db_repo.repo_name)) |
|
645 | 645 | |
|
646 | 646 | @LoginRequired() |
|
647 | 647 | @NotAnonymous() |
|
648 | 648 | @HasRepoPermissionAnyDecorator( |
|
649 | 649 | 'repository.read', 'repository.write', 'repository.admin') |
|
650 | 650 | @view_config( |
|
651 | 651 | route_name='pullrequest_new', request_method='GET', |
|
652 | 652 | renderer='rhodecode:templates/pullrequests/pullrequest.mako') |
|
653 | 653 | def pull_request_new(self): |
|
654 | 654 | _ = self.request.translate |
|
655 | 655 | c = self.load_default_context() |
|
656 | 656 | |
|
657 | 657 | self.assure_not_empty_repo() |
|
658 | 658 | source_repo = self.db_repo |
|
659 | 659 | |
|
660 | 660 | commit_id = self.request.GET.get('commit') |
|
661 | 661 | branch_ref = self.request.GET.get('branch') |
|
662 | 662 | bookmark_ref = self.request.GET.get('bookmark') |
|
663 | 663 | |
|
664 | 664 | try: |
|
665 | 665 | source_repo_data = PullRequestModel().generate_repo_data( |
|
666 | 666 | source_repo, commit_id=commit_id, |
|
667 | 667 | branch=branch_ref, bookmark=bookmark_ref, |
|
668 | 668 | translator=self.request.translate) |
|
669 | 669 | except CommitDoesNotExistError as e: |
|
670 | 670 | log.exception(e) |
|
671 | 671 | h.flash(_('Commit does not exist'), 'error') |
|
672 | 672 | raise HTTPFound( |
|
673 | 673 | h.route_path('pullrequest_new', repo_name=source_repo.repo_name)) |
|
674 | 674 | |
|
675 | 675 | default_target_repo = source_repo |
|
676 | 676 | |
|
677 | 677 | if source_repo.parent: |
|
678 | 678 | parent_vcs_obj = source_repo.parent.scm_instance() |
|
679 | 679 | if parent_vcs_obj and not parent_vcs_obj.is_empty(): |
|
680 | 680 | # change default if we have a parent repo |
|
681 | 681 | default_target_repo = source_repo.parent |
|
682 | 682 | |
|
683 | 683 | target_repo_data = PullRequestModel().generate_repo_data( |
|
684 | 684 | default_target_repo, translator=self.request.translate) |
|
685 | 685 | |
|
686 | 686 | selected_source_ref = source_repo_data['refs']['selected_ref'] |
|
687 | 687 | title_source_ref = '' |
|
688 | 688 | if selected_source_ref: |
|
689 | 689 | title_source_ref = selected_source_ref.split(':', 2)[1] |
|
690 | 690 | c.default_title = PullRequestModel().generate_pullrequest_title( |
|
691 | 691 | source=source_repo.repo_name, |
|
692 | 692 | source_ref=title_source_ref, |
|
693 | 693 | target=default_target_repo.repo_name |
|
694 | 694 | ) |
|
695 | 695 | |
|
696 | 696 | c.default_repo_data = { |
|
697 | 697 | 'source_repo_name': source_repo.repo_name, |
|
698 | 698 | 'source_refs_json': json.dumps(source_repo_data), |
|
699 | 699 | 'target_repo_name': default_target_repo.repo_name, |
|
700 | 700 | 'target_refs_json': json.dumps(target_repo_data), |
|
701 | 701 | } |
|
702 | 702 | c.default_source_ref = selected_source_ref |
|
703 | 703 | |
|
704 | 704 | return self._get_template_context(c) |
|
705 | 705 | |
|
706 | 706 | @LoginRequired() |
|
707 | 707 | @NotAnonymous() |
|
708 | 708 | @HasRepoPermissionAnyDecorator( |
|
709 | 709 | 'repository.read', 'repository.write', 'repository.admin') |
|
710 | 710 | @view_config( |
|
711 | 711 | route_name='pullrequest_repo_refs', request_method='GET', |
|
712 | 712 | renderer='json_ext', xhr=True) |
|
713 | 713 | def pull_request_repo_refs(self): |
|
714 | 714 | self.load_default_context() |
|
715 | 715 | target_repo_name = self.request.matchdict['target_repo_name'] |
|
716 | 716 | repo = Repository.get_by_repo_name(target_repo_name) |
|
717 | 717 | if not repo: |
|
718 | 718 | raise HTTPNotFound() |
|
719 | 719 | |
|
720 | 720 | target_perm = HasRepoPermissionAny( |
|
721 | 721 | 'repository.read', 'repository.write', 'repository.admin')( |
|
722 | 722 | target_repo_name) |
|
723 | 723 | if not target_perm: |
|
724 | 724 | raise HTTPNotFound() |
|
725 | 725 | |
|
726 | 726 | return PullRequestModel().generate_repo_data( |
|
727 | 727 | repo, translator=self.request.translate) |
|
728 | 728 | |
|
729 | 729 | @LoginRequired() |
|
730 | 730 | @NotAnonymous() |
|
731 | 731 | @HasRepoPermissionAnyDecorator( |
|
732 | 732 | 'repository.read', 'repository.write', 'repository.admin') |
|
733 | 733 | @view_config( |
|
734 | 734 | route_name='pullrequest_repo_destinations', request_method='GET', |
|
735 | 735 | renderer='json_ext', xhr=True) |
|
736 | 736 | def pull_request_repo_destinations(self): |
|
737 | 737 | _ = self.request.translate |
|
738 | 738 | filter_query = self.request.GET.get('query') |
|
739 | 739 | |
|
740 | 740 | query = Repository.query() \ |
|
741 | 741 | .order_by(func.length(Repository.repo_name)) \ |
|
742 | 742 | .filter( |
|
743 | 743 | or_(Repository.repo_name == self.db_repo.repo_name, |
|
744 | 744 | Repository.fork_id == self.db_repo.repo_id)) |
|
745 | 745 | |
|
746 | 746 | if filter_query: |
|
747 | 747 | ilike_expression = u'%{}%'.format(safe_unicode(filter_query)) |
|
748 | 748 | query = query.filter( |
|
749 | 749 | Repository.repo_name.ilike(ilike_expression)) |
|
750 | 750 | |
|
751 | 751 | add_parent = False |
|
752 | 752 | if self.db_repo.parent: |
|
753 | 753 | if filter_query in self.db_repo.parent.repo_name: |
|
754 | 754 | parent_vcs_obj = self.db_repo.parent.scm_instance() |
|
755 | 755 | if parent_vcs_obj and not parent_vcs_obj.is_empty(): |
|
756 | 756 | add_parent = True |
|
757 | 757 | |
|
758 | 758 | limit = 20 - 1 if add_parent else 20 |
|
759 | 759 | all_repos = query.limit(limit).all() |
|
760 | 760 | if add_parent: |
|
761 | 761 | all_repos += [self.db_repo.parent] |
|
762 | 762 | |
|
763 | 763 | repos = [] |
|
764 | 764 | for obj in ScmModel().get_repos(all_repos): |
|
765 | 765 | repos.append({ |
|
766 | 766 | 'id': obj['name'], |
|
767 | 767 | 'text': obj['name'], |
|
768 | 768 | 'type': 'repo', |
|
769 |
' |
|
|
769 | 'repo_id': obj['dbrepo']['repo_id'], | |
|
770 | 'repo_type': obj['dbrepo']['repo_type'], | |
|
771 | 'private': obj['dbrepo']['private'], | |
|
772 | ||
|
770 | 773 | }) |
|
771 | 774 | |
|
772 | 775 | data = { |
|
773 | 776 | 'more': False, |
|
774 | 777 | 'results': [{ |
|
775 | 778 | 'text': _('Repositories'), |
|
776 | 779 | 'children': repos |
|
777 | 780 | }] if repos else [] |
|
778 | 781 | } |
|
779 | 782 | return data |
|
780 | 783 | |
|
781 | 784 | @LoginRequired() |
|
782 | 785 | @NotAnonymous() |
|
783 | 786 | @HasRepoPermissionAnyDecorator( |
|
784 | 787 | 'repository.read', 'repository.write', 'repository.admin') |
|
785 | 788 | @CSRFRequired() |
|
786 | 789 | @view_config( |
|
787 | 790 | route_name='pullrequest_create', request_method='POST', |
|
788 | 791 | renderer=None) |
|
789 | 792 | def pull_request_create(self): |
|
790 | 793 | _ = self.request.translate |
|
791 | 794 | self.assure_not_empty_repo() |
|
792 | 795 | self.load_default_context() |
|
793 | 796 | |
|
794 | 797 | controls = peppercorn.parse(self.request.POST.items()) |
|
795 | 798 | |
|
796 | 799 | try: |
|
797 | 800 | form = PullRequestForm( |
|
798 | 801 | self.request.translate, self.db_repo.repo_id)() |
|
799 | 802 | _form = form.to_python(controls) |
|
800 | 803 | except formencode.Invalid as errors: |
|
801 | 804 | if errors.error_dict.get('revisions'): |
|
802 | 805 | msg = 'Revisions: %s' % errors.error_dict['revisions'] |
|
803 | 806 | elif errors.error_dict.get('pullrequest_title'): |
|
804 | 807 | msg = errors.error_dict.get('pullrequest_title') |
|
805 | 808 | else: |
|
806 | 809 | msg = _('Error creating pull request: {}').format(errors) |
|
807 | 810 | log.exception(msg) |
|
808 | 811 | h.flash(msg, 'error') |
|
809 | 812 | |
|
810 | 813 | # would rather just go back to form ... |
|
811 | 814 | raise HTTPFound( |
|
812 | 815 | h.route_path('pullrequest_new', repo_name=self.db_repo_name)) |
|
813 | 816 | |
|
814 | 817 | source_repo = _form['source_repo'] |
|
815 | 818 | source_ref = _form['source_ref'] |
|
816 | 819 | target_repo = _form['target_repo'] |
|
817 | 820 | target_ref = _form['target_ref'] |
|
818 | 821 | commit_ids = _form['revisions'][::-1] |
|
819 | 822 | |
|
820 | 823 | # find the ancestor for this pr |
|
821 | 824 | source_db_repo = Repository.get_by_repo_name(_form['source_repo']) |
|
822 | 825 | target_db_repo = Repository.get_by_repo_name(_form['target_repo']) |
|
823 | 826 | |
|
824 | 827 | # re-check permissions again here |
|
825 | 828 | # source_repo we must have read permissions |
|
826 | 829 | |
|
827 | 830 | source_perm = HasRepoPermissionAny( |
|
828 | 831 | 'repository.read', |
|
829 | 832 | 'repository.write', 'repository.admin')(source_db_repo.repo_name) |
|
830 | 833 | if not source_perm: |
|
831 | 834 | msg = _('Not Enough permissions to source repo `{}`.'.format( |
|
832 | 835 | source_db_repo.repo_name)) |
|
833 | 836 | h.flash(msg, category='error') |
|
834 | 837 | # copy the args back to redirect |
|
835 | 838 | org_query = self.request.GET.mixed() |
|
836 | 839 | raise HTTPFound( |
|
837 | 840 | h.route_path('pullrequest_new', repo_name=self.db_repo_name, |
|
838 | 841 | _query=org_query)) |
|
839 | 842 | |
|
840 | 843 | # target repo we must have read permissions, and also later on |
|
841 | 844 | # we want to check branch permissions here |
|
842 | 845 | target_perm = HasRepoPermissionAny( |
|
843 | 846 | 'repository.read', |
|
844 | 847 | 'repository.write', 'repository.admin')(target_db_repo.repo_name) |
|
845 | 848 | if not target_perm: |
|
846 | 849 | msg = _('Not Enough permissions to target repo `{}`.'.format( |
|
847 | 850 | target_db_repo.repo_name)) |
|
848 | 851 | h.flash(msg, category='error') |
|
849 | 852 | # copy the args back to redirect |
|
850 | 853 | org_query = self.request.GET.mixed() |
|
851 | 854 | raise HTTPFound( |
|
852 | 855 | h.route_path('pullrequest_new', repo_name=self.db_repo_name, |
|
853 | 856 | _query=org_query)) |
|
854 | 857 | |
|
855 | 858 | source_scm = source_db_repo.scm_instance() |
|
856 | 859 | target_scm = target_db_repo.scm_instance() |
|
857 | 860 | |
|
858 | 861 | source_commit = source_scm.get_commit(source_ref.split(':')[-1]) |
|
859 | 862 | target_commit = target_scm.get_commit(target_ref.split(':')[-1]) |
|
860 | 863 | |
|
861 | 864 | ancestor = source_scm.get_common_ancestor( |
|
862 | 865 | source_commit.raw_id, target_commit.raw_id, target_scm) |
|
863 | 866 | |
|
864 | 867 | target_ref_type, target_ref_name, __ = _form['target_ref'].split(':') |
|
865 | 868 | target_ref = ':'.join((target_ref_type, target_ref_name, ancestor)) |
|
866 | 869 | |
|
867 | 870 | pullrequest_title = _form['pullrequest_title'] |
|
868 | 871 | title_source_ref = source_ref.split(':', 2)[1] |
|
869 | 872 | if not pullrequest_title: |
|
870 | 873 | pullrequest_title = PullRequestModel().generate_pullrequest_title( |
|
871 | 874 | source=source_repo, |
|
872 | 875 | source_ref=title_source_ref, |
|
873 | 876 | target=target_repo |
|
874 | 877 | ) |
|
875 | 878 | |
|
876 | 879 | description = _form['pullrequest_desc'] |
|
877 | 880 | |
|
878 | 881 | get_default_reviewers_data, validate_default_reviewers = \ |
|
879 | 882 | PullRequestModel().get_reviewer_functions() |
|
880 | 883 | |
|
881 | 884 | # recalculate reviewers logic, to make sure we can validate this |
|
882 | 885 | reviewer_rules = get_default_reviewers_data( |
|
883 | 886 | self._rhodecode_db_user, source_db_repo, |
|
884 | 887 | source_commit, target_db_repo, target_commit) |
|
885 | 888 | |
|
886 | 889 | given_reviewers = _form['review_members'] |
|
887 | 890 | reviewers = validate_default_reviewers(given_reviewers, reviewer_rules) |
|
888 | 891 | |
|
889 | 892 | try: |
|
890 | 893 | pull_request = PullRequestModel().create( |
|
891 | 894 | self._rhodecode_user.user_id, source_repo, source_ref, |
|
892 | 895 | target_repo, target_ref, commit_ids, reviewers, |
|
893 | 896 | pullrequest_title, description, reviewer_rules |
|
894 | 897 | ) |
|
895 | 898 | Session().commit() |
|
896 | 899 | |
|
897 | 900 | h.flash(_('Successfully opened new pull request'), |
|
898 | 901 | category='success') |
|
899 | 902 | except Exception: |
|
900 | 903 | msg = _('Error occurred during creation of this pull request.') |
|
901 | 904 | log.exception(msg) |
|
902 | 905 | h.flash(msg, category='error') |
|
903 | 906 | |
|
904 | 907 | # copy the args back to redirect |
|
905 | 908 | org_query = self.request.GET.mixed() |
|
906 | 909 | raise HTTPFound( |
|
907 | 910 | h.route_path('pullrequest_new', repo_name=self.db_repo_name, |
|
908 | 911 | _query=org_query)) |
|
909 | 912 | |
|
910 | 913 | raise HTTPFound( |
|
911 | 914 | h.route_path('pullrequest_show', repo_name=target_repo, |
|
912 | 915 | pull_request_id=pull_request.pull_request_id)) |
|
913 | 916 | |
|
914 | 917 | @LoginRequired() |
|
915 | 918 | @NotAnonymous() |
|
916 | 919 | @HasRepoPermissionAnyDecorator( |
|
917 | 920 | 'repository.read', 'repository.write', 'repository.admin') |
|
918 | 921 | @CSRFRequired() |
|
919 | 922 | @view_config( |
|
920 | 923 | route_name='pullrequest_update', request_method='POST', |
|
921 | 924 | renderer='json_ext') |
|
922 | 925 | def pull_request_update(self): |
|
923 | 926 | pull_request = PullRequest.get_or_404( |
|
924 | 927 | self.request.matchdict['pull_request_id']) |
|
925 | 928 | _ = self.request.translate |
|
926 | 929 | |
|
927 | 930 | self.load_default_context() |
|
928 | 931 | |
|
929 | 932 | if pull_request.is_closed(): |
|
930 | 933 | log.debug('update: forbidden because pull request is closed') |
|
931 | 934 | msg = _(u'Cannot update closed pull requests.') |
|
932 | 935 | h.flash(msg, category='error') |
|
933 | 936 | return True |
|
934 | 937 | |
|
935 | 938 | # only owner or admin can update it |
|
936 | 939 | allowed_to_update = PullRequestModel().check_user_update( |
|
937 | 940 | pull_request, self._rhodecode_user) |
|
938 | 941 | if allowed_to_update: |
|
939 | 942 | controls = peppercorn.parse(self.request.POST.items()) |
|
940 | 943 | |
|
941 | 944 | if 'review_members' in controls: |
|
942 | 945 | self._update_reviewers( |
|
943 | 946 | pull_request, controls['review_members'], |
|
944 | 947 | pull_request.reviewer_data) |
|
945 | 948 | elif str2bool(self.request.POST.get('update_commits', 'false')): |
|
946 | 949 | self._update_commits(pull_request) |
|
947 | 950 | elif str2bool(self.request.POST.get('edit_pull_request', 'false')): |
|
948 | 951 | self._edit_pull_request(pull_request) |
|
949 | 952 | else: |
|
950 | 953 | raise HTTPBadRequest() |
|
951 | 954 | return True |
|
952 | 955 | raise HTTPForbidden() |
|
953 | 956 | |
|
954 | 957 | def _edit_pull_request(self, pull_request): |
|
955 | 958 | _ = self.request.translate |
|
956 | 959 | try: |
|
957 | 960 | PullRequestModel().edit( |
|
958 | 961 | pull_request, self.request.POST.get('title'), |
|
959 | 962 | self.request.POST.get('description'), self._rhodecode_user) |
|
960 | 963 | except ValueError: |
|
961 | 964 | msg = _(u'Cannot update closed pull requests.') |
|
962 | 965 | h.flash(msg, category='error') |
|
963 | 966 | return |
|
964 | 967 | else: |
|
965 | 968 | Session().commit() |
|
966 | 969 | |
|
967 | 970 | msg = _(u'Pull request title & description updated.') |
|
968 | 971 | h.flash(msg, category='success') |
|
969 | 972 | return |
|
970 | 973 | |
|
971 | 974 | def _update_commits(self, pull_request): |
|
972 | 975 | _ = self.request.translate |
|
973 | 976 | resp = PullRequestModel().update_commits(pull_request) |
|
974 | 977 | |
|
975 | 978 | if resp.executed: |
|
976 | 979 | |
|
977 | 980 | if resp.target_changed and resp.source_changed: |
|
978 | 981 | changed = 'target and source repositories' |
|
979 | 982 | elif resp.target_changed and not resp.source_changed: |
|
980 | 983 | changed = 'target repository' |
|
981 | 984 | elif not resp.target_changed and resp.source_changed: |
|
982 | 985 | changed = 'source repository' |
|
983 | 986 | else: |
|
984 | 987 | changed = 'nothing' |
|
985 | 988 | |
|
986 | 989 | msg = _( |
|
987 | 990 | u'Pull request updated to "{source_commit_id}" with ' |
|
988 | 991 | u'{count_added} added, {count_removed} removed commits. ' |
|
989 | 992 | u'Source of changes: {change_source}') |
|
990 | 993 | msg = msg.format( |
|
991 | 994 | source_commit_id=pull_request.source_ref_parts.commit_id, |
|
992 | 995 | count_added=len(resp.changes.added), |
|
993 | 996 | count_removed=len(resp.changes.removed), |
|
994 | 997 | change_source=changed) |
|
995 | 998 | h.flash(msg, category='success') |
|
996 | 999 | |
|
997 | 1000 | channel = '/repo${}$/pr/{}'.format( |
|
998 | 1001 | pull_request.target_repo.repo_name, |
|
999 | 1002 | pull_request.pull_request_id) |
|
1000 | 1003 | message = msg + ( |
|
1001 | 1004 | ' - <a onclick="window.location.reload()">' |
|
1002 | 1005 | '<strong>{}</strong></a>'.format(_('Reload page'))) |
|
1003 | 1006 | channelstream.post_message( |
|
1004 | 1007 | channel, message, self._rhodecode_user.username, |
|
1005 | 1008 | registry=self.request.registry) |
|
1006 | 1009 | else: |
|
1007 | 1010 | msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason] |
|
1008 | 1011 | warning_reasons = [ |
|
1009 | 1012 | UpdateFailureReason.NO_CHANGE, |
|
1010 | 1013 | UpdateFailureReason.WRONG_REF_TYPE, |
|
1011 | 1014 | ] |
|
1012 | 1015 | category = 'warning' if resp.reason in warning_reasons else 'error' |
|
1013 | 1016 | h.flash(msg, category=category) |
|
1014 | 1017 | |
|
1015 | 1018 | @LoginRequired() |
|
1016 | 1019 | @NotAnonymous() |
|
1017 | 1020 | @HasRepoPermissionAnyDecorator( |
|
1018 | 1021 | 'repository.read', 'repository.write', 'repository.admin') |
|
1019 | 1022 | @CSRFRequired() |
|
1020 | 1023 | @view_config( |
|
1021 | 1024 | route_name='pullrequest_merge', request_method='POST', |
|
1022 | 1025 | renderer='json_ext') |
|
1023 | 1026 | def pull_request_merge(self): |
|
1024 | 1027 | """ |
|
1025 | 1028 | Merge will perform a server-side merge of the specified |
|
1026 | 1029 | pull request, if the pull request is approved and mergeable. |
|
1027 | 1030 | After successful merging, the pull request is automatically |
|
1028 | 1031 | closed, with a relevant comment. |
|
1029 | 1032 | """ |
|
1030 | 1033 | pull_request = PullRequest.get_or_404( |
|
1031 | 1034 | self.request.matchdict['pull_request_id']) |
|
1032 | 1035 | |
|
1033 | 1036 | self.load_default_context() |
|
1034 | 1037 | check = MergeCheck.validate(pull_request, self._rhodecode_db_user, |
|
1035 | 1038 | translator=self.request.translate) |
|
1036 | 1039 | merge_possible = not check.failed |
|
1037 | 1040 | |
|
1038 | 1041 | for err_type, error_msg in check.errors: |
|
1039 | 1042 | h.flash(error_msg, category=err_type) |
|
1040 | 1043 | |
|
1041 | 1044 | if merge_possible: |
|
1042 | 1045 | log.debug("Pre-conditions checked, trying to merge.") |
|
1043 | 1046 | extras = vcs_operation_context( |
|
1044 | 1047 | self.request.environ, repo_name=pull_request.target_repo.repo_name, |
|
1045 | 1048 | username=self._rhodecode_db_user.username, action='push', |
|
1046 | 1049 | scm=pull_request.target_repo.repo_type) |
|
1047 | 1050 | self._merge_pull_request( |
|
1048 | 1051 | pull_request, self._rhodecode_db_user, extras) |
|
1049 | 1052 | else: |
|
1050 | 1053 | log.debug("Pre-conditions failed, NOT merging.") |
|
1051 | 1054 | |
|
1052 | 1055 | raise HTTPFound( |
|
1053 | 1056 | h.route_path('pullrequest_show', |
|
1054 | 1057 | repo_name=pull_request.target_repo.repo_name, |
|
1055 | 1058 | pull_request_id=pull_request.pull_request_id)) |
|
1056 | 1059 | |
|
1057 | 1060 | def _merge_pull_request(self, pull_request, user, extras): |
|
1058 | 1061 | _ = self.request.translate |
|
1059 | 1062 | merge_resp = PullRequestModel().merge(pull_request, user, extras=extras) |
|
1060 | 1063 | |
|
1061 | 1064 | if merge_resp.executed: |
|
1062 | 1065 | log.debug("The merge was successful, closing the pull request.") |
|
1063 | 1066 | PullRequestModel().close_pull_request( |
|
1064 | 1067 | pull_request.pull_request_id, user) |
|
1065 | 1068 | Session().commit() |
|
1066 | 1069 | msg = _('Pull request was successfully merged and closed.') |
|
1067 | 1070 | h.flash(msg, category='success') |
|
1068 | 1071 | else: |
|
1069 | 1072 | log.debug( |
|
1070 | 1073 | "The merge was not successful. Merge response: %s", |
|
1071 | 1074 | merge_resp) |
|
1072 | 1075 | msg = PullRequestModel().merge_status_message( |
|
1073 | 1076 | merge_resp.failure_reason) |
|
1074 | 1077 | h.flash(msg, category='error') |
|
1075 | 1078 | |
|
1076 | 1079 | def _update_reviewers(self, pull_request, review_members, reviewer_rules): |
|
1077 | 1080 | _ = self.request.translate |
|
1078 | 1081 | get_default_reviewers_data, validate_default_reviewers = \ |
|
1079 | 1082 | PullRequestModel().get_reviewer_functions() |
|
1080 | 1083 | |
|
1081 | 1084 | try: |
|
1082 | 1085 | reviewers = validate_default_reviewers(review_members, reviewer_rules) |
|
1083 | 1086 | except ValueError as e: |
|
1084 | 1087 | log.error('Reviewers Validation: {}'.format(e)) |
|
1085 | 1088 | h.flash(e, category='error') |
|
1086 | 1089 | return |
|
1087 | 1090 | |
|
1088 | 1091 | PullRequestModel().update_reviewers( |
|
1089 | 1092 | pull_request, reviewers, self._rhodecode_user) |
|
1090 | 1093 | h.flash(_('Pull request reviewers updated.'), category='success') |
|
1091 | 1094 | Session().commit() |
|
1092 | 1095 | |
|
1093 | 1096 | @LoginRequired() |
|
1094 | 1097 | @NotAnonymous() |
|
1095 | 1098 | @HasRepoPermissionAnyDecorator( |
|
1096 | 1099 | 'repository.read', 'repository.write', 'repository.admin') |
|
1097 | 1100 | @CSRFRequired() |
|
1098 | 1101 | @view_config( |
|
1099 | 1102 | route_name='pullrequest_delete', request_method='POST', |
|
1100 | 1103 | renderer='json_ext') |
|
1101 | 1104 | def pull_request_delete(self): |
|
1102 | 1105 | _ = self.request.translate |
|
1103 | 1106 | |
|
1104 | 1107 | pull_request = PullRequest.get_or_404( |
|
1105 | 1108 | self.request.matchdict['pull_request_id']) |
|
1106 | 1109 | self.load_default_context() |
|
1107 | 1110 | |
|
1108 | 1111 | pr_closed = pull_request.is_closed() |
|
1109 | 1112 | allowed_to_delete = PullRequestModel().check_user_delete( |
|
1110 | 1113 | pull_request, self._rhodecode_user) and not pr_closed |
|
1111 | 1114 | |
|
1112 | 1115 | # only owner can delete it ! |
|
1113 | 1116 | if allowed_to_delete: |
|
1114 | 1117 | PullRequestModel().delete(pull_request, self._rhodecode_user) |
|
1115 | 1118 | Session().commit() |
|
1116 | 1119 | h.flash(_('Successfully deleted pull request'), |
|
1117 | 1120 | category='success') |
|
1118 | 1121 | raise HTTPFound(h.route_path('pullrequest_show_all', |
|
1119 | 1122 | repo_name=self.db_repo_name)) |
|
1120 | 1123 | |
|
1121 | 1124 | log.warning('user %s tried to delete pull request without access', |
|
1122 | 1125 | self._rhodecode_user) |
|
1123 | 1126 | raise HTTPNotFound() |
|
1124 | 1127 | |
|
1125 | 1128 | @LoginRequired() |
|
1126 | 1129 | @NotAnonymous() |
|
1127 | 1130 | @HasRepoPermissionAnyDecorator( |
|
1128 | 1131 | 'repository.read', 'repository.write', 'repository.admin') |
|
1129 | 1132 | @CSRFRequired() |
|
1130 | 1133 | @view_config( |
|
1131 | 1134 | route_name='pullrequest_comment_create', request_method='POST', |
|
1132 | 1135 | renderer='json_ext') |
|
1133 | 1136 | def pull_request_comment_create(self): |
|
1134 | 1137 | _ = self.request.translate |
|
1135 | 1138 | |
|
1136 | 1139 | pull_request = PullRequest.get_or_404( |
|
1137 | 1140 | self.request.matchdict['pull_request_id']) |
|
1138 | 1141 | pull_request_id = pull_request.pull_request_id |
|
1139 | 1142 | |
|
1140 | 1143 | if pull_request.is_closed(): |
|
1141 | 1144 | log.debug('comment: forbidden because pull request is closed') |
|
1142 | 1145 | raise HTTPForbidden() |
|
1143 | 1146 | |
|
1144 | 1147 | allowed_to_comment = PullRequestModel().check_user_comment( |
|
1145 | 1148 | pull_request, self._rhodecode_user) |
|
1146 | 1149 | if not allowed_to_comment: |
|
1147 | 1150 | log.debug( |
|
1148 | 1151 | 'comment: forbidden because pull request is from forbidden repo') |
|
1149 | 1152 | raise HTTPForbidden() |
|
1150 | 1153 | |
|
1151 | 1154 | c = self.load_default_context() |
|
1152 | 1155 | |
|
1153 | 1156 | status = self.request.POST.get('changeset_status', None) |
|
1154 | 1157 | text = self.request.POST.get('text') |
|
1155 | 1158 | comment_type = self.request.POST.get('comment_type') |
|
1156 | 1159 | resolves_comment_id = self.request.POST.get('resolves_comment_id', None) |
|
1157 | 1160 | close_pull_request = self.request.POST.get('close_pull_request') |
|
1158 | 1161 | |
|
1159 | 1162 | # the logic here should work like following, if we submit close |
|
1160 | 1163 | # pr comment, use `close_pull_request_with_comment` function |
|
1161 | 1164 | # else handle regular comment logic |
|
1162 | 1165 | |
|
1163 | 1166 | if close_pull_request: |
|
1164 | 1167 | # only owner or admin or person with write permissions |
|
1165 | 1168 | allowed_to_close = PullRequestModel().check_user_update( |
|
1166 | 1169 | pull_request, self._rhodecode_user) |
|
1167 | 1170 | if not allowed_to_close: |
|
1168 | 1171 | log.debug('comment: forbidden because not allowed to close ' |
|
1169 | 1172 | 'pull request %s', pull_request_id) |
|
1170 | 1173 | raise HTTPForbidden() |
|
1171 | 1174 | comment, status = PullRequestModel().close_pull_request_with_comment( |
|
1172 | 1175 | pull_request, self._rhodecode_user, self.db_repo, message=text) |
|
1173 | 1176 | Session().flush() |
|
1174 | 1177 | events.trigger( |
|
1175 | 1178 | events.PullRequestCommentEvent(pull_request, comment)) |
|
1176 | 1179 | |
|
1177 | 1180 | else: |
|
1178 | 1181 | # regular comment case, could be inline, or one with status. |
|
1179 | 1182 | # for that one we check also permissions |
|
1180 | 1183 | |
|
1181 | 1184 | allowed_to_change_status = PullRequestModel().check_user_change_status( |
|
1182 | 1185 | pull_request, self._rhodecode_user) |
|
1183 | 1186 | |
|
1184 | 1187 | if status and allowed_to_change_status: |
|
1185 | 1188 | message = (_('Status change %(transition_icon)s %(status)s') |
|
1186 | 1189 | % {'transition_icon': '>', |
|
1187 | 1190 | 'status': ChangesetStatus.get_status_lbl(status)}) |
|
1188 | 1191 | text = text or message |
|
1189 | 1192 | |
|
1190 | 1193 | comment = CommentsModel().create( |
|
1191 | 1194 | text=text, |
|
1192 | 1195 | repo=self.db_repo.repo_id, |
|
1193 | 1196 | user=self._rhodecode_user.user_id, |
|
1194 | 1197 | pull_request=pull_request, |
|
1195 | 1198 | f_path=self.request.POST.get('f_path'), |
|
1196 | 1199 | line_no=self.request.POST.get('line'), |
|
1197 | 1200 | status_change=(ChangesetStatus.get_status_lbl(status) |
|
1198 | 1201 | if status and allowed_to_change_status else None), |
|
1199 | 1202 | status_change_type=(status |
|
1200 | 1203 | if status and allowed_to_change_status else None), |
|
1201 | 1204 | comment_type=comment_type, |
|
1202 | 1205 | resolves_comment_id=resolves_comment_id |
|
1203 | 1206 | ) |
|
1204 | 1207 | |
|
1205 | 1208 | if allowed_to_change_status: |
|
1206 | 1209 | # calculate old status before we change it |
|
1207 | 1210 | old_calculated_status = pull_request.calculated_review_status() |
|
1208 | 1211 | |
|
1209 | 1212 | # get status if set ! |
|
1210 | 1213 | if status: |
|
1211 | 1214 | ChangesetStatusModel().set_status( |
|
1212 | 1215 | self.db_repo.repo_id, |
|
1213 | 1216 | status, |
|
1214 | 1217 | self._rhodecode_user.user_id, |
|
1215 | 1218 | comment, |
|
1216 | 1219 | pull_request=pull_request |
|
1217 | 1220 | ) |
|
1218 | 1221 | |
|
1219 | 1222 | Session().flush() |
|
1220 | 1223 | # this is somehow required to get access to some relationship |
|
1221 | 1224 | # loaded on comment |
|
1222 | 1225 | Session().refresh(comment) |
|
1223 | 1226 | |
|
1224 | 1227 | events.trigger( |
|
1225 | 1228 | events.PullRequestCommentEvent(pull_request, comment)) |
|
1226 | 1229 | |
|
1227 | 1230 | # we now calculate the status of pull request, and based on that |
|
1228 | 1231 | # calculation we set the commits status |
|
1229 | 1232 | calculated_status = pull_request.calculated_review_status() |
|
1230 | 1233 | if old_calculated_status != calculated_status: |
|
1231 | 1234 | PullRequestModel()._trigger_pull_request_hook( |
|
1232 | 1235 | pull_request, self._rhodecode_user, 'review_status_change') |
|
1233 | 1236 | |
|
1234 | 1237 | Session().commit() |
|
1235 | 1238 | |
|
1236 | 1239 | data = { |
|
1237 | 1240 | 'target_id': h.safeid(h.safe_unicode( |
|
1238 | 1241 | self.request.POST.get('f_path'))), |
|
1239 | 1242 | } |
|
1240 | 1243 | if comment: |
|
1241 | 1244 | c.co = comment |
|
1242 | 1245 | rendered_comment = render( |
|
1243 | 1246 | 'rhodecode:templates/changeset/changeset_comment_block.mako', |
|
1244 | 1247 | self._get_template_context(c), self.request) |
|
1245 | 1248 | |
|
1246 | 1249 | data.update(comment.get_dict()) |
|
1247 | 1250 | data.update({'rendered_text': rendered_comment}) |
|
1248 | 1251 | |
|
1249 | 1252 | return data |
|
1250 | 1253 | |
|
1251 | 1254 | @LoginRequired() |
|
1252 | 1255 | @NotAnonymous() |
|
1253 | 1256 | @HasRepoPermissionAnyDecorator( |
|
1254 | 1257 | 'repository.read', 'repository.write', 'repository.admin') |
|
1255 | 1258 | @CSRFRequired() |
|
1256 | 1259 | @view_config( |
|
1257 | 1260 | route_name='pullrequest_comment_delete', request_method='POST', |
|
1258 | 1261 | renderer='json_ext') |
|
1259 | 1262 | def pull_request_comment_delete(self): |
|
1260 | 1263 | pull_request = PullRequest.get_or_404( |
|
1261 | 1264 | self.request.matchdict['pull_request_id']) |
|
1262 | 1265 | |
|
1263 | 1266 | comment = ChangesetComment.get_or_404( |
|
1264 | 1267 | self.request.matchdict['comment_id']) |
|
1265 | 1268 | comment_id = comment.comment_id |
|
1266 | 1269 | |
|
1267 | 1270 | if pull_request.is_closed(): |
|
1268 | 1271 | log.debug('comment: forbidden because pull request is closed') |
|
1269 | 1272 | raise HTTPForbidden() |
|
1270 | 1273 | |
|
1271 | 1274 | if not comment: |
|
1272 | 1275 | log.debug('Comment with id:%s not found, skipping', comment_id) |
|
1273 | 1276 | # comment already deleted in another call probably |
|
1274 | 1277 | return True |
|
1275 | 1278 | |
|
1276 | 1279 | if comment.pull_request.is_closed(): |
|
1277 | 1280 | # don't allow deleting comments on closed pull request |
|
1278 | 1281 | raise HTTPForbidden() |
|
1279 | 1282 | |
|
1280 | 1283 | is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name) |
|
1281 | 1284 | super_admin = h.HasPermissionAny('hg.admin')() |
|
1282 | 1285 | comment_owner = comment.author.user_id == self._rhodecode_user.user_id |
|
1283 | 1286 | is_repo_comment = comment.repo.repo_name == self.db_repo_name |
|
1284 | 1287 | comment_repo_admin = is_repo_admin and is_repo_comment |
|
1285 | 1288 | |
|
1286 | 1289 | if super_admin or comment_owner or comment_repo_admin: |
|
1287 | 1290 | old_calculated_status = comment.pull_request.calculated_review_status() |
|
1288 | 1291 | CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user) |
|
1289 | 1292 | Session().commit() |
|
1290 | 1293 | calculated_status = comment.pull_request.calculated_review_status() |
|
1291 | 1294 | if old_calculated_status != calculated_status: |
|
1292 | 1295 | PullRequestModel()._trigger_pull_request_hook( |
|
1293 | 1296 | comment.pull_request, self._rhodecode_user, 'review_status_change') |
|
1294 | 1297 | return True |
|
1295 | 1298 | else: |
|
1296 | 1299 | log.warning('No permissions for user %s to delete comment_id: %s', |
|
1297 | 1300 | self._rhodecode_db_user, comment_id) |
|
1298 | 1301 | raise HTTPNotFound() |
@@ -1,644 +1,684 b'' | |||
|
1 | 1 | // navigation.less |
|
2 | 2 | // For use in RhodeCode applications; |
|
3 | 3 | // see style guide documentation for guidelines. |
|
4 | 4 | |
|
5 | 5 | // HEADER NAVIGATION |
|
6 | 6 | |
|
7 | 7 | .horizontal-list { |
|
8 | 8 | float: right; |
|
9 | 9 | display: block; |
|
10 | 10 | margin: 0; |
|
11 | 11 | padding: 0; |
|
12 | 12 | -webkit-padding-start: 0; |
|
13 | 13 | text-align: left; |
|
14 | 14 | font-size: @navigation-fontsize; |
|
15 | 15 | color: @grey6; |
|
16 | 16 | z-index:10; |
|
17 | 17 | |
|
18 | 18 | li { |
|
19 | 19 | line-height: 1em; |
|
20 | 20 | list-style-type: none; |
|
21 | 21 | |
|
22 | 22 | a { |
|
23 | 23 | padding: 0 .5em; |
|
24 | 24 | |
|
25 | 25 | &.menu_link_notifications { |
|
26 | 26 | .pill(7px,@rcblue); |
|
27 | 27 | display: inline; |
|
28 | 28 | margin: 0 7px 0 .7em; |
|
29 | 29 | font-size: @basefontsize; |
|
30 | 30 | color: white; |
|
31 | 31 | |
|
32 | 32 | &.empty { |
|
33 | 33 | background-color: @grey4; |
|
34 | 34 | } |
|
35 | 35 | |
|
36 | 36 | &:hover { |
|
37 | 37 | background-color: @rcdarkblue; |
|
38 | 38 | } |
|
39 | 39 | } |
|
40 | 40 | } |
|
41 | 41 | .pill_container { |
|
42 | 42 | margin: 1.25em 0px 0px 0px; |
|
43 | 43 | float: right; |
|
44 | 44 | } |
|
45 | 45 | |
|
46 | 46 | &#quick_login_li { |
|
47 | 47 | &:hover { |
|
48 | 48 | color: @grey5; |
|
49 | 49 | } |
|
50 | 50 | |
|
51 | 51 | a.menu_link_notifications { |
|
52 | 52 | color: white; |
|
53 | 53 | } |
|
54 | 54 | |
|
55 | 55 | .user { |
|
56 | 56 | padding-bottom: 10px; |
|
57 | 57 | } |
|
58 | 58 | |
|
59 | 59 | &.open { |
|
60 | 60 | .user { |
|
61 | 61 | border-bottom: 5px solid @rcblue; |
|
62 | 62 | } |
|
63 | 63 | } |
|
64 | 64 | } |
|
65 | 65 | |
|
66 | 66 | &:before { content: none; } |
|
67 | 67 | |
|
68 | 68 | &:last-child { |
|
69 | 69 | .menulabel { |
|
70 | 70 | padding-right: 0; |
|
71 | 71 | border-right: none; |
|
72 | 72 | |
|
73 | 73 | .show_more { |
|
74 | 74 | padding-right: 0; |
|
75 | 75 | } |
|
76 | 76 | } |
|
77 | 77 | |
|
78 | 78 | &> a { |
|
79 | 79 | border-bottom: none; |
|
80 | 80 | } |
|
81 | 81 | } |
|
82 | 82 | |
|
83 | 83 | &.active { |
|
84 | 84 | border-bottom: 5px solid @rcblue; |
|
85 | 85 | } |
|
86 | 86 | |
|
87 | 87 | &.open { |
|
88 | 88 | |
|
89 | 89 | a { |
|
90 | 90 | color: white; |
|
91 | 91 | } |
|
92 | 92 | } |
|
93 | 93 | |
|
94 | 94 | &:focus { |
|
95 | 95 | outline: none; |
|
96 | 96 | } |
|
97 | 97 | |
|
98 | 98 | ul li { |
|
99 | 99 | display: block; |
|
100 | 100 | |
|
101 | 101 | &:last-child> a { |
|
102 | 102 | border-bottom: none; |
|
103 | 103 | } |
|
104 | 104 | |
|
105 | 105 | ul li:last-child a { |
|
106 | 106 | /* we don't expect more then 3 levels of submenu and the third |
|
107 | 107 | level can have different html structure */ |
|
108 | 108 | border-bottom: none; |
|
109 | 109 | } |
|
110 | 110 | } |
|
111 | 111 | } |
|
112 | 112 | |
|
113 | 113 | > li { |
|
114 | 114 | float: left; |
|
115 | 115 | display: block; |
|
116 | 116 | padding: 0; |
|
117 | 117 | |
|
118 | 118 | > a, |
|
119 | 119 | &.has_select2 a { |
|
120 | 120 | display: block; |
|
121 | 121 | padding: 10px 0 2px; |
|
122 | 122 | |
|
123 | 123 | .show_more { |
|
124 | 124 | margin-top: -4px; |
|
125 | 125 | padding-right: .5em; |
|
126 | 126 | } |
|
127 | 127 | } |
|
128 | 128 | |
|
129 | 129 | .menulabel { |
|
130 | 130 | padding: 0 .5em; |
|
131 | 131 | line-height: 1em; |
|
132 | 132 | // for this specifically we do not use a variable |
|
133 | 133 | border-right: 1px solid @grey4; |
|
134 | 134 | } |
|
135 | 135 | |
|
136 | 136 | .pr_notifications { |
|
137 | 137 | padding-left: .5em; |
|
138 | 138 | } |
|
139 | 139 | |
|
140 | 140 | .pr_notifications + .menulabel { |
|
141 | 141 | display:inline; |
|
142 | 142 | padding-left: 0; |
|
143 | 143 | } |
|
144 | 144 | |
|
145 | 145 | &:hover, |
|
146 | 146 | &.open, |
|
147 | 147 | &.active { |
|
148 | 148 | a { |
|
149 | 149 | color: @grey1; |
|
150 | 150 | } |
|
151 | 151 | } |
|
152 | 152 | } |
|
153 | 153 | |
|
154 | 154 | pre { |
|
155 | 155 | margin: 0; |
|
156 | 156 | padding: 0; |
|
157 | 157 | } |
|
158 | 158 | |
|
159 | 159 | .select2-container, |
|
160 | 160 | .menulink.childs { |
|
161 | 161 | position: relative; |
|
162 | 162 | } |
|
163 | 163 | |
|
164 | 164 | #quick_login { |
|
165 | 165 | |
|
166 | 166 | li a { |
|
167 | 167 | padding: .5em 0; |
|
168 | 168 | border-bottom: none; |
|
169 | 169 | color: @grey2; |
|
170 | 170 | |
|
171 | 171 | &:hover { color: @grey1; } |
|
172 | 172 | } |
|
173 | 173 | |
|
174 | 174 | .show_more { |
|
175 | 175 | padding-left: .5em; |
|
176 | 176 | } |
|
177 | 177 | } |
|
178 | 178 | |
|
179 | 179 | #quick_login_link { |
|
180 | 180 | display: inline-block; |
|
181 | 181 | |
|
182 | 182 | .gravatar { |
|
183 | 183 | border: 1px solid @grey2; |
|
184 | 184 | } |
|
185 | 185 | |
|
186 | 186 | .gravatar-login { |
|
187 | 187 | height: 20px; |
|
188 | 188 | width: 20px; |
|
189 | 189 | margin: -8px 0; |
|
190 | 190 | padding: 0; |
|
191 | 191 | } |
|
192 | 192 | |
|
193 | 193 | &:hover .user { |
|
194 | 194 | color: @grey6; |
|
195 | 195 | } |
|
196 | 196 | } |
|
197 | 197 | } |
|
198 | 198 | .header .horizontal-list { |
|
199 | 199 | |
|
200 | 200 | li { |
|
201 | 201 | |
|
202 | 202 | &#quick_login_li { |
|
203 | 203 | padding-left: .5em; |
|
204 | 204 | |
|
205 | 205 | &:hover #quick_login_link { |
|
206 | 206 | color: inherit; |
|
207 | 207 | } |
|
208 | 208 | } |
|
209 | 209 | |
|
210 | 210 | &:before { content: none; } |
|
211 | 211 | } |
|
212 | 212 | |
|
213 | 213 | > li { |
|
214 | 214 | |
|
215 | 215 | a { |
|
216 | 216 | padding: 18px 0 12px 0; |
|
217 | 217 | color: @nav-grey; |
|
218 | 218 | |
|
219 | 219 | &.menu_link_notifications { |
|
220 | 220 | padding: 1px 8px; |
|
221 | 221 | } |
|
222 | 222 | } |
|
223 | 223 | |
|
224 | 224 | &:hover, |
|
225 | 225 | &.open, |
|
226 | 226 | &.active { |
|
227 | 227 | .pill_container a { |
|
228 | 228 | // don't select text for the pill container, it has it' own |
|
229 | 229 | // hover behaviour |
|
230 | 230 | color: @nav-grey; |
|
231 | 231 | } |
|
232 | 232 | } |
|
233 | 233 | |
|
234 | 234 | &:hover, |
|
235 | 235 | &.open, |
|
236 | 236 | &.active { |
|
237 | 237 | a { |
|
238 | 238 | color: @grey6; |
|
239 | 239 | } |
|
240 | 240 | } |
|
241 | 241 | |
|
242 | 242 | .select2-dropdown-open a { |
|
243 | 243 | color: @grey6; |
|
244 | 244 | } |
|
245 | 245 | |
|
246 | 246 | .repo-switcher { |
|
247 | 247 | padding-left: 0; |
|
248 | 248 | |
|
249 | 249 | .menulabel { |
|
250 | 250 | padding-left: 0; |
|
251 | 251 | } |
|
252 | 252 | } |
|
253 | 253 | } |
|
254 | 254 | |
|
255 | 255 | li ul li { |
|
256 | 256 | background-color:@grey2; |
|
257 | 257 | |
|
258 | 258 | a { |
|
259 | 259 | padding: .5em 0; |
|
260 | 260 | border-bottom: @border-thickness solid @border-default-color; |
|
261 | 261 | color: @grey6; |
|
262 | 262 | } |
|
263 | 263 | |
|
264 | 264 | &:last-child a, &.last a{ |
|
265 | 265 | border-bottom: none; |
|
266 | 266 | } |
|
267 | 267 | |
|
268 | 268 | &:hover { |
|
269 | 269 | background-color: @grey3; |
|
270 | 270 | } |
|
271 | 271 | } |
|
272 | 272 | |
|
273 | 273 | .submenu { |
|
274 | 274 | margin-top: 5px; |
|
275 | 275 | } |
|
276 | 276 | } |
|
277 | 277 | |
|
278 | 278 | // SUBMENUS |
|
279 | 279 | .navigation .submenu { |
|
280 | 280 | display: none; |
|
281 | 281 | } |
|
282 | 282 | |
|
283 | 283 | .navigation li.open { |
|
284 | ||
|
285 | .submenu, | |
|
286 | .repo_switcher { | |
|
287 | display: block; | |
|
288 | } | |
|
284 | .submenu { | |
|
285 | display: block; | |
|
286 | } | |
|
289 | 287 | } |
|
290 | 288 | |
|
291 | 289 | .navigation li:last-child .submenu { |
|
292 | 290 | right: -20px; |
|
293 | 291 | left: auto; |
|
294 | 292 | } |
|
295 | 293 | |
|
296 | 294 | .submenu { |
|
297 | 295 | position: absolute; |
|
298 | 296 | top: 100%; |
|
299 | 297 | left: 0; |
|
300 | 298 | min-width: 150px; |
|
301 | 299 | margin: 6px 0 0; |
|
302 | 300 | padding: 0; |
|
303 | 301 | text-align: left; |
|
304 | 302 | font-family: @text-light; |
|
305 | 303 | border-radius: @border-radius; |
|
306 | 304 | z-index: 20; |
|
307 | 305 | |
|
308 | 306 | li { |
|
309 | 307 | display: block; |
|
310 | 308 | margin: 0; |
|
311 | 309 | padding: 0 .5em; |
|
312 | 310 | line-height: 1em; |
|
313 | 311 | color: @grey3; |
|
314 | 312 | background-color: @grey6; |
|
315 | 313 | |
|
316 | 314 | &:before { content: none; } |
|
317 | 315 | |
|
318 | 316 | a { |
|
319 | 317 | display: block; |
|
320 | 318 | width: 100%; |
|
321 | 319 | padding: .5em 0; |
|
322 | 320 | border-right: none; |
|
323 | 321 | border-bottom: @border-thickness solid white; |
|
324 | 322 | color: @grey3; |
|
325 | 323 | } |
|
326 | 324 | |
|
327 | 325 | ul { |
|
328 | 326 | display: none; |
|
329 | 327 | position: absolute; |
|
330 | 328 | top: 0; |
|
331 | 329 | right: 100%; |
|
332 | 330 | padding: 0; |
|
333 | 331 | z-index: 30; |
|
334 | 332 | } |
|
335 | 333 | &:hover { |
|
336 | 334 | background-color: @grey5; |
|
337 | 335 | -webkit-transition: background .3s; |
|
338 | 336 | -moz-transition: background .3s; |
|
339 | 337 | -o-transition: background .3s; |
|
340 | 338 | transition: background .3s; |
|
341 | 339 | |
|
342 | 340 | ul { |
|
343 | 341 | display: block; |
|
344 | 342 | } |
|
345 | 343 | } |
|
346 | 344 | } |
|
347 | 345 | } |
|
348 | 346 | |
|
349 | 347 | |
|
350 | 348 | |
|
351 | 349 | |
|
352 | 350 | // repo dropdown |
|
353 | 351 | .quick_repo_menu { |
|
354 | 352 | width: 15px; |
|
355 | 353 | text-align: center; |
|
356 | 354 | position: relative; |
|
357 | 355 | cursor: pointer; |
|
358 | 356 | |
|
359 | 357 | div { |
|
360 | 358 | overflow: visible !important; |
|
361 | 359 | } |
|
362 | 360 | |
|
363 | 361 | &.sorting { |
|
364 | 362 | cursor: auto; |
|
365 | 363 | } |
|
366 | 364 | |
|
367 | 365 | &:hover { |
|
368 | 366 | .menu_items_container { |
|
369 | 367 | position: absolute; |
|
370 | 368 | display: block; |
|
371 | 369 | } |
|
372 | 370 | .menu_items { |
|
373 | 371 | display: block; |
|
374 | 372 | } |
|
375 | 373 | } |
|
376 | 374 | |
|
377 | 375 | i { |
|
378 | 376 | margin: 0; |
|
379 | 377 | color: @grey4; |
|
380 | 378 | } |
|
381 | 379 | |
|
382 | 380 | .menu_items_container { |
|
383 | 381 | position: absolute; |
|
384 | 382 | top: 0; |
|
385 | 383 | left: 100%; |
|
386 | 384 | margin: 0; |
|
387 | 385 | padding: 0; |
|
388 | 386 | list-style: none; |
|
389 | 387 | background-color: @grey6; |
|
390 | 388 | z-index: 999; |
|
391 | 389 | text-align: left; |
|
392 | 390 | |
|
393 | 391 | a { |
|
394 | 392 | color: @grey2; |
|
395 | 393 | } |
|
396 | 394 | |
|
397 | 395 | ul.menu_items { |
|
398 | 396 | margin: 0; |
|
399 | 397 | padding: 0; |
|
400 | 398 | } |
|
401 | 399 | |
|
402 | 400 | li { |
|
403 | 401 | margin: 0; |
|
404 | 402 | padding: 0; |
|
405 | 403 | line-height: 1em; |
|
406 | 404 | list-style-type: none; |
|
407 | 405 | |
|
408 | 406 | &:before { content: none; } |
|
409 | 407 | |
|
410 | 408 | a { |
|
411 | 409 | display: block; |
|
412 | 410 | height: 16px; |
|
413 | 411 | padding: 8px; //must add up to td height (28px) |
|
414 | 412 | |
|
415 | 413 | &:hover { |
|
416 | 414 | background-color: @grey5; |
|
417 | 415 | -webkit-transition: background .3s; |
|
418 | 416 | -moz-transition: background .3s; |
|
419 | 417 | -o-transition: background .3s; |
|
420 | 418 | transition: background .3s; |
|
421 | 419 | } |
|
422 | 420 | } |
|
423 | 421 | } |
|
424 | 422 | } |
|
425 | 423 | } |
|
426 | 424 | |
|
427 | 425 | // Header Repository Switcher |
|
428 | 426 | // Select2 Dropdown |
|
429 | 427 | #select2-drop.select2-drop.repo-switcher-dropdown { |
|
430 | 428 | width: auto !important; |
|
431 | 429 | margin-top: 5px; |
|
432 | 430 | padding: 1em 0; |
|
433 | 431 | text-align: left; |
|
434 | 432 | .border-radius-bottom(@border-radius); |
|
435 | 433 | border-color: transparent; |
|
436 | 434 | color: @grey6; |
|
437 | 435 | background-color: @grey2; |
|
438 | 436 | |
|
439 | 437 | input { |
|
440 | 438 | min-width: 90%; |
|
441 | 439 | } |
|
442 | 440 | |
|
443 | 441 | ul.select2-result-sub { |
|
444 | 442 | |
|
445 | 443 | li { |
|
446 | 444 | line-height: 1em; |
|
447 | 445 | |
|
448 | 446 | &:hover, |
|
449 | 447 | &.select2-highlighted { |
|
450 | 448 | background-color: @grey3; |
|
451 | 449 | } |
|
452 | 450 | } |
|
453 | 451 | |
|
454 | 452 | &:before { content: none; } |
|
455 | 453 | } |
|
456 | 454 | |
|
457 | 455 | ul.select2-results { |
|
458 | 456 | min-width: 200px; |
|
459 | 457 | margin: 0; |
|
460 | 458 | padding: 0; |
|
461 | 459 | list-style-type: none; |
|
462 | 460 | overflow-x: visible; |
|
463 | 461 | overflow-y: scroll; |
|
464 | 462 | |
|
465 | 463 | li { |
|
466 | 464 | padding: 0 8px; |
|
467 | 465 | line-height: 1em; |
|
468 | 466 | color: @grey6; |
|
469 | 467 | |
|
470 | 468 | &:before { content: none; } |
|
471 | 469 | |
|
472 | 470 | &>.select2-result-label { |
|
473 | 471 | padding: 8px 0; |
|
474 | 472 | border-bottom: @border-thickness solid @grey3; |
|
475 | 473 | white-space: nowrap; |
|
476 | 474 | color: @grey5; |
|
477 | 475 | cursor: pointer; |
|
478 | 476 | } |
|
479 | 477 | |
|
480 | 478 | &.select2-result-with-children { |
|
481 | 479 | margin: 0; |
|
482 | 480 | padding: 0; |
|
483 | 481 | } |
|
484 | 482 | |
|
485 | 483 | &.select2-result-unselectable > .select2-result-label { |
|
486 | 484 | margin: 0 8px; |
|
487 | 485 | } |
|
488 | 486 | |
|
489 | 487 | } |
|
490 | 488 | } |
|
491 | 489 | |
|
492 | 490 | ul.select2-result-sub { |
|
493 | 491 | margin: 0; |
|
494 | 492 | padding: 0; |
|
495 | 493 | |
|
496 | 494 | li { |
|
497 | 495 | display: block; |
|
498 | 496 | margin: 0; |
|
499 | 497 | border-right: none; |
|
500 | 498 | line-height: 1em; |
|
501 | 499 | font-family: @text-light; |
|
502 | 500 | color: @grey2; |
|
503 | 501 | |
|
504 | 502 | &:before { content: none; } |
|
505 | 503 | |
|
506 | 504 | &:hover { |
|
507 | 505 | background-color: @grey3; |
|
508 | 506 | } |
|
509 | 507 | } |
|
510 | 508 | } |
|
511 | 509 | } |
|
512 | 510 | |
|
513 | 511 | |
|
514 | 512 | #context-bar { |
|
515 | 513 | display: block; |
|
516 | 514 | margin: 0 auto; |
|
517 | 515 | padding: 0 @header-padding; |
|
518 | 516 | background-color: @grey6; |
|
519 | 517 | border-bottom: @border-thickness solid @grey5; |
|
520 | 518 | |
|
521 | 519 | .clear { |
|
522 | 520 | clear: both; |
|
523 | 521 | } |
|
524 | 522 | } |
|
525 | 523 | |
|
526 | 524 | ul#context-pages { |
|
527 | 525 | li { |
|
528 | 526 | line-height: 1em; |
|
529 | 527 | |
|
530 | 528 | &:before { content: none; } |
|
531 | 529 | |
|
532 | 530 | a { |
|
533 | 531 | color: @grey3; |
|
534 | 532 | } |
|
535 | 533 | |
|
536 | 534 | &.active { |
|
537 | 535 | // special case, non-variable color |
|
538 | 536 | border-bottom: 4px solid @nav-grey; |
|
539 | 537 | |
|
540 | 538 | a { |
|
541 | 539 | color: @grey1; |
|
542 | 540 | } |
|
543 | 541 | } |
|
544 | 542 | } |
|
545 | 543 | } |
|
546 | 544 | |
|
547 | 545 | // PAGINATION |
|
548 | 546 | |
|
549 | 547 | .pagination { |
|
550 | 548 | border: @border-thickness solid @rcblue; |
|
551 | 549 | color: @rcblue; |
|
552 | 550 | |
|
553 | 551 | .current { |
|
554 | 552 | color: @grey4; |
|
555 | 553 | } |
|
556 | 554 | } |
|
557 | 555 | |
|
558 | 556 | .dataTables_processing { |
|
559 | 557 | text-align: center; |
|
560 | 558 | font-size: 1.1em; |
|
561 | 559 | position: relative; |
|
562 | 560 | top: 95px; |
|
563 | 561 | } |
|
564 | 562 | |
|
565 | 563 | .dataTables_paginate, .pagination-wh { |
|
566 | 564 | text-align: left; |
|
567 | 565 | display: inline-block; |
|
568 | 566 | border-left: 1px solid @rcblue; |
|
569 | 567 | float: none; |
|
570 | 568 | overflow: hidden; |
|
571 | 569 | |
|
572 | 570 | .paginate_button, .pager_curpage, |
|
573 | 571 | .pager_link, .pg-previous, .pg-next, .pager_dotdot { |
|
574 | 572 | display: inline-block; |
|
575 | 573 | padding: @menupadding/4 @menupadding; |
|
576 | 574 | border: 1px solid @rcblue; |
|
577 | 575 | border-left: 0; |
|
578 | 576 | color: @rcblue; |
|
579 | 577 | cursor: pointer; |
|
580 | 578 | float: left; |
|
581 | 579 | } |
|
582 | 580 | |
|
583 | 581 | .pager_curpage, .pager_dotdot, |
|
584 | 582 | .paginate_button.current, .paginate_button.disabled, |
|
585 | 583 | .disabled { |
|
586 | 584 | color: @grey3; |
|
587 | 585 | cursor: default; |
|
588 | 586 | } |
|
589 | 587 | |
|
590 | 588 | .ellipsis { |
|
591 | 589 | display: inline-block; |
|
592 | 590 | text-align: left; |
|
593 | 591 | padding: @menupadding/4 @menupadding; |
|
594 | 592 | border: 1px solid @rcblue; |
|
595 | 593 | border-left: 0; |
|
596 | 594 | float: left; |
|
597 | 595 | } |
|
598 | 596 | } |
|
599 | 597 | |
|
600 | 598 | // SIDEBAR |
|
601 | 599 | |
|
602 | 600 | .sidebar { |
|
603 | 601 | .block-left; |
|
604 | 602 | clear: left; |
|
605 | 603 | max-width: @sidebar-width; |
|
606 | 604 | margin-right: @sidebarpadding; |
|
607 | 605 | padding-right: @sidebarpadding; |
|
608 | 606 | font-family: @text-regular; |
|
609 | 607 | color: @grey1; |
|
610 | 608 | |
|
611 | 609 | &#graph_nodes { |
|
612 | 610 | clear:both; |
|
613 | 611 | width: auto; |
|
614 | 612 | margin-left: -100px; |
|
615 | 613 | padding: 0; |
|
616 | 614 | border: none; |
|
617 | 615 | } |
|
618 | 616 | |
|
619 | 617 | .nav-pills { |
|
620 | 618 | margin: 0; |
|
621 | 619 | } |
|
622 | 620 | |
|
623 | 621 | .nav { |
|
624 | 622 | list-style: none; |
|
625 | 623 | padding: 0; |
|
626 | 624 | |
|
627 | 625 | li { |
|
628 | 626 | padding-bottom: @menupadding; |
|
629 | 627 | line-height: 1em; |
|
630 | 628 | color: @grey4; |
|
631 | 629 | |
|
632 | 630 | &.active a { |
|
633 | 631 | color: @grey2; |
|
634 | 632 | } |
|
635 | 633 | |
|
636 | 634 | a { |
|
637 | 635 | color: @grey4; |
|
638 | 636 | } |
|
639 | 637 | |
|
640 | 638 | &:before { content: none; } |
|
641 | 639 | } |
|
642 | 640 | |
|
643 | 641 | } |
|
644 | 642 | } |
|
643 | ||
|
644 | .main_filter_help_box { | |
|
645 | padding: 7px 7px; | |
|
646 | border-top: 1px solid @grey4; | |
|
647 | border-right: 1px solid @grey4; | |
|
648 | border-bottom: 1px solid @grey4; | |
|
649 | display: inline-block; | |
|
650 | vertical-align: top; | |
|
651 | margin-left: -5px; | |
|
652 | background: @grey3; | |
|
653 | } | |
|
654 | ||
|
655 | .main_filter_input_box { | |
|
656 | display: inline-block; | |
|
657 | } | |
|
658 | ||
|
659 | .main_filter_box { | |
|
660 | margin: 9px 0 0 0; | |
|
661 | } | |
|
662 | ||
|
663 | #main_filter_help { | |
|
664 | background: @grey3; | |
|
665 | border: 1px solid black; | |
|
666 | position: absolute; | |
|
667 | white-space: pre-wrap; | |
|
668 | z-index: 9999; | |
|
669 | color: @nav-grey; | |
|
670 | margin: 1px 7px; | |
|
671 | padding: 0 2px; | |
|
672 | } | |
|
673 | ||
|
674 | .main_filter_input { | |
|
675 | padding: 6px; | |
|
676 | min-width: 220px; | |
|
677 | color: @nav-grey; | |
|
678 | background: @grey3; | |
|
679 | } | |
|
680 | ||
|
681 | .main_filter_input::placeholder { | |
|
682 | color: @nav-grey; | |
|
683 | opacity: 1; | |
|
684 | } |
@@ -1,545 +1,545 b'' | |||
|
1 | 1 | // |
|
2 | 2 | // Typography |
|
3 | 3 | // modified from Bootstrap |
|
4 | 4 | // -------------------------------------------------- |
|
5 | 5 | |
|
6 | 6 | // Base |
|
7 | 7 | body { |
|
8 | 8 | font-size: @basefontsize; |
|
9 | 9 | font-family: @text-light; |
|
10 | 10 | letter-spacing: .02em; |
|
11 | 11 | color: @grey2; |
|
12 | 12 | } |
|
13 | 13 | |
|
14 | 14 | #content, label{ |
|
15 | 15 | font-size: @basefontsize; |
|
16 | 16 | } |
|
17 | 17 | |
|
18 | 18 | label { |
|
19 | 19 | color: @grey2; |
|
20 | 20 | } |
|
21 | 21 | |
|
22 | 22 | ::selection { background: @rchighlightblue; } |
|
23 | 23 | |
|
24 | 24 | // Headings |
|
25 | 25 | // ------------------------- |
|
26 | 26 | |
|
27 | 27 | h1, h2, h3, h4, h5, h6, |
|
28 | 28 | .h1, .h2, .h3, .h4, .h5, .h6 { |
|
29 | 29 | margin: 0 0 @textmargin 0; |
|
30 | 30 | padding: 0; |
|
31 | 31 | line-height: 1.8em; |
|
32 | 32 | color: @text-color; |
|
33 | 33 | a { |
|
34 | 34 | color: @rcblue; |
|
35 | 35 | } |
|
36 | 36 | } |
|
37 | 37 | |
|
38 | 38 | h1, .h1 { font-size: 1.54em; font-family: @text-bold; } |
|
39 | 39 | h2, .h2 { font-size: 1.23em; font-family: @text-semibold; } |
|
40 | 40 | h3, .h3 { font-size: 1.23em; font-family: @text-regular; } |
|
41 | 41 | h4, .h4 { font-size: 1em; font-family: @text-bold; } |
|
42 | 42 | h5, .h5 { font-size: 1em; font-family: @text-bold-italic; } |
|
43 | 43 | h6, .h6 { font-size: 1em; font-family: @text-bold-italic; } |
|
44 | 44 | |
|
45 | 45 | // Breadcrumbs |
|
46 | 46 | .breadcrumbs { |
|
47 | 47 | &:extend(h1); |
|
48 | 48 | margin: 0; |
|
49 | 49 | } |
|
50 | 50 | |
|
51 | 51 | .breadcrumbs_light { |
|
52 | 52 | float:left; |
|
53 | 53 | font-size: 1.3em; |
|
54 | 54 | line-height: 38px; |
|
55 | 55 | } |
|
56 | 56 | |
|
57 | 57 | // Body text |
|
58 | 58 | // ------------------------- |
|
59 | 59 | |
|
60 | 60 | p { |
|
61 | 61 | margin: 0 0 @textmargin 0; |
|
62 | 62 | padding: 0; |
|
63 | 63 | line-height: 2em; |
|
64 | 64 | } |
|
65 | 65 | |
|
66 | 66 | .lead { |
|
67 | 67 | margin-bottom: @textmargin; |
|
68 | 68 | font-weight: 300; |
|
69 | 69 | line-height: 1.4; |
|
70 | 70 | |
|
71 | 71 | @media (min-width: @screen-sm-min) { |
|
72 | 72 | font-size: (@basefontsize * 1.5); |
|
73 | 73 | } |
|
74 | 74 | } |
|
75 | 75 | |
|
76 | 76 | a, |
|
77 | 77 | .link { |
|
78 | 78 | color: @rcblue; |
|
79 | 79 | text-decoration: none; |
|
80 | 80 | outline: none; |
|
81 | 81 | cursor: pointer; |
|
82 | 82 | |
|
83 | 83 | &:focus { |
|
84 | 84 | outline: none; |
|
85 | 85 | } |
|
86 | 86 | |
|
87 | 87 | &:hover { |
|
88 | 88 | color: @rcdarkblue; |
|
89 | 89 | } |
|
90 | 90 | } |
|
91 | 91 | |
|
92 | 92 | img { |
|
93 | 93 | border: none; |
|
94 | 94 | outline: none; |
|
95 | 95 | } |
|
96 | 96 | |
|
97 | 97 | strong { |
|
98 | 98 | font-family: @text-bold; |
|
99 | 99 | } |
|
100 | 100 | |
|
101 | 101 | em { |
|
102 | 102 | font-family: @text-italic; |
|
103 | 103 | } |
|
104 | 104 | |
|
105 | 105 | strong em, |
|
106 | 106 | em strong { |
|
107 | 107 | font-family: @text-bold-italic; |
|
108 | 108 | } |
|
109 | 109 | |
|
110 | 110 | //TODO: lisa: b and i are depreciated, but we are still using them in places. |
|
111 | 111 | // Should probably make some decision whether to keep or lose these. |
|
112 | 112 | b { |
|
113 | 113 | |
|
114 | 114 | } |
|
115 | 115 | |
|
116 | 116 | i { |
|
117 | 117 | font-style: normal; |
|
118 | 118 | } |
|
119 | 119 | |
|
120 | 120 | label { |
|
121 | 121 | color: @text-color; |
|
122 | 122 | |
|
123 | 123 | input[type="checkbox"] { |
|
124 | 124 | margin-right: 1em; |
|
125 | 125 | } |
|
126 | 126 | input[type="radio"] { |
|
127 | 127 | margin-right: 1em; |
|
128 | 128 | } |
|
129 | 129 | } |
|
130 | 130 | |
|
131 | 131 | code, |
|
132 | 132 | .code { |
|
133 | 133 | font-size: .95em; |
|
134 | 134 | font-family: @text-code; |
|
135 | 135 | color: @grey3; |
|
136 | 136 | |
|
137 | 137 | a { |
|
138 | 138 | color: lighten(@rcblue,10%) |
|
139 | 139 | } |
|
140 | 140 | } |
|
141 | 141 | |
|
142 | 142 | pre { |
|
143 | 143 | margin: 0; |
|
144 | 144 | padding: 0; |
|
145 | 145 | border: 0; |
|
146 | 146 | outline: 0; |
|
147 | 147 | font-size: @basefontsize*.95; |
|
148 | 148 | line-height: 1.4em; |
|
149 | 149 | font-family: @text-code; |
|
150 | 150 | color: @grey3; |
|
151 | 151 | } |
|
152 | 152 | |
|
153 | 153 | // Emphasis & misc |
|
154 | 154 | // ------------------------- |
|
155 | 155 | |
|
156 | 156 | small, |
|
157 | 157 | .small { |
|
158 | 158 | font-size: 75%; |
|
159 | 159 | font-weight: normal; |
|
160 | 160 | line-height: 1em; |
|
161 | 161 | } |
|
162 | 162 | |
|
163 | 163 | mark, |
|
164 | 164 | .mark { |
|
165 | 165 | background-color: @rclightblue; |
|
166 | 166 | padding: .2em; |
|
167 | 167 | } |
|
168 | 168 | |
|
169 | 169 | // Alignment |
|
170 | 170 | .text-left { text-align: left; } |
|
171 | 171 | .text-right { text-align: right; } |
|
172 | 172 | .text-center { text-align: center; } |
|
173 | 173 | .text-justify { text-align: justify; } |
|
174 | 174 | .text-nowrap { white-space: nowrap; } |
|
175 | 175 | |
|
176 | 176 | // Transformation |
|
177 | 177 | .text-lowercase { text-transform: lowercase; } |
|
178 | 178 | .text-uppercase { text-transform: uppercase; } |
|
179 | 179 | .text-capitalize { text-transform: capitalize; } |
|
180 | 180 | |
|
181 | 181 | // Contextual colors |
|
182 | 182 | .text-muted { |
|
183 | 183 | color: @grey4; |
|
184 | 184 | } |
|
185 | 185 | .text-primary { |
|
186 | 186 | color: @rcblue; |
|
187 | 187 | } |
|
188 | 188 | .text-success { |
|
189 | 189 | color: @alert1; |
|
190 | 190 | } |
|
191 | 191 | .text-info { |
|
192 | 192 | color: @alert4; |
|
193 | 193 | } |
|
194 | 194 | .text-warning { |
|
195 | 195 | color: @alert3; |
|
196 | 196 | } |
|
197 | 197 | .text-danger { |
|
198 | 198 | color: @alert2; |
|
199 | 199 | } |
|
200 | 200 | |
|
201 | 201 | // Contextual backgrounds |
|
202 | 202 | .bg-primary { |
|
203 | 203 | background-color: white; |
|
204 | 204 | } |
|
205 | 205 | .bg-success { |
|
206 | 206 | background-color: @alert1; |
|
207 | 207 | } |
|
208 | 208 | .bg-info { |
|
209 | 209 | background-color: @alert4; |
|
210 | 210 | } |
|
211 | 211 | .bg-warning { |
|
212 | 212 | background-color: @alert3; |
|
213 | 213 | } |
|
214 | 214 | .bg-danger { |
|
215 | 215 | background-color: @alert2; |
|
216 | 216 | } |
|
217 | 217 | |
|
218 | 218 | |
|
219 | 219 | // Page header |
|
220 | 220 | // ------------------------- |
|
221 | 221 | |
|
222 | 222 | .page-header { |
|
223 | 223 | margin: @pagepadding 0 @textmargin; |
|
224 | 224 | border-bottom: @border-thickness solid @grey5; |
|
225 | 225 | } |
|
226 | 226 | |
|
227 | 227 | .title { |
|
228 | 228 | clear: both; |
|
229 | 229 | float: left; |
|
230 | 230 | width: 100%; |
|
231 | margin: @pagepadding 0 @pagepadding; | |
|
231 | margin: @pagepadding/2 0 @pagepadding; | |
|
232 | 232 | |
|
233 | 233 | .breadcrumbs{ |
|
234 | 234 | float: left; |
|
235 | 235 | clear: both; |
|
236 | 236 | width: 700px; |
|
237 | 237 | margin: 0; |
|
238 | 238 | |
|
239 | 239 | .q_filter_box { |
|
240 | 240 | margin-right: @padding; |
|
241 | 241 | } |
|
242 | 242 | } |
|
243 | 243 | |
|
244 | 244 | h1 a { |
|
245 | 245 | color: @rcblue; |
|
246 | 246 | } |
|
247 | 247 | |
|
248 | 248 | input{ |
|
249 | 249 | margin-right: @padding; |
|
250 | 250 | } |
|
251 | 251 | |
|
252 | 252 | h5, .h5 { |
|
253 | 253 | color: @grey1; |
|
254 | 254 | margin-bottom: @space; |
|
255 | 255 | |
|
256 | 256 | span { |
|
257 | 257 | display: inline-block; |
|
258 | 258 | } |
|
259 | 259 | } |
|
260 | 260 | |
|
261 | 261 | p { |
|
262 | 262 | margin-bottom: 0; |
|
263 | 263 | } |
|
264 | 264 | |
|
265 | 265 | .links { |
|
266 | 266 | float: right; |
|
267 | 267 | display: inline; |
|
268 | 268 | margin: 0; |
|
269 | 269 | padding-left: 0; |
|
270 | 270 | list-style: none; |
|
271 | 271 | text-align: right; |
|
272 | 272 | |
|
273 | 273 | li:before { content: none; } |
|
274 | 274 | li { float: right; } |
|
275 | 275 | a { |
|
276 | 276 | display: inline-block; |
|
277 | 277 | margin-left: @textmargin/2; |
|
278 | 278 | } |
|
279 | 279 | } |
|
280 | 280 | |
|
281 | 281 | .title-content { |
|
282 | 282 | float: left; |
|
283 | 283 | margin: 0; |
|
284 | 284 | padding: 0; |
|
285 | 285 | |
|
286 | 286 | & + .breadcrumbs { |
|
287 | 287 | margin-top: @padding; |
|
288 | 288 | } |
|
289 | 289 | |
|
290 | 290 | & + .links { |
|
291 | 291 | margin-top: -@button-padding; |
|
292 | 292 | |
|
293 | 293 | & + .breadcrumbs { |
|
294 | 294 | margin-top: @padding; |
|
295 | 295 | } |
|
296 | 296 | } |
|
297 | 297 | } |
|
298 | 298 | |
|
299 | 299 | .title-main { |
|
300 | 300 | font-size: @repo-title-fontsize; |
|
301 | 301 | } |
|
302 | 302 | |
|
303 | 303 | .title-description { |
|
304 | 304 | margin-top: .5em; |
|
305 | 305 | } |
|
306 | 306 | |
|
307 | 307 | .q_filter_box { |
|
308 | 308 | width: 200px; |
|
309 | 309 | } |
|
310 | 310 | |
|
311 | 311 | } |
|
312 | 312 | |
|
313 | 313 | #readme .title { |
|
314 | 314 | text-transform: none; |
|
315 | 315 | } |
|
316 | 316 | |
|
317 | 317 | // Lists |
|
318 | 318 | // ------------------------- |
|
319 | 319 | |
|
320 | 320 | // Unordered and Ordered lists |
|
321 | 321 | ul, |
|
322 | 322 | ol { |
|
323 | 323 | margin-top: 0; |
|
324 | 324 | margin-bottom: @textmargin; |
|
325 | 325 | ul, |
|
326 | 326 | ol { |
|
327 | 327 | margin-bottom: 0; |
|
328 | 328 | } |
|
329 | 329 | } |
|
330 | 330 | |
|
331 | 331 | li { |
|
332 | 332 | line-height: 2em; |
|
333 | 333 | } |
|
334 | 334 | |
|
335 | 335 | ul li { |
|
336 | 336 | position: relative; |
|
337 | 337 | display: block; |
|
338 | 338 | list-style-type: none; |
|
339 | 339 | |
|
340 | 340 | &:before { |
|
341 | 341 | content: "\2014\00A0"; |
|
342 | 342 | position: absolute; |
|
343 | 343 | top: 0; |
|
344 | 344 | left: -1.25em; |
|
345 | 345 | } |
|
346 | 346 | |
|
347 | 347 | p:first-child { |
|
348 | 348 | display:inline; |
|
349 | 349 | } |
|
350 | 350 | } |
|
351 | 351 | |
|
352 | 352 | // List options |
|
353 | 353 | |
|
354 | 354 | // Unstyled keeps list items block level, just removes default browser padding and list-style |
|
355 | 355 | .list-unstyled { |
|
356 | 356 | padding-left: 0; |
|
357 | 357 | list-style: none; |
|
358 | 358 | li:before { content: none; } |
|
359 | 359 | } |
|
360 | 360 | |
|
361 | 361 | // Inline turns list items into inline-block |
|
362 | 362 | .list-inline { |
|
363 | 363 | .list-unstyled(); |
|
364 | 364 | margin-left: -5px; |
|
365 | 365 | |
|
366 | 366 | > li { |
|
367 | 367 | display: inline-block; |
|
368 | 368 | padding-left: 5px; |
|
369 | 369 | padding-right: 5px; |
|
370 | 370 | } |
|
371 | 371 | } |
|
372 | 372 | |
|
373 | 373 | // Description Lists |
|
374 | 374 | |
|
375 | 375 | dl { |
|
376 | 376 | margin-top: 0; // Remove browser default |
|
377 | 377 | margin-bottom: @textmargin; |
|
378 | 378 | } |
|
379 | 379 | |
|
380 | 380 | dt, |
|
381 | 381 | dd { |
|
382 | 382 | line-height: 1.4em; |
|
383 | 383 | } |
|
384 | 384 | |
|
385 | 385 | dt { |
|
386 | 386 | margin: @textmargin 0 0 0; |
|
387 | 387 | font-family: @text-bold; |
|
388 | 388 | } |
|
389 | 389 | |
|
390 | 390 | dd { |
|
391 | 391 | margin-left: 0; // Undo browser default |
|
392 | 392 | } |
|
393 | 393 | |
|
394 | 394 | // Horizontal description lists |
|
395 | 395 | // Defaults to being stacked without any of the below styles applied, until the |
|
396 | 396 | // grid breakpoint is reached (default of ~768px). |
|
397 | 397 | // These are used in forms as well; see style guide. |
|
398 | 398 | // TODO: lisa: These should really not be used in forms. |
|
399 | 399 | |
|
400 | 400 | .dl-horizontal { |
|
401 | 401 | |
|
402 | 402 | overflow: hidden; |
|
403 | 403 | margin-bottom: @space; |
|
404 | 404 | |
|
405 | 405 | dt, dd { |
|
406 | 406 | float: left; |
|
407 | 407 | margin: 5px 0 5px 0; |
|
408 | 408 | } |
|
409 | 409 | |
|
410 | 410 | dt { |
|
411 | 411 | clear: left; |
|
412 | 412 | width: @label-width - @form-vertical-margin; |
|
413 | 413 | } |
|
414 | 414 | |
|
415 | 415 | dd { |
|
416 | 416 | &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present |
|
417 | 417 | margin-left: @form-vertical-margin; |
|
418 | 418 | max-width: @form-max-width - (@label-width - @form-vertical-margin) - @form-vertical-margin; |
|
419 | 419 | } |
|
420 | 420 | |
|
421 | 421 | pre { |
|
422 | 422 | margin: 0; |
|
423 | 423 | } |
|
424 | 424 | |
|
425 | 425 | &.settings { |
|
426 | 426 | dt { |
|
427 | 427 | text-align: left; |
|
428 | 428 | } |
|
429 | 429 | } |
|
430 | 430 | |
|
431 | 431 | @media (min-width: 768px) { |
|
432 | 432 | dt { |
|
433 | 433 | float: left; |
|
434 | 434 | width: 180px; |
|
435 | 435 | clear: left; |
|
436 | 436 | text-align: right; |
|
437 | 437 | } |
|
438 | 438 | dd { |
|
439 | 439 | margin-left: 20px; |
|
440 | 440 | } |
|
441 | 441 | } |
|
442 | 442 | } |
|
443 | 443 | |
|
444 | 444 | |
|
445 | 445 | // Misc |
|
446 | 446 | // ------------------------- |
|
447 | 447 | |
|
448 | 448 | // Abbreviations and acronyms |
|
449 | 449 | abbr[title], |
|
450 | 450 | abbr[data-original-title] { |
|
451 | 451 | cursor: help; |
|
452 | 452 | border-bottom: @border-thickness dotted @grey4; |
|
453 | 453 | } |
|
454 | 454 | .initialism { |
|
455 | 455 | font-size: 90%; |
|
456 | 456 | text-transform: uppercase; |
|
457 | 457 | } |
|
458 | 458 | |
|
459 | 459 | // Blockquotes |
|
460 | 460 | blockquote { |
|
461 | 461 | padding: 1em 2em; |
|
462 | 462 | margin: 0 0 2em; |
|
463 | 463 | font-size: @basefontsize; |
|
464 | 464 | border-left: 2px solid @grey6; |
|
465 | 465 | |
|
466 | 466 | p, |
|
467 | 467 | ul, |
|
468 | 468 | ol { |
|
469 | 469 | &:last-child { |
|
470 | 470 | margin-bottom: 0; |
|
471 | 471 | } |
|
472 | 472 | } |
|
473 | 473 | |
|
474 | 474 | footer, |
|
475 | 475 | small, |
|
476 | 476 | .small { |
|
477 | 477 | display: block; |
|
478 | 478 | font-size: 80%; |
|
479 | 479 | |
|
480 | 480 | &:before { |
|
481 | 481 | content: '\2014 \00A0'; // em dash, nbsp |
|
482 | 482 | } |
|
483 | 483 | } |
|
484 | 484 | } |
|
485 | 485 | |
|
486 | 486 | // Opposite alignment of blockquote |
|
487 | 487 | // |
|
488 | 488 | .blockquote-reverse, |
|
489 | 489 | blockquote.pull-right { |
|
490 | 490 | padding-right: 15px; |
|
491 | 491 | padding-left: 0; |
|
492 | 492 | border-right: 5px solid @grey6; |
|
493 | 493 | border-left: 0; |
|
494 | 494 | text-align: right; |
|
495 | 495 | |
|
496 | 496 | // Account for citation |
|
497 | 497 | footer, |
|
498 | 498 | small, |
|
499 | 499 | .small { |
|
500 | 500 | &:before { content: ''; } |
|
501 | 501 | &:after { |
|
502 | 502 | content: '\00A0 \2014'; // nbsp, em dash |
|
503 | 503 | } |
|
504 | 504 | } |
|
505 | 505 | } |
|
506 | 506 | |
|
507 | 507 | // Addresses |
|
508 | 508 | address { |
|
509 | 509 | margin-bottom: 2em; |
|
510 | 510 | font-style: normal; |
|
511 | 511 | line-height: 1.8em; |
|
512 | 512 | } |
|
513 | 513 | |
|
514 | 514 | .error-message { |
|
515 | 515 | display: block; |
|
516 | 516 | margin: @padding/3 0; |
|
517 | 517 | color: @alert2; |
|
518 | 518 | } |
|
519 | 519 | |
|
520 | 520 | .issue-tracker-link { |
|
521 | 521 | color: @rcblue; |
|
522 | 522 | } |
|
523 | 523 | |
|
524 | 524 | .info_text{ |
|
525 | 525 | font-size: @basefontsize; |
|
526 | 526 | color: @grey4; |
|
527 | 527 | font-family: @text-regular; |
|
528 | 528 | } |
|
529 | 529 | |
|
530 | 530 | // help block text |
|
531 | 531 | .help-block { |
|
532 | 532 | display: block; |
|
533 | 533 | margin: 0 0 @padding; |
|
534 | 534 | color: @grey4; |
|
535 | 535 | font-family: @text-light; |
|
536 | 536 | &.pre-formatting { |
|
537 | 537 | white-space: pre; |
|
538 | 538 | } |
|
539 | 539 | } |
|
540 | 540 | |
|
541 | 541 | .error-message { |
|
542 | 542 | display: block; |
|
543 | 543 | margin: @padding/3 0; |
|
544 | 544 | color: @alert2; |
|
545 | 545 | } |
@@ -1,101 +1,101 b'' | |||
|
1 | 1 | // Global keyboard bindings |
|
2 | 2 | |
|
3 | 3 | function setRCMouseBindings(repoName, repoLandingRev) { |
|
4 | 4 | |
|
5 | 5 | /** custom callback for supressing mousetrap from firing */ |
|
6 | 6 | Mousetrap.stopCallback = function(e, element) { |
|
7 | 7 | // if the element has the class "mousetrap" then no need to stop |
|
8 | 8 | if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { |
|
9 | 9 | return false; |
|
10 | 10 | } |
|
11 | 11 | |
|
12 | 12 | // stop for input, select, and textarea |
|
13 | 13 | return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable; |
|
14 | 14 | }; |
|
15 | 15 | |
|
16 | 16 | // general help "?" |
|
17 | 17 | Mousetrap.bind(['?'], function(e) { |
|
18 | 18 | $('#help_kb').modal({}); |
|
19 | 19 | }); |
|
20 | 20 | |
|
21 | 21 | // / open the quick filter |
|
22 | 22 | Mousetrap.bind(['/'], function(e) { |
|
23 | $('#repo_switcher').select2('open'); | |
|
23 | $('#main_filter').get(0).focus(); | |
|
24 | 24 | |
|
25 | 25 | // return false to prevent default browser behavior |
|
26 | 26 | // and stop event from bubbling |
|
27 | 27 | return false; |
|
28 | 28 | }); |
|
29 | 29 | |
|
30 | 30 | // ctrl/command+b, show the the main bar |
|
31 | 31 | Mousetrap.bind(['command+b', 'ctrl+b'], function(e) { |
|
32 | 32 | var $headerInner = $('#header-inner'), |
|
33 | 33 | $content = $('#content'); |
|
34 | 34 | if ($headerInner.hasClass('hover') && $content.hasClass('hover')) { |
|
35 | 35 | $headerInner.removeClass('hover'); |
|
36 | 36 | $content.removeClass('hover'); |
|
37 | 37 | } else { |
|
38 | 38 | $headerInner.addClass('hover'); |
|
39 | 39 | $content.addClass('hover'); |
|
40 | 40 | } |
|
41 | 41 | return false; |
|
42 | 42 | }); |
|
43 | 43 | |
|
44 | 44 | // general nav g + action |
|
45 | 45 | Mousetrap.bind(['g h'], function(e) { |
|
46 | 46 | window.location = pyroutes.url('home'); |
|
47 | 47 | }); |
|
48 | 48 | Mousetrap.bind(['g g'], function(e) { |
|
49 | 49 | window.location = pyroutes.url('gists_show', {'private': 1}); |
|
50 | 50 | }); |
|
51 | 51 | Mousetrap.bind(['g G'], function(e) { |
|
52 | 52 | window.location = pyroutes.url('gists_show', {'public': 1}); |
|
53 | 53 | }); |
|
54 | 54 | Mousetrap.bind(['n g'], function(e) { |
|
55 | 55 | window.location = pyroutes.url('gists_new'); |
|
56 | 56 | }); |
|
57 | 57 | Mousetrap.bind(['n r'], function(e) { |
|
58 | 58 | window.location = pyroutes.url('repo_new'); |
|
59 | 59 | }); |
|
60 | 60 | |
|
61 | 61 | if (repoName && repoName != '') { |
|
62 | 62 | // nav in repo context |
|
63 | 63 | Mousetrap.bind(['g s'], function(e) { |
|
64 | 64 | window.location = pyroutes.url( |
|
65 | 65 | 'repo_summary', {'repo_name': repoName}); |
|
66 | 66 | }); |
|
67 | 67 | Mousetrap.bind(['g c'], function(e) { |
|
68 | 68 | window.location = pyroutes.url( |
|
69 | 69 | 'repo_changelog', {'repo_name': repoName}); |
|
70 | 70 | }); |
|
71 | 71 | Mousetrap.bind(['g F'], function(e) { |
|
72 | 72 | window.location = pyroutes.url( |
|
73 | 73 | 'repo_files', |
|
74 | 74 | { |
|
75 | 75 | 'repo_name': repoName, |
|
76 | 76 | 'commit_id': repoLandingRev, |
|
77 | 77 | 'f_path': '', |
|
78 | 78 | 'search': '1' |
|
79 | 79 | }); |
|
80 | 80 | }); |
|
81 | 81 | Mousetrap.bind(['g f'], function(e) { |
|
82 | 82 | window.location = pyroutes.url( |
|
83 | 83 | 'repo_files', |
|
84 | 84 | { |
|
85 | 85 | 'repo_name': repoName, |
|
86 | 86 | 'commit_id': repoLandingRev, |
|
87 | 87 | 'f_path': '' |
|
88 | 88 | }); |
|
89 | 89 | }); |
|
90 | 90 | Mousetrap.bind(['g o'], function(e) { |
|
91 | 91 | window.location = pyroutes.url( |
|
92 | 92 | 'edit_repo', {'repo_name': repoName}); |
|
93 | 93 | }); |
|
94 | 94 | Mousetrap.bind(['g O'], function(e) { |
|
95 | 95 | window.location = pyroutes.url( |
|
96 | 96 | 'edit_repo_perms', {'repo_name': repoName}); |
|
97 | 97 | }); |
|
98 | 98 | } |
|
99 | 99 | } |
|
100 | 100 | |
|
101 | 101 | setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit); |
@@ -1,188 +1,188 b'' | |||
|
1 | 1 | <div class="panel panel-default"> |
|
2 | 2 | <div class="panel-heading"> |
|
3 | 3 | <h3 class="panel-title">${_('Authentication Tokens')}</h3> |
|
4 | 4 | </div> |
|
5 | 5 | <div class="panel-body"> |
|
6 | 6 | <div class="apikeys_wrap"> |
|
7 | 7 | <p> |
|
8 | 8 | ${_('Each token can have a role. Token with a role can be used only in given context, ' |
|
9 | 9 | 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')} |
|
10 | 10 | </p> |
|
11 | 11 | <table class="rctable auth_tokens"> |
|
12 | 12 | <tr> |
|
13 | 13 | <th>${_('Token')}</th> |
|
14 | 14 | <th>${_('Scope')}</th> |
|
15 | 15 | <th>${_('Description')}</th> |
|
16 | 16 | <th>${_('Role')}</th> |
|
17 | 17 | <th>${_('Expiration')}</th> |
|
18 | 18 | <th>${_('Action')}</th> |
|
19 | 19 | </tr> |
|
20 | 20 | %if c.user_auth_tokens: |
|
21 | 21 | %for auth_token in c.user_auth_tokens: |
|
22 | 22 | <tr class="${'expired' if auth_token.expired else ''}"> |
|
23 | 23 | <td class="truncate-wrap td-authtoken"> |
|
24 | 24 | <div class="user_auth_tokens truncate autoexpand"> |
|
25 | 25 | <code>${auth_token.api_key}</code> |
|
26 | 26 | </div> |
|
27 | 27 | </td> |
|
28 | 28 | <td class="td">${auth_token.scope_humanized}</td> |
|
29 | 29 | <td class="td-wrap">${auth_token.description}</td> |
|
30 | 30 | <td class="td-tags"> |
|
31 | 31 | <span class="tag disabled">${auth_token.role_humanized}</span> |
|
32 | 32 | </td> |
|
33 | 33 | <td class="td-exp"> |
|
34 | 34 | %if auth_token.expires == -1: |
|
35 | 35 | ${_('never')} |
|
36 | 36 | %else: |
|
37 | 37 | %if auth_token.expired: |
|
38 | 38 | <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span> |
|
39 | 39 | %else: |
|
40 | 40 | ${h.age_component(h.time_to_utcdatetime(auth_token.expires))} |
|
41 | 41 | %endif |
|
42 | 42 | %endif |
|
43 | 43 | </td> |
|
44 | 44 | <td class="td-action"> |
|
45 | 45 | ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)} |
|
46 | 46 | ${h.hidden('del_auth_token', auth_token.user_api_key_id)} |
|
47 | 47 | <button class="btn btn-link btn-danger" type="submit" |
|
48 | 48 | onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');"> |
|
49 | 49 | ${_('Delete')} |
|
50 | 50 | </button> |
|
51 | 51 | ${h.end_form()} |
|
52 | 52 | </td> |
|
53 | 53 | </tr> |
|
54 | 54 | %endfor |
|
55 | 55 | %else: |
|
56 | 56 | <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr> |
|
57 | 57 | %endif |
|
58 | 58 | </table> |
|
59 | 59 | </div> |
|
60 | 60 | |
|
61 | 61 | <div class="user_auth_tokens"> |
|
62 | 62 | ${h.secure_form(h.route_path('my_account_auth_tokens_add'), request=request)} |
|
63 | 63 | <div class="form form-vertical"> |
|
64 | 64 | <!-- fields --> |
|
65 | 65 | <div class="fields"> |
|
66 | 66 | <div class="field"> |
|
67 | 67 | <div class="label"> |
|
68 | 68 | <label for="new_email">${_('New authentication token')}:</label> |
|
69 | 69 | </div> |
|
70 | 70 | <div class="input"> |
|
71 | 71 | ${h.text('description', class_='medium', placeholder=_('Description'))} |
|
72 | 72 | ${h.hidden('lifetime')} |
|
73 | 73 | ${h.select('role', '', c.role_options)} |
|
74 | 74 | |
|
75 | 75 | % if c.allow_scoped_tokens: |
|
76 | 76 | ${h.hidden('scope_repo_id')} |
|
77 | 77 | % else: |
|
78 | 78 | ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')} |
|
79 | 79 | % endif |
|
80 | 80 | </div> |
|
81 | 81 | <p class="help-block"> |
|
82 | 82 | ${_('Repository scope works only with tokens with VCS type.')} |
|
83 | 83 | </p> |
|
84 | 84 | </div> |
|
85 | 85 | <div class="buttons"> |
|
86 | 86 | ${h.submit('save',_('Add'),class_="btn")} |
|
87 | 87 | ${h.reset('reset',_('Reset'),class_="btn")} |
|
88 | 88 | </div> |
|
89 | 89 | </div> |
|
90 | 90 | </div> |
|
91 | 91 | ${h.end_form()} |
|
92 | 92 | </div> |
|
93 | 93 | </div> |
|
94 | 94 | </div> |
|
95 | 95 | <script> |
|
96 | 96 | $(document).ready(function(){ |
|
97 | 97 | |
|
98 | 98 | var select2Options = { |
|
99 | 99 | 'containerCssClass': "drop-menu", |
|
100 | 100 | 'dropdownCssClass': "drop-menu-dropdown", |
|
101 | 101 | 'dropdownAutoWidth': true |
|
102 | 102 | }; |
|
103 | 103 | $("#role").select2(select2Options); |
|
104 | 104 | |
|
105 | 105 | var preloadData = { |
|
106 | 106 | results: [ |
|
107 | 107 | % for entry in c.lifetime_values: |
|
108 | 108 | {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','} |
|
109 | 109 | % endfor |
|
110 | 110 | ] |
|
111 | 111 | }; |
|
112 | 112 | |
|
113 | 113 | $("#lifetime").select2({ |
|
114 | 114 | containerCssClass: "drop-menu", |
|
115 | 115 | dropdownCssClass: "drop-menu-dropdown", |
|
116 | 116 | dropdownAutoWidth: true, |
|
117 | 117 | data: preloadData, |
|
118 | 118 | placeholder: "${_('Select or enter expiration date')}", |
|
119 | 119 | query: function(query) { |
|
120 | 120 | feedLifetimeOptions(query, preloadData); |
|
121 | 121 | } |
|
122 | 122 | }); |
|
123 | 123 | |
|
124 | 124 | |
|
125 | 125 | var repoFilter = function(data) { |
|
126 | 126 | var results = []; |
|
127 | 127 | |
|
128 | 128 | if (!data.results[0]) { |
|
129 | 129 | return data |
|
130 | 130 | } |
|
131 | 131 | |
|
132 | 132 | $.each(data.results[0].children, function() { |
|
133 | 133 | // replace name to ID for submision |
|
134 |
this.id = this. |
|
|
134 | this.id = this.repo_id; | |
|
135 | 135 | results.push(this); |
|
136 | 136 | }); |
|
137 | 137 | |
|
138 | 138 | data.results[0].children = results; |
|
139 | 139 | return data; |
|
140 | 140 | }; |
|
141 | 141 | |
|
142 | 142 | $("#scope_repo_id_disabled").select2(select2Options); |
|
143 | 143 | |
|
144 | 144 | var selectVcsScope = function() { |
|
145 | 145 | // select vcs scope and disable input |
|
146 | 146 | $("#role").select2("val", "${c.role_vcs}").trigger('change'); |
|
147 | 147 | $("#role").select2("readonly", true) |
|
148 | 148 | }; |
|
149 | 149 | |
|
150 | 150 | $("#scope_repo_id").select2({ |
|
151 | 151 | cachedDataSource: {}, |
|
152 | 152 | minimumInputLength: 2, |
|
153 | 153 | placeholder: "${_('repository scope')}", |
|
154 | 154 | dropdownAutoWidth: true, |
|
155 | 155 | containerCssClass: "drop-menu", |
|
156 | 156 | dropdownCssClass: "drop-menu-dropdown", |
|
157 | formatResult: formatResult, | |
|
157 | formatResult: formatRepoResult, | |
|
158 | 158 | query: $.debounce(250, function(query){ |
|
159 | 159 | self = this; |
|
160 | 160 | var cacheKey = query.term; |
|
161 | 161 | var cachedData = self.cachedDataSource[cacheKey]; |
|
162 | 162 | |
|
163 | 163 | if (cachedData) { |
|
164 | 164 | query.callback({results: cachedData.results}); |
|
165 | 165 | } else { |
|
166 | 166 | $.ajax({ |
|
167 | 167 | url: pyroutes.url('repo_list_data'), |
|
168 | 168 | data: {'query': query.term}, |
|
169 | 169 | dataType: 'json', |
|
170 | 170 | type: 'GET', |
|
171 | 171 | success: function(data) { |
|
172 | 172 | data = repoFilter(data); |
|
173 | 173 | self.cachedDataSource[cacheKey] = data; |
|
174 | 174 | query.callback({results: data.results}); |
|
175 | 175 | }, |
|
176 | 176 | error: function(data, textStatus, errorThrown) { |
|
177 | 177 | alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); |
|
178 | 178 | } |
|
179 | 179 | }) |
|
180 | 180 | } |
|
181 | 181 | }) |
|
182 | 182 | }); |
|
183 | 183 | $("#scope_repo_id").on('select2-selecting', function(e){ |
|
184 | 184 | selectVcsScope() |
|
185 | 185 | }); |
|
186 | 186 | |
|
187 | 187 | }); |
|
188 | 188 | </script> |
@@ -1,211 +1,211 b'' | |||
|
1 | 1 | <%namespace name="base" file="/base/base.mako"/> |
|
2 | 2 | |
|
3 | 3 | <% |
|
4 | 4 | elems = [ |
|
5 | 5 | (_('Owner'), lambda:base.gravatar_with_user(c.rhodecode_db_repo.user.email), '', ''), |
|
6 | 6 | (_('Created on'), h.format_date(c.rhodecode_db_repo.created_on), '', ''), |
|
7 | 7 | (_('Updated on'), h.format_date(c.rhodecode_db_repo.updated_on), '', ''), |
|
8 | 8 | (_('Cached Commit id'), lambda: h.link_to(c.rhodecode_db_repo.changeset_cache.get('short_id'), h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.changeset_cache.get('raw_id'))), '', ''), |
|
9 | 9 | (_('Attached scoped tokens'), len(c.rhodecode_db_repo.scoped_tokens), '', [x.user for x in c.rhodecode_db_repo.scoped_tokens]), |
|
10 | 10 | ] |
|
11 | 11 | %> |
|
12 | 12 | |
|
13 | 13 | <div class="panel panel-default"> |
|
14 | 14 | <div class="panel-heading" id="advanced-info" > |
|
15 | 15 | <h3 class="panel-title">${_('Repository: %s') % c.rhodecode_db_repo.repo_name} <a class="permalink" href="#advanced-info"> ΒΆ</a></h3> |
|
16 | 16 | </div> |
|
17 | 17 | <div class="panel-body"> |
|
18 | 18 | ${base.dt_info_panel(elems)} |
|
19 | 19 | </div> |
|
20 | 20 | </div> |
|
21 | 21 | |
|
22 | 22 | |
|
23 | 23 | <div class="panel panel-default"> |
|
24 | 24 | <div class="panel-heading" id="advanced-fork"> |
|
25 | 25 | <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"> ΒΆ</a></h3> |
|
26 | 26 | </div> |
|
27 | 27 | <div class="panel-body"> |
|
28 | 28 | ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.rhodecode_db_repo.repo_name), request=request)} |
|
29 | 29 | |
|
30 | 30 | % if c.rhodecode_db_repo.fork: |
|
31 | 31 | <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.rhodecode_db_repo.fork.repo_name, h.route_path('repo_summary', repo_name=c.rhodecode_db_repo.fork.repo_name))})} |
|
32 | 32 | | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div> |
|
33 | 33 | % endif |
|
34 | 34 | |
|
35 | 35 | <div class="field"> |
|
36 | 36 | ${h.hidden('id_fork_of')} |
|
37 | 37 | ${h.submit('set_as_fork_%s' % c.rhodecode_db_repo.repo_name,_('Set'),class_="btn btn-small",)} |
|
38 | 38 | </div> |
|
39 | 39 | <div class="field"> |
|
40 | 40 | <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span> |
|
41 | 41 | </div> |
|
42 | 42 | ${h.end_form()} |
|
43 | 43 | </div> |
|
44 | 44 | </div> |
|
45 | 45 | |
|
46 | 46 | |
|
47 | 47 | <div class="panel panel-default"> |
|
48 | 48 | <div class="panel-heading" id="advanced-journal"> |
|
49 | 49 | <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"> ΒΆ</a></h3> |
|
50 | 50 | </div> |
|
51 | 51 | <div class="panel-body"> |
|
52 | 52 | ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.rhodecode_db_repo.repo_name), request=request)} |
|
53 | 53 | <div class="field"> |
|
54 | 54 | %if c.in_public_journal: |
|
55 | 55 | <button class="btn btn-small" type="submit"> |
|
56 | 56 | ${_('Remove from Public Journal')} |
|
57 | 57 | </button> |
|
58 | 58 | %else: |
|
59 | 59 | <button class="btn btn-small" type="submit"> |
|
60 | 60 | ${_('Add to Public Journal')} |
|
61 | 61 | </button> |
|
62 | 62 | %endif |
|
63 | 63 | </div> |
|
64 | 64 | <div class="field" > |
|
65 | 65 | <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span> |
|
66 | 66 | </div> |
|
67 | 67 | ${h.end_form()} |
|
68 | 68 | </div> |
|
69 | 69 | </div> |
|
70 | 70 | |
|
71 | 71 | |
|
72 | 72 | <div class="panel panel-default"> |
|
73 | 73 | <div class="panel-heading" id="advanced-locking"> |
|
74 | 74 | <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"> ΒΆ</a></h3> |
|
75 | 75 | </div> |
|
76 | 76 | <div class="panel-body"> |
|
77 | 77 | ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.rhodecode_db_repo.repo_name), request=request)} |
|
78 | 78 | |
|
79 | 79 | %if c.rhodecode_db_repo.locked[0]: |
|
80 | 80 | <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.rhodecode_db_repo.locked[0]), |
|
81 | 81 | h.format_date(h. time_to_datetime(c.rhodecode_db_repo.locked[1])), c.rhodecode_db_repo.locked[2])}</div> |
|
82 | 82 | %else: |
|
83 | 83 | <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div> |
|
84 | 84 | %endif |
|
85 | 85 | |
|
86 | 86 | <div class="field" > |
|
87 | 87 | %if c.rhodecode_db_repo.locked[0]: |
|
88 | 88 | ${h.hidden('set_unlock', '1')} |
|
89 | 89 | <button class="btn btn-small" type="submit" |
|
90 | 90 | onclick="return confirm('${_('Confirm to unlock repository.')}');"> |
|
91 | 91 | <i class="icon-unlock"></i> |
|
92 | 92 | ${_('Unlock repository')} |
|
93 | 93 | </button> |
|
94 | 94 | %else: |
|
95 | 95 | ${h.hidden('set_lock', '1')} |
|
96 | 96 | <button class="btn btn-small" type="submit" |
|
97 | 97 | onclick="return confirm('${_('Confirm to lock repository.')}');"> |
|
98 | 98 | <i class="icon-lock"></i> |
|
99 | 99 | ${_('Lock Repository')} |
|
100 | 100 | </button> |
|
101 | 101 | %endif |
|
102 | 102 | </div> |
|
103 | 103 | <div class="field" > |
|
104 | 104 | <span class="help-block"> |
|
105 | 105 | ${_('Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again.')} |
|
106 | 106 | </span> |
|
107 | 107 | </div> |
|
108 | 108 | ${h.end_form()} |
|
109 | 109 | </div> |
|
110 | 110 | </div> |
|
111 | 111 | |
|
112 | 112 | <div class="panel panel-danger"> |
|
113 | 113 | <div class="panel-heading" id="advanced-delete"> |
|
114 | 114 | <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"> ΒΆ</a></h3> |
|
115 | 115 | </div> |
|
116 | 116 | <div class="panel-body"> |
|
117 | 117 | ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), request=request)} |
|
118 | 118 | <table class="display"> |
|
119 | 119 | <tr> |
|
120 | 120 | <td> |
|
121 | 121 | ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.rhodecode_db_repo.forks.count()) % c.rhodecode_db_repo.forks.count()} |
|
122 | 122 | </td> |
|
123 | 123 | <td> |
|
124 | 124 | %if c.rhodecode_db_repo.forks.count(): |
|
125 | 125 | <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label> |
|
126 | 126 | %endif |
|
127 | 127 | </td> |
|
128 | 128 | <td> |
|
129 | 129 | %if c.rhodecode_db_repo.forks.count(): |
|
130 | 130 | <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label> |
|
131 | 131 | %endif |
|
132 | 132 | </td> |
|
133 | 133 | </tr> |
|
134 | 134 | </table> |
|
135 | 135 | <div style="margin: 0 0 20px 0" class="fake-space"></div> |
|
136 | 136 | |
|
137 | 137 | <div class="field"> |
|
138 | 138 | <button class="btn btn-small btn-danger" type="submit" |
|
139 | 139 | onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');"> |
|
140 | 140 | <i class="icon-remove-sign"></i> |
|
141 | 141 | ${_('Delete This Repository')} |
|
142 | 142 | </button> |
|
143 | 143 | </div> |
|
144 | 144 | <div class="field"> |
|
145 | 145 | <span class="help-block"> |
|
146 | 146 | ${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools.')} |
|
147 | 147 | </span> |
|
148 | 148 | </div> |
|
149 | 149 | |
|
150 | 150 | ${h.end_form()} |
|
151 | 151 | </div> |
|
152 | 152 | </div> |
|
153 | 153 | |
|
154 | 154 | |
|
155 | 155 | <script> |
|
156 | 156 | |
|
157 | 157 | var currentRepoId = ${c.rhodecode_db_repo.repo_id}; |
|
158 | 158 | |
|
159 | 159 | var repoTypeFilter = function(data) { |
|
160 | 160 | var results = []; |
|
161 | 161 | |
|
162 | 162 | if (!data.results[0]) { |
|
163 | 163 | return data |
|
164 | 164 | } |
|
165 | 165 | |
|
166 | 166 | $.each(data.results[0].children, function() { |
|
167 | 167 | // filter out the SAME repo, it cannot be used as fork of itself |
|
168 |
if (this |
|
|
169 |
this.id = this. |
|
|
168 | if (this.repo_id != currentRepoId) { | |
|
169 | this.id = this.repo_id; | |
|
170 | 170 | results.push(this) |
|
171 | 171 | } |
|
172 | 172 | }); |
|
173 | 173 | data.results[0].children = results; |
|
174 | 174 | return data; |
|
175 | 175 | }; |
|
176 | 176 | |
|
177 | 177 | $("#id_fork_of").select2({ |
|
178 | 178 | cachedDataSource: {}, |
|
179 | 179 | minimumInputLength: 2, |
|
180 | 180 | placeholder: "${_('Change repository') if c.rhodecode_db_repo.fork else _('Pick repository')}", |
|
181 | 181 | dropdownAutoWidth: true, |
|
182 | 182 | containerCssClass: "drop-menu", |
|
183 | 183 | dropdownCssClass: "drop-menu-dropdown", |
|
184 | formatResult: formatResult, | |
|
184 | formatResult: formatRepoResult, | |
|
185 | 185 | query: $.debounce(250, function(query){ |
|
186 | 186 | self = this; |
|
187 | 187 | var cacheKey = query.term; |
|
188 | 188 | var cachedData = self.cachedDataSource[cacheKey]; |
|
189 | 189 | |
|
190 | 190 | if (cachedData) { |
|
191 | 191 | query.callback({results: cachedData.results}); |
|
192 | 192 | } else { |
|
193 | 193 | $.ajax({ |
|
194 | 194 | url: pyroutes.url('repo_list_data'), |
|
195 | 195 | data: {'query': query.term, repo_type: '${c.rhodecode_db_repo.repo_type}'}, |
|
196 | 196 | dataType: 'json', |
|
197 | 197 | type: 'GET', |
|
198 | 198 | success: function(data) { |
|
199 | 199 | data = repoTypeFilter(data); |
|
200 | 200 | self.cachedDataSource[cacheKey] = data; |
|
201 | 201 | query.callback({results: data.results}); |
|
202 | 202 | }, |
|
203 | 203 | error: function(data, textStatus, errorThrown) { |
|
204 | 204 | alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); |
|
205 | 205 | } |
|
206 | 206 | }) |
|
207 | 207 | } |
|
208 | 208 | }) |
|
209 | 209 | }); |
|
210 | 210 | </script> |
|
211 | 211 |
@@ -1,186 +1,186 b'' | |||
|
1 | 1 | <div class="panel panel-default"> |
|
2 | 2 | <div class="panel-heading"> |
|
3 | 3 | <h3 class="panel-title">${_('Authentication Tokens')}</h3> |
|
4 | 4 | </div> |
|
5 | 5 | <div class="panel-body"> |
|
6 | 6 | <div class="apikeys_wrap"> |
|
7 | 7 | <p> |
|
8 | 8 | ${_('Each token can have a role. Token with a role can be used only in given context, ' |
|
9 | 9 | 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')} |
|
10 | 10 | </p> |
|
11 | 11 | <table class="rctable auth_tokens"> |
|
12 | 12 | <tr> |
|
13 | 13 | <th>${_('Token')}</th> |
|
14 | 14 | <th>${_('Scope')}</th> |
|
15 | 15 | <th>${_('Description')}</th> |
|
16 | 16 | <th>${_('Role')}</th> |
|
17 | 17 | <th>${_('Expiration')}</th> |
|
18 | 18 | <th>${_('Action')}</th> |
|
19 | 19 | </tr> |
|
20 | 20 | %if c.user_auth_tokens: |
|
21 | 21 | %for auth_token in c.user_auth_tokens: |
|
22 | 22 | <tr class="${'expired' if auth_token.expired else ''}"> |
|
23 | 23 | <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td> |
|
24 | 24 | <td class="td">${auth_token.scope_humanized}</td> |
|
25 | 25 | <td class="td-wrap">${auth_token.description}</td> |
|
26 | 26 | <td class="td-tags"> |
|
27 | 27 | <span class="tag disabled">${auth_token.role_humanized}</span> |
|
28 | 28 | </td> |
|
29 | 29 | <td class="td-exp"> |
|
30 | 30 | %if auth_token.expires == -1: |
|
31 | 31 | ${_('never')} |
|
32 | 32 | %else: |
|
33 | 33 | %if auth_token.expired: |
|
34 | 34 | <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span> |
|
35 | 35 | %else: |
|
36 | 36 | ${h.age_component(h.time_to_utcdatetime(auth_token.expires))} |
|
37 | 37 | %endif |
|
38 | 38 | %endif |
|
39 | 39 | </td> |
|
40 | 40 | <td class="td-action"> |
|
41 | 41 | ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), request=request)} |
|
42 | 42 | ${h.hidden('del_auth_token', auth_token.user_api_key_id)} |
|
43 | 43 | <button class="btn btn-link btn-danger" type="submit" |
|
44 | 44 | onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');"> |
|
45 | 45 | ${_('Delete')} |
|
46 | 46 | </button> |
|
47 | 47 | ${h.end_form()} |
|
48 | 48 | </td> |
|
49 | 49 | </tr> |
|
50 | 50 | %endfor |
|
51 | 51 | %else: |
|
52 | 52 | <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr> |
|
53 | 53 | %endif |
|
54 | 54 | </table> |
|
55 | 55 | </div> |
|
56 | 56 | |
|
57 | 57 | <div class="user_auth_tokens"> |
|
58 | 58 | ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), request=request)} |
|
59 | 59 | <div class="form form-vertical"> |
|
60 | 60 | <!-- fields --> |
|
61 | 61 | <div class="fields"> |
|
62 | 62 | <div class="field"> |
|
63 | 63 | <div class="label"> |
|
64 | 64 | <label for="new_email">${_('New authentication token')}:</label> |
|
65 | 65 | </div> |
|
66 | 66 | <div class="input"> |
|
67 | 67 | ${h.text('description', class_='medium', placeholder=_('Description'))} |
|
68 | 68 | ${h.hidden('lifetime')} |
|
69 | 69 | ${h.select('role', '', c.role_options)} |
|
70 | 70 | |
|
71 | 71 | % if c.allow_scoped_tokens: |
|
72 | 72 | ${h.hidden('scope_repo_id')} |
|
73 | 73 | % else: |
|
74 | 74 | ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')} |
|
75 | 75 | % endif |
|
76 | 76 | </div> |
|
77 | 77 | <p class="help-block"> |
|
78 | 78 | ${_('Repository scope works only with tokens with VCS type.')} |
|
79 | 79 | </p> |
|
80 | 80 | </div> |
|
81 | 81 | <div class="buttons"> |
|
82 | 82 | ${h.submit('save',_('Add'),class_="btn")} |
|
83 | 83 | ${h.reset('reset',_('Reset'),class_="btn")} |
|
84 | 84 | </div> |
|
85 | 85 | </div> |
|
86 | 86 | </div> |
|
87 | 87 | ${h.end_form()} |
|
88 | 88 | </div> |
|
89 | 89 | </div> |
|
90 | 90 | </div> |
|
91 | 91 | |
|
92 | 92 | <script> |
|
93 | 93 | |
|
94 | 94 | $(document).ready(function(){ |
|
95 | 95 | |
|
96 | 96 | var select2Options = { |
|
97 | 97 | 'containerCssClass': "drop-menu", |
|
98 | 98 | 'dropdownCssClass': "drop-menu-dropdown", |
|
99 | 99 | 'dropdownAutoWidth': true |
|
100 | 100 | }; |
|
101 | 101 | $("#role").select2(select2Options); |
|
102 | 102 | |
|
103 | 103 | var preloadData = { |
|
104 | 104 | results: [ |
|
105 | 105 | % for entry in c.lifetime_values: |
|
106 | 106 | {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','} |
|
107 | 107 | % endfor |
|
108 | 108 | ] |
|
109 | 109 | }; |
|
110 | 110 | |
|
111 | 111 | $("#lifetime").select2({ |
|
112 | 112 | containerCssClass: "drop-menu", |
|
113 | 113 | dropdownCssClass: "drop-menu-dropdown", |
|
114 | 114 | dropdownAutoWidth: true, |
|
115 | 115 | data: preloadData, |
|
116 | 116 | placeholder: "${_('Select or enter expiration date')}", |
|
117 | 117 | query: function(query) { |
|
118 | 118 | feedLifetimeOptions(query, preloadData); |
|
119 | 119 | } |
|
120 | 120 | }); |
|
121 | 121 | |
|
122 | 122 | |
|
123 | 123 | var repoFilter = function(data) { |
|
124 | 124 | var results = []; |
|
125 | 125 | |
|
126 | 126 | if (!data.results[0]) { |
|
127 | 127 | return data |
|
128 | 128 | } |
|
129 | 129 | |
|
130 | 130 | $.each(data.results[0].children, function() { |
|
131 | 131 | // replace name to ID for submision |
|
132 |
this.id = this. |
|
|
132 | this.id = this.repo_id; | |
|
133 | 133 | results.push(this); |
|
134 | 134 | }); |
|
135 | 135 | |
|
136 | 136 | data.results[0].children = results; |
|
137 | 137 | return data; |
|
138 | 138 | }; |
|
139 | 139 | |
|
140 | 140 | $("#scope_repo_id_disabled").select2(select2Options); |
|
141 | 141 | |
|
142 | 142 | var selectVcsScope = function() { |
|
143 | 143 | // select vcs scope and disable input |
|
144 | 144 | $("#role").select2("val", "${c.role_vcs}").trigger('change'); |
|
145 | 145 | $("#role").select2("readonly", true) |
|
146 | 146 | }; |
|
147 | 147 | |
|
148 | 148 | $("#scope_repo_id").select2({ |
|
149 | 149 | cachedDataSource: {}, |
|
150 | 150 | minimumInputLength: 2, |
|
151 | 151 | placeholder: "${_('repository scope')}", |
|
152 | 152 | dropdownAutoWidth: true, |
|
153 | 153 | containerCssClass: "drop-menu", |
|
154 | 154 | dropdownCssClass: "drop-menu-dropdown", |
|
155 | formatResult: formatResult, | |
|
155 | formatResult: formatRepoResult, | |
|
156 | 156 | query: $.debounce(250, function(query){ |
|
157 | 157 | self = this; |
|
158 | 158 | var cacheKey = query.term; |
|
159 | 159 | var cachedData = self.cachedDataSource[cacheKey]; |
|
160 | 160 | |
|
161 | 161 | if (cachedData) { |
|
162 | 162 | query.callback({results: cachedData.results}); |
|
163 | 163 | } else { |
|
164 | 164 | $.ajax({ |
|
165 | 165 | url: pyroutes.url('repo_list_data'), |
|
166 | 166 | data: {'query': query.term}, |
|
167 | 167 | dataType: 'json', |
|
168 | 168 | type: 'GET', |
|
169 | 169 | success: function(data) { |
|
170 | 170 | data = repoFilter(data); |
|
171 | 171 | self.cachedDataSource[cacheKey] = data; |
|
172 | 172 | query.callback({results: data.results}); |
|
173 | 173 | }, |
|
174 | 174 | error: function(data, textStatus, errorThrown) { |
|
175 | 175 | alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); |
|
176 | 176 | } |
|
177 | 177 | }) |
|
178 | 178 | } |
|
179 | 179 | }) |
|
180 | 180 | }); |
|
181 | 181 | $("#scope_repo_id").on('select2-selecting', function(e){ |
|
182 | 182 | selectVcsScope() |
|
183 | 183 | }); |
|
184 | 184 | |
|
185 | 185 | }); |
|
186 | 186 | </script> |
@@ -1,613 +1,662 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-216x60.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 | © 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: %s') % 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(c.rhodecode_db_repo)} |
|
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 | <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a> |
|
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 |
<ul id="context-pages" class="horizontal-list |
|
|
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 | ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()" |
|
235 | 235 | %if c.rhodecode_db_repo.repo_type in ['git','hg']: |
|
236 | 236 | <li class="${is_active('showpullrequest')}"> |
|
237 | 237 | <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)}"> |
|
238 | 238 | %if c.repository_pull_requests: |
|
239 | 239 | <span class="pr_notifications">${c.repository_pull_requests}</span> |
|
240 | 240 | %endif |
|
241 | 241 | <div class="menulabel">${_('Pull Requests')}</div> |
|
242 | 242 | </a> |
|
243 | 243 | </li> |
|
244 | 244 | %endif |
|
245 | 245 | <li class="${is_active('options')}"> |
|
246 | 246 | <a class="menulink dropdown"> |
|
247 | 247 | <div class="menulabel">${_('Options')} <div class="show_more"></div></div> |
|
248 | 248 | </a> |
|
249 | 249 | <ul class="submenu"> |
|
250 | 250 | %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): |
|
251 | 251 | <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li> |
|
252 | 252 | %endif |
|
253 | 253 | %if c.rhodecode_db_repo.fork: |
|
254 | 254 | <li> |
|
255 | 255 | <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}" |
|
256 | 256 | href="${h.route_path('repo_compare', |
|
257 | 257 | repo_name=c.rhodecode_db_repo.fork.repo_name, |
|
258 | 258 | source_ref_type=c.rhodecode_db_repo.landing_rev[0], |
|
259 | 259 | source_ref=c.rhodecode_db_repo.landing_rev[1], |
|
260 | 260 | target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0], |
|
261 | 261 | target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], |
|
262 | 262 | _query=dict(merge=1))}" |
|
263 | 263 | > |
|
264 | 264 | ${_('Compare fork')} |
|
265 | 265 | </a> |
|
266 | 266 | </li> |
|
267 | 267 | %endif |
|
268 | 268 | |
|
269 | 269 | <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li> |
|
270 | 270 | |
|
271 | 271 | %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking: |
|
272 | 272 | %if c.rhodecode_db_repo.locked[0]: |
|
273 | 273 | <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li> |
|
274 | 274 | %else: |
|
275 | 275 | <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li> |
|
276 | 276 | %endif |
|
277 | 277 | %endif |
|
278 | 278 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
279 | 279 | %if c.rhodecode_db_repo.repo_type in ['git','hg']: |
|
280 | 280 | <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li> |
|
281 | 281 | <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li> |
|
282 | 282 | %endif |
|
283 | 283 | %endif |
|
284 | 284 | </ul> |
|
285 | 285 | </li> |
|
286 | 286 | </ul> |
|
287 | 287 | </div> |
|
288 | 288 | <div class="clear"></div> |
|
289 | 289 | </div> |
|
290 | 290 | <!--- END CONTEXT BAR --> |
|
291 | 291 | |
|
292 | 292 | </%def> |
|
293 | 293 | |
|
294 | 294 | <%def name="usermenu(active=False)"> |
|
295 | 295 | ## USER MENU |
|
296 | 296 | <li id="quick_login_li" class="${'active' if active else ''}"> |
|
297 | 297 | <a id="quick_login_link" class="menulink childs"> |
|
298 | 298 | ${gravatar(c.rhodecode_user.email, 20)} |
|
299 | 299 | <span class="user"> |
|
300 | 300 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
301 | 301 | <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div> |
|
302 | 302 | %else: |
|
303 | 303 | <span>${_('Sign in')}</span> |
|
304 | 304 | %endif |
|
305 | 305 | </span> |
|
306 | 306 | </a> |
|
307 | 307 | |
|
308 | 308 | <div class="user-menu submenu"> |
|
309 | 309 | <div id="quick_login"> |
|
310 | 310 | %if c.rhodecode_user.username == h.DEFAULT_USER: |
|
311 | 311 | <h4>${_('Sign in to your account')}</h4> |
|
312 | 312 | ${h.form(h.route_path('login', _query={'came_from': h.current_route_path(request)}), needs_csrf_token=False)} |
|
313 | 313 | <div class="form form-vertical"> |
|
314 | 314 | <div class="fields"> |
|
315 | 315 | <div class="field"> |
|
316 | 316 | <div class="label"> |
|
317 | 317 | <label for="username">${_('Username')}:</label> |
|
318 | 318 | </div> |
|
319 | 319 | <div class="input"> |
|
320 | 320 | ${h.text('username',class_='focus',tabindex=1)} |
|
321 | 321 | </div> |
|
322 | 322 | |
|
323 | 323 | </div> |
|
324 | 324 | <div class="field"> |
|
325 | 325 | <div class="label"> |
|
326 | 326 | <label for="password">${_('Password')}:</label> |
|
327 | 327 | %if h.HasPermissionAny('hg.password_reset.enabled')(): |
|
328 | 328 | <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span> |
|
329 | 329 | %endif |
|
330 | 330 | </div> |
|
331 | 331 | <div class="input"> |
|
332 | 332 | ${h.password('password',class_='focus',tabindex=2)} |
|
333 | 333 | </div> |
|
334 | 334 | </div> |
|
335 | 335 | <div class="buttons"> |
|
336 | 336 | <div class="register"> |
|
337 | 337 | %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')(): |
|
338 | 338 | ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/> |
|
339 | 339 | %endif |
|
340 | 340 | ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))} |
|
341 | 341 | </div> |
|
342 | 342 | <div class="submit"> |
|
343 | 343 | ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)} |
|
344 | 344 | </div> |
|
345 | 345 | </div> |
|
346 | 346 | </div> |
|
347 | 347 | </div> |
|
348 | 348 | ${h.end_form()} |
|
349 | 349 | %else: |
|
350 | 350 | <div class=""> |
|
351 | 351 | <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div> |
|
352 | 352 | <div class="full_name">${c.rhodecode_user.full_name_or_username}</div> |
|
353 | 353 | <div class="email">${c.rhodecode_user.email}</div> |
|
354 | 354 | </div> |
|
355 | 355 | <div class=""> |
|
356 | 356 | <ol class="links"> |
|
357 | 357 | <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li> |
|
358 | 358 | % if c.rhodecode_user.personal_repo_group: |
|
359 | 359 | <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> |
|
360 | 360 | % endif |
|
361 | 361 | <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li> |
|
362 | 362 | |
|
363 | 363 | <li class="logout"> |
|
364 | 364 | ${h.secure_form(h.route_path('logout'), request=request)} |
|
365 | 365 | ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")} |
|
366 | 366 | ${h.end_form()} |
|
367 | 367 | </li> |
|
368 | 368 | </ol> |
|
369 | 369 | </div> |
|
370 | 370 | %endif |
|
371 | 371 | </div> |
|
372 | 372 | </div> |
|
373 | 373 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
374 | 374 | <div class="pill_container"> |
|
375 | 375 | <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a> |
|
376 | 376 | </div> |
|
377 | 377 | % endif |
|
378 | 378 | </li> |
|
379 | 379 | </%def> |
|
380 | 380 | |
|
381 | 381 | <%def name="menu_items(active=None)"> |
|
382 | 382 | <% |
|
383 | 383 | def is_active(selected): |
|
384 | 384 | if selected == active: |
|
385 | 385 | return "active" |
|
386 | 386 | return "" |
|
387 | 387 | %> |
|
388 | 388 | <ul id="quick" class="main_nav navigation horizontal-list"> |
|
389 | <!-- repo switcher --> | |
|
390 | <li class="${is_active('repositories')} repo_switcher_li has_select2"> | |
|
391 | <input id="repo_switcher" name="repo_switcher" type="hidden"> | |
|
392 | </li> | |
|
389 | ||
|
390 | ## Main filter | |
|
391 | <li> | |
|
392 | <div class="menulabel main_filter_box"> | |
|
393 | <div class="main_filter_input_box"> | |
|
394 | <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/> | |
|
395 | </div> | |
|
396 | <div class="main_filter_help_box"> | |
|
397 | <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a> | |
|
398 | </div> | |
|
399 | </div> | |
|
400 | ||
|
401 | <div id="main_filter_help" style="display: none"> | |
|
402 | Use '/' key to quickly access this field. | |
|
403 | Enter name of repository, or repository group for quick search. | |
|
404 | ||
|
405 | Prefix query to allow special search: | |
|
406 | ||
|
407 | For usernames, e.g user:admin | |
|
408 | ||
|
409 | For commit hash/id, e.g commit:efced4 | |
|
410 | ||
|
411 | </div> | |
|
412 | </li> | |
|
393 | 413 | |
|
394 | 414 | ## ROOT MENU |
|
395 | 415 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
396 | 416 | <li class="${is_active('journal')}"> |
|
397 | 417 | <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}"> |
|
398 | 418 | <div class="menulabel">${_('Journal')}</div> |
|
399 | 419 | </a> |
|
400 | 420 | </li> |
|
401 | 421 | %else: |
|
402 | 422 | <li class="${is_active('journal')}"> |
|
403 | 423 | <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}"> |
|
404 | 424 | <div class="menulabel">${_('Public journal')}</div> |
|
405 | 425 | </a> |
|
406 | 426 | </li> |
|
407 | 427 | %endif |
|
408 | 428 | <li class="${is_active('gists')}"> |
|
409 | 429 | <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}"> |
|
410 | 430 | <div class="menulabel">${_('Gists')}</div> |
|
411 | 431 | </a> |
|
412 | 432 | </li> |
|
413 | 433 | <li class="${is_active('search')}"> |
|
414 | 434 | <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}"> |
|
415 | 435 | <div class="menulabel">${_('Search')}</div> |
|
416 | 436 | </a> |
|
417 | 437 | </li> |
|
418 | 438 | % if h.HasPermissionAll('hg.admin')('access admin main page'): |
|
419 | 439 | <li class="${is_active('admin')}"> |
|
420 | 440 | <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;"> |
|
421 | 441 | <div class="menulabel">${_('Admin')} <div class="show_more"></div></div> |
|
422 | 442 | </a> |
|
423 | 443 | ${admin_menu()} |
|
424 | 444 | </li> |
|
425 | 445 | % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin: |
|
426 | 446 | <li class="${is_active('admin')}"> |
|
427 | 447 | <a class="menulink childs" title="${_('Delegated Admin settings')}"> |
|
428 | 448 | <div class="menulabel">${_('Admin')} <div class="show_more"></div></div> |
|
429 | 449 | </a> |
|
430 | 450 | ${admin_menu_simple(c.rhodecode_user.repositories_admin, |
|
431 | 451 | c.rhodecode_user.repository_groups_admin, |
|
432 | 452 | c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())} |
|
433 | 453 | </li> |
|
434 | 454 | % endif |
|
455 | ## render extra user menu | |
|
456 | ${usermenu(active=(active=='my_account'))} | |
|
457 | ||
|
435 | 458 |
|
|
436 | 459 | <li class="${is_active('debug_style')}"> |
|
437 | 460 | <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}"> |
|
438 | 461 | <div class="menulabel">${_('Style')}</div> |
|
439 | 462 | </a> |
|
440 | 463 | </li> |
|
441 | 464 | % endif |
|
442 | ## render extra user menu | |
|
443 | ${usermenu(active=(active=='my_account'))} | |
|
444 | 465 | </ul> |
|
445 | 466 | |
|
446 | 467 | <script type="text/javascript"> |
|
447 |
var visual |
|
|
448 | ||
|
449 | /*format the look of items in the list*/ | |
|
450 | var format = function(state, escapeMarkup){ | |
|
451 | if (!state.id){ | |
|
452 | return state.text; // optgroup | |
|
453 | } | |
|
454 | var obj_dict = state.obj; | |
|
455 | var tmpl = ''; | |
|
468 | var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True"; | |
|
456 | 469 | |
|
457 | if(obj_dict && state.type == 'repo'){ | |
|
458 | if(obj_dict['repo_type'] === 'hg'){ | |
|
459 | tmpl += '<i class="icon-hg"></i> '; | |
|
460 | } | |
|
461 | else if(obj_dict['repo_type'] === 'git'){ | |
|
462 | tmpl += '<i class="icon-git"></i> '; | |
|
463 | } | |
|
464 | else if(obj_dict['repo_type'] === 'svn'){ | |
|
465 | tmpl += '<i class="icon-svn"></i> '; | |
|
470 | var formatRepoResult = function(result, container, query, escapeMarkup) { | |
|
471 | return function(data, escapeMarkup) { | |
|
472 | if (!data.repo_id){ | |
|
473 | return data.text; // optgroup text Repositories | |
|
466 | 474 | } |
|
467 | if(obj_dict['private']){ | |
|
468 |
|
|
|
469 | } | |
|
470 | else if(visual_show_public_icon){ | |
|
471 | tmpl += '<i class="icon-unlock-alt"></i> '; | |
|
475 | ||
|
476 | var tmpl = ''; | |
|
477 | var repoType = data['repo_type']; | |
|
478 | var repoName = data['text']; | |
|
479 | ||
|
480 | if(data && data.type == 'repo'){ | |
|
481 | if(repoType === 'hg'){ | |
|
482 | tmpl += '<i class="icon-hg"></i> '; | |
|
483 | } | |
|
484 | else if(repoType === 'git'){ | |
|
485 | tmpl += '<i class="icon-git"></i> '; | |
|
486 | } | |
|
487 | else if(repoType === 'svn'){ | |
|
488 | tmpl += '<i class="icon-svn"></i> '; | |
|
489 | } | |
|
490 | if(data['private']){ | |
|
491 | tmpl += '<i class="icon-lock" ></i> '; | |
|
492 | } | |
|
493 | else if(visualShowPublicIcon){ | |
|
494 | tmpl += '<i class="icon-unlock-alt"></i> '; | |
|
495 | } | |
|
472 | 496 | } |
|
473 | } | |
|
474 | if(obj_dict && state.type == 'commit') { | |
|
475 | tmpl += '<i class="icon-tag"></i>'; | |
|
476 | } | |
|
477 | if(obj_dict && state.type == 'group'){ | |
|
478 | tmpl += '<i class="icon-folder-close"></i> '; | |
|
479 | } | |
|
480 | tmpl += escapeMarkup(state.text); | |
|
481 | return tmpl; | |
|
482 | }; | |
|
497 | tmpl += escapeMarkup(repoName); | |
|
498 | return tmpl; | |
|
483 | 499 | |
|
484 | var formatResult = function(result, container, query, escapeMarkup) { | |
|
485 | return format(result, escapeMarkup); | |
|
486 | }; | |
|
487 | ||
|
488 | var formatSelection = function(data, container, escapeMarkup) { | |
|
489 | return format(data, escapeMarkup); | |
|
500 | }(result, escapeMarkup); | |
|
490 | 501 | }; |
|
491 | 502 | |
|
492 | $("#repo_switcher").select2({ | |
|
493 | cachedDataSource: {}, | |
|
494 | minimumInputLength: 2, | |
|
495 | placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>', | |
|
496 | dropdownAutoWidth: true, | |
|
497 | formatResult: formatResult, | |
|
498 | formatSelection: formatSelection, | |
|
499 | containerCssClass: "repo-switcher", | |
|
500 | dropdownCssClass: "repo-switcher-dropdown", | |
|
501 | escapeMarkup: function(m){ | |
|
502 | // don't escape our custom placeholder | |
|
503 | if(m.substr(0,23) == '<div class="menulabel">'){ | |
|
504 |
|
|
|
505 | } | |
|
503 | ||
|
504 | var autocompleteMainFilterFormatResult = function (data, value, org_formatter) { | |
|
505 | ||
|
506 | if (value.split(':').length === 2) { | |
|
507 | value = value.split(':')[1] | |
|
508 | } | |
|
509 | ||
|
510 | var searchType = data['type']; | |
|
511 | var valueDisplay = data['value_display']; | |
|
512 | ||
|
513 | var escapeRegExChars = function (value) { | |
|
514 | return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); | |
|
515 | }; | |
|
516 | var pattern = '(' + escapeRegExChars(value) + ')'; | |
|
517 | ||
|
518 | // highlight match | |
|
519 | valueDisplay = Select2.util.escapeMarkup(valueDisplay); | |
|
520 | valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>'); | |
|
521 | ||
|
522 | var icon = ''; | |
|
506 | 523 | |
|
507 | return Select2.util.escapeMarkup(m); | |
|
508 | }, | |
|
509 | query: $.debounce(250, function(query){ | |
|
510 | self = this; | |
|
511 | var cacheKey = query.term; | |
|
512 | var cachedData = self.cachedDataSource[cacheKey]; | |
|
524 | if (searchType === 'search') { | |
|
525 | icon += '<i class="icon-more"></i> '; | |
|
526 | } | |
|
527 | else if (searchType === 'repo') { | |
|
528 | if (data['repo_type'] === 'hg') { | |
|
529 | icon += '<i class="icon-hg"></i> '; | |
|
530 | } | |
|
531 | else if (data['repo_type'] === 'git') { | |
|
532 | icon += '<i class="icon-git"></i> '; | |
|
533 | } | |
|
534 | else if (data['repo_type'] === 'svn') { | |
|
535 | icon += '<i class="icon-svn"></i> '; | |
|
536 | } | |
|
537 | if (data['private']) { | |
|
538 | icon += '<i class="icon-lock" ></i> '; | |
|
539 | } | |
|
540 | else if (visualShowPublicIcon) { | |
|
541 | icon += '<i class="icon-unlock-alt"></i> '; | |
|
542 | } | |
|
543 | } | |
|
544 | else if (searchType === 'repo_group') { | |
|
545 | icon += '<i class="icon-folder-close"></i> '; | |
|
546 | } | |
|
547 | else if (searchType === 'user') { | |
|
548 | icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']); | |
|
549 | } | |
|
550 | else if (searchType === 'commit') { | |
|
551 | icon += '<i class="icon-tag"></i>'; | |
|
552 | } | |
|
513 | 553 | |
|
514 | if (cachedData) { | |
|
515 | query.callback({results: cachedData.results}); | |
|
516 | } else { | |
|
517 | $.ajax({ | |
|
518 | url: pyroutes.url('goto_switcher_data'), | |
|
519 | data: {'query': query.term}, | |
|
520 | dataType: 'json', | |
|
521 | type: 'GET', | |
|
522 | success: function(data) { | |
|
523 | self.cachedDataSource[cacheKey] = data; | |
|
524 | query.callback({results: data.results}); | |
|
525 | }, | |
|
526 | error: function(data, textStatus, errorThrown) { | |
|
527 | alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); | |
|
528 | } | |
|
529 | }) | |
|
530 |
|
|
|
531 |
|
|
|
554 | var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'; | |
|
555 | return tmpl.format(icon, valueDisplay); | |
|
556 | }; | |
|
557 | ||
|
558 | var handleSelect = function(element, suggestion) { | |
|
559 | window.location = suggestion['url']; | |
|
560 | }; | |
|
561 | var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) { | |
|
562 | if (queryLowerCase.split(':').length === 2) { | |
|
563 | queryLowerCase = queryLowerCase.split(':')[1] | |
|
564 | } | |
|
565 | return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1; | |
|
566 | }; | |
|
567 | ||
|
568 | $('#main_filter').autocomplete({ | |
|
569 | serviceUrl: pyroutes.url('goto_switcher_data'), | |
|
570 | minChars:2, | |
|
571 | maxHeight:400, | |
|
572 | deferRequestBy: 300, //miliseconds | |
|
573 | tabDisabled: true, | |
|
574 | autoSelectFirst: true, | |
|
575 | formatResult: autocompleteMainFilterFormatResult, | |
|
576 | lookupFilter: autocompleteMainFilterResult, | |
|
577 | onSelect: function(element, suggestion){ | |
|
578 | handleSelect(element, suggestion); | |
|
579 | return false; | |
|
580 | } | |
|
532 | 581 | }); |
|
533 | 582 | |
|
534 | $("#repo_switcher").on('select2-selecting', function(e){ | |
|
535 | e.preventDefault(); | |
|
536 | window.location = e.choice.url; | |
|
537 | }); | |
|
583 | showMainFilterBox = function () { | |
|
584 | $('#main_filter_help').toggle(); | |
|
585 | } | |
|
538 | 586 | |
|
539 | 587 | </script> |
|
540 | 588 | <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script> |
|
541 | 589 | </%def> |
|
542 | 590 | |
|
543 | 591 | <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> |
|
544 | 592 | <div class="modal-dialog"> |
|
545 | 593 | <div class="modal-content"> |
|
546 | 594 | <div class="modal-header"> |
|
547 | 595 | <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> |
|
548 | 596 | <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4> |
|
549 | 597 | </div> |
|
550 | 598 | <div class="modal-body"> |
|
551 | 599 | <div class="block-left"> |
|
552 | 600 | <table class="keyboard-mappings"> |
|
553 | 601 | <tbody> |
|
554 | 602 | <tr> |
|
555 | 603 | <th></th> |
|
556 | 604 | <th>${_('Site-wide shortcuts')}</th> |
|
557 | 605 | </tr> |
|
558 | 606 | <% |
|
559 | 607 | elems = [ |
|
560 |
('/', ' |
|
|
608 | ('/', 'Use quick search box'), | |
|
561 | 609 | ('g h', 'Goto home page'), |
|
562 | 610 | ('g g', 'Goto my private gists page'), |
|
563 | 611 | ('g G', 'Goto my public gists page'), |
|
564 | 612 | ('n r', 'New repository page'), |
|
565 | 613 | ('n g', 'New gist page'), |
|
566 | 614 | ] |
|
567 | 615 | %> |
|
568 | 616 | %for key, desc in elems: |
|
569 | 617 | <tr> |
|
570 | 618 | <td class="keys"> |
|
571 | 619 | <span class="key tag">${key}</span> |
|
572 | 620 | </td> |
|
573 | 621 | <td>${desc}</td> |
|
574 | 622 | </tr> |
|
575 | 623 | %endfor |
|
576 | 624 | </tbody> |
|
577 | 625 | </table> |
|
578 | 626 | </div> |
|
579 | 627 | <div class="block-left"> |
|
580 | 628 | <table class="keyboard-mappings"> |
|
581 | 629 | <tbody> |
|
582 | 630 | <tr> |
|
583 | 631 | <th></th> |
|
584 | 632 | <th>${_('Repositories')}</th> |
|
585 | 633 | </tr> |
|
586 | 634 | <% |
|
587 | 635 | elems = [ |
|
588 | 636 | ('g s', 'Goto summary page'), |
|
589 | 637 | ('g c', 'Goto changelog page'), |
|
590 | 638 | ('g f', 'Goto files page'), |
|
591 | 639 | ('g F', 'Goto files page with file search activated'), |
|
592 | 640 | ('g p', 'Goto pull requests page'), |
|
593 | 641 | ('g o', 'Goto repository settings'), |
|
594 | 642 | ('g O', 'Goto repository permissions settings'), |
|
595 | 643 | ] |
|
596 | 644 | %> |
|
597 | 645 | %for key, desc in elems: |
|
598 | 646 | <tr> |
|
599 | 647 | <td class="keys"> |
|
600 | 648 | <span class="key tag">${key}</span> |
|
601 | 649 | </td> |
|
602 | 650 | <td>${desc}</td> |
|
603 | 651 | </tr> |
|
604 | 652 | %endfor |
|
605 | 653 | </tbody> |
|
606 | 654 | </table> |
|
607 | 655 | </div> |
|
608 | 656 | </div> |
|
609 | 657 | <div class="modal-footer"> |
|
610 | 658 | </div> |
|
611 | 659 | </div><!-- /.modal-content --> |
|
612 | 660 | </div><!-- /.modal-dialog --> |
|
613 | 661 | </div><!-- /.modal --> |
|
662 |
@@ -1,201 +1,141 b'' | |||
|
1 | 1 | <%inherit file="/base/base.mako"/> |
|
2 | 2 | |
|
3 | 3 | <%def name="main()"> |
|
4 | 4 | <div class="box"> |
|
5 | 5 | <!-- box / title --> |
|
6 | 6 | <div class="title"> |
|
7 | 7 | <div class="block-left breadcrumbs"> |
|
8 | <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/> | |
|
9 | 8 | ${self.breadcrumbs()} |
|
10 |
<span id="match_container" style="display:none"> |
|
|
9 | <span id="match_container" style="display:none"><span id="match_count">0</span> ${_('matches')}</span> | |
|
11 | 10 | </div> |
|
12 | 11 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
13 | 12 | <div class="block-right"> |
|
14 | 13 | <% |
|
15 | 14 | is_admin = h.HasPermissionAny('hg.admin')('can create repos index page') |
|
16 | 15 | create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page') |
|
17 | 16 | create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page') |
|
18 | 17 | create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page') |
|
19 | 18 | |
|
20 | 19 | gr_name = c.repo_group.group_name if c.repo_group else None |
|
21 | 20 | # create repositories with write permission on group is set to true |
|
22 | 21 | create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')() |
|
23 | 22 | group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page') |
|
24 | 23 | group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page') |
|
25 | 24 | %> |
|
26 | 25 | |
|
27 | 26 | %if not c.repo_group: |
|
28 | 27 | ## no repository group context here |
|
29 | 28 | %if is_admin or create_repo: |
|
30 | 29 | <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a> |
|
31 | 30 | %endif |
|
32 | 31 | |
|
33 | 32 | %if is_admin or create_repo_group: |
|
34 | 33 | <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a> |
|
35 | 34 | %endif |
|
36 | 35 | %else: |
|
37 | 36 | ##we're inside other repository group other terms apply |
|
38 | 37 | %if is_admin or group_admin or (group_write and create_on_write): |
|
39 | 38 | <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a> |
|
40 | 39 | %endif |
|
41 | 40 | %if is_admin or group_admin: |
|
42 | 41 | <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a> |
|
43 | 42 | %endif |
|
44 | 43 | %if is_admin or group_admin: |
|
45 | 44 | <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')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a> |
|
46 | 45 | %endif |
|
47 | 46 | %endif |
|
48 | 47 | </div> |
|
49 | 48 | %endif |
|
50 | 49 | </div> |
|
51 | 50 | <!-- end box / title --> |
|
52 | 51 | <div class="table"> |
|
53 | 52 | <div id="groups_list_wrap"> |
|
54 | 53 | <table id="group_list_table" class="display"></table> |
|
55 | 54 | </div> |
|
56 | 55 | </div> |
|
57 | 56 | |
|
58 | 57 | <div class="table"> |
|
59 | 58 | <div id="repos_list_wrap"> |
|
60 | 59 | <table id="repo_list_table" class="display"></table> |
|
61 | 60 | </div> |
|
62 | 61 | </div> |
|
63 | 62 | |
|
64 | 63 | ## no repository groups and repos present, show something to the users |
|
65 | 64 | % if c.repo_groups_data == '[]' and c.repos_data == '[]': |
|
66 | 65 | <div class="table"> |
|
67 | 66 | <h2 class="no-object-border"> |
|
68 | 67 | ${_('No repositories or repositories groups exists here.')} |
|
69 | 68 | </h2> |
|
70 | 69 | </div> |
|
71 | 70 | % endif |
|
72 | 71 | |
|
73 | 72 | </div> |
|
74 | 73 | <script> |
|
75 | 74 | $(document).ready(function() { |
|
76 | 75 | |
|
77 | 76 | // repo group list |
|
78 | 77 | % if c.repo_groups_data != '[]': |
|
79 | 78 | $('#group_list_table').DataTable({ |
|
80 | 79 | data: ${c.repo_groups_data|n}, |
|
81 | 80 | dom: 'rtp', |
|
82 | 81 | pageLength: ${c.visual.dashboard_items}, |
|
83 | 82 | order: [[ 0, "asc" ]], |
|
84 | 83 | columns: [ |
|
85 | 84 | { data: {"_": "name", |
|
86 | 85 | "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" }, |
|
87 | 86 | { data: 'menu', "bSortable": false, className: "quick_repo_menu" }, |
|
88 | 87 | { data: {"_": "desc", |
|
89 | 88 | "sort": "desc"}, title: "${_('Description')}", className: "td-description" }, |
|
90 | 89 | { data: {"_": "last_change", |
|
91 | 90 | "sort": "last_change_raw", |
|
92 | 91 | "type": Number}, title: "${_('Last Change')}", className: "td-time" }, |
|
93 | 92 | { data: {"_": "owner", |
|
94 | 93 | "sort": "owner"}, title: "${_('Owner')}", className: "td-user" } |
|
95 | 94 | ], |
|
96 | 95 | language: { |
|
97 | 96 | paginate: DEFAULT_GRID_PAGINATION, |
|
98 | 97 | emptyTable: _gettext("No repository groups available yet.") |
|
99 | 98 | }, |
|
100 | 99 | "drawCallback": function( settings, json ) { |
|
101 | 100 | timeagoActivate(); |
|
102 | 101 | quick_repo_menu(); |
|
103 | 102 | } |
|
104 | 103 | }); |
|
105 | 104 | % endif |
|
106 | 105 | |
|
107 | 106 | // repo list |
|
108 | 107 | % if c.repos_data != '[]': |
|
109 | 108 | $('#repo_list_table').DataTable({ |
|
110 | 109 | data: ${c.repos_data|n}, |
|
111 | 110 | dom: 'rtp', |
|
112 | 111 | order: [[ 0, "asc" ]], |
|
113 | 112 | pageLength: ${c.visual.dashboard_items}, |
|
114 | 113 | columns: [ |
|
115 | 114 | { data: {"_": "name", |
|
116 | 115 | "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" }, |
|
117 | 116 | { data: 'menu', "bSortable": false, className: "quick_repo_menu" }, |
|
118 | 117 | { data: {"_": "desc", |
|
119 | 118 | "sort": "desc"}, title: "${_('Description')}", className: "td-description" }, |
|
120 | 119 | { data: {"_": "last_change", |
|
121 | 120 | "sort": "last_change_raw", |
|
122 | 121 | "type": Number}, title: "${_('Last Change')}", className: "td-time" }, |
|
123 | 122 | { data: {"_": "last_changeset", |
|
124 | 123 | "sort": "last_changeset_raw", |
|
125 | 124 | "type": Number}, title: "${_('Commit')}", className: "td-hash" }, |
|
126 | 125 | { data: {"_": "owner", |
|
127 | 126 | "sort": "owner"}, title: "${_('Owner')}", className: "td-user" } |
|
128 | 127 | ], |
|
129 | 128 | language: { |
|
130 | 129 | paginate: DEFAULT_GRID_PAGINATION, |
|
131 | 130 | emptyTable: _gettext("No repositories available yet.") |
|
132 | 131 | }, |
|
133 | 132 | "drawCallback": function( settings, json ) { |
|
134 | 133 | timeagoActivate(); |
|
135 | 134 | quick_repo_menu(); |
|
136 | 135 | } |
|
137 | 136 | }); |
|
138 | 137 | % endif |
|
139 | 138 | |
|
140 | var getDatatableCount = function() { | |
|
141 | var reposCount = 0; | |
|
142 | var reposCountTotal = 0; | |
|
143 | ||
|
144 | % if c.repos_data != '[]': | |
|
145 | var pageInfo = $('#repo_list_table').dataTable().api().page.info(); | |
|
146 | var reposCount = pageInfo.recordsDisplay; | |
|
147 | var reposCountTotal = pageInfo.recordsTotal; | |
|
148 | % endif | |
|
149 | ||
|
150 | var repoGroupsCount = 0; | |
|
151 | var repoGroupsCountTotal = 0; | |
|
152 | ||
|
153 | % if c.repo_groups_data != '[]': | |
|
154 | var pageInfo = $('#group_list_table').dataTable().api().page.info(); | |
|
155 | var repoGroupsCount = pageInfo.recordsDisplay; | |
|
156 | var repoGroupsCountTotal = pageInfo.recordsTotal; | |
|
157 | % endif | |
|
158 | ||
|
159 | if (repoGroupsCount !== repoGroupsCountTotal) { | |
|
160 | $('#match_count').text(reposCount + repoGroupsCount); | |
|
161 | } | |
|
162 | if (reposCount !== reposCountTotal) { | |
|
163 | $('#match_container').show(); | |
|
164 | } | |
|
165 | if ($('#q_filter').val() === '') { | |
|
166 | $('#match_container').hide(); | |
|
167 | } | |
|
168 | }; | |
|
169 | ||
|
170 | // update the counter when doing search | |
|
171 | $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) { | |
|
172 | getDatatableCount(); | |
|
173 | }); | |
|
174 | ||
|
175 | // filter, filter both grids | |
|
176 | $('#q_filter').on( 'keyup', function () { | |
|
177 | ||
|
178 | % if c.repo_groups_data != '[]': | |
|
179 | var repo_group_api = $('#group_list_table').dataTable().api(); | |
|
180 | repo_group_api | |
|
181 | .columns( 0 ) | |
|
182 | .search( this.value ) | |
|
183 | .draw(); | |
|
184 | % endif | |
|
185 | ||
|
186 | % if c.repos_data != '[]': | |
|
187 | var repo_api = $('#repo_list_table').dataTable().api(); | |
|
188 | repo_api | |
|
189 | .columns( 0 ) | |
|
190 | .search( this.value ) | |
|
191 | .draw(); | |
|
192 | % endif | |
|
193 | ||
|
194 | }); | |
|
195 | ||
|
196 | // refilter table if page load via back button | |
|
197 | $("#q_filter").trigger('keyup'); | |
|
198 | ||
|
199 | 139 | }); |
|
200 | 140 | </script> |
|
201 | 141 | </%def> |
@@ -1,537 +1,537 b'' | |||
|
1 | 1 | <%inherit file="/base/base.mako"/> |
|
2 | 2 | |
|
3 | 3 | <%def name="title()"> |
|
4 | 4 | ${c.repo_name} ${_('New pull request')} |
|
5 | 5 | </%def> |
|
6 | 6 | |
|
7 | 7 | <%def name="breadcrumbs_links()"> |
|
8 | 8 | ${_('New pull request')} |
|
9 | 9 | </%def> |
|
10 | 10 | |
|
11 | 11 | <%def name="menu_bar_nav()"> |
|
12 | 12 | ${self.menu_items(active='repositories')} |
|
13 | 13 | </%def> |
|
14 | 14 | |
|
15 | 15 | <%def name="menu_bar_subnav()"> |
|
16 | 16 | ${self.repo_menu(active='showpullrequest')} |
|
17 | 17 | </%def> |
|
18 | 18 | |
|
19 | 19 | <%def name="main()"> |
|
20 | 20 | <div class="box"> |
|
21 | 21 | <div class="title"> |
|
22 | 22 | ${self.repo_page_title(c.rhodecode_db_repo)} |
|
23 | 23 | </div> |
|
24 | 24 | |
|
25 | 25 | ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)} |
|
26 | 26 | |
|
27 | 27 | ${self.breadcrumbs()} |
|
28 | 28 | |
|
29 | 29 | <div class="box pr-summary"> |
|
30 | 30 | |
|
31 | 31 | <div class="summary-details block-left"> |
|
32 | 32 | |
|
33 | 33 | |
|
34 | 34 | <div class="pr-details-title"> |
|
35 | 35 | ${_('Pull request summary')} |
|
36 | 36 | </div> |
|
37 | 37 | |
|
38 | 38 | <div class="form" style="padding-top: 10px"> |
|
39 | 39 | <!-- fields --> |
|
40 | 40 | |
|
41 | 41 | <div class="fields" > |
|
42 | 42 | |
|
43 | 43 | <div class="field"> |
|
44 | 44 | <div class="label"> |
|
45 | 45 | <label for="pullrequest_title">${_('Title')}:</label> |
|
46 | 46 | </div> |
|
47 | 47 | <div class="input"> |
|
48 | 48 | ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")} |
|
49 | 49 | </div> |
|
50 | 50 | </div> |
|
51 | 51 | |
|
52 | 52 | <div class="field"> |
|
53 | 53 | <div class="label label-textarea"> |
|
54 | 54 | <label for="pullrequest_desc">${_('Description')}:</label> |
|
55 | 55 | </div> |
|
56 | 56 | <div class="textarea text-area editor"> |
|
57 | 57 | ${h.textarea('pullrequest_desc',size=30, )} |
|
58 | 58 | <span class="help-block">${_('Write a short description on this pull request')}</span> |
|
59 | 59 | </div> |
|
60 | 60 | </div> |
|
61 | 61 | |
|
62 | 62 | <div class="field"> |
|
63 | 63 | <div class="label label-textarea"> |
|
64 | 64 | <label for="pullrequest_desc">${_('Commit flow')}:</label> |
|
65 | 65 | </div> |
|
66 | 66 | |
|
67 | 67 | ## TODO: johbo: Abusing the "content" class here to get the |
|
68 | 68 | ## desired effect. Should be replaced by a proper solution. |
|
69 | 69 | |
|
70 | 70 | ##ORG |
|
71 | 71 | <div class="content"> |
|
72 | 72 | <strong>${_('Source repository')}:</strong> |
|
73 | 73 | ${c.rhodecode_db_repo.description} |
|
74 | 74 | </div> |
|
75 | 75 | <div class="content"> |
|
76 | 76 | ${h.hidden('source_repo')} |
|
77 | 77 | ${h.hidden('source_ref')} |
|
78 | 78 | </div> |
|
79 | 79 | |
|
80 | 80 | ##OTHER, most Probably the PARENT OF THIS FORK |
|
81 | 81 | <div class="content"> |
|
82 | 82 | ## filled with JS |
|
83 | 83 | <div id="target_repo_desc"></div> |
|
84 | 84 | </div> |
|
85 | 85 | |
|
86 | 86 | <div class="content"> |
|
87 | 87 | ${h.hidden('target_repo')} |
|
88 | 88 | ${h.hidden('target_ref')} |
|
89 | 89 | <span id="target_ref_loading" style="display: none"> |
|
90 | 90 | ${_('Loading refs...')} |
|
91 | 91 | </span> |
|
92 | 92 | </div> |
|
93 | 93 | </div> |
|
94 | 94 | |
|
95 | 95 | <div class="field"> |
|
96 | 96 | <div class="label label-textarea"> |
|
97 | 97 | <label for="pullrequest_submit"></label> |
|
98 | 98 | </div> |
|
99 | 99 | <div class="input"> |
|
100 | 100 | <div class="pr-submit-button"> |
|
101 | 101 | ${h.submit('save',_('Submit Pull Request'),class_="btn")} |
|
102 | 102 | </div> |
|
103 | 103 | <div id="pr_open_message"></div> |
|
104 | 104 | </div> |
|
105 | 105 | </div> |
|
106 | 106 | |
|
107 | 107 | <div class="pr-spacing-container"></div> |
|
108 | 108 | </div> |
|
109 | 109 | </div> |
|
110 | 110 | </div> |
|
111 | 111 | <div> |
|
112 | 112 | ## AUTHOR |
|
113 | 113 | <div class="reviewers-title block-right"> |
|
114 | 114 | <div class="pr-details-title"> |
|
115 | 115 | ${_('Author of this pull request')} |
|
116 | 116 | </div> |
|
117 | 117 | </div> |
|
118 | 118 | <div class="block-right pr-details-content reviewers"> |
|
119 | 119 | <ul class="group_members"> |
|
120 | 120 | <li> |
|
121 | 121 | ${self.gravatar_with_user(c.rhodecode_user.email, 16)} |
|
122 | 122 | </li> |
|
123 | 123 | </ul> |
|
124 | 124 | </div> |
|
125 | 125 | |
|
126 | 126 | ## REVIEW RULES |
|
127 | 127 | <div id="review_rules" style="display: none" class="reviewers-title block-right"> |
|
128 | 128 | <div class="pr-details-title"> |
|
129 | 129 | ${_('Reviewer rules')} |
|
130 | 130 | </div> |
|
131 | 131 | <div class="pr-reviewer-rules"> |
|
132 | 132 | ## review rules will be appended here, by default reviewers logic |
|
133 | 133 | </div> |
|
134 | 134 | </div> |
|
135 | 135 | |
|
136 | 136 | ## REVIEWERS |
|
137 | 137 | <div class="reviewers-title block-right"> |
|
138 | 138 | <div class="pr-details-title"> |
|
139 | 139 | ${_('Pull request reviewers')} |
|
140 | 140 | <span class="calculate-reviewers"> - ${_('loading...')}</span> |
|
141 | 141 | </div> |
|
142 | 142 | </div> |
|
143 | 143 | <div id="reviewers" class="block-right pr-details-content reviewers"> |
|
144 | 144 | ## members goes here, filled via JS based on initial selection ! |
|
145 | 145 | <input type="hidden" name="__start__" value="review_members:sequence"> |
|
146 | 146 | <ul id="review_members" class="group_members"></ul> |
|
147 | 147 | <input type="hidden" name="__end__" value="review_members:sequence"> |
|
148 | 148 | <div id="add_reviewer_input" class='ac'> |
|
149 | 149 | <div class="reviewer_ac"> |
|
150 | 150 | ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))} |
|
151 | 151 | <div id="reviewers_container"></div> |
|
152 | 152 | </div> |
|
153 | 153 | </div> |
|
154 | 154 | </div> |
|
155 | 155 | </div> |
|
156 | 156 | </div> |
|
157 | 157 | <div class="box"> |
|
158 | 158 | <div> |
|
159 | 159 | ## overview pulled by ajax |
|
160 | 160 | <div id="pull_request_overview"></div> |
|
161 | 161 | </div> |
|
162 | 162 | </div> |
|
163 | 163 | ${h.end_form()} |
|
164 | 164 | </div> |
|
165 | 165 | |
|
166 | 166 | <script type="text/javascript"> |
|
167 | 167 | $(function(){ |
|
168 | 168 | var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}'; |
|
169 | 169 | var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n}; |
|
170 | 170 | var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}'; |
|
171 | 171 | var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n}; |
|
172 | 172 | |
|
173 | 173 | var $pullRequestForm = $('#pull_request_form'); |
|
174 | 174 | var $sourceRepo = $('#source_repo', $pullRequestForm); |
|
175 | 175 | var $targetRepo = $('#target_repo', $pullRequestForm); |
|
176 | 176 | var $sourceRef = $('#source_ref', $pullRequestForm); |
|
177 | 177 | var $targetRef = $('#target_ref', $pullRequestForm); |
|
178 | 178 | |
|
179 | 179 | var sourceRepo = function() { return $sourceRepo.eq(0).val() }; |
|
180 | 180 | var sourceRef = function() { return $sourceRef.eq(0).val().split(':') }; |
|
181 | 181 | |
|
182 | 182 | var targetRepo = function() { return $targetRepo.eq(0).val() }; |
|
183 | 183 | var targetRef = function() { return $targetRef.eq(0).val().split(':') }; |
|
184 | 184 | |
|
185 | 185 | var calculateContainerWidth = function() { |
|
186 | 186 | var maxWidth = 0; |
|
187 | 187 | var repoSelect2Containers = ['#source_repo', '#target_repo']; |
|
188 | 188 | $.each(repoSelect2Containers, function(idx, value) { |
|
189 | 189 | $(value).select2('container').width('auto'); |
|
190 | 190 | var curWidth = $(value).select2('container').width(); |
|
191 | 191 | if (maxWidth <= curWidth) { |
|
192 | 192 | maxWidth = curWidth; |
|
193 | 193 | } |
|
194 | 194 | $.each(repoSelect2Containers, function(idx, value) { |
|
195 | 195 | $(value).select2('container').width(maxWidth + 10); |
|
196 | 196 | }); |
|
197 | 197 | }); |
|
198 | 198 | }; |
|
199 | 199 | |
|
200 | 200 | var initRefSelection = function(selectedRef) { |
|
201 | 201 | return function(element, callback) { |
|
202 | 202 | // translate our select2 id into a text, it's a mapping to show |
|
203 | 203 | // simple label when selecting by internal ID. |
|
204 | 204 | var id, refData; |
|
205 | 205 | if (selectedRef === undefined || selectedRef === null) { |
|
206 | 206 | id = element.val(); |
|
207 | 207 | refData = element.val().split(':'); |
|
208 | 208 | |
|
209 | 209 | if (refData.length !== 3){ |
|
210 | 210 | refData = ["", "", ""] |
|
211 | 211 | } |
|
212 | 212 | } else { |
|
213 | 213 | id = selectedRef; |
|
214 | 214 | refData = selectedRef.split(':'); |
|
215 | 215 | } |
|
216 | 216 | |
|
217 | 217 | var text = refData[1]; |
|
218 | 218 | if (refData[0] === 'rev') { |
|
219 | 219 | text = text.substring(0, 12); |
|
220 | 220 | } |
|
221 | 221 | |
|
222 | 222 | var data = {id: id, text: text}; |
|
223 | 223 | callback(data); |
|
224 | 224 | }; |
|
225 | 225 | }; |
|
226 | 226 | |
|
227 | 227 | var formatRefSelection = function(item) { |
|
228 | 228 | var prefix = ''; |
|
229 | 229 | var refData = item.id.split(':'); |
|
230 | 230 | if (refData[0] === 'branch') { |
|
231 | 231 | prefix = '<i class="icon-branch"></i>'; |
|
232 | 232 | } |
|
233 | 233 | else if (refData[0] === 'book') { |
|
234 | 234 | prefix = '<i class="icon-bookmark"></i>'; |
|
235 | 235 | } |
|
236 | 236 | else if (refData[0] === 'tag') { |
|
237 | 237 | prefix = '<i class="icon-tag"></i>'; |
|
238 | 238 | } |
|
239 | 239 | |
|
240 | 240 | var originalOption = item.element; |
|
241 | 241 | return prefix + item.text; |
|
242 | 242 | }; |
|
243 | 243 | |
|
244 | 244 | // custom code mirror |
|
245 | 245 | var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc'); |
|
246 | 246 | |
|
247 | 247 | reviewersController = new ReviewersController(); |
|
248 | 248 | |
|
249 | 249 | var queryTargetRepo = function(self, query) { |
|
250 | 250 | // cache ALL results if query is empty |
|
251 | 251 | var cacheKey = query.term || '__'; |
|
252 | 252 | var cachedData = self.cachedDataSource[cacheKey]; |
|
253 | 253 | |
|
254 | 254 | if (cachedData) { |
|
255 | 255 | query.callback({results: cachedData.results}); |
|
256 | 256 | } else { |
|
257 | 257 | $.ajax({ |
|
258 | 258 | url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}), |
|
259 | 259 | data: {query: query.term}, |
|
260 | 260 | dataType: 'json', |
|
261 | 261 | type: 'GET', |
|
262 | 262 | success: function(data) { |
|
263 | 263 | self.cachedDataSource[cacheKey] = data; |
|
264 | 264 | query.callback({results: data.results}); |
|
265 | 265 | }, |
|
266 | 266 | error: function(data, textStatus, errorThrown) { |
|
267 | 267 | alert( |
|
268 | 268 | "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); |
|
269 | 269 | } |
|
270 | 270 | }); |
|
271 | 271 | } |
|
272 | 272 | }; |
|
273 | 273 | |
|
274 | 274 | var queryTargetRefs = function(initialData, query) { |
|
275 | 275 | var data = {results: []}; |
|
276 | 276 | // filter initialData |
|
277 | 277 | $.each(initialData, function() { |
|
278 | 278 | var section = this.text; |
|
279 | 279 | var children = []; |
|
280 | 280 | $.each(this.children, function() { |
|
281 | 281 | if (query.term.length === 0 || |
|
282 | 282 | this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) { |
|
283 | 283 | children.push({'id': this.id, 'text': this.text}) |
|
284 | 284 | } |
|
285 | 285 | }); |
|
286 | 286 | data.results.push({'text': section, 'children': children}) |
|
287 | 287 | }); |
|
288 | 288 | query.callback({results: data.results}); |
|
289 | 289 | }; |
|
290 | 290 | |
|
291 | 291 | var loadRepoRefDiffPreview = function() { |
|
292 | 292 | |
|
293 | 293 | var url_data = { |
|
294 | 294 | 'repo_name': targetRepo(), |
|
295 | 295 | 'target_repo': sourceRepo(), |
|
296 | 296 | 'source_ref': targetRef()[2], |
|
297 | 297 | 'source_ref_type': 'rev', |
|
298 | 298 | 'target_ref': sourceRef()[2], |
|
299 | 299 | 'target_ref_type': 'rev', |
|
300 | 300 | 'merge': true, |
|
301 | 301 | '_': Date.now() // bypass browser caching |
|
302 | 302 | }; // gather the source/target ref and repo here |
|
303 | 303 | |
|
304 | 304 | if (sourceRef().length !== 3 || targetRef().length !== 3) { |
|
305 | 305 | prButtonLock(true, "${_('Please select source and target')}"); |
|
306 | 306 | return; |
|
307 | 307 | } |
|
308 | 308 | var url = pyroutes.url('repo_compare', url_data); |
|
309 | 309 | |
|
310 | 310 | // lock PR button, so we cannot send PR before it's calculated |
|
311 | 311 | prButtonLock(true, "${_('Loading compare ...')}", 'compare'); |
|
312 | 312 | |
|
313 | 313 | if (loadRepoRefDiffPreview._currentRequest) { |
|
314 | 314 | loadRepoRefDiffPreview._currentRequest.abort(); |
|
315 | 315 | } |
|
316 | 316 | |
|
317 | 317 | loadRepoRefDiffPreview._currentRequest = $.get(url) |
|
318 | 318 | .error(function(data, textStatus, errorThrown) { |
|
319 | 319 | if (textStatus !== 'abort') { |
|
320 | 320 | alert( |
|
321 | 321 | "Error while processing request.\nError code {0} ({1}).".format( |
|
322 | 322 | data.status, data.statusText)); |
|
323 | 323 | } |
|
324 | 324 | |
|
325 | 325 | }) |
|
326 | 326 | .done(function(data) { |
|
327 | 327 | loadRepoRefDiffPreview._currentRequest = null; |
|
328 | 328 | $('#pull_request_overview').html(data); |
|
329 | 329 | |
|
330 | 330 | var commitElements = $(data).find('tr[commit_id]'); |
|
331 | 331 | |
|
332 | 332 | var prTitleAndDesc = getTitleAndDescription( |
|
333 | 333 | sourceRef()[1], commitElements, 5); |
|
334 | 334 | |
|
335 | 335 | var title = prTitleAndDesc[0]; |
|
336 | 336 | var proposedDescription = prTitleAndDesc[1]; |
|
337 | 337 | |
|
338 | 338 | var useGeneratedTitle = ( |
|
339 | 339 | $('#pullrequest_title').hasClass('autogenerated-title') || |
|
340 | 340 | $('#pullrequest_title').val() === ""); |
|
341 | 341 | |
|
342 | 342 | if (title && useGeneratedTitle) { |
|
343 | 343 | // use generated title if we haven't specified our own |
|
344 | 344 | $('#pullrequest_title').val(title); |
|
345 | 345 | $('#pullrequest_title').addClass('autogenerated-title'); |
|
346 | 346 | |
|
347 | 347 | } |
|
348 | 348 | |
|
349 | 349 | var useGeneratedDescription = ( |
|
350 | 350 | !codeMirrorInstance._userDefinedDesc || |
|
351 | 351 | codeMirrorInstance.getValue() === ""); |
|
352 | 352 | |
|
353 | 353 | if (proposedDescription && useGeneratedDescription) { |
|
354 | 354 | // set proposed content, if we haven't defined our own, |
|
355 | 355 | // or we don't have description written |
|
356 | 356 | codeMirrorInstance._userDefinedDesc = false; // reset state |
|
357 | 357 | codeMirrorInstance.setValue(proposedDescription); |
|
358 | 358 | } |
|
359 | 359 | |
|
360 | 360 | var msg = ''; |
|
361 | 361 | if (commitElements.length === 1) { |
|
362 | 362 | msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}"; |
|
363 | 363 | } else { |
|
364 | 364 | msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}"; |
|
365 | 365 | } |
|
366 | 366 | |
|
367 | 367 | msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url); |
|
368 | 368 | |
|
369 | 369 | if (commitElements.length) { |
|
370 | 370 | var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length); |
|
371 | 371 | prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare'); |
|
372 | 372 | } |
|
373 | 373 | else { |
|
374 | 374 | prButtonLock(true, "${_('There are no commits to merge.')}", 'compare'); |
|
375 | 375 | } |
|
376 | 376 | |
|
377 | 377 | |
|
378 | 378 | }); |
|
379 | 379 | }; |
|
380 | 380 | |
|
381 | 381 | var Select2Box = function(element, overrides) { |
|
382 | 382 | var globalDefaults = { |
|
383 | 383 | dropdownAutoWidth: true, |
|
384 | 384 | containerCssClass: "drop-menu", |
|
385 | 385 | dropdownCssClass: "drop-menu-dropdown" |
|
386 | 386 | }; |
|
387 | 387 | |
|
388 | 388 | var initSelect2 = function(defaultOptions) { |
|
389 | 389 | var options = jQuery.extend(globalDefaults, defaultOptions, overrides); |
|
390 | 390 | element.select2(options); |
|
391 | 391 | }; |
|
392 | 392 | |
|
393 | 393 | return { |
|
394 | 394 | initRef: function() { |
|
395 | 395 | var defaultOptions = { |
|
396 | 396 | minimumResultsForSearch: 5, |
|
397 | 397 | formatSelection: formatRefSelection |
|
398 | 398 | }; |
|
399 | 399 | |
|
400 | 400 | initSelect2(defaultOptions); |
|
401 | 401 | }, |
|
402 | 402 | |
|
403 | 403 | initRepo: function(defaultValue, readOnly) { |
|
404 | 404 | var defaultOptions = { |
|
405 | 405 | initSelection : function (element, callback) { |
|
406 | 406 | var data = {id: defaultValue, text: defaultValue}; |
|
407 | 407 | callback(data); |
|
408 | 408 | } |
|
409 | 409 | }; |
|
410 | 410 | |
|
411 | 411 | initSelect2(defaultOptions); |
|
412 | 412 | |
|
413 | 413 | element.select2('val', defaultSourceRepo); |
|
414 | 414 | if (readOnly === true) { |
|
415 | 415 | element.select2('readonly', true); |
|
416 | 416 | } |
|
417 | 417 | } |
|
418 | 418 | }; |
|
419 | 419 | }; |
|
420 | 420 | |
|
421 | 421 | var initTargetRefs = function(refsData, selectedRef) { |
|
422 | 422 | |
|
423 | 423 | Select2Box($targetRef, { |
|
424 | 424 | placeholder: "${_('Select commit reference')}", |
|
425 | 425 | query: function(query) { |
|
426 | 426 | queryTargetRefs(refsData, query); |
|
427 | 427 | }, |
|
428 | 428 | initSelection : initRefSelection(selectedRef) |
|
429 | 429 | }).initRef(); |
|
430 | 430 | |
|
431 | 431 | if (!(selectedRef === undefined)) { |
|
432 | 432 | $targetRef.select2('val', selectedRef); |
|
433 | 433 | } |
|
434 | 434 | }; |
|
435 | 435 | |
|
436 | 436 | var targetRepoChanged = function(repoData) { |
|
437 | 437 | // generate new DESC of target repo displayed next to select |
|
438 | 438 | var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']}); |
|
439 | 439 | $('#target_repo_desc').html( |
|
440 | 440 | "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink) |
|
441 | 441 | ); |
|
442 | 442 | |
|
443 | 443 | // generate dynamic select2 for refs. |
|
444 | 444 | initTargetRefs(repoData['refs']['select2_refs'], |
|
445 | 445 | repoData['refs']['selected_ref']); |
|
446 | 446 | |
|
447 | 447 | }; |
|
448 | 448 | |
|
449 | 449 | var sourceRefSelect2 = Select2Box($sourceRef, { |
|
450 | 450 | placeholder: "${_('Select commit reference')}", |
|
451 | 451 | query: function(query) { |
|
452 | 452 | var initialData = defaultSourceRepoData['refs']['select2_refs']; |
|
453 | 453 | queryTargetRefs(initialData, query) |
|
454 | 454 | }, |
|
455 | 455 | initSelection: initRefSelection() |
|
456 | 456 | } |
|
457 | 457 | ); |
|
458 | 458 | |
|
459 | 459 | var sourceRepoSelect2 = Select2Box($sourceRepo, { |
|
460 | 460 | query: function(query) {} |
|
461 | 461 | }); |
|
462 | 462 | |
|
463 | 463 | var targetRepoSelect2 = Select2Box($targetRepo, { |
|
464 | 464 | cachedDataSource: {}, |
|
465 | 465 | query: $.debounce(250, function(query) { |
|
466 | 466 | queryTargetRepo(this, query); |
|
467 | 467 | }), |
|
468 | formatResult: formatResult | |
|
468 | formatResult: formatRepoResult | |
|
469 | 469 | }); |
|
470 | 470 | |
|
471 | 471 | sourceRefSelect2.initRef(); |
|
472 | 472 | |
|
473 | 473 | sourceRepoSelect2.initRepo(defaultSourceRepo, true); |
|
474 | 474 | |
|
475 | 475 | targetRepoSelect2.initRepo(defaultTargetRepo, false); |
|
476 | 476 | |
|
477 | 477 | $sourceRef.on('change', function(e){ |
|
478 | 478 | loadRepoRefDiffPreview(); |
|
479 | 479 | reviewersController.loadDefaultReviewers( |
|
480 | 480 | sourceRepo(), sourceRef(), targetRepo(), targetRef()); |
|
481 | 481 | }); |
|
482 | 482 | |
|
483 | 483 | $targetRef.on('change', function(e){ |
|
484 | 484 | loadRepoRefDiffPreview(); |
|
485 | 485 | reviewersController.loadDefaultReviewers( |
|
486 | 486 | sourceRepo(), sourceRef(), targetRepo(), targetRef()); |
|
487 | 487 | }); |
|
488 | 488 | |
|
489 | 489 | $targetRepo.on('change', function(e){ |
|
490 | 490 | var repoName = $(this).val(); |
|
491 | 491 | calculateContainerWidth(); |
|
492 | 492 | $targetRef.select2('destroy'); |
|
493 | 493 | $('#target_ref_loading').show(); |
|
494 | 494 | |
|
495 | 495 | $.ajax({ |
|
496 | 496 | url: pyroutes.url('pullrequest_repo_refs', |
|
497 | 497 | {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}), |
|
498 | 498 | data: {}, |
|
499 | 499 | dataType: 'json', |
|
500 | 500 | type: 'GET', |
|
501 | 501 | success: function(data) { |
|
502 | 502 | $('#target_ref_loading').hide(); |
|
503 | 503 | targetRepoChanged(data); |
|
504 | 504 | loadRepoRefDiffPreview(); |
|
505 | 505 | }, |
|
506 | 506 | error: function(data, textStatus, errorThrown) { |
|
507 | 507 | alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); |
|
508 | 508 | } |
|
509 | 509 | }) |
|
510 | 510 | |
|
511 | 511 | }); |
|
512 | 512 | |
|
513 | 513 | prButtonLock(true, "${_('Please select source and target')}", 'all'); |
|
514 | 514 | |
|
515 | 515 | // auto-load on init, the target refs select2 |
|
516 | 516 | calculateContainerWidth(); |
|
517 | 517 | targetRepoChanged(defaultTargetRepoData); |
|
518 | 518 | |
|
519 | 519 | $('#pullrequest_title').on('keyup', function(e){ |
|
520 | 520 | $(this).removeClass('autogenerated-title'); |
|
521 | 521 | }); |
|
522 | 522 | |
|
523 | 523 | % if c.default_source_ref: |
|
524 | 524 | // in case we have a pre-selected value, use it now |
|
525 | 525 | $sourceRef.select2('val', '${c.default_source_ref}'); |
|
526 | 526 | // diff preview load |
|
527 | 527 | loadRepoRefDiffPreview(); |
|
528 | 528 | // default reviewers |
|
529 | 529 | reviewersController.loadDefaultReviewers( |
|
530 | 530 | sourceRepo(), sourceRef(), targetRepo(), targetRef()); |
|
531 | 531 | % endif |
|
532 | 532 | |
|
533 | 533 | ReviewerAutoComplete('#user'); |
|
534 | 534 | }); |
|
535 | 535 | </script> |
|
536 | 536 | |
|
537 | 537 | </%def> |
General Comments 0
You need to be logged in to leave comments.
Login now