mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Split UserManagement into seperate Cluster and Namespace Roles and RoleBindings (#2755)
This commit is contained in:
1 parent
1ed7892b26
commit
a1a1c240e9
69 files changed
+2896
-901
No files matched your search
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
.RoleBindingDetails {
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import "./details.scss";
|
||||
|
||||
import { reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import React from "react";
|
||||
|
||||
import { KubeEventDetails } from "../../+events/kube-event-details";
|
||||
import type { ClusterRoleBinding, ClusterRoleBindingSubject } from "../../../api/endpoints";
|
||||
import { kubeObjectDetailRegistry } from "../../../api/kube-object-detail-registry";
|
||||
import { autoBind, ObservableHashSet, prevDefault } from "../../../utils";
|
||||
import { AddRemoveButtons } from "../../add-remove-buttons";
|
||||
import { ConfirmDialog } from "../../confirm-dialog";
|
||||
import { DrawerTitle } from "../../drawer";
|
||||
import type { KubeObjectDetailsProps } from "../../kube-object";
|
||||
import { KubeObjectMeta } from "../../kube-object/kube-object-meta";
|
||||
import { Table, TableCell, TableHead, TableRow } from "../../table";
|
||||
import { ClusterRoleBindingDialog } from "./dialog";
|
||||
import { clusterRoleBindingsStore } from "./store";
|
||||
import { hashClusterRoleBindingSubject } from "./hashers";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<ClusterRoleBinding> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterRoleBindingDetails extends React.Component<Props> {
|
||||
selectedSubjects = new ObservableHashSet<ClusterRoleBindingSubject>([], hashClusterRoleBindingSubject);
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.props.object, () => {
|
||||
this.selectedSubjects.clear();
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
removeSelectedSubjects() {
|
||||
const { object: clusterRoleBinding } = this.props;
|
||||
const { selectedSubjects } = this;
|
||||
|
||||
ConfirmDialog.open({
|
||||
ok: () => clusterRoleBindingsStore.removeSubjects(clusterRoleBinding, selectedSubjects),
|
||||
labelOk: `Remove`,
|
||||
message: (
|
||||
<p>Remove selected bindings for <b>{clusterRoleBinding.getName()}</b>?</p>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedSubjects } = this;
|
||||
const { object: clusterRoleBinding } = this.props;
|
||||
|
||||
if (!clusterRoleBinding) {
|
||||
return null;
|
||||
}
|
||||
const { roleRef } = clusterRoleBinding;
|
||||
const subjects = clusterRoleBinding.getSubjects();
|
||||
|
||||
return (
|
||||
<div className="RoleBindingDetails">
|
||||
<KubeObjectMeta object={clusterRoleBinding} />
|
||||
|
||||
<DrawerTitle title="Reference" />
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableCell>Kind</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>API Group</TableCell>
|
||||
</TableHead>
|
||||
<TableRow>
|
||||
<TableCell>{roleRef.kind}</TableCell>
|
||||
<TableCell>{roleRef.name}</TableCell>
|
||||
<TableCell>{roleRef.apiGroup}</TableCell>
|
||||
</TableRow>
|
||||
</Table>
|
||||
|
||||
<DrawerTitle title="Bindings" />
|
||||
{subjects.length > 0 && (
|
||||
<Table selectable className="bindings box grow">
|
||||
<TableHead>
|
||||
<TableCell checkbox />
|
||||
<TableCell className="binding">Name</TableCell>
|
||||
<TableCell className="type">Type</TableCell>
|
||||
</TableHead>
|
||||
{
|
||||
subjects.map((subject, i) => {
|
||||
const { kind, name } = subject;
|
||||
const isSelected = selectedSubjects.has(subject);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={i}
|
||||
selected={isSelected}
|
||||
onClick={prevDefault(() => this.selectedSubjects.toggle(subject))}
|
||||
>
|
||||
<TableCell checkbox isChecked={isSelected} />
|
||||
<TableCell className="binding">{name}</TableCell>
|
||||
<TableCell className="type">{kind}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Table>
|
||||
)}
|
||||
|
||||
<AddRemoveButtons
|
||||
onAdd={() => ClusterRoleBindingDialog.open(clusterRoleBinding)}
|
||||
onRemove={selectedSubjects.size ? this.removeSelectedSubjects : null}
|
||||
addTooltip={`Add bindings to ${roleRef.name}`}
|
||||
removeTooltip={`Remove selected bindings from ${roleRef.name}`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
kubeObjectDetailRegistry.add({
|
||||
kind: "ClusterRoleBinding",
|
||||
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
||||
components: {
|
||||
Details: (props) => <ClusterRoleBindingDetails {...props} />
|
||||
}
|
||||
});
|
||||
kubeObjectDetailRegistry.add({
|
||||
kind: "ClusterRoleBinding",
|
||||
apiVersions: ["rbac.authorization.k8s.io/v1"],
|
||||
priority: 5,
|
||||
components: {
|
||||
Details: (props) => <KubeEventDetails {...props} />
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
.AddClusterRoleBindingDialog {
|
||||
.Select + .Select {
|
||||
margin-top: $margin /2;
|
||||
}
|
||||
.Checkbox {
|
||||
margin-top: $margin;
|
||||
}
|
||||
.name {
|
||||
color: #a0a0a0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import "./dialog.scss";
|
||||
|
||||
import { action, computed, makeObservable, observable, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import React from "react";
|
||||
|
||||
import { serviceAccountsStore } from "../+service-accounts/store";
|
||||
import { ClusterRole, ClusterRoleBinding, ClusterRoleBindingSubject, ServiceAccount } from "../../../api/endpoints";
|
||||
import { Dialog, DialogProps } from "../../dialog";
|
||||
import { EditableList } from "../../editable-list";
|
||||
import { Icon } from "../../icon";
|
||||
import { showDetails } from "../../kube-object";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Notifications } from "../../notifications";
|
||||
import { Select, SelectOption } from "../../select";
|
||||
import { Wizard, WizardStep } from "../../wizard";
|
||||
import { clusterRoleBindingsStore } from "./store";
|
||||
import { clusterRolesStore } from "../+cluster-roles/store";
|
||||
import { getRoleRefSelectOption, ServiceAccountOption } from "../select-options";
|
||||
import { ObservableHashSet, nFircate } from "../../../utils";
|
||||
import { Input } from "../../input";
|
||||
|
||||
interface Props extends Partial<DialogProps> {
|
||||
}
|
||||
|
||||
interface DialogState {
|
||||
isOpen: boolean;
|
||||
data?: ClusterRoleBinding;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterRoleBindingDialog extends React.Component<Props> {
|
||||
static state = observable.object<DialogState>({
|
||||
isOpen: false,
|
||||
});
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.isEditing, () => {
|
||||
this.bindingName = ClusterRoleBindingDialog.state.data?.getName();
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
static open(roleBinding?: ClusterRoleBinding) {
|
||||
ClusterRoleBindingDialog.state.isOpen = true;
|
||||
ClusterRoleBindingDialog.state.data = roleBinding;
|
||||
}
|
||||
|
||||
static close() {
|
||||
ClusterRoleBindingDialog.state.isOpen = false;
|
||||
}
|
||||
|
||||
get clusterRoleBinding(): ClusterRoleBinding {
|
||||
return ClusterRoleBindingDialog.state.data;
|
||||
}
|
||||
|
||||
@computed get isEditing() {
|
||||
return !!this.clusterRoleBinding;
|
||||
}
|
||||
|
||||
@observable selectedRoleRef: ClusterRole | undefined = undefined;
|
||||
@observable bindingName = "";
|
||||
selectedAccounts = new ObservableHashSet<ServiceAccount>([], sa => sa.metadata.uid);
|
||||
selectedUsers = observable.set<string>([]);
|
||||
selectedGroups = observable.set<string>([]);
|
||||
|
||||
@computed get selectedBindings(): ClusterRoleBindingSubject[] {
|
||||
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,
|
||||
];
|
||||
}
|
||||
|
||||
@computed get clusterRoleRefoptions(): SelectOption<ClusterRole>[] {
|
||||
return clusterRolesStore.items.map(getRoleRefSelectOption);
|
||||
}
|
||||
|
||||
@computed get serviceAccountOptions(): ServiceAccountOption[] {
|
||||
return serviceAccountsStore.items.map(account => {
|
||||
const name = account.getName();
|
||||
const namespace = account.getNs();
|
||||
|
||||
return {
|
||||
value: `${account.getName()}%${account.getNs()}`,
|
||||
account,
|
||||
label: <><Icon small material="account_box" /> {name} ({namespace})</>
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@computed get selectedServiceAccountOptions(): ServiceAccountOption[] {
|
||||
return this.serviceAccountOptions.filter(({ account }) => this.selectedAccounts.has(account));
|
||||
}
|
||||
|
||||
@action
|
||||
onOpen = () => {
|
||||
const binding = this.clusterRoleBinding;
|
||||
|
||||
if (!binding) {
|
||||
return this.reset();
|
||||
}
|
||||
|
||||
this.selectedRoleRef = clusterRolesStore
|
||||
.items
|
||||
.find(item => item.getName() === binding.roleRef.name);
|
||||
this.bindingName = this.clusterRoleBinding.getName();
|
||||
|
||||
const [saSubjects, uSubjects, gSubjects] = nFircate(binding.getSubjects(), "kind", ["ServiceAccount", "User", "Group"]);
|
||||
const accountNames = new Set(saSubjects.map(acc => acc.name));
|
||||
|
||||
this.selectedAccounts.replace(
|
||||
serviceAccountsStore.items
|
||||
.filter(sa => accountNames.has(sa.getName()))
|
||||
);
|
||||
this.selectedUsers.replace(uSubjects.map(user => user.name));
|
||||
this.selectedGroups.replace(gSubjects.map(group => group.name));
|
||||
};
|
||||
|
||||
@action
|
||||
reset = () => {
|
||||
this.selectedRoleRef = undefined;
|
||||
this.bindingName = "";
|
||||
this.selectedAccounts.clear();
|
||||
this.selectedUsers.clear();
|
||||
this.selectedGroups.clear();
|
||||
};
|
||||
|
||||
createBindings = async () => {
|
||||
const { selectedRoleRef, selectedBindings, bindingName } = this;
|
||||
|
||||
try {
|
||||
const { selfLink } = this.isEditing
|
||||
? await clusterRoleBindingsStore.updateSubjects(this.clusterRoleBinding, selectedBindings)
|
||||
: await clusterRoleBindingsStore.create({ name: bindingName }, {
|
||||
subjects: selectedBindings,
|
||||
roleRef: {
|
||||
name: selectedRoleRef.getName(),
|
||||
kind: selectedRoleRef.kind,
|
||||
}
|
||||
});
|
||||
|
||||
showDetails(selfLink);
|
||||
ClusterRoleBindingDialog.close();
|
||||
} catch (err) {
|
||||
Notifications.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
renderContents() {
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="Cluster Role Reference" />
|
||||
<Select
|
||||
themeName="light"
|
||||
placeholder="Select cluster role ..."
|
||||
isDisabled={this.isEditing}
|
||||
options={this.clusterRoleRefoptions}
|
||||
value={this.selectedRoleRef}
|
||||
onChange={({ value }: SelectOption<ClusterRole> ) => {
|
||||
if (!this.selectedRoleRef || this.bindingName === this.selectedRoleRef.getName()) {
|
||||
this.bindingName = value.getName();
|
||||
}
|
||||
|
||||
this.selectedRoleRef = value;
|
||||
}}
|
||||
/>
|
||||
|
||||
<SubTitle title="Binding Name" />
|
||||
<Input
|
||||
placeholder="Name of ClusterRoleBinding ..."
|
||||
disabled={this.isEditing}
|
||||
value={this.bindingName}
|
||||
onChange={val => this.bindingName = 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
|
||||
isMulti
|
||||
themeName="light"
|
||||
placeholder="Select service accounts ..."
|
||||
autoConvertOptions={false}
|
||||
options={this.serviceAccountOptions}
|
||||
value={this.selectedServiceAccountOptions}
|
||||
onChange={(selected: ServiceAccountOption[] | null) => {
|
||||
if (selected) {
|
||||
this.selectedAccounts.replace(selected.map(opt => opt.account));
|
||||
} else {
|
||||
this.selectedAccounts.clear();
|
||||
}
|
||||
}}
|
||||
maxMenuHeight={200}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { ...dialogProps } = this.props;
|
||||
const [action, nextLabel] = this.isEditing ? ["Edit", "Update"] : ["Add", "Create"];
|
||||
const disableNext = !this.selectedRoleRef || !this.selectedBindings.length || !this.bindingName;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...dialogProps}
|
||||
className="AddClusterRoleBindingDialog"
|
||||
isOpen={ClusterRoleBindingDialog.state.isOpen}
|
||||
close={ClusterRoleBindingDialog.close}
|
||||
onClose={this.reset}
|
||||
onOpen={this.onOpen}
|
||||
>
|
||||
<Wizard
|
||||
header={<h5>{action} ClusterRoleBinding</h5>}
|
||||
done={ClusterRoleBindingDialog.close}
|
||||
>
|
||||
<WizardStep
|
||||
nextLabel={nextLabel}
|
||||
next={this.createBindings}
|
||||
disabledNext={disableNext}
|
||||
>
|
||||
{this.renderContents()}
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { MD5 } from "crypto-js";
|
||||
import type { ClusterRoleBindingSubject } from "../../../api/endpoints";
|
||||
|
||||
export function hashClusterRoleBindingSubject(subject: ClusterRoleBindingSubject): string {
|
||||
return MD5(JSON.stringify([
|
||||
["kind", subject.kind],
|
||||
["name", subject.name],
|
||||
["apiGroup", subject.apiGroup],
|
||||
])).toString();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
export * from "./view";
|
||||
export * from "./details";
|
||||
export * from "./dialog";
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { apiManager } from "../../../api/api-manager";
|
||||
import { ClusterRoleBinding, clusterRoleBindingApi, ClusterRoleBindingSubject } from "../../../api/endpoints";
|
||||
import { KubeObjectStore } from "../../../kube-object.store";
|
||||
import { autoBind, HashSet } from "../../../utils";
|
||||
import { hashClusterRoleBindingSubject } from "./hashers";
|
||||
|
||||
export class ClusterRoleBindingsStore extends KubeObjectStore<ClusterRoleBinding> {
|
||||
api = clusterRoleBindingApi;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
protected sortItems(items: ClusterRoleBinding[]) {
|
||||
return super.sortItems(items, [
|
||||
clusterRoleBinding => clusterRoleBinding.kind,
|
||||
clusterRoleBinding => clusterRoleBinding.getName()
|
||||
]);
|
||||
}
|
||||
|
||||
async updateSubjects(clusterRoleBinding: ClusterRoleBinding, subjects: ClusterRoleBindingSubject[]) {
|
||||
return this.update(clusterRoleBinding, {
|
||||
roleRef: clusterRoleBinding.roleRef,
|
||||
subjects,
|
||||
});
|
||||
}
|
||||
|
||||
async removeSubjects(clusterRoleBinding: ClusterRoleBinding, subjectsToRemove: Iterable<ClusterRoleBindingSubject>) {
|
||||
const currentSubjects = new HashSet(clusterRoleBinding.getSubjects(), hashClusterRoleBindingSubject);
|
||||
|
||||
for (const subject of subjectsToRemove) {
|
||||
currentSubjects.delete(subject);
|
||||
}
|
||||
|
||||
return this.updateSubjects(clusterRoleBinding, currentSubjects.toJSON());
|
||||
}
|
||||
}
|
||||
|
||||
export const clusterRoleBindingsStore = new ClusterRoleBindingsStore();
|
||||
|
||||
apiManager.registerStore(clusterRoleBindingsStore);
|
||||
@@ -0,0 +1,11 @@
|
||||
.ClusterRoleBindings {
|
||||
.help-icon {
|
||||
margin-left: $margin / 2;
|
||||
}
|
||||
|
||||
.TableCell {
|
||||
&.warning {
|
||||
@include table-cell-warning;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import "./view.scss";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import type { ClusterRoleBinding } from "../../../api/endpoints";
|
||||
import { KubeObjectListLayout } from "../../kube-object";
|
||||
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import type { ClusterRoleBindingsRouteParams } from "../user-management.route";
|
||||
import { ClusterRoleBindingDialog } from "./dialog";
|
||||
import { clusterRoleBindingsStore } from "./store";
|
||||
import { clusterRolesStore } from "../+cluster-roles/store";
|
||||
import { serviceAccountsStore } from "../+service-accounts/store";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
namespace = "namespace",
|
||||
bindings = "bindings",
|
||||
age = "age",
|
||||
}
|
||||
|
||||
interface Props extends RouteComponentProps<ClusterRoleBindingsRouteParams> {
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterRoleBindings extends React.Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="access_cluster_role_bindings"
|
||||
className="ClusterRoleBindings"
|
||||
store={clusterRoleBindingsStore}
|
||||
dependentStores={[clusterRolesStore, serviceAccountsStore]}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: (binding: ClusterRoleBinding) => binding.getName(),
|
||||
[columnId.bindings]: (binding: ClusterRoleBinding) => binding.getSubjectNames(),
|
||||
[columnId.age]: (binding: ClusterRoleBinding) => binding.getTimeDiffFromNow(),
|
||||
}}
|
||||
searchFilters={[
|
||||
(binding: ClusterRoleBinding) => binding.getSearchFields(),
|
||||
(binding: ClusterRoleBinding) => binding.getSubjectNames(),
|
||||
]}
|
||||
renderHeaderTitle="Cluster Role Bindings"
|
||||
renderTableHeader={[
|
||||
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
||||
{ className: "warning", showWithColumn: columnId.name },
|
||||
{ title: "Bindings", className: "bindings", sortBy: columnId.bindings, id: columnId.bindings },
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
]}
|
||||
renderTableContents={(binding: ClusterRoleBinding) => [
|
||||
binding.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={binding} />,
|
||||
binding.getSubjectNames(),
|
||||
binding.getAge(),
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
onAdd: () => ClusterRoleBindingDialog.open(),
|
||||
addTooltip: "Create new ClusterRoleBinding",
|
||||
}}
|
||||
/>
|
||||
<ClusterRoleBindingDialog />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user