Merge branch 'tagview'

This commit is contained in:
Joseph Schorr 2015-03-16 17:14:43 -04:00
commit 3c305beeea
160 changed files with 151133 additions and 497 deletions

View file

@ -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)

View file

@ -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')

View file

@ -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]

View file

@ -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()

View file

@ -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)

View file

@ -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:

View file

@ -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))

View file

@ -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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View file

@ -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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View file

@ -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;

View file

@ -0,0 +1,8 @@
.repo-star .star-icon:hover {
cursor: pointer;
cursor: hand;
}
.repo-star .star-icon.starred {
color: #ffba6d;
}

View file

@ -0,0 +1,3 @@
.repository-events-table-element .notification-row i.fa {
margin-right: 6px;
}

View file

@ -0,0 +1,5 @@
.tag-info-sidebar .control-bar {
padding-top: 10px;
margin-top: 10px;
border-top: 1px solid #eee;
}

View file

@ -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;
}

View 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;
}

View file

@ -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;

View file

@ -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">

View 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>

View 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>

View file

@ -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>

View file

@ -0,0 +1,3 @@
<span class="co-checkable-item" ng-click="toggleItem()"
ng-class="controller.isChecked(item, controller.checked) ? 'checked': 'not-checked'">
</span>

View file

@ -0,0 +1 @@
<li><a href="javascript:void(0)" ng-click="selected()"><span ng-transclude/></a></li>

View 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>

View 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">&times;</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>

View file

@ -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>

View file

@ -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>

View 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>

View 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>

View 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>

View file

@ -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>

View file

@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View file

@ -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>

View 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>

View 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">&times;</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>

View file

@ -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 -->

View file

@ -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;
});

View 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;
});

View 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;
});

View 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;
});

View 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;
});

View 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;
});

View file

@ -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) {
}

View 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;
});

View 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;
});

View file

@ -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 = {

View file

@ -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 () {

View 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;
});

View 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;
});

View 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;
});

View file

@ -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) {

View 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;
});

View 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;
});

View 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;
});

View 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;
});

View file

@ -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';
}
}

View 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;
});

View 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;
});

View file

@ -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];

View file

@ -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);

View file

@ -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.

View file

@ -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;

View file

@ -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) {

View file

@ -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;
}]);

View file

@ -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

View 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">&times;</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">&times;</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 -->

View file

@ -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>

View file

@ -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">&times;</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">&times;</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>

View file

@ -0,0 +1,7 @@
{
"removed": [],
"added": [
"/elasticsearch-0.90.5.tar.gz"
],
"changed": []
}

View file

@ -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": []
}

View file

@ -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"
]
}

View file

@ -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"
]
}

View file

@ -0,0 +1,7 @@
{
"removed": [],
"added": [
"/elasticsearch-0.90.5.tar.gz"
],
"changed": []
}

View file

@ -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

View file

@ -0,0 +1,7 @@
{
"removed": [],
"added": [
"/elasticsearch-0.90.5.tar.gz"
],
"changed": []
}

View file

@ -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