##// END OF EJS Templates
ui: normalize main grid sizes and columns
marcink -
r3557:77e4ef40 default
parent child Browse files
Show More
@@ -1,773 +1,777 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 repo group model for RhodeCode
24 24 """
25 25
26 26 import os
27 27 import datetime
28 28 import itertools
29 29 import logging
30 30 import shutil
31 31 import traceback
32 32 import string
33 33
34 34 from zope.cachedescriptors.property import Lazy as LazyProperty
35 35
36 36 from rhodecode import events
37 37 from rhodecode.model import BaseModel
38 38 from rhodecode.model.db import (_hash_key,
39 39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 40 UserGroup, Repository)
41 41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 42 from rhodecode.lib.caching_query import FromCache
43 43 from rhodecode.lib.utils2 import action_logger_generic, datetime_to_time
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class RepoGroupModel(BaseModel):
49 49
50 50 cls = RepoGroup
51 51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 52 PERSONAL_GROUP_PATTERN = '${username}' # default
53 53
54 54 def _get_user_group(self, users_group):
55 55 return self._get_instance(UserGroup, users_group,
56 56 callback=UserGroup.get_by_group_name)
57 57
58 58 def _get_repo_group(self, repo_group):
59 59 return self._get_instance(RepoGroup, repo_group,
60 60 callback=RepoGroup.get_by_group_name)
61 61
62 62 @LazyProperty
63 63 def repos_path(self):
64 64 """
65 65 Gets the repositories root path from database
66 66 """
67 67
68 68 settings_model = VcsSettingsModel(sa=self.sa)
69 69 return settings_model.get_repos_location()
70 70
71 71 def get_by_group_name(self, repo_group_name, cache=None):
72 72 repo = self.sa.query(RepoGroup) \
73 73 .filter(RepoGroup.group_name == repo_group_name)
74 74
75 75 if cache:
76 76 name_key = _hash_key(repo_group_name)
77 77 repo = repo.options(
78 78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
79 79 return repo.scalar()
80 80
81 81 def get_default_create_personal_repo_group(self):
82 82 value = SettingsModel().get_setting_by_name(
83 83 'create_personal_repo_group')
84 84 return value.app_settings_value if value else None or False
85 85
86 86 def get_personal_group_name_pattern(self):
87 87 value = SettingsModel().get_setting_by_name(
88 88 'personal_repo_group_pattern')
89 89 val = value.app_settings_value if value else None
90 90 group_template = val or self.PERSONAL_GROUP_PATTERN
91 91
92 92 group_template = group_template.lstrip('/')
93 93 return group_template
94 94
95 95 def get_personal_group_name(self, user):
96 96 template = self.get_personal_group_name_pattern()
97 97 return string.Template(template).safe_substitute(
98 98 username=user.username,
99 99 user_id=user.user_id,
100 100 )
101 101
102 102 def create_personal_repo_group(self, user, commit_early=True):
103 103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
104 104 personal_repo_group_name = self.get_personal_group_name(user)
105 105
106 106 # create a new one
107 107 RepoGroupModel().create(
108 108 group_name=personal_repo_group_name,
109 109 group_description=desc,
110 110 owner=user.username,
111 111 personal=True,
112 112 commit_early=commit_early)
113 113
114 114 def _create_default_perms(self, new_group):
115 115 # create default permission
116 116 default_perm = 'group.read'
117 117 def_user = User.get_default_user()
118 118 for p in def_user.user_perms:
119 119 if p.permission.permission_name.startswith('group.'):
120 120 default_perm = p.permission.permission_name
121 121 break
122 122
123 123 repo_group_to_perm = UserRepoGroupToPerm()
124 124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
125 125
126 126 repo_group_to_perm.group = new_group
127 127 repo_group_to_perm.user_id = def_user.user_id
128 128 return repo_group_to_perm
129 129
130 130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
131 131 get_object=False):
132 132 """
133 133 Get's the group name and a parent group name from given group name.
134 134 If repo_in_path is set to truth, we asume the full path also includes
135 135 repo name, in such case we clean the last element.
136 136
137 137 :param group_name_full:
138 138 """
139 139 split_paths = 1
140 140 if repo_in_path:
141 141 split_paths = 2
142 142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
143 143
144 144 if repo_in_path and len(_parts) > 1:
145 145 # such case last element is the repo_name
146 146 _parts.pop(-1)
147 147 group_name_cleaned = _parts[-1] # just the group name
148 148 parent_repo_group_name = None
149 149
150 150 if len(_parts) > 1:
151 151 parent_repo_group_name = _parts[0]
152 152
153 153 parent_group = None
154 154 if parent_repo_group_name:
155 155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
156 156
157 157 if get_object:
158 158 return group_name_cleaned, parent_repo_group_name, parent_group
159 159
160 160 return group_name_cleaned, parent_repo_group_name
161 161
162 162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
163 163 create_path = os.path.join(self.repos_path, group_name)
164 164 log.debug('creating new group in %s', create_path)
165 165
166 166 if os.path.isdir(create_path):
167 167 if exc_on_failure:
168 168 abs_create_path = os.path.abspath(create_path)
169 169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
170 170 return False
171 171 return True
172 172
173 173 def _create_group(self, group_name):
174 174 """
175 175 makes repository group on filesystem
176 176
177 177 :param repo_name:
178 178 :param parent_id:
179 179 """
180 180
181 181 self.check_exist_filesystem(group_name)
182 182 create_path = os.path.join(self.repos_path, group_name)
183 183 log.debug('creating new group in %s', create_path)
184 184 os.makedirs(create_path, mode=0o755)
185 185 log.debug('created group in %s', create_path)
186 186
187 187 def _rename_group(self, old, new):
188 188 """
189 189 Renames a group on filesystem
190 190
191 191 :param group_name:
192 192 """
193 193
194 194 if old == new:
195 195 log.debug('skipping group rename')
196 196 return
197 197
198 198 log.debug('renaming repository group from %s to %s', old, new)
199 199
200 200 old_path = os.path.join(self.repos_path, old)
201 201 new_path = os.path.join(self.repos_path, new)
202 202
203 203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
204 204
205 205 if os.path.isdir(new_path):
206 206 raise Exception('Was trying to rename to already '
207 207 'existing dir %s' % new_path)
208 208 shutil.move(old_path, new_path)
209 209
210 210 def _delete_filesystem_group(self, group, force_delete=False):
211 211 """
212 212 Deletes a group from a filesystem
213 213
214 214 :param group: instance of group from database
215 215 :param force_delete: use shutil rmtree to remove all objects
216 216 """
217 217 paths = group.full_path.split(RepoGroup.url_sep())
218 218 paths = os.sep.join(paths)
219 219
220 220 rm_path = os.path.join(self.repos_path, paths)
221 221 log.info("Removing group %s", rm_path)
222 222 # delete only if that path really exists
223 223 if os.path.isdir(rm_path):
224 224 if force_delete:
225 225 shutil.rmtree(rm_path)
226 226 else:
227 227 # archive that group`
228 228 _now = datetime.datetime.now()
229 229 _ms = str(_now.microsecond).rjust(6, '0')
230 230 _d = 'rm__%s_GROUP_%s' % (
231 231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
232 232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
233 233
234 234 def create(self, group_name, group_description, owner, just_db=False,
235 235 copy_permissions=False, personal=None, commit_early=True):
236 236
237 237 (group_name_cleaned,
238 238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
239 239
240 240 parent_group = None
241 241 if parent_group_name:
242 242 parent_group = self._get_repo_group(parent_group_name)
243 243 if not parent_group:
244 244 # we tried to create a nested group, but the parent is not
245 245 # existing
246 246 raise ValueError(
247 247 'Parent group `%s` given in `%s` group name '
248 248 'is not yet existing.' % (parent_group_name, group_name))
249 249
250 250 # because we are doing a cleanup, we need to check if such directory
251 251 # already exists. If we don't do that we can accidentally delete
252 252 # existing directory via cleanup that can cause data issues, since
253 253 # delete does a folder rename to special syntax later cleanup
254 254 # functions can delete this
255 255 cleanup_group = self.check_exist_filesystem(group_name,
256 256 exc_on_failure=False)
257 257 user = self._get_user(owner)
258 258 if not user:
259 259 raise ValueError('Owner %s not found as rhodecode user', owner)
260 260
261 261 try:
262 262 new_repo_group = RepoGroup()
263 263 new_repo_group.user = user
264 264 new_repo_group.group_description = group_description or group_name
265 265 new_repo_group.parent_group = parent_group
266 266 new_repo_group.group_name = group_name
267 267 new_repo_group.personal = personal
268 268
269 269 self.sa.add(new_repo_group)
270 270
271 271 # create an ADMIN permission for owner except if we're super admin,
272 272 # later owner should go into the owner field of groups
273 273 if not user.is_admin:
274 274 self.grant_user_permission(repo_group=new_repo_group,
275 275 user=owner, perm='group.admin')
276 276
277 277 if parent_group and copy_permissions:
278 278 # copy permissions from parent
279 279 user_perms = UserRepoGroupToPerm.query() \
280 280 .filter(UserRepoGroupToPerm.group == parent_group).all()
281 281
282 282 group_perms = UserGroupRepoGroupToPerm.query() \
283 283 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
284 284
285 285 for perm in user_perms:
286 286 # don't copy over the permission for user who is creating
287 287 # this group, if he is not super admin he get's admin
288 288 # permission set above
289 289 if perm.user != user or user.is_admin:
290 290 UserRepoGroupToPerm.create(
291 291 perm.user, new_repo_group, perm.permission)
292 292
293 293 for perm in group_perms:
294 294 UserGroupRepoGroupToPerm.create(
295 295 perm.users_group, new_repo_group, perm.permission)
296 296 else:
297 297 perm_obj = self._create_default_perms(new_repo_group)
298 298 self.sa.add(perm_obj)
299 299
300 300 # now commit the changes, earlier so we are sure everything is in
301 301 # the database.
302 302 if commit_early:
303 303 self.sa.commit()
304 304 if not just_db:
305 305 self._create_group(new_repo_group.group_name)
306 306
307 307 # trigger the post hook
308 308 from rhodecode.lib.hooks_base import log_create_repository_group
309 309 repo_group = RepoGroup.get_by_group_name(group_name)
310 310 log_create_repository_group(
311 311 created_by=user.username, **repo_group.get_dict())
312 312
313 313 # Trigger create event.
314 314 events.trigger(events.RepoGroupCreateEvent(repo_group))
315 315
316 316 return new_repo_group
317 317 except Exception:
318 318 self.sa.rollback()
319 319 log.exception('Exception occurred when creating repository group, '
320 320 'doing cleanup...')
321 321 # rollback things manually !
322 322 repo_group = RepoGroup.get_by_group_name(group_name)
323 323 if repo_group:
324 324 RepoGroup.delete(repo_group.group_id)
325 325 self.sa.commit()
326 326 if cleanup_group:
327 327 RepoGroupModel()._delete_filesystem_group(repo_group)
328 328 raise
329 329
330 330 def update_permissions(
331 331 self, repo_group, perm_additions=None, perm_updates=None,
332 332 perm_deletions=None, recursive=None, check_perms=True,
333 333 cur_user=None):
334 334 from rhodecode.model.repo import RepoModel
335 335 from rhodecode.lib.auth import HasUserGroupPermissionAny
336 336
337 337 if not perm_additions:
338 338 perm_additions = []
339 339 if not perm_updates:
340 340 perm_updates = []
341 341 if not perm_deletions:
342 342 perm_deletions = []
343 343
344 344 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
345 345
346 346 changes = {
347 347 'added': [],
348 348 'updated': [],
349 349 'deleted': []
350 350 }
351 351
352 352 def _set_perm_user(obj, user, perm):
353 353 if isinstance(obj, RepoGroup):
354 354 self.grant_user_permission(
355 355 repo_group=obj, user=user, perm=perm)
356 356 elif isinstance(obj, Repository):
357 357 # private repos will not allow to change the default
358 358 # permissions using recursive mode
359 359 if obj.private and user == User.DEFAULT_USER:
360 360 return
361 361
362 362 # we set group permission but we have to switch to repo
363 363 # permission
364 364 perm = perm.replace('group.', 'repository.')
365 365 RepoModel().grant_user_permission(
366 366 repo=obj, user=user, perm=perm)
367 367
368 368 def _set_perm_group(obj, users_group, perm):
369 369 if isinstance(obj, RepoGroup):
370 370 self.grant_user_group_permission(
371 371 repo_group=obj, group_name=users_group, perm=perm)
372 372 elif isinstance(obj, Repository):
373 373 # we set group permission but we have to switch to repo
374 374 # permission
375 375 perm = perm.replace('group.', 'repository.')
376 376 RepoModel().grant_user_group_permission(
377 377 repo=obj, group_name=users_group, perm=perm)
378 378
379 379 def _revoke_perm_user(obj, user):
380 380 if isinstance(obj, RepoGroup):
381 381 self.revoke_user_permission(repo_group=obj, user=user)
382 382 elif isinstance(obj, Repository):
383 383 RepoModel().revoke_user_permission(repo=obj, user=user)
384 384
385 385 def _revoke_perm_group(obj, user_group):
386 386 if isinstance(obj, RepoGroup):
387 387 self.revoke_user_group_permission(
388 388 repo_group=obj, group_name=user_group)
389 389 elif isinstance(obj, Repository):
390 390 RepoModel().revoke_user_group_permission(
391 391 repo=obj, group_name=user_group)
392 392
393 393 # start updates
394 394 log.debug('Now updating permissions for %s in recursive mode:%s',
395 395 repo_group, recursive)
396 396
397 397 # initialize check function, we'll call that multiple times
398 398 has_group_perm = HasUserGroupPermissionAny(*req_perms)
399 399
400 400 for obj in repo_group.recursive_groups_and_repos():
401 401 # iterated obj is an instance of a repos group or repository in
402 402 # that group, recursive option can be: none, repos, groups, all
403 403 if recursive == 'all':
404 404 obj = obj
405 405 elif recursive == 'repos':
406 406 # skip groups, other than this one
407 407 if isinstance(obj, RepoGroup) and not obj == repo_group:
408 408 continue
409 409 elif recursive == 'groups':
410 410 # skip repos
411 411 if isinstance(obj, Repository):
412 412 continue
413 413 else: # recursive == 'none':
414 414 # DEFAULT option - don't apply to iterated objects
415 415 # also we do a break at the end of this loop. if we are not
416 416 # in recursive mode
417 417 obj = repo_group
418 418
419 419 change_obj = obj.get_api_data()
420 420
421 421 # update permissions
422 422 for member_id, perm, member_type in perm_updates:
423 423 member_id = int(member_id)
424 424 if member_type == 'user':
425 425 member_name = User.get(member_id).username
426 426 # this updates also current one if found
427 427 _set_perm_user(obj, user=member_id, perm=perm)
428 428 elif member_type == 'user_group':
429 429 member_name = UserGroup.get(member_id).users_group_name
430 430 if not check_perms or has_group_perm(member_name,
431 431 user=cur_user):
432 432 _set_perm_group(obj, users_group=member_id, perm=perm)
433 433 else:
434 434 raise ValueError("member_type must be 'user' or 'user_group' "
435 435 "got {} instead".format(member_type))
436 436
437 437 changes['updated'].append(
438 438 {'change_obj': change_obj, 'type': member_type,
439 439 'id': member_id, 'name': member_name, 'new_perm': perm})
440 440
441 441 # set new permissions
442 442 for member_id, perm, member_type in perm_additions:
443 443 member_id = int(member_id)
444 444 if member_type == 'user':
445 445 member_name = User.get(member_id).username
446 446 _set_perm_user(obj, user=member_id, perm=perm)
447 447 elif member_type == 'user_group':
448 448 # check if we have permissions to alter this usergroup
449 449 member_name = UserGroup.get(member_id).users_group_name
450 450 if not check_perms or has_group_perm(member_name,
451 451 user=cur_user):
452 452 _set_perm_group(obj, users_group=member_id, perm=perm)
453 453 else:
454 454 raise ValueError("member_type must be 'user' or 'user_group' "
455 455 "got {} instead".format(member_type))
456 456
457 457 changes['added'].append(
458 458 {'change_obj': change_obj, 'type': member_type,
459 459 'id': member_id, 'name': member_name, 'new_perm': perm})
460 460
461 461 # delete permissions
462 462 for member_id, perm, member_type in perm_deletions:
463 463 member_id = int(member_id)
464 464 if member_type == 'user':
465 465 member_name = User.get(member_id).username
466 466 _revoke_perm_user(obj, user=member_id)
467 467 elif member_type == 'user_group':
468 468 # check if we have permissions to alter this usergroup
469 469 member_name = UserGroup.get(member_id).users_group_name
470 470 if not check_perms or has_group_perm(member_name,
471 471 user=cur_user):
472 472 _revoke_perm_group(obj, user_group=member_id)
473 473 else:
474 474 raise ValueError("member_type must be 'user' or 'user_group' "
475 475 "got {} instead".format(member_type))
476 476
477 477 changes['deleted'].append(
478 478 {'change_obj': change_obj, 'type': member_type,
479 479 'id': member_id, 'name': member_name, 'new_perm': perm})
480 480
481 481 # if it's not recursive call for all,repos,groups
482 482 # break the loop and don't proceed with other changes
483 483 if recursive not in ['all', 'repos', 'groups']:
484 484 break
485 485
486 486 return changes
487 487
488 488 def update(self, repo_group, form_data):
489 489 try:
490 490 repo_group = self._get_repo_group(repo_group)
491 491 old_path = repo_group.full_path
492 492
493 493 # change properties
494 494 if 'group_description' in form_data:
495 495 repo_group.group_description = form_data['group_description']
496 496
497 497 if 'enable_locking' in form_data:
498 498 repo_group.enable_locking = form_data['enable_locking']
499 499
500 500 if 'group_parent_id' in form_data:
501 501 parent_group = (
502 502 self._get_repo_group(form_data['group_parent_id']))
503 503 repo_group.group_parent_id = (
504 504 parent_group.group_id if parent_group else None)
505 505 repo_group.parent_group = parent_group
506 506
507 507 # mikhail: to update the full_path, we have to explicitly
508 508 # update group_name
509 509 group_name = form_data.get('group_name', repo_group.name)
510 510 repo_group.group_name = repo_group.get_new_name(group_name)
511 511
512 512 new_path = repo_group.full_path
513 513
514 514 if 'user' in form_data:
515 515 repo_group.user = User.get_by_username(form_data['user'])
516 516 repo_group.updated_on = datetime.datetime.now()
517 517 self.sa.add(repo_group)
518 518
519 519 # iterate over all members of this groups and do fixes
520 520 # set locking if given
521 521 # if obj is a repoGroup also fix the name of the group according
522 522 # to the parent
523 523 # if obj is a Repo fix it's name
524 524 # this can be potentially heavy operation
525 525 for obj in repo_group.recursive_groups_and_repos():
526 526 # set the value from it's parent
527 527 obj.enable_locking = repo_group.enable_locking
528 528 if isinstance(obj, RepoGroup):
529 529 new_name = obj.get_new_name(obj.name)
530 530 log.debug('Fixing group %s to new name %s',
531 531 obj.group_name, new_name)
532 532 obj.group_name = new_name
533 533 obj.updated_on = datetime.datetime.now()
534 534 elif isinstance(obj, Repository):
535 535 # we need to get all repositories from this new group and
536 536 # rename them accordingly to new group path
537 537 new_name = obj.get_new_name(obj.just_name)
538 538 log.debug('Fixing repo %s to new name %s',
539 539 obj.repo_name, new_name)
540 540 obj.repo_name = new_name
541 541 obj.updated_on = datetime.datetime.now()
542 542 self.sa.add(obj)
543 543
544 544 self._rename_group(old_path, new_path)
545 545
546 546 # Trigger update event.
547 547 events.trigger(events.RepoGroupUpdateEvent(repo_group))
548 548
549 549 return repo_group
550 550 except Exception:
551 551 log.error(traceback.format_exc())
552 552 raise
553 553
554 554 def delete(self, repo_group, force_delete=False, fs_remove=True):
555 555 repo_group = self._get_repo_group(repo_group)
556 556 if not repo_group:
557 557 return False
558 558 try:
559 559 self.sa.delete(repo_group)
560 560 if fs_remove:
561 561 self._delete_filesystem_group(repo_group, force_delete)
562 562 else:
563 563 log.debug('skipping removal from filesystem')
564 564
565 565 # Trigger delete event.
566 566 events.trigger(events.RepoGroupDeleteEvent(repo_group))
567 567 return True
568 568
569 569 except Exception:
570 570 log.error('Error removing repo_group %s', repo_group)
571 571 raise
572 572
573 573 def grant_user_permission(self, repo_group, user, perm):
574 574 """
575 575 Grant permission for user on given repository group, or update
576 576 existing one if found
577 577
578 578 :param repo_group: Instance of RepoGroup, repositories_group_id,
579 579 or repositories_group name
580 580 :param user: Instance of User, user_id or username
581 581 :param perm: Instance of Permission, or permission_name
582 582 """
583 583
584 584 repo_group = self._get_repo_group(repo_group)
585 585 user = self._get_user(user)
586 586 permission = self._get_perm(perm)
587 587
588 588 # check if we have that permission already
589 589 obj = self.sa.query(UserRepoGroupToPerm)\
590 590 .filter(UserRepoGroupToPerm.user == user)\
591 591 .filter(UserRepoGroupToPerm.group == repo_group)\
592 592 .scalar()
593 593 if obj is None:
594 594 # create new !
595 595 obj = UserRepoGroupToPerm()
596 596 obj.group = repo_group
597 597 obj.user = user
598 598 obj.permission = permission
599 599 self.sa.add(obj)
600 600 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
601 601 action_logger_generic(
602 602 'granted permission: {} to user: {} on repogroup: {}'.format(
603 603 perm, user, repo_group), namespace='security.repogroup')
604 604 return obj
605 605
606 606 def revoke_user_permission(self, repo_group, user):
607 607 """
608 608 Revoke permission for user on given repository group
609 609
610 610 :param repo_group: Instance of RepoGroup, repositories_group_id,
611 611 or repositories_group name
612 612 :param user: Instance of User, user_id or username
613 613 """
614 614
615 615 repo_group = self._get_repo_group(repo_group)
616 616 user = self._get_user(user)
617 617
618 618 obj = self.sa.query(UserRepoGroupToPerm)\
619 619 .filter(UserRepoGroupToPerm.user == user)\
620 620 .filter(UserRepoGroupToPerm.group == repo_group)\
621 621 .scalar()
622 622 if obj:
623 623 self.sa.delete(obj)
624 624 log.debug('Revoked perm on %s on %s', repo_group, user)
625 625 action_logger_generic(
626 626 'revoked permission from user: {} on repogroup: {}'.format(
627 627 user, repo_group), namespace='security.repogroup')
628 628
629 629 def grant_user_group_permission(self, repo_group, group_name, perm):
630 630 """
631 631 Grant permission for user group on given repository group, or update
632 632 existing one if found
633 633
634 634 :param repo_group: Instance of RepoGroup, repositories_group_id,
635 635 or repositories_group name
636 636 :param group_name: Instance of UserGroup, users_group_id,
637 637 or user group name
638 638 :param perm: Instance of Permission, or permission_name
639 639 """
640 640 repo_group = self._get_repo_group(repo_group)
641 641 group_name = self._get_user_group(group_name)
642 642 permission = self._get_perm(perm)
643 643
644 644 # check if we have that permission already
645 645 obj = self.sa.query(UserGroupRepoGroupToPerm)\
646 646 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
647 647 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
648 648 .scalar()
649 649
650 650 if obj is None:
651 651 # create new
652 652 obj = UserGroupRepoGroupToPerm()
653 653
654 654 obj.group = repo_group
655 655 obj.users_group = group_name
656 656 obj.permission = permission
657 657 self.sa.add(obj)
658 658 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
659 659 action_logger_generic(
660 660 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
661 661 perm, group_name, repo_group), namespace='security.repogroup')
662 662 return obj
663 663
664 664 def revoke_user_group_permission(self, repo_group, group_name):
665 665 """
666 666 Revoke permission for user group on given repository group
667 667
668 668 :param repo_group: Instance of RepoGroup, repositories_group_id,
669 669 or repositories_group name
670 670 :param group_name: Instance of UserGroup, users_group_id,
671 671 or user group name
672 672 """
673 673 repo_group = self._get_repo_group(repo_group)
674 674 group_name = self._get_user_group(group_name)
675 675
676 676 obj = self.sa.query(UserGroupRepoGroupToPerm)\
677 677 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
678 678 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
679 679 .scalar()
680 680 if obj:
681 681 self.sa.delete(obj)
682 682 log.debug('Revoked perm to %s on %s', repo_group, group_name)
683 683 action_logger_generic(
684 684 'revoked permission from usergroup: {} on repogroup: {}'.format(
685 685 group_name, repo_group), namespace='security.repogroup')
686 686
687 687 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
688 688 super_user_actions=False):
689 689
690 690 from pyramid.threadlocal import get_current_request
691 691 _render = get_current_request().get_partial_renderer(
692 692 'rhodecode:templates/data_table/_dt_elements.mako')
693 693 c = _render.get_call_context()
694 694 h = _render.get_helpers()
695 695
696 696 def quick_menu(repo_group_name):
697 697 return _render('quick_repo_group_menu', repo_group_name)
698 698
699 699 def repo_group_lnk(repo_group_name):
700 700 return _render('repo_group_name', repo_group_name)
701 701
702 702 def last_change(last_change):
703 703 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
704 704 last_change = last_change + datetime.timedelta(seconds=
705 705 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
706 706 return _render("last_change", last_change)
707 707
708 708 def desc(desc, personal):
709 709 return _render(
710 710 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
711 711
712 712 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
713 713 return _render(
714 714 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
715 715
716 716 def repo_group_name(repo_group_name, children_groups):
717 717 return _render("repo_group_name", repo_group_name, children_groups)
718 718
719 719 def user_profile(username):
720 720 return _render('user_profile', username)
721 721
722 722 repo_group_data = []
723 723 for group in repo_group_list:
724 724
725 725 row = {
726 726 "menu": quick_menu(group.group_name),
727 727 "name": repo_group_lnk(group.group_name),
728 728 "name_raw": group.group_name,
729 729 "last_change": last_change(group.last_db_change),
730 730 "last_change_raw": datetime_to_time(group.last_db_change),
731
732 "last_changeset": "",
733 "last_changeset_raw": "",
734
731 735 "desc": desc(group.description_safe, group.personal),
732 736 "top_level_repos": 0,
733 737 "owner": user_profile(group.user.username)
734 738 }
735 739 if admin:
736 740 repo_count = group.repositories.count()
737 741 children_groups = map(
738 742 h.safe_unicode,
739 743 itertools.chain((g.name for g in group.parents),
740 744 (x.name for x in [group])))
741 745 row.update({
742 746 "action": repo_group_actions(
743 747 group.group_id, group.group_name, repo_count),
744 748 "top_level_repos": repo_count,
745 749 "name": repo_group_name(group.group_name, children_groups),
746 750
747 751 })
748 752 repo_group_data.append(row)
749 753
750 754 return repo_group_data
751 755
752 756 def _get_defaults(self, repo_group_name):
753 757 repo_group = RepoGroup.get_by_group_name(repo_group_name)
754 758
755 759 if repo_group is None:
756 760 return None
757 761
758 762 defaults = repo_group.get_dict()
759 763 defaults['repo_group_name'] = repo_group.name
760 764 defaults['repo_group_description'] = repo_group.group_description
761 765 defaults['repo_group_enable_locking'] = repo_group.enable_locking
762 766
763 767 # we use -1 as this is how in HTML, we mark an empty group
764 768 defaults['repo_group'] = defaults['group_parent_id'] or -1
765 769
766 770 # fill owner
767 771 if repo_group.user:
768 772 defaults.update({'user': repo_group.user.username})
769 773 else:
770 774 replacement_user = User.get_first_super_admin().username
771 775 defaults.update({'user': replacement_user})
772 776
773 777 return defaults
@@ -1,509 +1,514 b''
1 1
2 2 // tables.less
3 3 // For use in RhodeCode application tables;
4 4 // see style guide documentation for guidelines.
5 5
6 6 // TABLES
7 7
8 8 .rctable,
9 9 table.rctable,
10 10 table.dataTable {
11 11 clear:both;
12 12 width: 100%;
13 13 margin: 0 auto @padding;
14 14 padding: 0;
15 15 vertical-align: baseline;
16 16 line-height:1.5em;
17 17 border: none;
18 18 outline: none;
19 19 border-collapse: collapse;
20 20 border-spacing: 0;
21 21 color: @grey2;
22 22
23 23 b {
24 24 font-weight: normal;
25 25 }
26 26
27 27 em {
28 28 font-weight: bold;
29 29 font-style: normal;
30 30 }
31 31
32 32 th,
33 33 td {
34 34 height: auto;
35 35 max-width: 20%;
36 36 padding: .65em 1em .65em 0;
37 37 vertical-align: middle;
38 38 border-bottom: @border-thickness solid @grey5;
39 39 white-space: normal;
40 40
41 41 &.td-radio,
42 42 &.td-checkbox {
43 43 padding-right: 0;
44 44 text-align: center;
45 45
46 46 input {
47 47 margin: 0 1em;
48 48 }
49 49 }
50 50
51 51 &.truncate-wrap {
52 52 white-space: nowrap !important;
53 53 }
54 54
55 55 pre {
56 56 margin: 0;
57 57 }
58 58
59 59 .show_more {
60 60 height: inherit;
61 61 }
62 62 }
63 63
64 64 .expired td {
65 65 background-color: @grey7;
66 66 }
67 67 .inactive td {
68 68 background-color: @grey6;
69 69 }
70 70 th {
71 71 text-align: left;
72 72 font-weight: @text-semibold-weight;
73 73 font-family: @text-semibold;
74 74 }
75 75
76 76 .hl {
77 77 td {
78 78 background-color: lighten(@alert4,25%);
79 79 }
80 80 }
81 81
82 82 // Special Data Cell Types
83 83 // See style guide for desciptions and examples.
84 84
85 85 td {
86 86
87 87 &.user {
88 88 padding-left: 1em;
89 89 }
90 90
91 91 &.td-rss {
92 92 width: 20px;
93 93 min-width: 0;
94 94 margin: 0;
95 95 }
96 96
97 97 &.quick_repo_menu {
98 98 width: 15px;
99 99 text-align: center;
100 100
101 101 &:hover {
102 102 background-color: @grey5;
103 103 }
104 104 }
105 105
106 106 &.td-hash {
107 107 min-width: 80px;
108 108 width: 200px;
109 109
110 110 .obsolete {
111 111 text-decoration: line-through;
112 112 color: lighten(@grey2,25%);
113 113 }
114 114 }
115 115
116 116 &.td-time {
117 117 width: 160px;
118 118 white-space: nowrap;
119 119 }
120 120
121 121 &.annotate{
122 122 padding-right: 0;
123 123
124 124 div.annotatediv{
125 125 margin: 0 0.7em;
126 126 }
127 127 }
128 128
129 129 &.tags-col {
130 130 padding-right: 0;
131 131 }
132 132
133 133 &.td-description {
134 134 min-width: 350px;
135 135
136 136 &.truncate, .truncate-wrap {
137 137 white-space: nowrap;
138 138 overflow: hidden;
139 139 text-overflow: ellipsis;
140 140 max-width: 350px;
141 141 }
142 142 }
143 143
144 &.td-grid-name {
145 white-space: nowrap;
146 min-width: 300px;
147 }
148
144 149 &.td-componentname {
145 150 white-space: nowrap;
146 151 }
147 152
148 153 &.td-name {
149 154
150 155 }
151 156
152 157 &.td-journalaction {
153 158 min-width: 300px;
154 159
155 160 .journal_action_params {
156 161 // waiting for feedback
157 162 }
158 163 }
159 164
160 165 &.td-active {
161 166 padding-left: .65em;
162 167 }
163 168
164 169 &.td-url {
165 170 white-space: nowrap;
166 171 }
167 172
168 173 &.td-comments {
169 174 min-width: 3em;
170 175 }
171 176
172 177 &.td-buttons {
173 178 padding: .3em 0;
174 179 }
175 180 &.td-align-top {
176 181 vertical-align: text-top
177 182 }
178 183 &.td-action {
179 184 // this is for the remove/delete/edit buttons
180 185 padding-right: 0;
181 186 min-width: 95px;
182 187 text-transform: capitalize;
183 188
184 189 i {
185 190 display: none;
186 191 }
187 192 }
188 193
189 194 // TODO: lisa: this needs to be cleaned up with the buttons
190 195 .grid_edit,
191 196 .grid_delete {
192 197 display: inline-block;
193 198 margin: 0 @padding/3 0 0;
194 199 font-family: @text-light;
195 200
196 201 i {
197 202 display: none;
198 203 }
199 204 }
200 205
201 206 .grid_edit + .grid_delete {
202 207 border-left: @border-thickness solid @grey5;
203 208 padding-left: @padding/2;
204 209 }
205 210
206 211 &.td-compare {
207 212
208 213 input {
209 214 margin-right: 1em;
210 215 }
211 216
212 217 .compare-radio-button {
213 218 margin: 0 1em 0 0;
214 219 }
215 220
216 221
217 222 }
218 223
219 224 &.td-tags {
220 225 padding: .5em 1em .5em 0;
221 226 width: 140px;
222 227
223 228 .tag {
224 229 margin: 1px;
225 230 float: left;
226 231 }
227 232 }
228 233
229 234 .icon-svn, .icon-hg, .icon-git {
230 235 font-size: 1.4em;
231 236 }
232 237
233 238 &.collapse_commit,
234 239 &.expand_commit {
235 240 padding-right: 0;
236 241 padding-left: 1em;
237 242 cursor: pointer;
238 243 width: 20px;
239 244 }
240 245 }
241 246
242 247 .perm_admin_row {
243 248 color: @grey4;
244 249 background-color: @grey6;
245 250 }
246 251
247 252 .noborder {
248 253 border: none;
249 254
250 255 td {
251 256 border: none;
252 257 }
253 258 }
254 259 }
255 260 .rctable.audit-log {
256 261 td {
257 262 vertical-align: top;
258 263 }
259 264 }
260 265
261 266 // TRUNCATING
262 267 // TODO: lisaq: should this possibly be moved out of tables.less?
263 268 // for truncated text
264 269 // used inside of table cells and in code block headers
265 270 .truncate-wrap {
266 271 white-space: nowrap !important;
267 272
268 273 //truncated text
269 274 .truncate {
270 275 max-width: 450px;
271 276 width: 300px;
272 277 overflow: hidden;
273 278 text-overflow: ellipsis;
274 279 -o-text-overflow: ellipsis;
275 280 -ms-text-overflow: ellipsis;
276 281
277 282 &.autoexpand {
278 283 width: 120px;
279 284 margin-right: 200px;
280 285 }
281 286 }
282 287 &:hover .truncate.autoexpand {
283 288 overflow: visible;
284 289 }
285 290
286 291 .tags-truncate {
287 292 width: 150px;
288 293 height: 22px;
289 294 overflow: hidden;
290 295
291 296 .tag {
292 297 display: inline-block;
293 298 }
294 299
295 300 &.truncate {
296 301 height: 22px;
297 302 max-height:2em;
298 303 width: 140px;
299 304 }
300 305 }
301 306 }
302 307
303 308 .apikeys_wrap {
304 309 margin-bottom: @padding;
305 310
306 311 table.rctable td:first-child {
307 312 width: 340px;
308 313 }
309 314 }
310 315
311 316
312 317
313 318 // SPECIAL CASES
314 319
315 320 // Repository Followers
316 321 table.rctable.followers_data {
317 322 width: 75%;
318 323 margin: 0;
319 324 }
320 325
321 326 // Repository List
322 327 // Group Members List
323 328 table.rctable.group_members,
324 329 table#repo_list_table {
325 330 min-width: 600px;
326 331 }
327 332
328 333 // Keyboard mappings
329 334 table.keyboard-mappings {
330 335 th {
331 336 text-align: left;
332 337 font-weight: @text-semibold-weight;
333 338 font-family: @text-semibold;
334 339 }
335 340 }
336 341
337 342 // Branches, Tags, and Bookmarks
338 343 #obj_list_table.dataTable {
339 344 td.td-time {
340 345 padding-right: 1em;
341 346 }
342 347 }
343 348
344 349 // User Admin
345 350 .rctable.useremails,
346 351 .rctable.account_emails {
347 352 .tag,
348 353 .btn {
349 354 float: right;
350 355 }
351 356 .btn { //to line up with tags
352 357 margin-right: 1.65em;
353 358 }
354 359 }
355 360
356 361 // User List
357 362 #user_list_table {
358 363
359 364 td.td-user {
360 365 min-width: 100px;
361 366 }
362 367 }
363 368
364 369 // Pull Request List Table
365 370 #pull_request_list_table.dataTable {
366 371
367 372 //TODO: lisa: This needs to be removed once the description is adjusted
368 373 // for using an expand_commit button (see issue 765)
369 374 td {
370 375 vertical-align: middle;
371 376 }
372 377 }
373 378
374 379 // Settings (no border)
375 380 table.rctable.dl-settings {
376 381 td {
377 382 border: none;
378 383 vertical-align: baseline;
379 384 }
380 385 }
381 386
382 387
383 388 // Statistics
384 389 table.trending_language_tbl {
385 390 width: 100%;
386 391 line-height: 1em;
387 392
388 393 td div {
389 394 overflow: visible;
390 395 }
391 396 }
392 397
393 398 .trending_language_tbl, .trending_language_tbl td {
394 399 border: 0;
395 400 margin: 0;
396 401 padding: 0;
397 402 background: transparent;
398 403 }
399 404
400 405 .trending_language_tbl, .trending_language_tbl tr {
401 406 border-spacing: 0 3px;
402 407 }
403 408
404 409 .trending_language {
405 410 position: relative;
406 411 width: 100%;
407 412 height: 19px;
408 413 overflow: hidden;
409 414 background-color: @grey6;
410 415
411 416 span, b{
412 417 position: absolute;
413 418 display: block;
414 419 height: 12px;
415 420 margin-bottom: 0px;
416 421 white-space: pre;
417 422 padding: floor(@basefontsize/4);
418 423 top: 0;
419 424 left: 0;
420 425 }
421 426
422 427 span{
423 428 color: @text-color;
424 429 z-index: 0;
425 430 min-width: 20px;
426 431 }
427 432
428 433 b {
429 434 z-index: 1;
430 435 overflow: hidden;
431 436 background-color: @rcblue;
432 437 color: #FFF;
433 438 text-decoration: none;
434 439 }
435 440
436 441 }
437 442
438 443 // Changesets
439 444 #changesets.rctable {
440 445
441 446 // td must be fixed height for graph
442 447 td {
443 448 height: 32px;
444 449 padding: 0 1em 0 0;
445 450 vertical-align: middle;
446 451 white-space: nowrap;
447 452
448 453 &.td-description {
449 454 white-space: normal;
450 455 }
451 456
452 457 &.expand_commit {
453 458 padding-right: 0;
454 459 cursor: pointer;
455 460 width: 20px;
456 461 }
457 462 }
458 463 }
459 464
460 465 // Compare
461 466 table.compare_view_commits {
462 467 margin-top: @space;
463 468
464 469 td.td-time {
465 470 padding-left: .5em;
466 471 }
467 472
468 473 // special case to not show hover actions on hidden indicator
469 474 tr.compare_select_hidden:hover {
470 475 cursor: inherit;
471 476
472 477 td {
473 478 background-color: inherit;
474 479 }
475 480 }
476 481
477 482 tr:hover {
478 483 cursor: pointer;
479 484
480 485 td {
481 486 background-color: lighten(@alert4,25%);
482 487 }
483 488 }
484 489
485 490
486 491 }
487 492
488 493 .file_history {
489 494 td.td-actions {
490 495 text-align: right;
491 496 }
492 497 }
493 498
494 499
495 500 // Gist List
496 501 #gist_list_table {
497 502 td {
498 503 vertical-align: middle;
499 504
500 505 div{
501 506 display: inline-block;
502 507 vertical-align: middle;
503 508 }
504 509
505 510 img{
506 511 vertical-align: middle;
507 512 }
508 513 }
509 514 }
@@ -1,138 +1,141 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3
4 4 <%def name="menu_bar_subnav()">
5 5 % if c.repo_group:
6 6 ${self.repo_group_menu(active='home')}
7 7 % endif
8 8 </%def>
9 9
10 10
11 11 <%def name="main()">
12 12 <div class="box">
13 13 <!-- box / title -->
14 14 <div class="title">
15 15 % if c.repo_group:
16 16 ${self.repo_group_page_title(c.repo_group)}
17 17 ## context actions
18 18 <div>
19 19 <ul class="links icon-only-links block-right">
20 20 <li></li>
21 21 </ul>
22 22 </div>
23 23 % endif
24 24
25 25 %if c.rhodecode_user.username != h.DEFAULT_USER:
26 26 <div class="block-right">
27 27 <%
28 28 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
29 29 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
30 30 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
31 31 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
32 32 %>
33 33
34 34 %if not c.repo_group:
35 35 ## no repository group context here
36 36 %if is_admin or create_repo:
37 37 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
38 38 %endif
39 39
40 40 %if is_admin or create_repo_group:
41 41 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
42 42 %endif
43 43 %endif
44 44 </div>
45 45 %endif
46 46 </div>
47 47 <!-- end box / title -->
48 48 <div class="table">
49 49 <div id="groups_list_wrap">
50 50 <table id="group_list_table" class="display" style="width: 100%"></table>
51 51 </div>
52 52 </div>
53 53
54 54 <div class="table">
55 55 <div id="repos_list_wrap">
56 56 <table id="repo_list_table" class="display" style="width: 100%"></table>
57 57 </div>
58 58 </div>
59 59
60 60 ## no repository groups and repos present, show something to the users
61 61 % if c.repo_groups_data == '[]' and c.repos_data == '[]':
62 62 <div class="table">
63 63 <h2 class="no-object-border">
64 64 ${_('No repositories or repositories groups exists here.')}
65 65 </h2>
66 66 </div>
67 67 % endif
68 68
69 69 </div>
70 70 <script>
71 71 $(document).ready(function() {
72 72
73 73 // repo group list
74 74 % if c.repo_groups_data != '[]':
75 75 $('#group_list_table').DataTable({
76 76 data: ${c.repo_groups_data|n},
77 77 dom: 'rtp',
78 78 pageLength: ${c.visual.dashboard_items},
79 79 order: [[ 0, "asc" ]],
80 80 columns: [
81 81 { data: {"_": "name",
82 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
82 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-grid-name" },
83 83 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
84 84 { data: {"_": "desc",
85 85 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
86 86 { data: {"_": "last_change",
87 87 "sort": "last_change_raw",
88 88 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
89 { data: {"_": "last_changeset",
90 "sort": "last_changeset_raw",
91 "type": Number}, title: "", className: "td-hash" },
89 92 { data: {"_": "owner",
90 93 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
91 94 ],
92 95 language: {
93 96 paginate: DEFAULT_GRID_PAGINATION,
94 97 emptyTable: _gettext("No repository groups available yet.")
95 98 },
96 99 "drawCallback": function( settings, json ) {
97 100 timeagoActivate();
98 101 quick_repo_menu();
99 102 }
100 103 });
101 104 % endif
102 105
103 106 // repo list
104 107 % if c.repos_data != '[]':
105 108 $('#repo_list_table').DataTable({
106 109 data: ${c.repos_data|n},
107 110 dom: 'rtp',
108 111 order: [[ 0, "asc" ]],
109 112 pageLength: ${c.visual.dashboard_items},
110 113 columns: [
111 114 { data: {"_": "name",
112 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
115 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-grid-name" },
113 116 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
114 117 { data: {"_": "desc",
115 118 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
116 119 { data: {"_": "last_change",
117 120 "sort": "last_change_raw",
118 121 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
119 122 { data: {"_": "last_changeset",
120 123 "sort": "last_changeset_raw",
121 124 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
122 125 { data: {"_": "owner",
123 126 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
124 127 ],
125 128 language: {
126 129 paginate: DEFAULT_GRID_PAGINATION,
127 130 emptyTable: _gettext("No repositories available yet.")
128 131 },
129 132 "drawCallback": function( settings, json ) {
130 133 timeagoActivate();
131 134 quick_repo_menu();
132 135 }
133 136 });
134 137 % endif
135 138
136 139 });
137 140 </script>
138 141 </%def>
General Comments 0
You need to be logged in to leave comments. Login now