1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Mobx-6 migration (#2718)

* mobx-6 migration -- part 1

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6 migration -- part 2 (npx mobx-undecorate --keepDecorators)

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6 migration -- part 3 (more fixes)

Signed-off-by: Roman <ixrock@gmail.com>

* unwrap possible observables from IPC-messaging

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6 migration -- remove @autobind as class-decorator

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6: replacing @autobind() as method-decorator to @boundMethod

Signed-off-by: Roman <ixrock@gmail.com>

* mobx-6: use toJS()-wrapper since monkey-patching require(mobx).toJS doesn't work

Signed-off-by: Roman <ixrock@gmail.com>

* removed `@observable static`

Signed-off-by: Roman <ixrock@gmail.com>

* use {useDefineForClassFields: true} in tsconfig.json

Signed-off-by: Roman <ixrock@gmail.com>

* remove ExtendedObservableMap

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix: removed makeObservable(this) from "terminal-tab.tsx"

Signed-off-by: Roman <ixrock@gmail.com>

* storage-helper refactoring

Signed-off-by: Roman <ixrock@gmail.com>

* normalize usages of #observable-value.toJSON() / attempt to catch the wind

Signed-off-by: Roman <ixrock@gmail.com>

* refactoring, more possible branch fixes + lint

Signed-off-by: Roman <ixrock@gmail.com>

* debugging cluster-view error -- part 1

Signed-off-by: Roman <ixrock@gmail.com>

* fix: refreshing cluster-view on ready

Signed-off-by: Roman <ixrock@gmail.com>

* fix: various app-crashes related to KubeObject.spec.* access from "undefined"
fix: config-map-details crash

Signed-off-by: Roman <ixrock@gmail.com>

* fix: namespace-store refactoring / saving selected-namespaces to external json-file

Signed-off-by: Roman <ixrock@gmail.com>

* fix: don't cache mobx.when(() => this.someObservable) cause might not work as expected due later call of makeObservable(this) in constructor

Signed-off-by: Roman <ixrock@gmail.com>

* fix: app-crash on editing k8s resource

Signed-off-by: Roman <ixrock@gmail.com>

* fix: restore "all namespaces" on page reload

Signed-off-by: Roman <ixrock@gmail.com>

* - fix: persist table-sort params and cluster-view's sidebar state to lens-local-storage
- new-feature: auto-open main-window's devtools in development-mode (yes/no/ugly?)

Signed-off-by: Roman <ixrock@gmail.com>

* fix: crd definition details -> crashing with <AceEditor mode="json"> (added missing mode-file in ace-editor.tsx)

Signed-off-by: Roman <ixrock@gmail.com>

* fix: crd definitions -> groups selector couldn't deselect last selected option

Signed-off-by: Roman <ixrock@gmail.com>

* refactoring: extensions-api exports clarification for "@k8slens/extensions"

Signed-off-by: Roman <ixrock@gmail.com>

* fix: various app-crashes related to kube-events (events page, some details page, overview, etc.)

Signed-off-by: Roman <ixrock@gmail.com>

* Reverted "use {useDefineForClassFields: true} in tsconfig.json" (various app-crash fixes)
This flag seems to be not possible to use with class-inheritance in some cases.

Example / demo:
`KubeObject` class has initial type definitions for the fields like: "metadata", "kind", etc.
and constructor() has Object.assign(this, data);
Meanwhile child class, e.g. KubeEvent inherited from KubeObject and has it's own extra type definitions for underlying resource, e.g. "involvedObject", "source", etc.

So calling super(data) doesn't work as expected for child class as it's own type definitions overwrites data from parent's constructor with `undefined` at later point.

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge lint-fixes

Signed-off-by: Roman <ixrock@gmail.com>

* catalog.tsx / catalog-entities.store.ts refactoring & fixes

Signed-off-by: Roman <ixrock@gmail.com>

* fix: Catalog -> Browse all tab

Signed-off-by: Roman <ixrock@gmail.com>

* fix: CommandPalette doesn't appear from global menu by click/hotkey

Signed-off-by: Roman <ixrock@gmail.com>

* - Merging interfaces & classses to avoid overwriting fields from parent's super(data)-call with Object.assign(this, data). Otherwise use "declare" keyword at class field definition.

- Revamping {useDefineForClassFields: true} to avoid issues with non-observable class fields in some cases (from previous commit):

```
@observer
export class CommandContainer extends React.Component<CommandContainerProps> {
  // without some defined initial value "commandComponent" is non-observable for some reasons
  // when tsconfig.ts has {useDefineForClassFields:false}
  @observable.ref commandComponent: React.ReactNode = null;

  constructor(props: CommandContainerProps) {
    super(props);
    makeObservable(this);
  }
```

Signed-off-by: Roman <ixrock@gmail.com>

* update KubeObject class type definition

Signed-off-by: Roman <ixrock@gmail.com>

* clean up / responding to comments

Signed-off-by: Roman <ixrock@gmail.com>

* fix: app-crash when navigating to catalog from active cluster-view, refactoring `catalog-entity-store`

Signed-off-by: Roman <ixrock@gmail.com>

* catalog-pusher clean up, replaced .observe_() to external observe() helper from "mobx"

Signed-off-by: Roman <ixrock@gmail.com>

* fix: catalog's items stale/non-observable (after connection to the cluster status still "disconnected"), lint-fixes

Signed-off-by: Roman <ixrock@gmail.com>

* fix: Catalog is empty after closing main-window and re-opening app from Tray

Signed-off-by: Roman <ixrock@gmail.com>

* fix: HotBar's icon context menu items non-observable (no "disconnect cluster", etc.)

Signed-off-by: Roman <ixrock@gmail.com>

* lint-fix/license check

Signed-off-by: Roman <ixrock@gmail.com>

* fix: redirect to catalog when disconnecting active cluster

Signed-off-by: Roman <ixrock@gmail.com>

* fix: refresh visibility of active cluster-view on switching from hotbar/catalog

Signed-off-by: Roman <ixrock@gmail.com>

* updated package.json for built-in extensions to use "*" version for packages served from main app

Signed-off-by: Roman <ixrock@gmail.com>

* - added missing makeObservable(this) to metrics-settings.tsx
- updated package-lock.json for built-in extensions
- lint fixes

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge clean up fix, updated package-lock.json for built-in extensions after `make clean-extensions && make build-extensions`

Signed-off-by: Roman <ixrock@gmail.com>

* fix unit-tests

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge fixes

Signed-off-by: Roman <ixrock@gmail.com>

* make lint happy

Signed-off-by: Roman <ixrock@gmail.com>

* reverted some changes, removed auto-opening devtools in dev-mode

Signed-off-by: Roman <ixrock@gmail.com>

* merge fixes

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge conflict fixes:
- proper handling and navigating into catalog's active category via URL-builder

Signed-off-by: Roman <ixrock@gmail.com>

* reverting splitted params for catalog's page route to "/catalog/:group?/:kind?"

Signed-off-by: Roman <ixrock@gmail.com>

* clean-up: remove app's injecting dependencies from `extensions/kube-object-event-status/package.json`

Signed-off-by: Roman <ixrock@gmail.com>

* master-merge fix: added missing makeObservable(this) for extensions.tsx

Signed-off-by: Roman <ixrock@gmail.com>

* fix: catalog entity context menu stale/unobservable

Signed-off-by: Roman <ixrock@gmail.com>

Co-authored-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Roman 2021-05-25 10:24:31 +03:00 committed by GitHub
parent 0c45dd807e
commit 2c3b510997
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
256 changed files with 2359 additions and 1473 deletions

View File

@ -25,7 +25,7 @@ The following example code creates a store for the `appPreferences` guide exampl
``` typescript
import { Store } from "@k8slens/extensions";
import { observable, toJS } from "mobx";
import { observable, makeObservable } from "mobx";
export type ExamplePreferencesModel = {
enabled: boolean;
@ -42,6 +42,7 @@ export class ExamplePreferencesStore extends Store.ExtensionStore<ExamplePrefere
enabled: false
}
});
makeObservable(this);
}
protected fromStore({ enabled }: ExamplePreferencesModel): void {
@ -49,11 +50,9 @@ export class ExamplePreferencesStore extends Store.ExtensionStore<ExamplePrefere
}
toJSON(): ExamplePreferencesModel {
return toJS({
return {
enabled: this.enabled
}, {
recurseEverything: true
});
};
}
}
```
@ -73,7 +72,6 @@ The `enabled` field of the `ExamplePreferencesStore` is set to the value from th
The `toJSON()` method is complementary to `fromStore()`.
It is called when the store is being saved.
`toJSON()` must provide a JSON serializable object, facilitating its storage in JSON format.
The `toJS()` function from [`mobx`](https://mobx.js.org/README.html) is convenient for this purpose, and is used here.
Finally, `ExamplePreferencesStore` is created by calling `ExamplePreferencesStore.getInstanceOrCreate()`, and exported for use by other parts of the extension.
Note that `ExamplePreferencesStore` is a singleton.

View File

@ -1661,6 +1661,12 @@
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@ -1715,6 +1721,15 @@
"path-exists": "^3.0.0"
}
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -1881,6 +1896,27 @@
"minimist": "^1.2.5"
}
},
"mobx": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/mobx/-/mobx-6.3.1.tgz",
"integrity": "sha512-by0VQNBltxLKcQ0uIz2h2mnVv1ZV0ief8mRRw+0k7mL2vSS3806rZg42bt8Vy78szaODtij/5iWIGvc46ZC3Cw==",
"dev": true
},
"mobx-react": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-7.2.0.tgz",
"integrity": "sha512-KHUjZ3HBmZlNnPd1M82jcdVsQRDlfym38zJhZEs33VxyVQTvL77hODCArq6+C1P1k/6erEeo2R7rpE7ZeOL7dg==",
"dev": true,
"requires": {
"mobx-react-lite": "^3.2.0"
}
},
"mobx-react-lite": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.2.0.tgz",
"integrity": "sha512-q5+UHIqYCOpBoFm/PElDuOhbcatvTllgRp3M1s+Hp5j0Z6XNgDbgqxawJ0ZAUEyKM8X1zs70PCuhAIzX1f4Q/g==",
"dev": true
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -2290,6 +2326,16 @@
"safe-buffer": "^5.1.0"
}
},
"react": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",

View File

@ -13,6 +13,7 @@
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"useDefineForClassFields": true,
"jsx": "react"
},
"include": [

View File

@ -23,6 +23,5 @@
"ts-loader": "^8.0.4",
"typescript": "^4.0.3",
"webpack": "^4.44.2"
},
"dependencies": {}
}
}

View File

