core: revamp of automation/scheduler/artifacts EE functionality

This commit is contained in:
RhodeCode Admin 2023-08-16 15:57:08 +02:00
parent dd61323584
commit e898385b32
27 changed files with 296 additions and 79 deletions

View file

@ -42,8 +42,8 @@ from rhodecode.model.repo import ReadmeFinder
log = logging.getLogger(__name__)
ADMIN_PREFIX = '/_admin'
STATIC_FILE_PREFIX = '/_static'
ADMIN_PREFIX: str = '/_admin'
STATIC_FILE_PREFIX: str = '/_static'
URL_NAME_REQUIREMENTS = {
# group name can have a slash in them, but they must not end with a slash

View file

@ -101,8 +101,6 @@ class NavigationRegistry(object):
'admin_settings_sessions'),
NavEntry('open_source', _('Open Source Licenses'),
'admin_settings_open_source'),
NavEntry('automation', _('Automation'),
'admin_settings_automation')
]
_labs_entry = NavEntry('labs', _('Labs'),

View file

@ -18,6 +18,8 @@
from rhodecode.apps._base import ADMIN_PREFIX
from rhodecode.apps._base.navigation import includeme as nav_includeme
from rhodecode.apps.admin.views.main_views import AdminMainView
def admin_routes(config):
@ -26,9 +28,10 @@ def admin_routes(config):
"""
from rhodecode.apps.admin.views.audit_logs import AdminAuditLogsView
from rhodecode.apps.admin.views.artifacts import AdminArtifactsView
from rhodecode.apps.admin.views.automation import AdminAutomationView
from rhodecode.apps.admin.views.scheduler import AdminSchedulerView
from rhodecode.apps.admin.views.defaults import AdminDefaultSettingsView
from rhodecode.apps.admin.views.exception_tracker import ExceptionsTrackerView
from rhodecode.apps.admin.views.main_views import AdminMainView
from rhodecode.apps.admin.views.open_source_licenses import OpenSourceLicensesAdminSettingsView
from rhodecode.apps.admin.views.permissions import AdminPermissionsView
from rhodecode.apps.admin.views.process_management import AdminProcessManagementView
@ -76,6 +79,7 @@ def admin_routes(config):
attr='artifacts',
route_name='admin_artifacts_show_all', request_method='GET',
renderer='rhodecode:templates/admin/artifacts/artifacts.mako')
# EE views
config.add_route(
name='admin_artifacts_show_info',
@ -87,6 +91,26 @@ def admin_routes(config):
name='admin_artifacts_update',
pattern=ADMIN_PREFIX + '/artifacts/{uid}/update')
# Automation EE feature
config.add_route(
'admin_automation',
pattern=ADMIN_PREFIX + '/automation')
config.add_view(
AdminAutomationView,
attr='automation',
route_name='admin_automation', request_method='GET',
renderer='rhodecode:templates/admin/automation/automation.mako')
# Scheduler EE feature
config.add_route(
'admin_scheduler',
pattern=ADMIN_PREFIX + '/scheduler')
config.add_view(
AdminSchedulerView,
attr='scheduler',
route_name='admin_scheduler', request_method='GET',
renderer='rhodecode:templates/admin/scheduler/scheduler.mako')
config.add_route(
name='admin_settings_open_source',
pattern='/settings/open_source')
@ -440,16 +464,6 @@ def admin_routes(config):
route_name='admin_settings_labs_update', request_method='POST',
renderer='rhodecode:templates/admin/settings/settings.mako')
# Automation EE feature
config.add_route(
'admin_settings_automation',
pattern=ADMIN_PREFIX + '/settings/automation')
config.add_view(
AdminSettingsView,
attr='settings_automation',
route_name='admin_settings_automation', request_method='GET',
renderer='rhodecode:templates/admin/settings/settings.mako')
# global permissions
config.add_route(
@ -1039,9 +1053,6 @@ def admin_routes(config):
def includeme(config):
from rhodecode.apps._base.navigation import includeme as nav_includeme
from rhodecode.apps.admin.views.main_views import AdminMainView
# Create admin navigation registry and add it to the pyramid registry.
nav_includeme(config)

View file

@ -0,0 +1,38 @@
# Copyright (C) 2016-2023 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import logging
from rhodecode.apps._base import BaseAppView
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
log = logging.getLogger(__name__)
class AdminAutomationView(BaseAppView):
def load_default_context(self):
c = self._get_local_tmpl_context()
return c
@LoginRequired()
@HasPermissionAllDecorator('hg.admin')
def automation(self):
c = self.load_default_context()
c.active = 'automation'
return self._get_template_context(c)

View file

@ -0,0 +1,38 @@
# Copyright (C) 2016-2023 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import logging
from rhodecode.apps._base import BaseAppView
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
log = logging.getLogger(__name__)
class AdminSchedulerView(BaseAppView):
def load_default_context(self):
c = self._get_local_tmpl_context()
return c
@LoginRequired()
@HasPermissionAllDecorator('hg.admin')
def scheduler(self):
c = self.load_default_context()
c.active = 'scheduler'
return self._get_template_context(c)

View file

@ -248,8 +248,9 @@ class AdminSettingsView(BaseAppView):
added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
PermissionModel().trigger_permission_flush()
def _repr(l):
return ', '.join(map(safe_str, l)) or '-'
def _repr(rm_repo):
return ', '.join(map(safe_str, rm_repo)) or '-'
h.flash(_('Repositories successfully '
'rescanned added: %s ; removed: %s') %
(_repr(added), _repr(removed)),
@ -621,14 +622,6 @@ class AdminSettingsView(BaseAppView):
return self._get_template_context(c)
@LoginRequired()
@HasPermissionAllDecorator('hg.admin')
def settings_automation(self):
c = self.load_default_context()
c.active = 'automation'
return self._get_template_context(c)
@LoginRequired()
@HasPermissionAllDecorator('hg.admin')
def settings_labs(self):

View file

@ -413,6 +413,7 @@ class RepoFilesView(RepoAppView):
archive_cache_disable = self.request.GET.get('no_cache')
d_cache = get_archival_cache_store(config=CONFIG)
# NOTE: we get the config to pass to a call to lazy-init the SAME type of cache on vcsserver
d_cache_conf = get_archival_config(config=CONFIG)

View file

@ -30,6 +30,9 @@ class PullRequestEvent(RepoEvent):
:param pullrequest: a :class:`PullRequest` instance
"""
name = 'pullrequest-event'
display_name = lazy_ugettext('pullrequest generic event')
description = lazy_ugettext('All events within a context of a pull request')
def __init__(self, pullrequest):
super().__init__(pullrequest.target_repo)

View file

@ -21,7 +21,7 @@ import logging
import datetime
from rhodecode.translation import lazy_ugettext
from rhodecode.model.db import User, Repository, Session
from rhodecode.model.db import User, Repository
from rhodecode.events.base import RhodeCodeIntegrationEvent
from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
@ -34,7 +34,7 @@ def _commits_as_dict(event, commit_ids, repos):
:param event: class calling this method
:param commit_ids: commits to get
:param repos: list of repos to check
:param repos: a list of repos to check
"""
from rhodecode.lib.utils2 import extract_mentioned_users
from rhodecode.lib.helpers import (
@ -154,11 +154,12 @@ def _issues_as_dict(commits):
class RepoEvent(RhodeCodeIntegrationEvent):
"""
Base class for events acting on a repository.
:param repo: a :class:`Repository` instance
"""
def __init__(self, repo):
"""
:param repo: a :class:`Repository` instance
"""
super().__init__()
self.repo = repo
@ -299,6 +300,9 @@ class RepoVCSEvent(RepoEvent):
"""
Base class for events triggered by the VCS
"""
name = ''
display_name = 'generic_vcs_event'
def __init__(self, repo_name, extras):
self.repo = Repository.get_by_repo_name(repo_name)
if not self.repo:

View file

@ -44,7 +44,7 @@ def run_task(task, *args, **kwargs):
import celery
log.debug('Got task `%s` for execution, celery mode enabled:%s', task, rhodecode.CELERY_ENABLED)
if task is None:
raise ValueError('Got non-existing task for execution')
raise ValueError(f'Got non-existing task: {task} for execution')
exec_mode = 'sync'
allow_async = True

View file

@ -19,9 +19,7 @@
import sqlalchemy
from sqlalchemy import UnicodeText
from sqlalchemy.ext.mutable import Mutable, \
MutableList as MutationList, \
MutableDict as MutationDict
from sqlalchemy.ext.mutable import Mutable, MutableList, MutableDict
from rhodecode.lib import ext_json
@ -30,7 +28,7 @@ class JsonRaw(str):
"""
Allows interacting with a JSON types field using a raw string.
For example::
For example:
db_instance = JsonTable()
db_instance.enabled = True
db_instance.json_data = JsonRaw('{"a": 4}')
@ -100,7 +98,7 @@ class MutationObj(Mutable):
return MutationList.coerce(key, value)
return value
def de_coerce(self):
def de_coerce(self) -> "MutationObj":
return self
@classmethod
@ -153,6 +151,16 @@ class MutationObj(Mutable):
propagate=True)
class MutationList(MutableList):
def de_coerce(self):
return list(self)
class MutationDict(MutableDict):
def de_coerce(self):
return dict(self)
def JsonType(impl=None, **kwargs):
"""
Helper for using a mutation obj, it allows to use .with_variant easily.

View file

@ -5378,14 +5378,14 @@ class ScheduleEntry(Base, BaseModel):
except ValueError:
return dict()
def _as_raw(self, val, indent=None):
def _as_raw(self, val, indent=False):
if hasattr(val, 'de_coerce'):
val = val.de_coerce()
if val:
if indent:
ext_json.formatted_json(val)
val = ext_json.formatted_str_json(val)
else:
val = ext_json.json.dumps(val)
val = ext_json.str_json(val)
return val
@ -5393,10 +5393,10 @@ class ScheduleEntry(Base, BaseModel):
def schedule_definition_raw(self):
return self._as_raw(self.schedule_definition)
def args_raw(self, indent=None):
def args_raw(self, indent=False):
return self._as_raw(self.task_args, indent)
def kwargs_raw(self, indent=None):
def kwargs_raw(self, indent=False):
return self._as_raw(self.task_kwargs, indent)
def __repr__(self):
@ -5609,6 +5609,19 @@ class FileStore(Base, BaseModel):
nullable=True, unique=None, default=None)
repo_group = relationship('RepoGroup', lazy='joined')
@classmethod
def get_scope(cls, scope_type, scope_id):
if scope_type == 'repo':
return f'repo:{scope_id}'
elif scope_type == 'repo-group':
return f'repo-group:{scope_id}'
elif scope_type == 'user':
return f'user:{scope_id}'
elif scope_type == 'user-group':
return f'user-group:{scope_id}'
else:
return scope_type
@classmethod
def get_by_store_uid(cls, file_store_uid, safe=False):
if safe:

View file

@ -25,7 +25,7 @@ import colander
from rhodecode.translation import _
from rhodecode.lib.utils2 import glob2re
from rhodecode.lib.str_utils import safe_str
from rhodecode.lib.ext_json import json
from rhodecode.lib.ext_json import json, sjson
log = logging.getLogger(__name__)
@ -152,8 +152,9 @@ def json_validator(node, value):
def json_validator_with_exc(node, value):
try:
json.loads(value)
except (Exception,) as e:
msg = _(f'Please enter a valid json object: `{e}`')
msg = _(f'Please enter a valid json object type={type(value)}: `{e}`')
raise colander.Invalid(node, msg)

View file

@ -20,6 +20,8 @@ function registerRCRoutes() {
pyroutes.register('admin_artifacts_update', '/_admin/artifacts/%(uid)s/update', ['uid']);
pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
pyroutes.register('admin_automation', '/_admin/automation', []);
pyroutes.register('admin_automation_update', '/_admin/automation/%(entry_id)s/update', ['entry_id']);
pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
pyroutes.register('admin_home', '/_admin', []);
@ -37,9 +39,9 @@ function registerRCRoutes() {
pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
pyroutes.register('admin_scheduler', '/_admin/scheduler', []);
pyroutes.register('admin_scheduler_show_tasks', '/_admin/scheduler/_tasks', []);
pyroutes.register('admin_settings', '/_admin/settings', []);
pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
@ -66,14 +68,12 @@ function registerRCRoutes() {
pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
pyroutes.register('admin_settings_scheduler_create', '/_admin/scheduler/create', []);
pyroutes.register('admin_settings_scheduler_delete', '/_admin/scheduler/%(schedule_id)s/delete', ['schedule_id']);
pyroutes.register('admin_settings_scheduler_edit', '/_admin/scheduler/%(schedule_id)s', ['schedule_id']);
pyroutes.register('admin_settings_scheduler_execute', '/_admin/scheduler/%(schedule_id)s/execute', ['schedule_id']);
pyroutes.register('admin_settings_scheduler_new', '/_admin/scheduler/new', []);
pyroutes.register('admin_settings_scheduler_update', '/_admin/scheduler/%(schedule_id)s/update', ['schedule_id']);
pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
@ -308,7 +308,6 @@ function registerRCRoutes() {
pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
@ -362,6 +361,7 @@ function registerRCRoutes() {
pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
pyroutes.register('repo_settings_quick_actions', '/%(repo_name)s/settings/quick-action', ['repo_name']);
pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);

View file

@ -27,7 +27,10 @@
</div>
<div class="panel-body">
<h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
<p>
Artifacts are a binary file storage within RhodeCode that allows asset management next to version control system with fine-grained access control.
This functionality allows release builds or other types of binary asset to be stored and managed by RhodeCode.
</p>
</div>
</div>

View file

@ -0,0 +1,37 @@
<%inherit file="/base/base.mako"/>
<%def name="title()">
${_('Artifacts Admin')}
%if c.rhodecode_name:
&middot; ${h.branding(c.rhodecode_name)}
%endif
</%def>
<%def name="breadcrumbs_links()"></%def>
<%def name="menu_bar_nav()">
${self.menu_items(active='admin')}
</%def>
<%def name="menu_bar_subnav()">
${self.admin_menu(active='automation')}
</%def>
<%def name="main()">
<div class="box">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">${_('Automation Administration.')}</h3>
</div>
<div class="panel-body">
<h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
<img alt="admin-automation" style="width: 100%; height: 100%" src="${h.asset('images/ee_features/admin_automation.png')}"/>
</div>
</div>
</div>
</%def>

View file

@ -39,7 +39,7 @@
% if c.rhodecode_user.username != h.DEFAULT_USER:
<div class="pull-right">
<a class="btn btn-primary" href="${h.route_path('gists_new')}" >
${_(u'Create New Gist')}
${_('Create New Gist')}
</a>
</div>
% endif

View file

@ -61,7 +61,7 @@
create_url = h.route_path('global_integrations_new')
%>
<p class="pull-right">
<a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
<a href="${create_url}" class="btn btn-small btn-success">${_('Create new integration')}</a>
</p>
<table class="rctable integrations">
@ -99,7 +99,7 @@
%>
%endif
<a href="${create_url}">${_(u'Create one')}</a>
<a href="${create_url}">${_('Create one')}</a>
</td>
</tr>
%endif

View file

@ -39,7 +39,7 @@
<td class="delegated-admin-repo-groups">${len(c.auth_user.repository_groups_admin)}</td>
<td>
% if c.can_create_repo_group:
<a href="${h.route_path('repo_group_new')}" class="">${_(u'Add Repository Group')}</a>
<a href="${h.route_path('repo_group_new')}" class="">${_('Add Repository Group')}</a>
% endif
</td>
</tr>
@ -48,7 +48,7 @@
<td class="delegated-admin-user-groups">${len(c.auth_user.user_groups_admin)}</td>
<td>
% if c.can_create_user_group:
<a href="${h.route_path('user_groups_new')}" class="">${_(u'Add User Group')}</a>
<a href="${h.route_path('user_groups_new')}" class="">${_('Add User Group')}</a>
% endif
</td>
</tr>

View file

@ -27,7 +27,7 @@
<ul class="links">
%if c.can_create_repo_group:
<li>
<a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
<a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-success">${_('Add Repository Group')}</a>
</li>
%endif
</ul>

View file

@ -27,7 +27,7 @@
<ul class="links">
%if c.can_create_repo:
<li>
<a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_(u'Add Repository')}</a>
<a href="${h.route_path('repo_new')}" class="btn btn-small btn-success">${_('Add Repository')}</a>
</li>
%endif
</ul>

View file

@ -0,0 +1,39 @@
<%inherit file="/base/base.mako"/>
<%def name="title()">
${_('Artifacts Admin')}
%if c.rhodecode_name:
&middot; ${h.branding(c.rhodecode_name)}
%endif
</%def>
<%def name="breadcrumbs_links()"></%def>
<%def name="menu_bar_nav()">
${self.menu_items(active='admin')}
</%def>
<%def name="menu_bar_subnav()">
${self.admin_menu(active='scheduler')}
</%def>
<%def name="main()">
<div class="box">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">${_('Scheduler Administration.')}</h3>
</div>
<div class="panel-body">
<h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
<p>
Scheduler enables management of automation tasks, and defining new custom cron-like actions to be executed within RhodeCode system.
</p>
</div>
</div>
</div>
</%def>

View file

@ -1,9 +0,0 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">${_('Admin Automation')}</h3>
</div>
<div class="panel-body">
<h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
<img style="width: 100%; height: 100%" src="${h.asset('images/ee_features/admin_automation.png')}"/>
</div>
</div>

View file

@ -27,7 +27,7 @@
<ul class="links">
%if c.can_create_user_group:
<li>
<a href="${h.route_path('user_groups_new')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
<a href="${h.route_path('user_groups_new')}" class="btn btn-small btn-success">${_('Add User Group')}</a>
</li>
%endif
</ul>

View file

@ -27,7 +27,7 @@
<ul class="links">
<li>
<a href="${h.route_path('users_new')}" class="btn btn-small btn-success">${_(u'Add User')}</a>
<a href="${h.route_path('users_new')}" class="btn btn-small btn-success">${_('Add User')}</a>
</li>
</ul>
</div>

View file

@ -107,7 +107,7 @@
<ul id="context-pages" class="navigation horizontal-list">
## super-admin case
## super-admin case (Top Menu)
% if c.is_super_admin:
<li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
<li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
@ -115,6 +115,8 @@
<li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
<li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
<li class="${h.is_active('artifacts', active)}"><a href="${h.route_path('admin_artifacts')}">${_('Artifacts')}</a></li>
<li class="${h.is_active('automation', active)}"><a href="${h.route_path('admin_automation')}">${_('Automation')}</a></li>
<li class="${h.is_active('scheduler', active)}"><a href="${h.route_path('admin_scheduler')}">${_('Scheduler')}</a></li>
<li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
<li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
<li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
@ -540,7 +542,7 @@
% if can_create_repo_groups_in_group:
<li>
<a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
<a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository Group')}</a>
</li>
% endif
% endif
@ -568,12 +570,12 @@
% if can_create_repo_groups:
<li>
<a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
<a href="${h.route_path('repo_group_new')}" >${_('New Repository Group')}</a>
</li>
% endif
<li>
<a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
<a href="${h.route_path('gists_new')}">${_('New Gist')}</a>
</li>
</ol>

View file

@ -0,0 +1,37 @@
<%inherit file="/base/base.mako"/>
<%def name="title()">
${_('Artifacts Admin')}
%if c.rhodecode_name:
&middot; ${h.branding(c.rhodecode_name)}
%endif
</%def>
<%def name="breadcrumbs_links()"></%def>
<%def name="menu_bar_nav()">
${self.menu_items(active='admin')}
</%def>
<%def name="menu_bar_subnav()">
${self.admin_menu(active='automation')}
</%def>
<%def name="main()">
<div class="box">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">${_('Automation Administration.')}</h3>
</div>
<div class="panel-body">
<h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
<img style="width: 100%; height: 100%" src="${h.asset('images/ee_features/admin_automation.png')}"/>
</div>
</div>
</div>
</%def>