mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
281 lines
8.5 KiB
TypeScript
281 lines
8.5 KiB
TypeScript
import "./add-role-binding-dialog.scss"
|
|
|
|
import React from "react";
|
|
import { computed, observable } from "mobx";
|
|
import { observer } from "mobx-react";
|
|
import { t, Trans } from "@lingui/macro";
|
|
import { _i18n } from "../../i18n";
|
|
import { Dialog, DialogProps } from "../dialog";
|
|
import { Wizard, WizardStep } from "../wizard";
|
|
import { Select, SelectOption } from "../select";
|
|
import { SubTitle } from "../layout/sub-title";
|
|
import { IRoleBindingSubject, RoleBinding, ServiceAccount } from "../../api/endpoints";
|
|
import { Icon } from "../icon";
|
|
import { Input } from "../input";
|
|
import { NamespaceSelect } from "../+namespaces/namespace-select";
|
|
import { Checkbox } from "../checkbox";
|
|
import { KubeObject } from "../../api/kube-object";
|
|
import { Notifications } from "../notifications";
|
|
import { showDetails } from "../../navigation";
|
|
import { rolesStore } from "../+user-management-roles/roles.store";
|
|
import { namespaceStore } from "../+namespaces/namespace.store";
|
|
import { serviceAccountsStore } from "../+user-management-service-accounts/service-accounts.store";
|
|
import { roleBindingsStore } from "./role-bindings.store";
|
|
|
|
interface BindingSelectOption extends SelectOption {
|
|
value: string; // binding name
|
|
item?: ServiceAccount | any;
|
|
subject?: IRoleBindingSubject; // used for new user/group when users-management-api not available
|
|
}
|
|
|
|
interface Props extends Partial<DialogProps> {
|
|
}
|
|
|
|
@observer
|
|
export class AddRoleBindingDialog extends React.Component<Props> {
|
|
@observable static isOpen = false;
|
|
@observable static data: RoleBinding = null;
|
|
|
|
static open(roleBinding?: RoleBinding) {
|
|
AddRoleBindingDialog.isOpen = true;
|
|
AddRoleBindingDialog.data = roleBinding;
|
|
}
|
|
|
|
static close() {
|
|
AddRoleBindingDialog.isOpen = false;
|
|
}
|
|
|
|
get roleBinding(): RoleBinding {
|
|
return AddRoleBindingDialog.data;
|
|
}
|
|
|
|
@observable isLoading = false;
|
|
@observable selectedRoleId = "";
|
|
@observable useRoleForBindingName = true;
|
|
@observable bindingName = ""; // new role-binding name
|
|
@observable bindContext = ""; // empty value means "cluster-wide", otherwise bind to namespace
|
|
@observable selectedAccounts = observable.array<ServiceAccount>([], { deep: false });
|
|
|
|
@computed get isEditing() {
|
|
return !!this.roleBinding;
|
|
}
|
|
|
|
@computed get selectedRole() {
|
|
return rolesStore.items.find(role => role.getId() === this.selectedRoleId);
|
|
}
|
|
|
|
@computed get selectedBindings() {
|
|
return [
|
|
...this.selectedAccounts,
|
|
]
|
|
}
|
|
|
|
close = () => {
|
|
AddRoleBindingDialog.close();
|
|
}
|
|
|
|
async loadData() {
|
|
const stores = [
|
|
namespaceStore,
|
|
rolesStore,
|
|
serviceAccountsStore,
|
|
];
|
|
this.isLoading = true;
|
|
await Promise.all(stores.map(store => store.loadAll()));
|
|
this.isLoading = false;
|
|
}
|
|
|
|
onOpen = async () => {
|
|
await this.loadData();
|
|
|
|
if (this.roleBinding) {
|
|
const { name, kind } = this.roleBinding.roleRef;
|
|
const role = rolesStore.items.find(role => role.kind === kind && role.getName() === name);
|
|
if (role) {
|
|
this.selectedRoleId = role.getId();
|
|
this.bindContext = role.getNs() || "";
|
|
}
|
|
}
|
|
}
|
|
|
|
reset = () => {
|
|
this.selectedRoleId = "";
|
|
this.bindContext = "";
|
|
this.selectedAccounts.clear();
|
|
}
|
|
|
|
onBindContextChange = (namespace: string) => {
|
|
this.bindContext = namespace;
|
|
const roleContext = this.selectedRole && this.selectedRole.getNs() || "";
|
|
if (this.bindContext && this.bindContext !== roleContext) {
|
|
this.selectedRoleId = ""; // reset previously selected role for specific context
|
|
}
|
|
}
|
|
|
|
createBindings = async () => {
|
|
const { selectedRole, bindContext: namespace, selectedBindings, bindingName, useRoleForBindingName } = this;
|
|
|
|
const subjects = selectedBindings.map((item: KubeObject | IRoleBindingSubject) => {
|
|
if (item instanceof KubeObject) {
|
|
return {
|
|
name: item.getName(),
|
|
kind: item.kind,
|
|
namespace: item.getNs(),
|
|
}
|
|
}
|
|
return item;
|
|
});
|
|
|
|
try {
|
|
let roleBinding: RoleBinding;
|
|
if (this.isEditing) {
|
|
roleBinding = await roleBindingsStore.updateSubjects({
|
|
roleBinding: this.roleBinding,
|
|
addSubjects: subjects,
|
|
});
|
|
}
|
|
else {
|
|
const name = useRoleForBindingName ? selectedRole.getName() : bindingName;
|
|
roleBinding = await roleBindingsStore.create({ name, namespace }, {
|
|
subjects: subjects,
|
|
roleRef: {
|
|
name: selectedRole.getName(),
|
|
kind: selectedRole.kind,
|
|
}
|
|
});
|
|
}
|
|
showDetails(roleBinding.selfLink);
|
|
this.close();
|
|
} catch (err) {
|
|
Notifications.error(err);
|
|
}
|
|
};
|
|
|
|
@computed get roleOptions(): BindingSelectOption[] {
|
|
let roles = rolesStore.items.filter(KubeObject.isNonSystem);
|
|
if (this.bindContext) {
|
|
// show only cluster-roles or roles for selected context namespace
|
|
roles = roles.filter(role => !role.getNs() || role.getNs() === this.bindContext);
|
|
}
|
|
return roles.map(role => {
|
|
const name = role.getName();
|
|
const namespace = role.getNs();
|
|
return {
|
|
value: role.getId(),
|
|
label: name + (namespace ? ` (${namespace})` : "")
|
|
}
|
|
})
|
|
}
|
|
|
|
@computed get serviceAccountOptions(): BindingSelectOption[] {
|
|
return serviceAccountsStore.items.map(account => {
|
|
const name = account.getName();
|
|
const namespace = account.getNs();
|
|
return {
|
|
item: account,
|
|
value: name,
|
|
label: <><Icon small material="account_box"/> {name} ({namespace})</>
|
|
}
|
|
})
|
|
}
|
|
|
|
renderContents() {
|
|
const unwrapBindings = (options: BindingSelectOption[]) => options.map(option => option.item || option.subject);
|
|
return (
|
|
<>
|
|
<SubTitle title={<Trans>Context</Trans>}/>
|
|
<NamespaceSelect
|
|
showClusterOption
|
|
themeName="light"
|
|
isDisabled={this.isEditing}
|
|
value={this.bindContext}
|
|
onChange={({ value }) => this.onBindContextChange(value)}
|
|
/>
|
|
|
|
<SubTitle title={<Trans>Role</Trans>}/>
|
|
<Select
|
|
key={this.selectedRoleId}
|
|
themeName="light"
|
|
placeholder={_i18n._(t`Select role..`)}
|
|
isDisabled={this.isEditing}
|
|
options={this.roleOptions}
|
|
value={this.selectedRoleId}
|
|
onChange={({ value }) => this.selectedRoleId = value}
|
|
/>
|
|
{
|
|
!this.isEditing && (
|
|
<>
|
|
<Checkbox
|
|
theme="light"
|
|
label={<Trans>Use same name for RoleBinding</Trans>}
|
|
value={this.useRoleForBindingName}
|
|
onChange={v => this.useRoleForBindingName = v}
|
|
/>
|
|
{
|
|
!this.useRoleForBindingName && (
|
|
<Input
|
|
autoFocus
|
|
placeholder={_i18n._(t`Name`)}
|
|
disabled={this.isEditing}
|
|
value={this.bindingName}
|
|
onChange={v => this.bindingName = v}
|
|
/>
|
|
)
|
|
}
|
|
</>
|
|
)
|
|
}
|
|
|
|
<SubTitle title={<Trans>Binding targets</Trans>}/>
|
|
<Select
|
|
isMulti
|
|
themeName="light"
|
|
placeholder={_i18n._(t`Select service accounts`)}
|
|
autoConvertOptions={false}
|
|
options={this.serviceAccountOptions}
|
|
onChange={(opts: BindingSelectOption[]) => {
|
|
if (!opts) opts = [];
|
|
this.selectedAccounts.replace(unwrapBindings(opts));
|
|
}}
|
|
maxMenuHeight={200}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
render() {
|
|
const { ...dialogProps } = this.props;
|
|
const { isEditing, roleBinding, selectedRole, selectedBindings } = this;
|
|
const roleBindingName = roleBinding ? roleBinding.getName() : "";
|
|
const header = (
|
|
<h5>
|
|
{roleBindingName
|
|
? <Trans>Edit RoleBinding <span className="name">{roleBindingName}</span></Trans>
|
|
: <Trans>Add RoleBinding</Trans>
|
|
}
|
|
</h5>
|
|
);
|
|
const disableNext = this.isLoading || !selectedRole || !selectedBindings.length;
|
|
const nextLabel = isEditing ? <Trans>Update</Trans> : <Trans>Create</Trans>;
|
|
return (
|
|
<Dialog
|
|
{...dialogProps}
|
|
className="AddRoleBindingDialog"
|
|
isOpen={AddRoleBindingDialog.isOpen}
|
|
onOpen={this.onOpen}
|
|
close={this.close}
|
|
>
|
|
<Wizard header={header} done={this.close}>
|
|
<WizardStep
|
|
nextLabel={nextLabel} next={this.createBindings}
|
|
disabledNext={disableNext}
|
|
loading={this.isLoading}
|
|
>
|
|
{this.renderContents()}
|
|
</WizardStep>
|
|
</Wizard>
|
|
</Dialog>
|
|
)
|
|
}
|
|
}
|