Add ability to revert tags via time machine

This commit is contained in:
Joseph Schorr 2015-04-16 17:18:00 -04:00
parent 2a77bd2c92
commit f19d2f684e
16 changed files with 277 additions and 19 deletions

View file

@ -65,7 +65,22 @@
padding-left: 20px;
}
.repo-panel-tags-element .options-col .fa-download {
.repo-panel-tags-element .options-col .fa-download, .repo-panel-tags-element .options-col .fa-history {
color: #999;
cursor: pointer;
}
.repo-panel-tags-element .tag-image-history-item .image-id {
font-family: Consolas, "Lucida Console", Monaco, monospace;
font-size: 12px;
}
.repo-panel-tags-element .tag-image-history-item .image-apply-time {
color: #ccc;
font-size: 11px;
padding-left: 20px;
}
.repo-panel-tags-element .tag-image-history-item .fa-circle-o {
margin-right: 2px;
}

View file

@ -41,14 +41,15 @@
}
.repo-tag-history-element .history-entry .history-date-break:before {
content: "";
content: "\f073";
font-family: FontAwesome;
position: absolute;
border-radius: 50%;
width: 12px;
height: 12px;
background: #ccc;
top: 4px;
left: -7px;
top: 1px;
left: -9px;
background: white;
}
.repo-tag-history-element .history-entry .history-icon {
@ -79,11 +80,20 @@
font-family: FontAwesome;
}
.repo-tag-history-element .history-entry.revert .history-icon:before {
content: "\f0e2";
font-family: FontAwesome;
}
.repo-tag-history-element .history-entry.delete .history-icon:before {
content: "\f014";
font-family: FontAwesome;
}
.repo-tag-history-element .history-entry.current.revert .history-icon {
background-color: #F0C577;
}
.repo-tag-history-element .history-entry.current.move .history-icon {
background-color: #77BFF0;
}

View file

@ -0,0 +1,4 @@
.tag-operations-dialog .image-id {
font-family: Consolas, "Lucida Console", Monaco, monospace;
font-size: 12px;
}

View file

@ -35,6 +35,12 @@
from image
<span class="image-link" repository="repository" image-id="entry.old_docker_image_id"></span>
</span>
<span ng-switch-when="revert">
was reverted to image
<span class="image-link" repository="repository" image-id="entry.docker_image_id"></span>
from image
<span class="image-link" repository="repository" image-id="entry.old_docker_image_id"></span>
</span>
</span>
</div>
<div class="history-datetime">{{ entry.time | amDateFormat:'dddd, MMMM Do YYYY, h:mm:ss a' }}</div>

View file

@ -77,6 +77,7 @@
style="min-width: 120px;">
<a href="javascript:void(0)" ng-click="orderBy('image_id')">Image</a>
</td>
<td class="options-col" ng-if="repository.can_write"></td>
<td class="options-col"></td>
<td class="options-col"></td>
</thead>
@ -105,12 +106,30 @@
ng-click="fetchTagActionHandler.askFetchTag(tag)">
</i>
</td>
<td class="options-col" ng-if="repository.can_write">
<div class="dropdown" style="text-align: left;">
<i class="fa fa-history dropdown-toggle" data-toggle="dropdown" data-title="Tag History"
ng-click="loadTagHistory(tag)"
bs-tooltip></i>
<ul class="dropdown-menu pull-right">
<li ng-if="!tagHistory[tag.name]"><div class="cor-loader"></div></li>
<li class="tag-image-history-item" ng-repeat="entry in tagHistory[tag.name]">
<a href="javascript:void(0)" ng-click="askRevertTag(tag, entry.docker_image_id)">
<div class="image-id">
<i class="fa fa-circle-o"
ng-style="{'color': imageMap[entry.docker_image_id].color || '#eee'}"></i>
{{ entry.docker_image_id.substr(0, 12) }}
</div>
<div class="image-apply-time">
{{ entry.start_ts * 1000 | amDateFormat:'dddd, MMMM Do YYYY, h:mm:ss a' }}
</div>
</a>
</li>
</ul>
</div>
</td>
<td class="options-col">
<span class="cor-options-menu" ng-if="repository.can_write">
<span class="cor-option" option-click="showHistory(true, tag.name)"
ng-if="tag.last_modified">
<i class="fa fa-history"></i> View Tag History
</span>
<span class="cor-option" option-click="askDeleteTag(tag.name)">
<i class="fa fa-times"></i> Delete Tag
</span>
@ -137,5 +156,4 @@
<div class="tag-operations-dialog" repository="repository" images="images"
action-handler="tagActionHandler"></div>
<div class="fetch-tag-dialog" repository="repository" action-handler="fetchTagActionHandler">
</div>
<div class="fetch-tag-dialog" repository="repository" action-handler="fetchTagActionHandler"></div>

View file

@ -85,3 +85,20 @@
</div>
</div>
</div>
<!-- Recert Tag Confirm -->
<div class="cor-confirm-dialog"
dialog-context="revertTagInfo"
dialog-action="revertTag(info.tag, info.image_id, callback)"
dialog-title="Revert Tag"
dialog-action-title="Revert Tag">
<div class="co-alert co-alert-warning">
This will change the image to which the tag points.
</div>
Are you sure you want to revert tag
<span class="label label-default tag">{{ revertTagInfo.tag.name }}</span> to image
<span class="image-id">{{ revertTagInfo.image_id.substr(0, 12) }}?</span>
</div>
</div>

View file

@ -24,6 +24,7 @@ angular.module('quay').directive('repoPanelTags', function () {
};
$scope.iterationState = {};
$scope.tagHistory = {};
$scope.tagActionHandler = null;
$scope.showingHistory = false;
@ -84,6 +85,9 @@ angular.module('quay').directive('repoPanelTags', function () {
'count': imageMap[image_id].length,
'tags': imageMap[image_id]
});
imageMap[image_id]['color'] = colors(index);
++index;
}
});
@ -224,6 +228,24 @@ angular.module('quay').directive('repoPanelTags', function () {
return names.join(',');
};
$scope.loadTagHistory = function(tag) {
delete $scope.tagHistory[tag.name];
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'specificTag': tag.name,
'limit': 5
};
ApiService.listRepoTags(null, params).then(function(resp) {
$scope.tagHistory[tag.name] = resp.tags;
}, ApiService.errorDisplay('Could not load tag history'));
};
$scope.askRevertTag = function(tag, image_id) {
$scope.tagActionHandler.askRevertTag(tag, image_id);
};
}
};
return directiveDefinitionObject;

View file

@ -98,6 +98,7 @@ angular.module('quay').directive('logsView', function () {
return 'Remove permission for token {token} from repository {repo}';
}
},
'revert_tag': 'Tag {tag} reverted to image {image} from image {original_image}',
'delete_tag': 'Tag {tag} deleted in repository {repo} by user {username}',
'create_tag': 'Tag {tag} created in repository {repo} on image {image} by user {username}',
'move_tag': 'Tag {tag} moved from image {original_image} to image {image} in repository {repo} by user {username}',
@ -213,6 +214,7 @@ angular.module('quay').directive('logsView', function () {
'delete_tag': 'Delete Tag',
'create_tag': 'Create Tag',
'move_tag': 'Move Tag',
'revert_tag':' Revert Tag',
'org_create_team': 'Create team',
'org_delete_team': 'Delete team',
'org_add_team_member': 'Add team member',

View file

@ -56,6 +56,7 @@ angular.module('quay').directive('repoTagHistory', function () {
'action': action,
'start_ts': tag.start_ts,
'end_ts': tag.end_ts,
'reversion': tag.reversion,
'time': time * 1000, // JS expects ms, not s since epoch.
'docker_image_id': opt_docker_id || dockerImageId,
'old_docker_image_id': opt_old_docker_id || ''
@ -73,7 +74,8 @@ angular.module('quay').directive('repoTagHistory', function () {
var futureEntry = currentEntries.length > 0 ? currentEntries[currentEntries.length - 1] : {};
if (futureEntry.start_ts == tag.end_ts) {
removeEntry(futureEntry);
addEntry('move', tag.end_ts, futureEntry.docker_image_id, dockerImageId);
addEntry(futureEntry.reversion ? 'revert': 'move', tag.end_ts,
futureEntry.docker_image_id, dockerImageId);
} else {
addEntry('delete', tag.end_ts)
}

View file

@ -121,6 +121,25 @@ angular.module('quay').directive('tagOperationsDialog', function () {
}, errorHandler);
};
$scope.revertTag = function(tag, image_id, callback) {
if (!$scope.repository.can_write) { return; }
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
'tag': tag.name
};
var data = {
'image': image_id
};
var errorHandler = ApiService.errorDisplay('Cannot revert tag', callback);
ApiService.revertTag(data, params).then(function() {
callback(true);
markChanged([], [tag]);
}, errorHandler);
};
$scope.actionHandler = {
'askDeleteTag': function(tag) {
$scope.deleteTagInfo = {
@ -140,6 +159,20 @@ angular.module('quay').directive('tagOperationsDialog', function () {
$scope.addingTag = false;
$scope.addTagForm.$setPristine();
$element.find('#createOrMoveTagModal').modal('show');
},
'askRevertTag': function(tag, image_id) {
if (tag.image_id == image_id) {
bootbox.alert('This is the current image for the tag');
return;
}
$scope.revertTagInfo = {
'tag': tag,
'image_id': image_id
};
$element.find('#revertTagModal').modal('show');
}
};
}