Add a fetch tag dialog

This commit is contained in:
Joseph Schorr 2015-03-19 15:08:18 -04:00
parent c1d58bdd6c
commit 70aec00914
13 changed files with 258 additions and 2 deletions

View file

@ -1,3 +1,14 @@
.repo-panel-info-element .right-controls {
margin-bottom: 20px;
float: right;
}
.repo-panel-info-element .right-controls .copy-box {
width: 400px;
display: inline-block;
margin-left: 10px;
}
.repo-panel-info-element .stat-col {
border-right: 2px solid #eee;
}

View file

@ -63,4 +63,9 @@
.repo-panel-tags-element .options-col {
padding-left: 20px;
}
.repo-panel-tags-element .options-col .fa-download {
color: #999;
cursor: pointer;
}

View file

@ -0,0 +1,19 @@
.fetch-tag-dialog .modal-table {
width: 100%;
}
.fetch-tag-dialog .modal-table .first-col {
width: 140px;
}
.fetch-tag-dialog .co-dialog .modal-body {
padding: 20px;
}
.fetch-tag-dialog .entity-search {
margin: 10px;
}
.fetch-tag-dialog pre.command {
margin-top: 10px;
}

View file

@ -4787,6 +4787,20 @@ i.slack-icon {
height: 16px;
}
i.docker-icon {
background-image: url(/static/img/docker.png);
background-size: 16px;
width: 16px;
height: 16px;
}
i.rocket-icon {
background-image: url(/static/img/rocket.png);
background-size: 16px;
width: 16px;
height: 16px;
}
.external-notification-view-element {
margin: 10px;
padding: 6px;

View file

@ -0,0 +1,70 @@
<div class="fetch-tag-dialog-element">
<!-- Modal message dialog -->
<div class="co-dialog modal fade" id="fetchTagDialog">
<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">
Fetch Tag: <i class="fa fa-tag" style="margin-left: 6px; margin-right: 4px"></i> {{ currentTag.name }}
</h4>
</div>
<div class="modal-body">
<table class="modal-table">
<tr>
<td class="first-col">Image Format:</td>
<td>
<div class="dropdown-select"
placeholder="'(Select Image Format)'"
selected-item="currentFormat.title"
handle-item-selected="handleFormatSelected(datum)"
clear-value="clearCounter">
<!-- Icons -->
<i class="dropdown-select-icon fa fa-lg" ng-class="currentFormat.icon"></i>
<!-- Dropdown menu -->
<ul class="dropdown-select-menu pull-right" role="menu">
<li ng-repeat="format in formats">
<a href="javascript:void(0)" ng-click="setFormat(format)">
<i class="fa fa-lg" ng-class="format.icon"></i> {{ format.title }}
</a>
</li>
</ul>
</div>
</td>
</tr>
<tr ng-show="currentFormat.require_creds">
<td class="first-col">Pull Credentials:</td>
<td>
<div class="entity-search" namespace="repository.namespace"
placeholder="'Choose Pull Credentials'"
allowed-entities="['robot']"
clear-value="clearCounter"
auto-clear="false"
current-entity="currentEntity"></div>
</td>
</tr>
</table>
<div class="cor-loader-inline" ng-if="currentEntity && !currentRobot"></div>
<div ng-if="getCommand(currentFormat, currentRobot)">
Command:
<pre class="command">{{ getCommand(currentFormat, currentRobot) }}</pre>
</div>
</div>
<div class="modal-footer">
<div class="clipboard-copied-message" style="display: none">
Copied
</div>
<input type="hidden" name="command-data" id="command-data"
value="{{ getCommand(currentFormat, currentRobot) }}">
<button id="copyClipboard" type="button" class="btn btn-primary"
data-clipboard-target="command-data"
ng-show="getCommand(currentFormat, currentRobot)">Copy Command</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>

View file

@ -62,7 +62,14 @@
<!-- Repository Description -->
<div class="description-container">
<!-- Pull Controls -->
<div class="right-controls hidden-sm hidden-xs">
Pull Image: <div class="copy-box" hovering-message="true" value="pullCommand"></div>
</div>
<h4 style="font-size:20px;">Description</h4>
<div class="description markdown-input"
content="repository.description"
can-write="repository.can_write"

View file

@ -52,6 +52,7 @@
<a href="javascript:void(0)" ng-click="orderBy('image_id')">Image</a>
</td>
<td class="options-col"></td>
<td class="options-col"></td>
</thead>
<tr class="co-checkable-row"
@ -75,6 +76,11 @@
<span class="image-track-line" ng-class="trackLineClass($parent.$index, it)"
ng-style="{'borderColor': it.color}"></span>
</td>
<td class="options-col">
<i class="fa fa-download" data-title="Fetch Tag" bs-tooltip
ng-click="fetchTagActionHandler.askFetchTag(tag)">
</i>
</td>
<td class="options-col">
<span class="cor-options-menu" ng-if="repository.can_write">
<span class="cor-option" option-click="askDeleteTag(tag.name)">
@ -98,4 +104,7 @@
</div>
<div class="tag-operations-dialog" repository="repository" images="images"
action-handler="tagActionHandler"></div>
action-handler="tagActionHandler"></div>
<div class="fetch-tag-dialog" repository="repository" action-handler="fetchTagActionHandler">
</div>

BIN
static/img/docker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/img/rocket.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

View file

@ -12,7 +12,16 @@ angular.module('quay').directive('repoPanelInfo', function () {
'repository': '=repository',
'builds': '=builds'
},
controller: function($scope, $element, ApiService) {
controller: function($scope, $element, ApiService, Config) {
$scope.$watch('repository', function(repository) {
if (!$scope.repository) { return; }
var namespace = $scope.repository.namespace;
var name = $scope.repository.name;
$scope.pullCommand = 'docker pull ' + Config.getDomain() + '/' + namespace + '/' + name;
});
$scope.updateDescription = function(content) {
$scope.repository.description = content;
$scope.repository.put();

View file

@ -0,0 +1,104 @@
/**
* An element which adds a of dialog for fetching a tag.
*/
angular.module('quay').directive('fetchTagDialog', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/fetch-tag-dialog.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository',
'actionHandler': '=actionHandler'
},
controller: function($scope, $element, $timeout, ApiService, UserService, Config) {
$scope.clearCounter = 0;
$scope.currentFormat = null;
$scope.currentEntity = null;
$scope.currentRobot = null;
$scope.formats = [
{
'title': 'Squashed Docker Image',
'icon': 'fa-file-archive-o',
'command': 'curl -L -f {http}://{pull_user}:{pull_password}@{hostname}/c1/squash/{namespace}/{name}/{tag} | docker load',
'require_creds': true
},
{
'title': 'Basic Docker Pull',
'icon': 'docker-icon',
'command': 'docker pull {hostname}/{namespace}/{name}:{tag}'
}];
$scope.$watch('currentEntity', function(entity) {
if (!entity) {
$scope.currentRobot = null;
return;
}
if ($scope.currentRobot && $scope.currentRobot.name == entity.name) {
return;
}
$scope.currentRobot = null;
var parts = entity.name.split('+');
var namespace = parts[0];
var shortname = parts[1];
var params = {
'robot_shortname': shortname
};
var orgname = UserService.isOrganization(namespace) ? namespace : '';
ApiService.getRobot(orgname, null, params).then(function(resp) {
$scope.currentRobot = resp;
}, ApiService.errorDisplay('Cannot download robot token'));
});
$scope.getCommand = function(format, robot) {
if (!format || !format.command) { return ''; }
if (format.require_creds && !robot) { return ''; }
var params = {
'pull_user': robot ? robot.name : '',
'pull_password': robot ? robot.token : '',
'hostname': Config.getDomain(),
'http': Config.getHttp(),
'namespace': $scope.repository.namespace,
'name': $scope.repository.name,
'tag': $scope.currentTag.name
};
var value = format.command;
for (var param in params) {
if (!params.hasOwnProperty(param)) { continue; }
value = value.replace('{' + param + '}', params[param]);
}
return value;
};
$scope.setFormat = function(format) {
$scope.currentFormat = format;
};
$scope.actionHandler = {
'askFetchTag': function(tag) {
$scope.currentTag = tag;
$scope.currentFormat = null;
$scope.currentEntity = null;
$scope.currentRobot = null;
$scope.clearCounter++;
$element.find('#copyClipboard').clipboardCopy();
$element.find('#fetchTagDialog').modal({});
}
};
}
};
return directiveDefinitionObject;
});

View file

@ -54,6 +54,10 @@ angular.module('quay').factory('Config', [function() {
return config['PREFERRED_URL_SCHEME'] + '://' + auth + config['SERVER_HOSTNAME'];
};
config.getHttp = function() {
return config['PREFERRED_URL_SCHEME'];
};
config.getUrl = function(opt_path) {
var path = opt_path || '';
return config['PREFERRED_URL_SCHEME'] + '://' + config['SERVER_HOSTNAME'] + path;

View file

@ -83,6 +83,10 @@ function(ApiService, CookieService, $rootScope, Config) {
});
};
userService.isOrganization = function(name) {
return !!userService.getOrganization(name);
};
userService.getOrganization = function(name) {
if (!userResponse || !userResponse.organizations) { return null; }
for (var i = 0; i < userResponse.organizations.length; ++i) {