mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
refactor(namespace): migrate namespace access view to react [r8s-141] (#87)
This commit is contained in:
parent
8ed7cd80cb
commit
e9fc6d5598
62 changed files with 1018 additions and 610 deletions
|
@ -1,116 +0,0 @@
|
|||
<page-header
|
||||
ng-if="ctrl.state.viewReady"
|
||||
title="'Namespace access management'"
|
||||
breadcrumbs="[
|
||||
{ label:'Namespaces', link:'kubernetes.resourcePools' },
|
||||
{
|
||||
label:ctrl.pool.Namespace.Name,
|
||||
link: 'kubernetes.resourcePools.resourcePool',
|
||||
linkParams:{id: ctrl.pool.Namespace.Name}
|
||||
},
|
||||
'Access management'
|
||||
]"
|
||||
reload="true"
|
||||
>
|
||||
</page-header>
|
||||
|
||||
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
|
||||
|
||||
<div ng-if="ctrl.state.viewReady">
|
||||
<div class="row" ng-if="ctrl.pool">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="layers" title-text="Namespace"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
{{ ctrl.pool.Namespace.Name }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget ng-if="ctrl.availableUsersAndTeams">
|
||||
<rd-widget-header icon="user-x" title-text="Create access"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div
|
||||
ng-if="!ctrl.isRBACEnabled"
|
||||
class="small mx-[15px] mb-6 flex gap-1 rounded-lg border border-solid border-warning-5 bg-warning-2 p-4 text-warning-8 th-highcontrast:bg-yellow-11 th-highcontrast:text-white th-dark:bg-yellow-11 th-dark:text-white"
|
||||
>
|
||||
<div class="mt-0.5">
|
||||
<pr-icon icon="'alert-triangle'" feather="true" class-name="'text-warning-7 th-dark:text-white th-highcontrast:text-white'"></pr-icon>
|
||||
</div>
|
||||
<div>
|
||||
<p> Your cluster does not have Kubernetes role-based access control (RBAC) enabled. </p>
|
||||
<p> This means you can't use Portainer RBAC functionality to regulate access to environment resources based on user roles. </p>
|
||||
<p class="mb-0">
|
||||
To enable RBAC, start the <a
|
||||
class="th-highcontrast:text-blue-4 th-dark:text-blue-7"
|
||||
href="https://kubernetes.io/docs/concepts/overview/components/#kube-apiserver"
|
||||
target="_blank"
|
||||
>API server</a
|
||||
> with the <code class="bg-gray-4 box-decoration-clone th-highcontrast:bg-black th-dark:bg-black">--authorization-mode</code> flag set to a
|
||||
comma-separated list that includes <code class="bg-gray-4 th-highcontrast:bg-black th-dark:bg-black">RBAC</code>, for example:
|
||||
<code class="bg-gray-4 box-decoration-clone th-highcontrast:bg-black th-dark:bg-black">kube-apiserver --authorization-mode=Example1,RBAC,Example2</code>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="col-sm-12 small text-warning">
|
||||
<p class="vertical-center">
|
||||
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||
Adding user access will require the affected user(s) to logout and login for the changes to be taken into account.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<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>
|
||||
<namespace-access-users-selector
|
||||
ng-if="ctrl.availableUsersAndTeams.length > 0"
|
||||
input-id="users-selector"
|
||||
value="ctrl.formValues.multiselectOutput"
|
||||
options="ctrl.availableUsersAndTeams"
|
||||
on-change="(ctrl.onUsersAndTeamsChange)"
|
||||
></namespace-access-users-selector>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-sm vertical-center !ml-0"
|
||||
ng-disabled="ctrl.formValues.multiselectOutput.length === 0 || ctrl.actionInProgress"
|
||||
ng-click="ctrl.authorizeAccess()"
|
||||
button-spinner="ctrl.actionInProgress"
|
||||
>
|
||||
<span class="vertical-center" ng-hide="ctrl.state.actionInProgress"><pr-icon icon="'plus'" class="vertical-center"></pr-icon> Create access</span>
|
||||
<span ng-show="ctrl.state.actionInProgress">Creating access...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<namespace-access-datatable ng-if="ctrl.authorizedUsersAndTeams" dataset="ctrl.authorizedUsersAndTeams" on-remove="(ctrl.unauthorizeAccess)"> </namespace-access-datatable>
|
||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||
angular.module('portainer.kubernetes').component('kubernetesResourcePoolAccessView', {
|
||||
templateUrl: './resourcePoolAccess.html',
|
||||
controller: 'KubernetesResourcePoolAccessController',
|
||||
controllerAs: 'ctrl',
|
||||
bindings: {
|
||||
$transition$: '<',
|
||||
endpoint: '<',
|
||||
},
|
||||
});
|
|
@ -1,145 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import { KubernetesPortainerConfigMapConfigName, KubernetesPortainerConfigMapNamespace, KubernetesPortainerConfigMapAccessKey } from 'Kubernetes/models/config-map/models';
|
||||
import { UserAccessViewModel, TeamAccessViewModel } from 'Portainer/models/access';
|
||||
import KubernetesConfigMapHelper from 'Kubernetes/helpers/configMapHelper';
|
||||
import { getIsRBACEnabled } from '@/react/kubernetes/cluster/getIsRBACEnabled';
|
||||
|
||||
class KubernetesResourcePoolAccessController {
|
||||
/* @ngInject */
|
||||
constructor($async, $state, $scope, Notifications, KubernetesResourcePoolService, KubernetesConfigMapService, GroupService, AccessService, EndpointProvider) {
|
||||
this.$async = $async;
|
||||
this.$state = $state;
|
||||
this.$scope = $scope;
|
||||
this.EndpointProvider = EndpointProvider;
|
||||
this.Notifications = Notifications;
|
||||
this.KubernetesResourcePoolService = KubernetesResourcePoolService;
|
||||
this.KubernetesConfigMapService = KubernetesConfigMapService;
|
||||
|
||||
this.GroupService = GroupService;
|
||||
this.AccessService = AccessService;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
initAccessConfigMap(configMap) {
|
||||
configMap.Name = KubernetesPortainerConfigMapConfigName;
|
||||
configMap.Namespace = KubernetesPortainerConfigMapNamespace;
|
||||
configMap.Data[KubernetesPortainerConfigMapAccessKey] = {};
|
||||
return configMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
async onInit() {
|
||||
const endpoint = this.endpoint;
|
||||
this.state = {
|
||||
actionInProgress: false,
|
||||
viewReady: false,
|
||||
};
|
||||
|
||||
this.formValues = {
|
||||
multiselectOutput: [],
|
||||
};
|
||||
|
||||
// default to true if error is thrown
|
||||
this.isRBACEnabled = true;
|
||||
|
||||
try {
|
||||
const name = this.$transition$.params().id;
|
||||
let [pool, configMap] = await Promise.all([
|
||||
this.KubernetesResourcePoolService.get(name),
|
||||
this.KubernetesConfigMapService.getAccess(KubernetesPortainerConfigMapNamespace, KubernetesPortainerConfigMapConfigName),
|
||||
]);
|
||||
this.isRBACEnabled = await getIsRBACEnabled(this.EndpointProvider.endpointID());
|
||||
const group = await this.GroupService.group(endpoint.GroupId);
|
||||
const roles = [];
|
||||
const endpointAccesses = await this.AccessService.accesses(endpoint, group, roles);
|
||||
this.pool = pool;
|
||||
if (configMap.Id === 0) {
|
||||
configMap = this.initAccessConfigMap(configMap);
|
||||
}
|
||||
configMap = KubernetesConfigMapHelper.parseJSONData(configMap);
|
||||
|
||||
this.authorizedUsersAndTeams = [];
|
||||
this.accessConfigMap = configMap;
|
||||
const poolAccesses = configMap.Data[KubernetesPortainerConfigMapAccessKey][name];
|
||||
if (poolAccesses) {
|
||||
this.authorizedUsersAndTeams = _.filter(endpointAccesses.authorizedUsersAndTeams, (item) => {
|
||||
if (item instanceof UserAccessViewModel && poolAccesses.UserAccessPolicies) {
|
||||
return poolAccesses.UserAccessPolicies[item.Id] !== undefined;
|
||||
} else if (item instanceof TeamAccessViewModel && poolAccesses.TeamAccessPolicies) {
|
||||
return poolAccesses.TeamAccessPolicies[item.Id] !== undefined;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
this.availableUsersAndTeams = _.without(endpointAccesses.authorizedUsersAndTeams, ...this.authorizedUsersAndTeams);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to retrieve namespace information');
|
||||
} finally {
|
||||
this.state.viewReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
return this.$async(this.onInit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize access
|
||||
*/
|
||||
async authorizeAccessAsync() {
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
const newAccesses = _.concat(this.authorizedUsersAndTeams, this.formValues.multiselectOutput);
|
||||
const accessConfigMap = KubernetesConfigMapHelper.modifiyNamespaceAccesses(angular.copy(this.accessConfigMap), this.pool.Namespace.Name, newAccesses);
|
||||
await this.KubernetesConfigMapService.updateAccess(accessConfigMap);
|
||||
this.Notifications.success('Success', 'Access successfully created');
|
||||
this.$state.reload(this.$state.current);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to create accesses');
|
||||
}
|
||||
}
|
||||
|
||||
onUsersAndTeamsChange(value) {
|
||||
this.$scope.$evalAsync(() => {
|
||||
this.formValues.multiselectOutput = value;
|
||||
});
|
||||
}
|
||||
|
||||
authorizeAccess() {
|
||||
return this.$async(this.authorizeAccessAsync);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
async unauthorizeAccessAsync(selectedItems) {
|
||||
try {
|
||||
this.state.actionInProgress = true;
|
||||
const newAccesses = _.without(this.authorizedUsersAndTeams, ...selectedItems);
|
||||
const accessConfigMap = KubernetesConfigMapHelper.modifiyNamespaceAccesses(angular.copy(this.accessConfigMap), this.pool.Namespace.Name, newAccesses);
|
||||
await this.KubernetesConfigMapService.updateAccess(accessConfigMap);
|
||||
this.Notifications.success('Success', 'Access successfully removed');
|
||||
this.$state.reload(this.$state.current);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Unable to remove accesses');
|
||||
} finally {
|
||||
this.state.actionInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
unauthorizeAccess(selectedItems) {
|
||||
return this.$async(this.unauthorizeAccessAsync, selectedItems);
|
||||
}
|
||||
}
|
||||
|
||||
export default KubernetesResourcePoolAccessController;
|
||||
angular.module('portainer.kubernetes').controller('KubernetesResourcePoolAccessController', KubernetesResourcePoolAccessController);
|
Loading…
Add table
Add a link
Reference in a new issue