##// END OF EJS Templates
integrations: expose EE integrations as dummy objects to show...
marcink -
r2138:cfe7604e default
parent child Browse files
Show More
@@ -1,62 +1,68 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from rhodecode.integrations.registry import IntegrationTypeRegistry
23 from rhodecode.integrations.registry import IntegrationTypeRegistry
24 from rhodecode.integrations.types import webhook, slack, hipchat, email
24 from rhodecode.integrations.types import webhook, slack, hipchat, email, base
25
26 log = logging.getLogger(__name__)
25 log = logging.getLogger(__name__)
27
26
28
27
29 # TODO: dan: This is currently global until we figure out what to do about
28 # TODO: dan: This is currently global until we figure out what to do about
30 # VCS's not having a pyramid context - move it to pyramid app configuration
29 # VCS's not having a pyramid context - move it to pyramid app configuration
31 # includeme level later to allow per instance integration setup
30 # includeme level later to allow per instance integration setup
32 integration_type_registry = IntegrationTypeRegistry()
31 integration_type_registry = IntegrationTypeRegistry()
33
32
34 integration_type_registry.register_integration_type(
33 integration_type_registry.register_integration_type(
35 webhook.WebhookIntegrationType)
34 webhook.WebhookIntegrationType)
36 integration_type_registry.register_integration_type(
35 integration_type_registry.register_integration_type(
37 slack.SlackIntegrationType)
36 slack.SlackIntegrationType)
38 integration_type_registry.register_integration_type(
37 integration_type_registry.register_integration_type(
39 hipchat.HipchatIntegrationType)
38 hipchat.HipchatIntegrationType)
40 integration_type_registry.register_integration_type(
39 integration_type_registry.register_integration_type(
41 email.EmailIntegrationType)
40 email.EmailIntegrationType)
42
41
43
42
43 # dummy EE integration to show users what we have in EE edition
44 integration_type_registry.register_integration_type(
45 base.EEIntegration('Jira Issues integration', 'jira'))
46 integration_type_registry.register_integration_type(
47 base.EEIntegration('Redmine Tracker integration', 'redmine'))
48
49
44 def integrations_event_handler(event):
50 def integrations_event_handler(event):
45 """
51 """
46 Takes an event and passes it to all enabled integrations
52 Takes an event and passes it to all enabled integrations
47 """
53 """
48 from rhodecode.model.integration import IntegrationModel
54 from rhodecode.model.integration import IntegrationModel
49
55
50 integration_model = IntegrationModel()
56 integration_model = IntegrationModel()
51 integrations = integration_model.get_for_event(event)
57 integrations = integration_model.get_for_event(event)
52 for integration in integrations:
58 for integration in integrations:
53 try:
59 try:
54 integration_model.send_event(integration, event)
60 integration_model.send_event(integration, event)
55 except Exception:
61 except Exception:
56 log.exception(
62 log.exception(
57 'failure occurred when sending event %s to integration %s' % (
63 'failure occurred when sending event %s to integration %s' % (
58 event, integration))
64 event, integration))
59
65
60
66
61 def includeme(config):
67 def includeme(config):
62 config.include('rhodecode.integrations.routes')
68 config.include('rhodecode.integrations.routes')
@@ -1,37 +1,38 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2012-2017 RhodeCode GmbH
2 # Copyright (C) 2012-2017 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import logging
20 import logging
21 import collections
21
22
22 log = logging.getLogger(__name__)
23 log = logging.getLogger(__name__)
23
24
24
25
25 class IntegrationTypeRegistry(dict):
26 class IntegrationTypeRegistry(collections.OrderedDict):
26 """
27 """
27 Registry Class to hold IntegrationTypes
28 Registry Class to hold IntegrationTypes
28 """
29 """
29 def register_integration_type(self, IntegrationType):
30 def register_integration_type(self, IntegrationType):
30 key = IntegrationType.key
31 key = IntegrationType.key
31 if key in self:
32 if key in self:
32 log.warning(
33 log.warning(
33 'Overriding existing integration type %s (%s) with %s' % (
34 'Overriding existing integration type %s (%s) with %s' % (
34 self[key], key, IntegrationType))
35 self[key], key, IntegrationType))
35
36
36 self[key] = IntegrationType
37 self[key] = IntegrationType
37
38
@@ -1,264 +1,269 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.apps._base import ADMIN_PREFIX
23 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.model.db import Integration
24 from rhodecode.model.db import Integration
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.integrations import integration_type_registry
26 from rhodecode.integrations import integration_type_registry
27
27
28
28
29 def route_path(name, **kwargs):
29 def route_path(name, **kwargs):
30 return {
30 return {
31 'home': '/',
31 'home': '/',
32 }[name].format(**kwargs)
32 }[name].format(**kwargs)
33
33
34
34
35 @pytest.mark.usefixtures('app', 'autologin_user')
35 @pytest.mark.usefixtures('app', 'autologin_user')
36 class TestIntegrationsView(object):
36 class TestIntegrationsView(object):
37 pass
37 pass
38
38
39
39
40 class TestGlobalIntegrationsView(TestIntegrationsView):
40 class TestGlobalIntegrationsView(TestIntegrationsView):
41 def test_index_no_integrations(self):
41 def test_index_no_integrations(self):
42 url = ADMIN_PREFIX + '/integrations'
42 url = ADMIN_PREFIX + '/integrations'
43 response = self.app.get(url)
43 response = self.app.get(url)
44
44
45 assert response.status_code == 200
45 assert response.status_code == 200
46 assert 'exist yet' in response.body
46 response.mustcontain('exist yet')
47
47
48 def test_index_with_integrations(self, global_integration_stub):
48 def test_index_with_integrations(self, global_integration_stub):
49 url = ADMIN_PREFIX + '/integrations'
49 url = ADMIN_PREFIX + '/integrations'
50 response = self.app.get(url)
50 response = self.app.get(url)
51
51
52 assert response.status_code == 200
52 assert response.status_code == 200
53 assert 'exist yet' not in response.body
53 response.mustcontain(no=['exist yet'])
54 assert global_integration_stub.name in response.body
54 response.mustcontain(global_integration_stub.name)
55
55
56 @pytest.mark.parametrize(
56 @pytest.mark.parametrize(
57 'IntegrationType', integration_type_registry.values())
57 'IntegrationType', integration_type_registry.values())
58 def test_new_integration_page(self, IntegrationType):
58 def test_new_integration_page(self, IntegrationType):
59 url = ADMIN_PREFIX + '/integrations/new'
59 url = ADMIN_PREFIX + '/integrations/new'
60
60
61 response = self.app.get(url, status=200)
61 response = self.app.get(url, status=200)
62
62 if not IntegrationType.is_dummy:
63 url = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
63 url = (ADMIN_PREFIX + '/integrations/{integration}/new').format(
64 integration=IntegrationType.key)
64 integration=IntegrationType.key)
65 assert url in response.body
65 response.mustcontain(url)
66
66
67 @pytest.mark.parametrize(
67 @pytest.mark.parametrize(
68 'IntegrationType', integration_type_registry.values())
68 'IntegrationType', integration_type_registry.values())
69 def test_get_create_integration_page(self, IntegrationType):
69 def test_get_create_integration_page(self, IntegrationType):
70 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
70 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
71 integration_key=IntegrationType.key)
71 integration_key=IntegrationType.key)
72
72 if IntegrationType.is_dummy:
73 self.app.get(url, status=404)
74 else:
73 response = self.app.get(url, status=200)
75 response = self.app.get(url, status=200)
74
76 response.mustcontain(IntegrationType.display_name)
75 assert IntegrationType.display_name in response.body
76
77
77 def test_post_integration_page(self, StubIntegrationType, csrf_token,
78 def test_post_integration_page(self, StubIntegrationType, csrf_token,
78 test_repo_group, backend_random):
79 test_repo_group, backend_random):
79 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
80 url = ADMIN_PREFIX + '/integrations/{integration_key}/new'.format(
80 integration_key=StubIntegrationType.key)
81 integration_key=StubIntegrationType.key)
81
82
82 _post_integration_test_helper(self.app, url, csrf_token, admin_view=True,
83 _post_integration_test_helper(
84 self.app, url, csrf_token, admin_view=True,
83 repo=backend_random.repo, repo_group=test_repo_group)
85 repo=backend_random.repo, repo_group=test_repo_group)
84
86
85
87
86 class TestRepoIntegrationsView(TestIntegrationsView):
88 class TestRepoIntegrationsView(TestIntegrationsView):
87 def test_index_no_integrations(self, backend_random):
89 def test_index_no_integrations(self, backend_random):
88 url = '/{repo_name}/settings/integrations'.format(
90 url = '/{repo_name}/settings/integrations'.format(
89 repo_name=backend_random.repo.repo_name)
91 repo_name=backend_random.repo.repo_name)
90 response = self.app.get(url)
92 response = self.app.get(url)
91
93
92 assert response.status_code == 200
94 assert response.status_code == 200
93 assert 'exist yet' in response.body
95 response.mustcontain('exist yet')
94
96
95 def test_index_with_integrations(self, repo_integration_stub):
97 def test_index_with_integrations(self, repo_integration_stub):
96 url = '/{repo_name}/settings/integrations'.format(
98 url = '/{repo_name}/settings/integrations'.format(
97 repo_name=repo_integration_stub.repo.repo_name)
99 repo_name=repo_integration_stub.repo.repo_name)
98 stub_name = repo_integration_stub.name
100 stub_name = repo_integration_stub.name
99
101
100 response = self.app.get(url)
102 response = self.app.get(url)
101
103
102 assert response.status_code == 200
104 assert response.status_code == 200
103 assert stub_name in response.body
105 response.mustcontain(stub_name)
104 assert 'exist yet' not in response.body
106 response.mustcontain(no=['exist yet'])
105
107
106 @pytest.mark.parametrize(
108 @pytest.mark.parametrize(
107 'IntegrationType', integration_type_registry.values())
109 'IntegrationType', integration_type_registry.values())
108 def test_new_integration_page(self, backend_random, IntegrationType):
110 def test_new_integration_page(self, backend_random, IntegrationType):
109 repo_name = backend_random.repo.repo_name
111 repo_name = backend_random.repo.repo_name
110 url = '/{repo_name}/settings/integrations/new'.format(
112 url = '/{repo_name}/settings/integrations/new'.format(
111 repo_name=repo_name)
113 repo_name=repo_name)
112
114
113 response = self.app.get(url, status=200)
115 response = self.app.get(url, status=200)
114
116
115 url = '/{repo_name}/settings/integrations/{integration}/new'.format(
117 url = '/{repo_name}/settings/integrations/{integration}/new'.format(
116 repo_name=repo_name,
118 repo_name=repo_name,
117 integration=IntegrationType.key)
119 integration=IntegrationType.key)
118
120 if not IntegrationType.is_dummy:
119 assert url in response.body
121 response.mustcontain(url)
120
122
121 @pytest.mark.parametrize(
123 @pytest.mark.parametrize(
122 'IntegrationType', integration_type_registry.values())
124 'IntegrationType', integration_type_registry.values())
123 def test_get_create_integration_page(self, backend_random, IntegrationType):
125 def test_get_create_integration_page(self, backend_random, IntegrationType):
124 repo_name = backend_random.repo.repo_name
126 repo_name = backend_random.repo.repo_name
125 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
127 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
126 repo_name=repo_name, integration_key=IntegrationType.key)
128 repo_name=repo_name, integration_key=IntegrationType.key)
127
129 if IntegrationType.is_dummy:
130 self.app.get(url, status=404)
131 else:
128 response = self.app.get(url, status=200)
132 response = self.app.get(url, status=200)
129
133 response.mustcontain(IntegrationType.display_name)
130 assert IntegrationType.display_name in response.body
131
134
132 def test_post_integration_page(self, backend_random, test_repo_group,
135 def test_post_integration_page(self, backend_random, test_repo_group,
133 StubIntegrationType, csrf_token):
136 StubIntegrationType, csrf_token):
134 repo_name = backend_random.repo.repo_name
137 repo_name = backend_random.repo.repo_name
135 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
138 url = '/{repo_name}/settings/integrations/{integration_key}/new'.format(
136 repo_name=repo_name, integration_key=StubIntegrationType.key)
139 repo_name=repo_name, integration_key=StubIntegrationType.key)
137
140
138 _post_integration_test_helper(
141 _post_integration_test_helper(
139 self.app, url, csrf_token, admin_view=False,
142 self.app, url, csrf_token, admin_view=False,
140 repo=backend_random.repo, repo_group=test_repo_group)
143 repo=backend_random.repo, repo_group=test_repo_group)
141
144
142
145
143 class TestRepoGroupIntegrationsView(TestIntegrationsView):
146 class TestRepoGroupIntegrationsView(TestIntegrationsView):
144 def test_index_no_integrations(self, test_repo_group):
147 def test_index_no_integrations(self, test_repo_group):
145 url = '/{repo_group_name}/settings/integrations'.format(
148 url = '/{repo_group_name}/settings/integrations'.format(
146 repo_group_name=test_repo_group.group_name)
149 repo_group_name=test_repo_group.group_name)
147 response = self.app.get(url)
150 response = self.app.get(url)
148
151
149 assert response.status_code == 200
152 assert response.status_code == 200
150 assert 'exist yet' in response.body
153 response.mustcontain('exist yet')
151
154
152 def test_index_with_integrations(
155 def test_index_with_integrations(
153 self, test_repo_group, repogroup_integration_stub):
156 self, test_repo_group, repogroup_integration_stub):
154
157
155 url = '/{repo_group_name}/settings/integrations'.format(
158 url = '/{repo_group_name}/settings/integrations'.format(
156 repo_group_name=test_repo_group.group_name)
159 repo_group_name=test_repo_group.group_name)
157
160
158 stub_name = repogroup_integration_stub.name
161 stub_name = repogroup_integration_stub.name
159 response = self.app.get(url)
162 response = self.app.get(url)
160
163
161 assert response.status_code == 200
164 assert response.status_code == 200
162 assert 'exist yet' not in response.body
165 response.mustcontain(no=['exist yet'])
163 assert stub_name in response.body
166 response.mustcontain(stub_name)
164
167
165 def test_new_integration_page(self, test_repo_group):
168 def test_new_integration_page(self, test_repo_group):
166 repo_group_name = test_repo_group.group_name
169 repo_group_name = test_repo_group.group_name
167 url = '/{repo_group_name}/settings/integrations/new'.format(
170 url = '/{repo_group_name}/settings/integrations/new'.format(
168 repo_group_name=test_repo_group.group_name)
171 repo_group_name=test_repo_group.group_name)
169
172
170 response = self.app.get(url)
173 response = self.app.get(url)
171
174
172 assert response.status_code == 200
175 assert response.status_code == 200
173
176
174 for integration_key in integration_type_registry:
177 for integration_key, integration_obj in integration_type_registry.items():
175 nurl = ('/{repo_group_name}/settings/integrations/{integration}/new').format(
178 if not integration_obj.is_dummy:
179 nurl = (
180 '/{repo_group_name}/settings/integrations/{integration}/new').format(
176 repo_group_name=repo_group_name,
181 repo_group_name=repo_group_name,
177 integration=integration_key)
182 integration=integration_key)
178
183 response.mustcontain(nurl)
179 assert nurl in response.body
180
184
181 @pytest.mark.parametrize(
185 @pytest.mark.parametrize(
182 'IntegrationType', integration_type_registry.values())
186 'IntegrationType', integration_type_registry.values())
183 def test_get_create_integration_page(
187 def test_get_create_integration_page(
184 self, test_repo_group, IntegrationType):
188 self, test_repo_group, IntegrationType):
185
189
186 repo_group_name = test_repo_group.group_name
190 repo_group_name = test_repo_group.group_name
187 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
191 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
188 ).format(repo_group_name=repo_group_name,
192 ).format(repo_group_name=repo_group_name,
189 integration_key=IntegrationType.key)
193 integration_key=IntegrationType.key)
190
194
191 response = self.app.get(url)
195 if not IntegrationType.is_dummy:
192
196 response = self.app.get(url, status=200)
193 assert response.status_code == 200
197 response.mustcontain(IntegrationType.display_name)
194 assert IntegrationType.display_name in response.body
198 else:
199 self.app.get(url, status=404)
195
200
196 def test_post_integration_page(self, test_repo_group, backend_random,
201 def test_post_integration_page(self, test_repo_group, backend_random,
197 StubIntegrationType, csrf_token):
202 StubIntegrationType, csrf_token):
198
203
199 repo_group_name = test_repo_group.group_name
204 repo_group_name = test_repo_group.group_name
200 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
205 url = ('/{repo_group_name}/settings/integrations/{integration_key}/new'
201 ).format(repo_group_name=repo_group_name,
206 ).format(repo_group_name=repo_group_name,
202 integration_key=StubIntegrationType.key)
207 integration_key=StubIntegrationType.key)
203
208
204 _post_integration_test_helper(
209 _post_integration_test_helper(
205 self.app, url, csrf_token, admin_view=False,
210 self.app, url, csrf_token, admin_view=False,
206 repo=backend_random.repo, repo_group=test_repo_group)
211 repo=backend_random.repo, repo_group=test_repo_group)
207
212
208
213
209 def _post_integration_test_helper(app, url, csrf_token, repo, repo_group,
214 def _post_integration_test_helper(app, url, csrf_token, repo, repo_group,
210 admin_view):
215 admin_view):
211 """
216 """
212 Posts form data to create integration at the url given then deletes it and
217 Posts form data to create integration at the url given then deletes it and
213 checks if the redirect url is correct.
218 checks if the redirect url is correct.
214 """
219 """
215 repo_name = repo.repo_name
220 repo_name = repo.repo_name
216 repo_group_name = repo_group.group_name
221 repo_group_name = repo_group.group_name
217 app.post(url, params={}, status=403) # missing csrf check
222 app.post(url, params={}, status=403) # missing csrf check
218 response = app.post(url, params={'csrf_token': csrf_token})
223 response = app.post(url, params={'csrf_token': csrf_token})
219 assert response.status_code == 200
224 assert response.status_code == 200
220 assert 'Errors exist' in response.body
225 response.mustcontain('Errors exist')
221
226
222 scopes_destinations = [
227 scopes_destinations = [
223 ('global',
228 ('global',
224 ADMIN_PREFIX + '/integrations'),
229 ADMIN_PREFIX + '/integrations'),
225 ('root-repos',
230 ('root-repos',
226 ADMIN_PREFIX + '/integrations'),
231 ADMIN_PREFIX + '/integrations'),
227 ('repo:%s' % repo_name,
232 ('repo:%s' % repo_name,
228 '/%s/settings/integrations' % repo_name),
233 '/%s/settings/integrations' % repo_name),
229 ('repogroup:%s' % repo_group_name,
234 ('repogroup:%s' % repo_group_name,
230 '/%s/settings/integrations' % repo_group_name),
235 '/%s/settings/integrations' % repo_group_name),
231 ('repogroup-recursive:%s' % repo_group_name,
236 ('repogroup-recursive:%s' % repo_group_name,
232 '/%s/settings/integrations' % repo_group_name),
237 '/%s/settings/integrations' % repo_group_name),
233 ]
238 ]
234
239
235 for scope, destination in scopes_destinations:
240 for scope, destination in scopes_destinations:
236 if admin_view:
241 if admin_view:
237 destination = ADMIN_PREFIX + '/integrations'
242 destination = ADMIN_PREFIX + '/integrations'
238
243
239 form_data = [
244 form_data = [
240 ('csrf_token', csrf_token),
245 ('csrf_token', csrf_token),
241 ('__start__', 'options:mapping'),
246 ('__start__', 'options:mapping'),
242 ('name', 'test integration'),
247 ('name', 'test integration'),
243 ('scope', scope),
248 ('scope', scope),
244 ('enabled', 'true'),
249 ('enabled', 'true'),
245 ('__end__', 'options:mapping'),
250 ('__end__', 'options:mapping'),
246 ('__start__', 'settings:mapping'),
251 ('__start__', 'settings:mapping'),
247 ('test_int_field', '34'),
252 ('test_int_field', '34'),
248 ('test_string_field', ''), # empty value on purpose as it's required
253 ('test_string_field', ''), # empty value on purpose as it's required
249 ('__end__', 'settings:mapping'),
254 ('__end__', 'settings:mapping'),
250 ]
255 ]
251 errors_response = app.post(url, form_data)
256 errors_response = app.post(url, form_data)
252 assert 'Errors exist' in errors_response.body
257 assert 'Errors exist' in errors_response.body
253
258
254 form_data[-2] = ('test_string_field', 'data!')
259 form_data[-2] = ('test_string_field', 'data!')
255 assert Session().query(Integration).count() == 0
260 assert Session().query(Integration).count() == 0
256 created_response = app.post(url, form_data)
261 created_response = app.post(url, form_data)
257 assert Session().query(Integration).count() == 1
262 assert Session().query(Integration).count() == 1
258
263
259 delete_response = app.post(
264 delete_response = app.post(
260 created_response.location,
265 created_response.location,
261 params={'csrf_token': csrf_token, 'delete': 'delete'})
266 params={'csrf_token': csrf_token, 'delete': 'delete'})
262
267
263 assert Session().query(Integration).count() == 0
268 assert Session().query(Integration).count() == 0
264 assert delete_response.location.endswith(destination)
269 assert delete_response.location.endswith(destination)
@@ -1,101 +1,110 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22 from rhodecode.translation import _
22 from rhodecode.translation import _
23
23
24
24
25 class IntegrationTypeBase(object):
25 class IntegrationTypeBase(object):
26 """ Base class for IntegrationType plugins """
26 """ Base class for IntegrationType plugins """
27
27 is_dummy = False
28 description = ''
28 description = ''
29 icon = '''
29 icon = '''
30 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
30 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
31 <svg
31 <svg
32 xmlns:dc="http://purl.org/dc/elements/1.1/"
32 xmlns:dc="http://purl.org/dc/elements/1.1/"
33 xmlns:cc="http://creativecommons.org/ns#"
33 xmlns:cc="http://creativecommons.org/ns#"
34 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
34 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
35 xmlns:svg="http://www.w3.org/2000/svg"
35 xmlns:svg="http://www.w3.org/2000/svg"
36 xmlns="http://www.w3.org/2000/svg"
36 xmlns="http://www.w3.org/2000/svg"
37 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
37 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
38 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
38 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
39 viewBox="0 -256 1792 1792"
39 viewBox="0 -256 1792 1792"
40 id="svg3025"
40 id="svg3025"
41 version="1.1"
41 version="1.1"
42 inkscape:version="0.48.3.1 r9886"
42 inkscape:version="0.48.3.1 r9886"
43 width="100%"
43 width="100%"
44 height="100%"
44 height="100%"
45 sodipodi:docname="cog_font_awesome.svg">
45 sodipodi:docname="cog_font_awesome.svg">
46 <metadata
46 <metadata
47 id="metadata3035">
47 id="metadata3035">
48 <rdf:RDF>
48 <rdf:RDF>
49 <cc:Work
49 <cc:Work
50 rdf:about="">
50 rdf:about="">
51 <dc:format>image/svg+xml</dc:format>
51 <dc:format>image/svg+xml</dc:format>
52 <dc:type
52 <dc:type
53 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
53 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
54 </cc:Work>
54 </cc:Work>
55 </rdf:RDF>
55 </rdf:RDF>
56 </metadata>
56 </metadata>
57 <defs
57 <defs
58 id="defs3033" />
58 id="defs3033" />
59 <sodipodi:namedview
59 <sodipodi:namedview
60 pagecolor="#ffffff"
60 pagecolor="#ffffff"
61 bordercolor="#666666"
61 bordercolor="#666666"
62 borderopacity="1"
62 borderopacity="1"
63 objecttolerance="10"
63 objecttolerance="10"
64 gridtolerance="10"
64 gridtolerance="10"
65 guidetolerance="10"
65 guidetolerance="10"
66 inkscape:pageopacity="0"
66 inkscape:pageopacity="0"
67 inkscape:pageshadow="2"
67 inkscape:pageshadow="2"
68 inkscape:window-width="640"
68 inkscape:window-width="640"
69 inkscape:window-height="480"
69 inkscape:window-height="480"
70 id="namedview3031"
70 id="namedview3031"
71 showgrid="false"
71 showgrid="false"
72 inkscape:zoom="0.13169643"
72 inkscape:zoom="0.13169643"
73 inkscape:cx="896"
73 inkscape:cx="896"
74 inkscape:cy="896"
74 inkscape:cy="896"
75 inkscape:window-x="0"
75 inkscape:window-x="0"
76 inkscape:window-y="25"
76 inkscape:window-y="25"
77 inkscape:window-maximized="0"
77 inkscape:window-maximized="0"
78 inkscape:current-layer="svg3025" />
78 inkscape:current-layer="svg3025" />
79 <g
79 <g
80 transform="matrix(1,0,0,-1,121.49153,1285.4237)"
80 transform="matrix(1,0,0,-1,121.49153,1285.4237)"
81 id="g3027">
81 id="g3027">
82 <path
82 <path
83 d="m 1024,640 q 0,106 -75,181 -75,75 -181,75 -106,0 -181,-75 -75,-75 -75,-181 0,-106 75,-181 75,-75 181,-75 106,0 181,75 75,75 75,181 z m 512,109 V 527 q 0,-12 -8,-23 -8,-11 -20,-13 l -185,-28 q -19,-54 -39,-91 35,-50 107,-138 10,-12 10,-25 0,-13 -9,-23 -27,-37 -99,-108 -72,-71 -94,-71 -12,0 -26,9 l -138,108 q -44,-23 -91,-38 -16,-136 -29,-186 -7,-28 -36,-28 H 657 q -14,0 -24.5,8.5 Q 622,-111 621,-98 L 593,86 q -49,16 -90,37 L 362,16 Q 352,7 337,7 323,7 312,18 186,132 147,186 q -7,10 -7,23 0,12 8,23 15,21 51,66.5 36,45.5 54,70.5 -27,50 -41,99 L 29,495 Q 16,497 8,507.5 0,518 0,531 v 222 q 0,12 8,23 8,11 19,13 l 186,28 q 14,46 39,92 -40,57 -107,138 -10,12 -10,24 0,10 9,23 26,36 98.5,107.5 72.5,71.5 94.5,71.5 13,0 26,-10 l 138,-107 q 44,23 91,38 16,136 29,186 7,28 36,28 h 222 q 14,0 24.5,-8.5 Q 914,1391 915,1378 l 28,-184 q 49,-16 90,-37 l 142,107 q 9,9 24,9 13,0 25,-10 129,-119 165,-170 7,-8 7,-22 0,-12 -8,-23 -15,-21 -51,-66.5 -36,-45.5 -54,-70.5 26,-50 41,-98 l 183,-28 q 13,-2 21,-12.5 8,-10.5 8,-23.5 z"
83 d="m 1024,640 q 0,106 -75,181 -75,75 -181,75 -106,0 -181,-75 -75,-75 -75,-181 0,-106 75,-181 75,-75 181,-75 106,0 181,75 75,75 75,181 z m 512,109 V 527 q 0,-12 -8,-23 -8,-11 -20,-13 l -185,-28 q -19,-54 -39,-91 35,-50 107,-138 10,-12 10,-25 0,-13 -9,-23 -27,-37 -99,-108 -72,-71 -94,-71 -12,0 -26,9 l -138,108 q -44,-23 -91,-38 -16,-136 -29,-186 -7,-28 -36,-28 H 657 q -14,0 -24.5,8.5 Q 622,-111 621,-98 L 593,86 q -49,16 -90,37 L 362,16 Q 352,7 337,7 323,7 312,18 186,132 147,186 q -7,10 -7,23 0,12 8,23 15,21 51,66.5 36,45.5 54,70.5 -27,50 -41,99 L 29,495 Q 16,497 8,507.5 0,518 0,531 v 222 q 0,12 8,23 8,11 19,13 l 186,28 q 14,46 39,92 -40,57 -107,138 -10,12 -10,24 0,10 9,23 26,36 98.5,107.5 72.5,71.5 94.5,71.5 13,0 26,-10 l 138,-107 q 44,23 91,38 16,136 29,186 7,28 36,28 h 222 q 14,0 24.5,-8.5 Q 914,1391 915,1378 l 28,-184 q 49,-16 90,-37 l 142,107 q 9,9 24,9 13,0 25,-10 129,-119 165,-170 7,-8 7,-22 0,-12 -8,-23 -15,-21 -51,-66.5 -36,-45.5 -54,-70.5 26,-50 41,-98 l 183,-28 q 13,-2 21,-12.5 8,-10.5 8,-23.5 z"
84 id="path3029"
84 id="path3029"
85 inkscape:connector-curvature="0"
85 inkscape:connector-curvature="0"
86 style="fill:currentColor" />
86 style="fill:currentColor" />
87 </g>
87 </g>
88 </svg>
88 </svg>
89 '''
89 '''
90
90
91 def __init__(self, settings):
91 def __init__(self, settings):
92 """
92 """
93 :param settings: dict of settings to be used for the integration
93 :param settings: dict of settings to be used for the integration
94 """
94 """
95 self.settings = settings
95 self.settings = settings
96
96
97 def settings_schema(self):
97 def settings_schema(self):
98 """
98 """
99 A colander schema of settings for the integration type
99 A colander schema of settings for the integration type
100 """
100 """
101 return colander.Schema()
101 return colander.Schema()
102
103
104 class EEIntegration(IntegrationTypeBase):
105 description = 'Integration available in RhodeCode EE edition.'
106 is_dummy = True
107
108 def __init__(self, name, key, settings=None):
109 self.display_name = name
110 self.key = key
@@ -1,454 +1,459 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import deform
21 import deform
22 import logging
22 import logging
23 import peppercorn
23 import peppercorn
24 import webhelpers.paginate
24 import webhelpers.paginate
25
25
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
27
27
28 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
29 from rhodecode.integrations import integration_type_registry
29 from rhodecode.integrations import integration_type_registry
30 from rhodecode.apps.admin.navigation import navigation_list
30 from rhodecode.apps.admin.navigation import navigation_list
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
34 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.helpers import Page
35 from rhodecode.lib.helpers import Page
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.integration import IntegrationModel
38 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.model.validation_schema.schemas.integration_schema import (
39 from rhodecode.model.validation_schema.schemas.integration_schema import (
40 make_integration_schema, IntegrationScopeType)
40 make_integration_schema, IntegrationScopeType)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class IntegrationSettingsViewBase(BaseAppView):
45 class IntegrationSettingsViewBase(BaseAppView):
46 """
46 """
47 Base Integration settings view used by both repo / global settings
47 Base Integration settings view used by both repo / global settings
48 """
48 """
49
49
50 def __init__(self, context, request):
50 def __init__(self, context, request):
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
52 self._load_view_context()
52 self._load_view_context()
53
53
54 def _load_view_context(self):
54 def _load_view_context(self):
55 """
55 """
56 This avoids boilerplate for repo/global+list/edit+views/templates
56 This avoids boilerplate for repo/global+list/edit+views/templates
57 by doing all possible contexts at the same time however it should
57 by doing all possible contexts at the same time however it should
58 be split up into separate functions once more "contexts" exist
58 be split up into separate functions once more "contexts" exist
59 """
59 """
60
60
61 self.IntegrationType = None
61 self.IntegrationType = None
62 self.repo = None
62 self.repo = None
63 self.repo_group = None
63 self.repo_group = None
64 self.integration = None
64 self.integration = None
65 self.integrations = {}
65 self.integrations = {}
66
66
67 request = self.request
67 request = self.request
68
68
69 if 'repo_name' in request.matchdict: # in repo settings context
69 if 'repo_name' in request.matchdict: # in repo settings context
70 repo_name = request.matchdict['repo_name']
70 repo_name = request.matchdict['repo_name']
71 self.repo = Repository.get_by_repo_name(repo_name)
71 self.repo = Repository.get_by_repo_name(repo_name)
72
72
73 if 'repo_group_name' in request.matchdict: # in group settings context
73 if 'repo_group_name' in request.matchdict: # in group settings context
74 repo_group_name = request.matchdict['repo_group_name']
74 repo_group_name = request.matchdict['repo_group_name']
75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
76
76
77 if 'integration' in request.matchdict: # integration type context
77 if 'integration' in request.matchdict: # integration type context
78 integration_type = request.matchdict['integration']
78 integration_type = request.matchdict['integration']
79 if integration_type not in integration_type_registry:
80 raise HTTPNotFound()
81
79 self.IntegrationType = integration_type_registry[integration_type]
82 self.IntegrationType = integration_type_registry[integration_type]
83 if self.IntegrationType.is_dummy:
84 raise HTTPNotFound()
80
85
81 if 'integration_id' in request.matchdict: # single integration context
86 if 'integration_id' in request.matchdict: # single integration context
82 integration_id = request.matchdict['integration_id']
87 integration_id = request.matchdict['integration_id']
83 self.integration = Integration.get(integration_id)
88 self.integration = Integration.get(integration_id)
84
89
85 # extra perms check just in case
90 # extra perms check just in case
86 if not self._has_perms_for_integration(self.integration):
91 if not self._has_perms_for_integration(self.integration):
87 raise HTTPForbidden()
92 raise HTTPForbidden()
88
93
89 self.settings = self.integration and self.integration.settings or {}
94 self.settings = self.integration and self.integration.settings or {}
90 self.admin_view = not (self.repo or self.repo_group)
95 self.admin_view = not (self.repo or self.repo_group)
91
96
92 def _has_perms_for_integration(self, integration):
97 def _has_perms_for_integration(self, integration):
93 perms = self.request.user.permissions
98 perms = self.request.user.permissions
94
99
95 if 'hg.admin' in perms['global']:
100 if 'hg.admin' in perms['global']:
96 return True
101 return True
97
102
98 if integration.repo:
103 if integration.repo:
99 return perms['repositories'].get(
104 return perms['repositories'].get(
100 integration.repo.repo_name) == 'repository.admin'
105 integration.repo.repo_name) == 'repository.admin'
101
106
102 if integration.repo_group:
107 if integration.repo_group:
103 return perms['repositories_groups'].get(
108 return perms['repositories_groups'].get(
104 integration.repo_group.group_name) == 'group.admin'
109 integration.repo_group.group_name) == 'group.admin'
105
110
106 return False
111 return False
107
112
108 def _get_local_tmpl_context(self, include_app_defaults=False):
113 def _get_local_tmpl_context(self, include_app_defaults=False):
109 _ = self.request.translate
114 _ = self.request.translate
110 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
115 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
111 include_app_defaults=include_app_defaults)
116 include_app_defaults=include_app_defaults)
112
117
113 c.active = 'integrations'
118 c.active = 'integrations'
114
119
115 return c
120 return c
116
121
117 def _form_schema(self):
122 def _form_schema(self):
118 schema = make_integration_schema(IntegrationType=self.IntegrationType,
123 schema = make_integration_schema(IntegrationType=self.IntegrationType,
119 settings=self.settings)
124 settings=self.settings)
120
125
121 # returns a clone, important if mutating the schema later
126 # returns a clone, important if mutating the schema later
122 return schema.bind(
127 return schema.bind(
123 permissions=self.request.user.permissions,
128 permissions=self.request.user.permissions,
124 no_scope=not self.admin_view)
129 no_scope=not self.admin_view)
125
130
126 def _form_defaults(self):
131 def _form_defaults(self):
127 _ = self.request.translate
132 _ = self.request.translate
128 defaults = {}
133 defaults = {}
129
134
130 if self.integration:
135 if self.integration:
131 defaults['settings'] = self.integration.settings or {}
136 defaults['settings'] = self.integration.settings or {}
132 defaults['options'] = {
137 defaults['options'] = {
133 'name': self.integration.name,
138 'name': self.integration.name,
134 'enabled': self.integration.enabled,
139 'enabled': self.integration.enabled,
135 'scope': {
140 'scope': {
136 'repo': self.integration.repo,
141 'repo': self.integration.repo,
137 'repo_group': self.integration.repo_group,
142 'repo_group': self.integration.repo_group,
138 'child_repos_only': self.integration.child_repos_only,
143 'child_repos_only': self.integration.child_repos_only,
139 },
144 },
140 }
145 }
141 else:
146 else:
142 if self.repo:
147 if self.repo:
143 scope = _('{repo_name} repository').format(
148 scope = _('{repo_name} repository').format(
144 repo_name=self.repo.repo_name)
149 repo_name=self.repo.repo_name)
145 elif self.repo_group:
150 elif self.repo_group:
146 scope = _('{repo_group_name} repo group').format(
151 scope = _('{repo_group_name} repo group').format(
147 repo_group_name=self.repo_group.group_name)
152 repo_group_name=self.repo_group.group_name)
148 else:
153 else:
149 scope = _('Global')
154 scope = _('Global')
150
155
151 defaults['options'] = {
156 defaults['options'] = {
152 'enabled': True,
157 'enabled': True,
153 'name': _('{name} integration').format(
158 'name': _('{name} integration').format(
154 name=self.IntegrationType.display_name),
159 name=self.IntegrationType.display_name),
155 }
160 }
156 defaults['options']['scope'] = {
161 defaults['options']['scope'] = {
157 'repo': self.repo,
162 'repo': self.repo,
158 'repo_group': self.repo_group,
163 'repo_group': self.repo_group,
159 }
164 }
160
165
161 return defaults
166 return defaults
162
167
163 def _delete_integration(self, integration):
168 def _delete_integration(self, integration):
164 _ = self.request.translate
169 _ = self.request.translate
165 Session().delete(integration)
170 Session().delete(integration)
166 Session().commit()
171 Session().commit()
167 self.request.session.flash(
172 self.request.session.flash(
168 _('Integration {integration_name} deleted successfully.').format(
173 _('Integration {integration_name} deleted successfully.').format(
169 integration_name=integration.name),
174 integration_name=integration.name),
170 queue='success')
175 queue='success')
171
176
172 if self.repo:
177 if self.repo:
173 redirect_to = self.request.route_path(
178 redirect_to = self.request.route_path(
174 'repo_integrations_home', repo_name=self.repo.repo_name)
179 'repo_integrations_home', repo_name=self.repo.repo_name)
175 elif self.repo_group:
180 elif self.repo_group:
176 redirect_to = self.request.route_path(
181 redirect_to = self.request.route_path(
177 'repo_group_integrations_home',
182 'repo_group_integrations_home',
178 repo_group_name=self.repo_group.group_name)
183 repo_group_name=self.repo_group.group_name)
179 else:
184 else:
180 redirect_to = self.request.route_path('global_integrations_home')
185 redirect_to = self.request.route_path('global_integrations_home')
181 raise HTTPFound(redirect_to)
186 raise HTTPFound(redirect_to)
182
187
183 def _integration_list(self):
188 def _integration_list(self):
184 """ List integrations """
189 """ List integrations """
185
190
186 c = self.load_default_context()
191 c = self.load_default_context()
187 if self.repo:
192 if self.repo:
188 scope = self.repo
193 scope = self.repo
189 elif self.repo_group:
194 elif self.repo_group:
190 scope = self.repo_group
195 scope = self.repo_group
191 else:
196 else:
192 scope = 'all'
197 scope = 'all'
193
198
194 integrations = []
199 integrations = []
195
200
196 for IntType, integration in IntegrationModel().get_integrations(
201 for IntType, integration in IntegrationModel().get_integrations(
197 scope=scope, IntegrationType=self.IntegrationType):
202 scope=scope, IntegrationType=self.IntegrationType):
198
203
199 # extra permissions check *just in case*
204 # extra permissions check *just in case*
200 if not self._has_perms_for_integration(integration):
205 if not self._has_perms_for_integration(integration):
201 continue
206 continue
202
207
203 integrations.append((IntType, integration))
208 integrations.append((IntType, integration))
204
209
205 sort_arg = self.request.GET.get('sort', 'name:asc')
210 sort_arg = self.request.GET.get('sort', 'name:asc')
206 if ':' in sort_arg:
211 if ':' in sort_arg:
207 sort_field, sort_dir = sort_arg.split(':')
212 sort_field, sort_dir = sort_arg.split(':')
208 else:
213 else:
209 sort_field = sort_arg, 'asc'
214 sort_field = sort_arg, 'asc'
210
215
211 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
216 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
212
217
213 integrations.sort(
218 integrations.sort(
214 key=lambda x: getattr(x[1], sort_field),
219 key=lambda x: getattr(x[1], sort_field),
215 reverse=(sort_dir == 'desc'))
220 reverse=(sort_dir == 'desc'))
216
221
217 page_url = webhelpers.paginate.PageURL(
222 page_url = webhelpers.paginate.PageURL(
218 self.request.path, self.request.GET)
223 self.request.path, self.request.GET)
219 page = safe_int(self.request.GET.get('page', 1), 1)
224 page = safe_int(self.request.GET.get('page', 1), 1)
220
225
221 integrations = Page(
226 integrations = Page(
222 integrations, page=page, items_per_page=10, url=page_url)
227 integrations, page=page, items_per_page=10, url=page_url)
223
228
224 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
229 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
225
230
226 c.current_IntegrationType = self.IntegrationType
231 c.current_IntegrationType = self.IntegrationType
227 c.integrations_list = integrations
232 c.integrations_list = integrations
228 c.available_integrations = integration_type_registry
233 c.available_integrations = integration_type_registry
229
234
230 return self._get_template_context(c)
235 return self._get_template_context(c)
231
236
232 def _settings_get(self, defaults=None, form=None):
237 def _settings_get(self, defaults=None, form=None):
233 """
238 """
234 View that displays the integration settings as a form.
239 View that displays the integration settings as a form.
235 """
240 """
236 c = self.load_default_context()
241 c = self.load_default_context()
237
242
238 defaults = defaults or self._form_defaults()
243 defaults = defaults or self._form_defaults()
239 schema = self._form_schema()
244 schema = self._form_schema()
240
245
241 if self.integration:
246 if self.integration:
242 buttons = ('submit', 'delete')
247 buttons = ('submit', 'delete')
243 else:
248 else:
244 buttons = ('submit',)
249 buttons = ('submit',)
245
250
246 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
251 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
247
252
248 c.form = form
253 c.form = form
249 c.current_IntegrationType = self.IntegrationType
254 c.current_IntegrationType = self.IntegrationType
250 c.integration = self.integration
255 c.integration = self.integration
251
256
252 return self._get_template_context(c)
257 return self._get_template_context(c)
253
258
254 def _settings_post(self):
259 def _settings_post(self):
255 """
260 """
256 View that validates and stores the integration settings.
261 View that validates and stores the integration settings.
257 """
262 """
258 _ = self.request.translate
263 _ = self.request.translate
259
264
260 controls = self.request.POST.items()
265 controls = self.request.POST.items()
261 pstruct = peppercorn.parse(controls)
266 pstruct = peppercorn.parse(controls)
262
267
263 if self.integration and pstruct.get('delete'):
268 if self.integration and pstruct.get('delete'):
264 return self._delete_integration(self.integration)
269 return self._delete_integration(self.integration)
265
270
266 schema = self._form_schema()
271 schema = self._form_schema()
267
272
268 skip_settings_validation = False
273 skip_settings_validation = False
269 if self.integration and 'enabled' not in pstruct.get('options', {}):
274 if self.integration and 'enabled' not in pstruct.get('options', {}):
270 skip_settings_validation = True
275 skip_settings_validation = True
271 schema['settings'].validator = None
276 schema['settings'].validator = None
272 for field in schema['settings'].children:
277 for field in schema['settings'].children:
273 field.validator = None
278 field.validator = None
274 field.missing = ''
279 field.missing = ''
275
280
276 if self.integration:
281 if self.integration:
277 buttons = ('submit', 'delete')
282 buttons = ('submit', 'delete')
278 else:
283 else:
279 buttons = ('submit',)
284 buttons = ('submit',)
280
285
281 form = deform.Form(schema, buttons=buttons)
286 form = deform.Form(schema, buttons=buttons)
282
287
283 if not self.admin_view:
288 if not self.admin_view:
284 # scope is read only field in these cases, and has to be added
289 # scope is read only field in these cases, and has to be added
285 options = pstruct.setdefault('options', {})
290 options = pstruct.setdefault('options', {})
286 if 'scope' not in options:
291 if 'scope' not in options:
287 options['scope'] = IntegrationScopeType().serialize(None, {
292 options['scope'] = IntegrationScopeType().serialize(None, {
288 'repo': self.repo,
293 'repo': self.repo,
289 'repo_group': self.repo_group,
294 'repo_group': self.repo_group,
290 })
295 })
291
296
292 try:
297 try:
293 valid_data = form.validate_pstruct(pstruct)
298 valid_data = form.validate_pstruct(pstruct)
294 except deform.ValidationFailure as e:
299 except deform.ValidationFailure as e:
295 self.request.session.flash(
300 self.request.session.flash(
296 _('Errors exist when saving integration settings. '
301 _('Errors exist when saving integration settings. '
297 'Please check the form inputs.'),
302 'Please check the form inputs.'),
298 queue='error')
303 queue='error')
299 return self._settings_get(form=e)
304 return self._settings_get(form=e)
300
305
301 if not self.integration:
306 if not self.integration:
302 self.integration = Integration()
307 self.integration = Integration()
303 self.integration.integration_type = self.IntegrationType.key
308 self.integration.integration_type = self.IntegrationType.key
304 Session().add(self.integration)
309 Session().add(self.integration)
305
310
306 scope = valid_data['options']['scope']
311 scope = valid_data['options']['scope']
307
312
308 IntegrationModel().update_integration(self.integration,
313 IntegrationModel().update_integration(self.integration,
309 name=valid_data['options']['name'],
314 name=valid_data['options']['name'],
310 enabled=valid_data['options']['enabled'],
315 enabled=valid_data['options']['enabled'],
311 settings=valid_data['settings'],
316 settings=valid_data['settings'],
312 repo=scope['repo'],
317 repo=scope['repo'],
313 repo_group=scope['repo_group'],
318 repo_group=scope['repo_group'],
314 child_repos_only=scope['child_repos_only'],
319 child_repos_only=scope['child_repos_only'],
315 )
320 )
316
321
317 self.integration.settings = valid_data['settings']
322 self.integration.settings = valid_data['settings']
318 Session().commit()
323 Session().commit()
319 # Display success message and redirect.
324 # Display success message and redirect.
320 self.request.session.flash(
325 self.request.session.flash(
321 _('Integration {integration_name} updated successfully.').format(
326 _('Integration {integration_name} updated successfully.').format(
322 integration_name=self.IntegrationType.display_name),
327 integration_name=self.IntegrationType.display_name),
323 queue='success')
328 queue='success')
324
329
325 # if integration scope changes, we must redirect to the right place
330 # if integration scope changes, we must redirect to the right place
326 # keeping in mind if the original view was for /repo/ or /_admin/
331 # keeping in mind if the original view was for /repo/ or /_admin/
327 admin_view = not (self.repo or self.repo_group)
332 admin_view = not (self.repo or self.repo_group)
328
333
329 if self.integration.repo and not admin_view:
334 if self.integration.repo and not admin_view:
330 redirect_to = self.request.route_path(
335 redirect_to = self.request.route_path(
331 'repo_integrations_edit',
336 'repo_integrations_edit',
332 repo_name=self.integration.repo.repo_name,
337 repo_name=self.integration.repo.repo_name,
333 integration=self.integration.integration_type,
338 integration=self.integration.integration_type,
334 integration_id=self.integration.integration_id)
339 integration_id=self.integration.integration_id)
335 elif self.integration.repo_group and not admin_view:
340 elif self.integration.repo_group and not admin_view:
336 redirect_to = self.request.route_path(
341 redirect_to = self.request.route_path(
337 'repo_group_integrations_edit',
342 'repo_group_integrations_edit',
338 repo_group_name=self.integration.repo_group.group_name,
343 repo_group_name=self.integration.repo_group.group_name,
339 integration=self.integration.integration_type,
344 integration=self.integration.integration_type,
340 integration_id=self.integration.integration_id)
345 integration_id=self.integration.integration_id)
341 else:
346 else:
342 redirect_to = self.request.route_path(
347 redirect_to = self.request.route_path(
343 'global_integrations_edit',
348 'global_integrations_edit',
344 integration=self.integration.integration_type,
349 integration=self.integration.integration_type,
345 integration_id=self.integration.integration_id)
350 integration_id=self.integration.integration_id)
346
351
347 return HTTPFound(redirect_to)
352 return HTTPFound(redirect_to)
348
353
349 def _new_integration(self):
354 def _new_integration(self):
350 c = self.load_default_context()
355 c = self.load_default_context()
351 c.available_integrations = integration_type_registry
356 c.available_integrations = integration_type_registry
352 return self._get_template_context(c)
357 return self._get_template_context(c)
353
358
354 def load_default_context(self):
359 def load_default_context(self):
355 raise NotImplementedError()
360 raise NotImplementedError()
356
361
357
362
358 class GlobalIntegrationsView(IntegrationSettingsViewBase):
363 class GlobalIntegrationsView(IntegrationSettingsViewBase):
359 def load_default_context(self):
364 def load_default_context(self):
360 c = self._get_local_tmpl_context()
365 c = self._get_local_tmpl_context()
361 c.repo = self.repo
366 c.repo = self.repo
362 c.repo_group = self.repo_group
367 c.repo_group = self.repo_group
363 c.navlist = navigation_list(self.request)
368 c.navlist = navigation_list(self.request)
364 self._register_global_c(c)
369 self._register_global_c(c)
365 return c
370 return c
366
371
367 @LoginRequired()
372 @LoginRequired()
368 @HasPermissionAnyDecorator('hg.admin')
373 @HasPermissionAnyDecorator('hg.admin')
369 def integration_list(self):
374 def integration_list(self):
370 return self._integration_list()
375 return self._integration_list()
371
376
372 @LoginRequired()
377 @LoginRequired()
373 @HasPermissionAnyDecorator('hg.admin')
378 @HasPermissionAnyDecorator('hg.admin')
374 def settings_get(self):
379 def settings_get(self):
375 return self._settings_get()
380 return self._settings_get()
376
381
377 @LoginRequired()
382 @LoginRequired()
378 @HasPermissionAnyDecorator('hg.admin')
383 @HasPermissionAnyDecorator('hg.admin')
379 @CSRFRequired()
384 @CSRFRequired()
380 def settings_post(self):
385 def settings_post(self):
381 return self._settings_post()
386 return self._settings_post()
382
387
383 @LoginRequired()
388 @LoginRequired()
384 @HasPermissionAnyDecorator('hg.admin')
389 @HasPermissionAnyDecorator('hg.admin')
385 def new_integration(self):
390 def new_integration(self):
386 return self._new_integration()
391 return self._new_integration()
387
392
388
393
389 class RepoIntegrationsView(IntegrationSettingsViewBase):
394 class RepoIntegrationsView(IntegrationSettingsViewBase):
390 def load_default_context(self):
395 def load_default_context(self):
391 c = self._get_local_tmpl_context()
396 c = self._get_local_tmpl_context()
392
397
393 c.repo = self.repo
398 c.repo = self.repo
394 c.repo_group = self.repo_group
399 c.repo_group = self.repo_group
395
400
396 self.db_repo = self.repo
401 self.db_repo = self.repo
397 c.rhodecode_db_repo = self.repo
402 c.rhodecode_db_repo = self.repo
398 c.repo_name = self.db_repo.repo_name
403 c.repo_name = self.db_repo.repo_name
399 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
404 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
400
405
401 self._register_global_c(c)
406 self._register_global_c(c)
402 return c
407 return c
403
408
404 @LoginRequired()
409 @LoginRequired()
405 @HasRepoPermissionAnyDecorator('repository.admin')
410 @HasRepoPermissionAnyDecorator('repository.admin')
406 def integration_list(self):
411 def integration_list(self):
407 return self._integration_list()
412 return self._integration_list()
408
413
409 @LoginRequired()
414 @LoginRequired()
410 @HasRepoPermissionAnyDecorator('repository.admin')
415 @HasRepoPermissionAnyDecorator('repository.admin')
411 def settings_get(self):
416 def settings_get(self):
412 return self._settings_get()
417 return self._settings_get()
413
418
414 @LoginRequired()
419 @LoginRequired()
415 @HasRepoPermissionAnyDecorator('repository.admin')
420 @HasRepoPermissionAnyDecorator('repository.admin')
416 @CSRFRequired()
421 @CSRFRequired()
417 def settings_post(self):
422 def settings_post(self):
418 return self._settings_post()
423 return self._settings_post()
419
424
420 @LoginRequired()
425 @LoginRequired()
421 @HasRepoPermissionAnyDecorator('repository.admin')
426 @HasRepoPermissionAnyDecorator('repository.admin')
422 def new_integration(self):
427 def new_integration(self):
423 return self._new_integration()
428 return self._new_integration()
424
429
425
430
426 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
431 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
427 def load_default_context(self):
432 def load_default_context(self):
428 c = self._get_local_tmpl_context()
433 c = self._get_local_tmpl_context()
429 c.repo = self.repo
434 c.repo = self.repo
430 c.repo_group = self.repo_group
435 c.repo_group = self.repo_group
431 c.navlist = navigation_list(self.request)
436 c.navlist = navigation_list(self.request)
432 self._register_global_c(c)
437 self._register_global_c(c)
433 return c
438 return c
434
439
435 @LoginRequired()
440 @LoginRequired()
436 @HasRepoGroupPermissionAnyDecorator('group.admin')
441 @HasRepoGroupPermissionAnyDecorator('group.admin')
437 def integration_list(self):
442 def integration_list(self):
438 return self._integration_list()
443 return self._integration_list()
439
444
440 @LoginRequired()
445 @LoginRequired()
441 @HasRepoGroupPermissionAnyDecorator('group.admin')
446 @HasRepoGroupPermissionAnyDecorator('group.admin')
442 def settings_get(self):
447 def settings_get(self):
443 return self._settings_get()
448 return self._settings_get()
444
449
445 @LoginRequired()
450 @LoginRequired()
446 @HasRepoGroupPermissionAnyDecorator('group.admin')
451 @HasRepoGroupPermissionAnyDecorator('group.admin')
447 @CSRFRequired()
452 @CSRFRequired()
448 def settings_post(self):
453 def settings_post(self):
449 return self._settings_post()
454 return self._settings_post()
450
455
451 @LoginRequired()
456 @LoginRequired()
452 @HasRepoGroupPermissionAnyDecorator('group.admin')
457 @HasRepoGroupPermissionAnyDecorator('group.admin')
453 def new_integration(self):
458 def new_integration(self):
454 return self._new_integration()
459 return self._new_integration()
@@ -1,2400 +1,2403 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'fonts';
8 @import 'fonts';
9 @import 'variables';
9 @import 'variables';
10 @import 'bootstrap-variables';
10 @import 'bootstrap-variables';
11 @import 'form-bootstrap';
11 @import 'form-bootstrap';
12 @import 'codemirror';
12 @import 'codemirror';
13 @import 'legacy_code_styles';
13 @import 'legacy_code_styles';
14 @import 'readme-box';
14 @import 'readme-box';
15 @import 'progress-bar';
15 @import 'progress-bar';
16
16
17 @import 'type';
17 @import 'type';
18 @import 'alerts';
18 @import 'alerts';
19 @import 'buttons';
19 @import 'buttons';
20 @import 'tags';
20 @import 'tags';
21 @import 'code-block';
21 @import 'code-block';
22 @import 'examples';
22 @import 'examples';
23 @import 'login';
23 @import 'login';
24 @import 'main-content';
24 @import 'main-content';
25 @import 'select2';
25 @import 'select2';
26 @import 'comments';
26 @import 'comments';
27 @import 'panels-bootstrap';
27 @import 'panels-bootstrap';
28 @import 'panels';
28 @import 'panels';
29 @import 'deform';
29 @import 'deform';
30
30
31 //--- BASE ------------------//
31 //--- BASE ------------------//
32 .noscript-error {
32 .noscript-error {
33 top: 0;
33 top: 0;
34 left: 0;
34 left: 0;
35 width: 100%;
35 width: 100%;
36 z-index: 101;
36 z-index: 101;
37 text-align: center;
37 text-align: center;
38 font-family: @text-semibold;
38 font-family: @text-semibold;
39 font-size: 120%;
39 font-size: 120%;
40 color: white;
40 color: white;
41 background-color: @alert2;
41 background-color: @alert2;
42 padding: 5px 0 5px 0;
42 padding: 5px 0 5px 0;
43 }
43 }
44
44
45 html {
45 html {
46 display: table;
46 display: table;
47 height: 100%;
47 height: 100%;
48 width: 100%;
48 width: 100%;
49 }
49 }
50
50
51 body {
51 body {
52 display: table-cell;
52 display: table-cell;
53 width: 100%;
53 width: 100%;
54 }
54 }
55
55
56 //--- LAYOUT ------------------//
56 //--- LAYOUT ------------------//
57
57
58 .hidden{
58 .hidden{
59 display: none !important;
59 display: none !important;
60 }
60 }
61
61
62 .box{
62 .box{
63 float: left;
63 float: left;
64 width: 100%;
64 width: 100%;
65 }
65 }
66
66
67 .browser-header {
67 .browser-header {
68 clear: both;
68 clear: both;
69 }
69 }
70 .main {
70 .main {
71 clear: both;
71 clear: both;
72 padding:0 0 @pagepadding;
72 padding:0 0 @pagepadding;
73 height: auto;
73 height: auto;
74
74
75 &:after { //clearfix
75 &:after { //clearfix
76 content:"";
76 content:"";
77 clear:both;
77 clear:both;
78 width:100%;
78 width:100%;
79 display:block;
79 display:block;
80 }
80 }
81 }
81 }
82
82
83 .action-link{
83 .action-link{
84 margin-left: @padding;
84 margin-left: @padding;
85 padding-left: @padding;
85 padding-left: @padding;
86 border-left: @border-thickness solid @border-default-color;
86 border-left: @border-thickness solid @border-default-color;
87 }
87 }
88
88
89 input + .action-link, .action-link.first{
89 input + .action-link, .action-link.first{
90 border-left: none;
90 border-left: none;
91 }
91 }
92
92
93 .action-link.last{
93 .action-link.last{
94 margin-right: @padding;
94 margin-right: @padding;
95 padding-right: @padding;
95 padding-right: @padding;
96 }
96 }
97
97
98 .action-link.active,
98 .action-link.active,
99 .action-link.active a{
99 .action-link.active a{
100 color: @grey4;
100 color: @grey4;
101 }
101 }
102
102
103 .clipboard-action {
103 .clipboard-action {
104 cursor: pointer;
104 cursor: pointer;
105 }
105 }
106
106
107 ul.simple-list{
107 ul.simple-list{
108 list-style: none;
108 list-style: none;
109 margin: 0;
109 margin: 0;
110 padding: 0;
110 padding: 0;
111 }
111 }
112
112
113 .main-content {
113 .main-content {
114 padding-bottom: @pagepadding;
114 padding-bottom: @pagepadding;
115 }
115 }
116
116
117 .wide-mode-wrapper {
117 .wide-mode-wrapper {
118 max-width:4000px !important;
118 max-width:4000px !important;
119 }
119 }
120
120
121 .wrapper {
121 .wrapper {
122 position: relative;
122 position: relative;
123 max-width: @wrapper-maxwidth;
123 max-width: @wrapper-maxwidth;
124 margin: 0 auto;
124 margin: 0 auto;
125 }
125 }
126
126
127 #content {
127 #content {
128 clear: both;
128 clear: both;
129 padding: 0 @contentpadding;
129 padding: 0 @contentpadding;
130 }
130 }
131
131
132 .advanced-settings-fields{
132 .advanced-settings-fields{
133 input{
133 input{
134 margin-left: @textmargin;
134 margin-left: @textmargin;
135 margin-right: @padding/2;
135 margin-right: @padding/2;
136 }
136 }
137 }
137 }
138
138
139 .cs_files_title {
139 .cs_files_title {
140 margin: @pagepadding 0 0;
140 margin: @pagepadding 0 0;
141 }
141 }
142
142
143 input.inline[type="file"] {
143 input.inline[type="file"] {
144 display: inline;
144 display: inline;
145 }
145 }
146
146
147 .error_page {
147 .error_page {
148 margin: 10% auto;
148 margin: 10% auto;
149
149
150 h1 {
150 h1 {
151 color: @grey2;
151 color: @grey2;
152 }
152 }
153
153
154 .alert {
154 .alert {
155 margin: @padding 0;
155 margin: @padding 0;
156 }
156 }
157
157
158 .error-branding {
158 .error-branding {
159 font-family: @text-semibold;
159 font-family: @text-semibold;
160 color: @grey4;
160 color: @grey4;
161 }
161 }
162
162
163 .error_message {
163 .error_message {
164 font-family: @text-regular;
164 font-family: @text-regular;
165 }
165 }
166
166
167 .sidebar {
167 .sidebar {
168 min-height: 275px;
168 min-height: 275px;
169 margin: 0;
169 margin: 0;
170 padding: 0 0 @sidebarpadding @sidebarpadding;
170 padding: 0 0 @sidebarpadding @sidebarpadding;
171 border: none;
171 border: none;
172 }
172 }
173
173
174 .main-content {
174 .main-content {
175 position: relative;
175 position: relative;
176 margin: 0 @sidebarpadding @sidebarpadding;
176 margin: 0 @sidebarpadding @sidebarpadding;
177 padding: 0 0 0 @sidebarpadding;
177 padding: 0 0 0 @sidebarpadding;
178 border-left: @border-thickness solid @grey5;
178 border-left: @border-thickness solid @grey5;
179
179
180 @media (max-width:767px) {
180 @media (max-width:767px) {
181 clear: both;
181 clear: both;
182 width: 100%;
182 width: 100%;
183 margin: 0;
183 margin: 0;
184 border: none;
184 border: none;
185 }
185 }
186 }
186 }
187
187
188 .inner-column {
188 .inner-column {
189 float: left;
189 float: left;
190 width: 29.75%;
190 width: 29.75%;
191 min-height: 150px;
191 min-height: 150px;
192 margin: @sidebarpadding 2% 0 0;
192 margin: @sidebarpadding 2% 0 0;
193 padding: 0 2% 0 0;
193 padding: 0 2% 0 0;
194 border-right: @border-thickness solid @grey5;
194 border-right: @border-thickness solid @grey5;
195
195
196 @media (max-width:767px) {
196 @media (max-width:767px) {
197 clear: both;
197 clear: both;
198 width: 100%;
198 width: 100%;
199 border: none;
199 border: none;
200 }
200 }
201
201
202 ul {
202 ul {
203 padding-left: 1.25em;
203 padding-left: 1.25em;
204 }
204 }
205
205
206 &:last-child {
206 &:last-child {
207 margin: @sidebarpadding 0 0;
207 margin: @sidebarpadding 0 0;
208 border: none;
208 border: none;
209 }
209 }
210
210
211 h4 {
211 h4 {
212 margin: 0 0 @padding;
212 margin: 0 0 @padding;
213 font-family: @text-semibold;
213 font-family: @text-semibold;
214 }
214 }
215 }
215 }
216 }
216 }
217 .error-page-logo {
217 .error-page-logo {
218 width: 130px;
218 width: 130px;
219 height: 160px;
219 height: 160px;
220 }
220 }
221
221
222 // HEADER
222 // HEADER
223 .header {
223 .header {
224
224
225 // TODO: johbo: Fix login pages, so that they work without a min-height
225 // TODO: johbo: Fix login pages, so that they work without a min-height
226 // for the header and then remove the min-height. I chose a smaller value
226 // for the header and then remove the min-height. I chose a smaller value
227 // intentionally here to avoid rendering issues in the main navigation.
227 // intentionally here to avoid rendering issues in the main navigation.
228 min-height: 49px;
228 min-height: 49px;
229
229
230 position: relative;
230 position: relative;
231 vertical-align: bottom;
231 vertical-align: bottom;
232 padding: 0 @header-padding;
232 padding: 0 @header-padding;
233 background-color: @grey2;
233 background-color: @grey2;
234 color: @grey5;
234 color: @grey5;
235
235
236 .title {
236 .title {
237 overflow: visible;
237 overflow: visible;
238 }
238 }
239
239
240 &:before,
240 &:before,
241 &:after {
241 &:after {
242 content: "";
242 content: "";
243 clear: both;
243 clear: both;
244 width: 100%;
244 width: 100%;
245 }
245 }
246
246
247 // TODO: johbo: Avoids breaking "Repositories" chooser
247 // TODO: johbo: Avoids breaking "Repositories" chooser
248 .select2-container .select2-choice .select2-arrow {
248 .select2-container .select2-choice .select2-arrow {
249 display: none;
249 display: none;
250 }
250 }
251 }
251 }
252
252
253 #header-inner {
253 #header-inner {
254 &.title {
254 &.title {
255 margin: 0;
255 margin: 0;
256 }
256 }
257 &:before,
257 &:before,
258 &:after {
258 &:after {
259 content: "";
259 content: "";
260 clear: both;
260 clear: both;
261 }
261 }
262 }
262 }
263
263
264 // Gists
264 // Gists
265 #files_data {
265 #files_data {
266 clear: both; //for firefox
266 clear: both; //for firefox
267 }
267 }
268 #gistid {
268 #gistid {
269 margin-right: @padding;
269 margin-right: @padding;
270 }
270 }
271
271
272 // Global Settings Editor
272 // Global Settings Editor
273 .textarea.editor {
273 .textarea.editor {
274 float: left;
274 float: left;
275 position: relative;
275 position: relative;
276 max-width: @texteditor-width;
276 max-width: @texteditor-width;
277
277
278 select {
278 select {
279 position: absolute;
279 position: absolute;
280 top:10px;
280 top:10px;
281 right:0;
281 right:0;
282 }
282 }
283
283
284 .CodeMirror {
284 .CodeMirror {
285 margin: 0;
285 margin: 0;
286 }
286 }
287
287
288 .help-block {
288 .help-block {
289 margin: 0 0 @padding;
289 margin: 0 0 @padding;
290 padding:.5em;
290 padding:.5em;
291 background-color: @grey6;
291 background-color: @grey6;
292 &.pre-formatting {
292 &.pre-formatting {
293 white-space: pre;
293 white-space: pre;
294 }
294 }
295 }
295 }
296 }
296 }
297
297
298 ul.auth_plugins {
298 ul.auth_plugins {
299 margin: @padding 0 @padding @legend-width;
299 margin: @padding 0 @padding @legend-width;
300 padding: 0;
300 padding: 0;
301
301
302 li {
302 li {
303 margin-bottom: @padding;
303 margin-bottom: @padding;
304 line-height: 1em;
304 line-height: 1em;
305 list-style-type: none;
305 list-style-type: none;
306
306
307 .auth_buttons .btn {
307 .auth_buttons .btn {
308 margin-right: @padding;
308 margin-right: @padding;
309 }
309 }
310
310
311 &:before { content: none; }
311 &:before { content: none; }
312 }
312 }
313 }
313 }
314
314
315
315
316 // My Account PR list
316 // My Account PR list
317
317
318 #show_closed {
318 #show_closed {
319 margin: 0 1em 0 0;
319 margin: 0 1em 0 0;
320 }
320 }
321
321
322 .pullrequestlist {
322 .pullrequestlist {
323 .closed {
323 .closed {
324 background-color: @grey6;
324 background-color: @grey6;
325 }
325 }
326 .td-status {
326 .td-status {
327 padding-left: .5em;
327 padding-left: .5em;
328 }
328 }
329 .log-container .truncate {
329 .log-container .truncate {
330 height: 2.75em;
330 height: 2.75em;
331 white-space: pre-line;
331 white-space: pre-line;
332 }
332 }
333 table.rctable .user {
333 table.rctable .user {
334 padding-left: 0;
334 padding-left: 0;
335 }
335 }
336 table.rctable {
336 table.rctable {
337 td.td-description,
337 td.td-description,
338 .rc-user {
338 .rc-user {
339 min-width: auto;
339 min-width: auto;
340 }
340 }
341 }
341 }
342 }
342 }
343
343
344 // Pull Requests
344 // Pull Requests
345
345
346 .pullrequests_section_head {
346 .pullrequests_section_head {
347 display: block;
347 display: block;
348 clear: both;
348 clear: both;
349 margin: @padding 0;
349 margin: @padding 0;
350 font-family: @text-bold;
350 font-family: @text-bold;
351 }
351 }
352
352
353 .pr-origininfo, .pr-targetinfo {
353 .pr-origininfo, .pr-targetinfo {
354 position: relative;
354 position: relative;
355
355
356 .tag {
356 .tag {
357 display: inline-block;
357 display: inline-block;
358 margin: 0 1em .5em 0;
358 margin: 0 1em .5em 0;
359 }
359 }
360
360
361 .clone-url {
361 .clone-url {
362 display: inline-block;
362 display: inline-block;
363 margin: 0 0 .5em 0;
363 margin: 0 0 .5em 0;
364 padding: 0;
364 padding: 0;
365 line-height: 1.2em;
365 line-height: 1.2em;
366 }
366 }
367 }
367 }
368
368
369 .pr-mergeinfo {
369 .pr-mergeinfo {
370 min-width: 95% !important;
370 min-width: 95% !important;
371 padding: 0 !important;
371 padding: 0 !important;
372 border: 0;
372 border: 0;
373 }
373 }
374 .pr-mergeinfo-copy {
374 .pr-mergeinfo-copy {
375 padding: 0 0;
375 padding: 0 0;
376 }
376 }
377
377
378 .pr-pullinfo {
378 .pr-pullinfo {
379 min-width: 95% !important;
379 min-width: 95% !important;
380 padding: 0 !important;
380 padding: 0 !important;
381 border: 0;
381 border: 0;
382 }
382 }
383 .pr-pullinfo-copy {
383 .pr-pullinfo-copy {
384 padding: 0 0;
384 padding: 0 0;
385 }
385 }
386
386
387
387
388 #pr-title-input {
388 #pr-title-input {
389 width: 72%;
389 width: 72%;
390 font-size: 1em;
390 font-size: 1em;
391 font-family: @text-bold;
391 font-family: @text-bold;
392 margin: 0;
392 margin: 0;
393 padding: 0 0 0 @padding/4;
393 padding: 0 0 0 @padding/4;
394 line-height: 1.7em;
394 line-height: 1.7em;
395 color: @text-color;
395 color: @text-color;
396 letter-spacing: .02em;
396 letter-spacing: .02em;
397 }
397 }
398
398
399 #pullrequest_title {
399 #pullrequest_title {
400 width: 100%;
400 width: 100%;
401 box-sizing: border-box;
401 box-sizing: border-box;
402 }
402 }
403
403
404 #pr_open_message {
404 #pr_open_message {
405 border: @border-thickness solid #fff;
405 border: @border-thickness solid #fff;
406 border-radius: @border-radius;
406 border-radius: @border-radius;
407 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
407 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
408 text-align: left;
408 text-align: left;
409 overflow: hidden;
409 overflow: hidden;
410 }
410 }
411
411
412 .pr-submit-button {
412 .pr-submit-button {
413 float: right;
413 float: right;
414 margin: 0 0 0 5px;
414 margin: 0 0 0 5px;
415 }
415 }
416
416
417 .pr-spacing-container {
417 .pr-spacing-container {
418 padding: 20px;
418 padding: 20px;
419 clear: both
419 clear: both
420 }
420 }
421
421
422 #pr-description-input {
422 #pr-description-input {
423 margin-bottom: 0;
423 margin-bottom: 0;
424 }
424 }
425
425
426 .pr-description-label {
426 .pr-description-label {
427 vertical-align: top;
427 vertical-align: top;
428 }
428 }
429
429
430 .perms_section_head {
430 .perms_section_head {
431 min-width: 625px;
431 min-width: 625px;
432
432
433 h2 {
433 h2 {
434 margin-bottom: 0;
434 margin-bottom: 0;
435 }
435 }
436
436
437 .label-checkbox {
437 .label-checkbox {
438 float: left;
438 float: left;
439 }
439 }
440
440
441 &.field {
441 &.field {
442 margin: @space 0 @padding;
442 margin: @space 0 @padding;
443 }
443 }
444
444
445 &:first-child.field {
445 &:first-child.field {
446 margin-top: 0;
446 margin-top: 0;
447
447
448 .label {
448 .label {
449 margin-top: 0;
449 margin-top: 0;
450 padding-top: 0;
450 padding-top: 0;
451 }
451 }
452
452
453 .radios {
453 .radios {
454 padding-top: 0;
454 padding-top: 0;
455 }
455 }
456 }
456 }
457
457
458 .radios {
458 .radios {
459 position: relative;
459 position: relative;
460 width: 405px;
460 width: 405px;
461 }
461 }
462 }
462 }
463
463
464 //--- MODULES ------------------//
464 //--- MODULES ------------------//
465
465
466
466
467 // Server Announcement
467 // Server Announcement
468 #server-announcement {
468 #server-announcement {
469 width: 95%;
469 width: 95%;
470 margin: @padding auto;
470 margin: @padding auto;
471 padding: @padding;
471 padding: @padding;
472 border-width: 2px;
472 border-width: 2px;
473 border-style: solid;
473 border-style: solid;
474 .border-radius(2px);
474 .border-radius(2px);
475 font-family: @text-bold;
475 font-family: @text-bold;
476
476
477 &.info { border-color: @alert4; background-color: @alert4-inner; }
477 &.info { border-color: @alert4; background-color: @alert4-inner; }
478 &.warning { border-color: @alert3; background-color: @alert3-inner; }
478 &.warning { border-color: @alert3; background-color: @alert3-inner; }
479 &.error { border-color: @alert2; background-color: @alert2-inner; }
479 &.error { border-color: @alert2; background-color: @alert2-inner; }
480 &.success { border-color: @alert1; background-color: @alert1-inner; }
480 &.success { border-color: @alert1; background-color: @alert1-inner; }
481 &.neutral { border-color: @grey3; background-color: @grey6; }
481 &.neutral { border-color: @grey3; background-color: @grey6; }
482 }
482 }
483
483
484 // Fixed Sidebar Column
484 // Fixed Sidebar Column
485 .sidebar-col-wrapper {
485 .sidebar-col-wrapper {
486 padding-left: @sidebar-all-width;
486 padding-left: @sidebar-all-width;
487
487
488 .sidebar {
488 .sidebar {
489 width: @sidebar-width;
489 width: @sidebar-width;
490 margin-left: -@sidebar-all-width;
490 margin-left: -@sidebar-all-width;
491 }
491 }
492 }
492 }
493
493
494 .sidebar-col-wrapper.scw-small {
494 .sidebar-col-wrapper.scw-small {
495 padding-left: @sidebar-small-all-width;
495 padding-left: @sidebar-small-all-width;
496
496
497 .sidebar {
497 .sidebar {
498 width: @sidebar-small-width;
498 width: @sidebar-small-width;
499 margin-left: -@sidebar-small-all-width;
499 margin-left: -@sidebar-small-all-width;
500 }
500 }
501 }
501 }
502
502
503
503
504 // FOOTER
504 // FOOTER
505 #footer {
505 #footer {
506 padding: 0;
506 padding: 0;
507 text-align: center;
507 text-align: center;
508 vertical-align: middle;
508 vertical-align: middle;
509 color: @grey2;
509 color: @grey2;
510 background-color: @grey6;
510 background-color: @grey6;
511
511
512 p {
512 p {
513 margin: 0;
513 margin: 0;
514 padding: 1em;
514 padding: 1em;
515 line-height: 1em;
515 line-height: 1em;
516 }
516 }
517
517
518 .server-instance { //server instance
518 .server-instance { //server instance
519 display: none;
519 display: none;
520 }
520 }
521
521
522 .title {
522 .title {
523 float: none;
523 float: none;
524 margin: 0 auto;
524 margin: 0 auto;
525 }
525 }
526 }
526 }
527
527
528 button.close {
528 button.close {
529 padding: 0;
529 padding: 0;
530 cursor: pointer;
530 cursor: pointer;
531 background: transparent;
531 background: transparent;
532 border: 0;
532 border: 0;
533 .box-shadow(none);
533 .box-shadow(none);
534 -webkit-appearance: none;
534 -webkit-appearance: none;
535 }
535 }
536
536
537 .close {
537 .close {
538 float: right;
538 float: right;
539 font-size: 21px;
539 font-size: 21px;
540 font-family: @text-bootstrap;
540 font-family: @text-bootstrap;
541 line-height: 1em;
541 line-height: 1em;
542 font-weight: bold;
542 font-weight: bold;
543 color: @grey2;
543 color: @grey2;
544
544
545 &:hover,
545 &:hover,
546 &:focus {
546 &:focus {
547 color: @grey1;
547 color: @grey1;
548 text-decoration: none;
548 text-decoration: none;
549 cursor: pointer;
549 cursor: pointer;
550 }
550 }
551 }
551 }
552
552
553 // GRID
553 // GRID
554 .sorting,
554 .sorting,
555 .sorting_desc,
555 .sorting_desc,
556 .sorting_asc {
556 .sorting_asc {
557 cursor: pointer;
557 cursor: pointer;
558 }
558 }
559 .sorting_desc:after {
559 .sorting_desc:after {
560 content: "\00A0\25B2";
560 content: "\00A0\25B2";
561 font-size: .75em;
561 font-size: .75em;
562 }
562 }
563 .sorting_asc:after {
563 .sorting_asc:after {
564 content: "\00A0\25BC";
564 content: "\00A0\25BC";
565 font-size: .68em;
565 font-size: .68em;
566 }
566 }
567
567
568
568
569 .user_auth_tokens {
569 .user_auth_tokens {
570
570
571 &.truncate {
571 &.truncate {
572 white-space: nowrap;
572 white-space: nowrap;
573 overflow: hidden;
573 overflow: hidden;
574 text-overflow: ellipsis;
574 text-overflow: ellipsis;
575 }
575 }
576
576
577 .fields .field .input {
577 .fields .field .input {
578 margin: 0;
578 margin: 0;
579 }
579 }
580
580
581 input#description {
581 input#description {
582 width: 100px;
582 width: 100px;
583 margin: 0;
583 margin: 0;
584 }
584 }
585
585
586 .drop-menu {
586 .drop-menu {
587 // TODO: johbo: Remove this, should work out of the box when
587 // TODO: johbo: Remove this, should work out of the box when
588 // having multiple inputs inline
588 // having multiple inputs inline
589 margin: 0 0 0 5px;
589 margin: 0 0 0 5px;
590 }
590 }
591 }
591 }
592 #user_list_table {
592 #user_list_table {
593 .closed {
593 .closed {
594 background-color: @grey6;
594 background-color: @grey6;
595 }
595 }
596 }
596 }
597
597
598
598
599 input {
599 input {
600 &.disabled {
600 &.disabled {
601 opacity: .5;
601 opacity: .5;
602 }
602 }
603 }
603 }
604
604
605 // remove extra padding in firefox
605 // remove extra padding in firefox
606 input::-moz-focus-inner { border:0; padding:0 }
606 input::-moz-focus-inner { border:0; padding:0 }
607
607
608 .adjacent input {
608 .adjacent input {
609 margin-bottom: @padding;
609 margin-bottom: @padding;
610 }
610 }
611
611
612 .permissions_boxes {
612 .permissions_boxes {
613 display: block;
613 display: block;
614 }
614 }
615
615
616 //TODO: lisa: this should be in tables
616 //TODO: lisa: this should be in tables
617 .show_more_col {
617 .show_more_col {
618 width: 20px;
618 width: 20px;
619 }
619 }
620
620
621 //FORMS
621 //FORMS
622
622
623 .medium-inline,
623 .medium-inline,
624 input#description.medium-inline {
624 input#description.medium-inline {
625 display: inline;
625 display: inline;
626 width: @medium-inline-input-width;
626 width: @medium-inline-input-width;
627 min-width: 100px;
627 min-width: 100px;
628 }
628 }
629
629
630 select {
630 select {
631 //reset
631 //reset
632 -webkit-appearance: none;
632 -webkit-appearance: none;
633 -moz-appearance: none;
633 -moz-appearance: none;
634
634
635 display: inline-block;
635 display: inline-block;
636 height: 28px;
636 height: 28px;
637 width: auto;
637 width: auto;
638 margin: 0 @padding @padding 0;
638 margin: 0 @padding @padding 0;
639 padding: 0 18px 0 8px;
639 padding: 0 18px 0 8px;
640 line-height:1em;
640 line-height:1em;
641 font-size: @basefontsize;
641 font-size: @basefontsize;
642 border: @border-thickness solid @rcblue;
642 border: @border-thickness solid @rcblue;
643 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
643 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
644 color: @rcblue;
644 color: @rcblue;
645
645
646 &:after {
646 &:after {
647 content: "\00A0\25BE";
647 content: "\00A0\25BE";
648 }
648 }
649
649
650 &:focus {
650 &:focus {
651 outline: none;
651 outline: none;
652 }
652 }
653 }
653 }
654
654
655 option {
655 option {
656 &:focus {
656 &:focus {
657 outline: none;
657 outline: none;
658 }
658 }
659 }
659 }
660
660
661 input,
661 input,
662 textarea {
662 textarea {
663 padding: @input-padding;
663 padding: @input-padding;
664 border: @input-border-thickness solid @border-highlight-color;
664 border: @input-border-thickness solid @border-highlight-color;
665 .border-radius (@border-radius);
665 .border-radius (@border-radius);
666 font-family: @text-light;
666 font-family: @text-light;
667 font-size: @basefontsize;
667 font-size: @basefontsize;
668
668
669 &.input-sm {
669 &.input-sm {
670 padding: 5px;
670 padding: 5px;
671 }
671 }
672
672
673 &#description {
673 &#description {
674 min-width: @input-description-minwidth;
674 min-width: @input-description-minwidth;
675 min-height: 1em;
675 min-height: 1em;
676 padding: 10px;
676 padding: 10px;
677 }
677 }
678 }
678 }
679
679
680 .field-sm {
680 .field-sm {
681 input,
681 input,
682 textarea {
682 textarea {
683 padding: 5px;
683 padding: 5px;
684 }
684 }
685 }
685 }
686
686
687 textarea {
687 textarea {
688 display: block;
688 display: block;
689 clear: both;
689 clear: both;
690 width: 100%;
690 width: 100%;
691 min-height: 100px;
691 min-height: 100px;
692 margin-bottom: @padding;
692 margin-bottom: @padding;
693 .box-sizing(border-box);
693 .box-sizing(border-box);
694 overflow: auto;
694 overflow: auto;
695 }
695 }
696
696
697 label {
697 label {
698 font-family: @text-light;
698 font-family: @text-light;
699 }
699 }
700
700
701 // GRAVATARS
701 // GRAVATARS
702 // centers gravatar on username to the right
702 // centers gravatar on username to the right
703
703
704 .gravatar {
704 .gravatar {
705 display: inline;
705 display: inline;
706 min-width: 16px;
706 min-width: 16px;
707 min-height: 16px;
707 min-height: 16px;
708 margin: -5px 0;
708 margin: -5px 0;
709 padding: 0;
709 padding: 0;
710 line-height: 1em;
710 line-height: 1em;
711 border: 1px solid @grey4;
711 border: 1px solid @grey4;
712 box-sizing: content-box;
712 box-sizing: content-box;
713
713
714 &.gravatar-large {
714 &.gravatar-large {
715 margin: -0.5em .25em -0.5em 0;
715 margin: -0.5em .25em -0.5em 0;
716 }
716 }
717
717
718 & + .user {
718 & + .user {
719 display: inline;
719 display: inline;
720 margin: 0;
720 margin: 0;
721 padding: 0 0 0 .17em;
721 padding: 0 0 0 .17em;
722 line-height: 1em;
722 line-height: 1em;
723 }
723 }
724 }
724 }
725
725
726 .user-inline-data {
726 .user-inline-data {
727 display: inline-block;
727 display: inline-block;
728 float: left;
728 float: left;
729 padding-left: .5em;
729 padding-left: .5em;
730 line-height: 1.3em;
730 line-height: 1.3em;
731 }
731 }
732
732
733 .rc-user { // gravatar + user wrapper
733 .rc-user { // gravatar + user wrapper
734 float: left;
734 float: left;
735 position: relative;
735 position: relative;
736 min-width: 100px;
736 min-width: 100px;
737 max-width: 200px;
737 max-width: 200px;
738 min-height: (@gravatar-size + @border-thickness * 2); // account for border
738 min-height: (@gravatar-size + @border-thickness * 2); // account for border
739 display: block;
739 display: block;
740 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
740 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
741
741
742
742
743 .gravatar {
743 .gravatar {
744 display: block;
744 display: block;
745 position: absolute;
745 position: absolute;
746 top: 0;
746 top: 0;
747 left: 0;
747 left: 0;
748 min-width: @gravatar-size;
748 min-width: @gravatar-size;
749 min-height: @gravatar-size;
749 min-height: @gravatar-size;
750 margin: 0;
750 margin: 0;
751 }
751 }
752
752
753 .user {
753 .user {
754 display: block;
754 display: block;
755 max-width: 175px;
755 max-width: 175px;
756 padding-top: 2px;
756 padding-top: 2px;
757 overflow: hidden;
757 overflow: hidden;
758 text-overflow: ellipsis;
758 text-overflow: ellipsis;
759 }
759 }
760 }
760 }
761
761
762 .gist-gravatar,
762 .gist-gravatar,
763 .journal_container {
763 .journal_container {
764 .gravatar-large {
764 .gravatar-large {
765 margin: 0 .5em -10px 0;
765 margin: 0 .5em -10px 0;
766 }
766 }
767 }
767 }
768
768
769
769
770 // ADMIN SETTINGS
770 // ADMIN SETTINGS
771
771
772 // Tag Patterns
772 // Tag Patterns
773 .tag_patterns {
773 .tag_patterns {
774 .tag_input {
774 .tag_input {
775 margin-bottom: @padding;
775 margin-bottom: @padding;
776 }
776 }
777 }
777 }
778
778
779 .locked_input {
779 .locked_input {
780 position: relative;
780 position: relative;
781
781
782 input {
782 input {
783 display: inline;
783 display: inline;
784 margin: 3px 5px 0px 0px;
784 margin: 3px 5px 0px 0px;
785 }
785 }
786
786
787 br {
787 br {
788 display: none;
788 display: none;
789 }
789 }
790
790
791 .error-message {
791 .error-message {
792 float: left;
792 float: left;
793 width: 100%;
793 width: 100%;
794 }
794 }
795
795
796 .lock_input_button {
796 .lock_input_button {
797 display: inline;
797 display: inline;
798 }
798 }
799
799
800 .help-block {
800 .help-block {
801 clear: both;
801 clear: both;
802 }
802 }
803 }
803 }
804
804
805 // Notifications
805 // Notifications
806
806
807 .notifications_buttons {
807 .notifications_buttons {
808 margin: 0 0 @space 0;
808 margin: 0 0 @space 0;
809 padding: 0;
809 padding: 0;
810
810
811 .btn {
811 .btn {
812 display: inline-block;
812 display: inline-block;
813 }
813 }
814 }
814 }
815
815
816 .notification-list {
816 .notification-list {
817
817
818 div {
818 div {
819 display: inline-block;
819 display: inline-block;
820 vertical-align: middle;
820 vertical-align: middle;
821 }
821 }
822
822
823 .container {
823 .container {
824 display: block;
824 display: block;
825 margin: 0 0 @padding 0;
825 margin: 0 0 @padding 0;
826 }
826 }
827
827
828 .delete-notifications {
828 .delete-notifications {
829 margin-left: @padding;
829 margin-left: @padding;
830 text-align: right;
830 text-align: right;
831 cursor: pointer;
831 cursor: pointer;
832 }
832 }
833
833
834 .read-notifications {
834 .read-notifications {
835 margin-left: @padding/2;
835 margin-left: @padding/2;
836 text-align: right;
836 text-align: right;
837 width: 35px;
837 width: 35px;
838 cursor: pointer;
838 cursor: pointer;
839 }
839 }
840
840
841 .icon-minus-sign {
841 .icon-minus-sign {
842 color: @alert2;
842 color: @alert2;
843 }
843 }
844
844
845 .icon-ok-sign {
845 .icon-ok-sign {
846 color: @alert1;
846 color: @alert1;
847 }
847 }
848 }
848 }
849
849
850 .user_settings {
850 .user_settings {
851 float: left;
851 float: left;
852 clear: both;
852 clear: both;
853 display: block;
853 display: block;
854 width: 100%;
854 width: 100%;
855
855
856 .gravatar_box {
856 .gravatar_box {
857 margin-bottom: @padding;
857 margin-bottom: @padding;
858
858
859 &:after {
859 &:after {
860 content: " ";
860 content: " ";
861 clear: both;
861 clear: both;
862 width: 100%;
862 width: 100%;
863 }
863 }
864 }
864 }
865
865
866 .fields .field {
866 .fields .field {
867 clear: both;
867 clear: both;
868 }
868 }
869 }
869 }
870
870
871 .advanced_settings {
871 .advanced_settings {
872 margin-bottom: @space;
872 margin-bottom: @space;
873
873
874 .help-block {
874 .help-block {
875 margin-left: 0;
875 margin-left: 0;
876 }
876 }
877
877
878 button + .help-block {
878 button + .help-block {
879 margin-top: @padding;
879 margin-top: @padding;
880 }
880 }
881 }
881 }
882
882
883 // admin settings radio buttons and labels
883 // admin settings radio buttons and labels
884 .label-2 {
884 .label-2 {
885 float: left;
885 float: left;
886 width: @label2-width;
886 width: @label2-width;
887
887
888 label {
888 label {
889 color: @grey1;
889 color: @grey1;
890 }
890 }
891 }
891 }
892 .checkboxes {
892 .checkboxes {
893 float: left;
893 float: left;
894 width: @checkboxes-width;
894 width: @checkboxes-width;
895 margin-bottom: @padding;
895 margin-bottom: @padding;
896
896
897 .checkbox {
897 .checkbox {
898 width: 100%;
898 width: 100%;
899
899
900 label {
900 label {
901 margin: 0;
901 margin: 0;
902 padding: 0;
902 padding: 0;
903 }
903 }
904 }
904 }
905
905
906 .checkbox + .checkbox {
906 .checkbox + .checkbox {
907 display: inline-block;
907 display: inline-block;
908 }
908 }
909
909
910 label {
910 label {
911 margin-right: 1em;
911 margin-right: 1em;
912 }
912 }
913 }
913 }
914
914
915 // CHANGELOG
915 // CHANGELOG
916 .container_header {
916 .container_header {
917 float: left;
917 float: left;
918 display: block;
918 display: block;
919 width: 100%;
919 width: 100%;
920 margin: @padding 0 @padding;
920 margin: @padding 0 @padding;
921
921
922 #filter_changelog {
922 #filter_changelog {
923 float: left;
923 float: left;
924 margin-right: @padding;
924 margin-right: @padding;
925 }
925 }
926
926
927 .breadcrumbs_light {
927 .breadcrumbs_light {
928 display: inline-block;
928 display: inline-block;
929 }
929 }
930 }
930 }
931
931
932 .info_box {
932 .info_box {
933 float: right;
933 float: right;
934 }
934 }
935
935
936
936
937 #graph_nodes {
937 #graph_nodes {
938 padding-top: 43px;
938 padding-top: 43px;
939 }
939 }
940
940
941 #graph_content{
941 #graph_content{
942
942
943 // adjust for table headers so that graph renders properly
943 // adjust for table headers so that graph renders properly
944 // #graph_nodes padding - table cell padding
944 // #graph_nodes padding - table cell padding
945 padding-top: (@space - (@basefontsize * 2.4));
945 padding-top: (@space - (@basefontsize * 2.4));
946
946
947 &.graph_full_width {
947 &.graph_full_width {
948 width: 100%;
948 width: 100%;
949 max-width: 100%;
949 max-width: 100%;
950 }
950 }
951 }
951 }
952
952
953 #graph {
953 #graph {
954 .flag_status {
954 .flag_status {
955 margin: 0;
955 margin: 0;
956 }
956 }
957
957
958 .pagination-left {
958 .pagination-left {
959 float: left;
959 float: left;
960 clear: both;
960 clear: both;
961 }
961 }
962
962
963 .log-container {
963 .log-container {
964 max-width: 345px;
964 max-width: 345px;
965
965
966 .message{
966 .message{
967 max-width: 340px;
967 max-width: 340px;
968 }
968 }
969 }
969 }
970
970
971 .graph-col-wrapper {
971 .graph-col-wrapper {
972 padding-left: 110px;
972 padding-left: 110px;
973
973
974 #graph_nodes {
974 #graph_nodes {
975 width: 100px;
975 width: 100px;
976 margin-left: -110px;
976 margin-left: -110px;
977 float: left;
977 float: left;
978 clear: left;
978 clear: left;
979 }
979 }
980 }
980 }
981
981
982 .load-more-commits {
982 .load-more-commits {
983 text-align: center;
983 text-align: center;
984 }
984 }
985 .load-more-commits:hover {
985 .load-more-commits:hover {
986 background-color: @grey7;
986 background-color: @grey7;
987 }
987 }
988 .load-more-commits {
988 .load-more-commits {
989 a {
989 a {
990 display: block;
990 display: block;
991 }
991 }
992 }
992 }
993 }
993 }
994
994
995 #filter_changelog {
995 #filter_changelog {
996 float: left;
996 float: left;
997 }
997 }
998
998
999
999
1000 //--- THEME ------------------//
1000 //--- THEME ------------------//
1001
1001
1002 #logo {
1002 #logo {
1003 float: left;
1003 float: left;
1004 margin: 9px 0 0 0;
1004 margin: 9px 0 0 0;
1005
1005
1006 .header {
1006 .header {
1007 background-color: transparent;
1007 background-color: transparent;
1008 }
1008 }
1009
1009
1010 a {
1010 a {
1011 display: inline-block;
1011 display: inline-block;
1012 }
1012 }
1013
1013
1014 img {
1014 img {
1015 height:30px;
1015 height:30px;
1016 }
1016 }
1017 }
1017 }
1018
1018
1019 .logo-wrapper {
1019 .logo-wrapper {
1020 float:left;
1020 float:left;
1021 }
1021 }
1022
1022
1023 .branding{
1023 .branding{
1024 float: left;
1024 float: left;
1025 padding: 9px 2px;
1025 padding: 9px 2px;
1026 line-height: 1em;
1026 line-height: 1em;
1027 font-size: @navigation-fontsize;
1027 font-size: @navigation-fontsize;
1028 }
1028 }
1029
1029
1030 img {
1030 img {
1031 border: none;
1031 border: none;
1032 outline: none;
1032 outline: none;
1033 }
1033 }
1034 user-profile-header
1034 user-profile-header
1035 label {
1035 label {
1036
1036
1037 input[type="checkbox"] {
1037 input[type="checkbox"] {
1038 margin-right: 1em;
1038 margin-right: 1em;
1039 }
1039 }
1040 input[type="radio"] {
1040 input[type="radio"] {
1041 margin-right: 1em;
1041 margin-right: 1em;
1042 }
1042 }
1043 }
1043 }
1044
1044
1045 .flag_status {
1045 .flag_status {
1046 margin: 2px 8px 6px 2px;
1046 margin: 2px 8px 6px 2px;
1047 &.under_review {
1047 &.under_review {
1048 .circle(5px, @alert3);
1048 .circle(5px, @alert3);
1049 }
1049 }
1050 &.approved {
1050 &.approved {
1051 .circle(5px, @alert1);
1051 .circle(5px, @alert1);
1052 }
1052 }
1053 &.rejected,
1053 &.rejected,
1054 &.forced_closed{
1054 &.forced_closed{
1055 .circle(5px, @alert2);
1055 .circle(5px, @alert2);
1056 }
1056 }
1057 &.not_reviewed {
1057 &.not_reviewed {
1058 .circle(5px, @grey5);
1058 .circle(5px, @grey5);
1059 }
1059 }
1060 }
1060 }
1061
1061
1062 .flag_status_comment_box {
1062 .flag_status_comment_box {
1063 margin: 5px 6px 0px 2px;
1063 margin: 5px 6px 0px 2px;
1064 }
1064 }
1065 .test_pattern_preview {
1065 .test_pattern_preview {
1066 margin: @space 0;
1066 margin: @space 0;
1067
1067
1068 p {
1068 p {
1069 margin-bottom: 0;
1069 margin-bottom: 0;
1070 border-bottom: @border-thickness solid @border-default-color;
1070 border-bottom: @border-thickness solid @border-default-color;
1071 color: @grey3;
1071 color: @grey3;
1072 }
1072 }
1073
1073
1074 .btn {
1074 .btn {
1075 margin-bottom: @padding;
1075 margin-bottom: @padding;
1076 }
1076 }
1077 }
1077 }
1078 #test_pattern_result {
1078 #test_pattern_result {
1079 display: none;
1079 display: none;
1080 &:extend(pre);
1080 &:extend(pre);
1081 padding: .9em;
1081 padding: .9em;
1082 color: @grey3;
1082 color: @grey3;
1083 background-color: @grey7;
1083 background-color: @grey7;
1084 border-right: @border-thickness solid @border-default-color;
1084 border-right: @border-thickness solid @border-default-color;
1085 border-bottom: @border-thickness solid @border-default-color;
1085 border-bottom: @border-thickness solid @border-default-color;
1086 border-left: @border-thickness solid @border-default-color;
1086 border-left: @border-thickness solid @border-default-color;
1087 }
1087 }
1088
1088
1089 #repo_vcs_settings {
1089 #repo_vcs_settings {
1090 #inherit_overlay_vcs_default {
1090 #inherit_overlay_vcs_default {
1091 display: none;
1091 display: none;
1092 }
1092 }
1093 #inherit_overlay_vcs_custom {
1093 #inherit_overlay_vcs_custom {
1094 display: custom;
1094 display: custom;
1095 }
1095 }
1096 &.inherited {
1096 &.inherited {
1097 #inherit_overlay_vcs_default {
1097 #inherit_overlay_vcs_default {
1098 display: block;
1098 display: block;
1099 }
1099 }
1100 #inherit_overlay_vcs_custom {
1100 #inherit_overlay_vcs_custom {
1101 display: none;
1101 display: none;
1102 }
1102 }
1103 }
1103 }
1104 }
1104 }
1105
1105
1106 .issue-tracker-link {
1106 .issue-tracker-link {
1107 color: @rcblue;
1107 color: @rcblue;
1108 }
1108 }
1109
1109
1110 // Issue Tracker Table Show/Hide
1110 // Issue Tracker Table Show/Hide
1111 #repo_issue_tracker {
1111 #repo_issue_tracker {
1112 #inherit_overlay {
1112 #inherit_overlay {
1113 display: none;
1113 display: none;
1114 }
1114 }
1115 #custom_overlay {
1115 #custom_overlay {
1116 display: custom;
1116 display: custom;
1117 }
1117 }
1118 &.inherited {
1118 &.inherited {
1119 #inherit_overlay {
1119 #inherit_overlay {
1120 display: block;
1120 display: block;
1121 }
1121 }
1122 #custom_overlay {
1122 #custom_overlay {
1123 display: none;
1123 display: none;
1124 }
1124 }
1125 }
1125 }
1126 }
1126 }
1127 table.issuetracker {
1127 table.issuetracker {
1128 &.readonly {
1128 &.readonly {
1129 tr, td {
1129 tr, td {
1130 color: @grey3;
1130 color: @grey3;
1131 }
1131 }
1132 }
1132 }
1133 .edit {
1133 .edit {
1134 display: none;
1134 display: none;
1135 }
1135 }
1136 .editopen {
1136 .editopen {
1137 .edit {
1137 .edit {
1138 display: inline;
1138 display: inline;
1139 }
1139 }
1140 .entry {
1140 .entry {
1141 display: none;
1141 display: none;
1142 }
1142 }
1143 }
1143 }
1144 tr td.td-action {
1144 tr td.td-action {
1145 min-width: 117px;
1145 min-width: 117px;
1146 }
1146 }
1147 td input {
1147 td input {
1148 max-width: none;
1148 max-width: none;
1149 min-width: 30px;
1149 min-width: 30px;
1150 width: 80%;
1150 width: 80%;
1151 }
1151 }
1152 .issuetracker_pref input {
1152 .issuetracker_pref input {
1153 width: 40%;
1153 width: 40%;
1154 }
1154 }
1155 input.edit_issuetracker_update {
1155 input.edit_issuetracker_update {
1156 margin-right: 0;
1156 margin-right: 0;
1157 width: auto;
1157 width: auto;
1158 }
1158 }
1159 }
1159 }
1160
1160
1161 table.integrations {
1161 table.integrations {
1162 .td-icon {
1162 .td-icon {
1163 width: 20px;
1163 width: 20px;
1164 .integration-icon {
1164 .integration-icon {
1165 height: 20px;
1165 height: 20px;
1166 width: 20px;
1166 width: 20px;
1167 }
1167 }
1168 }
1168 }
1169 }
1169 }
1170
1170
1171 .integrations {
1171 .integrations {
1172 a.integration-box {
1172 a.integration-box {
1173 color: @text-color;
1173 color: @text-color;
1174 &:hover {
1174 &:hover {
1175 .panel {
1175 .panel {
1176 background: #fbfbfb;
1176 background: #fbfbfb;
1177 }
1177 }
1178 }
1178 }
1179 .integration-icon {
1179 .integration-icon {
1180 width: 30px;
1180 width: 30px;
1181 height: 30px;
1181 height: 30px;
1182 margin-right: 20px;
1182 margin-right: 20px;
1183 float: left;
1183 float: left;
1184 }
1184 }
1185
1185
1186 .panel-body {
1186 .panel-body {
1187 padding: 10px;
1187 padding: 10px;
1188 }
1188 }
1189 .panel {
1189 .panel {
1190 margin-bottom: 10px;
1190 margin-bottom: 10px;
1191 }
1191 }
1192 h2 {
1192 h2 {
1193 display: inline-block;
1193 display: inline-block;
1194 margin: 0;
1194 margin: 0;
1195 min-width: 140px;
1195 min-width: 140px;
1196 }
1196 }
1197 }
1197 }
1198 a.integration-box.dummy-integration {
1199 color: @grey4
1200 }
1198 }
1201 }
1199
1202
1200 //Permissions Settings
1203 //Permissions Settings
1201 #add_perm {
1204 #add_perm {
1202 margin: 0 0 @padding;
1205 margin: 0 0 @padding;
1203 cursor: pointer;
1206 cursor: pointer;
1204 }
1207 }
1205
1208
1206 .perm_ac {
1209 .perm_ac {
1207 input {
1210 input {
1208 width: 95%;
1211 width: 95%;
1209 }
1212 }
1210 }
1213 }
1211
1214
1212 .autocomplete-suggestions {
1215 .autocomplete-suggestions {
1213 width: auto !important; // overrides autocomplete.js
1216 width: auto !important; // overrides autocomplete.js
1214 margin: 0;
1217 margin: 0;
1215 border: @border-thickness solid @rcblue;
1218 border: @border-thickness solid @rcblue;
1216 border-radius: @border-radius;
1219 border-radius: @border-radius;
1217 color: @rcblue;
1220 color: @rcblue;
1218 background-color: white;
1221 background-color: white;
1219 }
1222 }
1220 .autocomplete-selected {
1223 .autocomplete-selected {
1221 background: #F0F0F0;
1224 background: #F0F0F0;
1222 }
1225 }
1223 .ac-container-wrap {
1226 .ac-container-wrap {
1224 margin: 0;
1227 margin: 0;
1225 padding: 8px;
1228 padding: 8px;
1226 border-bottom: @border-thickness solid @rclightblue;
1229 border-bottom: @border-thickness solid @rclightblue;
1227 list-style-type: none;
1230 list-style-type: none;
1228 cursor: pointer;
1231 cursor: pointer;
1229
1232
1230 &:hover {
1233 &:hover {
1231 background-color: @rclightblue;
1234 background-color: @rclightblue;
1232 }
1235 }
1233
1236
1234 img {
1237 img {
1235 height: @gravatar-size;
1238 height: @gravatar-size;
1236 width: @gravatar-size;
1239 width: @gravatar-size;
1237 margin-right: 1em;
1240 margin-right: 1em;
1238 }
1241 }
1239
1242
1240 strong {
1243 strong {
1241 font-weight: normal;
1244 font-weight: normal;
1242 }
1245 }
1243 }
1246 }
1244
1247
1245 // Settings Dropdown
1248 // Settings Dropdown
1246 .user-menu .container {
1249 .user-menu .container {
1247 padding: 0 4px;
1250 padding: 0 4px;
1248 margin: 0;
1251 margin: 0;
1249 }
1252 }
1250
1253
1251 .user-menu .gravatar {
1254 .user-menu .gravatar {
1252 cursor: pointer;
1255 cursor: pointer;
1253 }
1256 }
1254
1257
1255 .codeblock {
1258 .codeblock {
1256 margin-bottom: @padding;
1259 margin-bottom: @padding;
1257 clear: both;
1260 clear: both;
1258
1261
1259 .stats{
1262 .stats{
1260 overflow: hidden;
1263 overflow: hidden;
1261 }
1264 }
1262
1265
1263 .message{
1266 .message{
1264 textarea{
1267 textarea{
1265 margin: 0;
1268 margin: 0;
1266 }
1269 }
1267 }
1270 }
1268
1271
1269 .code-header {
1272 .code-header {
1270 .stats {
1273 .stats {
1271 line-height: 2em;
1274 line-height: 2em;
1272
1275
1273 .revision_id {
1276 .revision_id {
1274 margin-left: 0;
1277 margin-left: 0;
1275 }
1278 }
1276 .buttons {
1279 .buttons {
1277 padding-right: 0;
1280 padding-right: 0;
1278 }
1281 }
1279 }
1282 }
1280
1283
1281 .item{
1284 .item{
1282 margin-right: 0.5em;
1285 margin-right: 0.5em;
1283 }
1286 }
1284 }
1287 }
1285
1288
1286 #editor_container{
1289 #editor_container{
1287 position: relative;
1290 position: relative;
1288 margin: @padding;
1291 margin: @padding;
1289 }
1292 }
1290 }
1293 }
1291
1294
1292 #file_history_container {
1295 #file_history_container {
1293 display: none;
1296 display: none;
1294 }
1297 }
1295
1298
1296 .file-history-inner {
1299 .file-history-inner {
1297 margin-bottom: 10px;
1300 margin-bottom: 10px;
1298 }
1301 }
1299
1302
1300 // Pull Requests
1303 // Pull Requests
1301 .summary-details {
1304 .summary-details {
1302 width: 72%;
1305 width: 72%;
1303 }
1306 }
1304 .pr-summary {
1307 .pr-summary {
1305 border-bottom: @border-thickness solid @grey5;
1308 border-bottom: @border-thickness solid @grey5;
1306 margin-bottom: @space;
1309 margin-bottom: @space;
1307 }
1310 }
1308 .reviewers-title {
1311 .reviewers-title {
1309 width: 25%;
1312 width: 25%;
1310 min-width: 200px;
1313 min-width: 200px;
1311 }
1314 }
1312 .reviewers {
1315 .reviewers {
1313 width: 25%;
1316 width: 25%;
1314 min-width: 200px;
1317 min-width: 200px;
1315 }
1318 }
1316 .reviewers ul li {
1319 .reviewers ul li {
1317 position: relative;
1320 position: relative;
1318 width: 100%;
1321 width: 100%;
1319 margin-bottom: 8px;
1322 margin-bottom: 8px;
1320 }
1323 }
1321
1324
1322 .reviewer_entry {
1325 .reviewer_entry {
1323 min-height: 55px;
1326 min-height: 55px;
1324 }
1327 }
1325
1328
1326 .reviewers_member {
1329 .reviewers_member {
1327 width: 100%;
1330 width: 100%;
1328 overflow: auto;
1331 overflow: auto;
1329 }
1332 }
1330 .reviewer_reason {
1333 .reviewer_reason {
1331 padding-left: 20px;
1334 padding-left: 20px;
1332 }
1335 }
1333 .reviewer_status {
1336 .reviewer_status {
1334 display: inline-block;
1337 display: inline-block;
1335 vertical-align: top;
1338 vertical-align: top;
1336 width: 7%;
1339 width: 7%;
1337 min-width: 20px;
1340 min-width: 20px;
1338 height: 1.2em;
1341 height: 1.2em;
1339 margin-top: 3px;
1342 margin-top: 3px;
1340 line-height: 1em;
1343 line-height: 1em;
1341 }
1344 }
1342
1345
1343 .reviewer_name {
1346 .reviewer_name {
1344 display: inline-block;
1347 display: inline-block;
1345 max-width: 83%;
1348 max-width: 83%;
1346 padding-right: 20px;
1349 padding-right: 20px;
1347 vertical-align: middle;
1350 vertical-align: middle;
1348 line-height: 1;
1351 line-height: 1;
1349
1352
1350 .rc-user {
1353 .rc-user {
1351 min-width: 0;
1354 min-width: 0;
1352 margin: -2px 1em 0 0;
1355 margin: -2px 1em 0 0;
1353 }
1356 }
1354
1357
1355 .reviewer {
1358 .reviewer {
1356 float: left;
1359 float: left;
1357 }
1360 }
1358 }
1361 }
1359
1362
1360 .reviewer_member_mandatory,
1363 .reviewer_member_mandatory,
1361 .reviewer_member_mandatory_remove,
1364 .reviewer_member_mandatory_remove,
1362 .reviewer_member_remove {
1365 .reviewer_member_remove {
1363 position: absolute;
1366 position: absolute;
1364 right: 0;
1367 right: 0;
1365 top: 0;
1368 top: 0;
1366 width: 16px;
1369 width: 16px;
1367 margin-bottom: 10px;
1370 margin-bottom: 10px;
1368 padding: 0;
1371 padding: 0;
1369 color: black;
1372 color: black;
1370 }
1373 }
1371
1374
1372 .reviewer_member_mandatory_remove {
1375 .reviewer_member_mandatory_remove {
1373 color: @grey4;
1376 color: @grey4;
1374 }
1377 }
1375
1378
1376 .reviewer_member_mandatory {
1379 .reviewer_member_mandatory {
1377 padding-top:20px;
1380 padding-top:20px;
1378 }
1381 }
1379
1382
1380 .reviewer_member_status {
1383 .reviewer_member_status {
1381 margin-top: 5px;
1384 margin-top: 5px;
1382 }
1385 }
1383 .pr-summary #summary{
1386 .pr-summary #summary{
1384 width: 100%;
1387 width: 100%;
1385 }
1388 }
1386 .pr-summary .action_button:hover {
1389 .pr-summary .action_button:hover {
1387 border: 0;
1390 border: 0;
1388 cursor: pointer;
1391 cursor: pointer;
1389 }
1392 }
1390 .pr-details-title {
1393 .pr-details-title {
1391 padding-bottom: 8px;
1394 padding-bottom: 8px;
1392 border-bottom: @border-thickness solid @grey5;
1395 border-bottom: @border-thickness solid @grey5;
1393
1396
1394 .action_button.disabled {
1397 .action_button.disabled {
1395 color: @grey4;
1398 color: @grey4;
1396 cursor: inherit;
1399 cursor: inherit;
1397 }
1400 }
1398 .action_button {
1401 .action_button {
1399 color: @rcblue;
1402 color: @rcblue;
1400 }
1403 }
1401 }
1404 }
1402 .pr-details-content {
1405 .pr-details-content {
1403 margin-top: @textmargin;
1406 margin-top: @textmargin;
1404 margin-bottom: @textmargin;
1407 margin-bottom: @textmargin;
1405 }
1408 }
1406 .pr-description {
1409 .pr-description {
1407 white-space:pre-wrap;
1410 white-space:pre-wrap;
1408 }
1411 }
1409
1412
1410 .pr-reviewer-rules {
1413 .pr-reviewer-rules {
1411 padding: 10px 0px 20px 0px;
1414 padding: 10px 0px 20px 0px;
1412 }
1415 }
1413
1416
1414 .group_members {
1417 .group_members {
1415 margin-top: 0;
1418 margin-top: 0;
1416 padding: 0;
1419 padding: 0;
1417 list-style: outside none none;
1420 list-style: outside none none;
1418
1421
1419 img {
1422 img {
1420 height: @gravatar-size;
1423 height: @gravatar-size;
1421 width: @gravatar-size;
1424 width: @gravatar-size;
1422 margin-right: .5em;
1425 margin-right: .5em;
1423 margin-left: 3px;
1426 margin-left: 3px;
1424 }
1427 }
1425
1428
1426 .to-delete {
1429 .to-delete {
1427 .user {
1430 .user {
1428 text-decoration: line-through;
1431 text-decoration: line-through;
1429 }
1432 }
1430 }
1433 }
1431 }
1434 }
1432
1435
1433 .compare_view_commits_title {
1436 .compare_view_commits_title {
1434 .disabled {
1437 .disabled {
1435 cursor: inherit;
1438 cursor: inherit;
1436 &:hover{
1439 &:hover{
1437 background-color: inherit;
1440 background-color: inherit;
1438 color: inherit;
1441 color: inherit;
1439 }
1442 }
1440 }
1443 }
1441 }
1444 }
1442
1445
1443 .subtitle-compare {
1446 .subtitle-compare {
1444 margin: -15px 0px 0px 0px;
1447 margin: -15px 0px 0px 0px;
1445 }
1448 }
1446
1449
1447 .comments-summary-td {
1450 .comments-summary-td {
1448 border-top: 1px dashed @grey5;
1451 border-top: 1px dashed @grey5;
1449 }
1452 }
1450
1453
1451 // new entry in group_members
1454 // new entry in group_members
1452 .td-author-new-entry {
1455 .td-author-new-entry {
1453 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1456 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1454 }
1457 }
1455
1458
1456 .usergroup_member_remove {
1459 .usergroup_member_remove {
1457 width: 16px;
1460 width: 16px;
1458 margin-bottom: 10px;
1461 margin-bottom: 10px;
1459 padding: 0;
1462 padding: 0;
1460 color: black !important;
1463 color: black !important;
1461 cursor: pointer;
1464 cursor: pointer;
1462 }
1465 }
1463
1466
1464 .reviewer_ac .ac-input {
1467 .reviewer_ac .ac-input {
1465 width: 92%;
1468 width: 92%;
1466 margin-bottom: 1em;
1469 margin-bottom: 1em;
1467 }
1470 }
1468
1471
1469 .compare_view_commits tr{
1472 .compare_view_commits tr{
1470 height: 20px;
1473 height: 20px;
1471 }
1474 }
1472 .compare_view_commits td {
1475 .compare_view_commits td {
1473 vertical-align: top;
1476 vertical-align: top;
1474 padding-top: 10px;
1477 padding-top: 10px;
1475 }
1478 }
1476 .compare_view_commits .author {
1479 .compare_view_commits .author {
1477 margin-left: 5px;
1480 margin-left: 5px;
1478 }
1481 }
1479
1482
1480 .compare_view_commits {
1483 .compare_view_commits {
1481 .color-a {
1484 .color-a {
1482 color: @alert1;
1485 color: @alert1;
1483 }
1486 }
1484
1487
1485 .color-c {
1488 .color-c {
1486 color: @color3;
1489 color: @color3;
1487 }
1490 }
1488
1491
1489 .color-r {
1492 .color-r {
1490 color: @color5;
1493 color: @color5;
1491 }
1494 }
1492
1495
1493 .color-a-bg {
1496 .color-a-bg {
1494 background-color: @alert1;
1497 background-color: @alert1;
1495 }
1498 }
1496
1499
1497 .color-c-bg {
1500 .color-c-bg {
1498 background-color: @alert3;
1501 background-color: @alert3;
1499 }
1502 }
1500
1503
1501 .color-r-bg {
1504 .color-r-bg {
1502 background-color: @alert2;
1505 background-color: @alert2;
1503 }
1506 }
1504
1507
1505 .color-a-border {
1508 .color-a-border {
1506 border: 1px solid @alert1;
1509 border: 1px solid @alert1;
1507 }
1510 }
1508
1511
1509 .color-c-border {
1512 .color-c-border {
1510 border: 1px solid @alert3;
1513 border: 1px solid @alert3;
1511 }
1514 }
1512
1515
1513 .color-r-border {
1516 .color-r-border {
1514 border: 1px solid @alert2;
1517 border: 1px solid @alert2;
1515 }
1518 }
1516
1519
1517 .commit-change-indicator {
1520 .commit-change-indicator {
1518 width: 15px;
1521 width: 15px;
1519 height: 15px;
1522 height: 15px;
1520 position: relative;
1523 position: relative;
1521 left: 15px;
1524 left: 15px;
1522 }
1525 }
1523
1526
1524 .commit-change-content {
1527 .commit-change-content {
1525 text-align: center;
1528 text-align: center;
1526 vertical-align: middle;
1529 vertical-align: middle;
1527 line-height: 15px;
1530 line-height: 15px;
1528 }
1531 }
1529 }
1532 }
1530
1533
1531 .compare_view_files {
1534 .compare_view_files {
1532 width: 100%;
1535 width: 100%;
1533
1536
1534 td {
1537 td {
1535 vertical-align: middle;
1538 vertical-align: middle;
1536 }
1539 }
1537 }
1540 }
1538
1541
1539 .compare_view_filepath {
1542 .compare_view_filepath {
1540 color: @grey1;
1543 color: @grey1;
1541 }
1544 }
1542
1545
1543 .show_more {
1546 .show_more {
1544 display: inline-block;
1547 display: inline-block;
1545 position: relative;
1548 position: relative;
1546 vertical-align: middle;
1549 vertical-align: middle;
1547 width: 4px;
1550 width: 4px;
1548 height: @basefontsize;
1551 height: @basefontsize;
1549
1552
1550 &:after {
1553 &:after {
1551 content: "\00A0\25BE";
1554 content: "\00A0\25BE";
1552 display: inline-block;
1555 display: inline-block;
1553 width:10px;
1556 width:10px;
1554 line-height: 5px;
1557 line-height: 5px;
1555 font-size: 12px;
1558 font-size: 12px;
1556 cursor: pointer;
1559 cursor: pointer;
1557 }
1560 }
1558 }
1561 }
1559
1562
1560 .journal_more .show_more {
1563 .journal_more .show_more {
1561 display: inline;
1564 display: inline;
1562
1565
1563 &:after {
1566 &:after {
1564 content: none;
1567 content: none;
1565 }
1568 }
1566 }
1569 }
1567
1570
1568 .open .show_more:after,
1571 .open .show_more:after,
1569 .select2-dropdown-open .show_more:after {
1572 .select2-dropdown-open .show_more:after {
1570 .rotate(180deg);
1573 .rotate(180deg);
1571 margin-left: 4px;
1574 margin-left: 4px;
1572 }
1575 }
1573
1576
1574
1577
1575 .compare_view_commits .collapse_commit:after {
1578 .compare_view_commits .collapse_commit:after {
1576 cursor: pointer;
1579 cursor: pointer;
1577 content: "\00A0\25B4";
1580 content: "\00A0\25B4";
1578 margin-left: -3px;
1581 margin-left: -3px;
1579 font-size: 17px;
1582 font-size: 17px;
1580 color: @grey4;
1583 color: @grey4;
1581 }
1584 }
1582
1585
1583 .diff_links {
1586 .diff_links {
1584 margin-left: 8px;
1587 margin-left: 8px;
1585 }
1588 }
1586
1589
1587 div.ancestor {
1590 div.ancestor {
1588 margin: -30px 0px;
1591 margin: -30px 0px;
1589 }
1592 }
1590
1593
1591 .cs_icon_td input[type="checkbox"] {
1594 .cs_icon_td input[type="checkbox"] {
1592 display: none;
1595 display: none;
1593 }
1596 }
1594
1597
1595 .cs_icon_td .expand_file_icon:after {
1598 .cs_icon_td .expand_file_icon:after {
1596 cursor: pointer;
1599 cursor: pointer;
1597 content: "\00A0\25B6";
1600 content: "\00A0\25B6";
1598 font-size: 12px;
1601 font-size: 12px;
1599 color: @grey4;
1602 color: @grey4;
1600 }
1603 }
1601
1604
1602 .cs_icon_td .collapse_file_icon:after {
1605 .cs_icon_td .collapse_file_icon:after {
1603 cursor: pointer;
1606 cursor: pointer;
1604 content: "\00A0\25BC";
1607 content: "\00A0\25BC";
1605 font-size: 12px;
1608 font-size: 12px;
1606 color: @grey4;
1609 color: @grey4;
1607 }
1610 }
1608
1611
1609 /*new binary
1612 /*new binary
1610 NEW_FILENODE = 1
1613 NEW_FILENODE = 1
1611 DEL_FILENODE = 2
1614 DEL_FILENODE = 2
1612 MOD_FILENODE = 3
1615 MOD_FILENODE = 3
1613 RENAMED_FILENODE = 4
1616 RENAMED_FILENODE = 4
1614 COPIED_FILENODE = 5
1617 COPIED_FILENODE = 5
1615 CHMOD_FILENODE = 6
1618 CHMOD_FILENODE = 6
1616 BIN_FILENODE = 7
1619 BIN_FILENODE = 7
1617 */
1620 */
1618 .cs_files_expand {
1621 .cs_files_expand {
1619 font-size: @basefontsize + 5px;
1622 font-size: @basefontsize + 5px;
1620 line-height: 1.8em;
1623 line-height: 1.8em;
1621 float: right;
1624 float: right;
1622 }
1625 }
1623
1626
1624 .cs_files_expand span{
1627 .cs_files_expand span{
1625 color: @rcblue;
1628 color: @rcblue;
1626 cursor: pointer;
1629 cursor: pointer;
1627 }
1630 }
1628 .cs_files {
1631 .cs_files {
1629 clear: both;
1632 clear: both;
1630 padding-bottom: @padding;
1633 padding-bottom: @padding;
1631
1634
1632 .cur_cs {
1635 .cur_cs {
1633 margin: 10px 2px;
1636 margin: 10px 2px;
1634 font-weight: bold;
1637 font-weight: bold;
1635 }
1638 }
1636
1639
1637 .node {
1640 .node {
1638 float: left;
1641 float: left;
1639 }
1642 }
1640
1643
1641 .changes {
1644 .changes {
1642 float: right;
1645 float: right;
1643 color: white;
1646 color: white;
1644 font-size: @basefontsize - 4px;
1647 font-size: @basefontsize - 4px;
1645 margin-top: 4px;
1648 margin-top: 4px;
1646 opacity: 0.6;
1649 opacity: 0.6;
1647 filter: Alpha(opacity=60); /* IE8 and earlier */
1650 filter: Alpha(opacity=60); /* IE8 and earlier */
1648
1651
1649 .added {
1652 .added {
1650 background-color: @alert1;
1653 background-color: @alert1;
1651 float: left;
1654 float: left;
1652 text-align: center;
1655 text-align: center;
1653 }
1656 }
1654
1657
1655 .deleted {
1658 .deleted {
1656 background-color: @alert2;
1659 background-color: @alert2;
1657 float: left;
1660 float: left;
1658 text-align: center;
1661 text-align: center;
1659 }
1662 }
1660
1663
1661 .bin {
1664 .bin {
1662 background-color: @alert1;
1665 background-color: @alert1;
1663 text-align: center;
1666 text-align: center;
1664 }
1667 }
1665
1668
1666 /*new binary*/
1669 /*new binary*/
1667 .bin.bin1 {
1670 .bin.bin1 {
1668 background-color: @alert1;
1671 background-color: @alert1;
1669 text-align: center;
1672 text-align: center;
1670 }
1673 }
1671
1674
1672 /*deleted binary*/
1675 /*deleted binary*/
1673 .bin.bin2 {
1676 .bin.bin2 {
1674 background-color: @alert2;
1677 background-color: @alert2;
1675 text-align: center;
1678 text-align: center;
1676 }
1679 }
1677
1680
1678 /*mod binary*/
1681 /*mod binary*/
1679 .bin.bin3 {
1682 .bin.bin3 {
1680 background-color: @grey2;
1683 background-color: @grey2;
1681 text-align: center;
1684 text-align: center;
1682 }
1685 }
1683
1686
1684 /*rename file*/
1687 /*rename file*/
1685 .bin.bin4 {
1688 .bin.bin4 {
1686 background-color: @alert4;
1689 background-color: @alert4;
1687 text-align: center;
1690 text-align: center;
1688 }
1691 }
1689
1692
1690 /*copied file*/
1693 /*copied file*/
1691 .bin.bin5 {
1694 .bin.bin5 {
1692 background-color: @alert4;
1695 background-color: @alert4;
1693 text-align: center;
1696 text-align: center;
1694 }
1697 }
1695
1698
1696 /*chmod file*/
1699 /*chmod file*/
1697 .bin.bin6 {
1700 .bin.bin6 {
1698 background-color: @grey2;
1701 background-color: @grey2;
1699 text-align: center;
1702 text-align: center;
1700 }
1703 }
1701 }
1704 }
1702 }
1705 }
1703
1706
1704 .cs_files .cs_added, .cs_files .cs_A,
1707 .cs_files .cs_added, .cs_files .cs_A,
1705 .cs_files .cs_added, .cs_files .cs_M,
1708 .cs_files .cs_added, .cs_files .cs_M,
1706 .cs_files .cs_added, .cs_files .cs_D {
1709 .cs_files .cs_added, .cs_files .cs_D {
1707 height: 16px;
1710 height: 16px;
1708 padding-right: 10px;
1711 padding-right: 10px;
1709 margin-top: 7px;
1712 margin-top: 7px;
1710 text-align: left;
1713 text-align: left;
1711 }
1714 }
1712
1715
1713 .cs_icon_td {
1716 .cs_icon_td {
1714 min-width: 16px;
1717 min-width: 16px;
1715 width: 16px;
1718 width: 16px;
1716 }
1719 }
1717
1720
1718 .pull-request-merge {
1721 .pull-request-merge {
1719 border: 1px solid @grey5;
1722 border: 1px solid @grey5;
1720 padding: 10px 0px 20px;
1723 padding: 10px 0px 20px;
1721 margin-top: 10px;
1724 margin-top: 10px;
1722 margin-bottom: 20px;
1725 margin-bottom: 20px;
1723 }
1726 }
1724
1727
1725 .pull-request-merge ul {
1728 .pull-request-merge ul {
1726 padding: 0px 0px;
1729 padding: 0px 0px;
1727 }
1730 }
1728
1731
1729 .pull-request-merge li:before{
1732 .pull-request-merge li:before{
1730 content:none;
1733 content:none;
1731 }
1734 }
1732
1735
1733 .pull-request-merge .pull-request-wrap {
1736 .pull-request-merge .pull-request-wrap {
1734 height: auto;
1737 height: auto;
1735 padding: 0px 0px;
1738 padding: 0px 0px;
1736 text-align: right;
1739 text-align: right;
1737 }
1740 }
1738
1741
1739 .pull-request-merge span {
1742 .pull-request-merge span {
1740 margin-right: 5px;
1743 margin-right: 5px;
1741 }
1744 }
1742
1745
1743 .pull-request-merge-actions {
1746 .pull-request-merge-actions {
1744 min-height: 30px;
1747 min-height: 30px;
1745 padding: 0px 0px;
1748 padding: 0px 0px;
1746 }
1749 }
1747
1750
1748 .pull-request-merge-info {
1751 .pull-request-merge-info {
1749 padding: 0px 5px 5px 0px;
1752 padding: 0px 5px 5px 0px;
1750 }
1753 }
1751
1754
1752 .merge-status {
1755 .merge-status {
1753 margin-right: 5px;
1756 margin-right: 5px;
1754 }
1757 }
1755
1758
1756 .merge-message {
1759 .merge-message {
1757 font-size: 1.2em
1760 font-size: 1.2em
1758 }
1761 }
1759
1762
1760 .merge-message.success i,
1763 .merge-message.success i,
1761 .merge-icon.success i {
1764 .merge-icon.success i {
1762 color:@alert1;
1765 color:@alert1;
1763 }
1766 }
1764
1767
1765 .merge-message.warning i,
1768 .merge-message.warning i,
1766 .merge-icon.warning i {
1769 .merge-icon.warning i {
1767 color: @alert3;
1770 color: @alert3;
1768 }
1771 }
1769
1772
1770 .merge-message.error i,
1773 .merge-message.error i,
1771 .merge-icon.error i {
1774 .merge-icon.error i {
1772 color:@alert2;
1775 color:@alert2;
1773 }
1776 }
1774
1777
1775 .pr-versions {
1778 .pr-versions {
1776 font-size: 1.1em;
1779 font-size: 1.1em;
1777
1780
1778 table {
1781 table {
1779 padding: 0px 5px;
1782 padding: 0px 5px;
1780 }
1783 }
1781
1784
1782 td {
1785 td {
1783 line-height: 15px;
1786 line-height: 15px;
1784 }
1787 }
1785
1788
1786 .flag_status {
1789 .flag_status {
1787 margin: 0;
1790 margin: 0;
1788 }
1791 }
1789
1792
1790 .compare-radio-button {
1793 .compare-radio-button {
1791 position: relative;
1794 position: relative;
1792 top: -3px;
1795 top: -3px;
1793 }
1796 }
1794 }
1797 }
1795
1798
1796
1799
1797 #close_pull_request {
1800 #close_pull_request {
1798 margin-right: 0px;
1801 margin-right: 0px;
1799 }
1802 }
1800
1803
1801 .empty_data {
1804 .empty_data {
1802 color: @grey4;
1805 color: @grey4;
1803 }
1806 }
1804
1807
1805 #changeset_compare_view_content {
1808 #changeset_compare_view_content {
1806 margin-bottom: @space;
1809 margin-bottom: @space;
1807 clear: both;
1810 clear: both;
1808 width: 100%;
1811 width: 100%;
1809 box-sizing: border-box;
1812 box-sizing: border-box;
1810 .border-radius(@border-radius);
1813 .border-radius(@border-radius);
1811
1814
1812 .help-block {
1815 .help-block {
1813 margin: @padding 0;
1816 margin: @padding 0;
1814 color: @text-color;
1817 color: @text-color;
1815 &.pre-formatting {
1818 &.pre-formatting {
1816 white-space: pre;
1819 white-space: pre;
1817 }
1820 }
1818 }
1821 }
1819
1822
1820 .empty_data {
1823 .empty_data {
1821 margin: @padding 0;
1824 margin: @padding 0;
1822 }
1825 }
1823
1826
1824 .alert {
1827 .alert {
1825 margin-bottom: @space;
1828 margin-bottom: @space;
1826 }
1829 }
1827 }
1830 }
1828
1831
1829 .table_disp {
1832 .table_disp {
1830 .status {
1833 .status {
1831 width: auto;
1834 width: auto;
1832
1835
1833 .flag_status {
1836 .flag_status {
1834 float: left;
1837 float: left;
1835 }
1838 }
1836 }
1839 }
1837 }
1840 }
1838
1841
1839
1842
1840 .creation_in_progress {
1843 .creation_in_progress {
1841 color: @grey4
1844 color: @grey4
1842 }
1845 }
1843
1846
1844 .status_box_menu {
1847 .status_box_menu {
1845 margin: 0;
1848 margin: 0;
1846 }
1849 }
1847
1850
1848 .notification-table{
1851 .notification-table{
1849 margin-bottom: @space;
1852 margin-bottom: @space;
1850 display: table;
1853 display: table;
1851 width: 100%;
1854 width: 100%;
1852
1855
1853 .container{
1856 .container{
1854 display: table-row;
1857 display: table-row;
1855
1858
1856 .notification-header{
1859 .notification-header{
1857 border-bottom: @border-thickness solid @border-default-color;
1860 border-bottom: @border-thickness solid @border-default-color;
1858 }
1861 }
1859
1862
1860 .notification-subject{
1863 .notification-subject{
1861 display: table-cell;
1864 display: table-cell;
1862 }
1865 }
1863 }
1866 }
1864 }
1867 }
1865
1868
1866 // Notifications
1869 // Notifications
1867 .notification-header{
1870 .notification-header{
1868 display: table;
1871 display: table;
1869 width: 100%;
1872 width: 100%;
1870 padding: floor(@basefontsize/2) 0;
1873 padding: floor(@basefontsize/2) 0;
1871 line-height: 1em;
1874 line-height: 1em;
1872
1875
1873 .desc, .delete-notifications, .read-notifications{
1876 .desc, .delete-notifications, .read-notifications{
1874 display: table-cell;
1877 display: table-cell;
1875 text-align: left;
1878 text-align: left;
1876 }
1879 }
1877
1880
1878 .desc{
1881 .desc{
1879 width: 1163px;
1882 width: 1163px;
1880 }
1883 }
1881
1884
1882 .delete-notifications, .read-notifications{
1885 .delete-notifications, .read-notifications{
1883 width: 35px;
1886 width: 35px;
1884 min-width: 35px; //fixes when only one button is displayed
1887 min-width: 35px; //fixes when only one button is displayed
1885 }
1888 }
1886 }
1889 }
1887
1890
1888 .notification-body {
1891 .notification-body {
1889 .markdown-block,
1892 .markdown-block,
1890 .rst-block {
1893 .rst-block {
1891 padding: @padding 0;
1894 padding: @padding 0;
1892 }
1895 }
1893
1896
1894 .notification-subject {
1897 .notification-subject {
1895 padding: @textmargin 0;
1898 padding: @textmargin 0;
1896 border-bottom: @border-thickness solid @border-default-color;
1899 border-bottom: @border-thickness solid @border-default-color;
1897 }
1900 }
1898 }
1901 }
1899
1902
1900
1903
1901 .notifications_buttons{
1904 .notifications_buttons{
1902 float: right;
1905 float: right;
1903 }
1906 }
1904
1907
1905 #notification-status{
1908 #notification-status{
1906 display: inline;
1909 display: inline;
1907 }
1910 }
1908
1911
1909 // Repositories
1912 // Repositories
1910
1913
1911 #summary.fields{
1914 #summary.fields{
1912 display: table;
1915 display: table;
1913
1916
1914 .field{
1917 .field{
1915 display: table-row;
1918 display: table-row;
1916
1919
1917 .label-summary{
1920 .label-summary{
1918 display: table-cell;
1921 display: table-cell;
1919 min-width: @label-summary-minwidth;
1922 min-width: @label-summary-minwidth;
1920 padding-top: @padding/2;
1923 padding-top: @padding/2;
1921 padding-bottom: @padding/2;
1924 padding-bottom: @padding/2;
1922 padding-right: @padding/2;
1925 padding-right: @padding/2;
1923 }
1926 }
1924
1927
1925 .input{
1928 .input{
1926 display: table-cell;
1929 display: table-cell;
1927 padding: @padding/2;
1930 padding: @padding/2;
1928
1931
1929 input{
1932 input{
1930 min-width: 29em;
1933 min-width: 29em;
1931 padding: @padding/4;
1934 padding: @padding/4;
1932 }
1935 }
1933 }
1936 }
1934 .statistics, .downloads{
1937 .statistics, .downloads{
1935 .disabled{
1938 .disabled{
1936 color: @grey4;
1939 color: @grey4;
1937 }
1940 }
1938 }
1941 }
1939 }
1942 }
1940 }
1943 }
1941
1944
1942 #summary{
1945 #summary{
1943 width: 70%;
1946 width: 70%;
1944 }
1947 }
1945
1948
1946
1949
1947 // Journal
1950 // Journal
1948 .journal.title {
1951 .journal.title {
1949 h5 {
1952 h5 {
1950 float: left;
1953 float: left;
1951 margin: 0;
1954 margin: 0;
1952 width: 70%;
1955 width: 70%;
1953 }
1956 }
1954
1957
1955 ul {
1958 ul {
1956 float: right;
1959 float: right;
1957 display: inline-block;
1960 display: inline-block;
1958 margin: 0;
1961 margin: 0;
1959 width: 30%;
1962 width: 30%;
1960 text-align: right;
1963 text-align: right;
1961
1964
1962 li {
1965 li {
1963 display: inline;
1966 display: inline;
1964 font-size: @journal-fontsize;
1967 font-size: @journal-fontsize;
1965 line-height: 1em;
1968 line-height: 1em;
1966
1969
1967 &:before { content: none; }
1970 &:before { content: none; }
1968 }
1971 }
1969 }
1972 }
1970 }
1973 }
1971
1974
1972 .filterexample {
1975 .filterexample {
1973 position: absolute;
1976 position: absolute;
1974 top: 95px;
1977 top: 95px;
1975 left: @contentpadding;
1978 left: @contentpadding;
1976 color: @rcblue;
1979 color: @rcblue;
1977 font-size: 11px;
1980 font-size: 11px;
1978 font-family: @text-regular;
1981 font-family: @text-regular;
1979 cursor: help;
1982 cursor: help;
1980
1983
1981 &:hover {
1984 &:hover {
1982 color: @rcdarkblue;
1985 color: @rcdarkblue;
1983 }
1986 }
1984
1987
1985 @media (max-width:768px) {
1988 @media (max-width:768px) {
1986 position: relative;
1989 position: relative;
1987 top: auto;
1990 top: auto;
1988 left: auto;
1991 left: auto;
1989 display: block;
1992 display: block;
1990 }
1993 }
1991 }
1994 }
1992
1995
1993
1996
1994 #journal{
1997 #journal{
1995 margin-bottom: @space;
1998 margin-bottom: @space;
1996
1999
1997 .journal_day{
2000 .journal_day{
1998 margin-bottom: @textmargin/2;
2001 margin-bottom: @textmargin/2;
1999 padding-bottom: @textmargin/2;
2002 padding-bottom: @textmargin/2;
2000 font-size: @journal-fontsize;
2003 font-size: @journal-fontsize;
2001 border-bottom: @border-thickness solid @border-default-color;
2004 border-bottom: @border-thickness solid @border-default-color;
2002 }
2005 }
2003
2006
2004 .journal_container{
2007 .journal_container{
2005 margin-bottom: @space;
2008 margin-bottom: @space;
2006
2009
2007 .journal_user{
2010 .journal_user{
2008 display: inline-block;
2011 display: inline-block;
2009 }
2012 }
2010 .journal_action_container{
2013 .journal_action_container{
2011 display: block;
2014 display: block;
2012 margin-top: @textmargin;
2015 margin-top: @textmargin;
2013
2016
2014 div{
2017 div{
2015 display: inline;
2018 display: inline;
2016 }
2019 }
2017
2020
2018 div.journal_action_params{
2021 div.journal_action_params{
2019 display: block;
2022 display: block;
2020 }
2023 }
2021
2024
2022 div.journal_repo:after{
2025 div.journal_repo:after{
2023 content: "\A";
2026 content: "\A";
2024 white-space: pre;
2027 white-space: pre;
2025 }
2028 }
2026
2029
2027 div.date{
2030 div.date{
2028 display: block;
2031 display: block;
2029 margin-bottom: @textmargin;
2032 margin-bottom: @textmargin;
2030 }
2033 }
2031 }
2034 }
2032 }
2035 }
2033 }
2036 }
2034
2037
2035 // Files
2038 // Files
2036 .edit-file-title {
2039 .edit-file-title {
2037 border-bottom: @border-thickness solid @border-default-color;
2040 border-bottom: @border-thickness solid @border-default-color;
2038
2041
2039 .breadcrumbs {
2042 .breadcrumbs {
2040 margin-bottom: 0;
2043 margin-bottom: 0;
2041 }
2044 }
2042 }
2045 }
2043
2046
2044 .edit-file-fieldset {
2047 .edit-file-fieldset {
2045 margin-top: @sidebarpadding;
2048 margin-top: @sidebarpadding;
2046
2049
2047 .fieldset {
2050 .fieldset {
2048 .left-label {
2051 .left-label {
2049 width: 13%;
2052 width: 13%;
2050 }
2053 }
2051 .right-content {
2054 .right-content {
2052 width: 87%;
2055 width: 87%;
2053 max-width: 100%;
2056 max-width: 100%;
2054 }
2057 }
2055 .filename-label {
2058 .filename-label {
2056 margin-top: 13px;
2059 margin-top: 13px;
2057 }
2060 }
2058 .commit-message-label {
2061 .commit-message-label {
2059 margin-top: 4px;
2062 margin-top: 4px;
2060 }
2063 }
2061 .file-upload-input {
2064 .file-upload-input {
2062 input {
2065 input {
2063 display: none;
2066 display: none;
2064 }
2067 }
2065 margin-top: 10px;
2068 margin-top: 10px;
2066 }
2069 }
2067 .file-upload-label {
2070 .file-upload-label {
2068 margin-top: 10px;
2071 margin-top: 10px;
2069 }
2072 }
2070 p {
2073 p {
2071 margin-top: 5px;
2074 margin-top: 5px;
2072 }
2075 }
2073
2076
2074 }
2077 }
2075 .custom-path-link {
2078 .custom-path-link {
2076 margin-left: 5px;
2079 margin-left: 5px;
2077 }
2080 }
2078 #commit {
2081 #commit {
2079 resize: vertical;
2082 resize: vertical;
2080 }
2083 }
2081 }
2084 }
2082
2085
2083 .delete-file-preview {
2086 .delete-file-preview {
2084 max-height: 250px;
2087 max-height: 250px;
2085 }
2088 }
2086
2089
2087 .new-file,
2090 .new-file,
2088 #filter_activate,
2091 #filter_activate,
2089 #filter_deactivate {
2092 #filter_deactivate {
2090 float: left;
2093 float: left;
2091 margin: 0 0 0 15px;
2094 margin: 0 0 0 15px;
2092 }
2095 }
2093
2096
2094 h3.files_location{
2097 h3.files_location{
2095 line-height: 2.4em;
2098 line-height: 2.4em;
2096 }
2099 }
2097
2100
2098 .browser-nav {
2101 .browser-nav {
2099 display: table;
2102 display: table;
2100 margin-bottom: @space;
2103 margin-bottom: @space;
2101
2104
2102
2105
2103 .info_box {
2106 .info_box {
2104 display: inline-table;
2107 display: inline-table;
2105 height: 2.5em;
2108 height: 2.5em;
2106
2109
2107 .browser-cur-rev, .info_box_elem {
2110 .browser-cur-rev, .info_box_elem {
2108 display: table-cell;
2111 display: table-cell;
2109 vertical-align: middle;
2112 vertical-align: middle;
2110 }
2113 }
2111
2114
2112 .info_box_elem {
2115 .info_box_elem {
2113 border-top: @border-thickness solid @rcblue;
2116 border-top: @border-thickness solid @rcblue;
2114 border-bottom: @border-thickness solid @rcblue;
2117 border-bottom: @border-thickness solid @rcblue;
2115
2118
2116 #at_rev, a {
2119 #at_rev, a {
2117 padding: 0.6em 0.9em;
2120 padding: 0.6em 0.9em;
2118 margin: 0;
2121 margin: 0;
2119 .box-shadow(none);
2122 .box-shadow(none);
2120 border: 0;
2123 border: 0;
2121 height: 12px;
2124 height: 12px;
2122 }
2125 }
2123
2126
2124 input#at_rev {
2127 input#at_rev {
2125 max-width: 50px;
2128 max-width: 50px;
2126 text-align: right;
2129 text-align: right;
2127 }
2130 }
2128
2131
2129 &.previous {
2132 &.previous {
2130 border: @border-thickness solid @rcblue;
2133 border: @border-thickness solid @rcblue;
2131 .disabled {
2134 .disabled {
2132 color: @grey4;
2135 color: @grey4;
2133 cursor: not-allowed;
2136 cursor: not-allowed;
2134 }
2137 }
2135 }
2138 }
2136
2139
2137 &.next {
2140 &.next {
2138 border: @border-thickness solid @rcblue;
2141 border: @border-thickness solid @rcblue;
2139 .disabled {
2142 .disabled {
2140 color: @grey4;
2143 color: @grey4;
2141 cursor: not-allowed;
2144 cursor: not-allowed;
2142 }
2145 }
2143 }
2146 }
2144 }
2147 }
2145
2148
2146 .browser-cur-rev {
2149 .browser-cur-rev {
2147
2150
2148 span{
2151 span{
2149 margin: 0;
2152 margin: 0;
2150 color: @rcblue;
2153 color: @rcblue;
2151 height: 12px;
2154 height: 12px;
2152 display: inline-block;
2155 display: inline-block;
2153 padding: 0.7em 1em ;
2156 padding: 0.7em 1em ;
2154 border: @border-thickness solid @rcblue;
2157 border: @border-thickness solid @rcblue;
2155 margin-right: @padding;
2158 margin-right: @padding;
2156 }
2159 }
2157 }
2160 }
2158 }
2161 }
2159
2162
2160 .search_activate {
2163 .search_activate {
2161 display: table-cell;
2164 display: table-cell;
2162 vertical-align: middle;
2165 vertical-align: middle;
2163
2166
2164 input, label{
2167 input, label{
2165 margin: 0;
2168 margin: 0;
2166 padding: 0;
2169 padding: 0;
2167 }
2170 }
2168
2171
2169 input{
2172 input{
2170 margin-left: @textmargin;
2173 margin-left: @textmargin;
2171 }
2174 }
2172
2175
2173 }
2176 }
2174 }
2177 }
2175
2178
2176 .browser-cur-rev{
2179 .browser-cur-rev{
2177 margin-bottom: @textmargin;
2180 margin-bottom: @textmargin;
2178 }
2181 }
2179
2182
2180 #node_filter_box_loading{
2183 #node_filter_box_loading{
2181 .info_text;
2184 .info_text;
2182 }
2185 }
2183
2186
2184 .browser-search {
2187 .browser-search {
2185 margin: -25px 0px 5px 0px;
2188 margin: -25px 0px 5px 0px;
2186 }
2189 }
2187
2190
2188 .node-filter {
2191 .node-filter {
2189 font-size: @repo-title-fontsize;
2192 font-size: @repo-title-fontsize;
2190 padding: 4px 0px 0px 0px;
2193 padding: 4px 0px 0px 0px;
2191
2194
2192 .node-filter-path {
2195 .node-filter-path {
2193 float: left;
2196 float: left;
2194 color: @grey4;
2197 color: @grey4;
2195 }
2198 }
2196 .node-filter-input {
2199 .node-filter-input {
2197 float: left;
2200 float: left;
2198 margin: -2px 0px 0px 2px;
2201 margin: -2px 0px 0px 2px;
2199 input {
2202 input {
2200 padding: 2px;
2203 padding: 2px;
2201 border: none;
2204 border: none;
2202 font-size: @repo-title-fontsize;
2205 font-size: @repo-title-fontsize;
2203 }
2206 }
2204 }
2207 }
2205 }
2208 }
2206
2209
2207
2210
2208 .browser-result{
2211 .browser-result{
2209 td a{
2212 td a{
2210 margin-left: 0.5em;
2213 margin-left: 0.5em;
2211 display: inline-block;
2214 display: inline-block;
2212
2215
2213 em{
2216 em{
2214 font-family: @text-bold;
2217 font-family: @text-bold;
2215 }
2218 }
2216 }
2219 }
2217 }
2220 }
2218
2221
2219 .browser-highlight{
2222 .browser-highlight{
2220 background-color: @grey5-alpha;
2223 background-color: @grey5-alpha;
2221 }
2224 }
2222
2225
2223
2226
2224 // Search
2227 // Search
2225
2228
2226 .search-form{
2229 .search-form{
2227 #q {
2230 #q {
2228 width: @search-form-width;
2231 width: @search-form-width;
2229 }
2232 }
2230 .fields{
2233 .fields{
2231 margin: 0 0 @space;
2234 margin: 0 0 @space;
2232 }
2235 }
2233
2236
2234 label{
2237 label{
2235 display: inline-block;
2238 display: inline-block;
2236 margin-right: @textmargin;
2239 margin-right: @textmargin;
2237 padding-top: 0.25em;
2240 padding-top: 0.25em;
2238 }
2241 }
2239
2242
2240
2243
2241 .results{
2244 .results{
2242 clear: both;
2245 clear: both;
2243 margin: 0 0 @padding;
2246 margin: 0 0 @padding;
2244 }
2247 }
2245 }
2248 }
2246
2249
2247 div.search-feedback-items {
2250 div.search-feedback-items {
2248 display: inline-block;
2251 display: inline-block;
2249 padding:0px 0px 0px 96px;
2252 padding:0px 0px 0px 96px;
2250 }
2253 }
2251
2254
2252 div.search-code-body {
2255 div.search-code-body {
2253 background-color: #ffffff; padding: 5px 0 5px 10px;
2256 background-color: #ffffff; padding: 5px 0 5px 10px;
2254 pre {
2257 pre {
2255 .match { background-color: #faffa6;}
2258 .match { background-color: #faffa6;}
2256 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2259 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2257 }
2260 }
2258 }
2261 }
2259
2262
2260 .expand_commit.search {
2263 .expand_commit.search {
2261 .show_more.open {
2264 .show_more.open {
2262 height: auto;
2265 height: auto;
2263 max-height: none;
2266 max-height: none;
2264 }
2267 }
2265 }
2268 }
2266
2269
2267 .search-results {
2270 .search-results {
2268
2271
2269 h2 {
2272 h2 {
2270 margin-bottom: 0;
2273 margin-bottom: 0;
2271 }
2274 }
2272 .codeblock {
2275 .codeblock {
2273 border: none;
2276 border: none;
2274 background: transparent;
2277 background: transparent;
2275 }
2278 }
2276
2279
2277 .codeblock-header {
2280 .codeblock-header {
2278 border: none;
2281 border: none;
2279 background: transparent;
2282 background: transparent;
2280 }
2283 }
2281
2284
2282 .code-body {
2285 .code-body {
2283 border: @border-thickness solid @border-default-color;
2286 border: @border-thickness solid @border-default-color;
2284 .border-radius(@border-radius);
2287 .border-radius(@border-radius);
2285 }
2288 }
2286
2289
2287 .td-commit {
2290 .td-commit {
2288 &:extend(pre);
2291 &:extend(pre);
2289 border-bottom: @border-thickness solid @border-default-color;
2292 border-bottom: @border-thickness solid @border-default-color;
2290 }
2293 }
2291
2294
2292 .message {
2295 .message {
2293 height: auto;
2296 height: auto;
2294 max-width: 350px;
2297 max-width: 350px;
2295 white-space: normal;
2298 white-space: normal;
2296 text-overflow: initial;
2299 text-overflow: initial;
2297 overflow: visible;
2300 overflow: visible;
2298
2301
2299 .match { background-color: #faffa6;}
2302 .match { background-color: #faffa6;}
2300 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2303 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2301 }
2304 }
2302
2305
2303 }
2306 }
2304
2307
2305 table.rctable td.td-search-results div {
2308 table.rctable td.td-search-results div {
2306 max-width: 100%;
2309 max-width: 100%;
2307 }
2310 }
2308
2311
2309 #tip-box, .tip-box{
2312 #tip-box, .tip-box{
2310 padding: @menupadding/2;
2313 padding: @menupadding/2;
2311 display: block;
2314 display: block;
2312 border: @border-thickness solid @border-highlight-color;
2315 border: @border-thickness solid @border-highlight-color;
2313 .border-radius(@border-radius);
2316 .border-radius(@border-radius);
2314 background-color: white;
2317 background-color: white;
2315 z-index: 99;
2318 z-index: 99;
2316 white-space: pre-wrap;
2319 white-space: pre-wrap;
2317 }
2320 }
2318
2321
2319 #linktt {
2322 #linktt {
2320 width: 79px;
2323 width: 79px;
2321 }
2324 }
2322
2325
2323 #help_kb .modal-content{
2326 #help_kb .modal-content{
2324 max-width: 750px;
2327 max-width: 750px;
2325 margin: 10% auto;
2328 margin: 10% auto;
2326
2329
2327 table{
2330 table{
2328 td,th{
2331 td,th{
2329 border-bottom: none;
2332 border-bottom: none;
2330 line-height: 2.5em;
2333 line-height: 2.5em;
2331 }
2334 }
2332 th{
2335 th{
2333 padding-bottom: @textmargin/2;
2336 padding-bottom: @textmargin/2;
2334 }
2337 }
2335 td.keys{
2338 td.keys{
2336 text-align: center;
2339 text-align: center;
2337 }
2340 }
2338 }
2341 }
2339
2342
2340 .block-left{
2343 .block-left{
2341 width: 45%;
2344 width: 45%;
2342 margin-right: 5%;
2345 margin-right: 5%;
2343 }
2346 }
2344 .modal-footer{
2347 .modal-footer{
2345 clear: both;
2348 clear: both;
2346 }
2349 }
2347 .key.tag{
2350 .key.tag{
2348 padding: 0.5em;
2351 padding: 0.5em;
2349 background-color: @rcblue;
2352 background-color: @rcblue;
2350 color: white;
2353 color: white;
2351 border-color: @rcblue;
2354 border-color: @rcblue;
2352 .box-shadow(none);
2355 .box-shadow(none);
2353 }
2356 }
2354 }
2357 }
2355
2358
2356
2359
2357
2360
2358 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2361 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2359
2362
2360 @import 'statistics-graph';
2363 @import 'statistics-graph';
2361 @import 'tables';
2364 @import 'tables';
2362 @import 'forms';
2365 @import 'forms';
2363 @import 'diff';
2366 @import 'diff';
2364 @import 'summary';
2367 @import 'summary';
2365 @import 'navigation';
2368 @import 'navigation';
2366
2369
2367 //--- SHOW/HIDE SECTIONS --//
2370 //--- SHOW/HIDE SECTIONS --//
2368
2371
2369 .btn-collapse {
2372 .btn-collapse {
2370 float: right;
2373 float: right;
2371 text-align: right;
2374 text-align: right;
2372 font-family: @text-light;
2375 font-family: @text-light;
2373 font-size: @basefontsize;
2376 font-size: @basefontsize;
2374 cursor: pointer;
2377 cursor: pointer;
2375 border: none;
2378 border: none;
2376 color: @rcblue;
2379 color: @rcblue;
2377 }
2380 }
2378
2381
2379 table.rctable,
2382 table.rctable,
2380 table.dataTable {
2383 table.dataTable {
2381 .btn-collapse {
2384 .btn-collapse {
2382 float: right;
2385 float: right;
2383 text-align: right;
2386 text-align: right;
2384 }
2387 }
2385 }
2388 }
2386
2389
2387
2390
2388 // TODO: johbo: Fix for IE10, this avoids that we see a border
2391 // TODO: johbo: Fix for IE10, this avoids that we see a border
2389 // and padding around checkboxes and radio boxes. Move to the right place,
2392 // and padding around checkboxes and radio boxes. Move to the right place,
2390 // or better: Remove this once we did the form refactoring.
2393 // or better: Remove this once we did the form refactoring.
2391 input[type=checkbox],
2394 input[type=checkbox],
2392 input[type=radio] {
2395 input[type=radio] {
2393 padding: 0;
2396 padding: 0;
2394 border: none;
2397 border: none;
2395 }
2398 }
2396
2399
2397 .toggle-ajax-spinner{
2400 .toggle-ajax-spinner{
2398 height: 16px;
2401 height: 16px;
2399 width: 16px;
2402 width: 16px;
2400 }
2403 }
@@ -1,254 +1,256 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
5 %if c.repo:
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 %elif c.repo_group:
7 %elif c.repo_group:
8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
13 %else:
13 %else:
14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
15 &raquo;
15 &raquo;
16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
17 %endif
17 %endif
18 %if c.current_IntegrationType:
18 %if c.current_IntegrationType:
19 &raquo;
19 &raquo;
20 %if c.repo:
20 %if c.repo:
21 ${h.link_to(_('Integrations'),
21 ${h.link_to(_('Integrations'),
22 request.route_path(route_name='repo_integrations_home',
22 request.route_path(route_name='repo_integrations_home',
23 repo_name=c.repo.repo_name))}
23 repo_name=c.repo.repo_name))}
24 %elif c.repo_group:
24 %elif c.repo_group:
25 ${h.link_to(_('Integrations'),
25 ${h.link_to(_('Integrations'),
26 request.route_path(route_name='repo_group_integrations_home',
26 request.route_path(route_name='repo_group_integrations_home',
27 repo_group_name=c.repo_group.group_name))}
27 repo_group_name=c.repo_group.group_name))}
28 %else:
28 %else:
29 ${h.link_to(_('Integrations'),
29 ${h.link_to(_('Integrations'),
30 request.route_path(route_name='global_integrations_home'))}
30 request.route_path(route_name='global_integrations_home'))}
31 %endif
31 %endif
32 &raquo;
32 &raquo;
33 ${c.current_IntegrationType.display_name}
33 ${c.current_IntegrationType.display_name}
34 %else:
34 %else:
35 &raquo;
35 &raquo;
36 ${_('Integrations')}
36 ${_('Integrations')}
37 %endif
37 %endif
38 </%def>
38 </%def>
39
39
40 <div class="panel panel-default">
40 <div class="panel panel-default">
41 <div class="panel-heading">
41 <div class="panel-heading">
42 <h3 class="panel-title">
42 <h3 class="panel-title">
43 %if c.repo:
43 %if c.repo:
44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
45 %elif c.repo_group:
45 %elif c.repo_group:
46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
47 %else:
47 %else:
48 ${_('Current Integrations')}
48 ${_('Current Integrations')}
49 %endif
49 %endif
50 </h3>
50 </h3>
51 </div>
51 </div>
52 <div class="panel-body">
52 <div class="panel-body">
53 <%
53 <%
54 if c.repo:
54 if c.repo:
55 home_url = request.route_path('repo_integrations_home',
55 home_url = request.route_path('repo_integrations_home',
56 repo_name=c.repo.repo_name)
56 repo_name=c.repo.repo_name)
57 elif c.repo_group:
57 elif c.repo_group:
58 home_url = request.route_path('repo_group_integrations_home',
58 home_url = request.route_path('repo_group_integrations_home',
59 repo_group_name=c.repo_group.group_name)
59 repo_group_name=c.repo_group.group_name)
60 else:
60 else:
61 home_url = request.route_path('global_integrations_home')
61 home_url = request.route_path('global_integrations_home')
62 %>
62 %>
63
63
64 <a href="${home_url}" class="btn ${not c.current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
64 <a href="${home_url}" class="btn ${not c.current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
65
65
66 %for integration_key, IntegrationType in c.available_integrations.items():
66 %for integration_key, IntegrationType in c.available_integrations.items():
67 % if not IntegrationType.is_dummy:
67 <%
68 <%
68 if c.repo:
69 if c.repo:
69 list_url = request.route_path('repo_integrations_list',
70 list_url = request.route_path('repo_integrations_list',
70 repo_name=c.repo.repo_name,
71 repo_name=c.repo.repo_name,
71 integration=integration_key)
72 integration=integration_key)
72 elif c.repo_group:
73 elif c.repo_group:
73 list_url = request.route_path('repo_group_integrations_list',
74 list_url = request.route_path('repo_group_integrations_list',
74 repo_group_name=c.repo_group.group_name,
75 repo_group_name=c.repo_group.group_name,
75 integration=integration_key)
76 integration=integration_key)
76 else:
77 else:
77 list_url = request.route_path('global_integrations_list',
78 list_url = request.route_path('global_integrations_list',
78 integration=integration_key)
79 integration=integration_key)
79 %>
80 %>
80 <a href="${list_url}"
81 <a href="${list_url}"
81 class="btn ${c.current_IntegrationType and integration_key == c.current_IntegrationType.key and 'btn-primary' or ''}">
82 class="btn ${c.current_IntegrationType and integration_key == c.current_IntegrationType.key and 'btn-primary' or ''}">
82 ${IntegrationType.display_name}
83 ${IntegrationType.display_name}
83 </a>
84 </a>
85 % endif
84 %endfor
86 %endfor
85
87
86 <%
88 <%
87 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
89 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
88
90
89 if c.repo:
91 if c.repo:
90 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
92 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
91 elif c.repo_group:
93 elif c.repo_group:
92 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
94 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
93 else:
95 else:
94 create_url = h.route_path('global_integrations_new')
96 create_url = h.route_path('global_integrations_new')
95 %>
97 %>
96 <p class="pull-right">
98 <p class="pull-right">
97 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
99 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
98 </p>
100 </p>
99
101
100 <table class="rctable integrations">
102 <table class="rctable integrations">
101 <thead>
103 <thead>
102 <tr>
104 <tr>
103 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
105 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
104 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
106 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
105 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
107 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
106 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
108 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
107 <th>${_('Actions')}</th>
109 <th>${_('Actions')}</th>
108 <th></th>
110 <th></th>
109 </tr>
111 </tr>
110 </thead>
112 </thead>
111 <tbody>
113 <tbody>
112 %if not c.integrations_list:
114 %if not c.integrations_list:
113 <tr>
115 <tr>
114 <td colspan="7">
116 <td colspan="7">
115
117
116 %if c.repo:
118 %if c.repo:
117 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
119 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
118 %elif c.repo_group:
120 %elif c.repo_group:
119 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
121 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
120 %else:
122 %else:
121 ${_('No {type} integrations exist yet.').format(type=integration_type)}
123 ${_('No {type} integrations exist yet.').format(type=integration_type)}
122 %endif
124 %endif
123
125
124 %if c.current_IntegrationType:
126 %if c.current_IntegrationType:
125 <%
127 <%
126 if c.repo:
128 if c.repo:
127 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
129 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
128 elif c.repo_group:
130 elif c.repo_group:
129 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
131 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
130 else:
132 else:
131 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
133 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
132 %>
134 %>
133 %endif
135 %endif
134
136
135 <a href="${create_url}">${_(u'Create one')}</a>
137 <a href="${create_url}">${_(u'Create one')}</a>
136 </td>
138 </td>
137 </tr>
139 </tr>
138 %endif
140 %endif
139 %for IntegrationType, integration in c.integrations_list:
141 %for IntegrationType, integration in c.integrations_list:
140 <tr id="integration_${integration.integration_id}">
142 <tr id="integration_${integration.integration_id}">
141 <td class="td-enabled">
143 <td class="td-enabled">
142 %if integration.enabled:
144 %if integration.enabled:
143 <div class="flag_status approved pull-left"></div>
145 <div class="flag_status approved pull-left"></div>
144 %else:
146 %else:
145 <div class="flag_status rejected pull-left"></div>
147 <div class="flag_status rejected pull-left"></div>
146 %endif
148 %endif
147 </td>
149 </td>
148 <td class="td-description">
150 <td class="td-description">
149 ${integration.name}
151 ${integration.name}
150 </td>
152 </td>
151 <td class="td-icon">
153 <td class="td-icon">
152 %if integration.integration_type in c.available_integrations:
154 %if integration.integration_type in c.available_integrations:
153 <div class="integration-icon">
155 <div class="integration-icon">
154 ${c.available_integrations[integration.integration_type].icon|n}
156 ${c.available_integrations[integration.integration_type].icon|n}
155 </div>
157 </div>
156 %else:
158 %else:
157 ?
159 ?
158 %endif
160 %endif
159 </td>
161 </td>
160 <td class="td-type">
162 <td class="td-type">
161 ${integration.integration_type}
163 ${integration.integration_type}
162 </td>
164 </td>
163 <td class="td-scope">
165 <td class="td-scope">
164 %if integration.repo:
166 %if integration.repo:
165 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
167 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
166 ${_('repo')}:${integration.repo.repo_name}
168 ${_('repo')}:${integration.repo.repo_name}
167 </a>
169 </a>
168 %elif integration.repo_group:
170 %elif integration.repo_group:
169 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
171 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
170 ${_('repogroup')}:${integration.repo_group.group_name}
172 ${_('repogroup')}:${integration.repo_group.group_name}
171 %if integration.child_repos_only:
173 %if integration.child_repos_only:
172 ${_('child repos only')}
174 ${_('child repos only')}
173 %else:
175 %else:
174 ${_('cascade to all')}
176 ${_('cascade to all')}
175 %endif
177 %endif
176 </a>
178 </a>
177 %else:
179 %else:
178 %if integration.child_repos_only:
180 %if integration.child_repos_only:
179 ${_('top level repos only')}
181 ${_('top level repos only')}
180 %else:
182 %else:
181 ${_('global')}
183 ${_('global')}
182 %endif
184 %endif
183 </td>
185 </td>
184 %endif
186 %endif
185 <td class="td-action">
187 <td class="td-action">
186 %if not IntegrationType:
188 %if not IntegrationType:
187 ${_('unknown integration')}
189 ${_('unknown integration')}
188 %else:
190 %else:
189 <%
191 <%
190 if c.repo:
192 if c.repo:
191 edit_url = request.route_path('repo_integrations_edit',
193 edit_url = request.route_path('repo_integrations_edit',
192 repo_name=c.repo.repo_name,
194 repo_name=c.repo.repo_name,
193 integration=integration.integration_type,
195 integration=integration.integration_type,
194 integration_id=integration.integration_id)
196 integration_id=integration.integration_id)
195 elif c.repo_group:
197 elif c.repo_group:
196 edit_url = request.route_path('repo_group_integrations_edit',
198 edit_url = request.route_path('repo_group_integrations_edit',
197 repo_group_name=c.repo_group.group_name,
199 repo_group_name=c.repo_group.group_name,
198 integration=integration.integration_type,
200 integration=integration.integration_type,
199 integration_id=integration.integration_id)
201 integration_id=integration.integration_id)
200 else:
202 else:
201 edit_url = request.route_path('global_integrations_edit',
203 edit_url = request.route_path('global_integrations_edit',
202 integration=integration.integration_type,
204 integration=integration.integration_type,
203 integration_id=integration.integration_id)
205 integration_id=integration.integration_id)
204 %>
206 %>
205 <div class="grid_edit">
207 <div class="grid_edit">
206 <a href="${edit_url}">${_('Edit')}</a>
208 <a href="${edit_url}">${_('Edit')}</a>
207 </div>
209 </div>
208 <div class="grid_delete">
210 <div class="grid_delete">
209 <a href="${edit_url}"
211 <a href="${edit_url}"
210 class="btn btn-link btn-danger delete_integration_entry"
212 class="btn btn-link btn-danger delete_integration_entry"
211 data-desc="${integration.name}"
213 data-desc="${integration.name}"
212 data-uid="${integration.integration_id}">
214 data-uid="${integration.integration_id}">
213 ${_('Delete')}
215 ${_('Delete')}
214 </a>
216 </a>
215 </div>
217 </div>
216 %endif
218 %endif
217 </td>
219 </td>
218 </tr>
220 </tr>
219 %endfor
221 %endfor
220 <tr id="last-row"></tr>
222 <tr id="last-row"></tr>
221 </tbody>
223 </tbody>
222 </table>
224 </table>
223 <div class="integrations-paginator">
225 <div class="integrations-paginator">
224 <div class="pagination-wh pagination-left">
226 <div class="pagination-wh pagination-left">
225 ${c.integrations_list.pager('$link_previous ~2~ $link_next')}
227 ${c.integrations_list.pager('$link_previous ~2~ $link_next')}
226 </div>
228 </div>
227 </div>
229 </div>
228 </div>
230 </div>
229 </div>
231 </div>
230 <script type="text/javascript">
232 <script type="text/javascript">
231 var delete_integration = function(entry) {
233 var delete_integration = function(entry) {
232 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
234 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
233 var request = $.ajax({
235 var request = $.ajax({
234 type: "POST",
236 type: "POST",
235 url: $(entry).attr('href'),
237 url: $(entry).attr('href'),
236 data: {
238 data: {
237 'delete': 'delete',
239 'delete': 'delete',
238 'csrf_token': CSRF_TOKEN
240 'csrf_token': CSRF_TOKEN
239 },
241 },
240 success: function(){
242 success: function(){
241 location.reload();
243 location.reload();
242 },
244 },
243 error: function(data, textStatus, errorThrown){
245 error: function(data, textStatus, errorThrown){
244 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
246 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
245 }
247 }
246 });
248 });
247 };
249 };
248 };
250 };
249
251
250 $('.delete_integration_entry').on('click', function(e){
252 $('.delete_integration_entry').on('click', function(e){
251 e.preventDefault();
253 e.preventDefault();
252 delete_integration(this);
254 delete_integration(this);
253 });
255 });
254 </script> No newline at end of file
256 </script>
@@ -1,66 +1,68 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="widgets" file="/widgets.mako"/>
3 <%namespace name="widgets" file="/widgets.mako"/>
4
4
5 <%def name="breadcrumbs_links()">
5 <%def name="breadcrumbs_links()">
6 %if c.repo:
6 %if c.repo:
7 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
8 &raquo;
8 &raquo;
9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
10 %elif c.repo_group:
10 %elif c.repo_group:
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 &raquo;
12 &raquo;
13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
14 &raquo;
14 &raquo;
15 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
15 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
16 &raquo;
16 &raquo;
17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
18 %else:
18 %else:
19 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
19 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
20 &raquo;
20 &raquo;
21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
22 &raquo;
22 &raquo;
23 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
23 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
24 %endif
24 %endif
25 &raquo;
25 &raquo;
26 ${_('Create new integration')}
26 ${_('Create new integration')}
27 </%def>
27 </%def>
28 <%widgets:panel class_='integrations'>
28 <%widgets:panel class_='integrations'>
29 <%def name="title()">
29 <%def name="title()">
30 %if c.repo:
30 %if c.repo:
31 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
31 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
32 %elif c.repo_group:
32 %elif c.repo_group:
33 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
33 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
34 %else:
34 %else:
35 ${_('Create New Global Integration')}
35 ${_('Create New Global Integration')}
36 %endif
36 %endif
37 </%def>
37 </%def>
38
38
39 %for integration, IntegrationType in c.available_integrations.items():
39 %for integration, IntegrationObject in c.available_integrations.items():
40 <%
40 <%
41 if c.repo:
41 if c.repo:
42 create_url = request.route_path('repo_integrations_create',
42 create_url = request.route_path('repo_integrations_create',
43 repo_name=c.repo.repo_name,
43 repo_name=c.repo.repo_name,
44 integration=integration)
44 integration=integration)
45 elif c.repo_group:
45 elif c.repo_group:
46 create_url = request.route_path('repo_group_integrations_create',
46 create_url = request.route_path('repo_group_integrations_create',
47 repo_group_name=c.repo_group.group_name,
47 repo_group_name=c.repo_group.group_name,
48 integration=integration)
48 integration=integration)
49 else:
49 else:
50 create_url = request.route_path('global_integrations_create',
50 create_url = request.route_path('global_integrations_create',
51 integration=integration)
51 integration=integration)
52 if IntegrationObject.is_dummy:
53 create_url = request.current_route_path()
52 %>
54 %>
53 <a href="${create_url}" class="integration-box">
55 <a href="${create_url}" class="integration-box ${'dummy-integration' if IntegrationObject.is_dummy else ''}">
54 <%widgets:panel>
56 <%widgets:panel>
55 <h2>
57 <h2>
56 <div class="integration-icon">
58 <div class="integration-icon">
57 ${IntegrationType.icon|n}
59 ${IntegrationObject.icon|n}
58 </div>
60 </div>
59 ${IntegrationType.display_name}
61 ${IntegrationObject.display_name}
60 </h2>
62 </h2>
61 ${IntegrationType.description or _('No description available')}
63 ${IntegrationObject.description or _('No description available')}
62 </%widgets:panel>
64 </%widgets:panel>
63 </a>
65 </a>
64 %endfor
66 %endfor
65 <div style="clear:both"></div>
67 <div style="clear:both"></div>
66 </%widgets:panel>
68 </%widgets:panel>
General Comments 0
You need to be logged in to leave comments. Login now