Merge pull request #180 from coreos-inc/uiperf
UI performance fixes and updates
This commit is contained in:
commit
05d5238cc8
15 changed files with 208 additions and 37 deletions
|
@ -1151,8 +1151,15 @@ a:focus {
|
|||
float: right;
|
||||
}
|
||||
|
||||
.co-check-bar .co-filter-box .page-controls {
|
||||
margin-right: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.co-check-bar .co-filter-box input {
|
||||
width: 300px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.empty {
|
||||
|
|
33
static/css/directives/ui/page-controls.css
Normal file
33
static/css/directives/ui/page-controls.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
.page-controls-element {
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.page-controls-element .current-items {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
padding: 4px;
|
||||
border: 1px solid transparent;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.page-controls-element .current-items:hover {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.page-controls-element .btn {
|
||||
font-size: 18px;
|
||||
line-height: 14px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.page-controls-element .btn:disabled {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.page-controls-element .page-view {
|
||||
cursor: pointer;
|
||||
}
|
|
@ -9,4 +9,15 @@
|
|||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tag-operations-dialog .delete-tag-list {
|
||||
margin: 4px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.tag-operations-dialog .delete-tag-list li {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
margin: 4px;
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
<a href="/repository/{{ repository.namespace }}/{{ repository.name }}/image/{{ imageId }}"
|
||||
class="image-link-element">{{ imageId.substr(0, 12) }}</a>
|
||||
<a bo-href-i="/repository/{{ repository.namespace }}/{{ repository.name }}/image/{{ imageId }}"
|
||||
class="image-link-element" bindonce>{{ imageId.substr(0, 12) }}</a>
|
|
@ -11,13 +11,20 @@
|
|||
<input type="search" class="form-control" ng-model="filter" placeholder="{{ itemName }} filter...">
|
||||
</li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
<li ng-repeat="item in items | filter:filter">
|
||||
<li ng-repeat="item in items | filter:filter | limitTo:10">
|
||||
<a class="menu-item" href="javascript:void(0)" ng-click="toggleItem(item)">
|
||||
<span class="co-checkable-item" ng-class="isChecked(selectedItems, item) ? 'checked': 'not-checked'">
|
||||
</span>
|
||||
<span class="menu-item-template" ng-transcope></span>
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" ng-if="(items | filter:filter | limitTo:11).length == 11">
|
||||
<div class="empty" style="margin-top: 10px;">
|
||||
<div class="empty-secondary-msg">
|
||||
+ {{ (items | filter:filter).length - 10 }} additional
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li role="presentation" ng-if="(items | filter:filter).length == 0">
|
||||
<div class="empty">
|
||||
<div class="empty-primary-msg">No matching {{ itemName }}s found</div>
|
||||
|
|
20
static/directives/page-controls.html
Normal file
20
static/directives/page-controls.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<div class="page-controls-element">
|
||||
<span class="current-items dropdown">
|
||||
<span class="page-view" data-toggle="dropdown">
|
||||
{{ getPageStart(currentPage, pageSize, totalCount) }} - {{ getPageEnd(currentPage, pageSize, totalCount) }}
|
||||
of {{ totalCount }}
|
||||
</span>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="javascript:void(0)" ng-click="setPage(0)"><i class="fa fa-caret-square-o-left"></i>First Page</a></li>
|
||||
<li><a href="javascript:void(0)" ng-click="setPage(getPageCount(pageSize, totalCount) - 1)"><i class="fa fa-caret-square-o-right"></i>Last Page</a></li>
|
||||
</ul>
|
||||
</span>
|
||||
<span class="page-buttons btn-group">
|
||||
<button class="btn btn-default"
|
||||
ng-disabled="currentPage == 0"
|
||||
ng-click="changePage(-1)">❮</button>
|
||||
<button class="btn btn-default"
|
||||
ng-disabled="currentPage >= getPageCount(pageSize, totalCount) - 1"
|
||||
ng-click="changePage(1)">❯</button>
|
||||
</span>
|
||||
</div>
|
|
@ -53,11 +53,14 @@
|
|||
</span>
|
||||
|
||||
<span class="co-filter-box">
|
||||
<span class="page-controls" total-count="tags.length" current-page="options.page" page-size="50"></span>
|
||||
<input class="form-control" type="text" ng-model="options.tagFilter" placeholder="Filter Tags...">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<table class="co-table" id="tagsTable">
|
||||
<div class="cor-loader" ng-show="!isEnabled"></div>
|
||||
|
||||
<table class="co-table" id="tagsTable" ng-if="isEnabled">
|
||||
<thead>
|
||||
<td class="checkbox-col"></td>
|
||||
<td ng-class="tablePredicateClass('name', options.predicate, options.reverse)">
|
||||
|
@ -85,41 +88,42 @@
|
|||
</thead>
|
||||
|
||||
<tr class="co-checkable-row"
|
||||
ng-repeat="tag in tags"
|
||||
ng-class="checkedTags.isChecked(tag, checkedTags.checked) ? 'checked' : ''">
|
||||
ng-repeat="tag in tags | slice:(50 * options.page):(50 * (options.page + 1))"
|
||||
ng-class="checkedTags.isChecked(tag, checkedTags.checked) ? 'checked' : ''"
|
||||
bindonce>
|
||||
<td><span class="cor-checkable-item" controller="checkedTags" item="tag"></span></td>
|
||||
<td><span class="tag-span"><i class="fa fa-tag"></i> {{ tag.name }}</span></td>
|
||||
<td><span class="tag-span"><i class="fa fa-tag"></i><span bo-text="tag.name"></span></span></td>
|
||||
<td class="hidden-xs">
|
||||
<span am-time-ago="tag.last_modified" ng-if="tag.last_modified"></span>
|
||||
<span ng-if="!tag.last_modified">Unknown</span>
|
||||
<span am-time-ago="tag.last_modified" bo-if="tag.last_modified"></span>
|
||||
<span bo-if="!tag.last_modified">Unknown</span>
|
||||
</td>
|
||||
<td class="hidden-xs">{{ tag.size | bytes }}</td>
|
||||
<td class="hidden-xs" bo-text="tag.size | bytes"></td>
|
||||
<td class="hidden-xs image-id-col">
|
||||
<span class="image-link" repository="repository" image-id="tag.image_id"></span>
|
||||
</td>
|
||||
<td class="hidden-xs image-track" ng-repeat="it in imageTracks">
|
||||
<span class="image-track-dot" ng-if="it.image_id == tag.image_id"
|
||||
ng-style="{'borderColor': it.color}" ng-click="selectTrack(it)"></span>
|
||||
<span class="image-track-line" ng-class="trackLineClass($parent.$index, it)"
|
||||
ng-style="{'borderColor': it.color}"></span>
|
||||
<td class="hidden-xs image-track" ng-repeat="it in imageTracks" bindonce>
|
||||
<span class="image-track-dot" bo-if="it.image_id == tag.image_id"
|
||||
bo-style="{'borderColor': it.color}" ng-click="selectTrack(it)"></span>
|
||||
<span class="image-track-line" bo-class="trackLineClass($parent.$index, it)"
|
||||
bo-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" ng-if="repository.can_write">
|
||||
<td class="options-col" bo-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]">
|
||||
<li class="tag-image-history-item" ng-repeat="entry in tagHistory[tag.name]" bindonce>
|
||||
<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>
|
||||
bo-style="{'color': imageMap[entry.docker_image_id].color || '#eee'}"></i>
|
||||
{{ entry.docker_image_id.substr(0, 12) }}
|
||||
</div>
|
||||
<div class="image-apply-time">
|
||||
|
@ -131,12 +135,14 @@
|
|||
</div>
|
||||
</td>
|
||||
<td class="options-col">
|
||||
<span class="cor-options-menu" ng-if="repository.can_write">
|
||||
<span class="cor-option" option-click="askAddTag(tag)">
|
||||
<i class="fa fa-plus"></i> Add New Tag
|
||||
</span>
|
||||
<span class="cor-option" option-click="askDeleteTag(tag.name)">
|
||||
<i class="fa fa-times"></i> Delete Tag
|
||||
<span bo-if="repository.can_write">
|
||||
<span class="cor-options-menu">
|
||||
<span class="cor-option" option-click="askAddTag(tag)">
|
||||
<i class="fa fa-plus"></i> Add New Tag
|
||||
</span>
|
||||
<span class="cor-option" option-click="askDeleteTag(tag.name)">
|
||||
<i class="fa fa-times"></i> Delete Tag
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
dialog-title="Delete Tags"
|
||||
dialog-action-title="Delete Tags">
|
||||
Are you sure you want to delete the following tags:
|
||||
<ul>
|
||||
<ul class="delete-tag-list">
|
||||
<li ng-repeat="tag_info in deleteMultipleTagsInfo.tags">
|
||||
<span class="label label-default tag">{{ tag_info.name }}</span>
|
||||
</li>
|
||||
|
|
8
static/js/directives/filters/slice.js
Normal file
8
static/js/directives/filters/slice.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Slice filter.
|
||||
*/
|
||||
angular.module('quay').filter('slice', function() {
|
||||
return function(arr, start, end) {
|
||||
return (arr || []).slice(start, end);
|
||||
};
|
||||
});
|
|
@ -14,15 +14,18 @@ angular.module('quay').directive('repoPanelTags', function () {
|
|||
'imagesResource': '=imagesResource',
|
||||
'images': '=images',
|
||||
|
||||
'isEnabled': '=isEnabled',
|
||||
|
||||
'getImages': '&getImages'
|
||||
},
|
||||
controller: function($scope, $element, $filter, $location, ApiService, UIService) {
|
||||
var orderBy = $filter('orderBy');
|
||||
|
||||
$scope.checkedTags = UIService.createCheckStateController([]);
|
||||
$scope.checkedTags = UIService.createCheckStateController([], 'name');
|
||||
$scope.options = {
|
||||
'predicate': 'last_modified_datetime',
|
||||
'reverse': false
|
||||
'reverse': false,
|
||||
'page': 0
|
||||
};
|
||||
|
||||
$scope.iterationState = {};
|
||||
|
@ -100,7 +103,7 @@ angular.module('quay').directive('repoPanelTags', function () {
|
|||
$scope.tags = ordered;
|
||||
$scope.allTags = allTags;
|
||||
|
||||
$scope.checkedTags = UIService.createCheckStateController(ordered, checked);
|
||||
$scope.checkedTags = UIService.createCheckStateController(ordered, 'name', checked);
|
||||
$scope.checkedTags.listen(function(checked) {
|
||||
$scope.selectedTags = checked.map(function(tag_info) {
|
||||
return tag_info.name;
|
||||
|
@ -115,9 +118,9 @@ angular.module('quay').directive('repoPanelTags', function () {
|
|||
$scope.$watch('selectedTags', function(selectedTags) {
|
||||
if (!selectedTags || !$scope.repository || !$scope.imageMap) { return; }
|
||||
|
||||
$scope.checkedTags.checked = selectedTags.map(function(tag) {
|
||||
$scope.checkedTags.setChecked(selectedTags.map(function(tag) {
|
||||
return $scope.repository.tags[tag];
|
||||
});
|
||||
}));
|
||||
}, true);
|
||||
|
||||
$scope.$watch('repository', function(repository) {
|
||||
|
|
|
@ -110,10 +110,15 @@ angular.module('quay').directive('headerBar', function () {
|
|||
};
|
||||
|
||||
$scope.appLinkTarget = function() {
|
||||
if ($("div[ng-view]").length === 0) {
|
||||
return "_self";
|
||||
if ($scope._appLinkTarget) {
|
||||
return $scope._appLinkTarget;
|
||||
}
|
||||
return "";
|
||||
|
||||
if ($("div[ng-view]").length === 0) {
|
||||
return $scope._appLinkTarget = "_self";
|
||||
}
|
||||
|
||||
return $scope._appLinkTarget = "";
|
||||
};
|
||||
|
||||
$scope.getEnterpriseLogo = function() {
|
||||
|
|
41
static/js/directives/ui/page-controls.js
Normal file
41
static/js/directives/ui/page-controls.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* An element which displays controls for moving between pages of paginated results.
|
||||
*/
|
||||
angular.module('quay').directive('pageControls', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/page-controls.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'currentPage': '=currentPage',
|
||||
'pageSize': '=pageSize',
|
||||
'totalCount': '=totalCount'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
$scope.getPageStart = function(currentPage, pageSize, totalCount) {
|
||||
return Math.min((currentPage * pageSize) + 1, totalCount);
|
||||
};
|
||||
|
||||
$scope.getPageEnd = function(currentPage, pageSize, totalCount) {
|
||||
return Math.min(((currentPage + 1) * pageSize), totalCount);
|
||||
};
|
||||
|
||||
$scope.getPageCount = function(pageSize, totalCount) {
|
||||
return Math.ceil(totalCount / pageSize);
|
||||
};
|
||||
|
||||
$scope.changePage = function(offset) {
|
||||
$scope.currentPage += offset;
|
||||
$scope.currentPage = Math.max($scope.currentPage, 0);
|
||||
$scope.currentPage = Math.min($scope.currentPage, $scope.getPageCount($scope.pageSize, $scope.totalCount));
|
||||
};
|
||||
|
||||
$scope.setPage = function(page) {
|
||||
$scope.currentPage = page;
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -20,6 +20,7 @@
|
|||
$scope.imagesRequired = false;
|
||||
|
||||
// Tab-enabled counters.
|
||||
$scope.tagsShown = 0;
|
||||
$scope.logsShown = 0;
|
||||
$scope.buildsShown = 0;
|
||||
$scope.settingsShown = 0;
|
||||
|
@ -158,6 +159,12 @@
|
|||
$scope.logsShown++;
|
||||
};
|
||||
|
||||
$scope.showTags = function() {
|
||||
$timeout(function() {
|
||||
$scope.tagsShown = 1;
|
||||
}, 10);
|
||||
};
|
||||
|
||||
$scope.requireImages = function() {
|
||||
// Lazily load the repo's images if this is the first call to a tab
|
||||
// that needs the images.
|
||||
|
|
|
@ -2,10 +2,14 @@
|
|||
* Service which provides helper methods for performing some simple UI operations.
|
||||
*/
|
||||
angular.module('quay').factory('UIService', ['$timeout', '$rootScope', '$location', function($timeout, $rootScope, $location) {
|
||||
var CheckStateController = function(items, opt_checked) {
|
||||
var CheckStateController = function(items, itemKey, opt_checked) {
|
||||
this.items = items;
|
||||
this.itemKey = itemKey;
|
||||
this.checked = opt_checked || [];
|
||||
this.checkedMap = {};
|
||||
this.listeners_ = [];
|
||||
|
||||
this.buildMap_();
|
||||
};
|
||||
|
||||
CheckStateController.prototype.listen = function(callback) {
|
||||
|
@ -13,7 +17,7 @@ angular.module('quay').factory('UIService', ['$timeout', '$rootScope', '$locatio
|
|||
};
|
||||
|
||||
CheckStateController.prototype.isChecked = function(item) {
|
||||
return $.inArray(item, this.checked) >= 0;
|
||||
return !!this.checkedMap[item[this.itemKey]];
|
||||
};
|
||||
|
||||
CheckStateController.prototype.toggleItem = function(item) {
|
||||
|
@ -27,19 +31,35 @@ angular.module('quay').factory('UIService', ['$timeout', '$rootScope', '$locatio
|
|||
CheckStateController.prototype.toggleItems = function() {
|
||||
if (this.checked.length) {
|
||||
this.checked = [];
|
||||
this.checkedMap = {};
|
||||
} else {
|
||||
this.checked = this.items.slice();
|
||||
this.buildMap_();
|
||||
}
|
||||
this.callListeners_();
|
||||
};
|
||||
|
||||
CheckStateController.prototype.setChecked = function(items) {
|
||||
this.checked = items.slice();
|
||||
this.buildMap_();
|
||||
};
|
||||
|
||||
CheckStateController.prototype.buildMap_ = function() {
|
||||
var that = this;
|
||||
this.checked.forEach(function(item) {
|
||||
that.checkedMap[item[that.itemKey]] = true;
|
||||
});
|
||||
};
|
||||
|
||||
CheckStateController.prototype.checkByFilter = function(filter) {
|
||||
this.checked = $.grep(this.items, filter);
|
||||
this.buildMap_();
|
||||
this.callListeners_();
|
||||
};
|
||||
|
||||
CheckStateController.prototype.checkItem = function(item) {
|
||||
this.checked.push(item);
|
||||
this.checkedMap[item[this.itemKey]] = true;
|
||||
this.callListeners_();
|
||||
};
|
||||
|
||||
|
@ -47,6 +67,7 @@ angular.module('quay').factory('UIService', ['$timeout', '$rootScope', '$locatio
|
|||
this.checked = $.grep(this.checked, function(cItem) {
|
||||
return cItem != item;
|
||||
});
|
||||
this.checkedMap[item[this.itemKey]] = false;
|
||||
this.callListeners_();
|
||||
};
|
||||
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
<i class="fa fa-info-circle"></i>
|
||||
</span>
|
||||
|
||||
<span class="cor-tab" tab-title="Tags" tab-target="#tags" id="tagsTab">
|
||||
<span class="cor-tab" tab-title="Tags" tab-target="#tags" id="tagsTab"
|
||||
tab-init="showTags()">
|
||||
<i class="fa fa-tags"></i>
|
||||
</span>
|
||||
|
||||
|
@ -66,7 +67,8 @@
|
|||
images="viewScope.images"
|
||||
images-resource="viewScope.imagesResource"
|
||||
selected-tags="viewScope.selectedTags"
|
||||
get-images="getImages(callback)"></div>
|
||||
get-images="getImages(callback)"
|
||||
is-enabled="tagsShown"></div>
|
||||
</div>
|
||||
|
||||
<!-- Builds -->
|
||||
|
|
Reference in a new issue