import "./add-role-binding-dialog.scss"; import React from "react"; import { computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Dialog, DialogProps } from "../dialog"; import { Wizard, WizardStep } from "../wizard"; import { Select, SelectOption } from "../select"; import { SubTitle } from "../layout/sub-title"; import { IRoleBindingSubject, Role, 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 { 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"; import { showDetails } from "../kube-object"; import { KubeObjectStore } from "../../kube-object.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 { } @observer export class AddRoleBindingDialog extends React.Component { @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([], { 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: KubeObjectStore[] = [ namespaceStore, rolesStore, serviceAccountsStore, ]; this.isLoading = true; await Promise.all(stores.map(store => store.reloadAll())); 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, 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 as Role[]; 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: <> {name} ({namespace}) }; }); } renderContents() { const unwrapBindings = (options: BindingSelectOption[]) => options.map(option => option.item || option.subject); return ( <> this.onBindContextChange(value)} /> this.bindingName = v} /> ) } ) }