Merge branch 'tagview'
This commit is contained in:
commit
3c305beeea
160 changed files with 151133 additions and 497 deletions
|
@ -401,6 +401,7 @@ class ImageStorage(BaseModel):
|
|||
command = TextField(null=True)
|
||||
image_size = BigIntegerField(null=True)
|
||||
uncompressed_size = BigIntegerField(null=True)
|
||||
aggregate_size = BigIntegerField(null=True)
|
||||
uploading = BooleanField(default=True, null=True)
|
||||
|
||||
|
||||
|
|
|
@ -1450,6 +1450,18 @@ def set_image_size(docker_image_id, namespace_name, repository_name, image_size,
|
|||
|
||||
image.storage.image_size = image_size
|
||||
image.storage.uncompressed_size = uncompressed_size
|
||||
|
||||
ancestors = image.ancestors.split('/')[1:-1]
|
||||
if ancestors:
|
||||
try:
|
||||
parent_image = Image.get(Image.id == ancestors[-1])
|
||||
total_size = image_size + parent_image.storage.aggregate_size
|
||||
image.storage.aggregate_size = total_size
|
||||
except Image.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
image.storage.aggregate_size = image_size
|
||||
|
||||
image.storage.save()
|
||||
|
||||
return image
|
||||
|
@ -2072,11 +2084,14 @@ def get_repository_build(build_uuid):
|
|||
|
||||
|
||||
def list_repository_builds(namespace_name, repository_name, limit,
|
||||
include_inactive=True):
|
||||
include_inactive=True, since=None):
|
||||
query = (_get_build_base_query()
|
||||
.where(Repository.name == repository_name, Namespace.username == namespace_name)
|
||||
.limit(limit))
|
||||
|
||||
if since is not None:
|
||||
query = query.where(RepositoryBuild.started >= since)
|
||||
|
||||
if not include_inactive:
|
||||
query = query.where(RepositoryBuild.phase != 'error',
|
||||
RepositoryBuild.phase != 'complete')
|
||||
|
@ -2209,8 +2224,8 @@ def log_action(kind_name, user_or_organization_name, performer=None,
|
|||
kind = LogEntryKind.get(LogEntryKind.name == kind_name)
|
||||
account = User.get(User.username == user_or_organization_name)
|
||||
LogEntry.create(kind=kind, account=account, performer=performer,
|
||||
repository=repository, ip=ip,
|
||||
metadata_json=json.dumps(metadata), datetime=timestamp)
|
||||
repository=repository, ip=ip, metadata_json=json.dumps(metadata),
|
||||
datetime=timestamp)
|
||||
|
||||
|
||||
def create_build_trigger(repo, service_name, auth_token, user, pull_robot=None):
|
||||
|
@ -2525,6 +2540,26 @@ def cancel_repository_build(build, work_queue):
|
|||
build.delete_instance()
|
||||
return True
|
||||
|
||||
def get_repository_pushes(repository, time_delta):
|
||||
since = date.today() - time_delta
|
||||
push_repo = LogEntryKind.get(name = 'push_repo')
|
||||
return (LogEntry.select()
|
||||
.where(LogEntry.repository == repository)
|
||||
.where(LogEntry.kind == push_repo)
|
||||
.where(LogEntry.datetime >= since)
|
||||
.count())
|
||||
|
||||
def get_repository_pulls(repository, time_delta):
|
||||
since = date.today() - time_delta
|
||||
repo_pull = LogEntryKind.get(name = 'pull_repo')
|
||||
repo_verb = LogEntryKind.get(name = 'repo_verb')
|
||||
return (LogEntry.select()
|
||||
.where(LogEntry.repository == repository)
|
||||
.where((LogEntry.kind == repo_pull) | (LogEntry.kind == repo_verb))
|
||||
.where(LogEntry.datetime >= since)
|
||||
.count())
|
||||
|
||||
|
||||
def get_repository_usage():
|
||||
one_month_ago = date.today() - timedelta(weeks=4)
|
||||
repo_pull = LogEntryKind.get(name = 'pull_repo')
|
||||
|
|
|
@ -148,12 +148,17 @@ class RepositoryBuildList(RepositoryParamResource):
|
|||
@require_repo_read
|
||||
@parse_args
|
||||
@query_param('limit', 'The maximum number of builds to return', type=int, default=5)
|
||||
@query_param('since', 'Returns all builds since the given unix timecode', type=int, default=None)
|
||||
@nickname('getRepoBuilds')
|
||||
def get(self, args, namespace, repository):
|
||||
""" Get the list of repository builds. """
|
||||
limit = args['limit']
|
||||
builds = list(model.list_repository_builds(namespace, repository, limit))
|
||||
limit = args.get('limit', 5)
|
||||
since = args.get('since', None)
|
||||
|
||||
if since is not None:
|
||||
since = datetime.datetime.utcfromtimestamp(since)
|
||||
|
||||
builds = model.list_repository_builds(namespace, repository, limit, since=since)
|
||||
can_write = ModifyRepositoryPermission(namespace, repository).can()
|
||||
return {
|
||||
'builds': [build_status_view(build, can_write) for build in builds]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import logging
|
||||
import json
|
||||
import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from flask import request
|
||||
|
||||
|
@ -177,7 +179,9 @@ class Repository(RepositoryParamResource):
|
|||
def tag_view(tag):
|
||||
return {
|
||||
'name': tag.name,
|
||||
'image_id': tag.image.docker_image_id
|
||||
'image_id': tag.image.docker_image_id,
|
||||
'last_modified': format_date(datetime.datetime.fromtimestamp(tag.lifetime_start_ts)),
|
||||
'size': tag.image.storage.aggregate_size
|
||||
}
|
||||
|
||||
organization = None
|
||||
|
@ -196,6 +200,9 @@ class Repository(RepositoryParamResource):
|
|||
active_builds = model.list_repository_builds(namespace, repository, 1,
|
||||
include_inactive=False)
|
||||
|
||||
is_starred = (model.repository_is_starred(get_authenticated_user(), repo)
|
||||
if get_authenticated_user() else False)
|
||||
|
||||
return {
|
||||
'namespace': namespace,
|
||||
'name': repository,
|
||||
|
@ -206,7 +213,18 @@ class Repository(RepositoryParamResource):
|
|||
'is_public': is_public,
|
||||
'is_building': len(list(active_builds)) > 0,
|
||||
'is_organization': bool(organization),
|
||||
'status_token': repo.badge_token if not is_public else ''
|
||||
'is_starred': is_starred,
|
||||
'status_token': repo.badge_token if not is_public else '',
|
||||
'stats': {
|
||||
'pulls': {
|
||||
'today': model.get_repository_pulls(repo, timedelta(days=1)),
|
||||
'thirty_day': model.get_repository_pulls(repo, timedelta(days=30))
|
||||
},
|
||||
'pushes': {
|
||||
'today': model.get_repository_pushes(repo, timedelta(days=1)),
|
||||
'thirty_day': model.get_repository_pushes(repo, timedelta(days=30))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
raise NotFound()
|
||||
|
|
|
@ -247,6 +247,7 @@ def github_oauth_attach():
|
|||
|
||||
|
||||
@callback.route('/github/callback/trigger/<path:repository>', methods=['GET'])
|
||||
@callback.route('/github/callback/trigger/<path:repository>/__new', methods=['GET'])
|
||||
@route_show_if(features.GITHUB_BUILD)
|
||||
@require_session_login
|
||||
@parse_repository_name
|
||||
|
@ -260,9 +261,18 @@ def attach_github_build_trigger(namespace, repository):
|
|||
abort(404, message=msg)
|
||||
|
||||
trigger = model.create_build_trigger(repo, 'github', token, current_user.db_user())
|
||||
|
||||
# TODO(jschorr): Remove once the new layout is in place.
|
||||
admin_path = '%s/%s/%s' % (namespace, repository, 'admin')
|
||||
full_url = '%s%s%s' % (url_for('web.repository', path=admin_path), '?tab=trigger&new_trigger=',
|
||||
trigger.uuid)
|
||||
|
||||
if '__new' in request.url:
|
||||
repo_path = '%s/%s' % (namespace, repository)
|
||||
full_url = '%s%s%s' % (url_for('web.repository', path=repo_path), '?tab=builds&newtrigger=',
|
||||
trigger.uuid)
|
||||
|
||||
|
||||
logger.debug('Redirecting to full url: %s' % full_url)
|
||||
return redirect(full_url)
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ from app import app, oauth_apps, dockerfile_build_queue, LoginWrappedDBUser
|
|||
|
||||
from auth.permissions import QuayDeferredPermissionUser
|
||||
from auth import scopes
|
||||
from auth.auth_context import get_authenticated_user
|
||||
from endpoints.api.discovery import swagger_route_data
|
||||
from werkzeug.routing import BaseConverter
|
||||
from functools import wraps
|
||||
|
@ -225,7 +226,8 @@ def start_build(repository, dockerfile_id, tags, build_name, subdir, manual,
|
|||
'registry': host,
|
||||
'build_subdir': subdir,
|
||||
'trigger_metadata': trigger_metadata or {},
|
||||
'is_manual': manual
|
||||
'is_manual': manual,
|
||||
'manual_user': get_authenticated_user().username if get_authenticated_user() else None
|
||||
}
|
||||
|
||||
with app.config['DB_TRANSACTION_FACTORY'](db):
|
||||
|
@ -250,7 +252,8 @@ def start_build(repository, dockerfile_id, tags, build_name, subdir, manual,
|
|||
'repo': repository.name,
|
||||
'namespace': repository.namespace_user.username,
|
||||
'fileid': dockerfile_id,
|
||||
'manual': manual,
|
||||
'is_manual': manual,
|
||||
'manual_user': get_authenticated_user().username if get_authenticated_user() else None
|
||||
}
|
||||
|
||||
if trigger:
|
||||
|
@ -267,7 +270,8 @@ def start_build(repository, dockerfile_id, tags, build_name, subdir, manual,
|
|||
'build_id': build_request.uuid,
|
||||
'build_name': build_name,
|
||||
'docker_tags': tags,
|
||||
'is_manual': manual
|
||||
'is_manual': manual,
|
||||
'manual_user': get_authenticated_user().username if get_authenticated_user() else None
|
||||
}
|
||||
|
||||
if trigger:
|
||||
|
|
10
initdb.py
10
initdb.py
|
@ -75,7 +75,7 @@ def __create_subtree(repo, structure, creator_username, parent):
|
|||
new_image.storage.checksum = checksum
|
||||
new_image.storage.save()
|
||||
|
||||
creation_time = REFERENCE_DATE + timedelta(days=image_num)
|
||||
creation_time = REFERENCE_DATE + timedelta(weeks=image_num) + timedelta(days=i)
|
||||
command_list = SAMPLE_CMDS[image_num % len(SAMPLE_CMDS)]
|
||||
command = json.dumps(command_list) if command_list else None
|
||||
new_image = model.set_image_metadata(docker_image_id, repo.namespace_user.username, repo.name,
|
||||
|
@ -364,6 +364,14 @@ def populate_database():
|
|||
__generate_repository(new_user_1, 'simple', 'Simple repository.', False,
|
||||
[], (4, [], ['latest', 'prod']))
|
||||
|
||||
__generate_repository(new_user_1, 'sharedtags',
|
||||
'Shared tags repository',
|
||||
False, [(new_user_2, 'read'), (dtrobot[0], 'read')],
|
||||
(2, [(3, [], ['v2.0', 'v2.1', 'v2.2']),
|
||||
(1, [(1, [(1, [], ['prod', '581a284'])],
|
||||
['staging', '8423b58']),
|
||||
(1, [], None)], None)], None))
|
||||
|
||||
__generate_repository(new_user_1, 'history', 'Historical repository.', False,
|
||||
[], (4, [(2, [], 'latest'), (3, [], '#latest')], None))
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
width: 100%;
|
||||
display: table-cell;
|
||||
float: none;
|
||||
padding: 20px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.co-tabs li {
|
||||
|
@ -190,7 +190,7 @@
|
|||
width: 24px;
|
||||
}
|
||||
|
||||
.co-panel .co-panel-heading i.fa {
|
||||
.co-panel .co-panel-heading > i.fa {
|
||||
margin-right: 6px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
|
@ -754,3 +754,159 @@
|
|||
vertical-align: middle;
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
|
||||
.co-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.co-table td {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.co-table thead td {
|
||||
text-transform: uppercase;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.co-table thead td a {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.co-table thead td:after {
|
||||
content: "\f175";
|
||||
font-family: FontAwesome;
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.co-table thead td.current:after {
|
||||
content: "\f175";
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.co-table thead td.current.reversed:after {
|
||||
content: "\f176";
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.co-table thead td.current a {
|
||||
color: #337ab7;
|
||||
}
|
||||
|
||||
.co-table .checkbox-col {
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.co-table td.options-col {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.co-table .add-row td {
|
||||
border-top: 2px solid #eee;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.cor-checkable-menu {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.co-checkable-menu .co-checkable-menu-state {
|
||||
display: inline-block;
|
||||
margin-left: -1px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.co-checkable-menu .dropdown {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.co-checkable-item, .co-checkable-menu-state {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #ddd;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
position: relative
|
||||
}
|
||||
|
||||
.co-checkable-item:after, .co-checkable-menu-state:after {
|
||||
content: "\f00c";
|
||||
font-family: FontAwesome;
|
||||
color: #ccc;
|
||||
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 1px;
|
||||
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.co-checkable-menu-state.some:after {
|
||||
content: "-";
|
||||
font-size: 35px;
|
||||
top: -19px;
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
.co-checkable-item:hover:after {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.co-checkable-item.checked:after, .co-checkable-menu-state.all:after, .co-checkable-menu-state.some:after {
|
||||
visibility: visible;
|
||||
color: #428bca;
|
||||
}
|
||||
|
||||
.co-table .co-checkable-row.checked {
|
||||
background: #F6FCFF;
|
||||
}
|
||||
|
||||
.co-check-bar {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.co-check-bar .co-checked-actions {
|
||||
display: inline-block;
|
||||
border-left: 1px solid #eee;
|
||||
margin-left: 10px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.co-check-bar .co-checked-actions .btn {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.co-check-bar .co-checked-actions .btn .fa {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.co-check-bar .co-filter-box {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.co-check-bar .co-filter-box input {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.empty-primary-msg {
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-secondary-msg {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
|
12
static/css/directives/repo-view/repo-panel-builds.css
Normal file
12
static/css/directives/repo-view/repo-panel-builds.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
.repo-panel-builds .status-col {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.repo-panel-builds .building-tag {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.repo-panel-builds .building-tag .fa {
|
||||
margin-right: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
28
static/css/directives/repo-view/repo-panel-changes.css
Normal file
28
static/css/directives/repo-view/repo-panel-changes.css
Normal file
|
@ -0,0 +1,28 @@
|
|||
.repo-panel-changes .tab-header {
|
||||
margin-bottom: 30px !important;
|
||||
}
|
||||
|
||||
.repo-panel-changes .side-panel-title {
|
||||
font-size: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.repo-panel-changes .side-panel-title i.fa {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.repo-panel-changes .side-panel {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.repo-panel-changes .visualized-tag {
|
||||
font-size: 18px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.repo-panel-changes .visualized-tag .fa {
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
63
static/css/directives/repo-view/repo-panel-info.css
Normal file
63
static/css/directives/repo-view/repo-panel-info.css
Normal file
|
@ -0,0 +1,63 @@
|
|||
.repo-panel-info-element .stat-col {
|
||||
border-right: 2px solid #eee;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .stat-title {
|
||||
text-align: center;
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .stat {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .stat .stat-value {
|
||||
font-size: 46px;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .stat .stat-subtitle {
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .description-container {
|
||||
margin-top: 30px;
|
||||
border-top: 2px solid #eee;
|
||||
padding-top: 18px;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .description {
|
||||
padding-bottom: 0px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .description p {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .description-container h4:before {
|
||||
content: "\f02d";
|
||||
color: #black;
|
||||
font-size: 20px;
|
||||
font-family: FontAwesome;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .description .fa-edit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .build-mini-status {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.repo-panel-info-element .view-all {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
25
static/css/directives/repo-view/repo-panel-settings.css
Normal file
25
static/css/directives/repo-view/repo-panel-settings.css
Normal file
|
@ -0,0 +1,25 @@
|
|||
.repo-panel-settings-element .panel-section {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.repo-panel-settings-element .lock-section {
|
||||
position: relative;
|
||||
padding-left: 80px;
|
||||
}
|
||||
|
||||
.repo-panel-settings-element .lock-section .lock-icon {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 22px;
|
||||
color: #ccc;
|
||||
font-size: 46px;
|
||||
}
|
||||
|
||||
.repo-panel-settings-element .panel-section .btn {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.repo-panel-settings-element .panel-section .btn .fa {
|
||||
margin-right: 6px;
|
||||
}
|
65
static/css/directives/repo-view/repo-panel-tags.css
Normal file
65
static/css/directives/repo-view/repo-panel-tags.css
Normal file
|
@ -0,0 +1,65 @@
|
|||
.repo-panel-tags-element .fa-tag {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .empty {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .image-track {
|
||||
width: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .image-track-dot {
|
||||
display: inline-block;
|
||||
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
background: white;
|
||||
z-index: 300;
|
||||
|
||||
border: 2px solid black;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .image-track-line {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: -1px;
|
||||
left: 7px;
|
||||
width: 0px;
|
||||
display: inline-block;
|
||||
border-left: 2px solid black;
|
||||
|
||||
display: none;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .image-track-line.start {
|
||||
top: 18px;
|
||||
height: 24px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .image-track-line.middle {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .image-track-line.end {
|
||||
top: -1px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .image-id-col {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .options-col {
|
||||
padding-left: 20px;
|
||||
}
|
|
@ -31,4 +31,8 @@
|
|||
|
||||
.build-info-bar-element .build-side-info .timing {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.build-info-bar-element .source-commit-link {
|
||||
margin-left: 4px;
|
||||
}
|
33
static/css/directives/ui/build-mini-status.css
Normal file
33
static/css/directives/ui/build-mini-status.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
.build-mini-status {
|
||||
display: block;
|
||||
padding: 4px;
|
||||
position: relative;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.build-mini-status-element {
|
||||
color: black;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.build-mini-status .timing {
|
||||
display: inline-block;
|
||||
margin-left: 30px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.build-mini-status .timing .fa {
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.build-mini-status .build-description {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
left: 250px;
|
||||
bottom: 4px;
|
||||
line-height: 33px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
19
static/css/directives/ui/build-state-icon.css
Normal file
19
static/css/directives/ui/build-state-icon.css
Normal file
|
@ -0,0 +1,19 @@
|
|||
.build-state-icon {
|
||||
width: 42px;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.build-state-icon .error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.build-state-icon .internalerror {
|
||||
color: #DFFF00;
|
||||
}
|
||||
|
||||
.build-state-icon .complete {
|
||||
color: #2fcc66;
|
||||
}
|
15
static/css/directives/ui/filter-control.css
Normal file
15
static/css/directives/ui/filter-control.css
Normal file
|
@ -0,0 +1,15 @@
|
|||
.filter-control {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.filter-control a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.filter-control .not-selected a {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.filter-control .selected {
|
||||
font-weight: bold;
|
||||
}
|
47
static/css/directives/ui/image-changes-view.css
Normal file
47
static/css/directives/ui/image-changes-view.css
Normal file
|
@ -0,0 +1,47 @@
|
|||
.image-changes-view .change-count {
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.image-changes-view .change-count b {
|
||||
font-weight: normal;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.image-changes-view .changes-container .well {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.image-changes-view .changes-container i.fa-plus-square {
|
||||
color: rgb(73, 209, 73);
|
||||
}
|
||||
|
||||
.image-changes-view .changes-container i.fa-minus-square {
|
||||
color: rgb(209, 73, 73);
|
||||
}
|
||||
|
||||
.image-changes-view .changes-container i.fa-pencil-square {
|
||||
color: rgb(73, 100, 209);
|
||||
}
|
||||
|
||||
.image-changes-view .change-count:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.image-changes-view .change-count i {
|
||||
font-size: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.image-changes-view .change i {
|
||||
float: right;
|
||||
vertical-align: middle;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.image-changes-view .more-changes {
|
||||
padding: 6px;
|
||||
text-align: right;
|
||||
}
|
68
static/css/directives/ui/image-info-sidebar.css
Normal file
68
static/css/directives/ui/image-info-sidebar.css
Normal file
|
@ -0,0 +1,68 @@
|
|||
.image-info-sidebar .image-comment {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.image-info-sidebar .image-section {
|
||||
margin-top: 12px;
|
||||
padding-bottom: 2px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-info-sidebar .image-section .tag {
|
||||
margin: 2px;
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.image-info-sidebar .image-section .section-icon {
|
||||
float: left;
|
||||
font-size: 16px;
|
||||
margin-left: -4px;
|
||||
margin-right: 14px;
|
||||
color: #bbb;
|
||||
width: 18px;
|
||||
text-align: center;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.image-info-sidebar .image-section .section-info {
|
||||
padding: 4px;
|
||||
padding-left: 6px;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.05);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.05);
|
||||
background-color: #f5f5f5;
|
||||
|
||||
vertical-align: middle;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.image-info-sidebar .image-section .section-info-with-dropdown {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.image-info-sidebar .image-section .dropdown {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 2px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.image-info-sidebar .image-section .dropdown-button {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
|
||||
background: white;
|
||||
padding: 4px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
border: 1px solid #eee;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
|
@ -30,19 +30,6 @@
|
|||
right: 2px;
|
||||
}
|
||||
|
||||
.empty-primary-msg {
|
||||
font-size: 18px;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-secondary-msg {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.repo-list-title {
|
||||
margin-bottom: 30px;
|
||||
margin-top: 10px;
|
||||
|
@ -86,7 +73,7 @@
|
|||
background: linear-gradient(to bottom, rgba(255,240,188,1) 0%,rgba(255,255,255,0.5) 5%,rgba(255,255,255,0.49) 51%,rgba(255,255,255,0) 100%); /* W3C */
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
.repo-list-grid .star-icon {
|
||||
color: #ddd;
|
||||
display: block;
|
||||
font-size: 1.2em;
|
||||
|
@ -94,15 +81,6 @@
|
|||
line-height: 2em;
|
||||
}
|
||||
|
||||
.star-icon:hover {
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
.star-icon.starred {
|
||||
color: #ffba6d;
|
||||
}
|
||||
|
||||
.new-repo-listing {
|
||||
display: block;
|
||||
font-size: 14px;
|
8
static/css/directives/ui/repo-star.css
Normal file
8
static/css/directives/ui/repo-star.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
.repo-star .star-icon:hover {
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
|
||||
.repo-star .star-icon.starred {
|
||||
color: #ffba6d;
|
||||
}
|
3
static/css/directives/ui/repository-events-table.css
Normal file
3
static/css/directives/ui/repository-events-table.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.repository-events-table-element .notification-row i.fa {
|
||||
margin-right: 6px;
|
||||
}
|
5
static/css/directives/ui/tag-info-sidebar.css
Normal file
5
static/css/directives/ui/tag-info-sidebar.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
.tag-info-sidebar .control-bar {
|
||||
padding-top: 10px;
|
||||
margin-top: 10px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
|
@ -41,4 +41,12 @@
|
|||
|
||||
.triggered-build-description-element .commit-who:before {
|
||||
content: "by ";
|
||||
}
|
||||
|
||||
.triggered-build-description-element .manual {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.triggered-build-description-element .fa-user {
|
||||
margin-right: 4px;
|
||||
}
|
41
static/css/pages/repo-view.css
Normal file
41
static/css/pages/repo-view.css
Normal file
|
@ -0,0 +1,41 @@
|
|||
.repository-view .repo-circle {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.repository-view .cor-title-content .repo-star {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.repository-view .repo-star {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.repository-view .tab-header {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.repository-view .tab-header-controls {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.repository-view .tab-header-controls .btn .fa {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.repository-view .heading-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.repository-view .heading-controls {
|
||||
font-size: 14px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.repository-view .heading-controls .btn {
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.repository-view .heading-controls .btn .fa {
|
||||
margin-right: 6px;
|
||||
}
|
|
@ -324,6 +324,10 @@ nav.navbar-default .navbar-nav>li>a.active {
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.resource-view.element .resource-content.hidden .tab-content > .active {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.resource-view-element .resource-content.visible {
|
||||
z-index: 2;
|
||||
visibility: visible;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!-- Side information -->
|
||||
<div class="build-side-info">
|
||||
<!-- Build ID -->
|
||||
<div class="build-side-id">{{ build.id }}</div>
|
||||
<div class="build-side-id" ng-if="!hideId">{{ build.id }}</div>
|
||||
|
||||
<!-- Timing -->
|
||||
<div class="timing" ng-if="showTime">
|
||||
|
|
10
static/directives/build-mini-status.html
Normal file
10
static/directives/build-mini-status.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
<a class="build-mini-status-element" href="/repository/{{ build.repository.namespace }}/{{ build.repository.name }}/build/{{ build.id }}">
|
||||
<div>
|
||||
<span class="build-state-icon" build="build"></span>
|
||||
<span class="timing">
|
||||
<i class="fa fa-clock-o"></i><span am-time-ago="build.started || 0"></span>
|
||||
</span>
|
||||
|
||||
<div class="build-description triggered-build-description" build="build"></div>
|
||||
</div>
|
||||
</a>
|
8
static/directives/build-state-icon.html
Normal file
8
static/directives/build-state-icon.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<span class="build-state-icon-element" ng-class="build.phase">
|
||||
<span class="cor-loader-inline" ng-if="isBuilding(build)"></span>
|
||||
<span ng-if="!isBuilding(build)">
|
||||
<i class="fa fa-check-circle" ng-if="build.phase == 'complete'"></i>
|
||||
<i class="fa fa-times-circle" ng-if="build.phase == 'error'"></i>
|
||||
<i class="fa fa-exclamation-circle" ng-if="build.phase == 'internalerror'"></i>
|
||||
</span>
|
||||
</span>
|
|
@ -1,3 +1,4 @@
|
|||
<!-- NOTE: DEPRECATED -->
|
||||
<div id="build-status-container" class="build-status-container">
|
||||
<div>
|
||||
<span class="phase-icon" ng-class="build.phase"></span>
|
||||
|
|
3
static/directives/cor-checkable-item.html
Normal file
3
static/directives/cor-checkable-item.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<span class="co-checkable-item" ng-click="toggleItem()"
|
||||
ng-class="controller.isChecked(item, controller.checked) ? 'checked': 'not-checked'">
|
||||
</span>
|
1
static/directives/cor-checkable-menu-item.html
Normal file
1
static/directives/cor-checkable-menu-item.html
Normal file
|
@ -0,0 +1 @@
|
|||
<li><a href="javascript:void(0)" ng-click="selected()"><span ng-transclude/></a></li>
|
12
static/directives/cor-checkable-menu.html
Normal file
12
static/directives/cor-checkable-menu.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<span class="co-checkable-menu">
|
||||
<span class="dropdown" style="text-align: left;">
|
||||
<span class="btn btn-default" data-toggle="dropdown">
|
||||
<span class="co-checkable-menu-state"
|
||||
ng-class="getClass(controller.items, controller.checked)"
|
||||
ng-click="toggleItems($event)">
|
||||
</span>
|
||||
<span class="caret"></span>
|
||||
</span>
|
||||
<ul class="dropdown-menu" ng-transclude></ul>
|
||||
</span>
|
||||
</span>
|
25
static/directives/cor-confirm-dialog.html
Normal file
25
static/directives/cor-confirm-dialog.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<div class="cor-confirm-dialog-element">
|
||||
<div class="modal fade co-dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" ng-show="!working"
|
||||
data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">{{ dialogTitle }}</h4>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="working">
|
||||
<div class="cor-loader"></div>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="!working">
|
||||
<span ng-transclude/>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="!working">
|
||||
<button type="button" class="btn btn-primary" ng-click="performAction()">
|
||||
{{ dialogActionTitle }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div>
|
|
@ -1,3 +1,3 @@
|
|||
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-6">
|
||||
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-1">
|
||||
<span class="co-nav-title-action co-fx-text-shadow" ng-transclude></span>
|
||||
</div>
|
|
@ -1 +1 @@
|
|||
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-6" ng-transclude></div>
|
||||
<div class="col-lg-3 col-md-3 col-sm-3 hidden-xs" ng-transclude></div>
|
3
static/directives/filter-control.html
Normal file
3
static/directives/filter-control.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<span class="filter-control-element" ng-class="filter == value ? 'selected': 'not-selected'">
|
||||
<a href="javascript:void(0)" ng-click="setFilter()"><span ng-transclude/></a>
|
||||
</span>
|
33
static/directives/image-changes-view.html
Normal file
33
static/directives/image-changes-view.html
Normal file
|
@ -0,0 +1,33 @@
|
|||
<div class="image-changes-view-element">
|
||||
<div class="resource-view" resource="imageChangesResource">
|
||||
<div class="changes-container" ng-show="hasChanges">
|
||||
<span class="change-count added"
|
||||
ng-show="changeData.added.length > 0"
|
||||
data-title="Files Added"
|
||||
data-placement="top"
|
||||
data-container="body"
|
||||
bs-tooltip>
|
||||
<i class="fa fa-plus-square"></i>
|
||||
<b>{{ changeData.added.length }}</b>
|
||||
</span>
|
||||
<span class="change-count removed"
|
||||
ng-show="changeData.removed.length > 0"
|
||||
data-title="Files Removed"
|
||||
data-placement="top"
|
||||
data-container="body"
|
||||
bs-tooltip>
|
||||
<i class="fa fa-minus-square"></i>
|
||||
<b>{{ changeData.removed.length }}</b>
|
||||
</span>
|
||||
<span class="change-count changed"
|
||||
ng-show="changeData.changed.length > 0"
|
||||
data-title="Files Changes"
|
||||
data-placement="top"
|
||||
data-container="body"
|
||||
bs-tooltip>
|
||||
<i class="fa fa-pencil-square"></i>
|
||||
<b>{{ changeData.changed.length }}</b>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
105
static/directives/image-info-sidebar.html
Normal file
105
static/directives/image-info-sidebar.html
Normal file
|
@ -0,0 +1,105 @@
|
|||
<div class="image-info-sidebar-element">
|
||||
<!-- Comment -->
|
||||
<div class="image-comment" ng-if="imageData.comment">
|
||||
<blockquote style="margin-top: 10px;">
|
||||
<span class="markdown-view" content="imageData.comment"></span>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<!-- Image ID -->
|
||||
<div class="image-section">
|
||||
<i class="fa fa-code section-icon" bs-tooltip="tooltip.title" data-title="Full Image ID"></i>
|
||||
<span class="section-info">
|
||||
<a class="image-link" ng-href="{{ tracker.imageLink(image) }}">
|
||||
{{ imageData.id }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
<div class="image-section">
|
||||
<i class="fa fa-tag section-icon" data-title="Current Tags" bs-tooltip></i>
|
||||
<span class="section-info section-info-with-dropdown">
|
||||
<a class="label tag label-default" ng-repeat="tag in imageData.tags"
|
||||
href="javascript:void(0)" ng-click="tagSelected({'tag': tag})">
|
||||
{{ tag }}
|
||||
</a>
|
||||
<span style="color: #ccc;" ng-if="!imageData.tags.length">(No Tags)</span>
|
||||
|
||||
<div class="dropdown" data-placement="top"
|
||||
ng-if="tracker.repository.can_write || imageData.tags">
|
||||
<a href="javascript:void(0)" class="dropdown-button" data-toggle="dropdown"
|
||||
bs-tooltip="tooltip.title" data-title="Manage Tags"
|
||||
data-container="body">
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li ng-repeat="tag in imageData.tags">
|
||||
<a href="javascript:void(0)" ng-click="tagSelected({'tag': tag})">
|
||||
<i class="fa fa-tag"></i>{{ tag }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider" role="presentation"
|
||||
ng-if="tracker.repository.can_write && imageData.tags"></li>
|
||||
<li>
|
||||
<a href="javascript:void(0)"
|
||||
ng-click="addTagRequested({'image': image})"
|
||||
ng-if="tracker.repository.can_write">
|
||||
<i class="fa fa-plus"></i>Add New Tag
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Command -->
|
||||
<div class="image-section" ng-if="imageData.command && imageData.command.length">
|
||||
<i class="fa fa-terminal section-icon" data-title="Image Command" bs-tooltip></i>
|
||||
<span class="section-info">
|
||||
<span class="formatted-command trimmed"
|
||||
data-html="true"
|
||||
data-title="{{ getTooltipCommand(imageData) }}"
|
||||
data-placement="top" bs-tooltip>{{ getFormattedCommand(imageData) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Created -->
|
||||
<div class="image-section">
|
||||
<i class="fa fa-calendar section-icon" data-title="Created" bs-tooltip></i>
|
||||
<span class="section-info">
|
||||
<dd am-time-ago="parseDate(imageData.created)"></dd>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Size -->
|
||||
<div class="image-section">
|
||||
<i class="fa fa-cloud-upload section-icon"
|
||||
data-title="The amount of data sent between Docker and the registry when pushing/pulling"
|
||||
bs-tooltip></i>
|
||||
<span class="section-info">{{ imageData.size | bytes }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Locations -->
|
||||
<div class="image-section">
|
||||
<i class="fa fa-map-marker section-icon"
|
||||
data-title="The geographic region(s) in which this image data is located"
|
||||
bs-tooltip></i>
|
||||
<span class="section-info">
|
||||
<span class="location-view" location="location"
|
||||
ng-repeat="location in imageData.locations"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Changes -->
|
||||
<div class="image-section" ng-show="hasImageChanges">
|
||||
<i class="fa fa-code-fork section-icon"
|
||||
data-title="File Changes"
|
||||
bs-tooltip></i>
|
||||
<span class="section-info">
|
||||
<div class="image-changes-view" repository="tracker.repository" image="image"
|
||||
has-changes="hasImageChanges"></div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
|
@ -1,7 +1,7 @@
|
|||
<div class="markdown-input-container">
|
||||
<p ng-class="'lead ' + (canWrite ? 'editable' : 'noteditable')" ng-click="editContent()">
|
||||
<span class="markdown-view" content="content"></span>
|
||||
<span class="empty" ng-show="!content && canWrite">(Click to set {{ fieldTitle }})</span>
|
||||
<span class="empty" ng-show="!content && canWrite">Click to set {{ fieldTitle }}</span>
|
||||
<i class="fa fa-edit"></i>
|
||||
</p>
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
</a>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-2 col-sm-2 col-xs-2">
|
||||
<i ng-class="repository.is_starred ? 'starred fa fa-star' : 'fa fa-star-o'" class="star-icon" ng-click="toggleStar({repository: repository})"></i>
|
||||
<span class="repo-star" repository="repository"
|
||||
star-toggled="starToggled({'repository': repository})"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description markdown-view" content="repository.description" first-line-only="true" placeholder-needed="true"></div>
|
||||
|
|
5
static/directives/repo-star.html
Normal file
5
static/directives/repo-star.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
<span class="repo-star-element">
|
||||
<i ng-class="repository.is_starred ? 'starred fa fa-star' : 'fa fa-star-o'"
|
||||
class="star-icon" ng-click="toggleStar()">
|
||||
</i>
|
||||
</span>
|
187
static/directives/repo-view/repo-panel-builds.html
Normal file
187
static/directives/repo-view/repo-panel-builds.html
Normal file
|
@ -0,0 +1,187 @@
|
|||
<div class="repo-panel-builds-element">
|
||||
<div class="tab-header-controls">
|
||||
<button class="btn btn-primary" ng-click="showNewBuildDialog()">
|
||||
<i class="fa fa-plus"></i>Start Build
|
||||
</button>
|
||||
</div>
|
||||
<h3 class="tab-header">Repository Builds</h3>
|
||||
|
||||
<!-- Builds -->
|
||||
<div class="co-panel">
|
||||
<!-- Builds header controls -->
|
||||
<div class="co-panel-heading">
|
||||
<div class="heading-controls hidden-sm hidden-xs">
|
||||
<span class="filter-control" filter="options.filter" value="recent">Recent Builds</span>
|
||||
<span class="filter-control" filter="options.filter" value="48hour">Last 48 Hours</span>
|
||||
<span class="filter-control" filter="options.filter" value="30day">Last 30 days</span>
|
||||
</div>
|
||||
<div class="heading-title">
|
||||
<i class="fa fa-tasks"></i>
|
||||
Build History
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Builds list content -->
|
||||
<div class="panel-body">
|
||||
<div class="resource-view" resource="buildsResource" error-message="'Could not load build information'">
|
||||
|
||||
<!-- No builds found -->
|
||||
<div class="empty" ng-if="!fullBuilds.length">
|
||||
<div class="empty-primary-msg">No matching builds found</div>
|
||||
<div class="empty-secondary-msg">
|
||||
Please change the filter above to search for more builds.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Builds list table -->
|
||||
<table class="co-table" ng-if="fullBuilds.length">
|
||||
<thead>
|
||||
<td class="status-col"></td>
|
||||
<td ng-class="tablePredicateClass('id', options.predicate, options.reverse)">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('id')">Build ID</a>
|
||||
</td>
|
||||
<td ng-class="tablePredicateClass('tags', options.predicate, options.reverse)">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('tags')">Tags</a>
|
||||
</td>
|
||||
<td ng-class="tablePredicateClass('commit_sha', options.predicate, options.reverse)">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('commit_sha')">Triggered By</a>
|
||||
</td>
|
||||
<td ng-class="tablePredicateClass('started_datetime', options.predicate, options.reverse)">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('started_datetime')">Date Started</a>
|
||||
</td>
|
||||
<td class="options-col"></td>
|
||||
</thead>
|
||||
|
||||
<tr ng-repeat="build in fullBuilds">
|
||||
<td><span class="build-state-icon" build="build"></span></td>
|
||||
<td>
|
||||
<a href="/repository/{{ repository.namespace }}/{{ repository.name }}/build/{{ build.id }}">{{ build.id.substr(0, 8) }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="building-tag" ng-repeat="tag in build.building_tags">
|
||||
<i class="fa fa-tag"></i>{{ tag }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="triggered-build-description" build="build"></div>
|
||||
</td>
|
||||
<td>{{ build.started | amCalendar }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /Builds -->
|
||||
|
||||
<!-- Build Triggers -->
|
||||
<div class="co-panel" ng-if="repository.can_admin && TriggerService.getTypes().length">
|
||||
<!-- Builds header controls -->
|
||||
<div class="co-panel-heading">
|
||||
<i class="fa fa-flash"></i>
|
||||
Build Triggers
|
||||
|
||||
<div class="heading-controls hidden-sm hidden-xs">
|
||||
<!-- Add Build Trigger -->
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
|
||||
Create Build Trigger
|
||||
<b class="caret"></b>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right pull-right">
|
||||
<li ng-repeat="type in TriggerService.getTypes()">
|
||||
<a href="{{ TriggerService.getRedirectUrl(type, repository.namespace, repository.name) }}">
|
||||
<i class="fa fa-lg" ng-class="TriggerService.getMetadata(type).icon"></i>
|
||||
{{ TriggerService.getTitle(type) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Builds list content -->
|
||||
<div class="panel-body">
|
||||
<div class="resource-view" resource="triggersResource" error-message="'Could not load build triggers'">
|
||||
<!-- No Triggers defined -->
|
||||
<div class="empty" ng-if="!triggers.length">
|
||||
<div class="empty-primary-msg">No build triggers defined</div>
|
||||
<div class="empty-secondary-msg">
|
||||
Build triggers invoke builds whenever the triggered condition is met (source control push, webhook, etc)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Triggers list -->
|
||||
<table class="co-table" ng-if="triggers.length">
|
||||
<thead>
|
||||
<td>Trigger Name</td>
|
||||
<td>Dockerfile Location</td>
|
||||
<td>Branches/Tags</td>
|
||||
<td>Pull Robot</td>
|
||||
<td class="options-col"></td>
|
||||
</thead>
|
||||
|
||||
<tr ng-repeat="trigger in triggers | filter:{'is_active':false}">
|
||||
<td colspan="5" style="text-align: center">
|
||||
<span class="cor-loader-inline"></span>
|
||||
Trigger Setup in progress:
|
||||
<a href="javascript:void(0)" ng-click="setupTrigger(trigger)">Resume</a> |
|
||||
<a href="javascript:void(0)" ng-click="deleteTrigger(trigger)">Cancel</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-repeat="trigger in triggers | filter:{'is_active':true}">
|
||||
<td><div class="trigger-description" trigger="trigger" short="true"></div></td>
|
||||
<td>{{ trigger.subdir || '(Root Directory)' }}</td>
|
||||
<td>{{ trigger.config.branchtag_regex || '(All)' }}</td>
|
||||
<td>
|
||||
<span class="entity-reference" entity="trigger.pull_robot" ng-if="trigger.pull_robot"></span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cor-options-menu">
|
||||
<span class="cor-option" option-click="askRunTrigger(trigger)">
|
||||
<i class="fa fa-chevron-right"></i> Run Trigger Now
|
||||
</span>
|
||||
<span class="cor-option" option-click="askDeleteTrigger(trigger)">
|
||||
<i class="fa fa-times"></i> Delete Trigger
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /Build Triggers -->
|
||||
|
||||
<!-- Delete Tag Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="deleteTriggerInfo"
|
||||
dialog-action="deleteTrigger(info.trigger, callback)"
|
||||
dialog-title="Delete Build Trigger"
|
||||
dialog-action-title="Delete Trigger">
|
||||
Are you sure you want to delete this build trigger? No further builds will be automatically
|
||||
started.
|
||||
</div>
|
||||
|
||||
<!-- Dockerfile build dialog -->
|
||||
<div class="dockerfile-build-dialog"
|
||||
show-now="showBuildDialogCounter"
|
||||
repository="repository"
|
||||
build-started="handleBuildStarted(build)">
|
||||
</div>
|
||||
|
||||
<!-- Setup trigger dialog-->
|
||||
<div class="setup-trigger-dialog"
|
||||
repository="repository"
|
||||
trigger="currentSetupTrigger"
|
||||
canceled="cancelSetupTrigger(trigger)"
|
||||
counter="showTriggerSetupCounter"></div>
|
||||
|
||||
<!-- Manual trigger dialog -->
|
||||
<div class="manual-trigger-build-dialog"
|
||||
repository="repository"
|
||||
trigger="currentStartTrigger"
|
||||
counter="showTriggerStartDialogCounter"
|
||||
start-build="startTrigger(trigger, parameters)"></div>
|
||||
|
||||
</div>
|
63
static/directives/repo-view/repo-panel-changes.html
Normal file
63
static/directives/repo-view/repo-panel-changes.html
Normal file
|
@ -0,0 +1,63 @@
|
|||
<div class="repo-panel-changes-element">
|
||||
<!-- No Tags Selected -->
|
||||
<div class="empty" ng-if="!selectedTags.length">
|
||||
<div class="empty-primary-msg">No tags selected to view</div>
|
||||
<div class="empty-secondary-msg">
|
||||
Please select one or more tags in the <i class="fa fa-tags" style="margin-left: 4px; margin-right: 4px;"></i> Tags tab to visualize.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tags Selected -->
|
||||
<div ng-if="selectedTags.length > 0">
|
||||
<h3 class="tab-header">
|
||||
Visualize Tags:
|
||||
<span class="visualized-tag" ng-repeat="tag in selectedTags">
|
||||
<i class="fa fa-tag"></i>{{ tag }}
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div id="image-history row" class="resource-view" resource="imagesResource"
|
||||
error-message="'Cannot load repository images'">
|
||||
|
||||
<!-- Tree View container -->
|
||||
<div class="col-md-8">
|
||||
<div class="panel panel-default">
|
||||
<!-- Image history tree -->
|
||||
<div id="image-history-container" onresize="tree.notifyResized()"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Side Panel -->
|
||||
<div class="col-md-4">
|
||||
<div class="side-panel-title" ng-if="currentTag">
|
||||
<i class="fa fa-tag"></i>{{ currentTag }}
|
||||
</div>
|
||||
<div class="side-panel-title" ng-if="currentImage">
|
||||
<i class="fa fa-archive"></i>{{ currentImage.substr(0, 12) }}
|
||||
</div>
|
||||
|
||||
<div class="side-panel">
|
||||
<!-- Tag Info -->
|
||||
<div class="tag-info-sidebar"
|
||||
tracker="tracker"
|
||||
tag="currentTag"
|
||||
image-selected="setImage(image)"
|
||||
delete-tag-requested="tagActionHandler.askDeleteTag(tag)"
|
||||
ng-if="currentTag">
|
||||
</div>
|
||||
|
||||
<!-- Image Info -->
|
||||
<div class="image-info-sidebar"
|
||||
tracker="tracker"
|
||||
image="currentImage"
|
||||
tag-selected="setTag(tag)"
|
||||
add-tag-requested="tagActionHandler.askAddTag(image)"
|
||||
ng-if="currentImage">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tag-operations-dialog" repository="repository" images="images"
|
||||
action-handler="tagActionHandler" tag-changed="handleTagChanged(data)"></div>
|
72
static/directives/repo-view/repo-panel-info.html
Normal file
72
static/directives/repo-view/repo-panel-info.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
<div class="repo-panel-info-element">
|
||||
<!-- Repository stats and builds summary -->
|
||||
<div class="repository-stats row">
|
||||
<!-- Pull Stats -->
|
||||
<div class="col-sm-3 stat-col">
|
||||
<div class="stat-title">Repo Pulls</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-value">{{ repository.stats.pulls.today }}</div>
|
||||
<div class="stat-subtitle">Last 24 hours</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-value">{{ repository.stats.pulls.thirty_day }}</div>
|
||||
<div class="stat-subtitle">Last 30 days</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Push Stats -->
|
||||
<div class="col-sm-3 stat-col">
|
||||
<div class="stat-title">Repo Pushes</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-value">{{ repository.stats.pushes.today }}</div>
|
||||
<div class="stat-subtitle">Last 24 hours</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-value">{{ repository.stats.pushes.thirty_day }}</div>
|
||||
<div class="stat-subtitle">Last 30 days</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Builds -->
|
||||
<div class="col-sm-6">
|
||||
<div class="stat-title">Recent Repo Builds</div>
|
||||
|
||||
<!-- Loading -->
|
||||
<div class="cor-loader" ng-if="!builds"></div>
|
||||
|
||||
<!-- No Builds -->
|
||||
<div class="empty" ng-if="builds && !builds.length">
|
||||
<div class="empty-primary-msg">No builds have been run for this repository.</div>
|
||||
<div class="empty-secondary-msg" ng-if="repository.can_write">
|
||||
Click on the <i class="fa fa-tasks" style="margin-left: 6px"></i> Builds tab to start a new build.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Builds -->
|
||||
<div ng-if="builds && builds.length">
|
||||
<div class="build-mini-status" ng-repeat="build in builds" build="build"></div>
|
||||
</div>
|
||||
|
||||
<!-- View All -->
|
||||
<a href="/repository/{{ repository.namespace }}/{{ repository.name }}?tab=builds"
|
||||
class="view-all" ng-if="repository.can_admin && builds.length">
|
||||
View Build History
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Repository Description -->
|
||||
<div class="description-container">
|
||||
<h4 style="font-size:20px;">Description</h4>
|
||||
<div class="description markdown-input"
|
||||
content="repository.description"
|
||||
can-write="repository.can_write"
|
||||
content-changed="updateDescription"
|
||||
field-title="'repository description'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
95
static/directives/repo-view/repo-panel-settings.html
Normal file
95
static/directives/repo-view/repo-panel-settings.html
Normal file
|
@ -0,0 +1,95 @@
|
|||
<div class="repo-panel-settings-element">
|
||||
<h3 class="tab-header">Repository Settings</h3>
|
||||
|
||||
<!-- User/Team Permissions -->
|
||||
<div class="co-panel">
|
||||
<div class="co-panel-heading"><i class="fa fa-key"></i> User and Robot Permissions</div>
|
||||
<div class="panel-body">
|
||||
<div class="repository-permissions-table" repository="repository"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Access Tokens (DEPRECATED) -->
|
||||
<div class="co-panel" ng-show="hasTokens">
|
||||
<div class="co-panel-heading"><i class="fa fa-key"></i> Access Token Permissions</div>
|
||||
<div class="panel-body">
|
||||
<div class="repository-tokens-table" repository="repository" has-tokens="hasTokens"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Events and Notifications -->
|
||||
<div class="repository-events-table" repository="repository"></div>
|
||||
|
||||
<!-- Other settings -->
|
||||
<div class="co-panel">
|
||||
<div class="co-panel-heading"><i class="fa fa-gears"></i> Repository Settings</div>
|
||||
|
||||
<div class="cor-loader" ng-show="!repository"></div>
|
||||
|
||||
<div ng-show="repository">
|
||||
<!-- Public/Private -->
|
||||
<div class="panel-body panel-section lock-section" ng-if="!repository.is_public">
|
||||
<i class="fa fa-lock lock-icon"></i>
|
||||
<div>This repository is currently <b>private</b>. Only users on the permissions list may view and interact with it.</div>
|
||||
|
||||
<button class="btn btn-default" ng-click="askChangeAccess('public')">
|
||||
<i class="fa fa-unlock"></i>Make Public
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="panel-body panel-section lock-section" ng-if="repository.is_public">
|
||||
<i class="fa fa-unlock lock-icon"></i>
|
||||
|
||||
<div>This repository is currently <b>public</b> and is visible to all users, and may be pulled by all users.</div>
|
||||
|
||||
<button class="btn btn-default" ng-click="askChangeAccess('private')">
|
||||
<i class="fa fa-lock"></i>Make Private
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Delete Repository -->
|
||||
<div class="panel-body panel-section">
|
||||
<div>Deleting a repository <b>cannot be undone</b>. Here be dragons!</div>
|
||||
<button class="btn btn-danger" ng-click="askDelete()">
|
||||
<i class="fa fa-trash"></i>
|
||||
Delete Repository
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Build Status Badge -->
|
||||
<div class="panel-body panel-section">
|
||||
<!-- Status Image -->
|
||||
<a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}">
|
||||
<img ng-src="/repository/{{ repository.namespace }}/{{ repository.name }}/status?token={{ repository.status_token }}"
|
||||
data-title="Docker Repository on Quay.io">
|
||||
</a>
|
||||
|
||||
<!-- Embed formats -->
|
||||
<table style="margin-top: 20px; width: 600px;">
|
||||
<thead>
|
||||
<th style="width: 150px"></th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>Image (SVG):</td>
|
||||
<td>
|
||||
<div class="copy-box" hovering-message="true" value="getBadgeFormat('svg', repository)"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Markdown:</td>
|
||||
<td>
|
||||
<div class="copy-box" hovering-message="true" value="getBadgeFormat('md', repository)"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AsciiDoc:</td>
|
||||
<td>
|
||||
<div class="copy-box" hovering-message="true" value="getBadgeFormat('asciidoc', repository)"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
99
static/directives/repo-view/repo-panel-tags.html
Normal file
99
static/directives/repo-view/repo-panel-tags.html
Normal file
|
@ -0,0 +1,99 @@
|
|||
<div class="repo-panel-tags-element">
|
||||
<h3 class="tab-header">Repository Tags</h3>
|
||||
<div class="resource-view" resource="imagesResource" error-message="'Could not load images'">
|
||||
|
||||
<div class="co-check-bar">
|
||||
<span class="cor-checkable-menu" controller="checkedTags">
|
||||
<div class="cor-checkable-menu-item" item-filter="allTagFilter">
|
||||
<i class="fa fa-check-square-o"></i>All Tags
|
||||
</div>
|
||||
<div class="cor-checkable-menu-item" item-filter="noTagFilter(tag)">
|
||||
<i class="fa fa-square-o"></i>No Tags
|
||||
</div>
|
||||
<div class="cor-checkable-menu-item" item-filter="commitTagFilter(tag)">
|
||||
<i class="fa fa-git"></i>Commit SHAs
|
||||
</div>
|
||||
|
||||
<div class="cor-checkable-menu-item" item-filter="imageIDFilter(it.image_id, tag)"
|
||||
ng-repeat="it in imageTracks">
|
||||
<i class="fa fa-circle-o" ng-style="{'color': it.color}"></i> {{ it.image_id.substr(0, 12) }}
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<span class="co-checked-actions" ng-if="checkedTags.checked.length">
|
||||
<a href="javascript:void(0)" class="btn btn-default" ng-click="setTab('changes')">
|
||||
<i class="fa fa-code-fork"></i> Visualize
|
||||
</a>
|
||||
<button class="btn btn-default"
|
||||
ng-click="askDeleteMultipleTags(checkedTags.checked)"
|
||||
ng-if="repository.can_write">
|
||||
<i class="fa fa-times"></i> Delete
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<span class="co-filter-box">
|
||||
<input class="form-control" type="text" ng-model="options.tagFilter" placeholder="Filter Tags...">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<table class="co-table">
|
||||
<thead>
|
||||
<td class="checkbox-col"></td>
|
||||
<td ng-class="tablePredicateClass('name', options.predicate, options.reverse)">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('name')">Tag</a>
|
||||
</td>
|
||||
<td ng-class="tablePredicateClass('last_modified_datetime', options.predicate, options.reverse)">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('last_modified_datetime')">Last Modified</a>
|
||||
</td>
|
||||
<td ng-class="tablePredicateClass('size', options.predicate, options.reverse)">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('size')">Size</a>
|
||||
</td>
|
||||
<td ng-class="tablePredicateClass('image_id', options.predicate, options.reverse)"
|
||||
colspan="{{ imageTracks.length + 1 }}">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('image_id')">Image</a>
|
||||
</td>
|
||||
<td class="options-col"></td>
|
||||
</thead>
|
||||
|
||||
<tr class="co-checkable-row"
|
||||
ng-repeat="tag in tags"
|
||||
ng-class="checkedTags.isChecked(tag, checkedTags.checked) ? 'checked' : ''">
|
||||
<td><span class="cor-checkable-item" controller="checkedTags" item="tag"></span></td>
|
||||
<td><i class="fa fa-tag"></i> {{ tag.name }}</td>
|
||||
<td><span am-time-ago="tag.last_modified"></span></td>
|
||||
<td>{{ tag.size | bytes }}</td>
|
||||
<td class="image-id-col">
|
||||
<a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}/image/{{ tag.image_id }}">
|
||||
{{ tag.image_id.substr(0, 12) }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="image-track" ng-repeat="it in imageTracks">
|
||||
<span class="image-track-dot" ng-if="it.image_id == tag.image_id"
|
||||
ng-style="{'borderColor': it.color}"></span>
|
||||
<span class="image-track-line" ng-class="trackLineClass($parent.$index, it)"
|
||||
ng-style="{'borderColor': it.color}"></span>
|
||||
</td>
|
||||
<td class="options-col">
|
||||
<span class="cor-options-menu" ng-if="repository.can_write">
|
||||
<span class="cor-option" option-click="askDeleteTag(tag.name)">
|
||||
<i class="fa fa-times"></i> Delete Tag
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="empty" ng-if="allTags.length && !tags.length">
|
||||
<div class="empty-primary-msg">No matching tags found.</div>
|
||||
<div class="empty-secondary-msg">Try expanding your filtering terms.</div>
|
||||
</div>
|
||||
|
||||
<div class="empty" ng-if="!allTags.length">
|
||||
<div class="empty-primary-msg">This repository is empty.</div>
|
||||
<div class="empty-secondary-msg">Push a tag or initiate a build to populate this repository.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tag-operations-dialog" repository="repository" images="images"
|
||||
action-handler="tagActionHandler"></div>
|
75
static/directives/repository-events-table.html
Normal file
75
static/directives/repository-events-table.html
Normal file
|
@ -0,0 +1,75 @@
|
|||
<div class="repository-events-table-element">
|
||||
<div class="co-panel">
|
||||
<div class="co-panel-heading">
|
||||
<i class="fa fa-bell"></i> Events and Notifications
|
||||
|
||||
<div class="heading-controls hidden-sm hidden-xs">
|
||||
<button class="btn btn-primary" ng-click="askCreateNotification()">
|
||||
<i class="fa fa-plus"></i> Create Notification
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="resource-view" resource="notificationsResource"
|
||||
error-message="'Could not load repository events'">
|
||||
|
||||
<div class="empty" ng-if="!notifications.length">
|
||||
<div class="empty-primary-msg">No notification have been setup for this repository.</div>
|
||||
<div class="empty-secondary-msg" ng-if="repository.can_write">
|
||||
Click the "Create Notification" button above to add a new notification for a repository event.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="co-table permissions" ng-if="notifications.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Event</td>
|
||||
<td>Notification</td>
|
||||
<td class="options-col"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr class="notification-row" ng-repeat="notification in notifications">
|
||||
<td>
|
||||
<span class="notification-event">
|
||||
<i class="fa fa-lg" ng-class="getEventInfo(notification).icon"></i>
|
||||
{{ getEventInfo(notification).title }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="notification-method">
|
||||
<i class="fa fa-lg" ng-class="getMethodInfo(notification).icon"></i>
|
||||
{{ getMethodInfo(notification).title }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="cor-options-menu">
|
||||
<span class="cor-option" option-click="testNotification(notification)">
|
||||
<i class="fa fa-send"></i> Test Notification
|
||||
</span>
|
||||
<span class="cor-option" option-click="showWebhookInfo(notification)"
|
||||
ng-if="getMethodInfo(notification).id == 'webhook'">
|
||||
<i class="fa fa-book"></i>
|
||||
Webhook Documentation
|
||||
</span>
|
||||
<span class="cor-option" option-click="deleteNotification(notification)">
|
||||
<i class="fa fa-times"></i> Delete Notification
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New notification dialog-->
|
||||
<div class="create-external-notification-dialog"
|
||||
repository="repository"
|
||||
counter="showNewNotificationCounter"
|
||||
notification-created="handleNotificationCreated(notification)"></div>
|
||||
</div>
|
82
static/directives/repository-permissions-table.html
Normal file
82
static/directives/repository-permissions-table.html
Normal file
|
@ -0,0 +1,82 @@
|
|||
<div class="repository-permissions-table-element">
|
||||
<div class="resource-view"
|
||||
resources="[permissionResources.team, permissionResources.user]"
|
||||
error-message="'Could not load repository permissions'">
|
||||
<table class="co-table permissions">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Account Name</td>
|
||||
<td style="width: 300px">Permissions</td>
|
||||
<td class="options-col"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<!-- Team Permissions -->
|
||||
<tr ng-repeat="(name, permission) in permissionResources.team.value">
|
||||
<td class="team entity">
|
||||
<span class="entity-reference" namespace="repository.namespace"
|
||||
entity="buildEntityForPermission(name, permission, 'team')">
|
||||
</span>
|
||||
</td>
|
||||
<td class="user-permissions">
|
||||
<span class="role-group" current-role="permission.role" role-changed="setRole(role, name, 'team')" roles="roles"></span>
|
||||
</td>
|
||||
|
||||
<td class="options-col">
|
||||
<span class="cor-options-menu">
|
||||
<span class="cor-option" option-click="deleteRole(name, 'team')">
|
||||
<i class="fa fa-times"></i> Delete Permission
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- User Permissions -->
|
||||
<tr ng-repeat="(name, permission) in permissionResources.user.value">
|
||||
<td class="{{ 'user entity ' + (permission.is_org_member ? '' : 'outside') }}">
|
||||
<span class="entity-reference" namespace="repository.namespace"
|
||||
entity="buildEntityForPermission(name, permission, 'user')">
|
||||
</span>
|
||||
</td>
|
||||
<td class="user-permissions">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<span class="role-group" current-role="permission.role" role-changed="setRole(role, name, 'user')" roles="roles"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="options-col">
|
||||
<span class="cor-options-menu">
|
||||
<span class="cor-option" option-click="deleteRole(name, 'user')">
|
||||
<i class="fa fa-times"></i> Delete Permission
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="add-row">
|
||||
<td id="add-entity-permission" class="admin-search">
|
||||
<span class="entity-search" namespace="repository.namespace"
|
||||
placeholder="'Select a ' + (repository.is_organization ? 'team or ' : '') + 'user...'"
|
||||
current-entity="addPermissionInfo.entity"></span>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<span class="role-group" current-role="addPermissionInfo.role" roles="roles"
|
||||
role-changed="addPermissionInfo.role = role"></span>
|
||||
<button class="btn btn-success" style="margin-left: 10px"
|
||||
ng-disabled="!addPermissionInfo.role || !addPermissionInfo.entity"
|
||||
ng-click="addPermission()">
|
||||
Add Permission
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Grant Permission Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="grantPermissionInfo"
|
||||
dialog-action="grantPermission(info.entity, callback)"
|
||||
dialog-title="Grant Permission"
|
||||
dialog-action-title="Grant Permission">
|
||||
The selected user is outside of your organization. Are you sure you want to grant the user access to this repository?
|
||||
</div>
|
||||
</div>
|
35
static/directives/repository-tokens-table.html
Normal file
35
static/directives/repository-tokens-table.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
<div class="repository-tokens-table-element">
|
||||
<div class="resource-view" resource="tokensResource"
|
||||
error-message="'Could not load repository tokens'">
|
||||
<div class="alert alert-warning">Note: Access tokens are <strong>deprecated</strong> and will be removed in the near future. <a href="http://docs.quay.io/glossary/robot-accounts.html">Robot accounts</a> are the recommended replacement.
|
||||
</div>
|
||||
|
||||
<table class="co-table permissions">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="min-width: 400px;">Token Name</td>
|
||||
<td>Permissions</td>
|
||||
<td style="width: 95px;"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="(code, token) in tokens">
|
||||
<td class="user token">
|
||||
<i class="fa fa-key"></i>
|
||||
<a ng-click="showToken(token.code)">{{ token.friendlyName }}</a>
|
||||
</td>
|
||||
<td class="user-permissions">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-default" ng-click="changeTokenAccess(token.code, 'read')" ng-class="{read: 'active', write: ''}[token.role]">Read only</button>
|
||||
<button type="button" class="btn btn-default" ng-click="changeTokenAccess(token.code, 'write')" ng-class="{read: '', write: 'active'}[token.role]">Write</button>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="delete-ui" delete-title="'Delete Token'" perform-delete="deleteToken(token.code)"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
|
@ -3,7 +3,7 @@
|
|||
<div class="resource-error alert alert-info" ng-show="getState(resources) == 'error'">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
<div class="resource-content" ng-class="getState(resources) == 'ready' ? 'visible' : ''">
|
||||
<div class="resource-content" ng-class="getState(resources) == 'ready' ? 'visible' : 'hidden'">
|
||||
<span ng-transclude></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
42
static/directives/tag-info-sidebar.html
Normal file
42
static/directives/tag-info-sidebar.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
<div class="tag-info-sidebar-element">
|
||||
<dl class="dl-normal">
|
||||
<dt>Last Modified</dt>
|
||||
<dd am-time-ago="parseDate(tagImage.created)"></dd>
|
||||
|
||||
<dt>Total Compressed Size</dt>
|
||||
<dd>
|
||||
<span class="context-tooltip"
|
||||
data-title="The amount of data sent between Docker and the registry when pushing/pulling"
|
||||
data-container="body"
|
||||
bs-tooltip>
|
||||
{{ tracker.getTotalSize(tag) | bytes }}
|
||||
</span>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div class="tag-image-sizes">
|
||||
<div class="tag-image-size" ng-repeat="image in tracker.getImagesForTagBySize(tag) | limitTo: 10">
|
||||
<span class="size-limiter">
|
||||
<span class="size-bar"
|
||||
ng-style="{'width': (image.size / tracker.getTotalSize(tag)) * 100 + '%'}"
|
||||
data-title="{{ image.size | bytes }}"
|
||||
bs-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
<span class="size-title">
|
||||
<a class="image-size-link"
|
||||
href="javascript:void(0)"
|
||||
ng-click="imageSelected({'image': image.id})"
|
||||
data-image="{{ image.id.substr(0, 12) }}">
|
||||
{{ image.id.substr(0, 12) }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-bar" ng-show="tracker.repository.can_admin">
|
||||
<button class="btn btn-default" ng-click="deleteTagRequested({'tag': tag})">
|
||||
<i class="fa fa-times" style="margin-right: 6px;"></i>Delete Tag
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
87
static/directives/tag-operations-dialog.html
Normal file
87
static/directives/tag-operations-dialog.html
Normal file
|
@ -0,0 +1,87 @@
|
|||
<div class="tag-operations-dialog-element">
|
||||
<!-- Add Tag Dialog -->
|
||||
<div class="modal fade" id="createOrMoveTagModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"
|
||||
aria-hidden="true" ng-show="!addingTag">×</button>
|
||||
<h4 class="modal-title">{{ isAnotherImageTag(toTagImage, tagToCreate) ? 'Move' : 'Add' }} Tag to Image {{ toTagImage.substr(0, 12) }}</h4>
|
||||
</div>
|
||||
<form name="addTagForm" ng-submit="createOrMoveTag(toTagImage, tagToCreate);">
|
||||
<div class="modal-body">
|
||||
<div class="cor-loader" ng-show="addingTag"></div>
|
||||
<div ng-show="!addingTag">
|
||||
<input type="text" class="form-control" id="tagName"
|
||||
placeholder="Enter tag name"
|
||||
ng-model="tagToCreate" ng-pattern="/^([a-z0-9_\.-]){3,30}$/"
|
||||
ng-disabled="creatingTag" autofocus required>
|
||||
|
||||
<div style="margin: 10px; margin-top: 20px;"
|
||||
ng-show="isOwnedTag(toTagImage, tagToCreate)">
|
||||
Note: <span class="label tag label-default">{{ tagToCreate }}</span> is already applied to this image.
|
||||
</div>
|
||||
|
||||
<div style="margin: 10px; margin-top: 20px;"
|
||||
ng-show="isAnotherImageTag(toTagImage, tagToCreate)">
|
||||
Note: <span class="label tag label-default">{{ tagToCreate }}</span> is already applied to another image. This will <b>move</b> the tag.
|
||||
</div>
|
||||
|
||||
<div class="tag-specific-images-view"
|
||||
tag="tagToCreate"
|
||||
repository="repo"
|
||||
images="images"
|
||||
image-cutoff="toTagImage"
|
||||
style="margin: 10px; margin-top: 20px; margin-bottom: -10px;"
|
||||
ng-show="isAnotherImageTag(toTagImage, tagToCreate)">
|
||||
This will also delete any unattach images and delete the following images:
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="!addingTag">
|
||||
<button type="submit" class="btn btn-primary"
|
||||
ng-disabled="addTagForm.$invalid || isOwnedTag(toTagImage, tagToCreate)"
|
||||
ng-class="isAnotherImageTag(toTagImage, tagToCreate) ? 'btn-warning' : 'btn-primary'"
|
||||
ng-show="!creatingTag">
|
||||
{{ isAnotherImageTag(toTagImage, tagToCreate) ? 'Move Tag' : 'Create Tag' }}
|
||||
</button>
|
||||
<button class="btn btn-default" data-dismiss="modal" ng-show="!addingTag">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
<!-- Delete Tags Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="deleteMultipleTagsInfo"
|
||||
dialog-action="deleteMultipleTags(info.tags, callback)"
|
||||
dialog-title="Delete Tags"
|
||||
dialog-action-title="Delete Tags">
|
||||
Are you sure you want to delete the following tags:
|
||||
<ul>
|
||||
<li ng-repeat="tag_info in deleteMultipleTagsInfo.tags">
|
||||
<span class="label label-default tag">{{ tag_info.name }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div style="margin-top: 20px">
|
||||
<strong>Note: </strong>This operation can take several minutes.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Tag Confirm -->
|
||||
<div class="cor-confirm-dialog"
|
||||
dialog-context="deleteTagInfo"
|
||||
dialog-action="deleteTag(info.tag, callback)"
|
||||
dialog-title="Delete Tag"
|
||||
dialog-action-title="Delete Tag">
|
||||
Are you sure you want to delete tag
|
||||
<span class="label label-default tag">{{ deleteTagInfo.tag }}</span>?
|
||||
|
||||
<div class="tag-specific-images-view" tag="deleteTagInfo.tag" repository="repository"
|
||||
images="images" style="margin-top: 20px">
|
||||
The following images and any other images not referenced by a tag will be deleted:
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +1,14 @@
|
|||
<div class="triggered-build-description-element">
|
||||
<span ng-switch on="build.trigger.service">
|
||||
|
||||
<span class="manual" ng-if="!build.trigger && !build.job_config.manual_user">
|
||||
(Manually Triggered Build)
|
||||
</span>
|
||||
|
||||
<span ng-if="!build.trigger && build.job_config.manual_user">
|
||||
<i class="fa fa-user"></i> {{ build.job_config.manual_user }}
|
||||
</span>
|
||||
|
||||
<span ng-switch on="build.trigger.service" ng-if="build.trigger">
|
||||
<!-- GitHub -->
|
||||
<span ng-switch-when="github">
|
||||
<!-- Full Commit Information -->
|
||||
|
|
|
@ -162,6 +162,51 @@ angular.module("core-ui", [])
|
|||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('corConfirmDialog', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 1,
|
||||
templateUrl: '/static/directives/cor-confirm-dialog.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'dialogTitle': '@dialogTitle',
|
||||
'dialogActionTitle': '@dialogActionTitle',
|
||||
|
||||
'dialogContext': '=dialogContext',
|
||||
'dialogAction': '&dialogAction'
|
||||
},
|
||||
controller: function($rootScope, $scope, $element) {
|
||||
$scope.working = false;
|
||||
|
||||
$scope.$watch('dialogContext', function(dc) {
|
||||
if (!dc) { return; }
|
||||
$scope.show();
|
||||
});
|
||||
|
||||
$scope.performAction = function() {
|
||||
$scope.working = true;
|
||||
$scope.dialogAction({
|
||||
'info': $scope.dialogContext,
|
||||
'callback': function(result) {
|
||||
$scope.hide();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.show = function() {
|
||||
$scope.working = false;
|
||||
$element.find('.modal').modal({});
|
||||
};
|
||||
|
||||
$scope.hide = function() {
|
||||
$element.find('.modal').modal('hide');
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('corTabPanel', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 1,
|
||||
|
@ -581,4 +626,86 @@ angular.module("core-ui", [])
|
|||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('corCheckableMenu', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 1,
|
||||
templateUrl: '/static/directives/cor-checkable-menu.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'controller': '=controller'
|
||||
},
|
||||
controller: function($rootScope, $scope, $element) {
|
||||
$scope.getClass = function(items, checked) {
|
||||
if (checked.length == 0) {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
if (checked.length == items.length) {
|
||||
return 'all';
|
||||
}
|
||||
|
||||
return 'some';
|
||||
};
|
||||
|
||||
$scope.toggleItems = function($event) {
|
||||
$event.stopPropagation();
|
||||
$scope.controller.toggleItems();
|
||||
};
|
||||
|
||||
this.checkByFilter = function(filter) {
|
||||
$scope.controller.checkByFilter(function(tag) {
|
||||
return filter({'tag': tag});
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('corCheckableMenuItem', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 1,
|
||||
templateUrl: '/static/directives/cor-checkable-menu-item.html',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
require: '^corCheckableMenu',
|
||||
scope: {
|
||||
'itemFilter': '&itemFilter'
|
||||
},
|
||||
link: function($scope, $element, $attr, parent) {
|
||||
$scope.parent = parent;
|
||||
},
|
||||
|
||||
controller: function($rootScope, $scope, $element) {
|
||||
$scope.selected = function() {
|
||||
$scope.parent.checkByFilter(this.itemFilter);
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('corCheckableItem', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 1,
|
||||
templateUrl: '/static/directives/cor-checkable-item.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'item': '=item',
|
||||
'controller': '=controller'
|
||||
},
|
||||
controller: function($rootScope, $scope, $element) {
|
||||
$scope.toggleItem = function() {
|
||||
$scope.controller.toggleItem($scope.item);
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
232
static/js/directives/repo-view/repo-panel-builds.js
Normal file
232
static/js/directives/repo-view/repo-panel-builds.js
Normal file
|
@ -0,0 +1,232 @@
|
|||
/**
|
||||
* An element which displays the builds panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelBuilds', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-view/repo-panel-builds.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'builds': '=builds'
|
||||
},
|
||||
controller: function($scope, $element, $filter, $routeParams, ApiService, TriggerService) {
|
||||
var orderBy = $filter('orderBy');
|
||||
|
||||
$scope.TriggerService = TriggerService;
|
||||
|
||||
$scope.options = {
|
||||
'filter': 'recent',
|
||||
'reverse': false,
|
||||
'predicate': 'started_datetime'
|
||||
};
|
||||
|
||||
$scope.currentFilter = null;
|
||||
|
||||
$scope.currentStartTrigger = null;
|
||||
$scope.currentSetupTrigger = null;
|
||||
|
||||
$scope.showBuildDialogCounter = 0;
|
||||
$scope.showTriggerStartDialogCounter = 0;
|
||||
$scope.showTriggerSetupCounter = 0;
|
||||
|
||||
var updateBuilds = function() {
|
||||
if (!$scope.allBuilds) { return; }
|
||||
|
||||
var unordered = $scope.allBuilds.map(function(build_info) {
|
||||
var commit_sha = null;
|
||||
|
||||
if (build_info.job_config.trigger_metadata) {
|
||||
commit_sha = build_info.job_config.trigger_metadata.commit_sha;
|
||||
}
|
||||
|
||||
return $.extend(build_info, {
|
||||
'started_datetime': (new Date(build_info.started)).valueOf() * (-1),
|
||||
'building_tags': build_info.job_config.docker_tags,
|
||||
'commit_sha': commit_sha
|
||||
});
|
||||
});
|
||||
|
||||
$scope.fullBuilds = orderBy(unordered, $scope.options.predicate, $scope.options.reverse);
|
||||
};
|
||||
|
||||
var loadBuilds = function() {
|
||||
if (!$scope.builds || !$scope.repository || !$scope.options.filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: We only refresh if the filter has changed.
|
||||
var filter = $scope.options.filter;
|
||||
if ($scope.buildsResource && filter == $scope.currentFilter) { return; }
|
||||
|
||||
var since = null;
|
||||
|
||||
if ($scope.options.filter == '48hours') {
|
||||
since = Math.floor(moment().subtract(2, 'days').valueOf() / 1000);
|
||||
} else if ($scope.options.filter == '30days') {
|
||||
since = Math.floor(moment().subtract(30, 'days').valueOf() / 1000);
|
||||
} else {
|
||||
since = null;
|
||||
}
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'limit': 100,
|
||||
'since': since
|
||||
};
|
||||
|
||||
$scope.buildsResource = ApiService.getRepoBuildsAsResource(params).get(function(resp) {
|
||||
$scope.allBuilds = resp.builds;
|
||||
$scope.currentFilter = filter;
|
||||
updateBuilds();
|
||||
});
|
||||
};
|
||||
|
||||
var buildsChanged = function() {
|
||||
if (!$scope.allBuilds) {
|
||||
loadBuilds();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.builds || !$scope.repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace any build records with updated records from the server.
|
||||
$scope.builds.map(function(build) {
|
||||
for (var i = 0; i < $scope.allBuilds.length; ++i) {
|
||||
var current = $scope.allBuilds[i];
|
||||
if (current.id == build.id && current.phase != build.phase) {
|
||||
$scope.allBuilds[i] = build;
|
||||
break
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateBuilds();
|
||||
};
|
||||
|
||||
var loadBuildTriggers = function() {
|
||||
if (!$scope.repository || !$scope.repository.can_admin) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
$scope.triggersResource = ApiService.listBuildTriggersAsResource(params).get(function(resp) {
|
||||
$scope.triggers = resp.triggers;
|
||||
|
||||
// Check to see if we need to setup any trigger.
|
||||
var newTriggerId = $routeParams.newtrigger;
|
||||
if (newTriggerId) {
|
||||
$scope.triggers.map(function(trigger) {
|
||||
if (trigger['id'] == newTriggerId && !trigger['is_active']) {
|
||||
$scope.setupTrigger(trigger);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('repository', loadBuildTriggers);
|
||||
$scope.$watch('repository', loadBuilds);
|
||||
|
||||
$scope.$watch('builds', buildsChanged);
|
||||
|
||||
$scope.$watch('options.filter', loadBuilds);
|
||||
$scope.$watch('options.predicate', updateBuilds);
|
||||
$scope.$watch('options.reverse', updateBuilds);
|
||||
|
||||
$scope.tablePredicateClass = function(name, predicate, reverse) {
|
||||
if (name != predicate) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'current ' + (reverse ? 'reversed' : '');
|
||||
};
|
||||
|
||||
$scope.orderBy = function(predicate) {
|
||||
if (predicate == $scope.options.predicate) {
|
||||
$scope.options.reverse = !$scope.options.reverse;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.options.reverse = false;
|
||||
$scope.options.predicate = predicate;
|
||||
};
|
||||
|
||||
$scope.askDeleteTrigger = function(trigger) {
|
||||
$scope.deleteTriggerInfo = {
|
||||
'trigger': trigger
|
||||
};
|
||||
};
|
||||
|
||||
$scope.askRunTrigger = function(trigger) {
|
||||
$scope.currentStartTrigger = trigger;
|
||||
$scope.showTriggerStartDialogCounter++;
|
||||
};
|
||||
|
||||
$scope.cancelSetupTrigger = function(trigger) {
|
||||
if ($scope.currentSetupTrigger != trigger) { return; }
|
||||
|
||||
$scope.currentSetupTrigger = null;
|
||||
$scope.deleteTrigger(trigger);
|
||||
};
|
||||
|
||||
$scope.setupTrigger = function(trigger) {
|
||||
$scope.currentSetupTrigger = trigger;
|
||||
$scope.showTriggerSetupCounter++;
|
||||
};
|
||||
|
||||
$scope.startTrigger = function(trigger, opt_custom) {
|
||||
var parameters = TriggerService.getRunParameters(trigger.service);
|
||||
if (parameters.length && !opt_custom) {
|
||||
$scope.currentStartTrigger = trigger;
|
||||
$scope.showTriggerStartDialogCounter++;
|
||||
return;
|
||||
}
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': trigger.id
|
||||
};
|
||||
|
||||
ApiService.manuallyStartBuildTrigger(opt_custom || {}, params).then(function(resp) {
|
||||
$scope.allBuilds.push(resp);
|
||||
updateBuilds();
|
||||
}, ApiService.errorDisplay('Could not start build'));
|
||||
};
|
||||
|
||||
$scope.deleteTrigger = function(trigger, opt_callback) {
|
||||
if (!trigger) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': trigger.id
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Could not delete build trigger', function() {
|
||||
opt_callback && opt_callback(false);
|
||||
});
|
||||
|
||||
ApiService.deleteBuildTrigger(null, params).then(function(resp) {
|
||||
$scope.triggers.splice($scope.triggers.indexOf(trigger), 1);
|
||||
opt_callback && opt_callback(true);
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.showNewBuildDialog = function() {
|
||||
$scope.showBuildDialogCounter++;
|
||||
};
|
||||
|
||||
$scope.handleBuildStarted = function(build) {
|
||||
$scope.allBuilds.push(build);
|
||||
updateBuilds();
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
206
static/js/directives/repo-view/repo-panel-changes.js
Normal file
206
static/js/directives/repo-view/repo-panel-changes.js
Normal file
|
@ -0,0 +1,206 @@
|
|||
/**
|
||||
* An element which displays the changes visualization panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelChanges', function () {
|
||||
var RepositoryImageTracker = function(repository, images) {
|
||||
this.repository = repository;
|
||||
this.images = images;
|
||||
|
||||
// Build a map of image ID -> image.
|
||||
var imageIDMap = {};
|
||||
this.images.map(function(image) {
|
||||
imageIDMap[image.id] = image;
|
||||
});
|
||||
|
||||
this.imageMap_ = imageIDMap;
|
||||
};
|
||||
|
||||
RepositoryImageTracker.prototype.imageLink = function(image) {
|
||||
return '/repository/' + this.repository.namespace + '/' +
|
||||
this.repository.name + '/image/' + image;
|
||||
};
|
||||
|
||||
RepositoryImageTracker.prototype.getImageForTag = function(tag) {
|
||||
var tagData = this.lookupTag(tag);
|
||||
if (!tagData) { return null; }
|
||||
|
||||
return this.imageMap_[tagData.image_id];
|
||||
};
|
||||
|
||||
RepositoryImageTracker.prototype.lookupTag = function(tag) {
|
||||
return this.repository.tags[tag];
|
||||
};
|
||||
|
||||
RepositoryImageTracker.prototype.lookupImage = function(image) {
|
||||
return this.imageMap_[image];
|
||||
};
|
||||
|
||||
RepositoryImageTracker.prototype.forAllTagImages = function(tag, callback) {
|
||||
var tagData = this.lookupTag(tag);
|
||||
if (!tagData) { return; }
|
||||
|
||||
var tagImage = this.imageMap_[tagData.image_id];
|
||||
if (!tagImage) { return; }
|
||||
|
||||
// Callback the tag's image itself.
|
||||
callback(tagImage);
|
||||
|
||||
// Callback any parent images.
|
||||
if (!tagImage.ancestors) { return; }
|
||||
|
||||
var ancestors = tagImage.ancestors.split('/');
|
||||
for (var i = 0; i < ancestors.length; ++i) {
|
||||
var image = this.imageMap_[ancestors[i]];
|
||||
if (image) {
|
||||
callback(image);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RepositoryImageTracker.prototype.getTotalSize = function(tag) {
|
||||
var size = 0;
|
||||
this.forAllTagImages(tag, function(image) {
|
||||
size += image.size;
|
||||
});
|
||||
return size;
|
||||
};
|
||||
|
||||
RepositoryImageTracker.prototype.getImagesForTagBySize = function(tag) {
|
||||
var images = [];
|
||||
this.forAllTagImages(tag, function(image) {
|
||||
images.push(image);
|
||||
});
|
||||
|
||||
images.sort(function(a, b) {
|
||||
return b.size - a.size;
|
||||
});
|
||||
|
||||
return images;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-view/repo-panel-changes.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'selectedTags': '=selectedTags',
|
||||
'isEnabled': '=isEnabled'
|
||||
},
|
||||
controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) {
|
||||
|
||||
var update = function() {
|
||||
if (!$scope.repository || !$scope.selectedTags) { return; }
|
||||
|
||||
$scope.currentImage = null;
|
||||
$scope.currentTag = null;
|
||||
|
||||
if (!$scope.imagesResource) {
|
||||
loadImages();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('selectedTags', update)
|
||||
$scope.$watch('repository', update);
|
||||
|
||||
$scope.$watch('isEnabled', function(isEnabled) {
|
||||
if (isEnabled) {
|
||||
refreshTree();
|
||||
}
|
||||
});
|
||||
|
||||
var refreshTree = function() {
|
||||
if (!$scope.repository || !$scope.images) { return; }
|
||||
|
||||
$('#image-history-container').empty();
|
||||
|
||||
var tree = new ImageHistoryTree(
|
||||
$scope.repository.namespace,
|
||||
$scope.repository.name,
|
||||
$scope.images,
|
||||
UtilService.getFirstMarkdownLineAsText,
|
||||
$scope.getTimeSince,
|
||||
ImageMetadataService.getEscapedFormattedCommand,
|
||||
function(tag) {
|
||||
return $.inArray(tag, $scope.selectedTags) >= 0;
|
||||
});
|
||||
|
||||
$scope.tree = tree.draw('image-history-container');
|
||||
if ($scope.tree) {
|
||||
// Give enough time for the UI to be drawn before we resize the tree.
|
||||
$timeout(function() {
|
||||
$scope.tree.notifyResized();
|
||||
}, 100);
|
||||
|
||||
// Listen for changes to the selected tag and image in the tree.
|
||||
$($scope.tree).bind('tagChanged', function(e) {
|
||||
$scope.$apply(function() { $scope.setTag(e.tag); });
|
||||
});
|
||||
|
||||
$($scope.tree).bind('imageChanged', function(e) {
|
||||
$scope.$apply(function() { $scope.setImage(e.image.id); });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var loadImages = function(opt_callback) {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
$scope.imagesResource = ApiService.listRepositoryImagesAsResource(params).get(function(resp) {
|
||||
$scope.images = resp.images;
|
||||
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
|
||||
|
||||
if ($scope.selectedTags && $scope.selectedTags.length) {
|
||||
refreshTree();
|
||||
}
|
||||
|
||||
opt_callback && opt_callback();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setImage = function(image_id) {
|
||||
$scope.currentTag = null;
|
||||
$scope.currentImage = image_id;
|
||||
$scope.tree.setImage(image_id);
|
||||
};
|
||||
|
||||
$scope.setTag = function(tag) {
|
||||
$scope.currentTag = tag;
|
||||
$scope.currentImage = null;
|
||||
$scope.tree.setTag(tag);
|
||||
};
|
||||
|
||||
$scope.parseDate = function(dateString) {
|
||||
return Date.parse(dateString);
|
||||
};
|
||||
|
||||
$scope.getTimeSince = function(createdTime) {
|
||||
return moment($scope.parseDate(createdTime)).fromNow();
|
||||
};
|
||||
|
||||
$scope.handleTagChanged = function(data) {
|
||||
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
|
||||
|
||||
data.removed.map(function(tag) {
|
||||
$scope.currentImage = null;
|
||||
$scope.currentTag = null;
|
||||
});
|
||||
|
||||
data.added.map(function(tag) {
|
||||
$scope.selectedTags.push(tag);
|
||||
$scope.currentTag = tag;
|
||||
});
|
||||
|
||||
refreshTree();
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
24
static/js/directives/repo-view/repo-panel-info.js
Normal file
24
static/js/directives/repo-view/repo-panel-info.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* An element which displays the information panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelInfo', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-view/repo-panel-info.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'builds': '=builds'
|
||||
},
|
||||
controller: function($scope, $element, ApiService) {
|
||||
$scope.updateDescription = function(content) {
|
||||
$scope.repository.description = content;
|
||||
$scope.repository.put();
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
84
static/js/directives/repo-view/repo-panel-settings.js
Normal file
84
static/js/directives/repo-view/repo-panel-settings.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* An element which displays the settings panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelSettings', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-view/repo-panel-settings.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, Config) {
|
||||
$scope.getBadgeFormat = function(format, repository) {
|
||||
if (!repository) { return ''; }
|
||||
|
||||
var imageUrl = Config.getUrl('/repository/' + repository.namespace + '/' + repository.name + '/status');
|
||||
if (!$scope.repository.is_public) {
|
||||
imageUrl += '?token=' + repository.status_token;
|
||||
}
|
||||
|
||||
var linkUrl = Config.getUrl('/repository/' + repository.namespace + '/' + repository.name);
|
||||
|
||||
switch (format) {
|
||||
case 'svg':
|
||||
return imageUrl;
|
||||
|
||||
case 'md':
|
||||
return '[![Docker Repository on ' + Config.REGISTRY_TITLE_SHORT + '](' + imageUrl +
|
||||
' "Docker Repository on ' + Config.REGISTRY_TITLE_SHORT + '")](' + linkUrl + ')';
|
||||
|
||||
case 'asciidoc':
|
||||
return 'image:' + imageUrl + '["Docker Repository on ' + Config.REGISTRY_TITLE_SHORT + '", link="' + linkUrl + '"]';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
$scope.askDelete = function() {
|
||||
bootbox.confirm('Are you sure you want delete this repository?', function(r) {
|
||||
if (!r) { return; }
|
||||
$scope.deleteRepo();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteRepo = function() {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
ApiService.deleteRepository(null, params).then(function() {
|
||||
setTimeout(function() {
|
||||
document.location = '/repository/';
|
||||
}, 100);
|
||||
}, ApiService.errorDisplay('Could not delete repository'));
|
||||
};
|
||||
|
||||
|
||||
$scope.askChangeAccess = function(newAccess) {
|
||||
bootbox.confirm('Are you sure you want to make this repository ' + newAccess + '?', function(r) {
|
||||
if (!r) { return; }
|
||||
$scope.changeAccess(newAccess);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.changeAccess = function(newAccess) {
|
||||
var visibility = {
|
||||
'visibility': newAccess
|
||||
};
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
ApiService.changeRepoVisibility(visibility, params).then(function() {
|
||||
$scope.repository.is_public = newAccess == 'public';
|
||||
}, ApiService.errorDisplay('Could not change repository visibility'));
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
213
static/js/directives/repo-view/repo-panel-tags.js
Normal file
213
static/js/directives/repo-view/repo-panel-tags.js
Normal file
|
@ -0,0 +1,213 @@
|
|||
/**
|
||||
* An element which displays the tags panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelTags', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-view/repo-panel-tags.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'selectedTags': '=selectedTags'
|
||||
},
|
||||
controller: function($scope, $element, $filter, $location, ApiService, UIService) {
|
||||
var orderBy = $filter('orderBy');
|
||||
|
||||
$scope.checkedTags = UIService.createCheckStateController([]);
|
||||
$scope.options = {
|
||||
'predicate': 'last_modified_datetime',
|
||||
'reverse': false
|
||||
};
|
||||
|
||||
$scope.iterationState = {};
|
||||
$scope.tagActionHandler = null;
|
||||
|
||||
var loadImages = function() {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
$scope.imagesResource = ApiService.listRepositoryImagesAsResource(params).get(function(resp) {
|
||||
$scope.images = resp.images;
|
||||
});
|
||||
};
|
||||
|
||||
var setTagState = function() {
|
||||
if (!$scope.repository || !$scope.selectedTags) { return; }
|
||||
|
||||
var tags = [];
|
||||
var allTags = [];
|
||||
var imageMap = {};
|
||||
var imageTracks = [];
|
||||
|
||||
// Build a list of tags and filtered tags.
|
||||
for (var tag in $scope.repository.tags) {
|
||||
if (!$scope.repository.tags.hasOwnProperty(tag)) { continue; }
|
||||
|
||||
var tagInfo = $.extend($scope.repository.tags[tag], {
|
||||
'name': tag,
|
||||
'last_modified_datetime': new Date($scope.repository.tags[tag].last_modified)
|
||||
});
|
||||
|
||||
allTags.push(tagInfo);
|
||||
|
||||
if (!$scope.options.tagFilter || tag.indexOf($scope.options.tagFilter) >= 0 ||
|
||||
tagInfo.image_id.indexOf($scope.options.tagFilter) >= 0) {
|
||||
tags.push(tagInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the tags by the predicate and the reverse, and map the information.
|
||||
var imageIDs = [];
|
||||
var ordered = orderBy(tags, $scope.options.predicate, $scope.options.reverse);
|
||||
var checked = [];
|
||||
|
||||
for (var i = 0; i < ordered.length; ++i) {
|
||||
var tagInfo = ordered[i];
|
||||
if (!imageMap[tagInfo.image_id]) {
|
||||
imageMap[tagInfo.image_id] = [];
|
||||
imageIDs.push(tagInfo.image_id)
|
||||
}
|
||||
|
||||
imageMap[tagInfo.image_id].push(tagInfo);
|
||||
|
||||
if ($.inArray(tagInfo.name, $scope.selectedTags) >= 0) {
|
||||
checked.push(tagInfo);
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate the image tracks.
|
||||
var colors = d3.scale.category10();
|
||||
var index = 0;
|
||||
|
||||
imageIDs.sort().map(function(image_id) {
|
||||
if (imageMap[image_id].length >= 2){
|
||||
imageTracks.push({
|
||||
'image_id': image_id,
|
||||
'color': colors(index),
|
||||
'count': imageMap[image_id].length,
|
||||
'tags': imageMap[image_id]
|
||||
});
|
||||
++index;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.imageMap = imageMap;
|
||||
$scope.imageTracks = imageTracks;
|
||||
|
||||
$scope.tags = ordered;
|
||||
$scope.allTags = allTags;
|
||||
|
||||
$scope.checkedTags = UIService.createCheckStateController(ordered, checked);
|
||||
$scope.checkedTags.listen(function(checked) {
|
||||
$scope.selectedTags = checked.map(function(tag_info) {
|
||||
return tag_info.name;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$scope.$watch('options.predicate', setTagState);
|
||||
$scope.$watch('options.reverse', setTagState);
|
||||
$scope.$watch('options.tagFilter', setTagState);
|
||||
|
||||
$scope.$watch('selectedTags', function(selectedTags) {
|
||||
if (!selectedTags || !$scope.repository || !$scope.imageMap) { return; }
|
||||
|
||||
$scope.checkedTags.checked = selectedTags.map(function(tag) {
|
||||
return $scope.repository.tags[tag];
|
||||
});
|
||||
}, true);
|
||||
|
||||
$scope.$watch('repository', function(repository) {
|
||||
if (!repository) { return; }
|
||||
|
||||
// Load the repository's images.
|
||||
loadImages();
|
||||
|
||||
// Process each of the tags.
|
||||
setTagState();
|
||||
});
|
||||
|
||||
$scope.trackLineClass = function(index, track_info) {
|
||||
var startIndex = $.inArray(track_info.tags[0], $scope.tags);
|
||||
var endIndex = $.inArray(track_info.tags[track_info.tags.length - 1], $scope.tags);
|
||||
|
||||
if (index == startIndex) {
|
||||
return 'start';
|
||||
}
|
||||
|
||||
if (index == endIndex) {
|
||||
return 'end';
|
||||
}
|
||||
|
||||
if (index > startIndex && index < endIndex) {
|
||||
return 'middle';
|
||||
}
|
||||
|
||||
if (index < startIndex) {
|
||||
return 'before';
|
||||
}
|
||||
|
||||
if (index > endIndex) {
|
||||
return 'after';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.tablePredicateClass = function(name, predicate, reverse) {
|
||||
if (name != predicate) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'current ' + (reverse ? 'reversed' : '');
|
||||
};
|
||||
|
||||
$scope.askDeleteTag = function(tag) {
|
||||
$scope.tagActionHandler.askDeleteTag(tag);
|
||||
};
|
||||
|
||||
$scope.askDeleteMultipleTags = function(tags) {
|
||||
if (tags.length == 1) {
|
||||
$scope.askDeleteTag(tags[0].name);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.tagActionHandler.askDeleteMultipleTags(tags);
|
||||
};
|
||||
|
||||
$scope.orderBy = function(predicate) {
|
||||
if (predicate == $scope.options.predicate) {
|
||||
$scope.options.reverse = !$scope.options.reverse;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.options.reverse = false;
|
||||
$scope.options.predicate = predicate;
|
||||
};
|
||||
|
||||
$scope.commitTagFilter = function(tag) {
|
||||
var r = new RegExp('^[0-9a-f]{7}$');
|
||||
return tag.name.match(r);
|
||||
};
|
||||
|
||||
$scope.allTagFilter = function(tag) {
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.noTagFilter = function(tag) {
|
||||
return false;
|
||||
};
|
||||
|
||||
$scope.imageIDFilter = function(image_id, tag) {
|
||||
return tag.image_id == image_id;
|
||||
};
|
||||
|
||||
$scope.setTab = function(tab) {
|
||||
$location.search('tab', tab);
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
|
@ -10,7 +10,8 @@ angular.module('quay').directive('buildInfoBar', function () {
|
|||
restrict: 'C',
|
||||
scope: {
|
||||
'build': '=build',
|
||||
'showTime': '=showTime'
|
||||
'showTime': '=showTime',
|
||||
'hideId': '=hideId'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
}
|
||||
|
|
22
static/js/directives/ui/build-mini-status.js
Normal file
22
static/js/directives/ui/build-mini-status.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* An element which displays the status of a build as a mini-bar.
|
||||
*/
|
||||
angular.module('quay').directive('buildMiniStatus', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/build-mini-status.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'build': '=build'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.isBuilding = function(build) {
|
||||
if (!build) { return true; }
|
||||
return build.phase != 'complete' && build.phase != 'error';
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
22
static/js/directives/ui/build-state-icon.js
Normal file
22
static/js/directives/ui/build-state-icon.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* An element which displays an icon representing the state of the build.
|
||||
*/
|
||||
angular.module('quay').directive('buildStateIcon', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/build-state-icon.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'build': '=build'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.isBuilding = function(build) {
|
||||
if (!build) { return true; }
|
||||
return build.phase != 'complete' && build.phase != 'error';
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* An element which displays the status of a build.
|
||||
* DEPRECATED: An element which displays the status of a build.
|
||||
*/
|
||||
angular.module('quay').directive('buildStatus', function () {
|
||||
var directiveDefinitionObject = {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* An element which displays controls and information about a defined external notification on
|
||||
* DEPRECATED: An element which displays controls and information about a defined external notification on
|
||||
* a repository.
|
||||
*/
|
||||
angular.module('quay').directive('externalNotificationView', function () {
|
||||
|
|
22
static/js/directives/ui/filter-control.js
Normal file
22
static/js/directives/ui/filter-control.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* An element which displays a link to change a lookup filter, and shows whether it is selected.
|
||||
*/
|
||||
angular.module('quay').directive('filterControl', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/filter-control.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'filter': '=filter',
|
||||
'value': '@value'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.setFilter = function() {
|
||||
$scope.filter = $scope.value;
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
34
static/js/directives/ui/image-changes-view.js
Normal file
34
static/js/directives/ui/image-changes-view.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* An element which loads and displays UI representing the changes made in an image.
|
||||
*/
|
||||
angular.module('quay').directive('imageChangesView', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/image-changes-view.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'image': '=image',
|
||||
'hasChanges': '=hasChanges'
|
||||
},
|
||||
controller: function($scope, $element, ApiService) {
|
||||
$scope.$watch('image', function() {
|
||||
if (!$scope.image || !$scope.repository) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'image_id': $scope.image
|
||||
};
|
||||
|
||||
$scope.hasChanges = true;
|
||||
$scope.imageChangesResource = ApiService.getImageChangesAsResource(params).get(function(resp) {
|
||||
$scope.changeData = resp;
|
||||
$scope.hasChanges = resp.added.length || resp.removed.length || resp.changed.length;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
32
static/js/directives/ui/image-info-sidebar.js
Normal file
32
static/js/directives/ui/image-info-sidebar.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* An element which displays sidebar information for a image. Primarily used in the repo view.
|
||||
*/
|
||||
angular.module('quay').directive('imageInfoSidebar', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/image-info-sidebar.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'tracker': '=tracker',
|
||||
'image': '=image',
|
||||
|
||||
'tagSelected': '&tagSelected',
|
||||
'addTagRequested': '&addTagRequested'
|
||||
},
|
||||
controller: function($scope, $element, ImageMetadataService) {
|
||||
$scope.$watch('image', function(image) {
|
||||
if (!image || !$scope.tracker) { return; }
|
||||
$scope.imageData = $scope.tracker.lookupImage(image);
|
||||
});
|
||||
|
||||
$scope.parseDate = function(dateString) {
|
||||
return Date.parse(dateString);
|
||||
};
|
||||
|
||||
$scope.getFormattedCommand = ImageMetadataService.getFormattedCommand;
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -13,7 +13,7 @@ angular.module('quay').directive('repoListGrid', function () {
|
|||
starred: '=starred',
|
||||
user: "=user",
|
||||
namespace: '=namespace',
|
||||
toggleStar: '&toggleStar'
|
||||
starToggled: '&starToggled'
|
||||
},
|
||||
controller: function($scope, $element, UserService) {
|
||||
$scope.isOrganization = function(namespace) {
|
||||
|
|
53
static/js/directives/ui/repo-star.js
Normal file
53
static/js/directives/ui/repo-star.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* An element that displays the star status of a repository and allows it to be toggled.
|
||||
*/
|
||||
angular.module('quay').directive('repoStar', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-star.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
repository: '=repository',
|
||||
starToggled: '&starToggled'
|
||||
},
|
||||
controller: function($scope, $element, UserService, ApiService) {
|
||||
// Star a repository or unstar a repository.
|
||||
$scope.toggleStar = function() {
|
||||
if ($scope.repository.is_starred) {
|
||||
unstarRepo();
|
||||
} else {
|
||||
starRepo();
|
||||
}
|
||||
};
|
||||
|
||||
// Star a repository and update the UI.
|
||||
var starRepo = function() {
|
||||
var data = {
|
||||
'namespace': $scope.repository.namespace,
|
||||
'repository': $scope.repository.name
|
||||
};
|
||||
|
||||
ApiService.createStar(data).then(function(result) {
|
||||
$scope.repository.is_starred = true;
|
||||
$scope.starToggled({'repository': $scope.repository});
|
||||
}, ApiService.errorDisplay('Could not star repository'));
|
||||
};
|
||||
|
||||
// Unstar a repository and update the UI.
|
||||
var unstarRepo = function(repo) {
|
||||
var data = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
ApiService.deleteStar(null, data).then(function(result) {
|
||||
$scope.repository.is_starred = false;
|
||||
$scope.starToggled({'repository': $scope.repository});
|
||||
}, ApiService.errorDisplay('Could not unstar repository'));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return directiveDefinitionObject;
|
||||
});
|
91
static/js/directives/ui/repository-events-table.js
Normal file
91
static/js/directives/ui/repository-events-table.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* An element which displays a table of events on a repository and allows them to be
|
||||
* edited.
|
||||
*/
|
||||
angular.module('quay').directive('repositoryEventsTable', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repository-events-table.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, Restangular, UtilService, ExternalNotificationData) {
|
||||
$scope.showNewNotificationCounter = 0;
|
||||
|
||||
var loadNotifications = function() {
|
||||
if (!$scope.repository || $scope.notificationsResource) { return; }
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
$scope.notificationsResource = ApiService.listRepoNotificationsAsResource(params).get(
|
||||
function(resp) {
|
||||
$scope.notifications = resp.notifications;
|
||||
return $scope.notifications;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('repository', loadNotifications);
|
||||
loadNotifications();
|
||||
|
||||
$scope.handleNotificationCreated = function(notification) {
|
||||
$scope.notifications.push(notification);
|
||||
};
|
||||
|
||||
$scope.askCreateNotification = function() {
|
||||
$scope.showNewNotificationCounter++;
|
||||
};
|
||||
|
||||
$scope.getEventInfo = function(notification) {
|
||||
return ExternalNotificationData.getEventInfo(notification.event);
|
||||
};
|
||||
|
||||
$scope.getMethodInfo = function(notification) {
|
||||
return ExternalNotificationData.getMethodInfo(notification.method);
|
||||
};
|
||||
|
||||
$scope.deleteNotification = function(notification) {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'uuid': notification.uuid
|
||||
};
|
||||
|
||||
ApiService.deleteRepoNotification(null, params).then(function() {
|
||||
var index = $.inArray(notification, $scope.notifications);
|
||||
if (index < 0) { return; }
|
||||
$scope.notifications.splice(index, 1);
|
||||
}, ApiService.errorDisplay('Cannot delete notification'));
|
||||
};
|
||||
|
||||
$scope.showWebhookInfo = function(notification) {
|
||||
var eventId = notification.event;
|
||||
document.location = 'http://docs.quay.io/guides/notifications.html#webhook_' + eventId;
|
||||
};
|
||||
|
||||
$scope.testNotification = function(notification) {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'uuid': notification.uuid
|
||||
};
|
||||
|
||||
ApiService.testRepoNotification(null, params).then(function() {
|
||||
bootbox.dialog({
|
||||
"title": "Test Notification Queued",
|
||||
"message": "A test version of this notification has been queued and should appear shortly",
|
||||
"buttons": {
|
||||
"close": {
|
||||
"label": "Close",
|
||||
"className": "btn-primary"
|
||||
}
|
||||
}
|
||||
});
|
||||
}, ApiService.errorDisplay('Could not issue test notification'));
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
147
static/js/directives/ui/repository-permissions-table.js
Normal file
147
static/js/directives/ui/repository-permissions-table.js
Normal file
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* An element which displays a table of permissions on a repository and allows them to be
|
||||
* edited.
|
||||
*/
|
||||
angular.module('quay').directive('repositoryPermissionsTable', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repository-permissions-table.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, Restangular, UtilService) {
|
||||
$scope.roles = [
|
||||
{ 'id': 'read', 'title': 'Read', 'kind': 'success' },
|
||||
{ 'id': 'write', 'title': 'Write', 'kind': 'success' },
|
||||
{ 'id': 'admin', 'title': 'Admin', 'kind': 'primary' }
|
||||
];
|
||||
|
||||
$scope.permissionResources = {'team': {}, 'user': {}};
|
||||
$scope.permissionCache = {};
|
||||
$scope.permissions = {};
|
||||
$scope.addPermissionInfo = {};
|
||||
|
||||
var loadAllPermissions = function() {
|
||||
if (!$scope.repository) { return; }
|
||||
fetchPermissions('user');
|
||||
fetchPermissions('team');
|
||||
};
|
||||
|
||||
var fetchPermissions = function(kind) {
|
||||
if ($scope.permissionResources[kind]['loading'] != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
var Kind = kind[0].toUpperCase() + kind.substring(1);
|
||||
var r = ApiService['listRepo' + Kind + 'PermissionsAsResource'](params).get(function(resp) {
|
||||
$scope.permissions[kind] = resp.permissions;
|
||||
return resp.permissions;
|
||||
});
|
||||
|
||||
$scope.permissionResources[kind] = r;
|
||||
};
|
||||
|
||||
$scope.$watch('repository', loadAllPermissions);
|
||||
loadAllPermissions();
|
||||
|
||||
var getPermissionEndpoint = function(entityName, kind) {
|
||||
var namespace = $scope.repository.namespace;
|
||||
var name = $scope.repository.name;
|
||||
var url = UtilService.getRestUrl('repository', namespace, name, 'permissions', kind, entityName);
|
||||
return Restangular.one(url);
|
||||
};
|
||||
|
||||
$scope.buildEntityForPermission = function(name, permission, kind) {
|
||||
var key = name + ':' + kind;
|
||||
if ($scope.permissionCache[key]) {
|
||||
return $scope.permissionCache[key];
|
||||
}
|
||||
|
||||
return $scope.permissionCache[key] = {
|
||||
'kind': kind,
|
||||
'name': name,
|
||||
'is_robot': permission.is_robot,
|
||||
'is_org_member': permission.is_org_member
|
||||
};
|
||||
};
|
||||
|
||||
$scope.addPermission = function() {
|
||||
$scope.addPermissionInfo['working'] = true;
|
||||
$scope.addNewPermission($scope.addPermissionInfo.entity, $scope.addPermissionInfo.role)
|
||||
};
|
||||
|
||||
$scope.grantPermission = function(entity, callback) {
|
||||
$scope.addRole(entity.name, 'read', entity.kind, callback);
|
||||
};
|
||||
|
||||
$scope.addNewPermission = function(entity, opt_role) {
|
||||
// Don't allow duplicates.
|
||||
if (!entity || !entity.kind || $scope.permissions[entity.kind][entity.name]) {
|
||||
$scope.addPermissionInfo = {};
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.is_org_member === false) {
|
||||
$scope.grantPermissionInfo = {
|
||||
'entity': entity
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.addRole(entity.name, opt_role || 'read', entity.kind);
|
||||
};
|
||||
|
||||
$scope.deleteRole = function(entityName, kind) {
|
||||
var errorHandler = ApiService.errorDisplay('Cannot change permission', function(resp) {
|
||||
if (resp.status == 409) {
|
||||
return 'Cannot change permission as you do not have the authority';
|
||||
}
|
||||
});
|
||||
|
||||
var endpoint = getPermissionEndpoint(entityName, kind);
|
||||
endpoint.customDELETE().then(function() {
|
||||
delete $scope.permissions[kind][entityName];
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.addRole = function(entityName, role, kind, opt_callback) {
|
||||
var permission = {
|
||||
'role': role,
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Cannot change permission', function() {
|
||||
opt_callback && opt_callback(false);
|
||||
$scope.addPermissionInfo = {};
|
||||
});
|
||||
|
||||
var endpoint = getPermissionEndpoint(entityName, kind);
|
||||
endpoint.customPUT(permission).then(function(result) {
|
||||
$scope.permissions[kind][entityName] = result;
|
||||
$scope.addPermissionInfo = {};
|
||||
opt_callback && opt_callback(true)
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.setRole = function(role, entityName, kind) {
|
||||
var errorDisplay = ApiService.errorDisplay(function(resp) {
|
||||
$scope.permissions[kind][entityName] = {'role': currentRole};
|
||||
});
|
||||
|
||||
var permission = $scope.permissions[kind][entityName];
|
||||
var currentRole = permission.role;
|
||||
permission.role = role;
|
||||
|
||||
var endpoint = getPermissionEndpoint(entityName, kind);
|
||||
endpoint.customPUT(permission).then(function() {}, errorDisplay);
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
68
static/js/directives/ui/repository-tokens-table.js
Normal file
68
static/js/directives/ui/repository-tokens-table.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* An element which displays a table of tokens on a repository and allows them to be
|
||||
* edited.
|
||||
*/
|
||||
angular.module('quay').directive('repositoryTokensTable', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repository-tokens-table.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'hasTokens': '=hasTokens'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, Restangular, UtilService) {
|
||||
$scope.roles = [
|
||||
{ 'id': 'read', 'title': 'Read', 'kind': 'success' },
|
||||
{ 'id': 'write', 'title': 'Write', 'kind': 'success' },
|
||||
{ 'id': 'admin', 'title': 'Admin', 'kind': 'primary' }
|
||||
];
|
||||
|
||||
$scope.hasTokens = false;
|
||||
|
||||
var loadTokens = function() {
|
||||
if (!$scope.repository || $scope.tokensResource) { return; }
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
$scope.tokensResource = ApiService.listRepoTokensAsResource(params).get(function(resp) {
|
||||
$scope.tokens = resp.tokens;
|
||||
$scope.hasTokens = Object.keys($scope.tokens).length >= 1;
|
||||
}, ApiService.errorDisplay('Could not load access tokens'));
|
||||
};
|
||||
|
||||
$scope.$watch('repository', loadTokens);
|
||||
loadTokens();
|
||||
|
||||
$scope.deleteToken = function(tokenCode) {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'code': tokenCode
|
||||
};
|
||||
|
||||
ApiService.deleteToken(null, params).then(function() {
|
||||
delete $scope.tokens[tokenCode];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.changeTokenAccess = function(tokenCode, newAccess) {
|
||||
var role = {
|
||||
'role': newAccess
|
||||
};
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'code': tokenCode
|
||||
};
|
||||
|
||||
ApiService.changeToken(role, params).then(function(updated) {
|
||||
$scope.tokens[updated.code] = updated;
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -21,13 +21,17 @@ angular.module('quay').directive('resourceView', function () {
|
|||
}
|
||||
|
||||
var resources = $scope.resources || [$scope.resource];
|
||||
if (!resources.length) {
|
||||
return 'loading';
|
||||
}
|
||||
|
||||
for (var i = 0; i < resources.length; ++i) {
|
||||
var current = resources[i];
|
||||
if (current.loading) {
|
||||
return 'loading';
|
||||
}
|
||||
|
||||
if (current.error) {
|
||||
if (current.hasError) {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
|
28
static/js/directives/ui/tag-info-sidebar.js
Normal file
28
static/js/directives/ui/tag-info-sidebar.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* An element which displays sidebar information for a tag. Primarily used in the repo view.
|
||||
*/
|
||||
angular.module('quay').directive('tagInfoSidebar', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/tag-info-sidebar.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'tracker': '=tracker',
|
||||
'tag': '=tag',
|
||||
|
||||
'imageSelected': '&imageSelected',
|
||||
'deleteTagRequested': '&deleteTagRequested'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.$watch('tag', function(tag) {
|
||||
if (!tag || !$scope.tracker) { return; }
|
||||
|
||||
$scope.tagImage = $scope.tracker.getImageForTag(tag);
|
||||
$scope.tagData = $scope.tracker.lookupTag(tag);
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
148
static/js/directives/ui/tag-operations-dialog.js
Normal file
148
static/js/directives/ui/tag-operations-dialog.js
Normal file
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* An element which adds a series of dialogs for performing operations for tags (adding, moving
|
||||
* deleting).
|
||||
*/
|
||||
angular.module('quay').directive('tagOperationsDialog', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/tag-operations-dialog.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'images': '=images',
|
||||
'actionHandler': '=actionHandler',
|
||||
|
||||
'tagChanged': '&tagChanged'
|
||||
},
|
||||
controller: function($scope, $element, $timeout, ApiService) {
|
||||
$scope.addingTag = false;
|
||||
|
||||
var markChanged = function(added, removed) {
|
||||
// Reload the repository and the images.
|
||||
$scope.repository.get().then(function(resp) {
|
||||
$scope.repository = resp;
|
||||
|
||||
var params = {
|
||||
'repository': resp.namespace + '/' + resp.name
|
||||
};
|
||||
|
||||
ApiService.listRepositoryImages(null, params).then(function(resp) {
|
||||
$scope.images = resp.images;
|
||||
|
||||
// Note: We need the timeout here so that Angular can $digest the images change
|
||||
// on the parent scope before the tagChanged callback occurs.
|
||||
$timeout(function() {
|
||||
$scope.tagChanged({
|
||||
'data': { 'added': added, 'removed': removed }
|
||||
});
|
||||
}, 1);
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
$scope.isAnotherImageTag = function(image, tag) {
|
||||
if (!$scope.repository || !$scope.images) { return; }
|
||||
|
||||
var found = $scope.repository.tags[tag];
|
||||
if (found == null) { return false; }
|
||||
return found.image_id != image;
|
||||
};
|
||||
|
||||
$scope.isOwnedTag = function(image, tag) {
|
||||
if (!$scope.repository || !$scope.images) { return; }
|
||||
|
||||
var found = $scope.repository.tags[tag];
|
||||
if (found == null) { return false; }
|
||||
return found.image_id == image;
|
||||
};
|
||||
|
||||
$scope.createOrMoveTag = function(image, tag) {
|
||||
if (!$scope.repository.can_write) { return; }
|
||||
|
||||
$scope.addingTag = true;
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'tag': tag
|
||||
};
|
||||
|
||||
var data = {
|
||||
'image': image
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Cannot create or move tag', function(resp) {
|
||||
$element.find('#createOrMoveTagModal').modal('hide');
|
||||
});
|
||||
|
||||
ApiService.changeTagImage(data, params).then(function(resp) {
|
||||
$element.find('#createOrMoveTagModal').modal('hide');
|
||||
$scope.addingTag = false;
|
||||
markChanged([tag], []);
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.deleteMultipleTags = function(tags, callback) {
|
||||
var count = tags.length;
|
||||
var perform = function(index) {
|
||||
if (index >= count) {
|
||||
callback(true);
|
||||
markChanged([], tags);
|
||||
return;
|
||||
}
|
||||
|
||||
var tag_info = tags[index];
|
||||
$scope.deleteTag(tag_info.name, function(result) {
|
||||
if (!result) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
perform(index + 1);
|
||||
}, true);
|
||||
};
|
||||
|
||||
perform(0);
|
||||
};
|
||||
|
||||
$scope.deleteTag = function(tag, callback, opt_skipmarking) {
|
||||
if (!$scope.repository.can_write) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'tag': tag
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Cannot delete tag', callback);
|
||||
ApiService.deleteFullTag(null, params).then(function() {
|
||||
callback(true);
|
||||
!opt_skipmarking && markChanged([], [tag]);
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.actionHandler = {
|
||||
'askDeleteTag': function(tag) {
|
||||
$scope.deleteTagInfo = {
|
||||
'tag': tag
|
||||
};
|
||||
},
|
||||
|
||||
'askDeleteMultipleTags': function(tags) {
|
||||
$scope.deleteMultipleTagsInfo = {
|
||||
'tags': tags
|
||||
};
|
||||
},
|
||||
|
||||
'askAddTag': function(image) {
|
||||
$scope.tagToCreate = '';
|
||||
$scope.toTagImage = image;
|
||||
$scope.addingTag = false;
|
||||
$scope.addTagForm.$setPristine();
|
||||
$element.find('#createOrMoveTagModal').modal('show');
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -31,7 +31,9 @@ var DEPTH_WIDTH = 140;
|
|||
/**
|
||||
* Based off of http://mbostock.github.io/d3/talk/20111018/tree.html by Mike Bostock (@mbostock)
|
||||
*/
|
||||
function ImageHistoryTree(namespace, name, images, formatComment, formatTime, formatCommand) {
|
||||
function ImageHistoryTree(namespace, name, images, formatComment, formatTime, formatCommand,
|
||||
opt_tagFilter) {
|
||||
|
||||
/**
|
||||
* The namespace of the repo.
|
||||
*/
|
||||
|
@ -62,6 +64,11 @@ function ImageHistoryTree(namespace, name, images, formatComment, formatTime, fo
|
|||
*/
|
||||
this.formatCommand_ = formatCommand;
|
||||
|
||||
/**
|
||||
* Method for filtering the tags and image paths displayed in the tree.
|
||||
*/
|
||||
this.tagFilter_ = opt_tagFilter || function() { return true; };
|
||||
|
||||
/**
|
||||
* The current tag (if any).
|
||||
*/
|
||||
|
@ -153,7 +160,7 @@ ImageHistoryTree.prototype.updateDimensions_ = function() {
|
|||
$('#' + container).removeOverscroll();
|
||||
var viewportHeight = $(window).height();
|
||||
var boundingBox = document.getElementById(container).getBoundingClientRect();
|
||||
document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top - 150) + 'px';
|
||||
document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top - 100) + 'px';
|
||||
|
||||
this.setupOverscroll_();
|
||||
|
||||
|
@ -526,7 +533,14 @@ ImageHistoryTree.prototype.pruneUnreferenced_ = function(node) {
|
|||
return node.children.length == 0;
|
||||
}
|
||||
|
||||
return (node.children.length == 0 && node.tags.length == 0);
|
||||
var tags = [];
|
||||
for (var i = 0; i < node.tags.length; ++i) {
|
||||
if (this.tagFilter_(node.tags[i])) {
|
||||
tags.push(node.tags[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return (node.children.length == 0 && tags.length == 0);
|
||||
};
|
||||
|
||||
|
||||
|
@ -554,7 +568,7 @@ ImageHistoryTree.prototype.collapseNodes_ = function(node) {
|
|||
var current = node;
|
||||
var previous = node;
|
||||
var encountered = [];
|
||||
while (current.children.length == 1) {
|
||||
while (current.children.length == 1 && current.tags.length == 0) {
|
||||
encountered.push(current);
|
||||
previous = current;
|
||||
current = current.children[0];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(function() {
|
||||
/**
|
||||
* Repository admin/settings page.
|
||||
* DEPRECATED: Repository admin/settings page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('repo-admin', 'repo-admin.html', RepoAdminCtrl);
|
||||
|
|
|
@ -52,38 +52,14 @@
|
|||
return !!UserService.getOrganization(namespace);
|
||||
};
|
||||
|
||||
// Star a repository or unstar a repository.
|
||||
$scope.toggleStar = function(repo) {
|
||||
$scope.starToggled = function(repo) {
|
||||
if (repo.is_starred) {
|
||||
unstarRepo(repo);
|
||||
} else {
|
||||
starRepo(repo);
|
||||
}
|
||||
}
|
||||
|
||||
// Star a repository and update the UI.
|
||||
var starRepo = function(repo) {
|
||||
var data = {
|
||||
'namespace': repo.namespace,
|
||||
'repository': repo.name
|
||||
};
|
||||
ApiService.createStar(data).then(function(result) {
|
||||
repo.is_starred = true;
|
||||
$scope.starred_repositories.value.push(repo);
|
||||
}, ApiService.errorDisplay('Could not star repository'));
|
||||
};
|
||||
|
||||
// Unstar a repository and update the UI.
|
||||
var unstarRepo = function(repo) {
|
||||
var data = {
|
||||
'repository': repo.namespace + '/' + repo.name
|
||||
};
|
||||
ApiService.deleteStar(null, data).then(function(result) {
|
||||
repo.is_starred = false;
|
||||
} else {
|
||||
$scope.starred_repositories.value = $scope.starred_repositories.value.filter(function(repo) {
|
||||
return repo.is_starred;
|
||||
});
|
||||
}, ApiService.errorDisplay('Could not unstar repository'));
|
||||
}
|
||||
};
|
||||
|
||||
// Finds a duplicate repo if it exists. If it doesn't, inserts the repo.
|
||||
|
|
|
@ -3,10 +3,110 @@
|
|||
* Repository view page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('repo-view', 'repo-view.html', RepoCtrl);
|
||||
pages.create('repo-view', 'repo-view.html', RepoViewCtrl, {
|
||||
'newLayout': true,
|
||||
'title': '{{ namespace }}/{{ name }}',
|
||||
'description': 'Repository {{ namespace }}/{{ name }}'
|
||||
}, ['layout'])
|
||||
|
||||
pages.create('repo-view', 'old-repo-view.html', OldRepoViewCtrl, {
|
||||
}, ['old-layout']);
|
||||
}]);
|
||||
|
||||
function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout, Config, UtilService) {
|
||||
function RepoViewCtrl($scope, $routeParams, $location, ApiService, UserService, AngularPollChannel) {
|
||||
$scope.namespace = $routeParams.namespace;
|
||||
$scope.name = $routeParams.name;
|
||||
|
||||
$scope.logsShown = 0;
|
||||
$scope.viewScope = {
|
||||
'selectedTags': [],
|
||||
'repository': null,
|
||||
'builds': null,
|
||||
'changesVisible': false
|
||||
};
|
||||
|
||||
var buildPollChannel = null;
|
||||
|
||||
// Make sure we track the current user.
|
||||
UserService.updateUserIn($scope);
|
||||
|
||||
// Watch the selected tags and update the URL accordingly.
|
||||
$scope.$watch('viewScope.selectedTags', function(selectedTags) {
|
||||
if (!selectedTags || !$scope.viewScope.repository) { return; }
|
||||
|
||||
var tags = filterTags(selectedTags);
|
||||
if (!tags.length) {
|
||||
$location.search('tag', null);
|
||||
return;
|
||||
}
|
||||
|
||||
$location.search('tag', tags.join(','));
|
||||
}, true);
|
||||
|
||||
// Watch the repository to filter any tags removed.
|
||||
$scope.$watch('viewScope.repository', function(repository) {
|
||||
if (!repository) { return; }
|
||||
$scope.viewScope.selectedTags = filterTags($scope.viewScope.selectedTags);
|
||||
});
|
||||
|
||||
var filterTags = function(tags) {
|
||||
return (tags || []).filter(function(tag) {
|
||||
return !!$scope.viewScope.repository.tags[tag];
|
||||
});
|
||||
};
|
||||
|
||||
var loadRepository = function() {
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name
|
||||
};
|
||||
|
||||
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||
$scope.viewScope.repository = repo;
|
||||
$scope.setTags($routeParams.tag);
|
||||
|
||||
// Track builds.
|
||||
buildPollChannel = AngularPollChannel.create($scope, loadRepositoryBuilds, 5000 /* 5s */);
|
||||
buildPollChannel.start();
|
||||
});
|
||||
};
|
||||
|
||||
var loadRepositoryBuilds = function(callback) {
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name
|
||||
};
|
||||
|
||||
var errorHandler = function() {
|
||||
callback(false);
|
||||
};
|
||||
|
||||
$scope.repositoryBuildsResource = ApiService.getRepoBuildsAsResource(params, /* background */true).get(function(resp) {
|
||||
$scope.viewScope.builds = resp.builds;
|
||||
callback(true);
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
// Load the repository.
|
||||
loadRepository();
|
||||
|
||||
$scope.setTags = function(tagNames) {
|
||||
if (!tagNames) {
|
||||
$scope.viewScope.selectedTags = [];
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.viewScope.selectedTags = $.unique(tagNames.split(','));
|
||||
};
|
||||
|
||||
$scope.showLogs = function() {
|
||||
$scope.logsShown++;
|
||||
};
|
||||
|
||||
$scope.handleChangesState = function(value) {
|
||||
$scope.viewScope.changesVisible = value;
|
||||
};
|
||||
}
|
||||
|
||||
function OldRepoViewCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout, Config, UtilService) {
|
||||
$scope.Config = Config;
|
||||
|
||||
var namespace = $routeParams.namespace;
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
* Helper service for defining the various kinds of build triggers and retrieving information
|
||||
* about them.
|
||||
*/
|
||||
angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'KeyService',
|
||||
function(UtilService, $sanitize, KeyService) {
|
||||
angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'KeyService', 'Features', 'CookieService',
|
||||
function(UtilService, $sanitize, KeyService, Features, CookieService) {
|
||||
var triggerService = {};
|
||||
|
||||
var triggerTypes = {
|
||||
|
@ -28,15 +28,46 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
|
|||
var redirect_uri = KeyService['githubRedirectUri'] + '/trigger/' +
|
||||
namespace + '/' + repository;
|
||||
|
||||
// TODO(jschorr): Remove once the new layout is in place.
|
||||
if (CookieService.get('quay.exp-new-layout') == 'true') {
|
||||
redirect_uri += '/__new';
|
||||
}
|
||||
|
||||
var authorize_url = KeyService['githubTriggerAuthorizeUrl'];
|
||||
var client_id = KeyService['githubTriggerClientId'];
|
||||
|
||||
return authorize_url + 'client_id=' + client_id +
|
||||
'&scope=repo,user:email&redirect_uri=' + redirect_uri;
|
||||
},
|
||||
|
||||
'is_enabled': function() {
|
||||
return Features.GITHUB_BUILD;
|
||||
},
|
||||
|
||||
'icon': 'fa-github',
|
||||
|
||||
'title': function() {
|
||||
var isEnterprise = KeyService.isEnterprise('github-trigger');
|
||||
if (isEnterprise) {
|
||||
return 'GitHub Enterprise Repository Push';
|
||||
}
|
||||
|
||||
return 'GitHub Repository Push';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
triggerService.getTypes = function() {
|
||||
var types = [];
|
||||
for (var key in triggerTypes) {
|
||||
if (!triggerTypes.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
types.push(key);
|
||||
}
|
||||
return types;
|
||||
};
|
||||
|
||||
triggerService.getRedirectUrl = function(name, namespace, repository) {
|
||||
var type = triggerTypes[name];
|
||||
if (!type) {
|
||||
|
@ -45,6 +76,14 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
|
|||
return type['get_redirect_url'](namespace, repository);
|
||||
};
|
||||
|
||||
triggerService.getTitle = function(name) {
|
||||
var type = triggerTypes[name];
|
||||
if (!type) {
|
||||
return 'Unknown';
|
||||
}
|
||||
return type['title']();
|
||||
};
|
||||
|
||||
triggerService.getDescription = function(name, config) {
|
||||
var type = triggerTypes[name];
|
||||
if (!type) {
|
||||
|
@ -53,6 +92,10 @@ angular.module('quay').factory('TriggerService', ['UtilService', '$sanitize', 'K
|
|||
return type['description'](config);
|
||||
};
|
||||
|
||||
triggerService.getMetadata = function(name) {
|
||||
return triggerTypes[name];
|
||||
};
|
||||
|
||||
triggerService.getRunParameters = function(name, config) {
|
||||
var type = triggerTypes[name];
|
||||
if (!type) {
|
||||
|
|
|
@ -2,6 +2,61 @@
|
|||
* Service which provides helper methods for performing some simple UI operations.
|
||||
*/
|
||||
angular.module('quay').factory('UIService', [function() {
|
||||
var CheckStateController = function(items, opt_checked) {
|
||||
this.items = items;
|
||||
this.checked = opt_checked || [];
|
||||
this.listeners_ = [];
|
||||
};
|
||||
|
||||
CheckStateController.prototype.listen = function(callback) {
|
||||
this.listeners_.push(callback);
|
||||
};
|
||||
|
||||
CheckStateController.prototype.isChecked = function(item) {
|
||||
return $.inArray(item, this.checked) >= 0;
|
||||
};
|
||||
|
||||
CheckStateController.prototype.toggleItem = function(item) {
|
||||
if (this.isChecked(item)) {
|
||||
this.uncheckItem(item);
|
||||
} else {
|
||||
this.checkItem(item);
|
||||
}
|
||||
};
|
||||
|
||||
CheckStateController.prototype.toggleItems = function() {
|
||||
if (this.checked.length) {
|
||||
this.checked = [];
|
||||
} else {
|
||||
this.checked = this.items.slice();
|
||||
}
|
||||
this.callListeners_();
|
||||
};
|
||||
|
||||
CheckStateController.prototype.checkByFilter = function(filter) {
|
||||
this.checked = $.grep(this.items, filter);
|
||||
this.callListeners_();
|
||||
};
|
||||
|
||||
CheckStateController.prototype.checkItem = function(item) {
|
||||
this.checked.push(item);
|
||||
this.callListeners_();
|
||||
};
|
||||
|
||||
CheckStateController.prototype.uncheckItem = function(item) {
|
||||
this.checked = $.grep(this.checked, function(cItem) {
|
||||
return cItem != item;
|
||||
});
|
||||
this.callListeners_();
|
||||
};
|
||||
|
||||
CheckStateController.prototype.callListeners_ = function() {
|
||||
var checked = this.checked;
|
||||
this.listeners_.map(function(listener) {
|
||||
listener(checked);
|
||||
});
|
||||
};
|
||||
|
||||
var uiService = {};
|
||||
|
||||
uiService.hidePopover = function(elem) {
|
||||
|
@ -33,5 +88,9 @@ angular.module('quay').factory('UIService', [function() {
|
|||
}
|
||||
};
|
||||
|
||||
uiService.createCheckStateController = function(items, opt_checked) {
|
||||
return new CheckStateController(items, opt_checked);
|
||||
};
|
||||
|
||||
return uiService;
|
||||
}]);
|
||||
|
|
3
static/lib/angular-moment.min.js
vendored
3
static/lib/angular-moment.min.js
vendored
|
@ -1 +1,2 @@
|
|||
angular.module("angularMoment",[]).directive("amTimeAgo",["$window","$timeout",function(a,b){"use strict";return function(c,d,e){function f(){k&&(b.cancel(k),k=null)}function g(c){d.text(c.fromNow());var e=a.moment().diff(c,"minute"),f=3600;1>e?f=1:60>e?f=30:180>e&&(f=300),k=b(function(){g(c)},1e3*f,!1)}function h(){f(),g(a.moment(i,j))}var i,j,k=null;c.$watch(e.amTimeAgo,function(a){"undefined"!=typeof a&&null!==a&&(angular.isNumber(a)&&(a=new Date(a)),i=a,h())}),e.$observe("amFormat",function(a){j=a,h()}),c.$on("$destroy",function(){f()})}}]).filter("amDateFormat",["$window",function(a){"use strict";return function(b,c){return"undefined"==typeof b||null===b?"":(angular.isNumber(b)&&(b=new Date(b)),a.moment(b).format(c))}}]);
|
||||
"format global";"deps angular";"deps moment";!function(){"use strict";function a(a,b){return a.module("angularMoment",[]).constant("angularMomentConfig",{preprocess:null,timezone:"",format:null,statefulFilters:!0}).constant("moment",b).constant("amTimeAgoConfig",{withoutSuffix:!1,serverTime:null,titleFormat:null}).directive("amTimeAgo",["$window","moment","amMoment","amTimeAgoConfig","angularMomentConfig",function(b,c,d,e,f){return function(g,h,i){function j(){var a;if(e.serverTime){var b=(new Date).getTime(),d=b-u+e.serverTime;a=c(d)}else a=c();return a}function k(){q&&(b.clearTimeout(q),q=null)}function l(a){if(h.text(a.from(j(),s)),t&&!h.attr("title")&&h.attr("title",a.local().format(t)),!x){var c=Math.abs(j().diff(a,"minute")),d=3600;1>c?d=1:60>c?d=30:180>c&&(d=300),q=b.setTimeout(function(){l(a)},1e3*d)}}function m(a){y&&h.attr("datetime",a)}function n(){if(k(),o){var a=d.preprocessDate(o,v,r);l(a),m(a.toISOString())}}var o,p,q=null,r=f.format,s=e.withoutSuffix,t=e.titleFormat,u=(new Date).getTime(),v=f.preprocess,w=i.amTimeAgo.replace(/^::/,""),x=0===i.amTimeAgo.indexOf("::"),y="TIME"===h[0].nodeName.toUpperCase();p=g.$watch(w,function(a){return"undefined"==typeof a||null===a||""===a?(k(),void(o&&(h.text(""),m(""),o=null))):(o=a,n(),void(void 0!==a&&x&&p()))}),a.isDefined(i.amWithoutSuffix)&&g.$watch(i.amWithoutSuffix,function(a){"boolean"==typeof a?(s=a,n()):s=e.withoutSuffix}),i.$observe("amFormat",function(a){"undefined"!=typeof a&&(r=a,n())}),i.$observe("amPreprocess",function(a){v=a,n()}),g.$on("$destroy",function(){k()}),g.$on("amMoment:localeChanged",function(){n()})}}]).service("amMoment",["moment","$rootScope","$log","angularMomentConfig",function(b,c,d,e){this.preprocessors={utc:b.utc,unix:b.unix},this.changeLocale=function(d,e){var f=b.locale(d,e);return a.isDefined(d)&&c.$broadcast("amMoment:localeChanged"),f},this.changeTimezone=function(a){e.timezone=a,c.$broadcast("amMoment:timezoneChanged")},this.preprocessDate=function(c,f,g){return a.isUndefined(f)&&(f=e.preprocess),this.preprocessors[f]?this.preprocessors[f](c,g):(f&&d.warn("angular-moment: Ignoring unsupported value for preprocess: "+f),!isNaN(parseFloat(c))&&isFinite(c)?b(parseInt(c,10)):b(c,g))},this.applyTimezone=function(a){var b=e.timezone;return a&&b&&(a.tz?a=a.tz(b):d.warn("angular-moment: timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?")),a}}]).filter("amCalendar",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,d);var e=a(c);return e.isValid()?b.applyTimezone(e).calendar():""}return d.$stateful=c.statefulFilters,d}]).filter("amDateFormat",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d,e){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,e);var f=a(c);return f.isValid()?b.applyTimezone(f).format(d):""}return d.$stateful=c.statefulFilters,d}]).filter("amDurationFormat",["moment","angularMomentConfig",function(a,b){function c(b,c,d){return"undefined"==typeof b||null===b?"":a.duration(b,c).humanize(d)}return c.$stateful=b.statefulFilters,c}]).filter("amTimeAgo",["moment","amMoment","angularMomentConfig",function(a,b,c){function d(c,d,e){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,d);var f=a(c);return f.isValid()?b.applyTimezone(f).fromNow(e):""}return d.$stateful=c.statefulFilters,d}])}"function"==typeof define&&define.amd?define(["angular","moment"],a):"undefined"!=typeof module&&module&&module.exports?a(angular,require("moment")):a(angular,window.moment)}();
|
||||
//# sourceMappingURL=angular-moment.min.js.map
|
425
static/partials/old-repo-view.html
Normal file
425
static/partials/old-repo-view.html
Normal file
|
@ -0,0 +1,425 @@
|
|||
<div id="tagContextMenu" class="dropdown clearfix" tabindex="-1">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu">
|
||||
<li><a tabindex="-1" href="javascript:void(0)" ng-click="askDeleteTag(currentMenuTag)">Delete Tag</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="resource-view" resource="repository" error-message="'No Repository Found'">
|
||||
<div class="container-fluid repo repo-view">
|
||||
<!-- Repo Header -->
|
||||
<div class="header">
|
||||
<h3>
|
||||
<span class="repo-circle no-background" repo="repo"></span>
|
||||
<span class="repo-breadcrumb" repo="repo"></span>
|
||||
</h3>
|
||||
|
||||
<div class="repo-controls">
|
||||
<!-- Builds -->
|
||||
<div class="dropdown" data-placement="top" style="display: inline-block"
|
||||
bs-tooltip=""
|
||||
data-title="{{ runningBuilds.length ? 'Dockerfile Builds Running: ' + (runningBuilds.length) : 'Dockerfile Build' }}"
|
||||
quay-show="Features.BUILD_SUPPORT && (repo.can_write || buildHistory.length)">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-tasks fa-lg"></i>
|
||||
<span class="count" ng-class="runningBuilds.length ? 'visible' : ''"><span>{{ runningBuilds.length ? runningBuilds.length : '' }}</span></span>
|
||||
<b class="caret"></b>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-show="repo.can_write"><a href="{{ '/repository/' + repo.namespace + '/' + repo.name + '/build' }}">
|
||||
<i class="fa fa-tasks"></i>Dockerfile Build History</a>
|
||||
</li>
|
||||
<li ng-show="repo.can_write">
|
||||
<a href="javascript:void(0)" ng-click="showNewBuildDialog()">
|
||||
<i class="fa fa-plus" style="margin-left: 1px; margin-right: 8px;"></i>New Dockerfile Build
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="repo.can_admin">
|
||||
<a href="/repository/{{ repo.namespace }}/{{ repo.name }}/admin?tab=trigger">
|
||||
<i class="fa fa-bolt" style="margin-left: 3px; margin-right: 10px;"></i>Build Triggers
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="divider" ng-show="buildHistory && repo.can_write"></li>
|
||||
<li role="presentation" class="dropdown-header" ng-show="buildHistory.length">Recent Builds</li>
|
||||
<li ng-repeat="buildInfo in buildHistory">
|
||||
<div class="build-info" ng-class="repo.can_write ? 'clickable' : ''" ng-click="repo.can_write && showBuild(buildInfo)">
|
||||
<span class="build-status" build="buildInfo"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Admin -->
|
||||
<a id="admin-cog" href="{{ '/repository/' + repo.namespace + '/' + repo.name + '/admin' }}"
|
||||
ng-show="repo.can_admin">
|
||||
<button class="btn btn-default" data-title="Repository Settings" bs-tooltip="tooltip" data-placement="top">
|
||||
<i class="fa fa-cog fa-lg"></i></button></a>
|
||||
|
||||
<!-- Pull Command -->
|
||||
<span class="pull-command visible-md-inline">
|
||||
<div class="pull-container" ng-show="currentPullCommand">
|
||||
<button class="pull-selector dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa" ng-class="currentPullCommand.icon"></i>
|
||||
{{ currentPullCommand.shortTitle }}
|
||||
<b class="caret"></b>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="pullCommand in pullCommands">
|
||||
<a href="javascript:void(0)" ng-click="setCurrentPullCommand(pullCommand)"><i class="fa" ng-class="pullCommand.icon"></i>
|
||||
{{ pullCommand.title }}
|
||||
<sup ng-if="pullCommand.experimental">Experimental</sup>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="copy-box" hovering-message="true" value="currentPullCommand.command"></div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="description markdown-input" content="repo.description" can-write="repo.can_write"
|
||||
content-changed="updateForDescription" field-title="'repository description'"></div>
|
||||
|
||||
<!-- Empty messages -->
|
||||
<div ng-if="!currentTag.image_id && !currentImage">
|
||||
<!-- !building && !pushing -->
|
||||
<div class="repo-content" ng-show="!repo.is_building && !isPushing(images)">
|
||||
<div class="empty-message">
|
||||
This repository is empty
|
||||
</div>
|
||||
|
||||
<div class="empty-description" ng-show="repo.can_write">
|
||||
<div class="panel-default">
|
||||
<div class="panel-heading">How to push a new image to this repository:</div>
|
||||
<div class="panel-body">
|
||||
First login to the registry (if you have not done so already):
|
||||
<pre class="command">sudo docker login {{ Config.getDomain() }}</pre>
|
||||
|
||||
Tag an image to this repository:
|
||||
<pre class="command">sudo docker tag <i>0u123imageidgoeshere</i> {{ Config.getDomain() }}/{{repo.namespace}}/{{repo.name}}</pre>
|
||||
|
||||
Push the image to this repository:
|
||||
<pre class="command">sudo docker push {{ Config.getDomain() }}/{{repo.namespace}}/{{repo.name}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- building -->
|
||||
<div class="repo-content" ng-show="repo.is_building">
|
||||
<div class="empty-message">
|
||||
A build is currently processing. If this takes longer than an hour, please <a href="/contact">contact us</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- pushing -->
|
||||
<div class="repo-content" ng-show="!repo.is_building && isPushing(images)">
|
||||
<div class="empty-message">
|
||||
A push to this repository is in progress.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content view -->
|
||||
<div class="repo-content" ng-show="currentTag.image_id || currentImage">
|
||||
<!-- Image History -->
|
||||
<div id="image-history">
|
||||
<div class="row">
|
||||
<!-- Tree View container -->
|
||||
<div class="col-md-8">
|
||||
<div class="panel panel-default">
|
||||
<!-- Image history tree -->
|
||||
<div class="resource-view" resource="imageHistory">
|
||||
<div id="image-history-container" onresize="tree.notifyResized()"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Side Panel -->
|
||||
<div class="col-md-4">
|
||||
<div id="side-panel" class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<!-- Dropdown -->
|
||||
<div id="side-panel-dropdown" class="tag-dropdown dropdown" data-placement="top">
|
||||
<i class="fa fa-tag current-context-icon" ng-show="currentTag"></i>
|
||||
<i class="fa fa-archive current-context-icon" ng-show="!currentTag"></i>
|
||||
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="current-context">
|
||||
{{currentTag ? currentTag.name : currentImage.id.substr(0, 12)}}
|
||||
</span>
|
||||
<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="tag in repo.tags">
|
||||
<a href="javascript:void(0)" ng-click="setTag(tag.name, true)">
|
||||
<i class="fa fa-tag"></i>{{tag.name}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li ng-repeat="image in imageHistory.value">
|
||||
<a href="javascript:void(0)" ng-click="setImage(image.id, true)">
|
||||
{{image.id.substr(0, 12)}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="right-tag-controls">
|
||||
<i class="fa fa-tag" data-title="Tags" bs-tooltip="title">
|
||||
<span class="tag-count">{{getTagCount(repo)}}</span>
|
||||
</i>
|
||||
<i class="fa fa-archive" data-title="Images" bs-tooltip="title">
|
||||
<span class="tag-count">{{imageHistory.value.length}}</span>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<!-- Current Tag -->
|
||||
<div id="current-tag" ng-show="currentTag">
|
||||
<dl class="dl-normal">
|
||||
<dt>Last Modified</dt>
|
||||
<dd ng-if="!findImageForTag(currentTag, images)">
|
||||
<span class="quay-spinner"></span>
|
||||
</dd>
|
||||
|
||||
<dd am-time-ago="parseDate(findImageForTag(currentTag, images).created)"
|
||||
ng-if="findImageForTag(currentTag, images)">
|
||||
</dd>
|
||||
|
||||
<dt>Total Compressed Size</dt>
|
||||
<dd><span class="context-tooltip"
|
||||
data-title="The amount of data sent between Docker and the registry when pushing/pulling"
|
||||
bs-tooltip="tooltip.title" data-container="body">{{ getTotalSize(currentTag) | bytes }}</span>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div class="tag-image-sizes">
|
||||
<div class="tag-image-size" ng-repeat="image in getImagesForTagBySize(currentTag) | limitTo: 10">
|
||||
<span class="size-limiter">
|
||||
<span class="size-bar" style="{{ 'width:' + (image.size / getTotalSize(currentTag)) * 100 + '%' }}"
|
||||
bs-tooltip="" data-title="{{ image.size | bytes }}"></span>
|
||||
</span>
|
||||
<span class="size-title">
|
||||
<a class="image-size-link" href="javascript:void(0)" ng-click="setImage(image.id, true)"
|
||||
data-image="{{ image.id.substr(0, 12) }}">
|
||||
{{ image.id.substr(0, 12) }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-bar" ng-show="repo.can_admin">
|
||||
<button class="btn btn-default" ng-click="askDeleteTag(currentTag.name)">
|
||||
Delete Tag
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Image -->
|
||||
<div id="current-image" ng-show="currentImage && !currentTag">
|
||||
<div class="image-comment" ng-if="currentImage.comment">
|
||||
<blockquote style="margin-top: 10px;">
|
||||
<span class="markdown-view" content="currentImage.comment"></span>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<i class="fa fa-code section-icon" bs-tooltip="tooltip.title" data-title="Full Image ID"></i>
|
||||
<span class="section-info">
|
||||
<a class="image-link" href="{{'/repository/' + repo.namespace + '/' + repo.name + '/image/' + currentImage.id}}">{{ currentImage.id }}</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<i class="fa fa-tag section-icon" bs-tooltip="tooltip.title" data-title="Current Tags"></i>
|
||||
<span class="section-info section-info-with-dropdown">
|
||||
<a class="label tag label-default" ng-repeat="tag in currentImage.tags"
|
||||
href="/repository/{{ repo.namespace }}/{{ repo.name }}?tag={{ tag }}">
|
||||
{{ tag }}
|
||||
</a>
|
||||
<span style="color: #ccc;" ng-if="!currentImage.tags.length">(No Tags)</span>
|
||||
|
||||
|
||||
<div class="dropdown" data-placement="top" ng-if="repo.can_write || currentImage.tags">
|
||||
<a href="javascript:void(0)" class="dropdown-button" data-toggle="dropdown" bs-tooltip="tooltip.title" data-title="Manage Tags"
|
||||
data-container="body">
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li ng-repeat="tag in currentImage.tags">
|
||||
<a href="javascript:void(0)" ng-click="setTag(tag, true)">
|
||||
<i class="fa fa-tag"></i>{{tag}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider" role="presentation" ng-if="repo.can_write && currentImage.tags"></li>
|
||||
<li>
|
||||
<a href="javascript:void(0)" ng-click="showAddTag(currentImage)" ng-if="repo.can_write">
|
||||
<i class="fa fa-plus"></i>Add New Tag
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="image-section" ng-if="currentImage.command && currentImage.command.length">
|
||||
<i class="fa fa-terminal section-icon" bs-tooltip="tooltip.title" data-title="Image Command"></i>
|
||||
<span class="section-info">
|
||||
<span class="formatted-command trimmed"
|
||||
data-html="true"
|
||||
bs-tooltip="" data-title="{{ getTooltipCommand(currentImage) }}"
|
||||
data-placement="top">{{ getFormattedCommand(currentImage) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<i class="fa fa-calendar section-icon" bs-tooltip="tooltip.title" data-title="Created"></i>
|
||||
<span class="section-info">
|
||||
<dd am-time-ago="parseDate(currentImage.created)"></dd>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<i class="fa fa-cloud-upload section-icon" bs-tooltip="tooltip.title"
|
||||
data-title="The amount of data sent between Docker and the registry when pushing/pulling"></i>
|
||||
<span class="section-info">{{ currentImage.size | bytes }}</span>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<i class="fa fa-map-marker section-icon" bs-tooltip="tooltip.title"
|
||||
data-title="The geographic region(s) in which this image data is located"></i>
|
||||
<span class="section-info">
|
||||
<span class="location-view" location="location" ng-repeat="location in currentImage.locations"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Image changes loading -->
|
||||
<div class="resource-view" resource="currentImageChangeResource">
|
||||
<div class="changes-container small-changes-container section-info"
|
||||
ng-show="currentImageChanges.changed.length || currentImageChanges.added.length || currentImageChanges.removed.length">
|
||||
<div class="changes-count-container image-section">
|
||||
<i class="fa fa-code-fork section-icon" bs-tooltip="tooltip.title" data-title="File Changes"></i>
|
||||
<div style="float: right; display: inline-block">
|
||||
<span class="change-count added" ng-show="currentImageChanges.added.length > 0" data-title="Files Added"
|
||||
bs-tooltip="tooltip.title" data-placement="top" data-container="body">
|
||||
<i class="fa fa-plus-square"></i>
|
||||
<b>{{currentImageChanges.added.length}}</b>
|
||||
</span>
|
||||
<span class="change-count removed" ng-show="currentImageChanges.removed.length > 0" data-title="Files Removed"
|
||||
bs-tooltip="tooltip.title" data-placement="top" data-container="body">
|
||||
<i class="fa fa-minus-square"></i>
|
||||
<b>{{currentImageChanges.removed.length}}</b>
|
||||
</span>
|
||||
<span class="change-count changed" ng-show="currentImageChanges.changed.length > 0" data-title="Files Changed"
|
||||
bs-tooltip="tooltip.title" data-placement="top" data-container="body">
|
||||
<i class="fa fa-pencil-square"></i>
|
||||
<b>{{currentImageChanges.changed.length}}</b>
|
||||
</span>
|
||||
</div>
|
||||
<div id="collapseChanges" style="padding-top: 24px;">
|
||||
<div class="well well-sm">
|
||||
<div class="change added" ng-repeat="file in currentImageChanges.added | limitTo:2">
|
||||
<i class="fa fa-plus-square"></i>
|
||||
<span title="{{file}}">{{file}}</span>
|
||||
</div>
|
||||
<div class="change removed" ng-repeat="file in currentImageChanges.removed | limitTo:2">
|
||||
<i class="fa fa-minus-square"></i>
|
||||
<span title="{{file}}">{{file}}</span>
|
||||
</div>
|
||||
<div class="change changed" ng-repeat="file in currentImageChanges.changed | limitTo:2">
|
||||
<i class="fa fa-pencil-square"></i>
|
||||
<span title="{{file}}">{{file}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="more-changes" ng-show="getMoreCount(currentImageChanges) > 0">
|
||||
<a href="{{'/repository/' + repo.namespace + '/' + repo.name + '/image/' + currentImage.id}}">
|
||||
And {{getMoreCount(currentImageChanges)}} more...
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dockerfile-build-dialog" show-now="buildDialogShowCounter" repository="repo"
|
||||
build-started="handleBuildStarted(build)">
|
||||
</div>
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="addTagModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-show="!creatingTag">×</button>
|
||||
<h4 class="modal-title">{{ isAnotherImageTag(toTagImage, tagToCreate) ? 'Move' : 'Add' }} Tag to Image {{ toTagImage.id.substr(0, 12) }}</h4>
|
||||
</div>
|
||||
<form name="addTagForm" ng-submit="createOrMoveTag(toTagImage, tagToCreate, addTagForm.$invalid); addTagForm.$setPristine(); tagToCreate=''">
|
||||
<div class="modal-body">
|
||||
<input type="text" class="form-control" id="tagName" placeholder="Enter tag name"
|
||||
ng-model="tagToCreate" ng-pattern="/^([a-z0-9_\.-]){3,30}$/" required
|
||||
ng-disabled="creatingTag" autofocus>
|
||||
<div style="margin: 10px; margin-top: 20px;" ng-show="isOwnedTag(toTagImage, tagToCreate)">
|
||||
Note: <span class="label tag label-default">{{ tagToCreate }}</span> is already applied to this image.
|
||||
</div>
|
||||
<div style="margin: 10px; margin-top: 20px;" ng-show="isAnotherImageTag(toTagImage, tagToCreate)">
|
||||
Note: <span class="label tag label-default">{{ tagToCreate }}</span> is already applied to another image. This will <b>move</b> the tag.
|
||||
</div>
|
||||
<div class="tag-specific-images-view" tag="tagToCreate" repository="repo" images="images" image-cutoff="toTagImage"
|
||||
style="margin: 10px; margin-top: 20px; margin-bottom: -10px;" ng-show="isAnotherImageTag(toTagImage, tagToCreate)">
|
||||
This will also delete any unattach images and delete the following images:
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary"
|
||||
ng-disabled="!tagToCreate || addTagForm.$invalid || isOwnedTag(toTagImage, tagToCreate)"
|
||||
ng-class="isAnotherImageTag(toTagImage, tagToCreate) ? 'btn-warning' : 'btn-primary'" ng-show="!creatingTag">
|
||||
{{ isAnotherImageTag(toTagImage, tagToCreate) ? 'Move Tag' : 'Create Tag' }}
|
||||
</button>
|
||||
<button class="btn btn-default" data-dismiss="modal" ng-show="!creatingTag">Cancel</button>
|
||||
<div class="quay-spinner" ng-show="creatingTag"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="confirmdeleteTagModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Delete tag
|
||||
<span class="label tag" ng-class="tagToDelete == currentTag.name ? 'label-success' : 'label-default'">
|
||||
{{ tagToDelete }}
|
||||
</span>?
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="deletingTag">
|
||||
<div class="quay-spinner"></div>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="!deletingTag">
|
||||
Are you sure you want to delete tag
|
||||
<span class="label tag" ng-class="tagToDelete == currentTag.name ? 'label-success' : 'label-default'">
|
||||
{{ tagToDelete }}
|
||||
</span>?
|
||||
|
||||
<div class="tag-specific-images-view" tag="tagToDelete" repository="repo" images="images" style="margin-top: 20px">
|
||||
The following images and any other images not referenced by a tag will be deleted:
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="!deletingTag">
|
||||
<button type="button" class="btn btn-primary" ng-click="deleteTag(tagToDelete)">Delete Tag</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
|
@ -50,14 +50,15 @@
|
|||
<div class="repo-list-panel co-main-content-panel">
|
||||
<!-- Starred Repository Listing -->
|
||||
<div class="repo-list-grid" repositories-resource="starred_repositories"
|
||||
starred="true" toggle-star="toggleStar(repository)">
|
||||
starred="true"
|
||||
star-toggled="starToggled(repository)">
|
||||
</div>
|
||||
|
||||
<!-- User and Org Repository Listings -->
|
||||
<div ng-repeat="namespace in namespaces">
|
||||
<div class="repo-list-grid" repositories-resource="namespace.repositories"
|
||||
starred="false" user="user" namespace="namespace"
|
||||
toggle-star="toggleStar(repository)">
|
||||
star-toggled="starToggled(repository)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,425 +1,88 @@
|
|||
<div id="tagContextMenu" class="dropdown clearfix" tabindex="-1">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu">
|
||||
<li><a tabindex="-1" href="javascript:void(0)" ng-click="askDeleteTag(currentMenuTag)">Delete Tag</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="resource-view repository-view"
|
||||
resource="repositoryResource"
|
||||
error-message="'Repository not found'">
|
||||
<div class="page-content">
|
||||
<div class="cor-title">
|
||||
<span class="cor-title-link"></span>
|
||||
<span class="cor-title-content">
|
||||
<span class="repo-circle no-background" repo="viewScope.repository"></span>
|
||||
{{ namespace }} / {{ name }}
|
||||
<span class="repo-star" repository="viewScope.repository" ng-if="!user.anonymous"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="resource-view" resource="repository" error-message="'No Repository Found'">
|
||||
<div class="container-fluid repo repo-view">
|
||||
<!-- Repo Header -->
|
||||
<div class="header">
|
||||
<h3>
|
||||
<span class="repo-circle no-background" repo="repo"></span>
|
||||
<span class="repo-breadcrumb" repo="repo"></span>
|
||||
</h3>
|
||||
|
||||
<div class="repo-controls">
|
||||
<!-- Builds -->
|
||||
<div class="dropdown" data-placement="top" style="display: inline-block"
|
||||
bs-tooltip=""
|
||||
data-title="{{ runningBuilds.length ? 'Dockerfile Builds Running: ' + (runningBuilds.length) : 'Dockerfile Build' }}"
|
||||
quay-show="Features.BUILD_SUPPORT && (repo.can_write || buildHistory.length)">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-tasks fa-lg"></i>
|
||||
<span class="count" ng-class="runningBuilds.length ? 'visible' : ''"><span>{{ runningBuilds.length ? runningBuilds.length : '' }}</span></span>
|
||||
<b class="caret"></b>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-show="repo.can_write"><a href="{{ '/repository/' + repo.namespace + '/' + repo.name + '/build' }}">
|
||||
<i class="fa fa-tasks"></i>Dockerfile Build History</a>
|
||||
</li>
|
||||
<li ng-show="repo.can_write">
|
||||
<a href="javascript:void(0)" ng-click="showNewBuildDialog()">
|
||||
<i class="fa fa-plus" style="margin-left: 1px; margin-right: 8px;"></i>New Dockerfile Build
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="repo.can_admin">
|
||||
<a href="/repository/{{ repo.namespace }}/{{ repo.name }}/admin?tab=trigger">
|
||||
<i class="fa fa-bolt" style="margin-left: 3px; margin-right: 10px;"></i>Build Triggers
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" class="divider" ng-show="buildHistory && repo.can_write"></li>
|
||||
<li role="presentation" class="dropdown-header" ng-show="buildHistory.length">Recent Builds</li>
|
||||
<li ng-repeat="buildInfo in buildHistory">
|
||||
<div class="build-info" ng-class="repo.can_write ? 'clickable' : ''" ng-click="repo.can_write && showBuild(buildInfo)">
|
||||
<span class="build-status" build="buildInfo"></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Admin -->
|
||||
<a id="admin-cog" href="{{ '/repository/' + repo.namespace + '/' + repo.name + '/admin' }}"
|
||||
ng-show="repo.can_admin">
|
||||
<button class="btn btn-default" data-title="Repository Settings" bs-tooltip="tooltip" data-placement="top">
|
||||
<i class="fa fa-cog fa-lg"></i></button></a>
|
||||
|
||||
<!-- Pull Command -->
|
||||
<span class="pull-command visible-md-inline">
|
||||
<div class="pull-container" ng-show="currentPullCommand">
|
||||
<button class="pull-selector dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa" ng-class="currentPullCommand.icon"></i>
|
||||
{{ currentPullCommand.shortTitle }}
|
||||
<b class="caret"></b>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="pullCommand in pullCommands">
|
||||
<a href="javascript:void(0)" ng-click="setCurrentPullCommand(pullCommand)"><i class="fa" ng-class="pullCommand.icon"></i>
|
||||
{{ pullCommand.title }}
|
||||
<sup ng-if="pullCommand.experimental">Experimental</sup>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="copy-box" hovering-message="true" value="currentPullCommand.command"></div>
|
||||
</div>
|
||||
<div class="cor-tab-panel">
|
||||
<div class="cor-tabs">
|
||||
<span class="cor-tab" tab-active="true" tab-title="Information" tab-target="#info">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="description markdown-input" content="repo.description" can-write="repo.can_write"
|
||||
content-changed="updateForDescription" field-title="'repository description'"></div>
|
||||
<span class="cor-tab" tab-title="Tags" tab-target="#tags">
|
||||
<i class="fa fa-tags"></i>
|
||||
</span>
|
||||
|
||||
<!-- Empty messages -->
|
||||
<div ng-if="!currentTag.image_id && !currentImage">
|
||||
<!-- !building && !pushing -->
|
||||
<div class="repo-content" ng-show="!repo.is_building && !isPushing(images)">
|
||||
<div class="empty-message">
|
||||
This repository is empty
|
||||
<span class="cor-tab" tab-title="Builds" tab-target="#builds"
|
||||
quay-show="viewScope.repository.can_write && Features.BUILD_SUPPORT">
|
||||
<i class="fa fa-tasks"></i>
|
||||
</span>
|
||||
|
||||
<span class="cor-tab" tab-title="Visualize" tab-target="#changes"
|
||||
tab-shown="handleChangesState(true)" tab-hidden="handleChangesState(false)">
|
||||
<i class="fa fa-code-fork"></i>
|
||||
</span>
|
||||
|
||||
<!-- Admin Only Tabs -->
|
||||
<span class="cor-tab" tab-title="Usage Logs" tab-target="#logs" tab-init="showLogs()"
|
||||
ng-if="viewScope.repository.can_admin">
|
||||
<i class="fa fa-bar-chart"></i>
|
||||
</span>
|
||||
|
||||
<span class="cor-tab" tab-title="Settings" tab-target="#settings"
|
||||
ng-if="viewScope.repository.can_admin">
|
||||
<i class="fa fa-gear"></i>
|
||||
</span>
|
||||
</div> <!-- /cor-tabs -->
|
||||
|
||||
<div class="cor-tab-content">
|
||||
<!-- Information -->
|
||||
<div id="info" class="tab-pane active">
|
||||
<div class="repo-panel-info"
|
||||
repository="viewScope.repository"
|
||||
builds="viewScope.builds"></div>
|
||||
</div>
|
||||
|
||||
<div class="empty-description" ng-show="repo.can_write">
|
||||
<div class="panel-default">
|
||||
<div class="panel-heading">How to push a new image to this repository:</div>
|
||||
<div class="panel-body">
|
||||
First login to the registry (if you have not done so already):
|
||||
<pre class="command">sudo docker login {{ Config.getDomain() }}</pre>
|
||||
|
||||
Tag an image to this repository:
|
||||
<pre class="command">sudo docker tag <i>0u123imageidgoeshere</i> {{ Config.getDomain() }}/{{repo.namespace}}/{{repo.name}}</pre>
|
||||
|
||||
Push the image to this repository:
|
||||
<pre class="command">sudo docker push {{ Config.getDomain() }}/{{repo.namespace}}/{{repo.name}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tags -->
|
||||
<div id="tags" class="tab-pane">
|
||||
<div class="repo-panel-tags"
|
||||
repository="viewScope.repository"
|
||||
selected-tags="viewScope.selectedTags"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- building -->
|
||||
<div class="repo-content" ng-show="repo.is_building">
|
||||
<div class="empty-message">
|
||||
A build is currently processing. If this takes longer than an hour, please <a href="/contact">contact us</a>
|
||||
<!-- Builds -->
|
||||
<div id="builds" class="tab-pane">
|
||||
<div class="repo-panel-builds"
|
||||
repository="viewScope.repository"
|
||||
builds="viewScope.builds"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- pushing -->
|
||||
<div class="repo-content" ng-show="!repo.is_building && isPushing(images)">
|
||||
<div class="empty-message">
|
||||
A push to this repository is in progress.
|
||||
<!-- Changes -->
|
||||
<div id="changes" class="tab-pane">
|
||||
<div class="repo-panel-changes"
|
||||
repository="viewScope.repository"
|
||||
selected-tags="viewScope.selectedTags"
|
||||
is-enabled="viewScope.changesVisible"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content view -->
|
||||
<div class="repo-content" ng-show="currentTag.image_id || currentImage">
|
||||
<!-- Image History -->
|
||||
<div id="image-history">
|
||||
<div class="row">
|
||||
<!-- Tree View container -->
|
||||
<div class="col-md-8">
|
||||
<div class="panel panel-default">
|
||||
<!-- Image history tree -->
|
||||
<div class="resource-view" resource="imageHistory">
|
||||
<div id="image-history-container" onresize="tree.notifyResized()"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Side Panel -->
|
||||
<div class="col-md-4">
|
||||
<div id="side-panel" class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<!-- Dropdown -->
|
||||
<div id="side-panel-dropdown" class="tag-dropdown dropdown" data-placement="top">
|
||||
<i class="fa fa-tag current-context-icon" ng-show="currentTag"></i>
|
||||
<i class="fa fa-archive current-context-icon" ng-show="!currentTag"></i>
|
||||
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="current-context">
|
||||
{{currentTag ? currentTag.name : currentImage.id.substr(0, 12)}}
|
||||
</span>
|
||||
<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="tag in repo.tags">
|
||||
<a href="javascript:void(0)" ng-click="setTag(tag.name, true)">
|
||||
<i class="fa fa-tag"></i>{{tag.name}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li ng-repeat="image in imageHistory.value">
|
||||
<a href="javascript:void(0)" ng-click="setImage(image.id, true)">
|
||||
{{image.id.substr(0, 12)}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="right-tag-controls">
|
||||
<i class="fa fa-tag" data-title="Tags" bs-tooltip="title">
|
||||
<span class="tag-count">{{getTagCount(repo)}}</span>
|
||||
</i>
|
||||
<i class="fa fa-archive" data-title="Images" bs-tooltip="title">
|
||||
<span class="tag-count">{{imageHistory.value.length}}</span>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="panel-body">
|
||||
<!-- Current Tag -->
|
||||
<div id="current-tag" ng-show="currentTag">
|
||||
<dl class="dl-normal">
|
||||
<dt>Last Modified</dt>
|
||||
<dd ng-if="!findImageForTag(currentTag, images)">
|
||||
<span class="quay-spinner"></span>
|
||||
</dd>
|
||||
|
||||
<dd am-time-ago="parseDate(findImageForTag(currentTag, images).created)"
|
||||
ng-if="findImageForTag(currentTag, images)">
|
||||
</dd>
|
||||
|
||||
<dt>Total Compressed Size</dt>
|
||||
<dd><span class="context-tooltip"
|
||||
data-title="The amount of data sent between Docker and the registry when pushing/pulling"
|
||||
bs-tooltip="tooltip.title" data-container="body">{{ getTotalSize(currentTag) | bytes }}</span>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div class="tag-image-sizes">
|
||||
<div class="tag-image-size" ng-repeat="image in getImagesForTagBySize(currentTag) | limitTo: 10">
|
||||
<span class="size-limiter">
|
||||
<span class="size-bar" style="{{ 'width:' + (image.size / getTotalSize(currentTag)) * 100 + '%' }}"
|
||||
bs-tooltip="" data-title="{{ image.size | bytes }}"></span>
|
||||
</span>
|
||||
<span class="size-title">
|
||||
<a class="image-size-link" href="javascript:void(0)" ng-click="setImage(image.id, true)"
|
||||
data-image="{{ image.id.substr(0, 12) }}">
|
||||
{{ image.id.substr(0, 12) }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-bar" ng-show="repo.can_admin">
|
||||
<button class="btn btn-default" ng-click="askDeleteTag(currentTag.name)">
|
||||
Delete Tag
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Image -->
|
||||
<div id="current-image" ng-show="currentImage && !currentTag">
|
||||
<div class="image-comment" ng-if="currentImage.comment">
|
||||
<blockquote style="margin-top: 10px;">
|
||||
<span class="markdown-view" content="currentImage.comment"></span>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<i class="fa fa-code section-icon" bs-tooltip="tooltip.title" data-title="Full Image ID"></i>
|
||||
<span class="section-info">
|
||||
<a class="image-link" href="{{'/repository/' + repo.namespace + '/' + repo.name + '/image/' + currentImage.id}}">{{ currentImage.id }}</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<i class="fa fa-tag section-icon" bs-tooltip="tooltip.title" data-title="Current Tags"></i>
|
||||
<span class="section-info section-info-with-dropdown">
|
||||
<a class="label tag label-default" ng-repeat="tag in currentImage.tags"
|
||||
href="/repository/{{ repo.namespace }}/{{ repo.name }}?tag={{ tag }}">
|
||||
{{ tag }}
|
||||
</a>
|
||||
<span style="color: #ccc;" ng-if="!currentImage.tags.length">(No Tags)</span>
|
||||
|
||||
|
||||
<div class="dropdown" data-placement="top" ng-if="repo.can_write || currentImage.tags">
|
||||
<a href="javascript:void(0)" class="dropdown-button" data-toggle="dropdown" bs-tooltip="tooltip.title" data-title="Manage Tags"
|
||||
data-container="body">
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li ng-repeat="tag in currentImage.tags">
|
||||
<a href="javascript:void(0)" ng-click="setTag(tag, true)">
|
||||
<i class="fa fa-tag"></i>{{tag}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="divider" role="presentation" ng-if="repo.can_write && currentImage.tags"></li>
|
||||
<li>
|
||||
<a href="javascript:void(0)" ng-click="showAddTag(currentImage)" ng-if="repo.can_write">
|
||||
<i class="fa fa-plus"></i>Add New Tag
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="image-section" ng-if="currentImage.command && currentImage.command.length">
|
||||
<i class="fa fa-terminal section-icon" bs-tooltip="tooltip.title" data-title="Image Command"></i>
|
||||
<span class="section-info">
|
||||
<span class="formatted-command trimmed"
|
||||
data-html="true"
|
||||
bs-tooltip="" data-title="{{ getTooltipCommand(currentImage) }}"
|
||||
data-placement="top">{{ getFormattedCommand(currentImage) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<i class="fa fa-calendar section-icon" bs-tooltip="tooltip.title" data-title="Created"></i>
|
||||
<span class="section-info">
|
||||
<dd am-time-ago="parseDate(currentImage.created)"></dd>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<i class="fa fa-cloud-upload section-icon" bs-tooltip="tooltip.title"
|
||||
data-title="The amount of data sent between Docker and the registry when pushing/pulling"></i>
|
||||
<span class="section-info">{{ currentImage.size | bytes }}</span>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<i class="fa fa-map-marker section-icon" bs-tooltip="tooltip.title"
|
||||
data-title="The geographic region(s) in which this image data is located"></i>
|
||||
<span class="section-info">
|
||||
<span class="location-view" location="location" ng-repeat="location in currentImage.locations"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Image changes loading -->
|
||||
<div class="resource-view" resource="currentImageChangeResource">
|
||||
<div class="changes-container small-changes-container section-info"
|
||||
ng-show="currentImageChanges.changed.length || currentImageChanges.added.length || currentImageChanges.removed.length">
|
||||
<div class="changes-count-container image-section">
|
||||
<i class="fa fa-code-fork section-icon" bs-tooltip="tooltip.title" data-title="File Changes"></i>
|
||||
<div style="float: right; display: inline-block">
|
||||
<span class="change-count added" ng-show="currentImageChanges.added.length > 0" data-title="Files Added"
|
||||
bs-tooltip="tooltip.title" data-placement="top" data-container="body">
|
||||
<i class="fa fa-plus-square"></i>
|
||||
<b>{{currentImageChanges.added.length}}</b>
|
||||
</span>
|
||||
<span class="change-count removed" ng-show="currentImageChanges.removed.length > 0" data-title="Files Removed"
|
||||
bs-tooltip="tooltip.title" data-placement="top" data-container="body">
|
||||
<i class="fa fa-minus-square"></i>
|
||||
<b>{{currentImageChanges.removed.length}}</b>
|
||||
</span>
|
||||
<span class="change-count changed" ng-show="currentImageChanges.changed.length > 0" data-title="Files Changed"
|
||||
bs-tooltip="tooltip.title" data-placement="top" data-container="body">
|
||||
<i class="fa fa-pencil-square"></i>
|
||||
<b>{{currentImageChanges.changed.length}}</b>
|
||||
</span>
|
||||
</div>
|
||||
<div id="collapseChanges" style="padding-top: 24px;">
|
||||
<div class="well well-sm">
|
||||
<div class="change added" ng-repeat="file in currentImageChanges.added | limitTo:2">
|
||||
<i class="fa fa-plus-square"></i>
|
||||
<span title="{{file}}">{{file}}</span>
|
||||
</div>
|
||||
<div class="change removed" ng-repeat="file in currentImageChanges.removed | limitTo:2">
|
||||
<i class="fa fa-minus-square"></i>
|
||||
<span title="{{file}}">{{file}}</span>
|
||||
</div>
|
||||
<div class="change changed" ng-repeat="file in currentImageChanges.changed | limitTo:2">
|
||||
<i class="fa fa-pencil-square"></i>
|
||||
<span title="{{file}}">{{file}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="more-changes" ng-show="getMoreCount(currentImageChanges) > 0">
|
||||
<a href="{{'/repository/' + repo.namespace + '/' + repo.name + '/image/' + currentImage.id}}">
|
||||
And {{getMoreCount(currentImageChanges)}} more...
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Usage Logs -->
|
||||
<div id="logs" class="tab-pane" ng-if="viewScope.repository.can_admin">
|
||||
<div class="logs-view" repository="viewScope.repository" makevisible="logsShown"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings -->
|
||||
<div id="settings" class="tab-pane" ng-if="viewScope.repository.can_admin">
|
||||
<div class="repo-panel-settings" repository="viewScope.repository"></div>
|
||||
</div>
|
||||
</div> <!-- /cor-tab-content -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dockerfile-build-dialog" show-now="buildDialogShowCounter" repository="repo"
|
||||
build-started="handleBuildStarted(build)">
|
||||
</div>
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="addTagModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-show="!creatingTag">×</button>
|
||||
<h4 class="modal-title">{{ isAnotherImageTag(toTagImage, tagToCreate) ? 'Move' : 'Add' }} Tag to Image {{ toTagImage.id.substr(0, 12) }}</h4>
|
||||
</div>
|
||||
<form name="addTagForm" ng-submit="createOrMoveTag(toTagImage, tagToCreate, addTagForm.$invalid); addTagForm.$setPristine(); tagToCreate=''">
|
||||
<div class="modal-body">
|
||||
<input type="text" class="form-control" id="tagName" placeholder="Enter tag name"
|
||||
ng-model="tagToCreate" ng-pattern="/^([a-z0-9_\.-]){3,30}$/" required
|
||||
ng-disabled="creatingTag" autofocus>
|
||||
<div style="margin: 10px; margin-top: 20px;" ng-show="isOwnedTag(toTagImage, tagToCreate)">
|
||||
Note: <span class="label tag label-default">{{ tagToCreate }}</span> is already applied to this image.
|
||||
</div>
|
||||
<div style="margin: 10px; margin-top: 20px;" ng-show="isAnotherImageTag(toTagImage, tagToCreate)">
|
||||
Note: <span class="label tag label-default">{{ tagToCreate }}</span> is already applied to another image. This will <b>move</b> the tag.
|
||||
</div>
|
||||
<div class="tag-specific-images-view" tag="tagToCreate" repository="repo" images="images" image-cutoff="toTagImage"
|
||||
style="margin: 10px; margin-top: 20px; margin-bottom: -10px;" ng-show="isAnotherImageTag(toTagImage, tagToCreate)">
|
||||
This will also delete any unattach images and delete the following images:
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary"
|
||||
ng-disabled="!tagToCreate || addTagForm.$invalid || isOwnedTag(toTagImage, tagToCreate)"
|
||||
ng-class="isAnotherImageTag(toTagImage, tagToCreate) ? 'btn-warning' : 'btn-primary'" ng-show="!creatingTag">
|
||||
{{ isAnotherImageTag(toTagImage, tagToCreate) ? 'Move Tag' : 'Create Tag' }}
|
||||
</button>
|
||||
<button class="btn btn-default" data-dismiss="modal" ng-show="!creatingTag">Cancel</button>
|
||||
<div class="quay-spinner" ng-show="creatingTag"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
<!-- Modal message dialog -->
|
||||
<div class="modal fade" id="confirmdeleteTagModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Delete tag
|
||||
<span class="label tag" ng-class="tagToDelete == currentTag.name ? 'label-success' : 'label-default'">
|
||||
{{ tagToDelete }}
|
||||
</span>?
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="deletingTag">
|
||||
<div class="quay-spinner"></div>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="!deletingTag">
|
||||
Are you sure you want to delete tag
|
||||
<span class="label tag" ng-class="tagToDelete == currentTag.name ? 'label-success' : 'label-default'">
|
||||
{{ tagToDelete }}
|
||||
</span>?
|
||||
|
||||
<div class="tag-specific-images-view" tag="tagToDelete" repository="repo" images="images" style="margin-top: 20px">
|
||||
The following images and any other images not referenced by a tag will be deleted:
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="!deletingTag">
|
||||
<button type="button" class="btn btn-primary" ng-click="deleteTag(tagToDelete)">Delete Tag</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
</div>
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"removed": [],
|
||||
"added": [
|
||||
"/elasticsearch-0.90.5.tar.gz"
|
||||
],
|
||||
"changed": []
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"removed": [],
|
||||
"added": [
|
||||
"/opt/elasticsearch-0.90.5/LICENSE.txt",
|
||||
"/opt/elasticsearch-0.90.5/NOTICE.txt",
|
||||
"/opt/elasticsearch-0.90.5/README.textile",
|
||||
"/opt/elasticsearch-0.90.5/bin/elasticsearch",
|
||||
"/opt/elasticsearch-0.90.5/bin/elasticsearch.in.sh",
|
||||
"/opt/elasticsearch-0.90.5/bin/plugin",
|
||||
"/opt/elasticsearch-0.90.5/config/elasticsearch.yml",
|
||||
"/opt/elasticsearch-0.90.5/config/logging.yml",
|
||||
"/opt/elasticsearch-0.90.5/lib/elasticsearch-0.90.5.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/jna-3.3.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/jts-1.12.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/log4j-1.2.17.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-analyzers-common-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-codecs-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-core-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-grouping-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-highlighter-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-join-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-memory-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-misc-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-queries-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-queryparser-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-sandbox-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-spatial-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-suggest-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-amd64-freebsd-6.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-amd64-linux.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-amd64-solaris.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-ia64-linux.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-sparc-solaris.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-sparc64-solaris.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-universal-macosx.dylib",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-universal64-macosx.dylib",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-x86-freebsd-5.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-x86-freebsd-6.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-x86-linux.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-x86-solaris.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/sigar-1.6.4.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/spatial4j-0.3.jar"
|
||||
],
|
||||
"changed": []
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"removed": [],
|
||||
"added": [
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_Release",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_Release.gpg",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_main_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_main_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_main_source_Sources",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_multiverse_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_multiverse_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_multiverse_source_Sources",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_restricted_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_restricted_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_restricted_source_Sources",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_universe_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_universe_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_universe_source_Sources",
|
||||
"/var/lib/apt/lists/lock",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_Release",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_Release.gpg",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_main_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_main_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_main_source_Sources",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_multiverse_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_multiverse_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_multiverse_source_Sources",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_restricted_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_restricted_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_restricted_source_Sources",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_universe_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_universe_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_universe_source_Sources"
|
||||
],
|
||||
"changed": [
|
||||
"/var/cache/apt/pkgcache.bin",
|
||||
"/var/cache/apt/srcpkgcache.bin"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"removed": [],
|
||||
"added": [
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_Release",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_Release.gpg",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_main_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_main_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_main_source_Sources",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_multiverse_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_multiverse_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_multiverse_source_Sources",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_restricted_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_restricted_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_restricted_source_Sources",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_universe_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_universe_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/archive.ubuntu.com_ubuntu_dists_raring_universe_source_Sources",
|
||||
"/var/lib/apt/lists/lock",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_Release",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_Release.gpg",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_main_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_main_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_main_source_Sources",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_multiverse_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_multiverse_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_multiverse_source_Sources",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_restricted_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_restricted_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_restricted_source_Sources",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_universe_binary-amd64_Packages",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_universe_i18n_Translation-en",
|
||||
"/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_raring-security_universe_source_Sources"
|
||||
],
|
||||
"changed": [
|
||||
"/var/cache/apt/pkgcache.bin",
|
||||
"/var/cache/apt/srcpkgcache.bin"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"removed": [],
|
||||
"added": [
|
||||
"/elasticsearch-0.90.5.tar.gz"
|
||||
],
|
||||
"changed": []
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"removed": [],
|
||||
"added": [
|
||||
"/opt/elasticsearch-0.90.5/LICENSE.txt",
|
||||
"/opt/elasticsearch-0.90.5/NOTICE.txt",
|
||||
"/opt/elasticsearch-0.90.5/README.textile",
|
||||
"/opt/elasticsearch-0.90.5/bin/elasticsearch",
|
||||
"/opt/elasticsearch-0.90.5/bin/elasticsearch.in.sh",
|
||||
"/opt/elasticsearch-0.90.5/bin/plugin",
|
||||
"/opt/elasticsearch-0.90.5/config/elasticsearch.yml",
|
||||
"/opt/elasticsearch-0.90.5/config/logging.yml",
|
||||
"/opt/elasticsearch-0.90.5/lib/elasticsearch-0.90.5.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/jna-3.3.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/jts-1.12.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/log4j-1.2.17.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-analyzers-common-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-codecs-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-core-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-grouping-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-highlighter-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-join-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-memory-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-misc-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-queries-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-queryparser-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-sandbox-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-spatial-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/lucene-suggest-4.4.0.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-amd64-freebsd-6.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-amd64-linux.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-amd64-solaris.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-ia64-linux.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-sparc-solaris.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-sparc64-solaris.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-universal-macosx.dylib",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-universal64-macosx.dylib",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-x86-freebsd-5.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-x86-freebsd-6.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-x86-linux.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/libsigar-x86-solaris.so",
|
||||
"/opt/elasticsearch-0.90.5/lib/sigar/sigar-1.6.4.jar",
|
||||
"/opt/elasticsearch-0.90.5/lib/spatial4j-0.3.jar"
|
||||
],
|
||||
"changed": []
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"removed": [],
|
||||
"added": [
|
||||
"/elasticsearch-0.90.5.tar.gz"
|
||||
],
|
||||
"changed": []
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"removed": [],
|
||||
"added": [
|
||||
"/elasticsearch-0.90.5.tar.gz"
|
||||
],
|
||||
"changed": []
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue