mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
fix(registry): Performance issues with Registry Manager (#2648)
* fix(registry): fetch datatable details on page/filter/order state change instead of fetching all data on first load * fix(registry): fetch tags datatable details on state change instead of fetching all data on first load * fix(registry): add pagination support for tags + loading display on data load * fix(registry): debounce on text filter to avoid querying transient matching values * refactor(registry): rebase on latest develop * feat(registries): background tags and optimisation -- need code cleanup and for-await-of to cancel on page leave * refactor(registry-management): code cleanup * feat(registry): most optimized version -- need fix for add/retag * fix(registry): addTag working without page reload * fix(registry): retag working without reload * fix(registry): remove tag working without reload * fix(registry): remove repository working with latest changes * fix(registry): disable cache on firefox * feat(registry): use jquery for all 'most used' manifests requests * feat(registry): retag with progression + rewrite manifest REST service to jquery * fix(registry): remove forgotten DI * fix(registry): pagination on repository details * refactor(registry): info message + hidding images count until fetch has been done * fix(registry): fix selection reset deleting selectAll function and not resetting status * fix(registry): resetSelection was trying to set value on a getter * fix(registry): tags were dropped when too much tags were impacted by a tag removal * fix(registry): firefox add tag + progression * refactor(registry): rewording of elements * style(registry): add space between buttons and texts in status elements * fix(registry): cancelling a retag/delete action was not removing the status panel * fix(registry): tags count of empty repositories * feat(registry): reload page on action cancel to avoid desync * feat(registry): uncancellable modal on long operations * feat(registry): modal now closes on error + modal message improvement * feat(registries): remove empty repositories from the list * fix(registry): various bugfixes * feat(registry): independant timer on async actions + modal fix
This commit is contained in:
parent
8a8cef9b20
commit
2445a5aed5
31 changed files with 1372 additions and 421 deletions
|
@ -8,7 +8,7 @@
|
|||
</div>
|
||||
<div class="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search..." auto-focus ng-model-options="{ debounce: 300 }">
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-model-options="{ debounce: 300 }" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-filters nowrap-cells">
|
||||
|
@ -22,16 +22,12 @@
|
|||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('TagsCount')">
|
||||
Tags count
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'TagsCount' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'TagsCount' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
||||
<tr ng-hide="$ctrl.loading" dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
||||
ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<a ui-sref="portainer.registries.registry.repository({repository: item.Name})" class="monospaced"
|
||||
|
@ -39,7 +35,7 @@
|
|||
</td>
|
||||
<td>{{ item.TagsCount }}</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<tr ng-if="!$ctrl.dataset || $ctrl.loading">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
|
@ -59,7 +55,6 @@
|
|||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.extensions.registrymanagement').component('registryRepositoriesDatatable', {
|
||||
templateUrl: './registryRepositoriesDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
controller: 'RegistryRepositoriesDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
|
@ -8,6 +8,7 @@ angular.module('portainer.extensions.registrymanagement').component('registryRep
|
|||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
removeAction: '<'
|
||||
paginationAction: '<',
|
||||
loading: '<'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.controller('RegistryRepositoriesDatatableController', ['$scope', '$controller',
|
||||
function($scope, $controller) {
|
||||
var ctrl = this;
|
||||
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
this.state.orderBy = this.orderBy;
|
||||
|
||||
function areDifferent(a, b) {
|
||||
if (!a || !b) {
|
||||
return true;
|
||||
}
|
||||
var namesA = a.map( function(x){ return x.Name; } ).sort();
|
||||
var namesB = b.map( function(x){ return x.Name; } ).sort();
|
||||
return namesA.join(',') !== namesB.join(',');
|
||||
}
|
||||
|
||||
$scope.$watch(function() { return ctrl.state.filteredDataSet;},
|
||||
function(newValue, oldValue) {
|
||||
if (newValue && areDifferent(oldValue, newValue)) {
|
||||
ctrl.paginationAction(_.filter(newValue, {'TagsCount':0}));
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
]);
|
|
@ -3,18 +3,17 @@
|
|||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle">
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{
|
||||
$ctrl.titleText }}
|
||||
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<div class="actionBar" ng-if="$ctrl.advancedFeaturesAvailable">
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
|
||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
</div>
|
||||
<div class="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search..." auto-focus ng-model-options="{ debounce: 300 }">
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-model-options="{ debounce: 300 }" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover nowrap-cells">
|
||||
|
@ -32,25 +31,13 @@
|
|||
</a>
|
||||
</th>
|
||||
<th>Os/Architecture</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('ImageId')">
|
||||
Image ID
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ImageId' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'ImageId' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="$ctrl.changeOrderBy('Size')">
|
||||
Size
|
||||
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Size' && !$ctrl.state.reverseOrder"></i>
|
||||
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Size' && $ctrl.state.reverseOrder"></i>
|
||||
</a>
|
||||
</th>
|
||||
<th>Actions</th>
|
||||
<th>Image ID</th>
|
||||
<th>Compressed size</th>
|
||||
<th ng-if="$ctrl.advancedFeaturesAvailable">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
||||
<tr ng-hide="$ctrl.loading" dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
||||
ng-class="{active: item.Checked}">
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
|
@ -60,15 +47,16 @@
|
|||
{{ item.Name }}
|
||||
</td>
|
||||
<td>{{ item.Os }}/{{ item.Architecture }}</td>
|
||||
<td>{{ item.ImageId | truncate:40 }}</td>
|
||||
<td>{{ item.ImageId | trimshasum }}</td>
|
||||
<td>{{ item.Size | humansize }}</td>
|
||||
<td>
|
||||
<td ng-if="$ctrl.advancedFeaturesAvailable">
|
||||
<span ng-if="!item.Modified">
|
||||
<a class="interactive" ng-click="item.Modified = true; item.NewName = item.Name; $event.stopPropagation();">
|
||||
<i class="fa fa-tag" aria-hidden="true"></i> Retag
|
||||
</a>
|
||||
</span>
|
||||
<span ng-if="item.Modified">
|
||||
<portainer-tooltip position="bottom" message="Tag can only contain alphanumeric (a-zA-Z0-9) and special _ . - characters. Tag must not start with . - characters."></portainer-tooltip>
|
||||
<input class="input-sm" type="text" ng-model="item.NewName" on-enter-key="$ctrl.retagAction(item)"
|
||||
auto-focus ng-click="$event.stopPropagation();" />
|
||||
<a class="interactive" ng-click="item.Modified = false; $event.stopPropagation();"><i class="fa fa-times"></i></a>
|
||||
|
@ -76,11 +64,11 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!$ctrl.dataset">
|
||||
<td colspan="3" class="text-center text-muted">Loading...</td>
|
||||
<tr ng-if="$ctrl.loading">
|
||||
<td colspan="5" class="text-center text-muted">Loading...</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="3" class="text-center text-muted">No tag available.</td>
|
||||
<tr ng-if="!$ctrl.loading && $ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="5" class="text-center text-muted">No tag available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -96,7 +84,6 @@
|
|||
Items per page
|
||||
</span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('portainer.extensions.registrymanagement').component('registriesRepositoryTagsDatatable', {
|
||||
templateUrl: './registriesRepositoryTagsDatatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
controller: 'RegistryRepositoriesTagsDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
|
@ -9,6 +9,9 @@ angular.module('portainer.extensions.registrymanagement').component('registriesR
|
|||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
removeAction: '<',
|
||||
retagAction: '<'
|
||||
retagAction: '<',
|
||||
advancedFeaturesAvailable: '<',
|
||||
paginationAction: '<',
|
||||
loading: '<'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.controller('RegistryRepositoriesTagsDatatableController', ['$scope', '$controller',
|
||||
function($scope, $controller) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
var ctrl = this;
|
||||
this.state.orderBy = this.orderBy;
|
||||
|
||||
function diff(item) {
|
||||
return item.Name + item.ImageDigest;
|
||||
}
|
||||
|
||||
function areDifferent(a, b) {
|
||||
if (!a || !b) {
|
||||
return true;
|
||||
}
|
||||
var namesA = _.sortBy(_.map(a, diff));
|
||||
var namesB = _.sortBy(_.map(b, diff));
|
||||
return namesA.join(',') !== namesB.join(',');
|
||||
}
|
||||
|
||||
$scope.$watch(function() { return ctrl.state.filteredDataSet;},
|
||||
function(newValue, oldValue) {
|
||||
if (newValue && newValue.length && areDifferent(oldValue, newValue)) {
|
||||
ctrl.paginationAction(_.filter(newValue, {'ImageId': ''}));
|
||||
ctrl.resetSelectionState();
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
]);
|
Loading…
Add table
Add a link
Reference in a new issue