mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 08:19:40 +02:00
refactor(ui): replace ng selectors with react-select [EE-3608] (#7203)
Co-authored-by: LP B <xAt0mZ@users.noreply.github.com>
This commit is contained in:
parent
1e21961e6a
commit
ceaee4e175
66 changed files with 1188 additions and 625 deletions
|
@ -1,12 +1,20 @@
|
|||
export default class HelmTemplatesListController {
|
||||
/* @ngInject */
|
||||
constructor($async, DatatableService, HelmService, Notifications) {
|
||||
constructor($async, $scope, DatatableService, HelmService, Notifications) {
|
||||
this.$async = $async;
|
||||
this.$scope = $scope;
|
||||
this.DatatableService = DatatableService;
|
||||
this.HelmService = HelmService;
|
||||
this.Notifications = Notifications;
|
||||
|
||||
this.state = {
|
||||
textFilter: '',
|
||||
selectedCategory: '',
|
||||
categories: [],
|
||||
};
|
||||
|
||||
this.updateCategories = this.updateCategories.bind(this);
|
||||
this.onCategoryChange = this.onCategoryChange.bind(this);
|
||||
}
|
||||
|
||||
async updateCategories() {
|
||||
|
@ -16,18 +24,20 @@ export default class HelmTemplatesListController {
|
|||
.filter((a) => a) // filter out undefined/nulls
|
||||
.map((c) => c.category); // get annotation category
|
||||
const availableCategories = [...new Set(annotationCategories)].sort(); // unique and sort
|
||||
this.state.categories = availableCategories;
|
||||
this.state.categories = availableCategories.map((cat) => ({ label: cat, value: cat }));
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve helm charts categories');
|
||||
}
|
||||
}
|
||||
|
||||
onTextFilterChange() {
|
||||
this.DatatableService.setDataTableTextFilters(this.tableKey, this.state.textFilter);
|
||||
onCategoryChange(value) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.state.selectedCategory = value || '';
|
||||
});
|
||||
}
|
||||
|
||||
clearCategory() {
|
||||
this.state.selectedCategory = '';
|
||||
onTextFilterChange() {
|
||||
this.DatatableService.setDataTableTextFilters(this.tableKey, this.state.textFilter);
|
||||
}
|
||||
|
||||
$onChanges() {
|
||||
|
@ -38,12 +48,6 @@ export default class HelmTemplatesListController {
|
|||
|
||||
$onInit() {
|
||||
return this.$async(async () => {
|
||||
this.state = {
|
||||
textFilter: '',
|
||||
selectedCategory: '',
|
||||
categories: [],
|
||||
};
|
||||
|
||||
const textFilter = this.DatatableService.getDataTableTextFilters(this.tableKey);
|
||||
if (textFilter !== null) {
|
||||
this.state.textFilter = textFilter;
|
||||
|
|
|
@ -22,24 +22,21 @@
|
|||
ng-model-options="{ debounce: 300 }"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ui-select ng-model="$ctrl.state.selectedCategory" theme="bootstrap" append-to-body="true">
|
||||
<ui-select-match placeholder="Select a category">
|
||||
<a class="btn btn-xs btn-link pull-right vertical-center" ng-click="$ctrl.clearCategory()">
|
||||
<pr-icon icon="'x'" feather="true"></pr-icon>
|
||||
</a>
|
||||
<span class="vertical-center">{{ $select.selected }}</span>
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="category in ($ctrl.state.categories | filter: $select.search)">
|
||||
<span>{{ category }}</span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
<div class="w-1/5">
|
||||
<por-select
|
||||
placeholder="'Select a category'"
|
||||
value="$ctrl.state.selectedCategory"
|
||||
options="$ctrl.state.categories"
|
||||
on-change="($ctrl.onCategoryChange)"
|
||||
is-clearable="true"
|
||||
bind-to-body="true"
|
||||
></por-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="blocklist">
|
||||
<helm-templates-list-item
|
||||
ng-repeat="chart in $ctrl.charts | filter:$ctrl.state.textFilter | filter: $ctrl.state.selectedCategory "
|
||||
ng-repeat="chart in $ctrl.charts | filter:$ctrl.state.textFilter | filter: $ctrl.state.selectedCategory"
|
||||
model="chart"
|
||||
type-label="helm"
|
||||
on-select="($ctrl.selectAction)"
|
||||
|
|
|
@ -1,6 +1,53 @@
|
|||
import angular from 'angular';
|
||||
|
||||
export const componentsModule = angular.module(
|
||||
'portainer.kubernetes.react.components',
|
||||
[]
|
||||
).name;
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
import { NamespacesSelector } from '@/react/kubernetes/cluster/RegistryAccessView/NamespacesSelector';
|
||||
import { StorageAccessModeSelector } from '@/react/kubernetes/cluster/ConfigureView/StorageAccessModeSelector';
|
||||
import { NamespaceAccessUsersSelector } from '@/react/kubernetes/namespaces/AccessView/NamespaceAccessUsersSelector';
|
||||
import { CreateNamespaceRegistriesSelector } from '@/react/kubernetes/namespaces/CreateView/CreateNamespaceRegistriesSelector';
|
||||
|
||||
export const componentsModule = angular
|
||||
.module('portainer.kubernetes.react.components', [])
|
||||
.component(
|
||||
'namespacesSelector',
|
||||
r2a(NamespacesSelector, [
|
||||
'dataCy',
|
||||
'inputId',
|
||||
'name',
|
||||
'namespaces',
|
||||
'onChange',
|
||||
'placeholder',
|
||||
'value',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'storageAccessModeSelector',
|
||||
r2a(StorageAccessModeSelector, [
|
||||
'inputId',
|
||||
'onChange',
|
||||
'options',
|
||||
'value',
|
||||
'storageClassName',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'namespaceAccessUsersSelector',
|
||||
r2a(NamespaceAccessUsersSelector, [
|
||||
'inputId',
|
||||
'onChange',
|
||||
'options',
|
||||
'value',
|
||||
'dataCy',
|
||||
'placeholder',
|
||||
'name',
|
||||
])
|
||||
)
|
||||
.component(
|
||||
'createNamespaceRegistriesSelector',
|
||||
r2a(CreateNamespaceRegistriesSelector, [
|
||||
'inputId',
|
||||
'onChange',
|
||||
'options',
|
||||
'value',
|
||||
])
|
||||
).name;
|
||||
|
|
|
@ -2,8 +2,9 @@ import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
|||
|
||||
export default class KubernetesRegistryAccessController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, ModalService, EndpointService, Notifications, RegistryService, KubernetesResourcePoolService) {
|
||||
constructor($async, $scope, $state, ModalService, EndpointService, Notifications, RegistryService, KubernetesResourcePoolService) {
|
||||
this.$async = $async;
|
||||
this.$scope = $scope;
|
||||
this.$state = $state;
|
||||
this.ModalService = ModalService;
|
||||
this.Notifications = Notifications;
|
||||
|
@ -20,10 +21,11 @@ export default class KubernetesRegistryAccessController {
|
|||
this.savedResourcePools = [];
|
||||
|
||||
this.handleRemove = this.handleRemove.bind(this);
|
||||
this.onChangeResourcePools = this.onChangeResourcePools.bind(this);
|
||||
}
|
||||
|
||||
async submit() {
|
||||
return this.updateNamespaces([...this.savedResourcePools.map(({ value }) => value), ...this.selectedResourcePools.map((pool) => pool.name)]);
|
||||
return this.updateNamespaces([...this.savedResourcePools.map(({ value }) => value), ...this.selectedResourcePools]);
|
||||
}
|
||||
|
||||
handleRemove(namespaces) {
|
||||
|
@ -52,6 +54,12 @@ export default class KubernetesRegistryAccessController {
|
|||
});
|
||||
}
|
||||
|
||||
onChangeResourcePools(resourcePools) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.selectedResourcePools = resourcePools;
|
||||
});
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
|
|
|
@ -12,19 +12,14 @@
|
|||
<label class="col-sm-3 col-lg-2 control-label text-left" style="padding-top: 0"> Select namespaces </label>
|
||||
<div class="col-sm-9 col-lg-4" style="margin-bottom: 15px">
|
||||
<span class="small text-muted" ng-if="!$ctrl.resourcePools.length"> No namespaces available. </span>
|
||||
<span
|
||||
isteven-multi-select
|
||||
|
||||
<namespaces-selector
|
||||
ng-if="$ctrl.resourcePools.length"
|
||||
input-model="$ctrl.resourcePools"
|
||||
output-model="$ctrl.selectedResourcePools"
|
||||
button-label="name"
|
||||
item-label="name"
|
||||
tick-property="ticked"
|
||||
helper-elements="filter"
|
||||
search-property="name"
|
||||
translation="{nothingSelected: 'Select one or more namespaces', search: 'Search...'}"
|
||||
>
|
||||
</span>
|
||||
value="$ctrl.selectedResourcePools"
|
||||
namespaces="$ctrl.resourcePools"
|
||||
placeholder="'Select one or more namespaces'"
|
||||
on-change="($ctrl.onChangeResourcePools)"
|
||||
></namespaces-selector>
|
||||
</div>
|
||||
<div class="col-sm-12 small text-muted vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'" feather="true"></pr-icon>
|
||||
|
|
|
@ -299,19 +299,12 @@
|
|||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
isteven-multi-select
|
||||
input-model="class.availableAccessModes"
|
||||
output-model="class.AccessModes"
|
||||
button-label="Name"
|
||||
item-label="Description"
|
||||
tick-property="selected"
|
||||
directive-id="{{ class.Name }}"
|
||||
helper-elements=""
|
||||
translation="{nothingSelected: 'Not configured'}"
|
||||
data-cy="kubeSetup-storageAccessSelect{{ class.Name }}"
|
||||
>
|
||||
</span>
|
||||
<storage-access-mode-selector
|
||||
options="ctrl.availableAccessModes"
|
||||
value="class.AccessModes"
|
||||
on-change="(ctrl.onChangeStorageClassAccessMode)"
|
||||
storage-class-name="class.Name"
|
||||
></storage-access-mode-selector>
|
||||
</td>
|
||||
<td>
|
||||
<div style="margin: 5px">
|
||||
|
|
|
@ -45,6 +45,7 @@ class KubernetesConfigureController {
|
|||
this.limitedFeatureAutoWindow = FeatureId.HIDE_AUTO_UPDATE_WINDOW;
|
||||
this.onToggleAutoUpdate = this.onToggleAutoUpdate.bind(this);
|
||||
this.onChangeEnableResourceOverCommit = this.onChangeEnableResourceOverCommit.bind(this);
|
||||
this.onChangeStorageClassAccessMode = this.onChangeStorageClassAccessMode.bind(this);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
|
@ -263,6 +264,18 @@ class KubernetesConfigureController {
|
|||
});
|
||||
}
|
||||
|
||||
onChangeStorageClassAccessMode(storageClassName, accessModes) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
const storageClass = this.StorageClasses.find((item) => item.Name === storageClassName);
|
||||
|
||||
if (!storageClass) {
|
||||
throw new Error('Storage class not found');
|
||||
}
|
||||
|
||||
storageClass.AccessModes = accessModes;
|
||||
});
|
||||
}
|
||||
|
||||
/* #region ON INIT */
|
||||
async onInit() {
|
||||
this.state = {
|
||||
|
@ -288,18 +301,14 @@ class KubernetesConfigureController {
|
|||
};
|
||||
|
||||
try {
|
||||
this.availableAccessModes = new KubernetesStorageClassAccessPolicies();
|
||||
|
||||
[this.StorageClasses, this.endpoint] = await Promise.all([this.KubernetesStorageService.get(this.state.endpointId), this.EndpointService.endpoint(this.state.endpointId)]);
|
||||
_.forEach(this.StorageClasses, (item) => {
|
||||
item.availableAccessModes = new KubernetesStorageClassAccessPolicies();
|
||||
const storage = _.find(this.endpoint.Kubernetes.Configuration.StorageClasses, (sc) => sc.Name === item.Name);
|
||||
if (storage) {
|
||||
item.selected = true;
|
||||
_.forEach(storage.AccessModes, (access) => {
|
||||
const mode = _.find(item.availableAccessModes, { Name: access });
|
||||
if (mode) {
|
||||
mode.selected = true;
|
||||
}
|
||||
});
|
||||
item.AccessModes = storage.AccessModes.map((name) => this.availableAccessModes.find((accessMode) => accessMode.Name === name));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -52,25 +52,19 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left"> Select user(s) and/or team(s) </label>
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left" for="users-selector"> Select user(s) and/or team(s) </label>
|
||||
<div class="col-sm-9 col-lg-4">
|
||||
<span class="small text-muted" ng-if="ctrl.availableUsersAndTeams.length === 0">
|
||||
No user nor team access has been set on the environment. Head over to the
|
||||
<a ui-sref="portainer.endpoints">Environments view</a> to manage them.
|
||||
</span>
|
||||
<span
|
||||
isteven-multi-select
|
||||
<namespace-access-users-selector
|
||||
ng-if="ctrl.availableUsersAndTeams.length > 0"
|
||||
input-model="ctrl.availableUsersAndTeams"
|
||||
output-model="ctrl.formValues.multiselectOutput"
|
||||
button-label="icon Name"
|
||||
item-label="icon Name"
|
||||
tick-property="ticked"
|
||||
helper-elements="filter"
|
||||
search-property="Name"
|
||||
translation="{nothingSelected: 'Select one or more users and/or teams', search: 'Search...'}"
|
||||
>
|
||||
</span>
|
||||
input-id="users-selector"
|
||||
value="ctrl.formValues.multiselectOutput"
|
||||
options="ctrl.availableUsersAndTeams"
|
||||
on-change="(ctrl.onUsersAndTeamsChange)"
|
||||
></namespace-access-users-selector>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -80,7 +74,7 @@
|
|||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-sm !ml-0 vertical-center"
|
||||
ng-disabled="(ctrl.availableUsersAndTeams | filter:{ticked:true}).length === 0 || ctrl.actionInProgress"
|
||||
ng-disabled="ctrl.formValues.multiselectOutput.length === 0 || ctrl.actionInProgress"
|
||||
ng-click="ctrl.authorizeAccess()"
|
||||
button-spinner="ctrl.actionInProgress"
|
||||
>
|
||||
|
|
|
@ -6,9 +6,10 @@ import KubernetesConfigMapHelper from 'Kubernetes/helpers/configMapHelper';
|
|||
|
||||
class KubernetesResourcePoolAccessController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, Notifications, KubernetesResourcePoolService, KubernetesConfigMapService, GroupService, AccessService) {
|
||||
constructor($async, $state, $scope, Notifications, KubernetesResourcePoolService, KubernetesConfigMapService, GroupService, AccessService) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$scope = $scope;
|
||||
this.Notifications = Notifications;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesConfigMapService = KubernetesConfigMapService;
|
||||
|
@ -19,7 +20,7 @@ class KubernetesResourcePoolAccessController {
|
|||
this.onInit = this.onInit.bind(this);
|
||||
this.authorizeAccessAsync = this.authorizeAccessAsync.bind(this);
|
||||
this.unauthorizeAccessAsync = this.unauthorizeAccessAsync.bind(this);
|
||||
|
||||
this.onUsersAndTeamsChange = this.onUsersAndTeamsChange.bind(this);
|
||||
this.unauthorizeAccess = this.unauthorizeAccess.bind(this);
|
||||
}
|
||||
|
||||
|
@ -72,6 +73,7 @@ class KubernetesResourcePoolAccessController {
|
|||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
this.availableUsersAndTeams = _.without(endpointAccesses.authorizedUsersAndTeams, ...this.authorizedUsersAndTeams);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve namespace information');
|
||||
|
@ -100,6 +102,12 @@ class KubernetesResourcePoolAccessController {
|
|||
}
|
||||
}
|
||||
|
||||
onUsersAndTeamsChange(value) {
|
||||
this.$scope.$evalAsync(() => {
|
||||
this.formValues.multiselectOutput = value;
|
||||
});
|
||||
}
|
||||
|
||||
authorizeAccess() {
|
||||
return this.$async(this.authorizeAccessAsync);
|
||||
}
|
||||
|
|
|
@ -403,7 +403,7 @@
|
|||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left !pt-0"> Select registries </label>
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left !pt-0" for="registries-selector"> Select registries </label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<span class="small text-muted" ng-if="!$ctrl.registries.length && $ctrl.state.isAdmin">
|
||||
No registries available. Head over to the <a ui-sref="portainer.registries">registry view</a> to define a container registry.
|
||||
|
@ -411,20 +411,13 @@
|
|||
<span class="small text-muted" ng-if="!$ctrl.registries.length && !$ctrl.state.isAdmin">
|
||||
No registries available. Contact your administrator to create a container registry.
|
||||
</span>
|
||||
<span
|
||||
isteven-multi-select
|
||||
ng-if="$ctrl.registries.length"
|
||||
input-model="$ctrl.registries"
|
||||
output-model="$ctrl.formValues.Registries"
|
||||
button-label="Name"
|
||||
item-label="Name"
|
||||
tick-property="Checked"
|
||||
helper-elements="filter"
|
||||
search-property="Name"
|
||||
translation="{nothingSelected: 'Select one or more registries', search: 'Search...'}"
|
||||
data-cy="namespaceCreate-registrySelect"
|
||||
<create-namespace-registries-selector
|
||||
input-id="'registries-selector'"
|
||||
value="$ctrl.formValues.Registries"
|
||||
on-change="($ctrl.onRegistriesChange)"
|
||||
options="$ctrl.registries"
|
||||
>
|
||||
</span>
|
||||
</create-namespace-registries-selector>
|
||||
</div>
|
||||
</div>
|
||||
<!-- #endregion -->
|
||||
|
|
|
@ -39,6 +39,7 @@ class KubernetesCreateResourcePoolController {
|
|||
this.onToggleStorageQuota = this.onToggleStorageQuota.bind(this);
|
||||
this.onToggleLoadBalancerQuota = this.onToggleLoadBalancerQuota.bind(this);
|
||||
this.onToggleResourceQuota = this.onToggleResourceQuota.bind(this);
|
||||
this.onRegistriesChange = this.onRegistriesChange.bind(this);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
|
@ -92,6 +93,12 @@ class KubernetesCreateResourcePoolController {
|
|||
});
|
||||
}
|
||||
|
||||
onRegistriesChange(registries) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.formValues.Registries = registries;
|
||||
});
|
||||
}
|
||||
|
||||
addHostname(ingressClass) {
|
||||
ingressClass.Hosts.push(new KubernetesResourcePoolIngressClassHostFormValue());
|
||||
}
|
||||
|
|
|
@ -364,27 +364,21 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left !pt-0"> Select registries </label>
|
||||
<div class="col-sm-8 col-lg-9">
|
||||
<label class="col-sm-3 col-lg-2 control-label text-left !pt-0" for="registries-selector"> Select registries </label>
|
||||
<div class="col-sm-9 col-lg-4">
|
||||
<span class="small text-muted" ng-if="!ctrl.registries.length && ctrl.isAdmin">
|
||||
No registries available. Head over to the <a ui-sref="portainer.registries">registry view</a> to define a container registry.
|
||||
</span>
|
||||
<span class="small text-muted" ng-if="!ctrl.registries.length && !ctrl.isAdmin">
|
||||
No registries available. Contact your administrator to create a container registry.
|
||||
</span>
|
||||
<span
|
||||
isteven-multi-select
|
||||
ng-if="ctrl.registries.length"
|
||||
input-model="ctrl.registries"
|
||||
output-model="ctrl.formValues.Registries"
|
||||
button-label="Name"
|
||||
item-label="Name"
|
||||
tick-property="Checked"
|
||||
helper-elements="filter"
|
||||
search-property="Name"
|
||||
translation="{nothingSelected: 'Select one or more registries', search: 'Search...'}"
|
||||
<create-namespace-registries-selector
|
||||
input-id="'registries-selector'"
|
||||
value="ctrl.formValues.Registries"
|
||||
on-change="(ctrl.onRegistriesChange)"
|
||||
options="ctrl.registries"
|
||||
>
|
||||
</span>
|
||||
</create-namespace-registries-selector>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -73,6 +73,7 @@ class KubernetesResourcePoolController {
|
|||
this.getEvents = this.getEvents.bind(this);
|
||||
this.onToggleLoadBalancersQuota = this.onToggleLoadBalancersQuota.bind(this);
|
||||
this.onToggleStorageQuota = this.onToggleStorageQuota.bind(this);
|
||||
this.onRegistriesChange = this.onRegistriesChange.bind(this);
|
||||
}
|
||||
/* #endregion */
|
||||
|
||||
|
@ -101,6 +102,12 @@ class KubernetesResourcePoolController {
|
|||
}
|
||||
/* #endregion */
|
||||
|
||||
onRegistriesChange(registries) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.formValues.Registries = registries;
|
||||
});
|
||||
}
|
||||
|
||||
onToggleLoadBalancersQuota(checked) {
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.formValues.UseLoadBalancersQuota = checked;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue