1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/renderer/components/+user-management/+cluster-role-bindings/dialog/view.tsx
Sebastian Malton b86eb3d8db Remove all uses of legacy global Notification functions
Signed-off-by: Sebastian Malton <sebastian@malton.name>
2023-01-05 13:27:57 -05:00

315 lines
11 KiB
TypeScript

/**
* 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<DialogProps> {
}
interface Dependencies {
state: IObservableValue<ClusterRoleBindingDialogState>;
editBindingNameState: IObservableValue<string>;
clusterRoleStore: ClusterRoleStore;
serviceAccountStore: ServiceAccountStore;
clusterRoleBindingStore: ClusterRoleBindingStore;
closeClusterRoleBindingDialog: CloseClusterRoleBindingDialog;
openClusterRoleBindingDialog: OpenClusterRoleBindingDialog;
showDetails: ShowDetails;
showCheckedErrorNotification: ShowCheckedErrorNotification;
}
@observer
class NonInjectedClusterRoleBindingDialog extends React.Component<ClusterRoleBindingDialogProps & Dependencies> {
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<ServiceAccount>([], sa => sa.metadata.uid);
selectedUsers = observable.set<string>([]);
selectedGroups = observable.set<string>([]);
@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 (
<>
<SubTitle title="Cluster Role Reference" />
<Select
id="cluster-role-input"
themeName="light"
placeholder="Select cluster role ..."
isDisabled={this.isEditing}
options={this.clusterRoleOptions}
value={this.selectedRoleRef}
autoFocus={!this.isEditing}
formatOptionLabel={option => (
<>
<Icon
small
material="people"
tooltip={{
preferredPositions: TooltipPosition.LEFT,
children: option.value.kind,
}}
/>
{" "}
{option.value.getName()}
</>
)}
onChange={option => {
this.selectedRoleRef = option?.value;
const bindingName = this.props.editBindingNameState.get();
if (!this.selectedRoleRef || bindingName === this.selectedRoleRef.getName()) {
this.props.editBindingNameState.set(option?.value?.getName() ?? "");
}
}}
/>
<SubTitle title="Binding Name" />
<Input
placeholder="Name of ClusterRoleBinding ..."
disabled={this.isEditing}
value={this.props.editBindingNameState.get()}
onChange={val => this.props.editBindingNameState.set(val)}
/>
<SubTitle title="Binding targets" />
<b>Users</b>
<EditableList
placeholder="Bind to User Account ..."
add={(newUser) => this.selectedUsers.add(newUser)}
items={Array.from(this.selectedUsers)}
remove={({ oldItem }) => this.selectedUsers.delete(oldItem)}
/>
<b>Groups</b>
<EditableList
placeholder="Bind to User Group ..."
add={(newGroup) => this.selectedGroups.add(newGroup)}
items={Array.from(this.selectedGroups)}
remove={({ oldItem }) => this.selectedGroups.delete(oldItem)}
/>
<b>Service Accounts</b>
<Select
id="service-account-input"
isMulti
themeName="light"
placeholder="Select service accounts ..."
options={this.serviceAccountOptions}
formatOptionLabel={option => (
<>
<Icon small material="account_box" />
{` ${option.label}`}
</>
)}
onChange={onMultiSelectFor(this.selectedAccounts)}
maxMenuHeight={200}
/>
</>
);
}
render() {
const {
closeClusterRoleBindingDialog,
openClusterRoleBindingDialog,
clusterRoleStore,
editBindingNameState,
serviceAccountStore,
state,
...dialogProps
} = this.props;
const [action, nextLabel] = this.isEditing ? ["Edit", "Update"] : ["Add", "Create"];
const disableNext = !this.selectedRoleRef || !this.selectedBindings.length || !editBindingNameState.get();
return (
<Dialog
{...dialogProps}
className="AddClusterRoleBindingDialog"
isOpen={state.get().isOpen}
close={closeClusterRoleBindingDialog}
onClose={this.reset}
onOpen={this.onOpen}
>
<Wizard
header={(
<h5>
{`${action} ClusterRoleBinding`}
</h5>
)}
done={closeClusterRoleBindingDialog}
>
<WizardStep
nextLabel={nextLabel}
next={this.createBindings}
disabledNext={disableNext}
>
{this.renderContents()}
</WizardStep>
</Wizard>
</Dialog>
);
}
}
export const ClusterRoleBindingDialog = withInjectables<Dependencies, ClusterRoleBindingDialogProps>(NonInjectedClusterRoleBindingDialog, {
getProps: (di, props) => ({
...props,
clusterRoleStore: di.inject(clusterRoleStoreInjectable),
editBindingNameState: di.inject(editClusterRoleBindingNameStateInjectable),
serviceAccountStore: di.inject(serviceAccountStoreInjectable),
state: di.inject(clusterRoleBindingDialogStateInjectable),
clusterRoleBindingStore: di.inject(clusterRoleBindingStoreInjectable),
openClusterRoleBindingDialog: di.inject(openClusterRoleBindingDialogInjectable),
closeClusterRoleBindingDialog: di.inject(closeClusterRoleBindingDialogInjectable),
showDetails: di.inject(showDetailsInjectable),
showCheckedErrorNotification: di.inject(showCheckedErrorNotificationInjectable),
}),
});