[] = [];
topBarItems: TopBarRegistration[] = [];
additionalCategoryColumns: AdditionalCategoryColumnRegistration[] = [];
+ customCategoryViews: CustomCategoryViewRegistration[] = [];
async navigate(pageId?: string, params?: P) {
const { navigate } = await import("../renderer/navigation");
diff --git a/src/main/routes/port-forward-route.ts b/src/main/routes/port-forward-route.ts
index f2b1ecaeb4..4d688d68a6 100644
--- a/src/main/routes/port-forward-route.ts
+++ b/src/main/routes/port-forward-route.ts
@@ -14,11 +14,10 @@ export class PortForwardRoute {
const { namespace, resourceType, resourceName } = params;
const port = Number(query.get("port"));
const forwardPort = Number(query.get("forwardPort"));
- const protocol = query.get("protocol");
const portForward = PortForward.getPortforward({
clusterId: cluster.id, kind: resourceType, name: resourceName,
- namespace, port, forwardPort, protocol,
+ namespace, port, forwardPort,
});
respondJson(response, { port: portForward?.forwardPort ?? null });
diff --git a/src/main/routes/port-forward/port-forward.ts b/src/main/routes/port-forward/port-forward.ts
index 9ffeb9d851..91eaddc13f 100644
--- a/src/main/routes/port-forward/port-forward.ts
+++ b/src/main/routes/port-forward/port-forward.ts
@@ -16,7 +16,6 @@ export interface PortForwardArgs {
name: string;
port: number;
forwardPort: number;
- protocol?: string;
}
interface Dependencies {
@@ -32,8 +31,7 @@ export class PortForward {
pf.kind == forward.kind &&
pf.name == forward.name &&
pf.namespace == forward.namespace &&
- pf.port == forward.port &&
- (!forward.protocol || pf.protocol == forward.protocol)
+ pf.port == forward.port
));
}
@@ -44,7 +42,6 @@ export class PortForward {
public name: string;
public port: number;
public forwardPort: number;
- public protocol: string;
constructor(private dependencies: Dependencies, public pathToKubeConfig: string, args: PortForwardArgs) {
this.clusterId = args.clusterId;
@@ -53,7 +50,6 @@ export class PortForward {
this.name = args.name;
this.port = args.port;
this.forwardPort = args.forwardPort;
- this.protocol = args.protocol ?? "http";
}
public async start() {
diff --git a/src/main/routes/port-forward/route-port-forward/route-port-forward.ts b/src/main/routes/port-forward/route-port-forward/route-port-forward.ts
index 79e6b4626d..267bc7529c 100644
--- a/src/main/routes/port-forward/route-port-forward/route-port-forward.ts
+++ b/src/main/routes/port-forward/route-port-forward/route-port-forward.ts
@@ -18,7 +18,6 @@ export const routePortForward =
const { namespace, resourceType, resourceName } = params;
const port = Number(query.get("port"));
const forwardPort = Number(query.get("forwardPort"));
- const protocol = query.get("protocol");
try {
let portForward = PortForward.getPortforward({
@@ -28,7 +27,6 @@ export const routePortForward =
namespace,
port,
forwardPort,
- protocol,
});
if (!portForward) {
@@ -46,7 +44,6 @@ export const routePortForward =
name: resourceName,
port,
forwardPort: thePort,
- protocol,
});
const started = await portForward.start();
diff --git a/src/renderer/components/+apps-releases/create-release/create-release.injectable.ts b/src/renderer/components/+apps-releases/create-release/create-release.injectable.ts
new file mode 100644
index 0000000000..17c31ff6c2
--- /dev/null
+++ b/src/renderer/components/+apps-releases/create-release/create-release.injectable.ts
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import {
+ createRelease,
+ IReleaseCreatePayload,
+} from "../../../../common/k8s-api/endpoints/helm-releases.api";
+import releasesInjectable from "../releases.injectable";
+
+const createReleaseInjectable = getInjectable({
+ instantiate: (di) => {
+ const releases = di.inject(releasesInjectable);
+
+ return async (payload: IReleaseCreatePayload) => {
+ const release = await createRelease(payload);
+
+ releases.invalidate();
+
+ return release;
+ };
+ },
+
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default createReleaseInjectable;
diff --git a/src/renderer/components/+apps-releases/delete-release/delete-release.injectable.ts b/src/renderer/components/+apps-releases/delete-release/delete-release.injectable.ts
new file mode 100644
index 0000000000..df2e4fa1e9
--- /dev/null
+++ b/src/renderer/components/+apps-releases/delete-release/delete-release.injectable.ts
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import {
+ deleteRelease,
+ HelmRelease,
+} from "../../../../common/k8s-api/endpoints/helm-releases.api";
+import releasesInjectable from "../releases.injectable";
+
+const deleteReleaseInjectable = getInjectable({
+ instantiate: (di) => {
+ const releases = di.inject(releasesInjectable);
+
+ return async (release: HelmRelease) => {
+ await deleteRelease(release.getName(), release.getNs());
+
+ releases.invalidate();
+ };
+ },
+
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default deleteReleaseInjectable;
diff --git a/src/renderer/components/+apps-releases/release-details.tsx b/src/renderer/components/+apps-releases/release-details.tsx
deleted file mode 100644
index 7712ea4052..0000000000
--- a/src/renderer/components/+apps-releases/release-details.tsx
+++ /dev/null
@@ -1,321 +0,0 @@
-/**
- * Copyright (c) OpenLens Authors. All rights reserved.
- * Licensed under MIT License. See LICENSE in root directory for more information.
- */
-
-import "./release-details.scss";
-
-import React, { Component } from "react";
-import groupBy from "lodash/groupBy";
-import isEqual from "lodash/isEqual";
-import { makeObservable, observable, reaction } from "mobx";
-import { Link } from "react-router-dom";
-import kebabCase from "lodash/kebabCase";
-import { getRelease, getReleaseValues, HelmRelease, IReleaseDetails } from "../../../common/k8s-api/endpoints/helm-releases.api";
-import { HelmReleaseMenu } from "./release-menu";
-import { Drawer, DrawerItem, DrawerTitle } from "../drawer";
-import { Badge } from "../badge";
-import { cssNames, stopPropagation } from "../../utils";
-import { disposeOnUnmount, observer } from "mobx-react";
-import { Spinner } from "../spinner";
-import { Table, TableCell, TableHead, TableRow } from "../table";
-import { Button } from "../button";
-import type { ReleaseStore } from "./release.store";
-import { Notifications } from "../notifications";
-import { ThemeStore } from "../../theme.store";
-import { apiManager } from "../../../common/k8s-api/api-manager";
-import { SubTitle } from "../layout/sub-title";
-import { secretsStore } from "../+config-secrets/secrets.store";
-import { Secret } from "../../../common/k8s-api/endpoints";
-import { getDetailsUrl } from "../kube-detail-params";
-import { Checkbox } from "../checkbox";
-import { MonacoEditor } from "../monaco-editor";
-import { withInjectables } from "@ogre-tools/injectable-react";
-import releaseStoreInjectable from "./release-store.injectable";
-import createUpgradeChartTabInjectable
- from "../dock/create-upgrade-chart-tab/create-upgrade-chart-tab.injectable";
-
-interface Props {
- release: HelmRelease;
- hideDetails(): void;
-}
-
-interface Dependencies {
- releaseStore: ReleaseStore
- createUpgradeChartTab: (release: HelmRelease) => void
-}
-
-@observer
-class NonInjectedReleaseDetails extends Component {
- @observable details: IReleaseDetails | null = null;
- @observable values = "";
- @observable valuesLoading = false;
- @observable showOnlyUserSuppliedValues = true;
- @observable saving = false;
- @observable releaseSecret: Secret;
- @observable error?: string = undefined;
-
- componentDidMount() {
- disposeOnUnmount(this, [
- reaction(() => this.props.release, release => {
- if (!release) return;
- this.loadDetails();
- this.loadValues();
- this.releaseSecret = null;
- }),
- reaction(() => secretsStore.getItems(), () => {
- if (!this.props.release) return;
- const { getReleaseSecret } = this.props.releaseStore;
- const { release } = this.props;
- const secret = getReleaseSecret(release);
-
- if (this.releaseSecret) {
- if (isEqual(this.releaseSecret.getLabels(), secret.getLabels())) return;
- this.loadDetails();
- }
- this.releaseSecret = secret;
- }),
- reaction(() => this.showOnlyUserSuppliedValues, () => {
- this.loadValues();
- }),
- ]);
- }
-
- constructor(props: Props & Dependencies) {
- super(props);
- makeObservable(this);
- }
-
- async loadDetails() {
- const { release } = this.props;
-
- try {
- this.details = null;
- this.details = await getRelease(release.getName(), release.getNs());
- } catch (error) {
- this.error = `Failed to get release details: ${error}`;
- }
- }
-
- async loadValues() {
- const { release } = this.props;
-
- try {
- this.valuesLoading = true;
- this.values = (await getReleaseValues(release.getName(), release.getNs(), !this.showOnlyUserSuppliedValues)) ?? "";
- } catch (error) {
- Notifications.error(`Failed to load values for ${release.getName()}: ${error}`);
- this.values = "";
- } finally {
- this.valuesLoading = false;
- }
- }
-
- updateValues = async () => {
- const { release } = this.props;
- const name = release.getName();
- const namespace = release.getNs();
- const data = {
- chart: release.getChart(),
- repo: await release.getRepo(),
- version: release.getVersion(),
- values: this.values,
- };
-
- this.saving = true;
-
- try {
- await this.props.releaseStore.update(name, namespace, data);
- Notifications.ok(
- Release {name} successfully updated!
,
- );
- } catch (err) {
- Notifications.error(err);
- }
- this.saving = false;
- };
-
- upgradeVersion = () => {
- const { release, hideDetails } = this.props;
-
- this.props.createUpgradeChartTab(release);
- hideDetails();
- };
-
- renderValues() {
- const { values, valuesLoading, saving } = this;
-
- return (
-
-
-
- this.showOnlyUserSuppliedValues = value}
- disabled={valuesLoading}
- />
- this.values = text}
- >
- {valuesLoading && }
-
-
-
-
- );
- }
-
- renderNotes() {
- if (!this.details.info?.notes) return null;
- const { notes } = this.details.info;
-
- return (
-
- {notes}
-
- );
- }
-
- renderResources() {
- const { resources } = this.details;
-
- if (!resources) return null;
- const groups = groupBy(resources, item => item.kind);
- const tables = Object.entries(groups).map(([kind, items]) => {
- return (
-
-
-
-
- Name
- {items[0].getNs() && Namespace }
- Age
-
- {items.map(item => {
- const name = item.getName();
- const namespace = item.getNs();
- const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == item.apiVersion);
- const detailsUrl = api ? getDetailsUrl(api.getUrl({ name, namespace })) : "";
-
- return (
-
-
- {detailsUrl ? {name} : name}
-
- {namespace && {namespace} }
- {item.getAge()}
-
- );
- })}
-
-
- );
- });
-
- return (
-
- {tables}
-
- );
- }
-
- renderContent() {
- const { release } = this.props;
-
- if (!release) return null;
-
- if (this.error) {
- return (
-
- {this.error}
-
- );
- }
-
- if (!this.details) {
- return ;
- }
-
- return (
-
-
-
- {release.getChart()}
-
-
-
-
- {release.getUpdated()} ago ({release.updated})
-
-
- {release.getNs()}
-
-
-
-
- {release.getVersion()}
-
-
-
-
-
-
- {this.renderValues()}
-
- {this.renderNotes()}
-
- {this.renderResources()}
-
- );
- }
-
- render() {
- const { release, hideDetails } = this.props;
- const title = release ? `Release: ${release.getName()}` : "";
- const toolbar = ;
-
- return (
-
- {this.renderContent()}
-
- );
- }
-}
-
-export const ReleaseDetails = withInjectables(
- NonInjectedReleaseDetails,
-
- {
- getProps: (di, props) => ({
- releaseStore: di.inject(releaseStoreInjectable),
- createUpgradeChartTab: di.inject(createUpgradeChartTabInjectable),
- ...props,
- }),
- },
-);
diff --git a/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts b/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts
new file mode 100644
index 0000000000..1dac3b716f
--- /dev/null
+++ b/src/renderer/components/+apps-releases/release-details/release-details.injectable.ts
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { getRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
+import { asyncComputed } from "@ogre-tools/injectable-react";
+import releaseInjectable from "./release.injectable";
+
+const releaseDetailsInjectable = getInjectable({
+ instantiate: (di) =>
+ asyncComputed(async () => {
+ const release = di.inject(releaseInjectable).value.get();
+
+ return await getRelease(release.name, release.namespace);
+ }),
+
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default releaseDetailsInjectable;
diff --git a/src/renderer/components/+apps-releases/release-details.scss b/src/renderer/components/+apps-releases/release-details/release-details.scss
similarity index 97%
rename from src/renderer/components/+apps-releases/release-details.scss
rename to src/renderer/components/+apps-releases/release-details/release-details.scss
index 20ce0a2cef..0a4acfcca3 100644
--- a/src/renderer/components/+apps-releases/release-details.scss
+++ b/src/renderer/components/+apps-releases/release-details/release-details.scss
@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
-@import "release.mixins";
+@import "../release.mixins";
.ReleaseDetails {
.DrawerItem {
diff --git a/src/renderer/components/+apps-releases/release-details/release-details.tsx b/src/renderer/components/+apps-releases/release-details/release-details.tsx
new file mode 100644
index 0000000000..77f6ab2d87
--- /dev/null
+++ b/src/renderer/components/+apps-releases/release-details/release-details.tsx
@@ -0,0 +1,280 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+
+import "./release-details.scss";
+
+import React, { Component } from "react";
+import groupBy from "lodash/groupBy";
+import { computed, makeObservable, observable } from "mobx";
+import { Link } from "react-router-dom";
+import kebabCase from "lodash/kebabCase";
+import type { HelmRelease, IReleaseDetails, IReleaseUpdateDetails, IReleaseUpdatePayload } from "../../../../common/k8s-api/endpoints/helm-releases.api";
+import { HelmReleaseMenu } from "../release-menu";
+import { Drawer, DrawerItem, DrawerTitle } from "../../drawer";
+import { Badge } from "../../badge";
+import { cssNames, stopPropagation } from "../../../utils";
+import { Observer, observer } from "mobx-react";
+import { Spinner } from "../../spinner";
+import { Table, TableCell, TableHead, TableRow } from "../../table";
+import { Button } from "../../button";
+import { Notifications } from "../../notifications";
+import { ThemeStore } from "../../../theme.store";
+import { apiManager } from "../../../../common/k8s-api/api-manager";
+import { SubTitle } from "../../layout/sub-title";
+import { getDetailsUrl } from "../../kube-detail-params";
+import { Checkbox } from "../../checkbox";
+import { MonacoEditor } from "../../monaco-editor";
+import { IAsyncComputed, withInjectables } from "@ogre-tools/injectable-react";
+import createUpgradeChartTabInjectable from "../../dock/create-upgrade-chart-tab/create-upgrade-chart-tab.injectable";
+import updateReleaseInjectable from "../update-release/update-release.injectable";
+import releaseInjectable from "./release.injectable";
+import releaseDetailsInjectable from "./release-details.injectable";
+import releaseValuesInjectable from "./release-values.injectable";
+import userSuppliedValuesAreShownInjectable from "./user-supplied-values-are-shown.injectable";
+
+interface Props {
+ hideDetails(): void;
+}
+
+interface Dependencies {
+ release: IAsyncComputed
+ releaseDetails: IAsyncComputed
+ releaseValues: IAsyncComputed
+ updateRelease: (name: string, namespace: string, payload: IReleaseUpdatePayload) => Promise
+ createUpgradeChartTab: (release: HelmRelease) => void
+ userSuppliedValuesAreShown: { toggle: () => void, value: boolean }
+}
+
+@observer
+class NonInjectedReleaseDetails extends Component {
+ @observable saving = false;
+
+ private nonSavedValues: string;
+
+ constructor(props: Props & Dependencies) {
+ super(props);
+ makeObservable(this);
+ }
+
+ @computed get release() {
+ return this.props.release.value.get();
+ }
+
+ @computed get details() {
+ return this.props.releaseDetails.value.get();
+ }
+
+ updateValues = async () => {
+ const name = this.release.getName();
+ const namespace = this.release.getNs();
+ const data = {
+ chart: this.release.getChart(),
+ repo: await this.release.getRepo(),
+ version: this.release.getVersion(),
+ values: this.nonSavedValues,
+ };
+
+ this.saving = true;
+
+ try {
+ await this.props.updateRelease(name, namespace, data);
+ Notifications.ok(
+ Release {name} successfully updated!
,
+ );
+
+ this.props.releaseValues.invalidate();
+ } catch (err) {
+ Notifications.error(err);
+ }
+ this.saving = false;
+ };
+
+ upgradeVersion = () => {
+ const { hideDetails } = this.props;
+
+ this.props.createUpgradeChartTab(this.release);
+ hideDetails();
+ };
+
+ renderValues() {
+ return (
+
+ {() => {
+ const { saving } = this;
+
+ const releaseValuesArePending =
+ this.props.releaseValues.pending.get();
+
+ this.nonSavedValues = this.props.releaseValues.value.get();
+
+ return (
+
+
+
+
+ (this.nonSavedValues = text)}
+ />
+
+
+
+ );
+ }}
+
+ );
+ }
+
+ renderNotes() {
+ if (!this.details.info?.notes) return null;
+ const { notes } = this.details.info;
+
+ return (
+
+ {notes}
+
+ );
+ }
+
+ renderResources() {
+ const { resources } = this.details;
+
+ if (!resources) return null;
+ const groups = groupBy(resources, item => item.kind);
+ const tables = Object.entries(groups).map(([kind, items]) => {
+ return (
+
+
+
+
+ Name
+ {items[0].getNs() && Namespace }
+ Age
+
+ {items.map(item => {
+ const name = item.getName();
+ const namespace = item.getNs();
+ const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == item.apiVersion);
+ const detailsUrl = api ? getDetailsUrl(api.getUrl({ name, namespace })) : "";
+
+ return (
+
+
+ {detailsUrl ? {name} : name}
+
+ {namespace && {namespace} }
+ {item.getAge()}
+
+ );
+ })}
+
+
+ );
+ });
+
+ return (
+
+ {tables}
+
+ );
+ }
+
+ renderContent() {
+ if (!this.release) return null;
+
+ if (!this.details) {
+ return ;
+ }
+
+ return (
+
+
+
+ {this.release.getChart()}
+
+
+
+
+ {this.release.getUpdated()} ago ({this.release.updated})
+
+
+ {this.release.getNs()}
+
+
+
+
+ {this.release.getVersion()}
+
+
+
+
+
+
+ {this.renderValues()}
+
+ {this.renderNotes()}
+
+ {this.renderResources()}
+
+ );
+ }
+
+ render() {
+ const { hideDetails } = this.props;
+ const title = this.release ? `Release: ${this.release.getName()}` : "";
+ const toolbar = ;
+
+ return (
+
+ {this.renderContent()}
+
+ );
+ }
+}
+
+export const ReleaseDetails = withInjectables(
+ NonInjectedReleaseDetails,
+
+ {
+ getProps: (di, props) => ({
+ release: di.inject(releaseInjectable),
+ releaseDetails: di.inject(releaseDetailsInjectable),
+ releaseValues: di.inject(releaseValuesInjectable),
+
+ userSuppliedValuesAreShown: di.inject(userSuppliedValuesAreShownInjectable),
+
+ updateRelease: di.inject(updateReleaseInjectable),
+ createUpgradeChartTab: di.inject(createUpgradeChartTabInjectable),
+ ...props,
+ }),
+ },
+);
diff --git a/src/renderer/components/+apps-releases/release-details/release-route-parameters.injectable.ts b/src/renderer/components/+apps-releases/release-details/release-route-parameters.injectable.ts
new file mode 100644
index 0000000000..d179c539be
--- /dev/null
+++ b/src/renderer/components/+apps-releases/release-details/release-route-parameters.injectable.ts
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { computed } from "mobx";
+import { matchPath } from "react-router";
+import observableHistoryInjectable from "../../../navigation/observable-history.injectable";
+import { releaseRoute, ReleaseRouteParams } from "../../../../common/routes";
+
+const releaseRouteParametersInjectable = getInjectable({
+ instantiate: (di) => {
+ const observableHistory = di.inject(observableHistoryInjectable);
+
+ return computed(() => {
+ const releasePathParameters = matchPath(observableHistory.location.pathname, {
+ path: releaseRoute.path,
+ });
+
+ if (!releasePathParameters) {
+ return {};
+ }
+
+ return releasePathParameters.params;
+ });
+ },
+
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default releaseRouteParametersInjectable;
diff --git a/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts b/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts
new file mode 100644
index 0000000000..d7cc2c965c
--- /dev/null
+++ b/src/renderer/components/+apps-releases/release-details/release-values.injectable.ts
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { getReleaseValues } from "../../../../common/k8s-api/endpoints/helm-releases.api";
+import { asyncComputed } from "@ogre-tools/injectable-react";
+import releaseInjectable from "./release.injectable";
+import { Notifications } from "../../notifications";
+import userSuppliedValuesAreShownInjectable from "./user-supplied-values-are-shown.injectable";
+
+const releaseValuesInjectable = getInjectable({
+ instantiate: (di) =>
+ asyncComputed(async () => {
+ const release = di.inject(releaseInjectable).value.get();
+ const userSuppliedValuesAreShown = di.inject(userSuppliedValuesAreShownInjectable).value;
+
+ try {
+ return await getReleaseValues(release.getName(), release.getNs(), !userSuppliedValuesAreShown) ?? "";
+ } catch (error) {
+ Notifications.error(`Failed to load values for ${release.getName()}: ${error}`);
+
+ return "";
+ }
+ }),
+
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default releaseValuesInjectable;
diff --git a/src/renderer/components/+apps-releases/release-details/release.injectable.ts b/src/renderer/components/+apps-releases/release-details/release.injectable.ts
new file mode 100644
index 0000000000..6da2295765
--- /dev/null
+++ b/src/renderer/components/+apps-releases/release-details/release.injectable.ts
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { matches } from "lodash/fp";
+import releasesInjectable from "../releases.injectable";
+import releaseRouteParametersInjectable from "./release-route-parameters.injectable";
+import { asyncComputed } from "@ogre-tools/injectable-react";
+
+const releaseInjectable = getInjectable({
+ instantiate: (di) => {
+ const releases = di.inject(releasesInjectable);
+ const releaseRouteParameters = di.inject(releaseRouteParametersInjectable);
+
+ return asyncComputed(async () => {
+ const { name, namespace } = releaseRouteParameters.get();
+
+ if (!name || !namespace) {
+ return null;
+ }
+
+ return releases.value.get().find(matches({ name, namespace }));
+ });
+ },
+
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default releaseInjectable;
diff --git a/src/renderer/components/+apps-releases/release-details/user-supplied-values-are-shown.injectable.ts b/src/renderer/components/+apps-releases/release-details/user-supplied-values-are-shown.injectable.ts
new file mode 100644
index 0000000000..374f982882
--- /dev/null
+++ b/src/renderer/components/+apps-releases/release-details/user-supplied-values-are-shown.injectable.ts
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { observable } from "mobx";
+
+const userSuppliedValuesAreShownInjectable = getInjectable({
+ instantiate: () => {
+ const state = observable.box(false);
+
+ return {
+ get value() {
+ return state.get();
+ },
+
+ toggle: () => {
+ state.set(!state.get());
+ },
+ };
+ },
+
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default userSuppliedValuesAreShownInjectable;
+
diff --git a/src/renderer/components/+apps-releases/release-menu.tsx b/src/renderer/components/+apps-releases/release-menu.tsx
index a4d4587e86..a1dc263806 100644
--- a/src/renderer/components/+apps-releases/release-menu.tsx
+++ b/src/renderer/components/+apps-releases/release-menu.tsx
@@ -6,16 +6,13 @@
import React from "react";
import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
import { cssNames } from "../../utils";
-import type { ReleaseStore } from "./release.store";
import { MenuActions, MenuActionsProps } from "../menu/menu-actions";
import { MenuItem } from "../menu";
import { Icon } from "../icon";
import { withInjectables } from "@ogre-tools/injectable-react";
-import releaseStoreInjectable from "./release-store.injectable";
-import createUpgradeChartTabInjectable
- from "../dock/create-upgrade-chart-tab/create-upgrade-chart-tab.injectable";
-import releaseRollbackDialogModelInjectable
- from "./release-rollback-dialog-model/release-rollback-dialog-model.injectable";
+import createUpgradeChartTabInjectable from "../dock/create-upgrade-chart-tab/create-upgrade-chart-tab.injectable";
+import releaseRollbackDialogModelInjectable from "./release-rollback-dialog-model/release-rollback-dialog-model.injectable";
+import deleteReleaseInjectable from "./delete-release/delete-release.injectable";
interface Props extends MenuActionsProps {
release: HelmRelease;
@@ -23,14 +20,14 @@ interface Props extends MenuActionsProps {
}
interface Dependencies {
- releaseStore: ReleaseStore
+ deleteRelease: (release: HelmRelease) => Promise
createUpgradeChartTab: (release: HelmRelease) => void
openRollbackDialog: (release: HelmRelease) => void
}
class NonInjectedHelmReleaseMenu extends React.Component {
remove = () => {
- return this.props.releaseStore.remove(this.props.release);
+ return this.props.deleteRelease(this.props.release);
};
upgrade = () => {
@@ -87,7 +84,7 @@ export const HelmReleaseMenu = withInjectables(
{
getProps: (di, props) => ({
- releaseStore: di.inject(releaseStoreInjectable),
+ deleteRelease: di.inject(deleteReleaseInjectable),
createUpgradeChartTab: di.inject(createUpgradeChartTabInjectable),
openRollbackDialog: di.inject(releaseRollbackDialogModelInjectable).open,
diff --git a/src/renderer/components/+apps-releases/release-rollback-dialog.tsx b/src/renderer/components/+apps-releases/release-rollback-dialog.tsx
index d8f731f902..03f789475d 100644
--- a/src/renderer/components/+apps-releases/release-rollback-dialog.tsx
+++ b/src/renderer/components/+apps-releases/release-rollback-dialog.tsx
@@ -15,16 +15,16 @@ import { Select, SelectOption } from "../select";
import { Notifications } from "../notifications";
import orderBy from "lodash/orderBy";
import { withInjectables } from "@ogre-tools/injectable-react";
-import releaseStoreInjectable from "./release-store.injectable";
import releaseRollbackDialogModelInjectable
from "./release-rollback-dialog-model/release-rollback-dialog-model.injectable";
import type { ReleaseRollbackDialogModel } from "./release-rollback-dialog-model/release-rollback-dialog-model";
+import rollbackReleaseInjectable from "./rollback-release/rollback-release.injectable";
interface Props extends DialogProps {
}
interface Dependencies {
- rollbackRelease: (releaseName: string, namespace: string, revisionNumber: number) => Promise
+ rollbackRelease: (releaseName: string, namespace: string, revisionNumber: number) => Promise
model: ReleaseRollbackDialogModel
}
@@ -119,7 +119,7 @@ export const ReleaseRollbackDialog = withInjectables(
{
getProps: (di, props) => ({
- rollbackRelease: di.inject(releaseStoreInjectable).rollback,
+ rollbackRelease: di.inject(rollbackReleaseInjectable),
model: di.inject(releaseRollbackDialogModelInjectable),
...props,
}),
diff --git a/src/renderer/components/+apps-releases/release-store.injectable.ts b/src/renderer/components/+apps-releases/release-store.injectable.ts
deleted file mode 100644
index 3404201071..0000000000
--- a/src/renderer/components/+apps-releases/release-store.injectable.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * Copyright (c) OpenLens Authors. All rights reserved.
- * Licensed under MIT License. See LICENSE in root directory for more information.
- */
-import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
-import { ReleaseStore } from "./release.store";
-import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable";
-
-const releaseStoreInjectable = getInjectable({
- instantiate: (di) => new ReleaseStore({
- namespaceStore: di.inject(namespaceStoreInjectable),
- }),
-
- lifecycle: lifecycleEnum.singleton,
-});
-
-export default releaseStoreInjectable;
diff --git a/src/renderer/components/+apps-releases/release.store.ts b/src/renderer/components/+apps-releases/release.store.ts
deleted file mode 100644
index 3755a70080..0000000000
--- a/src/renderer/components/+apps-releases/release.store.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-/**
- * Copyright (c) OpenLens Authors. All rights reserved.
- * Licensed under MIT License. See LICENSE in root directory for more information.
- */
-
-import isEqual from "lodash/isEqual";
-import { action, observable, reaction, when, makeObservable } from "mobx";
-import { autoBind } from "../../utils";
-import { createRelease, deleteRelease, HelmRelease, IReleaseCreatePayload, IReleaseUpdatePayload, listReleases, rollbackRelease, updateRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
-import { ItemStore } from "../../../common/item.store";
-import type { Secret } from "../../../common/k8s-api/endpoints";
-import { secretsStore } from "../+config-secrets/secrets.store";
-import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store";
-import { Notifications } from "../notifications";
-
-interface Dependencies {
- namespaceStore: NamespaceStore
-}
-
-export class ReleaseStore extends ItemStore {
- releaseSecrets = observable.map();
-
- constructor(private dependencies: Dependencies ) {
- super();
- makeObservable(this);
- autoBind(this);
-
- when(() => secretsStore.isLoaded, () => {
- this.releaseSecrets.replace(this.getReleaseSecrets());
- });
- }
-
- watchAssociatedSecrets(): (() => void) {
- return reaction(() => secretsStore.getItems(), () => {
- if (this.isLoading) return;
- const newSecrets = this.getReleaseSecrets();
- const amountChanged = newSecrets.length !== this.releaseSecrets.size;
- const labelsChanged = newSecrets.some(([id, secret]) => (
- !isEqual(secret.getLabels(), this.releaseSecrets.get(id)?.getLabels())
- ));
-
- if (amountChanged || labelsChanged) {
- this.loadFromContextNamespaces();
- }
- this.releaseSecrets.replace(newSecrets);
- }, {
- fireImmediately: true,
- });
- }
-
- watchSelectedNamespaces(): (() => void) {
- return reaction(() => this.dependencies.namespaceStore.context.contextNamespaces, namespaces => {
- this.loadAll(namespaces);
- }, {
- fireImmediately: true,
- });
- }
-
- private getReleaseSecrets() {
- return secretsStore
- .getByLabel({ owner: "helm" })
- .map(s => [s.getId(), s] as const);
- }
-
- getReleaseSecret(release: HelmRelease) {
- return secretsStore.getByLabel({
- owner: "helm",
- name: release.getName(),
- })
- .find(secret => secret.getNs() == release.getNs());
- }
-
- @action
- async loadAll(namespaces: string[]) {
- this.isLoading = true;
- this.isLoaded = false;
-
- try {
- const items = await this.loadItems(namespaces);
-
- this.items.replace(this.sortItems(items));
- this.isLoaded = true;
- this.failedLoading = false;
- } catch (error) {
- this.failedLoading = true;
- console.warn("Loading Helm Chart releases has failed", error);
-
- if (error.error) {
- Notifications.error(error.error);
- }
- } finally {
- this.isLoading = false;
- }
- }
-
- async loadFromContextNamespaces(): Promise {
- return this.loadAll(this.dependencies.namespaceStore.context.contextNamespaces);
- }
-
- async loadItems(namespaces: string[]) {
- const isLoadingAll = this.dependencies.namespaceStore.context.allNamespaces?.length > 1
- && this.dependencies.namespaceStore.context.cluster.accessibleNamespaces.length === 0
- && this.dependencies.namespaceStore.context.allNamespaces.every(ns => namespaces.includes(ns));
-
- if (isLoadingAll) {
- return listReleases();
- }
-
- return Promise // load resources per namespace
- .all(namespaces.map(namespace => listReleases(namespace)))
- .then(items => items.flat());
- }
-
- create = async (payload: IReleaseCreatePayload) => {
- const response = await createRelease(payload);
-
- if (this.isLoaded) this.loadFromContextNamespaces();
-
- return response;
- };
-
- async update(name: string, namespace: string, payload: IReleaseUpdatePayload) {
- const response = await updateRelease(name, namespace, payload);
-
- if (this.isLoaded) this.loadFromContextNamespaces();
-
- return response;
- }
-
- rollback = async (name: string, namespace: string, revision: number) => {
- const response = await rollbackRelease(name, namespace, revision);
-
- if (this.isLoaded) this.loadFromContextNamespaces();
-
- return response;
- };
-
- async remove(release: HelmRelease) {
- return super.removeItem(release, () => deleteRelease(release.getName(), release.getNs()));
- }
-
- async removeSelectedItems() {
- return Promise.all(this.selectedItems.map(this.remove));
- }
-}
diff --git a/src/renderer/components/+apps-releases/releases.injectable.ts b/src/renderer/components/+apps-releases/releases.injectable.ts
new file mode 100644
index 0000000000..9858fb1834
--- /dev/null
+++ b/src/renderer/components/+apps-releases/releases.injectable.ts
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { asyncComputed } from "@ogre-tools/injectable-react";
+import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable";
+import { listReleases } from "../../../common/k8s-api/endpoints/helm-releases.api";
+
+const releasesInjectable = getInjectable({
+ instantiate: (di) => {
+ const namespaceStore = di.inject(namespaceStoreInjectable);
+
+ // TODO: Inject clusterContext directly instead of accessing dependency of a dependency
+ const clusterContext = namespaceStore.context;
+
+ return asyncComputed(async () => {
+ const contextNamespaces = namespaceStore.contextNamespaces || [];
+
+ const isLoadingAll =
+ clusterContext.allNamespaces?.length > 1 &&
+ clusterContext.cluster.accessibleNamespaces.length === 0 &&
+ clusterContext.allNamespaces.every((namespace) =>
+ contextNamespaces.includes(namespace),
+ );
+
+ const releaseArrays = await (isLoadingAll ? listReleases() : Promise.all(
+ contextNamespaces.map((namespace) =>
+ listReleases(namespace),
+ ),
+ ));
+
+ return releaseArrays.flat();
+ }, []);
+ },
+
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default releasesInjectable;
diff --git a/src/renderer/components/+apps-releases/releases.tsx b/src/renderer/components/+apps-releases/releases.tsx
index 998e99e902..a71cc8e7a1 100644
--- a/src/renderer/components/+apps-releases/releases.tsx
+++ b/src/renderer/components/+apps-releases/releases.tsx
@@ -3,26 +3,30 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
+import "../item-object-list/item-list-layout.scss";
import "./releases.scss";
import React, { Component } from "react";
-import kebabCase from "lodash/kebabCase";
-import { disposeOnUnmount, observer } from "mobx-react";
import type { RouteComponentProps } from "react-router";
-import type { ReleaseStore } from "./release.store";
import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
-import { ReleaseDetails } from "./release-details";
-import { ReleaseRollbackDialog } from "./release-rollback-dialog";
import { navigation } from "../../navigation";
-import { ItemListLayout } from "../item-object-list/item-list-layout";
-import { HelmReleaseMenu } from "./release-menu";
-import { secretsStore } from "../+config-secrets/secrets.store";
-import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
import type { ReleaseRouteParams } from "../../../common/routes";
import { releaseURL } from "../../../common/routes";
import { withInjectables } from "@ogre-tools/injectable-react";
-import releaseStoreInjectable from "./release-store.injectable";
import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable";
+import { ItemListLayout } from "../item-object-list";
+import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
+import { kebabCase } from "lodash/fp";
+import { HelmReleaseMenu } from "./release-menu";
+import type { ItemStore } from "../../../common/item.store";
+import { ReleaseRollbackDialog } from "./release-rollback-dialog";
+import { ReleaseDetails } from "./release-details/release-details";
+import removableReleasesInjectable from "./removable-releases.injectable";
+import type { RemovableHelmRelease } from "./removable-releases";
+import { observer } from "mobx-react";
+import type { IComputedValue } from "mobx";
+import releasesInjectable from "./releases.injectable";
+import { Spinner } from "../spinner";
enum columnId {
name = "name",
@@ -39,7 +43,8 @@ interface Props extends RouteComponentProps {
}
interface Dependencies {
- releaseStore: ReleaseStore
+ releases: IComputedValue
+ releasesArePending: IComputedValue
selectNamespace: (namespace: string) => void
}
@@ -51,27 +56,10 @@ class NonInjectedHelmReleases extends Component {
if (namespace) {
this.props.selectNamespace(namespace);
}
-
- disposeOnUnmount(this, [
- this.props.releaseStore.watchAssociatedSecrets(),
- this.props.releaseStore.watchSelectedNamespaces(),
- ]);
- }
-
- get selectedRelease() {
- const { match: { params: { name, namespace }}} = this.props;
-
- return this.props.releaseStore.items.find(release => {
- return release.getName() == name && release.getNs() == namespace;
- });
}
onDetails = (item: HelmRelease) => {
- if (item === this.selectedRelease) {
- this.hideDetails();
- } else {
- this.showDetails(item);
- }
+ this.showDetails(item);
};
showDetails = (item: HelmRelease) => {
@@ -101,14 +89,57 @@ class NonInjectedHelmReleases extends Component {
}
render() {
+ if (this.props.releasesArePending.get()) {
+ // TODO: Make Spinner "center" work properly
+ return
;
+ }
+
+ const releases = this.props.releases;
+
+ // TODO: Implement ItemListLayout without stateful stores
+ const legacyReleaseStore = {
+ get items() {
+ return releases.get();
+ },
+
+ loadAll: () => Promise.resolve(),
+ isLoaded: true,
+ failedLoading: false,
+
+ getTotalCount: () => releases.get().length,
+
+ toggleSelection: (item) => {
+ item.toggle();
+ },
+
+ isSelectedAll: () =>
+ releases.get().every((release) => release.isSelected),
+
+ toggleSelectionAll: () => {
+ releases.get().forEach((release) => release.toggle());
+ },
+
+ isSelected: (item) => item.isSelected,
+
+ get selectedItems() {
+ return releases.get().filter((release) => release.isSelected);
+ },
+
+ removeSelectedItems() {
+ return Promise.all(
+ releases.get().filter((release) => release.isSelected).map((release) => release.delete()),
+ );
+ },
+ } as ItemStore;
+
return (
<>
release.getName(),
[columnId.namespace]: release => release.getNs(),
@@ -167,13 +198,13 @@ class NonInjectedHelmReleases extends Component {
customizeRemoveDialog={selectedItems => ({
message: this.renderRemoveDialogMessage(selectedItems),
})}
- detailsItem={this.selectedRelease}
onDetails={this.onDetails}
/>
+
+
>
);
@@ -185,7 +216,8 @@ export const HelmReleases = withInjectables(
{
getProps: (di, props) => ({
- releaseStore: di.inject(releaseStoreInjectable),
+ releases: di.inject(removableReleasesInjectable),
+ releasesArePending: di.inject(releasesInjectable).pending,
selectNamespace: di.inject(namespaceStoreInjectable).selectNamespaces,
...props,
}),
diff --git a/src/renderer/components/+apps-releases/removable-releases.injectable.ts b/src/renderer/components/+apps-releases/removable-releases.injectable.ts
new file mode 100644
index 0000000000..6d41c63768
--- /dev/null
+++ b/src/renderer/components/+apps-releases/removable-releases.injectable.ts
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { observable } from "mobx";
+import releasesInjectable from "./releases.injectable";
+import deleteReleaseInjectable from "./delete-release/delete-release.injectable";
+import { removableReleases } from "./removable-releases";
+
+const removableReleasesInjectable = getInjectable({
+ instantiate: (di) =>
+ removableReleases({
+ releases: di.inject(releasesInjectable),
+ deleteRelease: di.inject(deleteReleaseInjectable),
+ releaseSelectionStatus: observable.map(),
+ }),
+
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default removableReleasesInjectable;
diff --git a/src/renderer/components/+apps-releases/removable-releases.ts b/src/renderer/components/+apps-releases/removable-releases.ts
new file mode 100644
index 0000000000..5d72e0b5ea
--- /dev/null
+++ b/src/renderer/components/+apps-releases/removable-releases.ts
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import type { IAsyncComputed } from "@ogre-tools/injectable-react";
+import { computed, ObservableMap } from "mobx";
+import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
+
+interface Dependencies {
+ releases: IAsyncComputed;
+ releaseSelectionStatus: ObservableMap;
+ deleteRelease: (release: HelmRelease) => Promise;
+}
+
+export interface RemovableHelmRelease extends HelmRelease {
+ toggle: () => void;
+ isSelected: boolean;
+ delete: () => Promise;
+}
+
+export const removableReleases = ({
+ releases,
+ releaseSelectionStatus,
+ deleteRelease,
+}: Dependencies) => {
+ const isSelected = (release: HelmRelease) =>
+ releaseSelectionStatus.get(release.getId()) || false;
+
+ return computed(() =>
+ releases.value.get().map(
+ (release): RemovableHelmRelease => ({
+ ...release,
+
+ toggle: () => {
+ releaseSelectionStatus.set(release.getId(), !isSelected(release));
+ },
+
+ get isSelected() {
+ return isSelected(release);
+ },
+
+ delete: async () => {
+ await deleteRelease(release);
+ },
+ }),
+ ),
+ );
+};
diff --git a/src/renderer/components/+apps-releases/rollback-release/rollback-release.injectable.ts b/src/renderer/components/+apps-releases/rollback-release/rollback-release.injectable.ts
new file mode 100644
index 0000000000..fe04ce71e6
--- /dev/null
+++ b/src/renderer/components/+apps-releases/rollback-release/rollback-release.injectable.ts
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { rollbackRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
+import releasesInjectable from "../releases.injectable";
+
+const rollbackReleaseInjectable = getInjectable({
+ instantiate: (di) => {
+ const releases = di.inject(releasesInjectable);
+
+ return async (name: string, namespace: string, revision: number) => {
+ await rollbackRelease(name, namespace, revision);
+
+ releases.invalidate();
+ };
+ },
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default rollbackReleaseInjectable;
diff --git a/src/renderer/components/+apps-releases/update-release/update-release.injectable.ts b/src/renderer/components/+apps-releases/update-release/update-release.injectable.ts
new file mode 100644
index 0000000000..2e5a681f51
--- /dev/null
+++ b/src/renderer/components/+apps-releases/update-release/update-release.injectable.ts
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { updateRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api";
+
+const updateReleaseInjectable = getInjectable({
+ instantiate: () => updateRelease,
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default updateReleaseInjectable;
diff --git a/src/renderer/components/+catalog/__tests__/custom-views.test.ts b/src/renderer/components/+catalog/__tests__/custom-views.test.ts
new file mode 100644
index 0000000000..af2dad0f55
--- /dev/null
+++ b/src/renderer/components/+catalog/__tests__/custom-views.test.ts
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+
+import type { ConfigurableDependencyInjectionContainer } from "@ogre-tools/injectable";
+import { computed } from "mobx";
+import type React from "react";
+import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension";
+import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable";
+import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
+import type { CustomCategoryViewRegistration } from "../custom-views";
+import customCategoryViewsInjectable from "../custom-views.injectable";
+
+describe("Custom Category Views", () => {
+ let di: ConfigurableDependencyInjectionContainer;
+
+ beforeEach(() => {
+ di = getDiForUnitTesting();
+ });
+
+ it("should order items correctly over all extensions", () => {
+ const component1 = (): React.ReactElement => null;
+ const component2 = (): React.ReactElement => null;
+
+ di.override(rendererExtensionsInjectable, () => computed(() => [
+ {
+ customCategoryViews: [
+ {
+ components: {
+ View: component1,
+ },
+ group: "foo",
+ kind: "bar",
+ priority: 100,
+ } as CustomCategoryViewRegistration,
+ ],
+ },
+ {
+ customCategoryViews: [
+ {
+ components: {
+ View: component2,
+ },
+ group: "foo",
+ kind: "bar",
+ priority: 95,
+ } as CustomCategoryViewRegistration,
+ ],
+ },
+ ] as LensRendererExtension[]));
+
+ const customCategoryViews = di.inject(customCategoryViewsInjectable);
+ const { after } = customCategoryViews.get().get("foo").get("bar");
+
+ expect(after[0].View).toBe(component2);
+ expect(after[1].View).toBe(component1);
+ });
+
+ it("should put put priority < 50 items in before", () => {
+ const component1 = (): React.ReactElement => null;
+ const component2 = (): React.ReactElement => null;
+
+ di.override(rendererExtensionsInjectable, () => computed(() => [
+ {
+ customCategoryViews: [
+ {
+ components: {
+ View: component1,
+ },
+ group: "foo",
+ kind: "bar",
+ priority: 40,
+ } as CustomCategoryViewRegistration,
+ ],
+ },
+ {
+ customCategoryViews: [
+ {
+ components: {
+ View: component2,
+ },
+ group: "foo",
+ kind: "bar",
+ priority: 95,
+ } as CustomCategoryViewRegistration,
+ ],
+ },
+ ] as LensRendererExtension[]));
+
+ const customCategoryViews = di.inject(customCategoryViewsInjectable);
+ const { before } = customCategoryViews.get().get("foo").get("bar");
+
+ expect(before[0].View).toBe(component1);
+ });
+});
diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx
index 107e02db30..194f35f481 100644
--- a/src/renderer/components/+catalog/catalog.tsx
+++ b/src/renderer/components/+catalog/catalog.tsx
@@ -8,7 +8,7 @@ import styles from "./catalog.module.scss";
import React from "react";
import { disposeOnUnmount, observer } from "mobx-react";
import { ItemListLayout } from "../item-object-list";
-import { action, makeObservable, observable, reaction, runInAction, when } from "mobx";
+import { action, IComputedValue, makeObservable, observable, reaction, runInAction, when } from "mobx";
import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store";
import { navigate } from "../../navigation";
import { MenuItem, MenuActions } from "../menu";
@@ -33,6 +33,9 @@ import catalogPreviousActiveTabStorageInjectable from "./catalog-previous-active
import catalogEntityStoreInjectable from "./catalog-entity-store/catalog-entity-store.injectable";
import type { GetCategoryColumnsParams, CategoryColumns } from "./get-category-columns.injectable";
import getCategoryColumnsInjectable from "./get-category-columns.injectable";
+import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable";
+import customCategoryViewsInjectable from "./custom-views.injectable";
+import type { CustomCategoryViewComponents } from "./custom-views";
interface Props extends RouteComponentProps {}
@@ -40,6 +43,7 @@ interface Dependencies {
catalogPreviousActiveTabStorage: { set: (value: string ) => void };
catalogEntityStore: CatalogEntityStore;
getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
+ customCategoryViews: IComputedValue>>;
}
@observer
@@ -213,16 +217,44 @@ class NonInjectedCatalog extends React.Component {
);
}
+ renderViews = () => {
+ const { catalogEntityStore, customCategoryViews } = this.props;
+ const { activeCategory } = catalogEntityStore;
+
+ if (!activeCategory) {
+ return this.renderList();
+ }
+
+ const customViews = customCategoryViews.get()
+ .get(activeCategory.spec.group)
+ ?.get(activeCategory.spec.names.kind);
+ const renderView = ({ View }: CustomCategoryViewComponents, index: number) => (
+
+ );
+
+ return (
+ <>
+ {customViews?.before.map(renderView)}
+ {this.renderList()}
+ {customViews?.after.map(renderView)}
+ >
+ );
+ };
+
renderList() {
- const { activeCategory } = this.props.catalogEntityStore;
- const tableId = activeCategory ? `catalog-items-${activeCategory.metadata.name.replace(" ", "")}` : "catalog-items";
+ const { catalogEntityStore, getCategoryColumns } = this.props;
+ const { activeCategory } = catalogEntityStore;
+ const tableId = activeCategory
+ ? `catalog-items-${activeCategory.metadata.name.replace(" ", "")}`
+ : "catalog-items";
if (this.activeTab === undefined) {
return null;
}
- const { sortingCallbacks, searchFilters, renderTableContents, renderTableHeader } = this.props.getCategoryColumns({ activeCategory });
-
return (
{
renderHeaderTitle={activeCategory?.metadata.name ?? "Browse All"}
isSelectable={false}
isConfigurable={true}
- store={this.props.catalogEntityStore}
- sortingCallbacks={sortingCallbacks}
- searchFilters={searchFilters}
- renderTableHeader={renderTableHeader}
+ store={catalogEntityStore}
customizeTableRowProps={entity => ({
disabled: !entity.isEnabled(),
})}
- renderTableContents={renderTableContents}
+ {...getCategoryColumns({ activeCategory })}
onDetails={this.onDetails}
renderItemMenu={this.renderItemMenu}
/>
@@ -254,7 +283,7 @@ class NonInjectedCatalog extends React.Component {
return (
- {this.renderList()}
+ {this.renderViews()}
{
selectedEntity
@@ -281,6 +310,7 @@ export const Catalog = withInjectables( NonInjectedCatalog,
catalogEntityStore: di.inject(catalogEntityStoreInjectable),
catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
getCategoryColumns: di.inject(getCategoryColumnsInjectable),
+ customCategoryViews: di.inject(customCategoryViewsInjectable),
...props,
}),
});
diff --git a/src/renderer/components/+catalog/custom-category-columns.injectable.tsx b/src/renderer/components/+catalog/custom-category-columns.injectable.tsx
index 26d4a304d7..2d315f383a 100644
--- a/src/renderer/components/+catalog/custom-category-columns.injectable.tsx
+++ b/src/renderer/components/+catalog/custom-category-columns.injectable.tsx
@@ -6,7 +6,7 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { computed, IComputedValue } from "mobx";
import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
-import { getOrInsert } from "../../utils";
+import { getOrInsert, getOrInsertMap } from "../../utils";
import type { RegisteredAdditionalCategoryColumn } from "./custom-category-columns";
interface Dependencies {
@@ -19,7 +19,7 @@ function getAdditionCategoryColumns({ extensions }: Dependencies): IComputedValu
for (const ext of extensions.get()) {
for (const { renderCell, titleProps, priority = 50, searchFilter, sortCallback, ...registration } of ext.additionalCategoryColumns) {
- const byGroup = getOrInsert(res, registration.group, new Map());
+ const byGroup = getOrInsertMap(res, registration.group);
const byKind = getOrInsert(byGroup, registration.kind, []);
const id = `${ext.name}:${registration.id}`;
diff --git a/src/renderer/components/+catalog/custom-views.injectable.ts b/src/renderer/components/+catalog/custom-views.injectable.ts
new file mode 100644
index 0000000000..70fbd6457a
--- /dev/null
+++ b/src/renderer/components/+catalog/custom-views.injectable.ts
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { orderBy } from "lodash";
+import { computed, IComputedValue } from "mobx";
+import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension";
+import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable";
+import { getOrInsert, getOrInsertMap } from "../../utils";
+import type { CustomCategoryViewComponents } from "./custom-views";
+
+interface Dependencies {
+ extensions: IComputedValue;
+}
+
+export interface RegisteredCustomCategoryViewDecl {
+ /**
+ * The asc sorted list of items with priority set to < 50
+ */
+ before: CustomCategoryViewComponents[];
+ /**
+ * The asc sorted list of items with priority not set or set to >= 50
+ */
+ after: CustomCategoryViewComponents[];
+}
+
+function getCustomCategoryViews({ extensions }: Dependencies): IComputedValue>> {
+ return computed(() => {
+ const res = new Map>();
+ const registrations = extensions.get()
+ .flatMap(ext => ext.customCategoryViews)
+ .map(({ priority = 50, ...rest }) => ({ priority, ...rest }));
+ const sortedRegistrations = orderBy(registrations, "priority", "asc");
+
+ for (const { priority, group, kind, components } of sortedRegistrations) {
+ const byGroup = getOrInsertMap(res, group);
+ const { before, after } = getOrInsert(byGroup, kind, { before: [], after: [] });
+
+ if (priority < 50) {
+ before.push(components);
+ } else {
+ after.push(components);
+ }
+ }
+
+ return res;
+ });
+}
+
+const customCategoryViewsInjectable = getInjectable({
+ instantiate: (di) => getCustomCategoryViews({
+ extensions: di.inject(rendererExtensionsInjectable),
+ }),
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default customCategoryViewsInjectable;
diff --git a/src/renderer/components/+catalog/custom-views.ts b/src/renderer/components/+catalog/custom-views.ts
new file mode 100644
index 0000000000..73ee0a853a
--- /dev/null
+++ b/src/renderer/components/+catalog/custom-views.ts
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+
+import type React from "react";
+import type { CatalogCategory } from "../../api/catalog-entity";
+
+/**
+ * The props for CustomCategoryViewComponents.View
+ */
+export interface CustomCategoryViewProps {
+ /**
+ * The category instance itself
+ */
+ category: CatalogCategory;
+}
+
+/**
+ * The components for the category view.
+ */
+export interface CustomCategoryViewComponents {
+ View: React.ComponentType;
+}
+
+/**
+ * This is the type used to declare additional views for a specific category
+ */
+export interface CustomCategoryViewRegistration {
+ /**
+ * The catalog entity kind that is declared by the category for this registration
+ *
+ * e.g.
+ * - `"KubernetesCluster"`
+ */
+ kind: string;
+
+ /**
+ * The catalog entity group that is declared by the category for this registration
+ *
+ * e.g.
+ * - `"entity.k8slens.dev"`
+ */
+ group: string;
+
+ /**
+ * The sorting order value. Used to determine the total order of the views.
+ *
+ * @default 50
+ */
+ priority?: number;
+
+ /**
+ * The components for this registration
+ */
+ components: CustomCategoryViewComponents;
+}
diff --git a/src/renderer/components/+preferences/application.tsx b/src/renderer/components/+preferences/application.tsx
index deaae917d0..93dd20a417 100644
--- a/src/renderer/components/+preferences/application.tsx
+++ b/src/renderer/components/+preferences/application.tsx
@@ -10,7 +10,6 @@ import { Select, SelectOption } from "../select";
import { ThemeStore } from "../../theme.store";
import { UserStore } from "../../../common/user-store";
import { Input } from "../input";
-import { isWindows } from "../../../common/vars";
import { Switch } from "../switch";
import moment from "moment-timezone";
import { CONSTANTS, defaultExtensionRegistryUrl, ExtensionRegistryLocation } from "../../../common/user-store/preferences-helpers";
@@ -36,16 +35,7 @@ interface Dependencies {
const NonInjectedApplication: React.FC = ({ appPreferenceItems }) => {
const userStore = UserStore.getInstance();
- const defaultShell = process.env.SHELL
- || process.env.PTYSHELL
- || (
- isWindows
- ? "powershell.exe"
- : "System default shell"
- );
-
const [customUrl, setCustomUrl] = React.useState(userStore.extensionRegistryUrl.customUrl || "");
- const [shell, setShell] = React.useState(userStore.shell || "");
const extensionSettings = appPreferenceItems.get().filter((preference) => preference.showInPreferencesTab === "application");
const themeStore = ThemeStore.getInstance();
@@ -62,43 +52,7 @@ const NonInjectedApplication: React.FC = ({ appPreferenceItems })
/>
-
-
-
-
- userStore.terminalTheme = value}
- />
-
-
-
-
-
-
- userStore.terminalCopyOnSelect = !userStore.terminalCopyOnSelect}
- >
- Copy on select and paste on right-click
-
-
-
-
+
diff --git a/src/renderer/components/+preferences/editor.tsx b/src/renderer/components/+preferences/editor.tsx
index dceddff7b5..2f3e03fff5 100644
--- a/src/renderer/components/+preferences/editor.tsx
+++ b/src/renderer/components/+preferences/editor.tsx
@@ -69,6 +69,27 @@ export const Editor = observer(() => {
onChange={value => editorConfiguration.tabSize = Number(value)}
/>
+
+
);
});
diff --git a/src/renderer/components/+preferences/preferences.tsx b/src/renderer/components/+preferences/preferences.tsx
index 828433fe2f..ddb1e0077e 100644
--- a/src/renderer/components/+preferences/preferences.tsx
+++ b/src/renderer/components/+preferences/preferences.tsx
@@ -22,6 +22,8 @@ import {
editorRoute,
telemetryRoute,
telemetryURL,
+ terminalRoute,
+ terminalURL,
} from "../../../common/routes";
import { navigateWithoutHistoryChange, navigation } from "../../navigation";
import { SettingLayout } from "../layout/setting-layout";
@@ -29,6 +31,7 @@ import { Tab, Tabs } from "../tabs";
import { Application } from "./application";
import { Kubernetes } from "./kubernetes";
import { Editor } from "./editor";
+import { Terminal } from "./terminal";
import { LensProxy } from "./proxy";
import { Telemetry } from "./telemetry";
import { Extensions } from "./extensions";
@@ -56,6 +59,7 @@ const NonInjectedPreferences: React.FC = ({ appPreferenceItems })
+
{(telemetryExtensions.length > 0 || !!sentryDsn) &&
}
@@ -77,6 +81,7 @@ const NonInjectedPreferences: React.FC = ({ appPreferenceItems })
+
diff --git a/src/renderer/components/+preferences/terminal.tsx b/src/renderer/components/+preferences/terminal.tsx
new file mode 100644
index 0000000000..b76f2ba567
--- /dev/null
+++ b/src/renderer/components/+preferences/terminal.tsx
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+
+import React from "react";
+import { observer } from "mobx-react";
+import { UserStore } from "../../../common/user-store";
+import { SubTitle } from "../layout/sub-title";
+import { Input, InputValidators } from "../input";
+import { isWindows } from "../../../common/vars";
+import { Switch } from "../switch";
+import { Select } from "../select";
+import { ThemeStore } from "../../theme.store";
+
+export const Terminal = observer(() => {
+ const userStore = UserStore.getInstance();
+ const themeStore = ThemeStore.getInstance();
+ const defaultShell = process.env.SHELL
+ || process.env.PTYSHELL
+ || (
+ isWindows
+ ? "powershell.exe"
+ : "System default shell"
+ );
+
+ return (
+
+
+
+
+ userStore.terminalCopyOnSelect = !userStore.terminalCopyOnSelect}
+ >
+ Copy on select and paste on right-click
+
+
+
+
+
+ userStore.terminalTheme = value}
+ />
+
+
+
+
+
);
+});
diff --git a/src/renderer/components/dock/install-chart.tsx b/src/renderer/components/dock/install-chart.tsx
index 29cd13fe5d..d159a8df08 100644
--- a/src/renderer/components/dock/install-chart.tsx
+++ b/src/renderer/components/dock/install-chart.tsx
@@ -27,10 +27,10 @@ import type {
IReleaseCreatePayload,
IReleaseUpdateDetails,
} from "../../../common/k8s-api/endpoints/helm-releases.api";
-import releaseStoreInjectable from "../+apps-releases/release-store.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import installChartStoreInjectable from "./install-chart-store/install-chart-store.injectable";
import dockStoreInjectable from "./dock-store/dock-store.injectable";
+import createReleaseInjectable from "../+apps-releases/create-release/create-release.injectable";
interface Props {
tab: DockTab;
@@ -222,7 +222,7 @@ export const InstallChart = withInjectables(
{
getProps: (di, props) => ({
- createRelease: di.inject(releaseStoreInjectable).create,
+ createRelease: di.inject(createReleaseInjectable),
installChartStore: di.inject(installChartStoreInjectable),
dockStore: di.inject(dockStoreInjectable),
...props,
diff --git a/src/renderer/components/dock/terminal/terminal.ts b/src/renderer/components/dock/terminal/terminal.ts
index 39b42d6521..718c855b97 100644
--- a/src/renderer/components/dock/terminal/terminal.ts
+++ b/src/renderer/components/dock/terminal/terminal.ts
@@ -11,24 +11,28 @@ import type { DockStore, TabId } from "../dock-store/dock.store";
import { TerminalApi, TerminalChannels } from "../../../api/terminal-api";
import { ThemeStore } from "../../../theme.store";
import { disposer } from "../../../utils";
-import { isMac } from "../../../../common/vars";
+import { isMac, defaultTerminalFontFamily } from "../../../../common/vars";
import { once } from "lodash";
import { UserStore } from "../../../../common/user-store";
import { clipboard } from "electron";
import logger from "../../../../common/logger";
+import type { TerminalConfig } from "../../../../common/user-store/preferences-helpers";
interface Dependencies {
dockStore: DockStore
}
export class Terminal {
+
+ private terminalConfig: TerminalConfig = UserStore.getInstance().terminalConfig;
+
public static get spawningPool() {
return document.getElementById("terminal-init");
}
static async preloadFonts() {
const fontPath = require("../../fonts/roboto-mono-nerd.ttf").default; // eslint-disable-line @typescript-eslint/no-var-requires
- const fontFace = new FontFace("RobotoMono", `url(${fontPath})`);
+ const fontFace = new FontFace(defaultTerminalFontFamily, `url(${fontPath})`);
await fontFace.load();
document.fonts.add(fontFace);
@@ -37,8 +41,8 @@ export class Terminal {
private xterm: XTerm | null = new XTerm({
cursorBlink: true,
cursorStyle: "bar",
- fontSize: 13,
- fontFamily: "RobotoMono",
+ fontSize: this.terminalConfig.fontSize,
+ fontFamily: this.terminalConfig.fontFamily,
});
private readonly fitAddon = new FitAddon();
private scrollPos = 0;
@@ -97,6 +101,12 @@ export class Terminal {
}, {
fireImmediately: true,
}),
+ reaction(() => UserStore.getInstance().terminalConfig.fontSize, this.setFontSize, {
+ fireImmediately: true,
+ }),
+ reaction(() => UserStore.getInstance().terminalConfig.fontFamily, this.setFontFamily, {
+ fireImmediately: true,
+ }),
dependencies.dockStore.onResize(this.onResize),
() => onDataHandler.dispose(),
() => this.fitAddon.dispose(),
@@ -192,6 +202,14 @@ export class Terminal {
}
};
+ setFontSize = (size: number) => {
+ this.xterm.options.fontSize = size;
+ };
+
+ setFontFamily = (family: string) => {
+ this.xterm.options.fontFamily = family;
+ };
+
keyHandler = (evt: KeyboardEvent): boolean => {
const { code, ctrlKey, metaKey } = evt;
diff --git a/src/renderer/components/dock/upgrade-chart-store/upgrade-chart-store.injectable.ts b/src/renderer/components/dock/upgrade-chart-store/upgrade-chart-store.injectable.ts
index 7a660c133a..d0732e7fdb 100644
--- a/src/renderer/components/dock/upgrade-chart-store/upgrade-chart-store.injectable.ts
+++ b/src/renderer/components/dock/upgrade-chart-store/upgrade-chart-store.injectable.ts
@@ -4,10 +4,10 @@
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { UpgradeChartStore } from "./upgrade-chart.store";
-import releaseStoreInjectable from "../../+apps-releases/release-store.injectable";
import dockStoreInjectable from "../dock-store/dock-store.injectable";
import createDockTabStoreInjectable from "../dock-tab-store/create-dock-tab-store.injectable";
import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable";
+import releasesInjectable from "../../+apps-releases/releases.injectable";
const upgradeChartStoreInjectable = getInjectable({
instantiate: (di) => {
@@ -16,7 +16,7 @@ const upgradeChartStoreInjectable = getInjectable({
const valuesStore = createDockTabStore();
return new UpgradeChartStore({
- releaseStore: di.inject(releaseStoreInjectable),
+ releases: di.inject(releasesInjectable),
dockStore: di.inject(dockStoreInjectable),
createStorage: di.inject(createStorageInjectable),
valuesStore,
diff --git a/src/renderer/components/dock/upgrade-chart-store/upgrade-chart.store.ts b/src/renderer/components/dock/upgrade-chart-store/upgrade-chart.store.ts
index 917bc46ce8..f6e6a2dde2 100644
--- a/src/renderer/components/dock/upgrade-chart-store/upgrade-chart.store.ts
+++ b/src/renderer/components/dock/upgrade-chart-store/upgrade-chart.store.ts
@@ -3,12 +3,22 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
-import { action, autorun, computed, IReactionDisposer, reaction, makeObservable } from "mobx";
+import {
+ action,
+ autorun,
+ computed,
+ IReactionDisposer,
+ reaction,
+ makeObservable,
+} from "mobx";
import { DockStore, DockTab, TabId, TabKind } from "../dock-store/dock.store";
import { DockTabStorageState, DockTabStore } from "../dock-tab-store/dock-tab.store";
-import { getReleaseValues } from "../../../../common/k8s-api/endpoints/helm-releases.api";
-import type { ReleaseStore } from "../../+apps-releases/release.store";
+import {
+ getReleaseValues,
+ HelmRelease,
+} from "../../../../common/k8s-api/endpoints/helm-releases.api";
import { iter, StorageHelper } from "../../../utils";
+import type { IAsyncComputed } from "@ogre-tools/injectable-react";
export interface IChartUpgradeData {
releaseName: string;
@@ -16,7 +26,7 @@ export interface IChartUpgradeData {
}
interface Dependencies {
- releaseStore: ReleaseStore
+ releases: IAsyncComputed
valuesStore: DockTabStore
dockStore: DockStore
createStorage: (storageKey: string, options: DockTabStorageState) => StorageHelper>
@@ -60,14 +70,14 @@ export class UpgradeChartStore extends DockTabStore {
return;
}
const dispose = reaction(() => {
- const release = this.dependencies.releaseStore.getByName(releaseName);
+ const release = this.dependencies.releases.value.get().find(release => release.getName() === releaseName);
return release?.getRevision(); // watch changes only by revision
},
release => {
const releaseTab = this.getTabByRelease(releaseName);
- if (!this.dependencies.releaseStore.isLoaded || !releaseTab) {
+ if (!releaseTab) {
return;
}
@@ -91,7 +101,7 @@ export class UpgradeChartStore extends DockTabStore {
isLoading(tabId = this.dependencies.dockStore.selectedTabId) {
const values = this.values.getData(tabId);
- return !this.dependencies.releaseStore.isLoaded || values === undefined;
+ return values === undefined;
}
@action
@@ -99,7 +109,6 @@ export class UpgradeChartStore extends DockTabStore {
const values = this.values.getData(tabId);
await Promise.all([
- !this.dependencies.releaseStore.isLoaded && this.dependencies.releaseStore.loadFromContextNamespaces(),
!values && this.loadValues(tabId),
]);
}
diff --git a/src/renderer/components/dock/upgrade-chart.tsx b/src/renderer/components/dock/upgrade-chart.tsx
index 0136d65d53..270656e9f4 100644
--- a/src/renderer/components/dock/upgrade-chart.tsx
+++ b/src/renderer/components/dock/upgrade-chart.tsx
@@ -13,15 +13,22 @@ import type { DockTab } from "./dock-store/dock.store";
import { InfoPanel } from "./info-panel";
import type { UpgradeChartStore } from "./upgrade-chart-store/upgrade-chart.store";
import { Spinner } from "../spinner";
-import type { ReleaseStore } from "../+apps-releases/release.store";
import { Badge } from "../badge";
import { EditorPanel } from "./editor-panel";
-import { helmChartStore, IChartVersion } from "../+apps-helm-charts/helm-chart.store";
-import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api";
+import {
+ helmChartStore,
+ IChartVersion,
+} from "../+apps-helm-charts/helm-chart.store";
+import type {
+ HelmRelease,
+ IReleaseUpdateDetails,
+ IReleaseUpdatePayload,
+} from "../../../common/k8s-api/endpoints/helm-releases.api";
import { Select, SelectOption } from "../select";
-import { withInjectables } from "@ogre-tools/injectable-react";
-import releaseStoreInjectable from "../+apps-releases/release-store.injectable";
+import { IAsyncComputed, withInjectables } from "@ogre-tools/injectable-react";
import upgradeChartStoreInjectable from "./upgrade-chart-store/upgrade-chart-store.injectable";
+import updateReleaseInjectable from "../+apps-releases/update-release/update-release.injectable";
+import releasesInjectable from "../+apps-releases/releases.injectable";
interface Props {
className?: string;
@@ -29,8 +36,9 @@ interface Props {
}
interface Dependencies {
- releaseStore: ReleaseStore
+ releases: IAsyncComputed
upgradeChartStore: UpgradeChartStore
+ updateRelease: (name: string, namespace: string, payload: IReleaseUpdatePayload) => Promise
}
@observer
@@ -61,7 +69,7 @@ export class NonInjectedUpgradeChart extends React.Component release.getName() === tabData.releaseName);
}
get value() {
@@ -95,7 +103,7 @@ export class NonInjectedUpgradeChart extends React.Component(
{
getProps: (di, props) => ({
- releaseStore: di.inject(releaseStoreInjectable),
+ releases: di.inject(releasesInjectable),
+ updateRelease: di.inject(updateReleaseInjectable),
upgradeChartStore: di.inject(upgradeChartStoreInjectable),
...props,
}),
diff --git a/src/renderer/components/drawer/drawer.tsx b/src/renderer/components/drawer/drawer.tsx
index ce06c26945..deaf6bad2b 100644
--- a/src/renderer/components/drawer/drawer.tsx
+++ b/src/renderer/components/drawer/drawer.tsx
@@ -11,12 +11,13 @@ import { createPortal } from "react-dom";
import { cssNames, noop, StorageHelper } from "../../utils";
import { Icon } from "../icon";
import { Animate, AnimateName } from "../animate";
-import { history } from "../../navigation";
import { ResizeDirection, ResizeGrowthDirection, ResizeSide, ResizingAnchor } from "../resizing-anchor";
import drawerStorageInjectable, {
defaultDrawerWidth,
} from "./drawer-storage/drawer-storage.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
+import historyInjectable from "../../navigation/history.injectable";
+import type { History } from "history";
export type DrawerPosition = "top" | "left" | "right" | "bottom";
@@ -59,6 +60,7 @@ resizingAnchorProps.set("top", [ResizeDirection.VERTICAL, ResizeSide.TRAILING, R
resizingAnchorProps.set("bottom", [ResizeDirection.VERTICAL, ResizeSide.LEADING, ResizeGrowthDirection.BOTTOM_TO_TOP]);
interface Dependencies {
+ history: History
drawerStorage: StorageHelper<{ width: number }>;
}
@@ -70,7 +72,7 @@ class NonInjectedDrawer extends React.Component();
- private stopListenLocation = history.listen(() => {
+ private stopListenLocation = this.props.history.listen(() => {
this.restoreScrollPos();
});
@@ -111,14 +113,14 @@ class NonInjectedDrawer extends React.Component {
if (!this.scrollElem) return;
- const key = history.location.key;
+ const key = this.props.history.location.key;
this.scrollPos.set(key, this.scrollElem.scrollTop);
};
restoreScrollPos = () => {
if (!this.scrollElem) return;
- const key = history.location.key;
+ const key = this.props.history.location.key;
this.scrollElem.scrollTop = this.scrollPos.get(key) || 0;
};
@@ -232,6 +234,7 @@ export const Drawer = withInjectables(
{
getProps: (di, props) => ({
+ history: di.inject(historyInjectable),
drawerStorage: di.inject(drawerStorageInjectable),
...props,
}),
diff --git a/src/renderer/frames/cluster-frame/cluster-frame.tsx b/src/renderer/frames/cluster-frame/cluster-frame.tsx
index 8cc1277e6c..cad742f485 100755
--- a/src/renderer/frames/cluster-frame/cluster-frame.tsx
+++ b/src/renderer/frames/cluster-frame/cluster-frame.tsx
@@ -6,7 +6,6 @@ import React from "react";
import { observable, makeObservable } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react";
import { Redirect, Route, Router, Switch } from "react-router";
-import { history } from "../../navigation";
import { UserManagement } from "../../components/+user-management/user-management";
import { ConfirmDialog } from "../../components/confirm-dialog";
import { ClusterOverview } from "../../components/+cluster/cluster-overview";
@@ -41,17 +40,18 @@ import { PortForwardDialog } from "../../port-forward";
import { DeleteClusterDialog } from "../../components/delete-cluster-dialog";
import type { NamespaceStore } from "../../components/+namespaces/namespace-store/namespace.store";
import { withInjectables } from "@ogre-tools/injectable-react";
-import namespaceStoreInjectable
- from "../../components/+namespaces/namespace-store/namespace-store.injectable";
+import namespaceStoreInjectable from "../../components/+namespaces/namespace-store/namespace-store.injectable";
import type { ClusterId } from "../../../common/cluster-types";
-import hostedClusterInjectable
- from "../../../common/cluster-store/hosted-cluster/hosted-cluster.injectable";
+import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster/hosted-cluster.injectable";
import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
import type { KubeObject } from "../../../common/k8s-api/kube-object";
import type { Disposer } from "../../../common/utils";
import kubeWatchApiInjectable from "../../kube-watch-api/kube-watch-api.injectable";
+import historyInjectable from "../../navigation/history.injectable";
+import type { History } from "history";
interface Dependencies {
+ history: History,
namespaceStore: NamespaceStore
hostedClusterId: ClusterId
subscribeStores: (stores: KubeObjectStore[]) => Disposer
@@ -135,10 +135,11 @@ class NonInjectedClusterFrame extends React.Component {
render() {
return (
-
+
} footer={ }>
+
@@ -181,6 +182,7 @@ class NonInjectedClusterFrame extends React.Component {
export const ClusterFrame = withInjectables(NonInjectedClusterFrame, {
getProps: di => ({
+ history: di.inject(historyInjectable),
namespaceStore: di.inject(namespaceStoreInjectable),
hostedClusterId: di.inject(hostedClusterInjectable).id,
subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores,
diff --git a/src/renderer/frames/root-frame/root-frame.tsx b/src/renderer/frames/root-frame/root-frame.tsx
index bc148f3a70..2216865b67 100644
--- a/src/renderer/frames/root-frame/root-frame.tsx
+++ b/src/renderer/frames/root-frame/root-frame.tsx
@@ -7,7 +7,6 @@ import { injectSystemCAs } from "../../../common/system-ca";
import React from "react";
import { Route, Router, Switch } from "react-router";
import { observer } from "mobx-react";
-import { history } from "../../navigation";
import { ClusterManager } from "../../components/cluster-manager";
import { ErrorBoundary } from "../../components/error-boundary";
import { Notifications } from "../../components/notifications";
@@ -16,14 +15,21 @@ import { CommandContainer } from "../../components/command-palette/command-conta
import { ipcRenderer } from "electron";
import { IpcRendererNavigationEvents } from "../../navigation/events";
import { ClusterFrameHandler } from "../../components/cluster-manager/lens-views";
+import historyInjectable from "../../navigation/history.injectable";
+import { withInjectables } from "@ogre-tools/injectable-react";
+import type { History } from "history";
injectSystemCAs();
+interface Dependencies {
+ history: History
+}
+
@observer
-export class RootFrame extends React.Component {
+class NonInjectedRootFrame extends React.Component {
static displayName = "RootFrame";
- constructor(props: {}) {
+ constructor(props: Dependencies) {
super(props);
ClusterFrameHandler.createInstance();
@@ -35,7 +41,7 @@ export class RootFrame extends React.Component {
render() {
return (
-
+
@@ -48,3 +54,7 @@ export class RootFrame extends React.Component {
);
}
}
+
+export const RootFrame = withInjectables(NonInjectedRootFrame, {
+ getProps: (di) => ({ history: di.inject(historyInjectable) }),
+});
diff --git a/src/renderer/navigation/history.injectable.ts b/src/renderer/navigation/history.injectable.ts
new file mode 100644
index 0000000000..0f5acaa2d8
--- /dev/null
+++ b/src/renderer/navigation/history.injectable.ts
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+import { history } from "./history";
+
+const historyInjectable = getInjectable({
+ instantiate: () => history,
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default historyInjectable;
diff --git a/src/renderer/navigation/history.ts b/src/renderer/navigation/history.ts
index 87c5625209..fa7df172b4 100644
--- a/src/renderer/navigation/history.ts
+++ b/src/renderer/navigation/history.ts
@@ -14,8 +14,14 @@ export const searchParamsOptions: ObservableSearchParamsOptions = {
joinArraysWith: ",", // param values splitter, applicable only with {joinArrays:true}
};
+/**
+ * @deprecated: Switch to using di.inject(historyInjectable)
+ */
export const history = ipcRenderer ? createBrowserHistory() : createMemoryHistory();
+/**
+ * @deprecated: Switch to using di.inject(observableHistoryInjectable)
+ */
export const navigation = createObservableHistory(history, {
searchParams: searchParamsOptions,
});
diff --git a/src/renderer/navigation/observable-history.injectable.ts b/src/renderer/navigation/observable-history.injectable.ts
new file mode 100644
index 0000000000..db50db04f2
--- /dev/null
+++ b/src/renderer/navigation/observable-history.injectable.ts
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
+
+import { navigation as observableHistory } from "./history";
+
+const observableHistoryInjectable = getInjectable({
+ instantiate: () => observableHistory,
+
+ lifecycle: lifecycleEnum.singleton,
+});
+
+export default observableHistoryInjectable;
diff --git a/src/renderer/port-forward/port-forward-store/port-forward-store.ts b/src/renderer/port-forward/port-forward-store/port-forward-store.ts
index 10dec7eb79..070f6746b7 100644
--- a/src/renderer/port-forward/port-forward-store/port-forward-store.ts
+++ b/src/renderer/port-forward/port-forward-store/port-forward-store.ts
@@ -273,11 +273,9 @@ export class PortForwardStore extends ItemStore {
let response: PortForwardResult;
try {
- const protocol = pf.protocol ?? "http";
-
response = await apiBase.post(
`/pods/port-forward/${pf.namespace}/${pf.kind}/${pf.name}`,
- { query: { port, forwardPort, protocol }},
+ { query: { port, forwardPort }},
);
// expecting the received port to be the specified port, unless the specified port is 0, which indicates any available port is suitable
@@ -355,11 +353,11 @@ function portForwardsEqual(portForward: ForwardedPort) {
}
async function getActivePortForward(portForward: ForwardedPort): Promise {
- const { port, forwardPort, protocol } = portForward;
+ const { port, forwardPort } = portForward;
let response: PortForwardResult;
try {
- response = await apiBase.get(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort, protocol }});
+ response = await apiBase.get(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort }});
} catch (error) {
logger.warn(`[PORT-FORWARD-STORE] Error getting active port-forward: ${error}`, portForward);
}
diff --git a/yarn.lock b/yarn.lock
index 7c44755ed2..7a57eb259d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -984,19 +984,19 @@
dependencies:
lodash "^4.17.21"
-"@ogre-tools/injectable-react@3.1.1":
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/@ogre-tools/injectable-react/-/injectable-react-3.1.1.tgz#d95ecec518ba798c36fa3a6f651fa52748e72b00"
- integrity sha512-Fhb/51NzrLzkA3G5zCpNOshvm0el1gROWGHkBqq1d/8PEekcEijIL8HZ6B/ylCWjQTJ1MaYViJdzs2iNP1oQxw==
+"@ogre-tools/injectable-react@3.2.0":
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/@ogre-tools/injectable-react/-/injectable-react-3.2.0.tgz#468542e846952deb8e7a4f6757da4813ee8f11fa"
+ integrity sha512-VU5l0uKe86psVzEPbXl1TLlflnoL+uSeOaOCy/mAGzau4nqRb+eA4RzYgzUs/D9tDYzJ7Es+LZWD9uSyXmpyXg==
dependencies:
"@ogre-tools/fp" "^3.0.0"
- "@ogre-tools/injectable" "^3.1.1"
+ "@ogre-tools/injectable" "^3.2.0"
lodash "^4.17.21"
-"@ogre-tools/injectable@3.1.1", "@ogre-tools/injectable@^3.1.1":
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/@ogre-tools/injectable/-/injectable-3.1.1.tgz#2f293a90e4d3f730ebab2689fd609edc24ffc563"
- integrity sha512-X7cDU2Mkcl2bP8JtR9l/Hx31jmKYEuCVJGjZIYxWlE1Nvd3HGq98oTV5uEGNP6+GjLHhXjzoscT9SKKzexyQWg==
+"@ogre-tools/injectable@3.2.0", "@ogre-tools/injectable@^3.2.0":
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/@ogre-tools/injectable/-/injectable-3.2.0.tgz#7d8f653cb3a2c0253a29422bcffd5123308600a9"
+ integrity sha512-aRlRdvLefJMBvFu1tRlTGNgpMbqE250lwMXT8Y6/ruC88rCL+TygCWcsJELad1OgqX1cfHkCgCYeQeohV+G3Zg==
dependencies:
"@ogre-tools/fp" "^3.0.0"
lodash "^4.17.21"