mirror of https://github.com/portainer/portainer
refactor(registries): migrate gitlab projects table to react [EE-4709] (#10792)
parent
3f3db75d85
commit
960d18998f
|
@ -1,104 +0,0 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar vertical-center">
|
||||
<div class="toolBarTitle vertical-center">
|
||||
<pr-icon icon="$ctrl.titleIcon" class-name="'icon-blue'"></pr-icon>
|
||||
{{ $ctrl.titleText }}
|
||||
</div>
|
||||
<div class="searchBar vertical-center">
|
||||
<pr-icon icon="'search'" class="searchIcon"></pr-icon>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus />
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table-hover nowrap-cells table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<div class="vertical-center">
|
||||
<span class="md-checkbox vertical-center">
|
||||
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
|
||||
<label for="select_all"></label>
|
||||
</span>
|
||||
<table-column-header
|
||||
col-title="'Namespace'"
|
||||
can-sort="true"
|
||||
is-sorted="$ctrl.state.orderBy === 'Namespace'"
|
||||
is-sorted-desc="$ctrl.state.orderBy === 'Namespace' && $ctrl.state.reverseOrder"
|
||||
ng-click="$ctrl.changeOrderBy('Namespace')"
|
||||
></table-column-header>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<table-column-header
|
||||
col-title="'Name'"
|
||||
can-sort="true"
|
||||
is-sorted="$ctrl.state.orderBy === 'Name'"
|
||||
is-sorted-desc="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"
|
||||
ng-click="$ctrl.changeOrderBy('Name')"
|
||||
></table-column-header>
|
||||
</th>
|
||||
<th>
|
||||
<table-column-header
|
||||
col-title="'PathWithNamespace'"
|
||||
can-sort="true"
|
||||
is-sorted="$ctrl.state.orderBy === 'PathWithNamespace'"
|
||||
is-sorted-desc="$ctrl.state.orderBy === 'PathWithNamespace' && $ctrl.state.reverseOrder"
|
||||
ng-click="$ctrl.changeOrderBy('PathWithNamespace')"
|
||||
></table-column-header>
|
||||
</th>
|
||||
<th>
|
||||
<table-column-header col-title="'Description'" can-sort="false"></table-column-header>
|
||||
</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))"
|
||||
ng-class="{ active: item.Checked }"
|
||||
>
|
||||
<td>
|
||||
<span class="md-checkbox">
|
||||
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="$ctrl.disableSelection(item)" />
|
||||
<label for="select_{{ $index }}"></label>
|
||||
</span>
|
||||
{{ item.Namespace }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.Name }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.PathWithNamespace }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.Description }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
|
||||
<td colspan="3" class="text-muted text-center">No projects available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer" ng-if="$ctrl.dataset">
|
||||
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
|
||||
<div class="paginationControls">
|
||||
<form class="form-inline">
|
||||
<span class="limitSelector">
|
||||
<span style="margin-right: 5px"> Items per page </span>
|
||||
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()" data-cy="component-paginationSelect">
|
||||
<option value="0">All</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</span>
|
||||
<dir-pagination-controls max-size="5"></dir-pagination-controls>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||
angular.module('portainer.app').component('gitlabProjectsDatatable', {
|
||||
templateUrl: './gitlabProjectsDatatable.html',
|
||||
controller: 'GitlabProjectsDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
state: '=',
|
||||
},
|
||||
});
|
|
@ -1,50 +0,0 @@
|
|||
angular.module('portainer.app').controller('GitlabProjectsDatatableController', [
|
||||
'$scope',
|
||||
'$controller',
|
||||
'DatatableService',
|
||||
function ($scope, $controller, DatatableService) {
|
||||
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
|
||||
|
||||
this.disableSelection = function (item) {
|
||||
return !this.allowSelection(item);
|
||||
};
|
||||
|
||||
// based on RegistryGitlabProject model
|
||||
this.allowSelection = function (item) {
|
||||
return item.RegistryEnabled;
|
||||
};
|
||||
|
||||
this.$onInit = function () {
|
||||
this.setDefaults();
|
||||
this.prepareTableFromDataset();
|
||||
|
||||
this.state.orderBy = this.orderBy;
|
||||
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
|
||||
if (storedOrder !== null) {
|
||||
this.state.reverseOrder = storedOrder.reverse;
|
||||
this.state.orderBy = storedOrder.orderBy;
|
||||
}
|
||||
|
||||
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
|
||||
if (textFilter !== null) {
|
||||
this.state.textFilter = textFilter;
|
||||
this.onTextFilterChange();
|
||||
}
|
||||
|
||||
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
|
||||
if (storedFilters !== null) {
|
||||
this.filters = storedFilters;
|
||||
}
|
||||
if (this.filters && this.filters.state) {
|
||||
this.filters.state.open = false;
|
||||
}
|
||||
|
||||
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
|
||||
if (storedSettings !== null) {
|
||||
this.settings = storedSettings;
|
||||
this.settings.open = false;
|
||||
}
|
||||
this.onSettingsRepeaterChange();
|
||||
};
|
||||
},
|
||||
]);
|
|
@ -0,0 +1,12 @@
|
|||
/* @ngInject */
|
||||
export default function RegistryFormGitlab($scope) {
|
||||
this.selectedRegistries = [];
|
||||
|
||||
this.onChangeRegistries = onChangeRegistries.bind(this);
|
||||
|
||||
function onChangeRegistries(value) {
|
||||
$scope.$evalAsync(() => {
|
||||
this.selectedRegistries = value;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -130,24 +130,15 @@
|
|||
If you can't select a project, make sure that registry feature is activated on it.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<gitlab-projects-datatable
|
||||
title-text="GitLab projects"
|
||||
title-icon="list"
|
||||
dataset="$ctrl.projects"
|
||||
table-key="gitlab_projects"
|
||||
state="$ctrl.state.gitlab"
|
||||
order-by="Name"
|
||||
></gitlab-projects-datatable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<gitlab-project-selector dataset="$ctrl.projects" value="$ctrl.selectedRegistries" on-change="($ctrl.onChangeRegistries)"></gitlab-project-selector>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
ng-click="$ctrl.createRegistries()"
|
||||
ng-click="$ctrl.createRegistries($ctrl.selectedRegistries)"
|
||||
class="btn btn-primary btn-sm"
|
||||
ng-disabled="$ctrl.actionInProgress || !$ctrl.state.gitlab.selectedItemCount"
|
||||
ng-disabled="$ctrl.actionInProgress || !$ctrl.selectedRegistries.length"
|
||||
button-spinner="$ctrl.actionInProgress"
|
||||
analytics-on
|
||||
analytics-category="portainer"
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import controller from './registry-form-gitlab.controller';
|
||||
|
||||
angular.module('portainer.app').component('registryFormGitlab', {
|
||||
templateUrl: './registry-form-gitlab.html',
|
||||
controller,
|
||||
bindings: {
|
||||
model: '=',
|
||||
retrieveRegistries: '<',
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
export function RegistryGitlabProject(project) {
|
||||
this.Id = project.id;
|
||||
this.Description = project.description;
|
||||
this.Name = project.name;
|
||||
this.Namespace = project.namespace ? project.namespace.name : '';
|
||||
this.PathWithNamespace = project.path_with_namespace;
|
||||
this.RegistryEnabled = project.container_registry_enabled;
|
||||
}
|
|
@ -5,6 +5,7 @@ import { withReactQuery } from '@/react-tools/withReactQuery';
|
|||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
||||
import { RepositoriesDatatable } from '@/react/portainer/registries/repositories/ListView/RepositoriesDatatable';
|
||||
import { TagsDatatable } from '@/react/portainer/registries/repositories/ItemView/TagsDatatable/TagsDatatable';
|
||||
import { GitlabProjectTable } from '@/react/portainer/registries/CreateView/GitlabProjectsTable/GitlabProjectsTable';
|
||||
|
||||
export const registriesModule = angular
|
||||
.module('portainer.app.react.components.registries', [])
|
||||
|
@ -20,4 +21,8 @@ export const registriesModule = angular
|
|||
'onRemove',
|
||||
'onRetag',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'gitlabProjectSelector',
|
||||
r2a(GitlabProjectTable, ['dataset', 'onChange', 'value'])
|
||||
).name;
|
||||
|
|
|
@ -84,6 +84,10 @@ angular.module('portainer.app').factory('RegistryService', [
|
|||
return Registries.create(payload).$promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@/portainer/models/registry').RegistryCreateFormValues} model
|
||||
* @param {Array<import('@/react/portainer/registries/types/gitlabProject').RegistryGitlabProject>} projects
|
||||
*/
|
||||
function createGitlabRegistries(model, projects) {
|
||||
const promises = [];
|
||||
_.forEach(projects, (p) => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash-es';
|
||||
import { RegistryGitlabProject } from '../models/gitlabRegistry';
|
||||
import { RegistryGitlabProject } from '@/react/portainer/registries/types/gitlabProject';
|
||||
import { RegistryRepositoryGitlabViewModel } from '../models/registryRepository';
|
||||
|
||||
angular.module('portainer.app').factory('RegistryGitlabService', [
|
||||
|
|
|
@ -12,12 +12,6 @@ class CreateRegistryController {
|
|||
this.state = {
|
||||
actionInProgress: false,
|
||||
overrideConfiguration: false,
|
||||
gitlab: {
|
||||
get selectedItemCount() {
|
||||
return this.selectedItems.length || 0;
|
||||
},
|
||||
selectedItems: [],
|
||||
},
|
||||
originViewReference: 'portainer.registries',
|
||||
originalEndpointId: null,
|
||||
};
|
||||
|
@ -121,11 +115,11 @@ class CreateRegistryController {
|
|||
});
|
||||
}
|
||||
|
||||
createGitlabRegistries() {
|
||||
createGitlabRegistries(registries) {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
await this.RegistryService.createGitlabRegistries(this.model, this.state.gitlab.selectedItems);
|
||||
await this.RegistryService.createGitlabRegistries(this.model, registries);
|
||||
this.Notifications.success('Success', 'Registries successfully created');
|
||||
this.$state.go(this.state.originViewReference, { endpointId: this.state.originalEndpointId });
|
||||
} catch (err) {
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { ListIcon } from 'lucide-react';
|
||||
|
||||
import { createPersistedStore } from '@@/datatables/types';
|
||||
import { useTableState } from '@@/datatables/useTableState';
|
||||
import { Datatable } from '@@/datatables';
|
||||
import { withControlledSelected } from '@@/datatables/extend-options/withControlledSelected';
|
||||
|
||||
import { RegistryGitlabProject } from '../../types/gitlabProject';
|
||||
|
||||
const helper = createColumnHelper<RegistryGitlabProject>();
|
||||
|
||||
const columns = [
|
||||
helper.accessor('Namespace', {}),
|
||||
helper.accessor('Name', {}),
|
||||
helper.accessor('PathWithNamespace', {
|
||||
header: 'Path with namespace',
|
||||
}),
|
||||
helper.accessor('Description', {}),
|
||||
];
|
||||
|
||||
const tableKey = 'gitlab-projects';
|
||||
const store = createPersistedStore(tableKey, 'Name');
|
||||
|
||||
export function GitlabProjectTable({
|
||||
dataset,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
dataset: RegistryGitlabProject[];
|
||||
value: RegistryGitlabProject[];
|
||||
onChange: (value: RegistryGitlabProject[]) => void;
|
||||
}) {
|
||||
const tableState = useTableState(store, tableKey);
|
||||
|
||||
return (
|
||||
<Datatable
|
||||
columns={columns}
|
||||
dataset={dataset}
|
||||
settingsManager={tableState}
|
||||
emptyContentLabel="No projects available."
|
||||
title="Gitlab projects"
|
||||
titleIcon={ListIcon}
|
||||
extendTableOptions={withControlledSelected(
|
||||
(ids) => onChange(dataset.filter(({ Id }) => ids.includes(`${Id}`))),
|
||||
value.map(({ Id }) => `${Id}`)
|
||||
)}
|
||||
isRowSelectable={({ original: item }) => item.RegistryEnabled}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
interface GitlabProjectResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
namespace?: {
|
||||
name: string;
|
||||
};
|
||||
path_with_namespace: string;
|
||||
container_registry_enabled: boolean;
|
||||
}
|
||||
|
||||
export class RegistryGitlabProject {
|
||||
Id: number;
|
||||
|
||||
Description: string;
|
||||
|
||||
Name: string;
|
||||
|
||||
Namespace: string;
|
||||
|
||||
PathWithNamespace: string;
|
||||
|
||||
RegistryEnabled: boolean;
|
||||
|
||||
constructor(project: GitlabProjectResponse) {
|
||||
this.Id = project.id;
|
||||
this.Description = project.description;
|
||||
this.Name = project.name;
|
||||
this.Namespace = project.namespace ? project.namespace.name : '';
|
||||
this.PathWithNamespace = project.path_with_namespace;
|
||||
this.RegistryEnabled = project.container_registry_enabled;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue