Merge remote-tracking branch 'origin/master' into tagyourit

Conflicts:
	endpoints/api.py
	static/js/app.js
	static/partials/view-repo.html
	test/data/test.db
	test/specs.py
	test/test_api_usage.py
This commit is contained in:
jakedt 2014-03-26 19:42:29 -04:00
commit 302bfb27ae
123 changed files with 16314 additions and 3789 deletions

View file

@ -9,7 +9,74 @@
}
}
.notification-view-element {
cursor: pointer;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
position: relative;
max-width: 320px;
}
.notification-view-element .orginfo {
margin-top: 8px;
float: left;
}
.notification-view-element .orginfo .orgname {
font-size: 12px;
color: #aaa;
}
.notification-view-element .circle {
position: absolute;
top: 14px;
left: 0px;
width: 12px;
height: 12px;
display: inline-block;
border-radius: 50%;
}
.notification-view-element .datetime {
margin-top: 16px;
font-size: 12px;
color: #aaa;
text-align: right;
}
.notification-view-element .message {
margin-bottom: 4px;
}
.notification-view-element .container {
padding: 10px;
border-radius: 6px;
margin-left: 16px;
}
.notification-view-element .container:hover {
background: rgba(66, 139, 202, 0.1);
}
.dockerfile-path {
margin-top: 10px;
padding: 20px;
padding-bottom: 0px;
font-family: Consolas, "Lucida Console", Monaco, monospace;
font-size: 14px;
}
.dockerfile-path:before {
content: "\f15b";
font-family: FontAwesome;
margin-right: 8px;
font-size: 18px;
}
.dockerfile-view {
margin-top: 10px;
margin: 20px;
padding: 20px;
background: #F7F6F6;
@ -282,21 +349,6 @@ i.toggle-icon:hover {
vertical-align: middle;
}
#copyClipboard {
cursor: pointer;
}
#copyClipboard.zeroclipboard-is-hover {
background: #428bca;
color: white;
}
#clipboardCopied.hovering {
position: absolute;
right: 0px;
top: 40px;
}
.content-container {
padding-bottom: 70px;
}
@ -506,7 +558,22 @@ i.toggle-icon:hover {
min-width: 200px;
}
.user-notification {
.notification-primary {
background: #428bca;
color: white;
}
.notification-info {
color: black;
background: #d9edf7;
}
.notification-warning {
color: #8a6d3b;
background: #fcf8e3;
}
.notification-error {
background: red;
}
@ -775,11 +842,20 @@ i.toggle-icon:hover {
margin-bottom: 16px;
}
.new-repo .section-title {
float: right;
color: #aaa;
}
.new-repo .repo-option {
margin: 6px;
margin-top: 16px;
}
.new-repo .repo-option label {
font-weight: normal;
}
.new-repo .repo-option i {
font-size: 18px;
padding-left: 10px;
@ -1596,10 +1672,17 @@ p.editable:hover i {
}
.repo .empty-description {
max-width: 600px;
padding: 6px;
}
.repo .empty-description pre:last-child {
margin-bottom: 0px;
}
.repo .empty-description .panel-default {
margin-top: 20px;
}
.repo dl.dl-horizontal dt {
width: 80px;
padding-right: 10px;
@ -1710,7 +1793,38 @@ p.editable:hover i {
margin-top: 28px;
}
#clipboardCopied {
.copy-box-element {
position: relative;
}
.global-zeroclipboard-container embed {
cursor: pointer;
}
#copyClipboard.zeroclipboard-is-hover, .copy-box-element .zeroclipboard-is-hover {
background: #428bca;
color: white;
cursor: pointer !important;
}
#clipboardCopied.hovering, .copy-box-element .hovering {
position: absolute;
right: 0px;
top: 40px;
pointer-events: none;
z-index: 100;
}
.copy-box-element .id-container {
display: inline-block;
vertical-align: middle;
}
.copy-box-element input {
background-color: white !important;
}
#clipboardCopied, .clipboard-copied-message {
font-size: 0.8em;
display: inline-block;
margin-right: 10px;
@ -1721,7 +1835,7 @@ p.editable:hover i {
border-radius: 4px;
}
#clipboardCopied.animated {
#clipboardCopied.animated, .clipboard-copied-message {
-webkit-animation: fadeOut 4s ease-in-out 0s 1 forwards;
-moz-animation: fadeOut 4s ease-in-out 0s 1 forwards;
-ms-animation: fadeOut 4s ease-in-out 0s 1 forwards;
@ -2037,6 +2151,13 @@ p.editable:hover i {
left: 4px;
}
.repo-admin .right-controls {
text-align: right;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
}
.repo-admin .right-info {
font-size: 11px;
margin-top: 10px;
@ -2205,16 +2326,16 @@ p.editable:hover i {
padding-right: 6px;
}
.delete-ui {
.delete-ui-element {
outline: none;
}
.delete-ui i {
.delete-ui-element i {
cursor: pointer;
vertical-align: middle;
}
.delete-ui .delete-ui-button {
.delete-ui-element .delete-ui-button {
display: inline-block;
vertical-align: middle;
color: white;
@ -2230,15 +2351,15 @@ p.editable:hover i {
transition: width 500ms ease-in-out;
}
.delete-ui .delete-ui-button button {
.delete-ui-element .delete-ui-button button {
padding: 4px;
}
.delete-ui:focus i {
.delete-ui-element:focus i {
visibility: hidden;
}
.delete-ui:focus .delete-ui-button {
.delete-ui-element:focus .delete-ui-button {
width: 60px;
}
@ -2812,7 +2933,7 @@ p.editable:hover i {
margin-bottom: 10px;
}
.create-org .step-container .description {
.form-group .description {
margin-top: 10px;
display: block;
color: #888;
@ -2820,7 +2941,7 @@ p.editable:hover i {
margin-left: 10px;
}
.create-org .form-group input {
.form-group.nested input {
margin-top: 10px;
margin-left: 10px;
}
@ -2927,9 +3048,10 @@ p.editable:hover i {
.tt-suggestion {
display: block;
padding: 3px 20px;
cursor: pointer;
}
.tt-suggestion.tt-is-under-cursor {
.tt-suggestion.tt-cursor {
color: #fff;
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
@ -2941,10 +3063,17 @@ p.editable:hover i {
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)
}
.tt-suggestion.tt-is-under-cursor a {
.tt-suggestion.tt-cursor a {
color: #fff;
}
.tt-empty {
padding: 10px;
font-size: 12px;
color: #aaa;
white-space: nowrap;
}
.tt-suggestion p {
margin: 0;
}
@ -3380,4 +3509,216 @@ pre.command:before {
.label.MAINTAINER {
border-color: #aaa !important;
}
.dropdown-select {
margin: 10px;
position: relative;
}
.dropdown-select .dropdown-select-icon {
position: absolute;
top: 6px;
left: 6px;
z-index: 2;
display: none;
}
.dropdown-select .dropdown-select-icon.fa {
top: 10px;
left: 8px;
font-size: 20px;
}
.dropdown-select .dropdown-select-icon.none-icon {
color: #ccc;
display: inline;
}
.dropdown-select.has-item .dropdown-select-icon {
display: inline;
}
.dropdown-select.has-item .dropdown-select-icon.none-icon {
display: none;
}
.dropdown-select .lookahead-input {
padding-left: 32px;
}
.dropdown-select .twitter-typeahead {
display: block !important;
}
.dropdown-select .twitter-typeahead .tt-hint {
padding-left: 32px;
}
.dropdown-select .dropdown {
position: absolute;
right: 0px;
top: 0px;
}
.dropdown-select .dropdown button.dropdown-toggle {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
.trigger-setup-github-element .github-org-icon {
width: 20px;
margin-right: 8px;
vertical-align: middle;
}
.trigger-setup-github-element li.github-repo-listing i {
margin-right: 10px;
margin-left: 6px;
}
.trigger-setup-github-element li.github-org-header {
padding-left: 6px;
}
.slideinout {
-webkit-transition:0.5s all;
transition:0.5s linear all;
opacity: 1;
position: relative;
height: 100px;
opacity: 1;
}
.slideinout.ng-hide {
opacity: 0;
height: 0px;
}
.slideinout.ng-hide-add, .slideinout.ng-hide-remove {
display: block !important;
}
.auth-header > img {
float: left;
margin-top: 8px;
margin-right: 20px;
}
.auth-header {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
margin-bottom: 10px;
}
.auth-scopes .reason {
margin-top: 20px;
margin-bottom: 20px;
font-size: 18px;
}
.auth-scopes ul {
margin-top: 10px;
list-style: none;
}
.auth-scopes li {
display: block;
}
.auth-scopes .scope {
max-width: 500px;
}
.auth-scopes .scope-container:last-child {
border-bottom: 0px;
}
.auth-scopes .panel-default {
border: 0px;
margin-bottom: 0px;
padding-bottom: 10px;
box-shadow: none;
}
.auth-scopes .panel-default:last-child {
border-bottom: 0px;
}
.auth-scopes .panel-heading {
border: 0px;
background: transparent;
}
.auth-scopes .scope .title {
min-width: 300px;
cursor: pointer;
display: inline-block;
}
.auth-scopes .scope .title a {
color: #444;
}
.auth-scopes .scope .description {
padding: 10px;
}
.auth-scopes .scope i {
margin-right: 10px;
margin-top: 2px;
}
.auth-scopes .scope i.fa-lg {
font-size: 24px;
}
.auth-scopes .title i.arrow:before {
content: "\f0d7";
}
.auth-scopes .title.collapsed i.arrow:before {
content: "\f0da" !important;
}
.auth-container .button-bar form {
display: inline-block;
}
.auth-container .button-bar {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
}
.auth-container .button-bar button {
margin: 6px;
}
.manage-application #oauth td {
padding: 6px;
padding-bottom: 20px;
}
.manage-application .button-bar {
margin-top: 10px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.auth-info .by:before {
content: "by";
margin-right: 4px;
}
.auth-info .by {
color: #aaa;
font-size: 12px;
}
.auth-info .scope {
cursor: pointer;
margin-right: 4px;
}

View file

@ -0,0 +1,12 @@
<div class="application-info-element" style="padding-bottom: 18px">
<div class="auth-header">
<img src="//www.gravatar.com/avatar/{{ application.gravatar }}?s=48&d=identicon">
<h2><a href="{{ application.url }}" target="_blank">{{ application.name }}</a></h2>
<h4>
{{ application.organization.name }}
</h4>
</div>
<div style="padding-top: 10px">
{{ application.description || '(No Description)' }}
</div>
</div>

View file

@ -0,0 +1,24 @@
<div class="application-manager-element">
<div class="quay-spinner" ng-show="loading"></div>
<div class="container" ng-show="!loading">
<div class="side-controls">
<span class="popup-input-button" placeholder="'Application Name'" submitted="createApplication(value)">
<i class="fa fa-plus"></i> Create New Application
</span>
</div>
<table class="table">
<thead>
<th>Application Name</th>
<th>Application URI</th>
</thead>
<tr ng-repeat="app in applications">
<td><a href="/organization/{{ organization.name }}/application/{{ app.client_id }}">{{ app.name }}</a></td>
<td><a href="{{ app.application_uri }}" ng-if="app.application_uri" target="_blank">{{ app.application_uri }}</a></td>
</tr>
</table>
</div>
</div>

View file

@ -0,0 +1,15 @@
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body" style="padding: 4px; padding-left: 20px;">
<button type="button" class="close" ng-click="$hide()" style="padding: 4px;">
&times;
</button>
<div class="application-info" application="applicationInfo"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" ng-click="$hide()">Close</button>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,4 @@
<span class="application-reference-element">
<i class="fa fa-cloud"></i>
<a href="javascript:void(0)" ng-click="showAppDetails()">{{ title }}</a>
</span>

View file

@ -0,0 +1,14 @@
<div class="copy-box-element">
<div class="id-container">
<div class="input-group">
<input type="text" class="form-control" value="{{ value }}" readonly>
<span class="input-group-addon" title="Copy to Clipboard">
<i class="fa fa-copy"></i>
</span>
</div>
</div>
<div class="clipboard-copied-message" ng-class="hoveringMessage ? 'hovering' : ''" style="display: none">
Copied to clipboard
</div>
</div>

View file

@ -0,0 +1,4 @@
<span class="delete-ui-element" ng-click="focus()">
<span class="delete-ui-button" ng-click="performDelete()"><button class="btn btn-danger">{{ buttonTitleInternal }}</button></span>
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="left" title="{{ deleteTitle }}"></i>
</span>

View file

@ -0,0 +1 @@
<ng-transclude>

View file

@ -0,0 +1 @@
<ul class="dropdown-menu" ng-transclude></ul>

View file

@ -0,0 +1,13 @@
<div class="dropdown-select-element" ng-class="selectedItem ? 'has-item' : ''">
<div class="current-item">
<div class="dropdown-select-icon-transclude"></div>
<input type="text" class="lookahead-input form-control" placeholder="{{ placeholder }}"></input>
</div>
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
<span class="caret"></span>
</button>
<div class="dropdown-select-menu-transclude"></div>
</div>
<div class="transcluded" ng-transclude>
</div>

View file

@ -1,10 +1,7 @@
<!-- Quay -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="fa-bar"></span>
<span class="fa-bar"></span>
<span class="fa-bar"></span>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse" style="padding: 0px; padding-left: 4px; padding-right: 4px;">
<span style="font-size: 24px">&equiv;</span>
</button>
<a class="navbar-brand" href="/" target="{{ appLinkTarget() }}">
<img src="/static/img/quay-logo.png">
@ -39,11 +36,14 @@
<a href="javascript:void(0)" class="dropdown-toggle user-dropdown" data-toggle="dropdown">
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=32&d=identicon" />
{{ user.username }}
<span class="badge user-notification notification-animated" ng-show="user.askForPassword || overPlan"
bs-tooltip="(user.askForPassword ? 'A password is needed for this account<br>' : '') + (overPlan ? 'You are using more private repositories than your plan allows' : '')"
<span class="badge user-notification notification-animated"
ng-show="notificationService.notifications.length"
ng-class="notificationService.notificationClasses"
bs-tooltip=""
title="User Notifications"
data-placement="left"
data-container="body">
{{ (user.askForPassword ? 1 : 0) + (overPlan ? 1 : 0) }}
{{ notificationService.notifications.length }}
</span>
<b class="caret"></b>
</a>
@ -51,8 +51,16 @@
<li>
<a href="/user/" target="{{ appLinkTarget() }}">
Account Settings
<span class="badge user-notification" ng-show="user.askForPassword || overPlan">
{{ (user.askForPassword ? 1 : 0) + (overPlan ? 1 : 0) }}
</a>
</li>
<li ng-if="notificationService.notifications.length">
<a href="javascript:void(0)" data-template="/static/directives/notification-bar.html"
data-animation="am-slide-right" bs-aside="aside" data-container="body">
Notifications
<span class="badge user-notification"
ng-class="notificationService.notificationClasses"
ng-show="notificationService.notifications.length">
{{ notificationService.notifications.length }}
</span>
</a>
</li>

View file

@ -6,9 +6,9 @@
<span class="entity-reference" name="performer.username" isrobot="performer.is_robot" ng-show="performer"></span>
<span id="logs-range" class="mini">
From
<input type="text" class="logs-date-picker input-sm" name="start" ng-model="logStartDate" data-date-format="mm/dd/yyyy" bs-datepicker/>
<input type="text" class="logs-date-picker input-sm" name="start" ng-model="logStartDate" data-max-date="{{ logEndDate }}" data-container="body" bs-datepicker/>
<span class="add-on">to</span>
<input type="text" class="logs-date-picker input-sm" name="end" ng-model="logEndDate" data-date-format="mm/dd/yyyy" bs-datepicker/>
<input type="text" class="logs-date-picker input-sm" name="end" ng-model="logEndDate" data-min-date="{{ logStartDate }}" bs-datepicker/>
</span>
</span>
<span class="right">
@ -42,7 +42,7 @@
<thead>
<th>Description</th>
<th style="min-width: 226px">Date/Time</th>
<th>User/Token</th>
<th>User/Token/App</th>
</thead>
<tbody>
@ -53,14 +53,24 @@
</td>
<td>{{ log.datetime }}</td>
<td>
<span class="log-performer" ng-show="log.performer">
<span class="log-performer" ng-if="log.metadata.oauth_token_application">
<div>
<span class="application-reference" title="log.metadata.oauth_token_application"
client-id="log.metadata.oauth_token_application_id"></span>
</div>
<div style="text-align: center; font-size: 12px; color: #aaa; padding: 4px;">on behalf of</div>
<div>
<span class="entity-reference" entity="log.performer" namespace="organization.name"></span>
</div>
</span>
<span class="log-performer" ng-if="!log.metadata.oauth_token_application && log.performer">
<span class="entity-reference" entity="log.performer" namespace="organization.name"></span>
</span>
<span class="log-performer" ng-show="!log.performer && log.metadata.token">
<span class="log-performer" ng-if="!log.performer && log.metadata.token">
<i class="fa fa-key"></i>
<span>{{ log.metadata.token }}</span>
</span>
<span ng-show="!log.performer && !log.metadata.token">
<span ng-if="!log.performer && !log.metadata.token">
(anonymous)
</span>
</td>

View file

@ -0,0 +1,15 @@
<div class="aside" tabindex="-1" role="dialog">
<div class="aside-dialog">
<div class="aside-content">
<div class="aside-header">
<button type="button" class="close" ng-click="$hide()">&times;</button>
<h4 class="aside-title">Notifications</h4>
</div>
<div class="aside-body">
<div ng-repeat="notification in notificationService.notifications">
<div class="notification-view" notification="notification" parent="this"></div>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,11 @@
<div class="notification-view-element">
<div class="container" ng-click="showNotification();">
<div class="circle" ng-class="getClass(notification)"></div>
<div class="message" ng-bind-html="getMessage(notification)"></div>
<div class="orginfo" ng-if="notification.organization">
<img src="//www.gravatar.com/avatar/{{ getGravatar(notification.organization) }}?s=24&d=identicon" />
<span class="orgname">{{ notification.organization }}</span>
</div>
<div class="datetime">{{ parseDate(notification.created) | date:'medium'}}</div>
</div>
</div>

View file

@ -1,5 +1,6 @@
<button class="btn btn-success" data-trigger="click" bs-popover="'static/directives/popup-input-dialog.html'"
data-placement="bottom" ng-click="popupShown()">
<button class="btn btn-success" data-trigger="click"
data-content-template="static/directives/popup-input-dialog.html"
data-placement="bottom" ng-click="popupShown()" bs-popover>
<span ng-transclude></span>
</button>

View file

@ -1,4 +1,4 @@
<form name="popupinput" ng-submit="inputSubmit(); hide()" novalidate>
<input id="input-box" type="text form-control" placeholder="{{ placeholder }}" ng-blur="hide()"
<form name="popupinput" ng-submit="inputSubmit(); $hide()" novalidate>
<input id="input-box" type="text form-control" placeholder="{{ placeholder }}" ng-blur="$hide()"
ng-pattern="getRegexp(pattern)" ng-model="inputValue" ng-trim="false" ng-minlength="2" required>
</form>

View file

@ -48,10 +48,7 @@
<span class="role-group" current-role="prototype.role" role-changed="setRole(role, prototype)" roles="roles"></span>
</td>
<td>
<span class="delete-ui" tabindex="0">
<span class="delete-ui-button" ng-click="deletePrototype(prototype)"><button class="btn btn-danger">Delete</button></span>
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="right" title="Delete Permission"></i>
</span>
<span class="delete-ui" delete-title="'Delete Permission'" perform-delete="deletePrototype(prototype)"></span>
</td>
</tr>
</table>

View file

@ -1,7 +1,5 @@
<div class="resource-view-element">
<div class="resource-spinner" ng-class="resource.loading ? 'visible' : ''">
<div class="small-spinner"></div>
</div>
<div class="quay-spinner" ng-show="resource.loading"></div>
<div class="resource-error" ng-show="!resource.loading && resource.hasError">
{{ errorMessage }}
</div>

View file

@ -24,10 +24,7 @@
</a>
</td>
<td>
<span class="delete-ui" tabindex="0">
<span class="delete-ui-button" ng-click="deleteRobot(robotInfo)"><button class="btn btn-danger">Delete</button></span>
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="right" title="Delete Robot Account"></i>
</span>
<span class="delete-ui" delete-title="'Delete Robot Account'" perform-delete="deleteRobot(robotInfo)"></span>
</td>
</tr>
</table>

View file

@ -5,7 +5,8 @@
ng-class="getImageListingClasses(image)">
<span class="image-listing-circle"></span>
<span class="image-listing-line"></span>
<span class="context-tooltip image-listing-id" bs-tooltip="getFirstTextLine(image.comment)">
<span class="context-tooltip image-listing-id" bs-tooltip="" title="getFirstTextLine(image.comment)"
data-html="true">
{{ image.id.substr(0, 12) }}
</span>
</div>

View file

@ -0,0 +1,23 @@
<span class="trigger-description-element" ng-switch on="trigger.service">
<span ng-switch-when="github">
<i class="fa fa-github fa-lg" style="margin-right: 6px" title="GitHub" bs-tooltip="tooltip.title"></i>
Push to GitHub repository <a href="https://github.com/{{ trigger.config.build_source }}" target="_new">{{ trigger.config.build_source }}</a>
<div style="margin-top: 4px; margin-left: 26px; font-size: 12px; color: gray;" ng-if="trigger.config.subdir">
<span>Dockerfile:
<a href="https://github.com/{{ trigger.config.build_source }}/tree/{{ trigger.config.master_branch || 'master' }}/{{ trigger.config.subdir }}/Dockerfile" target="_blank">
//{{ trigger.config.subdir }}/Dockerfile
</a>
</span>
</div>
<div style="margin-top: 4px; margin-left: 26px; font-size: 12px; color: gray;" ng-if="!trigger.config.subdir && !short">
<span>Dockerfile:
<a href="https://github.com/{{ trigger.config.build_source }}/tree/{{ trigger.config.master_branch || 'master' }}/Dockerfile" target="_blank">
//Dockerfile
</a>
</span>
</div>
</span>
<span ng-switch-default>
Unknown
</span>
</span>

View file

@ -0,0 +1,61 @@
<div class="trigger-setup-github-element">
<div ng-show="loading">
<span class="quay-spinner" style="vertical-align: middle; margin-right: 10px"></span>
Loading Repository List
</div>
<div ng-show="!loading">
<div style="margin-bottom: 18px">Please choose the GitHub repository that will trigger the build:</div>
<!-- Repository select -->
<div class="dropdown-select" placeholder="'Select a repository'" selected-item="currentRepo"
lookahead-items="repoLookahead">
<!-- Icons -->
<i class="dropdown-select-icon none-icon fa fa-github fa-lg"></i>
<img class="dropdown-select-icon github-org-icon" ng-src="{{ currentRepo.avatar_url ? currentRepo.avatar_url : '//www.gravatar.com/avatar/' }}">
<!-- Dropdown menu -->
<ul class="dropdown-select-menu" role="menu">
<li ng-repeat-start="org in orgs" role="presentation" class="dropdown-header github-org-header">
<img ng-src="{{ org.info.avatar_url }}" class="github-org-icon">{{ org.info.name }}
</li>
<li ng-repeat="repo in org.repos" class="github-repo-listing">
<a href="javascript:void(0)" ng-click="selectRepo(repo, org)"><i class="fa fa-github fa-lg"></i> {{ repo }}</a>
</li>
<li role="presentation" class="divider" ng-repeat-end ng-show="$index < orgs.length - 1"></li>
</ul>
</div>
<!-- Dockerfile folder select -->
<div class="slideinout" ng-show="currentRepo">
<div style="margin-top: 10px">Dockerfile Location:</div>
<div class="dropdown-select" placeholder="'(Repository Root)'" selected-item="currentLocation"
lookahead-items="locations" handle-input="handleLocationInput(input)" handle-item-selected="handleLocationSelected(datum)">
<!-- Icons -->
<i class="dropdown-select-icon none-icon fa fa-folder-o fa-lg" ng-show="isInvalidLocation"></i>
<i class="dropdown-select-icon none-icon fa fa-folder fa-lg" style="color: black;" ng-show="!isInvalidLocation"></i>
<i class="dropdown-select-icon fa fa-folder fa-lg"></i>
<!-- Dropdown menu -->
<ul class="dropdown-select-menu" role="menu">
<li ng-repeat="location in locations">
<a href="javascript:void(0)" ng-click="setLocation(location)" ng-if="!location"><i class="fa fa-github fa-lg"></i> Repository Root</a>
<a href="javascript:void(0)" ng-click="setLocation(location)" ng-if="location"><i class="fa fa-folder fa-lg"></i> {{ location }}</a>
</li>
<li class="dropdown-header" role="presentation" ng-show="!locations.length">No Dockerfiles found in repository</li>
</ul>
</div>
<div class="quay-spinner" ng-show="!locations && !locationError"></div>
<div class="alert alert-warning" ng-show="locations && !locations.length">
Warning: No Dockerfiles were found in {{ currentRepo.repo }}
</div>
<div class="alert alert-warning" ng-show="locationError">
{{ locationError }}
</div>
<div class="alert alert-info" ng-show="locations.length && isInvalidLocation">
Note: The folder does not currently exist or contain a Dockerfile
</div>
</div>
</div>
</div>

View file

@ -3,7 +3,7 @@
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title accordion-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" data-target="#collapseSignin">
<a id="signinToggle" class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" data-target="#collapseSignin">
Sign In
</a>
</h4>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="453.54px" height="453.54px" viewBox="0 0 453.54 453.54" enable-background="new 0 0 453.54 453.54" xml:space="preserve">
<rect fill="#00A9D3" width="453.54" height="453.54"/>
<g>
<path fill="#CAD5DA" d="M88.553,86.368L119.628,34l16.113,6.042l21.004,17.264c0,0,14.963,16.688,15.826,16.977
c0.863,0.288,18.415-12.373,18.415-12.373l9.783-1.726c0,0,10.934,46.038,11.221,48.339s32.515,54.958,32.803,56.972
c0.287,2.014-0.288,72.222-2.302,73.085c-2.015,0.863-108.189,9.494-108.189,9.494l-59.561-4.604V120.608L88.553,86.368z"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="111.6489" y1="52.2725" x2="107.9083" y2="201.6065">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon fill="url(#SVGID_1_)" points="120.204,36.59 91.143,86.943 78.194,120.32 83.374,199.735 139.482,208.655 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="155.4292" y1="35.3975" x2="192.2593" y2="175.8124">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon fill="url(#SVGID_2_)" points="122.218,36.59 146.1,190.24 240.478,169.235 151.147,111.113 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="180.3018" y1="100.7939" x2="155.2686" y2="32.0245">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon fill="url(#SVGID_3_)" points="125.383,38.316 154.156,107.085 206.812,107.085 198.899,63.493 178.039,86.368
169.695,80.9 155.883,59.896 136.029,42.632 "/>
</g>
<path fill="#CAD5DA" d="M50.447,81.587L59.739,77l2.667,8.333L69.573,95.5l6.667,41.5c0,0-20,18.333-20.667,18.5s-19.167-5-19.167-5
l5.833-35.5l1.91-18.329L50.447,81.587z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="54.2388" y1="80.3335" x2="54.2388" y2="118.3335">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="1" style="stop-color:#000000"/>
</linearGradient>
<polygon fill="url(#SVGID_4_)" points="51.406,82 45.906,96 44.072,115.585 64.406,118.333 56.322,80.333 "/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

99
static/img/500/ship.svg Normal file
View file

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="90px" height="453.54px" viewBox="0 0 90 453.54" enable-background="new 0 0 453.54 453.54" xml:space="preserve">
<g>
<rect x="79.797" y="74.656" fill="#231F20" width="0.561" height="1.094"/>
<rect x="80.756" y="74.656" fill="#231F20" width="0.561" height="1.094"/>
<g>
<polygon fill="#4B5059" points="79.16,77.125 79.16,76.188 81.723,76.188 81.723,75.25 79.098,75.25 77.848,80.812 81.723,80.812
81.723,78.5 79.16,78.5 79.16,77.562 81.723,77.562 81.723,77.125 "/>
<rect x="79.16" y="76.188" fill="#A73D37" width="2.562" height="0.938"/>
<rect x="79.16" y="77.562" fill="#A73D37" width="2.562" height="0.938"/>
</g>
<rect x="72.41" y="79.625" fill="#9BA9B2" width="10.5" height="7.75"/>
<polygon fill="#E8E5D1" points="80.598,86.062 78.41,81.125 71.098,81.125 71.098,81.844 69.504,81.844 69.504,84.156
71.098,84.156 71.098,93.711 84.598,94.25 84.598,86.062 "/>
<polyline fill="#E8E5D1" points="2.41,90.819 2.41,85.562 2.973,85.562 3.66,90.819 "/>
<g>
<g>
<rect x="31.306" y="91.138" fill="#707E68" width="5.821" height="1.972"/>
<rect x="25.159" y="91.138" fill="#BFA176" width="5.821" height="1.972"/>
<rect x="37.451" y="91.138" fill="#B07959" width="5.821" height="1.972"/>
<rect x="43.597" y="91.138" fill="#9A6B50" width="5.821" height="1.972"/>
<rect x="49.742" y="91.138" fill="#7E3F20" width="5.821" height="1.972"/>
<rect x="55.887" y="91.138" fill="#D6543B" width="5.822" height="1.972"/>
<rect x="62.033" y="91.138" fill="#B07959" width="5.821" height="1.972"/>
</g>
<g>
<g>
<rect x="62.033" y="88.847" fill="#707E68" width="5.821" height="1.972"/>
<rect x="55.887" y="88.847" fill="#B07959" width="5.822" height="1.972"/>
<rect x="49.742" y="88.847" fill="#D2B48C" width="5.821" height="1.972"/>
<rect x="43.597" y="88.847" fill="#7E3F20" width="5.821" height="1.972"/>
<rect x="37.451" y="88.847" fill="#D6543B" width="5.821" height="1.972"/>
<rect x="31.306" y="88.847" fill="#DEA374" width="5.821" height="1.972"/>
</g>
<g>
<rect x="25.159" y="88.847" fill="#707E68" width="5.821" height="1.972"/>
<rect x="19.014" y="88.847" fill="#B07959" width="5.821" height="1.972"/>
<rect x="12.868" y="88.847" fill="#D2B48C" width="5.822" height="1.972"/>
<rect x="6.723" y="88.847" fill="#7E3F20" width="5.821" height="1.972"/>
</g>
</g>
<g>
<rect x="37.45" y="84.266" fill="#707E68" width="5.821" height="1.972"/>
<rect x="43.597" y="84.266" fill="#BFA176" width="5.821" height="1.972"/>
<rect x="31.305" y="84.266" fill="#B07959" width="5.821" height="1.972"/>
<rect x="25.159" y="84.266" fill="#9A6B50" width="5.821" height="1.972"/>
<rect x="19.014" y="84.266" fill="#7E3F20" width="5.821" height="1.972"/>
<rect x="12.868" y="84.266" fill="#D6543B" width="5.822" height="1.972"/>
<rect x="6.723" y="84.266" fill="#B07959" width="5.821" height="1.972"/>
</g>
<g>
<g>
<rect x="6.723" y="86.557" fill="#707E68" width="5.821" height="1.972"/>
<rect x="12.868" y="86.557" fill="#B07959" width="5.822" height="1.972"/>
<rect x="19.014" y="86.557" fill="#D2B48C" width="5.821" height="1.972"/>
<rect x="25.159" y="86.557" fill="#7E3F20" width="5.821" height="1.972"/>
<rect x="31.305" y="86.557" fill="#D6543B" width="5.821" height="1.972"/>
<rect x="37.45" y="86.557" fill="#DEA374" width="5.821" height="1.972"/>
</g>
<g>
<rect x="43.597" y="86.557" fill="#707E68" width="5.821" height="1.972"/>
<rect x="49.742" y="86.557" fill="#B07959" width="5.821" height="1.972"/>
<rect x="55.887" y="86.557" fill="#D2B48C" width="5.822" height="1.972"/>
<rect x="62.033" y="86.557" fill="#7E3F20" width="5.821" height="1.972"/>
</g>
</g>
</g>
<polygon fill="#A82526" points="87.473,92.819 24.348,92.819 24.348,90.069 6.848,90.069 0,90.069 1.721,94.688 87.473,94.688 "/>
<path fill="#59565F" d="M1.721,94.688l0.502,1.347c-5,2.965,0,5.61,0,5.61s83.5,0.799,83.677,0s1.573-3.076,1.573-3.076v-3.881
H1.721z"/>
<g>
<rect x="69.91" y="82.312" fill="#636B62" width="1.156" height="0.844"/>
<rect x="71.551" y="82.312" fill="#636B62" width="1.156" height="0.844"/>
<rect x="73.191" y="82.312" fill="#636B62" width="1.156" height="0.844"/>
<rect x="74.832" y="82.312" fill="#636B62" width="1.156" height="0.844"/>
<rect x="76.473" y="82.312" fill="#636B62" width="1.156" height="0.844"/>
</g>
<g>
<rect x="77.246" y="87.163" fill="#9BA9B2" width="0.828" height="0.844"/>
<rect x="78.422" y="87.163" fill="#9BA9B2" width="0.828" height="0.844"/>
<rect x="79.598" y="87.163" fill="#9BA9B2" width="0.828" height="0.844"/>
<rect x="80.773" y="87.163" fill="#9BA9B2" width="0.828" height="0.844"/>
<rect x="81.949" y="87.163" fill="#9BA9B2" width="0.828" height="0.844"/>
</g>
<g>
<rect x="77.309" y="89.225" fill="#9BA9B2" width="0.828" height="0.844"/>
<rect x="78.484" y="89.225" fill="#9BA9B2" width="0.828" height="0.844"/>
<rect x="79.66" y="89.225" fill="#9BA9B2" width="0.828" height="0.844"/>
<rect x="80.836" y="89.225" fill="#9BA9B2" width="0.828" height="0.844"/>
<rect x="82.012" y="89.225" fill="#9BA9B2" width="0.828" height="0.844"/>
</g>
<circle fill="#5F3F2B" cx="4.191" cy="92.281" r="0.531"/>
<rect x="80.186" y="82.312" fill="#808080" width="0.828" height="0.844"/>
<rect x="81.361" y="82.312" fill="#808080" width="0.828" height="0.844"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

39
static/img/500/water.svg Normal file
View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="453.54px" height="453.54px" viewBox="0 0 453.54 453.54" enable-background="new 0 0 453.54 453.54" xml:space="preserve">
<g>
<path fill="#608DA2" d="M35.218,327.768c0,0,6.968-3.055,15.596-5.071c8.628-2.019,32.723-5.351,38.561-6.217
s9.791-1.617,9.791-1.617s-1.682-3.031,3.623-6.049c0,0-1.65,5.794,6.934,6.829c0,0-1.543,3.026-6.945,1.529
c-5.403-1.498-23.869,18.74-50.771,15.825C52.005,332.997,35.635,333.734,35.218,327.768z"/>
<path fill="#608DA2" d="M52.425,331.592c0,0,1.417-0.072,2.878,2.231c1.462,2.306,2.81,4.634,4.659,3.272
c0,0,0.811-0.655-0.449-3.124s-1.633-2.268-3.502-3.169"/>
<path fill="#CCCCCC" d="M35.778,328.087c0,0,20.057-1.34,23.473-1.82c3.416-0.48,13.04-1.496,15.736-2.264
s23.476-6.962,25.139-7.27c1.664-0.308,7.606-0.167,7.836-0.738c0.229-0.572-6.029,0.688-5.465-6.043c0,0-3.349,0.708-3.042,4.812
l0.079,0.359c0,0-21.463,3.516-25.714,3.978C69.568,319.562,46.746,322.869,35.778,328.087z"/>
</g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="225.002" y1="450.7471" x2="228.5022" y2="101.2466">
<stop offset="0" style="stop-color:#2E3192"/>
<stop offset="1" style="stop-color:#0071BC"/>
</linearGradient>
<path opacity="0.8" fill="url(#SVGID_1_)" d="M450.2,93.711c-3.546,0-3.546,2.115-7.091,2.115c-3.546,0-3.546-2.115-7.092-2.115
s-3.546,2.115-7.09,2.115c-3.545,0-3.545-2.115-7.089-2.115c-3.545,0-3.545,2.115-7.089,2.115c-3.546,0-3.546-2.115-7.092-2.115
s-3.546,2.115-7.092,2.115c-3.545,0-3.545-2.115-7.091-2.115s-3.546,2.115-7.091,2.115c-3.544,0-3.544-2.115-7.09-2.115
c-3.545,0-3.545,2.115-7.089,2.115c-3.547,0-3.547-2.115-7.093-2.115c-3.547,0-3.547,2.115-7.093,2.115s-3.546-2.115-7.09-2.115
c-3.546,0-3.546,2.115-7.091,2.115s-3.545-2.115-7.091-2.115c-3.547,0-3.547,2.115-7.093,2.115s-3.546-2.115-7.093-2.115
c-3.546,0-3.546,2.115-7.09,2.115c-3.546,0-3.546-2.115-7.092-2.115c-3.547,0-3.547,2.115-7.093,2.115
c-3.547,0-3.547-2.115-7.093-2.115s-3.546,2.115-7.092,2.115c-3.545,0-3.545-2.115-7.092-2.115c-3.546,0-3.546,2.115-7.093,2.115
c-3.546,0-3.546-2.115-7.092-2.115c-3.547,0-3.547,2.115-7.092,2.115c-3.547,0-3.547-2.115-7.094-2.115
c-3.546,0-3.546,2.115-7.092,2.115c-3.547,0-3.547-2.115-7.094-2.115s-3.547,2.115-7.093,2.115c-3.545,0-3.545-2.115-7.093-2.115
c-3.547,0-3.547,2.115-7.092,2.115c-3.546,0-3.546-2.115-7.093-2.115s-3.547,2.115-7.095,2.115c-3.544,0-3.544-2.115-7.089-2.115
s-3.544,2.115-7.088,2.115c-3.544,0-3.544-2.115-7.091-2.115c-3.546,0-3.546,2.115-7.093,2.115c-3.545,0-3.545-2.115-7.089-2.115
c-3.546,0-3.546,2.115-7.091,2.115c-3.547,0-3.547-2.115-7.094-2.115c-3.544,0-3.544,2.115-7.091,2.115s-3.546-2.115-7.093-2.115
c-3.546,0-3.546,2.115-7.093,2.115c-3.547,0-3.547-2.115-7.093-2.115c-3.546,0-3.546,2.115-7.093,2.115
c-3.548,0-3.548-2.115-7.095-2.115s-3.547,2.115-7.093,2.115c-3.548,0-3.548-2.115-7.096-2.115s-3.548,2.115-7.096,2.115
c-3.547,0-3.547-2.115-7.096-2.115c-3.548,0-3.548,2.115-7.095,2.115c-3.548,0-3.548-2.115-7.096-2.115
c-3.549,0-3.549,2.115-7.098,2.115c-3.546,0-3.546-2.115-7.093-2.115s-3.547,2.115-7.095,2.115s-3.548-2.115-7.096-2.115
c-3.547,0-3.547,2.115-7.096,2.115c-3.549,0-3.549-2.115-7.098-2.115c-3.546,0-3.546,2.115-7.094,2.115
c-3.55,0-3.55-2.115-7.1-2.115c-3.553,0-3.553,2.115-7.105,2.115c-1.646,0-2.527-0.454-3.354-0.942V453.54h453.54V94.647
C452.718,94.162,451.837,93.711,450.2,93.711z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 183 KiB

Before After
Before After

File diff suppressed because it is too large Load diff

View file

@ -638,10 +638,8 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
$rootScope.description = jQuery(getFirstTextLine(repo.description)).text() ||
'Visualization of images and tags for ' + kind + ' Docker repository: ' + qualifiedRepoName;
// If the repository is marked as building, start monitoring it for changes.
if (repo.is_building) {
startBuildInfoTimer(repo);
}
// Load the builds for this repository. If none are active it will cancel the poll.
startBuildInfoTimer(repo);
$('#copyClipboard').clipboardCopy();
});
@ -672,15 +670,19 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
};
ApiService.getRepoBuilds(null, params, true).then(function(resp) {
// Build a filtered list of the builds that are currently running.
var runningBuilds = [];
for (var i = 0; i < resp.builds.length; ++i) {
var build = resp.builds[i];
if (build.status != 'complete') {
if (build['phase'] != 'complete' && build['phase'] != 'error') {
runningBuilds.push(build);
}
}
$scope.buildsInfo = runningBuilds;
var existingBuilds = $scope.runningBuilds || [];
$scope.runningBuilds = runningBuilds;
$scope.buildHistory = resp.builds;
if (!runningBuilds.length) {
// Cancel the build timer.
cancelBuildInfoTimer();
@ -688,8 +690,10 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
// Mark the repo as no longer building.
$scope.repo.is_building = false;
// Reload the repo information.
loadViewInfo();
// Reload the repo information if all of the builds recently finished.
if (existingBuilds.length > 0) {
loadViewInfo();
}
}
});
};
@ -798,9 +802,23 @@ function BuildPackageCtrl($scope, Restangular, ApiService, $routeParams, $rootSc
// itself (should) be the Dockerfile.
if (zipFiles && Object.keys(zipFiles).length) {
// Load the dockerfile contents.
var dockerfile = zip.file('Dockerfile');
var dockerfilePath = 'Dockerfile';
if ($scope.repobuild['job_config']) {
var dockerfileFolder = ($scope.repobuild['job_config']['build_subdir'] || '');
if (dockerfileFolder[0] == '/') {
dockerfileFolder = dockerfileFolder.substr(1);
}
if (dockerfileFolder && dockerfileFolder[dockerfileFolder.length - 1] != '/') {
dockerfileFolder += '/';
}
dockerfilePath = dockerfileFolder + 'Dockerfile';
}
var dockerfile = zip.file(dockerfilePath);
if (dockerfile) {
$scope.dockerFileContents = dockerfile.asText();
$scope.dockerFilePath = dockerfilePath;
}
// Build the zip file tree.
@ -815,21 +833,17 @@ function BuildPackageCtrl($scope, Restangular, ApiService, $routeParams, $rootSc
});
} else {
$scope.dockerFileContents = response;
$scope.dockerFilePath = 'Dockerfile';
}
$scope.loaded = true;
};
var downloadBuildPack = function() {
var downloadBuildPack = function(url) {
$scope.downloadProgress = 0;
$scope.downloading = true;
ApiService.getRepoBuildArchiveUrl(null, params).then(function(resp) {
startDownload(resp['url']);
}, function() {
$scope.downloading = false;
$scope.downloadError = true;
});
startDownload(url);
};
var startDownload = function(url) {
@ -880,7 +894,7 @@ function BuildPackageCtrl($scope, Restangular, ApiService, $routeParams, $rootSc
'name': name
};
downloadBuildPack();
downloadBuildPack(resp['archive_url']);
return resp;
});
};
@ -888,7 +902,8 @@ function BuildPackageCtrl($scope, Restangular, ApiService, $routeParams, $rootSc
getBuildInfo();
}
function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize, ansi2html) {
function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize,
ansi2html, AngularViewArray) {
var namespace = $routeParams.namespace;
var name = $routeParams.name;
var pollTimerHandle = null;
@ -904,7 +919,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
}
});
$scope.builds = [];
$scope.builds = null;
$scope.polling = false;
$scope.buildDialogShowCounter = 0;
@ -914,12 +929,16 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
};
$scope.handleBuildStarted = function(newBuild) {
$scope.builds.push(newBuild);
$scope.builds.unshift(newBuild);
$scope.setCurrentBuild(newBuild['id'], true);
};
$scope.adjustLogHeight = function() {
$('.build-logs').height($(window).height() - 415);
var triggerOffset = 0;
if ($scope.currentBuild && $scope.currentBuild.trigger) {
triggerOffset = 85;
}
$('.build-logs').height($(window).height() - 415 - triggerOffset);
};
$scope.askRestartBuild = function(build) {
@ -929,8 +948,14 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
$scope.restartBuild = function(build) {
$('#confirmRestartBuildModal').modal('hide');
var subdirectory = '';
if (build['job_config']) {
subdirectory = build['job_config']['build_subdir'] || '';
}
var data = {
'file_id': build['resource_key']
'file_id': build['resource_key'],
'subdirectory': subdirectory
};
var params = {
@ -938,26 +963,18 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
};
ApiService.requestRepoBuild(data, params).then(function(newBuild) {
$scope.builds.push(newBuild);
$scope.builds.unshift(newBuild);
$scope.setCurrentBuild(newBuild['id'], true);
});
};
$scope.hasLogs = function(container) {
return ((container.logs && container.logs.length) || (container._logs && container._logs.length));
return container.logs.hasEntries;
};
$scope.toggleLogs = function(container) {
if (container._logs) {
container.logs = container._logs;
container._logs = null;
} else {
container._logs = container.logs;
container.logs = null;
}
};
$scope.setCurrentBuild = function(buildId, opt_updateURL) {
if (!$scope.builds) { return; }
// Find the build.
for (var i = 0; i < $scope.builds.length; ++i) {
if ($scope.builds[i].id == buildId) {
@ -1042,17 +1059,13 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
var entry = logs[i];
var type = entry['type'] || 'entry';
if (type == 'command' || type == 'phase' || type == 'error') {
entry['_logs'] = [];
entry['logs'] = AngularViewArray.create();
entry['index'] = startIndex + i;
$scope.logEntries.push(entry);
$scope.currentParentEntry = entry;
$scope.currentParentEntry = entry;
} else if ($scope.currentParentEntry) {
if ($scope.currentParentEntry['logs']) {
$scope.currentParentEntry['logs'].push(entry);
} else {
$scope.currentParentEntry['_logs'].push(entry);
}
$scope.currentParentEntry['logs'].push(entry);
}
}
};
@ -1120,7 +1133,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
if ($location.search().current) {
$scope.setCurrentBuild($location.search().current, false);
} else if ($scope.builds.length > 0) {
$scope.setCurrentBuild($scope.builds[$scope.builds.length - 1].id, true);
$scope.setCurrentBuild($scope.builds[0].id, true);
}
});
};
@ -1128,7 +1141,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
fetchRepository();
}
function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope) {
function RepoAdminCtrl($scope, Restangular, ApiService, KeyService, $routeParams, $rootScope, $location) {
var namespace = $routeParams.namespace;
var name = $routeParams.name;
@ -1138,6 +1151,33 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
$scope.permissionCache = {};
$scope.githubRedirectUri = KeyService.githubRedirectUri;
$scope.githubClientId = KeyService.githubClientId;
$scope.getBadgeFormat = function(format, repo) {
if (!repo) { return; }
var imageUrl = 'https://quay.io/repository/' + namespace + '/' + name + '/status';
if (!$scope.repo.is_public) {
imageUrl += '?token=' + $scope.repo.status_token;
}
var linkUrl = 'https://quay.io/repository/' + namespace + '/' + name;
switch (format) {
case 'svg':
return imageUrl;
case 'md':
return '[![Docker Repository on Quay.io](' + imageUrl + ' "Docker Repository on Quay.io")](' + linkUrl + ')';
case 'asciidoc':
return 'image:' + imageUrl + '["Docker Repository on Quay.io", link="' + linkUrl + '"]';
}
return '';
};
$scope.buildEntityForPermission = function(name, permission, kind) {
var key = name + ':' + kind;
if ($scope.permissionCache[key]) {
@ -1196,7 +1236,7 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
};
var permissionPost = Restangular.one(getRestUrl('repository', namespace, name, 'permissions', kind, entityName));
permissionPost.customPOST(permission).then(function(result) {
permissionPost.customPUT(permission).then(function(result) {
$scope.permissions[kind][entityName] = result;
}, function(result) {
$('#cannotchangeModal').modal({});
@ -1358,6 +1398,130 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
});
};
$scope.showBuild = function(buildInfo) {
$location.path('/repository/' + namespace + '/' + name + '/build');
$location.search('current', buildInfo.id);
};
$scope.loadTriggerBuildHistory = function(trigger) {
trigger.$loadingHistory = true;
var params = {
'repository': namespace + '/' + name,
'trigger_uuid': trigger.id,
'limit': 3
};
ApiService.listTriggerRecentBuilds(null, params).then(function(resp) {
trigger.$builds = resp['builds'];
trigger.$loadingHistory = false;
});
};
$scope.loadTriggers = function() {
var params = {
'repository': namespace + '/' + 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.new_trigger;
if (newTriggerId) {
for (var i = 0; i < $scope.triggers.length; ++i) {
var trigger = $scope.triggers[i];
if (trigger['id'] == newTriggerId && !trigger['is_active']) {
$scope.setupTrigger(trigger);
break;
}
}
}
return $scope.triggers;
});
};
$scope.setupTrigger = function(trigger) {
$scope.triggerSetupReady = false;
$scope.currentSetupTrigger = trigger;
$('#setupTriggerModal').modal({});
$('#setupTriggerModal').on('hidden.bs.modal', function () {
$scope.$apply(function() {
$scope.cancelSetupTrigger();
});
});
};
$scope.finishSetupTrigger = function(trigger) {
$('#setupTriggerModal').modal('hide');
$scope.currentSetupTrigger = null;
var params = {
'repository': namespace + '/' + name,
'trigger_uuid': trigger.id
};
ApiService.activateBuildTrigger(trigger['config'], params).then(function(resp) {
trigger['is_active'] = true;
}, function(resp) {
$scope.triggers.splice($scope.triggers.indexOf(trigger), 1);
bootbox.dialog({
"message": resp['data']['message'] || 'The build trigger setup could not be completed',
"title": "Could not activate build trigger",
"buttons": {
"close": {
"label": "Close",
"className": "btn-primary"
}
}
});
});
};
$scope.cancelSetupTrigger = function() {
if (!$scope.currentSetupTrigger) { return; }
$('#setupTriggerModal').modal('hide');
$scope.deleteTrigger($scope.currentSetupTrigger);
$scope.currentSetupTrigger = null;
};
$scope.startTrigger = function(trigger) {
var params = {
'repository': namespace + '/' + name,
'trigger_uuid': trigger.id
};
ApiService.manuallyStartBuildTrigger(null, params).then(function(resp) {
window.console.log(resp);
var url = '/repository/' + namespace + '/' + name + '/build?current=' + resp['id'];
document.location = url;
}, function(resp) {
bootbox.dialog({
"message": resp['message'] || 'The build could not be started',
"title": "Could not start build",
"buttons": {
"close": {
"label": "Close",
"className": "btn-primary"
}
}
});
});
};
$scope.deleteTrigger = function(trigger) {
var params = {
'repository': namespace + '/' + name,
'trigger_uuid': trigger.id
};
ApiService.deleteBuildTrigger(null, params).then(function(resp) {
$scope.triggers.splice($scope.triggers.indexOf(trigger), 1);
});
};
var fetchTokens = function() {
var params = {
'repository': namespace + '/' + name
@ -1421,7 +1585,6 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
}
UserService.updateUserIn($scope, function(user) {
$scope.askForPassword = user.askForPassword;
$scope.cuser = jQuery.extend({}, user);
for (var i = 0; i < $scope.cuser.logins.length; i++) {
@ -1447,12 +1610,42 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
$scope.org = {};
$scope.githubRedirectUri = KeyService.githubRedirectUri;
$scope.githubClientId = KeyService.githubClientId;
$scope.authorizedApps = null;
$('.form-change').popover();
$scope.logsShown = 0;
$scope.invoicesShown = 0;
$scope.loadAuthedApps = function() {
if ($scope.authorizedApps) { return; }
ApiService.listUserAuthorizations().then(function(resp) {
$scope.authorizedApps = resp['authorizations'];
});
};
$scope.deleteAccess = function(accessTokenInfo) {
var params = {
'access_token_uuid': accessTokenInfo['uuid']
};
ApiService.deleteUserAuthorization(null, params).then(function(resp) {
$scope.authorizedApps.splice($scope.authorizedApps.indexOf(accessTokenInfo), 1);
}, function(resp) {
bootbox.dialog({
"message": resp.message || 'Could not revoke authorization',
"title": "Cannot revoke authorization",
"buttons": {
"close": {
"label": "Close",
"className": "btn-primary"
}
}
});
});
};
$scope.loadLogs = function() {
if (!$scope.hasPaidBusinessPlan) { return; }
$scope.logsShown++;
@ -1518,7 +1711,8 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
$scope.sentEmail = $scope.cuser.email;
// Reset the form.
$scope.cuser.repeatEmail = '';
delete $scope.cuser['repeatEmail'];
$scope.changeEmailForm.$setPristine();
}, function(result) {
$scope.updatingUser = false;
@ -1540,8 +1734,9 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
$scope.changePasswordSuccess = true;
// Reset the form
$scope.cuser.password = '';
$scope.cuser.repeatPassword = '';
delete $scope.cuser['password']
delete $scope.cuser['repeatPassword']
$scope.changePasswordForm.$setPristine();
// Reload the user.
@ -1614,6 +1809,16 @@ function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, I
}, 10);
};
var fetchRepository = function() {
var params = {
'repository': namespace + '/' + name
};
ApiService.getRepoAsResource(params).get(function(repo) {
$scope.repo = repo;
});
};
var fetchImage = function() {
var params = {
'repository': namespace + '/' + name,
@ -1621,10 +1826,13 @@ function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, I
};
$scope.image = ApiService.getImageAsResource(params).get(function(image) {
$scope.repo = {
'name': name,
'namespace': namespace
};
if (!$scope.repo) {
$scope.repo = {
'name': name,
'namespace': namespace,
'is_public': true
};
}
$rootScope.title = 'View Image - ' + image.id;
$rootScope.description = 'Viewing docker image ' + image.id + ' under repository ' + namespace + '/' + name +
@ -1665,6 +1873,9 @@ function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, I
});
};
// Fetch the repository.
fetchRepository();
// Fetch the image.
fetchImage();
}
@ -1673,13 +1884,16 @@ function V1Ctrl($scope, $location, UserService) {
UserService.updateUserIn($scope);
}
function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService, PlanService) {
function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService, PlanService, KeyService) {
UserService.updateUserIn($scope);
$scope.githubRedirectUri = KeyService.githubRedirectUri;
$scope.githubClientId = KeyService.githubClientId;
$scope.repo = {
'is_public': 1,
'description': '',
'initialize': false
'initialize': ''
};
// Watch the namespace on the repo. If it changes, we update the plan and the public/private
@ -1691,37 +1905,14 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService
var isUserNamespace = (namespace == $scope.user.username);
$scope.checkingPlan = true;
$scope.planRequired = null;
$scope.isUserNamespace = isUserNamespace;
if (isUserNamespace) {
// Load the user's subscription information in case they want to create a private
// repository.
ApiService.getUserPrivateCount().then(function(resp) {
if (resp.privateCount + 1 > resp.reposAllowed) {
PlanService.getMinimumPlan(resp.privateCount + 1, false, function(minimum) {
$scope.planRequired = minimum;
});
}
// Determine whether private repositories are allowed for the namespace.
checkPrivateAllowed();
$scope.checkingPlan = false;
}, function() {
$scope.planRequired = {};
$scope.checkingPlan = false;
});
} else {
ApiService.getOrganizationPrivateAllowed(null, {'orgname': namespace}).then(function(resp) {
$scope.planRequired = resp.privateAllowed ? null : {};
$scope.checkingPlan = false;
}, function() {
$scope.planRequired = {};
$scope.checkingPlan = false;
});
// Auto-set to private repo.
$scope.repo.is_public = '0';
}
// Default to private repos for organizations.
$scope.repo.is_public = isUserNamespace ? '1' : '0';
});
$scope.changeNamespace = function(namespace) {
@ -1771,12 +1962,20 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService
$scope.creating = false;
$scope.created = created;
// Repository created. Start the upload process if applicable.
if ($scope.repo.initialize) {
// Start the upload process if applicable.
if ($scope.repo.initialize == 'dockerfile' || $scope.repo.initialize == 'zipfile') {
$scope.createdForBuild = created;
return;
}
// Conduct the Github redirect if applicable.
if ($scope.repo.initialize == 'github') {
window.location = 'https://github.com/login/oauth/authorize?client_id=' + $scope.githubClientId +
'&scope=repo,user:email&redirect_uri=' + $scope.githubRedirectUri + '/trigger/' +
repo.namespace + '/' + repo.name;
return;
}
// Otherwise, redirect to the repo page.
$location.path('/repository/' + created.namespace + '/' + created.name);
}, function(result) {
@ -1800,7 +1999,35 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService
}
};
PlanService.changePlan($scope, null, $scope.planRequired.stripeId, callbacks);
var namespace = $scope.isUserNamespace ? null : $scope.repo.namespace;
PlanService.changePlan($scope, namespace, $scope.planRequired.stripeId, callbacks);
};
var checkPrivateAllowed = function() {
if (!$scope.repo || !$scope.repo.namespace) { return; }
$scope.checkingPlan = true;
var isUserNamespace = $scope.isUserNamespace;
ApiService.getPrivateAllowed(isUserNamespace ? null : $scope.repo.namespace).then(function(resp) {
$scope.checkingPlan = false;
if (resp['privateAllowed']) {
$scope.planRequired = null;
return;
}
if (resp['privateCount'] == null) {
// Organization where we are not the admin.
$scope.planRequired = {};
return;
}
// Otherwise, lookup the matching plan.
PlanService.getMinimumPlan(resp['privateCount'] + 1, !isUserNamespace, function(minimum) {
$scope.planRequired = minimum;
});
});
};
var subscribedToPlan = function(sub) {
@ -1810,16 +2037,7 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService
PlanService.getPlan(sub.plan, function(subscribedPlan) {
$scope.subscribedPlan = subscribedPlan;
$scope.planRequired = null;
// Check to see if the current plan allows for an additional private repository to
// be created.
var privateAllowed = $scope.subscription.usedPrivateRepos < $scope.subscribedPlan.privateRepos;
if (!privateAllowed) {
// If not, find the minimum repository that does.
PlanService.getMinimumPlan($scope.subscription.usedPrivateRepos + 1, !$scope.isUserNamespace, function(minimum) {
$scope.planRequired = minimum;
});
}
checkPrivateAllowed();
});
};
}
@ -1933,12 +2151,17 @@ function OrgAdminCtrl($rootScope, $scope, $timeout, Restangular, $routeParams, U
$scope.invoiceLoading = true;
$scope.logsShown = 0;
$scope.invoicesShown = 0;
$scope.applicationsShown = 0;
$scope.changingOrganization = false;
$scope.loadLogs = function() {
$scope.logsShown++;
};
$scope.loadApplications = function() {
$scope.applicationsShown++;
};
$scope.loadInvoices = function() {
$scope.invoicesShown++;
};
@ -2223,4 +2446,132 @@ function OrgMemberLogsCtrl($scope, $routeParams, $rootScope, $timeout, Restangul
// Load the org info and the member info.
loadOrganization();
loadMemberInfo();
}
function ManageApplicationCtrl($scope, $routeParams, $rootScope, $location, $timeout, ApiService) {
var orgname = $routeParams.orgname;
var clientId = $routeParams.clientid;
$scope.updating = false;
$scope.askResetClientSecret = function() {
$('#resetSecretModal').modal({});
};
$scope.askDelete = function() {
$('#deleteAppModal').modal({});
};
$scope.deleteApplication = function() {
var params = {
'orgname': orgname,
'client_id': clientId
};
$('#deleteAppModal').modal('hide');
ApiService.deleteOrganizationApplication(null, params).then(function(resp) {
$timeout(function() {
$location.path('/organization/' + orgname + '/admin');
}, 500);
}, function(resp) {
bootbox.dialog({
"message": resp.message || 'Could not delete application',
"title": "Cannot delete application",
"buttons": {
"close": {
"label": "Close",
"className": "btn-primary"
}
}
});
});
};
$scope.updateApplication = function() {
$scope.updating = true;
var params = {
'orgname': orgname,
'client_id': clientId
};
if (!$scope.application['description']) {
delete $scope.application['description'];
}
if (!$scope.application['gravatar_email']) {
delete $scope.application['gravatar_email'];
}
ApiService.updateOrganizationApplication($scope.application, params).then(function(resp) {
$scope.application = resp;
$scope.updating = false;
}, function(resp) {
$scope.updating = false;
bootbox.dialog({
"message": resp.message || 'Could not update application',
"title": "Cannot update application",
"buttons": {
"close": {
"label": "Close",
"className": "btn-primary"
}
}
});
});
};
$scope.resetClientSecret = function() {
var params = {
'orgname': orgname,
'client_id': clientId
};
$('#resetSecretModal').modal('hide');
ApiService.resetOrganizationApplicationClientSecret(null, params).then(function(resp) {
$scope.application = resp;
}, function(resp) {
bootbox.dialog({
"message": resp.message || 'Could not reset client secret',
"title": "Cannot reset client secret",
"buttons": {
"close": {
"label": "Close",
"className": "btn-primary"
}
}
});
});
};
var loadOrganization = function() {
$scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) {
$scope.organization = org;
return org;
});
};
var loadApplicationInfo = function() {
var params = {
'orgname': orgname,
'client_id': clientId
};
$scope.appResource = ApiService.getOrganizationApplicationAsResource(params).get(function(resp) {
$scope.application = resp;
$rootScope.title = 'Manage Application ' + $scope.application.name + ' (' + $scope.orgname + ')';
$rootScope.description = 'Manage the details of application ' + $scope.application.name +
' under organization ' + $scope.orgname;
return resp;
});
};
// Load the organization and application info.
loadOrganization();
loadApplicationInfo();
}

View file

@ -872,7 +872,8 @@ function FileTreeBase() {
* Calculates the dimensions of the tree.
*/
FileTreeBase.prototype.calculateDimensions_ = function(container) {
var cw = document.getElementById(container).clientWidth;
var containerElm = document.getElementById(container);
var cw = containerElm ? containerElm.clientWidth : 1200;
var barHeight = 20;
var ch = (this.getNodesHeight() * barHeight) + 40;
@ -1470,7 +1471,7 @@ function LogUsageChart(titleMap) {
* Builds the D3-representation of the data.
*/
LogUsageChart.prototype.buildData_ = function(logs) {
var parseDate = d3.time.format("%a, %d %b %Y %H:%M:%S GMT").parse
var parseDate = d3.time.format("%a, %d %b %Y %H:%M:%S %Z").parse
// Build entries for each kind of event that occurred, on each day. We have one
// entry per {kind, day} pair.

8
static/lib/angular-motion.min.css vendored Normal file

File diff suppressed because one or more lines are too long

3543
static/lib/angular-strap.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

9
static/lib/angular-strap.tpl.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

7
static/lib/typeahead.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -37,8 +37,9 @@
<div class="tab-content">
<!-- Dockerfile view -->
<div class="tab-pane active" id="dockerfile">
<div class="dockerfile-view" contents="dockerFileContents"></div>
<span ng-show="!dockerFileContents">No Dockerfile found in the build pack</span>
<div class="dockerfile-path" ng-if="dockerFileContents">{{ dockerFilePath }}</div>
<div class="dockerfile-view" contents="dockerFileContents" ng-if="dockerFileContents"></div>
<span ng-if="!dockerFileContents">No Dockerfile found in the build pack</span>
</div>
<!-- File tree -->

View file

@ -17,20 +17,7 @@
<dl class="dl-normal">
<dt>Full Image ID</dt>
<dd>
<div>
<div class="id-container">
<div class="input-group">
<input id="full-id" type="text" class="form-control" value="{{ image.value.id }}" readonly>
<span id="copyClipboard" class="input-group-addon" title="Copy to Clipboard" data-clipboard-target="full-id">
<i class="fa fa-copy"></i>
</span>
</div>
</div>
<div id="clipboardCopied" style="display: none">
Copied to clipboard
</div>
</div>
<div class="copy-box" value="image.value.id"></div>
</dd>
<dt>Created</dt>
<dd am-time-ago="parseDate(image.value.created)"></dd>

View file

@ -106,15 +106,20 @@
</div>
<div class="tour-section row">
<div class="col-md-7"><img src="/static/img/repo-changes.png" title="View Image - Quay.io" data-screenshot-url="https://quay.io/repository/devtable/image/..." class="img-responsive"></div>
<div class="col-md-7"><img src="/static/img/build-history.png" title="View Image - Quay.io"
data-screenshot-url="https://quay.io/repository/devtable/building/build"
class="img-responsive"></div>
<div class="col-md-5">
<div class="tour-section-title">Docker diff in the cloud</div>
<div class="tour-section-title">Dockerfile Build in the cloud</div>
<div class="tour-section-description">
We wanted to know what was changing in each image of our repositories just as much as you do. So we added diffs. Now you can see exactly which files were <b>added</b>, <b>changed</b>, or <b>removed</b> for each image. We've also provided two awesome ways to view your changes, either in a filterable list, or in a drill down tree view.
Like to use <b>Dockerfiles</b> to build your images? Simply upload your Dockerfile (and any additional files it needs) and we'll build your Dockerfile into an image and push it to your repository.
</div>
<div class="tour-section-description">
If you store your Dockerfile in <i class="fa fa-github fa-lg" style="margin: 6px;"></i><b>GitHub</b>, add a <b>Build Trigger</b> to your repository and we'll start a Dockerfile build for every change you make.
</div>
</div>
</div>
<div class="tour-section row">
<div class="col-md-7 col-md-push-5"><img src="/static/img/repo-admin.png" title="Repository Admin - Quay.io" data-screenshot-url="https://quay.io/repository/devtable/complex/admin" class="img-responsive"></div>
<div class="col-md-5 col-md-pull-7">
@ -128,4 +133,18 @@
<div class="tour-section-description">Want to share with the world? Make your repository <b>fully public</b>.</div>
</div>
</div>
<div class="tour-section row">
<div class="col-md-7"><img src="/static/img/repo-changes.png" title="View Image - Quay.io" data-screenshot-url="https://quay.io/repository/devtable/image/..." class="img-responsive"></div>
<div class="col-md-5">
<div class="tour-section-title">Docker diff whenever you need it</div>
<div class="tour-section-description">
We wanted to know what was changing in each image of our repositories just as much as you do. So we added diffs. Now you can see exactly which files were <b>added</b>, <b>changed</b>, or <b>removed</b> for each image. We've also provided two awesome ways to view your changes, either in a filterable list, or in a drill down tree view.
</div>
</div>
</div>
<div style="border-top: 1px solid #eee; padding-top: 20px;">
<a href="https://mixpanel.com/f/partner"><img src="//cdn.mxpnl.com/site_media/images/partner/badge_light.png" alt="Mobile Analytics" /></a>
</div>
</div>

View file

@ -0,0 +1,165 @@
<div class="resource-view" resource="appResource" error-message="'Application not found'">
</div>
<div ng-show="application">
<div class="container manage-application">
<!-- Header -->
<div class="row">
<div class="col-md-12">
<div class="auth-header">
<img src="//www.gravatar.com/avatar/{{ application.gravatar_email | gravatar }}?s=48&d=identicon">
<h2>{{ application.name || '(Untitled)' }}</h2>
<h4>
<img src="//www.gravatar.com/avatar/{{ organization.gravatar }}?s=24&d=identicon" style="vertical-align: middle; margin-right: 4px;">
<span style="vertical-align: middle"><a href="/organization/{{ organization.name }}/admin">{{ organization.name }}</a></span>
</h4>
</div>
</div>
</div>
<div class="row" style="margin-top: 10px" ng-if="!application.redirect_uri">
<div class="alert alert-warning">
Warning: There is no OAuth Redirect setup for this application. Please enter it in the <strong>Settings</strong> tab.
</div>
</div>
<!-- Content -->
<div class="row" style="margin-top: 10px">
<!-- Side tabs -->
<div class="col-md-2">
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#settings">Settings</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#oauth">OAuth Information</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#delete">Delete Application</a></li>
</ul>
</div>
<!-- Content -->
<div class="col-md-10">
<div class="tab-content">
<!-- Settings tab -->
<div id="settings" class="tab-pane active">
<form method="put" name="applicationForm" id="applicationForm" ng-submit="updateApplication()">
<div class="form-group nested">
<label for="fieldAppName">Application Name</label>
<input type="text" class="form-control" id="fieldAppName" placeholder="Application Name" required ng-model="application.name">
<div class="description">The name of the application that is displayed to users</div>
</div>
<div class="form-group nested">
<label for="fieldAppURI">Homepage URL</label>
<input type="url" class="form-control" id="fieldAppURI" placeholder="Homepage URL" required ng-model="application.application_uri">
<div class="description">The URL to which the application will link in the authorization view</div>
</div>
<div class="form-group nested">
<label for="fieldAppDescription">Description (optional)</label>
<input type="text" class="form-control" id="fieldAppURI" placeholder="Description" ng-model="application.description">
<div class="description">The user friendly description of the application</div>
</div>
<div class="form-group nested">
<label for="fieldAppGravatar">Gravatar E-mail (optional)</label>
<input type="email" class="form-control" id="fieldAppGravatar" placeholder="Gravatar E-mail" ng-model="application.gravatar_email">
<div class="description">An e-mail address representing the <a href="http://en.gravatar.com/" target="_blank">Gravatar</a> for the application. See above for the icon.</div>
</div>
<div class="form-group nested" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
<label for="fieldAppRedirect">Redirect/Callback URL Prefix</label>
<input type="url" class="form-control" id="fieldAppRedirect" placeholder="OAuth Redirect URL" ng-model="application.redirect_uri" required>
<div class="description">Allowed prefix for the application's OAuth redirection/callback URLs</div>
</div>
<div class="button-bar">
<button class="btn btn-success btn-large" type="submit" ng-disabled="applicationForm.$invalid || updating">
Update Application
</button>
<span class="quay-spinner" ng-show="updating"></span>
</div>
</form>
</div>
<!-- Delete tab -->
<div id="delete" class="tab-pane">
<div class="panel panel-default">
<div class="panel-body">
<div style="text-align: center">
<div class="alert alert-danger">Deleting an application <b>cannot be undone</b>. Any existing users of your application will <strong>break!</strong>. Here be dragons!</div>
<button class="btn btn-danger" ng-click="askDelete()">Delete Application</button>
</div>
</div>
</div>
</div>
<!-- OAuth tab -->
<div id="oauth" class="tab-pane">
<table style="margin-top: 20px;">
<thead>
<th style="width: 150px"></th>
<th style="width: 250px"></th>
<th></th>
</thead>
<tr>
<td>Client ID:</td>
<td style="width: 250px">
<div class="copy-box" hovering-message="true" value="application.client_id"></div>
</td>
</tr>
<tr>
<td>Client Secret: <i class="fa fa-lock fa-lg" title="Keep this secret!" bs-tooltip style="margin-left: 10px"></i></td>
<td>
{{ application.client_secret }}
</td>
<td style="padding-left: 10px">
<button class="btn btn-primary" ng-click="askResetClientSecret()">Reset Client Secret</button>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal message dialog -->
<div class="modal fade" id="resetSecretModal">
<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">Reset Client Secret?</h4>
</div>
<div class="modal-body">
<div class="alert alert-info">
Note that resetting the Client Secret for this application will <strong>not</strong> invalidate any user tokens.
</div>
<div>Are you sure you want to reset your Client Secret? Any existing users of this Secret <strong>will break!</strong></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-click="resetClientSecret()">Yes, I'm sure</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- Modal message dialog -->
<div class="modal fade" id="deleteAppModal">
<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 Application?</h4>
</div>
<div class="modal-body">
Are you <b>absolutely, positively</b> sure you would like to delete this application? This <b>cannot be undone</b>.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" ng-click="deleteApplication()">Delete Application</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

View file

@ -50,7 +50,7 @@
<h3>Setup the new organization</h3>
<form method="post" name="newOrgForm" id="newOrgForm" ng-submit="createNewOrg()">
<div class="form-group">
<div class="form-group nested">
<label for="orgName">Organization Name</label>
<input id="orgName" name="orgName" type="text" class="form-control" placeholder="Organization Name"
ng-model="org.name" required autofocus data-trigger="manual" data-content="{{ createError }}"
@ -58,7 +58,7 @@
<span class="description">This will also be the namespace for your repositories</span>
</div>
<div class="form-group">
<div class="form-group nested">
<label for="orgName">Organization Email</label>
<input id="orgEmail" name="orgEmail" type="email" class="form-control" placeholder="Organization Email"
ng-model="org.email" required>
@ -66,7 +66,7 @@
</div>
<!-- Plans Table -->
<div class="form-group plan-group">
<div class="form-group nested plan-group">
<strong>Choose your organization's plan</strong>
<div class="plans-table" plans="plans" current-plan="currentPlan"></div>
</div>

View file

@ -25,13 +25,18 @@
<span class="namespace-selector" user="user" namespace="repo.namespace" require-create="true"></span>
<span style="color: #ccc">/</span>
<span class="name-container">
<input id="repoName" name="repoName" type="text" class="form-control" placeholder="Repository Name" ng-model="repo.name" required autofocus data-trigger="manual" data-content="{{ createError }}" data-placement="right">
<input id="repoName" name="repoName" type="text" class="form-control" placeholder="Repository Name" ng-model="repo.name"
required autofocus data-trigger="manual" data-content="{{ createError }}" data-placement="right" ng-pattern="/^[.a-z0-9_-]+$/">
</span>
<span class="alert alert-warning" ng-show="!newRepoForm.repoName.$error.required && !newRepoForm.repoName.$valid" style="margin-left: 10px;">
Repository names must match [a-z0-9_-]+
</span>
</div>
</div>
<div class="section">
<strong>Description:</strong><br>
<div class="section-title">Repository Description</div>
<br>
<div class="description markdown-input" content="repo.description" can-write="true"
field-title="'repository description'"></div>
</div>
@ -42,13 +47,14 @@
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-8">
<div class="section-title">Repository Visibility</div>
<div class="section">
<div class="repo-option">
<input type="radio" id="publicrepo" name="publicorprivate" ng-model="repo.is_public" value="1">
<i class="fa fa-unlock fa-large" title="Public Repository"></i>
<div class="option-description">
<label for="publicrepo">Public</label>
<label for="publicrepo"><strong>Public</strong></label>
<span class="description-text">Anyone can see and pull from this repository. You choose who can push.</span>
</div>
</div>
@ -57,62 +63,109 @@
<i class="fa fa-lock fa-large" title="Private Repository"></i>
<div class="option-description">
<label for="privaterepo">Private</label>
<label for="privaterepo"><strong>Private</strong></label>
<span class="description-text">You choose who can see, pull and push from/to this repository.</span>
</div>
</div>
<!-- Payment -->
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && isUserNamespace">
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && planRequired.title">
<div class="alert alert-warning">
In order to make this repository private, youll need to upgrade your plan to
<b style="border-bottom: 1px dotted black;" bs-tooltip="'<b>' + planRequired.title + '</b><br>' + planRequired.privateRepos + ' private repositories'">
In order to make this repository private
<span ng-if="isUserNamespace">under your personal namespace</span>
<span ng-if="!isUserNamespace">under the organization <b>{{ repo.namespace }}</b></span>, you will need to upgrade your plan to
<b style="border-bottom: 1px dotted black;" data-html="true"
title="{{ '<b>' + planRequired.title + '</b><br>' + planRequired.privateRepos + ' private repositories' }}" bs-tooltip>
{{ planRequired.title }}
</b>.
This will cost $<span>{{ planRequired.price / 100 }}</span>/month.
</div>
<a class="btn btn-primary" ng-click="upgradePlan()" ng-show="!planChanging">Upgrade now</a>
<span ng-show="user.organizations.length == 1">or did you mean to create this repository
<span ng-if="isUserNamespace && user.organizations.length == 1">or did you mean to create this repository
under <a href="javascript:void(0)" ng-click="changeNamespace(user.organizations[0].name)"><b>{{ user.organizations[0].name }}</b></a>?</span>
<div class="quay-spinner" ng-show="planChanging"></div>
</div>
<div class="quay-spinner" ng-show="repo.is_public == '0' && checkingPlan"></div>
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && !isUserNamespace">
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && !isUserNamespace && !planRequired.title">
<div class="alert alert-warning">
This organization has reached its private repository limit. Please contact your administrator.
This organization has reached its private repository limit. Please contact your administrator.
</div>
</div>
</div>
</div>
</div>
<!-- Initialize repository -->
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-8">
<div class="section">
<input type="checkbox" class="cbox" id="initialize" name="initialize" ng-model="repo.initialize">
<div class="option-description">
<label for="initialize">Initialize Repository from <a href="http://www.docker.io/learn/dockerfile/" target="_new">Dockerfile</a></label>
<span class="description-text">Automatically populate your repository with a new image constructed from a Dockerfile</span>
</div>
<div class="section-title">Initialize repository</div>
<div class="initialize-repo" ng-show="repo.initialize">
<div class="dockerfile-build-form" repository="createdForBuild" upload-failed="handleBuildFailed(message)"
build-started="handleBuildStarted()" build-failed="handleBuildFailed(message)" start-now="createdForBuild"
has-dockerfile="hasDockerfile" uploading="uploading" building="building"></div>
<div style="padding-top: 10px;">
<!-- Empty -->
<div class="repo-option">
<input type="radio" id="initEmpty" name="initialize" ng-model="repo.initialize" value="">
<i class="fa fa-hdd-o fa-lg" style="padding: 6px; padding-left: 8px; padding-right: 6px;"></i>
<label for="initEmpty" style="color: #aaa;">(Empty repository)</label>
</div>
<!-- Dockerfile -->
<div class="repo-option">
<input type="radio" id="initDockerfile" name="initialize" ng-model="repo.initialize" value="dockerfile">
<i class="fa fa-file fa-lg" style="padding: 6px; padding-left: 10px; padding-right: 8px;"></i>
<label for="initDockerfile">Initialize from a <b>Dockerfile</b></label>
</div>
<!-- Zip file -->
<div class="repo-option">
<input type="radio" id="initZipfile" name="initialize" ng-model="repo.initialize" value="zipfile">
<i class="fa fa-archive fa-lg" style="padding: 6px; padding-left: 10px; padding-right: 8px;"></i>
<label for="initZipfile">Initialize from a ZIP file (containing a Dockerfile and other supporting files)</label>
</div>
<!-- Github -->
<div class="repo-option">
<input type="radio" id="initGithub" name="initialize" ng-model="repo.initialize" value="github">
<i class="fa fa-github fa-lg" style="padding: 6px; padding-left: 10px; padding-right: 12px;"></i>
<label for="initGithub">Link to a GitHub Repository</label>
</div>
</div>
</div>
</div>
</div>
<div class="row" ng-show="repo.initialize == 'dockerfile' || repo.initialize == 'zipfile'">
<div class="col-md-1"></div>
<div class="col-md-8">
<div class="section">
<div class="section-title">Upload <span ng-if="repo.initialize == 'dockerfile'">Dockerfile</span><span ng-if="repo.initialize == 'zipfile'">ZIP file</span></div>
<div style="padding-top: 20px;">
<div class="initialize-repo">
<div class="dockerfile-build-form" repository="createdForBuild" upload-failed="handleBuildFailed(message)"
build-started="handleBuildStarted()" build-failed="handleBuildFailed(message)" start-now="createdForBuild"
has-dockerfile="hasDockerfile" uploading="uploading" building="building"></div>
</div>
</div>
</div>
</div>
</div>
<div class="row" ng-show="repo.initialize == 'github'">
<div class="col-md-1"></div>
<div class="col-md-8">
<div class="alert alert-info">
You will be redirected to authorize via GitHub once the repository has been created
</div>
</div>
</div>
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-8">
<button class="btn btn-large btn-success" type="submit"
ng-disabled="uploading || building || newRepoForm.$invalid || (repo.is_public == '0' && (planRequired || checkingPlan)) || (repo.initialize && !hasDockerfile)">
ng-disabled="uploading || building || newRepoForm.$invalid || (repo.is_public == '0' && (planRequired || checkingPlan)) || ((repo.initialize == 'dockerfile' || repo.initialize == 'zipfile') && !hasDockerfile)">
Create Repository
</button>
</div>

View file

@ -12,6 +12,7 @@
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#members" ng-click="loadMembers()">Members</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#prototypes">Default Permissions</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#applications" ng-click="loadApplications()">Applications</a></li>
<li ng-show="hasPaidPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billingoptions">Billing</a></li>
<li ng-show="hasPaidPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#billing" ng-click="loadInvoices()">Billing History</a></li>
</ul>
@ -60,6 +61,11 @@
<div class="logs-view" organization="organization" visible="logsShown"></div>
</div>
<!-- Applications tab -->
<div id="applications" class="tab-pane">
<div class="application-manager" organization="organization" visible="applicationsShown"></div>
</div>
<!-- Billing Options tab -->
<div id="billingoptions" class="tab-pane">
<div class="billing-options" organization="organization"></div>

View file

@ -18,6 +18,8 @@
<div class="col-md-2">
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#permissions">Permissions</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#trigger" ng-click="loadTriggers()">Build Triggers</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#badge">Status Badge</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#webhook" ng-click="loadWebhooks()">Webhooks</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#publicprivate">Public/Private</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#delete">Delete</a></li>
@ -33,6 +35,52 @@
<div id="logs" class="tab-pane">
<div class="logs-view" repository="repo" visible="logsShown"></div>
</div>
<!-- Badge tab -->
<div id="badge" class="tab-pane">
<div class="panel panel-default">
<div class="panel-heading">Status Badge
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="Embeddable widget for displaying the status of the repository"></i>
</div>
<div class="panel-body">
<div class="alert alert-warning" ng-if="!repo.is_public">
Note: This repository is currently <b>private</b>. Publishing this badge will reveal the status information of your repository (and links may
not work for unregistered users).
</div>
<!-- Status Image -->
<a ng-href="/repository/{{ repo.namespace }}/{{ repo.name }}" ng-if="repo && repo.name">
<img ng-src="/repository/{{ repo.namespace }}/{{ repo.name }}/status?token={{ repo.status_token }}" 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', repo)"></div>
</td>
</tr>
<tr>
<td>Markdown:</td>
<td>
<div class="copy-box" hovering-message="true" value="getBadgeFormat('md', repo)"></div>
</td>
</tr>
<tr>
<td>AsciiDoc:</td>
<td>
<div class="copy-box" hovering-message="true" value="getBadgeFormat('asciidoc', repo)"></div>
</td>
</tr>
</table>
</div>
</div>
</div>
<!-- Permissions tab -->
<div id="permissions" class="tab-pane active">
@ -64,10 +112,7 @@
<span class="role-group" current-role="permission.role" role-changed="setRole(role, name, 'team')" roles="roles"></span>
</td>
<td>
<span class="delete-ui" tabindex="0">
<span class="delete-ui-button" ng-click="deleteRole(name, 'team')"><button class="btn btn-danger">Delete</button></span>
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="right" title="Delete Permission"></i>
</span>
<span class="delete-ui" delete-title="'Delete Permission'" perform-delete="deleteRole(name, 'team')"></span>
</td>
</tr>
@ -84,10 +129,7 @@
</div>
</td>
<td>
<span class="delete-ui" tabindex="0" title="Delete Permission">
<span class="delete-ui-button" ng-click="deleteRole(name, 'user')"><button class="btn btn-danger">Delete</button></span>
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="right" title="Delete Permission"></i>
</span>
<span class="delete-ui" delete-title="'Delete Permission'" perform-delete="deleteRole(name, 'user')"></span>
</td>
</tr>
@ -132,10 +174,7 @@
</div>
</td>
<td>
<span class="delete-ui" tabindex="0">
<span class="delete-ui-button" ng-click="deleteToken(token.code)"><button class="btn btn-danger" type="button">Delete</button></span>
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="right" title="Delete Token"></i>
</span>
<span class="delete-ui" delete-title="'Delete Token'" perform-delete="deleteToken(token.code)"></span>
</td>
</tr>
@ -174,10 +213,7 @@
<tr ng-repeat="webhook in webhooks">
<td>{{ webhook.parameters.url }}</td>
<td>
<span class="delete-ui" tabindex="0">
<span class="delete-ui-button" ng-click="deleteWebhook(webhook)"><button class="btn btn-danger">Delete</button></span>
<i class="fa fa-times" bs-tooltip="tooltip.title" data-placement="right" title="Delete Webhook"></i>
</span>
<span class="delete-ui" delete-title="'Delete Webhook'" perform-delete="deleteWebhook(webhook)"></span>
</td>
</tr>
</tbody>
@ -206,6 +242,89 @@
</div>
</div>
<!-- Triggers tab -->
<div id="trigger" class="tab-pane">
<div class="panel panel-default">
<div class="panel-heading">Build Triggers
<i class="info-icon fa fa-info-circle" data-placement="left" data-content="Triggers from various services (such as GitHub) which tell the repository to be built and updated."></i>
</div>
<div class="panel-body">
<!-- Resource view -->
<div class="resource-view" resource="triggersResource" error-message="'Could not load build triggers'">
<div ng-show="!triggers.length">No build triggers defined for this repository</div>
<table class="permissions" ng-show="triggers.length">
<thead>
<tr>
<td style="width: 562px;">Trigger</td>
<td style="width: 104px;"></td>
</tr>
</thead>
<tbody>
<tr ng-repeat="trigger in triggers">
<td>
<div ng-show="!trigger.is_active" style="color: #444;">
<span class="quay-spinner" style="vertical-align: middle; margin-right: 6px;"></span>
Setting up trigger
</div>
<div ng-show="trigger.is_active" class="trigger-description" trigger="trigger"></div>
</td>
<td style="white-space: nowrap;">
<div class="dropdown" style="display: inline-block" ng-visible="trigger.is_active">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="Build History" bs-tooltip="tooltip.title" data-container="body"
ng-click="loadTriggerBuildHistory(trigger)">
<i class="fa fa-tasks"></i>
<b class="caret"></b>
</button>
<ul class="dropdown-menu dropdown-menu-right pull-right">
<li ng-show="trigger.$loadingHistory" style="text-align: center"><span class="quay-spinner" style="padding: 4px;"></span></li>
<li role="presentation" class="dropdown-header" ng-show="!trigger.$loadingHistory && !trigger.$builds.length">No builds have been triggered</li>
<li role="presentation" class="dropdown-header" ng-show="!trigger.$loadingHistory && trigger.$builds.length">Build History</li>
<li ng-repeat="buildInfo in trigger.$builds">
<div class="build-info clickable" ng-click="showBuild(buildInfo)">
<span class="build-status" build="buildInfo"></span>
</div>
</li>
</ul>
</div>
<div class="dropdown" style="display: inline-block">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" title="Trigger Settings" bs-tooltip="tooltip.title" data-container="body">
<i class="fa fa-cog"></i>
<b class="caret"></b>
</button>
<ul class="dropdown-menu dropdown-menu-right pull-right">
<li><a href="javascript:void(0)" ng-click="setupTrigger(trigger)" ng-show="!trigger.is_active"><i class="fa fa-wrench"></i>Resume Setup</a></li>
<li><a href="javascript:void(0)" ng-click="startTrigger(trigger)" ng-show="trigger.is_active"><i class="fa fa-tasks"></i>Build Now</a></li>
<li><a href="javascript:void(0)" ng-click="deleteTrigger(trigger)"><i class="fa fa-times"></i>Delete Trigger</a></li>
</ul>
</div>
</td>
</tr>
</tbody>
</table>
<!-- Right controls -->
<div class="right-controls">
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
New Trigger
<b class="caret"></b>
</button>
<ul class="dropdown-menu dropdown-menu-right pull-right">
<li><a href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=repo,user:email&redirect_uri={{ githubRedirectUri }}/trigger/{{ repo.namespace }}/{{ repo.name }}"><i class="fa fa-github fa-lg"></i>GitHub - Repository Push</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Public/private tab -->
<div id="publicprivate" class="tab-pane">
<!-- Public/Private -->
@ -259,6 +378,30 @@
<i class="fa fa-key"></i> {{ shownToken.friendlyName }}
</div>
<!-- Modal message dialog -->
<div class="modal fade" id="setupTriggerModal">
<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">Setup new build trigger</h4>
</div>
<div class="modal-body">
<div class="trigger-description-element" ng-switch on="currentSetupTrigger.service">
<div ng-switch-when="github">
<div class="trigger-setup-github" repository="repo" trigger="currentSetupTrigger"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" ng-disabled="!currentSetupTrigger.$ready" ng-click="finishSetupTrigger(currentSetupTrigger)">Finished</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- Modal message dialog -->
<div class="modal fade" id="cannotchangeModal">
<div class="modal-dialog">

View file

@ -18,7 +18,11 @@
</div>
</div>
<div class="row" ng-show="!builds.length">
<div class="row" ng-show="!builds">
<div class="quay-spinner"></div>
</div>
<div class="row" ng-show="builds && !builds.length">
There are no builds for this repository
</div>
@ -26,7 +30,7 @@
<!-- Side tabs -->
<div class="col-sm-2">
<ul class="nav nav-pills nav-stacked">
<li ng-class="currentBuild == build ? 'active' : ''" ng-repeat="build in builds | reverse">
<li ng-class="currentBuild == build ? 'active' : ''" ng-repeat="build in builds">
<a class="build-tab-link" href="javascript:void(0)" ng-click="setCurrentBuild(build.id, true)">
<span class="phase-icon" ng-class="build.phase"></span>
<span>{{ build.display_name }}</span>
@ -39,6 +43,10 @@
<div class="col-sm-10">
<div class="tab-content" onresize="adjustLogHeight()">
<div ng-repeat="build in builds" class="tab-pane build-pane" ng-class="currentBuild == build ? 'active' : ''">
<div class="alert alert-info" ng-show="build.trigger">
Triggered by: <span class="trigger-description" trigger="build.trigger" short="true" style="margin-left: 10px"></span>
</div>
<div class="build-header">
<div class="timing">
<i class="fa fa-clock-o"></i>
@ -62,9 +70,9 @@
<div class="log-container" ng-class="container.type" ng-repeat="container in logEntries">
<div class="container-header" ng-class="container.type == 'phase' ? container.message : ''"
ng-switch on="container.type" ng-click="toggleLogs(container)">
ng-switch on="container.type" ng-click="container.logs.toggle()">
<i class="fa chevron"
ng-class="container.logs ? 'fa-chevron-down' : 'fa-chevron-right'" ng-show="hasLogs(container)"></i>
ng-class="container.logs.isVisible ? 'fa-chevron-down' : 'fa-chevron-right'" ng-show="hasLogs(container)"></i>
<div ng-switch-when="phase">
<span class="container-content build-log-phase" phase="container"></span>
</div>
@ -77,8 +85,8 @@
</div>
<!-- Display the entries for the container -->
<div class="container-logs" ng-show="container.logs">
<div class="log-entry" bindonce ng-repeat="entry in container.logs">
<div class="container-logs" ng-show="container.logs.isVisible">
<div class="log-entry" bindonce ng-repeat="entry in container.logs.visibleEntries">
<span class="id" bo-text="$index + container.index + 1"></span>
<span class="message" bo-html="processANSI(entry.message, container)"></span>
</div>

View file

@ -17,16 +17,14 @@
<span class="entity-reference" entity="member" namespace="organization.name"></span>
</td>
<td>
<span class="delete-ui" tabindex="0" title="Remove User" ng-show="canEditMembers">
<span class="delete-ui-button" ng-click="removeMember(member.name)"><button class="btn btn-danger">Remove</button></span>
<i class="fa fa-times"></i>
</span>
<span class="delete-ui" delete-title="'Remove User From Team'" button-title="'Remove'"
perform-delete="removeMember(member.name)" ng-if="canEditMembers"></span>
</td>
</tr>
<tr ng-show="canEditMembers">
<td colspan="2">
<span class="entity-search" namespace="orgname" include-teams="false" input-title="'Add a user...'"
<span class="entity-search" namespace="orgname" include-teams="false" input-title="'Add a Quay.io user...'"
entity-selected="addNewMember" is-organization="true"
current-entity="selectedMember"></span>
</td>

View file

@ -16,12 +16,6 @@
</div>
</div>
<div class="row" ng-show="askForPassword">
<div class="col-md-12">
<div class="alert alert-warning">Your account does not currently have a password. You will need to create a password before you will be able to <strong>push</strong> or <strong>pull</strong> repositories.</div>
</div>
</div>
<div class="row">
<!-- Side tabs -->
<div class="col-md-2">
@ -33,6 +27,7 @@
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#email">Account E-mail</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Change Password</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#github">GitHub Login</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#authorized" ng-click="loadAuthedApps()">Authorized Applications</a></li>
<li ng-show="hasPaidBusinessPlan"><a href="javascript:void(0)" data-toggle="tab" data-target="#logs" ng-click="loadLogs()">Usage Logs</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#migrate" id="migrateTab">Convert to Organization</a></li>
</ul>
@ -41,6 +36,55 @@
<!-- Content -->
<div class="col-md-10">
<div class="tab-content">
<!-- Authorized applications tab -->
<div id="authorized" class="tab-pane">
<div class="quay-spinner" ng-show="!authorizedApps"></div>
<div class="panel" ng-show="authorizedApps != null">
<div class="panel-body" ng-show="!authorizedApps.length">
You have not authorized any external applications
</div>
<div class="panel-body" ng-show="authorizedApps.length">
<div class="alert alert-info">
These are the applications you have authorized to view information and perform Quay.io actions on your behalf.
</div>
<table class="table">
<thead>
<th>Application Name</th>
<th>Authorized Permissions</th>
<th style="width: 150px">Revoke</th>
</thead>
<tr class="auth-info" ng-repeat="authInfo in authorizedApps">
<td>
<img src="//www.gravatar.com/avatar/{{ authInfo.gravatar }}?s=16&d=identicon">
<a href="{{ authInfo.application.url }}" ng-if="authInfo.application.url" target="_blank"
title="{{ authInfo.application.description || authInfo.application.name }}" bs-tooltip>
{{ authInfo.application.name }}
</a>
<span ng-if="!authInfo.application.url" title="{{ authInfo.application.description || authInfo.application.name }}" bs-tooltip>
{{ authInfo.application.name }}
</span>
<span class="by">{{ authInfo.application.organization.name }}</span>
</td>
<td>
<span class="label label-default scope"
ng-class="{'repo:admin': 'label-primary', 'repo:write': 'label-success', 'repo:create': 'label-success'}[scopeInfo.scope]"
ng-repeat="scopeInfo in authInfo.scopes" title="{{ scopeInfo.description }}" bs-tooltip>
{{ scopeInfo.scope }}
</span>
</td>
<td>
<span class="delete-ui" delete-title="'Revoke Authorization'" button-title="'Revoke'" perform-delete="deleteAccess(authInfo)"></span>
</td>
</tr>
</table>
</div>
</div>
</div>
<!-- Logs tab -->
<div id="logs" class="tab-pane">
<div class="logs-view" user="user" visible="logsShown"></div>

View file

@ -16,11 +16,12 @@
<div class="repo-controls">
<!-- Builds -->
<div class="dropdown" data-placement="top" style="display: inline-block"
bs-tooltip="buildsInfo ? 'Dockerfile Builds Running: ' + (buildsInfo.length) : 'Dockerfile Build'"
ng-show="repo.can_write || buildsInfo.length">
bs-tooltip=""
title="{{ runningBuilds.length ? 'Dockerfile Builds Running: ' + (runningBuilds.length) : 'Dockerfile Build' }}"
ng-show="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="buildsInfo ? 'visible' : ''"><span>{{ buildsInfo ? buildsInfo.length : '' }}</span></span>
<span class="count" ng-class="runningBuilds.length ? 'visible' : ''"><span>{{ runningBuilds.length ? runningBuilds.length : '' }}</span></span>
<b class="caret"></b>
</button>
<ul class="dropdown-menu">
@ -32,9 +33,14 @@
<i class="fa fa-plus" style="margin-left: 1px; margin-right: 8px;"></i>New Dockerfile Build
</a>
</li>
<li role="presentation" class="divider" ng-show="buildsInfo && repo.can_write"></li>
<li role="presentation" class="dropdown-header" ng-show="buildsInfo">Current Builds</li>
<li ng-repeat="buildInfo in buildsInfo">
<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>
@ -45,7 +51,7 @@
<!-- Admin -->
<a id="admin-cog" href="{{ '/repository/' + repo.namespace + '/' + repo.name + '/admin' }}"
ng-show="repo.can_admin">
<button class="btn btn-default" title="Repository Settings" bs-tooltip="tooltip.title" data-placement="top">
<button class="btn btn-default" title="Repository Settings" bs-tooltip="tooltip" data-placement="top">
<i class="fa fa-cog fa-lg"></i></button></a>
<!-- Pull Command -->
@ -63,7 +69,6 @@
Copied to clipboard
</div>
</span>
</div>
</div>
@ -78,10 +83,21 @@
</div>
<div class="empty-description" ng-show="repo.can_write">
To push images to this repository:<br><br>
<pre>sudo docker tag <i>0u123imageidgoeshere</i> quay.io/{{repo.namespace}}/{{repo.name}}
sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
<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 Quay.io (if you have not done so already):
<pre class="command">sudo docker login quay.io</pre>
Tag an image to this repository:
<pre class="command">sudo docker tag <i>0u123imageidgoeshere</i> quay.io/{{repo.namespace}}/{{repo.name}}</pre>
Push the image to this repository:
<pre class="command">sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
</div>
</div>
</div>
</div>
<div class="repo-content" ng-show="!currentTag.image && repo.is_building">
@ -159,7 +175,7 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
<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="image.size | bytes"></span>
bs-tooltip="" title="{{ image.size | bytes }}"></span>
</span>
<span class="size-title"><a href="javascript:void(0)" ng-click="setImage(image.id, true)">{{ image.id.substr(0, 12) }}</a></span>
</div>
@ -223,7 +239,8 @@ sudo docker push quay.io/{{repo.namespace}}/{{repo.name}}</pre>
<i class="fa fa-terminal section-icon" bs-tooltip="tooltip.title" title="Image Command"></i>
<span class="section-info">
<span class="formatted-command trimmed"
bs-tooltip="getTooltipCommand(currentImage)"
data-html="true"
bs-tooltip="" title="{{ getTooltipCommand(currentImage) }}"
data-placement="top">{{ getFormattedCommand(currentImage) }}</span>
</span>
</div>