/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import "./view.scss"; import type { IObservableValue } from "mobx"; import { action, computed, makeObservable, observable } from "mobx"; import { observer } from "mobx-react"; import React from "react"; import type { ClusterRole, ServiceAccount } from "../../../../../common/k8s-api/endpoints"; import type { DialogProps } from "../../../dialog"; import { Dialog } from "../../../dialog"; import { EditableList } from "../../../editable-list"; import { Icon } from "../../../icon"; import { SubTitle } from "../../../layout/sub-title"; import { onMultiSelectFor, Select } from "../../../select"; import { Wizard, WizardStep } from "../../../wizard"; import { ObservableHashSet, nFircate } from "../../../../utils"; import { Input } from "../../../input"; import { TooltipPosition } from "../../../tooltip"; import type { Subject } from "../../../../../common/k8s-api/endpoints/types/subject"; import type { ClusterRoleBindingDialogState } from "./state.injectable"; import type { ClusterRoleStore } from "../../+cluster-roles/store"; import type { ServiceAccountStore } from "../../+service-accounts/store"; import { withInjectables } from "@ogre-tools/injectable-react"; import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable"; import editClusterRoleBindingNameStateInjectable from "./edit-name-state.injectable"; import serviceAccountStoreInjectable from "../../+service-accounts/store.injectable"; import clusterRoleBindingDialogStateInjectable from "./state.injectable"; import type { CloseClusterRoleBindingDialog } from "./close.injectable"; import type { OpenClusterRoleBindingDialog } from "./open.injectable"; import openClusterRoleBindingDialogInjectable from "./open.injectable"; import closeClusterRoleBindingDialogInjectable from "./close.injectable"; import type { ShowDetails } from "../../../kube-detail-params/show-details.injectable"; import type { ClusterRoleBindingStore } from "../store"; import clusterRoleBindingStoreInjectable from "../store.injectable"; import showDetailsInjectable from "../../../kube-detail-params/show-details.injectable"; import type { ShowCheckedErrorNotification } from "../../../notifications/show-checked-error.injectable"; import showCheckedErrorNotificationInjectable from "../../../notifications/show-checked-error.injectable"; export interface ClusterRoleBindingDialogProps extends Partial { } interface Dependencies { state: IObservableValue; editBindingNameState: IObservableValue; clusterRoleStore: ClusterRoleStore; serviceAccountStore: ServiceAccountStore; clusterRoleBindingStore: ClusterRoleBindingStore; closeClusterRoleBindingDialog: CloseClusterRoleBindingDialog; openClusterRoleBindingDialog: OpenClusterRoleBindingDialog; showDetails: ShowDetails; showCheckedErrorNotification: ShowCheckedErrorNotification; } @observer class NonInjectedClusterRoleBindingDialog extends React.Component { constructor(props: ClusterRoleBindingDialogProps & Dependencies) { super(props); makeObservable(this); } @computed get clusterRoleOptions() { return this.props.clusterRoleStore.items.map(clusterRole => ({ value: clusterRole, label: clusterRole.getName(), })); } @computed get serviceAccountOptions() { return this.props.serviceAccountStore.items.map(serviceAccount => ({ value: serviceAccount, label: `${serviceAccount.getName()} (${serviceAccount.getNs()})`, isSelected: this.selectedAccounts.has(serviceAccount), })); } get clusterRoleBinding() { return this.props.state.get().clusterRoleBinding; } get isEditing() { return !!this.clusterRoleBinding; } @observable selectedRoleRef: ClusterRole | undefined = undefined; selectedAccounts = new ObservableHashSet([], sa => sa.metadata.uid); selectedUsers = observable.set([]); selectedGroups = observable.set([]); @computed get selectedBindings(): Subject[] { const serviceAccounts = Array.from(this.selectedAccounts, sa => ({ name: sa.getName(), kind: "ServiceAccount" as const, namespace: sa.getNs(), })); const users = Array.from(this.selectedUsers, user => ({ name: user, kind: "User" as const, })); const groups = Array.from(this.selectedGroups, group => ({ name: group, kind: "Group" as const, })); return [ ...serviceAccounts, ...users, ...groups, ]; } onOpen = action(() => { const binding = this.clusterRoleBinding; if (!binding) { return this.reset(); } this.selectedRoleRef = this.props.clusterRoleStore .items .find(item => item.getName() === binding.roleRef.name); const [saSubjects, uSubjects, gSubjects] = nFircate(binding.getSubjects(), "kind", ["ServiceAccount", "User", "Group"]); const accountNames = new Set(saSubjects.map(acc => acc.name)); this.selectedAccounts.replace( this.props.serviceAccountStore.items .filter(sa => accountNames.has(sa.getName())), ); this.selectedUsers.replace(uSubjects.map(user => user.name)); this.selectedGroups.replace(gSubjects.map(group => group.name)); }); reset = action(() => { this.selectedRoleRef = undefined; this.selectedAccounts.clear(); this.selectedUsers.clear(); this.selectedGroups.clear(); }); createBindings = async () => { const { closeClusterRoleBindingDialog, clusterRoleBindingStore, editBindingNameState, showDetails, } = this.props; const { selectedRoleRef, selectedBindings, clusterRoleBinding } = this; if (!clusterRoleBinding || !selectedRoleRef) { return; } try { const { selfLink } = this.isEditing ? await clusterRoleBindingStore.updateSubjects(clusterRoleBinding, selectedBindings) : await clusterRoleBindingStore.create({ name: editBindingNameState.get() }, { subjects: selectedBindings, roleRef: { name: selectedRoleRef.getName(), kind: selectedRoleRef.kind, }, }); showDetails(selfLink); closeClusterRoleBindingDialog(); } catch (err) { this.props.showCheckedErrorNotification(err, `Unknown error occured while ${this.isEditing ? "editing the" : "creating a"} ClusterRoleBinding`); } }; renderContents() { return ( <> this.props.editBindingNameState.set(val)} /> Users this.selectedUsers.add(newUser)} items={Array.from(this.selectedUsers)} remove={({ oldItem }) => this.selectedUsers.delete(oldItem)} /> Groups this.selectedGroups.add(newGroup)} items={Array.from(this.selectedGroups)} remove={({ oldItem }) => this.selectedGroups.delete(oldItem)} /> Service Accounts