mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Cascade different refactorings to eliminate global shared state from app paths
Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
1 parent
908a40975e
commit
9d33fff906
336 files changed
+9105
-3307
No files matched your search
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { AddNamespaceDialogModel } from "./add-namespace-dialog-model";
|
||||
|
||||
const addNamespaceDialogModelInjectable = getInjectable({
|
||||
instantiate: () => new AddNamespaceDialogModel(),
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default addNamespaceDialogModelInjectable;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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 { observable, makeObservable, action } from "mobx";
|
||||
|
||||
export class AddNamespaceDialogModel {
|
||||
isOpen = false;
|
||||
|
||||
constructor() {
|
||||
makeObservable(this, {
|
||||
isOpen: observable,
|
||||
open: action,
|
||||
close: action,
|
||||
});
|
||||
}
|
||||
|
||||
open = () => {
|
||||
this.isOpen = true;
|
||||
};
|
||||
|
||||
close = () => {
|
||||
this.isOpen = false;
|
||||
};
|
||||
}
|
||||
@@ -26,38 +26,35 @@ import { observable, makeObservable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Dialog, DialogProps } from "../dialog";
|
||||
import { Wizard, WizardStep } from "../wizard";
|
||||
import { namespaceStore } from "./namespace.store";
|
||||
import type { Namespace } from "../../../common/k8s-api/endpoints";
|
||||
import { Input } from "../input";
|
||||
import { systemName } from "../input/input_validators";
|
||||
import { Notifications } from "../notifications";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
|
||||
import type { AddNamespaceDialogModel } from "./add-namespace-dialog-model/add-namespace-dialog-model";
|
||||
import addNamespaceDialogModelInjectable
|
||||
from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable";
|
||||
|
||||
interface Props extends DialogProps {
|
||||
onSuccess?(ns: Namespace): void;
|
||||
onError?(error: any): void;
|
||||
}
|
||||
|
||||
const dialogState = observable.object({
|
||||
isOpen: false,
|
||||
});
|
||||
interface Dependencies {
|
||||
createNamespace: (params: { name: string }) => Promise<Namespace>,
|
||||
model: AddNamespaceDialogModel
|
||||
}
|
||||
|
||||
@observer
|
||||
export class AddNamespaceDialog extends React.Component<Props> {
|
||||
class NonInjectedAddNamespaceDialog extends React.Component<Props & Dependencies> {
|
||||
@observable namespace = "";
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
static open() {
|
||||
dialogState.isOpen = true;
|
||||
}
|
||||
|
||||
static close() {
|
||||
dialogState.isOpen = false;
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.namespace = "";
|
||||
};
|
||||
@@ -67,10 +64,10 @@ export class AddNamespaceDialog extends React.Component<Props> {
|
||||
const { onSuccess, onError } = this.props;
|
||||
|
||||
try {
|
||||
const created = await namespaceStore.create({ name: namespace });
|
||||
const created = await this.props.createNamespace({ name: namespace });
|
||||
|
||||
onSuccess?.(created);
|
||||
AddNamespaceDialog.close();
|
||||
this.props.close();
|
||||
} catch (err) {
|
||||
Notifications.error(err);
|
||||
onError?.(err);
|
||||
@@ -78,7 +75,7 @@ export class AddNamespaceDialog extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { ...dialogProps } = this.props;
|
||||
const { model, createNamespace, ...dialogProps } = this.props;
|
||||
const { namespace } = this;
|
||||
const header = <h5>Create Namespace</h5>;
|
||||
|
||||
@@ -86,11 +83,11 @@ export class AddNamespaceDialog extends React.Component<Props> {
|
||||
<Dialog
|
||||
{...dialogProps}
|
||||
className="AddNamespaceDialog"
|
||||
isOpen={dialogState.isOpen}
|
||||
isOpen={this.props.model.isOpen}
|
||||
onOpen={this.reset}
|
||||
close={AddNamespaceDialog.close}
|
||||
close={this.props.model.close}
|
||||
>
|
||||
<Wizard header={header} done={AddNamespaceDialog.close}>
|
||||
<Wizard header={header} done={this.props.model.close}>
|
||||
<WizardStep
|
||||
contentClass="flex gaps column"
|
||||
nextLabel="Create"
|
||||
@@ -110,3 +107,16 @@ export class AddNamespaceDialog extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const AddNamespaceDialog = withInjectables<Dependencies, Props>(
|
||||
NonInjectedAddNamespaceDialog,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
createNamespace: di.inject(namespaceStoreInjectable).create,
|
||||
model: di.inject(addNamespaceDialogModelInjectable),
|
||||
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -25,7 +25,7 @@ import React from "react";
|
||||
import { computed, makeObservable, observable, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { DrawerItem } from "../drawer";
|
||||
import { boundMethod, cssNames } from "../../utils";
|
||||
import { boundMethod, cssNames, Disposer } from "../../utils";
|
||||
import { getMetricsForNamespace, IPodMetrics, Namespace } from "../../../common/k8s-api/endpoints";
|
||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -39,16 +39,24 @@ import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||
import { getDetailsUrl } from "../kube-detail-params";
|
||||
import logger from "../../../common/logger";
|
||||
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import kubeWatchApiInjectable
|
||||
from "../../kube-watch-api/kube-watch-api.injectable";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<Namespace> {
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
subscribeStores: (stores: KubeObjectStore<KubeObject>[]) => Disposer
|
||||
}
|
||||
|
||||
@observer
|
||||
export class NamespaceDetails extends React.Component<Props> {
|
||||
class NonInjectedNamespaceDetails extends React.Component<Props & Dependencies> {
|
||||
@observable metrics: IPodMetrics = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
@@ -58,7 +66,8 @@ export class NamespaceDetails extends React.Component<Props> {
|
||||
reaction(() => this.props.object, () => {
|
||||
this.metrics = null;
|
||||
}),
|
||||
kubeWatchApi.subscribeStores([
|
||||
|
||||
this.props.subscribeStores([
|
||||
resourceQuotaStore,
|
||||
limitRangeStore,
|
||||
]),
|
||||
@@ -138,3 +147,15 @@ export class NamespaceDetails extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const NamespaceDetails = withInjectables<Dependencies, Props>(
|
||||
NonInjectedNamespaceDetails,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores,
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 { NamespaceSelectFilterModel } from "./namespace-select-filter-model";
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
|
||||
const NamespaceSelectFilterModelInjectable = getInjectable({
|
||||
instantiate: () => new NamespaceSelectFilterModel(),
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default NamespaceSelectFilterModelInjectable;
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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 { observable, makeObservable, action } from "mobx";
|
||||
|
||||
export class NamespaceSelectFilterModel {
|
||||
constructor() {
|
||||
makeObservable(this, {
|
||||
menuIsOpen: observable,
|
||||
closeMenu: action,
|
||||
openMenu: action,
|
||||
toggleMenu: action,
|
||||
isMultiSelection: observable,
|
||||
setIsMultiSelection: action,
|
||||
});
|
||||
}
|
||||
|
||||
menuIsOpen = false;
|
||||
|
||||
closeMenu = () => {
|
||||
this.menuIsOpen = false;
|
||||
};
|
||||
|
||||
openMenu = () => {
|
||||
this.menuIsOpen = true;
|
||||
};
|
||||
|
||||
toggleMenu = () => {
|
||||
this.menuIsOpen = !this.menuIsOpen;
|
||||
};
|
||||
|
||||
isMultiSelection = false;
|
||||
|
||||
setIsMultiSelection = (isMultiSelection: boolean) => {
|
||||
this.isMultiSelection = isMultiSelection;
|
||||
};
|
||||
}
|
||||
@@ -24,41 +24,26 @@ import "./namespace-select-filter.scss";
|
||||
import React from "react";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { components, PlaceholderProps } from "react-select";
|
||||
import { action, computed, makeObservable, observable, reaction } from "mobx";
|
||||
import { action, makeObservable, observable, reaction } from "mobx";
|
||||
|
||||
import { Icon } from "../icon";
|
||||
import { NamespaceSelect } from "./namespace-select";
|
||||
import { namespaceStore } from "./namespace.store";
|
||||
import type { NamespaceStore } from "./namespace-store/namespace.store";
|
||||
|
||||
import type { SelectOption, SelectProps } from "../select";
|
||||
import { isMac } from "../../../common/vars";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
|
||||
import type { NamespaceSelectFilterModel } from "./namespace-select-filter-model/namespace-select-filter-model";
|
||||
import namespaceSelectFilterModelInjectable from "./namespace-select-filter-model/namespace-select-filter-model.injectable";
|
||||
|
||||
const Placeholder = observer((props: PlaceholderProps<any, boolean>) => {
|
||||
const getPlaceholder = (): React.ReactNode => {
|
||||
const namespaces = namespaceStore.contextNamespaces;
|
||||
|
||||
if (namespaceStore.areAllSelectedImplicitly || !namespaces.length) {
|
||||
return <>All namespaces</>;
|
||||
}
|
||||
|
||||
if (namespaces.length === 1) {
|
||||
return <>Namespace: {namespaces[0]}</>;
|
||||
}
|
||||
|
||||
return <>Namespaces: {namespaces.join(", ")}</>;
|
||||
};
|
||||
|
||||
return (
|
||||
<components.Placeholder {...props}>
|
||||
{getPlaceholder()}
|
||||
</components.Placeholder>
|
||||
);
|
||||
});
|
||||
interface Dependencies {
|
||||
model: NamespaceSelectFilterModel,
|
||||
namespaceStore: NamespaceStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class NamespaceSelectFilter extends React.Component<SelectProps> {
|
||||
static isMultiSelection = observable.box(false);
|
||||
static isMenuOpen = observable.box(false);
|
||||
class NonInjectedNamespaceSelectFilter extends React.Component<SelectProps & Dependencies> {
|
||||
|
||||
/**
|
||||
* Only updated on every open
|
||||
@@ -66,32 +51,20 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
|
||||
private selected = observable.set<string>();
|
||||
private didToggle = false;
|
||||
|
||||
constructor(props: SelectProps) {
|
||||
constructor(props: SelectProps & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@computed get isMultiSelection() {
|
||||
return NamespaceSelectFilter.isMultiSelection.get();
|
||||
}
|
||||
|
||||
set isMultiSelection(val: boolean) {
|
||||
NamespaceSelectFilter.isMultiSelection.set(val);
|
||||
}
|
||||
|
||||
@computed get isMenuOpen() {
|
||||
return NamespaceSelectFilter.isMenuOpen.get();
|
||||
}
|
||||
|
||||
set isMenuOpen(val: boolean) {
|
||||
NamespaceSelectFilter.isMenuOpen.set(val);
|
||||
get model() {
|
||||
return this.props.model;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.isMenuOpen, newVal => {
|
||||
reaction(() => this.model.menuIsOpen, newVal => {
|
||||
if (newVal) { // rising edge of selection
|
||||
this.selected.replace(namespaceStore.selectedNames);
|
||||
this.selected.replace(this.props.namespaceStore.selectedNames);
|
||||
this.didToggle = false;
|
||||
}
|
||||
}),
|
||||
@@ -100,7 +73,7 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
|
||||
|
||||
formatOptionLabel({ value: namespace, label }: SelectOption) {
|
||||
if (namespace) {
|
||||
const isSelected = namespaceStore.hasContext(namespace);
|
||||
const isSelected = this.props.namespaceStore.hasContext(namespace);
|
||||
|
||||
return (
|
||||
<div className="flex gaps align-center">
|
||||
@@ -117,14 +90,14 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
|
||||
@action
|
||||
onChange = ([{ value: namespace }]: SelectOption[]) => {
|
||||
if (namespace) {
|
||||
if (this.isMultiSelection) {
|
||||
if (this.model.isMultiSelection) {
|
||||
this.didToggle = true;
|
||||
namespaceStore.toggleSingle(namespace);
|
||||
this.props.namespaceStore.toggleSingle(namespace);
|
||||
} else {
|
||||
namespaceStore.selectSingle(namespace);
|
||||
this.props.namespaceStore.selectSingle(namespace);
|
||||
}
|
||||
} else {
|
||||
namespaceStore.selectAll();
|
||||
this.props.namespaceStore.selectAll();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,33 +112,33 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
|
||||
@action
|
||||
onKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (this.isSelectionKey(e)) {
|
||||
this.isMultiSelection = true;
|
||||
this.model.setIsMultiSelection(true);
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
onKeyUp = (e: React.KeyboardEvent) => {
|
||||
if (this.isSelectionKey(e)) {
|
||||
this.isMultiSelection = false;
|
||||
this.model.setIsMultiSelection(false);
|
||||
}
|
||||
|
||||
if (!this.isMultiSelection && this.didToggle) {
|
||||
this.isMenuOpen = false;
|
||||
if (!this.model.isMultiSelection && this.didToggle) {
|
||||
this.model.closeMenu();
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
onClick = () => {
|
||||
if (!this.isMenuOpen) {
|
||||
this.isMenuOpen = true;
|
||||
} else if (!this.isMultiSelection) {
|
||||
this.isMenuOpen = !this.isMenuOpen;
|
||||
if (!this.model.menuIsOpen) {
|
||||
this.model.openMenu();
|
||||
} else if (!this.model.isMultiSelection) {
|
||||
this.model.toggleMenu();
|
||||
}
|
||||
};
|
||||
|
||||
reset = () => {
|
||||
this.isMultiSelection = false;
|
||||
this.isMenuOpen = false;
|
||||
this.model.setIsMultiSelection(true);
|
||||
this.model.closeMenu();
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -173,7 +146,7 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
|
||||
<div onKeyUp={this.onKeyUp} onKeyDown={this.onKeyDown} onClick={this.onClick}>
|
||||
<NamespaceSelect
|
||||
isMulti={true}
|
||||
menuIsOpen={this.isMenuOpen}
|
||||
menuIsOpen={this.model.menuIsOpen}
|
||||
components={{ Placeholder }}
|
||||
showAllNamespacesOption={true}
|
||||
closeMenuOnSelect={false}
|
||||
@@ -190,3 +163,58 @@ export class NamespaceSelectFilter extends React.Component<SelectProps> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const NamespaceSelectFilter = withInjectables<Dependencies, SelectProps>(
|
||||
NonInjectedNamespaceSelectFilter,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
model: di.inject(namespaceSelectFilterModelInjectable),
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
type CustomPlaceholderProps = PlaceholderProps<any, boolean>;
|
||||
|
||||
interface PlaceholderDependencies {
|
||||
namespaceStore: NamespaceStore
|
||||
}
|
||||
|
||||
const NonInjectedPlaceholder = observer(
|
||||
({ namespaceStore, ...props }: CustomPlaceholderProps & PlaceholderDependencies) => {
|
||||
const getPlaceholder = (): React.ReactNode => {
|
||||
const namespaces = namespaceStore.contextNamespaces;
|
||||
|
||||
if (namespaceStore.areAllSelectedImplicitly || !namespaces.length) {
|
||||
return <>All namespaces</>;
|
||||
}
|
||||
|
||||
if (namespaces.length === 1) {
|
||||
return <>Namespace: {namespaces[0]}</>;
|
||||
}
|
||||
|
||||
return <>Namespaces: {namespaces.join(", ")}</>;
|
||||
};
|
||||
|
||||
return (
|
||||
<components.Placeholder {...props}>
|
||||
{getPlaceholder()}
|
||||
</components.Placeholder>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
const Placeholder = withInjectables<PlaceholderDependencies, CustomPlaceholderProps>(
|
||||
NonInjectedPlaceholder,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -27,7 +27,9 @@ import { observer } from "mobx-react";
|
||||
import { Select, SelectOption, SelectProps } from "../select";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Icon } from "../icon";
|
||||
import { namespaceStore } from "./namespace.store";
|
||||
import type { NamespaceStore } from "./namespace-store/namespace.store";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
|
||||
|
||||
interface Props extends SelectProps {
|
||||
showIcons?: boolean;
|
||||
@@ -40,11 +42,15 @@ const defaultProps: Partial<Props> = {
|
||||
showIcons: true,
|
||||
};
|
||||
|
||||
interface Dependencies {
|
||||
namespaceStore: NamespaceStore
|
||||
}
|
||||
|
||||
@observer
|
||||
export class NamespaceSelect extends React.Component<Props> {
|
||||
class NonInjectedNamespaceSelect extends React.Component<Props & Dependencies> {
|
||||
static defaultProps = defaultProps as object;
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: Props & Dependencies) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
@@ -53,7 +59,7 @@ export class NamespaceSelect extends React.Component<Props> {
|
||||
|
||||
@computed.struct get options(): SelectOption[] {
|
||||
const { customizeOptions, showAllNamespacesOption, sort } = this.props;
|
||||
let options: SelectOption[] = namespaceStore.items.map(ns => ({ value: ns.getName() }));
|
||||
let options: SelectOption[] = this.props.namespaceStore.items.map(ns => ({ value: ns.getName() }));
|
||||
|
||||
if (sort) {
|
||||
options.sort(sort);
|
||||
@@ -97,3 +103,14 @@ export class NamespaceSelect extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const NamespaceSelect = withInjectables<Dependencies, Props>(
|
||||
NonInjectedNamespaceSelect,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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 { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { NamespaceStore } from "./namespace.store";
|
||||
import apiManagerInjectable from "../../kube-object-menu/dependencies/api-manager.injectable";
|
||||
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
|
||||
|
||||
const namespaceStoreInjectable = getInjectable({
|
||||
instantiate: (di) => {
|
||||
const createStorage = di.inject(createStorageInjectable);
|
||||
|
||||
const storage = createStorage<string[] | undefined>(
|
||||
"selected_namespaces",
|
||||
undefined,
|
||||
);
|
||||
|
||||
const namespaceStore = new NamespaceStore({
|
||||
storage,
|
||||
});
|
||||
|
||||
const apiManager = di.inject(apiManagerInjectable);
|
||||
|
||||
apiManager.registerStore(namespaceStore);
|
||||
|
||||
return namespaceStore;
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
});
|
||||
|
||||
export default namespaceStoreInjectable;
|
||||
+20
-22
@@ -20,16 +20,18 @@
|
||||
*/
|
||||
|
||||
import { action, comparer, computed, IReactionDisposer, makeObservable, reaction } from "mobx";
|
||||
import { autoBind, createStorage, noop, ToggleSet } from "../../utils";
|
||||
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../../common/k8s-api/kube-object.store";
|
||||
import { Namespace, namespacesApi } from "../../../common/k8s-api/endpoints/namespaces.api";
|
||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||
import { autoBind, noop, StorageHelper, ToggleSet } from "../../../utils";
|
||||
import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../../../common/k8s-api/kube-object.store";
|
||||
import { Namespace, namespacesApi } from "../../../../common/k8s-api/endpoints/namespaces.api";
|
||||
|
||||
interface Dependencies {
|
||||
storage: StorageHelper<string[] | undefined>
|
||||
}
|
||||
|
||||
export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
api = namespacesApi;
|
||||
private storage = createStorage<string[] | undefined>("selected_namespaces", undefined);
|
||||
|
||||
constructor() {
|
||||
constructor(private dependencies: Dependencies) {
|
||||
super();
|
||||
makeObservable(this);
|
||||
autoBind(this);
|
||||
@@ -39,7 +41,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
|
||||
private async init() {
|
||||
await this.contextReady;
|
||||
await this.storage.whenReady;
|
||||
await this.dependencies.storage.whenReady;
|
||||
|
||||
this.selectNamespaces(this.initialNamespaces);
|
||||
this.autoLoadAllowedNamespaces();
|
||||
@@ -61,7 +63,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
|
||||
private get initialNamespaces(): string[] {
|
||||
const { allowedNamespaces } = this;
|
||||
const selectedNamespaces = this.storage.get(); // raw namespaces, undefined on first load
|
||||
const selectedNamespaces = this.dependencies.storage.get(); // raw namespaces, undefined on first load
|
||||
|
||||
// return previously saved namespaces from local-storage (if any)
|
||||
if (Array.isArray(selectedNamespaces)) {
|
||||
@@ -83,7 +85,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
* The current value (list of namespaces names) in the storage layer
|
||||
*/
|
||||
@computed private get selectedNamespaces(): string[] {
|
||||
return this.storage.get() ?? [];
|
||||
return this.dependencies.storage.get() ?? [];
|
||||
}
|
||||
|
||||
@computed get allowedNamespaces(): string[] {
|
||||
@@ -147,22 +149,21 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
return namespaces;
|
||||
}
|
||||
|
||||
@action
|
||||
selectNamespaces(namespace: string | string[]) {
|
||||
@action selectNamespaces = (namespace: string | string[]) => {
|
||||
const namespaces = Array.from(new Set([namespace].flat()));
|
||||
|
||||
this.storage.set(namespaces);
|
||||
}
|
||||
this.dependencies.storage.set(namespaces);
|
||||
};
|
||||
|
||||
@action
|
||||
clearSelected(namespaces?: string | string[]) {
|
||||
if (namespaces) {
|
||||
const resettingNamespaces = [namespaces].flat();
|
||||
const newNamespaces = this.storage.get().filter(ns => !resettingNamespaces.includes(ns));
|
||||
const newNamespaces = this.dependencies.storage.get().filter(ns => !resettingNamespaces.includes(ns));
|
||||
|
||||
this.storage.set(newNamespaces);
|
||||
this.dependencies.storage.set(newNamespaces);
|
||||
} else {
|
||||
this.storage.reset();
|
||||
this.dependencies.storage.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +197,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
nextState.toggle(namespace);
|
||||
}
|
||||
|
||||
this.storage.set([...nextState]);
|
||||
this.dependencies.storage.set([...nextState]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,14 +210,14 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
const nextState = new ToggleSet(this.contextNamespaces);
|
||||
|
||||
nextState.toggle(namespace);
|
||||
this.storage.set([...nextState]);
|
||||
this.dependencies.storage.set([...nextState]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the given namespace the sole selected namespace
|
||||
*/
|
||||
selectSingle(namespace: string) {
|
||||
this.storage.set([namespace]);
|
||||
this.dependencies.storage.set([namespace]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,9 +248,6 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||
}
|
||||
}
|
||||
|
||||
export const namespaceStore = new NamespaceStore();
|
||||
apiManager.registerStore(namespaceStore);
|
||||
|
||||
export function getDummyNamespace(name: string) {
|
||||
return new Namespace({
|
||||
kind: Namespace.kind,
|
||||
@@ -28,9 +28,13 @@ import { TabLayout } from "../layout/tab-layout";
|
||||
import { Badge } from "../badge";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { namespaceStore } from "./namespace.store";
|
||||
import type { NamespaceStore } from "./namespace-store/namespace.store";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { NamespacesRouteParams } from "../../../common/routes";
|
||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
|
||||
import addNamespaceDialogModelInjectable
|
||||
from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@@ -42,7 +46,12 @@ enum columnId {
|
||||
interface Props extends RouteComponentProps<NamespacesRouteParams> {
|
||||
}
|
||||
|
||||
export class Namespaces extends React.Component<Props> {
|
||||
interface Dependencies {
|
||||
namespaceStore: NamespaceStore
|
||||
openAddNamespaceDialog: () => void
|
||||
}
|
||||
|
||||
class NonInjectedNamespaces extends React.Component<Props & Dependencies> {
|
||||
render() {
|
||||
return (
|
||||
<TabLayout>
|
||||
@@ -50,7 +59,7 @@ export class Namespaces extends React.Component<Props> {
|
||||
isConfigurable
|
||||
tableId="namespaces"
|
||||
className="Namespaces"
|
||||
store={namespaceStore}
|
||||
store={this.props.namespaceStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: ns => ns.getName(),
|
||||
[columnId.labels]: ns => ns.getLabels(),
|
||||
@@ -78,7 +87,7 @@ export class Namespaces extends React.Component<Props> {
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
addTooltip: "Add Namespace",
|
||||
onAdd: () => AddNamespaceDialog.open(),
|
||||
onAdd: () => this.props.openAddNamespaceDialog(),
|
||||
}}
|
||||
customizeTableRowProps={item => ({
|
||||
disabled: item.getStatus() === NamespaceStatus.TERMINATING,
|
||||
@@ -89,3 +98,15 @@ export class Namespaces extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const Namespaces = withInjectables<Dependencies, Props>(
|
||||
NonInjectedNamespaces,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
namespaceStore: di.inject(namespaceStoreInjectable),
|
||||
openAddNamespaceDialog: di.inject(addNamespaceDialogModelInjectable).open,
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user