Show More
@@ -1,178 +1,179 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2011-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import logging |
|
22 | 22 | |
|
23 | 23 | import deform |
|
24 | 24 | from pyramid.httpexceptions import HTTPFound |
|
25 | 25 | from pyramid.view import view_config |
|
26 | 26 | |
|
27 | 27 | from rhodecode.apps._base import RepoAppView |
|
28 | 28 | from rhodecode.forms import RcForm |
|
29 | 29 | from rhodecode.lib import helpers as h |
|
30 | 30 | from rhodecode.lib import audit_logger |
|
31 | 31 | from rhodecode.lib.auth import ( |
|
32 | 32 | LoginRequired, HasRepoPermissionAnyDecorator, |
|
33 | 33 | HasRepoPermissionAllDecorator, CSRFRequired) |
|
34 | 34 | from rhodecode.model.db import RepositoryField, RepoGroup |
|
35 | 35 | from rhodecode.model.meta import Session |
|
36 | 36 | from rhodecode.model.repo import RepoModel |
|
37 | 37 | from rhodecode.model.scm import RepoGroupList, ScmModel |
|
38 | 38 | from rhodecode.model.validation_schema.schemas import repo_schema |
|
39 | 39 | |
|
40 | 40 | log = logging.getLogger(__name__) |
|
41 | 41 | |
|
42 | 42 | |
|
43 | 43 | class RepoSettingsView(RepoAppView): |
|
44 | 44 | |
|
45 | 45 | def load_default_context(self): |
|
46 | 46 | c = self._get_local_tmpl_context() |
|
47 | 47 | |
|
48 | 48 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead |
|
49 | 49 | c.repo_info = self.db_repo |
|
50 | 50 | |
|
51 | 51 | acl_groups = RepoGroupList( |
|
52 | 52 | RepoGroup.query().all(), |
|
53 | 53 | perm_set=['group.write', 'group.admin']) |
|
54 | 54 | c.repo_groups = RepoGroup.groups_choices(groups=acl_groups) |
|
55 | 55 | c.repo_groups_choices = map(lambda k: k[0], c.repo_groups) |
|
56 | 56 | |
|
57 | 57 | # in case someone no longer have a group.write access to a repository |
|
58 | 58 | # pre fill the list with this entry, we don't care if this is the same |
|
59 | 59 | # but it will allow saving repo data properly. |
|
60 | 60 | repo_group = self.db_repo.group |
|
61 | 61 | if repo_group and repo_group.group_id not in c.repo_groups_choices: |
|
62 | 62 | c.repo_groups_choices.append(repo_group.group_id) |
|
63 | 63 | c.repo_groups.append(RepoGroup._generate_choice(repo_group)) |
|
64 | 64 | |
|
65 | 65 | if c.repository_requirements_missing or self.rhodecode_vcs_repo is None: |
|
66 | 66 | # we might be in missing requirement state, so we load things |
|
67 | 67 | # without touching scm_instance() |
|
68 | 68 | c.landing_revs_choices, c.landing_revs = \ |
|
69 | 69 | ScmModel().get_repo_landing_revs() |
|
70 | 70 | else: |
|
71 | 71 | c.landing_revs_choices, c.landing_revs = \ |
|
72 | 72 | ScmModel().get_repo_landing_revs(self.db_repo) |
|
73 | 73 | |
|
74 | 74 | c.personal_repo_group = c.auth_user.personal_repo_group |
|
75 | 75 | c.repo_fields = RepositoryField.query()\ |
|
76 | 76 | .filter(RepositoryField.repository == self.db_repo).all() |
|
77 | 77 | |
|
78 | 78 | self._register_global_c(c) |
|
79 | 79 | return c |
|
80 | 80 | |
|
81 | 81 | def _get_schema(self, c, old_values=None): |
|
82 | 82 | return repo_schema.RepoSettingsSchema().bind( |
|
83 | repo_type=self.db_repo.repo_type, | |
|
83 | 84 | repo_type_options=[self.db_repo.repo_type], |
|
84 | 85 | repo_ref_options=c.landing_revs_choices, |
|
85 | 86 | repo_ref_items=c.landing_revs, |
|
86 | 87 | repo_repo_group_options=c.repo_groups_choices, |
|
87 | 88 | repo_repo_group_items=c.repo_groups, |
|
88 | 89 | # user caller |
|
89 | 90 | user=self._rhodecode_user, |
|
90 | 91 | old_values=old_values |
|
91 | 92 | ) |
|
92 | 93 | |
|
93 | 94 | @LoginRequired() |
|
94 | 95 | @HasRepoPermissionAnyDecorator('repository.admin') |
|
95 | 96 | @view_config( |
|
96 | 97 | route_name='edit_repo', request_method='GET', |
|
97 | 98 | renderer='rhodecode:templates/admin/repos/repo_edit.mako') |
|
98 | 99 | def edit_settings(self): |
|
99 | 100 | c = self.load_default_context() |
|
100 | 101 | c.active = 'settings' |
|
101 | 102 | |
|
102 | 103 | defaults = RepoModel()._get_defaults(self.db_repo_name) |
|
103 | 104 | defaults['repo_owner'] = defaults['user'] |
|
104 | 105 | defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev'] |
|
105 | 106 | |
|
106 | 107 | schema = self._get_schema(c) |
|
107 | 108 | c.form = RcForm(schema, appstruct=defaults) |
|
108 | 109 | return self._get_template_context(c) |
|
109 | 110 | |
|
110 | 111 | @LoginRequired() |
|
111 | 112 | @HasRepoPermissionAllDecorator('repository.admin') |
|
112 | 113 | @CSRFRequired() |
|
113 | 114 | @view_config( |
|
114 | 115 | route_name='edit_repo', request_method='POST', |
|
115 | 116 | renderer='rhodecode:templates/admin/repos/repo_edit.mako') |
|
116 | 117 | def edit_settings_update(self): |
|
117 | 118 | _ = self.request.translate |
|
118 | 119 | c = self.load_default_context() |
|
119 | 120 | c.active = 'settings' |
|
120 | 121 | old_repo_name = self.db_repo_name |
|
121 | 122 | |
|
122 | 123 | old_values = self.db_repo.get_api_data() |
|
123 | 124 | schema = self._get_schema(c, old_values=old_values) |
|
124 | 125 | |
|
125 | 126 | c.form = RcForm(schema) |
|
126 | 127 | pstruct = self.request.POST.items() |
|
127 | 128 | pstruct.append(('repo_type', self.db_repo.repo_type)) |
|
128 | 129 | try: |
|
129 | 130 | schema_data = c.form.validate(pstruct) |
|
130 | 131 | except deform.ValidationFailure as err_form: |
|
131 | 132 | return self._get_template_context(c) |
|
132 | 133 | |
|
133 | 134 | # data is now VALID, proceed with updates |
|
134 | 135 | # save validated data back into the updates dict |
|
135 | 136 | validated_updates = dict( |
|
136 | 137 | repo_name=schema_data['repo_group']['repo_name_without_group'], |
|
137 | 138 | repo_group=schema_data['repo_group']['repo_group_id'], |
|
138 | 139 | |
|
139 | 140 | user=schema_data['repo_owner'], |
|
140 | 141 | repo_description=schema_data['repo_description'], |
|
141 | 142 | repo_private=schema_data['repo_private'], |
|
142 | 143 | clone_uri=schema_data['repo_clone_uri'], |
|
143 | 144 | repo_landing_rev=schema_data['repo_landing_commit_ref'], |
|
144 | 145 | repo_enable_statistics=schema_data['repo_enable_statistics'], |
|
145 | 146 | repo_enable_locking=schema_data['repo_enable_locking'], |
|
146 | 147 | repo_enable_downloads=schema_data['repo_enable_downloads'], |
|
147 | 148 | ) |
|
148 | 149 | # detect if CLONE URI changed, if we get OLD means we keep old values |
|
149 | 150 | if schema_data['repo_clone_uri_change'] == 'OLD': |
|
150 | 151 | validated_updates['clone_uri'] = self.db_repo.clone_uri |
|
151 | 152 | |
|
152 | 153 | # use the new full name for redirect |
|
153 | 154 | new_repo_name = schema_data['repo_group']['repo_name_with_group'] |
|
154 | 155 | |
|
155 | 156 | # save extra fields into our validated data |
|
156 | 157 | for key, value in pstruct: |
|
157 | 158 | if key.startswith(RepositoryField.PREFIX): |
|
158 | 159 | validated_updates[key] = value |
|
159 | 160 | |
|
160 | 161 | try: |
|
161 | 162 | RepoModel().update(self.db_repo, **validated_updates) |
|
162 | 163 | ScmModel().mark_for_invalidation(new_repo_name) |
|
163 | 164 | |
|
164 | 165 | audit_logger.store( |
|
165 | 166 | 'repo.edit', action_data={'old_data': old_values}, |
|
166 | 167 | user=self._rhodecode_user, repo=self.db_repo) |
|
167 | 168 | |
|
168 | 169 | Session().commit() |
|
169 | 170 | |
|
170 | 171 | h.flash(_('Repository {} updated successfully').format( |
|
171 | 172 | old_repo_name), category='success') |
|
172 | 173 | except Exception: |
|
173 | 174 | log.exception("Exception during update of repository") |
|
174 | 175 | h.flash(_('Error occurred during update of repository {}').format( |
|
175 | 176 | old_repo_name), category='error') |
|
176 | 177 | |
|
177 | 178 | raise HTTPFound( |
|
178 | 179 | self.request.route_path('edit_repo', repo_name=new_repo_name)) |
@@ -1,360 +1,414 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import colander |
|
22 | 22 | import deform.widget |
|
23 | 23 | |
|
24 | 24 | from rhodecode.translation import _ |
|
25 | 25 | from rhodecode.model.validation_schema.utils import convert_to_optgroup |
|
26 | 26 | from rhodecode.model.validation_schema import validators, preparers, types |
|
27 | 27 | |
|
28 | 28 | DEFAULT_LANDING_REF = 'rev:tip' |
|
29 | 29 | |
|
30 | 30 | |
|
31 | 31 | def get_group_and_repo(repo_name): |
|
32 | 32 | from rhodecode.model.repo_group import RepoGroupModel |
|
33 | 33 | return RepoGroupModel()._get_group_name_and_parent( |
|
34 | 34 | repo_name, get_object=True) |
|
35 | 35 | |
|
36 | 36 | |
|
37 | 37 | def get_repo_group(repo_group_id): |
|
38 | 38 | from rhodecode.model.repo_group import RepoGroup |
|
39 | 39 | return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR |
|
40 | 40 | |
|
41 | 41 | |
|
42 | 42 | @colander.deferred |
|
43 | 43 | def deferred_repo_type_validator(node, kw): |
|
44 | 44 | options = kw.get('repo_type_options', []) |
|
45 | 45 | return colander.OneOf([x for x in options]) |
|
46 | 46 | |
|
47 | 47 | |
|
48 | 48 | @colander.deferred |
|
49 | 49 | def deferred_repo_owner_validator(node, kw): |
|
50 | 50 | |
|
51 | 51 | def repo_owner_validator(node, value): |
|
52 | 52 | from rhodecode.model.db import User |
|
53 | 53 | existing = User.get_by_username(value) |
|
54 | 54 | if not existing: |
|
55 | 55 | msg = _(u'Repo owner with id `{}` does not exists').format(value) |
|
56 | 56 | raise colander.Invalid(node, msg) |
|
57 | 57 | |
|
58 | 58 | return repo_owner_validator |
|
59 | 59 | |
|
60 | 60 | |
|
61 | 61 | @colander.deferred |
|
62 | 62 | def deferred_landing_ref_validator(node, kw): |
|
63 | 63 | options = kw.get( |
|
64 | 64 | 'repo_ref_options', [DEFAULT_LANDING_REF]) |
|
65 | 65 | return colander.OneOf([x for x in options]) |
|
66 | 66 | |
|
67 | 67 | |
|
68 | 68 | @colander.deferred |
|
69 | def deferred_clone_uri_validator(node, kw): | |
|
70 | repo_type = kw.get('repo_type') | |
|
71 | validator = validators.CloneUriValidator(repo_type) | |
|
72 | return validator | |
|
73 | ||
|
74 | ||
|
75 | @colander.deferred | |
|
69 | 76 | def deferred_landing_ref_widget(node, kw): |
|
70 | 77 | items = kw.get( |
|
71 | 78 | 'repo_ref_items', [(DEFAULT_LANDING_REF, DEFAULT_LANDING_REF)]) |
|
72 | 79 | items = convert_to_optgroup(items) |
|
73 | 80 | return deform.widget.Select2Widget(values=items) |
|
74 | 81 | |
|
75 | 82 | |
|
76 | 83 | @colander.deferred |
|
77 | 84 | def deferred_fork_of_validator(node, kw): |
|
78 | 85 | old_values = kw.get('old_values') or {} |
|
79 | 86 | |
|
80 | 87 | def fork_of_validator(node, value): |
|
81 | 88 | from rhodecode.model.db import Repository, RepoGroup |
|
82 | 89 | existing = Repository.get_by_repo_name(value) |
|
83 | 90 | if not existing: |
|
84 | 91 | msg = _(u'Fork with id `{}` does not exists').format(value) |
|
85 | 92 | raise colander.Invalid(node, msg) |
|
86 | 93 | elif old_values['repo_name'] == existing.repo_name: |
|
87 | 94 | msg = _(u'Cannot set fork of ' |
|
88 | 95 | u'parameter of this repository to itself').format(value) |
|
89 | 96 | raise colander.Invalid(node, msg) |
|
90 | 97 | |
|
91 | 98 | return fork_of_validator |
|
92 | 99 | |
|
93 | 100 | |
|
94 | 101 | @colander.deferred |
|
95 | 102 | def deferred_can_write_to_group_validator(node, kw): |
|
96 | 103 | request_user = kw.get('user') |
|
97 | 104 | old_values = kw.get('old_values') or {} |
|
98 | 105 | |
|
99 | 106 | def can_write_to_group_validator(node, value): |
|
100 | 107 | """ |
|
101 | 108 | Checks if given repo path is writable by user. This includes checks if |
|
102 | 109 | user is allowed to create repositories under root path or under |
|
103 | 110 | repo group paths |
|
104 | 111 | """ |
|
105 | 112 | |
|
106 | 113 | from rhodecode.lib.auth import ( |
|
107 | 114 | HasPermissionAny, HasRepoGroupPermissionAny) |
|
108 | 115 | from rhodecode.model.repo_group import RepoGroupModel |
|
109 | 116 | |
|
110 | 117 | messages = { |
|
111 | 118 | 'invalid_repo_group': |
|
112 | 119 | _(u"Repository group `{}` does not exist"), |
|
113 | 120 | # permissions denied we expose as not existing, to prevent |
|
114 | 121 | # resource discovery |
|
115 | 122 | 'permission_denied': |
|
116 | 123 | _(u"Repository group `{}` does not exist"), |
|
117 | 124 | 'permission_denied_root': |
|
118 | 125 | _(u"You do not have the permission to store " |
|
119 | 126 | u"repositories in the root location.") |
|
120 | 127 | } |
|
121 | 128 | |
|
122 | 129 | value = value['repo_group_name'] |
|
123 | 130 | |
|
124 | 131 | is_root_location = value is types.RootLocation |
|
125 | 132 | # NOT initialized validators, we must call them |
|
126 | 133 | can_create_repos_at_root = HasPermissionAny( |
|
127 | 134 | 'hg.admin', 'hg.create.repository') |
|
128 | 135 | |
|
129 | 136 | # if values is root location, we simply need to check if we can write |
|
130 | 137 | # to root location ! |
|
131 | 138 | if is_root_location: |
|
132 | 139 | if can_create_repos_at_root(user=request_user): |
|
133 | 140 | # we can create repo group inside tool-level. No more checks |
|
134 | 141 | # are required |
|
135 | 142 | return |
|
136 | 143 | else: |
|
137 | 144 | # "fake" node name as repo_name, otherwise we oddly report |
|
138 | 145 | # the error as if it was coming form repo_group |
|
139 | 146 | # however repo_group is empty when using root location. |
|
140 | 147 | node.name = 'repo_name' |
|
141 | 148 | raise colander.Invalid(node, messages['permission_denied_root']) |
|
142 | 149 | |
|
143 | 150 | # parent group not exists ? throw an error |
|
144 | 151 | repo_group = RepoGroupModel().get_by_group_name(value) |
|
145 | 152 | if value and not repo_group: |
|
146 | 153 | raise colander.Invalid( |
|
147 | 154 | node, messages['invalid_repo_group'].format(value)) |
|
148 | 155 | |
|
149 | 156 | gr_name = repo_group.group_name |
|
150 | 157 | |
|
151 | 158 | # create repositories with write permission on group is set to true |
|
152 | 159 | create_on_write = HasPermissionAny( |
|
153 | 160 | 'hg.create.write_on_repogroup.true')(user=request_user) |
|
154 | 161 | |
|
155 | 162 | group_admin = HasRepoGroupPermissionAny('group.admin')( |
|
156 | 163 | gr_name, 'can write into group validator', user=request_user) |
|
157 | 164 | group_write = HasRepoGroupPermissionAny('group.write')( |
|
158 | 165 | gr_name, 'can write into group validator', user=request_user) |
|
159 | 166 | |
|
160 | 167 | forbidden = not (group_admin or (group_write and create_on_write)) |
|
161 | 168 | |
|
162 | 169 | # TODO: handling of old values, and detecting no-change in path |
|
163 | 170 | # to skip permission checks in such cases. This only needs to be |
|
164 | 171 | # implemented if we use this schema in forms as well |
|
165 | 172 | |
|
166 | 173 | # gid = (old_data['repo_group'].get('group_id') |
|
167 | 174 | # if (old_data and 'repo_group' in old_data) else None) |
|
168 | 175 | # value_changed = gid != safe_int(value) |
|
169 | 176 | # new = not old_data |
|
170 | 177 | |
|
171 | 178 | # do check if we changed the value, there's a case that someone got |
|
172 | 179 | # revoked write permissions to a repository, he still created, we |
|
173 | 180 | # don't need to check permission if he didn't change the value of |
|
174 | 181 | # groups in form box |
|
175 | 182 | # if value_changed or new: |
|
176 | 183 | # # parent group need to be existing |
|
177 | 184 | # TODO: ENDS HERE |
|
178 | 185 | |
|
179 | 186 | if repo_group and forbidden: |
|
180 | 187 | msg = messages['permission_denied'].format(value) |
|
181 | 188 | raise colander.Invalid(node, msg) |
|
182 | 189 | |
|
183 | 190 | return can_write_to_group_validator |
|
184 | 191 | |
|
185 | 192 | |
|
186 | 193 | @colander.deferred |
|
187 | 194 | def deferred_unique_name_validator(node, kw): |
|
188 | 195 | request_user = kw.get('user') |
|
189 | 196 | old_values = kw.get('old_values') or {} |
|
190 | 197 | |
|
191 | 198 | def unique_name_validator(node, value): |
|
192 | 199 | from rhodecode.model.db import Repository, RepoGroup |
|
193 | 200 | name_changed = value != old_values.get('repo_name') |
|
194 | 201 | |
|
195 | 202 | existing = Repository.get_by_repo_name(value) |
|
196 | 203 | if name_changed and existing: |
|
197 | 204 | msg = _(u'Repository with name `{}` already exists').format(value) |
|
198 | 205 | raise colander.Invalid(node, msg) |
|
199 | 206 | |
|
200 | 207 | existing_group = RepoGroup.get_by_group_name(value) |
|
201 | 208 | if name_changed and existing_group: |
|
202 | 209 | msg = _(u'Repository group with name `{}` already exists').format( |
|
203 | 210 | value) |
|
204 | 211 | raise colander.Invalid(node, msg) |
|
205 | 212 | return unique_name_validator |
|
206 | 213 | |
|
207 | 214 | |
|
208 | 215 | @colander.deferred |
|
209 | 216 | def deferred_repo_name_validator(node, kw): |
|
210 | 217 | def no_git_suffix_validator(node, value): |
|
211 | 218 | if value.endswith('.git'): |
|
212 | 219 | msg = _('Repository name cannot end with .git') |
|
213 | 220 | raise colander.Invalid(node, msg) |
|
214 | 221 | return colander.All( |
|
215 | 222 | no_git_suffix_validator, validators.valid_name_validator) |
|
216 | 223 | |
|
217 | 224 | |
|
218 | 225 | @colander.deferred |
|
219 | 226 | def deferred_repo_group_validator(node, kw): |
|
220 | 227 | options = kw.get( |
|
221 | 228 | 'repo_repo_group_options') |
|
222 | 229 | return colander.OneOf([x for x in options]) |
|
223 | 230 | |
|
224 | 231 | |
|
225 | 232 | @colander.deferred |
|
226 | 233 | def deferred_repo_group_widget(node, kw): |
|
227 | 234 | items = kw.get('repo_repo_group_items') |
|
228 | 235 | return deform.widget.Select2Widget(values=items) |
|
229 | 236 | |
|
230 | 237 | |
|
231 | 238 | class GroupType(colander.Mapping): |
|
232 | 239 | def _validate(self, node, value): |
|
233 | 240 | try: |
|
234 | 241 | return dict(repo_group_name=value) |
|
235 | 242 | except Exception as e: |
|
236 | 243 | raise colander.Invalid( |
|
237 | 244 | node, '"${val}" is not a mapping type: ${err}'.format( |
|
238 | 245 | val=value, err=e)) |
|
239 | 246 | |
|
240 | 247 | def deserialize(self, node, cstruct): |
|
241 | 248 | if cstruct is colander.null: |
|
242 | 249 | return cstruct |
|
243 | 250 | |
|
244 | 251 | appstruct = super(GroupType, self).deserialize(node, cstruct) |
|
245 | 252 | validated_name = appstruct['repo_group_name'] |
|
246 | 253 | |
|
247 | 254 | # inject group based on once deserialized data |
|
248 | 255 | (repo_name_without_group, |
|
249 | 256 | parent_group_name, |
|
250 | 257 | parent_group) = get_group_and_repo(validated_name) |
|
251 | 258 | |
|
252 | 259 | appstruct['repo_name_with_group'] = validated_name |
|
253 | 260 | appstruct['repo_name_without_group'] = repo_name_without_group |
|
254 | 261 | appstruct['repo_group_name'] = parent_group_name or types.RootLocation |
|
255 | 262 | |
|
256 | 263 | if parent_group: |
|
257 | 264 | appstruct['repo_group_id'] = parent_group.group_id |
|
258 | 265 | |
|
259 | 266 | return appstruct |
|
260 | 267 | |
|
261 | 268 | |
|
262 | 269 | class GroupSchema(colander.SchemaNode): |
|
263 | 270 | schema_type = GroupType |
|
264 | 271 | validator = deferred_can_write_to_group_validator |
|
265 | 272 | missing = colander.null |
|
266 | 273 | |
|
267 | 274 | |
|
268 | 275 | class RepoGroup(GroupSchema): |
|
269 | 276 | repo_group_name = colander.SchemaNode( |
|
270 | 277 | types.GroupNameType()) |
|
271 | 278 | repo_group_id = colander.SchemaNode( |
|
272 | 279 | colander.String(), missing=None) |
|
273 | 280 | repo_name_without_group = colander.SchemaNode( |
|
274 | 281 | colander.String(), missing=None) |
|
275 | 282 | |
|
276 | 283 | |
|
277 | 284 | class RepoGroupAccessSchema(colander.MappingSchema): |
|
278 | 285 | repo_group = RepoGroup() |
|
279 | 286 | |
|
280 | 287 | |
|
281 | 288 | class RepoNameUniqueSchema(colander.MappingSchema): |
|
282 | 289 | unique_repo_name = colander.SchemaNode( |
|
283 | 290 | colander.String(), |
|
284 | 291 | validator=deferred_unique_name_validator) |
|
285 | 292 | |
|
286 | 293 | |
|
287 | 294 | class RepoSchema(colander.MappingSchema): |
|
288 | 295 | |
|
289 | 296 | repo_name = colander.SchemaNode( |
|
290 | 297 | types.RepoNameType(), |
|
291 | 298 | validator=deferred_repo_name_validator) |
|
292 | 299 | |
|
293 | 300 | repo_type = colander.SchemaNode( |
|
294 | 301 | colander.String(), |
|
295 | 302 | validator=deferred_repo_type_validator) |
|
296 | 303 | |
|
297 | 304 | repo_owner = colander.SchemaNode( |
|
298 | 305 | colander.String(), |
|
299 | 306 | validator=deferred_repo_owner_validator, |
|
300 | 307 | widget=deform.widget.TextInputWidget()) |
|
301 | 308 | |
|
302 | 309 | repo_description = colander.SchemaNode( |
|
303 | 310 | colander.String(), missing='', |
|
304 | 311 | widget=deform.widget.TextAreaWidget()) |
|
305 | 312 | |
|
306 | 313 | repo_landing_commit_ref = colander.SchemaNode( |
|
307 | 314 | colander.String(), |
|
308 | 315 | validator=deferred_landing_ref_validator, |
|
309 | 316 | preparers=[preparers.strip_preparer], |
|
310 | 317 | missing=DEFAULT_LANDING_REF, |
|
311 | 318 | widget=deferred_landing_ref_widget) |
|
312 | 319 | |
|
313 | 320 | repo_clone_uri = colander.SchemaNode( |
|
314 | 321 | colander.String(), |
|
315 | 322 | validator=colander.All(colander.Length(min=1)), |
|
316 | 323 | preparers=[preparers.strip_preparer], |
|
317 | 324 | missing='') |
|
318 | 325 | |
|
319 | 326 | repo_fork_of = colander.SchemaNode( |
|
320 | 327 | colander.String(), |
|
321 | 328 | validator=deferred_fork_of_validator, |
|
322 | 329 | missing=None) |
|
323 | 330 | |
|
324 | 331 | repo_private = colander.SchemaNode( |
|
325 | 332 | types.StringBooleanType(), |
|
326 | 333 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
327 | 334 | repo_copy_permissions = colander.SchemaNode( |
|
328 | 335 | types.StringBooleanType(), |
|
329 | 336 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
330 | 337 | repo_enable_statistics = colander.SchemaNode( |
|
331 | 338 | types.StringBooleanType(), |
|
332 | 339 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
333 | 340 | repo_enable_downloads = colander.SchemaNode( |
|
334 | 341 | types.StringBooleanType(), |
|
335 | 342 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
336 | 343 | repo_enable_locking = colander.SchemaNode( |
|
337 | 344 | types.StringBooleanType(), |
|
338 | 345 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
339 | 346 | |
|
340 | 347 | def deserialize(self, cstruct): |
|
341 | 348 | """ |
|
342 | 349 | Custom deserialize that allows to chain validation, and verify |
|
343 | 350 | permissions, and as last step uniqueness |
|
344 | 351 | """ |
|
345 | 352 | |
|
346 | 353 | # first pass, to validate given data |
|
347 | 354 | appstruct = super(RepoSchema, self).deserialize(cstruct) |
|
348 | 355 | validated_name = appstruct['repo_name'] |
|
349 | 356 | |
|
350 | 357 | # second pass to validate permissions to repo_group |
|
351 | 358 | second = RepoGroupAccessSchema().bind(**self.bindings) |
|
352 | 359 | appstruct_second = second.deserialize({'repo_group': validated_name}) |
|
353 | 360 | # save result |
|
354 | 361 | appstruct['repo_group'] = appstruct_second['repo_group'] |
|
355 | 362 | |
|
356 | 363 | # thirds to validate uniqueness |
|
357 | 364 | third = RepoNameUniqueSchema().bind(**self.bindings) |
|
358 | 365 | third.deserialize({'unique_repo_name': validated_name}) |
|
359 | 366 | |
|
360 | 367 | return appstruct |
|
368 | ||
|
369 | ||
|
370 | class RepoSettingsSchema(RepoSchema): | |
|
371 | repo_group = colander.SchemaNode( | |
|
372 | colander.Integer(), | |
|
373 | validator=deferred_repo_group_validator, | |
|
374 | widget=deferred_repo_group_widget, | |
|
375 | missing='') | |
|
376 | ||
|
377 | repo_clone_uri_change = colander.SchemaNode( | |
|
378 | colander.String(), | |
|
379 | missing='NEW') | |
|
380 | ||
|
381 | repo_clone_uri = colander.SchemaNode( | |
|
382 | colander.String(), | |
|
383 | preparers=[preparers.strip_preparer], | |
|
384 | validator=deferred_clone_uri_validator, | |
|
385 | missing='') | |
|
386 | ||
|
387 | def deserialize(self, cstruct): | |
|
388 | """ | |
|
389 | Custom deserialize that allows to chain validation, and verify | |
|
390 | permissions, and as last step uniqueness | |
|
391 | """ | |
|
392 | ||
|
393 | # first pass, to validate given data | |
|
394 | appstruct = super(RepoSchema, self).deserialize(cstruct) | |
|
395 | validated_name = appstruct['repo_name'] | |
|
396 | # because of repoSchema adds repo-group as an ID, we inject it as | |
|
397 | # full name here because validators require it, it's unwrapped later | |
|
398 | # so it's safe to use and final name is going to be without group anyway | |
|
399 | ||
|
400 | group, separator = get_repo_group(appstruct['repo_group']) | |
|
401 | if group: | |
|
402 | validated_name = separator.join([group.group_name, validated_name]) | |
|
403 | ||
|
404 | # second pass to validate permissions to repo_group | |
|
405 | second = RepoGroupAccessSchema().bind(**self.bindings) | |
|
406 | appstruct_second = second.deserialize({'repo_group': validated_name}) | |
|
407 | # save result | |
|
408 | appstruct['repo_group'] = appstruct_second['repo_group'] | |
|
409 | ||
|
410 | # thirds to validate uniqueness | |
|
411 | third = RepoNameUniqueSchema().bind(**self.bindings) | |
|
412 | third.deserialize({'unique_repo_name': validated_name}) | |
|
413 | ||
|
414 | return appstruct |
@@ -1,48 +1,140 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2011-2017 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 | ||
|
1 | 21 | import os |
|
2 | 22 | import re |
|
23 | import logging | |
|
24 | ||
|
3 | 25 | |
|
4 | 26 | import ipaddress |
|
5 | 27 | import colander |
|
6 | 28 | |
|
7 | 29 | from rhodecode.translation import _ |
|
8 | 30 | from rhodecode.lib.utils2 import glob2re |
|
9 | 31 | |
|
32 | log = logging.getLogger(__name__) | |
|
33 | ||
|
10 | 34 | |
|
11 | 35 | def ip_addr_validator(node, value): |
|
12 | 36 | try: |
|
13 | 37 | # this raises an ValueError if address is not IpV4 or IpV6 |
|
14 | 38 | ipaddress.ip_network(value, strict=False) |
|
15 | 39 | except ValueError: |
|
16 | 40 | msg = _(u'Please enter a valid IPv4 or IpV6 address') |
|
17 | 41 | raise colander.Invalid(node, msg) |
|
18 | 42 | |
|
19 | 43 | |
|
20 | 44 | class IpAddrValidator(object): |
|
21 | 45 | def __init__(self, strict=True): |
|
22 | 46 | self.strict = strict |
|
23 | 47 | |
|
24 | 48 | def __call__(self, node, value): |
|
25 | 49 | try: |
|
26 | 50 | # this raises an ValueError if address is not IpV4 or IpV6 |
|
27 | 51 | ipaddress.ip_network(value, strict=self.strict) |
|
28 | 52 | except ValueError: |
|
29 | 53 | msg = _(u'Please enter a valid IPv4 or IpV6 address') |
|
30 | 54 | raise colander.Invalid(node, msg) |
|
31 | 55 | |
|
32 | 56 | |
|
33 | 57 | def glob_validator(node, value): |
|
34 | 58 | try: |
|
35 | 59 | re.compile('^' + glob2re(value) + '$') |
|
36 | 60 | except Exception: |
|
37 | 61 | msg = _(u'Invalid glob pattern') |
|
38 | 62 | raise colander.Invalid(node, msg) |
|
39 | 63 | |
|
40 | 64 | |
|
41 | 65 | def valid_name_validator(node, value): |
|
42 | 66 | from rhodecode.model.validation_schema import types |
|
43 | 67 | if value is types.RootLocation: |
|
44 | 68 | return |
|
45 | 69 | |
|
46 | 70 | msg = _('Name must start with a letter or number. Got `{}`').format(value) |
|
47 | 71 | if not re.match(r'^[a-zA-z0-9]{1,}', value): |
|
48 | 72 | raise colander.Invalid(node, msg) |
|
73 | ||
|
74 | ||
|
75 | class InvalidCloneUrl(Exception): | |
|
76 | allowed_prefixes = () | |
|
77 | ||
|
78 | ||
|
79 | def url_validator(url, repo_type, config): | |
|
80 | from rhodecode.lib.vcs.backends.hg import MercurialRepository | |
|
81 | from rhodecode.lib.vcs.backends.git import GitRepository | |
|
82 | from rhodecode.lib.vcs.backends.svn import SubversionRepository | |
|
83 | ||
|
84 | if repo_type == 'hg': | |
|
85 | allowed_prefixes = ('http', 'svn+http', 'git+http') | |
|
86 | ||
|
87 | if 'http' in url[:4]: | |
|
88 | # initially check if it's at least the proper URL | |
|
89 | # or does it pass basic auth | |
|
90 | ||
|
91 | MercurialRepository.check_url(url, config) | |
|
92 | elif 'svn+http' in url[:8]: # svn->hg import | |
|
93 | SubversionRepository.check_url(url, config) | |
|
94 | elif 'git+http' in url[:8]: # git->hg import | |
|
95 | raise NotImplementedError() | |
|
96 | else: | |
|
97 | exc = InvalidCloneUrl('Clone from URI %s not allowed. ' | |
|
98 | 'Allowed url must start with one of %s' | |
|
99 | % (url, ','.join(allowed_prefixes))) | |
|
100 | exc.allowed_prefixes = allowed_prefixes | |
|
101 | raise exc | |
|
102 | ||
|
103 | elif repo_type == 'git': | |
|
104 | allowed_prefixes = ('http', 'svn+http', 'hg+http') | |
|
105 | if 'http' in url[:4]: | |
|
106 | # initially check if it's at least the proper URL | |
|
107 | # or does it pass basic auth | |
|
108 | GitRepository.check_url(url, config) | |
|
109 | elif 'svn+http' in url[:8]: # svn->git import | |
|
110 | raise NotImplementedError() | |
|
111 | elif 'hg+http' in url[:8]: # hg->git import | |
|
112 | raise NotImplementedError() | |
|
113 | else: | |
|
114 | exc = InvalidCloneUrl('Clone from URI %s not allowed. ' | |
|
115 | 'Allowed url must start with one of %s' | |
|
116 | % (url, ','.join(allowed_prefixes))) | |
|
117 | exc.allowed_prefixes = allowed_prefixes | |
|
118 | raise exc | |
|
119 | ||
|
120 | ||
|
121 | class CloneUriValidator(object): | |
|
122 | def __init__(self, repo_type): | |
|
123 | self.repo_type = repo_type | |
|
124 | ||
|
125 | def __call__(self, node, value): | |
|
126 | from rhodecode.lib.utils import make_db_config | |
|
127 | try: | |
|
128 | config = make_db_config(clear_session=False) | |
|
129 | url_validator(value, self.repo_type, config) | |
|
130 | except InvalidCloneUrl as e: | |
|
131 | log.warning(e) | |
|
132 | msg = _(u'Invalid clone url, provide a valid clone ' | |
|
133 | u'url starting with one of {allowed_prefixes}').format( | |
|
134 | allowed_prefixes=e.allowed_prefixes) | |
|
135 | raise colander.Invalid(node, msg) | |
|
136 | except Exception: | |
|
137 | log.exception('Url validation failed') | |
|
138 | msg = _(u'invalid clone url for {repo_type} repository').format( | |
|
139 | repo_type=self.repo_type) | |
|
140 | raise colander.Invalid(node, msg) |
@@ -1,249 +1,260 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%namespace name="base" file="/base/base.mako"/> |
|
3 | 3 | |
|
4 | 4 | <div class="panel panel-default"> |
|
5 | 5 | <div class="panel-heading"> |
|
6 | 6 | <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3> |
|
7 | 7 | </div> |
|
8 | 8 | <div class="panel-body"> |
|
9 | 9 | ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST')} |
|
10 | 10 | <div class="form"> |
|
11 | 11 | <!-- fields --> |
|
12 | 12 | <div class="fields"> |
|
13 | 13 | |
|
14 | 14 | <div class="field"> |
|
15 | 15 | <div class="label"> |
|
16 | 16 | <label for="repo_name">${_('Name')}:</label> |
|
17 | 17 | </div> |
|
18 | 18 | <div class="input"> |
|
19 | 19 | ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n} |
|
20 | 20 | ${c.form.render_error(request, c.form['repo_name'])|n} |
|
21 | 21 | |
|
22 | 22 | <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p> |
|
23 | 23 | <p id="clone_id" style="display:none;"> |
|
24 | 24 | ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/> |
|
25 | 25 | ${_('''In case this repository is renamed or moved into another group the repository url changes. |
|
26 | 26 | Using above url guarantees that this repository will always be accessible under such url. |
|
27 | 27 | Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p> |
|
28 | 28 | </div> |
|
29 | 29 | </div> |
|
30 | 30 | |
|
31 | 31 | <div class="field"> |
|
32 | 32 | <div class="label"> |
|
33 | 33 | <label for="repo_group">${_('Repository group')}:</label> |
|
34 | 34 | </div> |
|
35 | 35 | <div class="select"> |
|
36 | 36 | ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n} |
|
37 | 37 | ${c.form.render_error(request, c.form['repo_group'])|n} |
|
38 | 38 | |
|
39 | 39 | % if c.personal_repo_group: |
|
40 | 40 | <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false"> |
|
41 | 41 | ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}} |
|
42 | 42 | </a> |
|
43 | 43 | % endif |
|
44 | 44 | <p class="help-block">${_('Optional select a group to put this repository into.')}</p> |
|
45 | 45 | </div> |
|
46 | 46 | </div> |
|
47 | 47 | |
|
48 | 48 | % if c.rhodecode_db_repo.repo_type != 'svn': |
|
49 | 49 | <div class="field"> |
|
50 | 50 | <div class="label"> |
|
51 | 51 | <label for="clone_uri">${_('Remote uri')}:</label> |
|
52 | 52 | </div> |
|
53 | 53 | <div class="input"> |
|
54 | 54 | %if c.rhodecode_db_repo.clone_uri: |
|
55 | ## display | |
|
55 | ## display, if we don't have any errors | |
|
56 | % if not c.form['repo_clone_uri'].error: | |
|
56 | 57 |
|
|
57 | 58 | <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span> |
|
58 | 59 | <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span> |
|
59 | 60 | </div> |
|
61 | % endif | |
|
62 | ||
|
60 | 63 | ## alter field |
|
61 | <div id="alter_clone_uri" style="display: none"> | |
|
64 | <div id="alter_clone_uri" style="${'' if c.form['repo_clone_uri'].error else 'display: none'}"> | |
|
62 | 65 | ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n} |
|
63 | 66 | ${c.form.render_error(request, c.form['repo_clone_uri'])|n} |
|
64 |
|
|
|
67 | % if c.form['repo_clone_uri'].error: | |
|
68 | ## we got error from form subit, means we modify the url | |
|
69 | ${h.hidden('repo_clone_uri_change', 'MOD')} | |
|
70 | % else: | |
|
71 | ${h.hidden('repo_clone_uri_change', 'OLD')} | |
|
72 | % endif | |
|
65 | 73 | |
|
74 | % if not c.form['repo_clone_uri'].error: | |
|
66 | 75 | <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span> |
|
76 | % endif | |
|
77 | ||
|
67 | 78 | </div> |
|
68 | 79 | %else: |
|
69 | 80 | ## not set yet, display form to set it |
|
70 | 81 | ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n} |
|
71 | 82 | ${c.form.render_error(request, c.form['repo_clone_uri'])|n} |
|
72 | 83 | ${h.hidden('repo_clone_uri_change', 'NEW')} |
|
73 | 84 | %endif |
|
74 | 85 | <p id="alter_clone_uri_help_block" class="help-block"> |
|
75 | 86 | <% pull_link = h.literal(h.link_to('remote sync', h.url('edit_repo_remote', repo_name=c.repo_name))) %> |
|
76 | 87 | ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/> |
|
77 | 88 | ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')} |
|
78 | 89 | </p> |
|
79 | 90 | </div> |
|
80 | 91 | </div> |
|
81 | 92 | % else: |
|
82 | 93 | ${h.hidden('repo_clone_uri', '')} |
|
83 | 94 | % endif |
|
84 | 95 | |
|
85 | 96 | <div class="field"> |
|
86 | 97 | <div class="label"> |
|
87 | 98 | <label for="repo_landing_commit_ref">${_('Landing commit')}:</label> |
|
88 | 99 | </div> |
|
89 | 100 | <div class="select"> |
|
90 | 101 | ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n} |
|
91 | 102 | ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n} |
|
92 | 103 | <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p> |
|
93 | 104 | </div> |
|
94 | 105 | </div> |
|
95 | 106 | |
|
96 | 107 | <div class="field badged-field"> |
|
97 | 108 | <div class="label"> |
|
98 | 109 | <label for="repo_owner">${_('Owner')}:</label> |
|
99 | 110 | </div> |
|
100 | 111 | <div class="input"> |
|
101 | 112 | <div class="badge-input-container"> |
|
102 | 113 | <div class="user-badge"> |
|
103 | 114 | ${base.gravatar_with_user(c.rhodecode_db_repo.user.email, show_disabled=not c.rhodecode_db_repo.user.active)} |
|
104 | 115 | </div> |
|
105 | 116 | <div class="badge-input-wrap"> |
|
106 | 117 | ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n} |
|
107 | 118 | </div> |
|
108 | 119 | </div> |
|
109 | 120 | ${c.form.render_error(request, c.form['repo_owner'])|n} |
|
110 | 121 | <p class="help-block">${_('Change owner of this repository.')}</p> |
|
111 | 122 | </div> |
|
112 | 123 | </div> |
|
113 | 124 | |
|
114 | 125 | <div class="field"> |
|
115 | 126 | <div class="label label-textarea"> |
|
116 | 127 | <label for="repo_description">${_('Description')}:</label> |
|
117 | 128 | </div> |
|
118 | 129 | <div class="textarea text-area editor"> |
|
119 | 130 | ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n} |
|
120 | 131 | ${c.form.render_error(request, c.form['repo_description'])|n} |
|
121 | 132 | <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p> |
|
122 | 133 | </div> |
|
123 | 134 | </div> |
|
124 | 135 | |
|
125 | 136 | <div class="field"> |
|
126 | 137 | <div class="label label-checkbox"> |
|
127 | 138 | <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label> |
|
128 | 139 | </div> |
|
129 | 140 | <div class="checkboxes"> |
|
130 | 141 | ${c.form['repo_private'].render(css_class='medium')|n} |
|
131 | 142 | ${c.form.render_error(request, c.form['repo_private'])|n} |
|
132 | 143 | <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span> |
|
133 | 144 | </div> |
|
134 | 145 | </div> |
|
135 | 146 | <div class="field"> |
|
136 | 147 | <div class="label label-checkbox"> |
|
137 | 148 | <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label> |
|
138 | 149 | </div> |
|
139 | 150 | <div class="checkboxes"> |
|
140 | 151 | ${c.form['repo_enable_statistics'].render(css_class='medium')|n} |
|
141 | 152 | ${c.form.render_error(request, c.form['repo_enable_statistics'])|n} |
|
142 | 153 | <span class="help-block">${_('Enable statistics window on summary page.')}</span> |
|
143 | 154 | </div> |
|
144 | 155 | </div> |
|
145 | 156 | <div class="field"> |
|
146 | 157 | <div class="label label-checkbox"> |
|
147 | 158 | <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label> |
|
148 | 159 | </div> |
|
149 | 160 | <div class="checkboxes"> |
|
150 | 161 | ${c.form['repo_enable_downloads'].render(css_class='medium')|n} |
|
151 | 162 | ${c.form.render_error(request, c.form['repo_enable_downloads'])|n} |
|
152 | 163 | <span class="help-block">${_('Enable download menu on summary page.')}</span> |
|
153 | 164 | </div> |
|
154 | 165 | </div> |
|
155 | 166 | <div class="field"> |
|
156 | 167 | <div class="label label-checkbox"> |
|
157 | 168 | <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label> |
|
158 | 169 | </div> |
|
159 | 170 | <div class="checkboxes"> |
|
160 | 171 | ${c.form['repo_enable_locking'].render(css_class='medium')|n} |
|
161 | 172 | ${c.form.render_error(request, c.form['repo_enable_locking'])|n} |
|
162 | 173 | <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span> |
|
163 | 174 | </div> |
|
164 | 175 | </div> |
|
165 | 176 | |
|
166 | 177 | %if c.visual.repository_fields: |
|
167 | 178 | ## EXTRA FIELDS |
|
168 | 179 | %for field in c.repo_fields: |
|
169 | 180 | <div class="field"> |
|
170 | 181 | <div class="label"> |
|
171 | 182 | <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label> |
|
172 | 183 | </div> |
|
173 | 184 | <div class="input input-medium"> |
|
174 | 185 | ${h.text(field.field_key_prefixed, field.field_value, class_='medium')} |
|
175 | 186 | %if field.field_desc: |
|
176 | 187 | <span class="help-block">${field.field_desc}</span> |
|
177 | 188 | %endif |
|
178 | 189 | </div> |
|
179 | 190 | </div> |
|
180 | 191 | %endfor |
|
181 | 192 | %endif |
|
182 | 193 | <div class="buttons"> |
|
183 | 194 | ${h.submit('save',_('Save'),class_="btn")} |
|
184 | 195 | ${h.reset('reset',_('Reset'),class_="btn")} |
|
185 | 196 | </div> |
|
186 | 197 | </div> |
|
187 | 198 | </div> |
|
188 | 199 | ${h.end_form()} |
|
189 | 200 | </div> |
|
190 | 201 | </div> |
|
191 | 202 | |
|
192 | 203 | <script> |
|
193 | 204 | $(document).ready(function(){ |
|
194 | 205 | var cloneUrl = function() { |
|
195 | 206 | var alterButton = $('#alter_clone_uri'); |
|
196 | 207 | var editButton = $('#edit_clone_uri'); |
|
197 | 208 | var cancelEditButton = $('#cancel_edit_clone_uri'); |
|
198 | 209 | var hiddenUrl = $('#clone_uri_hidden'); |
|
199 | 210 | var hiddenUrlValue = $('#clone_uri_hidden_value'); |
|
200 | 211 | var input = $('#clone_uri'); |
|
201 | 212 | var helpBlock = $('#alter_clone_uri_help_block'); |
|
202 | 213 | var changedFlag = $('#repo_clone_uri_change'); |
|
203 | 214 | var originalText = helpBlock.html(); |
|
204 | 215 | var obfuscatedUrl = hiddenUrlValue.html(); |
|
205 | 216 | |
|
206 | 217 | var edit = function(e) { |
|
207 | 218 | alterButton.show(); |
|
208 | 219 | editButton.hide(); |
|
209 | 220 | hiddenUrl.hide(); |
|
210 | 221 | |
|
211 | 222 | //add the old value next to input for verification |
|
212 | 223 | helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText); |
|
213 | 224 | changedFlag.val('MOD'); |
|
214 | 225 | }; |
|
215 | 226 | |
|
216 | 227 | var cancelEdit = function(e) { |
|
217 | 228 | alterButton.hide(); |
|
218 | 229 | editButton.show(); |
|
219 | 230 | hiddenUrl.show(); |
|
220 | 231 | |
|
221 | 232 | helpBlock.html(originalText); |
|
222 | 233 | changedFlag.val('OLD'); |
|
223 | 234 | input.val(''); |
|
224 | 235 | }; |
|
225 | 236 | |
|
226 | 237 | var initEvents = function() { |
|
227 | 238 | editButton.on('click', edit); |
|
228 | 239 | cancelEditButton.on('click', cancelEdit); |
|
229 | 240 | }; |
|
230 | 241 | |
|
231 | 242 | var setInitialState = function() { |
|
232 | 243 | if (input.hasClass('error')) { |
|
233 | 244 | alterButton.show(); |
|
234 | 245 | editButton.hide(); |
|
235 | 246 | hiddenUrl.hide(); |
|
236 | 247 | } |
|
237 | 248 | }; |
|
238 | 249 | |
|
239 | 250 | setInitialState(); |
|
240 | 251 | initEvents(); |
|
241 | 252 | }(); |
|
242 | 253 | |
|
243 | 254 | selectMyGroup = function(element) { |
|
244 | 255 | $("#repo_group").val($(element).data('personalGroupId')).trigger("change"); |
|
245 | 256 | }; |
|
246 | 257 | |
|
247 | 258 | UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}'); |
|
248 | 259 | }); |
|
249 | 260 | </script> |
General Comments 0
You need to be logged in to leave comments.
Login now