##// END OF EJS Templates
notifications: support real-time notifications with websockets via channelstream
ergo -
r526:1b57d2ee default
parent child Browse files
Show More
@@ -0,0 +1,79 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import os
22
23 from pyramid.settings import asbool
24
25 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.lib.ext_json import json
27
28
29 def url_gen(request):
30 urls = {
31 'connect': request.route_url('channelstream_connect'),
32 'subscribe': request.route_url('channelstream_subscribe')
33 }
34 return json.dumps(urls)
35
36
37 PLUGIN_DEFINITION = {
38 'name': 'channelstream',
39 'config': {
40 'javascript': [],
41 'css': [],
42 'template_hooks': {
43 'plugin_init_template': 'rhodecode:templates/channelstream/plugin_init.html'
44 },
45 'url_gen': url_gen,
46 'static': None,
47 'enabled': False,
48 'server': '',
49 'secret': ''
50 }
51 }
52
53
54 def includeme(config):
55 settings = config.registry.settings
56 PLUGIN_DEFINITION['config']['enabled'] = asbool(
57 settings.get('channelstream.enabled'))
58 PLUGIN_DEFINITION['config']['server'] = settings.get(
59 'channelstream.server', '')
60 PLUGIN_DEFINITION['config']['secret'] = settings.get(
61 'channelstream.secret', '')
62 PLUGIN_DEFINITION['config']['history.location'] = settings.get(
63 'channelstream.history.location', '')
64 config.register_rhodecode_plugin(
65 PLUGIN_DEFINITION['name'],
66 PLUGIN_DEFINITION['config']
67 )
68 # create plugin history location
69 history_dir = PLUGIN_DEFINITION['config']['history.location']
70 if history_dir and not os.path.exists(history_dir):
71 os.makedirs(history_dir, 0750)
72
73 config.add_route(
74 name='channelstream_connect',
75 pattern=ADMIN_PREFIX + '/channelstream/connect')
76 config.add_route(
77 name='channelstream_subscribe',
78 pattern=ADMIN_PREFIX + '/channelstream/subscribe')
79 config.scan('rhodecode.channelstream')
@@ -0,0 +1,177 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Channel Stream controller for rhodecode
23
24 :created_on: Oct 10, 2015
25 :author: marcinl
26 :copyright: (c) 2013-2015 RhodeCode GmbH.
27 :license: Commercial License, see LICENSE for more details.
28 """
29
30 import logging
31 import uuid
32
33 from pylons import tmpl_context as c
34 from pyramid.settings import asbool
35 from pyramid.view import view_config
36 from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPBadGateway
37
38 from rhodecode.lib.channelstream import (
39 channelstream_request,
40 ChannelstreamConnectionException,
41 ChannelstreamPermissionException,
42 check_channel_permissions,
43 get_connection_validators,
44 get_user_data,
45 parse_channels_info,
46 update_history_from_logs,
47 STATE_PUBLIC_KEYS)
48 from rhodecode.lib.auth import NotAnonymous
49 from rhodecode.lib.utils2 import str2bool
50
51 log = logging.getLogger(__name__)
52
53
54 class ChannelstreamView(object):
55 def __init__(self, context, request):
56 self.context = context
57 self.request = request
58
59 # Some of the decorators rely on this attribute to be present
60 # on the class of the decorated method.
61 self._rhodecode_user = request.user
62 registry = request.registry
63 self.channelstream_config = registry.rhodecode_plugins['channelstream']
64 if not self.channelstream_config.get('enabled'):
65 log.exception('Channelstream plugin is disabled')
66 raise HTTPBadRequest()
67
68 @NotAnonymous()
69 @view_config(route_name='channelstream_connect', renderer='json')
70 def connect(self):
71 """ handle authorization of users trying to connect """
72 try:
73 json_body = self.request.json_body
74 except Exception:
75 log.exception('Failed to decode json from request')
76 raise HTTPBadRequest()
77 try:
78 channels = check_channel_permissions(
79 json_body.get('channels'),
80 get_connection_validators(self.request.registry))
81 except ChannelstreamPermissionException:
82 log.error('Incorrect permissions for requested channels')
83 raise HTTPForbidden()
84
85 user = c.rhodecode_user
86 if user.user_id:
87 user_data = get_user_data(user.user_id)
88 else:
89 user_data = {
90 'id': None,
91 'username': None,
92 'first_name': None,
93 'last_name': None,
94 'icon_link': None,
95 'display_name': None,
96 'display_link': None,
97 }
98 payload = {
99 'username': user.username,
100 'user_state': user_data,
101 'conn_id': str(uuid.uuid4()),
102 'channels': channels,
103 'channel_configs': {},
104 'state_public_keys': STATE_PUBLIC_KEYS,
105 'info': {
106 'exclude_channels': ['broadcast']
107 }
108 }
109 filtered_channels = [channel for channel in channels
110 if channel != 'broadcast']
111 for channel in filtered_channels:
112 payload['channel_configs'][channel] = {
113 'notify_presence': True,
114 'history_size': 100,
115 'store_history': True,
116 'broadcast_presence_with_user_lists': True
117 }
118 # connect user to server
119 try:
120 connect_result = channelstream_request(self.channelstream_config,
121 payload, '/connect')
122 except ChannelstreamConnectionException:
123 log.exception('Channelstream service is down')
124 return HTTPBadGateway()
125
126 connect_result['channels'] = channels
127 connect_result['channels_info'] = parse_channels_info(
128 connect_result['channels_info'],
129 include_channel_info=filtered_channels)
130 update_history_from_logs(self.channelstream_config,
131 filtered_channels, connect_result)
132 return connect_result
133
134 @NotAnonymous()
135 @view_config(route_name='channelstream_subscribe', renderer='json')
136 def subscribe(self):
137 """ can be used to subscribe specific connection to other channels """
138 try:
139 json_body = self.request.json_body
140 except Exception:
141 log.exception('Failed to decode json from request')
142 raise HTTPBadRequest()
143 try:
144 channels = check_channel_permissions(
145 json_body.get('channels'),
146 get_connection_validators(self.request.registry))
147 except ChannelstreamPermissionException:
148 log.error('Incorrect permissions for requested channels')
149 raise HTTPForbidden()
150 payload = {'conn_id': json_body.get('conn_id', ''),
151 'channels': channels,
152 'channel_configs': {},
153 'info': {
154 'exclude_channels': ['broadcast']}
155 }
156 filtered_channels = [chan for chan in channels if chan != 'broadcast']
157 for channel in filtered_channels:
158 payload['channel_configs'][channel] = {
159 'notify_presence': True,
160 'history_size': 100,
161 'store_history': True,
162 'broadcast_presence_with_user_lists': True
163 }
164 try:
165 connect_result = channelstream_request(
166 self.channelstream_config, payload, '/subscribe')
167 except ChannelstreamConnectionException:
168 log.exception('Channelstream service is down')
169 return HTTPBadGateway()
170 # include_channel_info will limit history only to new channel
171 # to not overwrite histories on other channels in client
172 connect_result['channels_info'] = parse_channels_info(
173 connect_result['channels_info'],
174 include_channel_info=filtered_channels)
175 update_history_from_logs(self.channelstream_config,
176 filtered_channels, connect_result)
177 return connect_result
@@ -0,0 +1,219 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import os
23
24 import itsdangerous
25 import requests
26
27 from dogpile.core import ReadWriteMutex
28
29 import rhodecode.lib.helpers as h
30
31 from rhodecode.lib.auth import HasRepoPermissionAny
32 from rhodecode.lib.ext_json import json
33 from rhodecode.model.db import User
34
35 log = logging.getLogger(__name__)
36
37 LOCK = ReadWriteMutex()
38
39 STATE_PUBLIC_KEYS = ['id', 'username', 'first_name', 'last_name',
40 'icon_link', 'display_name', 'display_link']
41
42
43 class ChannelstreamException(Exception):
44 pass
45
46
47 class ChannelstreamConnectionException(Exception):
48 pass
49
50
51 class ChannelstreamPermissionException(Exception):
52 pass
53
54
55 def channelstream_request(config, payload, endpoint, raise_exc=True):
56 signer = itsdangerous.TimestampSigner(config['secret'])
57 sig_for_server = signer.sign(endpoint)
58 secret_headers = {'x-channelstream-secret': sig_for_server,
59 'x-channelstream-endpoint': endpoint,
60 'Content-Type': 'application/json'}
61 req_url = 'http://{}{}'.format(config['server'], endpoint)
62 response = None
63 try:
64 response = requests.post(req_url, data=json.dumps(payload),
65 headers=secret_headers).json()
66 except requests.ConnectionError:
67 log.exception('ConnectionError happened')
68 if raise_exc:
69 raise ChannelstreamConnectionException()
70 except Exception:
71 log.exception('Exception related to channelstream happened')
72 if raise_exc:
73 raise ChannelstreamConnectionException()
74 return response
75
76
77 def get_user_data(user_id):
78 user = User.get(user_id)
79 return {
80 'id': user.user_id,
81 'username': user.username,
82 'first_name': user.name,
83 'last_name': user.lastname,
84 'icon_link': h.gravatar_url(user.email, 14),
85 'display_name': h.person(user, 'username_or_name_or_email'),
86 'display_link': h.link_to_user(user),
87 }
88
89
90 def broadcast_validator(channel_name):
91 """ checks if user can access the broadcast channel """
92 if channel_name == 'broadcast':
93 return True
94
95
96 def repo_validator(channel_name):
97 """ checks if user can access the broadcast channel """
98 channel_prefix = '/repo$'
99 if channel_name.startswith(channel_prefix):
100 elements = channel_name[len(channel_prefix):].split('$')
101 repo_name = elements[0]
102 can_access = HasRepoPermissionAny(
103 'repository.read',
104 'repository.write',
105 'repository.admin')(repo_name)
106 log.debug('permission check for {} channel '
107 'resulted in {}'.format(repo_name, can_access))
108 if can_access:
109 return True
110 return False
111
112
113 def check_channel_permissions(channels, plugin_validators, should_raise=True):
114 valid_channels = []
115
116 validators = [broadcast_validator, repo_validator]
117 if plugin_validators:
118 validators.extend(plugin_validators)
119 for channel_name in channels:
120 is_valid = False
121 for validator in validators:
122 if validator(channel_name):
123 is_valid = True
124 break
125 if is_valid:
126 valid_channels.append(channel_name)
127 else:
128 if should_raise:
129 raise ChannelstreamPermissionException()
130 return valid_channels
131
132
133 def get_channels_info(self, channels):
134 payload = {'channels': channels}
135 # gather persistence info
136 return channelstream_request(self._config(), payload, '/info')
137
138
139 def parse_channels_info(info_result, include_channel_info=None):
140 """
141 Returns data that contains only secure information that can be
142 presented to clients
143 """
144 include_channel_info = include_channel_info or []
145
146 user_state_dict = {}
147 for userinfo in info_result['users']:
148 user_state_dict[userinfo['user']] = {
149 k: v for k, v in userinfo['state'].items()
150 if k in STATE_PUBLIC_KEYS
151 }
152
153 channels_info = {}
154
155 for c_name, c_info in info_result['channels'].items():
156 if c_name not in include_channel_info:
157 continue
158 connected_list = []
159 for userinfo in c_info['users']:
160 connected_list.append({
161 'user': userinfo['user'],
162 'state': user_state_dict[userinfo['user']]
163 })
164 channels_info[c_name] = {'users': connected_list,
165 'history': c_info['history']}
166
167 return channels_info
168
169
170 def log_filepath(history_location, channel_name):
171 filename = '{}.log'.format(channel_name.encode('hex'))
172 filepath = os.path.join(history_location, filename)
173 return filepath
174
175
176 def read_history(history_location, channel_name):
177 filepath = log_filepath(history_location, channel_name)
178 if not os.path.exists(filepath):
179 return []
180 history_lines_limit = -100
181 history = []
182 with open(filepath, 'rb') as f:
183 for line in f.readlines()[history_lines_limit:]:
184 try:
185 history.append(json.loads(line))
186 except Exception:
187 log.exception('Failed to load history')
188 return history
189
190
191 def update_history_from_logs(config, channels, payload):
192 history_location = config.get('history.location')
193 for channel in channels:
194 history = read_history(history_location, channel)
195 payload['channels_info'][channel]['history'] = history
196
197
198 def write_history(config, message):
199 """ writes a messge to a base64encoded filename """
200 history_location = config.get('history.location')
201 if not os.path.exists(history_location):
202 return
203 try:
204 LOCK.acquire_write_lock()
205 filepath = log_filepath(history_location, message['channel'])
206 with open(filepath, 'ab') as f:
207 json.dump(message, f)
208 f.write('\n')
209 finally:
210 LOCK.release_write_lock()
211
212
213 def get_connection_validators(registry):
214 validators = []
215 for k, config in registry.rhodecode_plugins.iteritems():
216 validator = config.get('channelstream', {}).get('connect_validator')
217 if validator:
218 validators.append(validator)
219 return validators
@@ -0,0 +1,268 b''
1 // Mix-ins
2 .borderRadius(@radius) {
3 -moz-border-radius: @radius;
4 -webkit-border-radius: @radius;
5 border-radius: @radius;
6 }
7
8 .boxShadow(@boxShadow) {
9 -moz-box-shadow: @boxShadow;
10 -webkit-box-shadow: @boxShadow;
11 box-shadow: @boxShadow;
12 }
13
14 .opacity(@opacity) {
15 @opacityPercent: @opacity * 100;
16 opacity: @opacity;
17 -ms-filter: ~"progid:DXImageTransform.Microsoft.Alpha(Opacity=@{opacityPercent})";
18 filter: ~"alpha(opacity=@{opacityPercent})";
19 }
20
21 .wordWrap(@wordWrap: break-word) {
22 -ms-word-wrap: @wordWrap;
23 word-wrap: @wordWrap;
24 }
25
26 // Variables
27 @black: #000000;
28 @grey: #999999;
29 @light-grey: #CCCCCC;
30 @white: #FFFFFF;
31 @near-black: #030303;
32 @green: #51A351;
33 @red: #BD362F;
34 @blue: #2F96B4;
35 @orange: #F89406;
36 @default-container-opacity: .8;
37
38 // Styles
39 .toast-title {
40 font-weight: bold;
41 }
42
43 .toast-message {
44 .wordWrap();
45
46 a,
47 label {
48 color: @near-black;
49 }
50
51 a:hover {
52 color: @light-grey;
53 text-decoration: none;
54 }
55 }
56
57 .toast-close-button {
58 position: relative;
59 right: -0.3em;
60 top: -0.3em;
61 float: right;
62 font-size: 20px;
63 font-weight: bold;
64 color: @black;
65 -webkit-text-shadow: 0 1px 0 rgba(255,255,255,1);
66 text-shadow: 0 1px 0 rgba(255,255,255,1);
67 .opacity(0.8);
68
69 &:hover,
70 &:focus {
71 color: @black;
72 text-decoration: none;
73 cursor: pointer;
74 .opacity(0.4);
75 }
76 }
77
78 /*Additional properties for button version
79 iOS requires the button element instead of an anchor tag.
80 If you want the anchor version, it requires `href="#"`.*/
81 button.toast-close-button {
82 padding: 0;
83 cursor: pointer;
84 background: transparent;
85 border: 0;
86 -webkit-appearance: none;
87 }
88
89 //#endregion
90
91 .toast-top-center {
92 top: 0;
93 right: 0;
94 width: 100%;
95 }
96
97 .toast-bottom-center {
98 bottom: 0;
99 right: 0;
100 width: 100%;
101 }
102
103 .toast-top-full-width {
104 top: 0;
105 right: 0;
106 width: 100%;
107 }
108
109 .toast-bottom-full-width {
110 bottom: 0;
111 right: 0;
112 width: 100%;
113 }
114
115 .toast-top-left {
116 top: 12px;
117 left: 12px;
118 }
119
120 .toast-top-right {
121 top: 12px;
122 right: 12px;
123 }
124
125 .toast-bottom-right {
126 right: 12px;
127 bottom: 12px;
128 }
129
130 .toast-bottom-left {
131 bottom: 12px;
132 left: 12px;
133 }
134
135 #toast-container {
136 position: fixed;
137 z-index: 999999;
138 // The container should not be clickable.
139 pointer-events: none;
140 * {
141 -moz-box-sizing: border-box;
142 -webkit-box-sizing: border-box;
143 box-sizing: border-box;
144 }
145
146 > div {
147 position: relative;
148 // The toast itself should be clickable.
149 pointer-events: auto;
150 overflow: hidden;
151 margin: 0 0 6px;
152 padding: 15px;
153 width: 300px;
154 .borderRadius(1px 1px 1px 1px);
155 background-position: 15px center;
156 background-repeat: no-repeat;
157 color: @near-black;
158 .opacity(@default-container-opacity);
159 }
160
161 > :hover {
162 .opacity(1);
163 cursor: pointer;
164 }
165
166 > .toast-info {
167 //background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
168 }
169
170 > .toast-error {
171 //background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
172 }
173
174 > .toast-success {
175 //background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
176 }
177
178 > .toast-warning {
179 //background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
180 }
181
182 /*overrides*/
183 &.toast-top-center > div,
184 &.toast-bottom-center > div {
185 width: 400px;
186 margin-left: auto;
187 margin-right: auto;
188 }
189
190 &.toast-top-full-width > div,
191 &.toast-bottom-full-width > div {
192 width: 96%;
193 margin-left: auto;
194 margin-right: auto;
195 }
196 }
197
198 .toast {
199 border-color: @near-black;
200 border-style: solid;
201 border-width: 2px 2px 2px 25px;
202 background-color: @white;
203 }
204
205 .toast-success {
206 border-color: @green;
207 }
208
209 .toast-error {
210 border-color: @red;
211 }
212
213 .toast-info {
214 border-color: @blue;
215 }
216
217 .toast-warning {
218 border-color: @orange;
219 }
220
221 .toast-progress {
222 position: absolute;
223 left: 0;
224 bottom: 0;
225 height: 4px;
226 background-color: @black;
227 .opacity(0.4);
228 }
229
230 /*Responsive Design*/
231
232 @media all and (max-width: 240px) {
233 #toast-container {
234
235 > div {
236 padding: 8px;
237 width: 11em;
238 }
239
240 & .toast-close-button {
241 right: -0.2em;
242 top: -0.2em;
243 }
244 }
245 }
246
247 @media all and (min-width: 241px) and (max-width: 480px) {
248 #toast-container {
249 > div {
250 padding: 8px;
251 width: 18em;
252 }
253
254 & .toast-close-button {
255 right: -0.2em;
256 top: -0.2em;
257 }
258 }
259 }
260
261 @media all and (min-width: 481px) and (max-width: 768px) {
262 #toast-container {
263 > div {
264 padding: 15px;
265 width: 25em;
266 }
267 }
268 }
@@ -0,0 +1,435 b''
1 /*
2 * Toastr
3 * Copyright 2012-2015
4 * Authors: John Papa, Hans FjΓ€llemark, and Tim Ferrell.
5 * All Rights Reserved.
6 * Use, reproduction, distribution, and modification of this code is subject to the terms and
7 * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
8 *
9 * ARIA Support: Greta Krafsig
10 *
11 * Project: https://github.com/CodeSeven/toastr
12 */
13 /* global define */
14 (function (define) {
15 define(['jquery'], function ($) {
16 return (function () {
17 var $container;
18 var listener;
19 var toastId = 0;
20 var toastType = {
21 error: 'error',
22 info: 'info',
23 success: 'success',
24 warning: 'warning'
25 };
26
27 var toastr = {
28 clear: clear,
29 remove: remove,
30 error: error,
31 getContainer: getContainer,
32 info: info,
33 options: {},
34 subscribe: subscribe,
35 success: success,
36 version: '2.1.2',
37 warning: warning
38 };
39
40 var previousToast;
41
42 return toastr;
43
44 ////////////////
45
46 function error(message, title, optionsOverride) {
47 return notify({
48 type: toastType.error,
49 iconClass: getOptions().iconClasses.error,
50 message: message,
51 optionsOverride: optionsOverride,
52 title: title
53 });
54 }
55
56 function getContainer(options, create) {
57 if (!options) { options = getOptions(); }
58 $container = $('#' + options.containerId);
59 if ($container.length) {
60 return $container;
61 }
62 if (create) {
63 $container = createContainer(options);
64 }
65 return $container;
66 }
67
68 function info(message, title, optionsOverride) {
69 return notify({
70 type: toastType.info,
71 iconClass: getOptions().iconClasses.info,
72 message: message,
73 optionsOverride: optionsOverride,
74 title: title
75 });
76 }
77
78 function subscribe(callback) {
79 listener = callback;
80 }
81
82 function success(message, title, optionsOverride) {
83 return notify({
84 type: toastType.success,
85 iconClass: getOptions().iconClasses.success,
86 message: message,
87 optionsOverride: optionsOverride,
88 title: title
89 });
90 }
91
92 function warning(message, title, optionsOverride) {
93 return notify({
94 type: toastType.warning,
95 iconClass: getOptions().iconClasses.warning,
96 message: message,
97 optionsOverride: optionsOverride,
98 title: title
99 });
100 }
101
102 function clear($toastElement, clearOptions) {
103 var options = getOptions();
104 if (!$container) { getContainer(options); }
105 if (!clearToast($toastElement, options, clearOptions)) {
106 clearContainer(options);
107 }
108 }
109
110 function remove($toastElement) {
111 var options = getOptions();
112 if (!$container) { getContainer(options); }
113 if ($toastElement && $(':focus', $toastElement).length === 0) {
114 removeToast($toastElement);
115 return;
116 }
117 if ($container.children().length) {
118 $container.remove();
119 }
120 }
121
122 // internal functions
123
124 function clearContainer (options) {
125 var toastsToClear = $container.children();
126 for (var i = toastsToClear.length - 1; i >= 0; i--) {
127 clearToast($(toastsToClear[i]), options);
128 }
129 }
130
131 function clearToast ($toastElement, options, clearOptions) {
132 var force = clearOptions && clearOptions.force ? clearOptions.force : false;
133 if ($toastElement && (force || $(':focus', $toastElement).length === 0)) {
134 $toastElement[options.hideMethod]({
135 duration: options.hideDuration,
136 easing: options.hideEasing,
137 complete: function () { removeToast($toastElement); }
138 });
139 return true;
140 }
141 return false;
142 }
143
144 function createContainer(options) {
145 $container = $('<div/>')
146 .attr('id', options.containerId)
147 .addClass(options.positionClass)
148 .attr('aria-live', 'polite')
149 .attr('role', 'alert');
150
151 $container.appendTo($(options.target));
152 return $container;
153 }
154
155 function getDefaults() {
156 return {
157 tapToDismiss: true,
158 toastClass: 'toast',
159 containerId: 'toast-container',
160 debug: false,
161
162 showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
163 showDuration: 300,
164 showEasing: 'swing', //swing and linear are built into jQuery
165 onShown: undefined,
166 hideMethod: 'fadeOut',
167 hideDuration: 1000,
168 hideEasing: 'swing',
169 onHidden: undefined,
170 closeMethod: false,
171 closeDuration: false,
172 closeEasing: false,
173
174 extendedTimeOut: 1000,
175 iconClasses: {
176 error: 'toast-error',
177 info: 'toast-info',
178 success: 'toast-success',
179 warning: 'toast-warning'
180 },
181 iconClass: 'toast-info',
182 positionClass: 'toast-top-right',
183 timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky
184 titleClass: 'toast-title',
185 messageClass: 'toast-message',
186 escapeHtml: false,
187 target: 'body',
188 closeHtml: '<button type="button">&times;</button>',
189 newestOnTop: true,
190 preventDuplicates: false,
191 progressBar: false
192 };
193 }
194
195 function publish(args) {
196 if (!listener) { return; }
197 listener(args);
198 }
199
200 function notify(map) {
201 var options = getOptions();
202 var iconClass = map.iconClass || options.iconClass;
203
204 if (typeof (map.optionsOverride) !== 'undefined') {
205 options = $.extend(options, map.optionsOverride);
206 iconClass = map.optionsOverride.iconClass || iconClass;
207 }
208
209 if (shouldExit(options, map)) { return; }
210
211 toastId++;
212
213 $container = getContainer(options, true);
214
215 var intervalId = null;
216 var $toastElement = $('<div/>');
217 var $titleElement = $('<div/>');
218 var $messageElement = $('<div/>');
219 var $progressElement = $('<div/>');
220 var $closeElement = $(options.closeHtml);
221 var progressBar = {
222 intervalId: null,
223 hideEta: null,
224 maxHideTime: null
225 };
226 var response = {
227 toastId: toastId,
228 state: 'visible',
229 startTime: new Date(),
230 options: options,
231 map: map
232 };
233
234 personalizeToast();
235
236 displayToast();
237
238 handleEvents();
239
240 publish(response);
241
242 if (options.debug && console) {
243 console.log(response);
244 }
245
246 return $toastElement;
247
248 function escapeHtml(source) {
249 if (source == null)
250 source = "";
251
252 return new String(source)
253 .replace(/&/g, '&amp;')
254 .replace(/"/g, '&quot;')
255 .replace(/'/g, '&#39;')
256 .replace(/</g, '&lt;')
257 .replace(/>/g, '&gt;');
258 }
259
260 function personalizeToast() {
261 setIcon();
262 setTitle();
263 setMessage();
264 setCloseButton();
265 setProgressBar();
266 setSequence();
267 }
268
269 function handleEvents() {
270 $toastElement.hover(stickAround, delayedHideToast);
271 if (!options.onclick && options.tapToDismiss) {
272 $toastElement.click(hideToast);
273 }
274
275 if (options.closeButton && $closeElement) {
276 $closeElement.click(function (event) {
277 if (event.stopPropagation) {
278 event.stopPropagation();
279 } else if (event.cancelBubble !== undefined && event.cancelBubble !== true) {
280 event.cancelBubble = true;
281 }
282 hideToast(true);
283 });
284 }
285
286 if (options.onclick) {
287 $toastElement.click(function (event) {
288 options.onclick(event);
289 hideToast();
290 });
291 }
292 }
293
294 function displayToast() {
295 $toastElement.hide();
296
297 $toastElement[options.showMethod](
298 {duration: options.showDuration, easing: options.showEasing, complete: options.onShown}
299 );
300
301 if (options.timeOut > 0) {
302 intervalId = setTimeout(hideToast, options.timeOut);
303 progressBar.maxHideTime = parseFloat(options.timeOut);
304 progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
305 if (options.progressBar) {
306 progressBar.intervalId = setInterval(updateProgress, 10);
307 }
308 }
309 }
310
311 function setIcon() {
312 if (map.iconClass) {
313 $toastElement.addClass(options.toastClass).addClass(iconClass);
314 }
315 }
316
317 function setSequence() {
318 if (options.newestOnTop) {
319 $container.prepend($toastElement);
320 } else {
321 $container.append($toastElement);
322 }
323 }
324
325 function setTitle() {
326 if (map.title) {
327 $titleElement.append(!options.escapeHtml ? map.title : escapeHtml(map.title)).addClass(options.titleClass);
328 $toastElement.append($titleElement);
329 }
330 }
331
332 function setMessage() {
333 if (map.message) {
334 $messageElement.append(!options.escapeHtml ? map.message : escapeHtml(map.message)).addClass(options.messageClass);
335 $toastElement.append($messageElement);
336 }
337 }
338
339 function setCloseButton() {
340 if (options.closeButton) {
341 $closeElement.addClass('toast-close-button').attr('role', 'button');
342 $toastElement.prepend($closeElement);
343 }
344 }
345
346 function setProgressBar() {
347 if (options.progressBar) {
348 $progressElement.addClass('toast-progress');
349 $toastElement.prepend($progressElement);
350 }
351 }
352
353 function shouldExit(options, map) {
354 if (options.preventDuplicates) {
355 if (map.message === previousToast) {
356 return true;
357 } else {
358 previousToast = map.message;
359 }
360 }
361 return false;
362 }
363
364 function hideToast(override) {
365 var method = override && options.closeMethod !== false ? options.closeMethod : options.hideMethod;
366 var duration = override && options.closeDuration !== false ?
367 options.closeDuration : options.hideDuration;
368 var easing = override && options.closeEasing !== false ? options.closeEasing : options.hideEasing;
369 if ($(':focus', $toastElement).length && !override) {
370 return;
371 }
372 clearTimeout(progressBar.intervalId);
373 return $toastElement[method]({
374 duration: duration,
375 easing: easing,
376 complete: function () {
377 removeToast($toastElement);
378 if (options.onHidden && response.state !== 'hidden') {
379 options.onHidden();
380 }
381 response.state = 'hidden';
382 response.endTime = new Date();
383 publish(response);
384 }
385 });
386 }
387
388 function delayedHideToast() {
389 if (options.timeOut > 0 || options.extendedTimeOut > 0) {
390 intervalId = setTimeout(hideToast, options.extendedTimeOut);
391 progressBar.maxHideTime = parseFloat(options.extendedTimeOut);
392 progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
393 }
394 }
395
396 function stickAround() {
397 clearTimeout(intervalId);
398 progressBar.hideEta = 0;
399 $toastElement.stop(true, true)[options.showMethod](
400 {duration: options.showDuration, easing: options.showEasing}
401 );
402 }
403
404 function updateProgress() {
405 var percentage = ((progressBar.hideEta - (new Date().getTime())) / progressBar.maxHideTime) * 100;
406 $progressElement.width(percentage + '%');
407 }
408 }
409
410 function getOptions() {
411 return $.extend({}, getDefaults(), toastr.options);
412 }
413
414 function removeToast($toastElement) {
415 if (!$container) { $container = getContainer(); }
416 if ($toastElement.is(':visible')) {
417 return;
418 }
419 $toastElement.remove();
420 $toastElement = null;
421 if ($container.children().length === 0) {
422 $container.remove();
423 previousToast = undefined;
424 }
425 }
426
427 })();
428 });
429 }(typeof define === 'function' && define.amd ? define : function (deps, factory) {
430 if (typeof module !== 'undefined' && module.exports) { //Node
431 module.exports = factory(require('jquery'));
432 } else {
433 window.toastr = factory(window.jQuery);
434 }
435 }));
@@ -0,0 +1,219 b''
1 "use strict";
2 /** leak object to top level scope **/
3 var ccLog = undefined;
4 // global code-mirror logger;, to enable run
5 // Logger.get('ConnectionController').setLevel(Logger.DEBUG)
6 ccLog = Logger.get('ConnectionController');
7 ccLog.setLevel(Logger.OFF);
8
9 var ConnectionController;
10 var connCtrlr;
11 var registerViewChannels;
12
13 (function () {
14 ConnectionController = function (webappUrl, serverUrl, urls) {
15 var self = this;
16
17 var channels = ['broadcast'];
18 this.state = {
19 open: false,
20 webappUrl: webappUrl,
21 serverUrl: serverUrl,
22 connId: null,
23 socket: null,
24 channels: channels,
25 heartbeat: null,
26 channelsInfo: {},
27 urls: urls
28 };
29 this.channelNameParsers = [];
30
31 this.addChannelNameParser = function (fn) {
32 if (this.channelNameParsers.indexOf(fn) === -1) {
33 this.channelNameParsers.push(fn);
34 }
35 };
36
37 this.listen = function () {
38 if (window.WebSocket) {
39 ccLog.debug('attempting to create socket');
40 var socket_url = self.state.serverUrl + "/ws?conn_id=" + self.state.connId;
41 var socket_conf = {
42 url: socket_url,
43 handleAs: 'json',
44 headers: {
45 "Accept": "application/json",
46 "Content-Type": "application/json"
47 }
48 };
49 self.state.socket = new WebSocket(socket_conf.url);
50
51 self.state.socket.onopen = function (event) {
52 ccLog.debug('open event', event);
53 if (self.state.heartbeat === null) {
54 self.state.heartbeat = setInterval(function () {
55 if (self.state.socket.readyState === WebSocket.OPEN) {
56 self.state.socket.send('heartbeat');
57 }
58 }, 10000)
59 }
60 };
61 self.state.socket.onmessage = function (event) {
62 var data = $.parseJSON(event.data);
63 for (var i = 0; i < data.length; i++) {
64 if (data[i].message.topic) {
65 ccLog.debug('publishing',
66 data[i].message.topic, data[i]);
67 $.Topic(data[i].message.topic).publish(data[i])
68 }
69 else {
70 cclog.warning('unhandled message', data);
71 }
72 }
73 };
74 self.state.socket.onclose = function (event) {
75 ccLog.debug('closed event', event);
76 setTimeout(function () {
77 self.connect(true);
78 }, 5000);
79 };
80
81 self.state.socket.onerror = function (event) {
82 ccLog.debug('error event', event);
83 };
84 }
85 else {
86 ccLog.debug('attempting to create long polling connection');
87 var poolUrl = self.state.serverUrl + "/listen?conn_id=" + self.state.connId;
88 self.state.socket = $.ajax({
89 url: poolUrl
90 }).done(function (data) {
91 ccLog.debug('data', data);
92 var data = $.parseJSON(data);
93 for (var i = 0; i < data.length; i++) {
94 if (data[i].message.topic) {
95 ccLog.info('publishing',
96 data[i].message.topic, data[i]);
97 $.Topic(data[i].message.topic).publish(data[i])
98 }
99 else {
100 cclog.warning('unhandled message', data);
101 }
102 }
103 self.listen();
104 }).fail(function () {
105 ccLog.debug('longpoll error');
106 setTimeout(function () {
107 self.connect(true);
108 }, 5000);
109 });
110 }
111
112 };
113
114 this.connect = function (create_new_socket) {
115 var connReq = {'channels': self.state.channels};
116 ccLog.debug('try obtaining connection info', connReq);
117 $.ajax({
118 url: self.state.urls.connect,
119 type: "POST",
120 contentType: "application/json",
121 data: JSON.stringify(connReq),
122 dataType: "json"
123 }).done(function (data) {
124 ccLog.debug('Got connection:', data.conn_id);
125 self.state.channels = data.channels;
126 self.state.channelsInfo = data.channels_info;
127 self.state.connId = data.conn_id;
128 if (create_new_socket) {
129 self.listen();
130 }
131 self.update();
132 }).fail(function () {
133 setTimeout(function () {
134 self.connect(create_new_socket);
135 }, 5000);
136 });
137 self.update();
138 };
139
140 this.subscribeToChannels = function (channels) {
141 var new_channels = [];
142 for (var i = 0; i < channels.length; i++) {
143 var channel = channels[i];
144 if (self.state.channels.indexOf(channel)) {
145 self.state.channels.push(channel);
146 new_channels.push(channel)
147 }
148 }
149 /**
150 * only execute the request if socket is present because subscribe
151 * can actually add channels before initial app connection
152 **/
153 if (new_channels && self.state.socket !== null) {
154 var connReq = {
155 'channels': self.state.channels,
156 'conn_id': self.state.connId
157 };
158 $.ajax({
159 url: self.state.urls.subscribe,
160 type: "POST",
161 contentType: "application/json",
162 data: JSON.stringify(connReq),
163 dataType: "json"
164 }).done(function (data) {
165 self.state.channels = data.channels;
166 self.state.channelsInfo = data.channels_info;
167 self.update();
168 });
169 }
170 self.update();
171 };
172
173 this.update = function () {
174 for (var key in this.state.channelsInfo) {
175 if (this.state.channelsInfo.hasOwnProperty(key)) {
176 // update channels with latest info
177 $.Topic('/connection_controller/channel_update').publish(
178 {channel: key, state: this.state.channelsInfo[key]});
179 }
180 }
181 /**
182 * checks current channel list in state and if channel is not present
183 * converts them into executable "commands" and pushes them on topics
184 */
185 for (var i = 0; i < this.state.channels.length; i++) {
186 var channel = this.state.channels[i];
187 for (var j = 0; j < this.channelNameParsers.length; j++) {
188 this.channelNameParsers[j](channel);
189 }
190 }
191 };
192
193 this.run = function () {
194 this.connect(true);
195 };
196
197 $.Topic('/connection_controller/subscribe').subscribe(
198 self.subscribeToChannels);
199 };
200
201 $.Topic('/plugins/__REGISTER__').subscribe(function (data) {
202 // enable chat controller
203 if (window.CHANNELSTREAM_SETTINGS && window.CHANNELSTREAM_SETTINGS.enabled) {
204 $(document).ready(function () {
205 connCtrlr.run();
206 });
207 }
208 });
209
210 registerViewChannels = function (){
211 // subscribe to PR repo channel for PR's'
212 if (templateContext.pull_request_data.pull_request_id) {
213 var channelName = '/repo$' + templateContext.repo_name + '$/pr/' +
214 String(templateContext.pull_request_data.pull_request_id);
215 connCtrlr.state.channels.push(channelName);
216 }
217 }
218
219 })();
@@ -0,0 +1,60 b''
1 "use strict";
2
3 toastr.options = {
4 "closeButton": true,
5 "debug": false,
6 "newestOnTop": false,
7 "progressBar": false,
8 "positionClass": "toast-top-center",
9 "preventDuplicates": false,
10 "onclick": null,
11 "showDuration": "300",
12 "hideDuration": "300",
13 "timeOut": "0",
14 "extendedTimeOut": "0",
15 "showEasing": "swing",
16 "hideEasing": "linear",
17 "showMethod": "fadeIn",
18 "hideMethod": "fadeOut"
19 };
20
21 function notifySystem(data) {
22 var notification = new Notification(data.message.level + ': ' + data.message.message);
23 };
24
25 function notifyToaster(data){
26 toastr[data.message.level](data.message.message);
27 }
28
29 function handleNotifications(data) {
30
31 if (!templateContext.rhodecode_user.notification_status && !data.testMessage) {
32 // do not act if notifications are disabled
33 return
34 }
35 // use only js notifications for now
36 var onlyJS = true;
37 if (!("Notification" in window) || onlyJS) {
38 // use legacy notificartion
39 notifyToaster(data);
40 }
41 else {
42 // Let's check whether notification permissions have already been granted
43 if (Notification.permission === "granted") {
44 notifySystem(data);
45 }
46 // Otherwise, we need to ask the user for permission
47 else if (Notification.permission !== 'denied') {
48 Notification.requestPermission(function (permission) {
49 if (permission === "granted") {
50 notifySystem(data);
51 }
52 });
53 }
54 else{
55 notifyToaster(data);
56 }
57 }
58 };
59
60 $.Topic('/notifications').subscribe(handleNotifications);
@@ -0,0 +1,24 b''
1 <script>
2 var CHANNELSTREAM_URLS = ${config['url_gen'](request)|n};
3 %if request.registry.rhodecode_plugins['channelstream']['enabled'] and c.rhodecode_user.username != h.DEFAULT_USER:
4 var CHANNELSTREAM_SETTINGS = {
5 'enabled': true,
6 'ws_location': '${request.registry.settings.get('channelstream.ws_url')}',
7 'webapp_location': '${h.url('/', qualified=True)[:-1]}'
8 };
9 %else:
10 var CHANNELSTREAM_SETTINGS = {
11 'enabled':false,
12 'ws_location': '',
13 'webapp_location': ''};
14 %endif
15
16 if (CHANNELSTREAM_SETTINGS.enabled) {
17 connCtrlr = new ConnectionController(
18 CHANNELSTREAM_SETTINGS.webapp_location,
19 CHANNELSTREAM_SETTINGS.ws_location,
20 CHANNELSTREAM_URLS
21 );
22 registerViewChannels();
23 }
24 </script>
@@ -1,141 +1,144 b''
1 module.exports = function(grunt) {
1 module.exports = function(grunt) {
2 grunt.initConfig({
2 grunt.initConfig({
3
3
4 dirs: {
4 dirs: {
5 css: "rhodecode/public/css",
5 css: "rhodecode/public/css",
6 js: {
6 js: {
7 "src": "rhodecode/public/js/src",
7 "src": "rhodecode/public/js/src",
8 "dest": "rhodecode/public/js"
8 "dest": "rhodecode/public/js"
9 }
9 }
10 },
10 },
11
11
12 concat: {
12 concat: {
13 dist: {
13 dist: {
14 src: [
14 src: [
15 // Base libraries
15 // Base libraries
16 '<%= dirs.js.src %>/jquery-1.11.1.min.js',
16 '<%= dirs.js.src %>/jquery-1.11.1.min.js',
17 '<%= dirs.js.src %>/logging.js',
17 '<%= dirs.js.src %>/logging.js',
18 '<%= dirs.js.src %>/bootstrap.js',
18 '<%= dirs.js.src %>/bootstrap.js',
19 '<%= dirs.js.src %>/mousetrap.js',
19 '<%= dirs.js.src %>/mousetrap.js',
20 '<%= dirs.js.src %>/moment.js',
20 '<%= dirs.js.src %>/moment.js',
21 '<%= dirs.js.src %>/appenlight-client-0.4.1.min.js',
21 '<%= dirs.js.src %>/appenlight-client-0.4.1.min.js',
22 '<%= dirs.js.src %>/i18n_utils.js',
22 '<%= dirs.js.src %>/i18n_utils.js',
23 '<%= dirs.js.src %>/deform.js',
23 '<%= dirs.js.src %>/deform.js',
24
24
25 // Plugins
25 // Plugins
26 '<%= dirs.js.src %>/plugins/jquery.pjax.js',
26 '<%= dirs.js.src %>/plugins/jquery.pjax.js',
27 '<%= dirs.js.src %>/plugins/jquery.dataTables.js',
27 '<%= dirs.js.src %>/plugins/jquery.dataTables.js',
28 '<%= dirs.js.src %>/plugins/flavoured_checkbox.js',
28 '<%= dirs.js.src %>/plugins/flavoured_checkbox.js',
29 '<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js',
29 '<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js',
30 '<%= dirs.js.src %>/plugins/jquery.autocomplete.js',
30 '<%= dirs.js.src %>/plugins/jquery.autocomplete.js',
31 '<%= dirs.js.src %>/plugins/jquery.debounce.js',
31 '<%= dirs.js.src %>/plugins/jquery.debounce.js',
32 '<%= dirs.js.src %>/plugins/jquery.mark.js',
32 '<%= dirs.js.src %>/plugins/jquery.mark.js',
33 '<%= dirs.js.src %>/plugins/jquery.timeago.js',
33 '<%= dirs.js.src %>/plugins/jquery.timeago.js',
34 '<%= dirs.js.src %>/plugins/jquery.timeago-extension.js',
34 '<%= dirs.js.src %>/plugins/jquery.timeago-extension.js',
35 '<%= dirs.js.src %>/plugins/toastr.js',
35
36
36 // Select2
37 // Select2
37 '<%= dirs.js.src %>/select2/select2.js',
38 '<%= dirs.js.src %>/select2/select2.js',
38
39
39 // Code-mirror
40 // Code-mirror
40 '<%= dirs.js.src %>/codemirror/codemirror.js',
41 '<%= dirs.js.src %>/codemirror/codemirror.js',
41 '<%= dirs.js.src %>/codemirror/codemirror_loadmode.js',
42 '<%= dirs.js.src %>/codemirror/codemirror_loadmode.js',
42 '<%= dirs.js.src %>/codemirror/codemirror_hint.js',
43 '<%= dirs.js.src %>/codemirror/codemirror_hint.js',
43 '<%= dirs.js.src %>/codemirror/codemirror_overlay.js',
44 '<%= dirs.js.src %>/codemirror/codemirror_overlay.js',
44 '<%= dirs.js.src %>/codemirror/codemirror_placeholder.js',
45 '<%= dirs.js.src %>/codemirror/codemirror_placeholder.js',
45 // TODO: mikhail: this is an exception. Since the code mirror modes
46 // TODO: mikhail: this is an exception. Since the code mirror modes
46 // are loaded "on the fly", we need to keep them in a public folder
47 // are loaded "on the fly", we need to keep them in a public folder
47 '<%= dirs.js.dest %>/mode/meta.js',
48 '<%= dirs.js.dest %>/mode/meta.js',
48 '<%= dirs.js.dest %>/mode/meta_ext.js',
49 '<%= dirs.js.dest %>/mode/meta_ext.js',
49 '<%= dirs.js.dest %>/rhodecode/i18n/select2/translations.js',
50 '<%= dirs.js.dest %>/rhodecode/i18n/select2/translations.js',
50
51
51 // Rhodecode utilities
52 // Rhodecode utilities
52 '<%= dirs.js.src %>/rhodecode/utils/array.js',
53 '<%= dirs.js.src %>/rhodecode/utils/array.js',
53 '<%= dirs.js.src %>/rhodecode/utils/string.js',
54 '<%= dirs.js.src %>/rhodecode/utils/string.js',
54 '<%= dirs.js.src %>/rhodecode/utils/pyroutes.js',
55 '<%= dirs.js.src %>/rhodecode/utils/pyroutes.js',
55 '<%= dirs.js.src %>/rhodecode/utils/ajax.js',
56 '<%= dirs.js.src %>/rhodecode/utils/ajax.js',
56 '<%= dirs.js.src %>/rhodecode/utils/autocomplete.js',
57 '<%= dirs.js.src %>/rhodecode/utils/autocomplete.js',
57 '<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js',
58 '<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js',
58 '<%= dirs.js.src %>/rhodecode/utils/ie.js',
59 '<%= dirs.js.src %>/rhodecode/utils/ie.js',
59 '<%= dirs.js.src %>/rhodecode/utils/os.js',
60 '<%= dirs.js.src %>/rhodecode/utils/os.js',
60 '<%= dirs.js.src %>/rhodecode/utils/topics.js',
61 '<%= dirs.js.src %>/rhodecode/utils/topics.js',
61
62
62 // Rhodecode widgets
63 // Rhodecode widgets
63 '<%= dirs.js.src %>/rhodecode/widgets/multiselect.js',
64 '<%= dirs.js.src %>/rhodecode/widgets/multiselect.js',
64
65
65 // Rhodecode components
66 // Rhodecode components
66 '<%= dirs.js.src %>/rhodecode/init.js',
67 '<%= dirs.js.src %>/rhodecode/init.js',
68 '<%= dirs.js.src %>/rhodecode/connection_controller.js',
67 '<%= dirs.js.src %>/rhodecode/codemirror.js',
69 '<%= dirs.js.src %>/rhodecode/codemirror.js',
68 '<%= dirs.js.src %>/rhodecode/comments.js',
70 '<%= dirs.js.src %>/rhodecode/comments.js',
69 '<%= dirs.js.src %>/rhodecode/constants.js',
71 '<%= dirs.js.src %>/rhodecode/constants.js',
70 '<%= dirs.js.src %>/rhodecode/files.js',
72 '<%= dirs.js.src %>/rhodecode/files.js',
71 '<%= dirs.js.src %>/rhodecode/followers.js',
73 '<%= dirs.js.src %>/rhodecode/followers.js',
72 '<%= dirs.js.src %>/rhodecode/menus.js',
74 '<%= dirs.js.src %>/rhodecode/menus.js',
73 '<%= dirs.js.src %>/rhodecode/notifications.js',
75 '<%= dirs.js.src %>/rhodecode/notifications.js',
74 '<%= dirs.js.src %>/rhodecode/permissions.js',
76 '<%= dirs.js.src %>/rhodecode/permissions.js',
75 '<%= dirs.js.src %>/rhodecode/pjax.js',
77 '<%= dirs.js.src %>/rhodecode/pjax.js',
76 '<%= dirs.js.src %>/rhodecode/pullrequests.js',
78 '<%= dirs.js.src %>/rhodecode/pullrequests.js',
77 '<%= dirs.js.src %>/rhodecode/settings.js',
79 '<%= dirs.js.src %>/rhodecode/settings.js',
78 '<%= dirs.js.src %>/rhodecode/select2_widgets.js',
80 '<%= dirs.js.src %>/rhodecode/select2_widgets.js',
79 '<%= dirs.js.src %>/rhodecode/tooltips.js',
81 '<%= dirs.js.src %>/rhodecode/tooltips.js',
80 '<%= dirs.js.src %>/rhodecode/users.js',
82 '<%= dirs.js.src %>/rhodecode/users.js',
83 '<%= dirs.js.src %>/rhodecode/utils/notifications.js',
81 '<%= dirs.js.src %>/rhodecode/appenlight.js',
84 '<%= dirs.js.src %>/rhodecode/appenlight.js',
82
85
83 // Rhodecode main module
86 // Rhodecode main module
84 '<%= dirs.js.src %>/rhodecode.js'
87 '<%= dirs.js.src %>/rhodecode.js'
85 ],
88 ],
86 dest: '<%= dirs.js.dest %>/scripts.js',
89 dest: '<%= dirs.js.dest %>/scripts.js',
87 nonull: true
90 nonull: true
88 }
91 }
89 },
92 },
90
93
91 less: {
94 less: {
92 development: {
95 development: {
93 options: {
96 options: {
94 compress: false,
97 compress: false,
95 yuicompress: false,
98 yuicompress: false,
96 optimization: 0
99 optimization: 0
97 },
100 },
98 files: {
101 files: {
99 "<%= dirs.css %>/style.css": "<%= dirs.css %>/main.less"
102 "<%= dirs.css %>/style.css": "<%= dirs.css %>/main.less"
100 }
103 }
101 },
104 },
102 production: {
105 production: {
103 options: {
106 options: {
104 compress: true,
107 compress: true,
105 yuicompress: true,
108 yuicompress: true,
106 optimization: 2
109 optimization: 2
107 },
110 },
108 files: {
111 files: {
109 "<%= dirs.css %>/style.css": "<%= dirs.css %>/main.less"
112 "<%= dirs.css %>/style.css": "<%= dirs.css %>/main.less"
110 }
113 }
111 }
114 }
112 },
115 },
113
116
114 watch: {
117 watch: {
115 less: {
118 less: {
116 files: ["<%= dirs.css %>/*.less"],
119 files: ["<%= dirs.css %>/*.less"],
117 tasks: ["less:production"]
120 tasks: ["less:production"]
118 },
121 },
119 js: {
122 js: {
120 files: ["<%= dirs.js.src %>/**/*.js"],
123 files: ["<%= dirs.js.src %>/**/*.js"],
121 tasks: ["concat:dist"]
124 tasks: ["concat:dist"]
122 }
125 }
123 },
126 },
124
127
125 jshint: {
128 jshint: {
126 rhodecode: {
129 rhodecode: {
127 src: '<%= dirs.js.src %>/rhodecode/**/*.js',
130 src: '<%= dirs.js.src %>/rhodecode/**/*.js',
128 options: {
131 options: {
129 jshintrc: '.jshintrc'
132 jshintrc: '.jshintrc'
130 }
133 }
131 }
134 }
132 }
135 }
133 });
136 });
134
137
135 grunt.loadNpmTasks('grunt-contrib-less');
138 grunt.loadNpmTasks('grunt-contrib-less');
136 grunt.loadNpmTasks('grunt-contrib-concat');
139 grunt.loadNpmTasks('grunt-contrib-concat');
137 grunt.loadNpmTasks('grunt-contrib-watch');
140 grunt.loadNpmTasks('grunt-contrib-watch');
138 grunt.loadNpmTasks('grunt-contrib-jshint');
141 grunt.loadNpmTasks('grunt-contrib-jshint');
139
142
140 grunt.registerTask('default', ['less:production', 'concat:dist']);
143 grunt.registerTask('default', ['less:production', 'concat:dist']);
141 };
144 };
@@ -1,612 +1,623 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode Enterprise - configuration file #
3 # RhodeCode Enterprise - configuration file #
4 # Built-in functions and variables #
4 # Built-in functions and variables #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 # #
6 # #
7 ################################################################################
7 ################################################################################
8
8
9 [DEFAULT]
9 [DEFAULT]
10 debug = true
10 debug = true
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the email address which should receive ##
12 ## Uncomment and replace with the email address which should receive ##
13 ## any error reports after an application crash ##
13 ## any error reports after an application crash ##
14 ## Additionally these settings will be used by the RhodeCode mailing system ##
14 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20 #email_prefix = [RhodeCode]
21
21
22 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
23 #smtp_username =
23 #smtp_username =
24 #smtp_password =
24 #smtp_password =
25 #smtp_port =
25 #smtp_port =
26 #smtp_use_tls = false
26 #smtp_use_tls = false
27 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
29 #smtp_auth =
30
30
31 [server:main]
31 [server:main]
32 ## COMMON ##
32 ## COMMON ##
33 host = 127.0.0.1
33 host = 127.0.0.1
34 port = 5000
34 port = 5000
35
35
36 ##################################
36 ##################################
37 ## WAITRESS WSGI SERVER ##
37 ## WAITRESS WSGI SERVER ##
38 ## Recommended for Development ##
38 ## Recommended for Development ##
39 ##################################
39 ##################################
40 use = egg:waitress#main
40 use = egg:waitress#main
41 ## number of worker threads
41 ## number of worker threads
42 threads = 5
42 threads = 5
43 ## MAX BODY SIZE 100GB
43 ## MAX BODY SIZE 100GB
44 max_request_body_size = 107374182400
44 max_request_body_size = 107374182400
45 ## Use poll instead of select, fixes file descriptors limits problems.
45 ## Use poll instead of select, fixes file descriptors limits problems.
46 ## May not work on old windows systems.
46 ## May not work on old windows systems.
47 asyncore_use_poll = true
47 asyncore_use_poll = true
48
48
49
49
50 ##########################
50 ##########################
51 ## GUNICORN WSGI SERVER ##
51 ## GUNICORN WSGI SERVER ##
52 ##########################
52 ##########################
53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
54 #use = egg:gunicorn#main
54 #use = egg:gunicorn#main
55 ## Sets the number of process workers. You must set `instance_id = *`
55 ## Sets the number of process workers. You must set `instance_id = *`
56 ## when this option is set to more than one worker, recommended
56 ## when this option is set to more than one worker, recommended
57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 ## The `instance_id = *` must be set in the [app:main] section below
58 ## The `instance_id = *` must be set in the [app:main] section below
59 #workers = 2
59 #workers = 2
60 ## number of threads for each of the worker, must be set to 1 for gevent
60 ## number of threads for each of the worker, must be set to 1 for gevent
61 ## generally recommened to be at 1
61 ## generally recommened to be at 1
62 #threads = 1
62 #threads = 1
63 ## process name
63 ## process name
64 #proc_name = rhodecode
64 #proc_name = rhodecode
65 ## type of worker class, one of sync, gevent
65 ## type of worker class, one of sync, gevent
66 ## recommended for bigger setup is using of of other than sync one
66 ## recommended for bigger setup is using of of other than sync one
67 #worker_class = sync
67 #worker_class = sync
68 ## The maximum number of simultaneous clients. Valid only for Gevent
68 ## The maximum number of simultaneous clients. Valid only for Gevent
69 #worker_connections = 10
69 #worker_connections = 10
70 ## max number of requests that worker will handle before being gracefully
70 ## max number of requests that worker will handle before being gracefully
71 ## restarted, could prevent memory leaks
71 ## restarted, could prevent memory leaks
72 #max_requests = 1000
72 #max_requests = 1000
73 #max_requests_jitter = 30
73 #max_requests_jitter = 30
74 ## amount of time a worker can spend with handling a request before it
74 ## amount of time a worker can spend with handling a request before it
75 ## gets killed and restarted. Set to 6hrs
75 ## gets killed and restarted. Set to 6hrs
76 #timeout = 21600
76 #timeout = 21600
77
77
78
78
79 ## prefix middleware for RhodeCode, disables force_https flag.
79 ## prefix middleware for RhodeCode, disables force_https flag.
80 ## allows to set RhodeCode under a prefix in server.
80 ## allows to set RhodeCode under a prefix in server.
81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 #[filter:proxy-prefix]
82 #[filter:proxy-prefix]
83 #use = egg:PasteDeploy#prefix
83 #use = egg:PasteDeploy#prefix
84 #prefix = /<your-prefix>
84 #prefix = /<your-prefix>
85
85
86 [app:main]
86 [app:main]
87 use = egg:rhodecode-enterprise-ce
87 use = egg:rhodecode-enterprise-ce
88 ## enable proxy prefix middleware, defined below
88 ## enable proxy prefix middleware, defined below
89 #filter-with = proxy-prefix
89 #filter-with = proxy-prefix
90
90
91 # During development the we want to have the debug toolbar enabled
91 # During development the we want to have the debug toolbar enabled
92 pyramid.includes =
92 pyramid.includes =
93 pyramid_debugtoolbar
93 pyramid_debugtoolbar
94 rhodecode.utils.debugtoolbar
94 rhodecode.utils.debugtoolbar
95 rhodecode.lib.middleware.request_wrapper
95 rhodecode.lib.middleware.request_wrapper
96
96
97 pyramid.reload_templates = true
97 pyramid.reload_templates = true
98
98
99 debugtoolbar.hosts = 0.0.0.0/0
99 debugtoolbar.hosts = 0.0.0.0/0
100 debugtoolbar.exclude_prefixes =
100 debugtoolbar.exclude_prefixes =
101 /css
101 /css
102 /fonts
102 /fonts
103 /images
103 /images
104 /js
104 /js
105
105
106 ## RHODECODE PLUGINS ##
106 ## RHODECODE PLUGINS ##
107 rhodecode.includes =
107 rhodecode.includes =
108 rhodecode.api
108 rhodecode.api
109
109
110
110
111 # api prefix url
111 # api prefix url
112 rhodecode.api.url = /_admin/api
112 rhodecode.api.url = /_admin/api
113
113
114
114
115 ## END RHODECODE PLUGINS ##
115 ## END RHODECODE PLUGINS ##
116
116
117 ## encryption key used to encrypt social plugin tokens,
117 ## encryption key used to encrypt social plugin tokens,
118 ## remote_urls with credentials etc, if not set it defaults to
118 ## remote_urls with credentials etc, if not set it defaults to
119 ## `beaker.session.secret`
119 ## `beaker.session.secret`
120 #rhodecode.encrypted_values.secret =
120 #rhodecode.encrypted_values.secret =
121
121
122 ## decryption strict mode (enabled by default). It controls if decryption raises
122 ## decryption strict mode (enabled by default). It controls if decryption raises
123 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
123 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
124 #rhodecode.encrypted_values.strict = false
124 #rhodecode.encrypted_values.strict = false
125
125
126 full_stack = true
126 full_stack = true
127
127
128 ## return gzipped responses from Rhodecode (static files/application)
128 ## return gzipped responses from Rhodecode (static files/application)
129 gzip_responses = true
129 gzip_responses = true
130
130
131 # autogenerate javascript routes file on startup
131 # autogenerate javascript routes file on startup
132 generate_js_files = false
132 generate_js_files = false
133
133
134 ## Optional Languages
134 ## Optional Languages
135 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
135 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
136 lang = en
136 lang = en
137
137
138 ## perform a full repository scan on each server start, this should be
138 ## perform a full repository scan on each server start, this should be
139 ## set to false after first startup, to allow faster server restarts.
139 ## set to false after first startup, to allow faster server restarts.
140 startup.import_repos = false
140 startup.import_repos = false
141
141
142 ## Uncomment and set this path to use archive download cache.
142 ## Uncomment and set this path to use archive download cache.
143 ## Once enabled, generated archives will be cached at this location
143 ## Once enabled, generated archives will be cached at this location
144 ## and served from the cache during subsequent requests for the same archive of
144 ## and served from the cache during subsequent requests for the same archive of
145 ## the repository.
145 ## the repository.
146 #archive_cache_dir = /tmp/tarballcache
146 #archive_cache_dir = /tmp/tarballcache
147
147
148 ## change this to unique ID for security
148 ## change this to unique ID for security
149 app_instance_uuid = rc-production
149 app_instance_uuid = rc-production
150
150
151 ## cut off limit for large diffs (size in bytes)
151 ## cut off limit for large diffs (size in bytes)
152 cut_off_limit_diff = 1024000
152 cut_off_limit_diff = 1024000
153 cut_off_limit_file = 256000
153 cut_off_limit_file = 256000
154
154
155 ## use cache version of scm repo everywhere
155 ## use cache version of scm repo everywhere
156 vcs_full_cache = true
156 vcs_full_cache = true
157
157
158 ## force https in RhodeCode, fixes https redirects, assumes it's always https
158 ## force https in RhodeCode, fixes https redirects, assumes it's always https
159 ## Normally this is controlled by proper http flags sent from http server
159 ## Normally this is controlled by proper http flags sent from http server
160 force_https = false
160 force_https = false
161
161
162 ## use Strict-Transport-Security headers
162 ## use Strict-Transport-Security headers
163 use_htsts = false
163 use_htsts = false
164
164
165 ## number of commits stats will parse on each iteration
165 ## number of commits stats will parse on each iteration
166 commit_parse_limit = 25
166 commit_parse_limit = 25
167
167
168 ## git rev filter option, --all is the default filter, if you need to
168 ## git rev filter option, --all is the default filter, if you need to
169 ## hide all refs in changelog switch this to --branches --tags
169 ## hide all refs in changelog switch this to --branches --tags
170 git_rev_filter = --branches --tags
170 git_rev_filter = --branches --tags
171
171
172 # Set to true if your repos are exposed using the dumb protocol
172 # Set to true if your repos are exposed using the dumb protocol
173 git_update_server_info = false
173 git_update_server_info = false
174
174
175 ## RSS/ATOM feed options
175 ## RSS/ATOM feed options
176 rss_cut_off_limit = 256000
176 rss_cut_off_limit = 256000
177 rss_items_per_page = 10
177 rss_items_per_page = 10
178 rss_include_diff = false
178 rss_include_diff = false
179
179
180 ## gist URL alias, used to create nicer urls for gist. This should be an
180 ## gist URL alias, used to create nicer urls for gist. This should be an
181 ## url that does rewrites to _admin/gists/<gistid>.
181 ## url that does rewrites to _admin/gists/<gistid>.
182 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
182 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
183 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
183 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
184 gist_alias_url =
184 gist_alias_url =
185
185
186 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
186 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
187 ## used for access.
187 ## used for access.
188 ## Adding ?auth_token = <token> to the url authenticates this request as if it
188 ## Adding ?auth_token = <token> to the url authenticates this request as if it
189 ## came from the the logged in user who own this authentication token.
189 ## came from the the logged in user who own this authentication token.
190 ##
190 ##
191 ## Syntax is <ControllerClass>:<function_pattern>.
191 ## Syntax is <ControllerClass>:<function_pattern>.
192 ## To enable access to raw_files put `FilesController:raw`.
192 ## To enable access to raw_files put `FilesController:raw`.
193 ## To enable access to patches add `ChangesetController:changeset_patch`.
193 ## To enable access to patches add `ChangesetController:changeset_patch`.
194 ## The list should be "," separated and on a single line.
194 ## The list should be "," separated and on a single line.
195 ##
195 ##
196 ## Recommended controllers to enable:
196 ## Recommended controllers to enable:
197 # ChangesetController:changeset_patch,
197 # ChangesetController:changeset_patch,
198 # ChangesetController:changeset_raw,
198 # ChangesetController:changeset_raw,
199 # FilesController:raw,
199 # FilesController:raw,
200 # FilesController:archivefile,
200 # FilesController:archivefile,
201 # GistsController:*,
201 # GistsController:*,
202 api_access_controllers_whitelist =
202 api_access_controllers_whitelist =
203
203
204 ## default encoding used to convert from and to unicode
204 ## default encoding used to convert from and to unicode
205 ## can be also a comma separated list of encoding in case of mixed encodings
205 ## can be also a comma separated list of encoding in case of mixed encodings
206 default_encoding = UTF-8
206 default_encoding = UTF-8
207
207
208 ## instance-id prefix
208 ## instance-id prefix
209 ## a prefix key for this instance used for cache invalidation when running
209 ## a prefix key for this instance used for cache invalidation when running
210 ## multiple instances of rhodecode, make sure it's globally unique for
210 ## multiple instances of rhodecode, make sure it's globally unique for
211 ## all running rhodecode instances. Leave empty if you don't use it
211 ## all running rhodecode instances. Leave empty if you don't use it
212 instance_id =
212 instance_id =
213
213
214 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
214 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
215 ## of an authentication plugin also if it is disabled by it's settings.
215 ## of an authentication plugin also if it is disabled by it's settings.
216 ## This could be useful if you are unable to log in to the system due to broken
216 ## This could be useful if you are unable to log in to the system due to broken
217 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
217 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
218 ## module to log in again and fix the settings.
218 ## module to log in again and fix the settings.
219 ##
219 ##
220 ## Available builtin plugin IDs (hash is part of the ID):
220 ## Available builtin plugin IDs (hash is part of the ID):
221 ## egg:rhodecode-enterprise-ce#rhodecode
221 ## egg:rhodecode-enterprise-ce#rhodecode
222 ## egg:rhodecode-enterprise-ce#pam
222 ## egg:rhodecode-enterprise-ce#pam
223 ## egg:rhodecode-enterprise-ce#ldap
223 ## egg:rhodecode-enterprise-ce#ldap
224 ## egg:rhodecode-enterprise-ce#jasig_cas
224 ## egg:rhodecode-enterprise-ce#jasig_cas
225 ## egg:rhodecode-enterprise-ce#headers
225 ## egg:rhodecode-enterprise-ce#headers
226 ## egg:rhodecode-enterprise-ce#crowd
226 ## egg:rhodecode-enterprise-ce#crowd
227 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
227 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
228
228
229 ## alternative return HTTP header for failed authentication. Default HTTP
229 ## alternative return HTTP header for failed authentication. Default HTTP
230 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
230 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
231 ## handling that causing a series of failed authentication calls.
231 ## handling that causing a series of failed authentication calls.
232 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
232 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
233 ## This will be served instead of default 401 on bad authnetication
233 ## This will be served instead of default 401 on bad authnetication
234 auth_ret_code =
234 auth_ret_code =
235
235
236 ## use special detection method when serving auth_ret_code, instead of serving
236 ## use special detection method when serving auth_ret_code, instead of serving
237 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
237 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
238 ## and then serve auth_ret_code to clients
238 ## and then serve auth_ret_code to clients
239 auth_ret_code_detection = false
239 auth_ret_code_detection = false
240
240
241 ## locking return code. When repository is locked return this HTTP code. 2XX
241 ## locking return code. When repository is locked return this HTTP code. 2XX
242 ## codes don't break the transactions while 4XX codes do
242 ## codes don't break the transactions while 4XX codes do
243 lock_ret_code = 423
243 lock_ret_code = 423
244
244
245 ## allows to change the repository location in settings page
245 ## allows to change the repository location in settings page
246 allow_repo_location_change = true
246 allow_repo_location_change = true
247
247
248 ## allows to setup custom hooks in settings page
248 ## allows to setup custom hooks in settings page
249 allow_custom_hooks_settings = true
249 allow_custom_hooks_settings = true
250
250
251 ## generated license token, goto license page in RhodeCode settings to obtain
251 ## generated license token, goto license page in RhodeCode settings to obtain
252 ## new token
252 ## new token
253 license_token =
253 license_token =
254
254
255 ## supervisor connection uri, for managing supervisor and logs.
255 ## supervisor connection uri, for managing supervisor and logs.
256 supervisor.uri =
256 supervisor.uri =
257 ## supervisord group name/id we only want this RC instance to handle
257 ## supervisord group name/id we only want this RC instance to handle
258 supervisor.group_id = dev
258 supervisor.group_id = dev
259
259
260 ## Display extended labs settings
260 ## Display extended labs settings
261 labs_settings_active = true
261 labs_settings_active = true
262
262
263 ####################################
263 ####################################
264 ### CELERY CONFIG ####
264 ### CELERY CONFIG ####
265 ####################################
265 ####################################
266 use_celery = false
266 use_celery = false
267 broker.host = localhost
267 broker.host = localhost
268 broker.vhost = rabbitmqhost
268 broker.vhost = rabbitmqhost
269 broker.port = 5672
269 broker.port = 5672
270 broker.user = rabbitmq
270 broker.user = rabbitmq
271 broker.password = qweqwe
271 broker.password = qweqwe
272
272
273 celery.imports = rhodecode.lib.celerylib.tasks
273 celery.imports = rhodecode.lib.celerylib.tasks
274
274
275 celery.result.backend = amqp
275 celery.result.backend = amqp
276 celery.result.dburi = amqp://
276 celery.result.dburi = amqp://
277 celery.result.serialier = json
277 celery.result.serialier = json
278
278
279 #celery.send.task.error.emails = true
279 #celery.send.task.error.emails = true
280 #celery.amqp.task.result.expires = 18000
280 #celery.amqp.task.result.expires = 18000
281
281
282 celeryd.concurrency = 2
282 celeryd.concurrency = 2
283 #celeryd.log.file = celeryd.log
283 #celeryd.log.file = celeryd.log
284 celeryd.log.level = debug
284 celeryd.log.level = debug
285 celeryd.max.tasks.per.child = 1
285 celeryd.max.tasks.per.child = 1
286
286
287 ## tasks will never be sent to the queue, but executed locally instead.
287 ## tasks will never be sent to the queue, but executed locally instead.
288 celery.always.eager = false
288 celery.always.eager = false
289
289
290 ####################################
290 ####################################
291 ### BEAKER CACHE ####
291 ### BEAKER CACHE ####
292 ####################################
292 ####################################
293 # default cache dir for templates. Putting this into a ramdisk
293 # default cache dir for templates. Putting this into a ramdisk
294 ## can boost performance, eg. %(here)s/data_ramdisk
294 ## can boost performance, eg. %(here)s/data_ramdisk
295 cache_dir = %(here)s/data
295 cache_dir = %(here)s/data
296
296
297 ## locking and default file storage for Beaker. Putting this into a ramdisk
297 ## locking and default file storage for Beaker. Putting this into a ramdisk
298 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
298 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
299 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
299 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
300 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
300 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
301
301
302 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
302 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
303
303
304 beaker.cache.super_short_term.type = memory
304 beaker.cache.super_short_term.type = memory
305 beaker.cache.super_short_term.expire = 10
305 beaker.cache.super_short_term.expire = 10
306 beaker.cache.super_short_term.key_length = 256
306 beaker.cache.super_short_term.key_length = 256
307
307
308 beaker.cache.short_term.type = memory
308 beaker.cache.short_term.type = memory
309 beaker.cache.short_term.expire = 60
309 beaker.cache.short_term.expire = 60
310 beaker.cache.short_term.key_length = 256
310 beaker.cache.short_term.key_length = 256
311
311
312 beaker.cache.long_term.type = memory
312 beaker.cache.long_term.type = memory
313 beaker.cache.long_term.expire = 36000
313 beaker.cache.long_term.expire = 36000
314 beaker.cache.long_term.key_length = 256
314 beaker.cache.long_term.key_length = 256
315
315
316 beaker.cache.sql_cache_short.type = memory
316 beaker.cache.sql_cache_short.type = memory
317 beaker.cache.sql_cache_short.expire = 10
317 beaker.cache.sql_cache_short.expire = 10
318 beaker.cache.sql_cache_short.key_length = 256
318 beaker.cache.sql_cache_short.key_length = 256
319
319
320 # default is memory cache, configure only if required
320 # default is memory cache, configure only if required
321 # using multi-node or multi-worker setup
321 # using multi-node or multi-worker setup
322 #beaker.cache.auth_plugins.type = ext:database
322 #beaker.cache.auth_plugins.type = ext:database
323 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
323 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
324 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
324 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
325 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
325 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
326 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
326 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
327 #beaker.cache.auth_plugins.sa.pool_size = 10
327 #beaker.cache.auth_plugins.sa.pool_size = 10
328 #beaker.cache.auth_plugins.sa.max_overflow = 0
328 #beaker.cache.auth_plugins.sa.max_overflow = 0
329
329
330 beaker.cache.repo_cache_long.type = memorylru_base
330 beaker.cache.repo_cache_long.type = memorylru_base
331 beaker.cache.repo_cache_long.max_items = 4096
331 beaker.cache.repo_cache_long.max_items = 4096
332 beaker.cache.repo_cache_long.expire = 2592000
332 beaker.cache.repo_cache_long.expire = 2592000
333
333
334 # default is memorylru_base cache, configure only if required
334 # default is memorylru_base cache, configure only if required
335 # using multi-node or multi-worker setup
335 # using multi-node or multi-worker setup
336 #beaker.cache.repo_cache_long.type = ext:memcached
336 #beaker.cache.repo_cache_long.type = ext:memcached
337 #beaker.cache.repo_cache_long.url = localhost:11211
337 #beaker.cache.repo_cache_long.url = localhost:11211
338 #beaker.cache.repo_cache_long.expire = 1209600
338 #beaker.cache.repo_cache_long.expire = 1209600
339 #beaker.cache.repo_cache_long.key_length = 256
339 #beaker.cache.repo_cache_long.key_length = 256
340
340
341 ####################################
341 ####################################
342 ### BEAKER SESSION ####
342 ### BEAKER SESSION ####
343 ####################################
343 ####################################
344
344
345 ## .session.type is type of storage options for the session, current allowed
345 ## .session.type is type of storage options for the session, current allowed
346 ## types are file, ext:memcached, ext:database, and memory (default).
346 ## types are file, ext:memcached, ext:database, and memory (default).
347 beaker.session.type = file
347 beaker.session.type = file
348 beaker.session.data_dir = %(here)s/data/sessions/data
348 beaker.session.data_dir = %(here)s/data/sessions/data
349
349
350 ## db based session, fast, and allows easy management over logged in users ##
350 ## db based session, fast, and allows easy management over logged in users ##
351 #beaker.session.type = ext:database
351 #beaker.session.type = ext:database
352 #beaker.session.table_name = db_session
352 #beaker.session.table_name = db_session
353 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
353 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
354 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
354 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
355 #beaker.session.sa.pool_recycle = 3600
355 #beaker.session.sa.pool_recycle = 3600
356 #beaker.session.sa.echo = false
356 #beaker.session.sa.echo = false
357
357
358 beaker.session.key = rhodecode
358 beaker.session.key = rhodecode
359 beaker.session.secret = develop-rc-uytcxaz
359 beaker.session.secret = develop-rc-uytcxaz
360 beaker.session.lock_dir = %(here)s/data/sessions/lock
360 beaker.session.lock_dir = %(here)s/data/sessions/lock
361
361
362 ## Secure encrypted cookie. Requires AES and AES python libraries
362 ## Secure encrypted cookie. Requires AES and AES python libraries
363 ## you must disable beaker.session.secret to use this
363 ## you must disable beaker.session.secret to use this
364 #beaker.session.encrypt_key = <key_for_encryption>
364 #beaker.session.encrypt_key = <key_for_encryption>
365 #beaker.session.validate_key = <validation_key>
365 #beaker.session.validate_key = <validation_key>
366
366
367 ## sets session as invalid(also logging out user) if it haven not been
367 ## sets session as invalid(also logging out user) if it haven not been
368 ## accessed for given amount of time in seconds
368 ## accessed for given amount of time in seconds
369 beaker.session.timeout = 2592000
369 beaker.session.timeout = 2592000
370 beaker.session.httponly = true
370 beaker.session.httponly = true
371 #beaker.session.cookie_path = /<your-prefix>
371 #beaker.session.cookie_path = /<your-prefix>
372
372
373 ## uncomment for https secure cookie
373 ## uncomment for https secure cookie
374 beaker.session.secure = false
374 beaker.session.secure = false
375
375
376 ## auto save the session to not to use .save()
376 ## auto save the session to not to use .save()
377 beaker.session.auto = false
377 beaker.session.auto = false
378
378
379 ## default cookie expiration time in seconds, set to `true` to set expire
379 ## default cookie expiration time in seconds, set to `true` to set expire
380 ## at browser close
380 ## at browser close
381 #beaker.session.cookie_expires = 3600
381 #beaker.session.cookie_expires = 3600
382
382
383 ###################################
383 ###################################
384 ## SEARCH INDEXING CONFIGURATION ##
384 ## SEARCH INDEXING CONFIGURATION ##
385 ###################################
385 ###################################
386 ## Full text search indexer is available in rhodecode-tools under
386 ## Full text search indexer is available in rhodecode-tools under
387 ## `rhodecode-tools index` command
387 ## `rhodecode-tools index` command
388
388
389 # WHOOSH Backend, doesn't require additional services to run
389 # WHOOSH Backend, doesn't require additional services to run
390 # it works good with few dozen repos
390 # it works good with few dozen repos
391 search.module = rhodecode.lib.index.whoosh
391 search.module = rhodecode.lib.index.whoosh
392 search.location = %(here)s/data/index
392 search.location = %(here)s/data/index
393
393
394 ########################################
395 ### CHANNELSTREAM CONFIG ####
396 ########################################
397
398 channelstream.enabled = true
399 # location of channelstream server on the backend
400 channelstream.server = 127.0.0.1:9800
401 # location of the channelstream server from outside world
402 channelstream.ws_url = ws://127.0.0.1:9800
403 channelstream.secret = secret
404
394 ###################################
405 ###################################
395 ## APPENLIGHT CONFIG ##
406 ## APPENLIGHT CONFIG ##
396 ###################################
407 ###################################
397
408
398 ## Appenlight is tailored to work with RhodeCode, see
409 ## Appenlight is tailored to work with RhodeCode, see
399 ## http://appenlight.com for details how to obtain an account
410 ## http://appenlight.com for details how to obtain an account
400
411
401 ## appenlight integration enabled
412 ## appenlight integration enabled
402 appenlight = false
413 appenlight = false
403
414
404 appenlight.server_url = https://api.appenlight.com
415 appenlight.server_url = https://api.appenlight.com
405 appenlight.api_key = YOUR_API_KEY
416 appenlight.api_key = YOUR_API_KEY
406 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
417 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
407
418
408 # used for JS client
419 # used for JS client
409 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
420 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
410
421
411 ## TWEAK AMOUNT OF INFO SENT HERE
422 ## TWEAK AMOUNT OF INFO SENT HERE
412
423
413 ## enables 404 error logging (default False)
424 ## enables 404 error logging (default False)
414 appenlight.report_404 = false
425 appenlight.report_404 = false
415
426
416 ## time in seconds after request is considered being slow (default 1)
427 ## time in seconds after request is considered being slow (default 1)
417 appenlight.slow_request_time = 1
428 appenlight.slow_request_time = 1
418
429
419 ## record slow requests in application
430 ## record slow requests in application
420 ## (needs to be enabled for slow datastore recording and time tracking)
431 ## (needs to be enabled for slow datastore recording and time tracking)
421 appenlight.slow_requests = true
432 appenlight.slow_requests = true
422
433
423 ## enable hooking to application loggers
434 ## enable hooking to application loggers
424 appenlight.logging = true
435 appenlight.logging = true
425
436
426 ## minimum log level for log capture
437 ## minimum log level for log capture
427 appenlight.logging.level = WARNING
438 appenlight.logging.level = WARNING
428
439
429 ## send logs only from erroneous/slow requests
440 ## send logs only from erroneous/slow requests
430 ## (saves API quota for intensive logging)
441 ## (saves API quota for intensive logging)
431 appenlight.logging_on_error = false
442 appenlight.logging_on_error = false
432
443
433 ## list of additonal keywords that should be grabbed from environ object
444 ## list of additonal keywords that should be grabbed from environ object
434 ## can be string with comma separated list of words in lowercase
445 ## can be string with comma separated list of words in lowercase
435 ## (by default client will always send following info:
446 ## (by default client will always send following info:
436 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
447 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
437 ## start with HTTP* this list be extended with additional keywords here
448 ## start with HTTP* this list be extended with additional keywords here
438 appenlight.environ_keys_whitelist =
449 appenlight.environ_keys_whitelist =
439
450
440 ## list of keywords that should be blanked from request object
451 ## list of keywords that should be blanked from request object
441 ## can be string with comma separated list of words in lowercase
452 ## can be string with comma separated list of words in lowercase
442 ## (by default client will always blank keys that contain following words
453 ## (by default client will always blank keys that contain following words
443 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
454 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
444 ## this list be extended with additional keywords set here
455 ## this list be extended with additional keywords set here
445 appenlight.request_keys_blacklist =
456 appenlight.request_keys_blacklist =
446
457
447 ## list of namespaces that should be ignores when gathering log entries
458 ## list of namespaces that should be ignores when gathering log entries
448 ## can be string with comma separated list of namespaces
459 ## can be string with comma separated list of namespaces
449 ## (by default the client ignores own entries: appenlight_client.client)
460 ## (by default the client ignores own entries: appenlight_client.client)
450 appenlight.log_namespace_blacklist =
461 appenlight.log_namespace_blacklist =
451
462
452
463
453 ################################################################################
464 ################################################################################
454 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
465 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
455 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
466 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
456 ## execute malicious code after an exception is raised. ##
467 ## execute malicious code after an exception is raised. ##
457 ################################################################################
468 ################################################################################
458 #set debug = false
469 #set debug = false
459
470
460
471
461 ##############
472 ##############
462 ## STYLING ##
473 ## STYLING ##
463 ##############
474 ##############
464 debug_style = true
475 debug_style = true
465
476
466 #########################################################
477 #########################################################
467 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
478 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
468 #########################################################
479 #########################################################
469 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
480 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
470 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
481 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
471 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
482 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
472
483
473 # see sqlalchemy docs for other advanced settings
484 # see sqlalchemy docs for other advanced settings
474
485
475 ## print the sql statements to output
486 ## print the sql statements to output
476 sqlalchemy.db1.echo = false
487 sqlalchemy.db1.echo = false
477 ## recycle the connections after this ammount of seconds
488 ## recycle the connections after this ammount of seconds
478 sqlalchemy.db1.pool_recycle = 3600
489 sqlalchemy.db1.pool_recycle = 3600
479 sqlalchemy.db1.convert_unicode = true
490 sqlalchemy.db1.convert_unicode = true
480
491
481 ## the number of connections to keep open inside the connection pool.
492 ## the number of connections to keep open inside the connection pool.
482 ## 0 indicates no limit
493 ## 0 indicates no limit
483 #sqlalchemy.db1.pool_size = 5
494 #sqlalchemy.db1.pool_size = 5
484
495
485 ## the number of connections to allow in connection pool "overflow", that is
496 ## the number of connections to allow in connection pool "overflow", that is
486 ## connections that can be opened above and beyond the pool_size setting,
497 ## connections that can be opened above and beyond the pool_size setting,
487 ## which defaults to five.
498 ## which defaults to five.
488 #sqlalchemy.db1.max_overflow = 10
499 #sqlalchemy.db1.max_overflow = 10
489
500
490
501
491 ##################
502 ##################
492 ### VCS CONFIG ###
503 ### VCS CONFIG ###
493 ##################
504 ##################
494 vcs.server.enable = true
505 vcs.server.enable = true
495 vcs.server = localhost:9900
506 vcs.server = localhost:9900
496
507
497 ## Web server connectivity protocol, responsible for web based VCS operatations
508 ## Web server connectivity protocol, responsible for web based VCS operatations
498 ## Available protocols are:
509 ## Available protocols are:
499 ## `pyro4` - using pyro4 server
510 ## `pyro4` - using pyro4 server
500 ## `http` - using http-rpc backend
511 ## `http` - using http-rpc backend
501 vcs.server.protocol = http
512 vcs.server.protocol = http
502
513
503 ## Push/Pull operations protocol, available options are:
514 ## Push/Pull operations protocol, available options are:
504 ## `pyro4` - using pyro4 server
515 ## `pyro4` - using pyro4 server
505 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
516 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
506 ## `vcsserver.scm_app` - internal app (EE only)
517 ## `vcsserver.scm_app` - internal app (EE only)
507 vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
518 vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
508
519
509 ## Push/Pull operations hooks protocol, available options are:
520 ## Push/Pull operations hooks protocol, available options are:
510 ## `pyro4` - using pyro4 server
521 ## `pyro4` - using pyro4 server
511 ## `http` - using http-rpc backend
522 ## `http` - using http-rpc backend
512 vcs.hooks.protocol = http
523 vcs.hooks.protocol = http
513
524
514 vcs.server.log_level = debug
525 vcs.server.log_level = debug
515 ## Start VCSServer with this instance as a subprocess, usefull for development
526 ## Start VCSServer with this instance as a subprocess, usefull for development
516 vcs.start_server = true
527 vcs.start_server = true
517 vcs.backends = hg, git, svn
528 vcs.backends = hg, git, svn
518 vcs.connection_timeout = 3600
529 vcs.connection_timeout = 3600
519 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
530 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
520 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
531 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
521 #vcs.svn.compatible_version = pre-1.8-compatible
532 #vcs.svn.compatible_version = pre-1.8-compatible
522
533
523 ################################
534 ################################
524 ### LOGGING CONFIGURATION ####
535 ### LOGGING CONFIGURATION ####
525 ################################
536 ################################
526 [loggers]
537 [loggers]
527 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates
538 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates
528
539
529 [handlers]
540 [handlers]
530 keys = console, console_sql
541 keys = console, console_sql
531
542
532 [formatters]
543 [formatters]
533 keys = generic, color_formatter, color_formatter_sql
544 keys = generic, color_formatter, color_formatter_sql
534
545
535 #############
546 #############
536 ## LOGGERS ##
547 ## LOGGERS ##
537 #############
548 #############
538 [logger_root]
549 [logger_root]
539 level = NOTSET
550 level = NOTSET
540 handlers = console
551 handlers = console
541
552
542 [logger_routes]
553 [logger_routes]
543 level = DEBUG
554 level = DEBUG
544 handlers =
555 handlers =
545 qualname = routes.middleware
556 qualname = routes.middleware
546 ## "level = DEBUG" logs the route matched and routing variables.
557 ## "level = DEBUG" logs the route matched and routing variables.
547 propagate = 1
558 propagate = 1
548
559
549 [logger_beaker]
560 [logger_beaker]
550 level = DEBUG
561 level = DEBUG
551 handlers =
562 handlers =
552 qualname = beaker.container
563 qualname = beaker.container
553 propagate = 1
564 propagate = 1
554
565
555 [logger_pyro4]
566 [logger_pyro4]
556 level = DEBUG
567 level = DEBUG
557 handlers =
568 handlers =
558 qualname = Pyro4
569 qualname = Pyro4
559 propagate = 1
570 propagate = 1
560
571
561 [logger_templates]
572 [logger_templates]
562 level = INFO
573 level = INFO
563 handlers =
574 handlers =
564 qualname = pylons.templating
575 qualname = pylons.templating
565 propagate = 1
576 propagate = 1
566
577
567 [logger_rhodecode]
578 [logger_rhodecode]
568 level = DEBUG
579 level = DEBUG
569 handlers =
580 handlers =
570 qualname = rhodecode
581 qualname = rhodecode
571 propagate = 1
582 propagate = 1
572
583
573 [logger_sqlalchemy]
584 [logger_sqlalchemy]
574 level = INFO
585 level = INFO
575 handlers = console_sql
586 handlers = console_sql
576 qualname = sqlalchemy.engine
587 qualname = sqlalchemy.engine
577 propagate = 0
588 propagate = 0
578
589
579 ##############
590 ##############
580 ## HANDLERS ##
591 ## HANDLERS ##
581 ##############
592 ##############
582
593
583 [handler_console]
594 [handler_console]
584 class = StreamHandler
595 class = StreamHandler
585 args = (sys.stderr,)
596 args = (sys.stderr,)
586 level = DEBUG
597 level = DEBUG
587 formatter = color_formatter
598 formatter = color_formatter
588
599
589 [handler_console_sql]
600 [handler_console_sql]
590 class = StreamHandler
601 class = StreamHandler
591 args = (sys.stderr,)
602 args = (sys.stderr,)
592 level = DEBUG
603 level = DEBUG
593 formatter = color_formatter_sql
604 formatter = color_formatter_sql
594
605
595 ################
606 ################
596 ## FORMATTERS ##
607 ## FORMATTERS ##
597 ################
608 ################
598
609
599 [formatter_generic]
610 [formatter_generic]
600 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
611 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
601 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
612 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
602 datefmt = %Y-%m-%d %H:%M:%S
613 datefmt = %Y-%m-%d %H:%M:%S
603
614
604 [formatter_color_formatter]
615 [formatter_color_formatter]
605 class = rhodecode.lib.logging_formatter.ColorFormatter
616 class = rhodecode.lib.logging_formatter.ColorFormatter
606 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
617 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
607 datefmt = %Y-%m-%d %H:%M:%S
618 datefmt = %Y-%m-%d %H:%M:%S
608
619
609 [formatter_color_formatter_sql]
620 [formatter_color_formatter_sql]
610 class = rhodecode.lib.logging_formatter.ColorFormatterSql
621 class = rhodecode.lib.logging_formatter.ColorFormatterSql
611 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
622 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
612 datefmt = %Y-%m-%d %H:%M:%S
623 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,581 +1,592 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode Enterprise - configuration file #
3 # RhodeCode Enterprise - configuration file #
4 # Built-in functions and variables #
4 # Built-in functions and variables #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 # #
6 # #
7 ################################################################################
7 ################################################################################
8
8
9 [DEFAULT]
9 [DEFAULT]
10 debug = true
10 debug = true
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the email address which should receive ##
12 ## Uncomment and replace with the email address which should receive ##
13 ## any error reports after an application crash ##
13 ## any error reports after an application crash ##
14 ## Additionally these settings will be used by the RhodeCode mailing system ##
14 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20 #email_prefix = [RhodeCode]
21
21
22 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
23 #smtp_username =
23 #smtp_username =
24 #smtp_password =
24 #smtp_password =
25 #smtp_port =
25 #smtp_port =
26 #smtp_use_tls = false
26 #smtp_use_tls = false
27 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 ## Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
29 #smtp_auth =
30
30
31 [server:main]
31 [server:main]
32 ## COMMON ##
32 ## COMMON ##
33 host = 127.0.0.1
33 host = 127.0.0.1
34 port = 5000
34 port = 5000
35
35
36 ##################################
36 ##################################
37 ## WAITRESS WSGI SERVER ##
37 ## WAITRESS WSGI SERVER ##
38 ## Recommended for Development ##
38 ## Recommended for Development ##
39 ##################################
39 ##################################
40 #use = egg:waitress#main
40 #use = egg:waitress#main
41 ## number of worker threads
41 ## number of worker threads
42 #threads = 5
42 #threads = 5
43 ## MAX BODY SIZE 100GB
43 ## MAX BODY SIZE 100GB
44 #max_request_body_size = 107374182400
44 #max_request_body_size = 107374182400
45 ## Use poll instead of select, fixes file descriptors limits problems.
45 ## Use poll instead of select, fixes file descriptors limits problems.
46 ## May not work on old windows systems.
46 ## May not work on old windows systems.
47 #asyncore_use_poll = true
47 #asyncore_use_poll = true
48
48
49
49
50 ##########################
50 ##########################
51 ## GUNICORN WSGI SERVER ##
51 ## GUNICORN WSGI SERVER ##
52 ##########################
52 ##########################
53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
53 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
54 use = egg:gunicorn#main
54 use = egg:gunicorn#main
55 ## Sets the number of process workers. You must set `instance_id = *`
55 ## Sets the number of process workers. You must set `instance_id = *`
56 ## when this option is set to more than one worker, recommended
56 ## when this option is set to more than one worker, recommended
57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
57 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
58 ## The `instance_id = *` must be set in the [app:main] section below
58 ## The `instance_id = *` must be set in the [app:main] section below
59 workers = 2
59 workers = 2
60 ## number of threads for each of the worker, must be set to 1 for gevent
60 ## number of threads for each of the worker, must be set to 1 for gevent
61 ## generally recommened to be at 1
61 ## generally recommened to be at 1
62 #threads = 1
62 #threads = 1
63 ## process name
63 ## process name
64 proc_name = rhodecode
64 proc_name = rhodecode
65 ## type of worker class, one of sync, gevent
65 ## type of worker class, one of sync, gevent
66 ## recommended for bigger setup is using of of other than sync one
66 ## recommended for bigger setup is using of of other than sync one
67 worker_class = sync
67 worker_class = sync
68 ## The maximum number of simultaneous clients. Valid only for Gevent
68 ## The maximum number of simultaneous clients. Valid only for Gevent
69 #worker_connections = 10
69 #worker_connections = 10
70 ## max number of requests that worker will handle before being gracefully
70 ## max number of requests that worker will handle before being gracefully
71 ## restarted, could prevent memory leaks
71 ## restarted, could prevent memory leaks
72 max_requests = 1000
72 max_requests = 1000
73 max_requests_jitter = 30
73 max_requests_jitter = 30
74 ## amount of time a worker can spend with handling a request before it
74 ## amount of time a worker can spend with handling a request before it
75 ## gets killed and restarted. Set to 6hrs
75 ## gets killed and restarted. Set to 6hrs
76 timeout = 21600
76 timeout = 21600
77
77
78
78
79 ## prefix middleware for RhodeCode, disables force_https flag.
79 ## prefix middleware for RhodeCode, disables force_https flag.
80 ## allows to set RhodeCode under a prefix in server.
80 ## allows to set RhodeCode under a prefix in server.
81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
81 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 #[filter:proxy-prefix]
82 #[filter:proxy-prefix]
83 #use = egg:PasteDeploy#prefix
83 #use = egg:PasteDeploy#prefix
84 #prefix = /<your-prefix>
84 #prefix = /<your-prefix>
85
85
86 [app:main]
86 [app:main]
87 use = egg:rhodecode-enterprise-ce
87 use = egg:rhodecode-enterprise-ce
88 ## enable proxy prefix middleware, defined below
88 ## enable proxy prefix middleware, defined below
89 #filter-with = proxy-prefix
89 #filter-with = proxy-prefix
90
90
91 ## encryption key used to encrypt social plugin tokens,
91 ## encryption key used to encrypt social plugin tokens,
92 ## remote_urls with credentials etc, if not set it defaults to
92 ## remote_urls with credentials etc, if not set it defaults to
93 ## `beaker.session.secret`
93 ## `beaker.session.secret`
94 #rhodecode.encrypted_values.secret =
94 #rhodecode.encrypted_values.secret =
95
95
96 ## decryption strict mode (enabled by default). It controls if decryption raises
96 ## decryption strict mode (enabled by default). It controls if decryption raises
97 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
97 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
98 #rhodecode.encrypted_values.strict = false
98 #rhodecode.encrypted_values.strict = false
99
99
100 full_stack = true
100 full_stack = true
101
101
102 ## return gzipped responses from Rhodecode (static files/application)
102 ## return gzipped responses from Rhodecode (static files/application)
103 gzip_responses = true
103 gzip_responses = true
104
104
105 # autogenerate javascript routes file on startup
105 # autogenerate javascript routes file on startup
106 generate_js_files = false
106 generate_js_files = false
107
107
108 ## Optional Languages
108 ## Optional Languages
109 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
109 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
110 lang = en
110 lang = en
111
111
112 ## perform a full repository scan on each server start, this should be
112 ## perform a full repository scan on each server start, this should be
113 ## set to false after first startup, to allow faster server restarts.
113 ## set to false after first startup, to allow faster server restarts.
114 startup.import_repos = false
114 startup.import_repos = false
115
115
116 ## Uncomment and set this path to use archive download cache.
116 ## Uncomment and set this path to use archive download cache.
117 ## Once enabled, generated archives will be cached at this location
117 ## Once enabled, generated archives will be cached at this location
118 ## and served from the cache during subsequent requests for the same archive of
118 ## and served from the cache during subsequent requests for the same archive of
119 ## the repository.
119 ## the repository.
120 #archive_cache_dir = /tmp/tarballcache
120 #archive_cache_dir = /tmp/tarballcache
121
121
122 ## change this to unique ID for security
122 ## change this to unique ID for security
123 app_instance_uuid = rc-production
123 app_instance_uuid = rc-production
124
124
125 ## cut off limit for large diffs (size in bytes)
125 ## cut off limit for large diffs (size in bytes)
126 cut_off_limit_diff = 1024000
126 cut_off_limit_diff = 1024000
127 cut_off_limit_file = 256000
127 cut_off_limit_file = 256000
128
128
129 ## use cache version of scm repo everywhere
129 ## use cache version of scm repo everywhere
130 vcs_full_cache = true
130 vcs_full_cache = true
131
131
132 ## force https in RhodeCode, fixes https redirects, assumes it's always https
132 ## force https in RhodeCode, fixes https redirects, assumes it's always https
133 ## Normally this is controlled by proper http flags sent from http server
133 ## Normally this is controlled by proper http flags sent from http server
134 force_https = false
134 force_https = false
135
135
136 ## use Strict-Transport-Security headers
136 ## use Strict-Transport-Security headers
137 use_htsts = false
137 use_htsts = false
138
138
139 ## number of commits stats will parse on each iteration
139 ## number of commits stats will parse on each iteration
140 commit_parse_limit = 25
140 commit_parse_limit = 25
141
141
142 ## git rev filter option, --all is the default filter, if you need to
142 ## git rev filter option, --all is the default filter, if you need to
143 ## hide all refs in changelog switch this to --branches --tags
143 ## hide all refs in changelog switch this to --branches --tags
144 git_rev_filter = --branches --tags
144 git_rev_filter = --branches --tags
145
145
146 # Set to true if your repos are exposed using the dumb protocol
146 # Set to true if your repos are exposed using the dumb protocol
147 git_update_server_info = false
147 git_update_server_info = false
148
148
149 ## RSS/ATOM feed options
149 ## RSS/ATOM feed options
150 rss_cut_off_limit = 256000
150 rss_cut_off_limit = 256000
151 rss_items_per_page = 10
151 rss_items_per_page = 10
152 rss_include_diff = false
152 rss_include_diff = false
153
153
154 ## gist URL alias, used to create nicer urls for gist. This should be an
154 ## gist URL alias, used to create nicer urls for gist. This should be an
155 ## url that does rewrites to _admin/gists/<gistid>.
155 ## url that does rewrites to _admin/gists/<gistid>.
156 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
156 ## example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
157 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
157 ## RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/<gistid>
158 gist_alias_url =
158 gist_alias_url =
159
159
160 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
160 ## List of controllers (using glob pattern syntax) that AUTH TOKENS could be
161 ## used for access.
161 ## used for access.
162 ## Adding ?auth_token = <token> to the url authenticates this request as if it
162 ## Adding ?auth_token = <token> to the url authenticates this request as if it
163 ## came from the the logged in user who own this authentication token.
163 ## came from the the logged in user who own this authentication token.
164 ##
164 ##
165 ## Syntax is <ControllerClass>:<function_pattern>.
165 ## Syntax is <ControllerClass>:<function_pattern>.
166 ## To enable access to raw_files put `FilesController:raw`.
166 ## To enable access to raw_files put `FilesController:raw`.
167 ## To enable access to patches add `ChangesetController:changeset_patch`.
167 ## To enable access to patches add `ChangesetController:changeset_patch`.
168 ## The list should be "," separated and on a single line.
168 ## The list should be "," separated and on a single line.
169 ##
169 ##
170 ## Recommended controllers to enable:
170 ## Recommended controllers to enable:
171 # ChangesetController:changeset_patch,
171 # ChangesetController:changeset_patch,
172 # ChangesetController:changeset_raw,
172 # ChangesetController:changeset_raw,
173 # FilesController:raw,
173 # FilesController:raw,
174 # FilesController:archivefile,
174 # FilesController:archivefile,
175 # GistsController:*,
175 # GistsController:*,
176 api_access_controllers_whitelist =
176 api_access_controllers_whitelist =
177
177
178 ## default encoding used to convert from and to unicode
178 ## default encoding used to convert from and to unicode
179 ## can be also a comma separated list of encoding in case of mixed encodings
179 ## can be also a comma separated list of encoding in case of mixed encodings
180 default_encoding = UTF-8
180 default_encoding = UTF-8
181
181
182 ## instance-id prefix
182 ## instance-id prefix
183 ## a prefix key for this instance used for cache invalidation when running
183 ## a prefix key for this instance used for cache invalidation when running
184 ## multiple instances of rhodecode, make sure it's globally unique for
184 ## multiple instances of rhodecode, make sure it's globally unique for
185 ## all running rhodecode instances. Leave empty if you don't use it
185 ## all running rhodecode instances. Leave empty if you don't use it
186 instance_id =
186 instance_id =
187
187
188 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
188 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
189 ## of an authentication plugin also if it is disabled by it's settings.
189 ## of an authentication plugin also if it is disabled by it's settings.
190 ## This could be useful if you are unable to log in to the system due to broken
190 ## This could be useful if you are unable to log in to the system due to broken
191 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
191 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
192 ## module to log in again and fix the settings.
192 ## module to log in again and fix the settings.
193 ##
193 ##
194 ## Available builtin plugin IDs (hash is part of the ID):
194 ## Available builtin plugin IDs (hash is part of the ID):
195 ## egg:rhodecode-enterprise-ce#rhodecode
195 ## egg:rhodecode-enterprise-ce#rhodecode
196 ## egg:rhodecode-enterprise-ce#pam
196 ## egg:rhodecode-enterprise-ce#pam
197 ## egg:rhodecode-enterprise-ce#ldap
197 ## egg:rhodecode-enterprise-ce#ldap
198 ## egg:rhodecode-enterprise-ce#jasig_cas
198 ## egg:rhodecode-enterprise-ce#jasig_cas
199 ## egg:rhodecode-enterprise-ce#headers
199 ## egg:rhodecode-enterprise-ce#headers
200 ## egg:rhodecode-enterprise-ce#crowd
200 ## egg:rhodecode-enterprise-ce#crowd
201 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
201 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
202
202
203 ## alternative return HTTP header for failed authentication. Default HTTP
203 ## alternative return HTTP header for failed authentication. Default HTTP
204 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
204 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
205 ## handling that causing a series of failed authentication calls.
205 ## handling that causing a series of failed authentication calls.
206 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
206 ## Set this variable to 403 to return HTTPForbidden, or any other HTTP code
207 ## This will be served instead of default 401 on bad authnetication
207 ## This will be served instead of default 401 on bad authnetication
208 auth_ret_code =
208 auth_ret_code =
209
209
210 ## use special detection method when serving auth_ret_code, instead of serving
210 ## use special detection method when serving auth_ret_code, instead of serving
211 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
211 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
212 ## and then serve auth_ret_code to clients
212 ## and then serve auth_ret_code to clients
213 auth_ret_code_detection = false
213 auth_ret_code_detection = false
214
214
215 ## locking return code. When repository is locked return this HTTP code. 2XX
215 ## locking return code. When repository is locked return this HTTP code. 2XX
216 ## codes don't break the transactions while 4XX codes do
216 ## codes don't break the transactions while 4XX codes do
217 lock_ret_code = 423
217 lock_ret_code = 423
218
218
219 ## allows to change the repository location in settings page
219 ## allows to change the repository location in settings page
220 allow_repo_location_change = true
220 allow_repo_location_change = true
221
221
222 ## allows to setup custom hooks in settings page
222 ## allows to setup custom hooks in settings page
223 allow_custom_hooks_settings = true
223 allow_custom_hooks_settings = true
224
224
225 ## generated license token, goto license page in RhodeCode settings to obtain
225 ## generated license token, goto license page in RhodeCode settings to obtain
226 ## new token
226 ## new token
227 license_token =
227 license_token =
228
228
229 ## supervisor connection uri, for managing supervisor and logs.
229 ## supervisor connection uri, for managing supervisor and logs.
230 supervisor.uri =
230 supervisor.uri =
231 ## supervisord group name/id we only want this RC instance to handle
231 ## supervisord group name/id we only want this RC instance to handle
232 supervisor.group_id = prod
232 supervisor.group_id = prod
233
233
234 ## Display extended labs settings
234 ## Display extended labs settings
235 labs_settings_active = true
235 labs_settings_active = true
236
236
237 ####################################
237 ####################################
238 ### CELERY CONFIG ####
238 ### CELERY CONFIG ####
239 ####################################
239 ####################################
240 use_celery = false
240 use_celery = false
241 broker.host = localhost
241 broker.host = localhost
242 broker.vhost = rabbitmqhost
242 broker.vhost = rabbitmqhost
243 broker.port = 5672
243 broker.port = 5672
244 broker.user = rabbitmq
244 broker.user = rabbitmq
245 broker.password = qweqwe
245 broker.password = qweqwe
246
246
247 celery.imports = rhodecode.lib.celerylib.tasks
247 celery.imports = rhodecode.lib.celerylib.tasks
248
248
249 celery.result.backend = amqp
249 celery.result.backend = amqp
250 celery.result.dburi = amqp://
250 celery.result.dburi = amqp://
251 celery.result.serialier = json
251 celery.result.serialier = json
252
252
253 #celery.send.task.error.emails = true
253 #celery.send.task.error.emails = true
254 #celery.amqp.task.result.expires = 18000
254 #celery.amqp.task.result.expires = 18000
255
255
256 celeryd.concurrency = 2
256 celeryd.concurrency = 2
257 #celeryd.log.file = celeryd.log
257 #celeryd.log.file = celeryd.log
258 celeryd.log.level = debug
258 celeryd.log.level = debug
259 celeryd.max.tasks.per.child = 1
259 celeryd.max.tasks.per.child = 1
260
260
261 ## tasks will never be sent to the queue, but executed locally instead.
261 ## tasks will never be sent to the queue, but executed locally instead.
262 celery.always.eager = false
262 celery.always.eager = false
263
263
264 ####################################
264 ####################################
265 ### BEAKER CACHE ####
265 ### BEAKER CACHE ####
266 ####################################
266 ####################################
267 # default cache dir for templates. Putting this into a ramdisk
267 # default cache dir for templates. Putting this into a ramdisk
268 ## can boost performance, eg. %(here)s/data_ramdisk
268 ## can boost performance, eg. %(here)s/data_ramdisk
269 cache_dir = %(here)s/data
269 cache_dir = %(here)s/data
270
270
271 ## locking and default file storage for Beaker. Putting this into a ramdisk
271 ## locking and default file storage for Beaker. Putting this into a ramdisk
272 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
272 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
273 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
273 beaker.cache.data_dir = %(here)s/data/cache/beaker_data
274 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
274 beaker.cache.lock_dir = %(here)s/data/cache/beaker_lock
275
275
276 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
276 beaker.cache.regions = super_short_term, short_term, long_term, sql_cache_short, auth_plugins, repo_cache_long
277
277
278 beaker.cache.super_short_term.type = memory
278 beaker.cache.super_short_term.type = memory
279 beaker.cache.super_short_term.expire = 10
279 beaker.cache.super_short_term.expire = 10
280 beaker.cache.super_short_term.key_length = 256
280 beaker.cache.super_short_term.key_length = 256
281
281
282 beaker.cache.short_term.type = memory
282 beaker.cache.short_term.type = memory
283 beaker.cache.short_term.expire = 60
283 beaker.cache.short_term.expire = 60
284 beaker.cache.short_term.key_length = 256
284 beaker.cache.short_term.key_length = 256
285
285
286 beaker.cache.long_term.type = memory
286 beaker.cache.long_term.type = memory
287 beaker.cache.long_term.expire = 36000
287 beaker.cache.long_term.expire = 36000
288 beaker.cache.long_term.key_length = 256
288 beaker.cache.long_term.key_length = 256
289
289
290 beaker.cache.sql_cache_short.type = memory
290 beaker.cache.sql_cache_short.type = memory
291 beaker.cache.sql_cache_short.expire = 10
291 beaker.cache.sql_cache_short.expire = 10
292 beaker.cache.sql_cache_short.key_length = 256
292 beaker.cache.sql_cache_short.key_length = 256
293
293
294 # default is memory cache, configure only if required
294 # default is memory cache, configure only if required
295 # using multi-node or multi-worker setup
295 # using multi-node or multi-worker setup
296 #beaker.cache.auth_plugins.type = ext:database
296 #beaker.cache.auth_plugins.type = ext:database
297 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
297 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
298 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
298 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
299 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
299 #beaker.cache.auth_plugins.url = mysql://root:secret@127.0.0.1/rhodecode
300 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
300 #beaker.cache.auth_plugins.sa.pool_recycle = 3600
301 #beaker.cache.auth_plugins.sa.pool_size = 10
301 #beaker.cache.auth_plugins.sa.pool_size = 10
302 #beaker.cache.auth_plugins.sa.max_overflow = 0
302 #beaker.cache.auth_plugins.sa.max_overflow = 0
303
303
304 beaker.cache.repo_cache_long.type = memorylru_base
304 beaker.cache.repo_cache_long.type = memorylru_base
305 beaker.cache.repo_cache_long.max_items = 4096
305 beaker.cache.repo_cache_long.max_items = 4096
306 beaker.cache.repo_cache_long.expire = 2592000
306 beaker.cache.repo_cache_long.expire = 2592000
307
307
308 # default is memorylru_base cache, configure only if required
308 # default is memorylru_base cache, configure only if required
309 # using multi-node or multi-worker setup
309 # using multi-node or multi-worker setup
310 #beaker.cache.repo_cache_long.type = ext:memcached
310 #beaker.cache.repo_cache_long.type = ext:memcached
311 #beaker.cache.repo_cache_long.url = localhost:11211
311 #beaker.cache.repo_cache_long.url = localhost:11211
312 #beaker.cache.repo_cache_long.expire = 1209600
312 #beaker.cache.repo_cache_long.expire = 1209600
313 #beaker.cache.repo_cache_long.key_length = 256
313 #beaker.cache.repo_cache_long.key_length = 256
314
314
315 ####################################
315 ####################################
316 ### BEAKER SESSION ####
316 ### BEAKER SESSION ####
317 ####################################
317 ####################################
318
318
319 ## .session.type is type of storage options for the session, current allowed
319 ## .session.type is type of storage options for the session, current allowed
320 ## types are file, ext:memcached, ext:database, and memory (default).
320 ## types are file, ext:memcached, ext:database, and memory (default).
321 beaker.session.type = file
321 beaker.session.type = file
322 beaker.session.data_dir = %(here)s/data/sessions/data
322 beaker.session.data_dir = %(here)s/data/sessions/data
323
323
324 ## db based session, fast, and allows easy management over logged in users ##
324 ## db based session, fast, and allows easy management over logged in users ##
325 #beaker.session.type = ext:database
325 #beaker.session.type = ext:database
326 #beaker.session.table_name = db_session
326 #beaker.session.table_name = db_session
327 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
327 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
328 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
328 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
329 #beaker.session.sa.pool_recycle = 3600
329 #beaker.session.sa.pool_recycle = 3600
330 #beaker.session.sa.echo = false
330 #beaker.session.sa.echo = false
331
331
332 beaker.session.key = rhodecode
332 beaker.session.key = rhodecode
333 beaker.session.secret = production-rc-uytcxaz
333 beaker.session.secret = production-rc-uytcxaz
334 beaker.session.lock_dir = %(here)s/data/sessions/lock
334 beaker.session.lock_dir = %(here)s/data/sessions/lock
335
335
336 ## Secure encrypted cookie. Requires AES and AES python libraries
336 ## Secure encrypted cookie. Requires AES and AES python libraries
337 ## you must disable beaker.session.secret to use this
337 ## you must disable beaker.session.secret to use this
338 #beaker.session.encrypt_key = <key_for_encryption>
338 #beaker.session.encrypt_key = <key_for_encryption>
339 #beaker.session.validate_key = <validation_key>
339 #beaker.session.validate_key = <validation_key>
340
340
341 ## sets session as invalid(also logging out user) if it haven not been
341 ## sets session as invalid(also logging out user) if it haven not been
342 ## accessed for given amount of time in seconds
342 ## accessed for given amount of time in seconds
343 beaker.session.timeout = 2592000
343 beaker.session.timeout = 2592000
344 beaker.session.httponly = true
344 beaker.session.httponly = true
345 #beaker.session.cookie_path = /<your-prefix>
345 #beaker.session.cookie_path = /<your-prefix>
346
346
347 ## uncomment for https secure cookie
347 ## uncomment for https secure cookie
348 beaker.session.secure = false
348 beaker.session.secure = false
349
349
350 ## auto save the session to not to use .save()
350 ## auto save the session to not to use .save()
351 beaker.session.auto = false
351 beaker.session.auto = false
352
352
353 ## default cookie expiration time in seconds, set to `true` to set expire
353 ## default cookie expiration time in seconds, set to `true` to set expire
354 ## at browser close
354 ## at browser close
355 #beaker.session.cookie_expires = 3600
355 #beaker.session.cookie_expires = 3600
356
356
357 ###################################
357 ###################################
358 ## SEARCH INDEXING CONFIGURATION ##
358 ## SEARCH INDEXING CONFIGURATION ##
359 ###################################
359 ###################################
360 ## Full text search indexer is available in rhodecode-tools under
360 ## Full text search indexer is available in rhodecode-tools under
361 ## `rhodecode-tools index` command
361 ## `rhodecode-tools index` command
362
362
363 # WHOOSH Backend, doesn't require additional services to run
363 # WHOOSH Backend, doesn't require additional services to run
364 # it works good with few dozen repos
364 # it works good with few dozen repos
365 search.module = rhodecode.lib.index.whoosh
365 search.module = rhodecode.lib.index.whoosh
366 search.location = %(here)s/data/index
366 search.location = %(here)s/data/index
367
367
368 ########################################
369 ### CHANNELSTREAM CONFIG ####
370 ########################################
371
372 channelstream.enabled = true
373 # location of channelstream server on the backend
374 channelstream.server = 127.0.0.1:9800
375 # location of the channelstream server from outside world
376 channelstream.ws_url = ws://127.0.0.1:9800
377 channelstream.secret = secret
378
368 ###################################
379 ###################################
369 ## APPENLIGHT CONFIG ##
380 ## APPENLIGHT CONFIG ##
370 ###################################
381 ###################################
371
382
372 ## Appenlight is tailored to work with RhodeCode, see
383 ## Appenlight is tailored to work with RhodeCode, see
373 ## http://appenlight.com for details how to obtain an account
384 ## http://appenlight.com for details how to obtain an account
374
385
375 ## appenlight integration enabled
386 ## appenlight integration enabled
376 appenlight = false
387 appenlight = false
377
388
378 appenlight.server_url = https://api.appenlight.com
389 appenlight.server_url = https://api.appenlight.com
379 appenlight.api_key = YOUR_API_KEY
390 appenlight.api_key = YOUR_API_KEY
380 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
391 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
381
392
382 # used for JS client
393 # used for JS client
383 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
394 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
384
395
385 ## TWEAK AMOUNT OF INFO SENT HERE
396 ## TWEAK AMOUNT OF INFO SENT HERE
386
397
387 ## enables 404 error logging (default False)
398 ## enables 404 error logging (default False)
388 appenlight.report_404 = false
399 appenlight.report_404 = false
389
400
390 ## time in seconds after request is considered being slow (default 1)
401 ## time in seconds after request is considered being slow (default 1)
391 appenlight.slow_request_time = 1
402 appenlight.slow_request_time = 1
392
403
393 ## record slow requests in application
404 ## record slow requests in application
394 ## (needs to be enabled for slow datastore recording and time tracking)
405 ## (needs to be enabled for slow datastore recording and time tracking)
395 appenlight.slow_requests = true
406 appenlight.slow_requests = true
396
407
397 ## enable hooking to application loggers
408 ## enable hooking to application loggers
398 appenlight.logging = true
409 appenlight.logging = true
399
410
400 ## minimum log level for log capture
411 ## minimum log level for log capture
401 appenlight.logging.level = WARNING
412 appenlight.logging.level = WARNING
402
413
403 ## send logs only from erroneous/slow requests
414 ## send logs only from erroneous/slow requests
404 ## (saves API quota for intensive logging)
415 ## (saves API quota for intensive logging)
405 appenlight.logging_on_error = false
416 appenlight.logging_on_error = false
406
417
407 ## list of additonal keywords that should be grabbed from environ object
418 ## list of additonal keywords that should be grabbed from environ object
408 ## can be string with comma separated list of words in lowercase
419 ## can be string with comma separated list of words in lowercase
409 ## (by default client will always send following info:
420 ## (by default client will always send following info:
410 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
421 ## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
411 ## start with HTTP* this list be extended with additional keywords here
422 ## start with HTTP* this list be extended with additional keywords here
412 appenlight.environ_keys_whitelist =
423 appenlight.environ_keys_whitelist =
413
424
414 ## list of keywords that should be blanked from request object
425 ## list of keywords that should be blanked from request object
415 ## can be string with comma separated list of words in lowercase
426 ## can be string with comma separated list of words in lowercase
416 ## (by default client will always blank keys that contain following words
427 ## (by default client will always blank keys that contain following words
417 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
428 ## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
418 ## this list be extended with additional keywords set here
429 ## this list be extended with additional keywords set here
419 appenlight.request_keys_blacklist =
430 appenlight.request_keys_blacklist =
420
431
421 ## list of namespaces that should be ignores when gathering log entries
432 ## list of namespaces that should be ignores when gathering log entries
422 ## can be string with comma separated list of namespaces
433 ## can be string with comma separated list of namespaces
423 ## (by default the client ignores own entries: appenlight_client.client)
434 ## (by default the client ignores own entries: appenlight_client.client)
424 appenlight.log_namespace_blacklist =
435 appenlight.log_namespace_blacklist =
425
436
426
437
427 ################################################################################
438 ################################################################################
428 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
439 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
429 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
440 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
430 ## execute malicious code after an exception is raised. ##
441 ## execute malicious code after an exception is raised. ##
431 ################################################################################
442 ################################################################################
432 set debug = false
443 set debug = false
433
444
434
445
435 #########################################################
446 #########################################################
436 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
447 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
437 #########################################################
448 #########################################################
438 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
449 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
439 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
450 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
440 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
451 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
441
452
442 # see sqlalchemy docs for other advanced settings
453 # see sqlalchemy docs for other advanced settings
443
454
444 ## print the sql statements to output
455 ## print the sql statements to output
445 sqlalchemy.db1.echo = false
456 sqlalchemy.db1.echo = false
446 ## recycle the connections after this ammount of seconds
457 ## recycle the connections after this ammount of seconds
447 sqlalchemy.db1.pool_recycle = 3600
458 sqlalchemy.db1.pool_recycle = 3600
448 sqlalchemy.db1.convert_unicode = true
459 sqlalchemy.db1.convert_unicode = true
449
460
450 ## the number of connections to keep open inside the connection pool.
461 ## the number of connections to keep open inside the connection pool.
451 ## 0 indicates no limit
462 ## 0 indicates no limit
452 #sqlalchemy.db1.pool_size = 5
463 #sqlalchemy.db1.pool_size = 5
453
464
454 ## the number of connections to allow in connection pool "overflow", that is
465 ## the number of connections to allow in connection pool "overflow", that is
455 ## connections that can be opened above and beyond the pool_size setting,
466 ## connections that can be opened above and beyond the pool_size setting,
456 ## which defaults to five.
467 ## which defaults to five.
457 #sqlalchemy.db1.max_overflow = 10
468 #sqlalchemy.db1.max_overflow = 10
458
469
459
470
460 ##################
471 ##################
461 ### VCS CONFIG ###
472 ### VCS CONFIG ###
462 ##################
473 ##################
463 vcs.server.enable = true
474 vcs.server.enable = true
464 vcs.server = localhost:9900
475 vcs.server = localhost:9900
465
476
466 ## Web server connectivity protocol, responsible for web based VCS operatations
477 ## Web server connectivity protocol, responsible for web based VCS operatations
467 ## Available protocols are:
478 ## Available protocols are:
468 ## `pyro4` - using pyro4 server
479 ## `pyro4` - using pyro4 server
469 ## `http` - using http-rpc backend
480 ## `http` - using http-rpc backend
470 #vcs.server.protocol = http
481 #vcs.server.protocol = http
471
482
472 ## Push/Pull operations protocol, available options are:
483 ## Push/Pull operations protocol, available options are:
473 ## `pyro4` - using pyro4 server
484 ## `pyro4` - using pyro4 server
474 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
485 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
475 ## `vcsserver.scm_app` - internal app (EE only)
486 ## `vcsserver.scm_app` - internal app (EE only)
476 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
487 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
477
488
478 ## Push/Pull operations hooks protocol, available options are:
489 ## Push/Pull operations hooks protocol, available options are:
479 ## `pyro4` - using pyro4 server
490 ## `pyro4` - using pyro4 server
480 ## `http` - using http-rpc backend
491 ## `http` - using http-rpc backend
481 #vcs.hooks.protocol = http
492 #vcs.hooks.protocol = http
482
493
483 vcs.server.log_level = info
494 vcs.server.log_level = info
484 ## Start VCSServer with this instance as a subprocess, usefull for development
495 ## Start VCSServer with this instance as a subprocess, usefull for development
485 vcs.start_server = false
496 vcs.start_server = false
486 vcs.backends = hg, git, svn
497 vcs.backends = hg, git, svn
487 vcs.connection_timeout = 3600
498 vcs.connection_timeout = 3600
488 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
499 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
489 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
500 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
490 #vcs.svn.compatible_version = pre-1.8-compatible
501 #vcs.svn.compatible_version = pre-1.8-compatible
491
502
492 ################################
503 ################################
493 ### LOGGING CONFIGURATION ####
504 ### LOGGING CONFIGURATION ####
494 ################################
505 ################################
495 [loggers]
506 [loggers]
496 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates
507 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates
497
508
498 [handlers]
509 [handlers]
499 keys = console, console_sql
510 keys = console, console_sql
500
511
501 [formatters]
512 [formatters]
502 keys = generic, color_formatter, color_formatter_sql
513 keys = generic, color_formatter, color_formatter_sql
503
514
504 #############
515 #############
505 ## LOGGERS ##
516 ## LOGGERS ##
506 #############
517 #############
507 [logger_root]
518 [logger_root]
508 level = NOTSET
519 level = NOTSET
509 handlers = console
520 handlers = console
510
521
511 [logger_routes]
522 [logger_routes]
512 level = DEBUG
523 level = DEBUG
513 handlers =
524 handlers =
514 qualname = routes.middleware
525 qualname = routes.middleware
515 ## "level = DEBUG" logs the route matched and routing variables.
526 ## "level = DEBUG" logs the route matched and routing variables.
516 propagate = 1
527 propagate = 1
517
528
518 [logger_beaker]
529 [logger_beaker]
519 level = DEBUG
530 level = DEBUG
520 handlers =
531 handlers =
521 qualname = beaker.container
532 qualname = beaker.container
522 propagate = 1
533 propagate = 1
523
534
524 [logger_pyro4]
535 [logger_pyro4]
525 level = DEBUG
536 level = DEBUG
526 handlers =
537 handlers =
527 qualname = Pyro4
538 qualname = Pyro4
528 propagate = 1
539 propagate = 1
529
540
530 [logger_templates]
541 [logger_templates]
531 level = INFO
542 level = INFO
532 handlers =
543 handlers =
533 qualname = pylons.templating
544 qualname = pylons.templating
534 propagate = 1
545 propagate = 1
535
546
536 [logger_rhodecode]
547 [logger_rhodecode]
537 level = DEBUG
548 level = DEBUG
538 handlers =
549 handlers =
539 qualname = rhodecode
550 qualname = rhodecode
540 propagate = 1
551 propagate = 1
541
552
542 [logger_sqlalchemy]
553 [logger_sqlalchemy]
543 level = INFO
554 level = INFO
544 handlers = console_sql
555 handlers = console_sql
545 qualname = sqlalchemy.engine
556 qualname = sqlalchemy.engine
546 propagate = 0
557 propagate = 0
547
558
548 ##############
559 ##############
549 ## HANDLERS ##
560 ## HANDLERS ##
550 ##############
561 ##############
551
562
552 [handler_console]
563 [handler_console]
553 class = StreamHandler
564 class = StreamHandler
554 args = (sys.stderr,)
565 args = (sys.stderr,)
555 level = INFO
566 level = INFO
556 formatter = generic
567 formatter = generic
557
568
558 [handler_console_sql]
569 [handler_console_sql]
559 class = StreamHandler
570 class = StreamHandler
560 args = (sys.stderr,)
571 args = (sys.stderr,)
561 level = WARN
572 level = WARN
562 formatter = generic
573 formatter = generic
563
574
564 ################
575 ################
565 ## FORMATTERS ##
576 ## FORMATTERS ##
566 ################
577 ################
567
578
568 [formatter_generic]
579 [formatter_generic]
569 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
580 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
570 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
581 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
571 datefmt = %Y-%m-%d %H:%M:%S
582 datefmt = %Y-%m-%d %H:%M:%S
572
583
573 [formatter_color_formatter]
584 [formatter_color_formatter]
574 class = rhodecode.lib.logging_formatter.ColorFormatter
585 class = rhodecode.lib.logging_formatter.ColorFormatter
575 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
586 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
576 datefmt = %Y-%m-%d %H:%M:%S
587 datefmt = %Y-%m-%d %H:%M:%S
577
588
578 [formatter_color_formatter_sql]
589 [formatter_color_formatter_sql]
579 class = rhodecode.lib.logging_formatter.ColorFormatterSql
590 class = rhodecode.lib.logging_formatter.ColorFormatterSql
580 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
591 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
581 datefmt = %Y-%m-%d %H:%M:%S
592 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,228 +1,229 b''
1 # Nix environment for the community edition
1 # Nix environment for the community edition
2 #
2 #
3 # This shall be as lean as possible, just producing the Enterprise
3 # This shall be as lean as possible, just producing the Enterprise
4 # derivation. For advanced tweaks to pimp up the development environment we use
4 # derivation. For advanced tweaks to pimp up the development environment we use
5 # "shell.nix" so that it does not have to clutter this file.
5 # "shell.nix" so that it does not have to clutter this file.
6
6
7 { pkgs ? (import <nixpkgs> {})
7 { pkgs ? (import <nixpkgs> {})
8 , pythonPackages ? "python27Packages"
8 , pythonPackages ? "python27Packages"
9 , pythonExternalOverrides ? self: super: {}
9 , pythonExternalOverrides ? self: super: {}
10 , doCheck ? true
10 , doCheck ? true
11 }:
11 }:
12
12
13 let pkgs_ = pkgs; in
13 let pkgs_ = pkgs; in
14
14
15 let
15 let
16 pkgs = pkgs_.overridePackages (self: super: {
16 pkgs = pkgs_.overridePackages (self: super: {
17 # Override subversion derivation to
17 # Override subversion derivation to
18 # - activate python bindings
18 # - activate python bindings
19 # - set version to 1.8
19 # - set version to 1.8
20 subversion = super.subversion18.override {
20 subversion = super.subversion18.override {
21 httpSupport = true;
21 httpSupport = true;
22 pythonBindings = true;
22 pythonBindings = true;
23 python = self.python27Packages.python;
23 python = self.python27Packages.python;
24 };
24 };
25 });
25 });
26
26
27 inherit (pkgs.lib) fix extends;
27 inherit (pkgs.lib) fix extends;
28
28
29 basePythonPackages = with builtins; if isAttrs pythonPackages
29 basePythonPackages = with builtins; if isAttrs pythonPackages
30 then pythonPackages
30 then pythonPackages
31 else getAttr pythonPackages pkgs;
31 else getAttr pythonPackages pkgs;
32
32
33 elem = builtins.elem;
33 elem = builtins.elem;
34 basename = path: with pkgs.lib; last (splitString "/" path);
34 basename = path: with pkgs.lib; last (splitString "/" path);
35 startsWith = prefix: full: let
35 startsWith = prefix: full: let
36 actualPrefix = builtins.substring 0 (builtins.stringLength prefix) full;
36 actualPrefix = builtins.substring 0 (builtins.stringLength prefix) full;
37 in actualPrefix == prefix;
37 in actualPrefix == prefix;
38
38
39 src-filter = path: type: with pkgs.lib;
39 src-filter = path: type: with pkgs.lib;
40 let
40 let
41 ext = last (splitString "." path);
41 ext = last (splitString "." path);
42 in
42 in
43 !elem (basename path) [
43 !elem (basename path) [
44 ".git" ".hg" "__pycache__" ".eggs" "node_modules"
44 ".git" ".hg" "__pycache__" ".eggs" "node_modules"
45 "build" "data" "tmp"] &&
45 "build" "data" "tmp"] &&
46 !elem ext ["egg-info" "pyc"] &&
46 !elem ext ["egg-info" "pyc"] &&
47 !startsWith "result" path;
47 !startsWith "result" path;
48
48
49 sources = pkgs.config.rc.sources or {};
49 sources = pkgs.config.rc.sources or {};
50 rhodecode-enterprise-ce-src = builtins.filterSource src-filter ./.;
50 rhodecode-enterprise-ce-src = builtins.filterSource src-filter ./.;
51
51
52 # Load the generated node packages
52 # Load the generated node packages
53 nodePackages = pkgs.callPackage "${pkgs.path}/pkgs/top-level/node-packages.nix" rec {
53 nodePackages = pkgs.callPackage "${pkgs.path}/pkgs/top-level/node-packages.nix" rec {
54 self = nodePackages;
54 self = nodePackages;
55 generated = pkgs.callPackage ./pkgs/node-packages.nix { inherit self; };
55 generated = pkgs.callPackage ./pkgs/node-packages.nix { inherit self; };
56 };
56 };
57
57
58 # TODO: Should be taken automatically out of the generates packages.
58 # TODO: Should be taken automatically out of the generates packages.
59 # apps.nix has one solution for this, although I'd prefer to have the deps
59 # apps.nix has one solution for this, although I'd prefer to have the deps
60 # from package.json mapped in here.
60 # from package.json mapped in here.
61 nodeDependencies = with nodePackages; [
61 nodeDependencies = with nodePackages; [
62 grunt
62 grunt
63 grunt-contrib-concat
63 grunt-contrib-concat
64 grunt-contrib-jshint
64 grunt-contrib-jshint
65 grunt-contrib-less
65 grunt-contrib-less
66 grunt-contrib-watch
66 grunt-contrib-watch
67 jshint
67 jshint
68 ];
68 ];
69
69
70 pythonGeneratedPackages = self: basePythonPackages.override (a: {
70 pythonGeneratedPackages = self: basePythonPackages.override (a: {
71 inherit self;
71 inherit self;
72 })
72 })
73 // (scopedImport {
73 // (scopedImport {
74 self = self;
74 self = self;
75 super = basePythonPackages;
75 super = basePythonPackages;
76 inherit pkgs;
76 inherit pkgs;
77 inherit (pkgs) fetchurl fetchgit;
77 inherit (pkgs) fetchurl fetchgit;
78 } ./pkgs/python-packages.nix);
78 } ./pkgs/python-packages.nix);
79
79
80 pythonOverrides = import ./pkgs/python-packages-overrides.nix {
80 pythonOverrides = import ./pkgs/python-packages-overrides.nix {
81 inherit
81 inherit
82 basePythonPackages
82 basePythonPackages
83 pkgs;
83 pkgs;
84 };
84 };
85
85
86 pythonLocalOverrides = self: super: {
86 pythonLocalOverrides = self: super: {
87 rhodecode-enterprise-ce =
87 rhodecode-enterprise-ce =
88 let
88 let
89 version = builtins.readFile ./rhodecode/VERSION;
89 version = builtins.readFile ./rhodecode/VERSION;
90 linkNodeModules = ''
90 linkNodeModules = ''
91 echo "Link node packages"
91 echo "Link node packages"
92 # TODO: check if this adds stuff as a dependency, closure size
92 # TODO: check if this adds stuff as a dependency, closure size
93 rm -fr node_modules
93 rm -fr node_modules
94 mkdir -p node_modules
94 mkdir -p node_modules
95 ${pkgs.lib.concatMapStrings (dep: ''
95 ${pkgs.lib.concatMapStrings (dep: ''
96 ln -sfv ${dep}/lib/node_modules/${dep.pkgName} node_modules/
96 ln -sfv ${dep}/lib/node_modules/${dep.pkgName} node_modules/
97 '') nodeDependencies}
97 '') nodeDependencies}
98 echo "DONE: Link node packages"
98 echo "DONE: Link node packages"
99 '';
99 '';
100 in super.rhodecode-enterprise-ce.override (attrs: {
100 in super.rhodecode-enterprise-ce.override (attrs: {
101
101
102 inherit
102 inherit
103 doCheck
103 doCheck
104 version;
104 version;
105 name = "rhodecode-enterprise-ce-${version}";
105 name = "rhodecode-enterprise-ce-${version}";
106 releaseName = "RhodeCodeEnterpriseCE-${version}";
106 releaseName = "RhodeCodeEnterpriseCE-${version}";
107 src = rhodecode-enterprise-ce-src;
107 src = rhodecode-enterprise-ce-src;
108
108
109 buildInputs =
109 buildInputs =
110 attrs.buildInputs ++
110 attrs.buildInputs ++
111 (with self; [
111 (with self; [
112 pkgs.nodePackages.grunt-cli
112 pkgs.nodePackages.grunt-cli
113 pkgs.subversion
113 pkgs.subversion
114 pytest-catchlog
114 pytest-catchlog
115 rhodecode-testdata
115 rhodecode-testdata
116 ]);
116 ]);
117
117
118 propagatedBuildInputs = attrs.propagatedBuildInputs ++ (with self; [
118 propagatedBuildInputs = attrs.propagatedBuildInputs ++ (with self; [
119 rhodecode-tools
119 rhodecode-tools
120 ]);
120 ]);
121
121
122 # TODO: johbo: Make a nicer way to expose the parts. Maybe
122 # TODO: johbo: Make a nicer way to expose the parts. Maybe
123 # pkgs/default.nix?
123 # pkgs/default.nix?
124 passthru = {
124 passthru = {
125 inherit
125 inherit
126 linkNodeModules
126 linkNodeModules
127 myPythonPackagesUnfix
127 myPythonPackagesUnfix
128 pythonLocalOverrides;
128 pythonLocalOverrides;
129 pythonPackages = self;
129 pythonPackages = self;
130 };
130 };
131
131
132 LC_ALL = "en_US.UTF-8";
132 LC_ALL = "en_US.UTF-8";
133 LOCALE_ARCHIVE =
133 LOCALE_ARCHIVE =
134 if pkgs.stdenv ? glibc
134 if pkgs.stdenv ? glibc
135 then "${pkgs.glibcLocales}/lib/locale/locale-archive"
135 then "${pkgs.glibcLocales}/lib/locale/locale-archive"
136 else "";
136 else "";
137
137
138 # Somewhat snappier setup of the development environment
138 # Somewhat snappier setup of the development environment
139 # TODO: move into shell.nix
139 # TODO: move into shell.nix
140 # TODO: think of supporting a stable path again, so that multiple shells
140 # TODO: think of supporting a stable path again, so that multiple shells
141 # can share it.
141 # can share it.
142 shellHook = ''
142 shellHook = ''
143 tmp_path=$(mktemp -d)
143 tmp_path=$(mktemp -d)
144 export PATH="$tmp_path/bin:$PATH"
144 export PATH="$tmp_path/bin:$PATH"
145 export PYTHONPATH="$tmp_path/${self.python.sitePackages}:$PYTHONPATH"
145 export PYTHONPATH="$tmp_path/${self.python.sitePackages}:$PYTHONPATH"
146 mkdir -p $tmp_path/${self.python.sitePackages}
146 mkdir -p $tmp_path/${self.python.sitePackages}
147 python setup.py develop --prefix $tmp_path --allow-hosts ""
147 python setup.py develop --prefix $tmp_path --allow-hosts ""
148 '' + linkNodeModules;
148 '' + linkNodeModules;
149
149
150 preCheck = ''
150 preCheck = ''
151 export PATH="$out/bin:$PATH"
151 export PATH="$out/bin:$PATH"
152 '';
152 '';
153
153
154 postCheck = ''
154 postCheck = ''
155 rm -rf $out/lib/${self.python.libPrefix}/site-packages/pytest_pylons
155 rm -rf $out/lib/${self.python.libPrefix}/site-packages/pytest_pylons
156 rm -rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/tests
156 rm -rf $out/lib/${self.python.libPrefix}/site-packages/rhodecode/tests
157 '';
157 '';
158
158
159 preBuild = linkNodeModules + ''
159 preBuild = linkNodeModules + ''
160 grunt
160 grunt
161 rm -fr node_modules
161 rm -fr node_modules
162 '';
162 '';
163
163
164 postInstall = ''
164 postInstall = ''
165 # python based programs need to be wrapped
165 # python based programs need to be wrapped
166 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
166 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
167 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
167 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
168 ln -s ${self.PasteScript}/bin/paster $out/bin/
168 ln -s ${self.PasteScript}/bin/paster $out/bin/
169 ln -s ${self.channelstream}/bin/channelstream $out/bin/
169 ln -s ${self.pyramid}/bin/* $out/bin/ #*/
170 ln -s ${self.pyramid}/bin/* $out/bin/ #*/
170
171
171 # rhodecode-tools
172 # rhodecode-tools
172 # TODO: johbo: re-think this. Do the tools import anything from enterprise?
173 # TODO: johbo: re-think this. Do the tools import anything from enterprise?
173 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
174 ln -s ${self.rhodecode-tools}/bin/rhodecode-* $out/bin/
174
175
175 # note that condition should be restricted when adding further tools
176 # note that condition should be restricted when adding further tools
176 for file in $out/bin/*; do #*/
177 for file in $out/bin/*; do #*/
177 wrapProgram $file \
178 wrapProgram $file \
178 --prefix PYTHONPATH : $PYTHONPATH \
179 --prefix PYTHONPATH : $PYTHONPATH \
179 --prefix PATH : $PATH \
180 --prefix PATH : $PATH \
180 --set PYTHONHASHSEED random
181 --set PYTHONHASHSEED random
181 done
182 done
182
183
183 mkdir $out/etc
184 mkdir $out/etc
184 cp configs/production.ini $out/etc
185 cp configs/production.ini $out/etc
185
186
186 echo "Writing meta information for rccontrol to nix-support/rccontrol"
187 echo "Writing meta information for rccontrol to nix-support/rccontrol"
187 mkdir -p $out/nix-support/rccontrol
188 mkdir -p $out/nix-support/rccontrol
188 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
189 cp -v rhodecode/VERSION $out/nix-support/rccontrol/version
189 echo "DONE: Meta information for rccontrol written"
190 echo "DONE: Meta information for rccontrol written"
190
191
191 # TODO: johbo: Make part of ac-tests
192 # TODO: johbo: Make part of ac-tests
192 if [ ! -f rhodecode/public/js/scripts.js ]; then
193 if [ ! -f rhodecode/public/js/scripts.js ]; then
193 echo "Missing scripts.js"
194 echo "Missing scripts.js"
194 exit 1
195 exit 1
195 fi
196 fi
196 if [ ! -f rhodecode/public/css/style.css ]; then
197 if [ ! -f rhodecode/public/css/style.css ]; then
197 echo "Missing style.css"
198 echo "Missing style.css"
198 exit 1
199 exit 1
199 fi
200 fi
200 '';
201 '';
201
202
202 });
203 });
203
204
204 rhodecode-testdata = import "${rhodecode-testdata-src}/default.nix" {
205 rhodecode-testdata = import "${rhodecode-testdata-src}/default.nix" {
205 inherit
206 inherit
206 doCheck
207 doCheck
207 pkgs
208 pkgs
208 pythonPackages;
209 pythonPackages;
209 };
210 };
210
211
211 };
212 };
212
213
213 rhodecode-testdata-src = sources.rhodecode-testdata or (
214 rhodecode-testdata-src = sources.rhodecode-testdata or (
214 pkgs.fetchhg {
215 pkgs.fetchhg {
215 url = "https://code.rhodecode.com/upstream/rc_testdata";
216 url = "https://code.rhodecode.com/upstream/rc_testdata";
216 rev = "v0.8.0";
217 rev = "v0.8.0";
217 sha256 = "0hy1ba134rq2f9si85yx7j4qhc9ky0hjzdk553s3q026i7km809m";
218 sha256 = "0hy1ba134rq2f9si85yx7j4qhc9ky0hjzdk553s3q026i7km809m";
218 });
219 });
219
220
220 # Apply all overrides and fix the final package set
221 # Apply all overrides and fix the final package set
221 myPythonPackagesUnfix =
222 myPythonPackagesUnfix =
222 (extends pythonExternalOverrides
223 (extends pythonExternalOverrides
223 (extends pythonLocalOverrides
224 (extends pythonLocalOverrides
224 (extends pythonOverrides
225 (extends pythonOverrides
225 pythonGeneratedPackages)));
226 pythonGeneratedPackages)));
226 myPythonPackages = (fix myPythonPackagesUnfix);
227 myPythonPackages = (fix myPythonPackagesUnfix);
227
228
228 in myPythonPackages.rhodecode-enterprise-ce
229 in myPythonPackages.rhodecode-enterprise-ce
@@ -1,192 +1,199 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Pylons environment configuration
22 Pylons environment configuration
23 """
23 """
24
24
25 import os
25 import os
26 import logging
26 import logging
27 import rhodecode
27 import rhodecode
28 import platform
28 import platform
29 import re
29 import re
30 import io
30 import io
31
31
32 from mako.lookup import TemplateLookup
32 from mako.lookup import TemplateLookup
33 from pylons.configuration import PylonsConfig
33 from pylons.configuration import PylonsConfig
34 from pylons.error import handle_mako_error
34 from pylons.error import handle_mako_error
35 from pyramid.settings import asbool
35 from pyramid.settings import asbool
36
36
37 # don't remove this import it does magic for celery
37 # don't remove this import it does magic for celery
38 from rhodecode.lib import celerypylons # noqa
38 from rhodecode.lib import celerypylons # noqa
39
39
40 import rhodecode.lib.app_globals as app_globals
40 import rhodecode.lib.app_globals as app_globals
41
41
42 from rhodecode.config import utils
42 from rhodecode.config import utils
43 from rhodecode.config.routing import make_map
43 from rhodecode.config.routing import make_map
44 from rhodecode.config.jsroutes import generate_jsroutes_content
44 from rhodecode.config.jsroutes import generate_jsroutes_content
45
45
46 from rhodecode.lib import helpers
46 from rhodecode.lib import helpers
47 from rhodecode.lib.auth import set_available_permissions
47 from rhodecode.lib.auth import set_available_permissions
48 from rhodecode.lib.utils import (
48 from rhodecode.lib.utils import (
49 repo2db_mapper, make_db_config, set_rhodecode_config,
49 repo2db_mapper, make_db_config, set_rhodecode_config,
50 load_rcextensions)
50 load_rcextensions)
51 from rhodecode.lib.utils2 import str2bool, aslist
51 from rhodecode.lib.utils2 import str2bool, aslist
52 from rhodecode.lib.vcs import connect_vcs, start_vcs_server
52 from rhodecode.lib.vcs import connect_vcs, start_vcs_server
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 def load_environment(global_conf, app_conf, initial=False,
57 def load_environment(global_conf, app_conf, initial=False,
58 test_env=None, test_index=None):
58 test_env=None, test_index=None):
59 """
59 """
60 Configure the Pylons environment via the ``pylons.config``
60 Configure the Pylons environment via the ``pylons.config``
61 object
61 object
62 """
62 """
63 config = PylonsConfig()
63 config = PylonsConfig()
64
64
65
65
66 # Pylons paths
66 # Pylons paths
67 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
67 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
68 paths = {
68 paths = {
69 'root': root,
69 'root': root,
70 'controllers': os.path.join(root, 'controllers'),
70 'controllers': os.path.join(root, 'controllers'),
71 'static_files': os.path.join(root, 'public'),
71 'static_files': os.path.join(root, 'public'),
72 'templates': [os.path.join(root, 'templates')],
72 'templates': [os.path.join(root, 'templates')],
73 }
73 }
74
74
75 # Initialize config with the basic options
75 # Initialize config with the basic options
76 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
76 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
77
77
78 # store some globals into rhodecode
78 # store some globals into rhodecode
79 rhodecode.CELERY_ENABLED = str2bool(config['app_conf'].get('use_celery'))
79 rhodecode.CELERY_ENABLED = str2bool(config['app_conf'].get('use_celery'))
80 rhodecode.CELERY_EAGER = str2bool(
80 rhodecode.CELERY_EAGER = str2bool(
81 config['app_conf'].get('celery.always.eager'))
81 config['app_conf'].get('celery.always.eager'))
82
82
83 config['routes.map'] = make_map(config)
83 config['routes.map'] = make_map(config)
84
84
85 if asbool(config.get('generate_js_files', 'false')):
85 if asbool(config.get('generate_js_files', 'false')):
86 jsroutes = config['routes.map'].jsroutes()
86 jsroutes = config['routes.map'].jsroutes()
87 jsroutes_file_content = generate_jsroutes_content(jsroutes)
87 jsroutes_file_content = generate_jsroutes_content(jsroutes)
88 jsroutes_file_path = os.path.join(
88 jsroutes_file_path = os.path.join(
89 paths['static_files'], 'js', 'rhodecode', 'routes.js')
89 paths['static_files'], 'js', 'rhodecode', 'routes.js')
90
90
91 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
91 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
92 f.write(jsroutes_file_content)
92 f.write(jsroutes_file_content)
93
93
94 config['pylons.app_globals'] = app_globals.Globals(config)
94 config['pylons.app_globals'] = app_globals.Globals(config)
95 config['pylons.h'] = helpers
95 config['pylons.h'] = helpers
96 rhodecode.CONFIG = config
96 rhodecode.CONFIG = config
97
97
98 load_rcextensions(root_path=config['here'])
98 load_rcextensions(root_path=config['here'])
99
99
100 # Setup cache object as early as possible
100 # Setup cache object as early as possible
101 import pylons
101 import pylons
102 pylons.cache._push_object(config['pylons.app_globals'].cache)
102 pylons.cache._push_object(config['pylons.app_globals'].cache)
103
103
104 # Create the Mako TemplateLookup, with the default auto-escaping
104 # Create the Mako TemplateLookup, with the default auto-escaping
105 config['pylons.app_globals'].mako_lookup = TemplateLookup(
105 config['pylons.app_globals'].mako_lookup = TemplateLookup(
106 directories=paths['templates'],
106 directories=paths['templates'],
107 error_handler=handle_mako_error,
107 error_handler=handle_mako_error,
108 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
108 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
109 input_encoding='utf-8', default_filters=['escape'],
109 input_encoding='utf-8', default_filters=['escape'],
110 imports=['from webhelpers.html import escape'])
110 imports=['from webhelpers.html import escape'])
111
111
112 # sets the c attribute access when don't existing attribute are accessed
112 # sets the c attribute access when don't existing attribute are accessed
113 config['pylons.strict_tmpl_context'] = True
113 config['pylons.strict_tmpl_context'] = True
114
114
115 # configure channelstream
116 config['channelstream_config'] = {
117 'enabled': asbool(config.get('channelstream.enabled', False)),
118 'server': config.get('channelstream.server'),
119 'secret': config.get('channelstream.secret')
120 }
121
115 # Limit backends to "vcs.backends" from configuration
122 # Limit backends to "vcs.backends" from configuration
116 backends = config['vcs.backends'] = aslist(
123 backends = config['vcs.backends'] = aslist(
117 config.get('vcs.backends', 'hg,git'), sep=',')
124 config.get('vcs.backends', 'hg,git'), sep=',')
118 for alias in rhodecode.BACKENDS.keys():
125 for alias in rhodecode.BACKENDS.keys():
119 if alias not in backends:
126 if alias not in backends:
120 del rhodecode.BACKENDS[alias]
127 del rhodecode.BACKENDS[alias]
121 log.info("Enabled backends: %s", backends)
128 log.info("Enabled backends: %s", backends)
122
129
123 # initialize vcs client and optionally run the server if enabled
130 # initialize vcs client and optionally run the server if enabled
124 vcs_server_uri = config.get('vcs.server', '')
131 vcs_server_uri = config.get('vcs.server', '')
125 vcs_server_enabled = str2bool(config.get('vcs.server.enable', 'true'))
132 vcs_server_enabled = str2bool(config.get('vcs.server.enable', 'true'))
126 start_server = (
133 start_server = (
127 str2bool(config.get('vcs.start_server', 'false')) and
134 str2bool(config.get('vcs.start_server', 'false')) and
128 not int(os.environ.get('RC_VCSSERVER_TEST_DISABLE', '0')))
135 not int(os.environ.get('RC_VCSSERVER_TEST_DISABLE', '0')))
129 if vcs_server_enabled and start_server:
136 if vcs_server_enabled and start_server:
130 log.info("Starting vcsserver")
137 log.info("Starting vcsserver")
131 start_vcs_server(server_and_port=vcs_server_uri,
138 start_vcs_server(server_and_port=vcs_server_uri,
132 protocol=utils.get_vcs_server_protocol(config),
139 protocol=utils.get_vcs_server_protocol(config),
133 log_level=config['vcs.server.log_level'])
140 log_level=config['vcs.server.log_level'])
134
141
135 set_available_permissions(config)
142 set_available_permissions(config)
136 db_cfg = make_db_config(clear_session=True)
143 db_cfg = make_db_config(clear_session=True)
137
144
138 repos_path = list(db_cfg.items('paths'))[0][1]
145 repos_path = list(db_cfg.items('paths'))[0][1]
139 config['base_path'] = repos_path
146 config['base_path'] = repos_path
140
147
141 config['vcs.hooks.direct_calls'] = _use_direct_hook_calls(config)
148 config['vcs.hooks.direct_calls'] = _use_direct_hook_calls(config)
142 config['vcs.hooks.protocol'] = _get_vcs_hooks_protocol(config)
149 config['vcs.hooks.protocol'] = _get_vcs_hooks_protocol(config)
143
150
144 # store db config also in main global CONFIG
151 # store db config also in main global CONFIG
145 set_rhodecode_config(config)
152 set_rhodecode_config(config)
146
153
147 # configure instance id
154 # configure instance id
148 utils.set_instance_id(config)
155 utils.set_instance_id(config)
149
156
150 # CONFIGURATION OPTIONS HERE (note: all config options will override
157 # CONFIGURATION OPTIONS HERE (note: all config options will override
151 # any Pylons config options)
158 # any Pylons config options)
152
159
153 # store config reference into our module to skip import magic of pylons
160 # store config reference into our module to skip import magic of pylons
154 rhodecode.CONFIG.update(config)
161 rhodecode.CONFIG.update(config)
155
162
156 utils.configure_pyro4(config)
163 utils.configure_pyro4(config)
157 utils.configure_vcs(config)
164 utils.configure_vcs(config)
158 if vcs_server_enabled:
165 if vcs_server_enabled:
159 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(config))
166 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(config))
160
167
161 import_on_startup = str2bool(config.get('startup.import_repos', False))
168 import_on_startup = str2bool(config.get('startup.import_repos', False))
162 if vcs_server_enabled and import_on_startup:
169 if vcs_server_enabled and import_on_startup:
163 repo2db_mapper(ScmModel().repo_scan(repos_path), remove_obsolete=False)
170 repo2db_mapper(ScmModel().repo_scan(repos_path), remove_obsolete=False)
164 return config
171 return config
165
172
166
173
167 def _use_direct_hook_calls(config):
174 def _use_direct_hook_calls(config):
168 default_direct_hook_calls = 'false'
175 default_direct_hook_calls = 'false'
169 direct_hook_calls = str2bool(
176 direct_hook_calls = str2bool(
170 config.get('vcs.hooks.direct_calls', default_direct_hook_calls))
177 config.get('vcs.hooks.direct_calls', default_direct_hook_calls))
171 return direct_hook_calls
178 return direct_hook_calls
172
179
173
180
174 def _get_vcs_hooks_protocol(config):
181 def _get_vcs_hooks_protocol(config):
175 protocol = config.get('vcs.hooks.protocol', 'pyro4').lower()
182 protocol = config.get('vcs.hooks.protocol', 'pyro4').lower()
176 return protocol
183 return protocol
177
184
178
185
179 def load_pyramid_environment(global_config, settings):
186 def load_pyramid_environment(global_config, settings):
180 # Some parts of the code expect a merge of global and app settings.
187 # Some parts of the code expect a merge of global and app settings.
181 settings_merged = global_config.copy()
188 settings_merged = global_config.copy()
182 settings_merged.update(settings)
189 settings_merged.update(settings)
183
190
184 # If this is a test run we prepare the test environment like
191 # If this is a test run we prepare the test environment like
185 # creating a test database, test search index and test repositories.
192 # creating a test database, test search index and test repositories.
186 # This has to be done before the database connection is initialized.
193 # This has to be done before the database connection is initialized.
187 if settings['is_test']:
194 if settings['is_test']:
188 rhodecode.is_test = True
195 rhodecode.is_test = True
189 utils.initialize_test_environment(settings_merged)
196 utils.initialize_test_environment(settings_merged)
190
197
191 # Initialize the database connection.
198 # Initialize the database connection.
192 utils.initialize_database(settings_merged)
199 utils.initialize_database(settings_merged)
@@ -1,395 +1,403 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Pylons middleware initialization
22 Pylons middleware initialization
23 """
23 """
24 import logging
24 import logging
25 from collections import OrderedDict
25
26
26 from paste.registry import RegistryManager
27 from paste.registry import RegistryManager
27 from paste.gzipper import make_gzip_middleware
28 from paste.gzipper import make_gzip_middleware
28 from pylons.wsgiapp import PylonsApp
29 from pylons.wsgiapp import PylonsApp
29 from pyramid.authorization import ACLAuthorizationPolicy
30 from pyramid.authorization import ACLAuthorizationPolicy
30 from pyramid.config import Configurator
31 from pyramid.config import Configurator
31 from pyramid.static import static_view
32 from pyramid.static import static_view
32 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
33 from pyramid.wsgi import wsgiapp
34 from pyramid.wsgi import wsgiapp
34 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
35 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
35 from pylons.controllers.util import abort, redirect
36 from pylons.controllers.util import abort, redirect
36 import pyramid.httpexceptions as httpexceptions
37 import pyramid.httpexceptions as httpexceptions
37 from pyramid.renderers import render_to_response, render
38 from pyramid.renderers import render_to_response, render
38 from routes.middleware import RoutesMiddleware
39 from routes.middleware import RoutesMiddleware
39 import routes.util
40 import routes.util
40
41
41 import rhodecode
42 import rhodecode
42 import rhodecode.integrations # do not remove this as it registers celery tasks
43 import rhodecode.integrations # do not remove this as it registers celery tasks
43 from rhodecode.config import patches
44 from rhodecode.config import patches
44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 from rhodecode.config.environment import (
46 from rhodecode.config.environment import (
46 load_environment, load_pyramid_environment)
47 load_environment, load_pyramid_environment)
47 from rhodecode.lib.middleware import csrf
48 from rhodecode.lib.middleware import csrf
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
50 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
50 from rhodecode.lib.middleware.https_fixup import HttpsFixup
51 from rhodecode.lib.middleware.https_fixup import HttpsFixup
51 from rhodecode.lib.middleware.vcs import VCSMiddleware
52 from rhodecode.lib.middleware.vcs import VCSMiddleware
52 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
53
54
54
55
55 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
56
57
57
58
58 # this is used to avoid avoid the route lookup overhead in routesmiddleware
59 # this is used to avoid avoid the route lookup overhead in routesmiddleware
59 # for certain routes which won't go to pylons to - eg. static files, debugger
60 # for certain routes which won't go to pylons to - eg. static files, debugger
60 # it is only needed for the pylons migration and can be removed once complete
61 # it is only needed for the pylons migration and can be removed once complete
61 class SkippableRoutesMiddleware(RoutesMiddleware):
62 class SkippableRoutesMiddleware(RoutesMiddleware):
62 """ Routes middleware that allows you to skip prefixes """
63 """ Routes middleware that allows you to skip prefixes """
63
64
64 def __init__(self, *args, **kw):
65 def __init__(self, *args, **kw):
65 self.skip_prefixes = kw.pop('skip_prefixes', [])
66 self.skip_prefixes = kw.pop('skip_prefixes', [])
66 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
67 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
67
68
68 def __call__(self, environ, start_response):
69 def __call__(self, environ, start_response):
69 for prefix in self.skip_prefixes:
70 for prefix in self.skip_prefixes:
70 if environ['PATH_INFO'].startswith(prefix):
71 if environ['PATH_INFO'].startswith(prefix):
72 # added to avoid the case when a missing /_static route falls
73 # through to pylons and causes an exception as pylons is
74 # expecting wsgiorg.routingargs to be set in the environ
75 # by RoutesMiddleware.
76 if 'wsgiorg.routing_args' not in environ:
77 environ['wsgiorg.routing_args'] = (None, {})
71 return self.app(environ, start_response)
78 return self.app(environ, start_response)
72
79
73 return super(SkippableRoutesMiddleware, self).__call__(
80 return super(SkippableRoutesMiddleware, self).__call__(
74 environ, start_response)
81 environ, start_response)
75
82
76
83
77 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
84 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
78 """Create a Pylons WSGI application and return it
85 """Create a Pylons WSGI application and return it
79
86
80 ``global_conf``
87 ``global_conf``
81 The inherited configuration for this application. Normally from
88 The inherited configuration for this application. Normally from
82 the [DEFAULT] section of the Paste ini file.
89 the [DEFAULT] section of the Paste ini file.
83
90
84 ``full_stack``
91 ``full_stack``
85 Whether or not this application provides a full WSGI stack (by
92 Whether or not this application provides a full WSGI stack (by
86 default, meaning it handles its own exceptions and errors).
93 default, meaning it handles its own exceptions and errors).
87 Disable full_stack when this application is "managed" by
94 Disable full_stack when this application is "managed" by
88 another WSGI middleware.
95 another WSGI middleware.
89
96
90 ``app_conf``
97 ``app_conf``
91 The application's local configuration. Normally specified in
98 The application's local configuration. Normally specified in
92 the [app:<name>] section of the Paste ini file (where <name>
99 the [app:<name>] section of the Paste ini file (where <name>
93 defaults to main).
100 defaults to main).
94
101
95 """
102 """
96 # Apply compatibility patches
103 # Apply compatibility patches
97 patches.kombu_1_5_1_python_2_7_11()
104 patches.kombu_1_5_1_python_2_7_11()
98 patches.inspect_getargspec()
105 patches.inspect_getargspec()
99
106
100 # Configure the Pylons environment
107 # Configure the Pylons environment
101 config = load_environment(global_conf, app_conf)
108 config = load_environment(global_conf, app_conf)
102
109
103 # The Pylons WSGI app
110 # The Pylons WSGI app
104 app = PylonsApp(config=config)
111 app = PylonsApp(config=config)
105 if rhodecode.is_test:
112 if rhodecode.is_test:
106 app = csrf.CSRFDetector(app)
113 app = csrf.CSRFDetector(app)
107
114
108 expected_origin = config.get('expected_origin')
115 expected_origin = config.get('expected_origin')
109 if expected_origin:
116 if expected_origin:
110 # The API can be accessed from other Origins.
117 # The API can be accessed from other Origins.
111 app = csrf.OriginChecker(app, expected_origin,
118 app = csrf.OriginChecker(app, expected_origin,
112 skip_urls=[routes.util.url_for('api')])
119 skip_urls=[routes.util.url_for('api')])
113
120
114
121
115 if asbool(full_stack):
122 if asbool(full_stack):
116
123
117 # Appenlight monitoring and error handler
124 # Appenlight monitoring and error handler
118 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
125 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
119
126
120 # we want our low level middleware to get to the request ASAP. We don't
127 # we want our low level middleware to get to the request ASAP. We don't
121 # need any pylons stack middleware in them
128 # need any pylons stack middleware in them
122 app = VCSMiddleware(app, config, appenlight_client)
129 app = VCSMiddleware(app, config, appenlight_client)
123
130
124 # Establish the Registry for this application
131 # Establish the Registry for this application
125 app = RegistryManager(app)
132 app = RegistryManager(app)
126
133
127 app.config = config
134 app.config = config
128
135
129 return app
136 return app
130
137
131
138
132 def make_pyramid_app(global_config, **settings):
139 def make_pyramid_app(global_config, **settings):
133 """
140 """
134 Constructs the WSGI application based on Pyramid and wraps the Pylons based
141 Constructs the WSGI application based on Pyramid and wraps the Pylons based
135 application.
142 application.
136
143
137 Specials:
144 Specials:
138
145
139 * We migrate from Pylons to Pyramid. While doing this, we keep both
146 * We migrate from Pylons to Pyramid. While doing this, we keep both
140 frameworks functional. This involves moving some WSGI middlewares around
147 frameworks functional. This involves moving some WSGI middlewares around
141 and providing access to some data internals, so that the old code is
148 and providing access to some data internals, so that the old code is
142 still functional.
149 still functional.
143
150
144 * The application can also be integrated like a plugin via the call to
151 * The application can also be integrated like a plugin via the call to
145 `includeme`. This is accompanied with the other utility functions which
152 `includeme`. This is accompanied with the other utility functions which
146 are called. Changing this should be done with great care to not break
153 are called. Changing this should be done with great care to not break
147 cases when these fragments are assembled from another place.
154 cases when these fragments are assembled from another place.
148
155
149 """
156 """
150 # The edition string should be available in pylons too, so we add it here
157 # The edition string should be available in pylons too, so we add it here
151 # before copying the settings.
158 # before copying the settings.
152 settings.setdefault('rhodecode.edition', 'Community Edition')
159 settings.setdefault('rhodecode.edition', 'Community Edition')
153
160
154 # As long as our Pylons application does expect "unprepared" settings, make
161 # As long as our Pylons application does expect "unprepared" settings, make
155 # sure that we keep an unmodified copy. This avoids unintentional change of
162 # sure that we keep an unmodified copy. This avoids unintentional change of
156 # behavior in the old application.
163 # behavior in the old application.
157 settings_pylons = settings.copy()
164 settings_pylons = settings.copy()
158
165
159 sanitize_settings_and_apply_defaults(settings)
166 sanitize_settings_and_apply_defaults(settings)
160 config = Configurator(settings=settings)
167 config = Configurator(settings=settings)
161 add_pylons_compat_data(config.registry, global_config, settings_pylons)
168 add_pylons_compat_data(config.registry, global_config, settings_pylons)
162
169
163 load_pyramid_environment(global_config, settings)
170 load_pyramid_environment(global_config, settings)
164
171
165 includeme_first(config)
172 includeme_first(config)
166 includeme(config)
173 includeme(config)
167 pyramid_app = config.make_wsgi_app()
174 pyramid_app = config.make_wsgi_app()
168 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
175 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
169 return pyramid_app
176 return pyramid_app
170
177
171
178
172 def add_pylons_compat_data(registry, global_config, settings):
179 def add_pylons_compat_data(registry, global_config, settings):
173 """
180 """
174 Attach data to the registry to support the Pylons integration.
181 Attach data to the registry to support the Pylons integration.
175 """
182 """
176 registry._pylons_compat_global_config = global_config
183 registry._pylons_compat_global_config = global_config
177 registry._pylons_compat_settings = settings
184 registry._pylons_compat_settings = settings
178
185
179
186
180 def webob_to_pyramid_http_response(webob_response):
187 def webob_to_pyramid_http_response(webob_response):
181 ResponseClass = httpexceptions.status_map[webob_response.status_int]
188 ResponseClass = httpexceptions.status_map[webob_response.status_int]
182 pyramid_response = ResponseClass(webob_response.status)
189 pyramid_response = ResponseClass(webob_response.status)
183 pyramid_response.status = webob_response.status
190 pyramid_response.status = webob_response.status
184 pyramid_response.headers.update(webob_response.headers)
191 pyramid_response.headers.update(webob_response.headers)
185 if pyramid_response.headers['content-type'] == 'text/html':
192 if pyramid_response.headers['content-type'] == 'text/html':
186 pyramid_response.headers['content-type'] = 'text/html; charset=UTF-8'
193 pyramid_response.headers['content-type'] = 'text/html; charset=UTF-8'
187 return pyramid_response
194 return pyramid_response
188
195
189
196
190 def error_handler(exception, request):
197 def error_handler(exception, request):
191 # TODO: dan: replace the old pylons error controller with this
198 # TODO: dan: replace the old pylons error controller with this
192 from rhodecode.model.settings import SettingsModel
199 from rhodecode.model.settings import SettingsModel
193 from rhodecode.lib.utils2 import AttributeDict
200 from rhodecode.lib.utils2 import AttributeDict
194
201
195 try:
202 try:
196 rc_config = SettingsModel().get_all_settings()
203 rc_config = SettingsModel().get_all_settings()
197 except Exception:
204 except Exception:
198 log.exception('failed to fetch settings')
205 log.exception('failed to fetch settings')
199 rc_config = {}
206 rc_config = {}
200
207
201 base_response = HTTPInternalServerError()
208 base_response = HTTPInternalServerError()
202 # prefer original exception for the response since it may have headers set
209 # prefer original exception for the response since it may have headers set
203 if isinstance(exception, HTTPError):
210 if isinstance(exception, HTTPError):
204 base_response = exception
211 base_response = exception
205
212
206 c = AttributeDict()
213 c = AttributeDict()
207 c.error_message = base_response.status
214 c.error_message = base_response.status
208 c.error_explanation = base_response.explanation or str(base_response)
215 c.error_explanation = base_response.explanation or str(base_response)
209 c.visual = AttributeDict()
216 c.visual = AttributeDict()
210
217
211 c.visual.rhodecode_support_url = (
218 c.visual.rhodecode_support_url = (
212 request.registry.settings.get('rhodecode_support_url') or
219 request.registry.settings.get('rhodecode_support_url') or
213 request.route_url('rhodecode_support')
220 request.route_url('rhodecode_support')
214 )
221 )
215 c.redirect_time = 0
222 c.redirect_time = 0
216 c.rhodecode_name = rc_config.get('rhodecode_title', '')
223 c.rhodecode_name = rc_config.get('rhodecode_title', '')
217 if not c.rhodecode_name:
224 if not c.rhodecode_name:
218 c.rhodecode_name = 'Rhodecode'
225 c.rhodecode_name = 'Rhodecode'
219
226
220 response = render_to_response(
227 response = render_to_response(
221 '/errors/error_document.html', {'c': c}, request=request,
228 '/errors/error_document.html', {'c': c}, request=request,
222 response=base_response)
229 response=base_response)
223
230
224 return response
231 return response
225
232
226
233
227 def includeme(config):
234 def includeme(config):
228 settings = config.registry.settings
235 settings = config.registry.settings
229
236
230 # plugin information
237 # plugin information
231 config.registry.rhodecode_plugins = {}
238 config.registry.rhodecode_plugins = OrderedDict()
232
239
233 config.add_directive(
240 config.add_directive(
234 'register_rhodecode_plugin', register_rhodecode_plugin)
241 'register_rhodecode_plugin', register_rhodecode_plugin)
235
242
236 if asbool(settings.get('appenlight', 'false')):
243 if asbool(settings.get('appenlight', 'false')):
237 config.include('appenlight_client.ext.pyramid_tween')
244 config.include('appenlight_client.ext.pyramid_tween')
238
245
239 # Includes which are required. The application would fail without them.
246 # Includes which are required. The application would fail without them.
240 config.include('pyramid_mako')
247 config.include('pyramid_mako')
241 config.include('pyramid_beaker')
248 config.include('pyramid_beaker')
249 config.include('rhodecode.channelstream')
242 config.include('rhodecode.admin')
250 config.include('rhodecode.admin')
243 config.include('rhodecode.authentication')
251 config.include('rhodecode.authentication')
244 config.include('rhodecode.integrations')
252 config.include('rhodecode.integrations')
245 config.include('rhodecode.login')
253 config.include('rhodecode.login')
246 config.include('rhodecode.tweens')
254 config.include('rhodecode.tweens')
247 config.include('rhodecode.api')
255 config.include('rhodecode.api')
248 config.add_route(
256 config.add_route(
249 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
257 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
250
258
251 # Set the authorization policy.
259 # Set the authorization policy.
252 authz_policy = ACLAuthorizationPolicy()
260 authz_policy = ACLAuthorizationPolicy()
253 config.set_authorization_policy(authz_policy)
261 config.set_authorization_policy(authz_policy)
254
262
255 # Set the default renderer for HTML templates to mako.
263 # Set the default renderer for HTML templates to mako.
256 config.add_mako_renderer('.html')
264 config.add_mako_renderer('.html')
257
265
258 # include RhodeCode plugins
266 # include RhodeCode plugins
259 includes = aslist(settings.get('rhodecode.includes', []))
267 includes = aslist(settings.get('rhodecode.includes', []))
260 for inc in includes:
268 for inc in includes:
261 config.include(inc)
269 config.include(inc)
262
270
263 pylons_app = make_app(
271 pylons_app = make_app(
264 config.registry._pylons_compat_global_config,
272 config.registry._pylons_compat_global_config,
265 **config.registry._pylons_compat_settings)
273 **config.registry._pylons_compat_settings)
266 config.registry._pylons_compat_config = pylons_app.config
274 config.registry._pylons_compat_config = pylons_app.config
267
275
268 pylons_app_as_view = wsgiapp(pylons_app)
276 pylons_app_as_view = wsgiapp(pylons_app)
269
277
270 # Protect from VCS Server error related pages when server is not available
278 # Protect from VCS Server error related pages when server is not available
271 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
279 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
272 if not vcs_server_enabled:
280 if not vcs_server_enabled:
273 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
281 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
274
282
275
283
276 def pylons_app_with_error_handler(context, request):
284 def pylons_app_with_error_handler(context, request):
277 """
285 """
278 Handle exceptions from rc pylons app:
286 Handle exceptions from rc pylons app:
279
287
280 - old webob type exceptions get converted to pyramid exceptions
288 - old webob type exceptions get converted to pyramid exceptions
281 - pyramid exceptions are passed to the error handler view
289 - pyramid exceptions are passed to the error handler view
282 """
290 """
283 try:
291 try:
284 response = pylons_app_as_view(context, request)
292 response = pylons_app_as_view(context, request)
285 if 400 <= response.status_int <= 599: # webob type error responses
293 if 400 <= response.status_int <= 599: # webob type error responses
286 return error_handler(
294 return error_handler(
287 webob_to_pyramid_http_response(response), request)
295 webob_to_pyramid_http_response(response), request)
288 except HTTPError as e: # pyramid type exceptions
296 except HTTPError as e: # pyramid type exceptions
289 return error_handler(e, request)
297 return error_handler(e, request)
290 except Exception:
298 except Exception:
291 if settings.get('debugtoolbar.enabled', False):
299 if settings.get('debugtoolbar.enabled', False):
292 raise
300 raise
293 return error_handler(HTTPInternalServerError(), request)
301 return error_handler(HTTPInternalServerError(), request)
294 return response
302 return response
295
303
296 # This is the glue which allows us to migrate in chunks. By registering the
304 # This is the glue which allows us to migrate in chunks. By registering the
297 # pylons based application as the "Not Found" view in Pyramid, we will
305 # pylons based application as the "Not Found" view in Pyramid, we will
298 # fallback to the old application each time the new one does not yet know
306 # fallback to the old application each time the new one does not yet know
299 # how to handle a request.
307 # how to handle a request.
300 config.add_notfound_view(pylons_app_with_error_handler)
308 config.add_notfound_view(pylons_app_with_error_handler)
301
309
302 if not settings.get('debugtoolbar.enabled', False):
310 if not settings.get('debugtoolbar.enabled', False):
303 # if no toolbar, then any exception gets caught and rendered
311 # if no toolbar, then any exception gets caught and rendered
304 config.add_view(error_handler, context=Exception)
312 config.add_view(error_handler, context=Exception)
305
313
306 config.add_view(error_handler, context=HTTPError)
314 config.add_view(error_handler, context=HTTPError)
307
315
308
316
309 def includeme_first(config):
317 def includeme_first(config):
310 # redirect automatic browser favicon.ico requests to correct place
318 # redirect automatic browser favicon.ico requests to correct place
311 def favicon_redirect(context, request):
319 def favicon_redirect(context, request):
312 return redirect(
320 return redirect(
313 request.static_url('rhodecode:public/images/favicon.ico'))
321 request.static_url('rhodecode:public/images/favicon.ico'))
314
322
315 config.add_view(favicon_redirect, route_name='favicon')
323 config.add_view(favicon_redirect, route_name='favicon')
316 config.add_route('favicon', '/favicon.ico')
324 config.add_route('favicon', '/favicon.ico')
317
325
318 config.add_static_view(
326 config.add_static_view(
319 '_static/deform', 'deform:static')
327 '_static/deform', 'deform:static')
320 config.add_static_view(
328 config.add_static_view(
321 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
329 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
322
330
323 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
331 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
324 """
332 """
325 Apply outer WSGI middlewares around the application.
333 Apply outer WSGI middlewares around the application.
326
334
327 Part of this has been moved up from the Pylons layer, so that the
335 Part of this has been moved up from the Pylons layer, so that the
328 data is also available if old Pylons code is hit through an already ported
336 data is also available if old Pylons code is hit through an already ported
329 view.
337 view.
330 """
338 """
331 settings = config.registry.settings
339 settings = config.registry.settings
332
340
333 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
341 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
334 pyramid_app = HttpsFixup(pyramid_app, settings)
342 pyramid_app = HttpsFixup(pyramid_app, settings)
335
343
336 # Add RoutesMiddleware to support the pylons compatibility tween during
344 # Add RoutesMiddleware to support the pylons compatibility tween during
337 # migration to pyramid.
345 # migration to pyramid.
338 pyramid_app = SkippableRoutesMiddleware(
346 pyramid_app = SkippableRoutesMiddleware(
339 pyramid_app, config.registry._pylons_compat_config['routes.map'],
347 pyramid_app, config.registry._pylons_compat_config['routes.map'],
340 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
348 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
341
349
342 if asbool(settings.get('appenlight', 'false')):
350 if asbool(settings.get('appenlight', 'false')):
343 pyramid_app, _ = wrap_in_appenlight_if_enabled(
351 pyramid_app, _ = wrap_in_appenlight_if_enabled(
344 pyramid_app, config.registry._pylons_compat_config)
352 pyramid_app, config.registry._pylons_compat_config)
345
353
346 if asbool(settings.get('gzip_responses', 'true')):
354 if asbool(settings.get('gzip_responses', 'true')):
347 pyramid_app = make_gzip_middleware(
355 pyramid_app = make_gzip_middleware(
348 pyramid_app, settings, compress_level=1)
356 pyramid_app, settings, compress_level=1)
349
357
350 return pyramid_app
358 return pyramid_app
351
359
352
360
353 def sanitize_settings_and_apply_defaults(settings):
361 def sanitize_settings_and_apply_defaults(settings):
354 """
362 """
355 Applies settings defaults and does all type conversion.
363 Applies settings defaults and does all type conversion.
356
364
357 We would move all settings parsing and preparation into this place, so that
365 We would move all settings parsing and preparation into this place, so that
358 we have only one place left which deals with this part. The remaining parts
366 we have only one place left which deals with this part. The remaining parts
359 of the application would start to rely fully on well prepared settings.
367 of the application would start to rely fully on well prepared settings.
360
368
361 This piece would later be split up per topic to avoid a big fat monster
369 This piece would later be split up per topic to avoid a big fat monster
362 function.
370 function.
363 """
371 """
364
372
365 # Pyramid's mako renderer has to search in the templates folder so that the
373 # Pyramid's mako renderer has to search in the templates folder so that the
366 # old templates still work. Ported and new templates are expected to use
374 # old templates still work. Ported and new templates are expected to use
367 # real asset specifications for the includes.
375 # real asset specifications for the includes.
368 mako_directories = settings.setdefault('mako.directories', [
376 mako_directories = settings.setdefault('mako.directories', [
369 # Base templates of the original Pylons application
377 # Base templates of the original Pylons application
370 'rhodecode:templates',
378 'rhodecode:templates',
371 ])
379 ])
372 log.debug(
380 log.debug(
373 "Using the following Mako template directories: %s",
381 "Using the following Mako template directories: %s",
374 mako_directories)
382 mako_directories)
375
383
376 # Default includes, possible to change as a user
384 # Default includes, possible to change as a user
377 pyramid_includes = settings.setdefault('pyramid.includes', [
385 pyramid_includes = settings.setdefault('pyramid.includes', [
378 'rhodecode.lib.middleware.request_wrapper',
386 'rhodecode.lib.middleware.request_wrapper',
379 ])
387 ])
380 log.debug(
388 log.debug(
381 "Using the following pyramid.includes: %s",
389 "Using the following pyramid.includes: %s",
382 pyramid_includes)
390 pyramid_includes)
383
391
384 # TODO: johbo: Re-think this, usually the call to config.include
392 # TODO: johbo: Re-think this, usually the call to config.include
385 # should allow to pass in a prefix.
393 # should allow to pass in a prefix.
386 settings.setdefault('rhodecode.api.url', '/_admin/api')
394 settings.setdefault('rhodecode.api.url', '/_admin/api')
387
395
388 _bool_setting(settings, 'vcs.server.enable', 'true')
396 _bool_setting(settings, 'vcs.server.enable', 'true')
389 _bool_setting(settings, 'is_test', 'false')
397 _bool_setting(settings, 'is_test', 'false')
390
398
391 return settings
399 return settings
392
400
393
401
394 def _bool_setting(settings, name, default):
402 def _bool_setting(settings, name, default):
395 settings[name] = asbool(settings.get(name, default))
403 settings[name] = asbool(settings.get(name, default))
@@ -1,1155 +1,1161 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 from rhodecode.config import routing_links
35 from rhodecode.config import routing_links
36
36
37 # prefix for non repository related links needs to be prefixed with `/`
37 # prefix for non repository related links needs to be prefixed with `/`
38 ADMIN_PREFIX = '/_admin'
38 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
39 STATIC_FILE_PREFIX = '/_static'
40
40
41 # Default requirements for URL parts
41 # Default requirements for URL parts
42 URL_NAME_REQUIREMENTS = {
42 URL_NAME_REQUIREMENTS = {
43 # group name can have a slash in them, but they must not end with a slash
43 # group name can have a slash in them, but they must not end with a slash
44 'group_name': r'.*?[^/]',
44 'group_name': r'.*?[^/]',
45 # repo names can have a slash in them, but they must not end with a slash
45 # repo names can have a slash in them, but they must not end with a slash
46 'repo_name': r'.*?[^/]',
46 'repo_name': r'.*?[^/]',
47 # file path eats up everything at the end
47 # file path eats up everything at the end
48 'f_path': r'.*',
48 'f_path': r'.*',
49 # reference types
49 # reference types
50 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 }
52 }
53
53
54
54
55 def add_route_requirements(route_path, requirements):
55 def add_route_requirements(route_path, requirements):
56 """
56 """
57 Adds regex requirements to pyramid routes using a mapping dict
57 Adds regex requirements to pyramid routes using a mapping dict
58
58
59 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
59 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
60 '/{action}/{id:\d+}'
60 '/{action}/{id:\d+}'
61
61
62 """
62 """
63 for key, regex in requirements.items():
63 for key, regex in requirements.items():
64 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
64 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
65 return route_path
65 return route_path
66
66
67
67
68 class JSRoutesMapper(Mapper):
68 class JSRoutesMapper(Mapper):
69 """
69 """
70 Wrapper for routes.Mapper to make pyroutes compatible url definitions
70 Wrapper for routes.Mapper to make pyroutes compatible url definitions
71 """
71 """
72 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
72 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
73 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
73 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
74 def __init__(self, *args, **kw):
74 def __init__(self, *args, **kw):
75 super(JSRoutesMapper, self).__init__(*args, **kw)
75 super(JSRoutesMapper, self).__init__(*args, **kw)
76 self._jsroutes = []
76 self._jsroutes = []
77
77
78 def connect(self, *args, **kw):
78 def connect(self, *args, **kw):
79 """
79 """
80 Wrapper for connect to take an extra argument jsroute=True
80 Wrapper for connect to take an extra argument jsroute=True
81
81
82 :param jsroute: boolean, if True will add the route to the pyroutes list
82 :param jsroute: boolean, if True will add the route to the pyroutes list
83 """
83 """
84 if kw.pop('jsroute', False):
84 if kw.pop('jsroute', False):
85 if not self._named_route_regex.match(args[0]):
85 if not self._named_route_regex.match(args[0]):
86 raise Exception('only named routes can be added to pyroutes')
86 raise Exception('only named routes can be added to pyroutes')
87 self._jsroutes.append(args[0])
87 self._jsroutes.append(args[0])
88
88
89 super(JSRoutesMapper, self).connect(*args, **kw)
89 super(JSRoutesMapper, self).connect(*args, **kw)
90
90
91 def _extract_route_information(self, route):
91 def _extract_route_information(self, route):
92 """
92 """
93 Convert a route into tuple(name, path, args), eg:
93 Convert a route into tuple(name, path, args), eg:
94 ('user_profile', '/profile/%(username)s', ['username'])
94 ('user_profile', '/profile/%(username)s', ['username'])
95 """
95 """
96 routepath = route.routepath
96 routepath = route.routepath
97 def replace(matchobj):
97 def replace(matchobj):
98 if matchobj.group(1):
98 if matchobj.group(1):
99 return "%%(%s)s" % matchobj.group(1).split(':')[0]
99 return "%%(%s)s" % matchobj.group(1).split(':')[0]
100 else:
100 else:
101 return "%%(%s)s" % matchobj.group(2)
101 return "%%(%s)s" % matchobj.group(2)
102
102
103 routepath = self._argument_prog.sub(replace, routepath)
103 routepath = self._argument_prog.sub(replace, routepath)
104 return (
104 return (
105 route.name,
105 route.name,
106 routepath,
106 routepath,
107 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
107 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
108 for arg in self._argument_prog.findall(route.routepath)]
108 for arg in self._argument_prog.findall(route.routepath)]
109 )
109 )
110
110
111 def jsroutes(self):
111 def jsroutes(self):
112 """
112 """
113 Return a list of pyroutes.js compatible routes
113 Return a list of pyroutes.js compatible routes
114 """
114 """
115 for route_name in self._jsroutes:
115 for route_name in self._jsroutes:
116 yield self._extract_route_information(self._routenames[route_name])
116 yield self._extract_route_information(self._routenames[route_name])
117
117
118
118
119 def make_map(config):
119 def make_map(config):
120 """Create, configure and return the routes Mapper"""
120 """Create, configure and return the routes Mapper"""
121 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
121 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
122 always_scan=config['debug'])
122 always_scan=config['debug'])
123 rmap.minimization = False
123 rmap.minimization = False
124 rmap.explicit = False
124 rmap.explicit = False
125
125
126 from rhodecode.lib.utils2 import str2bool
126 from rhodecode.lib.utils2 import str2bool
127 from rhodecode.model import repo, repo_group
127 from rhodecode.model import repo, repo_group
128
128
129 def check_repo(environ, match_dict):
129 def check_repo(environ, match_dict):
130 """
130 """
131 check for valid repository for proper 404 handling
131 check for valid repository for proper 404 handling
132
132
133 :param environ:
133 :param environ:
134 :param match_dict:
134 :param match_dict:
135 """
135 """
136 repo_name = match_dict.get('repo_name')
136 repo_name = match_dict.get('repo_name')
137
137
138 if match_dict.get('f_path'):
138 if match_dict.get('f_path'):
139 # fix for multiple initial slashes that causes errors
139 # fix for multiple initial slashes that causes errors
140 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
140 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 repo_model = repo.RepoModel()
141 repo_model = repo.RepoModel()
142 by_name_match = repo_model.get_by_repo_name(repo_name)
142 by_name_match = repo_model.get_by_repo_name(repo_name)
143 # if we match quickly from database, short circuit the operation,
143 # if we match quickly from database, short circuit the operation,
144 # and validate repo based on the type.
144 # and validate repo based on the type.
145 if by_name_match:
145 if by_name_match:
146 return True
146 return True
147
147
148 by_id_match = repo_model.get_repo_by_id(repo_name)
148 by_id_match = repo_model.get_repo_by_id(repo_name)
149 if by_id_match:
149 if by_id_match:
150 repo_name = by_id_match.repo_name
150 repo_name = by_id_match.repo_name
151 match_dict['repo_name'] = repo_name
151 match_dict['repo_name'] = repo_name
152 return True
152 return True
153
153
154 return False
154 return False
155
155
156 def check_group(environ, match_dict):
156 def check_group(environ, match_dict):
157 """
157 """
158 check for valid repository group path for proper 404 handling
158 check for valid repository group path for proper 404 handling
159
159
160 :param environ:
160 :param environ:
161 :param match_dict:
161 :param match_dict:
162 """
162 """
163 repo_group_name = match_dict.get('group_name')
163 repo_group_name = match_dict.get('group_name')
164 repo_group_model = repo_group.RepoGroupModel()
164 repo_group_model = repo_group.RepoGroupModel()
165 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
165 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 if by_name_match:
166 if by_name_match:
167 return True
167 return True
168
168
169 return False
169 return False
170
170
171 def check_user_group(environ, match_dict):
171 def check_user_group(environ, match_dict):
172 """
172 """
173 check for valid user group for proper 404 handling
173 check for valid user group for proper 404 handling
174
174
175 :param environ:
175 :param environ:
176 :param match_dict:
176 :param match_dict:
177 """
177 """
178 return True
178 return True
179
179
180 def check_int(environ, match_dict):
180 def check_int(environ, match_dict):
181 return match_dict.get('id').isdigit()
181 return match_dict.get('id').isdigit()
182
182
183
183
184 #==========================================================================
184 #==========================================================================
185 # CUSTOM ROUTES HERE
185 # CUSTOM ROUTES HERE
186 #==========================================================================
186 #==========================================================================
187
187
188 # MAIN PAGE
188 # MAIN PAGE
189 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
189 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
190 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
190 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
191 action='goto_switcher_data')
191 action='goto_switcher_data')
192 rmap.connect('repo_list_data', '/_repos', controller='home',
192 rmap.connect('repo_list_data', '/_repos', controller='home',
193 action='repo_list_data')
193 action='repo_list_data')
194
194
195 rmap.connect('user_autocomplete_data', '/_users', controller='home',
195 rmap.connect('user_autocomplete_data', '/_users', controller='home',
196 action='user_autocomplete_data', jsroute=True)
196 action='user_autocomplete_data', jsroute=True)
197 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
197 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
198 action='user_group_autocomplete_data')
198 action='user_group_autocomplete_data')
199
199
200 rmap.connect(
200 rmap.connect(
201 'user_profile', '/_profiles/{username}', controller='users',
201 'user_profile', '/_profiles/{username}', controller='users',
202 action='user_profile')
202 action='user_profile')
203
203
204 # TODO: johbo: Static links, to be replaced by our redirection mechanism
204 # TODO: johbo: Static links, to be replaced by our redirection mechanism
205 rmap.connect('rst_help',
205 rmap.connect('rst_help',
206 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
206 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
207 _static=True)
207 _static=True)
208 rmap.connect('markdown_help',
208 rmap.connect('markdown_help',
209 'http://daringfireball.net/projects/markdown/syntax',
209 'http://daringfireball.net/projects/markdown/syntax',
210 _static=True)
210 _static=True)
211 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
211 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
212 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
212 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
213 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
213 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
214 # TODO: anderson - making this a static link since redirect won't play
214 # TODO: anderson - making this a static link since redirect won't play
215 # nice with POST requests
215 # nice with POST requests
216 rmap.connect('enterprise_license_convert_from_old',
216 rmap.connect('enterprise_license_convert_from_old',
217 'https://rhodecode.com/u/license-upgrade',
217 'https://rhodecode.com/u/license-upgrade',
218 _static=True)
218 _static=True)
219
219
220 routing_links.connect_redirection_links(rmap)
220 routing_links.connect_redirection_links(rmap)
221
221
222 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
222 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
223 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
223 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
224
224
225 # ADMIN REPOSITORY ROUTES
225 # ADMIN REPOSITORY ROUTES
226 with rmap.submapper(path_prefix=ADMIN_PREFIX,
226 with rmap.submapper(path_prefix=ADMIN_PREFIX,
227 controller='admin/repos') as m:
227 controller='admin/repos') as m:
228 m.connect('repos', '/repos',
228 m.connect('repos', '/repos',
229 action='create', conditions={'method': ['POST']})
229 action='create', conditions={'method': ['POST']})
230 m.connect('repos', '/repos',
230 m.connect('repos', '/repos',
231 action='index', conditions={'method': ['GET']})
231 action='index', conditions={'method': ['GET']})
232 m.connect('new_repo', '/create_repository', jsroute=True,
232 m.connect('new_repo', '/create_repository', jsroute=True,
233 action='create_repository', conditions={'method': ['GET']})
233 action='create_repository', conditions={'method': ['GET']})
234 m.connect('/repos/{repo_name}',
234 m.connect('/repos/{repo_name}',
235 action='update', conditions={'method': ['PUT'],
235 action='update', conditions={'method': ['PUT'],
236 'function': check_repo},
236 'function': check_repo},
237 requirements=URL_NAME_REQUIREMENTS)
237 requirements=URL_NAME_REQUIREMENTS)
238 m.connect('delete_repo', '/repos/{repo_name}',
238 m.connect('delete_repo', '/repos/{repo_name}',
239 action='delete', conditions={'method': ['DELETE']},
239 action='delete', conditions={'method': ['DELETE']},
240 requirements=URL_NAME_REQUIREMENTS)
240 requirements=URL_NAME_REQUIREMENTS)
241 m.connect('repo', '/repos/{repo_name}',
241 m.connect('repo', '/repos/{repo_name}',
242 action='show', conditions={'method': ['GET'],
242 action='show', conditions={'method': ['GET'],
243 'function': check_repo},
243 'function': check_repo},
244 requirements=URL_NAME_REQUIREMENTS)
244 requirements=URL_NAME_REQUIREMENTS)
245
245
246 # ADMIN REPOSITORY GROUPS ROUTES
246 # ADMIN REPOSITORY GROUPS ROUTES
247 with rmap.submapper(path_prefix=ADMIN_PREFIX,
247 with rmap.submapper(path_prefix=ADMIN_PREFIX,
248 controller='admin/repo_groups') as m:
248 controller='admin/repo_groups') as m:
249 m.connect('repo_groups', '/repo_groups',
249 m.connect('repo_groups', '/repo_groups',
250 action='create', conditions={'method': ['POST']})
250 action='create', conditions={'method': ['POST']})
251 m.connect('repo_groups', '/repo_groups',
251 m.connect('repo_groups', '/repo_groups',
252 action='index', conditions={'method': ['GET']})
252 action='index', conditions={'method': ['GET']})
253 m.connect('new_repo_group', '/repo_groups/new',
253 m.connect('new_repo_group', '/repo_groups/new',
254 action='new', conditions={'method': ['GET']})
254 action='new', conditions={'method': ['GET']})
255 m.connect('update_repo_group', '/repo_groups/{group_name}',
255 m.connect('update_repo_group', '/repo_groups/{group_name}',
256 action='update', conditions={'method': ['PUT'],
256 action='update', conditions={'method': ['PUT'],
257 'function': check_group},
257 'function': check_group},
258 requirements=URL_NAME_REQUIREMENTS)
258 requirements=URL_NAME_REQUIREMENTS)
259
259
260 # EXTRAS REPO GROUP ROUTES
260 # EXTRAS REPO GROUP ROUTES
261 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
261 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
262 action='edit',
262 action='edit',
263 conditions={'method': ['GET'], 'function': check_group},
263 conditions={'method': ['GET'], 'function': check_group},
264 requirements=URL_NAME_REQUIREMENTS)
264 requirements=URL_NAME_REQUIREMENTS)
265 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
265 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
266 action='edit',
266 action='edit',
267 conditions={'method': ['PUT'], 'function': check_group},
267 conditions={'method': ['PUT'], 'function': check_group},
268 requirements=URL_NAME_REQUIREMENTS)
268 requirements=URL_NAME_REQUIREMENTS)
269
269
270 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
270 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
271 action='edit_repo_group_advanced',
271 action='edit_repo_group_advanced',
272 conditions={'method': ['GET'], 'function': check_group},
272 conditions={'method': ['GET'], 'function': check_group},
273 requirements=URL_NAME_REQUIREMENTS)
273 requirements=URL_NAME_REQUIREMENTS)
274 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
274 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
275 action='edit_repo_group_advanced',
275 action='edit_repo_group_advanced',
276 conditions={'method': ['PUT'], 'function': check_group},
276 conditions={'method': ['PUT'], 'function': check_group},
277 requirements=URL_NAME_REQUIREMENTS)
277 requirements=URL_NAME_REQUIREMENTS)
278
278
279 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
279 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
280 action='edit_repo_group_perms',
280 action='edit_repo_group_perms',
281 conditions={'method': ['GET'], 'function': check_group},
281 conditions={'method': ['GET'], 'function': check_group},
282 requirements=URL_NAME_REQUIREMENTS)
282 requirements=URL_NAME_REQUIREMENTS)
283 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
283 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
284 action='update_perms',
284 action='update_perms',
285 conditions={'method': ['PUT'], 'function': check_group},
285 conditions={'method': ['PUT'], 'function': check_group},
286 requirements=URL_NAME_REQUIREMENTS)
286 requirements=URL_NAME_REQUIREMENTS)
287
287
288 m.connect('delete_repo_group', '/repo_groups/{group_name}',
288 m.connect('delete_repo_group', '/repo_groups/{group_name}',
289 action='delete', conditions={'method': ['DELETE'],
289 action='delete', conditions={'method': ['DELETE'],
290 'function': check_group},
290 'function': check_group},
291 requirements=URL_NAME_REQUIREMENTS)
291 requirements=URL_NAME_REQUIREMENTS)
292
292
293 # ADMIN USER ROUTES
293 # ADMIN USER ROUTES
294 with rmap.submapper(path_prefix=ADMIN_PREFIX,
294 with rmap.submapper(path_prefix=ADMIN_PREFIX,
295 controller='admin/users') as m:
295 controller='admin/users') as m:
296 m.connect('users', '/users',
296 m.connect('users', '/users',
297 action='create', conditions={'method': ['POST']})
297 action='create', conditions={'method': ['POST']})
298 m.connect('users', '/users',
298 m.connect('users', '/users',
299 action='index', conditions={'method': ['GET']})
299 action='index', conditions={'method': ['GET']})
300 m.connect('new_user', '/users/new',
300 m.connect('new_user', '/users/new',
301 action='new', conditions={'method': ['GET']})
301 action='new', conditions={'method': ['GET']})
302 m.connect('update_user', '/users/{user_id}',
302 m.connect('update_user', '/users/{user_id}',
303 action='update', conditions={'method': ['PUT']})
303 action='update', conditions={'method': ['PUT']})
304 m.connect('delete_user', '/users/{user_id}',
304 m.connect('delete_user', '/users/{user_id}',
305 action='delete', conditions={'method': ['DELETE']})
305 action='delete', conditions={'method': ['DELETE']})
306 m.connect('edit_user', '/users/{user_id}/edit',
306 m.connect('edit_user', '/users/{user_id}/edit',
307 action='edit', conditions={'method': ['GET']})
307 action='edit', conditions={'method': ['GET']})
308 m.connect('user', '/users/{user_id}',
308 m.connect('user', '/users/{user_id}',
309 action='show', conditions={'method': ['GET']})
309 action='show', conditions={'method': ['GET']})
310 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
310 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
311 action='reset_password', conditions={'method': ['POST']})
311 action='reset_password', conditions={'method': ['POST']})
312 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
312 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
313 action='create_personal_repo_group', conditions={'method': ['POST']})
313 action='create_personal_repo_group', conditions={'method': ['POST']})
314
314
315 # EXTRAS USER ROUTES
315 # EXTRAS USER ROUTES
316 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
316 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
317 action='edit_advanced', conditions={'method': ['GET']})
317 action='edit_advanced', conditions={'method': ['GET']})
318 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
318 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
319 action='update_advanced', conditions={'method': ['PUT']})
319 action='update_advanced', conditions={'method': ['PUT']})
320
320
321 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
321 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
322 action='edit_auth_tokens', conditions={'method': ['GET']})
322 action='edit_auth_tokens', conditions={'method': ['GET']})
323 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
323 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
324 action='add_auth_token', conditions={'method': ['PUT']})
324 action='add_auth_token', conditions={'method': ['PUT']})
325 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
325 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
326 action='delete_auth_token', conditions={'method': ['DELETE']})
326 action='delete_auth_token', conditions={'method': ['DELETE']})
327
327
328 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
328 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
329 action='edit_global_perms', conditions={'method': ['GET']})
329 action='edit_global_perms', conditions={'method': ['GET']})
330 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
330 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
331 action='update_global_perms', conditions={'method': ['PUT']})
331 action='update_global_perms', conditions={'method': ['PUT']})
332
332
333 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
333 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
334 action='edit_perms_summary', conditions={'method': ['GET']})
334 action='edit_perms_summary', conditions={'method': ['GET']})
335
335
336 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
336 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
337 action='edit_emails', conditions={'method': ['GET']})
337 action='edit_emails', conditions={'method': ['GET']})
338 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
338 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
339 action='add_email', conditions={'method': ['PUT']})
339 action='add_email', conditions={'method': ['PUT']})
340 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
340 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
341 action='delete_email', conditions={'method': ['DELETE']})
341 action='delete_email', conditions={'method': ['DELETE']})
342
342
343 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
343 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
344 action='edit_ips', conditions={'method': ['GET']})
344 action='edit_ips', conditions={'method': ['GET']})
345 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
345 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
346 action='add_ip', conditions={'method': ['PUT']})
346 action='add_ip', conditions={'method': ['PUT']})
347 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
347 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
348 action='delete_ip', conditions={'method': ['DELETE']})
348 action='delete_ip', conditions={'method': ['DELETE']})
349
349
350 # ADMIN USER GROUPS REST ROUTES
350 # ADMIN USER GROUPS REST ROUTES
351 with rmap.submapper(path_prefix=ADMIN_PREFIX,
351 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 controller='admin/user_groups') as m:
352 controller='admin/user_groups') as m:
353 m.connect('users_groups', '/user_groups',
353 m.connect('users_groups', '/user_groups',
354 action='create', conditions={'method': ['POST']})
354 action='create', conditions={'method': ['POST']})
355 m.connect('users_groups', '/user_groups',
355 m.connect('users_groups', '/user_groups',
356 action='index', conditions={'method': ['GET']})
356 action='index', conditions={'method': ['GET']})
357 m.connect('new_users_group', '/user_groups/new',
357 m.connect('new_users_group', '/user_groups/new',
358 action='new', conditions={'method': ['GET']})
358 action='new', conditions={'method': ['GET']})
359 m.connect('update_users_group', '/user_groups/{user_group_id}',
359 m.connect('update_users_group', '/user_groups/{user_group_id}',
360 action='update', conditions={'method': ['PUT']})
360 action='update', conditions={'method': ['PUT']})
361 m.connect('delete_users_group', '/user_groups/{user_group_id}',
361 m.connect('delete_users_group', '/user_groups/{user_group_id}',
362 action='delete', conditions={'method': ['DELETE']})
362 action='delete', conditions={'method': ['DELETE']})
363 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
363 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
364 action='edit', conditions={'method': ['GET']},
364 action='edit', conditions={'method': ['GET']},
365 function=check_user_group)
365 function=check_user_group)
366
366
367 # EXTRAS USER GROUP ROUTES
367 # EXTRAS USER GROUP ROUTES
368 m.connect('edit_user_group_global_perms',
368 m.connect('edit_user_group_global_perms',
369 '/user_groups/{user_group_id}/edit/global_permissions',
369 '/user_groups/{user_group_id}/edit/global_permissions',
370 action='edit_global_perms', conditions={'method': ['GET']})
370 action='edit_global_perms', conditions={'method': ['GET']})
371 m.connect('edit_user_group_global_perms',
371 m.connect('edit_user_group_global_perms',
372 '/user_groups/{user_group_id}/edit/global_permissions',
372 '/user_groups/{user_group_id}/edit/global_permissions',
373 action='update_global_perms', conditions={'method': ['PUT']})
373 action='update_global_perms', conditions={'method': ['PUT']})
374 m.connect('edit_user_group_perms_summary',
374 m.connect('edit_user_group_perms_summary',
375 '/user_groups/{user_group_id}/edit/permissions_summary',
375 '/user_groups/{user_group_id}/edit/permissions_summary',
376 action='edit_perms_summary', conditions={'method': ['GET']})
376 action='edit_perms_summary', conditions={'method': ['GET']})
377
377
378 m.connect('edit_user_group_perms',
378 m.connect('edit_user_group_perms',
379 '/user_groups/{user_group_id}/edit/permissions',
379 '/user_groups/{user_group_id}/edit/permissions',
380 action='edit_perms', conditions={'method': ['GET']})
380 action='edit_perms', conditions={'method': ['GET']})
381 m.connect('edit_user_group_perms',
381 m.connect('edit_user_group_perms',
382 '/user_groups/{user_group_id}/edit/permissions',
382 '/user_groups/{user_group_id}/edit/permissions',
383 action='update_perms', conditions={'method': ['PUT']})
383 action='update_perms', conditions={'method': ['PUT']})
384
384
385 m.connect('edit_user_group_advanced',
385 m.connect('edit_user_group_advanced',
386 '/user_groups/{user_group_id}/edit/advanced',
386 '/user_groups/{user_group_id}/edit/advanced',
387 action='edit_advanced', conditions={'method': ['GET']})
387 action='edit_advanced', conditions={'method': ['GET']})
388
388
389 m.connect('edit_user_group_members',
389 m.connect('edit_user_group_members',
390 '/user_groups/{user_group_id}/edit/members', jsroute=True,
390 '/user_groups/{user_group_id}/edit/members', jsroute=True,
391 action='edit_members', conditions={'method': ['GET']})
391 action='edit_members', conditions={'method': ['GET']})
392
392
393 # ADMIN PERMISSIONS ROUTES
393 # ADMIN PERMISSIONS ROUTES
394 with rmap.submapper(path_prefix=ADMIN_PREFIX,
394 with rmap.submapper(path_prefix=ADMIN_PREFIX,
395 controller='admin/permissions') as m:
395 controller='admin/permissions') as m:
396 m.connect('admin_permissions_application', '/permissions/application',
396 m.connect('admin_permissions_application', '/permissions/application',
397 action='permission_application_update', conditions={'method': ['POST']})
397 action='permission_application_update', conditions={'method': ['POST']})
398 m.connect('admin_permissions_application', '/permissions/application',
398 m.connect('admin_permissions_application', '/permissions/application',
399 action='permission_application', conditions={'method': ['GET']})
399 action='permission_application', conditions={'method': ['GET']})
400
400
401 m.connect('admin_permissions_global', '/permissions/global',
401 m.connect('admin_permissions_global', '/permissions/global',
402 action='permission_global_update', conditions={'method': ['POST']})
402 action='permission_global_update', conditions={'method': ['POST']})
403 m.connect('admin_permissions_global', '/permissions/global',
403 m.connect('admin_permissions_global', '/permissions/global',
404 action='permission_global', conditions={'method': ['GET']})
404 action='permission_global', conditions={'method': ['GET']})
405
405
406 m.connect('admin_permissions_object', '/permissions/object',
406 m.connect('admin_permissions_object', '/permissions/object',
407 action='permission_objects_update', conditions={'method': ['POST']})
407 action='permission_objects_update', conditions={'method': ['POST']})
408 m.connect('admin_permissions_object', '/permissions/object',
408 m.connect('admin_permissions_object', '/permissions/object',
409 action='permission_objects', conditions={'method': ['GET']})
409 action='permission_objects', conditions={'method': ['GET']})
410
410
411 m.connect('admin_permissions_ips', '/permissions/ips',
411 m.connect('admin_permissions_ips', '/permissions/ips',
412 action='permission_ips', conditions={'method': ['POST']})
412 action='permission_ips', conditions={'method': ['POST']})
413 m.connect('admin_permissions_ips', '/permissions/ips',
413 m.connect('admin_permissions_ips', '/permissions/ips',
414 action='permission_ips', conditions={'method': ['GET']})
414 action='permission_ips', conditions={'method': ['GET']})
415
415
416 m.connect('admin_permissions_overview', '/permissions/overview',
416 m.connect('admin_permissions_overview', '/permissions/overview',
417 action='permission_perms', conditions={'method': ['GET']})
417 action='permission_perms', conditions={'method': ['GET']})
418
418
419 # ADMIN DEFAULTS REST ROUTES
419 # ADMIN DEFAULTS REST ROUTES
420 with rmap.submapper(path_prefix=ADMIN_PREFIX,
420 with rmap.submapper(path_prefix=ADMIN_PREFIX,
421 controller='admin/defaults') as m:
421 controller='admin/defaults') as m:
422 m.connect('admin_defaults_repositories', '/defaults/repositories',
422 m.connect('admin_defaults_repositories', '/defaults/repositories',
423 action='update_repository_defaults', conditions={'method': ['POST']})
423 action='update_repository_defaults', conditions={'method': ['POST']})
424 m.connect('admin_defaults_repositories', '/defaults/repositories',
424 m.connect('admin_defaults_repositories', '/defaults/repositories',
425 action='index', conditions={'method': ['GET']})
425 action='index', conditions={'method': ['GET']})
426
426
427 # ADMIN DEBUG STYLE ROUTES
427 # ADMIN DEBUG STYLE ROUTES
428 if str2bool(config.get('debug_style')):
428 if str2bool(config.get('debug_style')):
429 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
429 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
430 controller='debug_style') as m:
430 controller='debug_style') as m:
431 m.connect('debug_style_home', '',
431 m.connect('debug_style_home', '',
432 action='index', conditions={'method': ['GET']})
432 action='index', conditions={'method': ['GET']})
433 m.connect('debug_style_template', '/t/{t_path}',
433 m.connect('debug_style_template', '/t/{t_path}',
434 action='template', conditions={'method': ['GET']})
434 action='template', conditions={'method': ['GET']})
435
435
436 # ADMIN SETTINGS ROUTES
436 # ADMIN SETTINGS ROUTES
437 with rmap.submapper(path_prefix=ADMIN_PREFIX,
437 with rmap.submapper(path_prefix=ADMIN_PREFIX,
438 controller='admin/settings') as m:
438 controller='admin/settings') as m:
439
439
440 # default
440 # default
441 m.connect('admin_settings', '/settings',
441 m.connect('admin_settings', '/settings',
442 action='settings_global_update',
442 action='settings_global_update',
443 conditions={'method': ['POST']})
443 conditions={'method': ['POST']})
444 m.connect('admin_settings', '/settings',
444 m.connect('admin_settings', '/settings',
445 action='settings_global', conditions={'method': ['GET']})
445 action='settings_global', conditions={'method': ['GET']})
446
446
447 m.connect('admin_settings_vcs', '/settings/vcs',
447 m.connect('admin_settings_vcs', '/settings/vcs',
448 action='settings_vcs_update',
448 action='settings_vcs_update',
449 conditions={'method': ['POST']})
449 conditions={'method': ['POST']})
450 m.connect('admin_settings_vcs', '/settings/vcs',
450 m.connect('admin_settings_vcs', '/settings/vcs',
451 action='settings_vcs',
451 action='settings_vcs',
452 conditions={'method': ['GET']})
452 conditions={'method': ['GET']})
453 m.connect('admin_settings_vcs', '/settings/vcs',
453 m.connect('admin_settings_vcs', '/settings/vcs',
454 action='delete_svn_pattern',
454 action='delete_svn_pattern',
455 conditions={'method': ['DELETE']})
455 conditions={'method': ['DELETE']})
456
456
457 m.connect('admin_settings_mapping', '/settings/mapping',
457 m.connect('admin_settings_mapping', '/settings/mapping',
458 action='settings_mapping_update',
458 action='settings_mapping_update',
459 conditions={'method': ['POST']})
459 conditions={'method': ['POST']})
460 m.connect('admin_settings_mapping', '/settings/mapping',
460 m.connect('admin_settings_mapping', '/settings/mapping',
461 action='settings_mapping', conditions={'method': ['GET']})
461 action='settings_mapping', conditions={'method': ['GET']})
462
462
463 m.connect('admin_settings_global', '/settings/global',
463 m.connect('admin_settings_global', '/settings/global',
464 action='settings_global_update',
464 action='settings_global_update',
465 conditions={'method': ['POST']})
465 conditions={'method': ['POST']})
466 m.connect('admin_settings_global', '/settings/global',
466 m.connect('admin_settings_global', '/settings/global',
467 action='settings_global', conditions={'method': ['GET']})
467 action='settings_global', conditions={'method': ['GET']})
468
468
469 m.connect('admin_settings_visual', '/settings/visual',
469 m.connect('admin_settings_visual', '/settings/visual',
470 action='settings_visual_update',
470 action='settings_visual_update',
471 conditions={'method': ['POST']})
471 conditions={'method': ['POST']})
472 m.connect('admin_settings_visual', '/settings/visual',
472 m.connect('admin_settings_visual', '/settings/visual',
473 action='settings_visual', conditions={'method': ['GET']})
473 action='settings_visual', conditions={'method': ['GET']})
474
474
475 m.connect('admin_settings_issuetracker',
475 m.connect('admin_settings_issuetracker',
476 '/settings/issue-tracker', action='settings_issuetracker',
476 '/settings/issue-tracker', action='settings_issuetracker',
477 conditions={'method': ['GET']})
477 conditions={'method': ['GET']})
478 m.connect('admin_settings_issuetracker_save',
478 m.connect('admin_settings_issuetracker_save',
479 '/settings/issue-tracker/save',
479 '/settings/issue-tracker/save',
480 action='settings_issuetracker_save',
480 action='settings_issuetracker_save',
481 conditions={'method': ['POST']})
481 conditions={'method': ['POST']})
482 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
482 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
483 action='settings_issuetracker_test',
483 action='settings_issuetracker_test',
484 conditions={'method': ['POST']})
484 conditions={'method': ['POST']})
485 m.connect('admin_issuetracker_delete',
485 m.connect('admin_issuetracker_delete',
486 '/settings/issue-tracker/delete',
486 '/settings/issue-tracker/delete',
487 action='settings_issuetracker_delete',
487 action='settings_issuetracker_delete',
488 conditions={'method': ['DELETE']})
488 conditions={'method': ['DELETE']})
489
489
490 m.connect('admin_settings_email', '/settings/email',
490 m.connect('admin_settings_email', '/settings/email',
491 action='settings_email_update',
491 action='settings_email_update',
492 conditions={'method': ['POST']})
492 conditions={'method': ['POST']})
493 m.connect('admin_settings_email', '/settings/email',
493 m.connect('admin_settings_email', '/settings/email',
494 action='settings_email', conditions={'method': ['GET']})
494 action='settings_email', conditions={'method': ['GET']})
495
495
496 m.connect('admin_settings_hooks', '/settings/hooks',
496 m.connect('admin_settings_hooks', '/settings/hooks',
497 action='settings_hooks_update',
497 action='settings_hooks_update',
498 conditions={'method': ['POST', 'DELETE']})
498 conditions={'method': ['POST', 'DELETE']})
499 m.connect('admin_settings_hooks', '/settings/hooks',
499 m.connect('admin_settings_hooks', '/settings/hooks',
500 action='settings_hooks', conditions={'method': ['GET']})
500 action='settings_hooks', conditions={'method': ['GET']})
501
501
502 m.connect('admin_settings_search', '/settings/search',
502 m.connect('admin_settings_search', '/settings/search',
503 action='settings_search', conditions={'method': ['GET']})
503 action='settings_search', conditions={'method': ['GET']})
504
504
505 m.connect('admin_settings_system', '/settings/system',
505 m.connect('admin_settings_system', '/settings/system',
506 action='settings_system', conditions={'method': ['GET']})
506 action='settings_system', conditions={'method': ['GET']})
507
507
508 m.connect('admin_settings_system_update', '/settings/system/updates',
508 m.connect('admin_settings_system_update', '/settings/system/updates',
509 action='settings_system_update', conditions={'method': ['GET']})
509 action='settings_system_update', conditions={'method': ['GET']})
510
510
511 m.connect('admin_settings_supervisor', '/settings/supervisor',
511 m.connect('admin_settings_supervisor', '/settings/supervisor',
512 action='settings_supervisor', conditions={'method': ['GET']})
512 action='settings_supervisor', conditions={'method': ['GET']})
513 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
513 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
514 action='settings_supervisor_log', conditions={'method': ['GET']})
514 action='settings_supervisor_log', conditions={'method': ['GET']})
515
515
516 m.connect('admin_settings_labs', '/settings/labs',
516 m.connect('admin_settings_labs', '/settings/labs',
517 action='settings_labs_update',
517 action='settings_labs_update',
518 conditions={'method': ['POST']})
518 conditions={'method': ['POST']})
519 m.connect('admin_settings_labs', '/settings/labs',
519 m.connect('admin_settings_labs', '/settings/labs',
520 action='settings_labs', conditions={'method': ['GET']})
520 action='settings_labs', conditions={'method': ['GET']})
521
521
522 # ADMIN MY ACCOUNT
522 # ADMIN MY ACCOUNT
523 with rmap.submapper(path_prefix=ADMIN_PREFIX,
523 with rmap.submapper(path_prefix=ADMIN_PREFIX,
524 controller='admin/my_account') as m:
524 controller='admin/my_account') as m:
525
525
526 m.connect('my_account', '/my_account',
526 m.connect('my_account', '/my_account',
527 action='my_account', conditions={'method': ['GET']})
527 action='my_account', conditions={'method': ['GET']})
528 m.connect('my_account_edit', '/my_account/edit',
528 m.connect('my_account_edit', '/my_account/edit',
529 action='my_account_edit', conditions={'method': ['GET']})
529 action='my_account_edit', conditions={'method': ['GET']})
530 m.connect('my_account', '/my_account',
530 m.connect('my_account', '/my_account',
531 action='my_account_update', conditions={'method': ['POST']})
531 action='my_account_update', conditions={'method': ['POST']})
532
532
533 m.connect('my_account_password', '/my_account/password',
533 m.connect('my_account_password', '/my_account/password',
534 action='my_account_password', conditions={'method': ['GET']})
534 action='my_account_password', conditions={'method': ['GET']})
535 m.connect('my_account_password', '/my_account/password',
535 m.connect('my_account_password', '/my_account/password',
536 action='my_account_password_update', conditions={'method': ['POST']})
536 action='my_account_password_update', conditions={'method': ['POST']})
537
537
538 m.connect('my_account_repos', '/my_account/repos',
538 m.connect('my_account_repos', '/my_account/repos',
539 action='my_account_repos', conditions={'method': ['GET']})
539 action='my_account_repos', conditions={'method': ['GET']})
540
540
541 m.connect('my_account_watched', '/my_account/watched',
541 m.connect('my_account_watched', '/my_account/watched',
542 action='my_account_watched', conditions={'method': ['GET']})
542 action='my_account_watched', conditions={'method': ['GET']})
543
543
544 m.connect('my_account_pullrequests', '/my_account/pull_requests',
544 m.connect('my_account_pullrequests', '/my_account/pull_requests',
545 action='my_account_pullrequests', conditions={'method': ['GET']})
545 action='my_account_pullrequests', conditions={'method': ['GET']})
546
546
547 m.connect('my_account_perms', '/my_account/perms',
547 m.connect('my_account_perms', '/my_account/perms',
548 action='my_account_perms', conditions={'method': ['GET']})
548 action='my_account_perms', conditions={'method': ['GET']})
549
549
550 m.connect('my_account_emails', '/my_account/emails',
550 m.connect('my_account_emails', '/my_account/emails',
551 action='my_account_emails', conditions={'method': ['GET']})
551 action='my_account_emails', conditions={'method': ['GET']})
552 m.connect('my_account_emails', '/my_account/emails',
552 m.connect('my_account_emails', '/my_account/emails',
553 action='my_account_emails_add', conditions={'method': ['POST']})
553 action='my_account_emails_add', conditions={'method': ['POST']})
554 m.connect('my_account_emails', '/my_account/emails',
554 m.connect('my_account_emails', '/my_account/emails',
555 action='my_account_emails_delete', conditions={'method': ['DELETE']})
555 action='my_account_emails_delete', conditions={'method': ['DELETE']})
556
556
557 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
557 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
558 action='my_account_auth_tokens', conditions={'method': ['GET']})
558 action='my_account_auth_tokens', conditions={'method': ['GET']})
559 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
559 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
560 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
560 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
561 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
561 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
562 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
562 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
563 m.connect('my_account_notifications', '/my_account/notifications',
564 action='my_notifications',
565 conditions={'method': ['GET']})
566 m.connect('my_account_notifications_toggle_visibility',
567 '/my_account/toggle_visibility',
568 action='my_notifications_toggle_visibility',
569 conditions={'method': ['POST']})
563
570
564 # NOTIFICATION REST ROUTES
571 # NOTIFICATION REST ROUTES
565 with rmap.submapper(path_prefix=ADMIN_PREFIX,
572 with rmap.submapper(path_prefix=ADMIN_PREFIX,
566 controller='admin/notifications') as m:
573 controller='admin/notifications') as m:
567 m.connect('notifications', '/notifications',
574 m.connect('notifications', '/notifications',
568 action='index', conditions={'method': ['GET']})
575 action='index', conditions={'method': ['GET']})
569 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
576 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
570 action='mark_all_read', conditions={'method': ['POST']})
577 action='mark_all_read', conditions={'method': ['POST']})
571
572 m.connect('/notifications/{notification_id}',
578 m.connect('/notifications/{notification_id}',
573 action='update', conditions={'method': ['PUT']})
579 action='update', conditions={'method': ['PUT']})
574 m.connect('/notifications/{notification_id}',
580 m.connect('/notifications/{notification_id}',
575 action='delete', conditions={'method': ['DELETE']})
581 action='delete', conditions={'method': ['DELETE']})
576 m.connect('notification', '/notifications/{notification_id}',
582 m.connect('notification', '/notifications/{notification_id}',
577 action='show', conditions={'method': ['GET']})
583 action='show', conditions={'method': ['GET']})
578
584
579 # ADMIN GIST
585 # ADMIN GIST
580 with rmap.submapper(path_prefix=ADMIN_PREFIX,
586 with rmap.submapper(path_prefix=ADMIN_PREFIX,
581 controller='admin/gists') as m:
587 controller='admin/gists') as m:
582 m.connect('gists', '/gists',
588 m.connect('gists', '/gists',
583 action='create', conditions={'method': ['POST']})
589 action='create', conditions={'method': ['POST']})
584 m.connect('gists', '/gists', jsroute=True,
590 m.connect('gists', '/gists', jsroute=True,
585 action='index', conditions={'method': ['GET']})
591 action='index', conditions={'method': ['GET']})
586 m.connect('new_gist', '/gists/new', jsroute=True,
592 m.connect('new_gist', '/gists/new', jsroute=True,
587 action='new', conditions={'method': ['GET']})
593 action='new', conditions={'method': ['GET']})
588
594
589 m.connect('/gists/{gist_id}',
595 m.connect('/gists/{gist_id}',
590 action='delete', conditions={'method': ['DELETE']})
596 action='delete', conditions={'method': ['DELETE']})
591 m.connect('edit_gist', '/gists/{gist_id}/edit',
597 m.connect('edit_gist', '/gists/{gist_id}/edit',
592 action='edit_form', conditions={'method': ['GET']})
598 action='edit_form', conditions={'method': ['GET']})
593 m.connect('edit_gist', '/gists/{gist_id}/edit',
599 m.connect('edit_gist', '/gists/{gist_id}/edit',
594 action='edit', conditions={'method': ['POST']})
600 action='edit', conditions={'method': ['POST']})
595 m.connect(
601 m.connect(
596 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
602 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
597 action='check_revision', conditions={'method': ['GET']})
603 action='check_revision', conditions={'method': ['GET']})
598
604
599 m.connect('gist', '/gists/{gist_id}',
605 m.connect('gist', '/gists/{gist_id}',
600 action='show', conditions={'method': ['GET']})
606 action='show', conditions={'method': ['GET']})
601 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
607 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
602 revision='tip',
608 revision='tip',
603 action='show', conditions={'method': ['GET']})
609 action='show', conditions={'method': ['GET']})
604 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
610 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
605 revision='tip',
611 revision='tip',
606 action='show', conditions={'method': ['GET']})
612 action='show', conditions={'method': ['GET']})
607 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
613 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
608 revision='tip',
614 revision='tip',
609 action='show', conditions={'method': ['GET']},
615 action='show', conditions={'method': ['GET']},
610 requirements=URL_NAME_REQUIREMENTS)
616 requirements=URL_NAME_REQUIREMENTS)
611
617
612 # ADMIN MAIN PAGES
618 # ADMIN MAIN PAGES
613 with rmap.submapper(path_prefix=ADMIN_PREFIX,
619 with rmap.submapper(path_prefix=ADMIN_PREFIX,
614 controller='admin/admin') as m:
620 controller='admin/admin') as m:
615 m.connect('admin_home', '', action='index')
621 m.connect('admin_home', '', action='index')
616 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
622 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
617 action='add_repo')
623 action='add_repo')
618 m.connect(
624 m.connect(
619 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
625 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
620 action='pull_requests')
626 action='pull_requests')
621 m.connect(
627 m.connect(
622 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
628 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
623 action='pull_requests')
629 action='pull_requests')
624
630
625
631
626 # USER JOURNAL
632 # USER JOURNAL
627 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
633 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
628 controller='journal', action='index')
634 controller='journal', action='index')
629 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
635 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
630 controller='journal', action='journal_rss')
636 controller='journal', action='journal_rss')
631 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
637 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
632 controller='journal', action='journal_atom')
638 controller='journal', action='journal_atom')
633
639
634 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
640 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
635 controller='journal', action='public_journal')
641 controller='journal', action='public_journal')
636
642
637 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
643 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
638 controller='journal', action='public_journal_rss')
644 controller='journal', action='public_journal_rss')
639
645
640 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
646 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
641 controller='journal', action='public_journal_rss')
647 controller='journal', action='public_journal_rss')
642
648
643 rmap.connect('public_journal_atom',
649 rmap.connect('public_journal_atom',
644 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
650 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
645 action='public_journal_atom')
651 action='public_journal_atom')
646
652
647 rmap.connect('public_journal_atom_old',
653 rmap.connect('public_journal_atom_old',
648 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
654 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
649 action='public_journal_atom')
655 action='public_journal_atom')
650
656
651 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
657 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
652 controller='journal', action='toggle_following', jsroute=True,
658 controller='journal', action='toggle_following', jsroute=True,
653 conditions={'method': ['POST']})
659 conditions={'method': ['POST']})
654
660
655 # FULL TEXT SEARCH
661 # FULL TEXT SEARCH
656 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
662 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
657 controller='search')
663 controller='search')
658 rmap.connect('search_repo_home', '/{repo_name}/search',
664 rmap.connect('search_repo_home', '/{repo_name}/search',
659 controller='search',
665 controller='search',
660 action='index',
666 action='index',
661 conditions={'function': check_repo},
667 conditions={'function': check_repo},
662 requirements=URL_NAME_REQUIREMENTS)
668 requirements=URL_NAME_REQUIREMENTS)
663
669
664 # FEEDS
670 # FEEDS
665 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
671 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
666 controller='feed', action='rss',
672 controller='feed', action='rss',
667 conditions={'function': check_repo},
673 conditions={'function': check_repo},
668 requirements=URL_NAME_REQUIREMENTS)
674 requirements=URL_NAME_REQUIREMENTS)
669
675
670 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
676 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
671 controller='feed', action='atom',
677 controller='feed', action='atom',
672 conditions={'function': check_repo},
678 conditions={'function': check_repo},
673 requirements=URL_NAME_REQUIREMENTS)
679 requirements=URL_NAME_REQUIREMENTS)
674
680
675 #==========================================================================
681 #==========================================================================
676 # REPOSITORY ROUTES
682 # REPOSITORY ROUTES
677 #==========================================================================
683 #==========================================================================
678
684
679 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
685 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
680 controller='admin/repos', action='repo_creating',
686 controller='admin/repos', action='repo_creating',
681 requirements=URL_NAME_REQUIREMENTS)
687 requirements=URL_NAME_REQUIREMENTS)
682 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
688 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
683 controller='admin/repos', action='repo_check',
689 controller='admin/repos', action='repo_check',
684 requirements=URL_NAME_REQUIREMENTS)
690 requirements=URL_NAME_REQUIREMENTS)
685
691
686 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
692 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
687 controller='summary', action='repo_stats',
693 controller='summary', action='repo_stats',
688 conditions={'function': check_repo},
694 conditions={'function': check_repo},
689 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
695 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
690
696
691 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
697 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
692 controller='summary', action='repo_refs_data', jsroute=True,
698 controller='summary', action='repo_refs_data', jsroute=True,
693 requirements=URL_NAME_REQUIREMENTS)
699 requirements=URL_NAME_REQUIREMENTS)
694 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
700 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
695 controller='summary', action='repo_refs_changelog_data',
701 controller='summary', action='repo_refs_changelog_data',
696 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
702 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
697
703
698 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
704 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
699 controller='changeset', revision='tip', jsroute=True,
705 controller='changeset', revision='tip', jsroute=True,
700 conditions={'function': check_repo},
706 conditions={'function': check_repo},
701 requirements=URL_NAME_REQUIREMENTS)
707 requirements=URL_NAME_REQUIREMENTS)
702 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
708 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
703 controller='changeset', revision='tip', action='changeset_children',
709 controller='changeset', revision='tip', action='changeset_children',
704 conditions={'function': check_repo},
710 conditions={'function': check_repo},
705 requirements=URL_NAME_REQUIREMENTS)
711 requirements=URL_NAME_REQUIREMENTS)
706 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
712 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
707 controller='changeset', revision='tip', action='changeset_parents',
713 controller='changeset', revision='tip', action='changeset_parents',
708 conditions={'function': check_repo},
714 conditions={'function': check_repo},
709 requirements=URL_NAME_REQUIREMENTS)
715 requirements=URL_NAME_REQUIREMENTS)
710
716
711 # repo edit options
717 # repo edit options
712 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
718 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
713 controller='admin/repos', action='edit',
719 controller='admin/repos', action='edit',
714 conditions={'method': ['GET'], 'function': check_repo},
720 conditions={'method': ['GET'], 'function': check_repo},
715 requirements=URL_NAME_REQUIREMENTS)
721 requirements=URL_NAME_REQUIREMENTS)
716
722
717 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
723 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
718 jsroute=True,
724 jsroute=True,
719 controller='admin/repos', action='edit_permissions',
725 controller='admin/repos', action='edit_permissions',
720 conditions={'method': ['GET'], 'function': check_repo},
726 conditions={'method': ['GET'], 'function': check_repo},
721 requirements=URL_NAME_REQUIREMENTS)
727 requirements=URL_NAME_REQUIREMENTS)
722 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
728 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
723 controller='admin/repos', action='edit_permissions_update',
729 controller='admin/repos', action='edit_permissions_update',
724 conditions={'method': ['PUT'], 'function': check_repo},
730 conditions={'method': ['PUT'], 'function': check_repo},
725 requirements=URL_NAME_REQUIREMENTS)
731 requirements=URL_NAME_REQUIREMENTS)
726
732
727 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
733 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
728 controller='admin/repos', action='edit_fields',
734 controller='admin/repos', action='edit_fields',
729 conditions={'method': ['GET'], 'function': check_repo},
735 conditions={'method': ['GET'], 'function': check_repo},
730 requirements=URL_NAME_REQUIREMENTS)
736 requirements=URL_NAME_REQUIREMENTS)
731 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
737 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
732 controller='admin/repos', action='create_repo_field',
738 controller='admin/repos', action='create_repo_field',
733 conditions={'method': ['PUT'], 'function': check_repo},
739 conditions={'method': ['PUT'], 'function': check_repo},
734 requirements=URL_NAME_REQUIREMENTS)
740 requirements=URL_NAME_REQUIREMENTS)
735 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
741 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
736 controller='admin/repos', action='delete_repo_field',
742 controller='admin/repos', action='delete_repo_field',
737 conditions={'method': ['DELETE'], 'function': check_repo},
743 conditions={'method': ['DELETE'], 'function': check_repo},
738 requirements=URL_NAME_REQUIREMENTS)
744 requirements=URL_NAME_REQUIREMENTS)
739
745
740 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
746 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
741 controller='admin/repos', action='edit_advanced',
747 controller='admin/repos', action='edit_advanced',
742 conditions={'method': ['GET'], 'function': check_repo},
748 conditions={'method': ['GET'], 'function': check_repo},
743 requirements=URL_NAME_REQUIREMENTS)
749 requirements=URL_NAME_REQUIREMENTS)
744
750
745 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
751 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
746 controller='admin/repos', action='edit_advanced_locking',
752 controller='admin/repos', action='edit_advanced_locking',
747 conditions={'method': ['PUT'], 'function': check_repo},
753 conditions={'method': ['PUT'], 'function': check_repo},
748 requirements=URL_NAME_REQUIREMENTS)
754 requirements=URL_NAME_REQUIREMENTS)
749 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
755 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
750 controller='admin/repos', action='toggle_locking',
756 controller='admin/repos', action='toggle_locking',
751 conditions={'method': ['GET'], 'function': check_repo},
757 conditions={'method': ['GET'], 'function': check_repo},
752 requirements=URL_NAME_REQUIREMENTS)
758 requirements=URL_NAME_REQUIREMENTS)
753
759
754 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
760 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
755 controller='admin/repos', action='edit_advanced_journal',
761 controller='admin/repos', action='edit_advanced_journal',
756 conditions={'method': ['PUT'], 'function': check_repo},
762 conditions={'method': ['PUT'], 'function': check_repo},
757 requirements=URL_NAME_REQUIREMENTS)
763 requirements=URL_NAME_REQUIREMENTS)
758
764
759 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
765 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
760 controller='admin/repos', action='edit_advanced_fork',
766 controller='admin/repos', action='edit_advanced_fork',
761 conditions={'method': ['PUT'], 'function': check_repo},
767 conditions={'method': ['PUT'], 'function': check_repo},
762 requirements=URL_NAME_REQUIREMENTS)
768 requirements=URL_NAME_REQUIREMENTS)
763
769
764 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
770 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
765 controller='admin/repos', action='edit_caches_form',
771 controller='admin/repos', action='edit_caches_form',
766 conditions={'method': ['GET'], 'function': check_repo},
772 conditions={'method': ['GET'], 'function': check_repo},
767 requirements=URL_NAME_REQUIREMENTS)
773 requirements=URL_NAME_REQUIREMENTS)
768 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
774 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
769 controller='admin/repos', action='edit_caches',
775 controller='admin/repos', action='edit_caches',
770 conditions={'method': ['PUT'], 'function': check_repo},
776 conditions={'method': ['PUT'], 'function': check_repo},
771 requirements=URL_NAME_REQUIREMENTS)
777 requirements=URL_NAME_REQUIREMENTS)
772
778
773 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
779 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
774 controller='admin/repos', action='edit_remote_form',
780 controller='admin/repos', action='edit_remote_form',
775 conditions={'method': ['GET'], 'function': check_repo},
781 conditions={'method': ['GET'], 'function': check_repo},
776 requirements=URL_NAME_REQUIREMENTS)
782 requirements=URL_NAME_REQUIREMENTS)
777 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
783 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
778 controller='admin/repos', action='edit_remote',
784 controller='admin/repos', action='edit_remote',
779 conditions={'method': ['PUT'], 'function': check_repo},
785 conditions={'method': ['PUT'], 'function': check_repo},
780 requirements=URL_NAME_REQUIREMENTS)
786 requirements=URL_NAME_REQUIREMENTS)
781
787
782 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
788 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
783 controller='admin/repos', action='edit_statistics_form',
789 controller='admin/repos', action='edit_statistics_form',
784 conditions={'method': ['GET'], 'function': check_repo},
790 conditions={'method': ['GET'], 'function': check_repo},
785 requirements=URL_NAME_REQUIREMENTS)
791 requirements=URL_NAME_REQUIREMENTS)
786 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
792 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
787 controller='admin/repos', action='edit_statistics',
793 controller='admin/repos', action='edit_statistics',
788 conditions={'method': ['PUT'], 'function': check_repo},
794 conditions={'method': ['PUT'], 'function': check_repo},
789 requirements=URL_NAME_REQUIREMENTS)
795 requirements=URL_NAME_REQUIREMENTS)
790 rmap.connect('repo_settings_issuetracker',
796 rmap.connect('repo_settings_issuetracker',
791 '/{repo_name}/settings/issue-tracker',
797 '/{repo_name}/settings/issue-tracker',
792 controller='admin/repos', action='repo_issuetracker',
798 controller='admin/repos', action='repo_issuetracker',
793 conditions={'method': ['GET'], 'function': check_repo},
799 conditions={'method': ['GET'], 'function': check_repo},
794 requirements=URL_NAME_REQUIREMENTS)
800 requirements=URL_NAME_REQUIREMENTS)
795 rmap.connect('repo_issuetracker_test',
801 rmap.connect('repo_issuetracker_test',
796 '/{repo_name}/settings/issue-tracker/test',
802 '/{repo_name}/settings/issue-tracker/test',
797 controller='admin/repos', action='repo_issuetracker_test',
803 controller='admin/repos', action='repo_issuetracker_test',
798 conditions={'method': ['POST'], 'function': check_repo},
804 conditions={'method': ['POST'], 'function': check_repo},
799 requirements=URL_NAME_REQUIREMENTS)
805 requirements=URL_NAME_REQUIREMENTS)
800 rmap.connect('repo_issuetracker_delete',
806 rmap.connect('repo_issuetracker_delete',
801 '/{repo_name}/settings/issue-tracker/delete',
807 '/{repo_name}/settings/issue-tracker/delete',
802 controller='admin/repos', action='repo_issuetracker_delete',
808 controller='admin/repos', action='repo_issuetracker_delete',
803 conditions={'method': ['DELETE'], 'function': check_repo},
809 conditions={'method': ['DELETE'], 'function': check_repo},
804 requirements=URL_NAME_REQUIREMENTS)
810 requirements=URL_NAME_REQUIREMENTS)
805 rmap.connect('repo_issuetracker_save',
811 rmap.connect('repo_issuetracker_save',
806 '/{repo_name}/settings/issue-tracker/save',
812 '/{repo_name}/settings/issue-tracker/save',
807 controller='admin/repos', action='repo_issuetracker_save',
813 controller='admin/repos', action='repo_issuetracker_save',
808 conditions={'method': ['POST'], 'function': check_repo},
814 conditions={'method': ['POST'], 'function': check_repo},
809 requirements=URL_NAME_REQUIREMENTS)
815 requirements=URL_NAME_REQUIREMENTS)
810 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
816 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
811 controller='admin/repos', action='repo_settings_vcs_update',
817 controller='admin/repos', action='repo_settings_vcs_update',
812 conditions={'method': ['POST'], 'function': check_repo},
818 conditions={'method': ['POST'], 'function': check_repo},
813 requirements=URL_NAME_REQUIREMENTS)
819 requirements=URL_NAME_REQUIREMENTS)
814 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
820 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
815 controller='admin/repos', action='repo_settings_vcs',
821 controller='admin/repos', action='repo_settings_vcs',
816 conditions={'method': ['GET'], 'function': check_repo},
822 conditions={'method': ['GET'], 'function': check_repo},
817 requirements=URL_NAME_REQUIREMENTS)
823 requirements=URL_NAME_REQUIREMENTS)
818 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
824 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
819 controller='admin/repos', action='repo_delete_svn_pattern',
825 controller='admin/repos', action='repo_delete_svn_pattern',
820 conditions={'method': ['DELETE'], 'function': check_repo},
826 conditions={'method': ['DELETE'], 'function': check_repo},
821 requirements=URL_NAME_REQUIREMENTS)
827 requirements=URL_NAME_REQUIREMENTS)
822
828
823 # still working url for backward compat.
829 # still working url for backward compat.
824 rmap.connect('raw_changeset_home_depraced',
830 rmap.connect('raw_changeset_home_depraced',
825 '/{repo_name}/raw-changeset/{revision}',
831 '/{repo_name}/raw-changeset/{revision}',
826 controller='changeset', action='changeset_raw',
832 controller='changeset', action='changeset_raw',
827 revision='tip', conditions={'function': check_repo},
833 revision='tip', conditions={'function': check_repo},
828 requirements=URL_NAME_REQUIREMENTS)
834 requirements=URL_NAME_REQUIREMENTS)
829
835
830 # new URLs
836 # new URLs
831 rmap.connect('changeset_raw_home',
837 rmap.connect('changeset_raw_home',
832 '/{repo_name}/changeset-diff/{revision}',
838 '/{repo_name}/changeset-diff/{revision}',
833 controller='changeset', action='changeset_raw',
839 controller='changeset', action='changeset_raw',
834 revision='tip', conditions={'function': check_repo},
840 revision='tip', conditions={'function': check_repo},
835 requirements=URL_NAME_REQUIREMENTS)
841 requirements=URL_NAME_REQUIREMENTS)
836
842
837 rmap.connect('changeset_patch_home',
843 rmap.connect('changeset_patch_home',
838 '/{repo_name}/changeset-patch/{revision}',
844 '/{repo_name}/changeset-patch/{revision}',
839 controller='changeset', action='changeset_patch',
845 controller='changeset', action='changeset_patch',
840 revision='tip', conditions={'function': check_repo},
846 revision='tip', conditions={'function': check_repo},
841 requirements=URL_NAME_REQUIREMENTS)
847 requirements=URL_NAME_REQUIREMENTS)
842
848
843 rmap.connect('changeset_download_home',
849 rmap.connect('changeset_download_home',
844 '/{repo_name}/changeset-download/{revision}',
850 '/{repo_name}/changeset-download/{revision}',
845 controller='changeset', action='changeset_download',
851 controller='changeset', action='changeset_download',
846 revision='tip', conditions={'function': check_repo},
852 revision='tip', conditions={'function': check_repo},
847 requirements=URL_NAME_REQUIREMENTS)
853 requirements=URL_NAME_REQUIREMENTS)
848
854
849 rmap.connect('changeset_comment',
855 rmap.connect('changeset_comment',
850 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
856 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
851 controller='changeset', revision='tip', action='comment',
857 controller='changeset', revision='tip', action='comment',
852 conditions={'function': check_repo},
858 conditions={'function': check_repo},
853 requirements=URL_NAME_REQUIREMENTS)
859 requirements=URL_NAME_REQUIREMENTS)
854
860
855 rmap.connect('changeset_comment_preview',
861 rmap.connect('changeset_comment_preview',
856 '/{repo_name}/changeset/comment/preview', jsroute=True,
862 '/{repo_name}/changeset/comment/preview', jsroute=True,
857 controller='changeset', action='preview_comment',
863 controller='changeset', action='preview_comment',
858 conditions={'function': check_repo, 'method': ['POST']},
864 conditions={'function': check_repo, 'method': ['POST']},
859 requirements=URL_NAME_REQUIREMENTS)
865 requirements=URL_NAME_REQUIREMENTS)
860
866
861 rmap.connect('changeset_comment_delete',
867 rmap.connect('changeset_comment_delete',
862 '/{repo_name}/changeset/comment/{comment_id}/delete',
868 '/{repo_name}/changeset/comment/{comment_id}/delete',
863 controller='changeset', action='delete_comment',
869 controller='changeset', action='delete_comment',
864 conditions={'function': check_repo, 'method': ['DELETE']},
870 conditions={'function': check_repo, 'method': ['DELETE']},
865 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
871 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
866
872
867 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
873 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
868 controller='changeset', action='changeset_info',
874 controller='changeset', action='changeset_info',
869 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
875 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
870
876
871 rmap.connect('compare_home',
877 rmap.connect('compare_home',
872 '/{repo_name}/compare',
878 '/{repo_name}/compare',
873 controller='compare', action='index',
879 controller='compare', action='index',
874 conditions={'function': check_repo},
880 conditions={'function': check_repo},
875 requirements=URL_NAME_REQUIREMENTS)
881 requirements=URL_NAME_REQUIREMENTS)
876
882
877 rmap.connect('compare_url',
883 rmap.connect('compare_url',
878 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
884 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
879 controller='compare', action='compare',
885 controller='compare', action='compare',
880 conditions={'function': check_repo},
886 conditions={'function': check_repo},
881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
887 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
882
888
883 rmap.connect('pullrequest_home',
889 rmap.connect('pullrequest_home',
884 '/{repo_name}/pull-request/new', controller='pullrequests',
890 '/{repo_name}/pull-request/new', controller='pullrequests',
885 action='index', conditions={'function': check_repo,
891 action='index', conditions={'function': check_repo,
886 'method': ['GET']},
892 'method': ['GET']},
887 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
888
894
889 rmap.connect('pullrequest',
895 rmap.connect('pullrequest',
890 '/{repo_name}/pull-request/new', controller='pullrequests',
896 '/{repo_name}/pull-request/new', controller='pullrequests',
891 action='create', conditions={'function': check_repo,
897 action='create', conditions={'function': check_repo,
892 'method': ['POST']},
898 'method': ['POST']},
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
899 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
894
900
895 rmap.connect('pullrequest_repo_refs',
901 rmap.connect('pullrequest_repo_refs',
896 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
902 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
897 controller='pullrequests',
903 controller='pullrequests',
898 action='get_repo_refs',
904 action='get_repo_refs',
899 conditions={'function': check_repo, 'method': ['GET']},
905 conditions={'function': check_repo, 'method': ['GET']},
900 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
906 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
901
907
902 rmap.connect('pullrequest_repo_destinations',
908 rmap.connect('pullrequest_repo_destinations',
903 '/{repo_name}/pull-request/repo-destinations',
909 '/{repo_name}/pull-request/repo-destinations',
904 controller='pullrequests',
910 controller='pullrequests',
905 action='get_repo_destinations',
911 action='get_repo_destinations',
906 conditions={'function': check_repo, 'method': ['GET']},
912 conditions={'function': check_repo, 'method': ['GET']},
907 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
913 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
908
914
909 rmap.connect('pullrequest_show',
915 rmap.connect('pullrequest_show',
910 '/{repo_name}/pull-request/{pull_request_id}',
916 '/{repo_name}/pull-request/{pull_request_id}',
911 controller='pullrequests',
917 controller='pullrequests',
912 action='show', conditions={'function': check_repo,
918 action='show', conditions={'function': check_repo,
913 'method': ['GET']},
919 'method': ['GET']},
914 requirements=URL_NAME_REQUIREMENTS)
920 requirements=URL_NAME_REQUIREMENTS)
915
921
916 rmap.connect('pullrequest_update',
922 rmap.connect('pullrequest_update',
917 '/{repo_name}/pull-request/{pull_request_id}',
923 '/{repo_name}/pull-request/{pull_request_id}',
918 controller='pullrequests',
924 controller='pullrequests',
919 action='update', conditions={'function': check_repo,
925 action='update', conditions={'function': check_repo,
920 'method': ['PUT']},
926 'method': ['PUT']},
921 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
927 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
922
928
923 rmap.connect('pullrequest_merge',
929 rmap.connect('pullrequest_merge',
924 '/{repo_name}/pull-request/{pull_request_id}',
930 '/{repo_name}/pull-request/{pull_request_id}',
925 controller='pullrequests',
931 controller='pullrequests',
926 action='merge', conditions={'function': check_repo,
932 action='merge', conditions={'function': check_repo,
927 'method': ['POST']},
933 'method': ['POST']},
928 requirements=URL_NAME_REQUIREMENTS)
934 requirements=URL_NAME_REQUIREMENTS)
929
935
930 rmap.connect('pullrequest_delete',
936 rmap.connect('pullrequest_delete',
931 '/{repo_name}/pull-request/{pull_request_id}',
937 '/{repo_name}/pull-request/{pull_request_id}',
932 controller='pullrequests',
938 controller='pullrequests',
933 action='delete', conditions={'function': check_repo,
939 action='delete', conditions={'function': check_repo,
934 'method': ['DELETE']},
940 'method': ['DELETE']},
935 requirements=URL_NAME_REQUIREMENTS)
941 requirements=URL_NAME_REQUIREMENTS)
936
942
937 rmap.connect('pullrequest_show_all',
943 rmap.connect('pullrequest_show_all',
938 '/{repo_name}/pull-request',
944 '/{repo_name}/pull-request',
939 controller='pullrequests',
945 controller='pullrequests',
940 action='show_all', conditions={'function': check_repo,
946 action='show_all', conditions={'function': check_repo,
941 'method': ['GET']},
947 'method': ['GET']},
942 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
948 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
943
949
944 rmap.connect('pullrequest_comment',
950 rmap.connect('pullrequest_comment',
945 '/{repo_name}/pull-request-comment/{pull_request_id}',
951 '/{repo_name}/pull-request-comment/{pull_request_id}',
946 controller='pullrequests',
952 controller='pullrequests',
947 action='comment', conditions={'function': check_repo,
953 action='comment', conditions={'function': check_repo,
948 'method': ['POST']},
954 'method': ['POST']},
949 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
955 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
950
956
951 rmap.connect('pullrequest_comment_delete',
957 rmap.connect('pullrequest_comment_delete',
952 '/{repo_name}/pull-request-comment/{comment_id}/delete',
958 '/{repo_name}/pull-request-comment/{comment_id}/delete',
953 controller='pullrequests', action='delete_comment',
959 controller='pullrequests', action='delete_comment',
954 conditions={'function': check_repo, 'method': ['DELETE']},
960 conditions={'function': check_repo, 'method': ['DELETE']},
955 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
961 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
956
962
957 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
963 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
958 controller='summary', conditions={'function': check_repo},
964 controller='summary', conditions={'function': check_repo},
959 requirements=URL_NAME_REQUIREMENTS)
965 requirements=URL_NAME_REQUIREMENTS)
960
966
961 rmap.connect('branches_home', '/{repo_name}/branches',
967 rmap.connect('branches_home', '/{repo_name}/branches',
962 controller='branches', conditions={'function': check_repo},
968 controller='branches', conditions={'function': check_repo},
963 requirements=URL_NAME_REQUIREMENTS)
969 requirements=URL_NAME_REQUIREMENTS)
964
970
965 rmap.connect('tags_home', '/{repo_name}/tags',
971 rmap.connect('tags_home', '/{repo_name}/tags',
966 controller='tags', conditions={'function': check_repo},
972 controller='tags', conditions={'function': check_repo},
967 requirements=URL_NAME_REQUIREMENTS)
973 requirements=URL_NAME_REQUIREMENTS)
968
974
969 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
975 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
970 controller='bookmarks', conditions={'function': check_repo},
976 controller='bookmarks', conditions={'function': check_repo},
971 requirements=URL_NAME_REQUIREMENTS)
977 requirements=URL_NAME_REQUIREMENTS)
972
978
973 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
979 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
974 controller='changelog', conditions={'function': check_repo},
980 controller='changelog', conditions={'function': check_repo},
975 requirements=URL_NAME_REQUIREMENTS)
981 requirements=URL_NAME_REQUIREMENTS)
976
982
977 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
983 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
978 controller='changelog', action='changelog_summary',
984 controller='changelog', action='changelog_summary',
979 conditions={'function': check_repo},
985 conditions={'function': check_repo},
980 requirements=URL_NAME_REQUIREMENTS)
986 requirements=URL_NAME_REQUIREMENTS)
981
987
982 rmap.connect('changelog_file_home',
988 rmap.connect('changelog_file_home',
983 '/{repo_name}/changelog/{revision}/{f_path}',
989 '/{repo_name}/changelog/{revision}/{f_path}',
984 controller='changelog', f_path=None,
990 controller='changelog', f_path=None,
985 conditions={'function': check_repo},
991 conditions={'function': check_repo},
986 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
992 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
987
993
988 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
994 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
989 controller='changelog', action='changelog_details',
995 controller='changelog', action='changelog_details',
990 conditions={'function': check_repo},
996 conditions={'function': check_repo},
991 requirements=URL_NAME_REQUIREMENTS)
997 requirements=URL_NAME_REQUIREMENTS)
992
998
993 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
999 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
994 controller='files', revision='tip', f_path='',
1000 controller='files', revision='tip', f_path='',
995 conditions={'function': check_repo},
1001 conditions={'function': check_repo},
996 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1002 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
997
1003
998 rmap.connect('files_home_simple_catchrev',
1004 rmap.connect('files_home_simple_catchrev',
999 '/{repo_name}/files/{revision}',
1005 '/{repo_name}/files/{revision}',
1000 controller='files', revision='tip', f_path='',
1006 controller='files', revision='tip', f_path='',
1001 conditions={'function': check_repo},
1007 conditions={'function': check_repo},
1002 requirements=URL_NAME_REQUIREMENTS)
1008 requirements=URL_NAME_REQUIREMENTS)
1003
1009
1004 rmap.connect('files_home_simple_catchall',
1010 rmap.connect('files_home_simple_catchall',
1005 '/{repo_name}/files',
1011 '/{repo_name}/files',
1006 controller='files', revision='tip', f_path='',
1012 controller='files', revision='tip', f_path='',
1007 conditions={'function': check_repo},
1013 conditions={'function': check_repo},
1008 requirements=URL_NAME_REQUIREMENTS)
1014 requirements=URL_NAME_REQUIREMENTS)
1009
1015
1010 rmap.connect('files_history_home',
1016 rmap.connect('files_history_home',
1011 '/{repo_name}/history/{revision}/{f_path}',
1017 '/{repo_name}/history/{revision}/{f_path}',
1012 controller='files', action='history', revision='tip', f_path='',
1018 controller='files', action='history', revision='tip', f_path='',
1013 conditions={'function': check_repo},
1019 conditions={'function': check_repo},
1014 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1020 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1015
1021
1016 rmap.connect('files_authors_home',
1022 rmap.connect('files_authors_home',
1017 '/{repo_name}/authors/{revision}/{f_path}',
1023 '/{repo_name}/authors/{revision}/{f_path}',
1018 controller='files', action='authors', revision='tip', f_path='',
1024 controller='files', action='authors', revision='tip', f_path='',
1019 conditions={'function': check_repo},
1025 conditions={'function': check_repo},
1020 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1026 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1021
1027
1022 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1028 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1023 controller='files', action='diff', f_path='',
1029 controller='files', action='diff', f_path='',
1024 conditions={'function': check_repo},
1030 conditions={'function': check_repo},
1025 requirements=URL_NAME_REQUIREMENTS)
1031 requirements=URL_NAME_REQUIREMENTS)
1026
1032
1027 rmap.connect('files_diff_2way_home',
1033 rmap.connect('files_diff_2way_home',
1028 '/{repo_name}/diff-2way/{f_path}',
1034 '/{repo_name}/diff-2way/{f_path}',
1029 controller='files', action='diff_2way', f_path='',
1035 controller='files', action='diff_2way', f_path='',
1030 conditions={'function': check_repo},
1036 conditions={'function': check_repo},
1031 requirements=URL_NAME_REQUIREMENTS)
1037 requirements=URL_NAME_REQUIREMENTS)
1032
1038
1033 rmap.connect('files_rawfile_home',
1039 rmap.connect('files_rawfile_home',
1034 '/{repo_name}/rawfile/{revision}/{f_path}',
1040 '/{repo_name}/rawfile/{revision}/{f_path}',
1035 controller='files', action='rawfile', revision='tip',
1041 controller='files', action='rawfile', revision='tip',
1036 f_path='', conditions={'function': check_repo},
1042 f_path='', conditions={'function': check_repo},
1037 requirements=URL_NAME_REQUIREMENTS)
1043 requirements=URL_NAME_REQUIREMENTS)
1038
1044
1039 rmap.connect('files_raw_home',
1045 rmap.connect('files_raw_home',
1040 '/{repo_name}/raw/{revision}/{f_path}',
1046 '/{repo_name}/raw/{revision}/{f_path}',
1041 controller='files', action='raw', revision='tip', f_path='',
1047 controller='files', action='raw', revision='tip', f_path='',
1042 conditions={'function': check_repo},
1048 conditions={'function': check_repo},
1043 requirements=URL_NAME_REQUIREMENTS)
1049 requirements=URL_NAME_REQUIREMENTS)
1044
1050
1045 rmap.connect('files_render_home',
1051 rmap.connect('files_render_home',
1046 '/{repo_name}/render/{revision}/{f_path}',
1052 '/{repo_name}/render/{revision}/{f_path}',
1047 controller='files', action='index', revision='tip', f_path='',
1053 controller='files', action='index', revision='tip', f_path='',
1048 rendered=True, conditions={'function': check_repo},
1054 rendered=True, conditions={'function': check_repo},
1049 requirements=URL_NAME_REQUIREMENTS)
1055 requirements=URL_NAME_REQUIREMENTS)
1050
1056
1051 rmap.connect('files_annotate_home',
1057 rmap.connect('files_annotate_home',
1052 '/{repo_name}/annotate/{revision}/{f_path}',
1058 '/{repo_name}/annotate/{revision}/{f_path}',
1053 controller='files', action='index', revision='tip',
1059 controller='files', action='index', revision='tip',
1054 f_path='', annotate=True, conditions={'function': check_repo},
1060 f_path='', annotate=True, conditions={'function': check_repo},
1055 requirements=URL_NAME_REQUIREMENTS)
1061 requirements=URL_NAME_REQUIREMENTS)
1056
1062
1057 rmap.connect('files_edit',
1063 rmap.connect('files_edit',
1058 '/{repo_name}/edit/{revision}/{f_path}',
1064 '/{repo_name}/edit/{revision}/{f_path}',
1059 controller='files', action='edit', revision='tip',
1065 controller='files', action='edit', revision='tip',
1060 f_path='',
1066 f_path='',
1061 conditions={'function': check_repo, 'method': ['POST']},
1067 conditions={'function': check_repo, 'method': ['POST']},
1062 requirements=URL_NAME_REQUIREMENTS)
1068 requirements=URL_NAME_REQUIREMENTS)
1063
1069
1064 rmap.connect('files_edit_home',
1070 rmap.connect('files_edit_home',
1065 '/{repo_name}/edit/{revision}/{f_path}',
1071 '/{repo_name}/edit/{revision}/{f_path}',
1066 controller='files', action='edit_home', revision='tip',
1072 controller='files', action='edit_home', revision='tip',
1067 f_path='', conditions={'function': check_repo},
1073 f_path='', conditions={'function': check_repo},
1068 requirements=URL_NAME_REQUIREMENTS)
1074 requirements=URL_NAME_REQUIREMENTS)
1069
1075
1070 rmap.connect('files_add',
1076 rmap.connect('files_add',
1071 '/{repo_name}/add/{revision}/{f_path}',
1077 '/{repo_name}/add/{revision}/{f_path}',
1072 controller='files', action='add', revision='tip',
1078 controller='files', action='add', revision='tip',
1073 f_path='',
1079 f_path='',
1074 conditions={'function': check_repo, 'method': ['POST']},
1080 conditions={'function': check_repo, 'method': ['POST']},
1075 requirements=URL_NAME_REQUIREMENTS)
1081 requirements=URL_NAME_REQUIREMENTS)
1076
1082
1077 rmap.connect('files_add_home',
1083 rmap.connect('files_add_home',
1078 '/{repo_name}/add/{revision}/{f_path}',
1084 '/{repo_name}/add/{revision}/{f_path}',
1079 controller='files', action='add_home', revision='tip',
1085 controller='files', action='add_home', revision='tip',
1080 f_path='', conditions={'function': check_repo},
1086 f_path='', conditions={'function': check_repo},
1081 requirements=URL_NAME_REQUIREMENTS)
1087 requirements=URL_NAME_REQUIREMENTS)
1082
1088
1083 rmap.connect('files_delete',
1089 rmap.connect('files_delete',
1084 '/{repo_name}/delete/{revision}/{f_path}',
1090 '/{repo_name}/delete/{revision}/{f_path}',
1085 controller='files', action='delete', revision='tip',
1091 controller='files', action='delete', revision='tip',
1086 f_path='',
1092 f_path='',
1087 conditions={'function': check_repo, 'method': ['POST']},
1093 conditions={'function': check_repo, 'method': ['POST']},
1088 requirements=URL_NAME_REQUIREMENTS)
1094 requirements=URL_NAME_REQUIREMENTS)
1089
1095
1090 rmap.connect('files_delete_home',
1096 rmap.connect('files_delete_home',
1091 '/{repo_name}/delete/{revision}/{f_path}',
1097 '/{repo_name}/delete/{revision}/{f_path}',
1092 controller='files', action='delete_home', revision='tip',
1098 controller='files', action='delete_home', revision='tip',
1093 f_path='', conditions={'function': check_repo},
1099 f_path='', conditions={'function': check_repo},
1094 requirements=URL_NAME_REQUIREMENTS)
1100 requirements=URL_NAME_REQUIREMENTS)
1095
1101
1096 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1102 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1097 controller='files', action='archivefile',
1103 controller='files', action='archivefile',
1098 conditions={'function': check_repo},
1104 conditions={'function': check_repo},
1099 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1105 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1100
1106
1101 rmap.connect('files_nodelist_home',
1107 rmap.connect('files_nodelist_home',
1102 '/{repo_name}/nodelist/{revision}/{f_path}',
1108 '/{repo_name}/nodelist/{revision}/{f_path}',
1103 controller='files', action='nodelist',
1109 controller='files', action='nodelist',
1104 conditions={'function': check_repo},
1110 conditions={'function': check_repo},
1105 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1111 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1106
1112
1107 rmap.connect('files_nodetree_full',
1113 rmap.connect('files_nodetree_full',
1108 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1114 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1109 controller='files', action='nodetree_full',
1115 controller='files', action='nodetree_full',
1110 conditions={'function': check_repo},
1116 conditions={'function': check_repo},
1111 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1112
1118
1113 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1119 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1114 controller='forks', action='fork_create',
1120 controller='forks', action='fork_create',
1115 conditions={'function': check_repo, 'method': ['POST']},
1121 conditions={'function': check_repo, 'method': ['POST']},
1116 requirements=URL_NAME_REQUIREMENTS)
1122 requirements=URL_NAME_REQUIREMENTS)
1117
1123
1118 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1124 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1119 controller='forks', action='fork',
1125 controller='forks', action='fork',
1120 conditions={'function': check_repo},
1126 conditions={'function': check_repo},
1121 requirements=URL_NAME_REQUIREMENTS)
1127 requirements=URL_NAME_REQUIREMENTS)
1122
1128
1123 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1129 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1124 controller='forks', action='forks',
1130 controller='forks', action='forks',
1125 conditions={'function': check_repo},
1131 conditions={'function': check_repo},
1126 requirements=URL_NAME_REQUIREMENTS)
1132 requirements=URL_NAME_REQUIREMENTS)
1127
1133
1128 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1134 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1129 controller='followers', action='followers',
1135 controller='followers', action='followers',
1130 conditions={'function': check_repo},
1136 conditions={'function': check_repo},
1131 requirements=URL_NAME_REQUIREMENTS)
1137 requirements=URL_NAME_REQUIREMENTS)
1132
1138
1133 # must be here for proper group/repo catching pattern
1139 # must be here for proper group/repo catching pattern
1134 _connect_with_slash(
1140 _connect_with_slash(
1135 rmap, 'repo_group_home', '/{group_name}',
1141 rmap, 'repo_group_home', '/{group_name}',
1136 controller='home', action='index_repo_group',
1142 controller='home', action='index_repo_group',
1137 conditions={'function': check_group},
1143 conditions={'function': check_group},
1138 requirements=URL_NAME_REQUIREMENTS)
1144 requirements=URL_NAME_REQUIREMENTS)
1139
1145
1140 # catch all, at the end
1146 # catch all, at the end
1141 _connect_with_slash(
1147 _connect_with_slash(
1142 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1148 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1143 controller='summary', action='index',
1149 controller='summary', action='index',
1144 conditions={'function': check_repo},
1150 conditions={'function': check_repo},
1145 requirements=URL_NAME_REQUIREMENTS)
1151 requirements=URL_NAME_REQUIREMENTS)
1146
1152
1147 return rmap
1153 return rmap
1148
1154
1149
1155
1150 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1156 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1151 """
1157 """
1152 Connect a route with an optional trailing slash in `path`.
1158 Connect a route with an optional trailing slash in `path`.
1153 """
1159 """
1154 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1160 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1155 mapper.connect(name, path, *args, **kwargs)
1161 mapper.connect(name, path, *args, **kwargs)
@@ -1,348 +1,362 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2016 RhodeCode GmbH
3 # Copyright (C) 2013-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 my account controller for RhodeCode admin
23 my account controller for RhodeCode admin
24 """
24 """
25
25
26 import logging
26 import logging
27
27
28 import formencode
28 import formencode
29 from formencode import htmlfill
29 from formencode import htmlfill
30 from pylons import request, tmpl_context as c, url, session
30 from pylons import request, tmpl_context as c, url, session
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from sqlalchemy.orm import joinedload
33 from sqlalchemy.orm import joinedload
34
34
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import auth
36 from rhodecode.lib import auth
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
38 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
39 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.utils2 import safe_int, md5
40 from rhodecode.lib.utils2 import safe_int, md5
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 Repository, PullRequest, PullRequestReviewers, UserEmailMap, User,
43 Repository, PullRequest, PullRequestReviewers, UserEmailMap, User,
44 UserFollowing)
44 UserFollowing)
45 from rhodecode.model.forms import UserForm, PasswordChangeForm
45 from rhodecode.model.forms import UserForm, PasswordChangeForm
46 from rhodecode.model.scm import RepoList
46 from rhodecode.model.scm import RepoList
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.auth_token import AuthTokenModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class MyAccountController(BaseController):
55 class MyAccountController(BaseController):
56 """REST Controller styled on the Atom Publishing Protocol"""
56 """REST Controller styled on the Atom Publishing Protocol"""
57 # To properly map this controller, ensure your config/routing.py
57 # To properly map this controller, ensure your config/routing.py
58 # file has a resource setup:
58 # file has a resource setup:
59 # map.resource('setting', 'settings', controller='admin/settings',
59 # map.resource('setting', 'settings', controller='admin/settings',
60 # path_prefix='/admin', name_prefix='admin_')
60 # path_prefix='/admin', name_prefix='admin_')
61
61
62 @LoginRequired()
62 @LoginRequired()
63 @NotAnonymous()
63 @NotAnonymous()
64 def __before__(self):
64 def __before__(self):
65 super(MyAccountController, self).__before__()
65 super(MyAccountController, self).__before__()
66
66
67 def __load_data(self):
67 def __load_data(self):
68 c.user = User.get(c.rhodecode_user.user_id)
68 c.user = User.get(c.rhodecode_user.user_id)
69 if c.user.username == User.DEFAULT_USER:
69 if c.user.username == User.DEFAULT_USER:
70 h.flash(_("You can't edit this user since it's"
70 h.flash(_("You can't edit this user since it's"
71 " crucial for entire application"), category='warning')
71 " crucial for entire application"), category='warning')
72 return redirect(url('users'))
72 return redirect(url('users'))
73
73
74 def _load_my_repos_data(self, watched=False):
74 def _load_my_repos_data(self, watched=False):
75 if watched:
75 if watched:
76 admin = False
76 admin = False
77 follows_repos = Session().query(UserFollowing)\
77 follows_repos = Session().query(UserFollowing)\
78 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
78 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
79 .options(joinedload(UserFollowing.follows_repository))\
79 .options(joinedload(UserFollowing.follows_repository))\
80 .all()
80 .all()
81 repo_list = [x.follows_repository for x in follows_repos]
81 repo_list = [x.follows_repository for x in follows_repos]
82 else:
82 else:
83 admin = True
83 admin = True
84 repo_list = Repository.get_all_repos(
84 repo_list = Repository.get_all_repos(
85 user_id=c.rhodecode_user.user_id)
85 user_id=c.rhodecode_user.user_id)
86 repo_list = RepoList(repo_list, perm_set=[
86 repo_list = RepoList(repo_list, perm_set=[
87 'repository.read', 'repository.write', 'repository.admin'])
87 'repository.read', 'repository.write', 'repository.admin'])
88
88
89 repos_data = RepoModel().get_repos_as_dict(
89 repos_data = RepoModel().get_repos_as_dict(
90 repo_list=repo_list, admin=admin)
90 repo_list=repo_list, admin=admin)
91 # json used to render the grid
91 # json used to render the grid
92 return json.dumps(repos_data)
92 return json.dumps(repos_data)
93
93
94 @auth.CSRFRequired()
94 @auth.CSRFRequired()
95 def my_account_update(self):
95 def my_account_update(self):
96 """
96 """
97 POST /_admin/my_account Updates info of my account
97 POST /_admin/my_account Updates info of my account
98 """
98 """
99 # url('my_account')
99 # url('my_account')
100 c.active = 'profile_edit'
100 c.active = 'profile_edit'
101 self.__load_data()
101 self.__load_data()
102 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
102 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
103 ip_addr=self.ip_addr)
103 ip_addr=self.ip_addr)
104 c.extern_type = c.user.extern_type
104 c.extern_type = c.user.extern_type
105 c.extern_name = c.user.extern_name
105 c.extern_name = c.user.extern_name
106
106
107 defaults = c.user.get_dict()
107 defaults = c.user.get_dict()
108 update = False
108 update = False
109 _form = UserForm(edit=True,
109 _form = UserForm(edit=True,
110 old_data={'user_id': c.rhodecode_user.user_id,
110 old_data={'user_id': c.rhodecode_user.user_id,
111 'email': c.rhodecode_user.email})()
111 'email': c.rhodecode_user.email})()
112 form_result = {}
112 form_result = {}
113 try:
113 try:
114 post_data = dict(request.POST)
114 post_data = dict(request.POST)
115 post_data['new_password'] = ''
115 post_data['new_password'] = ''
116 post_data['password_confirmation'] = ''
116 post_data['password_confirmation'] = ''
117 form_result = _form.to_python(post_data)
117 form_result = _form.to_python(post_data)
118 # skip updating those attrs for my account
118 # skip updating those attrs for my account
119 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
119 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
120 'new_password', 'password_confirmation']
120 'new_password', 'password_confirmation']
121 # TODO: plugin should define if username can be updated
121 # TODO: plugin should define if username can be updated
122 if c.extern_type != "rhodecode":
122 if c.extern_type != "rhodecode":
123 # forbid updating username for external accounts
123 # forbid updating username for external accounts
124 skip_attrs.append('username')
124 skip_attrs.append('username')
125
125
126 UserModel().update_user(
126 UserModel().update_user(
127 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
127 c.rhodecode_user.user_id, skip_attrs=skip_attrs, **form_result)
128 h.flash(_('Your account was updated successfully'),
128 h.flash(_('Your account was updated successfully'),
129 category='success')
129 category='success')
130 Session().commit()
130 Session().commit()
131 update = True
131 update = True
132
132
133 except formencode.Invalid as errors:
133 except formencode.Invalid as errors:
134 return htmlfill.render(
134 return htmlfill.render(
135 render('admin/my_account/my_account.html'),
135 render('admin/my_account/my_account.html'),
136 defaults=errors.value,
136 defaults=errors.value,
137 errors=errors.error_dict or {},
137 errors=errors.error_dict or {},
138 prefix_error=False,
138 prefix_error=False,
139 encoding="UTF-8",
139 encoding="UTF-8",
140 force_defaults=False)
140 force_defaults=False)
141 except Exception:
141 except Exception:
142 log.exception("Exception updating user")
142 log.exception("Exception updating user")
143 h.flash(_('Error occurred during update of user %s')
143 h.flash(_('Error occurred during update of user %s')
144 % form_result.get('username'), category='error')
144 % form_result.get('username'), category='error')
145
145
146 if update:
146 if update:
147 return redirect('my_account')
147 return redirect('my_account')
148
148
149 return htmlfill.render(
149 return htmlfill.render(
150 render('admin/my_account/my_account.html'),
150 render('admin/my_account/my_account.html'),
151 defaults=defaults,
151 defaults=defaults,
152 encoding="UTF-8",
152 encoding="UTF-8",
153 force_defaults=False
153 force_defaults=False
154 )
154 )
155
155
156 def my_account(self):
156 def my_account(self):
157 """
157 """
158 GET /_admin/my_account Displays info about my account
158 GET /_admin/my_account Displays info about my account
159 """
159 """
160 # url('my_account')
160 # url('my_account')
161 c.active = 'profile'
161 c.active = 'profile'
162 self.__load_data()
162 self.__load_data()
163
163
164 defaults = c.user.get_dict()
164 defaults = c.user.get_dict()
165 return htmlfill.render(
165 return htmlfill.render(
166 render('admin/my_account/my_account.html'),
166 render('admin/my_account/my_account.html'),
167 defaults=defaults, encoding="UTF-8", force_defaults=False)
167 defaults=defaults, encoding="UTF-8", force_defaults=False)
168
168
169 def my_account_edit(self):
169 def my_account_edit(self):
170 """
170 """
171 GET /_admin/my_account/edit Displays edit form of my account
171 GET /_admin/my_account/edit Displays edit form of my account
172 """
172 """
173 c.active = 'profile_edit'
173 c.active = 'profile_edit'
174 self.__load_data()
174 self.__load_data()
175 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
175 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
176 ip_addr=self.ip_addr)
176 ip_addr=self.ip_addr)
177 c.extern_type = c.user.extern_type
177 c.extern_type = c.user.extern_type
178 c.extern_name = c.user.extern_name
178 c.extern_name = c.user.extern_name
179
179
180 defaults = c.user.get_dict()
180 defaults = c.user.get_dict()
181 return htmlfill.render(
181 return htmlfill.render(
182 render('admin/my_account/my_account.html'),
182 render('admin/my_account/my_account.html'),
183 defaults=defaults,
183 defaults=defaults,
184 encoding="UTF-8",
184 encoding="UTF-8",
185 force_defaults=False
185 force_defaults=False
186 )
186 )
187
187
188 @auth.CSRFRequired()
188 @auth.CSRFRequired()
189 def my_account_password_update(self):
189 def my_account_password_update(self):
190 c.active = 'password'
190 c.active = 'password'
191 self.__load_data()
191 self.__load_data()
192 _form = PasswordChangeForm(c.rhodecode_user.username)()
192 _form = PasswordChangeForm(c.rhodecode_user.username)()
193 try:
193 try:
194 form_result = _form.to_python(request.POST)
194 form_result = _form.to_python(request.POST)
195 UserModel().update_user(c.rhodecode_user.user_id, **form_result)
195 UserModel().update_user(c.rhodecode_user.user_id, **form_result)
196 instance = c.rhodecode_user.get_instance()
196 instance = c.rhodecode_user.get_instance()
197 instance.update_userdata(force_password_change=False)
197 instance.update_userdata(force_password_change=False)
198 Session().commit()
198 Session().commit()
199 session.setdefault('rhodecode_user', {}).update(
199 session.setdefault('rhodecode_user', {}).update(
200 {'password': md5(instance.password)})
200 {'password': md5(instance.password)})
201 session.save()
201 session.save()
202 h.flash(_("Successfully updated password"), category='success')
202 h.flash(_("Successfully updated password"), category='success')
203 except formencode.Invalid as errors:
203 except formencode.Invalid as errors:
204 return htmlfill.render(
204 return htmlfill.render(
205 render('admin/my_account/my_account.html'),
205 render('admin/my_account/my_account.html'),
206 defaults=errors.value,
206 defaults=errors.value,
207 errors=errors.error_dict or {},
207 errors=errors.error_dict or {},
208 prefix_error=False,
208 prefix_error=False,
209 encoding="UTF-8",
209 encoding="UTF-8",
210 force_defaults=False)
210 force_defaults=False)
211 except Exception:
211 except Exception:
212 log.exception("Exception updating password")
212 log.exception("Exception updating password")
213 h.flash(_('Error occurred during update of user password'),
213 h.flash(_('Error occurred during update of user password'),
214 category='error')
214 category='error')
215 return render('admin/my_account/my_account.html')
215 return render('admin/my_account/my_account.html')
216
216
217 def my_account_password(self):
217 def my_account_password(self):
218 c.active = 'password'
218 c.active = 'password'
219 self.__load_data()
219 self.__load_data()
220 return render('admin/my_account/my_account.html')
220 return render('admin/my_account/my_account.html')
221
221
222 def my_account_repos(self):
222 def my_account_repos(self):
223 c.active = 'repos'
223 c.active = 'repos'
224 self.__load_data()
224 self.__load_data()
225
225
226 # json used to render the grid
226 # json used to render the grid
227 c.data = self._load_my_repos_data()
227 c.data = self._load_my_repos_data()
228 return render('admin/my_account/my_account.html')
228 return render('admin/my_account/my_account.html')
229
229
230 def my_account_watched(self):
230 def my_account_watched(self):
231 c.active = 'watched'
231 c.active = 'watched'
232 self.__load_data()
232 self.__load_data()
233
233
234 # json used to render the grid
234 # json used to render the grid
235 c.data = self._load_my_repos_data(watched=True)
235 c.data = self._load_my_repos_data(watched=True)
236 return render('admin/my_account/my_account.html')
236 return render('admin/my_account/my_account.html')
237
237
238 def my_account_perms(self):
238 def my_account_perms(self):
239 c.active = 'perms'
239 c.active = 'perms'
240 self.__load_data()
240 self.__load_data()
241 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
241 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
242 ip_addr=self.ip_addr)
242 ip_addr=self.ip_addr)
243
243
244 return render('admin/my_account/my_account.html')
244 return render('admin/my_account/my_account.html')
245
245
246 def my_account_emails(self):
246 def my_account_emails(self):
247 c.active = 'emails'
247 c.active = 'emails'
248 self.__load_data()
248 self.__load_data()
249
249
250 c.user_email_map = UserEmailMap.query()\
250 c.user_email_map = UserEmailMap.query()\
251 .filter(UserEmailMap.user == c.user).all()
251 .filter(UserEmailMap.user == c.user).all()
252 return render('admin/my_account/my_account.html')
252 return render('admin/my_account/my_account.html')
253
253
254 @auth.CSRFRequired()
254 @auth.CSRFRequired()
255 def my_account_emails_add(self):
255 def my_account_emails_add(self):
256 email = request.POST.get('new_email')
256 email = request.POST.get('new_email')
257
257
258 try:
258 try:
259 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
259 UserModel().add_extra_email(c.rhodecode_user.user_id, email)
260 Session().commit()
260 Session().commit()
261 h.flash(_("Added new email address `%s` for user account") % email,
261 h.flash(_("Added new email address `%s` for user account") % email,
262 category='success')
262 category='success')
263 except formencode.Invalid as error:
263 except formencode.Invalid as error:
264 msg = error.error_dict['email']
264 msg = error.error_dict['email']
265 h.flash(msg, category='error')
265 h.flash(msg, category='error')
266 except Exception:
266 except Exception:
267 log.exception("Exception in my_account_emails")
267 log.exception("Exception in my_account_emails")
268 h.flash(_('An error occurred during email saving'),
268 h.flash(_('An error occurred during email saving'),
269 category='error')
269 category='error')
270 return redirect(url('my_account_emails'))
270 return redirect(url('my_account_emails'))
271
271
272 @auth.CSRFRequired()
272 @auth.CSRFRequired()
273 def my_account_emails_delete(self):
273 def my_account_emails_delete(self):
274 email_id = request.POST.get('del_email_id')
274 email_id = request.POST.get('del_email_id')
275 user_model = UserModel()
275 user_model = UserModel()
276 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
276 user_model.delete_extra_email(c.rhodecode_user.user_id, email_id)
277 Session().commit()
277 Session().commit()
278 h.flash(_("Removed email address from user account"),
278 h.flash(_("Removed email address from user account"),
279 category='success')
279 category='success')
280 return redirect(url('my_account_emails'))
280 return redirect(url('my_account_emails'))
281
281
282 def my_account_pullrequests(self):
282 def my_account_pullrequests(self):
283 c.active = 'pullrequests'
283 c.active = 'pullrequests'
284 self.__load_data()
284 self.__load_data()
285 c.show_closed = request.GET.get('pr_show_closed')
285 c.show_closed = request.GET.get('pr_show_closed')
286
286
287 def _filter(pr):
287 def _filter(pr):
288 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
288 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
289 if not c.show_closed:
289 if not c.show_closed:
290 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
290 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
291 return s
291 return s
292
292
293 c.my_pull_requests = _filter(
293 c.my_pull_requests = _filter(
294 PullRequest.query().filter(
294 PullRequest.query().filter(
295 PullRequest.user_id == c.rhodecode_user.user_id).all())
295 PullRequest.user_id == c.rhodecode_user.user_id).all())
296 my_prs = [
296 my_prs = [
297 x.pull_request for x in PullRequestReviewers.query().filter(
297 x.pull_request for x in PullRequestReviewers.query().filter(
298 PullRequestReviewers.user_id == c.rhodecode_user.user_id).all()]
298 PullRequestReviewers.user_id == c.rhodecode_user.user_id).all()]
299 c.participate_in_pull_requests = _filter(my_prs)
299 c.participate_in_pull_requests = _filter(my_prs)
300 return render('admin/my_account/my_account.html')
300 return render('admin/my_account/my_account.html')
301
301
302 def my_account_auth_tokens(self):
302 def my_account_auth_tokens(self):
303 c.active = 'auth_tokens'
303 c.active = 'auth_tokens'
304 self.__load_data()
304 self.__load_data()
305 show_expired = True
305 show_expired = True
306 c.lifetime_values = [
306 c.lifetime_values = [
307 (str(-1), _('forever')),
307 (str(-1), _('forever')),
308 (str(5), _('5 minutes')),
308 (str(5), _('5 minutes')),
309 (str(60), _('1 hour')),
309 (str(60), _('1 hour')),
310 (str(60 * 24), _('1 day')),
310 (str(60 * 24), _('1 day')),
311 (str(60 * 24 * 30), _('1 month')),
311 (str(60 * 24 * 30), _('1 month')),
312 ]
312 ]
313 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
313 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
314 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
314 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
315 for x in AuthTokenModel.cls.ROLES]
315 for x in AuthTokenModel.cls.ROLES]
316 c.role_options = [(c.role_values, _("Role"))]
316 c.role_options = [(c.role_values, _("Role"))]
317 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
317 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
318 c.rhodecode_user.user_id, show_expired=show_expired)
318 c.rhodecode_user.user_id, show_expired=show_expired)
319 return render('admin/my_account/my_account.html')
319 return render('admin/my_account/my_account.html')
320
320
321 @auth.CSRFRequired()
321 @auth.CSRFRequired()
322 def my_account_auth_tokens_add(self):
322 def my_account_auth_tokens_add(self):
323 lifetime = safe_int(request.POST.get('lifetime'), -1)
323 lifetime = safe_int(request.POST.get('lifetime'), -1)
324 description = request.POST.get('description')
324 description = request.POST.get('description')
325 role = request.POST.get('role')
325 role = request.POST.get('role')
326 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
326 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
327 role)
327 role)
328 Session().commit()
328 Session().commit()
329 h.flash(_("Auth token successfully created"), category='success')
329 h.flash(_("Auth token successfully created"), category='success')
330 return redirect(url('my_account_auth_tokens'))
330 return redirect(url('my_account_auth_tokens'))
331
331
332 @auth.CSRFRequired()
332 @auth.CSRFRequired()
333 def my_account_auth_tokens_delete(self):
333 def my_account_auth_tokens_delete(self):
334 auth_token = request.POST.get('del_auth_token')
334 auth_token = request.POST.get('del_auth_token')
335 user_id = c.rhodecode_user.user_id
335 user_id = c.rhodecode_user.user_id
336 if request.POST.get('del_auth_token_builtin'):
336 if request.POST.get('del_auth_token_builtin'):
337 user = User.get(user_id)
337 user = User.get(user_id)
338 if user:
338 if user:
339 user.api_key = generate_auth_token(user.username)
339 user.api_key = generate_auth_token(user.username)
340 Session().add(user)
340 Session().add(user)
341 Session().commit()
341 Session().commit()
342 h.flash(_("Auth token successfully reset"), category='success')
342 h.flash(_("Auth token successfully reset"), category='success')
343 elif auth_token:
343 elif auth_token:
344 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
344 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
345 Session().commit()
345 Session().commit()
346 h.flash(_("Auth token successfully deleted"), category='success')
346 h.flash(_("Auth token successfully deleted"), category='success')
347
347
348 return redirect(url('my_account_auth_tokens'))
348 return redirect(url('my_account_auth_tokens'))
349
350 def my_notifications(self):
351 c.active = 'notifications'
352 return render('admin/my_account/my_account.html')
353
354 @auth.CSRFRequired()
355 def my_notifications_toggle_visibility(self):
356 user = c.rhodecode_user.get_instance()
357 user_data = user.user_data
358 status = user_data.get('notification_status', False)
359 user_data['notification_status'] = not status
360 user.user_data = user_data
361 Session().commit()
362 return redirect(url('my_account_notifications'))
@@ -1,177 +1,178 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 notifications controller for RhodeCode
23 notifications controller for RhodeCode
24 """
24 """
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons import request
29 from pylons import request
30 from pylons import tmpl_context as c, url
30 from pylons import tmpl_context as c, url
31 from pylons.controllers.util import redirect, abort
31 from pylons.controllers.util import redirect, abort
32 import webhelpers.paginate
32 import webhelpers.paginate
33 from webob.exc import HTTPBadRequest
33 from webob.exc import HTTPBadRequest
34
34
35 from rhodecode.lib import auth
35 from rhodecode.lib import auth
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous
37 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.base import BaseController, render
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.helpers import Page
39 from rhodecode.lib.helpers import Page
40 from rhodecode.lib.utils2 import safe_int
40 from rhodecode.lib.utils2 import safe_int
41 from rhodecode.model.db import Notification
41 from rhodecode.model.db import Notification
42 from rhodecode.model.notification import NotificationModel
42 from rhodecode.model.notification import NotificationModel
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class NotificationsController(BaseController):
49 class NotificationsController(BaseController):
50 """REST Controller styled on the Atom Publishing Protocol"""
50 """REST Controller styled on the Atom Publishing Protocol"""
51 # To properly map this controller, ensure your config/routing.py
51 # To properly map this controller, ensure your config/routing.py
52 # file has a resource setup:
52 # file has a resource setup:
53 # map.resource('notification', 'notifications', controller='_admin/notifications',
53 # map.resource('notification', 'notifications', controller='_admin/notifications',
54 # path_prefix='/_admin', name_prefix='_admin_')
54 # path_prefix='/_admin', name_prefix='_admin_')
55
55
56 @LoginRequired()
56 @LoginRequired()
57 @NotAnonymous()
57 @NotAnonymous()
58 def __before__(self):
58 def __before__(self):
59 super(NotificationsController, self).__before__()
59 super(NotificationsController, self).__before__()
60
60
61 def index(self):
61 def index(self):
62 """GET /_admin/notifications: All items in the collection"""
62 """GET /_admin/notifications: All items in the collection"""
63 # url('notifications')
63 # url('notifications')
64 c.user = c.rhodecode_user
64 c.user = c.rhodecode_user
65 notif = NotificationModel().get_for_user(c.rhodecode_user.user_id,
65 notif = NotificationModel().get_for_user(c.rhodecode_user.user_id,
66 filter_=request.GET.getall('type'))
66 filter_=request.GET.getall('type'))
67
67
68 p = safe_int(request.GET.get('page', 1), 1)
68 p = safe_int(request.GET.get('page', 1), 1)
69 notifications_url = webhelpers.paginate.PageURL(
69 notifications_url = webhelpers.paginate.PageURL(
70 url('notifications'), request.GET)
70 url('notifications'), request.GET)
71 c.notifications = Page(notif, page=p, items_per_page=10,
71 c.notifications = Page(notif, page=p, items_per_page=10,
72 url=notifications_url)
72 url=notifications_url)
73 c.pull_request_type = Notification.TYPE_PULL_REQUEST
73 c.pull_request_type = Notification.TYPE_PULL_REQUEST
74 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
74 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
75 Notification.TYPE_PULL_REQUEST_COMMENT]
75 Notification.TYPE_PULL_REQUEST_COMMENT]
76
76
77 _current_filter = request.GET.getall('type')
77 _current_filter = request.GET.getall('type')
78 c.current_filter = 'all'
78 c.current_filter = 'all'
79 if _current_filter == [c.pull_request_type]:
79 if _current_filter == [c.pull_request_type]:
80 c.current_filter = 'pull_request'
80 c.current_filter = 'pull_request'
81 elif _current_filter == c.comment_type:
81 elif _current_filter == c.comment_type:
82 c.current_filter = 'comment'
82 c.current_filter = 'comment'
83
83
84 if request.is_xhr:
84 if request.is_xhr:
85 return render('admin/notifications/notifications_data.html')
85 return render('admin/notifications/notifications_data.html')
86
86
87 return render('admin/notifications/notifications.html')
87 return render('admin/notifications/notifications.html')
88
88
89
89 @auth.CSRFRequired()
90 @auth.CSRFRequired()
90 def mark_all_read(self):
91 def mark_all_read(self):
91 if request.is_xhr:
92 if request.is_xhr:
92 nm = NotificationModel()
93 nm = NotificationModel()
93 # mark all read
94 # mark all read
94 nm.mark_all_read_for_user(c.rhodecode_user.user_id,
95 nm.mark_all_read_for_user(c.rhodecode_user.user_id,
95 filter_=request.GET.getall('type'))
96 filter_=request.GET.getall('type'))
96 Session().commit()
97 Session().commit()
97 c.user = c.rhodecode_user
98 c.user = c.rhodecode_user
98 notif = nm.get_for_user(c.rhodecode_user.user_id,
99 notif = nm.get_for_user(c.rhodecode_user.user_id,
99 filter_=request.GET.getall('type'))
100 filter_=request.GET.getall('type'))
100 notifications_url = webhelpers.paginate.PageURL(
101 notifications_url = webhelpers.paginate.PageURL(
101 url('notifications'), request.GET)
102 url('notifications'), request.GET)
102 c.notifications = Page(notif, page=1, items_per_page=10,
103 c.notifications = Page(notif, page=1, items_per_page=10,
103 url=notifications_url)
104 url=notifications_url)
104 return render('admin/notifications/notifications_data.html')
105 return render('admin/notifications/notifications_data.html')
105
106
106 def _has_permissions(self, notification):
107 def _has_permissions(self, notification):
107 def is_owner():
108 def is_owner():
108 user_id = c.rhodecode_user.user_id
109 user_id = c.rhodecode_user.user_id
109 for user_notification in notification.notifications_to_users:
110 for user_notification in notification.notifications_to_users:
110 if user_notification.user.user_id == user_id:
111 if user_notification.user.user_id == user_id:
111 return True
112 return True
112 return False
113 return False
113 return h.HasPermissionAny('hg.admin')() or is_owner()
114 return h.HasPermissionAny('hg.admin')() or is_owner()
114
115
115 @auth.CSRFRequired()
116 @auth.CSRFRequired()
116 def update(self, notification_id):
117 def update(self, notification_id):
117 """PUT /_admin/notifications/id: Update an existing item"""
118 """PUT /_admin/notifications/id: Update an existing item"""
118 # Forms posted to this method should contain a hidden field:
119 # Forms posted to this method should contain a hidden field:
119 # <input type="hidden" name="_method" value="PUT" />
120 # <input type="hidden" name="_method" value="PUT" />
120 # Or using helpers:
121 # Or using helpers:
121 # h.form(url('notification', notification_id=ID),
122 # h.form(url('notification', notification_id=ID),
122 # method='put')
123 # method='put')
123 # url('notification', notification_id=ID)
124 # url('notification', notification_id=ID)
124 try:
125 try:
125 no = Notification.get(notification_id)
126 no = Notification.get(notification_id)
126 if self._has_permissions(no):
127 if self._has_permissions(no):
127 # deletes only notification2user
128 # deletes only notification2user
128 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
129 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
129 Session().commit()
130 Session().commit()
130 return 'ok'
131 return 'ok'
131 except Exception:
132 except Exception:
132 Session().rollback()
133 Session().rollback()
133 log.exception("Exception updating a notification item")
134 log.exception("Exception updating a notification item")
134 raise HTTPBadRequest()
135 raise HTTPBadRequest()
135
136
136 @auth.CSRFRequired()
137 @auth.CSRFRequired()
137 def delete(self, notification_id):
138 def delete(self, notification_id):
138 """DELETE /_admin/notifications/id: Delete an existing item"""
139 """DELETE /_admin/notifications/id: Delete an existing item"""
139 # Forms posted to this method should contain a hidden field:
140 # Forms posted to this method should contain a hidden field:
140 # <input type="hidden" name="_method" value="DELETE" />
141 # <input type="hidden" name="_method" value="DELETE" />
141 # Or using helpers:
142 # Or using helpers:
142 # h.form(url('notification', notification_id=ID),
143 # h.form(url('notification', notification_id=ID),
143 # method='delete')
144 # method='delete')
144 # url('notification', notification_id=ID)
145 # url('notification', notification_id=ID)
145 try:
146 try:
146 no = Notification.get(notification_id)
147 no = Notification.get(notification_id)
147 if self._has_permissions(no):
148 if self._has_permissions(no):
148 # deletes only notification2user
149 # deletes only notification2user
149 NotificationModel().delete(c.rhodecode_user.user_id, no)
150 NotificationModel().delete(c.rhodecode_user.user_id, no)
150 Session().commit()
151 Session().commit()
151 return 'ok'
152 return 'ok'
152 except Exception:
153 except Exception:
153 Session().rollback()
154 Session().rollback()
154 log.exception("Exception deleting a notification item")
155 log.exception("Exception deleting a notification item")
155 raise HTTPBadRequest()
156 raise HTTPBadRequest()
156
157
157 def show(self, notification_id):
158 def show(self, notification_id):
158 """GET /_admin/notifications/id: Show a specific item"""
159 """GET /_admin/notifications/id: Show a specific item"""
159 # url('notification', notification_id=ID)
160 # url('notification', notification_id=ID)
160 c.user = c.rhodecode_user
161 c.user = c.rhodecode_user
161 no = Notification.get(notification_id)
162 no = Notification.get(notification_id)
162
163
163 if no and self._has_permissions(no):
164 if no and self._has_permissions(no):
164 unotification = NotificationModel()\
165 unotification = NotificationModel()\
165 .get_user_notification(c.user.user_id, no)
166 .get_user_notification(c.user.user_id, no)
166
167
167 # if this association to user is not valid, we don't want to show
168 # if this association to user is not valid, we don't want to show
168 # this message
169 # this message
169 if unotification:
170 if unotification:
170 if not unotification.read:
171 if not unotification.read:
171 unotification.mark_as_read()
172 unotification.mark_as_read()
172 Session().commit()
173 Session().commit()
173 c.notification = no
174 c.notification = no
174
175
175 return render('admin/notifications/show_notification.html')
176 return render('admin/notifications/show_notification.html')
176
177
177 return abort(403)
178 return abort(403)
@@ -1,584 +1,585 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import ipaddress
30 import ipaddress
31
31
32 from paste.auth.basic import AuthBasicAuthenticator
32 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
33 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
34 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from pylons import config, tmpl_context as c, request, session, url
35 from pylons import config, tmpl_context as c, request, session, url
36 from pylons.controllers import WSGIController
36 from pylons.controllers import WSGIController
37 from pylons.controllers.util import redirect
37 from pylons.controllers.util import redirect
38 from pylons.i18n import translation
38 from pylons.i18n import translation
39 # marcink: don't remove this import
39 # marcink: don't remove this import
40 from pylons.templating import render_mako as render # noqa
40 from pylons.templating import render_mako as render # noqa
41 from pylons.i18n.translation import _
41 from pylons.i18n.translation import _
42 from webob.exc import HTTPFound
42 from webob.exc import HTTPFound
43
43
44
44
45 import rhodecode
45 import rhodecode
46 from rhodecode.authentication.base import VCS_TYPE
46 from rhodecode.authentication.base import VCS_TYPE
47 from rhodecode.lib import auth, utils2
47 from rhodecode.lib import auth, utils2
48 from rhodecode.lib import helpers as h
48 from rhodecode.lib import helpers as h
49 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
49 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
50 from rhodecode.lib.exceptions import UserCreationError
50 from rhodecode.lib.exceptions import UserCreationError
51 from rhodecode.lib.utils import (
51 from rhodecode.lib.utils import (
52 get_repo_slug, set_rhodecode_config, password_changed,
52 get_repo_slug, set_rhodecode_config, password_changed,
53 get_enabled_hook_classes)
53 get_enabled_hook_classes)
54 from rhodecode.lib.utils2 import (
54 from rhodecode.lib.utils2 import (
55 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
55 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
56 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
56 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
57 from rhodecode.model import meta
57 from rhodecode.model import meta
58 from rhodecode.model.db import Repository, User
58 from rhodecode.model.db import Repository, User
59 from rhodecode.model.notification import NotificationModel
59 from rhodecode.model.notification import NotificationModel
60 from rhodecode.model.scm import ScmModel
60 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
61 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
62
62
63
63
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66
66
67 def _filter_proxy(ip):
67 def _filter_proxy(ip):
68 """
68 """
69 Passed in IP addresses in HEADERS can be in a special format of multiple
69 Passed in IP addresses in HEADERS can be in a special format of multiple
70 ips. Those comma separated IPs are passed from various proxies in the
70 ips. Those comma separated IPs are passed from various proxies in the
71 chain of request processing. The left-most being the original client.
71 chain of request processing. The left-most being the original client.
72 We only care about the first IP which came from the org. client.
72 We only care about the first IP which came from the org. client.
73
73
74 :param ip: ip string from headers
74 :param ip: ip string from headers
75 """
75 """
76 if ',' in ip:
76 if ',' in ip:
77 _ips = ip.split(',')
77 _ips = ip.split(',')
78 _first_ip = _ips[0].strip()
78 _first_ip = _ips[0].strip()
79 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
79 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
80 return _first_ip
80 return _first_ip
81 return ip
81 return ip
82
82
83
83
84 def _filter_port(ip):
84 def _filter_port(ip):
85 """
85 """
86 Removes a port from ip, there are 4 main cases to handle here.
86 Removes a port from ip, there are 4 main cases to handle here.
87 - ipv4 eg. 127.0.0.1
87 - ipv4 eg. 127.0.0.1
88 - ipv6 eg. ::1
88 - ipv6 eg. ::1
89 - ipv4+port eg. 127.0.0.1:8080
89 - ipv4+port eg. 127.0.0.1:8080
90 - ipv6+port eg. [::1]:8080
90 - ipv6+port eg. [::1]:8080
91
91
92 :param ip:
92 :param ip:
93 """
93 """
94 def is_ipv6(ip_addr):
94 def is_ipv6(ip_addr):
95 if hasattr(socket, 'inet_pton'):
95 if hasattr(socket, 'inet_pton'):
96 try:
96 try:
97 socket.inet_pton(socket.AF_INET6, ip_addr)
97 socket.inet_pton(socket.AF_INET6, ip_addr)
98 except socket.error:
98 except socket.error:
99 return False
99 return False
100 else:
100 else:
101 # fallback to ipaddress
101 # fallback to ipaddress
102 try:
102 try:
103 ipaddress.IPv6Address(ip_addr)
103 ipaddress.IPv6Address(ip_addr)
104 except Exception:
104 except Exception:
105 return False
105 return False
106 return True
106 return True
107
107
108 if ':' not in ip: # must be ipv4 pure ip
108 if ':' not in ip: # must be ipv4 pure ip
109 return ip
109 return ip
110
110
111 if '[' in ip and ']' in ip: # ipv6 with port
111 if '[' in ip and ']' in ip: # ipv6 with port
112 return ip.split(']')[0][1:].lower()
112 return ip.split(']')[0][1:].lower()
113
113
114 # must be ipv6 or ipv4 with port
114 # must be ipv6 or ipv4 with port
115 if is_ipv6(ip):
115 if is_ipv6(ip):
116 return ip
116 return ip
117 else:
117 else:
118 ip, _port = ip.split(':')[:2] # means ipv4+port
118 ip, _port = ip.split(':')[:2] # means ipv4+port
119 return ip
119 return ip
120
120
121
121
122 def get_ip_addr(environ):
122 def get_ip_addr(environ):
123 proxy_key = 'HTTP_X_REAL_IP'
123 proxy_key = 'HTTP_X_REAL_IP'
124 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
124 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
125 def_key = 'REMOTE_ADDR'
125 def_key = 'REMOTE_ADDR'
126 _filters = lambda x: _filter_port(_filter_proxy(x))
126 _filters = lambda x: _filter_port(_filter_proxy(x))
127
127
128 ip = environ.get(proxy_key)
128 ip = environ.get(proxy_key)
129 if ip:
129 if ip:
130 return _filters(ip)
130 return _filters(ip)
131
131
132 ip = environ.get(proxy_key2)
132 ip = environ.get(proxy_key2)
133 if ip:
133 if ip:
134 return _filters(ip)
134 return _filters(ip)
135
135
136 ip = environ.get(def_key, '0.0.0.0')
136 ip = environ.get(def_key, '0.0.0.0')
137 return _filters(ip)
137 return _filters(ip)
138
138
139
139
140 def get_server_ip_addr(environ, log_errors=True):
140 def get_server_ip_addr(environ, log_errors=True):
141 hostname = environ.get('SERVER_NAME')
141 hostname = environ.get('SERVER_NAME')
142 try:
142 try:
143 return socket.gethostbyname(hostname)
143 return socket.gethostbyname(hostname)
144 except Exception as e:
144 except Exception as e:
145 if log_errors:
145 if log_errors:
146 # in some cases this lookup is not possible, and we don't want to
146 # in some cases this lookup is not possible, and we don't want to
147 # make it an exception in logs
147 # make it an exception in logs
148 log.exception('Could not retrieve server ip address: %s', e)
148 log.exception('Could not retrieve server ip address: %s', e)
149 return hostname
149 return hostname
150
150
151
151
152 def get_server_port(environ):
152 def get_server_port(environ):
153 return environ.get('SERVER_PORT')
153 return environ.get('SERVER_PORT')
154
154
155
155
156 def get_access_path(environ):
156 def get_access_path(environ):
157 path = environ.get('PATH_INFO')
157 path = environ.get('PATH_INFO')
158 org_req = environ.get('pylons.original_request')
158 org_req = environ.get('pylons.original_request')
159 if org_req:
159 if org_req:
160 path = org_req.environ.get('PATH_INFO')
160 path = org_req.environ.get('PATH_INFO')
161 return path
161 return path
162
162
163
163
164 def vcs_operation_context(
164 def vcs_operation_context(
165 environ, repo_name, username, action, scm, check_locking=True):
165 environ, repo_name, username, action, scm, check_locking=True):
166 """
166 """
167 Generate the context for a vcs operation, e.g. push or pull.
167 Generate the context for a vcs operation, e.g. push or pull.
168
168
169 This context is passed over the layers so that hooks triggered by the
169 This context is passed over the layers so that hooks triggered by the
170 vcs operation know details like the user, the user's IP address etc.
170 vcs operation know details like the user, the user's IP address etc.
171
171
172 :param check_locking: Allows to switch of the computation of the locking
172 :param check_locking: Allows to switch of the computation of the locking
173 data. This serves mainly the need of the simplevcs middleware to be
173 data. This serves mainly the need of the simplevcs middleware to be
174 able to disable this for certain operations.
174 able to disable this for certain operations.
175
175
176 """
176 """
177 # Tri-state value: False: unlock, None: nothing, True: lock
177 # Tri-state value: False: unlock, None: nothing, True: lock
178 make_lock = None
178 make_lock = None
179 locked_by = [None, None, None]
179 locked_by = [None, None, None]
180 is_anonymous = username == User.DEFAULT_USER
180 is_anonymous = username == User.DEFAULT_USER
181 if not is_anonymous and check_locking:
181 if not is_anonymous and check_locking:
182 log.debug('Checking locking on repository "%s"', repo_name)
182 log.debug('Checking locking on repository "%s"', repo_name)
183 user = User.get_by_username(username)
183 user = User.get_by_username(username)
184 repo = Repository.get_by_repo_name(repo_name)
184 repo = Repository.get_by_repo_name(repo_name)
185 make_lock, __, locked_by = repo.get_locking_state(
185 make_lock, __, locked_by = repo.get_locking_state(
186 action, user.user_id)
186 action, user.user_id)
187
187
188 settings_model = VcsSettingsModel(repo=repo_name)
188 settings_model = VcsSettingsModel(repo=repo_name)
189 ui_settings = settings_model.get_ui_settings()
189 ui_settings = settings_model.get_ui_settings()
190
190
191 extras = {
191 extras = {
192 'ip': get_ip_addr(environ),
192 'ip': get_ip_addr(environ),
193 'username': username,
193 'username': username,
194 'action': action,
194 'action': action,
195 'repository': repo_name,
195 'repository': repo_name,
196 'scm': scm,
196 'scm': scm,
197 'config': rhodecode.CONFIG['__file__'],
197 'config': rhodecode.CONFIG['__file__'],
198 'make_lock': make_lock,
198 'make_lock': make_lock,
199 'locked_by': locked_by,
199 'locked_by': locked_by,
200 'server_url': utils2.get_server_url(environ),
200 'server_url': utils2.get_server_url(environ),
201 'hooks': get_enabled_hook_classes(ui_settings),
201 'hooks': get_enabled_hook_classes(ui_settings),
202 }
202 }
203 return extras
203 return extras
204
204
205
205
206 class BasicAuth(AuthBasicAuthenticator):
206 class BasicAuth(AuthBasicAuthenticator):
207
207
208 def __init__(self, realm, authfunc, auth_http_code=None,
208 def __init__(self, realm, authfunc, auth_http_code=None,
209 initial_call_detection=False):
209 initial_call_detection=False):
210 self.realm = realm
210 self.realm = realm
211 self.initial_call = initial_call_detection
211 self.initial_call = initial_call_detection
212 self.authfunc = authfunc
212 self.authfunc = authfunc
213 self._rc_auth_http_code = auth_http_code
213 self._rc_auth_http_code = auth_http_code
214
214
215 def _get_response_from_code(self, http_code):
215 def _get_response_from_code(self, http_code):
216 try:
216 try:
217 return get_exception(safe_int(http_code))
217 return get_exception(safe_int(http_code))
218 except Exception:
218 except Exception:
219 log.exception('Failed to fetch response for code %s' % http_code)
219 log.exception('Failed to fetch response for code %s' % http_code)
220 return HTTPForbidden
220 return HTTPForbidden
221
221
222 def build_authentication(self):
222 def build_authentication(self):
223 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
223 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
224 if self._rc_auth_http_code and not self.initial_call:
224 if self._rc_auth_http_code and not self.initial_call:
225 # return alternative HTTP code if alternative http return code
225 # return alternative HTTP code if alternative http return code
226 # is specified in RhodeCode config, but ONLY if it's not the
226 # is specified in RhodeCode config, but ONLY if it's not the
227 # FIRST call
227 # FIRST call
228 custom_response_klass = self._get_response_from_code(
228 custom_response_klass = self._get_response_from_code(
229 self._rc_auth_http_code)
229 self._rc_auth_http_code)
230 return custom_response_klass(headers=head)
230 return custom_response_klass(headers=head)
231 return HTTPUnauthorized(headers=head)
231 return HTTPUnauthorized(headers=head)
232
232
233 def authenticate(self, environ):
233 def authenticate(self, environ):
234 authorization = AUTHORIZATION(environ)
234 authorization = AUTHORIZATION(environ)
235 if not authorization:
235 if not authorization:
236 return self.build_authentication()
236 return self.build_authentication()
237 (authmeth, auth) = authorization.split(' ', 1)
237 (authmeth, auth) = authorization.split(' ', 1)
238 if 'basic' != authmeth.lower():
238 if 'basic' != authmeth.lower():
239 return self.build_authentication()
239 return self.build_authentication()
240 auth = auth.strip().decode('base64')
240 auth = auth.strip().decode('base64')
241 _parts = auth.split(':', 1)
241 _parts = auth.split(':', 1)
242 if len(_parts) == 2:
242 if len(_parts) == 2:
243 username, password = _parts
243 username, password = _parts
244 if self.authfunc(
244 if self.authfunc(
245 username, password, environ, VCS_TYPE):
245 username, password, environ, VCS_TYPE):
246 return username
246 return username
247 if username and password:
247 if username and password:
248 # we mark that we actually executed authentication once, at
248 # we mark that we actually executed authentication once, at
249 # that point we can use the alternative auth code
249 # that point we can use the alternative auth code
250 self.initial_call = False
250 self.initial_call = False
251
251
252 return self.build_authentication()
252 return self.build_authentication()
253
253
254 __call__ = authenticate
254 __call__ = authenticate
255
255
256
256
257 def attach_context_attributes(context, request):
257 def attach_context_attributes(context, request):
258 """
258 """
259 Attach variables into template context called `c`, please note that
259 Attach variables into template context called `c`, please note that
260 request could be pylons or pyramid request in here.
260 request could be pylons or pyramid request in here.
261 """
261 """
262 rc_config = SettingsModel().get_all_settings(cache=True)
262 rc_config = SettingsModel().get_all_settings(cache=True)
263
263
264 context.rhodecode_version = rhodecode.__version__
264 context.rhodecode_version = rhodecode.__version__
265 context.rhodecode_edition = config.get('rhodecode.edition')
265 context.rhodecode_edition = config.get('rhodecode.edition')
266 # unique secret + version does not leak the version but keep consistency
266 # unique secret + version does not leak the version but keep consistency
267 context.rhodecode_version_hash = md5(
267 context.rhodecode_version_hash = md5(
268 config.get('beaker.session.secret', '') +
268 config.get('beaker.session.secret', '') +
269 rhodecode.__version__)[:8]
269 rhodecode.__version__)[:8]
270
270
271 # Default language set for the incoming request
271 # Default language set for the incoming request
272 context.language = translation.get_lang()[0]
272 context.language = translation.get_lang()[0]
273
273
274 # Visual options
274 # Visual options
275 context.visual = AttributeDict({})
275 context.visual = AttributeDict({})
276
276
277 # DB store
277 # DB store
278 context.visual.show_public_icon = str2bool(
278 context.visual.show_public_icon = str2bool(
279 rc_config.get('rhodecode_show_public_icon'))
279 rc_config.get('rhodecode_show_public_icon'))
280 context.visual.show_private_icon = str2bool(
280 context.visual.show_private_icon = str2bool(
281 rc_config.get('rhodecode_show_private_icon'))
281 rc_config.get('rhodecode_show_private_icon'))
282 context.visual.stylify_metatags = str2bool(
282 context.visual.stylify_metatags = str2bool(
283 rc_config.get('rhodecode_stylify_metatags'))
283 rc_config.get('rhodecode_stylify_metatags'))
284 context.visual.dashboard_items = safe_int(
284 context.visual.dashboard_items = safe_int(
285 rc_config.get('rhodecode_dashboard_items', 100))
285 rc_config.get('rhodecode_dashboard_items', 100))
286 context.visual.admin_grid_items = safe_int(
286 context.visual.admin_grid_items = safe_int(
287 rc_config.get('rhodecode_admin_grid_items', 100))
287 rc_config.get('rhodecode_admin_grid_items', 100))
288 context.visual.repository_fields = str2bool(
288 context.visual.repository_fields = str2bool(
289 rc_config.get('rhodecode_repository_fields'))
289 rc_config.get('rhodecode_repository_fields'))
290 context.visual.show_version = str2bool(
290 context.visual.show_version = str2bool(
291 rc_config.get('rhodecode_show_version'))
291 rc_config.get('rhodecode_show_version'))
292 context.visual.use_gravatar = str2bool(
292 context.visual.use_gravatar = str2bool(
293 rc_config.get('rhodecode_use_gravatar'))
293 rc_config.get('rhodecode_use_gravatar'))
294 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
294 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
295 context.visual.default_renderer = rc_config.get(
295 context.visual.default_renderer = rc_config.get(
296 'rhodecode_markup_renderer', 'rst')
296 'rhodecode_markup_renderer', 'rst')
297 context.visual.rhodecode_support_url = \
297 context.visual.rhodecode_support_url = \
298 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
298 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
299
299
300 context.pre_code = rc_config.get('rhodecode_pre_code')
300 context.pre_code = rc_config.get('rhodecode_pre_code')
301 context.post_code = rc_config.get('rhodecode_post_code')
301 context.post_code = rc_config.get('rhodecode_post_code')
302 context.rhodecode_name = rc_config.get('rhodecode_title')
302 context.rhodecode_name = rc_config.get('rhodecode_title')
303 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
303 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
304 # if we have specified default_encoding in the request, it has more
304 # if we have specified default_encoding in the request, it has more
305 # priority
305 # priority
306 if request.GET.get('default_encoding'):
306 if request.GET.get('default_encoding'):
307 context.default_encodings.insert(0, request.GET.get('default_encoding'))
307 context.default_encodings.insert(0, request.GET.get('default_encoding'))
308 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
308 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
309
309
310 # INI stored
310 # INI stored
311 context.labs_active = str2bool(
311 context.labs_active = str2bool(
312 config.get('labs_settings_active', 'false'))
312 config.get('labs_settings_active', 'false'))
313 context.visual.allow_repo_location_change = str2bool(
313 context.visual.allow_repo_location_change = str2bool(
314 config.get('allow_repo_location_change', True))
314 config.get('allow_repo_location_change', True))
315 context.visual.allow_custom_hooks_settings = str2bool(
315 context.visual.allow_custom_hooks_settings = str2bool(
316 config.get('allow_custom_hooks_settings', True))
316 config.get('allow_custom_hooks_settings', True))
317 context.debug_style = str2bool(config.get('debug_style', False))
317 context.debug_style = str2bool(config.get('debug_style', False))
318
318
319 context.rhodecode_instanceid = config.get('instance_id')
319 context.rhodecode_instanceid = config.get('instance_id')
320
320
321 # AppEnlight
321 # AppEnlight
322 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
322 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
323 context.appenlight_api_public_key = config.get(
323 context.appenlight_api_public_key = config.get(
324 'appenlight.api_public_key', '')
324 'appenlight.api_public_key', '')
325 context.appenlight_server_url = config.get('appenlight.server_url', '')
325 context.appenlight_server_url = config.get('appenlight.server_url', '')
326
326
327 # JS template context
327 # JS template context
328 context.template_context = {
328 context.template_context = {
329 'repo_name': None,
329 'repo_name': None,
330 'repo_type': None,
330 'repo_type': None,
331 'repo_landing_commit': None,
331 'repo_landing_commit': None,
332 'rhodecode_user': {
332 'rhodecode_user': {
333 'username': None,
333 'username': None,
334 'email': None,
334 'email': None,
335 'notification_status': False
335 },
336 },
336 'visual': {
337 'visual': {
337 'default_renderer': None
338 'default_renderer': None
338 },
339 },
339 'commit_data': {
340 'commit_data': {
340 'commit_id': None
341 'commit_id': None
341 },
342 },
342 'pull_request_data': {'pull_request_id': None},
343 'pull_request_data': {'pull_request_id': None},
343 'timeago': {
344 'timeago': {
344 'refresh_time': 120 * 1000,
345 'refresh_time': 120 * 1000,
345 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
346 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
346 },
347 },
347 'pylons_dispatch': {
348 'pylons_dispatch': {
348 # 'controller': request.environ['pylons.routes_dict']['controller'],
349 # 'controller': request.environ['pylons.routes_dict']['controller'],
349 # 'action': request.environ['pylons.routes_dict']['action'],
350 # 'action': request.environ['pylons.routes_dict']['action'],
350 },
351 },
351 'pyramid_dispatch': {
352 'pyramid_dispatch': {
352
353
353 },
354 },
354 'extra': {'plugins': {}}
355 'extra': {'plugins': {}}
355 }
356 }
356 # END CONFIG VARS
357 # END CONFIG VARS
357
358
358 # TODO: This dosn't work when called from pylons compatibility tween.
359 # TODO: This dosn't work when called from pylons compatibility tween.
359 # Fix this and remove it from base controller.
360 # Fix this and remove it from base controller.
360 # context.repo_name = get_repo_slug(request) # can be empty
361 # context.repo_name = get_repo_slug(request) # can be empty
361
362
362 context.csrf_token = auth.get_csrf_token()
363 context.csrf_token = auth.get_csrf_token()
363 context.backends = rhodecode.BACKENDS.keys()
364 context.backends = rhodecode.BACKENDS.keys()
364 context.backends.sort()
365 context.backends.sort()
365 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
366 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
366 context.rhodecode_user.user_id)
367 context.rhodecode_user.user_id)
367
368
368
369
369 def get_auth_user(environ):
370 def get_auth_user(environ):
370 ip_addr = get_ip_addr(environ)
371 ip_addr = get_ip_addr(environ)
371 # make sure that we update permissions each time we call controller
372 # make sure that we update permissions each time we call controller
372 _auth_token = (request.GET.get('auth_token', '') or
373 _auth_token = (request.GET.get('auth_token', '') or
373 request.GET.get('api_key', ''))
374 request.GET.get('api_key', ''))
374
375
375 if _auth_token:
376 if _auth_token:
376 # when using API_KEY we are sure user exists.
377 # when using API_KEY we are sure user exists.
377 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
378 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
378 authenticated = False
379 authenticated = False
379 else:
380 else:
380 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
381 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
381 try:
382 try:
382 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
383 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
383 ip_addr=ip_addr)
384 ip_addr=ip_addr)
384 except UserCreationError as e:
385 except UserCreationError as e:
385 h.flash(e, 'error')
386 h.flash(e, 'error')
386 # container auth or other auth functions that create users
387 # container auth or other auth functions that create users
387 # on the fly can throw this exception signaling that there's
388 # on the fly can throw this exception signaling that there's
388 # issue with user creation, explanation should be provided
389 # issue with user creation, explanation should be provided
389 # in Exception itself. We then create a simple blank
390 # in Exception itself. We then create a simple blank
390 # AuthUser
391 # AuthUser
391 auth_user = AuthUser(ip_addr=ip_addr)
392 auth_user = AuthUser(ip_addr=ip_addr)
392
393
393 if password_changed(auth_user, session):
394 if password_changed(auth_user, session):
394 session.invalidate()
395 session.invalidate()
395 cookie_store = CookieStoreWrapper(
396 cookie_store = CookieStoreWrapper(
396 session.get('rhodecode_user'))
397 session.get('rhodecode_user'))
397 auth_user = AuthUser(ip_addr=ip_addr)
398 auth_user = AuthUser(ip_addr=ip_addr)
398
399
399 authenticated = cookie_store.get('is_authenticated')
400 authenticated = cookie_store.get('is_authenticated')
400
401
401 if not auth_user.is_authenticated and auth_user.is_user_object:
402 if not auth_user.is_authenticated and auth_user.is_user_object:
402 # user is not authenticated and not empty
403 # user is not authenticated and not empty
403 auth_user.set_authenticated(authenticated)
404 auth_user.set_authenticated(authenticated)
404
405
405 return auth_user
406 return auth_user
406
407
407
408
408 class BaseController(WSGIController):
409 class BaseController(WSGIController):
409
410
410 def __before__(self):
411 def __before__(self):
411 """
412 """
412 __before__ is called before controller methods and after __call__
413 __before__ is called before controller methods and after __call__
413 """
414 """
414 # on each call propagate settings calls into global settings.
415 # on each call propagate settings calls into global settings.
415 set_rhodecode_config(config)
416 set_rhodecode_config(config)
416 attach_context_attributes(c, request)
417 attach_context_attributes(c, request)
417
418
418 # TODO: Remove this when fixed in attach_context_attributes()
419 # TODO: Remove this when fixed in attach_context_attributes()
419 c.repo_name = get_repo_slug(request) # can be empty
420 c.repo_name = get_repo_slug(request) # can be empty
420
421
421 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
422 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
422 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
423 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
423 self.sa = meta.Session
424 self.sa = meta.Session
424 self.scm_model = ScmModel(self.sa)
425 self.scm_model = ScmModel(self.sa)
425
426
426 default_lang = c.language
427 default_lang = c.language
427 user_lang = c.language
428 user_lang = c.language
428 try:
429 try:
429 user_obj = self._rhodecode_user.get_instance()
430 user_obj = self._rhodecode_user.get_instance()
430 if user_obj:
431 if user_obj:
431 user_lang = user_obj.user_data.get('language')
432 user_lang = user_obj.user_data.get('language')
432 except Exception:
433 except Exception:
433 log.exception('Failed to fetch user language for user %s',
434 log.exception('Failed to fetch user language for user %s',
434 self._rhodecode_user)
435 self._rhodecode_user)
435
436
436 if user_lang and user_lang != default_lang:
437 if user_lang and user_lang != default_lang:
437 log.debug('set language to %s for user %s', user_lang,
438 log.debug('set language to %s for user %s', user_lang,
438 self._rhodecode_user)
439 self._rhodecode_user)
439 translation.set_lang(user_lang)
440 translation.set_lang(user_lang)
440
441
441 def _dispatch_redirect(self, with_url, environ, start_response):
442 def _dispatch_redirect(self, with_url, environ, start_response):
442 resp = HTTPFound(with_url)
443 resp = HTTPFound(with_url)
443 environ['SCRIPT_NAME'] = '' # handle prefix middleware
444 environ['SCRIPT_NAME'] = '' # handle prefix middleware
444 environ['PATH_INFO'] = with_url
445 environ['PATH_INFO'] = with_url
445 return resp(environ, start_response)
446 return resp(environ, start_response)
446
447
447 def __call__(self, environ, start_response):
448 def __call__(self, environ, start_response):
448 """Invoke the Controller"""
449 """Invoke the Controller"""
449 # WSGIController.__call__ dispatches to the Controller method
450 # WSGIController.__call__ dispatches to the Controller method
450 # the request is routed to. This routing information is
451 # the request is routed to. This routing information is
451 # available in environ['pylons.routes_dict']
452 # available in environ['pylons.routes_dict']
452 from rhodecode.lib import helpers as h
453 from rhodecode.lib import helpers as h
453
454
454 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
455 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
455 if environ.get('debugtoolbar.wants_pylons_context', False):
456 if environ.get('debugtoolbar.wants_pylons_context', False):
456 environ['debugtoolbar.pylons_context'] = c._current_obj()
457 environ['debugtoolbar.pylons_context'] = c._current_obj()
457
458
458 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
459 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
459 environ['pylons.routes_dict']['action']])
460 environ['pylons.routes_dict']['action']])
460
461
461 self.rc_config = SettingsModel().get_all_settings(cache=True)
462 self.rc_config = SettingsModel().get_all_settings(cache=True)
462 self.ip_addr = get_ip_addr(environ)
463 self.ip_addr = get_ip_addr(environ)
463
464
464 # The rhodecode auth user is looked up and passed through the
465 # The rhodecode auth user is looked up and passed through the
465 # environ by the pylons compatibility tween in pyramid.
466 # environ by the pylons compatibility tween in pyramid.
466 # So we can just grab it from there.
467 # So we can just grab it from there.
467 auth_user = environ['rc_auth_user']
468 auth_user = environ['rc_auth_user']
468
469
469 # set globals for auth user
470 # set globals for auth user
470 request.user = auth_user
471 request.user = auth_user
471 c.rhodecode_user = self._rhodecode_user = auth_user
472 c.rhodecode_user = self._rhodecode_user = auth_user
472
473
473 log.info('IP: %s User: %s accessed %s [%s]' % (
474 log.info('IP: %s User: %s accessed %s [%s]' % (
474 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
475 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
475 _route_name)
476 _route_name)
476 )
477 )
477
478
478 # TODO: Maybe this should be move to pyramid to cover all views.
479 # TODO: Maybe this should be move to pyramid to cover all views.
479 # check user attributes for password change flag
480 # check user attributes for password change flag
480 user_obj = auth_user.get_instance()
481 user_obj = auth_user.get_instance()
481 if user_obj and user_obj.user_data.get('force_password_change'):
482 if user_obj and user_obj.user_data.get('force_password_change'):
482 h.flash('You are required to change your password', 'warning',
483 h.flash('You are required to change your password', 'warning',
483 ignore_duplicate=True)
484 ignore_duplicate=True)
484
485
485 skip_user_check_urls = [
486 skip_user_check_urls = [
486 'error.document', 'login.logout', 'login.index',
487 'error.document', 'login.logout', 'login.index',
487 'admin/my_account.my_account_password',
488 'admin/my_account.my_account_password',
488 'admin/my_account.my_account_password_update'
489 'admin/my_account.my_account_password_update'
489 ]
490 ]
490 if _route_name not in skip_user_check_urls:
491 if _route_name not in skip_user_check_urls:
491 return self._dispatch_redirect(
492 return self._dispatch_redirect(
492 url('my_account_password'), environ, start_response)
493 url('my_account_password'), environ, start_response)
493
494
494 return WSGIController.__call__(self, environ, start_response)
495 return WSGIController.__call__(self, environ, start_response)
495
496
496
497
497 class BaseRepoController(BaseController):
498 class BaseRepoController(BaseController):
498 """
499 """
499 Base class for controllers responsible for loading all needed data for
500 Base class for controllers responsible for loading all needed data for
500 repository loaded items are
501 repository loaded items are
501
502
502 c.rhodecode_repo: instance of scm repository
503 c.rhodecode_repo: instance of scm repository
503 c.rhodecode_db_repo: instance of db
504 c.rhodecode_db_repo: instance of db
504 c.repository_requirements_missing: shows that repository specific data
505 c.repository_requirements_missing: shows that repository specific data
505 could not be displayed due to the missing requirements
506 could not be displayed due to the missing requirements
506 c.repository_pull_requests: show number of open pull requests
507 c.repository_pull_requests: show number of open pull requests
507 """
508 """
508
509
509 def __before__(self):
510 def __before__(self):
510 super(BaseRepoController, self).__before__()
511 super(BaseRepoController, self).__before__()
511 if c.repo_name: # extracted from routes
512 if c.repo_name: # extracted from routes
512 db_repo = Repository.get_by_repo_name(c.repo_name)
513 db_repo = Repository.get_by_repo_name(c.repo_name)
513 if not db_repo:
514 if not db_repo:
514 return
515 return
515
516
516 log.debug(
517 log.debug(
517 'Found repository in database %s with state `%s`',
518 'Found repository in database %s with state `%s`',
518 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
519 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
519 route = getattr(request.environ.get('routes.route'), 'name', '')
520 route = getattr(request.environ.get('routes.route'), 'name', '')
520
521
521 # allow to delete repos that are somehow damages in filesystem
522 # allow to delete repos that are somehow damages in filesystem
522 if route in ['delete_repo']:
523 if route in ['delete_repo']:
523 return
524 return
524
525
525 if db_repo.repo_state in [Repository.STATE_PENDING]:
526 if db_repo.repo_state in [Repository.STATE_PENDING]:
526 if route in ['repo_creating_home']:
527 if route in ['repo_creating_home']:
527 return
528 return
528 check_url = url('repo_creating_home', repo_name=c.repo_name)
529 check_url = url('repo_creating_home', repo_name=c.repo_name)
529 return redirect(check_url)
530 return redirect(check_url)
530
531
531 self.rhodecode_db_repo = db_repo
532 self.rhodecode_db_repo = db_repo
532
533
533 missing_requirements = False
534 missing_requirements = False
534 try:
535 try:
535 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
536 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
536 except RepositoryRequirementError as e:
537 except RepositoryRequirementError as e:
537 missing_requirements = True
538 missing_requirements = True
538 self._handle_missing_requirements(e)
539 self._handle_missing_requirements(e)
539
540
540 if self.rhodecode_repo is None and not missing_requirements:
541 if self.rhodecode_repo is None and not missing_requirements:
541 log.error('%s this repository is present in database but it '
542 log.error('%s this repository is present in database but it '
542 'cannot be created as an scm instance', c.repo_name)
543 'cannot be created as an scm instance', c.repo_name)
543
544
544 h.flash(_(
545 h.flash(_(
545 "The repository at %(repo_name)s cannot be located.") %
546 "The repository at %(repo_name)s cannot be located.") %
546 {'repo_name': c.repo_name},
547 {'repo_name': c.repo_name},
547 category='error', ignore_duplicate=True)
548 category='error', ignore_duplicate=True)
548 redirect(url('home'))
549 redirect(url('home'))
549
550
550 # update last change according to VCS data
551 # update last change according to VCS data
551 if not missing_requirements:
552 if not missing_requirements:
552 commit = db_repo.get_commit(
553 commit = db_repo.get_commit(
553 pre_load=["author", "date", "message", "parents"])
554 pre_load=["author", "date", "message", "parents"])
554 db_repo.update_commit_cache(commit)
555 db_repo.update_commit_cache(commit)
555
556
556 # Prepare context
557 # Prepare context
557 c.rhodecode_db_repo = db_repo
558 c.rhodecode_db_repo = db_repo
558 c.rhodecode_repo = self.rhodecode_repo
559 c.rhodecode_repo = self.rhodecode_repo
559 c.repository_requirements_missing = missing_requirements
560 c.repository_requirements_missing = missing_requirements
560
561
561 self._update_global_counters(self.scm_model, db_repo)
562 self._update_global_counters(self.scm_model, db_repo)
562
563
563 def _update_global_counters(self, scm_model, db_repo):
564 def _update_global_counters(self, scm_model, db_repo):
564 """
565 """
565 Base variables that are exposed to every page of repository
566 Base variables that are exposed to every page of repository
566 """
567 """
567 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
568 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
568
569
569 def _handle_missing_requirements(self, error):
570 def _handle_missing_requirements(self, error):
570 self.rhodecode_repo = None
571 self.rhodecode_repo = None
571 log.error(
572 log.error(
572 'Requirements are missing for repository %s: %s',
573 'Requirements are missing for repository %s: %s',
573 c.repo_name, error.message)
574 c.repo_name, error.message)
574
575
575 summary_url = url('summary_home', repo_name=c.repo_name)
576 summary_url = url('summary_home', repo_name=c.repo_name)
576 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
577 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
577 settings_update_url = url('repo', repo_name=c.repo_name)
578 settings_update_url = url('repo', repo_name=c.repo_name)
578 path = request.path
579 path = request.path
579 should_redirect = (
580 should_redirect = (
580 path not in (summary_url, settings_update_url)
581 path not in (summary_url, settings_update_url)
581 and '/settings' not in path or path == statistics_url
582 and '/settings' not in path or path == statistics_url
582 )
583 )
583 if should_redirect:
584 if should_redirect:
584 redirect(summary_url)
585 redirect(summary_url)
@@ -1,471 +1,512 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 comments model for RhodeCode
22 comments model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import collections
27 import collections
28
28
29 from datetime import datetime
30
31 from pylons.i18n.translation import _
32 from pyramid.threadlocal import get_current_registry
29 from sqlalchemy.sql.expression import null
33 from sqlalchemy.sql.expression import null
30 from sqlalchemy.sql.functions import coalesce
34 from sqlalchemy.sql.functions import coalesce
31
35
32 from rhodecode.lib import helpers as h, diffs
36 from rhodecode.lib import helpers as h, diffs
37 from rhodecode.lib.channelstream import channelstream_request
33 from rhodecode.lib.utils import action_logger
38 from rhodecode.lib.utils import action_logger
34 from rhodecode.lib.utils2 import extract_mentioned_users
39 from rhodecode.lib.utils2 import extract_mentioned_users
35 from rhodecode.model import BaseModel
40 from rhodecode.model import BaseModel
36 from rhodecode.model.db import (
41 from rhodecode.model.db import (
37 ChangesetComment, User, Notification, PullRequest)
42 ChangesetComment, User, Notification, PullRequest)
38 from rhodecode.model.notification import NotificationModel
43 from rhodecode.model.notification import NotificationModel
39 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
40 from rhodecode.model.settings import VcsSettingsModel
45 from rhodecode.model.settings import VcsSettingsModel
41 from rhodecode.model.notification import EmailNotificationModel
46 from rhodecode.model.notification import EmailNotificationModel
42
47
43 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
44
49
45
50
46 class ChangesetCommentsModel(BaseModel):
51 class ChangesetCommentsModel(BaseModel):
47
52
48 cls = ChangesetComment
53 cls = ChangesetComment
49
54
50 DIFF_CONTEXT_BEFORE = 3
55 DIFF_CONTEXT_BEFORE = 3
51 DIFF_CONTEXT_AFTER = 3
56 DIFF_CONTEXT_AFTER = 3
52
57
53 def __get_commit_comment(self, changeset_comment):
58 def __get_commit_comment(self, changeset_comment):
54 return self._get_instance(ChangesetComment, changeset_comment)
59 return self._get_instance(ChangesetComment, changeset_comment)
55
60
56 def __get_pull_request(self, pull_request):
61 def __get_pull_request(self, pull_request):
57 return self._get_instance(PullRequest, pull_request)
62 return self._get_instance(PullRequest, pull_request)
58
63
59 def _extract_mentions(self, s):
64 def _extract_mentions(self, s):
60 user_objects = []
65 user_objects = []
61 for username in extract_mentioned_users(s):
66 for username in extract_mentioned_users(s):
62 user_obj = User.get_by_username(username, case_insensitive=True)
67 user_obj = User.get_by_username(username, case_insensitive=True)
63 if user_obj:
68 if user_obj:
64 user_objects.append(user_obj)
69 user_objects.append(user_obj)
65 return user_objects
70 return user_objects
66
71
67 def _get_renderer(self, global_renderer='rst'):
72 def _get_renderer(self, global_renderer='rst'):
68 try:
73 try:
69 # try reading from visual context
74 # try reading from visual context
70 from pylons import tmpl_context
75 from pylons import tmpl_context
71 global_renderer = tmpl_context.visual.default_renderer
76 global_renderer = tmpl_context.visual.default_renderer
72 except AttributeError:
77 except AttributeError:
73 log.debug("Renderer not set, falling back "
78 log.debug("Renderer not set, falling back "
74 "to default renderer '%s'", global_renderer)
79 "to default renderer '%s'", global_renderer)
75 except Exception:
80 except Exception:
76 log.error(traceback.format_exc())
81 log.error(traceback.format_exc())
77 return global_renderer
82 return global_renderer
78
83
79 def create(self, text, repo, user, revision=None, pull_request=None,
84 def create(self, text, repo, user, revision=None, pull_request=None,
80 f_path=None, line_no=None, status_change=None, closing_pr=False,
85 f_path=None, line_no=None, status_change=None, closing_pr=False,
81 send_email=True, renderer=None):
86 send_email=True, renderer=None):
82 """
87 """
83 Creates new comment for commit or pull request.
88 Creates new comment for commit or pull request.
84 IF status_change is not none this comment is associated with a
89 IF status_change is not none this comment is associated with a
85 status change of commit or commit associated with pull request
90 status change of commit or commit associated with pull request
86
91
87 :param text:
92 :param text:
88 :param repo:
93 :param repo:
89 :param user:
94 :param user:
90 :param revision:
95 :param revision:
91 :param pull_request:
96 :param pull_request:
92 :param f_path:
97 :param f_path:
93 :param line_no:
98 :param line_no:
94 :param status_change:
99 :param status_change:
95 :param closing_pr:
100 :param closing_pr:
96 :param send_email:
101 :param send_email:
97 """
102 """
98 if not text:
103 if not text:
99 log.warning('Missing text for comment, skipping...')
104 log.warning('Missing text for comment, skipping...')
100 return
105 return
101
106
102 if not renderer:
107 if not renderer:
103 renderer = self._get_renderer()
108 renderer = self._get_renderer()
104
109
105 repo = self._get_repo(repo)
110 repo = self._get_repo(repo)
106 user = self._get_user(user)
111 user = self._get_user(user)
107 comment = ChangesetComment()
112 comment = ChangesetComment()
108 comment.renderer = renderer
113 comment.renderer = renderer
109 comment.repo = repo
114 comment.repo = repo
110 comment.author = user
115 comment.author = user
111 comment.text = text
116 comment.text = text
112 comment.f_path = f_path
117 comment.f_path = f_path
113 comment.line_no = line_no
118 comment.line_no = line_no
114
119
115 #TODO (marcink): fix this and remove revision as param
120 #TODO (marcink): fix this and remove revision as param
116 commit_id = revision
121 commit_id = revision
117 pull_request_id = pull_request
122 pull_request_id = pull_request
118
123
119 commit_obj = None
124 commit_obj = None
120 pull_request_obj = None
125 pull_request_obj = None
121
126
122 if commit_id:
127 if commit_id:
123 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
128 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
124 # do a lookup, so we don't pass something bad here
129 # do a lookup, so we don't pass something bad here
125 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
130 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
126 comment.revision = commit_obj.raw_id
131 comment.revision = commit_obj.raw_id
127
132
128 elif pull_request_id:
133 elif pull_request_id:
129 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
134 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
130 pull_request_obj = self.__get_pull_request(pull_request_id)
135 pull_request_obj = self.__get_pull_request(pull_request_id)
131 comment.pull_request = pull_request_obj
136 comment.pull_request = pull_request_obj
132 else:
137 else:
133 raise Exception('Please specify commit or pull_request_id')
138 raise Exception('Please specify commit or pull_request_id')
134
139
135 Session().add(comment)
140 Session().add(comment)
136 Session().flush()
141 Session().flush()
137
142 kwargs = {
138 if send_email:
143 'user': user,
139 kwargs = {
144 'renderer_type': renderer,
140 'user': user,
145 'repo_name': repo.repo_name,
141 'renderer_type': renderer,
146 'status_change': status_change,
142 'repo_name': repo.repo_name,
147 'comment_body': text,
143 'status_change': status_change,
148 'comment_file': f_path,
144 'comment_body': text,
149 'comment_line': line_no,
145 'comment_file': f_path,
150 }
146 'comment_line': line_no,
147 }
148
151
149 if commit_obj:
152 if commit_obj:
150 recipients = ChangesetComment.get_users(
153 recipients = ChangesetComment.get_users(
151 revision=commit_obj.raw_id)
154 revision=commit_obj.raw_id)
152 # add commit author if it's in RhodeCode system
155 # add commit author if it's in RhodeCode system
153 cs_author = User.get_from_cs_author(commit_obj.author)
156 cs_author = User.get_from_cs_author(commit_obj.author)
154 if not cs_author:
157 if not cs_author:
155 # use repo owner if we cannot extract the author correctly
158 # use repo owner if we cannot extract the author correctly
156 cs_author = repo.user
159 cs_author = repo.user
157 recipients += [cs_author]
160 recipients += [cs_author]
158
161
159 commit_comment_url = self.get_url(comment)
162 commit_comment_url = self.get_url(comment)
160
163
161 target_repo_url = h.link_to(
164 target_repo_url = h.link_to(
162 repo.repo_name,
165 repo.repo_name,
163 h.url('summary_home',
166 h.url('summary_home',
164 repo_name=repo.repo_name, qualified=True))
167 repo_name=repo.repo_name, qualified=True))
165
168
166 # commit specifics
169 # commit specifics
167 kwargs.update({
170 kwargs.update({
168 'commit': commit_obj,
171 'commit': commit_obj,
169 'commit_message': commit_obj.message,
172 'commit_message': commit_obj.message,
170 'commit_target_repo': target_repo_url,
173 'commit_target_repo': target_repo_url,
171 'commit_comment_url': commit_comment_url,
174 'commit_comment_url': commit_comment_url,
172 })
175 })
173
176
174 elif pull_request_obj:
177 elif pull_request_obj:
175 # get the current participants of this pull request
178 # get the current participants of this pull request
176 recipients = ChangesetComment.get_users(
179 recipients = ChangesetComment.get_users(
177 pull_request_id=pull_request_obj.pull_request_id)
180 pull_request_id=pull_request_obj.pull_request_id)
178 # add pull request author
181 # add pull request author
179 recipients += [pull_request_obj.author]
182 recipients += [pull_request_obj.author]
180
183
181 # add the reviewers to notification
184 # add the reviewers to notification
182 recipients += [x.user for x in pull_request_obj.reviewers]
185 recipients += [x.user for x in pull_request_obj.reviewers]
183
186
184 pr_target_repo = pull_request_obj.target_repo
187 pr_target_repo = pull_request_obj.target_repo
185 pr_source_repo = pull_request_obj.source_repo
188 pr_source_repo = pull_request_obj.source_repo
186
189
187 pr_comment_url = h.url(
190 pr_comment_url = h.url(
188 'pullrequest_show',
191 'pullrequest_show',
189 repo_name=pr_target_repo.repo_name,
192 repo_name=pr_target_repo.repo_name,
190 pull_request_id=pull_request_obj.pull_request_id,
193 pull_request_id=pull_request_obj.pull_request_id,
191 anchor='comment-%s' % comment.comment_id,
194 anchor='comment-%s' % comment.comment_id,
192 qualified=True,)
195 qualified=True,)
193
196
194 # set some variables for email notification
197 # set some variables for email notification
195 pr_target_repo_url = h.url(
198 pr_target_repo_url = h.url(
196 'summary_home', repo_name=pr_target_repo.repo_name,
199 'summary_home', repo_name=pr_target_repo.repo_name,
197 qualified=True)
200 qualified=True)
198
201
199 pr_source_repo_url = h.url(
202 pr_source_repo_url = h.url(
200 'summary_home', repo_name=pr_source_repo.repo_name,
203 'summary_home', repo_name=pr_source_repo.repo_name,
201 qualified=True)
204 qualified=True)
202
205
203 # pull request specifics
206 # pull request specifics
204 kwargs.update({
207 kwargs.update({
205 'pull_request': pull_request_obj,
208 'pull_request': pull_request_obj,
206 'pr_id': pull_request_obj.pull_request_id,
209 'pr_id': pull_request_obj.pull_request_id,
207 'pr_target_repo': pr_target_repo,
210 'pr_target_repo': pr_target_repo,
208 'pr_target_repo_url': pr_target_repo_url,
211 'pr_target_repo_url': pr_target_repo_url,
209 'pr_source_repo': pr_source_repo,
212 'pr_source_repo': pr_source_repo,
210 'pr_source_repo_url': pr_source_repo_url,
213 'pr_source_repo_url': pr_source_repo_url,
211 'pr_comment_url': pr_comment_url,
214 'pr_comment_url': pr_comment_url,
212 'pr_closing': closing_pr,
215 'pr_closing': closing_pr,
213 })
216 })
214
217 if send_email:
215 # pre-generate the subject for notification itself
218 # pre-generate the subject for notification itself
216 (subject,
219 (subject,
217 _h, _e, # we don't care about those
220 _h, _e, # we don't care about those
218 body_plaintext) = EmailNotificationModel().render_email(
221 body_plaintext) = EmailNotificationModel().render_email(
219 notification_type, **kwargs)
222 notification_type, **kwargs)
220
223
221 mention_recipients = set(
224 mention_recipients = set(
222 self._extract_mentions(text)).difference(recipients)
225 self._extract_mentions(text)).difference(recipients)
223
226
224 # create notification objects, and emails
227 # create notification objects, and emails
225 NotificationModel().create(
228 NotificationModel().create(
226 created_by=user,
229 created_by=user,
227 notification_subject=subject,
230 notification_subject=subject,
228 notification_body=body_plaintext,
231 notification_body=body_plaintext,
229 notification_type=notification_type,
232 notification_type=notification_type,
230 recipients=recipients,
233 recipients=recipients,
231 mention_recipients=mention_recipients,
234 mention_recipients=mention_recipients,
232 email_kwargs=kwargs,
235 email_kwargs=kwargs,
233 )
236 )
234
237
235 action = (
238 action = (
236 'user_commented_pull_request:{}'.format(
239 'user_commented_pull_request:{}'.format(
237 comment.pull_request.pull_request_id)
240 comment.pull_request.pull_request_id)
238 if comment.pull_request
241 if comment.pull_request
239 else 'user_commented_revision:{}'.format(comment.revision)
242 else 'user_commented_revision:{}'.format(comment.revision)
240 )
243 )
241 action_logger(user, action, comment.repo)
244 action_logger(user, action, comment.repo)
242
245
246 registry = get_current_registry()
247 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
248 channelstream_config = rhodecode_plugins.get('channelstream')
249 msg_url = ''
250 if commit_obj:
251 msg_url = commit_comment_url
252 repo_name = repo.repo_name
253 elif pull_request_obj:
254 msg_url = pr_comment_url
255 repo_name = pr_target_repo.repo_name
256
257 if channelstream_config:
258 message = '<strong>{}</strong> {} - ' \
259 '<a onclick="window.location=\'{}\';' \
260 'window.location.reload()">' \
261 '<strong>{}</strong></a>'
262 message = message.format(
263 user.username, _('made a comment'), msg_url,
264 _('Refresh page'))
265 if channelstream_config:
266 channel = '/repo${}$/pr/{}'.format(
267 repo_name,
268 pull_request_id
269 )
270 payload = {
271 'type': 'message',
272 'timestamp': datetime.utcnow(),
273 'user': 'system',
274 'channel': channel,
275 'message': {
276 'message': message,
277 'level': 'info',
278 'topic': '/notifications'
279 }
280 }
281 channelstream_request(channelstream_config, [payload],
282 '/message', raise_exc=False)
283
243 return comment
284 return comment
244
285
245 def delete(self, comment):
286 def delete(self, comment):
246 """
287 """
247 Deletes given comment
288 Deletes given comment
248
289
249 :param comment_id:
290 :param comment_id:
250 """
291 """
251 comment = self.__get_commit_comment(comment)
292 comment = self.__get_commit_comment(comment)
252 Session().delete(comment)
293 Session().delete(comment)
253
294
254 return comment
295 return comment
255
296
256 def get_all_comments(self, repo_id, revision=None, pull_request=None):
297 def get_all_comments(self, repo_id, revision=None, pull_request=None):
257 q = ChangesetComment.query()\
298 q = ChangesetComment.query()\
258 .filter(ChangesetComment.repo_id == repo_id)
299 .filter(ChangesetComment.repo_id == repo_id)
259 if revision:
300 if revision:
260 q = q.filter(ChangesetComment.revision == revision)
301 q = q.filter(ChangesetComment.revision == revision)
261 elif pull_request:
302 elif pull_request:
262 pull_request = self.__get_pull_request(pull_request)
303 pull_request = self.__get_pull_request(pull_request)
263 q = q.filter(ChangesetComment.pull_request == pull_request)
304 q = q.filter(ChangesetComment.pull_request == pull_request)
264 else:
305 else:
265 raise Exception('Please specify commit or pull_request')
306 raise Exception('Please specify commit or pull_request')
266 q = q.order_by(ChangesetComment.created_on)
307 q = q.order_by(ChangesetComment.created_on)
267 return q.all()
308 return q.all()
268
309
269 def get_url(self, comment):
310 def get_url(self, comment):
270 comment = self.__get_commit_comment(comment)
311 comment = self.__get_commit_comment(comment)
271 if comment.pull_request:
312 if comment.pull_request:
272 return h.url(
313 return h.url(
273 'pullrequest_show',
314 'pullrequest_show',
274 repo_name=comment.pull_request.target_repo.repo_name,
315 repo_name=comment.pull_request.target_repo.repo_name,
275 pull_request_id=comment.pull_request.pull_request_id,
316 pull_request_id=comment.pull_request.pull_request_id,
276 anchor='comment-%s' % comment.comment_id,
317 anchor='comment-%s' % comment.comment_id,
277 qualified=True,)
318 qualified=True,)
278 else:
319 else:
279 return h.url(
320 return h.url(
280 'changeset_home',
321 'changeset_home',
281 repo_name=comment.repo.repo_name,
322 repo_name=comment.repo.repo_name,
282 revision=comment.revision,
323 revision=comment.revision,
283 anchor='comment-%s' % comment.comment_id,
324 anchor='comment-%s' % comment.comment_id,
284 qualified=True,)
325 qualified=True,)
285
326
286 def get_comments(self, repo_id, revision=None, pull_request=None):
327 def get_comments(self, repo_id, revision=None, pull_request=None):
287 """
328 """
288 Gets main comments based on revision or pull_request_id
329 Gets main comments based on revision or pull_request_id
289
330
290 :param repo_id:
331 :param repo_id:
291 :param revision:
332 :param revision:
292 :param pull_request:
333 :param pull_request:
293 """
334 """
294
335
295 q = ChangesetComment.query()\
336 q = ChangesetComment.query()\
296 .filter(ChangesetComment.repo_id == repo_id)\
337 .filter(ChangesetComment.repo_id == repo_id)\
297 .filter(ChangesetComment.line_no == None)\
338 .filter(ChangesetComment.line_no == None)\
298 .filter(ChangesetComment.f_path == None)
339 .filter(ChangesetComment.f_path == None)
299 if revision:
340 if revision:
300 q = q.filter(ChangesetComment.revision == revision)
341 q = q.filter(ChangesetComment.revision == revision)
301 elif pull_request:
342 elif pull_request:
302 pull_request = self.__get_pull_request(pull_request)
343 pull_request = self.__get_pull_request(pull_request)
303 q = q.filter(ChangesetComment.pull_request == pull_request)
344 q = q.filter(ChangesetComment.pull_request == pull_request)
304 else:
345 else:
305 raise Exception('Please specify commit or pull_request')
346 raise Exception('Please specify commit or pull_request')
306 q = q.order_by(ChangesetComment.created_on)
347 q = q.order_by(ChangesetComment.created_on)
307 return q.all()
348 return q.all()
308
349
309 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
350 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
310 q = self._get_inline_comments_query(repo_id, revision, pull_request)
351 q = self._get_inline_comments_query(repo_id, revision, pull_request)
311 return self._group_comments_by_path_and_line_number(q)
352 return self._group_comments_by_path_and_line_number(q)
312
353
313 def get_outdated_comments(self, repo_id, pull_request):
354 def get_outdated_comments(self, repo_id, pull_request):
314 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
355 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
315 # of a pull request.
356 # of a pull request.
316 q = self._all_inline_comments_of_pull_request(pull_request)
357 q = self._all_inline_comments_of_pull_request(pull_request)
317 q = q.filter(
358 q = q.filter(
318 ChangesetComment.display_state ==
359 ChangesetComment.display_state ==
319 ChangesetComment.COMMENT_OUTDATED
360 ChangesetComment.COMMENT_OUTDATED
320 ).order_by(ChangesetComment.comment_id.asc())
361 ).order_by(ChangesetComment.comment_id.asc())
321
362
322 return self._group_comments_by_path_and_line_number(q)
363 return self._group_comments_by_path_and_line_number(q)
323
364
324 def _get_inline_comments_query(self, repo_id, revision, pull_request):
365 def _get_inline_comments_query(self, repo_id, revision, pull_request):
325 # TODO: johbo: Split this into two methods: One for PR and one for
366 # TODO: johbo: Split this into two methods: One for PR and one for
326 # commit.
367 # commit.
327 if revision:
368 if revision:
328 q = Session().query(ChangesetComment).filter(
369 q = Session().query(ChangesetComment).filter(
329 ChangesetComment.repo_id == repo_id,
370 ChangesetComment.repo_id == repo_id,
330 ChangesetComment.line_no != null(),
371 ChangesetComment.line_no != null(),
331 ChangesetComment.f_path != null(),
372 ChangesetComment.f_path != null(),
332 ChangesetComment.revision == revision)
373 ChangesetComment.revision == revision)
333
374
334 elif pull_request:
375 elif pull_request:
335 pull_request = self.__get_pull_request(pull_request)
376 pull_request = self.__get_pull_request(pull_request)
336 if ChangesetCommentsModel.use_outdated_comments(pull_request):
377 if ChangesetCommentsModel.use_outdated_comments(pull_request):
337 q = self._visible_inline_comments_of_pull_request(pull_request)
378 q = self._visible_inline_comments_of_pull_request(pull_request)
338 else:
379 else:
339 q = self._all_inline_comments_of_pull_request(pull_request)
380 q = self._all_inline_comments_of_pull_request(pull_request)
340
381
341 else:
382 else:
342 raise Exception('Please specify commit or pull_request_id')
383 raise Exception('Please specify commit or pull_request_id')
343 q = q.order_by(ChangesetComment.comment_id.asc())
384 q = q.order_by(ChangesetComment.comment_id.asc())
344 return q
385 return q
345
386
346 def _group_comments_by_path_and_line_number(self, q):
387 def _group_comments_by_path_and_line_number(self, q):
347 comments = q.all()
388 comments = q.all()
348 paths = collections.defaultdict(lambda: collections.defaultdict(list))
389 paths = collections.defaultdict(lambda: collections.defaultdict(list))
349 for co in comments:
390 for co in comments:
350 paths[co.f_path][co.line_no].append(co)
391 paths[co.f_path][co.line_no].append(co)
351 return paths
392 return paths
352
393
353 @classmethod
394 @classmethod
354 def needed_extra_diff_context(cls):
395 def needed_extra_diff_context(cls):
355 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
396 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
356
397
357 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
398 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
358 if not ChangesetCommentsModel.use_outdated_comments(pull_request):
399 if not ChangesetCommentsModel.use_outdated_comments(pull_request):
359 return
400 return
360
401
361 comments = self._visible_inline_comments_of_pull_request(pull_request)
402 comments = self._visible_inline_comments_of_pull_request(pull_request)
362 comments_to_outdate = comments.all()
403 comments_to_outdate = comments.all()
363
404
364 for comment in comments_to_outdate:
405 for comment in comments_to_outdate:
365 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
406 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
366
407
367 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
408 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
368 diff_line = _parse_comment_line_number(comment.line_no)
409 diff_line = _parse_comment_line_number(comment.line_no)
369
410
370 try:
411 try:
371 old_context = old_diff_proc.get_context_of_line(
412 old_context = old_diff_proc.get_context_of_line(
372 path=comment.f_path, diff_line=diff_line)
413 path=comment.f_path, diff_line=diff_line)
373 new_context = new_diff_proc.get_context_of_line(
414 new_context = new_diff_proc.get_context_of_line(
374 path=comment.f_path, diff_line=diff_line)
415 path=comment.f_path, diff_line=diff_line)
375 except (diffs.LineNotInDiffException,
416 except (diffs.LineNotInDiffException,
376 diffs.FileNotInDiffException):
417 diffs.FileNotInDiffException):
377 comment.display_state = ChangesetComment.COMMENT_OUTDATED
418 comment.display_state = ChangesetComment.COMMENT_OUTDATED
378 return
419 return
379
420
380 if old_context == new_context:
421 if old_context == new_context:
381 return
422 return
382
423
383 if self._should_relocate_diff_line(diff_line):
424 if self._should_relocate_diff_line(diff_line):
384 new_diff_lines = new_diff_proc.find_context(
425 new_diff_lines = new_diff_proc.find_context(
385 path=comment.f_path, context=old_context,
426 path=comment.f_path, context=old_context,
386 offset=self.DIFF_CONTEXT_BEFORE)
427 offset=self.DIFF_CONTEXT_BEFORE)
387 if not new_diff_lines:
428 if not new_diff_lines:
388 comment.display_state = ChangesetComment.COMMENT_OUTDATED
429 comment.display_state = ChangesetComment.COMMENT_OUTDATED
389 else:
430 else:
390 new_diff_line = self._choose_closest_diff_line(
431 new_diff_line = self._choose_closest_diff_line(
391 diff_line, new_diff_lines)
432 diff_line, new_diff_lines)
392 comment.line_no = _diff_to_comment_line_number(new_diff_line)
433 comment.line_no = _diff_to_comment_line_number(new_diff_line)
393 else:
434 else:
394 comment.display_state = ChangesetComment.COMMENT_OUTDATED
435 comment.display_state = ChangesetComment.COMMENT_OUTDATED
395
436
396 def _should_relocate_diff_line(self, diff_line):
437 def _should_relocate_diff_line(self, diff_line):
397 """
438 """
398 Checks if relocation shall be tried for the given `diff_line`.
439 Checks if relocation shall be tried for the given `diff_line`.
399
440
400 If a comment points into the first lines, then we can have a situation
441 If a comment points into the first lines, then we can have a situation
401 that after an update another line has been added on top. In this case
442 that after an update another line has been added on top. In this case
402 we would find the context still and move the comment around. This
443 we would find the context still and move the comment around. This
403 would be wrong.
444 would be wrong.
404 """
445 """
405 should_relocate = (
446 should_relocate = (
406 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
447 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
407 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
448 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
408 return should_relocate
449 return should_relocate
409
450
410 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
451 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
411 candidate = new_diff_lines[0]
452 candidate = new_diff_lines[0]
412 best_delta = _diff_line_delta(diff_line, candidate)
453 best_delta = _diff_line_delta(diff_line, candidate)
413 for new_diff_line in new_diff_lines[1:]:
454 for new_diff_line in new_diff_lines[1:]:
414 delta = _diff_line_delta(diff_line, new_diff_line)
455 delta = _diff_line_delta(diff_line, new_diff_line)
415 if delta < best_delta:
456 if delta < best_delta:
416 candidate = new_diff_line
457 candidate = new_diff_line
417 best_delta = delta
458 best_delta = delta
418 return candidate
459 return candidate
419
460
420 def _visible_inline_comments_of_pull_request(self, pull_request):
461 def _visible_inline_comments_of_pull_request(self, pull_request):
421 comments = self._all_inline_comments_of_pull_request(pull_request)
462 comments = self._all_inline_comments_of_pull_request(pull_request)
422 comments = comments.filter(
463 comments = comments.filter(
423 coalesce(ChangesetComment.display_state, '') !=
464 coalesce(ChangesetComment.display_state, '') !=
424 ChangesetComment.COMMENT_OUTDATED)
465 ChangesetComment.COMMENT_OUTDATED)
425 return comments
466 return comments
426
467
427 def _all_inline_comments_of_pull_request(self, pull_request):
468 def _all_inline_comments_of_pull_request(self, pull_request):
428 comments = Session().query(ChangesetComment)\
469 comments = Session().query(ChangesetComment)\
429 .filter(ChangesetComment.line_no != None)\
470 .filter(ChangesetComment.line_no != None)\
430 .filter(ChangesetComment.f_path != None)\
471 .filter(ChangesetComment.f_path != None)\
431 .filter(ChangesetComment.pull_request == pull_request)
472 .filter(ChangesetComment.pull_request == pull_request)
432 return comments
473 return comments
433
474
434 @staticmethod
475 @staticmethod
435 def use_outdated_comments(pull_request):
476 def use_outdated_comments(pull_request):
436 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
477 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
437 settings = settings_model.get_general_settings()
478 settings = settings_model.get_general_settings()
438 return settings.get('rhodecode_use_outdated_comments', False)
479 return settings.get('rhodecode_use_outdated_comments', False)
439
480
440
481
441 def _parse_comment_line_number(line_no):
482 def _parse_comment_line_number(line_no):
442 """
483 """
443 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
484 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
444 """
485 """
445 old_line = None
486 old_line = None
446 new_line = None
487 new_line = None
447 if line_no.startswith('o'):
488 if line_no.startswith('o'):
448 old_line = int(line_no[1:])
489 old_line = int(line_no[1:])
449 elif line_no.startswith('n'):
490 elif line_no.startswith('n'):
450 new_line = int(line_no[1:])
491 new_line = int(line_no[1:])
451 else:
492 else:
452 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
493 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
453 return diffs.DiffLineNumber(old_line, new_line)
494 return diffs.DiffLineNumber(old_line, new_line)
454
495
455
496
456 def _diff_to_comment_line_number(diff_line):
497 def _diff_to_comment_line_number(diff_line):
457 if diff_line.new is not None:
498 if diff_line.new is not None:
458 return u'n{}'.format(diff_line.new)
499 return u'n{}'.format(diff_line.new)
459 elif diff_line.old is not None:
500 elif diff_line.old is not None:
460 return u'o{}'.format(diff_line.old)
501 return u'o{}'.format(diff_line.old)
461 return u''
502 return u''
462
503
463
504
464 def _diff_line_delta(a, b):
505 def _diff_line_delta(a, b):
465 if None not in (a.new, b.new):
506 if None not in (a.new, b.new):
466 return abs(a.new - b.new)
507 return abs(a.new - b.new)
467 elif None not in (a.old, b.old):
508 elif None not in (a.old, b.old):
468 return abs(a.old - b.old)
509 return abs(a.old - b.old)
469 else:
510 else:
470 raise ValueError(
511 raise ValueError(
471 "Cannot compute delta between {} and {}".format(a, b))
512 "Cannot compute delta between {} and {}".format(a, b))
@@ -1,2100 +1,2105 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 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'toastr';
28 @import 'deform';
29 @import 'deform';
29
30
30
31
31 //--- BASE ------------------//
32 //--- BASE ------------------//
32 .noscript-error {
33 .noscript-error {
33 top: 0;
34 top: 0;
34 left: 0;
35 left: 0;
35 width: 100%;
36 width: 100%;
36 z-index: 101;
37 z-index: 101;
37 text-align: center;
38 text-align: center;
38 font-family: @text-semibold;
39 font-family: @text-semibold;
39 font-size: 120%;
40 font-size: 120%;
40 color: white;
41 color: white;
41 background-color: @alert2;
42 background-color: @alert2;
42 padding: 5px 0 5px 0;
43 padding: 5px 0 5px 0;
43 }
44 }
44
45
45 html {
46 html {
46 display: table;
47 display: table;
47 height: 100%;
48 height: 100%;
48 width: 100%;
49 width: 100%;
49 }
50 }
50
51
51 body {
52 body {
52 display: table-cell;
53 display: table-cell;
53 width: 100%;
54 width: 100%;
54 }
55 }
55
56
56 //--- LAYOUT ------------------//
57 //--- LAYOUT ------------------//
57
58
58 .hidden{
59 .hidden{
59 display: none !important;
60 display: none !important;
60 }
61 }
61
62
62 .box{
63 .box{
63 float: left;
64 float: left;
64 width: 100%;
65 width: 100%;
65 }
66 }
66
67
67 .browser-header {
68 .browser-header {
68 clear: both;
69 clear: both;
69 }
70 }
70 .main {
71 .main {
71 clear: both;
72 clear: both;
72 padding:0 0 @pagepadding;
73 padding:0 0 @pagepadding;
73 height: auto;
74 height: auto;
74
75
75 &:after { //clearfix
76 &:after { //clearfix
76 content:"";
77 content:"";
77 clear:both;
78 clear:both;
78 width:100%;
79 width:100%;
79 display:block;
80 display:block;
80 }
81 }
81 }
82 }
82
83
83 .action-link{
84 .action-link{
84 margin-left: @padding;
85 margin-left: @padding;
85 padding-left: @padding;
86 padding-left: @padding;
86 border-left: @border-thickness solid @border-default-color;
87 border-left: @border-thickness solid @border-default-color;
87 }
88 }
88
89
89 input + .action-link, .action-link.first{
90 input + .action-link, .action-link.first{
90 border-left: none;
91 border-left: none;
91 }
92 }
92
93
93 .action-link.last{
94 .action-link.last{
94 margin-right: @padding;
95 margin-right: @padding;
95 padding-right: @padding;
96 padding-right: @padding;
96 }
97 }
97
98
98 .action-link.active,
99 .action-link.active,
99 .action-link.active a{
100 .action-link.active a{
100 color: @grey4;
101 color: @grey4;
101 }
102 }
102
103
103 ul.simple-list{
104 ul.simple-list{
104 list-style: none;
105 list-style: none;
105 margin: 0;
106 margin: 0;
106 padding: 0;
107 padding: 0;
107 }
108 }
108
109
109 .main-content {
110 .main-content {
110 padding-bottom: @pagepadding;
111 padding-bottom: @pagepadding;
111 }
112 }
112
113
113 .wrapper {
114 .wrapper {
114 position: relative;
115 position: relative;
115 max-width: @wrapper-maxwidth;
116 max-width: @wrapper-maxwidth;
116 margin: 0 auto;
117 margin: 0 auto;
117 }
118 }
118
119
119 #content {
120 #content {
120 clear: both;
121 clear: both;
121 padding: 0 @contentpadding;
122 padding: 0 @contentpadding;
122 }
123 }
123
124
124 .advanced-settings-fields{
125 .advanced-settings-fields{
125 input{
126 input{
126 margin-left: @textmargin;
127 margin-left: @textmargin;
127 margin-right: @padding/2;
128 margin-right: @padding/2;
128 }
129 }
129 }
130 }
130
131
131 .cs_files_title {
132 .cs_files_title {
132 margin: @pagepadding 0 0;
133 margin: @pagepadding 0 0;
133 }
134 }
134
135
135 input.inline[type="file"] {
136 input.inline[type="file"] {
136 display: inline;
137 display: inline;
137 }
138 }
138
139
139 .error_page {
140 .error_page {
140 margin: 10% auto;
141 margin: 10% auto;
141
142
142 h1 {
143 h1 {
143 color: @grey2;
144 color: @grey2;
144 }
145 }
145
146
146 .error-branding {
147 .error-branding {
147 font-family: @text-semibold;
148 font-family: @text-semibold;
148 color: @grey4;
149 color: @grey4;
149 }
150 }
150
151
151 .error_message {
152 .error_message {
152 font-family: @text-regular;
153 font-family: @text-regular;
153 }
154 }
154
155
155 .sidebar {
156 .sidebar {
156 min-height: 275px;
157 min-height: 275px;
157 margin: 0;
158 margin: 0;
158 padding: 0 0 @sidebarpadding @sidebarpadding;
159 padding: 0 0 @sidebarpadding @sidebarpadding;
159 border: none;
160 border: none;
160 }
161 }
161
162
162 .main-content {
163 .main-content {
163 position: relative;
164 position: relative;
164 margin: 0 @sidebarpadding @sidebarpadding;
165 margin: 0 @sidebarpadding @sidebarpadding;
165 padding: 0 0 0 @sidebarpadding;
166 padding: 0 0 0 @sidebarpadding;
166 border-left: @border-thickness solid @grey5;
167 border-left: @border-thickness solid @grey5;
167
168
168 @media (max-width:767px) {
169 @media (max-width:767px) {
169 clear: both;
170 clear: both;
170 width: 100%;
171 width: 100%;
171 margin: 0;
172 margin: 0;
172 border: none;
173 border: none;
173 }
174 }
174 }
175 }
175
176
176 .inner-column {
177 .inner-column {
177 float: left;
178 float: left;
178 width: 29.75%;
179 width: 29.75%;
179 min-height: 150px;
180 min-height: 150px;
180 margin: @sidebarpadding 2% 0 0;
181 margin: @sidebarpadding 2% 0 0;
181 padding: 0 2% 0 0;
182 padding: 0 2% 0 0;
182 border-right: @border-thickness solid @grey5;
183 border-right: @border-thickness solid @grey5;
183
184
184 @media (max-width:767px) {
185 @media (max-width:767px) {
185 clear: both;
186 clear: both;
186 width: 100%;
187 width: 100%;
187 border: none;
188 border: none;
188 }
189 }
189
190
190 ul {
191 ul {
191 padding-left: 1.25em;
192 padding-left: 1.25em;
192 }
193 }
193
194
194 &:last-child {
195 &:last-child {
195 margin: @sidebarpadding 0 0;
196 margin: @sidebarpadding 0 0;
196 border: none;
197 border: none;
197 }
198 }
198
199
199 h4 {
200 h4 {
200 margin: 0 0 @padding;
201 margin: 0 0 @padding;
201 font-family: @text-semibold;
202 font-family: @text-semibold;
202 }
203 }
203 }
204 }
204 }
205 }
205 .error-page-logo {
206 .error-page-logo {
206 width: 130px;
207 width: 130px;
207 height: 160px;
208 height: 160px;
208 }
209 }
209
210
210 // HEADER
211 // HEADER
211 .header {
212 .header {
212
213
213 // TODO: johbo: Fix login pages, so that they work without a min-height
214 // TODO: johbo: Fix login pages, so that they work without a min-height
214 // for the header and then remove the min-height. I chose a smaller value
215 // for the header and then remove the min-height. I chose a smaller value
215 // intentionally here to avoid rendering issues in the main navigation.
216 // intentionally here to avoid rendering issues in the main navigation.
216 min-height: 49px;
217 min-height: 49px;
217
218
218 position: relative;
219 position: relative;
219 vertical-align: bottom;
220 vertical-align: bottom;
220 padding: 0 @header-padding;
221 padding: 0 @header-padding;
221 background-color: @grey2;
222 background-color: @grey2;
222 color: @grey5;
223 color: @grey5;
223
224
224 .title {
225 .title {
225 overflow: visible;
226 overflow: visible;
226 }
227 }
227
228
228 &:before,
229 &:before,
229 &:after {
230 &:after {
230 content: "";
231 content: "";
231 clear: both;
232 clear: both;
232 width: 100%;
233 width: 100%;
233 }
234 }
234
235
235 // TODO: johbo: Avoids breaking "Repositories" chooser
236 // TODO: johbo: Avoids breaking "Repositories" chooser
236 .select2-container .select2-choice .select2-arrow {
237 .select2-container .select2-choice .select2-arrow {
237 display: none;
238 display: none;
238 }
239 }
239 }
240 }
240
241
241 #header-inner {
242 #header-inner {
242 &.title {
243 &.title {
243 margin: 0;
244 margin: 0;
244 }
245 }
245 &:before,
246 &:before,
246 &:after {
247 &:after {
247 content: "";
248 content: "";
248 clear: both;
249 clear: both;
249 }
250 }
250 }
251 }
251
252
252 // Gists
253 // Gists
253 #files_data {
254 #files_data {
254 clear: both; //for firefox
255 clear: both; //for firefox
255 }
256 }
256 #gistid {
257 #gistid {
257 margin-right: @padding;
258 margin-right: @padding;
258 }
259 }
259
260
260 // Global Settings Editor
261 // Global Settings Editor
261 .textarea.editor {
262 .textarea.editor {
262 float: left;
263 float: left;
263 position: relative;
264 position: relative;
264 max-width: @texteditor-width;
265 max-width: @texteditor-width;
265
266
266 select {
267 select {
267 position: absolute;
268 position: absolute;
268 top:10px;
269 top:10px;
269 right:0;
270 right:0;
270 }
271 }
271
272
272 .CodeMirror {
273 .CodeMirror {
273 margin: 0;
274 margin: 0;
274 }
275 }
275
276
276 .help-block {
277 .help-block {
277 margin: 0 0 @padding;
278 margin: 0 0 @padding;
278 padding:.5em;
279 padding:.5em;
279 background-color: @grey6;
280 background-color: @grey6;
280 }
281 }
281 }
282 }
282
283
283 ul.auth_plugins {
284 ul.auth_plugins {
284 margin: @padding 0 @padding @legend-width;
285 margin: @padding 0 @padding @legend-width;
285 padding: 0;
286 padding: 0;
286
287
287 li {
288 li {
288 margin-bottom: @padding;
289 margin-bottom: @padding;
289 line-height: 1em;
290 line-height: 1em;
290 list-style-type: none;
291 list-style-type: none;
291
292
292 .auth_buttons .btn {
293 .auth_buttons .btn {
293 margin-right: @padding;
294 margin-right: @padding;
294 }
295 }
295
296
296 &:before { content: none; }
297 &:before { content: none; }
297 }
298 }
298 }
299 }
299
300
300
301
301 // My Account PR list
302 // My Account PR list
302
303
303 #show_closed {
304 #show_closed {
304 margin: 0 1em 0 0;
305 margin: 0 1em 0 0;
305 }
306 }
306
307
307 .pullrequestlist {
308 .pullrequestlist {
308 .closed {
309 .closed {
309 background-color: @grey6;
310 background-color: @grey6;
310 }
311 }
311 .td-status {
312 .td-status {
312 padding-left: .5em;
313 padding-left: .5em;
313 }
314 }
314 .log-container .truncate {
315 .log-container .truncate {
315 height: 2.75em;
316 height: 2.75em;
316 white-space: pre-line;
317 white-space: pre-line;
317 }
318 }
318 table.rctable .user {
319 table.rctable .user {
319 padding-left: 0;
320 padding-left: 0;
320 }
321 }
321 table.rctable {
322 table.rctable {
322 td.td-description,
323 td.td-description,
323 .rc-user {
324 .rc-user {
324 min-width: auto;
325 min-width: auto;
325 }
326 }
326 }
327 }
327 }
328 }
328
329
329 // Pull Requests
330 // Pull Requests
330
331
331 .pullrequests_section_head {
332 .pullrequests_section_head {
332 display: block;
333 display: block;
333 clear: both;
334 clear: both;
334 margin: @padding 0;
335 margin: @padding 0;
335 font-family: @text-bold;
336 font-family: @text-bold;
336 }
337 }
337
338
338 .pr-origininfo, .pr-targetinfo {
339 .pr-origininfo, .pr-targetinfo {
339 position: relative;
340 position: relative;
340
341
341 .tag {
342 .tag {
342 display: inline-block;
343 display: inline-block;
343 margin: 0 1em .5em 0;
344 margin: 0 1em .5em 0;
344 }
345 }
345
346
346 .clone-url {
347 .clone-url {
347 display: inline-block;
348 display: inline-block;
348 margin: 0 0 .5em 0;
349 margin: 0 0 .5em 0;
349 padding: 0;
350 padding: 0;
350 line-height: 1.2em;
351 line-height: 1.2em;
351 }
352 }
352 }
353 }
353
354
354 .pr-pullinfo {
355 .pr-pullinfo {
355 clear: both;
356 clear: both;
356 margin: .5em 0;
357 margin: .5em 0;
357 }
358 }
358
359
359 #pr-title-input {
360 #pr-title-input {
360 width: 72%;
361 width: 72%;
361 font-size: 1em;
362 font-size: 1em;
362 font-family: @text-bold;
363 font-family: @text-bold;
363 margin: 0;
364 margin: 0;
364 padding: 0 0 0 @padding/4;
365 padding: 0 0 0 @padding/4;
365 line-height: 1.7em;
366 line-height: 1.7em;
366 color: @text-color;
367 color: @text-color;
367 letter-spacing: .02em;
368 letter-spacing: .02em;
368 }
369 }
369
370
370 #pullrequest_title {
371 #pullrequest_title {
371 width: 100%;
372 width: 100%;
372 box-sizing: border-box;
373 box-sizing: border-box;
373 }
374 }
374
375
375 #pr_open_message {
376 #pr_open_message {
376 border: @border-thickness solid #fff;
377 border: @border-thickness solid #fff;
377 border-radius: @border-radius;
378 border-radius: @border-radius;
378 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
379 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
379 text-align: right;
380 text-align: right;
380 overflow: hidden;
381 overflow: hidden;
381 }
382 }
382
383
383 .pr-submit-button {
384 .pr-submit-button {
384 float: right;
385 float: right;
385 margin: 0 0 0 5px;
386 margin: 0 0 0 5px;
386 }
387 }
387
388
388 .pr-spacing-container {
389 .pr-spacing-container {
389 padding: 20px;
390 padding: 20px;
390 clear: both
391 clear: both
391 }
392 }
392
393
393 #pr-description-input {
394 #pr-description-input {
394 margin-bottom: 0;
395 margin-bottom: 0;
395 }
396 }
396
397
397 .pr-description-label {
398 .pr-description-label {
398 vertical-align: top;
399 vertical-align: top;
399 }
400 }
400
401
401 .perms_section_head {
402 .perms_section_head {
402 min-width: 625px;
403 min-width: 625px;
403
404
404 h2 {
405 h2 {
405 margin-bottom: 0;
406 margin-bottom: 0;
406 }
407 }
407
408
408 .label-checkbox {
409 .label-checkbox {
409 float: left;
410 float: left;
410 }
411 }
411
412
412 &.field {
413 &.field {
413 margin: @space 0 @padding;
414 margin: @space 0 @padding;
414 }
415 }
415
416
416 &:first-child.field {
417 &:first-child.field {
417 margin-top: 0;
418 margin-top: 0;
418
419
419 .label {
420 .label {
420 margin-top: 0;
421 margin-top: 0;
421 padding-top: 0;
422 padding-top: 0;
422 }
423 }
423
424
424 .radios {
425 .radios {
425 padding-top: 0;
426 padding-top: 0;
426 }
427 }
427 }
428 }
428
429
429 .radios {
430 .radios {
430 float: right;
431 float: right;
431 position: relative;
432 position: relative;
432 width: 405px;
433 width: 405px;
433 }
434 }
434 }
435 }
435
436
436 //--- MODULES ------------------//
437 //--- MODULES ------------------//
437
438
438
439
439 // Fixed Sidebar Column
440 // Fixed Sidebar Column
440 .sidebar-col-wrapper {
441 .sidebar-col-wrapper {
441 padding-left: @sidebar-all-width;
442 padding-left: @sidebar-all-width;
442
443
443 .sidebar {
444 .sidebar {
444 width: @sidebar-width;
445 width: @sidebar-width;
445 margin-left: -@sidebar-all-width;
446 margin-left: -@sidebar-all-width;
446 }
447 }
447 }
448 }
448
449
449 .sidebar-col-wrapper.scw-small {
450 .sidebar-col-wrapper.scw-small {
450 padding-left: @sidebar-small-all-width;
451 padding-left: @sidebar-small-all-width;
451
452
452 .sidebar {
453 .sidebar {
453 width: @sidebar-small-width;
454 width: @sidebar-small-width;
454 margin-left: -@sidebar-small-all-width;
455 margin-left: -@sidebar-small-all-width;
455 }
456 }
456 }
457 }
457
458
458
459
459 // FOOTER
460 // FOOTER
460 #footer {
461 #footer {
461 padding: 0;
462 padding: 0;
462 text-align: center;
463 text-align: center;
463 vertical-align: middle;
464 vertical-align: middle;
464 color: @grey2;
465 color: @grey2;
465 background-color: @grey6;
466 background-color: @grey6;
466
467
467 p {
468 p {
468 margin: 0;
469 margin: 0;
469 padding: 1em;
470 padding: 1em;
470 line-height: 1em;
471 line-height: 1em;
471 }
472 }
472
473
473 .server-instance { //server instance
474 .server-instance { //server instance
474 display: none;
475 display: none;
475 }
476 }
476
477
477 .title {
478 .title {
478 float: none;
479 float: none;
479 margin: 0 auto;
480 margin: 0 auto;
480 }
481 }
481 }
482 }
482
483
483 button.close {
484 button.close {
484 padding: 0;
485 padding: 0;
485 cursor: pointer;
486 cursor: pointer;
486 background: transparent;
487 background: transparent;
487 border: 0;
488 border: 0;
488 .box-shadow(none);
489 .box-shadow(none);
489 -webkit-appearance: none;
490 -webkit-appearance: none;
490 }
491 }
491
492
492 .close {
493 .close {
493 float: right;
494 float: right;
494 font-size: 21px;
495 font-size: 21px;
495 font-family: @text-bootstrap;
496 font-family: @text-bootstrap;
496 line-height: 1em;
497 line-height: 1em;
497 font-weight: bold;
498 font-weight: bold;
498 color: @grey2;
499 color: @grey2;
499
500
500 &:hover,
501 &:hover,
501 &:focus {
502 &:focus {
502 color: @grey1;
503 color: @grey1;
503 text-decoration: none;
504 text-decoration: none;
504 cursor: pointer;
505 cursor: pointer;
505 }
506 }
506 }
507 }
507
508
508 // GRID
509 // GRID
509 .sorting,
510 .sorting,
510 .sorting_desc,
511 .sorting_desc,
511 .sorting_asc {
512 .sorting_asc {
512 cursor: pointer;
513 cursor: pointer;
513 }
514 }
514 .sorting_desc:after {
515 .sorting_desc:after {
515 content: "\00A0\25B2";
516 content: "\00A0\25B2";
516 font-size: .75em;
517 font-size: .75em;
517 }
518 }
518 .sorting_asc:after {
519 .sorting_asc:after {
519 content: "\00A0\25BC";
520 content: "\00A0\25BC";
520 font-size: .68em;
521 font-size: .68em;
521 }
522 }
522
523
523
524
524 .user_auth_tokens {
525 .user_auth_tokens {
525
526
526 &.truncate {
527 &.truncate {
527 white-space: nowrap;
528 white-space: nowrap;
528 overflow: hidden;
529 overflow: hidden;
529 text-overflow: ellipsis;
530 text-overflow: ellipsis;
530 }
531 }
531
532
532 .fields .field .input {
533 .fields .field .input {
533 margin: 0;
534 margin: 0;
534 }
535 }
535
536
536 input#description {
537 input#description {
537 width: 100px;
538 width: 100px;
538 margin: 0;
539 margin: 0;
539 }
540 }
540
541
541 .drop-menu {
542 .drop-menu {
542 // TODO: johbo: Remove this, should work out of the box when
543 // TODO: johbo: Remove this, should work out of the box when
543 // having multiple inputs inline
544 // having multiple inputs inline
544 margin: 0 0 0 5px;
545 margin: 0 0 0 5px;
545 }
546 }
546 }
547 }
547 #user_list_table {
548 #user_list_table {
548 .closed {
549 .closed {
549 background-color: @grey6;
550 background-color: @grey6;
550 }
551 }
551 }
552 }
552
553
553
554
554 input {
555 input {
555 &.disabled {
556 &.disabled {
556 opacity: .5;
557 opacity: .5;
557 }
558 }
558 }
559 }
559
560
560 // remove extra padding in firefox
561 // remove extra padding in firefox
561 input::-moz-focus-inner { border:0; padding:0 }
562 input::-moz-focus-inner { border:0; padding:0 }
562
563
563 .adjacent input {
564 .adjacent input {
564 margin-bottom: @padding;
565 margin-bottom: @padding;
565 }
566 }
566
567
567 .permissions_boxes {
568 .permissions_boxes {
568 display: block;
569 display: block;
569 }
570 }
570
571
571 //TODO: lisa: this should be in tables
572 //TODO: lisa: this should be in tables
572 .show_more_col {
573 .show_more_col {
573 width: 20px;
574 width: 20px;
574 }
575 }
575
576
576 //FORMS
577 //FORMS
577
578
578 .medium-inline,
579 .medium-inline,
579 input#description.medium-inline {
580 input#description.medium-inline {
580 display: inline;
581 display: inline;
581 width: @medium-inline-input-width;
582 width: @medium-inline-input-width;
582 min-width: 100px;
583 min-width: 100px;
583 }
584 }
584
585
585 select {
586 select {
586 //reset
587 //reset
587 -webkit-appearance: none;
588 -webkit-appearance: none;
588 -moz-appearance: none;
589 -moz-appearance: none;
589
590
590 display: inline-block;
591 display: inline-block;
591 height: 28px;
592 height: 28px;
592 width: auto;
593 width: auto;
593 margin: 0 @padding @padding 0;
594 margin: 0 @padding @padding 0;
594 padding: 0 18px 0 8px;
595 padding: 0 18px 0 8px;
595 line-height:1em;
596 line-height:1em;
596 font-size: @basefontsize;
597 font-size: @basefontsize;
597 border: @border-thickness solid @rcblue;
598 border: @border-thickness solid @rcblue;
598 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
599 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
599 color: @rcblue;
600 color: @rcblue;
600
601
601 &:after {
602 &:after {
602 content: "\00A0\25BE";
603 content: "\00A0\25BE";
603 }
604 }
604
605
605 &:focus {
606 &:focus {
606 outline: none;
607 outline: none;
607 }
608 }
608 }
609 }
609
610
610 option {
611 option {
611 &:focus {
612 &:focus {
612 outline: none;
613 outline: none;
613 }
614 }
614 }
615 }
615
616
616 input,
617 input,
617 textarea {
618 textarea {
618 padding: @input-padding;
619 padding: @input-padding;
619 border: @input-border-thickness solid @border-highlight-color;
620 border: @input-border-thickness solid @border-highlight-color;
620 .border-radius (@border-radius);
621 .border-radius (@border-radius);
621 font-family: @text-light;
622 font-family: @text-light;
622 font-size: @basefontsize;
623 font-size: @basefontsize;
623
624
624 &.input-sm {
625 &.input-sm {
625 padding: 5px;
626 padding: 5px;
626 }
627 }
627
628
628 &#description {
629 &#description {
629 min-width: @input-description-minwidth;
630 min-width: @input-description-minwidth;
630 min-height: 1em;
631 min-height: 1em;
631 padding: 10px;
632 padding: 10px;
632 }
633 }
633 }
634 }
634
635
635 .field-sm {
636 .field-sm {
636 input,
637 input,
637 textarea {
638 textarea {
638 padding: 5px;
639 padding: 5px;
639 }
640 }
640 }
641 }
641
642
642 textarea {
643 textarea {
643 display: block;
644 display: block;
644 clear: both;
645 clear: both;
645 width: 100%;
646 width: 100%;
646 min-height: 100px;
647 min-height: 100px;
647 margin-bottom: @padding;
648 margin-bottom: @padding;
648 .box-sizing(border-box);
649 .box-sizing(border-box);
649 overflow: auto;
650 overflow: auto;
650 }
651 }
651
652
652 label {
653 label {
653 font-family: @text-light;
654 font-family: @text-light;
654 }
655 }
655
656
656 // GRAVATARS
657 // GRAVATARS
657 // centers gravatar on username to the right
658 // centers gravatar on username to the right
658
659
659 .gravatar {
660 .gravatar {
660 display: inline;
661 display: inline;
661 min-width: 16px;
662 min-width: 16px;
662 min-height: 16px;
663 min-height: 16px;
663 margin: -5px 0;
664 margin: -5px 0;
664 padding: 0;
665 padding: 0;
665 line-height: 1em;
666 line-height: 1em;
666 border: 1px solid @grey4;
667 border: 1px solid @grey4;
667
668
668 &.gravatar-large {
669 &.gravatar-large {
669 margin: -0.5em .25em -0.5em 0;
670 margin: -0.5em .25em -0.5em 0;
670 }
671 }
671
672
672 & + .user {
673 & + .user {
673 display: inline;
674 display: inline;
674 margin: 0;
675 margin: 0;
675 padding: 0 0 0 .17em;
676 padding: 0 0 0 .17em;
676 line-height: 1em;
677 line-height: 1em;
677 }
678 }
678 }
679 }
679
680
680 .user-inline-data {
681 .user-inline-data {
681 display: inline-block;
682 display: inline-block;
682 float: left;
683 float: left;
683 padding-left: .5em;
684 padding-left: .5em;
684 line-height: 1.3em;
685 line-height: 1.3em;
685 }
686 }
686
687
687 .rc-user { // gravatar + user wrapper
688 .rc-user { // gravatar + user wrapper
688 float: left;
689 float: left;
689 position: relative;
690 position: relative;
690 min-width: 100px;
691 min-width: 100px;
691 max-width: 200px;
692 max-width: 200px;
692 min-height: (@gravatar-size + @border-thickness * 2); // account for border
693 min-height: (@gravatar-size + @border-thickness * 2); // account for border
693 display: block;
694 display: block;
694 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
695 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
695
696
696
697
697 .gravatar {
698 .gravatar {
698 display: block;
699 display: block;
699 position: absolute;
700 position: absolute;
700 top: 0;
701 top: 0;
701 left: 0;
702 left: 0;
702 min-width: @gravatar-size;
703 min-width: @gravatar-size;
703 min-height: @gravatar-size;
704 min-height: @gravatar-size;
704 margin: 0;
705 margin: 0;
705 }
706 }
706
707
707 .user {
708 .user {
708 display: block;
709 display: block;
709 max-width: 175px;
710 max-width: 175px;
710 padding-top: 2px;
711 padding-top: 2px;
711 overflow: hidden;
712 overflow: hidden;
712 text-overflow: ellipsis;
713 text-overflow: ellipsis;
713 }
714 }
714 }
715 }
715
716
716 .gist-gravatar,
717 .gist-gravatar,
717 .journal_container {
718 .journal_container {
718 .gravatar-large {
719 .gravatar-large {
719 margin: 0 .5em -10px 0;
720 margin: 0 .5em -10px 0;
720 }
721 }
721 }
722 }
722
723
723
724
724 // ADMIN SETTINGS
725 // ADMIN SETTINGS
725
726
726 // Tag Patterns
727 // Tag Patterns
727 .tag_patterns {
728 .tag_patterns {
728 .tag_input {
729 .tag_input {
729 margin-bottom: @padding;
730 margin-bottom: @padding;
730 }
731 }
731 }
732 }
732
733
733 .locked_input {
734 .locked_input {
734 position: relative;
735 position: relative;
735
736
736 input {
737 input {
737 display: inline;
738 display: inline;
738 margin-top: 3px;
739 margin-top: 3px;
739 }
740 }
740
741
741 br {
742 br {
742 display: none;
743 display: none;
743 }
744 }
744
745
745 .error-message {
746 .error-message {
746 float: left;
747 float: left;
747 width: 100%;
748 width: 100%;
748 }
749 }
749
750
750 .lock_input_button {
751 .lock_input_button {
751 display: inline;
752 display: inline;
752 }
753 }
753
754
754 .help-block {
755 .help-block {
755 clear: both;
756 clear: both;
756 }
757 }
757 }
758 }
758
759
759 // Notifications
760 // Notifications
760
761
761 .notifications_buttons {
762 .notifications_buttons {
762 margin: 0 0 @space 0;
763 margin: 0 0 @space 0;
763 padding: 0;
764 padding: 0;
764
765
765 .btn {
766 .btn {
766 display: inline-block;
767 display: inline-block;
767 }
768 }
768 }
769 }
769
770
770 .notification-list {
771 .notification-list {
771
772
772 div {
773 div {
773 display: inline-block;
774 display: inline-block;
774 vertical-align: middle;
775 vertical-align: middle;
775 }
776 }
776
777
777 .container {
778 .container {
778 display: block;
779 display: block;
779 margin: 0 0 @padding 0;
780 margin: 0 0 @padding 0;
780 }
781 }
781
782
782 .delete-notifications {
783 .delete-notifications {
783 margin-left: @padding;
784 margin-left: @padding;
784 text-align: right;
785 text-align: right;
785 cursor: pointer;
786 cursor: pointer;
786 }
787 }
787
788
788 .read-notifications {
789 .read-notifications {
789 margin-left: @padding/2;
790 margin-left: @padding/2;
790 text-align: right;
791 text-align: right;
791 width: 35px;
792 width: 35px;
792 cursor: pointer;
793 cursor: pointer;
793 }
794 }
794
795
795 .icon-minus-sign {
796 .icon-minus-sign {
796 color: @alert2;
797 color: @alert2;
797 }
798 }
798
799
799 .icon-ok-sign {
800 .icon-ok-sign {
800 color: @alert1;
801 color: @alert1;
801 }
802 }
802 }
803 }
803
804
804 .user_settings {
805 .user_settings {
805 float: left;
806 float: left;
806 clear: both;
807 clear: both;
807 display: block;
808 display: block;
808 width: 100%;
809 width: 100%;
809
810
810 .gravatar_box {
811 .gravatar_box {
811 margin-bottom: @padding;
812 margin-bottom: @padding;
812
813
813 &:after {
814 &:after {
814 content: " ";
815 content: " ";
815 clear: both;
816 clear: both;
816 width: 100%;
817 width: 100%;
817 }
818 }
818 }
819 }
819
820
820 .fields .field {
821 .fields .field {
821 clear: both;
822 clear: both;
822 }
823 }
823 }
824 }
824
825
825 .advanced_settings {
826 .advanced_settings {
826 margin-bottom: @space;
827 margin-bottom: @space;
827
828
828 .help-block {
829 .help-block {
829 margin-left: 0;
830 margin-left: 0;
830 }
831 }
831
832
832 button + .help-block {
833 button + .help-block {
833 margin-top: @padding;
834 margin-top: @padding;
834 }
835 }
835 }
836 }
836
837
837 // admin settings radio buttons and labels
838 // admin settings radio buttons and labels
838 .label-2 {
839 .label-2 {
839 float: left;
840 float: left;
840 width: @label2-width;
841 width: @label2-width;
841
842
842 label {
843 label {
843 color: @grey1;
844 color: @grey1;
844 }
845 }
845 }
846 }
846 .checkboxes {
847 .checkboxes {
847 float: left;
848 float: left;
848 width: @checkboxes-width;
849 width: @checkboxes-width;
849 margin-bottom: @padding;
850 margin-bottom: @padding;
850
851
851 .checkbox {
852 .checkbox {
852 width: 100%;
853 width: 100%;
853
854
854 label {
855 label {
855 margin: 0;
856 margin: 0;
856 padding: 0;
857 padding: 0;
857 }
858 }
858 }
859 }
859
860
860 .checkbox + .checkbox {
861 .checkbox + .checkbox {
861 display: inline-block;
862 display: inline-block;
862 }
863 }
863
864
864 label {
865 label {
865 margin-right: 1em;
866 margin-right: 1em;
866 }
867 }
867 }
868 }
868
869
869 // CHANGELOG
870 // CHANGELOG
870 .container_header {
871 .container_header {
871 float: left;
872 float: left;
872 display: block;
873 display: block;
873 width: 100%;
874 width: 100%;
874 margin: @padding 0 @padding;
875 margin: @padding 0 @padding;
875
876
876 #filter_changelog {
877 #filter_changelog {
877 float: left;
878 float: left;
878 margin-right: @padding;
879 margin-right: @padding;
879 }
880 }
880
881
881 .breadcrumbs_light {
882 .breadcrumbs_light {
882 display: inline-block;
883 display: inline-block;
883 }
884 }
884 }
885 }
885
886
886 .info_box {
887 .info_box {
887 float: right;
888 float: right;
888 }
889 }
889
890
890
891
891 #graph_nodes {
892 #graph_nodes {
892 padding-top: 43px;
893 padding-top: 43px;
893 }
894 }
894
895
895 #graph_content{
896 #graph_content{
896
897
897 // adjust for table headers so that graph renders properly
898 // adjust for table headers so that graph renders properly
898 // #graph_nodes padding - table cell padding
899 // #graph_nodes padding - table cell padding
899 padding-top: (@space - (@basefontsize * 2.4));
900 padding-top: (@space - (@basefontsize * 2.4));
900
901
901 &.graph_full_width {
902 &.graph_full_width {
902 width: 100%;
903 width: 100%;
903 max-width: 100%;
904 max-width: 100%;
904 }
905 }
905 }
906 }
906
907
907 #graph {
908 #graph {
908 .flag_status {
909 .flag_status {
909 margin: 0;
910 margin: 0;
910 }
911 }
911
912
912 .pagination-left {
913 .pagination-left {
913 float: left;
914 float: left;
914 clear: both;
915 clear: both;
915 }
916 }
916
917
917 .log-container {
918 .log-container {
918 max-width: 345px;
919 max-width: 345px;
919
920
920 .message{
921 .message{
921 max-width: 340px;
922 max-width: 340px;
922 }
923 }
923 }
924 }
924
925
925 .graph-col-wrapper {
926 .graph-col-wrapper {
926 padding-left: 110px;
927 padding-left: 110px;
927
928
928 #graph_nodes {
929 #graph_nodes {
929 width: 100px;
930 width: 100px;
930 margin-left: -110px;
931 margin-left: -110px;
931 float: left;
932 float: left;
932 clear: left;
933 clear: left;
933 }
934 }
934 }
935 }
935 }
936 }
936
937
937 #filter_changelog {
938 #filter_changelog {
938 float: left;
939 float: left;
939 }
940 }
940
941
941
942
942 //--- THEME ------------------//
943 //--- THEME ------------------//
943
944
944 #logo {
945 #logo {
945 float: left;
946 float: left;
946 margin: 9px 0 0 0;
947 margin: 9px 0 0 0;
947
948
948 .header {
949 .header {
949 background-color: transparent;
950 background-color: transparent;
950 }
951 }
951
952
952 a {
953 a {
953 display: inline-block;
954 display: inline-block;
954 }
955 }
955
956
956 img {
957 img {
957 height:30px;
958 height:30px;
958 }
959 }
959 }
960 }
960
961
961 .logo-wrapper {
962 .logo-wrapper {
962 float:left;
963 float:left;
963 }
964 }
964
965
965 .branding{
966 .branding{
966 float: left;
967 float: left;
967 padding: 9px 2px;
968 padding: 9px 2px;
968 line-height: 1em;
969 line-height: 1em;
969 font-size: @navigation-fontsize;
970 font-size: @navigation-fontsize;
970 }
971 }
971
972
972 img {
973 img {
973 border: none;
974 border: none;
974 outline: none;
975 outline: none;
975 }
976 }
976 user-profile-header
977 user-profile-header
977 label {
978 label {
978
979
979 input[type="checkbox"] {
980 input[type="checkbox"] {
980 margin-right: 1em;
981 margin-right: 1em;
981 }
982 }
982 input[type="radio"] {
983 input[type="radio"] {
983 margin-right: 1em;
984 margin-right: 1em;
984 }
985 }
985 }
986 }
986
987
987 .flag_status {
988 .flag_status {
988 margin: 2px 8px 6px 2px;
989 margin: 2px 8px 6px 2px;
989 &.under_review {
990 &.under_review {
990 .circle(5px, @alert3);
991 .circle(5px, @alert3);
991 }
992 }
992 &.approved {
993 &.approved {
993 .circle(5px, @alert1);
994 .circle(5px, @alert1);
994 }
995 }
995 &.rejected,
996 &.rejected,
996 &.forced_closed{
997 &.forced_closed{
997 .circle(5px, @alert2);
998 .circle(5px, @alert2);
998 }
999 }
999 &.not_reviewed {
1000 &.not_reviewed {
1000 .circle(5px, @grey5);
1001 .circle(5px, @grey5);
1001 }
1002 }
1002 }
1003 }
1003
1004
1004 .flag_status_comment_box {
1005 .flag_status_comment_box {
1005 margin: 5px 6px 0px 2px;
1006 margin: 5px 6px 0px 2px;
1006 }
1007 }
1007 .test_pattern_preview {
1008 .test_pattern_preview {
1008 margin: @space 0;
1009 margin: @space 0;
1009
1010
1010 p {
1011 p {
1011 margin-bottom: 0;
1012 margin-bottom: 0;
1012 border-bottom: @border-thickness solid @border-default-color;
1013 border-bottom: @border-thickness solid @border-default-color;
1013 color: @grey3;
1014 color: @grey3;
1014 }
1015 }
1015
1016
1016 .btn {
1017 .btn {
1017 margin-bottom: @padding;
1018 margin-bottom: @padding;
1018 }
1019 }
1019 }
1020 }
1020 #test_pattern_result {
1021 #test_pattern_result {
1021 display: none;
1022 display: none;
1022 &:extend(pre);
1023 &:extend(pre);
1023 padding: .9em;
1024 padding: .9em;
1024 color: @grey3;
1025 color: @grey3;
1025 background-color: @grey7;
1026 background-color: @grey7;
1026 border-right: @border-thickness solid @border-default-color;
1027 border-right: @border-thickness solid @border-default-color;
1027 border-bottom: @border-thickness solid @border-default-color;
1028 border-bottom: @border-thickness solid @border-default-color;
1028 border-left: @border-thickness solid @border-default-color;
1029 border-left: @border-thickness solid @border-default-color;
1029 }
1030 }
1030
1031
1031 #repo_vcs_settings {
1032 #repo_vcs_settings {
1032 #inherit_overlay_vcs_default {
1033 #inherit_overlay_vcs_default {
1033 display: none;
1034 display: none;
1034 }
1035 }
1035 #inherit_overlay_vcs_custom {
1036 #inherit_overlay_vcs_custom {
1036 display: custom;
1037 display: custom;
1037 }
1038 }
1038 &.inherited {
1039 &.inherited {
1039 #inherit_overlay_vcs_default {
1040 #inherit_overlay_vcs_default {
1040 display: block;
1041 display: block;
1041 }
1042 }
1042 #inherit_overlay_vcs_custom {
1043 #inherit_overlay_vcs_custom {
1043 display: none;
1044 display: none;
1044 }
1045 }
1045 }
1046 }
1046 }
1047 }
1047
1048
1048 .issue-tracker-link {
1049 .issue-tracker-link {
1049 color: @rcblue;
1050 color: @rcblue;
1050 }
1051 }
1051
1052
1052 // Issue Tracker Table Show/Hide
1053 // Issue Tracker Table Show/Hide
1053 #repo_issue_tracker {
1054 #repo_issue_tracker {
1054 #inherit_overlay {
1055 #inherit_overlay {
1055 display: none;
1056 display: none;
1056 }
1057 }
1057 #custom_overlay {
1058 #custom_overlay {
1058 display: custom;
1059 display: custom;
1059 }
1060 }
1060 &.inherited {
1061 &.inherited {
1061 #inherit_overlay {
1062 #inherit_overlay {
1062 display: block;
1063 display: block;
1063 }
1064 }
1064 #custom_overlay {
1065 #custom_overlay {
1065 display: none;
1066 display: none;
1066 }
1067 }
1067 }
1068 }
1068 }
1069 }
1069 table.issuetracker {
1070 table.issuetracker {
1070 &.readonly {
1071 &.readonly {
1071 tr, td {
1072 tr, td {
1072 color: @grey3;
1073 color: @grey3;
1073 }
1074 }
1074 }
1075 }
1075 .edit {
1076 .edit {
1076 display: none;
1077 display: none;
1077 }
1078 }
1078 .editopen {
1079 .editopen {
1079 .edit {
1080 .edit {
1080 display: inline;
1081 display: inline;
1081 }
1082 }
1082 .entry {
1083 .entry {
1083 display: none;
1084 display: none;
1084 }
1085 }
1085 }
1086 }
1086 tr td.td-action {
1087 tr td.td-action {
1087 min-width: 117px;
1088 min-width: 117px;
1088 }
1089 }
1089 td input {
1090 td input {
1090 max-width: none;
1091 max-width: none;
1091 min-width: 30px;
1092 min-width: 30px;
1092 width: 80%;
1093 width: 80%;
1093 }
1094 }
1094 .issuetracker_pref input {
1095 .issuetracker_pref input {
1095 width: 40%;
1096 width: 40%;
1096 }
1097 }
1097 input.edit_issuetracker_update {
1098 input.edit_issuetracker_update {
1098 margin-right: 0;
1099 margin-right: 0;
1099 width: auto;
1100 width: auto;
1100 }
1101 }
1101 }
1102 }
1102
1103
1103
1104
1104 //Permissions Settings
1105 //Permissions Settings
1105 #add_perm {
1106 #add_perm {
1106 margin: 0 0 @padding;
1107 margin: 0 0 @padding;
1107 cursor: pointer;
1108 cursor: pointer;
1108 }
1109 }
1109
1110
1110 .perm_ac {
1111 .perm_ac {
1111 input {
1112 input {
1112 width: 95%;
1113 width: 95%;
1113 }
1114 }
1114 }
1115 }
1115
1116
1116 .autocomplete-suggestions {
1117 .autocomplete-suggestions {
1117 width: auto !important; // overrides autocomplete.js
1118 width: auto !important; // overrides autocomplete.js
1118 margin: 0;
1119 margin: 0;
1119 border: @border-thickness solid @rcblue;
1120 border: @border-thickness solid @rcblue;
1120 border-radius: @border-radius;
1121 border-radius: @border-radius;
1121 color: @rcblue;
1122 color: @rcblue;
1122 background-color: white;
1123 background-color: white;
1123 }
1124 }
1124 .autocomplete-selected {
1125 .autocomplete-selected {
1125 background: #F0F0F0;
1126 background: #F0F0F0;
1126 }
1127 }
1127 .ac-container-wrap {
1128 .ac-container-wrap {
1128 margin: 0;
1129 margin: 0;
1129 padding: 8px;
1130 padding: 8px;
1130 border-bottom: @border-thickness solid @rclightblue;
1131 border-bottom: @border-thickness solid @rclightblue;
1131 list-style-type: none;
1132 list-style-type: none;
1132 cursor: pointer;
1133 cursor: pointer;
1133
1134
1134 &:hover {
1135 &:hover {
1135 background-color: @rclightblue;
1136 background-color: @rclightblue;
1136 }
1137 }
1137
1138
1138 img {
1139 img {
1139 margin-right: 1em;
1140 margin-right: 1em;
1140 }
1141 }
1141
1142
1142 strong {
1143 strong {
1143 font-weight: normal;
1144 font-weight: normal;
1144 }
1145 }
1145 }
1146 }
1146
1147
1147 // Settings Dropdown
1148 // Settings Dropdown
1148 .user-menu .container {
1149 .user-menu .container {
1149 padding: 0 4px;
1150 padding: 0 4px;
1150 margin: 0;
1151 margin: 0;
1151 }
1152 }
1152
1153
1153 .user-menu .gravatar {
1154 .user-menu .gravatar {
1154 cursor: pointer;
1155 cursor: pointer;
1155 }
1156 }
1156
1157
1157 .codeblock {
1158 .codeblock {
1158 margin-bottom: @padding;
1159 margin-bottom: @padding;
1159 clear: both;
1160 clear: both;
1160
1161
1161 .stats{
1162 .stats{
1162 overflow: hidden;
1163 overflow: hidden;
1163 }
1164 }
1164
1165
1165 .message{
1166 .message{
1166 textarea{
1167 textarea{
1167 margin: 0;
1168 margin: 0;
1168 }
1169 }
1169 }
1170 }
1170
1171
1171 .code-header {
1172 .code-header {
1172 .stats {
1173 .stats {
1173 line-height: 2em;
1174 line-height: 2em;
1174
1175
1175 .revision_id {
1176 .revision_id {
1176 margin-left: 0;
1177 margin-left: 0;
1177 }
1178 }
1178 .buttons {
1179 .buttons {
1179 padding-right: 0;
1180 padding-right: 0;
1180 }
1181 }
1181 }
1182 }
1182
1183
1183 .item{
1184 .item{
1184 margin-right: 0.5em;
1185 margin-right: 0.5em;
1185 }
1186 }
1186 }
1187 }
1187
1188
1188 #editor_container{
1189 #editor_container{
1189 position: relative;
1190 position: relative;
1190 margin: @padding;
1191 margin: @padding;
1191 }
1192 }
1192 }
1193 }
1193
1194
1194 #file_history_container {
1195 #file_history_container {
1195 display: none;
1196 display: none;
1196 }
1197 }
1197
1198
1198 .file-history-inner {
1199 .file-history-inner {
1199 margin-bottom: 10px;
1200 margin-bottom: 10px;
1200 }
1201 }
1201
1202
1202 // Pull Requests
1203 // Pull Requests
1203 .summary-details {
1204 .summary-details {
1204 width: 72%;
1205 width: 72%;
1205 }
1206 }
1206 .pr-summary {
1207 .pr-summary {
1207 border-bottom: @border-thickness solid @grey5;
1208 border-bottom: @border-thickness solid @grey5;
1208 margin-bottom: @space;
1209 margin-bottom: @space;
1209 }
1210 }
1210 .reviewers-title {
1211 .reviewers-title {
1211 width: 25%;
1212 width: 25%;
1212 min-width: 200px;
1213 min-width: 200px;
1213 }
1214 }
1214 .reviewers {
1215 .reviewers {
1215 width: 25%;
1216 width: 25%;
1216 min-width: 200px;
1217 min-width: 200px;
1217 }
1218 }
1218 .reviewers ul li {
1219 .reviewers ul li {
1219 position: relative;
1220 position: relative;
1220 width: 100%;
1221 width: 100%;
1221 margin-bottom: 8px;
1222 margin-bottom: 8px;
1222 }
1223 }
1223 .reviewers_member {
1224 .reviewers_member {
1224 width: 100%;
1225 width: 100%;
1225 overflow: auto;
1226 overflow: auto;
1226 }
1227 }
1227 .reviewer_status {
1228 .reviewer_status {
1228 display: inline-block;
1229 display: inline-block;
1229 vertical-align: top;
1230 vertical-align: top;
1230 width: 7%;
1231 width: 7%;
1231 min-width: 20px;
1232 min-width: 20px;
1232 height: 1.2em;
1233 height: 1.2em;
1233 margin-top: 3px;
1234 margin-top: 3px;
1234 line-height: 1em;
1235 line-height: 1em;
1235 }
1236 }
1236
1237
1237 .reviewer_name {
1238 .reviewer_name {
1238 display: inline-block;
1239 display: inline-block;
1239 max-width: 83%;
1240 max-width: 83%;
1240 padding-right: 20px;
1241 padding-right: 20px;
1241 vertical-align: middle;
1242 vertical-align: middle;
1242 line-height: 1;
1243 line-height: 1;
1243
1244
1244 .rc-user {
1245 .rc-user {
1245 min-width: 0;
1246 min-width: 0;
1246 margin: -2px 1em 0 0;
1247 margin: -2px 1em 0 0;
1247 }
1248 }
1248
1249
1249 .reviewer {
1250 .reviewer {
1250 float: left;
1251 float: left;
1251 }
1252 }
1252
1253
1253 &.to-delete {
1254 &.to-delete {
1254 .user,
1255 .user,
1255 .reviewer {
1256 .reviewer {
1256 text-decoration: line-through;
1257 text-decoration: line-through;
1257 }
1258 }
1258 }
1259 }
1259 }
1260 }
1260
1261
1261 .reviewer_member_remove {
1262 .reviewer_member_remove {
1262 position: absolute;
1263 position: absolute;
1263 right: 0;
1264 right: 0;
1264 top: 0;
1265 top: 0;
1265 width: 16px;
1266 width: 16px;
1266 margin-bottom: 10px;
1267 margin-bottom: 10px;
1267 padding: 0;
1268 padding: 0;
1268 color: black;
1269 color: black;
1269 }
1270 }
1270 .reviewer_member_status {
1271 .reviewer_member_status {
1271 margin-top: 5px;
1272 margin-top: 5px;
1272 }
1273 }
1273 .pr-summary #summary{
1274 .pr-summary #summary{
1274 width: 100%;
1275 width: 100%;
1275 }
1276 }
1276 .pr-summary .action_button:hover {
1277 .pr-summary .action_button:hover {
1277 border: 0;
1278 border: 0;
1278 cursor: pointer;
1279 cursor: pointer;
1279 }
1280 }
1280 .pr-details-title {
1281 .pr-details-title {
1281 padding-bottom: 8px;
1282 padding-bottom: 8px;
1282 border-bottom: @border-thickness solid @grey5;
1283 border-bottom: @border-thickness solid @grey5;
1283 .action_button {
1284 .action_button {
1284 color: @rcblue;
1285 color: @rcblue;
1285 }
1286 }
1286 }
1287 }
1287 .pr-details-content {
1288 .pr-details-content {
1288 margin-top: @textmargin;
1289 margin-top: @textmargin;
1289 margin-bottom: @textmargin;
1290 margin-bottom: @textmargin;
1290 }
1291 }
1291 .pr-description {
1292 .pr-description {
1292 white-space:pre-wrap;
1293 white-space:pre-wrap;
1293 }
1294 }
1294 .group_members {
1295 .group_members {
1295 margin-top: 0;
1296 margin-top: 0;
1296 padding: 0;
1297 padding: 0;
1297 list-style: outside none none;
1298 list-style: outside none none;
1298 }
1299 }
1299 .reviewer_ac .ac-input {
1300 .reviewer_ac .ac-input {
1300 width: 92%;
1301 width: 92%;
1301 margin-bottom: 1em;
1302 margin-bottom: 1em;
1302 }
1303 }
1303 #update_commits {
1304 #update_commits {
1304 float: right;
1305 float: right;
1305 }
1306 }
1306 .compare_view_commits tr{
1307 .compare_view_commits tr{
1307 height: 20px;
1308 height: 20px;
1308 }
1309 }
1309 .compare_view_commits td {
1310 .compare_view_commits td {
1310 vertical-align: top;
1311 vertical-align: top;
1311 padding-top: 10px;
1312 padding-top: 10px;
1312 }
1313 }
1313 .compare_view_commits .author {
1314 .compare_view_commits .author {
1314 margin-left: 5px;
1315 margin-left: 5px;
1315 }
1316 }
1316
1317
1317 .compare_view_files {
1318 .compare_view_files {
1318 width: 100%;
1319 width: 100%;
1319
1320
1320 td {
1321 td {
1321 vertical-align: middle;
1322 vertical-align: middle;
1322 }
1323 }
1323 }
1324 }
1324
1325
1325 .compare_view_filepath {
1326 .compare_view_filepath {
1326 color: @grey1;
1327 color: @grey1;
1327 }
1328 }
1328
1329
1329 .show_more {
1330 .show_more {
1330 display: inline-block;
1331 display: inline-block;
1331 position: relative;
1332 position: relative;
1332 vertical-align: middle;
1333 vertical-align: middle;
1333 width: 4px;
1334 width: 4px;
1334 height: @basefontsize;
1335 height: @basefontsize;
1335
1336
1336 &:after {
1337 &:after {
1337 content: "\00A0\25BE";
1338 content: "\00A0\25BE";
1338 display: inline-block;
1339 display: inline-block;
1339 width:10px;
1340 width:10px;
1340 line-height: 5px;
1341 line-height: 5px;
1341 font-size: 12px;
1342 font-size: 12px;
1342 cursor: pointer;
1343 cursor: pointer;
1343 }
1344 }
1344 }
1345 }
1345
1346
1346 .journal_more .show_more {
1347 .journal_more .show_more {
1347 display: inline;
1348 display: inline;
1348
1349
1349 &:after {
1350 &:after {
1350 content: none;
1351 content: none;
1351 }
1352 }
1352 }
1353 }
1353
1354
1354 .open .show_more:after,
1355 .open .show_more:after,
1355 .select2-dropdown-open .show_more:after {
1356 .select2-dropdown-open .show_more:after {
1356 .rotate(180deg);
1357 .rotate(180deg);
1357 margin-left: 4px;
1358 margin-left: 4px;
1358 }
1359 }
1359
1360
1360
1361
1361 .compare_view_commits .collapse_commit:after {
1362 .compare_view_commits .collapse_commit:after {
1362 cursor: pointer;
1363 cursor: pointer;
1363 content: "\00A0\25B4";
1364 content: "\00A0\25B4";
1364 margin-left: -3px;
1365 margin-left: -3px;
1365 font-size: 17px;
1366 font-size: 17px;
1366 color: @grey4;
1367 color: @grey4;
1367 }
1368 }
1368
1369
1369 .diff_links {
1370 .diff_links {
1370 margin-left: 8px;
1371 margin-left: 8px;
1371 }
1372 }
1372
1373
1373 p.ancestor {
1374 p.ancestor {
1374 margin: @padding 0;
1375 margin: @padding 0;
1375 }
1376 }
1376
1377
1377 .cs_icon_td input[type="checkbox"] {
1378 .cs_icon_td input[type="checkbox"] {
1378 display: none;
1379 display: none;
1379 }
1380 }
1380
1381
1381 .cs_icon_td .expand_file_icon:after {
1382 .cs_icon_td .expand_file_icon:after {
1382 cursor: pointer;
1383 cursor: pointer;
1383 content: "\00A0\25B6";
1384 content: "\00A0\25B6";
1384 font-size: 12px;
1385 font-size: 12px;
1385 color: @grey4;
1386 color: @grey4;
1386 }
1387 }
1387
1388
1388 .cs_icon_td .collapse_file_icon:after {
1389 .cs_icon_td .collapse_file_icon:after {
1389 cursor: pointer;
1390 cursor: pointer;
1390 content: "\00A0\25BC";
1391 content: "\00A0\25BC";
1391 font-size: 12px;
1392 font-size: 12px;
1392 color: @grey4;
1393 color: @grey4;
1393 }
1394 }
1394
1395
1395 /*new binary
1396 /*new binary
1396 NEW_FILENODE = 1
1397 NEW_FILENODE = 1
1397 DEL_FILENODE = 2
1398 DEL_FILENODE = 2
1398 MOD_FILENODE = 3
1399 MOD_FILENODE = 3
1399 RENAMED_FILENODE = 4
1400 RENAMED_FILENODE = 4
1400 COPIED_FILENODE = 5
1401 COPIED_FILENODE = 5
1401 CHMOD_FILENODE = 6
1402 CHMOD_FILENODE = 6
1402 BIN_FILENODE = 7
1403 BIN_FILENODE = 7
1403 */
1404 */
1404 .cs_files_expand {
1405 .cs_files_expand {
1405 font-size: @basefontsize + 5px;
1406 font-size: @basefontsize + 5px;
1406 line-height: 1.8em;
1407 line-height: 1.8em;
1407 float: right;
1408 float: right;
1408 }
1409 }
1409
1410
1410 .cs_files_expand span{
1411 .cs_files_expand span{
1411 color: @rcblue;
1412 color: @rcblue;
1412 cursor: pointer;
1413 cursor: pointer;
1413 }
1414 }
1414 .cs_files {
1415 .cs_files {
1415 clear: both;
1416 clear: both;
1416 padding-bottom: @padding;
1417 padding-bottom: @padding;
1417
1418
1418 .cur_cs {
1419 .cur_cs {
1419 margin: 10px 2px;
1420 margin: 10px 2px;
1420 font-weight: bold;
1421 font-weight: bold;
1421 }
1422 }
1422
1423
1423 .node {
1424 .node {
1424 float: left;
1425 float: left;
1425 }
1426 }
1426
1427
1427 .changes {
1428 .changes {
1428 float: right;
1429 float: right;
1429 color: white;
1430 color: white;
1430 font-size: @basefontsize - 4px;
1431 font-size: @basefontsize - 4px;
1431 margin-top: 4px;
1432 margin-top: 4px;
1432 opacity: 0.6;
1433 opacity: 0.6;
1433 filter: Alpha(opacity=60); /* IE8 and earlier */
1434 filter: Alpha(opacity=60); /* IE8 and earlier */
1434
1435
1435 .added {
1436 .added {
1436 background-color: @alert1;
1437 background-color: @alert1;
1437 float: left;
1438 float: left;
1438 text-align: center;
1439 text-align: center;
1439 }
1440 }
1440
1441
1441 .deleted {
1442 .deleted {
1442 background-color: @alert2;
1443 background-color: @alert2;
1443 float: left;
1444 float: left;
1444 text-align: center;
1445 text-align: center;
1445 }
1446 }
1446
1447
1447 .bin {
1448 .bin {
1448 background-color: @alert1;
1449 background-color: @alert1;
1449 text-align: center;
1450 text-align: center;
1450 }
1451 }
1451
1452
1452 /*new binary*/
1453 /*new binary*/
1453 .bin.bin1 {
1454 .bin.bin1 {
1454 background-color: @alert1;
1455 background-color: @alert1;
1455 text-align: center;
1456 text-align: center;
1456 }
1457 }
1457
1458
1458 /*deleted binary*/
1459 /*deleted binary*/
1459 .bin.bin2 {
1460 .bin.bin2 {
1460 background-color: @alert2;
1461 background-color: @alert2;
1461 text-align: center;
1462 text-align: center;
1462 }
1463 }
1463
1464
1464 /*mod binary*/
1465 /*mod binary*/
1465 .bin.bin3 {
1466 .bin.bin3 {
1466 background-color: @grey2;
1467 background-color: @grey2;
1467 text-align: center;
1468 text-align: center;
1468 }
1469 }
1469
1470
1470 /*rename file*/
1471 /*rename file*/
1471 .bin.bin4 {
1472 .bin.bin4 {
1472 background-color: @alert4;
1473 background-color: @alert4;
1473 text-align: center;
1474 text-align: center;
1474 }
1475 }
1475
1476
1476 /*copied file*/
1477 /*copied file*/
1477 .bin.bin5 {
1478 .bin.bin5 {
1478 background-color: @alert4;
1479 background-color: @alert4;
1479 text-align: center;
1480 text-align: center;
1480 }
1481 }
1481
1482
1482 /*chmod file*/
1483 /*chmod file*/
1483 .bin.bin6 {
1484 .bin.bin6 {
1484 background-color: @grey2;
1485 background-color: @grey2;
1485 text-align: center;
1486 text-align: center;
1486 }
1487 }
1487 }
1488 }
1488 }
1489 }
1489
1490
1490 .cs_files .cs_added, .cs_files .cs_A,
1491 .cs_files .cs_added, .cs_files .cs_A,
1491 .cs_files .cs_added, .cs_files .cs_M,
1492 .cs_files .cs_added, .cs_files .cs_M,
1492 .cs_files .cs_added, .cs_files .cs_D {
1493 .cs_files .cs_added, .cs_files .cs_D {
1493 height: 16px;
1494 height: 16px;
1494 padding-right: 10px;
1495 padding-right: 10px;
1495 margin-top: 7px;
1496 margin-top: 7px;
1496 text-align: left;
1497 text-align: left;
1497 }
1498 }
1498
1499
1499 .cs_icon_td {
1500 .cs_icon_td {
1500 min-width: 16px;
1501 min-width: 16px;
1501 width: 16px;
1502 width: 16px;
1502 }
1503 }
1503
1504
1504 .pull-request-merge {
1505 .pull-request-merge {
1505 padding: 10px 0;
1506 padding: 10px 0;
1506 margin-top: 10px;
1507 margin-top: 10px;
1507 margin-bottom: 20px;
1508 margin-bottom: 20px;
1508 }
1509 }
1509
1510
1510 .pull-request-merge .pull-request-wrap {
1511 .pull-request-merge .pull-request-wrap {
1511 height: 25px;
1512 height: 25px;
1512 padding: 5px 0;
1513 padding: 5px 0;
1513 }
1514 }
1514
1515
1515 .pull-request-merge span {
1516 .pull-request-merge span {
1516 margin-right: 10px;
1517 margin-right: 10px;
1517 }
1518 }
1518 #close_pull_request {
1519 #close_pull_request {
1519 margin-right: 0px;
1520 margin-right: 0px;
1520 }
1521 }
1521
1522
1522 .empty_data {
1523 .empty_data {
1523 color: @grey4;
1524 color: @grey4;
1524 }
1525 }
1525
1526
1526 #changeset_compare_view_content {
1527 #changeset_compare_view_content {
1527 margin-bottom: @space;
1528 margin-bottom: @space;
1528 clear: both;
1529 clear: both;
1529 width: 100%;
1530 width: 100%;
1530 box-sizing: border-box;
1531 box-sizing: border-box;
1531 .border-radius(@border-radius);
1532 .border-radius(@border-radius);
1532
1533
1533 .help-block {
1534 .help-block {
1534 margin: @padding 0;
1535 margin: @padding 0;
1535 color: @text-color;
1536 color: @text-color;
1536 }
1537 }
1537
1538
1538 .empty_data {
1539 .empty_data {
1539 margin: @padding 0;
1540 margin: @padding 0;
1540 }
1541 }
1541
1542
1542 .alert {
1543 .alert {
1543 margin-bottom: @space;
1544 margin-bottom: @space;
1544 }
1545 }
1545 }
1546 }
1546
1547
1547 .table_disp {
1548 .table_disp {
1548 .status {
1549 .status {
1549 width: auto;
1550 width: auto;
1550
1551
1551 .flag_status {
1552 .flag_status {
1552 float: left;
1553 float: left;
1553 }
1554 }
1554 }
1555 }
1555 }
1556 }
1556
1557
1557 .status_box_menu {
1558 .status_box_menu {
1558 margin: 0;
1559 margin: 0;
1559 }
1560 }
1560
1561
1561 .notification-table{
1562 .notification-table{
1562 margin-bottom: @space;
1563 margin-bottom: @space;
1563 display: table;
1564 display: table;
1564 width: 100%;
1565 width: 100%;
1565
1566
1566 .container{
1567 .container{
1567 display: table-row;
1568 display: table-row;
1568
1569
1569 .notification-header{
1570 .notification-header{
1570 border-bottom: @border-thickness solid @border-default-color;
1571 border-bottom: @border-thickness solid @border-default-color;
1571 }
1572 }
1572
1573
1573 .notification-subject{
1574 .notification-subject{
1574 display: table-cell;
1575 display: table-cell;
1575 }
1576 }
1576 }
1577 }
1577 }
1578 }
1578
1579
1579 // Notifications
1580 // Notifications
1580 .notification-header{
1581 .notification-header{
1581 display: table;
1582 display: table;
1582 width: 100%;
1583 width: 100%;
1583 padding: floor(@basefontsize/2) 0;
1584 padding: floor(@basefontsize/2) 0;
1584 line-height: 1em;
1585 line-height: 1em;
1585
1586
1586 .desc, .delete-notifications, .read-notifications{
1587 .desc, .delete-notifications, .read-notifications{
1587 display: table-cell;
1588 display: table-cell;
1588 text-align: left;
1589 text-align: left;
1589 }
1590 }
1590
1591
1591 .desc{
1592 .desc{
1592 width: 1163px;
1593 width: 1163px;
1593 }
1594 }
1594
1595
1595 .delete-notifications, .read-notifications{
1596 .delete-notifications, .read-notifications{
1596 width: 35px;
1597 width: 35px;
1597 min-width: 35px; //fixes when only one button is displayed
1598 min-width: 35px; //fixes when only one button is displayed
1598 }
1599 }
1599 }
1600 }
1600
1601
1601 .notification-body {
1602 .notification-body {
1602 .markdown-block,
1603 .markdown-block,
1603 .rst-block {
1604 .rst-block {
1604 padding: @padding 0;
1605 padding: @padding 0;
1605 }
1606 }
1606
1607
1607 .notification-subject {
1608 .notification-subject {
1608 padding: @textmargin 0;
1609 padding: @textmargin 0;
1609 border-bottom: @border-thickness solid @border-default-color;
1610 border-bottom: @border-thickness solid @border-default-color;
1610 }
1611 }
1611 }
1612 }
1612
1613
1613
1614
1614 .notifications_buttons{
1615 .notifications_buttons{
1615 float: right;
1616 float: right;
1616 }
1617 }
1617
1618
1619 #notification-status{
1620 display: inline;
1621 }
1622
1618 // Repositories
1623 // Repositories
1619
1624
1620 #summary.fields{
1625 #summary.fields{
1621 display: table;
1626 display: table;
1622
1627
1623 .field{
1628 .field{
1624 display: table-row;
1629 display: table-row;
1625
1630
1626 .label-summary{
1631 .label-summary{
1627 display: table-cell;
1632 display: table-cell;
1628 min-width: @label-summary-minwidth;
1633 min-width: @label-summary-minwidth;
1629 padding-top: @padding/2;
1634 padding-top: @padding/2;
1630 padding-bottom: @padding/2;
1635 padding-bottom: @padding/2;
1631 padding-right: @padding/2;
1636 padding-right: @padding/2;
1632 }
1637 }
1633
1638
1634 .input{
1639 .input{
1635 display: table-cell;
1640 display: table-cell;
1636 padding: @padding/2;
1641 padding: @padding/2;
1637
1642
1638 input{
1643 input{
1639 min-width: 29em;
1644 min-width: 29em;
1640 padding: @padding/4;
1645 padding: @padding/4;
1641 }
1646 }
1642 }
1647 }
1643 .statistics, .downloads{
1648 .statistics, .downloads{
1644 .disabled{
1649 .disabled{
1645 color: @grey4;
1650 color: @grey4;
1646 }
1651 }
1647 }
1652 }
1648 }
1653 }
1649 }
1654 }
1650
1655
1651 #summary{
1656 #summary{
1652 width: 70%;
1657 width: 70%;
1653 }
1658 }
1654
1659
1655
1660
1656 // Journal
1661 // Journal
1657 .journal.title {
1662 .journal.title {
1658 h5 {
1663 h5 {
1659 float: left;
1664 float: left;
1660 margin: 0;
1665 margin: 0;
1661 width: 70%;
1666 width: 70%;
1662 }
1667 }
1663
1668
1664 ul {
1669 ul {
1665 float: right;
1670 float: right;
1666 display: inline-block;
1671 display: inline-block;
1667 margin: 0;
1672 margin: 0;
1668 width: 30%;
1673 width: 30%;
1669 text-align: right;
1674 text-align: right;
1670
1675
1671 li {
1676 li {
1672 display: inline;
1677 display: inline;
1673 font-size: @journal-fontsize;
1678 font-size: @journal-fontsize;
1674 line-height: 1em;
1679 line-height: 1em;
1675
1680
1676 &:before { content: none; }
1681 &:before { content: none; }
1677 }
1682 }
1678 }
1683 }
1679 }
1684 }
1680
1685
1681 .filterexample {
1686 .filterexample {
1682 position: absolute;
1687 position: absolute;
1683 top: 95px;
1688 top: 95px;
1684 left: @contentpadding;
1689 left: @contentpadding;
1685 color: @rcblue;
1690 color: @rcblue;
1686 font-size: 11px;
1691 font-size: 11px;
1687 font-family: @text-regular;
1692 font-family: @text-regular;
1688 cursor: help;
1693 cursor: help;
1689
1694
1690 &:hover {
1695 &:hover {
1691 color: @rcdarkblue;
1696 color: @rcdarkblue;
1692 }
1697 }
1693
1698
1694 @media (max-width:768px) {
1699 @media (max-width:768px) {
1695 position: relative;
1700 position: relative;
1696 top: auto;
1701 top: auto;
1697 left: auto;
1702 left: auto;
1698 display: block;
1703 display: block;
1699 }
1704 }
1700 }
1705 }
1701
1706
1702
1707
1703 #journal{
1708 #journal{
1704 margin-bottom: @space;
1709 margin-bottom: @space;
1705
1710
1706 .journal_day{
1711 .journal_day{
1707 margin-bottom: @textmargin/2;
1712 margin-bottom: @textmargin/2;
1708 padding-bottom: @textmargin/2;
1713 padding-bottom: @textmargin/2;
1709 font-size: @journal-fontsize;
1714 font-size: @journal-fontsize;
1710 border-bottom: @border-thickness solid @border-default-color;
1715 border-bottom: @border-thickness solid @border-default-color;
1711 }
1716 }
1712
1717
1713 .journal_container{
1718 .journal_container{
1714 margin-bottom: @space;
1719 margin-bottom: @space;
1715
1720
1716 .journal_user{
1721 .journal_user{
1717 display: inline-block;
1722 display: inline-block;
1718 }
1723 }
1719 .journal_action_container{
1724 .journal_action_container{
1720 display: block;
1725 display: block;
1721 margin-top: @textmargin;
1726 margin-top: @textmargin;
1722
1727
1723 div{
1728 div{
1724 display: inline;
1729 display: inline;
1725 }
1730 }
1726
1731
1727 div.journal_action_params{
1732 div.journal_action_params{
1728 display: block;
1733 display: block;
1729 }
1734 }
1730
1735
1731 div.journal_repo:after{
1736 div.journal_repo:after{
1732 content: "\A";
1737 content: "\A";
1733 white-space: pre;
1738 white-space: pre;
1734 }
1739 }
1735
1740
1736 div.date{
1741 div.date{
1737 display: block;
1742 display: block;
1738 margin-bottom: @textmargin;
1743 margin-bottom: @textmargin;
1739 }
1744 }
1740 }
1745 }
1741 }
1746 }
1742 }
1747 }
1743
1748
1744 // Files
1749 // Files
1745 .edit-file-title {
1750 .edit-file-title {
1746 border-bottom: @border-thickness solid @border-default-color;
1751 border-bottom: @border-thickness solid @border-default-color;
1747
1752
1748 .breadcrumbs {
1753 .breadcrumbs {
1749 margin-bottom: 0;
1754 margin-bottom: 0;
1750 }
1755 }
1751 }
1756 }
1752
1757
1753 .edit-file-fieldset {
1758 .edit-file-fieldset {
1754 margin-top: @sidebarpadding;
1759 margin-top: @sidebarpadding;
1755
1760
1756 .fieldset {
1761 .fieldset {
1757 .left-label {
1762 .left-label {
1758 width: 13%;
1763 width: 13%;
1759 }
1764 }
1760 .right-content {
1765 .right-content {
1761 width: 87%;
1766 width: 87%;
1762 max-width: 100%;
1767 max-width: 100%;
1763 }
1768 }
1764 .filename-label {
1769 .filename-label {
1765 margin-top: 13px;
1770 margin-top: 13px;
1766 }
1771 }
1767 .commit-message-label {
1772 .commit-message-label {
1768 margin-top: 4px;
1773 margin-top: 4px;
1769 }
1774 }
1770 .file-upload-input {
1775 .file-upload-input {
1771 input {
1776 input {
1772 display: none;
1777 display: none;
1773 }
1778 }
1774 }
1779 }
1775 p {
1780 p {
1776 margin-top: 5px;
1781 margin-top: 5px;
1777 }
1782 }
1778
1783
1779 }
1784 }
1780 .custom-path-link {
1785 .custom-path-link {
1781 margin-left: 5px;
1786 margin-left: 5px;
1782 }
1787 }
1783 #commit {
1788 #commit {
1784 resize: vertical;
1789 resize: vertical;
1785 }
1790 }
1786 }
1791 }
1787
1792
1788 .delete-file-preview {
1793 .delete-file-preview {
1789 max-height: 250px;
1794 max-height: 250px;
1790 }
1795 }
1791
1796
1792 .new-file,
1797 .new-file,
1793 #filter_activate,
1798 #filter_activate,
1794 #filter_deactivate {
1799 #filter_deactivate {
1795 float: left;
1800 float: left;
1796 margin: 0 0 0 15px;
1801 margin: 0 0 0 15px;
1797 }
1802 }
1798
1803
1799 h3.files_location{
1804 h3.files_location{
1800 line-height: 2.4em;
1805 line-height: 2.4em;
1801 }
1806 }
1802
1807
1803 .browser-nav {
1808 .browser-nav {
1804 display: table;
1809 display: table;
1805 margin-bottom: @space;
1810 margin-bottom: @space;
1806
1811
1807
1812
1808 .info_box {
1813 .info_box {
1809 display: inline-table;
1814 display: inline-table;
1810 height: 2.5em;
1815 height: 2.5em;
1811
1816
1812 .browser-cur-rev, .info_box_elem {
1817 .browser-cur-rev, .info_box_elem {
1813 display: table-cell;
1818 display: table-cell;
1814 vertical-align: middle;
1819 vertical-align: middle;
1815 }
1820 }
1816
1821
1817 .info_box_elem {
1822 .info_box_elem {
1818 border-top: @border-thickness solid @rcblue;
1823 border-top: @border-thickness solid @rcblue;
1819 border-bottom: @border-thickness solid @rcblue;
1824 border-bottom: @border-thickness solid @rcblue;
1820
1825
1821 #at_rev, a {
1826 #at_rev, a {
1822 padding: 0.6em 0.9em;
1827 padding: 0.6em 0.9em;
1823 margin: 0;
1828 margin: 0;
1824 .box-shadow(none);
1829 .box-shadow(none);
1825 border: 0;
1830 border: 0;
1826 height: 12px;
1831 height: 12px;
1827 }
1832 }
1828
1833
1829 input#at_rev {
1834 input#at_rev {
1830 max-width: 50px;
1835 max-width: 50px;
1831 text-align: right;
1836 text-align: right;
1832 }
1837 }
1833
1838
1834 &.previous {
1839 &.previous {
1835 border: @border-thickness solid @rcblue;
1840 border: @border-thickness solid @rcblue;
1836 .disabled {
1841 .disabled {
1837 color: @grey4;
1842 color: @grey4;
1838 cursor: not-allowed;
1843 cursor: not-allowed;
1839 }
1844 }
1840 }
1845 }
1841
1846
1842 &.next {
1847 &.next {
1843 border: @border-thickness solid @rcblue;
1848 border: @border-thickness solid @rcblue;
1844 .disabled {
1849 .disabled {
1845 color: @grey4;
1850 color: @grey4;
1846 cursor: not-allowed;
1851 cursor: not-allowed;
1847 }
1852 }
1848 }
1853 }
1849 }
1854 }
1850
1855
1851 .browser-cur-rev {
1856 .browser-cur-rev {
1852
1857
1853 span{
1858 span{
1854 margin: 0;
1859 margin: 0;
1855 color: @rcblue;
1860 color: @rcblue;
1856 height: 12px;
1861 height: 12px;
1857 display: inline-block;
1862 display: inline-block;
1858 padding: 0.7em 1em ;
1863 padding: 0.7em 1em ;
1859 border: @border-thickness solid @rcblue;
1864 border: @border-thickness solid @rcblue;
1860 margin-right: @padding;
1865 margin-right: @padding;
1861 }
1866 }
1862 }
1867 }
1863 }
1868 }
1864
1869
1865 .search_activate {
1870 .search_activate {
1866 display: table-cell;
1871 display: table-cell;
1867 vertical-align: middle;
1872 vertical-align: middle;
1868
1873
1869 input, label{
1874 input, label{
1870 margin: 0;
1875 margin: 0;
1871 padding: 0;
1876 padding: 0;
1872 }
1877 }
1873
1878
1874 input{
1879 input{
1875 margin-left: @textmargin;
1880 margin-left: @textmargin;
1876 }
1881 }
1877
1882
1878 }
1883 }
1879 }
1884 }
1880
1885
1881 .browser-cur-rev{
1886 .browser-cur-rev{
1882 margin-bottom: @textmargin;
1887 margin-bottom: @textmargin;
1883 }
1888 }
1884
1889
1885 #node_filter_box_loading{
1890 #node_filter_box_loading{
1886 .info_text;
1891 .info_text;
1887 }
1892 }
1888
1893
1889 .browser-search {
1894 .browser-search {
1890 margin: -25px 0px 5px 0px;
1895 margin: -25px 0px 5px 0px;
1891 }
1896 }
1892
1897
1893 .node-filter {
1898 .node-filter {
1894 font-size: @repo-title-fontsize;
1899 font-size: @repo-title-fontsize;
1895 padding: 4px 0px 0px 0px;
1900 padding: 4px 0px 0px 0px;
1896
1901
1897 .node-filter-path {
1902 .node-filter-path {
1898 float: left;
1903 float: left;
1899 color: @grey4;
1904 color: @grey4;
1900 }
1905 }
1901 .node-filter-input {
1906 .node-filter-input {
1902 float: left;
1907 float: left;
1903 margin: -2px 0px 0px 2px;
1908 margin: -2px 0px 0px 2px;
1904 input {
1909 input {
1905 padding: 2px;
1910 padding: 2px;
1906 border: none;
1911 border: none;
1907 font-size: @repo-title-fontsize;
1912 font-size: @repo-title-fontsize;
1908 }
1913 }
1909 }
1914 }
1910 }
1915 }
1911
1916
1912
1917
1913 .browser-result{
1918 .browser-result{
1914 td a{
1919 td a{
1915 margin-left: 0.5em;
1920 margin-left: 0.5em;
1916 display: inline-block;
1921 display: inline-block;
1917
1922
1918 em{
1923 em{
1919 font-family: @text-bold;
1924 font-family: @text-bold;
1920 }
1925 }
1921 }
1926 }
1922 }
1927 }
1923
1928
1924 .browser-highlight{
1929 .browser-highlight{
1925 background-color: @grey5-alpha;
1930 background-color: @grey5-alpha;
1926 }
1931 }
1927
1932
1928
1933
1929 // Search
1934 // Search
1930
1935
1931 .search-form{
1936 .search-form{
1932 #q {
1937 #q {
1933 width: @search-form-width;
1938 width: @search-form-width;
1934 }
1939 }
1935 .fields{
1940 .fields{
1936 margin: 0 0 @space;
1941 margin: 0 0 @space;
1937 }
1942 }
1938
1943
1939 label{
1944 label{
1940 display: inline-block;
1945 display: inline-block;
1941 margin-right: @textmargin;
1946 margin-right: @textmargin;
1942 padding-top: 0.25em;
1947 padding-top: 0.25em;
1943 }
1948 }
1944
1949
1945
1950
1946 .results{
1951 .results{
1947 clear: both;
1952 clear: both;
1948 margin: 0 0 @padding;
1953 margin: 0 0 @padding;
1949 }
1954 }
1950 }
1955 }
1951
1956
1952 div.search-feedback-items {
1957 div.search-feedback-items {
1953 display: inline-block;
1958 display: inline-block;
1954 padding:0px 0px 0px 96px;
1959 padding:0px 0px 0px 96px;
1955 }
1960 }
1956
1961
1957 div.search-code-body {
1962 div.search-code-body {
1958 background-color: #ffffff; padding: 5px 0 5px 10px;
1963 background-color: #ffffff; padding: 5px 0 5px 10px;
1959 pre {
1964 pre {
1960 .match { background-color: #faffa6;}
1965 .match { background-color: #faffa6;}
1961 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
1966 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
1962 }
1967 }
1963 }
1968 }
1964
1969
1965 .expand_commit.search {
1970 .expand_commit.search {
1966 .show_more.open {
1971 .show_more.open {
1967 height: auto;
1972 height: auto;
1968 max-height: none;
1973 max-height: none;
1969 }
1974 }
1970 }
1975 }
1971
1976
1972 .search-results {
1977 .search-results {
1973
1978
1974 h2 {
1979 h2 {
1975 margin-bottom: 0;
1980 margin-bottom: 0;
1976 }
1981 }
1977 .codeblock {
1982 .codeblock {
1978 border: none;
1983 border: none;
1979 background: transparent;
1984 background: transparent;
1980 }
1985 }
1981
1986
1982 .codeblock-header {
1987 .codeblock-header {
1983 border: none;
1988 border: none;
1984 background: transparent;
1989 background: transparent;
1985 }
1990 }
1986
1991
1987 .code-body {
1992 .code-body {
1988 border: @border-thickness solid @border-default-color;
1993 border: @border-thickness solid @border-default-color;
1989 .border-radius(@border-radius);
1994 .border-radius(@border-radius);
1990 }
1995 }
1991
1996
1992 .td-commit {
1997 .td-commit {
1993 &:extend(pre);
1998 &:extend(pre);
1994 border-bottom: @border-thickness solid @border-default-color;
1999 border-bottom: @border-thickness solid @border-default-color;
1995 }
2000 }
1996
2001
1997 .message {
2002 .message {
1998 height: auto;
2003 height: auto;
1999 max-width: 350px;
2004 max-width: 350px;
2000 white-space: normal;
2005 white-space: normal;
2001 text-overflow: initial;
2006 text-overflow: initial;
2002 overflow: visible;
2007 overflow: visible;
2003
2008
2004 .match { background-color: #faffa6;}
2009 .match { background-color: #faffa6;}
2005 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2010 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2006 }
2011 }
2007
2012
2008 }
2013 }
2009
2014
2010 table.rctable td.td-search-results div {
2015 table.rctable td.td-search-results div {
2011 max-width: 100%;
2016 max-width: 100%;
2012 }
2017 }
2013
2018
2014 #tip-box, .tip-box{
2019 #tip-box, .tip-box{
2015 padding: @menupadding/2;
2020 padding: @menupadding/2;
2016 display: block;
2021 display: block;
2017 border: @border-thickness solid @border-highlight-color;
2022 border: @border-thickness solid @border-highlight-color;
2018 .border-radius(@border-radius);
2023 .border-radius(@border-radius);
2019 background-color: white;
2024 background-color: white;
2020 z-index: 99;
2025 z-index: 99;
2021 white-space: pre-wrap;
2026 white-space: pre-wrap;
2022 }
2027 }
2023
2028
2024 #linktt {
2029 #linktt {
2025 width: 79px;
2030 width: 79px;
2026 }
2031 }
2027
2032
2028 #help_kb .modal-content{
2033 #help_kb .modal-content{
2029 max-width: 750px;
2034 max-width: 750px;
2030 margin: 10% auto;
2035 margin: 10% auto;
2031
2036
2032 table{
2037 table{
2033 td,th{
2038 td,th{
2034 border-bottom: none;
2039 border-bottom: none;
2035 line-height: 2.5em;
2040 line-height: 2.5em;
2036 }
2041 }
2037 th{
2042 th{
2038 padding-bottom: @textmargin/2;
2043 padding-bottom: @textmargin/2;
2039 }
2044 }
2040 td.keys{
2045 td.keys{
2041 text-align: center;
2046 text-align: center;
2042 }
2047 }
2043 }
2048 }
2044
2049
2045 .block-left{
2050 .block-left{
2046 width: 45%;
2051 width: 45%;
2047 margin-right: 5%;
2052 margin-right: 5%;
2048 }
2053 }
2049 .modal-footer{
2054 .modal-footer{
2050 clear: both;
2055 clear: both;
2051 }
2056 }
2052 .key.tag{
2057 .key.tag{
2053 padding: 0.5em;
2058 padding: 0.5em;
2054 background-color: @rcblue;
2059 background-color: @rcblue;
2055 color: white;
2060 color: white;
2056 border-color: @rcblue;
2061 border-color: @rcblue;
2057 .box-shadow(none);
2062 .box-shadow(none);
2058 }
2063 }
2059 }
2064 }
2060
2065
2061
2066
2062
2067
2063 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2068 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2064
2069
2065 @import 'statistics-graph';
2070 @import 'statistics-graph';
2066 @import 'tables';
2071 @import 'tables';
2067 @import 'forms';
2072 @import 'forms';
2068 @import 'diff';
2073 @import 'diff';
2069 @import 'summary';
2074 @import 'summary';
2070 @import 'navigation';
2075 @import 'navigation';
2071
2076
2072 //--- SHOW/HIDE SECTIONS --//
2077 //--- SHOW/HIDE SECTIONS --//
2073
2078
2074 .btn-collapse {
2079 .btn-collapse {
2075 float: right;
2080 float: right;
2076 text-align: right;
2081 text-align: right;
2077 font-family: @text-light;
2082 font-family: @text-light;
2078 font-size: @basefontsize;
2083 font-size: @basefontsize;
2079 cursor: pointer;
2084 cursor: pointer;
2080 border: none;
2085 border: none;
2081 color: @rcblue;
2086 color: @rcblue;
2082 }
2087 }
2083
2088
2084 table.rctable,
2089 table.rctable,
2085 table.dataTable {
2090 table.dataTable {
2086 .btn-collapse {
2091 .btn-collapse {
2087 float: right;
2092 float: right;
2088 text-align: right;
2093 text-align: right;
2089 }
2094 }
2090 }
2095 }
2091
2096
2092
2097
2093 // TODO: johbo: Fix for IE10, this avoids that we see a border
2098 // TODO: johbo: Fix for IE10, this avoids that we see a border
2094 // and padding around checkboxes and radio boxes. Move to the right place,
2099 // and padding around checkboxes and radio boxes. Move to the right place,
2095 // or better: Remove this once we did the form refactoring.
2100 // or better: Remove this once we did the form refactoring.
2096 input[type=checkbox],
2101 input[type=checkbox],
2097 input[type=radio] {
2102 input[type=radio] {
2098 padding: 0;
2103 padding: 0;
2099 border: none;
2104 border: none;
2100 }
2105 }
@@ -1,3 +1,4 b''
1 /plugins/__REGISTER__ - launched after the onDomReady() code from rhodecode.js is executed
1 /plugins/__REGISTER__ - launched after the onDomReady() code from rhodecode.js is executed
2 /ui/plugins/code/anchor_focus - launched when rc starts to scroll on load to anchor on PR/Codeview
2 /ui/plugins/code/anchor_focus - launched when rc starts to scroll on load to anchor on PR/Codeview
3 /ui/plugins/code/comment_form_built - launched when injectInlineForm() is executed and the form object is created
3 /ui/plugins/code/comment_form_built - launched when injectInlineForm() is executed and the form object is created
4 /notifications - shows new event notifications No newline at end of file
@@ -1,51 +1,52 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('My account')} ${c.rhodecode_user.username}
5 ${_('My account')} ${c.rhodecode_user.username}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${_('My Account')}
12 ${_('My Account')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_nav()">
15 <%def name="menu_bar_nav()">
16 ${self.menu_items(active='admin')}
16 ${self.menu_items(active='admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 </div>
23 </div>
24
24
25 <div class="sidebar-col-wrapper scw-small">
25 <div class="sidebar-col-wrapper scw-small">
26 ##main
26 ##main
27 <div class="sidebar">
27 <div class="sidebar">
28 <ul class="nav nav-pills nav-stacked">
28 <ul class="nav nav-pills nav-stacked">
29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.url('my_account')}">${_('My Profile')}</a></li>
29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.url('my_account')}">${_('My Profile')}</a></li>
30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.url('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.url('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
32 ## TODO: Find a better integration of oauth views into navigation.
32 ## TODO: Find a better integration of oauth views into navigation.
33 %try:
33 %try:
34 <li class="${'active' if c.active=='oauth' else ''}"><a href="${h.route_path('my_account_oauth')}">${_('OAuth Identities')}</a></li>
34 <li class="${'active' if c.active=='oauth' else ''}"><a href="${h.route_path('my_account_oauth')}">${_('OAuth Identities')}</a></li>
35 %except KeyError:
35 %except KeyError:
36 %endtry
36 %endtry
37 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('my_account_emails')}">${_('My Emails')}</a></li>
37 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('my_account_emails')}">${_('My Emails')}</a></li>
38 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('My Repositories')}</a></li>
38 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('My Repositories')}</a></li>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched')}</a></li>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched')}</a></li>
40 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.url('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
40 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.url('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('My Permissions')}</a></li>
41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('My Permissions')}</a></li>
42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.url('my_account_notifications')}">${_('My Live Notifications')}</a></li>
42 </ul>
43 </ul>
43 </div>
44 </div>
44
45
45 <div class="main-content-full-width">
46 <div class="main-content-full-width">
46 <%include file="/admin/my_account/my_account_${c.active}.html"/>
47 <%include file="/admin/my_account/my_account_${c.active}.html"/>
47 </div>
48 </div>
48 </div>
49 </div>
49 </div>
50 </div>
50
51
51 </%def>
52 </%def>
@@ -1,72 +1,56 b''
1 <%namespace name="base" file="/base/base.html"/>
2
3 <div class="panel panel-default">
1 <div class="panel panel-default">
4 <div class="panel-heading">
2 <div class="panel-heading">
5 <h3 class="panel-title">${_('Account Emails')}</h3>
3 <h3 class="panel-title">${_('Your live notification settings')}</h3>
6 </div>
4 </div>
7
5
8 <div class="panel-body">
6 <div class="panel-body">
9 <div class="emails_wrap">
7
10 <table class="rctable account_emails">
8 <p><strong>IMPORTANT:</strong> This feature requires enabled channelstream websocket server to function correctly.</p>
11 <tr>
9
12 <td class="td-user">
10 <p class="hidden">Status of browser notifications permission: <strong id="browser-notification-status"></strong></p>
13 ${base.gravatar(c.user.email, 16)}
14 <span class="user email">${c.user.email}</span>
15 </td>
16 <td class="td-tags">
17 <span class="tag tag1">${_('Primary')}</span>
18 </td>
19 </tr>
20 %if c.user_email_map:
21 %for em in c.user_email_map:
22 <tr>
23 <td class="td-user">
24 ${base.gravatar(em.email, 16)}
25 <span class="user email">${em.email}</span>
26 </td>
27 <td class="td-action">
28 ${h.secure_form(url('my_account_emails'),method='delete')}
29 ${h.hidden('del_email_id',em.email_id)}
30 <button class="btn btn-link btn-danger" type="submit" id="remove_email_%s" % em.email_id
31 onclick="return confirm('${_('Confirm to delete this email: %s') % em.email}');">
32 ${_('Delete')}
33 </button>
34 ${h.end_form()}
35 </td>
36 </tr>
37 %endfor
38 %else:
39 <tr class="noborder">
40 <td colspan="3">
41 <div class="td-email">
42 ${_('No additional emails specified')}
43 </div>
44 </td>
45 </tr>
46 %endif
47 </table>
48 </div>
49
11
50 <div>
12 ${h.secure_form(url('my_account_notifications_toggle_visibility'), method='post', id='notification-status')}
51 ${h.secure_form(url('my_account_emails'), method='post')}
13 <button class="btn btn-default" type="submit">
52 <div class="form">
14 ${_('Notifications')} <strong>${_('Enabled') if c.rhodecode_user.get_instance().user_data.get('notification_status') else _('Disabled')}</strong>
53 <!-- fields -->
15 </button>
54 <div class="fields">
55 <div class="field">
56 <div class="label">
57 <label for="new_email">${_('New email address')}:</label>
58 </div>
59 <div class="input">
60 ${h.text('new_email', class_='medium')}
61 </div>
62 </div>
63 <div class="buttons">
64 ${h.submit('save',_('Add'),class_="btn")}
65 ${h.reset('reset',_('Reset'),class_="btn")}
66 </div>
67 </div>
68 </div>
69 ${h.end_form()}
16 ${h.end_form()}
70 </div>
17
18 <a class="btn btn-info" id="test-notification">Test notification</a>
19
71 </div>
20 </div>
72 </div>
21 </div>
22
23 <script type="application/javascript">
24
25 function checkBrowserStatus(){
26 var browserStatus = 'Unknown';
27
28 if (!("Notification" in window)) {
29 browserStatus = 'Not supported'
30 }
31 else if(Notification.permission === 'denied'){
32 browserStatus = 'Denied';
33 $('.flash_msg').append('<div class="alert alert-error">Notifications are blocked on browser level - you need to enable them in your browser settings.</div>')
34 }
35 else if(Notification.permission === 'granted'){
36 browserStatus = 'Allowed';
37 }
38
39 $('#browser-notification-status').text(browserStatus);
40 };
41
42 checkBrowserStatus();
43
44 $('#test-notification').on('click', function(e){
45 var levels = ['info', 'error', 'warning', 'success'];
46 var level = levels[Math.floor(Math.random()*levels.length)];
47 var payload = {
48 message: {
49 message: 'This is a test notification.',
50 level: level,
51 testMessage: true
52 }
53 };
54 $.Topic('/notifications').publish(payload);
55 })
56 </script>
@@ -1,14 +1,14 b''
1 <%
1 <%
2 from pyramid.renderers import render as pyramid_render
2 from pyramid.renderers import render as pyramid_render
3 from pyramid.threadlocal import get_current_registry, get_current_request
3 from pyramid.threadlocal import get_current_registry, get_current_request
4 pyramid_registry = get_current_registry()
4 pyramid_registry = get_current_registry()
5 %>
5 %>
6 % for plugin, config in pyramid_registry.rhodecode_plugins.items():
6 % for plugin, config in getattr(pyramid_registry, 'rhodecode_plugins', {}).items():
7 % if config['template_hooks'].get('plugin_init_template'):
7 % if config['template_hooks'].get('plugin_init_template'):
8 ${pyramid_render(config['template_hooks'].get('plugin_init_template'),
8 ${pyramid_render(config['template_hooks'].get('plugin_init_template'),
9 {'config':config}, request=get_current_request(), package='rc_ae')|n}
9 {'config':config}, request=get_current_request(), package='rc_ae')|n}
10 % endif
10 % endif
11 % endfor
11 % endfor
12 <script>
12 <script>
13 $.Topic('/plugins/__REGISTER__').prepareOrPublish({});
13 $.Topic('/plugins/__REGISTER__').prepareOrPublish({});
14 </script>
14 </script>
@@ -1,138 +1,137 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html>
2 <!DOCTYPE html>
3
3
4 <%
4 <%
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
6
6
7 if hasattr(c, 'rhodecode_db_repo'):
7 if hasattr(c, 'rhodecode_db_repo'):
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
10
10
11 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
11 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
12 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
12 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
13 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
13 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
14 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
14
15
15 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
16 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
16 %>
17 %>
17
18
18 <html xmlns="http://www.w3.org/1999/xhtml">
19 <html xmlns="http://www.w3.org/1999/xhtml">
19 <head>
20 <head>
20 <title>${self.title()}</title>
21 <title>${self.title()}</title>
21 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
22 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
22 <%def name="robots()">
23 <%def name="robots()">
23 <meta name="robots" content="index, nofollow"/>
24 <meta name="robots" content="index, nofollow"/>
24 </%def>
25 </%def>
25 ${self.robots()}
26 ${self.robots()}
26 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
27 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
27
28
28 ## CSS definitions
29 ## CSS definitions
29 <%def name="css()">
30 <%def name="css()">
30 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
31 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
31 <!--[if lt IE 9]>
32 <!--[if lt IE 9]>
32 <link rel="stylesheet" type="text/css" href="${h.asset('css/ie.css', ver=c.rhodecode_version_hash)}" media="screen"/>
33 <link rel="stylesheet" type="text/css" href="${h.asset('css/ie.css', ver=c.rhodecode_version_hash)}" media="screen"/>
33 <![endif]-->
34 <![endif]-->
34 ## EXTRA FOR CSS
35 ## EXTRA FOR CSS
35 ${self.css_extra()}
36 ${self.css_extra()}
36 </%def>
37 </%def>
37 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
38 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
38 <%def name="css_extra()">
39 <%def name="css_extra()">
39 </%def>
40 </%def>
40
41
41 ${self.css()}
42 ${self.css()}
42
43
43 ## JAVASCRIPT
44 ## JAVASCRIPT
44 <%def name="js()">
45 <%def name="js()">
45 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
46 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
46 <script type="text/javascript">
47 <script type="text/javascript">
47 // register templateContext to pass template variables to JS
48 // register templateContext to pass template variables to JS
48 var templateContext = ${h.json.dumps(c.template_context)|n};
49 var templateContext = ${h.json.dumps(c.template_context)|n};
49
50
50 var REPO_NAME = "${getattr(c, 'repo_name', '')}";
51 var REPO_NAME = "${getattr(c, 'repo_name', '')}";
51 %if hasattr(c, 'rhodecode_db_repo'):
52 %if hasattr(c, 'rhodecode_db_repo'):
52 var REPO_LANDING_REV = '${c.rhodecode_db_repo.landing_rev[1]}';
53 var REPO_LANDING_REV = '${c.rhodecode_db_repo.landing_rev[1]}';
53 var REPO_TYPE = '${c.rhodecode_db_repo.repo_type}';
54 var REPO_TYPE = '${c.rhodecode_db_repo.repo_type}';
54 %else:
55 %else:
55 var REPO_LANDING_REV = '';
56 var REPO_LANDING_REV = '';
56 var REPO_TYPE = '';
57 var REPO_TYPE = '';
57 %endif
58 %endif
58 var APPLICATION_URL = "${h.url('home').rstrip('/')}";
59 var APPLICATION_URL = "${h.url('home').rstrip('/')}";
59 var ASSET_URL = "${h.asset('')}";
60 var ASSET_URL = "${h.asset('')}";
60 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
61 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
61 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
62 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
62 % if getattr(c, 'rhodecode_user', None):
63 % if getattr(c, 'rhodecode_user', None):
63 var USER = {name:'${c.rhodecode_user.username}'};
64 var USER = {name:'${c.rhodecode_user.username}'};
64 % else:
65 % else:
65 var USER = {name:null};
66 var USER = {name:null};
66 % endif
67 % endif
67
68
68 var APPENLIGHT = {
69 var APPENLIGHT = {
69 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
70 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
70 key: '${getattr(c, "appenlight_api_public_key", "")}',
71 key: '${getattr(c, "appenlight_api_public_key", "")}',
71 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
72 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
72 requestInfo: {
73 requestInfo: {
73 % if getattr(c, 'rhodecode_user', None):
74 % if getattr(c, 'rhodecode_user', None):
74 ip: '${c.rhodecode_user.ip_addr}',
75 ip: '${c.rhodecode_user.ip_addr}',
75 username: '${c.rhodecode_user.username}'
76 username: '${c.rhodecode_user.username}'
76 % endif
77 % endif
77 }
78 }
78 };
79 };
79 </script>
80 </script>
80
81 <!--[if lt IE 9]>
81 <!--[if lt IE 9]>
82 <script language="javascript" type="text/javascript" src="${h.asset('js/excanvas.min.js')}"></script>
82 <script language="javascript" type="text/javascript" src="${h.asset('js/excanvas.min.js')}"></script>
83 <![endif]-->
83 <![endif]-->
84 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
84 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
85 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
85 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
86 ## avoide esaping the %N
86 ## avoide esaping the %N
87 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js'}";</script>
87 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js'}";</script>
88
88
89
89
90 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
90 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
91 ${self.js_extra()}
91 ${self.js_extra()}
92
92
93 <script type="text/javascript">
93 <script type="text/javascript">
94 $(document).ready(function(){
94 $(document).ready(function(){
95 show_more_event();
95 show_more_event();
96 timeagoActivate();
96 timeagoActivate();
97 })
97 })
98 </script>
98 </script>
99
99
100 </%def>
100 </%def>
101
101
102 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
102 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
103 <%def name="js_extra()"></%def>
103 <%def name="js_extra()"></%def>
104 ${self.js()}
104 ${self.js()}
105
105
106 <%def name="head_extra()"></%def>
106 <%def name="head_extra()"></%def>
107 ${self.head_extra()}
107 ${self.head_extra()}
108
109 <%include file="/base/plugins_base.html"/>
108 <%include file="/base/plugins_base.html"/>
110
109
111 ## extra stuff
110 ## extra stuff
112 %if c.pre_code:
111 %if c.pre_code:
113 ${c.pre_code|n}
112 ${c.pre_code|n}
114 %endif
113 %endif
115 </head>
114 </head>
116 <body id="body">
115 <body id="body">
117 <noscript>
116 <noscript>
118 <div class="noscript-error">
117 <div class="noscript-error">
119 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
118 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
120 </div>
119 </div>
121 </noscript>
120 </noscript>
122 ## IE hacks
121 ## IE hacks
123 <!--[if IE 7]>
122 <!--[if IE 7]>
124 <script>$(document.body).addClass('ie7')</script>
123 <script>$(document.body).addClass('ie7')</script>
125 <![endif]-->
124 <![endif]-->
126 <!--[if IE 8]>
125 <!--[if IE 8]>
127 <script>$(document.body).addClass('ie8')</script>
126 <script>$(document.body).addClass('ie8')</script>
128 <![endif]-->
127 <![endif]-->
129 <!--[if IE 9]>
128 <!--[if IE 9]>
130 <script>$(document.body).addClass('ie9')</script>
129 <script>$(document.body).addClass('ie9')</script>
131 <![endif]-->
130 <![endif]-->
132
131
133 ${next.body()}
132 ${next.body()}
134 %if c.post_code:
133 %if c.post_code:
135 ${c.post_code|n}
134 ${c.post_code|n}
136 %endif
135 %endif
137 </body>
136 </body>
138 </html>
137 </html>
General Comments 0
You need to be logged in to leave comments. Login now