mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Turn on strict mode in tsconfig.json, some helpful lints, and required cleanup where strictness necessitates it (#5195)
* Turn on strict mode in tsconfig.json - Add route, clusterRoute, and payloadValidatedClusterRoute helper functions to improve types with backend routes - Turn on the following new lints: - react/jsx-first-prop-new-line - react/jsx-wrap-multilines - react/jsx-one-expression-per-line - react/jsx-max-props-per-line - react/jsx-indent - react/jsx-indent-props Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix build Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace KubeObject scope strings with enum Signed-off-by: Sebastian Malton <sebastian@malton.name> * Revert package.json version changes Signed-off-by: Sebastian Malton <sebastian@malton.name> * revert move hostedCluster(Id) Signed-off-by: Sebastian Malton <sebastian@malton.name> * change some type param names to be not single letters Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove copy-extension-themes Signed-off-by: Sebastian Malton <sebastian@malton.name> * add new make clean action Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix build to not use webpack for generating types Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix kube-object-menu.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix select.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix catalog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * revert move fileNameMigration to index Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix ref logic error Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix log-resource-selector.test.tsx tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix dock-store.test.ts test by overriding createStorage to not touch file system Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix cluster.test.ts tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix kube=api.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fixed hotbar-store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix kubeconfig-manager.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix cluster-role-bindings/__tests__/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix role-bindings/__tests__/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix pods.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix delete-cluster-dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix daemonset.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix replicaset.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix statefulsets/dialog/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix replicasets/scale-dialog/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix deployments.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix deployments/scale/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix cronjob.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix stateful-set.api.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix deployment.api.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix api-manager.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix statefulset.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix job.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix pods.store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix scroll-spy.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix hotbar-remove-command.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix catalog-entity-registry.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix welcome.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix verify-that-all-routes-have-route-component.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix pod-tolerations.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * better fix for previous 3 fixes, plus also select.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix kube-object-menu.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix app-paths.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix dock-tabs.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix isReactNode typing Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix sub-title.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix drawer.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix list-layout.tsx and header.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix error-boundary.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix upgrade-chart/store.ts and dock-tab.store.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix install-chart/store.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix edit-resource/store.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix create-resource/store.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix namespace-select.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix namespace-select-filter.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix crd-list.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix wrong types for extensions Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix circular dependency Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix circular dependency on catalogCategoryRegistry Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix api-kube Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix type errors, most <Select /> errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * fixing more type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * some more fixing type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * convert all KubeApis to injectable with legacy global backups Signed-off-by: Sebastian Malton <sebastian@malton.name> * factor out into a common file all the exports Signed-off-by: Sebastian Malton <sebastian@malton.name> * convert all KubeObjectStores to injectable with legacy global backups Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix lint Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove unused legacy KubeApi globals Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix bad previous commit Signed-off-by: Sebastian Malton <sebastian@malton.name> * more crash fixing Signed-off-by: Sebastian Malton <sebastian@malton.name> * try and fix behavioural tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix sidebar-and-tab-navigation-for-core.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix sidebar-and-tab-navigation-for-extensions.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-using-application-menu.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix catalog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make ThemeStore non-singleton and fix navigation-to-terminal-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * extensions.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix catalog-entity-registry.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-using-application-menu.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix log-resource-selector.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix dock-tabs.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix delete-cluster-dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-kubernetes-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-editor-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-proxy-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-using-application-menu.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-application-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix dock-store.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix select.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix role-bindings/__tests__/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix hotbar-remove-command.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix cluster-role-bindings/__tests__/dialog.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-extension-specific-preferences.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-telemetry-preferences.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix closing-preferences.test.tsx Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-editor-preferences.test.ts Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix navigation-to-proxy-preferences.test.ts - Fix other type errors too Signed-off-by: Sebastian Malton <sebastian@malton.name> * final tweaks Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add more tsconfig files, fix bug in <Catalog> - Make all of history, navigation injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all of kube-details-params/ and navigate/ to injectable - This fixes a runtime error that was encountered during testing Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix runtime errors on renderer - remove all static uses of `createPageParam` (and then removed the legacy global) - Made LensRendererExtension and LensMainExtension just used dependencies and not the getLegacyDi - Fixed circular dep in extension-loader Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move registerStore calls to after injectMany Signed-off-by: Sebastian Malton <sebastian@malton.name> * replace all the rest of the legacy uses of apiManager Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix stack overflow and cycles in DI Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix NamespaceSelectFilter not opening Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix WizardStep and AddNamespaceDialog Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix KubeApi's not being registered Signed-off-by: Sebastian Malton <sebastian@malton.name> * cleanup WindowManager Signed-off-by: Sebastian Malton <sebastian@malton.name> * Proper fix for Wizard, fix NamespaceStore.subscribe Signed-off-by: Sebastian Malton <sebastian@malton.name> * Rewrite withTooltip to be more type correct - Fixes mobx related "too many recursive actions" error - Change all the uses of withTooltips to be functional components Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add e2e test to cover kube api registration Signed-off-by: Sebastian Malton <sebastian@malton.name> * cleanup internal-commands Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove cast in <Animate> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix command-palette e2e test Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type error after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix test name Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix lint Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix code to help CodeQL scanner Signed-off-by: Sebastian Malton <sebastian@malton.name> * update intree extension lock files Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix build-extensions picking wrong @types/react Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix tests from rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix type error Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make KubeconfigSyncManager more injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix crash in test mode for Dialog Signed-off-by: Sebastian Malton <sebastian@malton.name> * make Select snapshots deterministic Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix new type error Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix kube-object.store.test.ts typing Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix merge build issues Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix snapshots after merge Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix lint after merge Signed-off-by: Sebastian Malton <sebastian@malton.name> * reexport BaseKubeJsonApiObjectMetadata Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix typo in terminalSpawningPool Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove duplicate license header Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix typo to waitUntilDefined Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove iter use from getLegacyGlobalDiForExtensionApi Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove complex createStorage override Signed-off-by: Sebastian Malton <sebastian@malton.name> * override logger with mocks only when needed for tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove specialized overrideStore flags for getDiForUnitTesting Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove unnecessary | undefined types from the exactOptionalFieldTypes experiment Signed-off-by: Sebastian Malton <sebastian@malton.name> * use more descriptive name for local test mocks Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove unnecessary addition to 'make clean' target Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove oddity of KubeObjectStore.getById(undefined) being allowed Signed-off-by: Sebastian Malton <sebastian@malton.name> * rename KubeObject.getDescriptor in favour of name without fundemental JS meaning Signed-off-by: Sebastian Malton <sebastian@malton.name> * Simplify legacyRegisterApi when working in behaviour unit tests - Don't emit within main environment as there should be no auto registering there Signed-off-by: Sebastian Malton <sebastian@malton.name> * change confusing variable name in ReactiveDuration Signed-off-by: Sebastian Malton <sebastian@malton.name> * make visitor pattern more explicit for Entity contextMenuOpen Signed-off-by: Sebastian Malton <sebastian@malton.name> * toggleDetails -> toggleKubeDetailsPane is more specific Signed-off-by: Sebastian Malton <sebastian@malton.name> * remove outdated comment Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix bug where LensExtension dependencies are not set Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests from the revert of react 18 Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix more tests from merge Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix typings with new is-compatible-extension tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * more type fixing Signed-off-by: Sebastian Malton <sebastian@malton.name> * Revert in-tree extension versions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve name of guarding injectable for stores and apis - New name better implies that it is just a guard state and does not do anything Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add helper for <Select>.isMulti for storing in a Set<Value> Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix is-compatible-extension.test.ts types Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
381d77c633
commit
dfcb7c3330
1062 files changed
+26512
-28689
No files matched your search
+23
-12
@@ -5,14 +5,14 @@
|
||||
|
||||
import React from "react";
|
||||
import { ClusterRoleBindingDialog } from "../dialog";
|
||||
import { clusterRolesStore } from "../../+cluster-roles/store";
|
||||
import { ClusterRole } from "../../../../../common/k8s-api/endpoints";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { getDiForUnitTesting } from "../../../../getDiForUnitTesting";
|
||||
import type { DiRender } from "../../../test-utils/renderFor";
|
||||
import { renderFor } from "../../../test-utils/renderFor";
|
||||
|
||||
jest.mock("../../+cluster-roles/store");
|
||||
import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../../stores-apis-can-be-created.injectable";
|
||||
import ipcRendererInjectable from "../../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
|
||||
describe("ClusterRoleBindingDialog tests", () => {
|
||||
let render: DiRender;
|
||||
@@ -20,19 +20,30 @@ describe("ClusterRoleBindingDialog tests", () => {
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
(clusterRolesStore as any).items = [new ClusterRole({
|
||||
apiVersion: "rbac.authorization.k8s.io/v1",
|
||||
kind: "ClusterRole",
|
||||
metadata: {
|
||||
name: "foobar",
|
||||
resourceVersion: "1",
|
||||
uid: "1",
|
||||
},
|
||||
})];
|
||||
const store = di.inject(clusterRoleStoreInjectable);
|
||||
|
||||
store.items.replace([
|
||||
new ClusterRole({
|
||||
apiVersion: "rbac.authorization.k8s.io/v1",
|
||||
kind: "ClusterRole",
|
||||
metadata: {
|
||||
name: "foobar",
|
||||
resourceVersion: "1",
|
||||
uid: "1",
|
||||
selfLink: "/apis/rbac.authorization.k8s.io/v1/clusterroles/foobar",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import React from "react";
|
||||
|
||||
import type { ClusterRoleBinding, ClusterRoleBindingSubject } from "../../../../common/k8s-api/endpoints";
|
||||
import type { ClusterRoleBinding } from "../../../../common/k8s-api/endpoints";
|
||||
import { autoBind, ObservableHashSet, prevDefault } from "../../../utils";
|
||||
import { AddRemoveButtons } from "../../add-remove-buttons";
|
||||
import { DrawerTitle } from "../../drawer";
|
||||
@@ -17,8 +17,8 @@ import type { KubeObjectDetailsProps } from "../../kube-object-details";
|
||||
import { KubeObjectMeta } from "../../kube-object-meta";
|
||||
import { Table, TableCell, TableHead, TableRow } from "../../table";
|
||||
import { ClusterRoleBindingDialog } from "./dialog";
|
||||
import { clusterRoleBindingsStore } from "./store";
|
||||
import { hashClusterRoleBindingSubject } from "./hashers";
|
||||
import { clusterRoleBindingStore } from "./legacy-store";
|
||||
import { hashSubject } from "../hashers";
|
||||
import type { OpenConfirmDialog } from "../../confirm-dialog/open.injectable";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import openConfirmDialogInjectable from "../../confirm-dialog/open.injectable";
|
||||
@@ -32,7 +32,7 @@ interface Dependencies {
|
||||
|
||||
@observer
|
||||
class NonInjectedClusterRoleBindingDetails extends React.Component<ClusterRoleBindingDetailsProps & Dependencies> {
|
||||
selectedSubjects = new ObservableHashSet<ClusterRoleBindingSubject>([], hashClusterRoleBindingSubject);
|
||||
selectedSubjects = new ObservableHashSet([], hashSubject);
|
||||
|
||||
constructor(props: ClusterRoleBindingDetailsProps & Dependencies) {
|
||||
super(props);
|
||||
@@ -52,10 +52,14 @@ class NonInjectedClusterRoleBindingDetails extends React.Component<ClusterRoleBi
|
||||
const { selectedSubjects } = this;
|
||||
|
||||
openConfirmDialog({
|
||||
ok: () => clusterRoleBindingsStore.removeSubjects(clusterRoleBinding, selectedSubjects),
|
||||
ok: () => clusterRoleBindingStore.removeSubjects(clusterRoleBinding, selectedSubjects),
|
||||
labelOk: `Remove`,
|
||||
message: (
|
||||
<p>Remove selected bindings for <b>{clusterRoleBinding.getName()}</b>?</p>
|
||||
<p>
|
||||
Remove selected bindings for
|
||||
<b>{clusterRoleBinding.getName()}</b>
|
||||
?
|
||||
</p>
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -121,7 +125,7 @@ class NonInjectedClusterRoleBindingDetails extends React.Component<ClusterRoleBi
|
||||
|
||||
<AddRemoveButtons
|
||||
onAdd={() => ClusterRoleBindingDialog.open(clusterRoleBinding)}
|
||||
onRemove={selectedSubjects.size ? this.removeSelectedSubjects : null}
|
||||
onRemove={selectedSubjects.size ? this.removeSelectedSubjects : undefined}
|
||||
addTooltip={`Add bindings to ${roleRef.name}`}
|
||||
removeTooltip={`Remove selected bindings from ${roleRef.name}`}
|
||||
/>
|
||||
|
||||
@@ -9,8 +9,8 @@ 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 type { ClusterRole, ClusterRoleBinding, ClusterRoleBindingSubject, ServiceAccount } from "../../../../common/k8s-api/endpoints";
|
||||
import { serviceAccountStore } from "../+service-accounts/legacy-store";
|
||||
import type { ClusterRole, ClusterRoleBinding, ServiceAccount } from "../../../../common/k8s-api/endpoints";
|
||||
import type { DialogProps } from "../../dialog";
|
||||
import { Dialog } from "../../dialog";
|
||||
import { EditableList } from "../../editable-list";
|
||||
@@ -18,14 +18,14 @@ import { Icon } from "../../icon";
|
||||
import { showDetails } from "../../kube-detail-params";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Notifications } from "../../notifications";
|
||||
import type { SelectOption } from "../../select";
|
||||
import { Select } from "../../select";
|
||||
import { onMultiSelectFor, Select } from "../../select";
|
||||
import { Wizard, WizardStep } from "../../wizard";
|
||||
import { clusterRoleBindingsStore } from "./store";
|
||||
import { clusterRolesStore } from "../+cluster-roles/store";
|
||||
import { clusterRoleBindingStore } from "./legacy-store";
|
||||
import { clusterRoleStore } from "../+cluster-roles/legacy-store";
|
||||
import { ObservableHashSet, nFircate } from "../../../utils";
|
||||
import { Input } from "../../input";
|
||||
import { TooltipPosition } from "../../tooltip";
|
||||
import type { Subject } from "../../../../common/k8s-api/endpoints/types/subject";
|
||||
|
||||
export interface ClusterRoleBindingDialogProps extends Partial<DialogProps> {
|
||||
}
|
||||
@@ -35,12 +35,12 @@ interface DialogState {
|
||||
data?: ClusterRoleBinding;
|
||||
}
|
||||
|
||||
const dialogState = observable.object<DialogState>({
|
||||
isOpen: false,
|
||||
});
|
||||
|
||||
@observer
|
||||
export class ClusterRoleBindingDialog extends React.Component<ClusterRoleBindingDialogProps> {
|
||||
static state = observable.object<DialogState>({
|
||||
isOpen: false,
|
||||
});
|
||||
|
||||
constructor(props: ClusterRoleBindingDialogProps) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
@@ -49,25 +49,40 @@ export class ClusterRoleBindingDialog extends React.Component<ClusterRoleBinding
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.isEditing, () => {
|
||||
this.bindingName = ClusterRoleBindingDialog.state.data?.getName();
|
||||
this.bindingName = dialogState.data?.getName() ?? "";
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
static open(roleBinding?: ClusterRoleBinding) {
|
||||
ClusterRoleBindingDialog.state.isOpen = true;
|
||||
ClusterRoleBindingDialog.state.data = roleBinding;
|
||||
dialogState.isOpen = true;
|
||||
dialogState.data = roleBinding;
|
||||
}
|
||||
|
||||
static close() {
|
||||
ClusterRoleBindingDialog.state.isOpen = false;
|
||||
dialogState.isOpen = false;
|
||||
}
|
||||
|
||||
get clusterRoleBinding(): ClusterRoleBinding {
|
||||
return ClusterRoleBindingDialog.state.data;
|
||||
@computed get clusterRoleOptions() {
|
||||
return clusterRoleStore.items.map(clusterRole => ({
|
||||
value: clusterRole,
|
||||
label: clusterRole.getName(),
|
||||
}));
|
||||
}
|
||||
|
||||
@computed get isEditing() {
|
||||
@computed get serviceAccountOptions() {
|
||||
return serviceAccountStore.items.map(serviceAccount => ({
|
||||
value: serviceAccount,
|
||||
label: `${serviceAccount.getName()} (${serviceAccount.getNs()})`,
|
||||
isSelected: this.selectedAccounts.has(serviceAccount),
|
||||
}));
|
||||
}
|
||||
|
||||
get clusterRoleBinding() {
|
||||
return dialogState.data;
|
||||
}
|
||||
|
||||
get isEditing() {
|
||||
return !!this.clusterRoleBinding;
|
||||
}
|
||||
|
||||
@@ -77,7 +92,7 @@ export class ClusterRoleBindingDialog extends React.Component<ClusterRoleBinding
|
||||
selectedUsers = observable.set<string>([]);
|
||||
selectedGroups = observable.set<string>([]);
|
||||
|
||||
@computed get selectedBindings(): ClusterRoleBindingSubject[] {
|
||||
@computed get selectedBindings(): Subject[] {
|
||||
const serviceAccounts = Array.from(this.selectedAccounts, sa => ({
|
||||
name: sa.getName(),
|
||||
kind: "ServiceAccount" as const,
|
||||
@@ -99,24 +114,6 @@ export class ClusterRoleBindingDialog extends React.Component<ClusterRoleBinding
|
||||
];
|
||||
}
|
||||
|
||||
@computed get clusterRoleRefoptions(): SelectOption<ClusterRole>[] {
|
||||
return clusterRolesStore.items.map(value => ({
|
||||
value,
|
||||
label: value.getName(),
|
||||
}));
|
||||
}
|
||||
|
||||
@computed get serviceAccountOptions(): SelectOption<ServiceAccount>[] {
|
||||
return serviceAccountsStore.items.map(account => ({
|
||||
value: account,
|
||||
label: `${account.getName()} (${account.getNs()})`,
|
||||
}));
|
||||
}
|
||||
|
||||
@computed get selectedServiceAccountOptions(): SelectOption<ServiceAccount>[] {
|
||||
return this.serviceAccountOptions.filter(({ value }) => this.selectedAccounts.has(value));
|
||||
}
|
||||
|
||||
onOpen = action(() => {
|
||||
const binding = this.clusterRoleBinding;
|
||||
|
||||
@@ -124,16 +121,16 @@ export class ClusterRoleBindingDialog extends React.Component<ClusterRoleBinding
|
||||
return this.reset();
|
||||
}
|
||||
|
||||
this.selectedRoleRef = clusterRolesStore
|
||||
this.selectedRoleRef = clusterRoleStore
|
||||
.items
|
||||
.find(item => item.getName() === binding.roleRef.name);
|
||||
this.bindingName = this.clusterRoleBinding.getName();
|
||||
this.bindingName = binding.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
|
||||
serviceAccountStore.items
|
||||
.filter(sa => accountNames.has(sa.getName())),
|
||||
);
|
||||
this.selectedUsers.replace(uSubjects.map(user => user.name));
|
||||
@@ -149,12 +146,16 @@ export class ClusterRoleBindingDialog extends React.Component<ClusterRoleBinding
|
||||
});
|
||||
|
||||
createBindings = async () => {
|
||||
const { selectedRoleRef, selectedBindings, bindingName } = this;
|
||||
const { selectedRoleRef, selectedBindings, bindingName, clusterRoleBinding } = this;
|
||||
|
||||
if (!clusterRoleBinding || !selectedRoleRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { selfLink } = this.isEditing
|
||||
? await clusterRoleBindingsStore.updateSubjects(this.clusterRoleBinding, selectedBindings)
|
||||
: await clusterRoleBindingsStore.create({ name: bindingName }, {
|
||||
? await clusterRoleBindingStore.updateSubjects(clusterRoleBinding, selectedBindings)
|
||||
: await clusterRoleBindingStore.create({ name: bindingName }, {
|
||||
subjects: selectedBindings,
|
||||
roleRef: {
|
||||
name: selectedRoleRef.getName(),
|
||||
@@ -165,7 +166,7 @@ export class ClusterRoleBindingDialog extends React.Component<ClusterRoleBinding
|
||||
showDetails(selfLink);
|
||||
ClusterRoleBindingDialog.close();
|
||||
} catch (err) {
|
||||
Notifications.error(err);
|
||||
Notifications.checkedError(err, `Unknown error occured while ${this.isEditing ? "editing the" : "creating a"} ClusterRoleBinding`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -178,29 +179,29 @@ export class ClusterRoleBindingDialog extends React.Component<ClusterRoleBinding
|
||||
themeName="light"
|
||||
placeholder="Select cluster role ..."
|
||||
isDisabled={this.isEditing}
|
||||
options={this.clusterRoleRefoptions}
|
||||
options={this.clusterRoleOptions}
|
||||
value={this.selectedRoleRef}
|
||||
autoFocus={!this.isEditing}
|
||||
formatOptionLabel={({ value }: SelectOption<ClusterRole>) => (
|
||||
formatOptionLabel={option => (
|
||||
<>
|
||||
<Icon
|
||||
small
|
||||
material={value.kind === "Role" ? "person" : "people"}
|
||||
material="people"
|
||||
tooltip={{
|
||||
preferredPositions: TooltipPosition.LEFT,
|
||||
children: value.kind,
|
||||
children: option.value.kind,
|
||||
}}
|
||||
/>
|
||||
{" "}
|
||||
{value.getName()}
|
||||
{option.value.getName()}
|
||||
</>
|
||||
)}
|
||||
onChange={({ value }: SelectOption<ClusterRole> ) => {
|
||||
if (!this.selectedRoleRef || this.bindingName === this.selectedRoleRef.getName()) {
|
||||
this.bindingName = value.getName();
|
||||
}
|
||||
onChange={option => {
|
||||
this.selectedRoleRef = option?.value;
|
||||
|
||||
this.selectedRoleRef = value;
|
||||
if (!this.selectedRoleRef || this.bindingName === this.selectedRoleRef.getName()) {
|
||||
this.bindingName = option?.value?.getName() ?? "";
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -236,19 +237,14 @@ export class ClusterRoleBindingDialog extends React.Component<ClusterRoleBinding
|
||||
isMulti
|
||||
themeName="light"
|
||||
placeholder="Select service accounts ..."
|
||||
autoConvertOptions={false}
|
||||
options={this.serviceAccountOptions}
|
||||
value={this.selectedServiceAccountOptions}
|
||||
formatOptionLabel={({ value }: SelectOption<ServiceAccount>) => (
|
||||
<><Icon small material="account_box" /> {value.getName()} ({value.getNs()})</>
|
||||
formatOptionLabel={option => (
|
||||
<>
|
||||
<Icon small material="account_box" />
|
||||
{` ${option.label}`}
|
||||
</>
|
||||
)}
|
||||
onChange={(selected: SelectOption<ServiceAccount>[] | null) => {
|
||||
if (selected) {
|
||||
this.selectedAccounts.replace(selected.map(opt => opt.value));
|
||||
} else {
|
||||
this.selectedAccounts.clear();
|
||||
}
|
||||
}}
|
||||
onChange={onMultiSelectFor(this.selectedAccounts)}
|
||||
maxMenuHeight={200}
|
||||
/>
|
||||
</>
|
||||
@@ -264,13 +260,17 @@ export class ClusterRoleBindingDialog extends React.Component<ClusterRoleBinding
|
||||
<Dialog
|
||||
{...dialogProps}
|
||||
className="AddClusterRoleBindingDialog"
|
||||
isOpen={ClusterRoleBindingDialog.state.isOpen}
|
||||
isOpen={dialogState.isOpen}
|
||||
close={ClusterRoleBindingDialog.close}
|
||||
onClose={this.reset}
|
||||
onOpen={this.onOpen}
|
||||
>
|
||||
<Wizard
|
||||
header={<h5>{action} ClusterRoleBinding</h5>}
|
||||
header={(
|
||||
<h5>
|
||||
{`${action} ClusterRoleBinding`}
|
||||
</h5>
|
||||
)}
|
||||
done={ClusterRoleBindingDialog.close}
|
||||
>
|
||||
<WizardStep
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { MD5 } from "crypto-js";
|
||||
import type { ClusterRoleBindingSubject } from "../../../../common/k8s-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,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { asLegacyGlobalForExtensionApi } from "../../../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import clusterRoleBindingStoreInjectable from "./store.injectable";
|
||||
|
||||
/**
|
||||
* @deprecated use `di.inject(clusterRoleBindingStoreInjectable)` instead
|
||||
*/
|
||||
export const clusterRoleBindingStore = asLegacyGlobalForExtensionApi(clusterRoleBindingStoreInjectable);
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
import { storesAndApisCanBeCreatedInjectionToken } from "../../../../common/k8s-api/stores-apis-can-be-created.token";
|
||||
import clusterRoleBindingApiInjectable from "../../../../common/k8s-api/endpoints/cluster-role-binding.api.injectable";
|
||||
import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable";
|
||||
import { ClusterRoleBindingStore } from "./store";
|
||||
|
||||
const clusterRoleBindingStoreInjectable = getInjectable({
|
||||
id: "cluster-role-binding-store",
|
||||
instantiate: (di) => {
|
||||
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "clusterRoleBindingStore is only accessible in certain environments");
|
||||
|
||||
const api = di.inject(clusterRoleBindingApiInjectable);
|
||||
|
||||
return new ClusterRoleBindingStore(api);
|
||||
},
|
||||
injectionToken: kubeObjectStoreInjectionToken,
|
||||
});
|
||||
|
||||
export default clusterRoleBindingStoreInjectable;
|
||||
@@ -3,21 +3,13 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { apiManager } from "../../../../common/k8s-api/api-manager";
|
||||
import type { ClusterRoleBinding, ClusterRoleBindingSubject } from "../../../../common/k8s-api/endpoints";
|
||||
import { clusterRoleBindingApi } from "../../../../common/k8s-api/endpoints";
|
||||
import type { ClusterRoleBinding, ClusterRoleBindingApi, ClusterRoleBindingData } from "../../../../common/k8s-api/endpoints";
|
||||
import type { Subject } from "../../../../common/k8s-api/endpoints/types/subject";
|
||||
import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
|
||||
import { autoBind, HashSet } from "../../../utils";
|
||||
import { hashClusterRoleBindingSubject } from "./hashers";
|
||||
|
||||
export class ClusterRoleBindingsStore extends KubeObjectStore<ClusterRoleBinding> {
|
||||
api = clusterRoleBindingApi;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
autoBind(this);
|
||||
}
|
||||
import { HashSet } from "../../../utils";
|
||||
import { hashSubject } from "../hashers";
|
||||
|
||||
export class ClusterRoleBindingStore extends KubeObjectStore<ClusterRoleBinding, ClusterRoleBindingApi, ClusterRoleBindingData> {
|
||||
protected sortItems(items: ClusterRoleBinding[]) {
|
||||
return super.sortItems(items, [
|
||||
clusterRoleBinding => clusterRoleBinding.kind,
|
||||
@@ -25,15 +17,15 @@ export class ClusterRoleBindingsStore extends KubeObjectStore<ClusterRoleBinding
|
||||
]);
|
||||
}
|
||||
|
||||
async updateSubjects(clusterRoleBinding: ClusterRoleBinding, subjects: ClusterRoleBindingSubject[]) {
|
||||
async updateSubjects(clusterRoleBinding: ClusterRoleBinding, subjects: Subject[]) {
|
||||
return this.update(clusterRoleBinding, {
|
||||
roleRef: clusterRoleBinding.roleRef,
|
||||
subjects,
|
||||
});
|
||||
}
|
||||
|
||||
async removeSubjects(clusterRoleBinding: ClusterRoleBinding, subjectsToRemove: Iterable<ClusterRoleBindingSubject>) {
|
||||
const currentSubjects = new HashSet(clusterRoleBinding.getSubjects(), hashClusterRoleBindingSubject);
|
||||
async removeSubjects(clusterRoleBinding: ClusterRoleBinding, subjectsToRemove: Iterable<Subject>) {
|
||||
const currentSubjects = new HashSet(clusterRoleBinding.getSubjects(), hashSubject);
|
||||
|
||||
for (const subject of subjectsToRemove) {
|
||||
currentSubjects.delete(subject);
|
||||
@@ -43,6 +35,3 @@ export class ClusterRoleBindingsStore extends KubeObjectStore<ClusterRoleBinding
|
||||
}
|
||||
}
|
||||
|
||||
export const clusterRoleBindingsStore = new ClusterRoleBindingsStore();
|
||||
|
||||
apiManager.registerStore(clusterRoleBindingsStore);
|
||||
@@ -10,9 +10,9 @@ import React from "react";
|
||||
import { KubeObjectListLayout } from "../../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import { ClusterRoleBindingDialog } from "./dialog";
|
||||
import { clusterRoleBindingsStore } from "./store";
|
||||
import { clusterRolesStore } from "../+cluster-roles/store";
|
||||
import { serviceAccountsStore } from "../+service-accounts/store";
|
||||
import { clusterRoleBindingStore } from "./legacy-store";
|
||||
import { clusterRoleStore } from "../+cluster-roles/legacy-store";
|
||||
import { serviceAccountStore } from "../+service-accounts/legacy-store";
|
||||
import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
@@ -32,8 +32,8 @@ export class ClusterRoleBindings extends React.Component {
|
||||
isConfigurable
|
||||
tableId="access_cluster_role_bindings"
|
||||
className="ClusterRoleBindings"
|
||||
store={clusterRoleBindingsStore}
|
||||
dependentStores={[clusterRolesStore, serviceAccountsStore]}
|
||||
store={clusterRoleBindingStore}
|
||||
dependentStores={[clusterRoleStore, serviceAccountStore]}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: binding => binding.getName(),
|
||||
[columnId.bindings]: binding => binding.getSubjectNames(),
|
||||
|
||||
@@ -15,7 +15,7 @@ import { showDetails } from "../../kube-detail-params";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Notifications } from "../../notifications";
|
||||
import { Wizard, WizardStep } from "../../wizard";
|
||||
import { clusterRolesStore } from "./store";
|
||||
import { clusterRoleStore } from "./legacy-store";
|
||||
|
||||
export interface AddClusterRoleDialogProps extends Partial<DialogProps> {
|
||||
}
|
||||
@@ -45,13 +45,13 @@ export class AddClusterRoleDialog extends React.Component<AddClusterRoleDialogPr
|
||||
|
||||
createRole = async () => {
|
||||
try {
|
||||
const role = await clusterRolesStore.create({ name: this.clusterRoleName });
|
||||
const role = await clusterRoleStore.create({ name: this.clusterRoleName });
|
||||
|
||||
showDetails(role.selfLink);
|
||||
this.reset();
|
||||
AddClusterRoleDialog.close();
|
||||
} catch (err) {
|
||||
Notifications.error(err.toString());
|
||||
} catch (error) {
|
||||
Notifications.checkedError(error, "Unknown error occured while creating the role");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,7 +76,8 @@ export class AddClusterRoleDialog extends React.Component<AddClusterRoleDialogPr
|
||||
>
|
||||
<SubTitle title="ClusterRole Name" />
|
||||
<Input
|
||||
required autoFocus
|
||||
required
|
||||
autoFocus
|
||||
placeholder="Name"
|
||||
iconLeft="supervisor_account"
|
||||
value={this.clusterRoleName}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { asLegacyGlobalForExtensionApi } from "../../../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import clusterRoleStoreInjectable from "./store.injectable";
|
||||
|
||||
/**
|
||||
* @deprecated use `di.inject(clusterRoleStoreInjectable)` instead
|
||||
*/
|
||||
export const clusterRoleStore = asLegacyGlobalForExtensionApi(clusterRoleStoreInjectable);
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
import { storesAndApisCanBeCreatedInjectionToken } from "../../../../common/k8s-api/stores-apis-can-be-created.token";
|
||||
import clusterRoleApiInjectable from "../../../../common/k8s-api/endpoints/cluster-role.api.injectable";
|
||||
import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable";
|
||||
import { ClusterRolesStore } from "./store";
|
||||
|
||||
const clusterRoleStoreInjectable = getInjectable({
|
||||
id: "cluster-role-store",
|
||||
instantiate: (di) => {
|
||||
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "clusterRoleStore is only available in certain environments");
|
||||
|
||||
const api = di.inject(clusterRoleApiInjectable);
|
||||
|
||||
return new ClusterRolesStore(api);
|
||||
},
|
||||
injectionToken: kubeObjectStoreInjectionToken,
|
||||
});
|
||||
|
||||
export default clusterRoleStoreInjectable;
|
||||
@@ -2,20 +2,10 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { apiManager } from "../../../../common/k8s-api/api-manager";
|
||||
import type { ClusterRole } from "../../../../common/k8s-api/endpoints";
|
||||
import { clusterRoleApi } from "../../../../common/k8s-api/endpoints";
|
||||
import type { ClusterRole, ClusterRoleApi, ClusterRoleData } from "../../../../common/k8s-api/endpoints";
|
||||
import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
|
||||
import { autoBind } from "../../../utils";
|
||||
|
||||
export class ClusterRolesStore extends KubeObjectStore<ClusterRole> {
|
||||
api = clusterRoleApi;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
export class ClusterRolesStore extends KubeObjectStore<ClusterRole, ClusterRoleApi, ClusterRoleData> {
|
||||
protected sortItems(items: ClusterRole[]) {
|
||||
return super.sortItems(items, [
|
||||
clusterRole => clusterRole.kind,
|
||||
@@ -23,7 +13,3 @@ export class ClusterRolesStore extends KubeObjectStore<ClusterRole> {
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export const clusterRolesStore = new ClusterRolesStore();
|
||||
|
||||
apiManager.registerStore(clusterRolesStore);
|
||||
@@ -10,7 +10,7 @@ import React from "react";
|
||||
import { KubeObjectListLayout } from "../../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import { AddClusterRoleDialog } from "./add-dialog";
|
||||
import { clusterRolesStore } from "./store";
|
||||
import { clusterRoleStore } from "./legacy-store";
|
||||
import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
@@ -29,7 +29,7 @@ export class ClusterRoles extends React.Component {
|
||||
isConfigurable
|
||||
tableId="access_cluster_roles"
|
||||
className="ClusterRoles"
|
||||
store={clusterRolesStore}
|
||||
store={clusterRoleStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: clusterRole => clusterRole.getName(),
|
||||
[columnId.age]: clusterRole => -clusterRole.getCreationTimestamp(),
|
||||
|
||||
@@ -5,16 +5,15 @@
|
||||
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React from "react";
|
||||
import { clusterRolesStore } from "../../+cluster-roles/store";
|
||||
import { ClusterRole } from "../../../../../common/k8s-api/endpoints";
|
||||
import { RoleBindingDialog } from "../dialog";
|
||||
import { getDiForUnitTesting } from "../../../../getDiForUnitTesting";
|
||||
import type { DiRender } from "../../../test-utils/renderFor";
|
||||
import { renderFor } from "../../../test-utils/renderFor";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
|
||||
jest.mock("../../+cluster-roles/store");
|
||||
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../../stores-apis-can-be-created.injectable";
|
||||
import ipcRendererInjectable from "../../../../app-paths/get-value-from-registered-channel/ipc-renderer/ipc-renderer.injectable";
|
||||
|
||||
describe("RoleBindingDialog tests", () => {
|
||||
let render: DiRender;
|
||||
@@ -22,21 +21,31 @@ describe("RoleBindingDialog tests", () => {
|
||||
beforeEach(async () => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||
di.override(ipcRendererInjectable, () => ({
|
||||
on: jest.fn(),
|
||||
invoke: jest.fn(), // TODO: replace with proper mocking via the IPC bridge
|
||||
} as never));
|
||||
|
||||
await di.runSetups();
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
(clusterRolesStore as any).items = [new ClusterRole({
|
||||
apiVersion: "rbac.authorization.k8s.io/v1",
|
||||
kind: "ClusterRole",
|
||||
metadata: {
|
||||
name: "foobar",
|
||||
resourceVersion: "1",
|
||||
uid: "1",
|
||||
},
|
||||
})];
|
||||
const store = di.inject(clusterRoleStoreInjectable);
|
||||
|
||||
store.items.replace([
|
||||
new ClusterRole({
|
||||
apiVersion: "rbac.authorization.k8s.io/v1",
|
||||
kind: "ClusterRole",
|
||||
metadata: {
|
||||
name: "foobar",
|
||||
resourceVersion: "1",
|
||||
uid: "1",
|
||||
selfLink: "/apis/rbac.authorization.k8s.io/v1/clusterroles/foobar",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import "./details.scss";
|
||||
import { reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import type { RoleBinding, RoleBindingSubject } from "../../../../common/k8s-api/endpoints";
|
||||
import type { RoleBinding } from "../../../../common/k8s-api/endpoints";
|
||||
import { prevDefault } from "../../../utils";
|
||||
import { AddRemoveButtons } from "../../add-remove-buttons";
|
||||
import { DrawerTitle } from "../../drawer";
|
||||
@@ -16,9 +16,9 @@ import type { KubeObjectDetailsProps } from "../../kube-object-details";
|
||||
import { KubeObjectMeta } from "../../kube-object-meta";
|
||||
import { Table, TableCell, TableHead, TableRow } from "../../table";
|
||||
import { RoleBindingDialog } from "./dialog";
|
||||
import { roleBindingsStore } from "./store";
|
||||
import { roleBindingStore } from "./legacy-store";
|
||||
import { ObservableHashSet } from "../../../../common/utils/hash-set";
|
||||
import { hashRoleBindingSubject } from "./hashers";
|
||||
import { hashSubject } from "../hashers";
|
||||
import type { OpenConfirmDialog } from "../../confirm-dialog/open.injectable";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import openConfirmDialogInjectable from "../../confirm-dialog/open.injectable";
|
||||
@@ -32,7 +32,7 @@ interface Dependencies {
|
||||
|
||||
@observer
|
||||
class NonInjectedRoleBindingDetails extends React.Component<RoleBindingDetailsProps & Dependencies> {
|
||||
selectedSubjects = new ObservableHashSet<RoleBindingSubject>([], hashRoleBindingSubject);
|
||||
private readonly selectedSubjects = new ObservableHashSet([], hashSubject);
|
||||
|
||||
async componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
@@ -47,10 +47,14 @@ class NonInjectedRoleBindingDetails extends React.Component<RoleBindingDetailsPr
|
||||
const { selectedSubjects } = this;
|
||||
|
||||
openConfirmDialog({
|
||||
ok: () => roleBindingsStore.removeSubjects(roleBinding, selectedSubjects.toJSON()),
|
||||
ok: () => roleBindingStore.removeSubjects(roleBinding, selectedSubjects.toJSON()),
|
||||
labelOk: `Remove`,
|
||||
message: (
|
||||
<p>Remove selected bindings for <b>{roleBinding.getName()}</b>?</p>
|
||||
<p>
|
||||
Remove selected bindings for
|
||||
<b>{roleBinding.getName()}</b>
|
||||
?
|
||||
</p>
|
||||
),
|
||||
});
|
||||
};
|
||||
@@ -116,7 +120,7 @@ class NonInjectedRoleBindingDetails extends React.Component<RoleBindingDetailsPr
|
||||
|
||||
<AddRemoveButtons
|
||||
onAdd={() => RoleBindingDialog.open(roleBinding)}
|
||||
onRemove={selectedSubjects.size ? this.removeSelectedSubjects : null}
|
||||
onRemove={selectedSubjects.size ? this.removeSelectedSubjects : undefined}
|
||||
addTooltip={`Edit bindings of ${roleRef.name}`}
|
||||
removeTooltip={`Remove selected bindings from ${roleRef.name}`}
|
||||
/>
|
||||
|
||||
@@ -9,10 +9,10 @@ import { computed, observable, makeObservable, action } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import React from "react";
|
||||
|
||||
import { rolesStore } from "../+roles/store";
|
||||
import { serviceAccountsStore } from "../+service-accounts/store";
|
||||
import { roleStore } from "../+roles/legacy-store";
|
||||
import { serviceAccountStore } from "../+service-accounts/legacy-store";
|
||||
import { NamespaceSelect } from "../../+namespaces/namespace-select";
|
||||
import type { ClusterRole, Role, RoleBinding, RoleBindingSubject, ServiceAccount } from "../../../../common/k8s-api/endpoints";
|
||||
import type { ClusterRole, Role, RoleBinding, ServiceAccount } from "../../../../common/k8s-api/endpoints";
|
||||
import { roleApi } from "../../../../common/k8s-api/endpoints";
|
||||
import type { DialogProps } from "../../dialog";
|
||||
import { Dialog } from "../../dialog";
|
||||
@@ -22,12 +22,13 @@ import { showDetails } from "../../kube-detail-params";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Notifications } from "../../notifications";
|
||||
import type { SelectOption } from "../../select";
|
||||
import { Select } from "../../select";
|
||||
import { onMultiSelectFor, Select } from "../../select";
|
||||
import { Wizard, WizardStep } from "../../wizard";
|
||||
import { roleBindingsStore } from "./store";
|
||||
import { clusterRolesStore } from "../+cluster-roles/store";
|
||||
import { roleBindingStore } from "./legacy-store";
|
||||
import { clusterRoleStore } from "../+cluster-roles/legacy-store";
|
||||
import { Input } from "../../input";
|
||||
import { ObservableHashSet, nFircate } from "../../../utils";
|
||||
import type { Subject } from "../../../../common/k8s-api/endpoints/types/subject";
|
||||
|
||||
export interface RoleBindingDialogProps extends Partial<DialogProps> {
|
||||
}
|
||||
@@ -58,7 +59,7 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
RoleBindingDialog.state.data = undefined;
|
||||
}
|
||||
|
||||
get roleBinding(): RoleBinding {
|
||||
get roleBinding() {
|
||||
return RoleBindingDialog.state.data;
|
||||
}
|
||||
|
||||
@@ -66,26 +67,26 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
return !!this.roleBinding;
|
||||
}
|
||||
|
||||
@observable.ref selectedRoleRef: Role | ClusterRole | undefined = undefined;
|
||||
@observable.ref selectedRoleRef: Role | ClusterRole | null | undefined = null;
|
||||
@observable bindingName = "";
|
||||
@observable bindingNamespace = "";
|
||||
@observable bindingNamespace: string | null = null;
|
||||
selectedAccounts = new ObservableHashSet<ServiceAccount>([], sa => sa.metadata.uid);
|
||||
selectedUsers = observable.set<string>([]);
|
||||
selectedGroups = observable.set<string>([]);
|
||||
|
||||
@computed get selectedBindings(): RoleBindingSubject[] {
|
||||
const serviceAccounts = Array.from(this.selectedAccounts, sa => ({
|
||||
@computed get selectedBindings(): Subject[] {
|
||||
const serviceAccounts: Subject[] = Array.from(this.selectedAccounts, sa => ({
|
||||
name: sa.getName(),
|
||||
kind: "ServiceAccount" as const,
|
||||
kind: "ServiceAccount",
|
||||
namespace: sa.getNs(),
|
||||
}));
|
||||
const users = Array.from(this.selectedUsers, user => ({
|
||||
const users: Subject[] = Array.from(this.selectedUsers, user => ({
|
||||
name: user,
|
||||
kind: "User" as const,
|
||||
kind: "User",
|
||||
}));
|
||||
const groups = Array.from(this.selectedGroups, group => ({
|
||||
const groups: Subject[] = Array.from(this.selectedGroups, group => ({
|
||||
name: group,
|
||||
kind: "Group" as const,
|
||||
kind: "Group",
|
||||
}));
|
||||
|
||||
return [
|
||||
@@ -96,27 +97,25 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
}
|
||||
|
||||
@computed get roleRefOptions(): SelectOption<Role | ClusterRole>[] {
|
||||
const roles = rolesStore.items
|
||||
.filter(role => role.getNs() === this.bindingNamespace)
|
||||
.map(value => ({ value, label: value.getName() }));
|
||||
const clusterRoles = clusterRolesStore.items
|
||||
.map(value => ({ value, label: value.getName() }));
|
||||
const roles = roleStore.items
|
||||
.filter(role => role.getNs() === this.bindingNamespace);
|
||||
const clusterRoles = clusterRoleStore.items;
|
||||
|
||||
return [
|
||||
...roles,
|
||||
...clusterRoles,
|
||||
];
|
||||
}
|
||||
|
||||
@computed get serviceAccountOptions(): SelectOption<ServiceAccount>[] {
|
||||
return serviceAccountsStore.items.map(account => ({
|
||||
value: account,
|
||||
label: `${account.getName()} (${account.getNs()})`,
|
||||
].map(r => ({
|
||||
value: r,
|
||||
label: r.getName(),
|
||||
}));
|
||||
}
|
||||
|
||||
@computed get selectedServiceAccountOptions(): SelectOption<ServiceAccount>[] {
|
||||
return this.serviceAccountOptions.filter(({ value }) => this.selectedAccounts.has(value));
|
||||
@computed get serviceAccountOptions(): SelectOption<ServiceAccount>[] {
|
||||
return serviceAccountStore.items.map(serviceAccount => ({
|
||||
value: serviceAccount,
|
||||
label: `${serviceAccount.getName()} (${serviceAccount.getNs()})`,
|
||||
isSelected: this.selectedAccounts.has(serviceAccount),
|
||||
}));
|
||||
}
|
||||
|
||||
onOpen = action(() => {
|
||||
@@ -126,9 +125,13 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
return this.reset();
|
||||
}
|
||||
|
||||
this.selectedRoleRef = (binding.roleRef.kind === roleApi.kind ? rolesStore : clusterRolesStore)
|
||||
.items
|
||||
.find(item => item.getName() === binding.roleRef.name);
|
||||
const findByRoleRefName = (item: Role | ClusterRole) => item.getName() === binding.roleRef.name;
|
||||
|
||||
this.selectedRoleRef = (
|
||||
binding.roleRef.kind === roleApi.kind
|
||||
? roleStore.items.find(findByRoleRefName)
|
||||
: clusterRoleStore.items.find(findByRoleRefName)
|
||||
);
|
||||
|
||||
this.bindingName = binding.getName();
|
||||
this.bindingNamespace = binding.getNs();
|
||||
@@ -137,7 +140,7 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
const accountNames = new Set(saSubjects.map(acc => acc.name));
|
||||
|
||||
this.selectedAccounts.replace(
|
||||
serviceAccountsStore.items
|
||||
serviceAccountStore.items
|
||||
.filter(sa => accountNames.has(sa.getName())),
|
||||
);
|
||||
this.selectedUsers.replace(uSubjects.map(user => user.name));
|
||||
@@ -145,7 +148,7 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
});
|
||||
|
||||
reset = action(() => {
|
||||
this.selectedRoleRef = undefined;
|
||||
this.selectedRoleRef = null;
|
||||
this.bindingName = "";
|
||||
this.bindingNamespace = "";
|
||||
this.selectedAccounts.clear();
|
||||
@@ -154,12 +157,19 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
});
|
||||
|
||||
createBindings = async () => {
|
||||
const { selectedRoleRef, bindingNamespace: namespace, selectedBindings } = this;
|
||||
const { selectedRoleRef, bindingNamespace, selectedBindings, roleBinding, bindingName } = this;
|
||||
|
||||
if (!selectedRoleRef || !roleBinding || !bindingNamespace || !bindingName) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const roleBinding = this.isEditing
|
||||
? await roleBindingsStore.updateSubjects(this.roleBinding, selectedBindings)
|
||||
: await roleBindingsStore.create({ name: this.bindingName, namespace }, {
|
||||
const newRoleBinding = this.isEditing
|
||||
? await roleBindingStore.updateSubjects(roleBinding, selectedBindings)
|
||||
: await roleBindingStore.create({
|
||||
name: bindingName,
|
||||
namespace: bindingNamespace,
|
||||
}, {
|
||||
subjects: selectedBindings,
|
||||
roleRef: {
|
||||
name: selectedRoleRef.getName(),
|
||||
@@ -167,10 +177,10 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
},
|
||||
});
|
||||
|
||||
showDetails(roleBinding.selfLink);
|
||||
showDetails(newRoleBinding.selfLink);
|
||||
RoleBindingDialog.close();
|
||||
} catch (err) {
|
||||
Notifications.error(err);
|
||||
Notifications.checkedError(err, `Unknown error occured while ${this.isEditing ? "editing" : "creating"} role bindings.`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -184,7 +194,7 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
isDisabled={this.isEditing}
|
||||
value={this.bindingNamespace}
|
||||
autoFocus={!this.isEditing}
|
||||
onChange={({ value }) => this.bindingNamespace = value}
|
||||
onChange={opt => this.bindingNamespace = opt?.value ?? null}
|
||||
/>
|
||||
|
||||
<SubTitle title="Role Reference" />
|
||||
@@ -195,12 +205,12 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
isDisabled={this.isEditing}
|
||||
options={this.roleRefOptions}
|
||||
value={this.selectedRoleRef}
|
||||
onChange={({ value }) => {
|
||||
if (!this.selectedRoleRef || this.bindingName === this.selectedRoleRef.getName()) {
|
||||
this.bindingName = value.getName();
|
||||
}
|
||||
onChange={option => {
|
||||
this.selectedRoleRef = option?.value;
|
||||
|
||||
this.selectedRoleRef = value;
|
||||
if (!this.selectedRoleRef || this.bindingName === this.selectedRoleRef.getName()) {
|
||||
this.bindingName = option?.value.getName() ?? "";
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -235,19 +245,14 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
isMulti
|
||||
themeName="light"
|
||||
placeholder="Select service accounts ..."
|
||||
autoConvertOptions={false}
|
||||
options={this.serviceAccountOptions}
|
||||
value={this.selectedServiceAccountOptions}
|
||||
formatOptionLabel={({ value }: SelectOption<ServiceAccount>) => (
|
||||
<><Icon small material="account_box" /> {value.getName()} ({value.getNs()})</>
|
||||
formatOptionLabel={option => (
|
||||
<>
|
||||
<Icon small material="account_box" />
|
||||
{` ${option.label}`}
|
||||
</>
|
||||
)}
|
||||
onChange={(selected: SelectOption<ServiceAccount>[] | null) => {
|
||||
if (selected) {
|
||||
this.selectedAccounts.replace(selected.map(opt => opt.value));
|
||||
} else {
|
||||
this.selectedAccounts.clear();
|
||||
}
|
||||
}}
|
||||
onChange={onMultiSelectFor(this.selectedAccounts)}
|
||||
maxMenuHeight={200}
|
||||
/>
|
||||
</>
|
||||
@@ -269,7 +274,11 @@ export class RoleBindingDialog extends React.Component<RoleBindingDialogProps> {
|
||||
onOpen={this.onOpen}
|
||||
>
|
||||
<Wizard
|
||||
header={<h5>{action} RoleBinding</h5>}
|
||||
header={(
|
||||
<h5>
|
||||
{`${action} RoleBinding`}
|
||||
</h5>
|
||||
)}
|
||||
done={RoleBindingDialog.close}
|
||||
>
|
||||
<WizardStep
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { asLegacyGlobalForExtensionApi } from "../../../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import roleBindingStoreInjectable from "./store.injectable";
|
||||
|
||||
/**
|
||||
* @deprecated use `di.inject(roleBindingStoreInjectable)` instead
|
||||
*/
|
||||
export const roleBindingStore = asLegacyGlobalForExtensionApi(roleBindingStoreInjectable);
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable";
|
||||
import roleBindingApiInjectable from "../../../../common/k8s-api/endpoints/role-binding.api.injectable";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
|
||||
import { RoleBindingStore } from "./store";
|
||||
|
||||
const roleBindingStoreInjectable = getInjectable({
|
||||
id: "role-binding-store",
|
||||
instantiate: (di) => {
|
||||
assert(di.inject(storesAndApisCanBeCreatedInjectable), "roleBindingStore is only available in certain environments");
|
||||
|
||||
const api = di.inject(roleBindingApiInjectable);
|
||||
|
||||
return new RoleBindingStore(api);
|
||||
},
|
||||
injectionToken: kubeObjectStoreInjectionToken,
|
||||
});
|
||||
|
||||
export default roleBindingStoreInjectable;
|
||||
@@ -3,16 +3,13 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { apiManager } from "../../../../common/k8s-api/api-manager";
|
||||
import type { RoleBinding, RoleBindingSubject } from "../../../../common/k8s-api/endpoints";
|
||||
import { roleBindingApi } from "../../../../common/k8s-api/endpoints";
|
||||
import type { RoleBinding, RoleBindingApi, RoleBindingData } from "../../../../common/k8s-api/endpoints";
|
||||
import type { Subject } from "../../../../common/k8s-api/endpoints/types/subject";
|
||||
import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
|
||||
import { HashSet } from "../../../utils";
|
||||
import { hashRoleBindingSubject } from "./hashers";
|
||||
|
||||
export class RoleBindingsStore extends KubeObjectStore<RoleBinding> {
|
||||
api = roleBindingApi;
|
||||
import { hashSubject } from "../hashers";
|
||||
|
||||
export class RoleBindingStore extends KubeObjectStore<RoleBinding, RoleBindingApi, RoleBindingData> {
|
||||
protected sortItems(items: RoleBinding[]) {
|
||||
return super.sortItems(items, [
|
||||
roleBinding => roleBinding.kind,
|
||||
@@ -20,19 +17,15 @@ export class RoleBindingsStore extends KubeObjectStore<RoleBinding> {
|
||||
]);
|
||||
}
|
||||
|
||||
protected async createItem(params: { name: string; namespace: string }, data?: Partial<RoleBinding>) {
|
||||
return roleBindingApi.create(params, data);
|
||||
}
|
||||
|
||||
async updateSubjects(roleBinding: RoleBinding, subjects: RoleBindingSubject[]) {
|
||||
async updateSubjects(roleBinding: RoleBinding, subjects: Subject[]) {
|
||||
return this.update(roleBinding, {
|
||||
roleRef: roleBinding.roleRef,
|
||||
subjects,
|
||||
});
|
||||
}
|
||||
|
||||
async removeSubjects(roleBinding: RoleBinding, subjectsToRemove: Iterable<RoleBindingSubject>) {
|
||||
const currentSubjects = new HashSet(roleBinding.getSubjects(), hashRoleBindingSubject);
|
||||
async removeSubjects(roleBinding: RoleBinding, subjectsToRemove: Iterable<Subject>) {
|
||||
const currentSubjects = new HashSet(roleBinding.getSubjects(), hashSubject);
|
||||
|
||||
for (const subject of subjectsToRemove) {
|
||||
currentSubjects.delete(subject);
|
||||
@@ -41,7 +34,3 @@ export class RoleBindingsStore extends KubeObjectStore<RoleBinding> {
|
||||
return this.updateSubjects(roleBinding, currentSubjects.toJSON());
|
||||
}
|
||||
}
|
||||
|
||||
export const roleBindingsStore = new RoleBindingsStore();
|
||||
|
||||
apiManager.registerStore(roleBindingsStore);
|
||||
@@ -9,10 +9,10 @@ import React from "react";
|
||||
import { KubeObjectListLayout } from "../../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import { RoleBindingDialog } from "./dialog";
|
||||
import { roleBindingsStore } from "./store";
|
||||
import { rolesStore } from "../+roles/store";
|
||||
import { clusterRolesStore } from "../+cluster-roles/store";
|
||||
import { serviceAccountsStore } from "../+service-accounts/store";
|
||||
import { roleBindingStore } from "./legacy-store";
|
||||
import { roleStore } from "../+roles/legacy-store";
|
||||
import { clusterRoleStore } from "../+cluster-roles/legacy-store";
|
||||
import { serviceAccountStore } from "../+service-accounts/legacy-store";
|
||||
import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
@@ -32,8 +32,8 @@ export class RoleBindings extends React.Component {
|
||||
isConfigurable
|
||||
tableId="access_role_bindings"
|
||||
className="RoleBindings"
|
||||
store={roleBindingsStore}
|
||||
dependentStores={[rolesStore, clusterRolesStore, serviceAccountsStore]}
|
||||
store={roleBindingStore}
|
||||
dependentStores={[roleStore, clusterRoleStore, serviceAccountStore]}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: binding => binding.getName(),
|
||||
[columnId.namespace]: binding => binding.getNs(),
|
||||
|
||||
@@ -17,7 +17,7 @@ import { showDetails } from "../../kube-detail-params";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Notifications } from "../../notifications";
|
||||
import { Wizard, WizardStep } from "../../wizard";
|
||||
import { rolesStore } from "./store";
|
||||
import { roleStore } from "./legacy-store";
|
||||
|
||||
export interface AddRoleDialogProps extends Partial<DialogProps> {
|
||||
}
|
||||
@@ -49,13 +49,13 @@ export class AddRoleDialog extends React.Component<AddRoleDialogProps> {
|
||||
|
||||
createRole = async () => {
|
||||
try {
|
||||
const role = await rolesStore.create({ name: this.roleName, namespace: this.namespace });
|
||||
const role = await roleStore.create({ name: this.roleName, namespace: this.namespace });
|
||||
|
||||
showDetails(role.selfLink);
|
||||
this.reset();
|
||||
AddRoleDialog.close();
|
||||
} catch (err) {
|
||||
Notifications.error(err.toString());
|
||||
Notifications.checkedError(err, "Unknown error occured while creating role");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,7 +78,8 @@ export class AddRoleDialog extends React.Component<AddRoleDialogProps> {
|
||||
>
|
||||
<SubTitle title="Role Name" />
|
||||
<Input
|
||||
required autoFocus
|
||||
required
|
||||
autoFocus
|
||||
placeholder="Name"
|
||||
iconLeft="supervisor_account"
|
||||
value={this.roleName}
|
||||
@@ -89,7 +90,7 @@ export class AddRoleDialog extends React.Component<AddRoleDialogProps> {
|
||||
id="add-dialog-namespace-select-input"
|
||||
themeName="light"
|
||||
value={this.namespace}
|
||||
onChange={({ value }) => this.namespace = value}
|
||||
onChange={option => this.namespace = option?.value ?? "default"}
|
||||
/>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { asLegacyGlobalForExtensionApi } from "../../../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import roleStoreInjectable from "./store.injectable";
|
||||
|
||||
/**
|
||||
* @deprecated use `di.inject(roleStoreInjectable)` instead
|
||||
*/
|
||||
export const roleStore = asLegacyGlobalForExtensionApi(roleStoreInjectable);
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
import roleApiInjectable from "../../../../common/k8s-api/endpoints/role.api.injectable";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
|
||||
import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable";
|
||||
import { RoleStore } from "./store";
|
||||
|
||||
const roleStoreInjectable = getInjectable({
|
||||
id: "role-store",
|
||||
instantiate: (di) => {
|
||||
assert(di.inject(storesAndApisCanBeCreatedInjectable), "roleStore is only available in certain environments");
|
||||
|
||||
const api = di.inject(roleApiInjectable);
|
||||
|
||||
return new RoleStore(api);
|
||||
},
|
||||
injectionToken: kubeObjectStoreInjectionToken,
|
||||
});
|
||||
|
||||
export default roleStoreInjectable;
|
||||
@@ -2,32 +2,14 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { apiManager } from "../../../../common/k8s-api/api-manager";
|
||||
import type { Role } from "../../../../common/k8s-api/endpoints";
|
||||
import { roleApi } from "../../../../common/k8s-api/endpoints";
|
||||
import type { Role, RoleApi, RoleData } from "../../../../common/k8s-api/endpoints";
|
||||
import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
|
||||
import { autoBind } from "../../../utils";
|
||||
|
||||
export class RolesStore extends KubeObjectStore<Role> {
|
||||
api = roleApi;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
export class RoleStore extends KubeObjectStore<Role, RoleApi, RoleData> {
|
||||
protected sortItems(items: Role[]) {
|
||||
return super.sortItems(items, [
|
||||
role => role.kind,
|
||||
role => role.getName(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected async createItem(params: { name: string; namespace?: string }, data?: Partial<Role>) {
|
||||
return roleApi.create(params, data);
|
||||
}
|
||||
}
|
||||
|
||||
export const rolesStore = new RolesStore();
|
||||
|
||||
apiManager.registerStore(rolesStore);
|
||||
@@ -10,7 +10,7 @@ import React from "react";
|
||||
import { KubeObjectListLayout } from "../../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import { AddRoleDialog } from "./add-dialog";
|
||||
import { rolesStore } from "./store";
|
||||
import { roleStore } from "./legacy-store";
|
||||
import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
@@ -29,7 +29,7 @@ export class Roles extends React.Component {
|
||||
isConfigurable
|
||||
tableId="access_roles"
|
||||
className="Roles"
|
||||
store={rolesStore}
|
||||
store={roleStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: role => role.getName(),
|
||||
[columnId.namespace]: role => role.getNs(),
|
||||
|
||||
@@ -18,7 +18,7 @@ import { showDetails } from "../../kube-detail-params";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Notifications } from "../../notifications";
|
||||
import { Wizard, WizardStep } from "../../wizard";
|
||||
import { serviceAccountsStore } from "./store";
|
||||
import { serviceAccountStore } from "./legacy-store";
|
||||
|
||||
export interface CreateServiceAccountDialogProps extends Partial<DialogProps> {
|
||||
}
|
||||
@@ -47,13 +47,13 @@ export class CreateServiceAccountDialog extends React.Component<CreateServiceAcc
|
||||
const { name, namespace } = this;
|
||||
|
||||
try {
|
||||
const serviceAccount = await serviceAccountsStore.create({ namespace, name });
|
||||
const serviceAccount = await serviceAccountStore.create({ namespace, name });
|
||||
|
||||
this.name = "";
|
||||
showDetails(serviceAccount.selfLink);
|
||||
CreateServiceAccountDialog.close();
|
||||
} catch (err) {
|
||||
Notifications.error(err);
|
||||
Notifications.checkedError(err, "Unknown error occured while creating service account");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,14 +78,15 @@ export class CreateServiceAccountDialog extends React.Component<CreateServiceAcc
|
||||
placeholder="Enter a name"
|
||||
trim
|
||||
validators={systemName}
|
||||
value={name} onChange={v => this.name = v.toLowerCase()}
|
||||
value={name}
|
||||
onChange={v => this.name = v.toLowerCase()}
|
||||
/>
|
||||
<SubTitle title="Namespace" />
|
||||
<NamespaceSelect
|
||||
id="create-dialog-namespace-select-input"
|
||||
themeName="light"
|
||||
value={namespace}
|
||||
onChange={({ value }) => this.namespace = value}
|
||||
onChange={option => this.namespace = option?.value ?? "default"}
|
||||
/>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
|
||||
import "./details.scss";
|
||||
|
||||
import { autorun, observable, makeObservable } from "mobx";
|
||||
import { autorun, observable, runInAction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { secretsStore } from "../../+config-secrets/secrets.store";
|
||||
import type { ServiceAccount } from "../../../../common/k8s-api/endpoints";
|
||||
import { Secret, SecretType } from "../../../../common/k8s-api/endpoints";
|
||||
import { secretStore } from "../../+config-secrets/legacy-store";
|
||||
import type { Secret, ServiceAccount } from "../../../../common/k8s-api/endpoints";
|
||||
import { DrawerItem, DrawerTitle } from "../../drawer";
|
||||
import { Icon } from "../../icon";
|
||||
import type { KubeObjectDetailsProps } from "../../kube-object-details";
|
||||
@@ -24,41 +23,50 @@ import { getDetailsUrl } from "../../kube-detail-params";
|
||||
export interface ServiceAccountsDetailsProps extends KubeObjectDetailsProps<ServiceAccount> {
|
||||
}
|
||||
|
||||
const defensiveLoadSecretIn = (namespace: string) => (
|
||||
({ name }: { name: string }) => (
|
||||
secretStore.load({ name, namespace })
|
||||
.catch(() => name)
|
||||
)
|
||||
);
|
||||
|
||||
@observer
|
||||
export class ServiceAccountsDetails extends React.Component<ServiceAccountsDetailsProps> {
|
||||
@observable secrets: Secret[];
|
||||
@observable imagePullSecrets: Secret[];
|
||||
readonly secrets = observable.array<Secret | string>();
|
||||
readonly imagePullSecrets = observable.array<Secret | string>();
|
||||
|
||||
componentDidMount(): void {
|
||||
disposeOnUnmount(this, [
|
||||
autorun(async () => {
|
||||
this.secrets = null;
|
||||
this.imagePullSecrets = null;
|
||||
const { object: serviceAccount } = this.props;
|
||||
runInAction(() => {
|
||||
this.secrets.clear();
|
||||
this.imagePullSecrets.clear();
|
||||
});
|
||||
|
||||
if (!serviceAccount) {
|
||||
const { object: serviceAccount } = this.props;
|
||||
const namespace = serviceAccount?.getNs();
|
||||
|
||||
if (!namespace) {
|
||||
return;
|
||||
}
|
||||
const namespace = serviceAccount.getNs();
|
||||
const secrets = serviceAccount.getSecrets().map(({ name }) => {
|
||||
return secretsStore.load({ name, namespace });
|
||||
});
|
||||
|
||||
this.secrets = await Promise.all(secrets);
|
||||
const imagePullSecrets = serviceAccount.getImagePullSecrets().map(async ({ name }) => {
|
||||
return secretsStore.load({ name, namespace }).catch(() => this.generateDummySecretObject(name));
|
||||
});
|
||||
const defensiveLoadSecret = defensiveLoadSecretIn(namespace);
|
||||
|
||||
this.imagePullSecrets = await Promise.all(imagePullSecrets);
|
||||
const secretLoaders = Promise.all(serviceAccount.getSecrets().map(defensiveLoadSecret));
|
||||
const imagePullSecretLoaders = Promise.all(serviceAccount.getImagePullSecrets().map(defensiveLoadSecret));
|
||||
const [secrets, imagePullSecrets] = await Promise.all([
|
||||
secretLoaders,
|
||||
imagePullSecretLoaders,
|
||||
]);
|
||||
|
||||
runInAction(() => {
|
||||
this.secrets.replace(secrets);
|
||||
this.imagePullSecrets.replace(imagePullSecrets);
|
||||
});
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
constructor(props: ServiceAccountsDetailsProps) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
renderSecrets() {
|
||||
const { secrets } = this;
|
||||
|
||||
@@ -66,9 +74,12 @@ export class ServiceAccountsDetails extends React.Component<ServiceAccountsDetai
|
||||
return <Spinner center/>;
|
||||
}
|
||||
|
||||
return secrets.map(secret =>
|
||||
<ServiceAccountsSecret key={secret.getId()} secret={secret}/>,
|
||||
);
|
||||
return secrets.map(secret => (
|
||||
<ServiceAccountsSecret
|
||||
key={typeof secret === "string" ? secret : secret.getName()}
|
||||
secret={secret}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
renderImagePullSecrets() {
|
||||
@@ -81,14 +92,15 @@ export class ServiceAccountsDetails extends React.Component<ServiceAccountsDetai
|
||||
return this.renderSecretLinks(imagePullSecrets);
|
||||
}
|
||||
|
||||
renderSecretLinks(secrets: Secret[]) {
|
||||
renderSecretLinks(secrets: (Secret | string)[]) {
|
||||
return secrets.map((secret) => {
|
||||
if (secret.getId() === null) {
|
||||
if (typeof secret === "string") {
|
||||
return (
|
||||
<div key={secret.getName()}>
|
||||
{secret.getName()}
|
||||
<div key={secret}>
|
||||
{secret}
|
||||
<Icon
|
||||
small material="warning"
|
||||
small
|
||||
material="warning"
|
||||
tooltip="Secret is not found"
|
||||
/>
|
||||
</div>
|
||||
@@ -103,27 +115,13 @@ export class ServiceAccountsDetails extends React.Component<ServiceAccountsDetai
|
||||
});
|
||||
}
|
||||
|
||||
generateDummySecretObject(name: string) {
|
||||
return new Secret({
|
||||
apiVersion: "v1",
|
||||
kind: "Secret",
|
||||
metadata: {
|
||||
name,
|
||||
uid: null,
|
||||
selfLink: null,
|
||||
resourceVersion: null,
|
||||
},
|
||||
type: SecretType.Opaque,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { object: serviceAccount } = this.props;
|
||||
|
||||
if (!serviceAccount) {
|
||||
return null;
|
||||
}
|
||||
const tokens = secretsStore.items.filter(secret =>
|
||||
const tokens = secretStore.items.filter(secret =>
|
||||
secret.getNs() == serviceAccount.getNs() &&
|
||||
secret.getAnnotations().some(annot => annot == `kubernetes.io/service-account.name: ${serviceAccount.getName()}`),
|
||||
);
|
||||
@@ -133,16 +131,16 @@ export class ServiceAccountsDetails extends React.Component<ServiceAccountsDetai
|
||||
<div className="ServiceAccountsDetails">
|
||||
<KubeObjectMeta object={serviceAccount}/>
|
||||
|
||||
{tokens.length > 0 &&
|
||||
<DrawerItem name="Tokens" className="links">
|
||||
{this.renderSecretLinks(tokens)}
|
||||
</DrawerItem>
|
||||
}
|
||||
{imagePullSecrets.length > 0 &&
|
||||
<DrawerItem name="ImagePullSecrets" className="links">
|
||||
{this.renderImagePullSecrets()}
|
||||
</DrawerItem>
|
||||
}
|
||||
{tokens.length > 0 && (
|
||||
<DrawerItem name="Tokens" className="links">
|
||||
{this.renderSecretLinks(tokens)}
|
||||
</DrawerItem>
|
||||
)}
|
||||
{imagePullSecrets.length > 0 && (
|
||||
<DrawerItem name="ImagePullSecrets" className="links">
|
||||
{this.renderImagePullSecrets()}
|
||||
</DrawerItem>
|
||||
)}
|
||||
|
||||
<DrawerTitle>Mountable secrets</DrawerTitle>
|
||||
<div className="secrets">
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { asLegacyGlobalForExtensionApi } from "../../../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import serviceAccountStoreInjectable from "./store.injectable";
|
||||
|
||||
/**
|
||||
* @deprecated use `di.inject(serviceAccountStoreInjectable)` instead
|
||||
*/
|
||||
export const serviceAccountStore = asLegacyGlobalForExtensionApi(serviceAccountStoreInjectable);
|
||||
@@ -13,20 +13,24 @@ import { prevDefault } from "../../../utils";
|
||||
import { Icon } from "../../icon";
|
||||
|
||||
export interface ServiceAccountsSecretProps {
|
||||
secret: Secret;
|
||||
secret: Secret | string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
showToken: boolean;
|
||||
}
|
||||
|
||||
interface RenderRowArgs {
|
||||
name: string;
|
||||
value: React.ReactNode;
|
||||
}
|
||||
|
||||
export class ServiceAccountsSecret extends React.Component<ServiceAccountsSecretProps, State> {
|
||||
public state: State = {
|
||||
showToken: false,
|
||||
};
|
||||
|
||||
renderSecretValue() {
|
||||
const { secret } = this.props;
|
||||
renderSecretValue(secret: Secret) {
|
||||
const { showToken } = this.state;
|
||||
|
||||
return (
|
||||
@@ -35,7 +39,8 @@ export class ServiceAccountsSecret extends React.Component<ServiceAccountsSecret
|
||||
<>
|
||||
<span className="asterisks">{"•".repeat(16)}</span>
|
||||
<Icon
|
||||
small material="lock_open"
|
||||
small
|
||||
material="lock_open"
|
||||
tooltip="Show value"
|
||||
onClick={prevDefault(() => this.setState({ showToken: true }))}
|
||||
/>
|
||||
@@ -48,29 +53,52 @@ export class ServiceAccountsSecret extends React.Component<ServiceAccountsSecret
|
||||
);
|
||||
}
|
||||
|
||||
renderRow({ name, value }: RenderRowArgs) {
|
||||
return (
|
||||
<div className="secret-row">
|
||||
<span className="name">{name}</span>
|
||||
<span className="value">{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { metadata: { name, creationTimestamp }, type } = this.props.secret;
|
||||
const { secret } = this.props;
|
||||
|
||||
return (
|
||||
<div className="ServiceAccountsSecret box grow-fixed">
|
||||
<div className="secret-row">
|
||||
<span className="name">Name: </span>
|
||||
<span className="value">{name}</span>
|
||||
</div>
|
||||
<div className="secret-row">
|
||||
<span className="name">Value: </span>
|
||||
<span className="value flex align-center">{this.renderSecretValue()}</span>
|
||||
</div>
|
||||
<div className="secret-row">
|
||||
<span className="name">Created at: </span>
|
||||
<span className="value" title={creationTimestamp}>
|
||||
{moment(creationTimestamp).format("LLL")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="secret-row">
|
||||
<span className="name">Type: </span>
|
||||
<span className="value">{type}</span>
|
||||
</div>
|
||||
{this.renderRow({
|
||||
name: "Name: ",
|
||||
value: (
|
||||
typeof secret === "string"
|
||||
? secret
|
||||
: secret.getName()
|
||||
),
|
||||
})}
|
||||
{this.renderRow({
|
||||
name: "Value: ",
|
||||
value: (
|
||||
typeof secret === "string"
|
||||
? "<unknown>"
|
||||
: this.renderSecretValue(secret)
|
||||
),
|
||||
})}
|
||||
{this.renderRow({
|
||||
name: "Created at: ",
|
||||
value: (
|
||||
typeof secret === "string" || !secret.metadata.creationTimestamp
|
||||
? "<unknown>"
|
||||
: moment(secret.metadata.creationTimestamp).format("LLL")
|
||||
),
|
||||
})}
|
||||
{this.renderRow({
|
||||
name: "Type: ",
|
||||
value: (
|
||||
typeof secret === "string"
|
||||
? "<unknown>"
|
||||
: secret.type
|
||||
),
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@ export function ServiceAccountMenu(props: KubeObjectMenuProps<ServiceAccount>) {
|
||||
|
||||
return (
|
||||
<MenuItem onClick={() => openServiceAccountKubeConfig(object)}>
|
||||
<Icon material="insert_drive_file" tooltip="Kubeconfig File" interactive={toolbar} />
|
||||
<Icon
|
||||
material="insert_drive_file"
|
||||
tooltip="Kubeconfig File"
|
||||
interactive={toolbar}
|
||||
/>
|
||||
<span className="title">Kubeconfig</span>
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
import serviceAccountApiInjectable from "../../../../common/k8s-api/endpoints/service-account.api.injectable";
|
||||
import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable";
|
||||
import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable";
|
||||
import { ServiceAccountStore } from "./store";
|
||||
|
||||
const serviceAccountStoreInjectable = getInjectable({
|
||||
id: "service-account-store",
|
||||
instantiate: (di) => {
|
||||
assert(di.inject(storesAndApisCanBeCreatedInjectable), "serviceAccountStore is only available in certain environments");
|
||||
|
||||
const api = di.inject(serviceAccountApiInjectable);
|
||||
|
||||
return new ServiceAccountStore(api);
|
||||
},
|
||||
injectionToken: kubeObjectStoreInjectionToken,
|
||||
});
|
||||
|
||||
export default serviceAccountStoreInjectable;
|
||||
@@ -3,26 +3,13 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { apiManager } from "../../../../common/k8s-api/api-manager";
|
||||
import type { ServiceAccount } from "../../../../common/k8s-api/endpoints";
|
||||
import { serviceAccountsApi } from "../../../../common/k8s-api/endpoints";
|
||||
import type { ServiceAccount, ServiceAccountApi, ServiceAccountData } from "../../../../common/k8s-api/endpoints";
|
||||
import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store";
|
||||
import { autoBind } from "../../../utils";
|
||||
|
||||
export class ServiceAccountsStore extends KubeObjectStore<ServiceAccount> {
|
||||
api = serviceAccountsApi;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
export class ServiceAccountStore extends KubeObjectStore<ServiceAccount, ServiceAccountApi, ServiceAccountData> {
|
||||
protected async createItem(params: { name: string; namespace?: string }) {
|
||||
await super.createItem(params);
|
||||
|
||||
return this.api.get(params); // hackfix: load freshly created account, cause it doesn't have "secrets" field yet
|
||||
}
|
||||
}
|
||||
|
||||
export const serviceAccountsStore = new ServiceAccountsStore();
|
||||
apiManager.registerStore(serviceAccountsStore);
|
||||
@@ -10,7 +10,7 @@ import React from "react";
|
||||
import { KubeObjectListLayout } from "../../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import { CreateServiceAccountDialog } from "./create-dialog";
|
||||
import { serviceAccountsStore } from "./store";
|
||||
import { serviceAccountStore } from "./legacy-store";
|
||||
import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
@@ -28,7 +28,8 @@ export class ServiceAccounts extends React.Component {
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="access_service_accounts"
|
||||
className="ServiceAccounts" store={serviceAccountsStore}
|
||||
className="ServiceAccounts"
|
||||
store={serviceAccountStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: account => account.getName(),
|
||||
[columnId.namespace]: account => account.getNs(),
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@
|
||||
*/
|
||||
|
||||
import { MD5 } from "crypto-js";
|
||||
import type { RoleBindingSubject } from "../../../../common/k8s-api/endpoints";
|
||||
import type { Subject } from "../../../common/k8s-api/endpoints/types/subject";
|
||||
|
||||
export function hashRoleBindingSubject(subject: RoleBindingSubject): string {
|
||||
export function hashSubject(subject: Subject): string {
|
||||
return MD5(JSON.stringify([
|
||||
["kind", subject.kind],
|
||||
["name", subject.name],
|
||||
Reference in new issue
Block a user