@ -21,7 +21,7 @@
import React from "react";
import { Component, Catalog, K8sApi } from "@k8slens/extensions";
import { observer } from "mobx-react";
import { computed, observable } from "mobx";
import { computed, observable, makeObservable } from "mobx";
import { MetricsFeature, MetricsConfiguration } from "./metrics-feature";
interface Props {
@ -30,6 +30,11 @@ interface Props {
@observer
export class MetricsSettings extends React.Component<Props> {
constructor(props: Props) {
super(props);
makeObservable(this);
}
@observable featureStates = {
prometheus: false,
kubeStateMetrics: false,

View File

@ -13,6 +13,7 @@
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"useDefineForClassFields": true,
"jsx": "react"
},
"include": [

View File

@ -13,6 +13,7 @@
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"useDefineForClassFields": true,
"jsx": "react"
},
"include": [

View File

@ -13,6 +13,7 @@
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"useDefineForClassFields": true,
"jsx": "react"
},
"include": [

View File

@ -183,6 +183,8 @@
"@kubernetes/client-node": "^0.12.0",
"abort-controller": "^3.0.0",
"array-move": "^3.0.0",
"auto-bind": "^4.0.0",
"autobind-decorator": "^2.4.0",
"await-lock": "^2.1.0",
"byline": "^5.0.0",
"chalk": "^4.1.0",
@ -207,9 +209,9 @@
"mac-ca": "^1.0.4",
"marked": "^2.0.3",
"md5-file": "^5.0.0",
"mobx": "^5.15.7",
"mobx-observable-history": "^1.0.3",
"mobx-react": "^6.2.2",
"mobx": "^6.3.0",
"mobx-observable-history": "^2.0.1",
"mobx-react": "^7.1.0",
"mock-fs": "^4.12.0",
"moment": "^2.26.0",
"moment-timezone": "^0.5.33",

View File

@ -23,9 +23,8 @@ import path from "path";
import Config from "conf";
import type { Options as ConfOptions } from "conf/dist/source/types";
import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron";
import { IReactionOptions, observable, reaction, runInAction, when } from "mobx";
import Singleton from "./utils/singleton";
import { getAppVersion } from "./utils/app-version";
import { IReactionOptions, makeObservable, observable, reaction, runInAction, when } from "mobx";
import { getAppVersion, Singleton, toJS, Disposer } from "./utils";
import logger from "../main/logger";
import { broadcastMessage, subscribeToBroadcast, unsubscribeFromBroadcast } from "./ipc";
import isEqual from "lodash/isEqual";
@ -41,13 +40,18 @@ export interface BaseStoreParams<T = any> extends ConfOptions<T> {
*/
export abstract class BaseStore<T = any> extends Singleton {
protected storeConfig?: Config<T>;
protected syncDisposers: Function[] = [];
protected syncDisposers: Disposer[] = [];
whenLoaded = when(() => this.isLoaded);
@observable isLoaded = false;
get whenLoaded() {
return when(() => this.isLoaded);
}
protected constructor(protected params: BaseStoreParams) {
super();
makeObservable(this);
this.params = {
autoLoad: false,
syncEnabled: true,
@ -114,7 +118,11 @@ export abstract class BaseStore<T = any> extends Singleton {
enableSync() {
this.syncDisposers.push(
reaction(() => this.toJSON(), model => this.onModelChange(model), this.params.syncOptions),
reaction(
() => toJS(this.toJSON()), // unwrap possible observables and react to everything
model => this.onModelChange(model),
this.params.syncOptions,
),
);
if (ipcMain) {

View File

@ -19,7 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { action, computed, observable } from "mobx";
import { action, computed, observable, makeObservable } from "mobx";
import { Disposer, ExtendedMap } from "../utils";
import { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
@ -27,6 +27,10 @@ export class CatalogCategoryRegistry {
protected categories = observable.set<CatalogCategory>();
protected groupKinds = new ExtendedMap<string, ExtendedMap<string, CatalogCategory>>();
constructor() {
makeObservable(this);
}
@action add(category: CatalogCategory): Disposer {
this.categories.add(category);
this.updateGroupKinds(category);

View File

@ -20,7 +20,7 @@
*/
import { EventEmitter } from "events";
import { observable } from "mobx";
import { observable, makeObservable } from "mobx";
type ExtractEntityMetadataType<Entity> = Entity extends CatalogEntity<infer Metadata> ? Metadata : never;
type ExtractEntityStatusType<Entity> = Entity extends CatalogEntity<any, infer Status> ? Status : never;
@ -56,6 +56,12 @@ export abstract class CatalogCategory extends EventEmitter {
};
abstract spec: CatalogCategorySpec;
static parseId(id = ""): { group?: string, kind?: string } {
const [group, kind] = id.split("/") ?? [];
return { group, kind };
}
public getId(): string {
return `${this.spec.group}/${this.spec.names.kind}`;
}
@ -147,6 +153,7 @@ export abstract class CatalogEntity<
@observable spec: Spec;
constructor(data: CatalogEntityData<Metadata, Status, Spec>) {
makeObservable(this);
this.metadata = data.metadata;
this.status = data.status;
this.spec = data.spec;

View File

@ -22,7 +22,7 @@
import path from "path";
import { app, ipcMain, ipcRenderer, remote, webFrame } from "electron";
import { unlink } from "fs-extra";
import { action, comparer, computed, observable, reaction, toJS } from "mobx";
import { action, comparer, computed, observable, reaction, makeObservable } from "mobx";
import { BaseStore } from "./base-store";
import { Cluster, ClusterState } from "../main/cluster";
import migrations from "../migrations/cluster-store";
@ -33,7 +33,7 @@ import { saveToAppFiles } from "./utils/saveToAppFiles";
import type { KubeConfig } from "@kubernetes/client-node";
import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
import type { ResourceType } from "../renderer/components/cluster-settings/components/cluster-metrics-setting";
import { disposer, noop } from "./utils";
import { disposer, noop, toJS } from "./utils";
export interface ClusterIconUpload {
clusterId: string;
@ -148,6 +148,8 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
migrations,
});
makeObservable(this);
this.pushStateToViewsAutomatically();
}
@ -171,16 +173,16 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
});
} else if (ipcMain) {
handleRequest(ClusterStore.stateRequestChannel, (): clusterStateSync[] => {
const states: clusterStateSync[] = [];
const clusterStates: clusterStateSync[] = [];
this.clustersList.forEach((cluster) => {
states.push({
clusterStates.push({
state: cluster.getState(),
id: cluster.id
});
});
return states;
return clusterStates;
});
}
}
@ -309,7 +311,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
@action
protected fromStore({ activeCluster, clusters = [] }: ClusterStoreModel = {}) {
const currentClusters = this.clusters.toJS();
const currentClusters = new Map(this.clusters);
const newClusters = new Map<ClusterId, Cluster>();
const removedClusters = new Map<ClusterId, Cluster>();
@ -345,8 +347,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
return toJS({
activeCluster: this.activeCluster,
clusters: this.clustersList.map(cluster => cluster.toJSON()),
}, {
recurseEverything: true
});
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import * as Mobx from "mobx";
import * as Immer from "immer";
/**
* Setup default configuration for external npm-packages
*/
export default function configurePackages() {
// Docs: https://mobx.js.org/configuration.html
Mobx.configure({
enforceActions: "never",
isolateGlobalState: true,
// TODO: enable later (read more: https://mobx.js.org/migrating-from-4-or-5.html)
// computedRequiresReaction: true,
// reactionRequiresObservable: true,
// observableRequiresReaction: true,
});
// Docs: https://immerjs.github.io/immer/
// Required in `utils/storage-helper.ts`
Immer.setAutoFreeze(false); // allow to merge mobx observables
Immer.enableMapSet(); // allow to merge maps and sets
}

View File

@ -19,11 +19,12 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { action, comparer, observable, toJS } from "mobx";
import { action, comparer, observable, makeObservable } from "mobx";
import { BaseStore } from "./base-store";
import migrations from "../migrations/hotbar-store";
import * as uuid from "uuid";
import isNull from "lodash/isNull";
import { toJS } from "./utils";
import { CatalogEntity } from "./catalog";
export interface HotbarItem {
@ -69,6 +70,7 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
},
migrations,
});
makeObservable(this);
}
get activeHotbarId() {
@ -252,8 +254,6 @@ export class HotbarStore extends BaseStore<HotbarStoreModel> {
activeHotbarId: this.activeHotbarId
};
return toJS(model, {
recurseEverything: true,
});
return toJS(model);
}
}

View File

@ -23,29 +23,34 @@
// https://www.electronjs.org/docs/api/ipc-main
// https://www.electronjs.org/docs/api/ipc-renderer
import { ipcMain, ipcRenderer, webContents, remote } from "electron";
import { toJS } from "mobx";
import { ipcMain, ipcRenderer, remote, webContents } from "electron";
import { toJS } from "../utils/toJS";
import logger from "../../main/logger";
import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames";
import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames";
const subFramesChannel = "ipc:get-sub-frames";
export function handleRequest(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) {
ipcMain.handle(channel, listener);
ipcMain.handle(channel, async (event, ...args) => {
const payload = await listener(event, ...args);
return sanitizePayload(payload);
});
}
export async function requestMain(channel: string, ...args: any[]) {
return ipcRenderer.invoke(channel, ...args);
return ipcRenderer.invoke(channel, ...args.map(sanitizePayload));
}
function getSubFrames(): ClusterFrameInfo[] {
return toJS(Array.from(clusterFrameMap.values()), { recurseEverything: true });
return Array.from(clusterFrameMap.values());
}
export function broadcastMessage(channel: string, ...args: any[]) {
const views = (webContents || remote?.webContents)?.getAllWebContents();
if (!views) return;
args = args.map(sanitizePayload);
ipcRenderer?.send(channel, ...args);
ipcMain?.emit(channel, ...args);
@ -98,7 +103,13 @@ export function unsubscribeAllFromBroadcast(channel: string) {
}
export function bindBroadcastHandlers() {
handleRequest(subFramesChannel, () => {
return getSubFrames();
});
handleRequest(subFramesChannel, () => getSubFrames());
}
/**
* Sanitizing data for IPC-messaging before send.
* Removes possible observable values to avoid exceptions like "can't clone object".
*/
function sanitizePayload<T>(data: any): T {
return toJS(data);
}

View File

@ -19,9 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { action, computed, observable,reaction } from "mobx";
import { action, computed, observable, reaction, makeObservable } from "mobx";
import { dockStore } from "../renderer/components/dock/dock.store";
import { autobind } from "../renderer/utils";
import { boundMethod } from "../renderer/utils";
export class SearchStore {
/**
@ -54,6 +54,7 @@ export class SearchStore {
@observable activeOverlayIndex = -1;
constructor() {
makeObservable(this);
reaction(() => dockStore.selectedTabId, () => {
searchStore.reset();
});
@ -128,12 +129,12 @@ export class SearchStore {
return prev;
}
@autobind()
@boundMethod
public setNextOverlayActive(): void {
this.activeOverlayIndex = this.getNextOverlay(true);
}
@autobind()
@boundMethod
public setPrevOverlayActive(): void {
this.activeOverlayIndex = this.getPrevOverlay(true);
}
@ -159,7 +160,7 @@ export class SearchStore {
* @param line Index of the line where overlay is located
* @param occurrence Number of the overlay within one line
*/
@autobind()
@boundMethod
public isActiveOverlay(line: number, occurrence: number): boolean {
const firstLineIndex = this.occurrences.findIndex(item => item === line);

View File

@ -23,7 +23,7 @@ import type { ThemeId } from "../renderer/theme.store";
import { app, remote } from "electron";
import semver from "semver";
import { readFile } from "fs-extra";
import { action, computed, observable, reaction, toJS } from "mobx";
import { action, computed, observable, reaction, makeObservable } from "mobx";
import moment from "moment-timezone";
import { BaseStore } from "./base-store";
import migrations from "../migrations/user-store";
@ -34,7 +34,7 @@ import logger from "../main/logger";
import path from "path";
import os from "os";
import { fileNameMigration } from "../migrations/user-store";
import { ObservableToggleSet } from "../renderer/utils";
import { ObservableToggleSet, toJS } from "../renderer/utils";
export interface UserStoreModel {
kubeConfigPath: string;
@ -73,6 +73,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
configName: "lens-user-store",
migrations,
});
makeObservable(this);
}
@observable lastSeenAppVersion = "0.0.0";
@ -306,9 +307,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
},
};
return toJS(model, {
recurseEverything: true,
});
return toJS(model);
}
}

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { isObservable, observable } from "mobx";
import { toJS } from "../toJS";
describe("utils/toJS(data: any)", () => {
const y = { y: 2 };
const data = observable({ x: 1, y }, {}, {
deep: false, // this will keep ref to "y"
});
const data2 = {
x: 1, // partially observable
y: observable(y),
};
test("converts mobx-observable to corresponding js struct with links preserving", () => {
expect(toJS(data).y).toBe(y);
expect(isObservable(toJS(data).y)).toBeFalsy();
});
test("converts partially observable js struct", () => {
expect(toJS(data2).y).not.toBe(y);
expect(toJS(data2).y).toEqual(y);
expect(isObservable(toJS(data2).y)).toBeFalsy();
});
});

View File

@ -19,48 +19,22 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Decorator for binding class methods
// Can be applied to class or single method as @autobind()
type Constructor<T = {}> = new (...args: any[]) => T;
import {boundMethod, boundClass} from "autobind-decorator";
import autoBindClass, { Options } from "auto-bind";
import autoBindReactClass from "auto-bind/react";
export function autobind() {
return function (target: Constructor | object, prop?: string, descriptor?: PropertyDescriptor) {
if (target instanceof Function) return bindClass(target);
else return bindMethod(target, prop, descriptor);
// Automatically bind methods to their class instance
export function autoBind<T extends object>(obj: T, opts?: Options): T {
if ("componentWillUnmount" in obj) {
return autoBindReactClass(obj as any, opts);
}
return autoBindClass(obj, opts);
}
// Class/method decorators
// Note: @boundClass doesn't work with mobx-6.x/@action decorator
export {
boundClass,
boundMethod,
};
}
function bindClass<T extends Constructor>(constructor: T) {
const proto = constructor.prototype;
const descriptors = Object.getOwnPropertyDescriptors(proto);
const skipMethod = (methodName: string) => {
return methodName === "constructor"
|| typeof descriptors[methodName].value !== "function";
};
Object.keys(descriptors).forEach(prop => {
if (skipMethod(prop)) return;
const boundDescriptor = bindMethod(proto, prop, descriptors[prop]);
Object.defineProperty(proto, prop, boundDescriptor);
});
}
function bindMethod(target: object, prop?: string, descriptor?: PropertyDescriptor) {
if (!descriptor || typeof descriptor.value !== "function") {
throw new Error(`@autobind() must be used on class or method only`);
}
const { value: func, enumerable, configurable } = descriptor;
const boundFunc = new WeakMap<object, Function>();
return Object.defineProperty(target, prop, {
enumerable,
configurable,
get() {
if (this === target) return func; // direct access from prototype
if (!boundFunc.has(this)) boundFunc.set(this, func.bind(this));
return boundFunc.get(this);
}
});
}

View File

@ -27,6 +27,7 @@ export * from "./app-version";
export * from "./autobind";
export * from "./base64";
export * from "./camelCase";
export * from "./toJS";
export * from "./cloneJson";
export * from "./debouncePromise";
export * from "./defineGlobal";

39
src/common/utils/toJS.ts Normal file
View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Wrapper for mobx.toJS() to support partially observable objects as data-input (>= mobx6).
* Otherwise, output result won't be recursively converted to corresponding plain JS-structure.
*
* @example
* mobx.toJS({one: 1, two: observable.array([2])}); // "data.two" == ObservableArray<number>
*/
import * as mobx from "mobx";
import { isObservable, observable } from "mobx";
export function toJS<T>(data: T): T {
// make data observable for recursive toJS()-output
if (typeof data === "object" && !isObservable(data)) {
return mobx.toJS(observable.box(data).get());
}
return mobx.toJS(data);
}

View File

@ -19,7 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { action, ObservableSet } from "mobx";
import { ObservableSet } from "mobx";
export class ToggleSet<T> extends Set<T> {
public toggle(value: T): void {
@ -31,7 +31,6 @@ export class ToggleSet<T> extends Set<T> {
}
export class ObservableToggleSet<T> extends ObservableSet<T> {
@action
public toggle(value: T): void {
if (!this.delete(value)) {
// Set.prototype.delete returns false if `value` was not in the set

View File

@ -19,7 +19,8 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Extension-api types generation bundle
// Extensions-api types bundle (main + renderer)
// Available for lens-extensions via NPM-package "@k8slens/extensions"
export * from "./core-api";
export * from "./renderer-api";

View File

@ -23,11 +23,11 @@ import { watch } from "chokidar";
import { ipcRenderer } from "electron";
import { EventEmitter } from "events";
import fse from "fs-extra";
import { observable, reaction, toJS, when } from "mobx";
import { observable, reaction, when, makeObservable } from "mobx";
import os from "os";
import path from "path";
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
import { Singleton } from "../common/utils";
import { Singleton, toJS } from "../common/utils";
import logger from "../main/logger";
import { ExtensionInstallationStateStore } from "../renderer/components/+extensions/extension-install.store";
import { extensionInstaller, PackageJson } from "./extension-installer";
@ -86,13 +86,22 @@ export class ExtensionDiscovery extends Singleton {
// True if extensions have been loaded from the disk after app startup
@observable isLoaded = false;
whenLoaded = when(() => this.isLoaded);
get whenLoaded() {
return when(() => this.isLoaded);
}
// IPC channel to broadcast changes to extension-discovery from main
protected static readonly extensionDiscoveryChannel = "extension-discovery:main";
public events = new EventEmitter();
constructor() {
super();
makeObservable(this);
}
get localFolderPath(): string {
return path.join(os.homedir(), ".k8slens", "extensions");
}
@ -374,7 +383,7 @@ export class ExtensionDiscovery extends Singleton {
const userExtensions = await this.loadFromFolder(this.localFolderPath, bundledExtensions.map((extension) => extension.manifest.name));
for (const extension of userExtensions) {
if (await fse.pathExists(extension.manifestPath) === false) {
if ((await fse.pathExists(extension.manifestPath)) === false) {
await this.installPackage(extension.absolutePath);
}
}
@ -468,8 +477,6 @@ export class ExtensionDiscovery extends Singleton {
toJSON(): ExtensionDiscoveryChannelMessage {
return toJS({
isLoaded: this.isLoaded
}, {
recurseEverything: true
});
}

View File

@ -22,11 +22,11 @@
import { app, ipcRenderer, remote } from "electron";
import { EventEmitter } from "events";
import { isEqual } from "lodash";
import { action, computed, observable, reaction, toJS, when } from "mobx";
import { action, computed, makeObservable, observable, reaction, when } from "mobx";
import path from "path";
import { getHostedCluster } from "../common/cluster-store";
import { broadcastMessage, handleRequest, requestMain, subscribeToBroadcast } from "../common/ipc";
import { Singleton } from "../common/utils";
import { Singleton, toJS } from "../common/utils";
import logger from "../main/logger";
import type { InstalledExtension } from "./extension-discovery";
import { ExtensionsStore } from "./extensions-store";
@ -58,7 +58,16 @@ export class ExtensionLoader extends Singleton {
private events = new EventEmitter();
@observable isLoaded = false;
whenLoaded = when(() => this.isLoaded);
get whenLoaded() {
return when(() => this.isLoaded);
}
constructor() {
super();
makeObservable(this);
}
@computed get userExtensions(): Map<LensExtensionId, InstalledExtension> {
const extensions = this.toJSON();
@ -75,7 +84,7 @@ export class ExtensionLoader extends Singleton {
@computed get userExtensionsByName(): Map<string, LensExtension> {
const extensions = new Map();
for (const [, val] of this.instances.toJS()) {
for (const [, val] of this.instances.toJSON()) {
if (val.isBundled) {
continue;
}
@ -117,6 +126,11 @@ export class ExtensionLoader extends Singleton {
await Promise.all([this.whenLoaded, ExtensionsStore.getInstance().whenLoaded]);
// broadcasting extensions between main/renderer processes
reaction(() => this.toJSON(), () => this.broadcastExtensions(), {
fireImmediately: true,
});
// save state on change `extension.isEnabled`
reaction(() => this.storeState, extensionsState => {
ExtensionsStore.getInstance().mergeState(extensionsState);
@ -156,14 +170,10 @@ export class ExtensionLoader extends Singleton {
}
}
protected async initMain() {
protected async initMain() {
this.isLoaded = true;
this.loadOnMain();
reaction(() => this.toJSON(), () => {
this.broadcastExtensions();
});
handleRequest(ExtensionLoader.extensionsMainChannel, () => {
return Array.from(this.toJSON());
});
@ -173,7 +183,7 @@ export class ExtensionLoader extends Singleton {
});
}
protected async initRenderer() {
protected async initRenderer() {
const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => {
this.isLoaded = true;
this.syncExtensions(extensions);
@ -188,16 +198,20 @@ export class ExtensionLoader extends Singleton {
});
};
reaction(() => this.toJSON(), () => {
this.broadcastExtensions(false);
});
requestMain(ExtensionLoader.extensionsMainChannel).then(extensionListHandler);
subscribeToBroadcast(ExtensionLoader.extensionsMainChannel, (_event, extensions: [LensExtensionId, InstalledExtension][]) => {
extensionListHandler(extensions);
});
}
broadcastExtensions() {
const channel = ipcRenderer
? ExtensionLoader.extensionsRendererChannel
: ExtensionLoader.extensionsMainChannel;
broadcastMessage(channel, Array.from(this.extensions));
}
syncExtensions(extensions: [LensExtensionId, InstalledExtension][]) {
extensions.forEach(([lensExtensionId, extension]) => {
if (!isEqual(this.extensions.get(lensExtensionId), extension)) {
@ -255,7 +269,7 @@ export class ExtensionLoader extends Singleton {
const cluster = getHostedCluster();
this.autoInitExtensions(async (extension: LensRendererExtension) => {
if (await extension.isEnabledForCluster(cluster) === false) {
if ((await extension.isEnabledForCluster(cluster)) === false) {
return [];
}
@ -334,13 +348,6 @@ export class ExtensionLoader extends Singleton {
}
toJSON(): Map<LensExtensionId, InstalledExtension> {
return toJS(this.extensions, {
exportMapsAsObjects: false,
recurseEverything: true,
});
}
broadcastExtensions(main = true) {
broadcastMessage(main ? ExtensionLoader.extensionsMainChannel : ExtensionLoader.extensionsRendererChannel, Array.from(this.toJSON()));
return toJS(this.extensions);
}
}

View File

@ -21,7 +21,8 @@
import type { LensExtensionId } from "./lens-extension";
import { BaseStore } from "../common/base-store";
import { action, computed, observable, toJS } from "mobx";
import { action, computed, observable, makeObservable } from "mobx";
import { toJS } from "../common/utils";
export interface LensExtensionsStoreModel {
extensions: Record<LensExtensionId, LensExtensionState>;
@ -37,6 +38,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
super({
configName: "lens-extensions",
});
makeObservable(this);
}
@computed
@ -68,9 +70,7 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
toJSON(): LensExtensionsStoreModel {
return toJS({
extensions: this.state.toJSON(),
}, {
recurseEverything: true
extensions: Object.fromEntries(this.state),
});
}
}

View File

@ -20,7 +20,7 @@
*/
import type { InstalledExtension } from "./extension-discovery";
import { action, observable, reaction } from "mobx";
import { action, observable, reaction, makeObservable } from "mobx";
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
import logger from "../main/logger";
import type { ProtocolHandlerRegistration } from "./registries";
@ -52,6 +52,7 @@ export class LensExtension {
[Disposers] = disposer();
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
makeObservable(this);
this.id = id;
this.manifest = manifest;
this.manifestPath = manifestPath;

View File

@ -75,15 +75,22 @@ describe("getPageUrl", () => {
});
it("gets page url with custom params", () => {
const params: PageParams<string> = { test1: "one", test2: "2" };
const params: PageParams = { test1: "one", test2: "2" };
const searchParams = new URLSearchParams(params);
const pageUrl = getExtensionPageUrl({ extensionId: ext.name, pageId: "page-with-params", params });
const pageUrl = getExtensionPageUrl({
extensionId: ext.name,
pageId: "page-with-params",
params,
});
expect(pageUrl).toBe(`/extension/foo-bar/page-with-params?${searchParams}`);
});
it("gets page url with default custom params", () => {
const defaultPageUrl = getExtensionPageUrl({ extensionId: ext.name, pageId: "page-with-params", });
const defaultPageUrl = getExtensionPageUrl({
extensionId: ext.name,
pageId: "page-with-params",
});
expect(defaultPageUrl).toBe(`/extension/foo-bar/page-with-params?test1=test1-default`);
});

View File

@ -20,12 +20,16 @@
*/
// Base class for extensions-api registries
import { action, observable } from "mobx";
import { action, observable, makeObservable } from "mobx";
import { LensExtension } from "../lens-extension";
export class BaseRegistry<T, I = T> {
private items = observable.map<T, I>();
constructor() {
makeObservable(this);
}
getItems(): I[] {
return Array.from(this.items.values());
}

View File

@ -22,9 +22,9 @@
// Extensions API -> Commands
import { BaseRegistry } from "./base-registry";
import { action, observable } from "mobx";
import { LensExtension } from "../lens-extension";
import { CatalogEntity } from "../../common/catalog";
import { makeObservable, observable } from "mobx";
import type { LensExtension } from "../lens-extension";
import type { CatalogEntity } from "../../common/catalog";
export type CommandContext = {
entity?: CatalogEntity;
@ -39,9 +39,14 @@ export interface CommandRegistration {
}
export class CommandRegistry extends BaseRegistry<CommandRegistration> {
@observable activeEntity: CatalogEntity;
@observable.ref activeEntity: CatalogEntity;
constructor() {
super();
makeObservable(this);
}
@action
add(items: CommandRegistration | CommandRegistration[], extension?: LensExtension) {
const itemArray = [items].flat();

View File

@ -23,9 +23,8 @@
import type { IconProps } from "../../renderer/components/icon";
import type React from "react";
import type { PageTarget, RegisteredPage } from "./page-registry";
import { action } from "mobx";
import type { LensExtension } from "../lens-extension";
import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension";
export interface PageMenuRegistration {
target?: PageTarget;
@ -43,7 +42,6 @@ export interface PageMenuComponents {
}
export class PageMenuRegistry<T extends PageMenuRegistration> extends BaseRegistry<T> {
@action
add(items: T[], ext: LensExtension) {
const normalizedItems = items.map(menuItem => {
menuItem.target = {

View File

@ -24,9 +24,8 @@
import React from "react";
import { observer } from "mobx-react";
import { BaseRegistry } from "./base-registry";
import { LensExtension, sanitizeExtensionName } from "../lens-extension";
import type { PageParam, PageParamInit } from "../../renderer/navigation/page-param";
import { createPageParam } from "../../renderer/navigation/helpers";
import { LensExtension, LensExtensionId, sanitizeExtensionName } from "../lens-extension";
import { createPageParam, PageParam, PageParamInit, searchParamsOptions } from "../../renderer/navigation";
export interface PageRegistration {
/**
@ -34,21 +33,18 @@ export interface PageRegistration {
* When not provided, first registered page without "id" would be used for page-menus without target.pageId for same extension
*/
id?: string;
params?: PageParams<string | ExtensionPageParamInit>;
params?: PageParams<string | Omit<PageParamInit<any>, "name" | "prefix">>;
components: PageComponents;
}
// exclude "name" field since provided as key in page.params
export type ExtensionPageParamInit = Omit<PageParamInit, "name" | "isSystem">;
export interface PageComponents {
Page: React.ComponentType<any>;
}
export interface PageTarget<P = PageParams> {
export interface PageTarget {
extensionId?: string;
pageId?: string;
params?: P;
params?: PageParams;
}
export interface PageParams<V = any> {
@ -83,13 +79,12 @@ export function getExtensionPageUrl(target: PageTarget): string {
if (registeredPage?.params) {
Object.entries(registeredPage.params).forEach(([name, param]) => {
const paramValue = param.stringify(targetParams[name]);
pageUrl.searchParams.delete(name); // first off, clear existing value(s)
if (param.init.skipEmpty && param.isEmpty(paramValue)) {
pageUrl.searchParams.delete(name);
} else {
pageUrl.searchParams.set(name, paramValue);
}
param.stringify(targetParams[name]).forEach(value => {
if (searchParamsOptions.skipEmpty && !value) return;
pageUrl.searchParams.append(name, value);
});
});
}
@ -100,7 +95,7 @@ export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage>
protected getRegisteredItem(page: PageRegistration, ext: LensExtension): RegisteredPage {
const { id: pageId } = page;
const extensionId = ext.name;
const params = this.normalizeParams(page.params);
const params = this.normalizeParams(extensionId, page.params);
const components = this.normalizeComponents(page.components, params);
const url = getExtensionPageUrl({ extensionId, pageId });
@ -113,25 +108,48 @@ export class PageRegistry extends BaseRegistry<PageRegistration, RegisteredPage>
if (params) {
const { Page } = components;
// inject extension's page component props.params
components.Page = observer((props: object) => React.createElement(Page, { params, ...props }));
}
return components;
}
protected normalizeParams(params?: PageParams<string | ExtensionPageParamInit>): PageParams<PageParam> | undefined {
if (!params) {
return undefined;
}
Object.entries(params).forEach(([name, value]) => {
const paramInit: PageParamInit = typeof value === "object"
? { name, ...value }
: { name, defaultValue: value };
protected normalizeParams(extensionId: LensExtensionId, params?: PageParams<string | Partial<PageParamInit>>): PageParams<PageParam> {
if (!params) return undefined;
const normalizedParams: PageParams<PageParam> = {};
params[paramInit.name] = createPageParam(paramInit);
Object.entries(params).forEach(([paramName, paramValue]) => {
const paramInit: PageParamInit = {
name: paramName,
prefix: `${extensionId}:`,
defaultValue: paramValue,
};
// handle non-string params
if (typeof paramValue !== "string") {
const { defaultValue: value, parse, stringify } = paramValue;
const notAStringValue = typeof value !== "string" || (
Array.isArray(value) && !value.every(value => typeof value === "string")
);
if (notAStringValue && !(parse || stringify)) {
throw new Error(
`PageRegistry: param's "${paramName}" initialization has failed:
paramInit.parse() and paramInit.stringify() are required for non string | string[] "defaultValue"`
);
}
paramInit.defaultValue = value;
paramInit.parse = parse;
paramInit.stringify = stringify;
}
normalizedParams[paramName] = createPageParam(paramInit);
});
return params as PageParams<PageParam>;
return normalizedParams;
}
getByPageTarget(target: PageTarget): RegisteredPage | null {

View File

@ -19,15 +19,13 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { PageParam, PageParamInit } from "../../renderer/navigation/page-param";
import { navigation } from "../../renderer/navigation";
import { navigation, PageParam, PageParamInit } from "../../renderer/navigation";
export type { PageParamInit, PageParam } from "../../renderer/navigation/page-param";
export { navigate, isActiveRoute } from "../../renderer/navigation/helpers";
export { hideDetails, showDetails, getDetailsUrl } from "../../renderer/components/kube-object/kube-object-details";
export type { IURLParams } from "../../common/utils/buildUrl";
// exporting to extensions-api version of helper without `isSystem` flag
export function createPageParam<V = string>(init: PageParamInit<V>) {
export function createPageParam<V>(init: PageParamInit<V>) {
return new PageParam<V>(init, navigation);
}

View File

@ -20,6 +20,11 @@
*/
import fetchMock from "jest-fetch-mock";
import configurePackages from "./common/configure-packages";
// setup default configuration for external npm-packages
configurePackages();
// rewire global.fetch to call 'fetchMock'
fetchMock.enableMocks();

View File

@ -19,13 +19,14 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { reaction, toJS } from "mobx";
import { reaction } from "mobx";
import { broadcastMessage } from "../common/ipc";
import type { CatalogEntityRegistry } from "./catalog";
import "../common/catalog-entities/kubernetes-cluster";
import { toJS } from "../common/utils";
export function pushCatalogToRenderer(catalog: CatalogEntityRegistry) {
return reaction(() => toJS(catalog.items, { recurseEverything: true }), (items) => {
return reaction(() => toJS(catalog.items), (items) => {
broadcastMessage("catalog:items", items);
}, {
fireImmediately: true,

View File

@ -19,7 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { action, observable, IComputedValue, computed, ObservableMap, runInAction } from "mobx";
import { action, observable, IComputedValue, computed, ObservableMap, runInAction, makeObservable, observe } from "mobx";
import type { CatalogEntity } from "../../common/catalog";
import { catalogEntityRegistry } from "../../main/catalog";
import { watch } from "chokidar";
@ -45,6 +45,12 @@ export class KubeconfigSyncManager extends Singleton {
protected static readonly syncName = "lens:kube-sync";
constructor() {
super();
makeObservable(this);
}
@action
startSync(): void {
if (this.syncing) {
@ -69,7 +75,7 @@ export class KubeconfigSyncManager extends Singleton {
this.startNewSync(filePath);
}
this.syncListDisposer = UserStore.getInstance().syncKubeconfigEntries.observe(change => {
this.syncListDisposer = observe(UserStore.getInstance().syncKubeconfigEntries, change => {
switch (change.type) {
case "add":
this.startNewSync(change.name);

View File

@ -19,14 +19,16 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { action, computed, observable, IComputedValue, IObservableArray } from "mobx";
import { action, computed, IComputedValue, IObservableArray, makeObservable, observable } from "mobx";
import { CatalogCategoryRegistry, catalogCategoryRegistry, CatalogEntity } from "../../common/catalog";
import { iter } from "../../common/utils";
export class CatalogEntityRegistry {
protected sources = observable.map<string, IComputedValue<CatalogEntity[]>>([], { deep: true });
protected sources = observable.map<string, IComputedValue<CatalogEntity[]>>();
constructor(private categoryRegistry: CatalogCategoryRegistry) {}
constructor(private categoryRegistry: CatalogCategoryRegistry) {
makeObservable(this);
}
@action addObservableSource(id: string, source: IObservableArray<CatalogEntity>) {
this.sources.set(id, computed(() => source));

View File

@ -22,7 +22,7 @@
import "../common/cluster-ipc";
import type http from "http";
import { ipcMain } from "electron";
import { action, autorun, reaction, toJS } from "mobx";
import { action, autorun, makeObservable, reaction } from "mobx";
import { ClusterStore, getClusterIdFromHost } from "../common/cluster-store";
import type { Cluster } from "./cluster";
import logger from "./logger";
@ -32,38 +32,47 @@ import { catalogEntityRegistry } from "./catalog";
import { KubernetesCluster, KubernetesClusterPrometheusMetrics } from "../common/catalog-entities/kubernetes-cluster";
export class ClusterManager extends Singleton {
private store = ClusterStore.getInstance();
constructor() {
super();
makeObservable(this);
this.bindEvents();
}
reaction(() => toJS(ClusterStore.getInstance().clustersList, { recurseEverything: true }), () => {
this.updateCatalog(ClusterStore.getInstance().clustersList);
}, { fireImmediately: true });
private bindEvents() {
// reacting to every cluster's state change and total amount of items
reaction(
() => this.store.clustersList.map(c => c.getState()),
() => this.updateCatalog(this.store.clustersList),
{ fireImmediately: true, }
);
reaction(() => catalogEntityRegistry.getItemsForApiKind<KubernetesCluster>("entity.k8slens.dev/v1alpha1", "KubernetesCluster"), (entities) => {
this.syncClustersFromCatalog(entities);
});
// auto-stop removed clusters
autorun(() => {
const removedClusters = Array.from(ClusterStore.getInstance().removedClusters.values());
const removedClusters = Array.from(this.store.removedClusters.values());
if (removedClusters.length > 0) {
const meta = removedClusters.map(cluster => cluster.getMeta());
logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta);
removedClusters.forEach(cluster => cluster.disconnect());
ClusterStore.getInstance().removedClusters.clear();
this.store.removedClusters.clear();
}
}, {
delay: 250
});
ipcMain.on("network:offline", () => { this.onNetworkOffline(); });
ipcMain.on("network:online", () => { this.onNetworkOnline(); });
ipcMain.on("network:offline", this.onNetworkOffline);
ipcMain.on("network:online", this.onNetworkOnline);
}
@action protected updateCatalog(clusters: Cluster[]) {
@action
protected updateCatalog(clusters: Cluster[]) {
for (const cluster of clusters) {
const index = catalogEntityRegistry.items.findIndex((entity) => entity.metadata.uid === cluster.id);
@ -94,10 +103,10 @@ export class ClusterManager extends Singleton {
@action syncClustersFromCatalog(entities: KubernetesCluster[]) {
for (const entity of entities) {
const cluster = ClusterStore.getInstance().getById(entity.metadata.uid);
const cluster = this.store.getById(entity.metadata.uid);
if (!cluster) {
ClusterStore.getInstance().addCluster({
this.store.addCluster({
id: entity.metadata.uid,
preferences: {
clusterName: entity.metadata.name
@ -117,28 +126,28 @@ export class ClusterManager extends Singleton {
}
}
protected onNetworkOffline() {
protected onNetworkOffline = () => {
logger.info("[CLUSTER-MANAGER]: network is offline");
ClusterStore.getInstance().clustersList.forEach((cluster) => {
this.store.clustersList.forEach((cluster) => {
if (!cluster.disconnected) {
cluster.online = false;
cluster.accessible = false;
cluster.refreshConnectionStatus().catch((e) => e);
}
});
}
};
protected onNetworkOnline() {
protected onNetworkOnline = () => {
logger.info("[CLUSTER-MANAGER]: network is online");
ClusterStore.getInstance().clustersList.forEach((cluster) => {
this.store.clustersList.forEach((cluster) => {
if (!cluster.disconnected) {
cluster.refreshConnectionStatus().catch((e) => e);
}
});
}
};
stop() {
ClusterStore.getInstance().clusters.forEach((cluster: Cluster) => {
this.store.clusters.forEach((cluster: Cluster) => {
cluster.disconnect();
});
}
@ -150,18 +159,18 @@ export class ClusterManager extends Singleton {
if (req.headers.host.startsWith("127.0.0.1")) {
const clusterId = req.url.split("/")[1];
cluster = ClusterStore.getInstance().getById(clusterId);
cluster = this.store.getById(clusterId);
if (cluster) {
// we need to swap path prefix so that request is proxied to kube api
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix);
}
} else if (req.headers["x-cluster-id"]) {
cluster = ClusterStore.getInstance().getById(req.headers["x-cluster-id"].toString());
cluster = this.store.getById(req.headers["x-cluster-id"].toString());
} else {
const clusterId = getClusterIdFromHost(req.headers.host);
cluster = ClusterStore.getInstance().getById(clusterId);
cluster = this.store.getById(clusterId);
}
return cluster;
@ -169,9 +178,7 @@ export class ClusterManager extends Singleton {
}
export function catalogEntityFromCluster(cluster: Cluster) {
return new KubernetesCluster(toJS({
apiVersion: "entity.k8slens.dev/v1alpha1",
kind: "KubernetesCluster",
return new KubernetesCluster({
metadata: {
uid: cluster.id,
name: cluster.name,
@ -190,5 +197,5 @@ export function catalogEntityFromCluster(cluster: Cluster) {
message: "",
active: !cluster.disconnected
}
}));
});
}

View File

@ -21,7 +21,7 @@
import { ipcMain } from "electron";
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel } from "../common/cluster-store";
import { action, comparer, computed, observable, reaction, toJS, when } from "mobx";
import { action, comparer, computed, makeObservable, observable, reaction, when } from "mobx";
import { broadcastMessage, ClusterListNamespaceForbiddenChannel } from "../common/ipc";
import { ContextHandler } from "./context-handler";
import { AuthorizationV1Api, CoreV1Api, HttpError, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
@ -33,6 +33,7 @@ import logger from "./logger";
import { VersionDetector } from "./cluster-detectors/version-detector";
import { detectorRegistry } from "./cluster-detectors/detector-registry";
import plimit from "p-limit";
import { toJS } from "../common/utils";
export enum ClusterStatus {
AccessGranted = 2,
@ -91,7 +92,9 @@ export class Cluster implements ClusterModel, ClusterState {
protected activated = false;
private resourceAccessStatuses: Map<KubeApiResource, boolean> = new Map();
whenReady = when(() => this.ready);
get whenReady() {
return when(() => this.ready);
}
/**
* Kubeconfig context name
@ -227,9 +230,7 @@ export class Cluster implements ClusterModel, ClusterState {
@computed get prometheusPreferences(): ClusterPrometheusPreferences {
const { prometheus, prometheusProvider } = this.preferences;
return toJS({ prometheus, prometheusProvider }, {
recurseEverything: true,
});
return toJS({ prometheus, prometheusProvider });
}
/**
@ -240,6 +241,7 @@ export class Cluster implements ClusterModel, ClusterState {
}
constructor(model: ClusterModel) {
makeObservable(this);
this.id = model.id;
this.updateModel(model);
@ -570,9 +572,7 @@ export class Cluster implements ClusterModel, ClusterState {
accessibleNamespaces: this.accessibleNamespaces,
};
return toJS(model, {
recurseEverything: true
});
return toJS(model);
}
/**
@ -592,9 +592,7 @@ export class Cluster implements ClusterModel, ClusterState {
isGlobalWatchEnabled: this.isGlobalWatchEnabled,
};
return toJS(state, {
recurseEverything: true
});
return toJS(state);
}
/**

View File

@ -23,23 +23,25 @@ import { randomBytes } from "crypto";
import { SHA256 } from "crypto-js";
import { app, remote } from "electron";
import fse from "fs-extra";
import { action, observable, toJS } from "mobx";
import { action, makeObservable, observable } from "mobx";
import path from "path";
import { BaseStore } from "../common/base-store";
import type { LensExtensionId } from "../extensions/lens-extension";
import { toJS } from "../common/utils";
interface FSProvisionModel {
extensions: Record<string, string>; // extension names to paths
}
export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
@observable registeredExtensions = observable.map<LensExtensionId, string>();
registeredExtensions = observable.map<LensExtensionId, string>();
constructor() {
super({
configName: "lens-filesystem-provisioner-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
});
makeObservable(this);
}
/**
@ -71,9 +73,7 @@ export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
toJSON(): FSProvisionModel {
return toJS({
extensions: this.registeredExtensions.toJSON(),
}, {
recurseEverything: true
extensions: Object.fromEntries(this.registeredExtensions),
});
}
}

View File

@ -24,7 +24,7 @@
import "../common/system-ca";
import "../common/prometheus-providers";
import * as Mobx from "mobx";
import * as LensExtensions from "../extensions/core-api";
import * as LensExtensionsCoreApi from "../extensions/core-api";
import { app, autoUpdater, ipcMain, dialog, powerMonitor } from "electron";
import { appName, isMac, productName } from "../common/vars";
import path from "path";
@ -55,6 +55,7 @@ import { HotbarStore } from "../common/hotbar-store";
import { HelmRepoManager } from "./helm/helm-repo-manager";
import { KubeconfigSyncManager } from "./catalog-sources";
import { handleWsUpgrade } from "./proxy/ws-upgrade";
import configurePackages from "../common/configure-packages";
const workingDir = path.join(app.getPath("appData"), appName);
const cleanup = disposer();
@ -77,6 +78,7 @@ if (process.env.LENS_DISABLE_GPU) {
app.disableHardwareAcceleration();
}
configurePackages();
mangleProxyEnv();
if (app.commandLine.getSwitchValue("proxy-server") !== "") {
@ -191,15 +193,11 @@ app.on("ready", async () => {
cleanup.push(pushCatalogToRenderer(catalogEntityRegistry));
KubeconfigSyncManager.getInstance().startSync();
startUpdateChecking();
LensProtocolRouterMain
.getInstance()
.rendererLoaded = true;
LensProtocolRouterMain.getInstance().rendererLoaded = true;
});
ExtensionLoader.getInstance().whenLoaded.then(() => {
LensProtocolRouterMain
.getInstance()
.extensionsLoaded = true;
LensProtocolRouterMain.getInstance().extensionsLoaded = true;
});
logger.info("🧩 Initializing extensions");
@ -272,12 +270,16 @@ app.on("open-url", (event, rawUrl) => {
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl }));
});
// Extensions-api runtime exports
export const LensExtensionsApi = {
...LensExtensions,
/**
* Exports for virtual package "@k8slens/extensions" for main-process.
* All exporting names available in global runtime scope:
* e.g. global.Mobx, global.LensExtensions
*/
const LensExtensions = {
...LensExtensionsCoreApi,
};
export {
Mobx,
LensExtensionsApi as LensExtensions,
LensExtensions,
};

View File

@ -24,7 +24,7 @@ import * as proto from "../../common/protocol-handler";
import Url from "url-parse";
import type { LensExtension } from "../../extensions/lens-extension";
import { broadcastMessage } from "../../common/ipc";
import { observable, when } from "mobx";
import { observable, when, makeObservable } from "mobx";
export interface FallbackHandler {
(name: string): Promise<boolean>;
@ -36,6 +36,12 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter {
@observable rendererLoaded = false;
@observable extensionsLoaded = false;
constructor() {
super();
makeObservable(this);
}
/**
* Find the most specific registered handler, if it exists, and invoke it.
*

View File

@ -20,7 +20,7 @@
*/
import type { ClusterId } from "../common/cluster-store";
import { observable } from "mobx";
import { makeObservable, observable } from "mobx";
import { app, BrowserWindow, dialog, shell, webContents } from "electron";
import windowStateKeeper from "electron-window-state";
import { appEventBus } from "../common/event-bus";
@ -44,6 +44,7 @@ export class WindowManager extends Singleton {
constructor() {
super();
makeObservable(this);
this.bindEvents();
this.initMenu();
this.initTray();

View File

@ -21,15 +21,19 @@
import type { KubeObjectStore } from "../kube-object.store";
import { action, observable } from "mobx";
import { autobind } from "../utils";
import { action, observable, makeObservable } from "mobx";
import { autoBind } from "../utils";
import { KubeApi, parseKubeApi } from "./kube-api";
@autobind()
export class ApiManager {
private apis = observable.map<string, KubeApi>();
private stores = observable.map<string, KubeObjectStore>();
constructor() {
makeObservable(this);
autoBind(this);
}
getApi(pathOrCallback: string | ((api: KubeApi) => boolean)) {
if (typeof pathOrCallback === "string") {
return this.apis.get(pathOrCallback) || this.apis.get(parseKubeApi(pathOrCallback).apiBase);

View File

@ -19,7 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { computed, observable } from "mobx";
import { computed, observable, makeObservable } from "mobx";
import { subscribeToBroadcast } from "../../common/ipc";
import { CatalogCategory, CatalogEntity, CatalogEntityData, catalogCategoryRegistry, CatalogCategoryRegistry, CatalogEntityKindData } from "../../common/catalog";
import "../../common/catalog-entities";
@ -29,7 +29,9 @@ export class CatalogEntityRegistry {
protected rawItems = observable.array<CatalogEntityData & CatalogEntityKindData>([], { deep: true });
@observable protected _activeEntity: CatalogEntity;
constructor(private categoryRegistry: CatalogCategoryRegistry) {}
constructor(private categoryRegistry: CatalogCategoryRegistry) {
makeObservable(this);
}
init() {
subscribeToBroadcast("catalog:items", (ev, items: (CatalogEntityData & CatalogEntityKindData)[]) => {

View File

@ -19,11 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autobind } from "../../utils";
import { Role } from "./role.api";
import { KubeApi } from "../kube-api";
@autobind()
export class ClusterRole extends Role {
static kind = "ClusterRole";
static namespaced = false;

View File

@ -71,10 +71,7 @@ export interface IClusterMetrics<T = IMetrics> {
fsUsage: T;
}
export class Cluster extends KubeObject {
static kind = "Cluster";
static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters";
export interface Cluster {
spec: {
clusterNetwork?: {
serviceDomain?: string;
@ -106,6 +103,11 @@ export class Cluster extends KubeObject {
errorMessage?: string;
errorReason?: string;
};
}
export class Cluster extends KubeObject {
static kind = "Cluster";
static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters";
getStatus() {
if (this.metadata.deletionTimestamp) return ClusterStatus.REMOVING;

View File

@ -28,13 +28,15 @@ export interface IComponentStatusCondition {
message: string;
}
export interface ComponentStatus {
conditions: IComponentStatusCondition[];
}
export class ComponentStatus extends KubeObject {
static kind = "ComponentStatus";
static namespaced = false;
static apiBase = "/api/v1/componentstatuses";
conditions: IComponentStatusCondition[];
getTruthyConditions() {
return this.conditions.filter(c => c.status === "True");
}

View File

@ -21,10 +21,15 @@
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import { autobind } from "../../utils";
import { KubeApi } from "../kube-api";
import { autoBind } from "../../../common/utils";
export interface ConfigMap {
data: {
[param: string]: string;
};
}
@autobind()
export class ConfigMap extends KubeObject {
static kind = "ConfigMap";
static namespaced = true;
@ -32,12 +37,10 @@ export class ConfigMap extends KubeObject {
constructor(data: KubeJsonApiData) {
super(data);
this.data = this.data || {};
}
autoBind(this);
data: {
[param: string]: string;
};
this.data ??= {};
}
getKeys(): string[] {
return Object.keys(this.data);

View File

@ -38,11 +38,7 @@ type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & {
JSONPath: string;
};
export class CustomResourceDefinition extends KubeObject {
static kind = "CustomResourceDefinition";
static namespaced = false;
static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions";
export interface CustomResourceDefinition {
spec: {
group: string;
version?: string; // deprecated in v1 api
@ -84,6 +80,12 @@ export class CustomResourceDefinition extends KubeObject {
};
storedVersions: string[];
};
}
export class CustomResourceDefinition extends KubeObject {
static kind = "CustomResourceDefinition";
static namespaced = false;
static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions";
getResourceUrl() {
return crdResourcesURL({

View File

@ -23,8 +23,9 @@ import moment from "moment";
import { KubeObject } from "../kube-object";
import type { IPodContainer } from "./pods.api";
import { formatDuration } from "../../utils/formatDuration";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class CronJobApi extends KubeApi<CronJob> {
suspend(params: { namespace: string; name: string }) {
@ -58,28 +59,7 @@ export class CronJobApi extends KubeApi<CronJob> {
}
}
@autobind()
export class CronJob extends KubeObject {
static kind = "CronJob";
static namespaced = true;
static apiBase = "/apis/batch/v1beta1/cronjobs";
kind: string;
apiVersion: string;
metadata: {
name: string;
namespace: string;
selfLink: string;
uid: string;
resourceVersion: string;
creationTimestamp: string;
labels: {
[key: string]: string;
};
annotations: {
[key: string]: string;
};
};
export interface CronJob {
spec: {
schedule: string;
concurrencyPolicy: string;
@ -116,6 +96,17 @@ export class CronJob extends KubeObject {
status: {
lastScheduleTime?: string;
};
}
export class CronJob extends KubeObject {
static kind = "CronJob";
static namespaced = true;
static apiBase = "/apis/batch/v1beta1/cronjobs";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getSuspendFlag() {
return this.spec.suspend.toString();

View File

@ -22,16 +22,21 @@
import get from "lodash/get";
import type { IPodContainer } from "./pods.api";
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
@autobind()
export class DaemonSet extends WorkloadKubeObject {
static kind = "DaemonSet";
static namespaced = true;
static apiBase = "/apis/apps/v1/daemonsets";
spec: {
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
selector: {
matchLabels: {
[name: string]: string;
@ -73,7 +78,7 @@ export class DaemonSet extends WorkloadKubeObject {
};
revisionHistoryLimit: number;
};
status: {
declare status: {
currentNumberScheduled: number;
numberMisscheduled: number;
desiredNumberScheduled: number;

View File

@ -22,8 +22,9 @@
import moment from "moment";
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class DeploymentApi extends KubeApi<Deployment> {
protected getScaleApiUrl(params: { namespace: string; name: string }) {
@ -87,13 +88,17 @@ interface IContainerProbe {
failureThreshold?: number;
}
@autobind()
export class Deployment extends WorkloadKubeObject {
static kind = "Deployment";
static namespaced = true;
static apiBase = "/apis/apps/v1/deployments";
spec: {
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
replicas: number;
selector: { matchLabels: { [app: string]: string } };
template: {
@ -172,7 +177,7 @@ export class Deployment extends WorkloadKubeObject {
};
};
};
status: {
declare status: {
observedGeneration: number;
replicas: number;
updatedReplicas: number;

View File

@ -19,9 +19,10 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface IEndpointPort {
name?: string;
@ -121,13 +122,19 @@ export class EndpointSubset implements IEndpointSubset {
}
}
@autobind()
export interface Endpoint {
subsets: IEndpointSubset[];
}
export class Endpoint extends KubeObject {
static kind = "Endpoints";
static namespaced = true;
static apiBase = "/api/v1/endpoints";
subsets: IEndpointSubset[];
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getEndpointSubsets(): EndpointSubset[] {
const subsets = this.subsets || [];

View File

@ -22,15 +22,9 @@
import moment from "moment";
import { KubeObject } from "../kube-object";
import { formatDuration } from "../../utils/formatDuration";
import { autobind } from "../../utils";
import { KubeApi } from "../kube-api";
@autobind()
export class KubeEvent extends KubeObject {
static kind = "Event";
static namespaced = true;
static apiBase = "/api/v1/events";
export interface KubeEvent {
involvedObject: {
kind: string;
namespace: string;
@ -53,6 +47,12 @@ export class KubeEvent extends KubeObject {
eventTime: null;
reportingComponent: string;
reportingInstance: string;
}
export class KubeEvent extends KubeObject {
static kind = "Event";
static namespaced = true;
static apiBase = "/api/v1/events";
isWarning() {
return this.type === "Warning";

View File

@ -22,7 +22,7 @@
import { compile } from "path-to-regexp";
import { apiBase } from "../index";
import { stringify } from "querystring";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
export type RepoHelmChartList = Record<string, HelmChart[]>;
export type HelmChartList = Record<string, RepoHelmChartList>;
@ -83,16 +83,7 @@ export async function getChartValues(repo: string, name: string, version: string
return apiBase.get<string>(`/v2/charts/${repo}/${name}/values?${stringify({ version })}`);
}
@autobind()
export class HelmChart {
constructor(data: any) {
Object.assign(this, data);
}
static create(data: any) {
return new HelmChart(data);
}
export interface HelmChart {
apiVersion: string;
name: string;
version: string;
@ -114,6 +105,17 @@ export class HelmChart {
appVersion?: string;
deprecated?: boolean;
tillerVersion?: string;
}
export class HelmChart {
constructor(data: HelmChart) {
Object.assign(this, data);
autoBind(this);
}
static create(data: any) {
return new HelmChart(data);
}
getId() {
return `${this.repo}:${this.apiVersion}/${this.name}@${this.getAppVersion()}+${this.digest}`;

View File

@ -21,7 +21,7 @@
import jsYaml from "js-yaml";
import { compile } from "path-to-regexp";
import { autobind, formatDuration } from "../../utils";
import { autoBind, formatDuration } from "../../utils";
import capitalize from "lodash/capitalize";
import { apiBase } from "../index";
import { helmChartStore } from "../../components/+apps-helm-charts/helm-chart.store";
@ -155,16 +155,7 @@ export async function rollbackRelease(name: string, namespace: string, revision:
});
}
@autobind()
export class HelmRelease implements ItemObject {
constructor(data: any) {
Object.assign(this, data);
}
static create(data: any) {
return new HelmRelease(data);
}
export interface HelmRelease {
appVersion: string;
name: string;
namespace: string;
@ -172,6 +163,17 @@ export class HelmRelease implements ItemObject {
status: string;
updated: string;
revision: string;
}
export class HelmRelease implements ItemObject {
constructor(data: any) {
Object.assign(this, data);
autoBind(this);
}
static create(data: any) {
return new HelmRelease(data);
}
getId() {
return this.namespace + this.name;

View File

@ -59,11 +59,7 @@ export interface IHpaMetric {
}>;
}
export class HorizontalPodAutoscaler extends KubeObject {
static kind = "HorizontalPodAutoscaler";
static namespaced = true;
static apiBase = "/apis/autoscaling/v2beta1/horizontalpodautoscalers";
export interface HorizontalPodAutoscaler {
spec: {
scaleTargetRef: {
kind: string;
@ -86,6 +82,12 @@ export class HorizontalPodAutoscaler extends KubeObject {
type: string;
}[];
};
}
export class HorizontalPodAutoscaler extends KubeObject {
static kind = "HorizontalPodAutoscaler";
static namespaced = true;
static apiBase = "/apis/autoscaling/v2beta1/horizontalpodautoscalers";
getMaxPods() {
return this.spec.maxReplicas || 0;

View File

@ -20,9 +20,10 @@
*/
import { KubeObject } from "../kube-object";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { IMetrics, metricsApi } from "./metrics.api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class IngressApi extends KubeApi<Ingress> {
getMetrics(ingress: string, namespace: string): Promise<IIngressMetrics> {
@ -82,12 +83,7 @@ export const getBackendServiceNamePort = (backend: IIngressBackend) => {
return { serviceName, servicePort };
};
@autobind()
export class Ingress extends KubeObject {
static kind = "Ingress";
static namespaced = true;
static apiBase = "/apis/networking.k8s.io/v1/ingresses";
export interface Ingress {
spec: {
tls: {
secretName: string;
@ -117,6 +113,17 @@ export class Ingress extends KubeObject {
ingress: ILoadBalancerIngress[];
};
};
}
export class Ingress extends KubeObject {
static kind = "Ingress";
static namespaced = true;
static apiBase = "/apis/networking.k8s.io/v1/ingresses";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getRoutes() {
const { spec: { tls, rules } } = this;

View File

@ -20,19 +20,24 @@
*/
import get from "lodash/get";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
import type { IPodContainer } from "./pods.api";
import { KubeApi } from "../kube-api";
import type { JsonApiParams } from "../json-api";
import type { KubeJsonApiData } from "../kube-json-api";
@autobind()
export class Job extends WorkloadKubeObject {
static kind = "Job";
static namespaced = true;
static apiBase = "/apis/batch/v1/jobs";
spec: {
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
parallelism?: number;
completions?: number;
backoffLimit?: number;
@ -78,7 +83,7 @@ export class Job extends WorkloadKubeObject {
serviceAccount?: string;
schedulerName?: string;
};
status: {
declare status: {
conditions: {
type: string;
status: string;

View File

@ -21,7 +21,8 @@
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import type { KubeJsonApiData } from "../kube-json-api";
export enum LimitType {
CONTAINER = "Container",
@ -50,15 +51,21 @@ export interface LimitRangeItem extends LimitRangeParts {
type: string
}
@autobind()
export interface LimitRange {
spec: {
limits: LimitRangeItem[];
};
}
export class LimitRange extends KubeObject {
static kind = "LimitRange";
static namespaced = true;
static apiBase = "/api/v1/limitranges";
spec: {
limits: LimitRangeItem[];
};
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getContainerLimits() {
return this.spec.limits.filter(limit => limit.type === LimitType.CONTAINER);

View File

@ -21,25 +21,32 @@
import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import type { KubeJsonApiData } from "../kube-json-api";
export enum NamespaceStatus {
ACTIVE = "Active",
TERMINATING = "Terminating",
}
@autobind()
export interface Namespace {
status?: {
phase: string;
};
}
export class Namespace extends KubeObject {
static kind = "Namespace";
static namespaced = false;
static apiBase = "/api/v1/namespaces";
status?: {
phase: string;
};
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getStatus() {
return this.status ? this.status.phase : "-";
return this.status?.phase ?? "-";
}
}

View File

@ -20,8 +20,9 @@
*/
import { KubeObject } from "../kube-object";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface IPolicyIpBlock {
cidr: string;
@ -56,12 +57,7 @@ export interface IPolicyEgress {
}[];
}
@autobind()
export class NetworkPolicy extends KubeObject {
static kind = "NetworkPolicy";
static namespaced = true;
static apiBase = "/apis/networking.k8s.io/v1/networkpolicies";
export interface NetworkPolicy {
spec: {
podSelector: {
matchLabels: {
@ -73,6 +69,17 @@ export class NetworkPolicy extends KubeObject {
ingress: IPolicyIngress[];
egress: IPolicyEgress[];
};
}
export class NetworkPolicy extends KubeObject {
static kind = "NetworkPolicy";
static namespaced = true;
static apiBase = "/apis/networking.k8s.io/v1/networkpolicies";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getMatchLabels(): string[] {
if (!this.spec.podSelector || !this.spec.podSelector.matchLabels) return [];

View File

@ -20,9 +20,10 @@
*/
import { KubeObject } from "../kube-object";
import { autobind, cpuUnitsToNumber, unitsToBytes } from "../../utils";
import { autoBind, cpuUnitsToNumber, unitsToBytes } from "../../utils";
import { IMetrics, metricsApi } from "./metrics.api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class NodesApi extends KubeApi<Node> {
getMetrics(): Promise<INodeMetrics> {
@ -49,12 +50,7 @@ export interface INodeMetrics<T = IMetrics> {
fsSize: T;
}
@autobind()
export class Node extends KubeObject {
static kind = "Node";
static namespaced = false;
static apiBase = "/api/v1/nodes";
export interface Node {
spec: {
podCIDR: string;
externalID: string;
@ -105,6 +101,17 @@ export class Node extends KubeObject {
sizeBytes: number;
}[];
};
}
export class Node extends KubeObject {
static kind = "Node";
static namespaced = false;
static apiBase = "/api/v1/nodes";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getNodeConditionText() {
const { conditions } = this.status;

View File

@ -20,10 +20,11 @@
*/
import { KubeObject } from "../kube-object";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { IMetrics, metricsApi } from "./metrics.api";
import type { Pod } from "./pods.api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class PersistentVolumeClaimsApi extends KubeApi<PersistentVolumeClaim> {
getMetrics(pvcName: string, namespace: string): Promise<IPvcMetrics> {
@ -42,12 +43,7 @@ export interface IPvcMetrics<T = IMetrics> {
diskCapacity: T;
}
@autobind()
export class PersistentVolumeClaim extends KubeObject {
static kind = "PersistentVolumeClaim";
static namespaced = true;
static apiBase = "/api/v1/persistentvolumeclaims";
export interface PersistentVolumeClaim {
spec: {
accessModes: string[];
storageClassName: string;
@ -70,6 +66,17 @@ export class PersistentVolumeClaim extends KubeObject {
status: {
phase: string; // Pending
};
}
export class PersistentVolumeClaim extends KubeObject {
static kind = "PersistentVolumeClaim";
static namespaced = true;
static apiBase = "/api/v1/persistentvolumeclaims";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getPods(allPods: Pod[]): Pod[] {
const pods = allPods.filter(pod => pod.getNs() === this.getNs());

View File

@ -21,15 +21,11 @@
import { KubeObject } from "../kube-object";
import { unitsToBytes } from "../../utils/convertMemory";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
@autobind()
export class PersistentVolume extends KubeObject {
static kind = "PersistentVolume";
static namespaced = false;
static apiBase = "/api/v1/persistentvolumes";
export interface PersistentVolume {
spec: {
capacity: {
storage: string; // 8Gi
@ -65,6 +61,17 @@ export class PersistentVolume extends KubeObject {
phase: string;
reason?: string;
};
}
export class PersistentVolume extends KubeObject {
static kind = "PersistentVolume";
static namespaced = false;
static apiBase = "/api/v1/persistentvolumes";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getCapacity(inBytes = false) {
const capacity = this.spec.capacity;

View File

@ -22,11 +22,7 @@
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
export class PodMetrics extends KubeObject {
static kind = "PodMetrics";
static namespaced = true;
static apiBase = "/apis/metrics.k8s.io/v1beta1/pods";
export interface PodMetrics {
timestamp: string;
window: string;
containers: {
@ -38,6 +34,12 @@ export class PodMetrics extends KubeObject {
}[];
}
export class PodMetrics extends KubeObject {
static kind = "PodMetrics";
static namespaced = true;
static apiBase = "/apis/metrics.k8s.io/v1beta1/pods";
}
export const podMetricsApi = new KubeApi({
objectConstructor: PodMetrics,
});

View File

@ -19,16 +19,12 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
@autobind()
export class PodDisruptionBudget extends KubeObject {
static kind = "PodDisruptionBudget";
static namespaced = true;
static apiBase = "/apis/policy/v1beta1/poddisruptionbudgets";
export interface PodDisruptionBudget {
spec: {
minAvailable: string;
maxUnavailable: string;
@ -40,6 +36,17 @@ export class PodDisruptionBudget extends KubeObject {
disruptionsAllowed: number
expectedPods: number
};
}
export class PodDisruptionBudget extends KubeObject {
static kind = "PodDisruptionBudget";
static namespaced = true;
static apiBase = "/apis/policy/v1beta1/poddisruptionbudgets";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getSelectors() {
const selector = this.spec.selector;

View File

@ -20,9 +20,10 @@
*/
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { IMetrics, metricsApi } from "./metrics.api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class PodsApi extends KubeApi<Pod> {
async getLogs(params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> {
@ -202,13 +203,17 @@ export interface IPodContainerStatus {
started?: boolean;
}
@autobind()
export class Pod extends WorkloadKubeObject {
static kind = "Pod";
static namespaced = true;
static apiBase = "/api/v1/pods";
spec: {
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
volumes?: {
name: string;
persistentVolumeClaim: {
@ -265,7 +270,7 @@ export class Pod extends WorkloadKubeObject {
};
affinity?: IAffinity;
};
status?: {
declare status?: {
phase: string;
conditions: {
type: string;

View File

@ -19,16 +19,12 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
@autobind()
export class PodSecurityPolicy extends KubeObject {
static kind = "PodSecurityPolicy";
static namespaced = false;
static apiBase = "/apis/policy/v1beta1/podsecuritypolicies";
export interface PodSecurityPolicy {
spec: {
allowPrivilegeEscalation?: boolean;
allowedCSIDrivers?: {
@ -88,6 +84,17 @@ export class PodSecurityPolicy extends KubeObject {
};
volumes?: string[];
};
}
export class PodSecurityPolicy extends KubeObject {
static kind = "PodSecurityPolicy";
static namespaced = false;
static apiBase = "/apis/policy/v1beta1/podsecuritypolicies";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
isPrivileged() {
return !!this.spec.privileged;

View File

@ -20,10 +20,11 @@
*/
import get from "lodash/get";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { WorkloadKubeObject } from "../workload-kube-object";
import type { IPodContainer, Pod } from "./pods.api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class ReplicaSetApi extends KubeApi<ReplicaSet> {
protected getScaleApiUrl(params: { namespace: string; name: string }) {
@ -48,12 +49,17 @@ export class ReplicaSetApi extends KubeApi<ReplicaSet> {
}
}
@autobind()
export class ReplicaSet extends WorkloadKubeObject {
static kind = "ReplicaSet";
static namespaced = true;
static apiBase = "/apis/apps/v1/replicasets";
spec: {
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
replicas?: number;
selector: { matchLabels: { [app: string]: string } };
template?: {
@ -66,7 +72,7 @@ export class ReplicaSet extends WorkloadKubeObject {
};
minReadySeconds?: number;
};
status: {
declare status: {
replicas: number;
fullyLabeledReplicas?: number;
readyReplicas?: number;

View File

@ -21,7 +21,6 @@
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface IResourceQuotaValues {
[quota: string]: string;
@ -51,16 +50,7 @@ export interface IResourceQuotaValues {
"count/deployments.extensions"?: string;
}
export class ResourceQuota extends KubeObject {
static kind = "ResourceQuota";
static namespaced = true;
static apiBase = "/api/v1/resourcequotas";
constructor(data: KubeJsonApiData) {
super(data);
this.spec = this.spec || {} as any;
}
export interface ResourceQuota {
spec: {
hard: IResourceQuotaValues;
scopeSelector?: {
@ -76,6 +66,12 @@ export class ResourceQuota extends KubeObject {
hard: IResourceQuotaValues;
used: IResourceQuotaValues;
};
}
export class ResourceQuota extends KubeObject {
static kind = "ResourceQuota";
static namespaced = true;
static apiBase = "/api/v1/resourcequotas";
getScopeSelector() {
const { matchExpressions = [] } = this.spec.scopeSelector || {};

View File

@ -19,9 +19,10 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface IRoleBindingSubject {
kind: string;
@ -30,18 +31,24 @@ export interface IRoleBindingSubject {
apiGroup?: string;
}
@autobind()
export class RoleBinding extends KubeObject {
static kind = "RoleBinding";
static namespaced = true;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/rolebindings";
export interface RoleBinding {
subjects?: IRoleBindingSubject[];
roleRef: {
kind: string;
name: string;
apiGroup?: string;
};
}
export class RoleBinding extends KubeObject {
static kind = "RoleBinding";
static namespaced = true;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/rolebindings";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getSubjects() {
return this.subjects || [];

View File

@ -22,17 +22,19 @@
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
export class Role extends KubeObject {
static kind = "Role";
static namespaced = true;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/roles";
export interface Role {
rules: {
verbs: string[];
apiGroups: string[];
resources: string[];
resourceNames?: string[];
}[];
}
export class Role extends KubeObject {
static kind = "Role";
static namespaced = true;
static apiBase = "/apis/rbac.authorization.k8s.io/v1/roles";
getRules() {
return this.rules || [];

View File

@ -21,7 +21,7 @@
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
export enum SecretType {
@ -40,21 +40,24 @@ export interface ISecretRef {
name: string;
}
@autobind()
export class Secret extends KubeObject {
static kind = "Secret";
static namespaced = true;
static apiBase = "/api/v1/secrets";
export interface Secret {
type: SecretType;
data: {
[prop: string]: string;
token?: string;
};
}
export class Secret extends KubeObject {
static kind = "Secret";
static namespaced = true;
static apiBase = "/api/v1/secrets";
constructor(data: KubeJsonApiData) {
super(data);
this.data = this.data || {};
autoBind(this);
this.data ??= {};
}
getKeys(): string[] {

View File

@ -28,8 +28,7 @@ export class SelfSubjectRulesReviewApi extends KubeApi<SelfSubjectRulesReview> {
spec: {
namespace
},
}
);
});
}
}
@ -41,21 +40,21 @@ export interface ISelfSubjectReviewRule {
nonResourceURLs?: string[];
}
export class SelfSubjectRulesReview extends KubeObject {
static kind = "SelfSubjectRulesReview";
static namespaced = false;
static apiBase = "/apis/authorization.k8s.io/v1/selfsubjectrulesreviews";
export interface SelfSubjectRulesReview {
spec: {
// todo: add more types from api docs
namespace?: string;
};
status: {
resourceRules: ISelfSubjectReviewRule[];
nonResourceRules: ISelfSubjectReviewRule[];
incomplete: boolean;
};
}
export class SelfSubjectRulesReview extends KubeObject {
static kind = "SelfSubjectRulesReview";
static namespaced = false;
static apiBase = "/apis/authorization.k8s.io/v1/selfsubjectrulesreviews";
getResourceRules() {
const rules = this.status && this.status.resourceRules || [];

View File

@ -19,22 +19,29 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
@autobind()
export class ServiceAccount extends KubeObject {
static kind = "ServiceAccount";
static namespaced = true;
static apiBase = "/api/v1/serviceaccounts";
export interface ServiceAccount {
secrets?: {
name: string;
}[];
imagePullSecrets?: {
name: string;
}[];
}
export class ServiceAccount extends KubeObject {
static kind = "ServiceAccount";
static namespaced = true;
static apiBase = "/api/v1/serviceaccounts";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getSecrets() {
return this.secrets || [];

View File

@ -19,25 +19,21 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export interface IServicePort {
name?: string;
protocol: string;
port: number;
targetPort: number;
}
export class ServicePort implements IServicePort {
export interface ServicePort {
name?: string;
protocol: string;
port: number;
targetPort: number;
nodePort?: number;
}
constructor(data: IServicePort) {
export class ServicePort {
constructor(data: ServicePort) {
Object.assign(this, data);
}
@ -50,12 +46,7 @@ export class ServicePort implements IServicePort {
}
}
@autobind()
export class Service extends KubeObject {
static kind = "Service";
static namespaced = true;
static apiBase = "/api/v1/services";
export interface Service {
spec: {
type: string;
clusterIP: string;
@ -75,6 +66,17 @@ export class Service extends KubeObject {
}[];
};
};
}
export class Service extends KubeObject {
static kind = "Service";
static namespaced = true;
static apiBase = "/api/v1/services";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
getClusterIp() {
return this.spec.clusterIP;

View File

@ -22,8 +22,9 @@
import get from "lodash/get";
import type { IPodContainer } from "./pods.api";
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
export class StatefulSetApi extends KubeApi<StatefulSet> {
protected getScaleApiUrl(params: { namespace: string; name: string }) {
@ -48,13 +49,17 @@ export class StatefulSetApi extends KubeApi<StatefulSet> {
}
}
@autobind()
export class StatefulSet extends WorkloadKubeObject {
static kind = "StatefulSet";
static namespaced = true;
static apiBase = "/apis/apps/v1/statefulsets";
spec: {
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
declare spec: {
serviceName: string;
replicas: number;
selector: {
@ -107,7 +112,7 @@ export class StatefulSet extends WorkloadKubeObject {
};
}[];
};
status: {
declare status: {
observedGeneration: number;
replicas: number;
currentReplicas: number;

View File

@ -19,16 +19,12 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { autobind } from "../../utils";
import { autoBind } from "../../utils";
import { KubeObject } from "../kube-object";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
@autobind()
export class StorageClass extends KubeObject {
static kind = "StorageClass";
static namespaced = false;
static apiBase = "/apis/storage.k8s.io/v1/storageclasses";
export interface StorageClass {
provisioner: string; // e.g. "storage.k8s.io/v1"
mountOptions?: string[];
volumeBindingMode: string;
@ -36,6 +32,17 @@ export class StorageClass extends KubeObject {
parameters: {
[param: string]: string; // every provisioner has own set of these parameters
};
}
export class StorageClass extends KubeObject {
static kind = "StorageClass";
static namespaced = false;
static apiBase = "/apis/storage.k8s.io/v1/storageclasses";
constructor(data: KubeJsonApiData) {
super(data);
autoBind(this);
}
isDefault() {
const annotations = this.metadata.annotations || {};

View File

@ -23,7 +23,7 @@
import moment from "moment";
import type { KubeJsonApiData, KubeJsonApiDataList, KubeJsonApiListMetadata, KubeJsonApiMetadata } from "./kube-json-api";
import { autobind, formatDuration } from "../utils";
import { autoBind, formatDuration } from "../utils";
import type { ItemObject } from "../item.store";
import { apiKube } from "./index";
import type { JsonApiParams } from "./json-api";
@ -87,11 +87,16 @@ export class KubeStatus {
export type IKubeMetaField = keyof IKubeObjectMetadata;
@autobind()
export class KubeObject implements ItemObject {
export class KubeObject<Metadata extends IKubeObjectMetadata = IKubeObjectMetadata, Status = any, Spec = any> implements ItemObject {
static readonly kind: string;
static readonly namespaced: boolean;
apiVersion: string;
kind: string;
metadata: Metadata;
status?: Status;
spec?: Spec;
static create(data: any) {
return new KubeObject(data);
}
@ -176,13 +181,9 @@ export class KubeObject implements ItemObject {
constructor(data: KubeJsonApiData) {
Object.assign(this, data);
autoBind(this);
}
apiVersion: string;
kind: string;
metadata: IKubeObjectMetadata;
status?: any; // todo: type-safety support
get selfLink() {
return this.metadata.selfLink;
}
@ -265,7 +266,7 @@ export class KubeObject implements ItemObject {
}
// use unified resource-applier api for updating all k8s objects
async update<T extends KubeObject>(data: Partial<T>) {
async update<T extends KubeObject>(data: Partial<T>): Promise<T> {
return resourceApplierApi.update<T>({
...this.toPlainObject(),
...data,

View File

@ -26,8 +26,8 @@ import type { KubeObjectStore } from "../kube-object.store";
import type { ClusterContext } from "../components/context";
import plimit from "p-limit";
import { comparer, IReactionDisposer, observable, reaction, when } from "mobx";
import { autobind, noop } from "../utils";
import { comparer, IReactionDisposer, observable, reaction, makeObservable } from "mobx";
import { autoBind, noop } from "../utils";
import type { KubeApi } from "./kube-api";
import type { KubeJsonApiData } from "./kube-json-api";
import { isDebugging, isProduction } from "../../common/vars";
@ -50,11 +50,13 @@ export interface IKubeWatchLog {
cssStyle?: string;
}
@autobind()
export class KubeWatchApi {
@observable context: ClusterContext = null;
contextReady = when(() => Boolean(this.context));
constructor() {
makeObservable(this);
autoBind(this);
}
isAllowedApi(api: KubeApi): boolean {
return Boolean(this.context?.cluster.isAllowedResource(api.kind));

View File

@ -20,7 +20,7 @@
*/
import { stringify } from "querystring";
import { autobind, base64, EventEmitter } from "../utils";
import { boundMethod, base64, EventEmitter } from "../utils";
import { WebSocketApi } from "./websocket-api";
import isEqual from "lodash/isEqual";
import { isDevelopment } from "../../common/vars";
@ -106,7 +106,7 @@ export class TerminalApi extends WebSocketApi {
this.onReady.removeAllListeners();
}
@autobind()
@boundMethod
protected _onReady(data: string) {
if (!data) return true;
this.isReady = true;

View File

@ -19,7 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { observable } from "mobx";
import { observable, makeObservable } from "mobx";
import { EventEmitter } from "../../common/event-emitter";
interface IParams {
@ -66,6 +66,7 @@ export class WebSocketApi {
};
constructor(protected params: IParams) {
makeObservable(this);
this.params = Object.assign({}, WebSocketApi.defaultParams, params);
const { autoConnect, pingIntervalSeconds } = this.params;

View File

@ -68,8 +68,6 @@ export interface IAffinity {
}
export class WorkloadKubeObject extends KubeObject {
spec: any; // todo: add proper types
getSelectors(): string[] {
const selector = this.spec.selector;

View File

@ -26,13 +26,14 @@ import * as Mobx from "mobx";
import * as MobxReact from "mobx-react";
import * as ReactRouter from "react-router";
import * as ReactRouterDom from "react-router-dom";
import * as LensExtensionsCoreApi from "../extensions/core-api";
import * as LensExtensionsRendererApi from "../extensions/renderer-api";
import { render, unmountComponentAtNode } from "react-dom";
import { delay } from "../common/utils";
import { isMac, isDevelopment } from "../common/vars";
import { HotbarStore } from "../common/hotbar-store";
import { ClusterStore } from "../common/cluster-store";
import { UserStore } from "../common/user-store";
import * as LensExtensions from "../extensions/extension-api";
import { ExtensionDiscovery } from "../extensions/extension-discovery";
import { ExtensionLoader } from "../extensions/extension-loader";
import { ExtensionsStore } from "../extensions/extensions-store";
@ -43,6 +44,9 @@ import { ThemeStore } from "./theme.store";
import { HelmRepoManager } from "../main/helm/helm-repo-manager";
import { ExtensionInstallationStateStore } from "./components/+extensions/extension-install.store";
import { DefaultProps } from "./mui-base-theme";
import configurePackages from "../common/configure-packages";
configurePackages();
/**
* If this is a development buid, wait a second to attach
@ -59,15 +63,6 @@ type AppComponent = React.ComponentType & {
init?(): Promise<void>;
};
export {
React,
ReactRouter,
ReactRouterDom,
Mobx,
MobxReact,
LensExtensions
};
export async function bootstrap(App: AppComponent) {
const rootElem = document.getElementById("app");
@ -120,3 +115,23 @@ export async function bootstrap(App: AppComponent) {
// run
bootstrap(process.isMainFrame ? LensApp : App);
/**
* Exports for virtual package "@k8slens/extensions" for renderer-process.
* All exporting names available in global runtime scope:
* e.g. Devtools -> Console -> window.LensExtensions (renderer)
*/
const LensExtensions = {
...LensExtensionsCoreApi,
...LensExtensionsRendererApi,
};
export {
React,
ReactRouter,
ReactRouterDom,
Mobx,
MobxReact,
LensExtensions,
};

View File

@ -22,7 +22,7 @@
import "./add-cluster.scss";
import React from "react";
import { observer } from "mobx-react";
import { action, observable, runInAction } from "mobx";
import { action, observable, runInAction, makeObservable } from "mobx";
import { KubeConfig } from "@kubernetes/client-node";
import { AceEditor } from "../ace-editor";
import { Button } from "../button";
@ -50,6 +50,11 @@ export class AddCluster extends React.Component {
kubeContexts = observable.map<string, KubeConfig>();
constructor(props: {}) {
super(props);
makeObservable(this);
}
componentDidMount() {
appEventBus.emit({ name: "cluster-add", action: "start" });
}

View File

@ -23,10 +23,10 @@ import "./helm-chart-details.scss";
import React, { Component } from "react";
import { getChartDetails, HelmChart } from "../../api/endpoints/helm-charts.api";
import { observable, autorun } from "mobx";
import { observable, autorun, makeObservable } from "mobx";
import { observer } from "mobx-react";
import { Drawer, DrawerItem } from "../drawer";
import { autobind, stopPropagation } from "../../utils";
import { boundMethod, stopPropagation } from "../../utils";
import { MarkdownViewer } from "../markdown-viewer";
import { Spinner } from "../spinner";
import { Button } from "../button";
@ -48,6 +48,11 @@ export class HelmChartDetails extends Component<Props> {
private abortController?: AbortController;
constructor(props: Props) {
super(props);
makeObservable(this);
}
componentWillUnmount() {
this.abortController?.abort();
}
@ -67,7 +72,7 @@ export class HelmChartDetails extends Component<Props> {
});
});
@autobind()
@boundMethod
async onVersionChange({ value: version }: SelectOption<string>) {
this.selectedChart = this.chartVersions.find(chart => chart.version === version);
this.readme = null;
@ -84,7 +89,7 @@ export class HelmChartDetails extends Component<Props> {
}
}
@autobind()
@boundMethod
install() {
createInstallChartTab(this.selectedChart);
this.props.hideDetails();

View File

@ -20,8 +20,8 @@
*/
import semver from "semver";
import { observable } from "mobx";
import { autobind } from "../../utils";
import { observable, makeObservable } from "mobx";
import { autoBind } from "../../utils";
import { getChartDetails, HelmChart, listCharts } from "../../api/endpoints/helm-charts.api";
import { ItemStore } from "../../item.store";
import flatten from "lodash/flatten";
@ -31,10 +31,16 @@ export interface IChartVersion {
version: string;
}
@autobind()
export class HelmChartStore extends ItemStore<HelmChart> {
@observable versions = observable.map<string, IChartVersion[]>();
constructor() {
super();
makeObservable(this);
autoBind(this);
}
async loadAll() {
try {
const res = await this.loadItems(() => listCharts());

View File

@ -57,10 +57,10 @@ export class HelmCharts extends Component<Props> {
showDetails = (chart: HelmChart) => {
if (!chart) {
navigation.merge(helmChartsURL());
navigation.push(helmChartsURL());
}
else {
navigation.merge(helmChartsURL({
navigation.push(helmChartsURL({
params: {
chartName: chart.getName(),
repo: chart.getRepository(),

View File

@ -24,7 +24,7 @@ import "./release-details.scss";
import React, { Component } from "react";
import groupBy from "lodash/groupBy";
import isEqual from "lodash/isEqual";
import { observable, reaction } from "mobx";
import { observable, reaction, makeObservable } from "mobx";
import { Link } from "react-router-dom";
import kebabCase from "lodash/kebabCase";
import { getRelease, getReleaseValues, HelmRelease, IReleaseDetails } from "../../api/endpoints/helm-releases.api";
@ -72,7 +72,7 @@ export class ReleaseDetails extends Component<Props> {
);
@disposeOnUnmount
secretWatcher = reaction(() => secretsStore.items.toJS(), () => {
secretWatcher = reaction(() => secretsStore.getItems(), () => {
if (!this.props.release) return;
const { getReleaseSecret } = releaseStore;
const { release } = this.props;
@ -85,6 +85,11 @@ export class ReleaseDetails extends Component<Props> {
this.releaseSecret = secret;
});
constructor(props: Props) {
super(props);
makeObservable(this);
}
async loadDetails() {
const { release } = this.props;

View File

@ -21,7 +21,7 @@
import React from "react";
import type { HelmRelease } from "../../api/endpoints/helm-releases.api";
import { autobind, cssNames } from "../../utils";
import { boundMethod, cssNames } from "../../utils";
import { releaseStore } from "./release.store";
import { MenuActions, MenuActionsProps } from "../menu/menu-actions";
import { MenuItem } from "../menu";
@ -35,12 +35,12 @@ interface Props extends MenuActionsProps {
}
export class HelmReleaseMenu extends React.Component<Props> {
@autobind()
@boundMethod
remove() {
return releaseStore.remove(this.props.release);
}
@autobind()
@boundMethod
upgrade() {
const { release, hideDetails } = this.props;
@ -48,7 +48,7 @@ export class HelmReleaseMenu extends React.Component<Props> {
hideDetails && hideDetails();
}
@autobind()
@boundMethod
rollback() {
ReleaseRollbackDialog.open(this.props.release);
}

View File

@ -22,7 +22,7 @@
import "./release-rollback-dialog.scss";
import React from "react";
import { observable } from "mobx";
import { observable, makeObservable } from "mobx";
import { observer } from "mobx-react";
import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard";
@ -35,26 +35,33 @@ import orderBy from "lodash/orderBy";
interface Props extends DialogProps {
}
const dialogState = observable.object({
isOpen: false,
release: null as HelmRelease,
});
@observer
export class ReleaseRollbackDialog extends React.Component<Props> {
@observable static isOpen = false;
@observable.ref static release: HelmRelease = null;
@observable isLoading = false;
@observable revision: IReleaseRevision;
@observable revisions = observable.array<IReleaseRevision>();
constructor(props: Props) {
super(props);
makeObservable(this);
}
static open(release: HelmRelease) {
ReleaseRollbackDialog.isOpen = true;
ReleaseRollbackDialog.release = release;
dialogState.isOpen = true;
dialogState.release = release;
}
static close() {
ReleaseRollbackDialog.isOpen = false;
dialogState.isOpen = false;
}
get release(): HelmRelease {
return ReleaseRollbackDialog.release;
return dialogState.release;
}
onOpen = async () => {
@ -115,7 +122,7 @@ export class ReleaseRollbackDialog extends React.Component<Props> {
<Dialog
{...dialogProps}
className="ReleaseRollbackDialog"
isOpen={ReleaseRollbackDialog.isOpen}
isOpen={dialogState.isOpen}
onOpen={this.onOpen}
close={this.close}
>

View File

@ -20,8 +20,8 @@
*/
import isEqual from "lodash/isEqual";
import { action, observable, reaction, when } from "mobx";
import { autobind } from "../../utils";
import { action, observable, reaction, when, makeObservable } from "mobx";
import { autoBind } from "../../utils";
import { createRelease, deleteRelease, HelmRelease, IReleaseCreatePayload, IReleaseUpdatePayload, listReleases, rollbackRelease, updateRelease } from "../../api/endpoints/helm-releases.api";
import { ItemStore } from "../../item.store";
import type { Secret } from "../../api/endpoints";
@ -29,19 +29,21 @@ import { secretsStore } from "../+config-secrets/secrets.store";
import { namespaceStore } from "../+namespaces/namespace.store";
import { Notifications } from "../notifications";
@autobind()
export class ReleaseStore extends ItemStore<HelmRelease> {
releaseSecrets = observable.map<string, Secret>();
constructor() {
super();
makeObservable(this);
autoBind(this);
when(() => secretsStore.isLoaded, () => {
this.releaseSecrets.replace(this.getReleaseSecrets());
});
}
watchAssociatedSecrets(): (() => void) {
return reaction(() => secretsStore.items.toJS(), () => {
return reaction(() => secretsStore.getItems(), () => {
if (this.isLoading) return;
const newSecrets = this.getReleaseSecrets();
const amountChanged = newSecrets.length !== this.releaseSecrets.size;

View File

@ -67,7 +67,7 @@ export class HelmReleases extends Component<Props> {
}
showDetails = (item: HelmRelease) => {
navigation.merge(releaseURL({
navigation.push(releaseURL({
params: {
name: item.getName(),
namespace: item.getNs()
@ -76,7 +76,7 @@ export class HelmReleases extends Component<Props> {
};
hideDetails = () => {
navigation.merge(releaseURL());
navigation.push(releaseURL());
};
renderRemoveDialogMessage(selectedItems: HelmRelease[]) {

View File

@ -24,12 +24,10 @@ import { observer } from "mobx-react";
import { TabLayout, TabLayoutRoute } from "../layout/tab-layout";
import { HelmCharts, helmChartsRoute, helmChartsURL } from "../+apps-helm-charts";
import { HelmReleases, releaseRoute, releaseURL } from "../+apps-releases";
import { namespaceUrlParam } from "../+namespaces/namespace.store";
@observer
export class Apps extends React.Component {
static get tabRoutes(): TabLayoutRoute[] {
const query = namespaceUrlParam.toObjectParam();
return [
{
@ -41,7 +39,7 @@ export class Apps extends React.Component {
{
title: "Releases",
component: HelmReleases,
url: releaseURL({ query }),
url: releaseURL(),
routePath: releaseRoute.path.toString(),
},
];

View File

@ -24,8 +24,8 @@ import React from "react";
import { SpeedDial, SpeedDialAction } from "@material-ui/lab";
import { Icon } from "../icon";
import { disposeOnUnmount, observer } from "mobx-react";
import { observable, reaction } from "mobx";
import { autobind } from "../../../common/utils";
import { observable, reaction, makeObservable } from "mobx";
import { boundMethod } from "../../../common/utils";
import type { CatalogCategory, CatalogEntityAddMenuContext, CatalogEntityAddMenu } from "../../api/catalog-entity";
import { EventEmitter } from "events";
import { navigate } from "../../navigation";
@ -39,6 +39,11 @@ export class CatalogAddButton extends React.Component<CatalogAddButtonProps> {
@observable protected isOpen = false;
protected menuItems = observable.array<CatalogEntityAddMenu>([]);
constructor(props: CatalogAddButtonProps) {
super(props);
makeObservable(this);
}
componentDidMount() {
disposeOnUnmount(this, [
reaction(() => this.props.category, (category) => {
@ -56,17 +61,17 @@ export class CatalogAddButton extends React.Component<CatalogAddButtonProps> {
]);
}
@autobind()
@boundMethod
onOpen() {
this.isOpen = true;
}
@autobind()
@boundMethod
onClose() {
this.isOpen = false;
}
@autobind()
@boundMethod
onButtonClick() {
if (this.menuItems.length == 1) {
this.menuItems[0].onClick();

View File

@ -19,12 +19,12 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from "mobx";
import { catalogEntityRegistry } from "../../api/catalog-entity-registry";
import type { CatalogEntity, CatalogEntityActionContext } from "../../api/catalog-entity";
import { ItemObject, ItemStore } from "../../item.store";
import { autobind } from "../../utils";
import { CatalogCategory } from "../../../common/catalog";
import { autoBind } from "../../../common/utils";
export class CatalogEntityItem implements ItemObject {
constructor(public entity: CatalogEntity) {}
@ -84,8 +84,13 @@ export class CatalogEntityItem implements ItemObject {
}
}
@autobind()
export class CatalogEntityStore extends ItemStore<CatalogEntityItem> {
constructor() {
super();
makeObservable(this);
autoBind(this);
}
@observable activeCategory?: CatalogCategory;
@computed get entities() {

View File

@ -23,7 +23,7 @@ import "./catalog.scss";
import React from "react";
import { disposeOnUnmount, observer } from "mobx-react";
import { ItemListLayout } from "../item-object-list";
import { action, observable, reaction, when } from "mobx";
import { action, makeObservable, observable, reaction, when } from "mobx";
import { CatalogEntityItem, CatalogEntityStore } from "./catalog-entity.store";
import { navigate } from "../../navigation";
import { kebabCase } from "lodash";
@ -32,7 +32,6 @@ import { MenuItem, MenuActions } from "../menu";
import { CatalogEntityContextMenu, CatalogEntityContextMenuContext, catalogEntityRunContext } from "../../api/catalog-entity";
import { Badge } from "../badge";
import { HotbarStore } from "../../../common/hotbar-store";
import { autobind } from "../../utils";
import { ConfirmDialog } from "../confirm-dialog";
import { Tab, Tabs } from "../tabs";
import { catalogCategoryRegistry } from "../../../common/catalog";
@ -49,12 +48,18 @@ enum sortBy {
}
interface Props extends RouteComponentProps<ICatalogViewRouteParam> {}
@observer
export class Catalog extends React.Component<Props> {
@observable private catalogEntityStore?: CatalogEntityStore;
@observable.deep private contextMenu: CatalogEntityContextMenuContext;
@observable private contextMenu: CatalogEntityContextMenuContext;
@observable activeTab?: string;
constructor(props: Props) {
super(props);
makeObservable(this);
}
get routeActiveTab(): string | undefined {
const { group, kind } = this.props.match.params ?? {};
@ -155,8 +160,7 @@ export class Catalog extends React.Component<Props> {
);
}
@autobind()
renderItemMenu(item: CatalogEntityItem) {
renderItemMenu = (item: CatalogEntityItem) => {
const menuItems = this.contextMenu.menuItems.filter((menuItem) => !menuItem.onlyVisibleForSource || menuItem.onlyVisibleForSource === item.entity.metadata.source);
return (
@ -173,7 +177,7 @@ export class Catalog extends React.Component<Props> {
</MenuItem>
</MenuActions>
);
}
};
renderIcon(item: CatalogEntityItem) {
const category = catalogCategoryRegistry.getCategoryForEntity(item.entity);

Some files were not shown because too many files have changed in this diff Show More