Commit 1a3b4a20 authored by Alex Andreev's avatar Alex Andreev
Browse files

Merge branch 'master' into show-extension-preferences-in-separate-page

parents 9527ee83 dbdde192
Showing with 372 additions and 264 deletions
+372 -264
......@@ -278,11 +278,22 @@ export interface CatalogEntitySettingsMenu {
};
}
export interface CatalogEntityContextMenuNavigate {
/**
* @param pathname The location to navigate to in the main iframe
*/
(pathname: string, forceMainFrame?: boolean): void;
/**
* @param pathname The location to navigate to in the current iframe. Useful for when called within the cluster frame
*/
(pathname: string, forceMainFrame: false): void;
}
export interface CatalogEntityContextMenuContext {
/**
* Navigate to the specified pathname
*/
navigate: (pathname: string) => void;
navigate: CatalogEntityContextMenuNavigate;
menuItems: CatalogEntityContextMenu[];
}
......
......@@ -32,6 +32,7 @@ export * from "./objects";
export * from "./openBrowser";
export * from "./paths";
export * from "./promise-exec";
export * from "./readonly";
export * from "./reject-promise";
export * from "./singleton";
export * from "./sort-compare";
......
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ReadonlyDeep } from "type-fest";
export function readonly<T>(src: T): ReadonlyDeep<T> {
return src as ReadonlyDeep<T>;
}
......@@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export type { StatusBarRegistration } from "../../renderer/components/status-bar/status-bar-registration";
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../../renderer/components/kube-object-menu/kube-object-menu-registration";
export type { AppPreferenceRegistration, AppPreferenceComponents } from "../../renderer/components/+preferences/app-preferences/app-preference-registration";
export type { KubeObjectDetailRegistration, KubeObjectDetailComponents } from "../registries/kube-object-detail-registry";
export type { KubeObjectStatusRegistration } from "../../renderer/components/kube-object-status-icon/kube-object-status-registration";
......@@ -12,3 +12,4 @@ export type { ClusterPageMenuRegistration, ClusterPageMenuComponents } from "../
export type { ProtocolHandlerRegistration, RouteParams as ProtocolRouteParams, RouteHandler as ProtocolRouteHandler } from "../registries/protocol-handler";
export type { CustomCategoryViewProps, CustomCategoryViewComponents, CustomCategoryViewRegistration } from "../../renderer/components/+catalog/custom-views";
export type { ShellEnvModifier, ShellEnvContext } from "../../main/shell-session/shell-env-modifier/shell-env-modifier-registration";
export type { KubeObjectContextMenuItem, KubeObjectOnContextMenuOpenContext, KubeObjectOnContextMenuOpen, KubeObjectHandlers, KubeObjectHandlerRegistration } from "../../renderer/kube-object/handler";
......@@ -20,7 +20,7 @@ import type { AppPreferenceRegistration } from "../renderer/components/+preferen
import type { AdditionalCategoryColumnRegistration } from "../renderer/components/+catalog/custom-category-columns";
import type { CustomCategoryViewRegistration } from "../renderer/components/+catalog/custom-views";
import type { StatusBarRegistration } from "../renderer/components/status-bar/status-bar-registration";
import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration";
import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/kube-object-menu-registration";
import type { WorkloadsOverviewDetailRegistration } from "../renderer/components/+workloads-overview/workloads-overview-detail-registration";
import type { KubeObjectStatusRegistration } from "../renderer/components/kube-object-status-icon/kube-object-status-registration";
import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "./as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
......@@ -31,6 +31,7 @@ import { pipeline } from "@ogre-tools/fp";
import { getExtensionRoutePath } from "../renderer/routes/get-extension-route-path";
import { navigateToRouteInjectionToken } from "../common/front-end-routing/navigate-to-route-injection-token";
import type { AppPreferenceTabRegistration } from "../renderer/components/+preferences/app-preference-tab/app-preference-tab-registration";
import type { KubeObjectHandlerRegistration } from "../renderer/kube-object/handler";
export class LensRendererExtension extends LensExtension {
globalPages: registries.PageRegistration[] = [];
......@@ -51,6 +52,7 @@ export class LensRendererExtension extends LensExtension {
topBarItems: TopBarRegistration[] = [];
additionalCategoryColumns: AdditionalCategoryColumnRegistration[] = [];
customCategoryViews: CustomCategoryViewRegistration[] = [];
kubeObjectHandlers: KubeObjectHandlerRegistration[] = [];
async navigate<P extends object>(pageId?: string, params?: P) {
const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(
......
......@@ -15,6 +15,10 @@ import sendCommandInjectable from "../../renderer/components/dock/terminal/send-
import { podsStore } from "../../renderer/components/+workloads-pods/pods.store";
import renameTabInjectable from "../../renderer/components/dock/dock/rename-tab.injectable";
import { asLegacyGlobalObjectForExtensionApiWithModifications } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications";
import { ConfirmDialog as _ConfirmDialog } from "../../renderer/components/confirm-dialog";
import type { ConfirmDialogBooleanParams, ConfirmDialogParams, ConfirmDialogProps } from "../../renderer/components/confirm-dialog";
import openConfirmDialogInjectable from "../../renderer/components/confirm-dialog/open.injectable";
import confirmInjectable from "../../renderer/components/confirm-dialog/confirm.injectable";
// layouts
export * from "../../renderer/components/layout/main-layout";
......@@ -43,6 +47,16 @@ export type {
} from "../../renderer/components/+catalog/custom-category-columns";
// other components
export type {
ConfirmDialogBooleanParams,
ConfirmDialogParams,
ConfirmDialogProps,
};
export const ConfirmDialog = Object.assign(_ConfirmDialog, {
open: asLegacyGlobalFunctionForExtensionApi(openConfirmDialogInjectable),
confirm: asLegacyGlobalFunctionForExtensionApi(confirmInjectable),
});
export * from "../../renderer/components/icon";
export * from "../../renderer/components/tooltip";
export * from "../../renderer/components/tabs";
......@@ -50,7 +64,6 @@ export * from "../../renderer/components/table";
export * from "../../renderer/components/badge";
export * from "../../renderer/components/drawer";
export * from "../../renderer/components/dialog";
export * from "../../renderer/components/confirm-dialog";
export * from "../../renderer/components/line-progress";
export * from "../../renderer/components/menu";
export * from "../../renderer/components/notifications";
......
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { CatalogEntityContextMenu } from "../api/catalog-entity";
import withConfirmationInjectable from "../components/confirm-dialog/with-confirm.injectable";
export interface NormalizedCatalogEntityContextMenu {
title: string;
icon?: string;
onClick: () => void;
}
export type NormalizeCatalogEntityContextMenu = (menuItem: CatalogEntityContextMenu) => NormalizedCatalogEntityContextMenu;
const normalizeCatalogEntityContextMenuInjectable = getInjectable({
id: "normalize-catalog-entity-context-menu",
instantiate: (di): NormalizeCatalogEntityContextMenu => {
const withConfirmation = di.inject(withConfirmationInjectable);
return (menuItem) => {
if (menuItem.confirm) {
return {
title: menuItem.title,
icon: menuItem.icon,
onClick: withConfirmation({
message: menuItem.confirm.message,
ok: menuItem.onClick,
okButtonProps: {
primary: false,
accent: true,
},
}),
};
}
return menuItem;
};
},
});
export default normalizeCatalogEntityContextMenuInjectable;
......@@ -7,24 +7,32 @@ import React from "react";
import { cssNames } from "../../utils";
import type { MenuActionsProps } from "../menu/menu-actions";
import { MenuActions } from "../menu/menu-actions";
import type { CatalogEntity, CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
import type { CatalogEntity, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
import { observer } from "mobx-react";
import { makeObservable, observable } from "mobx";
import { navigate } from "../../navigation";
import { MenuItem } from "../menu";
import { ConfirmDialog } from "../confirm-dialog";
import { Icon } from "../icon";
import { HotbarToggleMenuItem } from "./hotbar-toggle-menu-item";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { Navigate } from "../../navigation/navigate.injectable";
import navigateInjectable from "../../navigation/navigate.injectable";
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
export interface CatalogEntityDrawerMenuProps<T extends CatalogEntity> extends MenuActionsProps {
entity: T;
}
interface Dependencies {
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
navigate: Navigate;
}
@observer
export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Component<CatalogEntityDrawerMenuProps<T>> {
class NonInjectedCatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Component<CatalogEntityDrawerMenuProps<T> & Dependencies> {
@observable private contextMenu: CatalogEntityContextMenuContext;
constructor(props: CatalogEntityDrawerMenuProps<T>) {
constructor(props: CatalogEntityDrawerMenuProps<T> & Dependencies) {
super(props);
makeObservable(this);
}
......@@ -32,52 +40,28 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
componentDidMount() {
this.contextMenu = {
menuItems: [],
navigate: (url: string) => navigate(url),
navigate: this.props.navigate,
};
this.props.entity?.onContextMenuOpen(this.contextMenu);
}
onMenuItemClick(menuItem: CatalogEntityContextMenu) {
if (menuItem.confirm) {
ConfirmDialog.open({
okButtonProps: {
primary: false,
accent: true,
},
ok: () => {
menuItem.onClick();
},
message: menuItem.confirm.message,
});
} else {
menuItem.onClick();
}
}
getMenuItems(entity: T): React.ReactChild[] {
if (!entity) {
return [];
}
const items: React.ReactChild[] = [];
for (const menuItem of this.contextMenu.menuItems) {
if (!menuItem.icon) {
continue;
}
const key = Icon.isSvg(menuItem.icon) ? "svg" : "material";
items.push(
<MenuItem key={menuItem.title} onClick={() => this.onMenuItemClick(menuItem)}>
const items = this.contextMenu.menuItems
.filter(menuItem => menuItem.icon)
.map(this.props.normalizeMenuItem)
.map(menuItem => (
<MenuItem key={menuItem.title} onClick={menuItem.onClick}>
<Icon
interactive
tooltip={menuItem.title}
{...{ [key]: menuItem.icon }}
{...{ [Icon.isSvg(menuItem.icon) ? "svg" : "material"]: menuItem.icon }}
/>
</MenuItem>,
);
}
</MenuItem>
));
items.push(
<HotbarToggleMenuItem
......@@ -109,3 +93,11 @@ export class CatalogEntityDrawerMenu<T extends CatalogEntity> extends React.Comp
);
}
}
export const CatalogEntityDrawerMenu = withInjectables<Dependencies, CatalogEntityDrawerMenuProps<CatalogEntity>>(NonInjectedCatalogEntityDrawerMenu, {
getProps: (di, props) => ({
...props,
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
navigate: di.inject(navigateInjectable),
}),
}) as <Entity extends CatalogEntity>(props: CatalogEntityDrawerMenuProps<Entity>) => React.ReactElement;
......@@ -11,10 +11,9 @@ import { ItemListLayout } from "../item-object-list";
import type { IComputedValue } from "mobx";
import { action, computed, 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";
import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity";
import { ConfirmDialog } from "../confirm-dialog";
import type { CatalogEntityContextMenuContext } from "../../api/catalog-entity";
import type { HotbarStore } from "../../../common/hotbar-store";
import type { CatalogEntity } from "../../../common/catalog";
import { catalogCategoryRegistry } from "../../../common/catalog";
import { CatalogAddButton } from "./catalog-add-button";
......@@ -42,7 +41,10 @@ import { browseCatalogTab } from "./catalog-browse-tab";
import type { AppEvent } from "../../../common/app-event-bus/event-bus";
import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable";
import hotbarStoreInjectable from "../../../common/hotbar-store.injectable";
import type { HotbarStore } from "../../../common/hotbar-store";
import type { Navigate } from "../../navigation/navigate.injectable";
import navigateInjectable from "../../navigation/navigate.injectable";
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
interface Dependencies {
catalogPreviousActiveTabStorage: { set: (value: string ) => void; get: () => string };
......@@ -50,14 +52,14 @@ interface Dependencies {
getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns;
customCategoryViews: IComputedValue<Map<string, Map<string, RegisteredCustomCategoryViewDecl>>>;
emitEvent: (event: AppEvent) => void;
routeParameters: {
group: IComputedValue<string>;
kind: IComputedValue<string>;
};
navigateToCatalog: NavigateToCatalog;
hotbarStore: HotbarStore;
navigate: Navigate;
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
}
@observer
......@@ -93,7 +95,7 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
async componentDidMount() {
this.contextMenu = {
menuItems: observable.array([]),
navigate: (url: string) => navigate(url),
navigate: this.props.navigate,
};
disposeOnUnmount(this, [
this.props.catalogEntityStore.watch(),
......@@ -149,23 +151,6 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
}
};
onMenuItemClick(menuItem: CatalogEntityContextMenu) {
if (menuItem.confirm) {
ConfirmDialog.open({
okButtonProps: {
primary: false,
accent: true,
},
ok: () => {
menuItem.onClick();
},
message: menuItem.confirm.message,
});
} else {
menuItem.onClick();
}
}
get categories() {
return catalogCategoryRegistry.items;
}
......@@ -209,11 +194,13 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
View Details
</MenuItem>
{
this.contextMenu.menuItems.map((menuItem, index) => (
<MenuItem key={index} onClick={() => this.onMenuItemClick(menuItem)}>
{menuItem.title}
</MenuItem>
))
this.contextMenu.menuItems
.map(this.props.normalizeMenuItem)
.map((menuItem, index) => (
<MenuItem key={index} onClick={menuItem.onClick}>
{menuItem.title}
</MenuItem>
))
}
<HotbarToggleMenuItem
key="hotbar-toggle"
......@@ -342,8 +329,9 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
}
}
export const Catalog = withInjectables<Dependencies>( NonInjectedCatalog, {
getProps: (di) => ({
export const Catalog = withInjectables<Dependencies>(NonInjectedCatalog, {
getProps: (di, props) => ({
...props,
catalogEntityStore: di.inject(catalogEntityStoreInjectable),
catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable),
getCategoryColumns: di.inject(getCategoryColumnsInjectable),
......@@ -352,5 +340,7 @@ export const Catalog = withInjectables<Dependencies>( NonInjectedCatalog, {
navigateToCatalog: di.inject(navigateToCatalogInjectable),
emitEvent: di.inject(appEventBusInjectable).emit,
hotbarStore: di.inject(hotbarStoreInjectable),
navigate: di.inject(navigateInjectable),
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
}),
});
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ExtendableDisposer } from "../../../common/utils";
import { downloadFile, downloadJson } from "../../../common/utils";
import { Notifications } from "../notifications";
import React from "react";
import path from "path";
import { SemVer } from "semver";
import URLParse from "url-parse";
import type { InstallRequest } from "./attempt-install/install-request";
import { reduce } from "lodash";
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
import type { Confirm } from "../confirm-dialog/confirm.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
import getBaseRegistryUrlInjectable from "./get-base-registry-url/get-base-registry-url.injectable";
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import confirmInjectable from "../confirm-dialog/confirm.injectable";
export interface ExtensionInfo {
name: string;
version?: string;
requireConfirmation?: boolean;
}
export type AttemptInstallByInfo = (info: ExtensionInfo) => Promise<void>;
interface Dependencies {
attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise<void>;
getBaseRegistryUrl: () => Promise<string>;
extensionInstallationStateStore: ExtensionInstallationStateStore;
confirm: Confirm;
}
const attemptInstallByInfo = ({
attemptInstall,
getBaseRegistryUrl,
extensionInstallationStateStore,
confirm,
}: Dependencies): AttemptInstallByInfo => (
async (info) => {
const { name, version, requireConfirmation = false } = info;
const disposer = extensionInstallationStateStore.startPreInstall();
const baseUrl = await getBaseRegistryUrl();
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
let json: any;
let finalVersion = version;
try {
json = await downloadJson({ url: registryUrl }).promise;
if (!json || json.error || typeof json.versions !== "object" || !json.versions) {
const message = json?.error ? `: ${json.error}` : "";
Notifications.error(`Failed to get registry information for that extension${message}`);
return disposer();
}
} catch (error) {
if (error instanceof SyntaxError) {
// assume invalid JSON
console.warn("Set registry has invalid json", { url: baseUrl }, error);
Notifications.error("Failed to get valid registry information for that extension. Registry did not return valid JSON");
} else {
console.error("Failed to download registry information", error);
Notifications.error(`Failed to get valid registry information for that extension. ${error}`);
}
return disposer();
}
if (version) {
if (!json.versions[version]) {
if (json["dist-tags"][version]) {
finalVersion = json["dist-tags"][version];
} else {
Notifications.error((
<p>
The <em>{name}</em> extension does not have a version or tag <code>{version}</code>.
</p>
));
return disposer();
}
}
} else {
const versions = Object.keys(json.versions)
.map(version => new SemVer(version, { loose: true, includePrerelease: true }))
// ignore pre-releases for auto picking the version
.filter(version => version.prerelease.length === 0);
finalVersion = reduce(
versions,
(prev, curr) => prev.compareMain(curr) === -1 ? curr : prev,
).format();
}
if (requireConfirmation) {
const proceed = await confirm({
message: (
<p>
Are you sure you want to install{" "}
<b>
{name}@{finalVersion}
</b>
?
</p>
),
labelCancel: "Cancel",
labelOk: "Install",
});
if (!proceed) {
return disposer();
}
}
const url = json.versions[finalVersion].dist.tarball;
const fileName = path.basename(url);
const { promise: dataP } = downloadFile({ url, timeout: 10 * 60 * 1000 });
return attemptInstall({ fileName, dataP }, disposer);
}
);
const attemptInstallByInfoInjectable = getInjectable({
id: "attempt-install-by-info",
instantiate: (di) => attemptInstallByInfo({
attemptInstall: di.inject(attemptInstallInjectable),
getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable),
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
confirm: di.inject(confirmInjectable),
}),
});
export default attemptInstallByInfoInjectable;
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { attemptInstallByInfo } from "./attempt-install-by-info";
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
import getBaseRegistryUrlInjectable from "../get-base-registry-url/get-base-registry-url.injectable";
import extensionInstallationStateStoreInjectable
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
const attemptInstallByInfoInjectable = getInjectable({
id: "attempt-install-by-info",
instantiate: (di) =>
attemptInstallByInfo({
attemptInstall: di.inject(attemptInstallInjectable),
getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable),
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
}),
});
export default attemptInstallByInfoInjectable;
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ExtendableDisposer } from "../../../../common/utils";
import { downloadFile, downloadJson } from "../../../../common/utils";
import { Notifications } from "../../notifications";
import { ConfirmDialog } from "../../confirm-dialog";
import React from "react";
import path from "path";
import { SemVer } from "semver";
import URLParse from "url-parse";
import type { InstallRequest } from "../attempt-install/install-request";
import lodash from "lodash";
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
export interface ExtensionInfo {
name: string;
version?: string;
requireConfirmation?: boolean;
}
interface Dependencies {
attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise<void>;
getBaseRegistryUrl: () => Promise<string>;
extensionInstallationStateStore: ExtensionInstallationStateStore;
}
export const attemptInstallByInfo = ({ attemptInstall, getBaseRegistryUrl, extensionInstallationStateStore }: Dependencies) => async ({
name,
version,
requireConfirmation = false,
}: ExtensionInfo) => {
const disposer = extensionInstallationStateStore.startPreInstall();
const baseUrl = await getBaseRegistryUrl();
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
let json: any;
try {
json = await downloadJson({ url: registryUrl }).promise;
if (!json || json.error || typeof json.versions !== "object" || !json.versions) {
const message = json?.error ? `: ${json.error}` : "";
Notifications.error(`Failed to get registry information for that extension${message}`);
return disposer();
}
} catch (error) {
if (error instanceof SyntaxError) {
// assume invalid JSON
console.warn("Set registry has invalid json", { url: baseUrl }, error);
Notifications.error("Failed to get valid registry information for that extension. Registry did not return valid JSON");
} else {
console.error("Failed to download registry information", error);
Notifications.error(`Failed to get valid registry information for that extension. ${error}`);
}
return disposer();
}
if (version) {
if (!json.versions[version]) {
if (json["dist-tags"][version]) {
version = json["dist-tags"][version];
} else {
Notifications.error(
<p>
The <em>{name}</em> extension does not have a version or tag{" "}
<code>{version}</code>.
</p>,
);
return disposer();
}
}
} else {
const versions = Object.keys(json.versions)
.map(
version =>
new SemVer(version, { loose: true, includePrerelease: true }),
)
// ignore pre-releases for auto picking the version
.filter(version => version.prerelease.length === 0);
version = lodash.reduce(versions, (prev, curr) =>
prev.compareMain(curr) === -1 ? curr : prev,
).format();
}
if (requireConfirmation) {
const proceed = await ConfirmDialog.confirm({
message: (
<p>
Are you sure you want to install{" "}
<b>
{name}@{version}
</b>
?
</p>
),
labelCancel: "Cancel",
labelOk: "Install",
});
if (!proceed) {
return disposer();
}
}
const url = json.versions[version].dist.tarball;
const fileName = path.basename(url);
const { promise: dataP } = downloadFile({ url, timeout: 10 * 60 * 1000 });
return attemptInstall({ fileName, dataP }, disposer);
};
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import React from "react";
import type { InstalledExtension } from "../../../extensions/extension-discovery/extension-discovery";
import type { LensExtensionId } from "../../../extensions/lens-extension";
import { extensionDisplayName } from "../../../extensions/lens-extension";
import type { Confirm } from "../confirm-dialog/confirm.injectable";
import confirmInjectable from "../confirm-dialog/confirm.injectable";
import uninstallExtensionInjectable from "./uninstall-extension/uninstall-extension.injectable";
interface Dependencies {
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
confirm: Confirm;
}
export type ConfirmUninstallExtension = (ext: InstalledExtension) => Promise<void>;
const confirmUninstallExtension = ({
uninstallExtension,
confirm,
}: Dependencies): ConfirmUninstallExtension => (
async (extension) => {
const displayName = extensionDisplayName(
extension.manifest.name,
extension.manifest.version,
);
const confirmed = await confirm({
message: (
<p>
Are you sure you want to uninstall extension <b>{displayName}</b>?
</p>
),
labelOk: "Yes",
labelCancel: "No",
});
if (confirmed) {
await uninstallExtension(extension.id);
}
}
);
const confirmUninstallExtensionInjectable = getInjectable({
id: "confirm-uninstall-extension",
instantiate: (di) => confirmUninstallExtension({
uninstallExtension: di.inject(uninstallExtensionInjectable),
confirm: di.inject(confirmInjectable),
}),
});
export default confirmUninstallExtensionInjectable;
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import type { InstalledExtension } from "../../../../extensions/extension-discovery/extension-discovery";
import type { LensExtensionId } from "../../../../extensions/lens-extension";
import { extensionDisplayName } from "../../../../extensions/lens-extension";
import { ConfirmDialog } from "../../confirm-dialog";
interface Dependencies {
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
}
export const confirmUninstallExtension =
({ uninstallExtension }: Dependencies) =>
async (extension: InstalledExtension): Promise<void> => {
const displayName = extensionDisplayName(
extension.manifest.name,
extension.manifest.version,
);
const confirmed = await ConfirmDialog.confirm({
message: (
<p>
Are you sure you want to uninstall extension <b>{displayName}</b>?
</p>
),
labelOk: "Yes",
labelCancel: "No",
});
if (confirmed) {
await uninstallExtension(extension.id);
}
};
......@@ -26,7 +26,8 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import userExtensionsInjectable from "./user-extensions/user-extensions.injectable";
import enableExtensionInjectable from "./enable-extension/enable-extension.injectable";
import disableExtensionInjectable from "./disable-extension/disable-extension.injectable";
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension/confirm-uninstall-extension.injectable";
import type { ConfirmUninstallExtension } from "./confirm-uninstall-extension.injectable";
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension.injectable";
import installFromInputInjectable from "./install-from-input/install-from-input.injectable";
import installFromSelectFileDialogInjectable from "./install-from-select-file-dialog.injectable";
import type { LensExtensionId } from "../../../extensions/lens-extension";
......@@ -39,7 +40,7 @@ interface Dependencies {
userExtensions: IComputedValue<InstalledExtension[]>;
enableExtension: (id: LensExtensionId) => void;
disableExtension: (id: LensExtensionId) => void;
confirmUninstallExtension: (extension: InstalledExtension) => Promise<void>;
confirmUninstallExtension: ConfirmUninstallExtension;
installFromInput: (input: string) => Promise<void>;
installFromSelectFileDialog: () => Promise<void>;
installOnDrop: (files: File[]) => Promise<void>;
......
......@@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
import { installFromInput } from "./install-from-input";
import attemptInstallByInfoInjectable from "../attempt-install-by-info/attempt-install-by-info.injectable";
import attemptInstallByInfoInjectable from "../attempt-install-by-info.injectable";
import extensionInstallationStateStoreInjectable
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
......
......@@ -12,7 +12,7 @@ import path from "path";
import React from "react";
import { readFileNotify } from "../read-file-notify/read-file-notify";
import type { InstallRequest } from "../attempt-install/install-request";
import type { ExtensionInfo } from "../attempt-install-by-info/attempt-install-by-info";
import type { ExtensionInfo } from "../attempt-install-by-info.injectable";
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
interface Dependencies {
......
......@@ -12,7 +12,6 @@ import React from "react";
import type { ClusterRoleBinding, ClusterRoleBindingSubject } from "../../../../common/k8s-api/endpoints";
import { autoBind, ObservableHashSet, prevDefault } from "../../../utils";
import { AddRemoveButtons } from "../../add-remove-buttons";
import { ConfirmDialog } from "../../confirm-dialog";
import { DrawerTitle } from "../../drawer";
import type { KubeObjectDetailsProps } from "../../kube-object-details";
import { KubeObjectMeta } from "../../kube-object-meta";
......@@ -20,15 +19,22 @@ import { Table, TableCell, TableHead, TableRow } from "../../table";
import { ClusterRoleBindingDialog } from "./dialog";
import { clusterRoleBindingsStore } from "./store";
import { hashClusterRoleBindingSubject } from "./hashers";
import type { OpenConfirmDialog } from "../../confirm-dialog/open.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import openConfirmDialogInjectable from "../../confirm-dialog/open.injectable";
export interface ClusterRoleBindingDetailsProps extends KubeObjectDetailsProps<ClusterRoleBinding> {
}
interface Dependencies {
openConfirmDialog: OpenConfirmDialog;
}
@observer
export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindingDetailsProps> {
class NonInjectedClusterRoleBindingDetails extends React.Component<ClusterRoleBindingDetailsProps & Dependencies> {
selectedSubjects = new ObservableHashSet<ClusterRoleBindingSubject>([], hashClusterRoleBindingSubject);
constructor(props: ClusterRoleBindingDetailsProps) {
constructor(props: ClusterRoleBindingDetailsProps & Dependencies) {
super(props);
autoBind(this);
}
......@@ -42,10 +48,10 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
}
removeSelectedSubjects() {
const { object: clusterRoleBinding } = this.props;
const { object: clusterRoleBinding, openConfirmDialog } = this.props;
const { selectedSubjects } = this;
ConfirmDialog.open({
openConfirmDialog({
ok: () => clusterRoleBindingsStore.removeSubjects(clusterRoleBinding, selectedSubjects),
labelOk: `Remove`,
message: (
......@@ -123,3 +129,10 @@ export class ClusterRoleBindingDetails extends React.Component<ClusterRoleBindin
);
}
}
export const ClusterRoleBindingDetails = withInjectables<Dependencies, ClusterRoleBindingDetailsProps>(NonInjectedClusterRoleBindingDetails, {
getProps: (di, props) => ({
...props,
openConfirmDialog: di.inject(openConfirmDialogInjectable),
}),
});
......@@ -11,7 +11,6 @@ import React from "react";
import type { RoleBinding, RoleBindingSubject } from "../../../../common/k8s-api/endpoints";
import { prevDefault } from "../../../utils";
import { AddRemoveButtons } from "../../add-remove-buttons";
import { ConfirmDialog } from "../../confirm-dialog";
import { DrawerTitle } from "../../drawer";
import type { KubeObjectDetailsProps } from "../../kube-object-details";
import { KubeObjectMeta } from "../../kube-object-meta";
......@@ -20,12 +19,19 @@ import { RoleBindingDialog } from "./dialog";
import { roleBindingsStore } from "./store";
import { ObservableHashSet } from "../../../../common/utils/hash-set";
import { hashRoleBindingSubject } from "./hashers";
import type { OpenConfirmDialog } from "../../confirm-dialog/open.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import openConfirmDialogInjectable from "../../confirm-dialog/open.injectable";
export interface RoleBindingDetailsProps extends KubeObjectDetailsProps<RoleBinding> {
}
interface Dependencies {
openConfirmDialog: OpenConfirmDialog;
}
@observer
export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps> {
class NonInjectedRoleBindingDetails extends React.Component<RoleBindingDetailsProps & Dependencies> {
selectedSubjects = new ObservableHashSet<RoleBindingSubject>([], hashRoleBindingSubject);
async componentDidMount() {
......@@ -37,10 +43,10 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
}
removeSelectedSubjects = () => {
const { object: roleBinding } = this.props;
const { object: roleBinding, openConfirmDialog } = this.props;
const { selectedSubjects } = this;
ConfirmDialog.open({
openConfirmDialog({
ok: () => roleBindingsStore.removeSubjects(roleBinding, selectedSubjects.toJSON()),
labelOk: `Remove`,
message: (
......@@ -118,3 +124,10 @@ export class RoleBindingDetails extends React.Component<RoleBindingDetailsProps>
);
}
}
export const RoleBindingDetails = withInjectables<Dependencies, RoleBindingDetailsProps>(NonInjectedRoleBindingDetails, {
getProps: (di, props) => ({
...props,
openConfirmDialog: di.inject(openConfirmDialogInjectable),
}),
});
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment