mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
fix in-tree extensions
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
parent
cc684d62c3
commit
10fee4a451
8221
extensions/metrics-cluster-feature/package-lock.json
generated
8221
extensions/metrics-cluster-feature/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,6 @@
|
||||
"dev": "npm run build --watch",
|
||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"jest": "^26.6.3",
|
||||
|
||||
@ -1,21 +1,55 @@
|
||||
import { LensRendererExtension } from "@k8slens/extensions";
|
||||
import { LensRendererExtension, Store, Interface, Component } from "@k8slens/extensions";
|
||||
import { MetricsFeature } from "./src/metrics-feature";
|
||||
import React from "react";
|
||||
|
||||
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
||||
clusterFeatures = [
|
||||
{
|
||||
title: "Metrics Stack",
|
||||
components: {
|
||||
Description: () => (
|
||||
<span>
|
||||
Enable timeseries data visualization (Prometheus stack) for your cluster.
|
||||
Install this only if you don't have existing Prometheus stack installed.
|
||||
You can see preview of manifests <a href="https://github.com/lensapp/lens/tree/master/extensions/metrics-cluster-feature/resources" rel="noreferrer" target="_blank">here</a>.
|
||||
</span>
|
||||
)
|
||||
},
|
||||
feature: new MetricsFeature()
|
||||
onActivate() {
|
||||
const category = Store.catalogCategories.getForGroupKind<Store.KubernetesClusterCategory>("entity.k8slens.dev", "KubernetesCluster");
|
||||
|
||||
if (!category) {
|
||||
return;
|
||||
}
|
||||
];
|
||||
|
||||
category.on("contextMenuOpen", this.clusterContextMenuOpen.bind(this));
|
||||
}
|
||||
|
||||
async clusterContextMenuOpen(cluster: Store.KubernetesCluster, ctx: Interface.CatalogEntityContextMenuContext) {
|
||||
if (!cluster.status.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metricsFeature = new MetricsFeature();
|
||||
|
||||
await metricsFeature.updateStatus(cluster);
|
||||
|
||||
if (metricsFeature.status.installed) {
|
||||
if (metricsFeature.status.canUpgrade) {
|
||||
ctx.menuItems.unshift({
|
||||
icon: "refresh",
|
||||
title: "Upgrade Lens Metrics stack",
|
||||
onClick: async () => {
|
||||
metricsFeature.upgrade(cluster);
|
||||
}
|
||||
});
|
||||
}
|
||||
ctx.menuItems.unshift({
|
||||
icon: "toggle_off",
|
||||
title: "Uninstall Lens Metrics stack",
|
||||
onClick: async () => {
|
||||
await metricsFeature.uninstall(cluster);
|
||||
|
||||
Component.Notifications.info(`Lens Metrics has been removed from ${cluster.metadata.name}`, { timeout: 10_000 });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ctx.menuItems.unshift({
|
||||
icon: "toggle_on",
|
||||
title: "Install Lens Metrics stack",
|
||||
onClick: async () => {
|
||||
metricsFeature.install(cluster);
|
||||
|
||||
Component.Notifications.info(`Lens Metrics is now installed to ${cluster.metadata.name}`, { timeout: 10_000 });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ export class MetricsFeature extends ClusterFeature.Feature {
|
||||
storageClass: null,
|
||||
};
|
||||
|
||||
async install(cluster: Store.Cluster): Promise<void> {
|
||||
async install(cluster: Store.KubernetesCluster): Promise<void> {
|
||||
// Check if there are storageclasses
|
||||
const storageClassApi = K8sApi.forCluster(cluster, K8sApi.StorageClass);
|
||||
const scs = await storageClassApi.list();
|
||||
@ -62,11 +62,11 @@ export class MetricsFeature extends ClusterFeature.Feature {
|
||||
super.applyResources(cluster, path.join(__dirname, "../resources/"));
|
||||
}
|
||||
|
||||
async upgrade(cluster: Store.Cluster): Promise<void> {
|
||||
async upgrade(cluster: Store.KubernetesCluster): Promise<void> {
|
||||
return this.install(cluster);
|
||||
}
|
||||
|
||||
async updateStatus(cluster: Store.Cluster): Promise<ClusterFeature.FeatureStatus> {
|
||||
async updateStatus(cluster: Store.KubernetesCluster): Promise<ClusterFeature.FeatureStatus> {
|
||||
try {
|
||||
const statefulSet = K8sApi.forCluster(cluster, K8sApi.StatefulSet);
|
||||
const prometheus = await statefulSet.get({name: "prometheus", namespace: "lens-metrics"});
|
||||
@ -87,12 +87,13 @@ export class MetricsFeature extends ClusterFeature.Feature {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
async uninstall(cluster: Store.Cluster): Promise<void> {
|
||||
async uninstall(cluster: Store.KubernetesCluster): Promise<void> {
|
||||
const namespaceApi = K8sApi.forCluster(cluster, K8sApi.Namespace);
|
||||
const clusterRoleBindingApi = K8sApi.forCluster(cluster, K8sApi.ClusterRoleBinding);
|
||||
const clusterRoleApi = K8sApi.forCluster(cluster, K8sApi.ClusterRole);
|
||||
|
||||
await namespaceApi.delete({name: "lens-metrics"});
|
||||
await clusterRoleBindingApi.delete({name: "lens-prometheus"});
|
||||
await clusterRoleApi.delete({name: "lens-prometheus"}); }
|
||||
await clusterRoleApi.delete({name: "lens-prometheus"});
|
||||
}
|
||||
}
|
||||
|
||||
8428
extensions/telemetry/package-lock.json
generated
8428
extensions/telemetry/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,6 @@
|
||||
"dev": "webpack --watch",
|
||||
"test": "jest --passWithNoTests --env=jsdom src $@"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"@types/analytics-node": "^3.1.3",
|
||||
|
||||
@ -102,13 +102,12 @@ export class Tracker extends Util.Singleton {
|
||||
}
|
||||
|
||||
protected reportData() {
|
||||
const clustersList = Store.clusterStore.enabledClustersList;
|
||||
const clustersList = Store.catalogEntities.getItemsForApiKind<Store.KubernetesCluster>("entity.k8slens.dev/v1alpha1", "KubernetesCluster");
|
||||
|
||||
this.event("generic-data", "report", {
|
||||
appVersion: App.version,
|
||||
os: this.os,
|
||||
clustersCount: clustersList.length,
|
||||
workspacesCount: Store.workspaceStore.enabledWorkspacesList.length,
|
||||
extensions: App.getEnabledExtensions()
|
||||
});
|
||||
|
||||
@ -118,10 +117,10 @@ export class Tracker extends Util.Singleton {
|
||||
});
|
||||
}
|
||||
|
||||
protected reportClusterData(cluster: Store.ClusterModel) {
|
||||
protected reportClusterData(cluster: Store.KubernetesCluster) {
|
||||
this.event("cluster-data", "report", {
|
||||
id: cluster.metadata.id,
|
||||
managed: !!cluster.ownerRef,
|
||||
managed: cluster.metadata.source !== "local",
|
||||
kubernetesVersion: cluster.metadata.version,
|
||||
distribution: cluster.metadata.distribution,
|
||||
nodesCount: cluster.metadata.nodes,
|
||||
|
||||
@ -16,19 +16,20 @@ export class CatalogCategoryRegistry {
|
||||
return toJS(this.categories);
|
||||
}
|
||||
|
||||
getEntityForData(data: CatalogEntityData) {
|
||||
const splitApiVersion = data.apiVersion.split("/");
|
||||
const group = splitApiVersion[0];
|
||||
const version = splitApiVersion[1];
|
||||
getForGroupKind<T extends CatalogCategory>(group: string, kind: string) {
|
||||
return this.categories.find((c) => c.spec.group === group && c.spec.names.kind === kind) as T;
|
||||
}
|
||||
|
||||
const category = this.categories.find((category) => {
|
||||
return category.spec.group === group && category.spec.names.kind === data.kind;
|
||||
});
|
||||
getEntityForData(data: CatalogEntityData) {
|
||||
const category = this.getCategoryForEntity(data);
|
||||
|
||||
if (!category) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const splitApiVersion = data.apiVersion.split("/");
|
||||
const version = splitApiVersion[1];
|
||||
|
||||
const specVersion = category.spec.versions.find((v) => v.name === version);
|
||||
|
||||
if (!specVersion) {
|
||||
@ -37,6 +38,19 @@ export class CatalogCategoryRegistry {
|
||||
|
||||
return new specVersion.entityClass(data);
|
||||
}
|
||||
|
||||
getCategoryForEntity<T extends CatalogCategory>(data: CatalogEntityData) {
|
||||
const splitApiVersion = data.apiVersion.split("/");
|
||||
const group = splitApiVersion[0];
|
||||
|
||||
const category = this.categories.find((category) => {
|
||||
return category.spec.group === group && category.spec.names.kind === data.kind;
|
||||
});
|
||||
|
||||
if (!category) return null;
|
||||
|
||||
return category as T;
|
||||
}
|
||||
}
|
||||
|
||||
export const catalogCategoryRegistry = new CatalogCategoryRegistry();
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { EventEmitter } from "events";
|
||||
import { observable } from "mobx";
|
||||
import { catalogCategoryRegistry } from "../catalog-category-registry";
|
||||
import { CatalogCategory, CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityData, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog-entity";
|
||||
@ -67,10 +68,14 @@ export class KubernetesCluster implements CatalogEntity {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const category = catalogCategoryRegistry.getCategoryForEntity<KubernetesClusterCategory>(this);
|
||||
|
||||
if (category) category.emit("contextMenuOpen", this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class KubernetesClusterCategory implements CatalogCategory {
|
||||
export class KubernetesClusterCategory extends EventEmitter implements CatalogCategory {
|
||||
public readonly apiVersion = "catalog.k8slens.dev/v1alpha1";
|
||||
public readonly kind = "CatalogCategory";
|
||||
public metadata = {
|
||||
|
||||
@ -3,11 +3,12 @@ import path from "path";
|
||||
import hb from "handlebars";
|
||||
import { observable } from "mobx";
|
||||
import { ResourceApplier } from "../main/resource-applier";
|
||||
import { Cluster } from "../main/cluster";
|
||||
import { KubernetesCluster } from "./core-api/stores";
|
||||
import logger from "../main/logger";
|
||||
import { app } from "electron";
|
||||
import { requestMain } from "../common/ipc";
|
||||
import { clusterKubectlApplyAllHandler } from "../common/cluster-ipc";
|
||||
import { clusterStore } from "../common/cluster-store";
|
||||
|
||||
export interface ClusterFeatureStatus {
|
||||
/** feature's current version, as set by the implementation */
|
||||
@ -44,7 +45,7 @@ export abstract class ClusterFeature {
|
||||
*
|
||||
* @param cluster the cluster that the feature is to be installed on
|
||||
*/
|
||||
abstract async install(cluster: Cluster): Promise<void>;
|
||||
abstract async install(cluster: KubernetesCluster): Promise<void>;
|
||||
|
||||
/**
|
||||
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be upgraded. The implementation
|
||||
@ -52,7 +53,7 @@ export abstract class ClusterFeature {
|
||||
*
|
||||
* @param cluster the cluster that the feature is to be upgraded on
|
||||
*/
|
||||
abstract async upgrade(cluster: Cluster): Promise<void>;
|
||||
abstract async upgrade(cluster: KubernetesCluster): Promise<void>;
|
||||
|
||||
/**
|
||||
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be uninstalled. The implementation
|
||||
@ -60,7 +61,7 @@ export abstract class ClusterFeature {
|
||||
*
|
||||
* @param cluster the cluster that the feature is to be uninstalled from
|
||||
*/
|
||||
abstract async uninstall(cluster: Cluster): Promise<void>;
|
||||
abstract async uninstall(cluster: KubernetesCluster): Promise<void>;
|
||||
|
||||
/**
|
||||
* to be implemented in the derived class, this method is called periodically by Lens to determine details about the feature's current status. The implementation
|
||||
@ -72,7 +73,7 @@ export abstract class ClusterFeature {
|
||||
*
|
||||
* @return a promise, resolved with the updated ClusterFeatureStatus
|
||||
*/
|
||||
abstract async updateStatus(cluster: Cluster): Promise<ClusterFeatureStatus>;
|
||||
abstract async updateStatus(cluster: KubernetesCluster): Promise<ClusterFeatureStatus>;
|
||||
|
||||
/**
|
||||
* this is a helper method that conveniently applies kubernetes resources to the cluster.
|
||||
@ -82,9 +83,15 @@ export abstract class ClusterFeature {
|
||||
* files are templated, the template parameters are filled using the templateContext field (See renderTemplate() method). Finally the resources are applied to the
|
||||
* cluster. As a string[] type resourceSpec is treated as an array of fully formed (not templated) kubernetes resources that are applied to the cluster
|
||||
*/
|
||||
protected async applyResources(cluster: Cluster, resourceSpec: string | string[]) {
|
||||
protected async applyResources(cluster: KubernetesCluster, resourceSpec: string | string[]) {
|
||||
let resources: string[];
|
||||
|
||||
const clusterModel = clusterStore.getById(cluster.metadata.uid);
|
||||
|
||||
if (!clusterModel) {
|
||||
throw new Error(`cluster not found`);
|
||||
}
|
||||
|
||||
if ( typeof resourceSpec === "string" ) {
|
||||
resources = this.renderTemplates(resourceSpec);
|
||||
} else {
|
||||
@ -92,9 +99,9 @@ export abstract class ClusterFeature {
|
||||
}
|
||||
|
||||
if (app) {
|
||||
await new ResourceApplier(cluster).kubectlApplyAll(resources);
|
||||
await new ResourceApplier(clusterModel).kubectlApplyAll(resources);
|
||||
} else {
|
||||
await requestMain(clusterKubectlApplyAllHandler, cluster.id, resources);
|
||||
await requestMain(clusterKubectlApplyAllHandler, cluster.metadata.uid, resources);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
|
||||
import { computed } from "mobx";
|
||||
import { CatalogEntity } from "../../common/catalog-entity";
|
||||
import { catalogEntityRegistry as registry } from "../../common/catalog-entity-registry";
|
||||
|
||||
export class CatalogEntityRegistry {
|
||||
@computed getItemsForApiKind<T extends CatalogEntity>(apiVersion: string, kind: string): T[] {
|
||||
return registry.getItemsForApiKind<T>(apiVersion, kind);
|
||||
}
|
||||
}
|
||||
|
||||
export const catalogEntities = new CatalogEntityRegistry();
|
||||
@ -1 +1,4 @@
|
||||
export { ExtensionStore } from "../extension-store";
|
||||
export { KubernetesCluster, KubernetesClusterCategory } from "../../common/catalog-entities/kubernetes-cluster";
|
||||
export { catalogCategoryRegistry as catalogCategories } from "../../common/catalog-category-registry";
|
||||
export { catalogEntities } from "./catalog";
|
||||
|
||||
1
src/extensions/interfaces/catalog.ts
Normal file
1
src/extensions/interfaces/catalog.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "../../common/catalog-entity";
|
||||
@ -1 +1,2 @@
|
||||
export * from "./registrations";
|
||||
export * from "./catalog";
|
||||
|
||||
@ -4,8 +4,6 @@ import React from "react";
|
||||
import { reaction } from "mobx";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { Features } from "./features";
|
||||
import { Removal } from "./removal";
|
||||
import { Status } from "./status";
|
||||
import { General } from "./general";
|
||||
import { Cluster } from "../../../main/cluster";
|
||||
@ -65,8 +63,6 @@ export class ClusterSettings extends React.Component<Props> {
|
||||
<PageLayout className="ClusterSettings" header={header} showOnTop={true}>
|
||||
<Status cluster={cluster}></Status>
|
||||
<General cluster={cluster}></General>
|
||||
<Features cluster={cluster}></Features>
|
||||
<Removal cluster={cluster}></Removal>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,110 +0,0 @@
|
||||
import React from "react";
|
||||
import { observable, reaction, comparer } from "mobx";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { Button } from "../../button";
|
||||
import { Notifications } from "../../notifications";
|
||||
import { Spinner } from "../../spinner";
|
||||
import { ClusterFeature } from "../../../../extensions/cluster-feature";
|
||||
import { interval } from "../../../utils";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster
|
||||
feature: ClusterFeature
|
||||
}
|
||||
|
||||
@observer
|
||||
export class InstallFeature extends React.Component<Props> {
|
||||
@observable loading = false;
|
||||
@observable message = "";
|
||||
|
||||
componentDidMount() {
|
||||
const feature = this.props.feature;
|
||||
const cluster = this.props.cluster;
|
||||
const statusUpdate = interval(20, () => {
|
||||
feature.updateStatus(cluster);
|
||||
});
|
||||
|
||||
statusUpdate.start(true);
|
||||
|
||||
disposeOnUnmount(this, () => {
|
||||
statusUpdate.stop();
|
||||
});
|
||||
|
||||
disposeOnUnmount(this,
|
||||
reaction(() => feature.status.installed, () => {
|
||||
this.loading = false;
|
||||
this.message = "";
|
||||
}, { equals: comparer.structural })
|
||||
);
|
||||
}
|
||||
|
||||
getActionButtons() {
|
||||
const { cluster, feature } = this.props;
|
||||
const disabled = !cluster.isAdmin || this.loading;
|
||||
const loadingIcon = this.loading ? <Spinner/> : null;
|
||||
|
||||
return (
|
||||
<div className="flex gaps align-center">
|
||||
{feature.status.canUpgrade &&
|
||||
<Button
|
||||
primary
|
||||
disabled={disabled}
|
||||
onClick={this.runAction(() =>
|
||||
feature.upgrade(cluster)
|
||||
)}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
}
|
||||
{feature.status.installed &&
|
||||
<Button
|
||||
accent
|
||||
disabled={disabled}
|
||||
onClick={this.runAction(async () => {
|
||||
this.message = "Uninstalling feature ...";
|
||||
feature.uninstall(cluster);
|
||||
})}
|
||||
>
|
||||
Uninstall
|
||||
</Button>
|
||||
}
|
||||
{!feature.status.installed && !feature.status.canUpgrade &&
|
||||
<Button
|
||||
primary
|
||||
disabled={disabled}
|
||||
onClick={this.runAction(async () =>{
|
||||
this.message = "Installing feature ...";
|
||||
feature.install(cluster);
|
||||
})}
|
||||
>
|
||||
Install
|
||||
</Button>
|
||||
}
|
||||
{loadingIcon}
|
||||
{!cluster.isAdmin && <span className='admin-note'>Actions can only be performed by admins.</span>}
|
||||
{cluster.isAdmin && this.loading && this.message !== "" && <span className='admin-note'>{this.message}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
runAction(action: () => Promise<any>): () => Promise<void> {
|
||||
return async () => {
|
||||
try {
|
||||
this.loading = true;
|
||||
await action();
|
||||
} catch (err) {
|
||||
Notifications.error(err.toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{this.props.children}
|
||||
<div className="button-area">{this.getActionButtons()}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
import React from "react";
|
||||
import { Cluster } from "../../../main/cluster";
|
||||
import { InstallFeature } from "./components/install-feature";
|
||||
import { SubTitle } from "../layout/sub-title";
|
||||
import { clusterFeatureRegistry } from "../../../extensions/registries/cluster-feature-registry";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
export class Features extends React.Component<Props> {
|
||||
render() {
|
||||
const { cluster } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Features</h2>
|
||||
{
|
||||
clusterFeatureRegistry
|
||||
.getItems()
|
||||
.map((f) => (
|
||||
<InstallFeature key={f.title} cluster={cluster} feature={f.feature}>
|
||||
<>
|
||||
<SubTitle title={f.title} />
|
||||
<p><f.components.Description /></p>
|
||||
</>
|
||||
</InstallFeature>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
import React from "react";
|
||||
import { Cluster } from "../../../main/cluster";
|
||||
import { RemoveClusterButton } from "./components/remove-cluster-button";
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
}
|
||||
|
||||
export class Removal extends React.Component<Props> {
|
||||
render() {
|
||||
const { cluster } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Removal</h2>
|
||||
<RemoveClusterButton cluster={cluster} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { computed, reaction } from "mobx";
|
||||
import { action, computed, reaction } from "mobx";
|
||||
import { CatalogEntity, catalogEntityRegistry } from "../../api/catalog-entity-registry";
|
||||
import { ItemObject, ItemStore } from "../../item.store";
|
||||
import { autobind } from "../../utils";
|
||||
@ -42,8 +42,9 @@ export class CatalogEntityItem implements ItemObject {
|
||||
this.entity.onRun(ctx);
|
||||
}
|
||||
|
||||
onContextMenuOpen(ctx: any) {
|
||||
this.entity.onContextMenuOpen(ctx);
|
||||
@action
|
||||
async onContextMenuOpen(ctx: any) {
|
||||
return this.entity.onContextMenuOpen(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import "./landing-page.scss";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { ItemListLayout } from "../item-object-list";
|
||||
import { IReactionDisposer, observable } from "mobx";
|
||||
import { IReactionDisposer, observable, reaction, toJS } from "mobx";
|
||||
import { CatalogEntityItem, CatalogEntityStore } from "./catalog-entity.store";
|
||||
import { navigate } from "../../navigation";
|
||||
import { kebabCase } from "lodash";
|
||||
@ -12,6 +12,8 @@ import { Icon } from "../icon";
|
||||
import { CatalogEntityContextMenuContext } from "../../api/catalog-entity-registry";
|
||||
import { Badge } from "../badge";
|
||||
import { hotbarStore } from "../../../common/hotbar-store";
|
||||
import { addClusterURL } from "../+add-cluster";
|
||||
import { autobind } from "../../utils";
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
@ -21,9 +23,14 @@ enum sortBy {
|
||||
@observer
|
||||
export class LandingPage extends React.Component {
|
||||
@observable private catalogEntityStore?: CatalogEntityStore;
|
||||
@observable.deep private contextMenu: CatalogEntityContextMenuContext;
|
||||
private disposers: IReactionDisposer[] = [];
|
||||
|
||||
componentDidMount() {
|
||||
this.contextMenu = {
|
||||
menuItems: [],
|
||||
navigate: (url: string) => navigate(url)
|
||||
};
|
||||
this.catalogEntityStore = new CatalogEntityStore();
|
||||
this.disposers.push(this.catalogEntityStore.watch());
|
||||
}
|
||||
@ -52,6 +59,31 @@ export class LandingPage extends React.Component {
|
||||
hotbar.items = hotbar.items.filter((i) => i.entity.uid !== item.id);
|
||||
}
|
||||
|
||||
@autobind()
|
||||
renderItemMenu(item: CatalogEntityItem) {
|
||||
const onOpen = async () => {
|
||||
await item.onContextMenuOpen(this.contextMenu);
|
||||
};
|
||||
|
||||
return (
|
||||
<MenuActions onOpen={() => onOpen()}>
|
||||
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item) }>
|
||||
<Icon material="add" interactive={true} title="Add to hotbar"/> Add to Hotbar
|
||||
</MenuItem>
|
||||
<MenuItem key="remove-from-hotbar" onClick={() => this.removeFromHotbar(item) }>
|
||||
<Icon material="clear" interactive={true} title="Remove from hotbar"/> Remove from Hotbar
|
||||
</MenuItem>
|
||||
{ this.contextMenu.menuItems.map((menuItem) => {
|
||||
return (
|
||||
<MenuItem key={menuItem.title} onClick={() => menuItem.onClick()}>
|
||||
<Icon material={menuItem.icon} interactive={true} title={menuItem.title}/> {menuItem.title}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuActions>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.catalogEntityStore) {
|
||||
return null;
|
||||
@ -81,31 +113,10 @@ export class LandingPage extends React.Component {
|
||||
{ title: item.phase, className: kebabCase(item.phase) }
|
||||
]}
|
||||
onDetails={(item: CatalogEntityItem) => item.onRun({ navigate: (url: string) => navigate(url)})}
|
||||
renderItemMenu={(item: CatalogEntityItem) => {
|
||||
const menuOpenContext: CatalogEntityContextMenuContext = {
|
||||
menuItems: [],
|
||||
navigate: (url: string) => navigate(url)
|
||||
};
|
||||
|
||||
item.onContextMenuOpen(menuOpenContext);
|
||||
|
||||
return (
|
||||
<MenuActions>
|
||||
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item) }>
|
||||
<Icon material="add" interactive={true} title="Add to hotbar"/> Add to Hotbar
|
||||
</MenuItem>
|
||||
<MenuItem key="remove-from-hotbar" onClick={() => this.removeFromHotbar(item) }>
|
||||
<Icon material="clear" interactive={true} title="Remove from hotbar"/> Remove from Hotbar
|
||||
</MenuItem>
|
||||
{ menuOpenContext.menuItems.map((menuItem) => {
|
||||
return (
|
||||
<MenuItem key={menuItem.title} onClick={() => menuItem.onClick()}>
|
||||
<Icon material={menuItem.icon} interactive={true} title={menuItem.title}/> {menuItem.title}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</MenuActions>
|
||||
);
|
||||
renderItemMenu={this.renderItemMenu}
|
||||
addRemoveButtons={{
|
||||
addTooltip: "Add Kubernetes Cluster",
|
||||
onAdd: () => navigate(addClusterURL()),
|
||||
}}
|
||||
/>
|
||||
</PageLayout>
|
||||
|
||||
@ -18,6 +18,7 @@ export interface MenuActionsProps extends Partial<MenuProps> {
|
||||
removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode);
|
||||
updateAction?(): void;
|
||||
removeAction?(): void;
|
||||
onOpen?(): void;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -70,6 +71,10 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
||||
...(typeof triggerIcon === "object" ? triggerIcon : {}),
|
||||
};
|
||||
|
||||
if (this.props.onOpen) {
|
||||
iconProps.onClick = this.props.onOpen;
|
||||
}
|
||||
|
||||
if (iconProps.tooltip && this.isOpen) {
|
||||
delete iconProps.tooltip; // don't show tooltip for icon when menu is open
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user