From 56197d8e2e3d00a47a56142aed6eafe0fe031812 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 19 Dec 2022 19:44:04 +0300 Subject: [PATCH 01/21] Fix resize anchor z-index (#6784) Signed-off-by: Alex Andreev Signed-off-by: Alex Andreev --- src/renderer/components/resizing-anchor/resizing-anchor.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/resizing-anchor/resizing-anchor.scss b/src/renderer/components/resizing-anchor/resizing-anchor.scss index 7e18e789c5..4abfd4e933 100644 --- a/src/renderer/components/resizing-anchor/resizing-anchor.scss +++ b/src/renderer/components/resizing-anchor/resizing-anchor.scss @@ -13,7 +13,7 @@ body.resizing { $dimension: 12px; position: absolute; - z-index: var(--z-index-base); + z-index: var(--z-index-above); &::after { content: " "; From 10849ae02726d8680b8396bc9fd522c82ff06846 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 07:53:17 -0500 Subject: [PATCH 02/21] Bump @types/node from 16.18.9 to 16.18.10 (#6791) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.18.9 to 16.18.10. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c6258f0668..232a4395ad 100644 --- a/package.json +++ b/package.json @@ -321,7 +321,7 @@ "@types/memorystream": "^0.3.0", "@types/mini-css-extract-plugin": "^2.4.0", "@types/mock-fs": "^4.13.1", - "@types/node": "^16.18.9", + "@types/node": "^16.18.10", "@types/proper-lockfile": "^4.1.2", "@types/randomcolor": "^0.5.7", "@types/react": "^17.0.45", diff --git a/yarn.lock b/yarn.lock index 8c1cc6e72b..3d971d840a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2357,10 +2357,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/node@^16.11.26", "@types/node@^16.18.9": - version "16.18.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.9.tgz#47c491cfbc10460571d766c16526748fa9ad96a1" - integrity sha512-nhrqXYxiQ+5B/tPorWum37VgAiefi/wmfJ1QZKGKKecC8/3HqcTTJD0O+VABSPwtseMMF7NCPVT9uGgwn0YqsQ== +"@types/node@^16.11.26", "@types/node@^16.18.10": + version "16.18.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.10.tgz#d7415ef18c94f8d4e4a82ebcc8b8999f965d8920" + integrity sha512-XU1+v7h81p7145ddPfjv7jtWvkSilpcnON3mQ+bDi9Yuf7OI56efOglXRyXWgQ57xH3fEQgh7WOJMncRHVew5w== "@types/parse-json@^4.0.0": version "4.0.0" From 8db81a4731445ea16b6cc6bbf14afaea8f2fa8ab Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 20 Dec 2022 04:53:33 -0800 Subject: [PATCH 03/21] Remove in-tree extensions to help facilitate a more secure and faster booting lens (#6775) * Remove kube-object-event-status extension Signed-off-by: Sebastian Malton * Remove cluster-metrics-feature intree extension Signed-off-by: Sebastian Malton * Remove lens-node-menu intree extension Signed-off-by: Sebastian Malton * Remove lens-pod-menu intree extension Signed-off-by: Sebastian Malton * Remove building extensions as part of build Signed-off-by: Sebastian Malton * Remove integration test touching previously bundled code Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- .eslintrc.js | 1 - .github/workflows/test.yml | 4 - .gitignore | 1 - .idea/lens.iml | 6 +- Makefile | 35 +- extensions/.eslintrc.js | 20 - extensions/.gitignore | 1 - extensions/kube-object-event-status/Makefile | 8 - .../kube-object-event-status/package.json | 21 - .../kube-object-event-status/renderer.tsx | 53 -- .../kube-object-event-status/src/resolver.tsx | 72 --- .../kube-object-event-status/tsconfig.json | 27 - .../webpack.config.js | 44 -- .../metrics-cluster-feature/package.json | 24 - .../metrics-cluster-feature/renderer.tsx | 27 - .../resources/01-namespace.yml.hb | 6 - .../resources/02-configmap.yml.hb | 281 ---------- .../resources/02-service-account.yml | 5 - .../resources/03-service.yml.hb | 18 - .../resources/03-statefulset.yml.hb | 113 ---- .../resources/04-rules.yml | 514 ------------------ .../resources/05-clusterrole.yml | 18 - .../resources/06-clusterrole-binding.yml | 12 - .../resources/10-node-exporter-ds.yml.hb | 79 --- .../resources/11-node-exporter-svc.yml | 18 - .../12-kube-state-metrics-clusterrole.yml | 128 ----- .../resources/12.kube-state-metrics-sa.yml | 5 - ...kube-state-metrics-clusterrole-binding.yml | 12 - .../14-kube-state-metrics-deployment.yml.hb | 46 -- .../resources/14-kube-state-metrics-svc.yml | 17 - .../src/metrics-feature.ts | 108 ---- .../src/metrics-settings.tsx | 276 ---------- .../metrics-cluster-feature/tsconfig.json | 27 - .../metrics-cluster-feature/webpack.config.js | 47 -- extensions/node-menu/package.json | 22 - extensions/node-menu/renderer.tsx | 21 - extensions/node-menu/src/node-menu.tsx | 122 ----- extensions/node-menu/tsconfig.json | 27 - extensions/node-menu/webpack.config.js | 44 -- extensions/pod-menu/package.json | 22 - extensions/pod-menu/renderer.tsx | 39 -- extensions/pod-menu/src/attach-menu.tsx | 106 ---- extensions/pod-menu/src/logs-menu.tsx | 87 --- extensions/pod-menu/src/shell-menu.tsx | 114 ---- extensions/pod-menu/tsconfig.json | 27 - extensions/pod-menu/webpack.config.js | 44 -- integration/__tests__/cluster-pages.tests.ts | 54 -- package.json | 14 +- 48 files changed, 5 insertions(+), 2812 deletions(-) delete mode 100644 extensions/.eslintrc.js delete mode 100644 extensions/.gitignore delete mode 100644 extensions/kube-object-event-status/Makefile delete mode 100644 extensions/kube-object-event-status/package.json delete mode 100644 extensions/kube-object-event-status/renderer.tsx delete mode 100644 extensions/kube-object-event-status/src/resolver.tsx delete mode 100644 extensions/kube-object-event-status/tsconfig.json delete mode 100644 extensions/kube-object-event-status/webpack.config.js delete mode 100644 extensions/metrics-cluster-feature/package.json delete mode 100644 extensions/metrics-cluster-feature/renderer.tsx delete mode 100644 extensions/metrics-cluster-feature/resources/01-namespace.yml.hb delete mode 100644 extensions/metrics-cluster-feature/resources/02-configmap.yml.hb delete mode 100644 extensions/metrics-cluster-feature/resources/02-service-account.yml delete mode 100644 extensions/metrics-cluster-feature/resources/03-service.yml.hb delete mode 100644 extensions/metrics-cluster-feature/resources/03-statefulset.yml.hb delete mode 100644 extensions/metrics-cluster-feature/resources/04-rules.yml delete mode 100644 extensions/metrics-cluster-feature/resources/05-clusterrole.yml delete mode 100644 extensions/metrics-cluster-feature/resources/06-clusterrole-binding.yml delete mode 100644 extensions/metrics-cluster-feature/resources/10-node-exporter-ds.yml.hb delete mode 100644 extensions/metrics-cluster-feature/resources/11-node-exporter-svc.yml delete mode 100644 extensions/metrics-cluster-feature/resources/12-kube-state-metrics-clusterrole.yml delete mode 100644 extensions/metrics-cluster-feature/resources/12.kube-state-metrics-sa.yml delete mode 100644 extensions/metrics-cluster-feature/resources/13-kube-state-metrics-clusterrole-binding.yml delete mode 100644 extensions/metrics-cluster-feature/resources/14-kube-state-metrics-deployment.yml.hb delete mode 100644 extensions/metrics-cluster-feature/resources/14-kube-state-metrics-svc.yml delete mode 100644 extensions/metrics-cluster-feature/src/metrics-feature.ts delete mode 100644 extensions/metrics-cluster-feature/src/metrics-settings.tsx delete mode 100644 extensions/metrics-cluster-feature/tsconfig.json delete mode 100644 extensions/metrics-cluster-feature/webpack.config.js delete mode 100644 extensions/node-menu/package.json delete mode 100644 extensions/node-menu/renderer.tsx delete mode 100644 extensions/node-menu/src/node-menu.tsx delete mode 100644 extensions/node-menu/tsconfig.json delete mode 100644 extensions/node-menu/webpack.config.js delete mode 100644 extensions/pod-menu/package.json delete mode 100644 extensions/pod-menu/renderer.tsx delete mode 100644 extensions/pod-menu/src/attach-menu.tsx delete mode 100644 extensions/pod-menu/src/logs-menu.tsx delete mode 100644 extensions/pod-menu/src/shell-menu.tsx delete mode 100644 extensions/pod-menu/tsconfig.json delete mode 100644 extensions/pod-menu/webpack.config.js diff --git a/.eslintrc.js b/.eslintrc.js index 52ba26dcb3..09a06987c4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,7 +11,6 @@ module.exports = { "**/dist/**/*", "**/static/**/*", "**/site/**/*", - "extensions/*/*.tgz", "build/webpack/**/*", ], settings: { diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee1ab58970..e71bfb0be8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,10 +57,6 @@ jobs: name: Run tests if: ${{ matrix.type == 'unit' }} - - run: make test-extensions - name: Run In-tree Extension tests - if: ${{ matrix.type == 'unit' }} - - run: make ci-validate-dev if: ${{ contains(github.event.pull_request.labels.*.name, 'dependencies') && matrix.type == 'unit' }} name: Validate dev mode will work diff --git a/.gitignore b/.gitignore index 0a79ea77c1..57139f691b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ src/extensions/*/*.js src/extensions/*/*.d.ts types/extension-api.d.ts types/extension-renderer-api.d.ts -extensions/*/dist docs/extensions/api site/ build/webpack/ diff --git a/.idea/lens.iml b/.idea/lens.iml index 88175e2aaa..3bef0f9888 100644 --- a/.idea/lens.iml +++ b/.idea/lens.iml @@ -6,10 +6,6 @@ - - - - @@ -20,4 +16,4 @@ - \ No newline at end of file + diff --git a/Makefile b/Makefile index 1a21ef0e02..07caefd5d2 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,6 @@ CMD_ARGS = $(filter-out $@,$(MAKECMDGOALS)) NPM_RELEASE_TAG ?= latest ELECTRON_BUILDER_EXTRA_ARGS ?= -EXTENSIONS_DIR = ./extensions -extensions = $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), ${dir}) -extension_node_modules = $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), ${dir}/node_modules) -extension_dists = $(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), ${dir}/dist) ifeq ($(OS),Windows_NT) DETECTED_OS := Windows @@ -28,10 +24,10 @@ compile-dev: node_modules yarn compile:renderer --cache .PHONY: validate-dev -ci-validate-dev: binaries/client build-extensions compile-dev +ci-validate-dev: binaries/client compile-dev .PHONY: dev -dev: binaries/client build-extensions +dev: binaries/client rm -rf static/build/ yarn run build:tray-icons yarn dev @@ -54,7 +50,6 @@ integration: build .PHONY: build build: node_modules binaries/client - $(MAKE) build-extensions -B yarn run build:tray-icons yarn run compile ifeq "$(DETECTED_OS)" "Windows" @@ -63,26 +58,6 @@ ifeq "$(DETECTED_OS)" "Windows" endif yarn run electron-builder --publish onTag $(ELECTRON_BUILDER_EXTRA_ARGS) -.NOTPARALLEL: $(extension_node_modules) -$(extension_node_modules): node_modules - cd $(@:/node_modules=) && ../../node_modules/.bin/npm install --no-audit --no-fund --no-save - -$(extension_dists): src/extensions/npm/extensions/dist $(extension_node_modules) - cd $(@:/dist=) && ../../node_modules/.bin/npm run build - rm -rf ./node_modules/$(shell basename $(@:/dist=)) - -.PHONY: clean-old-extensions -clean-old-extensions: - find ./extensions -mindepth 1 -maxdepth 1 -type d '!' -exec test -e '{}/package.json' \; -exec rm -rf {} \; - -.PHONY: build-extensions -build-extensions: node_modules clean-old-extensions $(extension_dists) - yarn install --check-files --frozen-lockfile --network-timeout=100000 - -.PHONY: test-extensions -test-extensions: $(extension_node_modules) - $(foreach dir, $(extensions), (cd $(dir) && npm run test || exit $?);) - src/extensions/npm/extensions/__mocks__: cp -r __mocks__ src/extensions/npm/extensions/ @@ -113,16 +88,12 @@ build-docs: docs: build-docs yarn mkdocs-serve-local -.PHONY: clean-extensions -clean-extensions: - rm -rf $(EXTENSIONS_DIR)/*/{dist,node_modules,*.tgz} - .PHONY: clean-npm clean-npm: rm -rf src/extensions/npm/extensions/{dist,__mocks__,node_modules} .PHONY: clean -clean: clean-npm clean-extensions +clean: clean-npm rm -rf binaries/client rm -rf dist rm -rf static/build diff --git a/extensions/.eslintrc.js b/extensions/.eslintrc.js deleted file mode 100644 index 9d7b71763d..0000000000 --- a/extensions/.eslintrc.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -module.exports = { - "overrides": [ - { - files: [ - "**/*.ts", - "**/*.tsx", - ], - rules: { - "import/no-unresolved": ["error", { - ignore: ["@k8slens/extensions"], - }], - }, - }, - ], -}; diff --git a/extensions/.gitignore b/extensions/.gitignore deleted file mode 100644 index 193cfa4791..0000000000 --- a/extensions/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*/*.tgz diff --git a/extensions/kube-object-event-status/Makefile b/extensions/kube-object-event-status/Makefile deleted file mode 100644 index 1c9223e184..0000000000 --- a/extensions/kube-object-event-status/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -install-deps: - npm install - -build: install-deps - npm run build - -test: - npm run test diff --git a/extensions/kube-object-event-status/package.json b/extensions/kube-object-event-status/package.json deleted file mode 100644 index 4e65471429..0000000000 --- a/extensions/kube-object-event-status/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "kube-object-event-status", - "version": "6.1.1", - "description": "Adds kube object status from events", - "renderer": "dist/renderer.js", - "lens": { - "metadata": {}, - "styles": [] - }, - "scripts": { - "build": "npx webpack", - "dev": "npx webpack -- --watch", - "test": "echo NO TESTS" - }, - "files": [ - "dist/**/*" - ], - "devDependencies": { - "@k8slens/extensions": "file:../../src/extensions/npm/extensions" - } -} diff --git a/extensions/kube-object-event-status/renderer.tsx b/extensions/kube-object-event-status/renderer.tsx deleted file mode 100644 index 65b92a2658..0000000000 --- a/extensions/kube-object-event-status/renderer.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { Renderer } from "@k8slens/extensions"; -import { resolveStatus, resolveStatusForCronJobs, resolveStatusForPods } from "./src/resolver"; - -export default class EventResourceStatusRendererExtension extends Renderer.LensExtension { - kubeObjectStatusTexts = [ - { - kind: "Pod", - apiVersions: ["v1"], - resolve: (pod: Renderer.K8sApi.Pod) => resolveStatusForPods(pod), - }, - { - kind: "ReplicaSet", - apiVersions: ["v1"], - resolve: (replicaSet: Renderer.K8sApi.ReplicaSet) => resolveStatus(replicaSet), - }, - { - kind: "Deployment", - apiVersions: ["apps/v1"], - resolve: (deployment: Renderer.K8sApi.Deployment) => resolveStatus(deployment), - }, - { - kind: "StatefulSet", - apiVersions: ["apps/v1"], - resolve: (statefulSet: Renderer.K8sApi.StatefulSet) => resolveStatus(statefulSet), - }, - { - kind: "DaemonSet", - apiVersions: ["apps/v1"], - resolve: (daemonSet: Renderer.K8sApi.DaemonSet) => resolveStatus(daemonSet), - }, - { - kind: "Job", - apiVersions: [ - "batch/v1", - "batch/v1beta1", - ], - resolve: (job: Renderer.K8sApi.Job) => resolveStatus(job), - }, - { - kind: "CronJob", - apiVersions: [ - "batch/v1", - "batch/v1beta1", - ], - resolve: (cronJob: Renderer.K8sApi.CronJob) => resolveStatusForCronJobs(cronJob), - }, - ]; -} diff --git a/extensions/kube-object-event-status/src/resolver.tsx b/extensions/kube-object-event-status/src/resolver.tsx deleted file mode 100644 index fa98290c49..0000000000 --- a/extensions/kube-object-event-status/src/resolver.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { Renderer } from "@k8slens/extensions"; - -const { apiManager, eventApi, KubeObjectStatusLevel } = Renderer.K8sApi; - -type KubeObject = Renderer.K8sApi.KubeObject; -type Pod = Renderer.K8sApi.Pod; -type CronJob = Renderer.K8sApi.CronJob; -type KubeObjectStatus = Renderer.K8sApi.KubeObjectStatus; -type EventStore = Renderer.K8sApi.EventStore; - -export function resolveStatus(object: KubeObject): KubeObjectStatus { - const eventStore = apiManager.getStore(eventApi); - const events = (eventStore as EventStore).getEventsByObject(object); - const warnings = events.filter(evt => evt.isWarning()); - - if (!events.length || !warnings.length) { - return null; - } - const event = [...warnings, ...events][0]; // get latest event - - return { - level: KubeObjectStatusLevel.WARNING, - text: `${event.message}`, - timestamp: event.metadata.creationTimestamp, - }; -} - -export function resolveStatusForPods(pod: Pod): KubeObjectStatus { - if (!pod.hasIssues()) { - return null; - } - const eventStore = apiManager.getStore(eventApi); - const events = (eventStore as EventStore).getEventsByObject(pod); - const warnings = events.filter(evt => evt.isWarning()); - - if (!events.length || !warnings.length) { - return null; - } - const event = [...warnings, ...events][0]; // get latest event - - return { - level: KubeObjectStatusLevel.WARNING, - text: `${event.message}`, - timestamp: event.metadata.creationTimestamp, - }; -} - -export function resolveStatusForCronJobs(cronJob: CronJob): KubeObjectStatus { - const eventStore = apiManager.getStore(eventApi); - let events = (eventStore as EventStore).getEventsByObject(cronJob); - const warnings = events.filter(evt => evt.isWarning()); - - if (cronJob.isNeverRun()) { - events = events.filter(event => event.reason != "FailedNeedsStart"); - } - - if (!events.length || !warnings.length) { - return null; - } - const event = [...warnings, ...events][0]; // get latest event - - return { - level: KubeObjectStatusLevel.WARNING, - text: `${event.message}`, - timestamp: event.metadata.creationTimestamp, - }; -} diff --git a/extensions/kube-object-event-status/tsconfig.json b/extensions/kube-object-event-status/tsconfig.json deleted file mode 100644 index 5b6c61577e..0000000000 --- a/extensions/kube-object-event-status/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "outDir": "dist", - "module": "CommonJS", - "target": "ES2017", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "moduleResolution": "Node", - "sourceMap": false, - "declaration": false, - "strict": false, - "noImplicitAny": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true, - "useDefineForClassFields": true, - "jsx": "react" - }, - "include": [ - "./*.ts", - "./*.tsx" - ], - "exclude": [ - "node_modules", - "*.js" - ] -} diff --git a/extensions/kube-object-event-status/webpack.config.js b/extensions/kube-object-event-status/webpack.config.js deleted file mode 100644 index 8fe82e48a7..0000000000 --- a/extensions/kube-object-event-status/webpack.config.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -const path = require("path"); - -module.exports = [ - { - entry: "./renderer.tsx", - context: __dirname, - target: "electron-renderer", - mode: "production", - optimization: { - minimize: false, - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - externals: [ - { - "@k8slens/extensions": "var global.LensExtensions", - "react": "var global.React", - "react-dom": "var global.ReactDOM", - "mobx": "var global.Mobx", - "mobx-react": "var global.MobxReact", - }, - ], - resolve: { - extensions: [ ".tsx", ".ts", ".js" ], - }, - output: { - libraryTarget: "commonjs2", - globalObject: "this", - filename: "renderer.js", - path: path.resolve(__dirname, "dist"), - }, - }, -]; diff --git a/extensions/metrics-cluster-feature/package.json b/extensions/metrics-cluster-feature/package.json deleted file mode 100644 index 3be7b58d58..0000000000 --- a/extensions/metrics-cluster-feature/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "lens-metrics-cluster-feature", - "version": "6.1.0", - "description": "Lens metrics cluster feature", - "renderer": "dist/renderer.js", - "lens": { - "metadata": {}, - "styles": [] - }, - "scripts": { - "build": "npx webpack", - "dev": "npx webpack -- --watch", - "test": "npx jest --passWithNoTests --env=jsdom src $@", - "clean": "rm -rf dist/ && rm *.tgz" - }, - "files": [ - "dist/**/*", - "resources/**/*" - ], - "devDependencies": { - "@k8slens/extensions": "file:../../src/extensions/npm/extensions", - "semver": "^7.3.2" - } -} diff --git a/extensions/metrics-cluster-feature/renderer.tsx b/extensions/metrics-cluster-feature/renderer.tsx deleted file mode 100644 index 8b04ca0b8f..0000000000 --- a/extensions/metrics-cluster-feature/renderer.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import React from "react"; -import type { Common } from "@k8slens/extensions"; -import { Renderer } from "@k8slens/extensions"; -import { MetricsSettings } from "./src/metrics-settings"; - -export default class ClusterMetricsFeatureExtension extends Renderer.LensExtension { - entitySettings = [ - { - apiVersions: ["entity.k8slens.dev/v1alpha1"], - kind: "KubernetesCluster", - title: "Lens Metrics", - priority: 5, - components: { - View: ({ entity = null }: { entity: Common.Catalog.KubernetesCluster }) => { - return ( - - ); - }, - }, - }, - ]; -} diff --git a/extensions/metrics-cluster-feature/resources/01-namespace.yml.hb b/extensions/metrics-cluster-feature/resources/01-namespace.yml.hb deleted file mode 100644 index dd3816fdff..0000000000 --- a/extensions/metrics-cluster-feature/resources/01-namespace.yml.hb +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: lens-metrics - annotations: - extensionVersion: "{{ version }}" diff --git a/extensions/metrics-cluster-feature/resources/02-configmap.yml.hb b/extensions/metrics-cluster-feature/resources/02-configmap.yml.hb deleted file mode 100644 index 582052bd87..0000000000 --- a/extensions/metrics-cluster-feature/resources/02-configmap.yml.hb +++ /dev/null @@ -1,281 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: prometheus-config - namespace: lens-metrics -data: - prometheus.yaml: |- - # Global config - global: - scrape_interval: 15s - - {{#if alertManagers}} - # AlertManager - alerting: - alertmanagers: - - static_configs: - - targets: - {{#each alertManagers}} - - {{this}} - {{/each}} - {{/if}} - - # Scrape configs for running Prometheus on a Kubernetes cluster. - # This uses separate scrape configs for cluster components (i.e. API server, node) - # and services to allow each to use different authentication configs. - # - # Kubernetes labels will be added as Prometheus labels on metrics via the - # `labelmap` relabeling action. - scrape_configs: - - # Scrape config for API servers. - # - # Kubernetes exposes API servers as endpoints to the default/kubernetes - # service so this uses `endpoints` role and uses relabelling to only keep - # the endpoints associated with the default/kubernetes service using the - # default named port `https`. This works for single API server deployments as - # well as HA API server deployments. - - job_name: 'kubernetes-apiservers' - kubernetes_sd_configs: - - role: endpoints - - scheme: https - tls_config: - ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt - # Using endpoints to discover kube-apiserver targets finds the pod IP - # (host IP since apiserver uses host network) which is not used in - # the server certificate. - insecure_skip_verify: true - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token - - # Keep only the default/kubernetes service endpoints for the https port. This - # will add targets for each API server which Kubernetes adds an endpoint to - # the default/kubernetes service. - relabel_configs: - - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] - action: keep - regex: default;kubernetes;https - - replacement: apiserver - action: replace - target_label: job - - # Scrape config for node (i.e. kubelet) /metrics (e.g. 'kubelet_'). Explore - # metrics from a node by scraping kubelet (127.0.0.1:10250/metrics). - - job_name: 'kubelet' - kubernetes_sd_configs: - - role: node - - scheme: https - tls_config: - ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt - # Kubelet certs don't have any fixed IP SANs - insecure_skip_verify: true - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token - - relabel_configs: - - action: labelmap - regex: __meta_kubernetes_node_label_(.+) - - replacement: 'lens-metrics' - target_label: kubernetes_namespace - - metric_relabel_configs: - - source_labels: - - namespace - action: replace - regex: (.+) - target_label: kubernetes_namespace - - # Scrape config for Kubelet cAdvisor. Explore metrics from a node by - # scraping kubelet (127.0.0.1:10250/metrics/cadvisor). - - job_name: 'kubernetes-cadvisor' - kubernetes_sd_configs: - - role: node - - scheme: https - metrics_path: /metrics/cadvisor - tls_config: - # Kubelet certs don't have any fixed IP SANs - insecure_skip_verify: true - ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token - - relabel_configs: - - action: labelmap - regex: __meta_kubernetes_node_label_(.+) - metric_relabel_configs: - - source_labels: - - namespace - action: replace - target_label: kubernetes_namespace - - source_labels: - - pod - regex: (.*) - replacement: $1 - action: replace - target_label: pod_name - - source_labels: - - container - regex: (.*) - replacement: $1 - action: replace - target_label: container_name - - # Scrap etcd metrics from masters via etcd-scraper-proxy - - job_name: 'etcd' - kubernetes_sd_configs: - - role: pod - scheme: http - relabel_configs: - - source_labels: [__meta_kubernetes_namespace] - action: keep - regex: 'kube-system' - - source_labels: [__meta_kubernetes_pod_label_component] - action: keep - regex: 'etcd-scraper-proxy' - - action: labelmap - regex: __meta_kubernetes_pod_label_(.+) - - # Scrape config for service endpoints. - # - # The relabeling allows the actual service scrape endpoint to be configured - # via the following annotations: - # - # * `prometheus.io/scrape`: Only scrape services that have a value of `true` - # * `prometheus.io/scheme`: If the metrics endpoint is secured then you will need - # to set this to `https` & most likely set the `tls_config` of the scrape config. - # * `prometheus.io/path`: If the metrics path is not `/metrics` override this. - # * `prometheus.io/port`: If the metrics are exposed on a different port to the - # service then set this appropriately. - - job_name: 'kubernetes-service-endpoints' - - kubernetes_sd_configs: - - role: endpoints - namespaces: - names: - - lens-metrics - - relabel_configs: - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] - action: keep - regex: true - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] - action: replace - target_label: __scheme__ - regex: (https?) - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] - action: replace - target_label: __metrics_path__ - regex: (.+) - - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] - action: replace - target_label: __address__ - regex: ([^:]+)(?::\d+)?;(\d+) - replacement: $1:$2 - - action: labelmap - regex: __meta_kubernetes_service_label_(.+) - - source_labels: [__meta_kubernetes_service_name] - action: replace - target_label: job - - action: replace - source_labels: - - __meta_kubernetes_pod_node_name - target_label: kubernetes_node - - source_labels: [__meta_kubernetes_namespace] - action: replace - target_label: kubernetes_namespace - metric_relabel_configs: - - source_labels: - - namespace - action: replace - regex: (.+) - target_label: kubernetes_namespace - - # Example scrape config for probing services via the Blackbox Exporter. - # - # The relabeling allows the actual service scrape endpoint to be configured - # via the following annotations: - # - # * `prometheus.io/probe`: Only probe services that have a value of `true` - - job_name: 'kubernetes-services' - - metrics_path: /probe - params: - module: [http_2xx] - - kubernetes_sd_configs: - - role: service - namespaces: - names: - - lens-metrics - - relabel_configs: - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe] - action: keep - regex: true - - source_labels: [__address__] - target_label: __param_target - - target_label: __address__ - replacement: blackbox - - source_labels: [__param_target] - target_label: instance - - action: labelmap - regex: __meta_kubernetes_service_label_(.+) - - source_labels: [__meta_kubernetes_service_name] - target_label: job - metric_relabel_configs: - - source_labels: - - namespace - action: replace - regex: (.+) - target_label: kubernetes_namespace - - # Example scrape config for pods - # - # The relabeling allows the actual pod scrape endpoint to be configured via the - # following annotations: - # - # * `prometheus.io/scrape`: Only scrape pods that have a value of `true` - # * `prometheus.io/path`: If the metrics path is not `/metrics` override this. - # * `prometheus.io/port`: Scrape the pod on the indicated port instead of the - # pod's declared ports (default is a port-free target if none are declared). - - job_name: 'kubernetes-pods' - - kubernetes_sd_configs: - - role: pod - namespaces: - names: - - lens-metrics - - relabel_configs: - - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] - action: keep - regex: true - - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] - action: replace - target_label: __metrics_path__ - regex: (.+) - - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] - action: replace - regex: ([^:]+)(?::\d+)?;(\d+) - replacement: $1:$2 - target_label: __address__ - - action: labelmap - regex: __meta_kubernetes_pod_label_(.+) - - source_labels: [__meta_kubernetes_namespace] - action: replace - target_label: kubernetes_namespace - - source_labels: [__meta_kubernetes_pod_name] - action: replace - target_label: kubernetes_pod_name - metric_relabel_configs: - - source_labels: - - namespace - action: replace - regex: (.+) - target_label: kubernetes_namespace - - # Rule files - rule_files: - - "/etc/prometheus/rules/*.rules" - - "/etc/prometheus/rules/*.yaml" - - "/etc/prometheus/rules/*.yml" diff --git a/extensions/metrics-cluster-feature/resources/02-service-account.yml b/extensions/metrics-cluster-feature/resources/02-service-account.yml deleted file mode 100644 index 5cb45a352f..0000000000 --- a/extensions/metrics-cluster-feature/resources/02-service-account.yml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: prometheus - namespace: lens-metrics diff --git a/extensions/metrics-cluster-feature/resources/03-service.yml.hb b/extensions/metrics-cluster-feature/resources/03-service.yml.hb deleted file mode 100644 index 3cdcdbc260..0000000000 --- a/extensions/metrics-cluster-feature/resources/03-service.yml.hb +++ /dev/null @@ -1,18 +0,0 @@ -{{#if prometheus.enabled}} -apiVersion: v1 -kind: Service -metadata: - name: prometheus - namespace: lens-metrics - annotations: - prometheus.io/scrape: 'true' -spec: - type: ClusterIP - selector: - name: prometheus - ports: - - name: web - protocol: TCP - port: 80 - targetPort: 9090 -{{/if}} diff --git a/extensions/metrics-cluster-feature/resources/03-statefulset.yml.hb b/extensions/metrics-cluster-feature/resources/03-statefulset.yml.hb deleted file mode 100644 index 288cd553b1..0000000000 --- a/extensions/metrics-cluster-feature/resources/03-statefulset.yml.hb +++ /dev/null @@ -1,113 +0,0 @@ -{{#if prometheus.enabled}} -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: prometheus - namespace: lens-metrics -spec: - replicas: {{replicas}} - serviceName: prometheus - selector: - matchLabels: - name: prometheus - template: - metadata: - labels: - name: prometheus - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/os - operator: In - values: - - linux - # <%- if config.node_selector -%> - # nodeSelector: - # <%- node_selector.to_h.each do |key, value| -%> - # <%= key %>: <%= value %> - # <%- end -%> - # <%- end -%> - # <%- unless config.tolerations.empty? -%> - # tolerations: - # <%- config.tolerations.each do |t| -%> - # - - # <%- t.each do |k,v| -%> - # <%= k %>: <%= v %> - # <%- end -%> - # <%- end -%> - # <%- end -%> - serviceAccountName: prometheus - initContainers: - - name: chown - image: docker.io/alpine:3.12 - command: ["chown", "-R", "65534:65534", "/var/lib/prometheus"] - volumeMounts: - - name: data - mountPath: /var/lib/prometheus - containers: - - name: prometheus - image: quay.io/prometheus/prometheus:v2.27.1 - args: - - --web.listen-address=0.0.0.0:9090 - - --config.file=/etc/prometheus/prometheus.yaml - - --storage.tsdb.path=/var/lib/prometheus - - --storage.tsdb.retention.time={{retention.time}} - - --storage.tsdb.retention.size={{retention.size}} - - --storage.tsdb.min-block-duration=2h - - --storage.tsdb.max-block-duration=2h - ports: - - name: web - containerPort: 9090 - volumeMounts: - - name: config - mountPath: /etc/prometheus - - name: rules - mountPath: /etc/prometheus/rules - - name: data - mountPath: /var/lib/prometheus - readinessProbe: - httpGet: - path: /-/ready - port: 9090 - initialDelaySeconds: 10 - timeoutSeconds: 10 - livenessProbe: - httpGet: - path: /-/healthy - port: 9090 - initialDelaySeconds: 10 - timeoutSeconds: 10 - resources: - requests: - cpu: 100m - memory: 512Mi - terminationGracePeriodSeconds: 30 - volumes: - - name: config - configMap: - name: prometheus-config - - name: rules - configMap: - name: prometheus-rules - {{#unless persistence.enabled}} - - name: data - emptyDir: {} - {{/unless}} - {{#if persistence.enabled}} - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: - - ReadWriteOnce - {{#if persistence.storageClass}} - storageClassName: "{{persistence.storageClass}}" - {{/if}} - resources: - requests: - storage: {{persistence.size}} - {{/if}} -{{/if}} diff --git a/extensions/metrics-cluster-feature/resources/04-rules.yml b/extensions/metrics-cluster-feature/resources/04-rules.yml deleted file mode 100644 index b7f088a003..0000000000 --- a/extensions/metrics-cluster-feature/resources/04-rules.yml +++ /dev/null @@ -1,514 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: prometheus-rules - namespace: lens-metrics -data: - alertmanager.rules.yaml: | - groups: - - name: alertmanager.rules - rules: - - alert: AlertmanagerConfigInconsistent - expr: count_values("config_hash", alertmanager_config_hash) BY (service) / ON(service) - GROUP_LEFT() label_replace(prometheus_operator_alertmanager_spec_replicas, "service", - "alertmanager-$1", "alertmanager", "(.*)") != 1 - for: 5m - labels: - severity: critical - annotations: - description: The configuration of the instances of the Alertmanager cluster - `{{$labels.service}}` are out of sync. - - alert: AlertmanagerDownOrMissing - expr: label_replace(prometheus_operator_alertmanager_spec_replicas, "job", "alertmanager-$1", - "alertmanager", "(.*)") / ON(job) GROUP_RIGHT() sum(up) BY (job) != 1 - for: 5m - labels: - severity: warning - annotations: - description: An unexpected number of Alertmanagers are scraped or Alertmanagers - disappeared from discovery. - - alert: AlertmanagerFailedReload - expr: alertmanager_config_last_reload_successful == 0 - for: 10m - labels: - severity: warning - annotations: - description: Reloading Alertmanager's configuration has failed for {{ $labels.namespace - }}/{{ $labels.pod}}. - etcd3.rules.yaml: | - groups: - - name: ./etcd3.rules - rules: - - alert: InsufficientMembers - expr: count(up{job="etcd"} == 0) > (count(up{job="etcd"}) / 2 - 1) - for: 3m - labels: - severity: critical - annotations: - description: If one more etcd member goes down the cluster will be unavailable - summary: etcd cluster insufficient members - - alert: NoLeader - expr: etcd_server_has_leader{job="etcd"} == 0 - for: 1m - labels: - severity: critical - annotations: - description: etcd member {{ $labels.instance }} has no leader - summary: etcd member has no leader - - alert: HighNumberOfLeaderChanges - expr: increase(etcd_server_leader_changes_seen_total{job="etcd"}[1h]) > 3 - labels: - severity: warning - annotations: - description: etcd instance {{ $labels.instance }} has seen {{ $value }} leader - changes within the last hour - summary: a high number of leader changes within the etcd cluster are happening - - alert: GRPCRequestsSlow - expr: histogram_quantile(0.99, sum(rate(grpc_server_handling_seconds_bucket{job="etcd",grpc_type="unary"}[5m])) by (grpc_service, grpc_method, le)) - > 0.15 - for: 10m - labels: - severity: critical - annotations: - description: on etcd instance {{ $labels.instance }} gRPC requests to {{ $labels.grpc_method - }} are slow - summary: slow gRPC requests - - alert: HighNumberOfFailedHTTPRequests - expr: sum(rate(etcd_http_failed_total{job="etcd"}[5m])) BY (method) / sum(rate(etcd_http_received_total{job="etcd"}[5m])) - BY (method) > 0.01 - for: 10m - labels: - severity: warning - annotations: - description: '{{ $value }}% of requests for {{ $labels.method }} failed on etcd - instance {{ $labels.instance }}' - summary: a high number of HTTP requests are failing - - alert: HighNumberOfFailedHTTPRequests - expr: sum(rate(etcd_http_failed_total{job="etcd"}[5m])) BY (method) / sum(rate(etcd_http_received_total{job="etcd"}[5m])) - BY (method) > 0.05 - for: 5m - labels: - severity: critical - annotations: - description: '{{ $value }}% of requests for {{ $labels.method }} failed on etcd - instance {{ $labels.instance }}' - summary: a high number of HTTP requests are failing - - alert: HTTPRequestsSlow - expr: histogram_quantile(0.99, rate(etcd_http_successful_duration_seconds_bucket[5m])) - > 0.15 - for: 10m - labels: - severity: warning - annotations: - description: on etcd instance {{ $labels.instance }} HTTP requests to {{ $labels.method - }} are slow - summary: slow HTTP requests - - alert: EtcdMemberCommunicationSlow - expr: histogram_quantile(0.99, rate(etcd_network_peer_round_trip_time_seconds_bucket[5m])) - > 0.15 - for: 10m - labels: - severity: warning - annotations: - description: etcd instance {{ $labels.instance }} member communication with - {{ $labels.To }} is slow - summary: etcd member communication is slow - - alert: HighNumberOfFailedProposals - expr: increase(etcd_server_proposals_failed_total{job="etcd"}[1h]) > 5 - labels: - severity: warning - annotations: - description: etcd instance {{ $labels.instance }} has seen {{ $value }} proposal - failures within the last hour - summary: a high number of proposals within the etcd cluster are failing - - alert: HighFsyncDurations - expr: histogram_quantile(0.99, rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) - > 0.5 - for: 10m - labels: - severity: warning - annotations: - description: etcd instance {{ $labels.instance }} fync durations are high - summary: high fsync durations - - alert: HighCommitDurations - expr: histogram_quantile(0.99, rate(etcd_disk_backend_commit_duration_seconds_bucket[5m])) - > 0.25 - for: 10m - labels: - severity: warning - annotations: - description: etcd instance {{ $labels.instance }} commit durations are high - summary: high commit durations - general.rules.yaml: | - groups: - - name: general.rules - rules: - - alert: TargetDown - expr: 100 * (count(up == 0) BY (job) / count(up) BY (job)) > 10 - for: 10m - labels: - severity: warning - annotations: - description: '{{ $value }}% of {{ $labels.job }} targets are down.' - summary: Targets are down - - record: fd_utilization - expr: process_open_fds / process_max_fds - - alert: FdExhaustionClose - expr: predict_linear(fd_utilization[1h], 3600 * 4) > 1 - for: 10m - labels: - severity: warning - annotations: - description: '{{ $labels.job }}: {{ $labels.namespace }}/{{ $labels.pod }} instance - will exhaust in file/socket descriptors within the next 4 hours' - summary: file descriptors soon exhausted - - alert: FdExhaustionClose - expr: predict_linear(fd_utilization[10m], 3600) > 1 - for: 10m - labels: - severity: critical - annotations: - description: '{{ $labels.job }}: {{ $labels.namespace }}/{{ $labels.pod }} instance - will exhaust in file/socket descriptors within the next hour' - summary: file descriptors soon exhausted - kube-state-metrics.rules.yaml: | - groups: - - name: kube-state-metrics.rules - rules: - - alert: DeploymentGenerationMismatch - expr: kube_deployment_status_observed_generation != kube_deployment_metadata_generation - for: 15m - labels: - severity: warning - annotations: - description: Observed deployment generation does not match expected one for - deployment {{$labels.namespaces}}/{{$labels.deployment}} - summary: Deployment is outdated - - alert: DeploymentReplicasNotUpdated - expr: ((kube_deployment_status_replicas_updated != kube_deployment_spec_replicas) - or (kube_deployment_status_replicas_available != kube_deployment_spec_replicas)) - unless (kube_deployment_spec_paused == 1) - for: 15m - labels: - severity: warning - annotations: - description: Replicas are not updated and available for deployment {{$labels.namespaces}}/{{$labels.deployment}} - summary: Deployment replicas are outdated - - alert: DaemonSetRolloutStuck - expr: kube_daemonset_status_number_ready / kube_daemonset_status_desired_number_scheduled - * 100 < 100 - for: 15m - labels: - severity: warning - annotations: - description: Only {{$value}}% of desired pods scheduled and ready for daemon - set {{$labels.namespaces}}/{{$labels.daemonset}} - summary: DaemonSet is missing pods - - alert: K8SDaemonSetsNotScheduled - expr: kube_daemonset_status_desired_number_scheduled - kube_daemonset_status_current_number_scheduled - > 0 - for: 10m - labels: - severity: warning - annotations: - description: A number of daemonsets are not scheduled. - summary: Daemonsets are not scheduled correctly - - alert: DaemonSetsMissScheduled - expr: kube_daemonset_status_number_misscheduled > 0 - for: 10m - labels: - severity: warning - annotations: - description: A number of daemonsets are running where they are not supposed - to run. - summary: Daemonsets are not scheduled correctly - - alert: PodFrequentlyRestarting - expr: increase(kube_pod_container_status_restarts_total[1h]) > 5 - for: 10m - labels: - severity: warning - annotations: - description: Pod {{$labels.namespaces}}/{{$labels.pod}} restarted {{$value}} - times within the last hour - summary: Pod is restarting frequently - kubelet.rules.yaml: | - groups: - - name: kubelet.rules - rules: - - alert: K8SNodeNotReady - expr: kube_node_status_condition{condition="Ready",status="true"} == 0 - for: 1h - labels: - severity: warning - annotations: - description: The Kubelet on {{ $labels.node }} has not checked in with the API, - or has set itself to NotReady, for more than an hour - summary: Node status is NotReady - - alert: K8SManyNodesNotReady - expr: count(kube_node_status_condition{condition="Ready",status="true"} == 0) - > 1 and (count(kube_node_status_condition{condition="Ready",status="true"} == - 0) / count(kube_node_status_condition{condition="Ready",status="true"})) > 0.2 - for: 1m - labels: - severity: critical - annotations: - description: '{{ $value }}% of Kubernetes nodes are not ready' - - alert: K8SKubeletDown - expr: count(up{job="kubelet"} == 0) / count(up{job="kubelet"}) * 100 > 3 - for: 1h - labels: - severity: warning - annotations: - description: Prometheus failed to scrape {{ $value }}% of kubelets. - - alert: K8SKubeletDown - expr: (absent(up{job="kubelet"} == 1) or count(up{job="kubelet"} == 0) / count(up{job="kubelet"})) - * 100 > 10 - for: 1h - labels: - severity: critical - annotations: - description: Prometheus failed to scrape {{ $value }}% of kubelets, or all Kubelets - have disappeared from service discovery. - summary: Many Kubelets cannot be scraped - - alert: K8SKubeletTooManyPods - expr: kubelet_running_pod_count > 100 - for: 10m - labels: - severity: warning - annotations: - description: Kubelet {{$labels.instance}} is running {{$value}} pods, close - to the limit of 110 - summary: Kubelet is close to pod limit - kubernetes.rules.yaml: | - groups: - - name: kubernetes.rules - rules: - - record: pod_name:container_memory_usage_bytes:sum - expr: sum(container_memory_usage_bytes{container_name!="POD",pod_name!=""}) BY - (pod_name) - - record: pod_name:container_spec_cpu_shares:sum - expr: sum(container_spec_cpu_shares{container_name!="POD",pod_name!=""}) BY (pod_name) - - record: pod_name:container_cpu_usage:sum - expr: sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name!=""}[5m])) - BY (pod_name) - - record: pod_name:container_fs_usage_bytes:sum - expr: sum(container_fs_usage_bytes{container_name!="POD",pod_name!=""}) BY (pod_name) - - record: namespace:container_memory_usage_bytes:sum - expr: sum(container_memory_usage_bytes{container_name!=""}) BY (namespace) - - record: namespace:container_spec_cpu_shares:sum - expr: sum(container_spec_cpu_shares{container_name!=""}) BY (namespace) - - record: namespace:container_cpu_usage:sum - expr: sum(rate(container_cpu_usage_seconds_total{container_name!="POD"}[5m])) - BY (namespace) - - record: cluster:memory_usage:ratio - expr: sum(container_memory_usage_bytes{container_name!="POD",pod_name!=""}) BY - (cluster) / sum(machine_memory_bytes) BY (cluster) - - record: cluster:container_spec_cpu_shares:ratio - expr: sum(container_spec_cpu_shares{container_name!="POD",pod_name!=""}) / 1000 - / sum(machine_cpu_cores) - - record: cluster:container_cpu_usage:ratio - expr: sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name!=""}[5m])) - / sum(machine_cpu_cores) - - record: apiserver_latency_seconds:quantile - expr: histogram_quantile(0.99, rate(apiserver_request_latencies_bucket[5m])) / - 1e+06 - labels: - quantile: "0.99" - - record: apiserver_latency:quantile_seconds - expr: histogram_quantile(0.9, rate(apiserver_request_latencies_bucket[5m])) / - 1e+06 - labels: - quantile: "0.9" - - record: apiserver_latency_seconds:quantile - expr: histogram_quantile(0.5, rate(apiserver_request_latencies_bucket[5m])) / - 1e+06 - labels: - quantile: "0.5" - - alert: APIServerLatencyHigh - expr: apiserver_latency_seconds:quantile{quantile="0.99",subresource!="log",verb!~"^(?:WATCH|WATCHLIST|PROXY|CONNECT)$"} - > 1 - for: 10m - labels: - severity: warning - annotations: - description: the API server has a 99th percentile latency of {{ $value }} seconds - for {{$labels.verb}} {{$labels.resource}} - - alert: APIServerLatencyHigh - expr: apiserver_latency_seconds:quantile{quantile="0.99",subresource!="log",verb!~"^(?:WATCH|WATCHLIST|PROXY|CONNECT)$"} - > 4 - for: 10m - labels: - severity: critical - annotations: - description: the API server has a 99th percentile latency of {{ $value }} seconds - for {{$labels.verb}} {{$labels.resource}} - - alert: APIServerErrorsHigh - expr: rate(apiserver_request_count{code=~"^(?:5..)$"}[5m]) / rate(apiserver_request_count[5m]) - * 100 > 2 - for: 10m - labels: - severity: warning - annotations: - description: API server returns errors for {{ $value }}% of requests - - alert: APIServerErrorsHigh - expr: rate(apiserver_request_count{code=~"^(?:5..)$"}[5m]) / rate(apiserver_request_count[5m]) - * 100 > 5 - for: 10m - labels: - severity: critical - annotations: - description: API server returns errors for {{ $value }}% of requests - - alert: K8SApiserverDown - expr: absent(up{job="apiserver"} == 1) - for: 20m - labels: - severity: critical - annotations: - description: No API servers are reachable or all have disappeared from service - discovery - - - alert: K8sCertificateExpirationNotice - labels: - severity: warning - annotations: - description: Kubernetes API Certificate is expiring soon (less than 7 days) - expr: sum(apiserver_client_certificate_expiration_seconds_bucket{le="604800"}) > 0 - - - alert: K8sCertificateExpirationNotice - labels: - severity: critical - annotations: - description: Kubernetes API Certificate is expiring in less than 1 day - expr: sum(apiserver_client_certificate_expiration_seconds_bucket{le="86400"}) > 0 - node.rules.yaml: | - groups: - - name: node.rules - rules: - - record: instance:node_cpu:rate:sum - expr: sum(rate(node_cpu{mode!="idle",mode!="iowait",mode!~"^(?:guest.*)$"}[3m])) - BY (instance) - - record: instance:node_filesystem_usage:sum - expr: sum((node_filesystem_size{mountpoint="/"} - node_filesystem_free{mountpoint="/"})) - BY (instance) - - record: instance:node_network_receive_bytes:rate:sum - expr: sum(rate(node_network_receive_bytes[3m])) BY (instance) - - record: instance:node_network_transmit_bytes:rate:sum - expr: sum(rate(node_network_transmit_bytes[3m])) BY (instance) - - record: instance:node_cpu:ratio - expr: sum(rate(node_cpu{mode!="idle"}[5m])) WITHOUT (cpu, mode) / ON(instance) - GROUP_LEFT() count(sum(node_cpu) BY (instance, cpu)) BY (instance) - - record: cluster:node_cpu:sum_rate5m - expr: sum(rate(node_cpu{mode!="idle"}[5m])) - - record: cluster:node_cpu:ratio - expr: cluster:node_cpu:rate5m / count(sum(node_cpu) BY (instance, cpu)) - - alert: NodeExporterDown - expr: absent(up{job="node-exporter"} == 1) - for: 10m - labels: - severity: warning - annotations: - description: Prometheus could not scrape a node-exporter for more than 10m, - or node-exporters have disappeared from discovery - - alert: NodeDiskRunningFull - expr: predict_linear(node_filesystem_free[6h], 3600 * 24) < 0 - for: 30m - labels: - severity: warning - annotations: - description: device {{$labels.device}} on node {{$labels.instance}} is running - full within the next 24 hours (mounted at {{$labels.mountpoint}}) - - alert: NodeDiskRunningFull - expr: predict_linear(node_filesystem_free[30m], 3600 * 2) < 0 - for: 10m - labels: - severity: critical - annotations: - description: device {{$labels.device}} on node {{$labels.instance}} is running - full within the next 2 hours (mounted at {{$labels.mountpoint}}) - - alert: InactiveRAIDDisk - expr: node_md_disks - node_md_disks_active > 0 - for: 10m - labels: - severity: warning - annotations: - description: '{{$value}} RAID disk(s) on node {{$labels.instance}} are inactive' - prometheus.rules.yaml: | - groups: - - name: prometheus.rules - rules: - - alert: PrometheusConfigReloadFailed - expr: prometheus_config_last_reload_successful == 0 - for: 10m - labels: - severity: warning - annotations: - description: Reloading Prometheus' configuration has failed for {{$labels.namespace}}/{{$labels.pod}} - - alert: PrometheusNotificationQueueRunningFull - expr: predict_linear(prometheus_notifications_queue_length[5m], 60 * 30) > prometheus_notifications_queue_capacity - for: 10m - labels: - severity: warning - annotations: - description: Prometheus' alert notification queue is running full for {{$labels.namespace}}/{{ - $labels.pod}} - - alert: PrometheusErrorSendingAlerts - expr: rate(prometheus_notifications_errors_total[5m]) / rate(prometheus_notifications_sent_total[5m]) - > 0.01 - for: 10m - labels: - severity: warning - annotations: - description: Errors while sending alerts from Prometheus {{$labels.namespace}}/{{ - $labels.pod}} to Alertmanager {{$labels.Alertmanager}} - - alert: PrometheusErrorSendingAlerts - expr: rate(prometheus_notifications_errors_total[5m]) / rate(prometheus_notifications_sent_total[5m]) - > 0.03 - for: 10m - labels: - severity: critical - annotations: - description: Errors while sending alerts from Prometheus {{$labels.namespace}}/{{ - $labels.pod}} to Alertmanager {{$labels.Alertmanager}} - - alert: PrometheusNotConnectedToAlertmanagers - expr: prometheus_notifications_alertmanagers_discovered < 1 - for: 10m - labels: - severity: warning - annotations: - description: Prometheus {{ $labels.namespace }}/{{ $labels.pod}} is not connected - to any Alertmanagers - - alert: PrometheusTSDBReloadsFailing - expr: increase(prometheus_tsdb_reloads_failures_total[2h]) > 0 - for: 12h - labels: - severity: warning - annotations: - description: '{{$labels.job}} at {{$labels.instance}} had {{$value | humanize}} - reload failures over the last four hours.' - summary: Prometheus has issues reloading data blocks from disk - - alert: PrometheusTSDBCompactionsFailing - expr: increase(prometheus_tsdb_compactions_failed_total[2h]) > 0 - for: 12h - labels: - severity: warning - annotations: - description: '{{$labels.job}} at {{$labels.instance}} had {{$value | humanize}} - compaction failures over the last four hours.' - summary: Prometheus has issues compacting sample blocks - - alert: PrometheusTSDBWALCorruptions - expr: tsdb_wal_corruptions_total > 0 - for: 4h - labels: - severity: warning - annotations: - description: '{{$labels.job}} at {{$labels.instance}} has a corrupted write-ahead - log (WAL).' - summary: Prometheus write-ahead log is corrupted - - alert: PrometheusNotIngestingSamples - expr: rate(prometheus_tsdb_head_samples_appended_total[5m]) <= 0 - for: 10m - labels: - severity: warning - annotations: - description: "Prometheus {{ $labels.namespace }}/{{ $labels.pod}} isn't ingesting samples." - summary: "Prometheus isn't ingesting samples" diff --git a/extensions/metrics-cluster-feature/resources/05-clusterrole.yml b/extensions/metrics-cluster-feature/resources/05-clusterrole.yml deleted file mode 100644 index 9b23edc84b..0000000000 --- a/extensions/metrics-cluster-feature/resources/05-clusterrole.yml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: lens-prometheus -rules: -- apiGroups: [""] - resources: - - nodes - - nodes/proxy - - nodes/metrics - - services - - endpoints - - pods - - ingresses - - configmaps - verbs: ["get", "list", "watch"] -- nonResourceURLs: ["/metrics"] - verbs: ["get"] diff --git a/extensions/metrics-cluster-feature/resources/06-clusterrole-binding.yml b/extensions/metrics-cluster-feature/resources/06-clusterrole-binding.yml deleted file mode 100644 index 37fc605f05..0000000000 --- a/extensions/metrics-cluster-feature/resources/06-clusterrole-binding.yml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: lens-prometheus -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: lens-prometheus -subjects: -- kind: ServiceAccount - name: prometheus - namespace: lens-metrics diff --git a/extensions/metrics-cluster-feature/resources/10-node-exporter-ds.yml.hb b/extensions/metrics-cluster-feature/resources/10-node-exporter-ds.yml.hb deleted file mode 100644 index c02fb93321..0000000000 --- a/extensions/metrics-cluster-feature/resources/10-node-exporter-ds.yml.hb +++ /dev/null @@ -1,79 +0,0 @@ -{{#if nodeExporter.enabled}} -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: node-exporter - namespace: lens-metrics -spec: - updateStrategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 1 - selector: - matchLabels: - name: node-exporter - phase: prod - template: - metadata: - labels: - name: node-exporter - phase: prod - annotations: - seccomp.security.alpha.kubernetes.io/pod: 'docker/default' - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/os - operator: In - values: - - linux - securityContext: - runAsNonRoot: true - runAsUser: 65534 - hostPID: true - containers: - - name: node-exporter - image: quay.io/prometheus/node-exporter:v1.1.2 - args: - - --path.procfs=/host/proc - - --path.sysfs=/host/sys - - --path.rootfs=/host/root - - --collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/docker|var/lib/containerd|var/lib/containers/.+)($|/) - - --collector.filesystem.ignored-fs-types=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$ - ports: - - name: metrics - containerPort: 9100 - resources: - requests: - cpu: 10m - memory: 24Mi - limits: - cpu: 200m - memory: 100Mi - volumeMounts: - - name: proc - mountPath: /host/proc - readOnly: true - - name: sys - mountPath: /host/sys - readOnly: true - - name: root - mountPath: /host/root - readOnly: true - tolerations: - - effect: NoSchedule - operator: Exists - volumes: - - name: proc - hostPath: - path: /proc - - name: sys - hostPath: - path: /sys - - name: root - hostPath: - path: / -{{/if}} diff --git a/extensions/metrics-cluster-feature/resources/11-node-exporter-svc.yml b/extensions/metrics-cluster-feature/resources/11-node-exporter-svc.yml deleted file mode 100644 index f85e878e06..0000000000 --- a/extensions/metrics-cluster-feature/resources/11-node-exporter-svc.yml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: node-exporter - namespace: lens-metrics - annotations: - prometheus.io/scrape: 'true' -spec: - type: ClusterIP - clusterIP: None - selector: - name: node-exporter - phase: prod - ports: - - name: metrics - protocol: TCP - port: 80 - targetPort: 9100 diff --git a/extensions/metrics-cluster-feature/resources/12-kube-state-metrics-clusterrole.yml b/extensions/metrics-cluster-feature/resources/12-kube-state-metrics-clusterrole.yml deleted file mode 100644 index 8101bcc05d..0000000000 --- a/extensions/metrics-cluster-feature/resources/12-kube-state-metrics-clusterrole.yml +++ /dev/null @@ -1,128 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: lens-kube-state-metrics -rules: - - apiGroups: - - "" - resources: - - configmaps - - secrets - - nodes - - pods - - services - - resourcequotas - - replicationcontrollers - - limitranges - - persistentvolumeclaims - - persistentvolumes - - namespaces - - endpoints - verbs: - - list - - watch - - apiGroups: - - extensions - resources: - - daemonsets - - deployments - - replicasets - - ingresses - verbs: - - list - - watch - - apiGroups: - - networking.k8s.io - resources: - - ingresses - verbs: - - list - - watch - - apiGroups: - - apps - resources: - - statefulsets - - daemonsets - - deployments - - replicasets - verbs: - - list - - watch - - apiGroups: - - batch - resources: - - cronjobs - - jobs - verbs: - - list - - watch - - apiGroups: - - autoscaling - resources: - - horizontalpodautoscalers - verbs: - - list - - watch - - apiGroups: - - authentication.k8s.io - resources: - - tokenreviews - verbs: - - create - - apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create - - apiGroups: - - policy - resources: - - poddisruptionbudgets - verbs: - - list - - watch - - apiGroups: - - certificates.k8s.io - resources: - - certificatesigningrequests - verbs: - - list - - watch - - apiGroups: - - storage.k8s.io - resources: - - storageclasses - - volumeattachments - verbs: - - list - - watch - - apiGroups: - - admissionregistration.k8s.io - resources: - - mutatingwebhookconfigurations - - validatingwebhookconfigurations - verbs: - - list - - watch - - apiGroups: - - networking.k8s.io - resources: - - networkpolicies - verbs: - - list - - watch - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - list - - watch - - apiGroups: - - scheduling.k8s.io - resources: - - priorityclasses - verbs: - - list - - watch diff --git a/extensions/metrics-cluster-feature/resources/12.kube-state-metrics-sa.yml b/extensions/metrics-cluster-feature/resources/12.kube-state-metrics-sa.yml deleted file mode 100644 index 2b3c22f8da..0000000000 --- a/extensions/metrics-cluster-feature/resources/12.kube-state-metrics-sa.yml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: kube-state-metrics - namespace: lens-metrics diff --git a/extensions/metrics-cluster-feature/resources/13-kube-state-metrics-clusterrole-binding.yml b/extensions/metrics-cluster-feature/resources/13-kube-state-metrics-clusterrole-binding.yml deleted file mode 100644 index e60dedae19..0000000000 --- a/extensions/metrics-cluster-feature/resources/13-kube-state-metrics-clusterrole-binding.yml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: lens-kube-state-metrics -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: lens-kube-state-metrics -subjects: -- kind: ServiceAccount - name: kube-state-metrics - namespace: lens-metrics diff --git a/extensions/metrics-cluster-feature/resources/14-kube-state-metrics-deployment.yml.hb b/extensions/metrics-cluster-feature/resources/14-kube-state-metrics-deployment.yml.hb deleted file mode 100644 index 0174d5c8f4..0000000000 --- a/extensions/metrics-cluster-feature/resources/14-kube-state-metrics-deployment.yml.hb +++ /dev/null @@ -1,46 +0,0 @@ -{{#if kubeStateMetrics.enabled}} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: kube-state-metrics - namespace: lens-metrics -spec: - selector: - matchLabels: - name: kube-state-metrics - replicas: 1 - template: - metadata: - labels: - name: kube-state-metrics - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/os - operator: In - values: - - linux - serviceAccountName: kube-state-metrics - containers: - - name: kube-state-metrics - image: k8s.gcr.io/kube-state-metrics/kube-state-metrics:v2.0.0 - ports: - - name: metrics - containerPort: 8080 - readinessProbe: - httpGet: - path: /healthz - port: 8080 - initialDelaySeconds: 5 - timeoutSeconds: 5 - resources: - requests: - cpu: 10m - memory: 32Mi - limits: - cpu: 200m - memory: 150Mi -{{/if}} diff --git a/extensions/metrics-cluster-feature/resources/14-kube-state-metrics-svc.yml b/extensions/metrics-cluster-feature/resources/14-kube-state-metrics-svc.yml deleted file mode 100644 index be6168ab7a..0000000000 --- a/extensions/metrics-cluster-feature/resources/14-kube-state-metrics-svc.yml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: kube-state-metrics - namespace: lens-metrics - labels: - name: kube-state-metrics - annotations: - prometheus.io/scrape: 'true' -spec: - ports: - - name: metrics - port: 8080 - targetPort: 8080 - protocol: TCP - selector: - name: kube-state-metrics diff --git a/extensions/metrics-cluster-feature/src/metrics-feature.ts b/extensions/metrics-cluster-feature/src/metrics-feature.ts deleted file mode 100644 index c9d4275abb..0000000000 --- a/extensions/metrics-cluster-feature/src/metrics-feature.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { Common } from "@k8slens/extensions"; -import { Renderer } from "@k8slens/extensions"; -import semver from "semver"; -import * as path from "path"; - -const { ResourceStack, forCluster, StorageClass, Namespace } = Renderer.K8sApi; - -type ResourceStack = Renderer.K8sApi.ResourceStack; - -export interface MetricsConfiguration { - // Placeholder for Metrics config structure - prometheus: { - enabled: boolean; - }; - persistence: { - enabled: boolean; - storageClass: string; - size: string; - }; - nodeExporter: { - enabled: boolean; - }; - kubeStateMetrics: { - enabled: boolean; - }; - retention: { - time: string; - size: string; - }; - alertManagers: string[]; - replicas: number; - storageClass: string; - version?: string; -} - -export interface MetricsStatus { - installed: boolean; - canUpgrade: boolean; -} - -export class MetricsFeature { - name = "lens-metrics"; - latestVersion = "v2.26.0-lens1"; - - protected stack: ResourceStack; - - constructor(protected cluster: Common.Catalog.KubernetesCluster) { - this.stack = new ResourceStack(cluster, this.name); - } - - get resourceFolder() { - return path.join(__dirname, "../resources/"); - } - - async install(config: MetricsConfiguration): Promise { - // Check if there are storageclasses - const storageClassApi = forCluster(this.cluster, StorageClass); - const scs = await storageClassApi.list(); - - config.persistence.enabled = scs.some(sc => ( - sc.metadata?.annotations?.["storageclass.kubernetes.io/is-default-class"] === "true" || - sc.metadata?.annotations?.["storageclass.beta.kubernetes.io/is-default-class"] === "true" - )); - - config.version = this.latestVersion; - - return this.stack.kubectlApplyFolder(this.resourceFolder, config, ["--prune"]); - } - - async upgrade(config: MetricsConfiguration): Promise { - return this.install(config); - } - - async getStatus(): Promise { - const status: MetricsStatus = { installed: false, canUpgrade: false }; - - try { - const namespaceApi = forCluster(this.cluster, Namespace); - const namespace = await namespaceApi.get({ name: "lens-metrics" }); - - if (namespace?.kind) { - const currentVersion = namespace.metadata.annotations?.extensionVersion || "0.0.0"; - - status.installed = true; - status.canUpgrade = semver.lt(currentVersion, this.latestVersion, true); - } else { - status.installed = false; - } - } catch(e) { - if (e?.error?.code === 404) { - status.installed = false; - } else { - console.warn("[LENS-METRICS] failed to resolve install state", e); - } - } - - return status; - } - - async uninstall(config: MetricsConfiguration): Promise { - return this.stack.kubectlDeleteFolder(this.resourceFolder, config); - } -} diff --git a/extensions/metrics-cluster-feature/src/metrics-settings.tsx b/extensions/metrics-cluster-feature/src/metrics-settings.tsx deleted file mode 100644 index 4ae4235780..0000000000 --- a/extensions/metrics-cluster-feature/src/metrics-settings.tsx +++ /dev/null @@ -1,276 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import React from "react"; -import type { Common } from "@k8slens/extensions"; -import { Renderer } from "@k8slens/extensions"; -import { observer } from "mobx-react"; -import { computed, observable, makeObservable } from "mobx"; -import type { MetricsConfiguration } from "./metrics-feature"; -import { MetricsFeature } from "./metrics-feature"; - -const { - K8sApi: { - forCluster, StatefulSet, DaemonSet, Deployment, - }, - Component: { - SubTitle, Switch, Button, - }, -} = Renderer; - -export interface MetricsSettingsProps { - cluster: Common.Catalog.KubernetesCluster; -} - -@observer -export class MetricsSettings extends React.Component { - constructor(props: MetricsSettingsProps) { - super(props); - makeObservable(this); - } - - @observable featureStates = { - prometheus: false, - kubeStateMetrics: false, - nodeExporter: false, - }; - @observable canUpgrade = false; - @observable upgrading = false; - @observable changed = false; - @observable inProgress = false; - - config: MetricsConfiguration = { - prometheus: { - enabled: false, - }, - persistence: { - enabled: false, - storageClass: null, - size: "20Gi", // kubernetes yaml value (no B suffix) - }, - nodeExporter: { - enabled: false, - }, - retention: { - time: "2d", - size: "5GiB", // argument for prometheus (requires B suffix) - }, - kubeStateMetrics: { - enabled: false, - }, - alertManagers: null, - replicas: 1, - storageClass: null, - }; - feature: MetricsFeature; - - @computed get isTogglable() { - if (this.inProgress) return false; - if (this.props.cluster.status.phase !== "connected") return false; - if (this.canUpgrade) return false; - if (!this.isActiveMetricsProvider) return false; - - return true; - } - - get metricsProvider() { - return this.props.cluster.spec?.metrics?.prometheus?.type || ""; - } - - get isActiveMetricsProvider() { - return (!this.metricsProvider || this.metricsProvider === "lens"); - } - - async componentDidMount() { - this.feature = new MetricsFeature(this.props.cluster); - - await this.updateFeatureStates(); - } - - async updateFeatureStates() { - const status = await this.feature.getStatus(); - - this.canUpgrade = status.canUpgrade; - - if (this.canUpgrade) { - this.changed = true; - } - - const statefulSet = forCluster(this.props.cluster, StatefulSet); - - try { - await statefulSet.get({ name: "prometheus", namespace: "lens-metrics" }); - this.featureStates.prometheus = true; - } catch(e) { - if (e?.error?.code === 404) { - this.featureStates.prometheus = false; - } else { - this.featureStates.prometheus = undefined; - } - } - - const deployment = forCluster(this.props.cluster, Deployment); - - try { - await deployment.get({ name: "kube-state-metrics", namespace: "lens-metrics" }); - this.featureStates.kubeStateMetrics = true; - } catch(e) { - if (e?.error?.code === 404) { - this.featureStates.kubeStateMetrics = false; - } else { - this.featureStates.kubeStateMetrics = undefined; - } - } - - const daemonSet = forCluster(this.props.cluster, DaemonSet); - - try { - await daemonSet.get({ name: "node-exporter", namespace: "lens-metrics" }); - this.featureStates.nodeExporter = true; - } catch(e) { - if (e?.error?.code === 404) { - this.featureStates.nodeExporter = false; - } else { - this.featureStates.nodeExporter = undefined; - } - } - } - - async save() { - this.config.prometheus.enabled = !!this.featureStates.prometheus; - this.config.kubeStateMetrics.enabled = !!this.featureStates.kubeStateMetrics; - this.config.nodeExporter.enabled = !!this.featureStates.nodeExporter; - - this.inProgress = true; - - try { - if (!this.config.prometheus.enabled && !this.config.kubeStateMetrics.enabled && !this.config.nodeExporter.enabled) { - await this.feature.uninstall(this.config); - } else { - await this.feature.install(this.config); - } - } finally { - this.inProgress = false; - this.changed = false; - - await this.updateFeatureStates(); - } - } - - async togglePrometheus(enabled: boolean) { - this.featureStates.prometheus = enabled; - this.changed = true; - } - - async toggleKubeStateMetrics(enabled: boolean) { - this.featureStates.kubeStateMetrics = enabled; - this.changed = true; - } - - async toggleNodeExporter(enabled: boolean) { - this.featureStates.nodeExporter = enabled; - this.changed = true; - } - - @computed get buttonLabel() { - const allDisabled = !this.featureStates.kubeStateMetrics && !this.featureStates.nodeExporter && !this.featureStates.prometheus; - - if (this.inProgress && this.canUpgrade) return "Upgrading ..."; - if (this.inProgress && allDisabled) return "Uninstalling ..."; - if (this.inProgress) return "Applying ..."; - if (this.canUpgrade) return "Upgrade"; - - if (this.changed && allDisabled) { - return "Uninstall"; - } - - return "Apply"; - } - - render() { - return ( -
- { this.props.cluster.status.phase !== "connected" && ( -
-

- Lens Metrics settings requires established connection to the cluster. -

-
- )} - { !this.isActiveMetricsProvider && ( -
-

- Other metrics provider is currently active. See "Metrics" tab for details. -

-
- )} -
- - this.togglePrometheus(checked)} - name="prometheus" - > - Enable bundled Prometheus metrics stack - - - Enable timeseries data visualization (Prometheus stack) for your cluster. - -
- -
- - this.toggleKubeStateMetrics(checked)} - name="kube-state-metrics" - > - Enable bundled kube-state-metrics stack - - - Enable Kubernetes API object metrics for your cluster. - Enable this only if you don't have existing kube-state-metrics stack installed. - -
- -
- - this.toggleNodeExporter(checked)} - name="node-exporter" - > - Enable bundled node-exporter stack - - - Enable node level metrics for your cluster. - Enable this only if you don't have existing node-exporter stack installed. - -
- -
-
-
-
-
- ); - } -} diff --git a/extensions/metrics-cluster-feature/tsconfig.json b/extensions/metrics-cluster-feature/tsconfig.json deleted file mode 100644 index f60a98c9ad..0000000000 --- a/extensions/metrics-cluster-feature/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "outDir": "dist", - "module": "CommonJS", - "target": "ES2017", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "moduleResolution": "Node", - "sourceMap": false, - "declaration": false, - "strict": false, - "noImplicitAny": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true, - "useDefineForClassFields": true, - "jsx": "react" - }, - "include": [ - "./**/*.ts", - "./**/*.tsx" - ], - "exclude": [ - "node_modules", - "*.js" - ] -} diff --git a/extensions/metrics-cluster-feature/webpack.config.js b/extensions/metrics-cluster-feature/webpack.config.js deleted file mode 100644 index 951cffdabc..0000000000 --- a/extensions/metrics-cluster-feature/webpack.config.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -const path = require("path"); - -module.exports = [ - { - entry: "./renderer.tsx", - context: __dirname, - target: "electron-renderer", - mode: "production", - optimization: { - minimize: false, - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - externals: [ - { - "@k8slens/extensions": "var global.LensExtensions", - "react": "var global.React", - "react-dom": "var global.ReactDOM", - "mobx": "var global.Mobx", - "mobx-react": "var global.MobxReact", - }, - ], - resolve: { - extensions: [ ".tsx", ".ts", ".js" ], - }, - output: { - libraryTarget: "commonjs2", - globalObject: "this", - filename: "renderer.js", - path: path.resolve(__dirname, "dist"), - }, - node: { - __dirname: false, - }, - }, -]; diff --git a/extensions/node-menu/package.json b/extensions/node-menu/package.json deleted file mode 100644 index df2d490ee0..0000000000 --- a/extensions/node-menu/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "lens-node-menu", - "version": "6.1.0", - "description": "Lens node menu", - "renderer": "dist/renderer.js", - "lens": { - "metadata": {}, - "styles": [] - }, - "scripts": { - "build": "npx webpack", - "dev": "npx webpack -- --watch", - "test": "npx jest --passWithNoTests --env=jsdom src $@" - }, - "files": [ - "dist/**/*" - ], - "dependencies": {}, - "devDependencies": { - "@k8slens/extensions": "file:../../src/extensions/npm/extensions" - } -} diff --git a/extensions/node-menu/renderer.tsx b/extensions/node-menu/renderer.tsx deleted file mode 100644 index 31557c908d..0000000000 --- a/extensions/node-menu/renderer.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { Renderer } from "@k8slens/extensions"; -import React from "react"; -import type { NodeMenuProps } from "./src/node-menu"; -import { NodeMenu } from "./src/node-menu"; - -export default class NodeMenuRendererExtension extends Renderer.LensExtension { - kubeObjectMenuItems = [ - { - kind: "Node", - apiVersions: ["v1"], - components: { - MenuItem: (props: NodeMenuProps) => , - }, - }, - ]; -} diff --git a/extensions/node-menu/src/node-menu.tsx b/extensions/node-menu/src/node-menu.tsx deleted file mode 100644 index adc7576206..0000000000 --- a/extensions/node-menu/src/node-menu.tsx +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import React from "react"; -import { Common, Renderer } from "@k8slens/extensions"; - -type Node = Renderer.K8sApi.Node; - -const { - Component: { - terminalStore, - createTerminalTab, - ConfirmDialog, - MenuItem, - Icon, - }, - Navigation, -} = Renderer; -const { - App, -} = Common; - - -export interface NodeMenuProps extends Renderer.Component.KubeObjectMenuProps { -} - -export function NodeMenu(props: NodeMenuProps) { - const { object: node, toolbar } = props; - - if (!node) { - return null; - } - - const nodeName = node.getName(); - const kubectlPath = App.Preferences.getKubectlPath() || "kubectl"; - - const sendToTerminal = (command: string) => { - terminalStore.sendCommand(command, { - enter: true, - newTab: true, - }); - Navigation.hideDetails(); - }; - - const shell = () => { - createTerminalTab({ - title: `Node: ${nodeName}`, - node: nodeName, - }); - Navigation.hideDetails(); - }; - - const cordon = () => { - sendToTerminal(`${kubectlPath} cordon ${nodeName}`); - }; - - const unCordon = () => { - sendToTerminal(`${kubectlPath} uncordon ${nodeName}`); - }; - - const drain = () => { - const command = `${kubectlPath} drain ${nodeName} --delete-local-data --ignore-daemonsets --force`; - - ConfirmDialog.open({ - ok: () => sendToTerminal(command), - labelOk: `Drain Node`, - message: ( -

- {"Are you sure you want to drain "} - {nodeName} - ? -

- ), - }); - }; - - return ( - <> - - - Shell - - { - node.isUnschedulable() - ? ( - - - Uncordon - - ) - : ( - - - Cordon - - ) - } - - - Drain - - - ); -} diff --git a/extensions/node-menu/tsconfig.json b/extensions/node-menu/tsconfig.json deleted file mode 100644 index 5b6c61577e..0000000000 --- a/extensions/node-menu/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "outDir": "dist", - "module": "CommonJS", - "target": "ES2017", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "moduleResolution": "Node", - "sourceMap": false, - "declaration": false, - "strict": false, - "noImplicitAny": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true, - "useDefineForClassFields": true, - "jsx": "react" - }, - "include": [ - "./*.ts", - "./*.tsx" - ], - "exclude": [ - "node_modules", - "*.js" - ] -} diff --git a/extensions/node-menu/webpack.config.js b/extensions/node-menu/webpack.config.js deleted file mode 100644 index 8fe82e48a7..0000000000 --- a/extensions/node-menu/webpack.config.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -const path = require("path"); - -module.exports = [ - { - entry: "./renderer.tsx", - context: __dirname, - target: "electron-renderer", - mode: "production", - optimization: { - minimize: false, - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - externals: [ - { - "@k8slens/extensions": "var global.LensExtensions", - "react": "var global.React", - "react-dom": "var global.ReactDOM", - "mobx": "var global.Mobx", - "mobx-react": "var global.MobxReact", - }, - ], - resolve: { - extensions: [ ".tsx", ".ts", ".js" ], - }, - output: { - libraryTarget: "commonjs2", - globalObject: "this", - filename: "renderer.js", - path: path.resolve(__dirname, "dist"), - }, - }, -]; diff --git a/extensions/pod-menu/package.json b/extensions/pod-menu/package.json deleted file mode 100644 index 3b44d3c44d..0000000000 --- a/extensions/pod-menu/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "lens-pod-menu", - "version": "6.1.0", - "description": "Lens pod menu", - "renderer": "dist/renderer.js", - "lens": { - "metadata": {}, - "styles": [] - }, - "scripts": { - "build": "npx webpack", - "dev": "npx webpack -- --watch", - "test": "npx jest --passWithNoTests --env=jsdom src $@" - }, - "files": [ - "dist/**/*" - ], - "dependencies": {}, - "devDependencies": { - "@k8slens/extensions": "file:../../src/extensions/npm/extensions" - } -} diff --git a/extensions/pod-menu/renderer.tsx b/extensions/pod-menu/renderer.tsx deleted file mode 100644 index 4788da9707..0000000000 --- a/extensions/pod-menu/renderer.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { Renderer } from "@k8slens/extensions"; -import type { PodAttachMenuProps } from "./src/attach-menu"; -import { PodAttachMenu } from "./src/attach-menu"; -import type { PodShellMenuProps } from "./src/shell-menu"; -import { PodShellMenu } from "./src/shell-menu"; -import type { PodLogsMenuProps } from "./src/logs-menu"; -import { PodLogsMenu } from "./src/logs-menu"; -import React from "react"; - -export default class PodMenuRendererExtension extends Renderer.LensExtension { - kubeObjectMenuItems = [ - { - kind: "Pod", - apiVersions: ["v1"], - components: { - MenuItem: (props: PodAttachMenuProps) => , - }, - }, - { - kind: "Pod", - apiVersions: ["v1"], - components: { - MenuItem: (props: PodShellMenuProps) => , - }, - }, - { - kind: "Pod", - apiVersions: ["v1"], - components: { - MenuItem: (props: PodLogsMenuProps) => , - }, - }, - ]; -} diff --git a/extensions/pod-menu/src/attach-menu.tsx b/extensions/pod-menu/src/attach-menu.tsx deleted file mode 100644 index fd6250bd30..0000000000 --- a/extensions/pod-menu/src/attach-menu.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - - - -import React from "react"; -import { Renderer, Common } from "@k8slens/extensions"; - -type Pod = Renderer.K8sApi.Pod; - -const { - Component: { - createTerminalTab, - terminalStore, - MenuItem, - Icon, - SubMenu, - StatusBrick, - }, - Navigation, -} = Renderer; -const { - Util, - App, -} = Common; - -export interface PodAttachMenuProps extends Renderer.Component.KubeObjectMenuProps { -} - -export class PodAttachMenu extends React.Component { - async attachToPod(container?: string) { - const { object: pod } = this.props; - - const kubectlPath = App.Preferences.getKubectlPath() || "kubectl"; - const commandParts = [ - kubectlPath, - "attach", - "-i", - "-t", - "-n", pod.getNs(), - pod.getName(), - ]; - - if (window.navigator.platform !== "Win32") { - commandParts.unshift("exec"); - } - - if (container) { - commandParts.push("-c", container); - } - - const shell = createTerminalTab({ - title: `Pod: ${pod.getName()} (namespace: ${pod.getNs()}) [Attached]`, - }); - - terminalStore.sendCommand(commandParts.join(" "), { - enter: true, - tabId: shell.id, - }); - - Navigation.hideDetails(); - } - - render() { - const { object, toolbar } = this.props; - const containers = object.getRunningContainers(); - - if (!containers.length) return null; - - return ( - this.attachToPod(containers[0].name))}> - - Attach Pod - {containers.length > 1 && ( - <> - - - { - containers.map(container => { - const { name } = container; - - return ( - this.attachToPod(name))} - className="flex align-center" - > - - {name} - - ); - }) - } - - - )} - - ); - } -} diff --git a/extensions/pod-menu/src/logs-menu.tsx b/extensions/pod-menu/src/logs-menu.tsx deleted file mode 100644 index e5c8496d3d..0000000000 --- a/extensions/pod-menu/src/logs-menu.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import React from "react"; -import { Renderer, Common } from "@k8slens/extensions"; - -type Pod = Renderer.K8sApi.Pod; -type IPodContainer = Renderer.K8sApi.IPodContainer; - -const { - Component: { - logTabStore, - MenuItem, - Icon, - SubMenu, - StatusBrick, - }, - Navigation, -} = Renderer; -const { - Util, -} = Common; - -export interface PodLogsMenuProps extends Renderer.Component.KubeObjectMenuProps { -} - -export class PodLogsMenu extends React.Component { - showLogs(container: IPodContainer) { - Navigation.hideDetails(); - const pod = this.props.object; - - logTabStore.createPodTab({ - selectedPod: pod, - selectedContainer: container, - }); - } - - render() { - const { object: pod, toolbar } = this.props; - const containers = pod.getAllContainers(); - const statuses = pod.getContainerStatuses(); - - if (!containers.length) return null; - - return ( - this.showLogs(containers[0]))}> - - Logs - {containers.length > 1 && ( - <> - - - { - containers.map(container => { - const { name } = container; - const status = statuses.find(status => status.name === name); - const brick = status ? ( - - ) : null; - - return ( - this.showLogs(container))} - className="flex align-center" - > - {brick} - {name} - - ); - }) - } - - - )} - - ); - } -} diff --git a/extensions/pod-menu/src/shell-menu.tsx b/extensions/pod-menu/src/shell-menu.tsx deleted file mode 100644 index 36be7c470b..0000000000 --- a/extensions/pod-menu/src/shell-menu.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - - - -import React from "react"; -import { Renderer, Common } from "@k8slens/extensions"; - -type Pod = Renderer.K8sApi.Pod; - -const { - Component: { - createTerminalTab, - terminalStore, - MenuItem, - Icon, - SubMenu, - StatusBrick, - }, - Navigation, -} = Renderer; -const { - Util, - App, -} = Common; - -export interface PodShellMenuProps extends Renderer.Component.KubeObjectMenuProps { -} - -export class PodShellMenu extends React.Component { - async execShell(container?: string) { - const { object: pod } = this.props; - - const kubectlPath = App.Preferences.getKubectlPath() || "kubectl"; - const commandParts = [ - kubectlPath, - "exec", - "-i", - "-t", - "-n", pod.getNs(), - pod.getName(), - ]; - - if (window.navigator.platform !== "Win32") { - commandParts.unshift("exec"); - } - - if (container) { - commandParts.push("-c", container); - } - - commandParts.push("--"); - - if (pod.getSelectedNodeOs() === "windows") { - commandParts.push("powershell"); - } else { - commandParts.push('sh -c "clear; (bash || ash || sh)"'); - } - - const shell = createTerminalTab({ - title: `Pod: ${pod.getName()} (namespace: ${pod.getNs()})`, - }); - - terminalStore.sendCommand(commandParts.join(" "), { - enter: true, - tabId: shell.id, - }); - - Navigation.hideDetails(); - } - - render() { - const { object, toolbar } = this.props; - const containers = object.getRunningContainers(); - - if (!containers.length) return null; - - return ( - this.execShell(containers[0].name))}> - - Shell - {containers.length > 1 && ( - <> - - - { - containers.map(container => { - const { name } = container; - - return ( - this.execShell(name))} - className="flex align-center" - > - - {name} - - ); - }) - } - - - )} - - ); - } -} diff --git a/extensions/pod-menu/tsconfig.json b/extensions/pod-menu/tsconfig.json deleted file mode 100644 index 5b6c61577e..0000000000 --- a/extensions/pod-menu/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "outDir": "dist", - "module": "CommonJS", - "target": "ES2017", - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "moduleResolution": "Node", - "sourceMap": false, - "declaration": false, - "strict": false, - "noImplicitAny": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true, - "useDefineForClassFields": true, - "jsx": "react" - }, - "include": [ - "./*.ts", - "./*.tsx" - ], - "exclude": [ - "node_modules", - "*.js" - ] -} diff --git a/extensions/pod-menu/webpack.config.js b/extensions/pod-menu/webpack.config.js deleted file mode 100644 index 8fe82e48a7..0000000000 --- a/extensions/pod-menu/webpack.config.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -const path = require("path"); - -module.exports = [ - { - entry: "./renderer.tsx", - context: __dirname, - target: "electron-renderer", - mode: "production", - optimization: { - minimize: false, - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules/, - }, - ], - }, - externals: [ - { - "@k8slens/extensions": "var global.LensExtensions", - "react": "var global.React", - "react-dom": "var global.ReactDOM", - "mobx": "var global.Mobx", - "mobx-react": "var global.MobxReact", - }, - ], - resolve: { - extensions: [ ".tsx", ".ts", ".js" ], - }, - output: { - libraryTarget: "commonjs2", - globalObject: "this", - filename: "renderer.js", - path: path.resolve(__dirname, "dist"), - }, - }, -]; diff --git a/integration/__tests__/cluster-pages.tests.ts b/integration/__tests__/cluster-pages.tests.ts index 842a38da88..e0b865d5af 100644 --- a/integration/__tests__/cluster-pages.tests.ts +++ b/integration/__tests__/cluster-pages.tests.ts @@ -76,60 +76,6 @@ describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => { 10 * 60 * 1000, ); - it( - "show logs and highlight the log search entries", - async () => { - await navigateToPods(frame); - - const namespacesSelector = await frame.waitForSelector( - ".NamespaceSelect", - ); - - await namespacesSelector.click(); - await namespacesSelector.type("kube-system"); - await namespacesSelector.press("Enter"); - await namespacesSelector.click(); - - const kubeApiServerRow = await frame.waitForSelector( - "div.TableCell >> text=kube-apiserver", - ); - - await kubeApiServerRow.click(); - await frame.waitForSelector(".Drawer", { state: "visible" }); - - const showPodLogsIcon = await frame.waitForSelector( - ".Drawer .drawer-title .Icon >> text=subject", - ); - - showPodLogsIcon.click(); - - // Check if controls are available - await frame.waitForSelector(".Dock.isOpen"); - await frame.waitForSelector(".LogList .VirtualList"); - await frame.waitForSelector(".LogResourceSelector"); - - const logSearchInput = await frame.waitForSelector( - ".LogSearch .SearchInput input", - ); - - await logSearchInput.type(":"); - await frame.waitForSelector(".LogList .list span.active"); - - const showTimestampsButton = await frame.waitForSelector( - "[data-testid='log-controls'] .show-timestamps", - ); - - await showTimestampsButton.click(); - - const showPreviousButton = await frame.waitForSelector( - "[data-testid='log-controls'] .show-previous", - ); - - await showPreviousButton.click(); - }, - 10 * 60 * 1000, - ); - it( "should show the default namespaces", async () => { diff --git a/package.json b/package.json index 232a4395ad..99fdec1759 100644 --- a/package.json +++ b/package.json @@ -59,12 +59,7 @@ "sentryDsn": "", "contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:", "welcomeRoute": "/welcome", - "extensions": [ - "kube-object-event-status", - "metrics-cluster-feature", - "node-menu", - "pod-menu" - ] + "extensions": [] }, "engines": { "node": ">=16 <17" @@ -234,14 +229,9 @@ "joi": "^17.7.0", "js-yaml": "^4.1.0", "jsdom": "^16.7.0", - "kube-object-event-status": "file:./extensions/kube-object-event-status", - "lens-metrics-cluster-feature": "file:./extensions/metrics-cluster-feature", - "lens-node-menu": "file:./extensions/node-menu", - "lens-pod-menu": "file:./extensions/pod-menu", "lodash": "^4.17.15", "marked": "^4.2.4", "md5-file": "^5.0.0", - "metrics-cluster-feature": "file:./extensions/metrics-cluster-feature", "mobx": "^6.7.0", "mobx-observable-history": "^2.0.3", "mobx-react": "^7.6.0", @@ -252,12 +242,10 @@ "monaco-editor": "^0.29.1", "monaco-editor-webpack-plugin": "^5.0.0", "node-fetch": "^3.3.0", - "node-menu": "file:./extensions/node-menu", "node-pty": "0.10.1", "npm": "^8.19.3", "p-limit": "^3.1.0", "path-to-regexp": "^6.2.0", - "pod-menu": "file:./extensions/pod-menu", "proper-lockfile": "^4.1.2", "react": "^17.0.2", "react-dom": "^17.0.2", From ed26e245e6b5f1a4ab717969a743936715ff201e Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 20 Dec 2022 05:33:48 -0800 Subject: [PATCH 04/21] Prevent shell sync commands from polluting history (#6668) * Prevent shell sync commands from polluting history Signed-off-by: Sebastian Malton * Fix tests Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- .../main/compute-unix-shell-environment.injectable.ts | 1 + .../shell-sync/main/compute-unix-shell-environment.test.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/features/shell-sync/main/compute-unix-shell-environment.injectable.ts b/src/features/shell-sync/main/compute-unix-shell-environment.injectable.ts index e0f2b2aeb1..fa6de94b57 100644 --- a/src/features/shell-sync/main/compute-unix-shell-environment.injectable.ts +++ b/src/features/shell-sync/main/compute-unix-shell-environment.injectable.ts @@ -84,6 +84,7 @@ const computeUnixShellEnvironmentInjectable = getInjectable({ } else if (!cshLikeShellName.test(shellName)) { // zsh (at least, maybe others) don't load RC files when in non-interactive mode, even when using -l (login) option shellArgs.push("-i"); + command = ` ${command}`; // This prevents the command from being added to the history } else { // Some shells don't support any other options when providing the -l (login) shell option } diff --git a/src/features/shell-sync/main/compute-unix-shell-environment.test.ts b/src/features/shell-sync/main/compute-unix-shell-environment.test.ts index 55766fbb8a..a540824286 100644 --- a/src/features/shell-sync/main/compute-unix-shell-environment.test.ts +++ b/src/features/shell-sync/main/compute-unix-shell-environment.test.ts @@ -218,7 +218,7 @@ describe("computeUnixShellEnvironment technical tests", () => { }); it("should send the command via stdin", () => { - expect(stdinValue).toBe(`'/some/process/exec/path' -p '"deadbeef" + JSON.stringify(process.env) + "deadbeef"'`); + expect(stdinValue).toBe(` '/some/process/exec/path' -p '"deadbeef" + JSON.stringify(process.env) + "deadbeef"'`); }); it("should close stdin", () => { From 5dde02733bb8142b7466bf65bf805bde597733dd Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 20 Dec 2022 16:34:43 +0300 Subject: [PATCH 05/21] Fix: load pods from all namespaces in node details (#6794) * Load pods from all namespaces in Node details Signed-off-by: Alex Andreev * Lint fixes Signed-off-by: Alex Andreev Signed-off-by: Alex Andreev --- src/renderer/components/+nodes/details.tsx | 5 ++++ ...oad-pods-from-all-namespaces.injectable.ts | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/renderer/components/+workloads-pods/load-pods-from-all-namespaces.injectable.ts diff --git a/src/renderer/components/+nodes/details.tsx b/src/renderer/components/+nodes/details.tsx index 4e1eeddb65..622f9f1b69 100644 --- a/src/renderer/components/+nodes/details.tsx +++ b/src/renderer/components/+nodes/details.tsx @@ -23,6 +23,7 @@ import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.inj import type { PodStore } from "../+workloads-pods/store"; import podStoreInjectable from "../+workloads-pods/store.injectable"; import loggerInjectable from "../../../common/logger.injectable"; +import loadPodsFromAllNamespacesInjectable from "../+workloads-pods/load-pods-from-all-namespaces.injectable"; export interface NodeDetailsProps extends KubeObjectDetailsProps { } @@ -31,6 +32,7 @@ interface Dependencies { subscribeStores: SubscribeStores; podStore: PodStore; logger: Logger; + loadPodsFromAllNamespaces: () => void; } @observer @@ -41,6 +43,8 @@ class NonInjectedNodeDetails extends React.Component(NonIn subscribeStores: di.inject(subscribeStoresInjectable), podStore: di.inject(podStoreInjectable), logger: di.inject(loggerInjectable), + loadPodsFromAllNamespaces: di.inject(loadPodsFromAllNamespacesInjectable), }), }); diff --git a/src/renderer/components/+workloads-pods/load-pods-from-all-namespaces.injectable.ts b/src/renderer/components/+workloads-pods/load-pods-from-all-namespaces.injectable.ts new file mode 100644 index 0000000000..f6e96d5b79 --- /dev/null +++ b/src/renderer/components/+workloads-pods/load-pods-from-all-namespaces.injectable.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectable } from "@ogre-tools/injectable"; +import namespaceStoreInjectable from "../+namespaces/store.injectable"; +import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable"; +import podStoreInjectable from "./store.injectable"; + +const loadPodsFromAllNamespacesInjectable = getInjectable({ + id: "load-pods-from-all-namespaces", + instantiate: (di) => { + const podStore = di.inject(podStoreInjectable); + const namespaceStore = di.inject(namespaceStoreInjectable); + const showErrorNotification = di.inject(showErrorNotificationInjectable); + + return () => { + podStore.loadAll({ + namespaces: [...namespaceStore.getItems().map(ns => ns.getName())], + onLoadFailure: error => + showErrorNotification(`Can not load Pods. ${String(error)}`), + }); + }; + }, +}); + +export default loadPodsFromAllNamespacesInjectable; From e762d07cf87035d1fbee8b74c8e9d7ae0956591f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 08:37:32 -0500 Subject: [PATCH 06/21] Bump esbuild from 0.16.9 to 0.16.10 (#6790) Bumps [esbuild](https://github.com/evanw/esbuild) from 0.16.9 to 0.16.10. - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.16.9...v0.16.10) --- updated-dependencies: - dependency-name: esbuild dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 228 +++++++++++++++++++++++++-------------------------- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index 99fdec1759..3429716034 100644 --- a/package.json +++ b/package.json @@ -353,7 +353,7 @@ "electron": "^19.1.9", "electron-builder": "^23.6.0", "electron-notarize": "^0.3.0", - "esbuild": "^0.16.9", + "esbuild": "^0.16.10", "esbuild-loader": "^2.20.0", "eslint": "^8.30.0", "eslint-import-resolver-typescript": "^3.5.2", diff --git a/yarn.lock b/yarn.lock index 3d971d840a..06411f098e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -598,125 +598,125 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@esbuild/android-arm64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.9.tgz#474da719599f99d820ec010c92846a4f685fa28a" - integrity sha512-ndIAZJUeLx4O+4AJbFQCurQW4VRUXjDsUvt1L+nP8bVELOWdmdCEOtlIweCUE6P+hU0uxYbEK2AEP0n5IVQvhg== +"@esbuild/android-arm64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.10.tgz#d784d8f13dbef50492ea55456fb50651e4036fbf" + integrity sha512-47Y+NwVKTldTlDhSgJHZ/RpvBQMUDG7eKihqaF/u6g7s0ZPz4J1vy8A3rwnnUOF2CuDn7w7Gj/QcMoWz3U3SJw== "@esbuild/android-arm@0.15.18": version "0.15.18" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz#266d40b8fdcf87962df8af05b76219bc786b4f80" integrity sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw== -"@esbuild/android-arm@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.9.tgz#b0747ec074bba3ca652bfa8de3f55acfbb2d259e" - integrity sha512-kW5ccqWHVOOTGUkkJbtfoImtqu3kA1PFkivM+9QPFSHphPfPBlBalX9eDRqPK+wHCqKhU48/78T791qPgC9e9A== +"@esbuild/android-arm@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.10.tgz#becf6b5647c091b039121db8c17300a7dfd1ab4a" + integrity sha512-RmJjQTRrO6VwUWDrzTBLmV4OJZTarYsiepLGlF2rYTVB701hSorPywPGvP6d8HCuuRibyXa5JX4s3jN2kHEtjQ== -"@esbuild/android-x64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.9.tgz#1cd75e8ed7d6d7eb5f9896f623df63882bd8e887" - integrity sha512-UbMcJB4EHrAVOnknQklREPgclNU2CPet2h+sCBCXmF2mfoYWopBn/CfTfeyOkb/JglOcdEADqAljFndMKnFtOw== +"@esbuild/android-x64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.10.tgz#648cacbb13a5047380a038e5d6d895015e31b525" + integrity sha512-C4PfnrBMcuAcOurQzpF1tTtZz94IXO5JmICJJ3NFJRHbXXsQUg9RFG45KvydKqtFfBaFLCHpduUkUfXwIvGnRg== -"@esbuild/darwin-arm64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.9.tgz#820c88738cd97621737abcd5f05400ae5e0c66e6" - integrity sha512-d7D7/nrt4CxPul98lx4PXhyNZwTYtbdaHhOSdXlZuu5zZIznjqtMqLac8Bv+IuT6SVHiHUwrkL6ywD7mOgLW+A== +"@esbuild/darwin-arm64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.10.tgz#3ca7fd9a456d11752df77df6c030f2d08f27bda9" + integrity sha512-bH/bpFwldyOKdi9HSLCLhhKeVgRYr9KblchwXgY2NeUHBB/BzTUHtUSBgGBmpydB1/4E37m+ggXXfSrnD7/E7g== -"@esbuild/darwin-x64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.9.tgz#5a19c00781595e0dfeef1826b3512d04c37b98ff" - integrity sha512-LZc+Wlz06AkJYtwWsBM3x2rSqTG8lntDuftsUNQ3fCx9ZttYtvlDcVtgb+NQ6t9s6K5No5zutN3pcjZEC2a4iQ== +"@esbuild/darwin-x64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.10.tgz#7eb71b8da4106627f01553def517d3c5e5942592" + integrity sha512-OXt7ijoLuy+AjDSKQWu+KdDFMBbdeaL6wtgMKtDUXKWHiAMKHan5+R1QAG6HD4+K0nnOvEJXKHeA9QhXNAjOTQ== -"@esbuild/freebsd-arm64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.9.tgz#2b7c16f5d15c259ed279b293b97c28c4a4bb107f" - integrity sha512-gIj0UQZlQo93CHYouHKkpzP7AuruSaMIm1etcWIxccFEVqCN1xDr6BWlN9bM+ol/f0W9w3hx3HDuEwcJVtGneQ== +"@esbuild/freebsd-arm64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.10.tgz#c69c78ee1d17d35ad2cf76a1bb67788000a84b43" + integrity sha512-shSQX/3GHuspE3Uxtq5kcFG/zqC+VuMnJkqV7LczO41cIe6CQaXHD3QdMLA4ziRq/m0vZo7JdterlgbmgNIAlQ== -"@esbuild/freebsd-x64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.9.tgz#2db48ffeeab149c2b970494a60b82bf3004b8630" - integrity sha512-GNors4vaMJ7lzGOuhzNc7jvgsQZqErGA8rsW+nck8N1nYu86CvsJW2seigVrQQWOV4QzEP8Zf3gm+QCjA2hnBQ== +"@esbuild/freebsd-x64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.10.tgz#a9804ab1b9366f915812af24ad5cfc1c0db01441" + integrity sha512-5YVc1zdeaJGASijZmTzSO4h6uKzsQGG3pkjI6fuXvolhm3hVRhZwnHJkforaZLmzvNv5Tb7a3QL2FAVmrgySIA== -"@esbuild/linux-arm64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.9.tgz#4c63c9f8ddd690d140ac3e0f360226d3fcdd75d8" - integrity sha512-YPxQunReYp8RQ1FvexFrOEqqf+nLbS3bKVZF5FRT2uKM7Wio7BeATqAwO02AyrdSEntt3I5fhFsujUChIa8CZg== +"@esbuild/linux-arm64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.10.tgz#d9a9ddfcb28ed8cced688bc112ef66283d6fa77f" + integrity sha512-2aqeNVxIaRfPcIaMZIFoblLh588sWyCbmj1HHCCs9WmeNWm+EIN0SmvsmPvTa/TsNZFKnxTcvkX2eszTcCqIrA== -"@esbuild/linux-arm@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.9.tgz#7704de1c2a30bc68d8f615d3ecb1cf68f001256a" - integrity sha512-cNx1EF99c2t1Ztn0lk9N+MuwBijGF8mH6nx9GFsB3e0lpUpPkCE/yt5d+7NP9EwJf5uzqdjutgVYoH1SNqzudA== +"@esbuild/linux-arm@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.10.tgz#f32cdac1d3319c83ae7f9f31238dd1284ee6bba2" + integrity sha512-c360287ZWI2miBnvIj23bPyVctgzeMT2kQKR+x94pVqIN44h3GF8VMEs1SFPH1UgyDr3yBbx3vowDS1SVhyVhA== -"@esbuild/linux-ia32@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.9.tgz#bf0fda9f046e6c8332d7c8350b8a94d63acb4ceb" - integrity sha512-zb12ixDIKNwFpIqR00J88FFitVwOEwO78EiUi8wi8FXlmSc3GtUuKV/BSO+730Kglt0B47+ZrJN1BhhOxZaVrw== +"@esbuild/linux-ia32@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.10.tgz#1e023478e42f3a01cad48f4af50120d4b639af03" + integrity sha512-sqMIEWeyrLGU7J5RB5fTkLRIFwsgsQ7ieWXlDLEmC2HblPYGb3AucD7inw2OrKFpRPKsec1l+lssiM3+NV5aOw== "@esbuild/linux-loong64@0.15.18": version "0.15.18" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239" integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ== -"@esbuild/linux-loong64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.9.tgz#c16378b898fa38f5f788f76fbce16a45c49c8793" - integrity sha512-X8te4NLxtHiNT6H+4Pfm5RklzItA1Qy4nfyttihGGX+Koc53Ar20ViC+myY70QJ8PDEOehinXZj/F7QK3A+MKQ== +"@esbuild/linux-loong64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.10.tgz#f9098865a69d1d6e2f8bda51c7f9d4240f20b771" + integrity sha512-O7Pd5hLEtTg37NC73pfhUOGTjx/+aXu5YoSq3ahCxcN7Bcr2F47mv+kG5t840thnsEzrv0oB70+LJu3gUgchvg== -"@esbuild/linux-mips64el@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.9.tgz#492605f13f19dc06c350d94e4048c21478b9dec4" - integrity sha512-ZqyMDLt02c5smoS3enlF54ndK5zK4IpClLTxF0hHfzHJlfm4y8IAkIF8LUW0W7zxcKy7oAwI7BRDqeVvC120SA== +"@esbuild/linux-mips64el@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.10.tgz#574725ad2ea81b7783b7ba7d1ab3475f8fdd8d32" + integrity sha512-FN8mZOH7531iPHM0kaFhAOqqNHoAb6r/YHW2ZIxNi0a85UBi2DO4Vuyn7t1p4UN8a4LoAnLOT1PqNgHkgBJgbA== -"@esbuild/linux-ppc64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.9.tgz#ccaf759fc4f7a5fe72bdac05b4f5bf18ef1fe01b" - integrity sha512-k+ca5W5LDBEF3lfDwMV6YNXwm4wEpw9krMnNvvlNz3MrKSD2Eb2c861O0MaKrZkG/buTQAP4vkavbLwgIe6xjg== +"@esbuild/linux-ppc64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.10.tgz#11da658c54514a693813af56bb28951d563a90c3" + integrity sha512-Dg9RiqdvHOAWnOKIOTsIx8dFX9EDlY2IbPEY7YFzchrCiTZmMkD7jWA9UdZbNUygPjdmQBVPRCrLydReFlX9yg== -"@esbuild/linux-riscv64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.9.tgz#542d0e68bc99fb658fe732b0917931c09775f1a3" - integrity sha512-GuInVdogjmg9DhgkEmNipHkC+3tzkanPJzgzTC2ihsvrruLyFoR1YrTGixblNSMPudQLpiqkcwGwwe0oqfrvfA== +"@esbuild/linux-riscv64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.10.tgz#3af4600adbd6c5a4a6f1da05771f4aa6774baab2" + integrity sha512-XMqtpjwzbmlar0BJIxmzu/RZ7EWlfVfH68Vadrva0Wj5UKOdKvqskuev2jY2oPV3aoQUyXwnMbMrFmloO2GfAw== -"@esbuild/linux-s390x@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.9.tgz#4398f9d9d64dba4cfa6eed267476eaa9c9b7f214" - integrity sha512-49wQ0aYkvwXonGsxc7LuuLNICMX8XtO92Iqmug5Qau0kpnV6SP34jk+jIeu4suHwAbSbRhVFtDv75yRmyfQcHw== +"@esbuild/linux-s390x@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.10.tgz#9e3377aaf0191a9d6628e806a279085ec4391f3e" + integrity sha512-fu7XtnoeRNFMx8DjK3gPWpFBDM2u5ba+FYwg27SjMJwKvJr4bDyKz5c+FLXLUSSAkMAt/UL+cUbEbra+rYtUgw== -"@esbuild/linux-x64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.9.tgz#67c6b418ef36addbca17af0d7a2274c37ddffba2" - integrity sha512-Nx4oKEAJ6EcQlt4dK7qJyuZUoXZG7CAeY22R7rqZijFzwFfMOD+gLP56uV7RrV86jGf8PeRY8TBsRmOcZoG42w== +"@esbuild/linux-x64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.10.tgz#7c41d4d697ce674e0083e7baa6231468f4650d85" + integrity sha512-61lcjVC/RldNNMUzQQdyCWjCxp9YLEQgIxErxU9XluX7juBdGKb0pvddS0vPNuCvotRbzijZ1pzII+26haWzbA== -"@esbuild/netbsd-x64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.9.tgz#22ed58e404ebeb2475b821bc4e25f1027eb0c912" - integrity sha512-d0WnpgJ+FTiMZXEQ1NOv9+0gvEhttbgKEvVqWWAtl1u9AvlspKXbodKHzQ5MLP6YV1y52Xp+p8FMYqj8ykTahg== +"@esbuild/netbsd-x64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.10.tgz#ebac59e3986834af04bbafcee7b0c1f31cd477c6" + integrity sha512-JeZXCX3viSA9j4HqSoygjssdqYdfHd6yCFWyfSekLbz4Ef+D2EjvsN02ZQPwYl5a5gg/ehdHgegHhlfOFP0HCA== -"@esbuild/openbsd-x64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.9.tgz#2b2597b4edd4d26946f7c56838680fbeb4d455eb" - integrity sha512-jccK11278dvEscHFfMk5EIPjF4wv1qGD0vps7mBV1a6TspdR36O28fgPem/SA/0pcsCPHjww5ouCLwP+JNAFlw== +"@esbuild/openbsd-x64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.10.tgz#9eaa6cac3b80db45090c0946e62de5b5689c61d1" + integrity sha512-3qpxQKuEVIIg8SebpXsp82OBrqjPV/OwNWmG+TnZDr3VGyChNnGMHccC1xkbxCHDQNnnXjxhMQNyHmdFJbmbRA== -"@esbuild/sunos-x64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.9.tgz#c132603a19ef79c0d7bd95afb09f41618ea8dda2" - integrity sha512-OetwTSsv6mIDLqN7I7I2oX9MmHGwG+AP+wKIHvq+6sIHwcPPJqRx+DJB55jy9JG13CWcdcQno/7V5MTJ5a0xfQ== +"@esbuild/sunos-x64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.10.tgz#31e5e4b814ef43d300e26511e486a4716a390d5f" + integrity sha512-z+q0xZ+et/7etz7WoMyXTHZ1rB8PMSNp/FOqURLJLOPb3GWJ2aj4oCqFCjPwEbW1rsT7JPpxeH/DwGAWk/I1Bg== -"@esbuild/win32-arm64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.9.tgz#bf74d007d7f0fe1fe32c4fff82d27b271b3e1d58" - integrity sha512-tKSSSK6unhxbGbHg+Cc+JhRzemkcsX0tPBvG0m5qsWbkShDK9c+/LSb13L18LWVdOQZwuA55Vbakxmt6OjBDOQ== +"@esbuild/win32-arm64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.10.tgz#ca58472dc03ca79e6d03f8a31113979ff253d94f" + integrity sha512-+YYu5sbQ9npkNT9Dec+tn1F/kjg6SMgr6bfi/6FpXYZvCRfu2YFPZGb+3x8K30s8eRxFpoG4sGhiSUkr1xbHEw== -"@esbuild/win32-ia32@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.9.tgz#e46478e77431bca1a8b80f6260fc6b0020aa8127" - integrity sha512-ZTQ5vhNS5gli0KK8I6/s6+LwXmNEfq1ftjnSVyyNm33dBw8zDpstqhGXYUbZSWWLvkqiRRjgxgmoncmi6Yy7Ng== +"@esbuild/win32-ia32@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.10.tgz#c572df2c65ab118feed0a5da5a4a193846d74e43" + integrity sha512-Aw7Fupk7XNehR1ftHGYwUteyJ2q+em/aE+fVU3YMTBN2V5A7Z4aVCSV+SvCp9HIIHZavPFBpbdP3VfjQpdf6Xg== -"@esbuild/win32-x64@0.16.9": - version "0.16.9" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.9.tgz#4595a29c2930c5157aa1be0963abbbac989647c9" - integrity sha512-C4ZX+YFIp6+lPrru3tpH6Gaapy8IBRHw/e7l63fzGDhn/EaiGpQgbIlT5paByyy+oMvRFQoxxyvC4LE0AjJMqQ== +"@esbuild/win32-x64@0.16.10": + version "0.16.10" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.10.tgz#0e9c6a5e69c10d96aff2386b7ee9646138c2a831" + integrity sha512-qddWullt3sC1EIpfHvCRBq3H4g3L86DZpD6n8k2XFjFVyp01D++uNbN1hT/JRsHxTbyyemZcpwL5aRlJwc/zFw== "@eslint/eslintrc@^1.4.0": version "1.4.0" @@ -5584,33 +5584,33 @@ esbuild@^0.15.6: esbuild-windows-64 "0.15.18" esbuild-windows-arm64 "0.15.18" -esbuild@^0.16.9: - version "0.16.9" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.9.tgz#01b6c3a6cbc072108253ac160a0734229bf8c921" - integrity sha512-gkH83yHyijMSZcZFs1IWew342eMdFuWXmQo3zkDPTre25LIPBJsXryg02M3u8OpTwCJdBkdaQwqKkDLnAsAeLQ== +esbuild@^0.16.10: + version "0.16.10" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.10.tgz#d485c28f1626a3f9c1796c952e4cd0561f0031bb" + integrity sha512-z5dIViHoVnw2l+NCJ3zj5behdXjYvXne9gL18OOivCadXDUhyDkeSvEtLcGVAJW2fNmh33TDUpsi704XYlDodw== optionalDependencies: - "@esbuild/android-arm" "0.16.9" - "@esbuild/android-arm64" "0.16.9" - "@esbuild/android-x64" "0.16.9" - "@esbuild/darwin-arm64" "0.16.9" - "@esbuild/darwin-x64" "0.16.9" - "@esbuild/freebsd-arm64" "0.16.9" - "@esbuild/freebsd-x64" "0.16.9" - "@esbuild/linux-arm" "0.16.9" - "@esbuild/linux-arm64" "0.16.9" - "@esbuild/linux-ia32" "0.16.9" - "@esbuild/linux-loong64" "0.16.9" - "@esbuild/linux-mips64el" "0.16.9" - "@esbuild/linux-ppc64" "0.16.9" - "@esbuild/linux-riscv64" "0.16.9" - "@esbuild/linux-s390x" "0.16.9" - "@esbuild/linux-x64" "0.16.9" - "@esbuild/netbsd-x64" "0.16.9" - "@esbuild/openbsd-x64" "0.16.9" - "@esbuild/sunos-x64" "0.16.9" - "@esbuild/win32-arm64" "0.16.9" - "@esbuild/win32-ia32" "0.16.9" - "@esbuild/win32-x64" "0.16.9" + "@esbuild/android-arm" "0.16.10" + "@esbuild/android-arm64" "0.16.10" + "@esbuild/android-x64" "0.16.10" + "@esbuild/darwin-arm64" "0.16.10" + "@esbuild/darwin-x64" "0.16.10" + "@esbuild/freebsd-arm64" "0.16.10" + "@esbuild/freebsd-x64" "0.16.10" + "@esbuild/linux-arm" "0.16.10" + "@esbuild/linux-arm64" "0.16.10" + "@esbuild/linux-ia32" "0.16.10" + "@esbuild/linux-loong64" "0.16.10" + "@esbuild/linux-mips64el" "0.16.10" + "@esbuild/linux-ppc64" "0.16.10" + "@esbuild/linux-riscv64" "0.16.10" + "@esbuild/linux-s390x" "0.16.10" + "@esbuild/linux-x64" "0.16.10" + "@esbuild/netbsd-x64" "0.16.10" + "@esbuild/openbsd-x64" "0.16.10" + "@esbuild/sunos-x64" "0.16.10" + "@esbuild/win32-arm64" "0.16.10" + "@esbuild/win32-ia32" "0.16.10" + "@esbuild/win32-x64" "0.16.10" escalade@^3.1.1: version "3.1.1" From 6ce7e8ee532f7680d6fe6a8d1ab163c4f58308a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 08:37:43 -0500 Subject: [PATCH 07/21] Bump sass from 1.57.0 to 1.57.1 (#6793) Bumps [sass](https://github.com/sass/dart-sass) from 1.57.0 to 1.57.1. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.57.0...1.57.1) --- updated-dependencies: - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3429716034..cb37a0685e 100644 --- a/package.json +++ b/package.json @@ -392,7 +392,7 @@ "react-select-event": "^5.5.1", "react-table": "^7.8.0", "react-window": "^1.8.8", - "sass": "^1.57.0", + "sass": "^1.57.1", "sass-loader": "^12.6.0", "sharp": "^0.31.2", "style-loader": "^3.3.1", diff --git a/yarn.lock b/yarn.lock index 06411f098e..b94098bb0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11299,10 +11299,10 @@ sass-loader@^12.6.0: klona "^2.0.4" neo-async "^2.6.2" -sass@^1.32.13, sass@^1.57.0: - version "1.57.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.57.0.tgz#64c4144ed4e1c0ccb96dc18aef2c424cdbc0c12b" - integrity sha512-IZNEJDTK1cF5B1cGA593TPAV/1S0ysUDxq9XHjX/+SMy0QfUny+nfUsq5ZP7wWSl4eEf7wDJcEZ8ABYFmh3m/w== +sass@^1.32.13, sass@^1.57.1: + version "1.57.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.57.1.tgz#dfafd46eb3ab94817145e8825208ecf7281119b5" + integrity sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" From 257582df76be60034c738af2271f20b8eaad141f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 08:38:04 -0500 Subject: [PATCH 08/21] Bump typedoc from 0.23.22 to 0.23.23 (#6792) Bumps [typedoc](https://github.com/TypeStrong/TypeDoc) from 0.23.22 to 0.23.23. - [Release notes](https://github.com/TypeStrong/TypeDoc/releases) - [Changelog](https://github.com/TypeStrong/typedoc/blob/master/CHANGELOG.md) - [Commits](https://github.com/TypeStrong/TypeDoc/compare/v0.23.22...v0.23.23) --- updated-dependencies: - dependency-name: typedoc dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index cb37a0685e..b0b341bd64 100644 --- a/package.json +++ b/package.json @@ -402,7 +402,7 @@ "ts-node": "^10.9.1", "type-fest": "^2.14.0", "typed-emitter": "^1.4.0", - "typedoc": "0.23.22", + "typedoc": "0.23.23", "typedoc-plugin-markdown": "^3.13.6", "typescript": "^4.9.4", "typescript-plugin-css-modules": "^3.4.0", diff --git a/yarn.lock b/yarn.lock index b94098bb0b..9a876289b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8922,7 +8922,7 @@ markdown@^0.5.0: dependencies: nopt "~2.1.1" -marked@^4.0.19, marked@^4.2.4: +marked@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.4.tgz#5a4ce6c7a1ae0c952601fce46376ee4cf1797e1c" integrity sha512-Wcc9ikX7Q5E4BYDPvh1C6QNSxrjC9tBgz+A/vAhp59KXUgachw++uMvMKiSW8oA85nopmPZcEvBoex/YLMsiyA== @@ -9088,10 +9088,10 @@ minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatc dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.0, minimatch@^5.0.1, minimatch@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" - integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== +minimatch@^5.0.0, minimatch@^5.0.1, minimatch@^5.1.0, minimatch@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.1.tgz#6c9dffcf9927ff2a31e74b5af11adf8b9604b022" + integrity sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g== dependencies: brace-expansion "^2.0.1" @@ -12592,14 +12592,14 @@ typedoc-plugin-markdown@^3.13.6: dependencies: handlebars "^4.7.7" -typedoc@0.23.22: - version "0.23.22" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.23.22.tgz#e25281ca816cd92ecfdaf3ec336d27e7bebb69ac" - integrity sha512-5sJkjK60xp8A7YpcYniu3+Wf0QcgojEnhzHuCN+CkdpQkKRhOspon/9+sGTkGI8kjVkZs3KHrhltpQyVhRMVfw== +typedoc@0.23.23: + version "0.23.23" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.23.23.tgz#9cf95b03d2d40031d8978b55e88b0b968d69f512" + integrity sha512-cg1YQWj+/BU6wq74iott513U16fbrPCbyYs04PHZgvoKJIc6EY4xNobyDZh4KMfRGW8Yjv6wwIzQyoqopKOUGw== dependencies: lunr "^2.3.9" - marked "^4.0.19" - minimatch "^5.1.0" + marked "^4.2.4" + minimatch "^5.1.1" shiki "^0.11.1" typescript-plugin-css-modules@^3.4.0: From e36f3d2d7043dc60d78cfcbca93aa67cab6bb3d2 Mon Sep 17 00:00:00 2001 From: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> Date: Tue, 20 Dec 2022 15:38:20 +0200 Subject: [PATCH 09/21] Navigation logging injectable (#6797) Move navigation logging to `setupLoggingForNavigationInjectable` to prevent cycle of injectables from occurring. Wasn't eventually needed for #6795 but still an improvement. Credit for the implementation goes to @Nokel81 , thanks! Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> Signed-off-by: Sami Tiilikainen <97873007+samitiilikainen@users.noreply.github.com> --- .../observable-history.injectable.ts | 9 ----- ...setup-logging-for-navigation.injectable.ts | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 src/renderer/navigation/setup-logging-for-navigation.injectable.ts diff --git a/src/renderer/navigation/observable-history.injectable.ts b/src/renderer/navigation/observable-history.injectable.ts index 81d9df2dd1..c813c675fd 100644 --- a/src/renderer/navigation/observable-history.injectable.ts +++ b/src/renderer/navigation/observable-history.injectable.ts @@ -4,7 +4,6 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { createObservableHistory } from "mobx-observable-history"; -import loggerInjectable from "../../common/logger.injectable"; import { searchParamsOptions } from "./search-params"; import historyInjectable from "./history.injectable"; @@ -13,18 +12,10 @@ const observableHistoryInjectable = getInjectable({ instantiate: (di) => { const history = di.inject(historyInjectable); - const logger = di.inject(loggerInjectable); const navigation = createObservableHistory(history, { searchParams: searchParamsOptions, }); - navigation.listen((location, action) => { - const isClusterView = !process.isMainFrame; - const domain = global.location.href; - - logger.debug(`[NAVIGATION]: ${action}-ing. Current is now:`, { isClusterView, domain, location }); - }); - return navigation; }, }); diff --git a/src/renderer/navigation/setup-logging-for-navigation.injectable.ts b/src/renderer/navigation/setup-logging-for-navigation.injectable.ts new file mode 100644 index 0000000000..b769b92db1 --- /dev/null +++ b/src/renderer/navigation/setup-logging-for-navigation.injectable.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import loggerInjectable from "../../common/logger.injectable"; +import { beforeFrameStartsInjectionToken } from "../before-frame-starts/tokens"; +import observableHistoryInjectable from "./observable-history.injectable"; + +const setupLoggingForNavigationInjectable = getInjectable({ + id: "setup-logging-for-navigation", + instantiate: (di) => ({ + id: "setup-logging-for-navigation", + run: () => { + const logger = di.inject(loggerInjectable); + const observableHistory = di.inject(observableHistoryInjectable); + + observableHistory.listen((location, action) => { + const isClusterView = !process.isMainFrame; + const domain = global.location.href; + + logger.debug(`[NAVIGATION]: ${action}-ing. Current is now:`, { + isClusterView, + domain, + location, + }); + }); + }, + }), + injectionToken: beforeFrameStartsInjectionToken, +}); + +export default setupLoggingForNavigationInjectable; From 443081493b2146d168438cdb2961504ce41e3114 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 20 Dec 2022 07:20:27 -0800 Subject: [PATCH 10/21] Fix allowed resources checks on GKE (#6657) * Add check for incomplete SelfSubjectRulesReview to fix GKE Signed-off-by: Sebastian Malton * Adding namespaced for KubeApiResource Signed-off-by: Sebastian Malton * Refactoring of AuthorizationNamespaceReview Signed-off-by: Sebastian Malton * Removing dead code Signed-off-by: Sebastian Malton * Refactoring ListApiResources Signed-off-by: Sebastian Malton * Extract ClusterContext into deps for KubeObjectStore to fix circular import Signed-off-by: Sebastian Malton * Fix remaining type errors Signed-off-by: Sebastian Malton * Fix crash in frame by consolidating setup into runnables Signed-off-by: Sebastian Malton * Fix type errors and remove dead code Signed-off-by: Sebastian Malton * Fix core resources not showing up Signed-off-by: Sebastian Malton * Fix namespaces not being shown Signed-off-by: Sebastian Malton * Simplify ClusterContext to remove something only NamespaceStore needs Signed-off-by: Sebastian Malton * Make sure the public API doesn't change Signed-off-by: Sebastian Malton * Fix lint Signed-off-by: Sebastian Malton * Fixing namespace-select-filter tests Signed-off-by: Sebastian Malton * Fix other tests requiring overrides Signed-off-by: Sebastian Malton * Fix kludge in cluster-frame tests Signed-off-by: Sebastian Malton * Fix remaining test failures Signed-off-by: Sebastian Malton * Fix integration test due to incorrect casting Signed-off-by: Sebastian Malton * Fix integration test and kube watches not working at all Signed-off-by: Sebastian Malton * Fix secret details test Signed-off-by: Sebastian Malton * Fix lint Signed-off-by: Sebastian Malton * Fix non-ApplicationBuilder tests by adding overrides Signed-off-by: Sebastian Malton * Fix crash due to trying to read hostedCluster too soon Signed-off-by: Sebastian Malton * Fix crash due to timing issues - Make injectable phases more explicit for renderer Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- .../allowed-resources-injection-token.ts | 5 +- ...thorization-namespace-review.injectable.ts | 87 -- src/common/cluster/cluster.ts | 129 +-- src/common/cluster/is-allowed-resource.ts | 20 - .../cluster/list-api-resources.injectable.ts | 91 -- .../request-api-resources.injectable.ts | 83 ++ ...t-namespace-list-permissions.injectable.ts | 78 ++ src/common/configure-packages.ts | 27 - .../config-maps-route.injectable.ts | 21 +- ...zontal-pod-autoscalers-route.injectable.ts | 19 +- .../config/leases/leases-route.injectable.ts | 19 +- .../limit-ranges-route.injectable.ts | 22 +- ...pod-disruption-budgets-route.injectable.ts | 19 +- .../priority-classes-route.injectable.ts | 19 +- .../resource-quotas-route.injectable.ts | 19 +- .../runtime-classes-route.injectable.ts | 19 +- .../secrets/secrets-route.injectable.ts | 19 +- .../cluster/events/events-route.injectable.ts | 19 +- .../namespaces/namespaces-route.injectable.ts | 19 +- .../endpoints/endpoints-route.injectable.ts | 19 +- .../ingresses/ingresses-route.injectable.ts | 26 +- .../network-policies-route.injectable.ts | 19 +- .../services/services-route.injectable.ts | 19 +- .../cluster/nodes/nodes-route.injectable.ts | 19 +- .../cluster-overview-route.injectable.ts | 19 +- ...rsistent-volume-claims-route.injectable.ts | 19 +- .../persistent-volumes-route.injectable.ts | 19 +- .../storage-classes-route.injectable.ts | 19 +- .../cluster-role-bindings-route.injectable.ts | 19 +- .../cluster-roles-route.injectable.ts | 19 +- .../pod-security-policies-route.injectable.ts | 9 +- .../role-bindings-route.injectable.ts | 9 +- .../roles/roles-route.injectable.ts | 19 +- .../service-accounts-route.injectable.ts | 19 +- .../cron-jobs/cron-jobs-route.injectable.ts | 19 +- .../daemonsets/daemonsets-route.injectable.ts | 19 +- .../deployments-route.injectable.ts | 19 +- .../workloads/jobs/jobs-route.injectable.ts | 19 +- .../workloads/pods/pods-route.injectable.ts | 19 +- .../replicasets-route.injectable.ts | 19 +- .../statefulsets-route.injectable.ts | 19 +- .../k8s-api/__tests__/api-manager.test.ts | 28 +- .../kube-api-version-detection.test.ts | 52 +- src/common/k8s-api/__tests__/kube-api.test.ts | 97 +-- .../__tests__/kube-object.store.test.ts | 23 +- .../auto-registration.injectable.ts | 74 -- .../k8s-api/api-manager/resource.store.ts | 5 +- src/common/k8s-api/cluster-context.ts | 13 - .../request-patch.injectable.ts | 29 +- .../request-update.injectable.ts | 25 +- src/common/k8s-api/json-api.ts | 4 +- src/common/k8s-api/kube-api.ts | 27 +- src/common/k8s-api/kube-object.store.ts | 106 +-- src/common/k8s-api/kube-object.ts | 15 +- src/common/logger.injectable.ts | 24 +- src/common/logger.ts | 4 +- src/common/rbac.ts | 219 ++++- src/common/utils/computed-or.ts | 11 + .../utils/is-allowed-resource.injectable.ts | 26 - src/common/utils/wait.ts | 13 +- src/common/vars.ts | 20 - src/extensions/common-api/k8s-api.ts | 44 +- src/extensions/renderer-api/k8s-api.ts | 20 +- .../catalog/opening-entity-details.test.tsx | 2 +- .../delete-cluster-dialog.test.tsx | 2 - .../edit-namespace-from-new-tab.test.tsx | 5 +- ...espace-from-previously-opened-tab.test.tsx | 5 +- .../renderer/setup-sync.injectable.ts | 4 +- .../cluster/store/renderer/init.injectable.ts | 4 +- .../visibility-of-sidebar-items.test.tsx | 29 +- .../cluster/workload-overview.test.tsx | 5 +- ...owing-settings-for-correct-entity.test.tsx | 2 +- .../renderer/init-store.injectable.ts | 4 +- .../hotbar/store/renderer/init.injectable.ts | 4 +- .../renderer/initialize.injectable.ts | 4 +- src/jest.setup.ts | 9 +- src/main/__test__/cluster.test.ts | 7 +- src/main/cluster/manager.ts | 2 +- .../allowed-resources.injectable.ts | 13 +- .../create-cluster.injectable.ts | 8 +- .../create-lens-window.injectable.ts | 5 +- .../load-monaco-themes.injectable.ts | 4 +- .../setup-auto-registration.injectable.ts | 12 +- ...up-current-cluster-broadcast.injectable.ts | 4 +- ...tes-cluster-catalog-add-menu.injectable.ts | 4 +- ...es-cluster-context-menu-open.injectable.ts | 4 +- .../setup-root-mac-class.injectable.ts | 4 +- .../runnables/setup-sentry.injectable.ts | 4 +- ...p-weblink-context-menu-open.injectable.tsx | 4 +- src/renderer/before-frame-starts/tokens.ts | 24 +- .../allowed-resources.injectable.ts | 25 - .../cluster-frame-context.injectable.ts | 25 - .../cluster-frame-context.ts | 48 +- ...for-cluster-scoped-resources.injectable.ts | 21 + .../for-namespaced-resources.injectable.ts | 64 ++ .../should-show-resource.injectable.ts | 27 + .../accessible-namespaces.injectable.ts | 18 + .../create-cluster.injectable.ts | 4 +- .../cluster-overview-store.injectable.ts | 2 + .../cluster-overview-store.ts | 5 +- .../+config-autoscalers/store.injectable.ts | 5 +- .../+config-leases/store.injectable.ts | 5 +- .../+config-limit-ranges/store.injectable.ts | 5 +- .../+config-maps/store.injectable.ts | 5 +- .../store.injectable.ts | 5 +- .../store.injectable.ts | 5 +- .../store.injectable.ts | 5 +- .../store.injectable.ts | 5 +- .../__tests__/secret-details.test.tsx | 16 + .../+config-secrets/store.injectable.ts | 5 +- .../definition.store.injectable.ts | 2 + .../+custom-resources/definition.store.ts | 6 +- .../components/+events/store.injectable.ts | 2 + src/renderer/components/+events/store.ts | 8 +- .../+helm-releases/releases.injectable.ts | 4 +- .../namespace-select-filter.test.tsx.snap | 806 +++++++++--------- .../namespace-select-filter.test.tsx | 273 +++--- .../+namespaces/store.injectable.ts | 4 + src/renderer/components/+namespaces/store.ts | 74 +- .../+network-endpoints/store.injectable.ts | 5 +- .../+network-ingresses/store.injectable.ts | 5 +- .../+network-policies/store.injectable.ts | 5 +- .../+network-services/store.injectable.ts | 5 +- .../components/+nodes/store.injectable.ts | 5 +- src/renderer/components/+nodes/store.ts | 6 +- .../store.injectable.ts | 5 +- .../+storage-classes/store.injectable.ts | 2 + .../components/+storage-classes/store.ts | 6 +- .../store.injectable.ts | 5 +- .../+storage-volumes/store.injectable.ts | 5 +- .../__tests__/dialog.test.tsx | 16 +- .../store.injectable.ts | 5 +- .../+cluster-roles/store.injectable.ts | 5 +- .../+role-bindings/__tests__/dialog.test.tsx | 16 +- .../+role-bindings/store.injectable.ts | 5 +- .../+roles/store.injectable.ts | 5 +- .../+service-accounts/store.injectable.ts | 5 +- .../+workloads-cronjobs/store.injectable.ts | 2 + .../components/+workloads-cronjobs/store.ts | 6 +- .../+workloads-daemonsets/store.injectable.ts | 2 + .../components/+workloads-daemonsets/store.ts | 6 +- .../store.injectable.ts | 2 + .../+workloads-deployments/store.ts | 6 +- .../+workloads-jobs/store.injectable.ts | 2 + .../components/+workloads-jobs/store.ts | 6 +- .../+workloads-overview/overview-statuses.tsx | 3 +- .../+workloads-overview/overview.tsx | 8 +- .../cron-jobs-workload.injectable.ts | 8 +- .../daemonsets-workload.injectable.ts | 5 +- .../deployments-workload.injectable.ts | 5 +- .../jobs-workload.injectable.ts | 5 +- .../pods-workload.injectable.ts | 5 +- .../replicasets-workload.injectable.ts | 5 +- .../statefulsets-workload.injectable.ts | 5 +- .../workloads/workload-injection-token.ts | 3 +- .../workloads/workloads.injectable.ts | 32 +- .../+workloads-pods/store.injectable.ts | 2 + .../components/+workloads-pods/store.ts | 6 +- .../store.injectable.ts | 2 + .../+workloads-replicasets/store.ts | 8 +- .../store.injectable.ts | 2 + .../+workloads-statefulsets/store.ts | 6 +- .../__tests__/cronjob.store.test.ts | 16 + .../__tests__/daemonset.store.test.ts | 16 + .../__tests__/deployments.store.test.ts | 16 + .../components/__tests__/job.store.test.ts | 16 + .../components/__tests__/pods.store.test.ts | 16 + .../__tests__/replicaset.store.test.ts | 16 + .../__tests__/statefulset.store.test.ts | 16 + .../accessible-namespaces.tsx | 4 +- .../components/dock/create-resource/view.tsx | 62 +- .../components/input/search-input.tsx | 22 +- .../kube-object-list-layout.tsx | 8 +- .../top-bar/start-state-sync.injectable.ts | 4 +- .../monaco-themes/clouds-midnight.json | 128 --- .../test-utils/get-application-builder.tsx | 38 +- .../cluster-frame/cluster-frame.test.tsx | 8 +- .../init-cluster-frame.injectable.ts | 6 +- .../init-cluster-frame/init-cluster-frame.ts | 22 +- .../root-frame/setup-system-ca.injectable.ts | 4 +- src/renderer/initializers/workload-events.tsx | 7 +- .../kube-watch-api.injectable.ts | 4 +- src/renderer/kube-watch-api/kube-watch-api.ts | 10 +- .../subscribe-stores.injectable.ts | 1 - src/renderer/navigation/events.ts | 32 +- .../start-frame/start-frame.injectable.ts | 24 +- .../stores/init-user-store.injectable.ts | 4 +- .../setup-apply-active-theme.injectable.ts | 4 +- .../start-listening-of-channels.injectable.ts | 4 +- src/renderer/utils/rbac.ts | 7 +- ...nitial-values-for-sync-boxes.injectable.ts | 4 +- .../vars/build-version/init.injectable.ts | 4 +- .../default-update-channel/init.injectable.ts | 4 +- .../vars/release-channel/init.injectable.ts | 4 +- .../semantic-build-version/init.injectable.ts | 4 +- src/test-utils/mock-responses.ts | 62 ++ 196 files changed, 2379 insertions(+), 2146 deletions(-) delete mode 100644 src/common/cluster/authorization-namespace-review.injectable.ts delete mode 100644 src/common/cluster/is-allowed-resource.ts delete mode 100644 src/common/cluster/list-api-resources.injectable.ts create mode 100644 src/common/cluster/request-api-resources.injectable.ts create mode 100644 src/common/cluster/request-namespace-list-permissions.injectable.ts delete mode 100644 src/common/configure-packages.ts delete mode 100644 src/common/k8s-api/api-manager/auto-registration.injectable.ts delete mode 100644 src/common/k8s-api/cluster-context.ts create mode 100644 src/common/utils/computed-or.ts delete mode 100644 src/common/utils/is-allowed-resource.injectable.ts delete mode 100644 src/renderer/cluster-frame-context/allowed-resources.injectable.ts delete mode 100644 src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts create mode 100644 src/renderer/cluster-frame-context/for-cluster-scoped-resources.injectable.ts create mode 100644 src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts create mode 100644 src/renderer/cluster-frame-context/should-show-resource.injectable.ts create mode 100644 src/renderer/cluster/accessible-namespaces.injectable.ts rename src/renderer/{create-cluster => cluster}/create-cluster.injectable.ts (92%) delete mode 100644 src/renderer/components/monaco-editor/monaco-themes/clouds-midnight.json create mode 100644 src/test-utils/mock-responses.ts diff --git a/src/common/cluster-store/allowed-resources-injection-token.ts b/src/common/cluster-store/allowed-resources-injection-token.ts index 353d0b309c..5b71038d04 100644 --- a/src/common/cluster-store/allowed-resources-injection-token.ts +++ b/src/common/cluster-store/allowed-resources-injection-token.ts @@ -5,7 +5,8 @@ import { getInjectionToken } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; +import type { KubeApiResourceDescriptor } from "../rbac"; -export const allowedResourcesInjectionToken = getInjectionToken>>({ - id: "allowed-resources", +export const shouldShowResourceInjectionToken = getInjectionToken, KubeApiResourceDescriptor>({ + id: "should-show-resource", }); diff --git a/src/common/cluster/authorization-namespace-review.injectable.ts b/src/common/cluster/authorization-namespace-review.injectable.ts deleted file mode 100644 index aa78453569..0000000000 --- a/src/common/cluster/authorization-namespace-review.injectable.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { KubeConfig } from "@kubernetes/client-node"; -import { AuthorizationV1Api } from "@kubernetes/client-node"; -import { getInjectable } from "@ogre-tools/injectable"; -import type { Logger } from "../logger"; -import loggerInjectable from "../logger.injectable"; -import type { KubeApiResource } from "../rbac"; - -/** - * Requests the permissions for actions on the kube cluster - * @param namespace The namespace of the resources - * @param availableResources List of available resources in the cluster to resolve glob values fir api groups - * @returns list of allowed resources names - */ -export type RequestNamespaceResources = (namespace: string, availableResources: KubeApiResource[]) => Promise; - -/** - * @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster - */ -export type AuthorizationNamespaceReview = (proxyConfig: KubeConfig) => RequestNamespaceResources; - -interface Dependencies { - logger: Logger; -} - -const authorizationNamespaceReview = ({ logger }: Dependencies): AuthorizationNamespaceReview => { - return (proxyConfig) => { - - const api = proxyConfig.makeApiClient(AuthorizationV1Api); - - return async (namespace, availableResources) => { - try { - const { body } = await api.createSelfSubjectRulesReview({ - apiVersion: "authorization.k8s.io/v1", - kind: "SelfSubjectRulesReview", - spec: { namespace }, - }); - - const resources = new Set(); - - body.status?.resourceRules.forEach(resourceRule => { - if (!resourceRule.verbs.some(verb => ["*", "list"].includes(verb)) || !resourceRule.resources) { - return; - } - - const apiGroups = resourceRule.apiGroups; - - if (resourceRule.resources.length === 1 && resourceRule.resources[0] === "*" && apiGroups) { - if (apiGroups[0] === "*") { - availableResources.forEach(resource => resources.add(resource.apiName)); - } else { - availableResources.forEach((apiResource)=> { - if (apiGroups.includes(apiResource.group || "")) { - resources.add(apiResource.apiName); - } - }); - } - } else { - resourceRule.resources.forEach(resource => resources.add(resource)); - } - - }); - - return [...resources]; - } catch (error) { - logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review: ${error}`, { namespace }); - - return []; - } - }; - }; -}; - -const authorizationNamespaceReviewInjectable = getInjectable({ - id: "authorization-namespace-review", - instantiate: (di) => { - const logger = di.inject(loggerInjectable); - - return authorizationNamespaceReview({ logger }); - }, -}); - -export default authorizationNamespaceReviewInjectable; diff --git a/src/common/cluster/cluster.ts b/src/common/cluster/cluster.ts index fe66c9fe1b..303ee89361 100644 --- a/src/common/cluster/cluster.ts +++ b/src/common/cluster/cluster.ts @@ -9,8 +9,8 @@ import type { KubeConfig } from "@kubernetes/client-node"; import { HttpError } from "@kubernetes/client-node"; import type { Kubectl } from "../../main/kubectl/kubectl"; import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager"; -import type { KubeApiResource, KubeResource } from "../rbac"; -import { apiResourceRecord, apiResources } from "../rbac"; +import type { KubeApiResource, KubeApiResourceDescriptor } from "../rbac"; +import { formatKubeApiResource } from "../rbac"; import type { VersionDetector } from "../../main/cluster-detectors/version-detector"; import type { DetectorRegistry } from "../../main/cluster-detectors/detector-registry"; import plimit from "p-limit"; @@ -25,8 +25,8 @@ import assert from "assert"; import type { Logger } from "../logger"; import type { BroadcastMessage } from "../ipc/broadcast-message.injectable"; import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable"; -import type { RequestNamespaceResources } from "./authorization-namespace-review.injectable"; -import type { RequestListApiResources } from "./list-api-resources.injectable"; +import type { CanListResource, RequestNamespaceListPermissions, RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable"; +import type { RequestApiResources } from "./request-api-resources.injectable"; export interface ClusterDependencies { readonly directoryForKubeConfigs: string; @@ -36,8 +36,8 @@ export interface ClusterDependencies { createContextHandler: (cluster: Cluster) => ClusterContextHandler; createKubectl: (clusterVersion: string) => Kubectl; createAuthorizationReview: (config: KubeConfig) => CanI; - createAuthorizationNamespaceReview: (config: KubeConfig) => RequestNamespaceResources; - createListApiResources: (cluster: Cluster) => RequestListApiResources; + requestApiResources: RequestApiResources; + requestNamespaceListPermissionsFor: RequestNamespaceListPermissionsFor; createListNamespaces: (config: KubeConfig) => ListNamespaces; createVersionDetector: (cluster: Cluster) => VersionDetector; broadcastMessage: BroadcastMessage; @@ -49,7 +49,7 @@ export interface ClusterDependencies { * * @beta */ -export class Cluster implements ClusterModel, ClusterState { +export class Cluster implements ClusterModel { /** Unique id for a cluster */ public readonly id: ClusterId; private kubeCtl: Kubectl | undefined; @@ -62,7 +62,6 @@ export class Cluster implements ClusterModel, ClusterState { protected readonly _proxyKubeconfigManager: KubeconfigManager | undefined; protected readonly eventsDisposer = disposer(); protected activated = false; - private readonly resourceAccessStatuses = new Map(); public get contextHandler() { // TODO: remove these once main/renderer are seperate classes @@ -163,25 +162,21 @@ export class Cluster implements ClusterModel, ClusterState { * @observable */ @observable metadata: ClusterMetadata = {}; + /** * List of allowed namespaces verified via K8S::SelfSubjectAccessReview api - * - * @observable */ - @observable allowedNamespaces: string[] = []; - /** - * List of allowed resources - * - * @observable - * @internal - */ - @observable allowedResources: string[] = []; + readonly allowedNamespaces = observable.array(); + /** * List of accessible namespaces provided by user in the Cluster Settings - * - * @observable */ - @observable accessibleNamespaces: string[] = []; + readonly accessibleNamespaces = observable.array(); + + private readonly knownResources = observable.array(); + + // The formatting of this is `group.name` or `name` (if in core) + private readonly allowedResources = observable.set(); /** * Labels for the catalog entity @@ -299,7 +294,7 @@ export class Cluster implements ClusterModel, ClusterState { } if (model.accessibleNamespaces) { - this.accessibleNamespaces = model.accessibleNamespaces; + this.accessibleNamespaces.replace(model.accessibleNamespaces); } if (model.labels) { @@ -433,8 +428,7 @@ export class Cluster implements ClusterModel, ClusterState { this.accessible = false; this.ready = false; this.activated = false; - this.allowedNamespaces = []; - this.resourceAccessStatuses.clear(); + this.allowedNamespaces.clear(); this.dependencies.logger.info(`[CLUSTER]: disconnected`, { id: this.id }); } @@ -474,8 +468,7 @@ export class Cluster implements ClusterModel, ClusterState { this.dependencies.logger.info(`[CLUSTER]: refreshAccessibility`, this.getMeta()); const proxyConfig = await this.getProxyKubeconfig(); const canI = this.dependencies.createAuthorizationReview(proxyConfig); - const requestNamespaceResources = this.dependencies.createAuthorizationNamespaceReview(proxyConfig); - const listApiResources = this.dependencies.createListApiResources(this); + const requestNamespaceListPermissions = this.dependencies.requestNamespaceListPermissionsFor(proxyConfig); this.isAdmin = await canI({ namespace: "kube-system", @@ -486,8 +479,9 @@ export class Cluster implements ClusterModel, ClusterState { verb: "watch", resource: "*", }); - this.allowedNamespaces = await this.getAllowedNamespaces(proxyConfig); - this.allowedResources = await this.getAllowedResources(listApiResources, requestNamespaceResources); + this.allowedNamespaces.replace(await this.requestAllowedNamespaces(proxyConfig)); + this.knownResources.replace(await this.dependencies.requestApiResources(this)); + this.allowedResources.replace(await this.getAllowedResources(requestNamespaceListPermissions)); this.ready = true; } @@ -600,7 +594,7 @@ export class Cluster implements ClusterModel, ClusterState { accessible: this.accessible, isAdmin: this.isAdmin, allowedNamespaces: this.allowedNamespaces, - allowedResources: this.allowedResources, + allowedResources: [...this.allowedResources], isGlobalWatchEnabled: this.isGlobalWatchEnabled, }); } @@ -611,8 +605,8 @@ export class Cluster implements ClusterModel, ClusterState { */ @action setState(state: ClusterState) { this.accessible = state.accessible; - this.allowedNamespaces = state.allowedNamespaces; - this.allowedResources = state.allowedResources; + this.allowedNamespaces.replace(state.allowedNamespaces); + this.allowedResources.replace(state.allowedResources); this.apiUrl = state.apiUrl; this.disconnected = state.disconnected; this.isAdmin = state.isAdmin; @@ -644,7 +638,7 @@ export class Cluster implements ClusterModel, ClusterState { this.dependencies.broadcastMessage(`cluster:${this.id}:connection-update`, update); } - protected async getAllowedNamespaces(proxyConfig: KubeConfig) { + protected async requestAllowedNamespaces(proxyConfig: KubeConfig) { if (this.accessibleNamespaces.length) { return this.accessibleNamespaces; } @@ -668,69 +662,28 @@ export class Cluster implements ClusterModel, ClusterState { } } - protected async getAllowedResources(listApiResources:RequestListApiResources, requestNamespaceResources: RequestNamespaceResources) { + protected async getAllowedResources(requestNamespaceListPermissions: RequestNamespaceListPermissions) { + if (!this.allowedNamespaces.length) { + return []; + } + try { - if (!this.allowedNamespaces.length) { - return []; - } + const apiLimit = plimit(5); // 5 concurrent api requests + const canListResourceCheckers = await Promise.all(( + this.allowedNamespaces.map(namespace => apiLimit(() => requestNamespaceListPermissions(namespace))) + )); + const canListNamespacedResource: CanListResource = (resource) => canListResourceCheckers.some(fn => fn(resource)); - const unknownResources = new Map(apiResources.map(resource => ([resource.apiName, resource]))); - - const availableResources = await listApiResources(); - const availableResourcesNames = new Set(availableResources.map(apiResource => apiResource.apiName)); - - [...unknownResources.values()].map(unknownResource => { - if (!availableResourcesNames.has(unknownResource.apiName)) { - this.resourceAccessStatuses.set(unknownResource, false); - unknownResources.delete(unknownResource.apiName); - } - }); - - if (unknownResources.size > 0) { - const apiLimit = plimit(5); // 5 concurrent api requests - - await Promise.all(this.allowedNamespaces.map(namespace => apiLimit(async () => { - if (unknownResources.size === 0) { - return; - } - - const namespaceResources = await requestNamespaceResources(namespace, availableResources); - - for (const resourceName of namespaceResources) { - const unknownResource = unknownResources.get(resourceName); - - if (unknownResource) { - this.resourceAccessStatuses.set(unknownResource, true); - unknownResources.delete(resourceName); - } - } - }))); - - for (const forbiddenResource of unknownResources.values()) { - this.resourceAccessStatuses.set(forbiddenResource, false); - } - } - - return apiResources - .filter((resource) => this.resourceAccessStatuses.get(resource)) - .map(apiResource => apiResource.apiName); + return this.knownResources + .filter(canListNamespacedResource) + .map(formatKubeApiResource); } catch (error) { return []; } } - isAllowedResource(kind: string): boolean { - if ((kind as KubeResource) in apiResourceRecord) { - return this.allowedResources.includes(kind); - } - - const apiResource = apiResources.find(resource => resource.kind === kind); - - if (apiResource) { - return this.allowedResources.includes(apiResource.apiName); - } - - return true; // allowed by default for other resources + shouldShowResource(resource: KubeApiResourceDescriptor): boolean { + return this.allowedResources.has(formatKubeApiResource(resource)); } isMetricHidden(resource: ClusterMetricsResourceType): boolean { diff --git a/src/common/cluster/is-allowed-resource.ts b/src/common/cluster/is-allowed-resource.ts deleted file mode 100644 index 7a6a392f78..0000000000 --- a/src/common/cluster/is-allowed-resource.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import type { KubeResource } from "../rbac"; -import { apiResourceRecord, apiResources } from "../rbac"; - -export const isAllowedResource = (allowedResources: string[]) => (kind: string): boolean => { - if ((kind as KubeResource) in apiResourceRecord) { - return allowedResources.includes(kind); - } - - const apiResource = apiResources.find(resource => resource.kind === kind); - - if (apiResource) { - return allowedResources.includes(apiResource.apiName); - } - - return true; // allowed by default for other resources -}; diff --git a/src/common/cluster/list-api-resources.injectable.ts b/src/common/cluster/list-api-resources.injectable.ts deleted file mode 100644 index ed9d5c9c39..0000000000 --- a/src/common/cluster/list-api-resources.injectable.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { - V1APIGroupList, - V1APIResourceList, - V1APIVersions, -} from "@kubernetes/client-node"; -import { getInjectable } from "@ogre-tools/injectable"; -import type { K8sRequest } from "../../main/k8s-request.injectable"; -import k8SRequestInjectable from "../../main/k8s-request.injectable"; -import type { Logger } from "../logger"; -import loggerInjectable from "../logger.injectable"; -import type { KubeApiResource, KubeResource } from "../rbac"; -import type { Cluster } from "./cluster"; -import plimit from "p-limit"; - -export type RequestListApiResources = () => Promise; - -/** - * @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster - */ -export type ListApiResources = (cluster: Cluster) => RequestListApiResources; - -interface Dependencies { - logger: Logger; - k8sRequest: K8sRequest; -} - -const listApiResources = ({ k8sRequest, logger }: Dependencies): ListApiResources => { - return (cluster) => { - const clusterRequest = (path: string) => k8sRequest(cluster, path); - const apiLimit = plimit(5); - - return async () => { - const resources: KubeApiResource[] = []; - - try { - const resourceListGroups:{ group:string;path:string }[] = []; - - await Promise.all( - [ - clusterRequest("/api").then((response:V1APIVersions)=>response.versions.forEach(version => resourceListGroups.push({ group:version, path:`/api/${version}` }))), - clusterRequest("/apis").then((response:V1APIGroupList) => response.groups.forEach(group => { - const preferredVersion = group.preferredVersion?.groupVersion; - - if (preferredVersion) { - resourceListGroups.push({ group:group.name, path:`/apis/${preferredVersion}` }); - } - })), - ], - ); - - await Promise.all( - resourceListGroups.map(({ group, path }) => apiLimit(async () => { - const apiResources:V1APIResourceList = await clusterRequest(path); - - if (apiResources.resources) { - resources.push( - ...apiResources.resources.filter(resource => resource.verbs.includes("list")).map((resource) => ({ - apiName: resource.name as KubeResource, - kind: resource.kind, - group, - })), - ); - } - }), - ), - ); - } catch (error) { - logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`); - } - - return resources; - }; - }; -}; - -const listApiResourcesInjectable = getInjectable({ - id: "list-api-resources", - instantiate: (di) => { - const k8sRequest = di.inject(k8SRequestInjectable); - const logger = di.inject(loggerInjectable); - - return listApiResources({ k8sRequest, logger }); - }, -}); - -export default listApiResourcesInjectable; diff --git a/src/common/cluster/request-api-resources.injectable.ts b/src/common/cluster/request-api-resources.injectable.ts new file mode 100644 index 0000000000..3e1a621f39 --- /dev/null +++ b/src/common/cluster/request-api-resources.injectable.ts @@ -0,0 +1,83 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { V1APIGroupList, V1APIResourceList, V1APIVersions } from "@kubernetes/client-node"; +import { getInjectable } from "@ogre-tools/injectable"; +import k8SRequestInjectable from "../../main/k8s-request.injectable"; +import loggerInjectable from "../logger.injectable"; +import type { KubeApiResource } from "../rbac"; +import type { Cluster } from "./cluster"; +import plimit from "p-limit"; + +export type RequestApiResources = (cluster: Cluster) => Promise; + +interface KubeResourceListGroup { + group: string; + path: string; +} + +const requestApiResourcesInjectable = getInjectable({ + id: "request-api-resources", + instantiate: (di): RequestApiResources => { + const k8sRequest = di.inject(k8SRequestInjectable); + const logger = di.inject(loggerInjectable); + + return async (cluster) => { + const apiLimit = plimit(5); + const kubeApiResources: KubeApiResource[] = []; + const resourceListGroups: KubeResourceListGroup[] = []; + + try { + await Promise.all([ + (async () => { + const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions; + + for (const version of versions) { + resourceListGroups.push({ + group: version, + path: `/api/${version}`, + }); + } + })(), + (async () => { + const { groups } = await k8sRequest(cluster, "/apis") as V1APIGroupList; + + for (const { preferredVersion, name } of groups) { + const { groupVersion } = preferredVersion ?? {}; + + if (groupVersion) { + resourceListGroups.push({ + group: name, + path: `/apis/${groupVersion}`, + }); + } + } + })(), + ]); + + await Promise.all( + resourceListGroups.map(({ group, path }) => apiLimit(async () => { + const { resources } = await k8sRequest(cluster, path) as V1APIResourceList; + + for (const resource of resources) { + kubeApiResources.push({ + apiName: resource.name, + kind: resource.kind, + group, + namespaced: resource.namespaced, + }); + } + })), + ); + } catch (error) { + logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`); + } + + return kubeApiResources; + }; + }, +}); + +export default requestApiResourcesInjectable; diff --git a/src/common/cluster/request-namespace-list-permissions.injectable.ts b/src/common/cluster/request-namespace-list-permissions.injectable.ts new file mode 100644 index 0000000000..62d2477e42 --- /dev/null +++ b/src/common/cluster/request-namespace-list-permissions.injectable.ts @@ -0,0 +1,78 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeConfig } from "@kubernetes/client-node"; +import { AuthorizationV1Api } from "@kubernetes/client-node"; +import { getInjectable } from "@ogre-tools/injectable"; +import loggerInjectable from "../logger.injectable"; +import type { KubeApiResource } from "../rbac"; + +export type CanListResource = (resource: KubeApiResource) => boolean; + +/** + * Requests the permissions for actions on the kube cluster + * @param namespace The namespace of the resources + */ +export type RequestNamespaceListPermissions = (namespace: string) => Promise; + +/** + * @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster + */ +export type RequestNamespaceListPermissionsFor = (proxyConfig: KubeConfig) => RequestNamespaceListPermissions; + +const requestNamespaceListPermissionsForInjectable = getInjectable({ + id: "request-namespace-list-permissions-for", + instantiate: (di): RequestNamespaceListPermissionsFor => { + const logger = di.inject(loggerInjectable); + + return (proxyConfig) => { + const api = proxyConfig.makeApiClient(AuthorizationV1Api); + + return async (namespace) => { + try { + const { body: { status }} = await api.createSelfSubjectRulesReview({ + apiVersion: "authorization.k8s.io/v1", + kind: "SelfSubjectRulesReview", + spec: { namespace }, + }); + + if (!status || status.incomplete) { + logger.warn(`[AUTHORIZATION-NAMESPACE-REVIEW]: allowing all resources in namespace="${namespace}" due to incomplete SelfSubjectRulesReview: ${status?.evaluationError}`); + + return () => true; + } + + const { resourceRules } = status; + + return (resource) => { + const resourceRule = resourceRules.find(({ + apiGroups = [], + resources = [], + }) => { + const isAboutRelevantApiGroup = apiGroups.includes("*") || apiGroups.includes(resource.group); + const isAboutResource = resources.includes("*") || resources.includes(resource.apiName); + + return isAboutRelevantApiGroup && isAboutResource; + }); + + if (!resourceRule) { + return false; + } + + const { verbs } = resourceRule; + + return verbs.includes("*") || verbs.includes("list"); + }; + } catch (error) { + logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review`, { namespace, error }); + + return () => true; + } + }; + }; + }, +}); + +export default requestNamespaceListPermissionsForInjectable; diff --git a/src/common/configure-packages.ts b/src/common/configure-packages.ts deleted file mode 100644 index ec48be44ce..0000000000 --- a/src/common/configure-packages.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -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", - - // 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 -} diff --git a/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts index 3ab2bc515c..6ea03fff08 100644 --- a/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts @@ -3,22 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const configMapsRouteInjectable = getInjectable({ id: "config-maps-route", - - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "configmaps"); - - return { - path: "/configmaps", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, - + instantiate: (di) => ({ + path: "/configmaps", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "configmaps", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts index fe1814734f..00002620ee 100644 --- a/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const horizontalPodAutoscalersRouteInjectable = getInjectable({ id: "horizontal-pod-autoscalers-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "horizontalpodautoscalers"); - - return { - path: "/hpa", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/hpa", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "horizontalpodautoscalers", + group: "autoscaling", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts index 65ee0e3ffa..ea4eb2ae59 100644 --- a/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const leasesRouteInjectable = getInjectable({ id: "leases", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "leases"); - - return { - path: "/leases", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/leases", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "leases", + group: "coordination.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts index bcf740113b..8623f3520e 100644 --- a/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts @@ -3,24 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const limitRangesRouteInjectable = getInjectable({ id: "limit-ranges-route", - instantiate: (di) => { - const limitRangesIsAllowed = di.inject( - isAllowedResourceInjectable, - "limitranges", - ); - - return { - path: "/limitranges", - clusterFrame: true, - isEnabled: limitRangesIsAllowed, - }; - }, + instantiate: (di) => ({ + path: "/limitranges", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "limitranges", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts index df6d89fb06..12ce0a2138 100644 --- a/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const podDisruptionBudgetsRouteInjectable = getInjectable({ id: "pod-disruption-budgets-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "poddisruptionbudgets"); - - return { - path: "/poddisruptionbudgets", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/poddisruptionbudgets", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "poddisruptionbudgets", + group: "policy", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts index 1c3632c261..75194b0541 100644 --- a/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const priorityClassesRouteInjectable = getInjectable({ id: "priority-classes-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "priorityclasses"); - - return { - path: "/priorityclasses", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/priorityclasses", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "priorityclasses", + group: "scheduling.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts index 496a42ed0c..209f77e19a 100644 --- a/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const resourceQuotasRouteInjectable = getInjectable({ id: "resource-quotas-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "resourcequotas"); - - return { - path: "/resourcequotas", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/resourcequotas", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "resourcequotas", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts index 72934f9158..beab83754f 100644 --- a/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const runtimeClassesRouteInjectable = getInjectable({ id: "runtime-classes-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "runtimeclasses"); - - return { - path: "/runtimeclasses", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/runtimeclasses", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "runtimeclasses", + group: "node.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts index f451f51d0c..079ddcbf83 100644 --- a/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const secretsRouteInjectable = getInjectable({ id: "secrets-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "secrets"); - - return { - path: "/secrets", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/secrets", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "secrets", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts b/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts index a53cb5d1f8..b3df358ad8 100644 --- a/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const eventsRouteInjectable = getInjectable({ id: "events-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "events"); - - return { - path: "/events", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/events", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "events", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts b/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts index 395c128682..2aa6c23efe 100644 --- a/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const namespacesRouteInjectable = getInjectable({ id: "namespaces-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "namespaces"); - - return { - path: "/namespaces", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/namespaces", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "namespaces", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts index e30df3123b..c88ec04714 100644 --- a/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const endpointsRouteInjectable = getInjectable({ id: "endpoints-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "endpoints"); - - return { - path: "/endpoints", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/endpoints", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "endpoints", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts index 12565deeaf..8e01646b82 100644 --- a/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts @@ -3,21 +3,27 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { computedOr } from "../../../../../utils/computed-or"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const ingressesRouteInjectable = getInjectable({ id: "ingresses-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "ingresses"); - - return { - path: "/ingresses", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/ingresses", + clusterFrame: true, + isEnabled: computedOr( + di.inject(shouldShowResourceInjectionToken, { + apiName: "ingresses", + group: "networking.k8s.io", + }), + di.inject(shouldShowResourceInjectionToken, { + apiName: "ingresses", + group: "extensions", + }), + ), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts index ead62ee435..38a1b8a7e2 100644 --- a/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const networkPoliciesRouteInjectable = getInjectable({ id: "network-policies-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "networkpolicies"); - - return { - path: "/network-policies", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/network-policies", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "networkpolicies", + group: "networking.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts index 033c95673b..53300ee241 100644 --- a/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const servicesRouteInjectable = getInjectable({ id: "services-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "services"); - - return { - path: "/services", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/services", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "services", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts index febd733343..81323843d5 100644 --- a/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const nodesRouteInjectable = getInjectable({ id: "nodes-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "nodes"); - - return { - path: "/nodes", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/nodes", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "nodes", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts b/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts index 209fc113d8..8315fd7773 100644 --- a/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const clusterOverviewRouteInjectable = getInjectable({ id: "cluster-overview-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "nodes"); - - return { - path: "/overview", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/overview", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "nodes", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts index 8879541355..1b96933136 100644 --- a/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const persistentVolumeClaimsRouteInjectable = getInjectable({ id: "persistent-volume-claims-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "persistentvolumeclaims"); - - return { - path: "/persistent-volume-claims", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/persistent-volume-claims", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "persistentvolumeclaims", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts index e6549ea45b..52f95b32c6 100644 --- a/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const persistentVolumesRouteInjectable = getInjectable({ id: "persistent-volumes-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "persistentvolumes"); - - return { - path: "/persistent-volumes", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/persistent-volumes", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "persistentvolumes", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts index 69f2b5d4ee..8702ab1602 100644 --- a/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const storageClassesRouteInjectable = getInjectable({ id: "storage-classes-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "storageclasses"); - - return { - path: "/storage-classes", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/storage-classes", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "storageclasses", + group: "storage.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts index f19491ee72..0903d5fced 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const clusterRoleBindingsRouteInjectable = getInjectable({ id: "cluster-role-bindings-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "clusterrolebindings"); - - return { - path: "/cluster-role-bindings", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/cluster-role-bindings", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "clusterrolebindings", + group: "rbac.authorization.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts index d21c2c33a4..9fce206667 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const clusterRolesRouteInjectable = getInjectable({ id: "cluster-roles-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "clusterroles"); - - return { - path: "/cluster-roles", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/cluster-roles", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "clusterroles", + group: "rbac.authorization.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts index 14cfcbedc5..2f35986916 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts @@ -3,19 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const podSecurityPoliciesRouteInjectable = getInjectable({ id: "pod-security-policies-route", instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "podsecuritypolicies"); - return { path: "/pod-security-policies", clusterFrame: true, - isEnabled: isAllowedResource, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "podsecuritypolicies", + group: "policy", + }), }; }, diff --git a/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts index 0f908e5876..759c1b8eda 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts @@ -3,19 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const roleBindingsRouteInjectable = getInjectable({ id: "role-bindings-route", instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "rolebindings"); - return { path: "/role-bindings", clusterFrame: true, - isEnabled: isAllowedResource, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "rolebindings", + group: "rbac.authorization.k8s.io", + }), }; }, diff --git a/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts index 94db156fa4..efe4cad810 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const rolesRouteInjectable = getInjectable({ id: "roles-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "roles"); - - return { - path: "/roles", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/roles", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "roles", + group: "rbac.authorization.k8s.io", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts index 4d79258c54..3bf6c1ec00 100644 --- a/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const serviceAccountsRouteInjectable = getInjectable({ id: "service-accounts-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "serviceaccounts"); - - return { - path: "/service-accounts", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/service-accounts", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "serviceaccounts", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts index 735ea94642..33453a2247 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const cronJobsRouteInjectable = getInjectable({ id: "cron-jobs-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "cronjobs"); - - return { - path: "/cronjobs", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/cronjobs", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "cronjobs", + group: "batch", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts index 55729813e8..f1ec2008fa 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const daemonsetsRouteInjectable = getInjectable({ id: "daemonsets-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "daemonsets"); - - return { - path: "/daemonsets", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/daemonsets", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "daemonsets", + group: "apps", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts index b9ff072e66..84c059780f 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const deploymentsRouteInjectable = getInjectable({ id: "deployments-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "deployments"); - - return { - path: "/deployments", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/deployments", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "deployments", + group: "apps", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts index d9190a7ea8..39cc89e88f 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const jobsRouteInjectable = getInjectable({ id: "jobs-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "jobs"); - - return { - path: "/jobs", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/jobs", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "jobs", + group: "batch", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts index e9fb2a2b16..577f1c1a91 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const podsRouteInjectable = getInjectable({ id: "pods-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "pods"); - - return { - path: "/pods", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/pods", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "pods", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts index 0319d27550..b790ce13ec 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const replicasetsRouteInjectable = getInjectable({ id: "replicasets-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "replicasets"); - - return { - path: "/replicasets", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/replicasets", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "replicasets", + group: "apps", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts index a3089fa62f..72c81b3bee 100644 --- a/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts +++ b/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts @@ -3,21 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const statefulsetsRouteInjectable = getInjectable({ id: "statefulsets-route", - instantiate: (di) => { - const isAllowedResource = di.inject(isAllowedResourceInjectable, "statefulsets"); - - return { - path: "/statefulsets", - clusterFrame: true, - isEnabled: isAllowedResource, - }; - }, + instantiate: (di) => ({ + path: "/statefulsets", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "statefulsets", + group: "apps", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/common/k8s-api/__tests__/api-manager.test.ts b/src/common/k8s-api/__tests__/api-manager.test.ts index 3e411b6584..e99ac62018 100644 --- a/src/common/k8s-api/__tests__/api-manager.test.ts +++ b/src/common/k8s-api/__tests__/api-manager.test.ts @@ -3,7 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { DiContainer } from "@ogre-tools/injectable"; +import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable"; +import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; +import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; +import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import type { ApiManager } from "../api-manager"; import apiManagerInjectable from "../api-manager/manager.injectable"; import { KubeApi } from "../kube-api"; @@ -22,9 +29,24 @@ class TestStore extends KubeObjectStore { describe("ApiManager", () => { let apiManager: ApiManager; + let di: DiContainer; beforeEach(() => { - const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di = getDiForUnitTesting({ doGeneralOverrides: true }); + + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); + di.override(storesAndApisCanBeCreatedInjectable, () => true); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); apiManager = di.inject(apiManagerInjectable); }); @@ -40,7 +62,9 @@ describe("ApiManager", () => { fallbackApiBases: [fallbackApiBase], checkPreferredVersion: true, }); - const kubeStore = new TestStore(kubeApi); + const kubeStore = new TestStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, kubeApi); apiManager.registerApi(apiBase, kubeApi); diff --git a/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts b/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts index eb13464716..e2caef39fa 100644 --- a/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts +++ b/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts @@ -3,43 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import type { KubeJsonApi } from "../kube-json-api"; -import { PassThrough } from "stream"; import type { ApiManager } from "../api-manager"; import { Ingress, IngressApi } from "../endpoints"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; import apiManagerInjectable from "../api-manager/manager.injectable"; -import autoRegistrationInjectable from "../api-manager/auto-registration.injectable"; import type { Fetch } from "../../fetch/fetch.injectable"; import fetchInjectable from "../../fetch/fetch.injectable"; import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import { flushPromises } from "../../test-utils/flush-promises"; import createKubeJsonApiInjectable from "../create-kube-json-api.injectable"; -import type { Response, Headers as NodeFetchHeaders } from "node-fetch"; - -const createMockResponseFromString = (url: string, data: string, statusCode = 200) => { - const res: jest.Mocked = { - buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }), - clone: jest.fn(() => res), - arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }), - blob: jest.fn(async () => { throw new Error("blob() is not supported"); }), - body: new PassThrough(), - bodyUsed: false, - headers: new Headers() as NodeFetchHeaders, - json: jest.fn(async () => JSON.parse(await res.text())), - ok: 200 <= statusCode && statusCode < 300, - redirected: 300 <= statusCode && statusCode < 400, - size: data.length, - status: statusCode, - statusText: "some-text", - text: jest.fn(async () => data), - type: "basic", - url, - formData: jest.fn(async () => { throw new Error("formData() is not supported"); }), - }; - - return res; -}; +import setupAutoRegistrationInjectable from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable"; +import { createMockResponseFromString } from "../../../test-utils/mock-responses"; +import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; +import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable"; +import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; +import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; describe("KubeApi", () => { let request: KubeJsonApi; @@ -52,6 +32,20 @@ describe("KubeApi", () => { fetchMock = asyncFn(); di.override(fetchInjectable, () => fetchMock); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); + di.override(storesAndApisCanBeCreatedInjectable, () => true); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + const createKubeJsonApi = di.inject(createKubeJsonApiInjectable); request = createKubeJsonApi({ @@ -60,7 +54,9 @@ describe("KubeApi", () => { }); registerApiSpy = jest.spyOn(di.inject(apiManagerInjectable), "registerApi"); - di.inject(autoRegistrationInjectable); + const setupAutoRegistration = di.inject(setupAutoRegistrationInjectable); + + setupAutoRegistration.run(); }); describe("on first call to IngressApi.get()", () => { diff --git a/src/common/k8s-api/__tests__/kube-api.test.ts b/src/common/k8s-api/__tests__/kube-api.test.ts index 4f67ed1401..a4f9fd5b21 100644 --- a/src/common/k8s-api/__tests__/kube-api.test.ts +++ b/src/common/k8s-api/__tests__/kube-api.test.ts @@ -8,7 +8,6 @@ import type { KubeJsonApi, KubeJsonApiData } from "../kube-json-api"; import { PassThrough } from "stream"; import { Deployment, DeploymentApi, NamespaceApi, Pod, PodApi } from "../endpoints"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; -import autoRegistrationInjectable from "../api-manager/auto-registration.injectable"; import type { Fetch } from "../../fetch/fetch.injectable"; import fetchInjectable from "../../fetch/fetch.injectable"; import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remote-cluster.injectable"; @@ -19,64 +18,14 @@ import { flushPromises } from "../../test-utils/flush-promises"; import createKubeJsonApiInjectable from "../create-kube-json-api.injectable"; import type { IKubeWatchEvent } from "../kube-watch-event"; import type { KubeJsonApiDataFor } from "../kube-object"; -import type { Response, Headers as NodeFetchHeaders } from "node-fetch"; import AbortController from "abort-controller"; - -const createMockResponseFromString = (url: string, data: string, statusCode = 200) => { - const res: jest.Mocked = { - buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }), - clone: jest.fn(() => res), - arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }), - blob: jest.fn(async () => { throw new Error("blob() is not supported"); }), - body: new PassThrough(), - bodyUsed: false, - headers: new Headers() as NodeFetchHeaders, - json: jest.fn(async () => JSON.parse(await res.text())), - ok: 200 <= statusCode && statusCode < 300, - redirected: 300 <= statusCode && statusCode < 400, - size: data.length, - status: statusCode, - statusText: "some-text", - text: jest.fn(async () => data), - type: "basic", - url, - formData: jest.fn(async () => { throw new Error("formData() is not supported"); }), - }; - - return res; -}; - -const createMockResponseFromStream = (url: string, stream: NodeJS.ReadableStream, statusCode = 200) => { - const res: jest.Mocked = { - buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }), - clone: jest.fn(() => res), - arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }), - blob: jest.fn(async () => { throw new Error("blob() is not supported"); }), - body: stream, - bodyUsed: false, - headers: new Headers() as NodeFetchHeaders, - json: jest.fn(async () => JSON.parse(await res.text())), - ok: 200 <= statusCode && statusCode < 300, - redirected: 300 <= statusCode && statusCode < 400, - size: 10, - status: statusCode, - statusText: "some-text", - text: jest.fn(() => { - const chunks: Buffer[] = []; - - return new Promise((resolve, reject) => { - stream.on("data", (chunk) => chunks.push(Buffer.from(chunk))); - stream.on("error", (err) => reject(err)); - stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); - }); - }), - type: "basic", - url, - formData: jest.fn(async () => { throw new Error("formData() is not supported"); }), - }; - - return res; -}; +import setupAutoRegistrationInjectable from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable"; +import { createMockResponseFromStream, createMockResponseFromString } from "../../../test-utils/mock-responses"; +import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; +import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable"; +import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; +import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; describe("createKubeApiForRemoteCluster", () => { let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster; @@ -85,6 +34,20 @@ describe("createKubeApiForRemoteCluster", () => { beforeEach(async () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); + di.override(storesAndApisCanBeCreatedInjectable, () => true); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + fetchMock = asyncFn(); di.override(fetchInjectable, () => fetchMock); @@ -174,6 +137,20 @@ describe("KubeApi", () => { beforeEach(async () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); + di.override(storesAndApisCanBeCreatedInjectable, () => true); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + fetchMock = asyncFn(); di.override(fetchInjectable, () => fetchMock); @@ -184,7 +161,9 @@ describe("KubeApi", () => { apiBase: "/api-kube", }); - di.inject(autoRegistrationInjectable); + const setupAutoRegistration = di.inject(setupAutoRegistrationInjectable); + + setupAutoRegistration.run(); }); describe("patching deployments", () => { diff --git a/src/common/k8s-api/__tests__/kube-object.store.test.ts b/src/common/k8s-api/__tests__/kube-object.store.test.ts index 91ed80fbde..afe755a6ba 100644 --- a/src/common/k8s-api/__tests__/kube-object.store.test.ts +++ b/src/common/k8s-api/__tests__/kube-object.store.test.ts @@ -3,27 +3,22 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { Cluster } from "../../cluster/cluster"; -import type { ClusterContext } from "../cluster-context"; import type { KubeApi } from "../kube-api"; import { KubeObject } from "../kube-object"; import type { KubeObjectStoreLoadingParams } from "../kube-object.store"; import { KubeObjectStore } from "../kube-object.store"; class FakeKubeObjectStore extends KubeObjectStore { - _context = { - allNamespaces: [], - contextNamespaces: [], - hasSelectedAll: false, - cluster: {} as Cluster, - } as ClusterContext; - - get context() { - return this._context; - } - constructor(private readonly _loadItems: (params: KubeObjectStoreLoadingParams) => KubeObject[], api: Partial>) { - super(api as KubeApi); + super({ + context: { + allNamespaces: [], + contextNamespaces: [], + hasSelectedAll: false, + isGlobalWatchEnabled: () => true, + isLoadingAll: () => true, + }, + }, api as KubeApi); } async loadItems(params: KubeObjectStoreLoadingParams) { diff --git a/src/common/k8s-api/api-manager/auto-registration.injectable.ts b/src/common/k8s-api/api-manager/auto-registration.injectable.ts deleted file mode 100644 index 0cf1a3055d..0000000000 --- a/src/common/k8s-api/api-manager/auto-registration.injectable.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import type { CustomResourceDefinition } from "../endpoints"; -import { KubeApi } from "../kube-api"; -import { KubeObject } from "../kube-object"; -import autoRegistrationEmitterInjectable from "./auto-registration-emitter.injectable"; -import apiManagerInjectable from "./manager.injectable"; -import { CustomResourceStore } from "./resource.store"; - -const autoRegistrationInjectable = getInjectable({ - id: "api-manager-auto-registration", - instantiate: (di) => { - const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable); - const beforeApiManagerInitializationCrds: CustomResourceDefinition[] = []; - const beforeApiManagerInitializationApis: KubeApi[] = []; - let initialized = false; - - const autoInitCustomResourceStore = (crd: CustomResourceDefinition) => { - const objectConstructor = class extends KubeObject { - static readonly kind = crd.getResourceKind(); - static readonly namespaced = crd.isNamespaced(); - static readonly apiBase = crd.getResourceApiBase(); - }; - - const api = (() => { - const rawApi = apiManager.getApi(objectConstructor.apiBase); - - if (rawApi) { - return rawApi; - } - - const api = new KubeApi({ objectConstructor }); - - apiManager.registerApi(api); - - return api; - })(); - - if (!apiManager.getStore(api)) { - apiManager.registerStore(new CustomResourceStore(api)); - } - }; - const autoInitKubeApi = (api: KubeApi) => { - apiManager.registerApi(api); - }; - - autoRegistrationEmitter - .on("customResourceDefinition", (crd) => { - if (initialized) { - autoInitCustomResourceStore(crd); - } else { - beforeApiManagerInitializationCrds.push(crd); - } - }) - .on("kubeApi", (api) => { - if (initialized) { - autoInitKubeApi(api); - } else { - beforeApiManagerInitializationApis.push(api); - } - }); - - const apiManager = di.inject(apiManagerInjectable); - - beforeApiManagerInitializationCrds.forEach(autoInitCustomResourceStore); - beforeApiManagerInitializationApis.forEach(autoInitKubeApi); - initialized = true; - }, -}); - -export default autoRegistrationInjectable; diff --git a/src/common/k8s-api/api-manager/resource.store.ts b/src/common/k8s-api/api-manager/resource.store.ts index 63ccdcf93d..c81ce7daec 100644 --- a/src/common/k8s-api/api-manager/resource.store.ts +++ b/src/common/k8s-api/api-manager/resource.store.ts @@ -4,11 +4,12 @@ */ import type { KubeApi } from "../kube-api"; +import type { KubeObjectStoreDependencies } from "../kube-object.store"; import { KubeObjectStore } from "../kube-object.store"; import type { KubeObject } from "../kube-object"; export class CustomResourceStore extends KubeObjectStore> { - constructor(api: KubeApi) { - super(api); + constructor(deps: KubeObjectStoreDependencies, api: KubeApi) { + super(deps, api); } } diff --git a/src/common/k8s-api/cluster-context.ts b/src/common/k8s-api/cluster-context.ts deleted file mode 100644 index 098d92642d..0000000000 --- a/src/common/k8s-api/cluster-context.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { Cluster } from "../cluster/cluster"; - -export interface ClusterContext { - cluster: Cluster; - allNamespaces: string[]; // available / allowed namespaces from cluster.ts - contextNamespaces: string[]; // selected by user (see: namespace-select.tsx) - hasSelectedAll: boolean; -} diff --git a/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts b/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts index 7290263a41..49271fb6d2 100644 --- a/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts +++ b/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts @@ -5,25 +5,44 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { Patch } from "rfc6902"; import apiBaseInjectable from "../../api-base.injectable"; +import type { AsyncResult } from "../../../utils/async-result"; import type { KubeJsonApiData } from "../../kube-json-api"; -export type RequestKubeObjectPatch = (name: string, kind: string, ns: string | undefined, patch: Patch) => Promise; +export type RequestKubeObjectPatch = (name: string, kind: string, ns: string | undefined, patch: Patch) => Promise>; const requestKubeObjectPatchInjectable = getInjectable({ id: "request-kube-object-patch", instantiate: (di): RequestKubeObjectPatch => { const apiBase = di.inject(apiBaseInjectable); - return (name, kind, ns, patch) => ( - apiBase.patch("/stack", { + return async (name, kind, ns, patch) => { + const result = await apiBase.patch("/stack", { data: { name, kind, ns, patch, }, - }) - ); + }) as AsyncResult; + + if (!result.callWasSuccessful) { + return result; + } + + try { + const response = JSON.parse(result.response); + + return { + callWasSuccessful: true, + response, + }; + } catch (error) { + return { + callWasSuccessful: false, + error: String(error), + }; + } + }; }, }); diff --git a/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts b/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts index 7d996253ee..1891a779cf 100644 --- a/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts +++ b/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts @@ -4,16 +4,37 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import apiBaseInjectable from "../../api-base.injectable"; +import type { AsyncResult } from "../../../utils/async-result"; import type { KubeJsonApiData } from "../../kube-json-api"; -export type RequestKubeObjectCreation = (resourceDescriptor: string) => Promise; +export type RequestKubeObjectCreation = (resourceDescriptor: string) => Promise>; const requestKubeObjectCreationInjectable = getInjectable({ id: "request-kube-object-creation", instantiate: (di): RequestKubeObjectCreation => { const apiBase = di.inject(apiBaseInjectable); - return (data) => apiBase.post("/stack", { data }); + return async (data) => { + const result = await apiBase.post("/stack", { data }) as AsyncResult; + + if (!result.callWasSuccessful) { + return result; + } + + try { + const response = JSON.parse(result.response); + + return { + callWasSuccessful: true, + response, + }; + } catch (error) { + return { + callWasSuccessful: false, + error: String(error), + }; + } + }; }, }); diff --git a/src/common/k8s-api/json-api.ts b/src/common/k8s-api/json-api.ts index c3e07bfa94..988378641c 100644 --- a/src/common/k8s-api/json-api.ts +++ b/src/common/k8s-api/json-api.ts @@ -104,7 +104,7 @@ export class JsonApi = Js ); const { query } = params ?? {}; - if (query) { + if (query && Object.keys(query).length > 0) { const queryString = stringify(query as unknown as QueryParams); reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString; @@ -171,7 +171,7 @@ export class JsonApi = Js reqInit.body = JSON.stringify(data); } - if (query) { + if (query && Object.keys(query).length > 0) { const queryString = stringify(query as unknown as QueryParams); reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString; diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index 7ca2995906..6e3ad4a72a 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -403,10 +403,11 @@ export class KubeApi< /** * This method differs from {@link formatUrlForNotListing} because this treats `""` as "all namespaces" + * NOTE: This is also useful for watching * @param namespace The namespace to list in or `""` for all namespaces */ - formatUrlForListing(namespace: string) { - return createKubeApiURL({ + formatUrlForListing(namespace: string | undefined, query?: Partial) { + const resourcePath = createKubeApiURL({ apiPrefix: this.apiPrefix, apiVersion: this.apiVersionWithGroup, resource: this.apiResource, @@ -414,15 +415,15 @@ export class KubeApi< ? namespace ?? "default" : undefined, }); + + return resourcePath + (query ? `?${stringify(this.normalizeQuery(query))}` : ""); } /** * Format a URL pathname and query for acting upon a specific resource. */ - formatUrlForNotListing(resource?: Partial, query?: Partial): string; - - formatUrlForNotListing({ name, namespace }: Partial = {}, query?: Partial) { - const resourcePath = createKubeApiURL({ + formatUrlForNotListing({ name, namespace }: Partial = {}) { + return createKubeApiURL({ apiPrefix: this.apiPrefix, apiVersion: this.apiVersionWithGroup, resource: this.apiResource, @@ -431,15 +432,17 @@ export class KubeApi< : undefined, name, }); - - return resourcePath + (query ? `?${stringify(this.normalizeQuery(query))}` : ""); } /** - * @deprecated use {@link formatUrlForNotListing} instead + * @deprecated use {@link formatUrlForNotListing} or {@link formatUrlForListing} instead */ getUrl(resource?: Partial, query?: Partial) { - return this.formatUrlForNotListing(resource, query); + if (query) { + return this.formatUrlForListing(resource?.namespace, query); + } + + return this.formatUrlForNotListing(resource); } protected normalizeQuery(query: Partial = {}) { @@ -625,14 +628,14 @@ export class KubeApi< } getWatchUrl(namespace?: string, query: KubeApiQueryParams = {}) { - return this.formatUrlForNotListing({ namespace }, { + return this.formatUrlForListing(namespace, { watch: 1, resourceVersion: this.getResourceVersion(namespace), ...query, }); } - watch(opts?: KubeApiWatchOptions): () => void { + watch(opts?: KubeApiWatchOptions): Disposer { let errorReceived = false; let timedRetry: NodeJS.Timeout; const { diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 309c183e42..9e7c541b58 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -3,11 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ClusterContext } from "./cluster-context"; - -import { action, computed, makeObservable, observable, reaction, when } from "mobx"; +import { action, computed, makeObservable, observable, reaction } from "mobx"; import type { Disposer } from "../utils"; -import { waitUntilDefined, autoBind, includes, noop, rejectPromiseBy } from "../utils"; +import { waitUntilDefined, autoBind, includes, rejectPromiseBy } from "../utils"; import type { KubeJsonApiDataFor, KubeObject } from "./kube-object"; import { KubeStatus } from "./kube-object"; import type { IKubeWatchEvent } from "./kube-watch-event"; @@ -21,6 +19,7 @@ import assert from "assert"; import type { PartialDeep } from "type-fest"; import { entries } from "../utils/objects"; import AbortController from "abort-controller"; +import type { ClusterContext } from "../../renderer/cluster-frame-context/cluster-frame-context"; export type OnLoadFailure = (error: unknown) => void; @@ -85,38 +84,26 @@ export type KubeApiDataFrom = A extends KubeApi = KubeApi>, D extends KubeJsonApiDataFor = KubeApiDataFrom, > extends ItemStore { - static readonly defaultContext = observable.box(); // TODO: support multiple cluster contexts - - public readonly api!: A; public readonly limit: number | undefined; public readonly bufferSize: number; - @observable private loadedNamespaces: string[] | undefined = undefined; - get contextReady() { - return when(() => Boolean(this.context)); - } + private readonly loadedNamespaces = observable.box(); - get namespacesReady() { - return when(() => Boolean(this.loadedNamespaces)); - } - - constructor(api: A, opts?: KubeObjectStoreOptions); - /** - * @deprecated Supply API instance through constructor - */ - constructor(); - constructor(api?: A, opts?: KubeObjectStoreOptions) { + constructor( + protected readonly dependencies: KubeObjectStoreDependencies, + public readonly api: A, + opts?: KubeObjectStoreOptions, + ) { super(); - - if (api) { - this.api = api; - } - this.limit = opts?.limit; this.bufferSize = opts?.bufferSize ?? 50_000; @@ -125,13 +112,9 @@ export abstract class KubeObjectStore< this.bindWatchEventsUpdater(); } - get context(): ClusterContext | undefined { - return KubeObjectStore.defaultContext.get(); - } - // TODO: Circular dependency: KubeObjectStore -> ClusterFrameContext -> NamespaceStore -> KubeObjectStore @computed get contextItems(): K[] { - const namespaces = this.context?.contextNamespaces ?? []; + const namespaces = this.dependencies.context.contextNamespaces; return this.items.filter(item => { const itemNamespace = item.getNs(); @@ -202,17 +185,11 @@ export abstract class KubeObjectStore< } protected async loadItems({ namespaces, reqInit, onLoadFailure }: KubeObjectStoreLoadingParams): Promise { - if (!this.context?.cluster?.isAllowedResource(this.api.kind)) { - return []; - } - - const isLoadingAll = this.context.allNamespaces?.length > 1 - && this.context.cluster.accessibleNamespaces.length === 0 - && this.context.allNamespaces.every(ns => namespaces.includes(ns)); + const isLoadingAll = this.dependencies.context.isLoadingAll(namespaces); if (!this.api.isNamespaced || isLoadingAll) { if (this.api.isNamespaced) { - this.loadedNamespaces = []; + this.loadedNamespaces.set([]); } const res = this.api.list({ reqInit }, this.query); @@ -234,7 +211,7 @@ export abstract class KubeObjectStore< return await res ?? []; } - this.loadedNamespaces = namespaces; + this.loadedNamespaces.set(namespaces); const results = await Promise.allSettled( namespaces.map(namespace => this.api.list({ namespace, reqInit }, this.query)), @@ -266,9 +243,7 @@ export abstract class KubeObjectStore< @action async loadAll({ namespaces, merge = true, reqInit, onLoadFailure }: KubeObjectStoreLoadAllParams = {}): Promise { - const context = await waitUntilDefined(() => this.context); - - namespaces ??= context.contextNamespaces; + namespaces ??= this.dependencies.context.contextNamespaces; this.isLoading = true; try { @@ -425,7 +400,7 @@ export abstract class KubeObjectStore< } // collect items from watch-api events to avoid UI blowing up with huge streams of data - protected eventsBuffer = observable.array>([], { deep: false }); + protected readonly eventsBuffer = observable.array>([], { deep: false }); protected bindWatchEventsUpdater(delay = 1000) { reaction(() => [...this.eventsBuffer], this.updateFromEventsBuffer, { @@ -435,25 +410,24 @@ export abstract class KubeObjectStore< subscribe({ onLoadFailure, abortController = new AbortController() }: KubeObjectStoreSubscribeParams = {}): Disposer { if (this.api.isNamespaced) { - Promise.race([ - rejectPromiseBy(abortController.signal), - Promise.all([ - waitUntilDefined(() => this.context), - this.namespacesReady, - ] as const), - ]) - .then(([context]) => { - assert(this.loadedNamespaces); + void (async () => { + try { + const loadedNamespaces = await Promise.race([ + rejectPromiseBy(abortController.signal), + waitUntilDefined(() => this.loadedNamespaces.get()), + ]); - if (context.cluster?.isGlobalWatchEnabled && this.loadedNamespaces.length === 0) { - return this.watchNamespace("", abortController, { onLoadFailure }); + if (this.dependencies.context.isGlobalWatchEnabled() && loadedNamespaces.length === 0) { + this.watchNamespace("", abortController, { onLoadFailure }); + } else { + for (const namespace of loadedNamespaces) { + this.watchNamespace(namespace, abortController, { onLoadFailure }); + } } - - for (const namespace of this.loadedNamespaces) { - this.watchNamespace(namespace, abortController, { onLoadFailure }); - } - }) - .catch(noop); // ignore DOMExceptions + } catch (error) { + console.error(`[KUBE-OBJECT-STORE]: failed to subscribe to ${this.api.apiBase}`, error); + } + })(); } else { this.watchNamespace("", abortController, { onLoadFailure }); } @@ -467,7 +441,7 @@ export abstract class KubeObjectStore< } let timedRetry: NodeJS.Timeout; - const watch = () => this.api.watch({ + const startNewWatch = () => this.api.watch({ namespace, abortController, callback, @@ -486,7 +460,7 @@ export abstract class KubeObjectStore< // not sure what to do, best to retry clearTimeout(timedRetry); - timedRetry = setTimeout(watch, 5000); + timedRetry = setTimeout(startNewWatch, 5000); } else if (error instanceof KubeStatus && error.code === 410) { clearTimeout(timedRetry); // resourceVersion has gone, let's try to reload @@ -495,11 +469,11 @@ export abstract class KubeObjectStore< namespace ? this.loadAll({ namespaces: [namespace], reqInit: { signal }, ...opts }) : this.loadAll({ merge: false, reqInit: { signal }, ...opts }) - ).then(watch); + ).then(startNewWatch); }, 1000); } else if (error) { // not sure what to do, best to retry clearTimeout(timedRetry); - timedRetry = setTimeout(watch, 5000); + timedRetry = setTimeout(startNewWatch, 5000); } if (data) { @@ -508,7 +482,7 @@ export abstract class KubeObjectStore< }; signal.addEventListener("abort", () => clearTimeout(timedRetry)); - watch(); + startNewWatch(); } @action diff --git a/src/common/k8s-api/kube-object.ts b/src/common/k8s-api/kube-object.ts index 24e34eff4b..53bc6defdf 100644 --- a/src/common/k8s-api/kube-object.ts +++ b/src/common/k8s-api/kube-object.ts @@ -645,8 +645,13 @@ export class KubeObject< } const requestKubeObjectPatch = asLegacyGlobalFunctionForExtensionApi(requestKubeObjectPatchInjectable); + const result = await requestKubeObjectPatch(this.getName(), this.kind, this.getNs(), patch); - return requestKubeObjectPatch(this.getName(), this.kind, this.getNs(), patch); + if (!result.callWasSuccessful) { + throw new Error(result.error); + } + + return result.response; } /** @@ -665,7 +670,13 @@ export class KubeObject< ...data, }); - return requestKubeObjectCreation(descriptor); + const result = await requestKubeObjectCreation(descriptor); + + if (!result.callWasSuccessful) { + throw new Error(result.error); + } + + return result.response; } /** diff --git a/src/common/logger.injectable.ts b/src/common/logger.injectable.ts index e1a085f199..8e9dd2a6a7 100644 --- a/src/common/logger.injectable.ts +++ b/src/common/logger.injectable.ts @@ -9,13 +9,23 @@ import { loggerTransportInjectionToken } from "./logger/transports"; const loggerInjectable = getInjectable({ id: "logger", - instantiate: (di): Logger => createLogger({ - format: format.combine( - format.splat(), - format.simple(), - ), - transports: di.injectMany(loggerTransportInjectionToken), - }), + instantiate: (di): Logger => { + const baseLogger = createLogger({ + format: format.combine( + format.splat(), + format.simple(), + ), + transports: di.injectMany(loggerTransportInjectionToken), + }); + + return { + debug: (message, ...data) => baseLogger.debug(message, ...data), + info: (message, ...data) => baseLogger.info(message, ...data), + warn: (message, ...data) => baseLogger.warn(message, ...data), + error: (message, ...data) => baseLogger.error(message, ...data), + silly: (message, ...data) => baseLogger.silly(message, ...data), + }; + }, }); export default loggerInjectable; diff --git a/src/common/logger.ts b/src/common/logger.ts index 948404a6b9..0b460a48ff 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -17,4 +17,6 @@ export interface Logger { /** * @deprecated use `di.inject(loggerInjectable)` instead */ -export default asLegacyGlobalForExtensionApi(loggerInjectable); +const logger = asLegacyGlobalForExtensionApi(loggerInjectable); + +export default logger; diff --git a/src/common/rbac.ts b/src/common/rbac.ts index bfa04ef46b..99e564a377 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -11,51 +11,190 @@ export type KubeResource = "priorityclasses" | "runtimeclasses" | "roles" | "clusterroles" | "rolebindings" | "clusterrolebindings" | "serviceaccounts"; -export interface KubeApiResource extends KubeApiResourceData { - apiName: KubeResource; // valid api resource name (e.g. "namespaces") +export interface KubeApiResource { + kind: string; + group: string; + apiName: string; + namespaced: boolean; } +export interface KubeApiResourceDescriptor { + apiName: string; + group: string; +} + +export const formatKubeApiResource = (res: KubeApiResourceDescriptor) => `${res.group}/${res.apiName}`; + export interface KubeApiResourceData { kind: string; // resource type (e.g. "Namespace") - group?: string; // api-group + group: string; // api-group, if empty then "core" + namespaced: boolean; } export const apiResourceRecord: Record = { - "clusterroles": { kind: "ClusterRole", group: "rbac.authorization.k8s.io" }, - "clusterrolebindings": { kind: "ClusterRoleBinding", group: "rbac.authorization.k8s.io" }, - "configmaps": { kind: "ConfigMap" }, //empty group means "core" - "cronjobs": { kind: "CronJob", group: "batch" }, - "customresourcedefinitions": { kind: "CustomResourceDefinition", group: "apiextensions.k8s.io" }, - "daemonsets": { kind: "DaemonSet", group: "apps" }, - "deployments": { kind: "Deployment", group: "apps" }, - "endpoints": { kind: "Endpoint" }, - "events": { kind: "Event" }, - "horizontalpodautoscalers": { kind: "HorizontalPodAutoscaler", group: "autoscaling" }, - "ingresses": { kind: "Ingress", group: "networking.k8s.io" }, - "jobs": { kind: "Job", group: "batch" }, - "namespaces": { kind: "Namespace" }, - "limitranges": { kind: "LimitRange" }, - "leases": { kind: "Lease" }, - "networkpolicies": { kind: "NetworkPolicy", group: "networking.k8s.io" }, - "nodes": { kind: "Node" }, - "persistentvolumes": { kind: "PersistentVolume" }, - "persistentvolumeclaims": { kind: "PersistentVolumeClaim" }, - "pods": { kind: "Pod" }, - "poddisruptionbudgets": { kind: "PodDisruptionBudget", group: "policy" }, - "podsecuritypolicies": { kind: "PodSecurityPolicy", group: "policy" }, - "priorityclasses": { kind: "PriorityClass", group: "scheduling.k8s.io" }, - "runtimeclasses": { kind: "RuntimeClass", group: "node.k8s.io" }, - "resourcequotas": { kind: "ResourceQuota" }, - "replicasets": { kind: "ReplicaSet", group: "apps" }, - "roles": { kind: "Role", group: "rbac.authorization.k8s.io" }, - "rolebindings": { kind: "RoleBinding", group: "rbac.authorization.k8s.io" }, - "secrets": { kind: "Secret" }, - "serviceaccounts": { kind: "ServiceAccount" }, - "services": { kind: "Service" }, - "statefulsets": { kind: "StatefulSet", group: "apps" }, - "storageclasses": { kind: "StorageClass", group: "storage.k8s.io" }, + clusterroles: { + kind: "ClusterRole", + group: "rbac.authorization.k8s.io", + namespaced: false, + }, + clusterrolebindings: { + kind: "ClusterRoleBinding", + group: "rbac.authorization.k8s.io", + namespaced: false, + }, + configmaps: { + kind: "ConfigMap", + group: "v1", + namespaced: true, + }, + cronjobs: { + kind: "CronJob", + group: "batch", + namespaced: true, + }, + customresourcedefinitions: { + kind: "CustomResourceDefinition", + group: "apiextensions.k8s.io", + namespaced: false, + }, + daemonsets: { + kind: "DaemonSet", + group: "apps", + namespaced: true, + }, + deployments: { + kind: "Deployment", + group: "apps", + namespaced: true, + }, + endpoints: { + kind: "Endpoint", + group: "v1", + namespaced: true, + }, + events: { + kind: "Event", + group: "v1", + namespaced: true, + }, + horizontalpodautoscalers: { + kind: "HorizontalPodAutoscaler", + group: "autoscaling", + namespaced: true, + }, + ingresses: { + kind: "Ingress", + group: "networking.k8s.io", + namespaced: true, + }, + jobs: { + kind: "Job", + group: "batch", + namespaced: true, + }, + namespaces: { + kind: "Namespace", + group: "v1", + namespaced: false, + }, + limitranges: { + kind: "LimitRange", + group: "v1", + namespaced: true, + }, + leases: { + kind: "Lease", + group: "v1", + namespaced: true, + }, + networkpolicies: { + kind: "NetworkPolicy", + group: "networking.k8s.io", + namespaced: true, + }, + nodes: { + kind: "Node", + group: "v1", + namespaced: false, + }, + persistentvolumes: { + kind: "PersistentVolume", + group: "v1", + namespaced: false, + }, + persistentvolumeclaims: { + kind: "PersistentVolumeClaim", + group: "v1", + namespaced: true, + }, + pods: { + kind: "Pod", + group: "v1", + namespaced: true, + }, + poddisruptionbudgets: { + kind: "PodDisruptionBudget", + group: "policy", + namespaced: true, + }, + podsecuritypolicies: { + kind: "PodSecurityPolicy", + group: "policy", + namespaced: false, + }, + priorityclasses: { + kind: "PriorityClass", + group: "scheduling.k8s.io", + namespaced: false, + }, + runtimeclasses: { + kind: "RuntimeClass", + group: "node.k8s.io", + namespaced: false, + }, + resourcequotas: { + kind: "ResourceQuota", + group: "v1", + namespaced: true, + }, + replicasets: { + kind: "ReplicaSet", + group: "apps", + namespaced: true, + }, + roles: { + kind: "Role", + group: "rbac.authorization.k8s.io", + namespaced: true, + }, + rolebindings: { + kind: "RoleBinding", + group: "rbac.authorization.k8s.io", + namespaced: true, + }, + secrets: { + kind: "Secret", + group: "v1", + namespaced: true, + }, + serviceaccounts: { + kind: "ServiceAccount", + group: "v1", + namespaced: true, + }, + services: { + kind: "Service", + group: "v1", + namespaced: true, + }, + statefulsets: { + kind: "StatefulSet", + group: "apps", + namespaced: true, + }, + storageclasses: { + kind: "StorageClass", + group: "storage.k8s.io", + namespaced: false, + }, }; - -// TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7) -export const apiResources: KubeApiResource[] = Object.entries(apiResourceRecord) - .map(([apiName, data]) => ({ apiName: apiName as KubeResource, ...data })); diff --git a/src/common/utils/computed-or.ts b/src/common/utils/computed-or.ts new file mode 100644 index 0000000000..4e93394924 --- /dev/null +++ b/src/common/utils/computed-or.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { IComputedValue } from "mobx"; +import { computed } from "mobx"; + +export const computedOr = (...values: IComputedValue[]) => computed(( + () => values.some(value => value.get()) +)); diff --git a/src/common/utils/is-allowed-resource.injectable.ts b/src/common/utils/is-allowed-resource.injectable.ts deleted file mode 100644 index 8841a8f0cc..0000000000 --- a/src/common/utils/is-allowed-resource.injectable.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import type { KubeResource } from "../rbac"; -import { allowedResourcesInjectionToken } from "../cluster-store/allowed-resources-injection-token"; - -export type IsAllowedResource = (resource: KubeResource) => boolean; - -const isAllowedResourceInjectable = getInjectable({ - id: "is-allowed-resource", - - instantiate: (di, resourceName: string) => { - const allowedResources = di.inject(allowedResourcesInjectionToken); - - return computed(() => allowedResources.get().has(resourceName)); - }, - - lifecycle: lifecycleEnum.keyedSingleton({ - getInstanceKey: (di, resource: string) => resource, - }), -}); - -export default isAllowedResourceInjectable; diff --git a/src/common/utils/wait.ts b/src/common/utils/wait.ts index 7bcbd1688b..402d556b5d 100644 --- a/src/common/utils/wait.ts +++ b/src/common/utils/wait.ts @@ -8,26 +8,23 @@ import type { Disposer } from "./disposer"; export async function waitUntilDefined(getter: (() => T | null | undefined) | IComputedValue, opts?: { timeout?: number }): Promise { return new Promise((resolve, reject) => { - let res: T | null | undefined; - when( () => { - res = typeof getter === "function" + const res = typeof getter === "function" ? getter() : getter.get(); + const isDefined = res != null; - if (res != null) { + if (isDefined) { resolve(res); - - return true; } - return false; + return isDefined; }, () => {}, { onError: reject, - ...opts, + ...(opts ?? {}), }, ); }); diff --git a/src/common/vars.ts b/src/common/vars.ts index cbde12ff5d..d58e0871e6 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -6,26 +6,6 @@ // App's common configuration for any process (main, renderer, build pipeline, etc.) import type { ThemeId } from "../renderer/themes/lens-theme"; -/** - * @deprecated Switch to using isMacInjectable - */ -export const isMac = process.platform === "darwin"; - -/** - * @deprecated Switch to using isWindowsInjectable - */ -export const isWindows = process.platform === "win32"; - -/** - * @deprecated Switch to using isLinuxInjectable - */ -export const isLinux = process.platform === "linux"; - -/** - * @deprecated switch to using `isDebuggingInjectable` - */ -export const isDebugging = ["true", "1", "yes", "y", "on"].includes((process.env.DEBUG ?? "").toLowerCase()); - /** * @deprecated Switch to using isTestEnvInjectable */ diff --git a/src/extensions/common-api/k8s-api.ts b/src/extensions/common-api/k8s-api.ts index e5c7013bc7..9b62af7551 100644 --- a/src/extensions/common-api/k8s-api.ts +++ b/src/extensions/common-api/k8s-api.ts @@ -15,6 +15,12 @@ import type { ResourceApplyingStack } from "../../common/k8s/resource-stack"; import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; import type { KubernetesCluster } from "./catalog"; +import type { KubeApiDataFrom, KubeObjectStoreOptions } from "../../common/k8s-api/kube-object.store"; +import { KubeObjectStore as InternalKubeObjectStore } from "../../common/k8s-api/kube-object.store"; +import type { KubeJsonApiDataFor, KubeObject } from "../../common/k8s-api/kube-object"; +import type { KubeApi } from "../../common/k8s-api/kube-api"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../renderer/cluster-frame-context/for-namespaced-resources.injectable"; +import type { ClusterContext } from "../../renderer/cluster-frame-context/cluster-frame-context"; export const apiManager = asLegacyGlobalForExtensionApi(apiManagerInjectable); export const forCluster = asLegacyGlobalFunctionForExtensionApi(createKubeApiForClusterInjectable); @@ -72,8 +78,44 @@ export { type KubeJsonApiData, } from "../../common/k8s-api/kube-json-api"; +export abstract class KubeObjectStore< + K extends KubeObject = KubeObject, + A extends KubeApi = KubeApi>, + D extends KubeJsonApiDataFor = KubeApiDataFrom, +> extends InternalKubeObjectStore { + /** + * @deprecated This is no longer used and shouldn't have been every really used + */ + static readonly context = { + set: (ctx: ClusterContext) => { + console.warn("Setting KubeObjectStore.context is no longer supported"); + void ctx; + }, + get: () => asLegacyGlobalForExtensionApi(clusterFrameContextForNamespacedResourcesInjectable), + }; + + get context() { + return this.dependencies.context; + } + + constructor(api: A, opts?: KubeObjectStoreOptions); + /** + * @deprecated Supply API instance through constructor + */ + constructor(); + constructor(api?: A, opts?: KubeObjectStoreOptions) { + super( + { + context: asLegacyGlobalForExtensionApi(clusterFrameContextForNamespacedResourcesInjectable), + }, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + api!, + opts, + ); + } +} + export { - KubeObjectStore, type JsonPatch, type KubeObjectStoreLoadAllParams, type KubeObjectStoreLoadingParams, diff --git a/src/extensions/renderer-api/k8s-api.ts b/src/extensions/renderer-api/k8s-api.ts index 3401d979f7..6335559409 100644 --- a/src/extensions/renderer-api/k8s-api.ts +++ b/src/extensions/renderer-api/k8s-api.ts @@ -3,8 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import type { KubeResource } from "../../common/rbac"; -import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable"; -import { castArray } from "lodash/fp"; +import { apiResourceRecord } from "../../common/rbac"; import { getLegacyGlobalDiForExtensionApi } from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import clusterRoleBindingApiInjectable from "../../common/k8s-api/endpoints/cluster-role-binding.api.injectable"; import clusterRoleApiInjectable from "../../common/k8s-api/endpoints/cluster-role.api.injectable"; @@ -37,13 +36,22 @@ import namespaceApiInjectable from "../../common/k8s-api/endpoints/namespace.api import kubeEventApiInjectable from "../../common/k8s-api/endpoints/events.api.injectable"; import roleBindingApiInjectable from "../../common/k8s-api/endpoints/role-binding.api.injectable"; import customResourceDefinitionApiInjectable from "../../common/k8s-api/endpoints/custom-resource-definition.api.injectable"; +import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; -export function isAllowedResource(resource: KubeResource | KubeResource[]) { - const resources = castArray(resource); +export function isAllowedResource(resources: KubeResource | KubeResource[]) { const di = getLegacyGlobalDiForExtensionApi(); - return resources.every((resourceName: any) => { - const _isAllowedResource = di.inject(isAllowedResourceInjectable, resourceName); + return [resources].flat().every((resourceName) => { + const resource = apiResourceRecord[resourceName]; + + if (!resource) { + return true; + } + + const _isAllowedResource = di.inject(shouldShowResourceInjectionToken, { + apiName: resourceName, + group: resource.group, + }); // Note: Legacy isAllowedResource does not advertise reactivity return _isAllowedResource.get(); diff --git a/src/features/catalog/opening-entity-details.test.tsx b/src/features/catalog/opening-entity-details.test.tsx index 2d548697ed..8ae49ff548 100644 --- a/src/features/catalog/opening-entity-details.test.tsx +++ b/src/features/catalog/opening-entity-details.test.tsx @@ -10,8 +10,8 @@ import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injec import type { Cluster } from "../../common/cluster/cluster"; import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable"; +import createClusterInjectable from "../../renderer/cluster/create-cluster.injectable"; import { type ApplicationBuilder, getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; -import createClusterInjectable from "../../renderer/create-cluster/create-cluster.injectable"; describe("opening catalog entity details panel", () => { let builder: ApplicationBuilder; diff --git a/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx b/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx index c96f2714e1..5c527edbb4 100644 --- a/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx +++ b/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx @@ -14,7 +14,6 @@ import kubectlBinaryNameInjectable from "../../../main/kubectl/binary-name.injec import kubectlDownloadingNormalizedArchInjectable from "../../../main/kubectl/normalized-arch.injectable"; import openDeleteClusterDialogInjectable, { type OpenDeleteClusterDialog } from "../../../renderer/components/delete-cluster-dialog/open.injectable"; import { type ApplicationBuilder, getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder"; -import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; import type { Cluster } from "../../../common/cluster/cluster"; import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; @@ -90,7 +89,6 @@ describe("Deleting a cluster", () => { }); builder.beforeWindowStart((windowDi) => { - windowDi.override(storesAndApisCanBeCreatedInjectable, () => true); openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable); }); diff --git a/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx b/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx index efe0cdf554..f37ada1736 100644 --- a/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx +++ b/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx @@ -98,7 +98,10 @@ describe("cluster/namespaces - edit namespace from new tab", () => { }); }); - builder.allowKubeResource("namespaces"); + builder.allowKubeResource({ + apiName: "namespaces", + group: "v1", + }); }); describe("when navigating to namespaces", () => { diff --git a/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx b/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx index a5f834563b..4cf63ee353 100644 --- a/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx +++ b/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx @@ -42,7 +42,10 @@ describe("cluster/namespaces - edit namespaces from previously opened tab", () = windowDi.override(callForResourceInjectable, () => callForNamespaceMock); }); - builder.allowKubeResource("namespaces"); + builder.allowKubeResource({ + apiName: "namespaces", + group: "v1", + }); }); describe("given tab was previously opened, when application is started", () => { diff --git a/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts b/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts index 93005543db..e4df4c7f8e 100644 --- a/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts +++ b/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; -import { beforeFrameStartsInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; import initClusterStoreInjectable from "../../store/renderer/init.injectable"; import requestInitialClusterStatesInjectable from "./request-initial.injectable"; @@ -23,7 +23,7 @@ const setupClusterStateSyncInjectable = getInjectable({ }, runAfter: di.inject(initClusterStoreInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupClusterStateSyncInjectable; diff --git a/src/features/cluster/store/renderer/init.injectable.ts b/src/features/cluster/store/renderer/init.injectable.ts index 2c2795de5c..66d2e31e09 100644 --- a/src/features/cluster/store/renderer/init.injectable.ts +++ b/src/features/cluster/store/renderer/init.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; -import { beforeFrameStartsInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; import initUserStoreInjectable from "../../../../renderer/stores/init-user-store.injectable"; const initClusterStoreInjectable = getInjectable({ @@ -18,7 +18,7 @@ const initClusterStoreInjectable = getInjectable({ }, runAfter: di.inject(initUserStoreInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initClusterStoreInjectable; diff --git a/src/features/cluster/visibility-of-sidebar-items.test.tsx b/src/features/cluster/visibility-of-sidebar-items.test.tsx index 0e09f0efa8..2eb42b7fdf 100644 --- a/src/features/cluster/visibility-of-sidebar-items.test.tsx +++ b/src/features/cluster/visibility-of-sidebar-items.test.tsx @@ -9,11 +9,11 @@ import { sidebarItemsInjectionToken } from "../../renderer/components/layout/sid import { computed, runInAction } from "mobx"; import { routeSpecificComponentInjectionToken } from "../../renderer/routes/route-specific-component-injection-token"; import React from "react"; -import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable"; import { frontEndRouteInjectionToken } from "../../common/front-end-routing/front-end-route-injection-token"; import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token"; +import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; describe("cluster - visibility of sidebar items", () => { let builder: ApplicationBuilder; @@ -50,7 +50,10 @@ describe("cluster - visibility of sidebar items", () => { describe("when kube resource becomes allowed", () => { beforeEach(() => { - builder.allowKubeResource("namespaces"); + builder.allowKubeResource({ + apiName: "namespaces", + group: "v1", + }); }); it("renders", () => { @@ -69,20 +72,14 @@ describe("cluster - visibility of sidebar items", () => { const testRouteInjectable = getInjectable({ id: "some-route-injectable-id", - instantiate: (di) => { - const someKubeResourceName = "namespaces"; - - const kubeResourceIsAllowed = di.inject( - isAllowedResourceInjectable, - someKubeResourceName, - ); - - return { - path: "/some-child-page", - isEnabled: kubeResourceIsAllowed, - clusterFrame: true, - }; - }, + instantiate: (di) => ({ + path: "/some-child-page", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "namespaces", + group: "v1", + }), + }), injectionToken: frontEndRouteInjectionToken, }); diff --git a/src/features/cluster/workload-overview.test.tsx b/src/features/cluster/workload-overview.test.tsx index 1a6750a8f9..205c837b47 100644 --- a/src/features/cluster/workload-overview.test.tsx +++ b/src/features/cluster/workload-overview.test.tsx @@ -13,7 +13,10 @@ describe("workload overview", () => { beforeEach(async () => { applicationBuilder = getApplicationBuilder().setEnvironmentToClusterFrame(); - applicationBuilder.allowKubeResource("pods"); + applicationBuilder.allowKubeResource({ + apiName: "pods", + group: "v1", + }); rendered = await applicationBuilder.render(); }); diff --git a/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx b/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx index 306582a188..36844cb3af 100644 --- a/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx +++ b/src/features/entity-settings/showing-settings-for-correct-entity.test.tsx @@ -11,7 +11,7 @@ import type { Cluster } from "../../common/cluster/cluster"; import navigateToEntitySettingsInjectable from "../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; import catalogEntityRegistryInjectable from "../../renderer/api/catalog/entity/registry.injectable"; import { type ApplicationBuilder, getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; -import createClusterInjectable from "../../renderer/create-cluster/create-cluster.injectable"; +import createClusterInjectable from "../../renderer/cluster/create-cluster.injectable"; describe("Showing correct entity settings", () => { let builder: ApplicationBuilder; diff --git a/src/features/file-system-provisioner/renderer/init-store.injectable.ts b/src/features/file-system-provisioner/renderer/init-store.injectable.ts index d241dcad38..0e64e49fd6 100644 --- a/src/features/file-system-provisioner/renderer/init-store.injectable.ts +++ b/src/features/file-system-provisioner/renderer/init-store.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import fileSystemProvisionerStoreInjectable from "../../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable"; -import { beforeFrameStartsInjectionToken } from "../../../renderer/before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../renderer/before-frame-starts/tokens"; const initFileSystemProvisionerStoreInjectable = getInjectable({ id: "init-file-system-provisioner-store", @@ -16,7 +16,7 @@ const initFileSystemProvisionerStoreInjectable = getInjectable({ store.load(); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initFileSystemProvisionerStoreInjectable; diff --git a/src/features/hotbar/store/renderer/init.injectable.ts b/src/features/hotbar/store/renderer/init.injectable.ts index 6d341f1ee8..c17e3a4858 100644 --- a/src/features/hotbar/store/renderer/init.injectable.ts +++ b/src/features/hotbar/store/renderer/init.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; -import { beforeFrameStartsInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; import initClusterStoreInjectable from "../../../cluster/store/renderer/init.injectable"; const initHotbarStoreInjectable = getInjectable({ @@ -18,7 +18,7 @@ const initHotbarStoreInjectable = getInjectable({ }, runAfter: di.inject(initClusterStoreInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initHotbarStoreInjectable; diff --git a/src/features/theme/system-type/renderer/initialize.injectable.ts b/src/features/theme/system-type/renderer/initialize.injectable.ts index e6ed81f4f1..849c8328b2 100644 --- a/src/features/theme/system-type/renderer/initialize.injectable.ts +++ b/src/features/theme/system-type/renderer/initialize.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; import initUserStoreInjectable from "../../../../renderer/stores/init-user-store.injectable"; import systemThemeConfigurationInjectable from "../../../../renderer/themes/system-theme.injectable"; import requestInitialSystemThemeTypeInjectable from "./request-initial.injectable"; @@ -20,7 +20,7 @@ const initializeSystemThemeTypeInjectable = getInjectable({ }, runAfter: di.inject(initUserStoreInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initializeSystemThemeTypeInjectable; diff --git a/src/jest.setup.ts b/src/jest.setup.ts index b0acfba878..1e7d0a8ce3 100644 --- a/src/jest.setup.ts +++ b/src/jest.setup.ts @@ -3,22 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import configurePackages from "./common/configure-packages"; import { configure } from "mobx"; import { setImmediate } from "timers"; import { TextEncoder, TextDecoder as TextDecoderNode } from "util"; import glob from "glob"; import path from "path"; - -// setup default configuration for external npm-packages -configurePackages(); +import { enableMapSet, setAutoFreeze } from "immer"; configure({ // Needed because we want to use jest.spyOn() // ref https://github.com/mobxjs/mobx/issues/2784 safeDescriptors: false, + enforceActions: "never", }); +setAutoFreeze(false); // allow to merge mobx observables +enableMapSet(); // allow to merge maps and sets + // Mock __non_webpack_require__ for tests globalThis.__non_webpack_require__ = jest.fn(); diff --git a/src/main/__test__/cluster.test.ts b/src/main/__test__/cluster.test.ts index 4a9b257623..d9e48fb3d9 100644 --- a/src/main/__test__/cluster.test.ts +++ b/src/main/__test__/cluster.test.ts @@ -10,7 +10,7 @@ import { getDiForUnitTesting } from "../getDiForUnitTesting"; import type { CreateCluster } from "../../common/cluster/create-cluster-injection-token"; import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token"; import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable"; -import authorizationNamespaceReviewInjectable from "../../common/cluster/authorization-namespace-review.injectable"; +import requestNamespaceListPermissionsForInjectable from "../../common/cluster/request-namespace-list-permissions.injectable"; import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable"; import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable"; import type { ClusterContextHandler } from "../context-handler/context-handler"; @@ -20,8 +20,6 @@ import directoryForTempInjectable from "../../common/app-paths/directory-for-tem import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable"; import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable"; -import { apiResourceRecord, apiResources } from "../../common/rbac"; -import listApiResourcesInjectable from "../../common/cluster/list-api-resources.injectable"; import pathExistsSyncInjectable from "../../common/fs/path-exists-sync.injectable"; import pathExistsInjectable from "../../common/fs/path-exists.injectable"; import readJsonSyncInjectable from "../../common/fs/read-json-sync.injectable"; @@ -46,8 +44,7 @@ describe("create clusters", () => { di.override(normalizedPlatformInjectable, () => "darwin"); di.override(broadcastMessageInjectable, () => async () => {}); di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true)); - di.override(authorizationNamespaceReviewInjectable, () => () => () => Promise.resolve(Object.keys(apiResourceRecord))); - di.override(listApiResourcesInjectable, () => () => () => Promise.resolve(apiResources)); + di.override(requestNamespaceListPermissionsForInjectable, () => () => async () => () => true); di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ])); di.override(createContextHandlerInjectable, () => (cluster) => ({ restartServer: jest.fn(), diff --git a/src/main/cluster/manager.ts b/src/main/cluster/manager.ts index d5c06c0619..83270941d4 100644 --- a/src/main/cluster/manager.ts +++ b/src/main/cluster/manager.ts @@ -210,7 +210,7 @@ export class ClusterManager { cluster.contextName = entity.spec.kubeconfigContext; if (entity.spec.accessibleNamespaces) { - cluster.accessibleNamespaces = entity.spec.accessibleNamespaces; + cluster.accessibleNamespaces.replace(entity.spec.accessibleNamespaces); } if (entity.spec.metrics) { diff --git a/src/main/create-cluster/allowed-resources.injectable.ts b/src/main/create-cluster/allowed-resources.injectable.ts index d614ca3d17..09beae9fd2 100644 --- a/src/main/create-cluster/allowed-resources.injectable.ts +++ b/src/main/create-cluster/allowed-resources.injectable.ts @@ -2,15 +2,20 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getInjectable } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import { allowedResourcesInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; +import type { KubeApiResourceDescriptor } from "../../common/rbac"; +import { formatKubeApiResource } from "../../common/rbac"; // TODO: Figure out implementation for this later. const allowedResourcesInjectable = getInjectable({ id: "allowed-resources", - instantiate: () => computed(() => new Set()), - injectionToken: allowedResourcesInjectionToken, + instantiate: () => computed(() => false), + injectionToken: shouldShowResourceInjectionToken, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, resource: KubeApiResourceDescriptor) => formatKubeApiResource(resource), + }), }); export default allowedResourcesInjectable; diff --git a/src/main/create-cluster/create-cluster.injectable.ts b/src/main/create-cluster/create-cluster.injectable.ts index e1782ec6c9..f5b302300c 100644 --- a/src/main/create-cluster/create-cluster.injectable.ts +++ b/src/main/create-cluster/create-cluster.injectable.ts @@ -11,14 +11,14 @@ import createKubectlInjectable from "../kubectl/create-kubectl.injectable"; import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable"; import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token"; import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable"; -import createAuthorizationNamespaceReview from "../../common/cluster/authorization-namespace-review.injectable"; import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable"; -import createListApiResourcesInjectable from "../../common/cluster/list-api-resources.injectable"; +import createListApiResourcesInjectable from "../../common/cluster/request-api-resources.injectable"; import loggerInjectable from "../../common/logger.injectable"; import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable"; import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable"; import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable"; import loadConfigfromFileInjectable from "../../common/kube-helpers/load-config-from-file.injectable"; +import requestNamespaceListPermissionsForInjectable from "../../common/cluster/request-namespace-list-permissions.injectable"; const createClusterInjectable = getInjectable({ id: "create-cluster", @@ -30,8 +30,8 @@ const createClusterInjectable = getInjectable({ createKubectl: di.inject(createKubectlInjectable), createContextHandler: di.inject(createContextHandlerInjectable), createAuthorizationReview: di.inject(authorizationReviewInjectable), - createAuthorizationNamespaceReview: di.inject(createAuthorizationNamespaceReview), - createListApiResources: di.inject(createListApiResourcesInjectable), + requestNamespaceListPermissionsFor: di.inject(requestNamespaceListPermissionsForInjectable), + requestApiResources: di.inject(createListApiResourcesInjectable), createListNamespaces: di.inject(listNamespacesInjectable), logger: di.inject(loggerInjectable), detectorRegistry: di.inject(detectorRegistryInjectable), diff --git a/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts index 9d760a8bc2..d2d8f344eb 100644 --- a/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts +++ b/src/main/start-main-application/lens-window/application-window/create-lens-window.injectable.ts @@ -5,7 +5,6 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { ContentSource, ElectronWindowTitleBarStyle } from "./create-electron-window.injectable"; import createElectronWindowForInjectable from "./create-electron-window.injectable"; -import assert from "assert"; import type { ClusterFrameInfo } from "../../../../common/cluster-frames"; export interface ElectronWindow { @@ -69,7 +68,9 @@ const createLensWindowInjectable = getInjectable({ let windowIsStarting = false; const showWindow = () => { - assert(browserWindow); + if (!browserWindow) { + throw new Error("Cannot show browserWindow, does not exist"); + } browserWindow.show(); windowIsShown = true; diff --git a/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts b/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts index 02a00523ad..ef85a84cd0 100644 --- a/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { customMonacoThemeInjectionToken } from "../../components/monaco-editor"; import addNewMonacoThemeInjectable from "../../monaco/add-new-theme.injectable"; -import { beforeFrameStartsInjectionToken } from "../tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../tokens"; const loadMonacoThemesInjectable = getInjectable({ id: "load-monaco-themes", @@ -18,7 +18,7 @@ const loadMonacoThemesInjectable = getInjectable({ customThemes.forEach(addNewMonacoTheme); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default loadMonacoThemesInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts index c2c58c22b0..ecfac3136a 100644 --- a/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts @@ -9,7 +9,9 @@ import { CustomResourceStore } from "../../../common/k8s-api/api-manager/resourc import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints"; import { KubeApi } from "../../../common/k8s-api/kube-api"; import { KubeObject } from "../../../common/k8s-api/kube-object"; -import { beforeClusterFrameStartsInjectionToken } from "../tokens"; +import { beforeClusterFrameStartsSecondInjectionToken } from "../tokens"; +import type { KubeObjectStoreDependencies } from "../../../common/k8s-api/kube-object.store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const setupAutoRegistrationInjectable = getInjectable({ id: "setup-auto-registration", @@ -19,6 +21,9 @@ const setupAutoRegistrationInjectable = getInjectable({ const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable); const beforeApiManagerInitializationCrds: CustomResourceDefinition[] = []; const beforeApiManagerInitializationApis: KubeApi[] = []; + const deps: KubeObjectStoreDependencies = { + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }; let initialized = false; const autoInitCustomResourceStore = (crd: CustomResourceDefinition) => { @@ -43,7 +48,7 @@ const setupAutoRegistrationInjectable = getInjectable({ })(); if (!apiManager.getStore(api)) { - apiManager.registerStore(new CustomResourceStore(api)); + apiManager.registerStore(new CustomResourceStore(deps, api)); } }; const autoInitKubeApi = (api: KubeApi) => { @@ -66,6 +71,7 @@ const setupAutoRegistrationInjectable = getInjectable({ } }); + // NOTE: this MUST happen after the event emitter listeners are registered const apiManager = di.inject(apiManagerInjectable); beforeApiManagerInitializationCrds.forEach(autoInitCustomResourceStore); @@ -73,7 +79,7 @@ const setupAutoRegistrationInjectable = getInjectable({ initialized = true; }, }), - injectionToken: beforeClusterFrameStartsInjectionToken, + injectionToken: beforeClusterFrameStartsSecondInjectionToken, }); export default setupAutoRegistrationInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts index 978eae3d95..a89ba58ec2 100644 --- a/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts @@ -7,7 +7,7 @@ import { reaction } from "mobx"; import { currentClusterMessageChannel } from "../../../common/cluster/current-cluster-channel"; import { sendMessageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token"; import matchedClusterIdInjectable from "../../navigation/matched-cluster-id.injectable"; -import { beforeMainFrameStartsInjectionToken } from "../tokens"; +import { beforeMainFrameStartsFirstInjectionToken } from "../tokens"; const setupCurrentClusterBroadcastInjectable = getInjectable({ id: "setup-current-cluster-broadcast", @@ -26,7 +26,7 @@ const setupCurrentClusterBroadcastInjectable = getInjectable({ ); }, }), - injectionToken: beforeMainFrameStartsInjectionToken, + injectionToken: beforeMainFrameStartsFirstInjectionToken, }); export default setupCurrentClusterBroadcastInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-catalog-add-menu.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-catalog-add-menu.injectable.ts index 406b1641af..20566b5dac 100644 --- a/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-catalog-add-menu.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-catalog-add-menu.injectable.ts @@ -9,7 +9,7 @@ import isLinuxInjectable from "../../../common/vars/is-linux.injectable"; import isWindowsInjectable from "../../../common/vars/is-windows.injectable"; import openPathPickingDialogInjectable from "../../../features/path-picking-dialog/renderer/pick-paths.injectable"; import addSyncEntriesInjectable from "../../initializers/add-sync-entries.injectable"; -import { beforeFrameStartsInjectionToken } from "../tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../tokens"; const setupKubernetesClusterCatalogAddMenuListenerInjectable = getInjectable({ id: "setup-kubernetes-cluster-catalog-add-menu-listener", @@ -75,7 +75,7 @@ const setupKubernetesClusterCatalogAddMenuListenerInjectable = getInjectable({ }); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupKubernetesClusterCatalogAddMenuListenerInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts index 150620c22a..73cc988e2c 100644 --- a/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts @@ -9,7 +9,7 @@ import readFileInjectable from "../../../common/fs/read-file.injectable"; import { loadConfigFromString } from "../../../common/kube-helpers"; import loggerInjectable from "../../../common/logger.injectable"; import openDeleteClusterDialogInjectable from "../../components/delete-cluster-dialog/open.injectable"; -import { beforeFrameStartsInjectionToken } from "../tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../tokens"; const setupKubernetesClusterContextMenuOpenInjectable = getInjectable({ id: "setup-kubernetes-cluster-context-menu-open", @@ -50,7 +50,7 @@ const setupKubernetesClusterContextMenuOpenInjectable = getInjectable({ }); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupKubernetesClusterContextMenuOpenInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts index 59a684ccc9..56b9ce66fa 100644 --- a/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import isMacInjectable from "../../../common/vars/is-mac.injectable"; -import { beforeFrameStartsInjectionToken } from "../tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../tokens"; const setupRootMacClassnameInjectable = getInjectable({ id: "setup-root-mac-classname", @@ -17,7 +17,7 @@ const setupRootMacClassnameInjectable = getInjectable({ rootElem?.classList.toggle("is-mac", isMac); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupRootMacClassnameInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts index 401cec99d5..c6af3b7614 100644 --- a/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts +++ b/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import initializeSentryReportingWithInjectable from "../../../common/error-reporting/initialize-sentry-reporting.injectable"; -import { beforeMainFrameStartsInjectionToken } from "../tokens"; +import { beforeMainFrameStartsFirstInjectionToken } from "../tokens"; import { init } from "@sentry/electron/renderer"; const setupSentryInjectable = getInjectable({ @@ -17,7 +17,7 @@ const setupSentryInjectable = getInjectable({ initializeSentryReportingWith(init); }, }), - injectionToken: beforeMainFrameStartsInjectionToken, + injectionToken: beforeMainFrameStartsFirstInjectionToken, }); export default setupSentryInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-weblink-context-menu-open.injectable.tsx b/src/renderer/before-frame-starts/runnables/setup-weblink-context-menu-open.injectable.tsx index 4c027d6047..7bf8f29422 100644 --- a/src/renderer/before-frame-starts/runnables/setup-weblink-context-menu-open.injectable.tsx +++ b/src/renderer/before-frame-starts/runnables/setup-weblink-context-menu-open.injectable.tsx @@ -7,7 +7,7 @@ import React from "react"; import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable"; import { WeblinkAddCommand } from "../../components/catalog-entities/weblink-add-command"; import commandOverlayInjectable from "../../components/command-palette/command-overlay.injectable"; -import { beforeFrameStartsInjectionToken } from "../tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../tokens"; const setupWeblickContextMenuOpenInjectable = getInjectable({ id: "setup-weblick-context-menu-open", @@ -28,7 +28,7 @@ const setupWeblickContextMenuOpenInjectable = getInjectable({ }); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupWeblickContextMenuOpenInjectable; diff --git a/src/renderer/before-frame-starts/tokens.ts b/src/renderer/before-frame-starts/tokens.ts index 77b0350315..460c44cde9 100644 --- a/src/renderer/before-frame-starts/tokens.ts +++ b/src/renderer/before-frame-starts/tokens.ts @@ -7,20 +7,30 @@ import type { Runnable } from "../../common/runnable/run-many-for"; // NOTE: these are run before any other token, mostly to set up things that all other runnables need export const beforeFrameStartsFirstInjectionToken = getInjectionToken({ - id: "even-before-frame-starts", + id: "before-frame-starts-first", }); // NOTE: these are only run when process.isMainFrame === true -export const beforeMainFrameStartsInjectionToken = getInjectionToken({ - id: "even-before-main-frame-starts", +export const beforeMainFrameStartsFirstInjectionToken = getInjectionToken({ + id: "before-main-frame-starts-first", }); // NOTE: these are only run when process.isMainFrame === false -export const beforeClusterFrameStartsInjectionToken = getInjectionToken({ - id: "even-before-cluster-frame-starts", +export const beforeClusterFrameStartsFirstInjectionToken = getInjectionToken({ + id: "before-cluster-frame-starts-first", }); -export const beforeFrameStartsInjectionToken = getInjectionToken({ - id: "before-frame-starts", +export const beforeFrameStartsSecondInjectionToken = getInjectionToken({ + id: "before-frame-starts-second", +}); + +// NOTE: these are only run when process.isMainFrame === true +export const beforeMainFrameStartsSecondInjectionToken = getInjectionToken({ + id: "before-main-frame-starts-second", +}); + +// NOTE: these are only run when process.isMainFrame === false +export const beforeClusterFrameStartsSecondInjectionToken = getInjectionToken({ + id: "before-cluster-frame-starts-second", }); diff --git a/src/renderer/cluster-frame-context/allowed-resources.injectable.ts b/src/renderer/cluster-frame-context/allowed-resources.injectable.ts deleted file mode 100644 index 646d931367..0000000000 --- a/src/renderer/cluster-frame-context/allowed-resources.injectable.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { comparer, computed } from "mobx"; -import hostedClusterInjectable from "./hosted-cluster.injectable"; -import { allowedResourcesInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; - -const allowedResourcesInjectable = getInjectable({ - id: "allowed-resources", - - instantiate: (di) => { - const cluster = di.inject(hostedClusterInjectable); - - return computed(() => new Set(cluster?.allowedResources), { - // This needs to be here so that during refresh changes are only propogated when necessary - equals: (cur, prev) => comparer.structural(cur, prev), - }); - }, - - injectionToken: allowedResourcesInjectionToken, -}); - -export default allowedResourcesInjectable; diff --git a/src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts b/src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts deleted file mode 100644 index a353a30b40..0000000000 --- a/src/renderer/cluster-frame-context/cluster-frame-context.injectable.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { ClusterFrameContext } from "./cluster-frame-context"; -import namespaceStoreInjectable from "../components/+namespaces/store.injectable"; -import hostedClusterInjectable from "./hosted-cluster.injectable"; -import assert from "assert"; - -const clusterFrameContextInjectable = getInjectable({ - id: "cluster-frame-context", - - instantiate: (di) => { - const cluster = di.inject(hostedClusterInjectable); - - assert(cluster, "This can only be injected within a cluster frame"); - - return new ClusterFrameContext(cluster, { - namespaceStore: di.inject(namespaceStoreInjectable), - }); - }, -}); - -export default clusterFrameContextInjectable; diff --git a/src/renderer/cluster-frame-context/cluster-frame-context.ts b/src/renderer/cluster-frame-context/cluster-frame-context.ts index 1f1625e9e1..e23bac5fd4 100755 --- a/src/renderer/cluster-frame-context/cluster-frame-context.ts +++ b/src/renderer/cluster-frame-context/cluster-frame-context.ts @@ -3,44 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { Cluster } from "../../common/cluster/cluster"; -import type { NamespaceStore } from "../components/+namespaces/store"; -import type { ClusterContext } from "../../common/k8s-api/cluster-context"; -import { computed, makeObservable } from "mobx"; +/** + * This type is used for KubeObjectStores + */ +export interface ClusterContext { + readonly allNamespaces: string[]; // available / allowed namespaces from cluster.ts + readonly contextNamespaces: string[]; // selected by user (see: namespace-select.tsx) + readonly hasSelectedAll: boolean; -interface Dependencies { - namespaceStore: NamespaceStore; -} - -export class ClusterFrameContext implements ClusterContext { - constructor(public cluster: Cluster, private dependencies: Dependencies) { - makeObservable(this); - } - - @computed get allNamespaces(): string[] { - // user given list of namespaces - if (this.cluster.accessibleNamespaces.length) { - return this.cluster.accessibleNamespaces; - } - - if (this.dependencies.namespaceStore.items.length > 0) { - // namespaces from kubernetes api - return this.dependencies.namespaceStore.items.map((namespace) => namespace.getName()); - } else { - // fallback to cluster resolved namespaces because we could not load list - return this.cluster.allowedNamespaces || []; - } - } - - @computed get contextNamespaces(): string[] { - return this.dependencies.namespaceStore.contextNamespaces; - } - - @computed get hasSelectedAll(): boolean { - const namespaces = new Set(this.contextNamespaces); - - return this.allNamespaces?.length > 1 - && this.cluster.accessibleNamespaces.length === 0 - && this.allNamespaces.every(ns => namespaces.has(ns)); - } + isLoadingAll(namespaces: string[]): boolean; + isGlobalWatchEnabled(): boolean; } diff --git a/src/renderer/cluster-frame-context/for-cluster-scoped-resources.injectable.ts b/src/renderer/cluster-frame-context/for-cluster-scoped-resources.injectable.ts new file mode 100644 index 0000000000..12e74cdc1f --- /dev/null +++ b/src/renderer/cluster-frame-context/for-cluster-scoped-resources.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { ClusterContext } from "./cluster-frame-context"; + +const clusterFrameContextForClusterScopedResourcesInjectable = getInjectable({ + id: "cluster-frame-context-for-cluster-scoped-resources", + instantiate: (): ClusterContext => ({ + // This doesn't matter as it is an optimization for namespaced resources only + isGlobalWatchEnabled: () => true, + // This is always the case for cluster scoped resources + isLoadingAll: () => true, + allNamespaces: [], + contextNamespaces: [], + hasSelectedAll: true, + }), +}); + +export default clusterFrameContextForClusterScopedResourcesInjectable; diff --git a/src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts b/src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts new file mode 100644 index 0000000000..1eb85b3a43 --- /dev/null +++ b/src/renderer/cluster-frame-context/for-namespaced-resources.injectable.ts @@ -0,0 +1,64 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { ClusterContext } from "./cluster-frame-context"; +import namespaceStoreInjectable from "../components/+namespaces/store.injectable"; +import hostedClusterInjectable from "./hosted-cluster.injectable"; +import assert from "assert"; +import { computed } from "mobx"; + +const clusterFrameContextForNamespacedResourcesInjectable = getInjectable({ + id: "cluster-frame-context-for-namespaced-resources", + + instantiate: (di): ClusterContext => { + const cluster = di.inject(hostedClusterInjectable); + const namespaceStore = di.inject(namespaceStoreInjectable); + + assert(cluster, "This can only be injected within a cluster frame"); + + const allNamespaces = computed(() => { + // user given list of namespaces + if (cluster.accessibleNamespaces.length) { + return cluster.accessibleNamespaces.slice(); + } + + if (namespaceStore.items.length > 0) { + // namespaces from kubernetes api + return namespaceStore.items.map((namespace) => namespace.getName()); + } + + // fallback to cluster resolved namespaces because we could not load list + return cluster.allowedNamespaces.slice(); + }); + const contextNamespaces = computed(() => namespaceStore.contextNamespaces); + const hasSelectedAll = computed(() => { + const namespaces = new Set(contextNamespaces.get()); + + return allNamespaces.get().length > 1 + && cluster.accessibleNamespaces.length === 0 + && allNamespaces.get().every(ns => namespaces.has(ns)); + }); + + return { + isLoadingAll: (namespaces) => ( + allNamespaces.get().length > 1 + && cluster.accessibleNamespaces.length === 0 + && allNamespaces.get().every(ns => namespaces.includes(ns)) + ), + isGlobalWatchEnabled: () => cluster.isGlobalWatchEnabled, + get allNamespaces() { + return allNamespaces.get(); + }, + get contextNamespaces() { + return contextNamespaces.get(); + }, + get hasSelectedAll() { + return hasSelectedAll.get(); + }, + }; + }, +}); + +export default clusterFrameContextForNamespacedResourcesInjectable; diff --git a/src/renderer/cluster-frame-context/should-show-resource.injectable.ts b/src/renderer/cluster-frame-context/should-show-resource.injectable.ts new file mode 100644 index 0000000000..2ec4132045 --- /dev/null +++ b/src/renderer/cluster-frame-context/should-show-resource.injectable.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import hostedClusterInjectable from "./hosted-cluster.injectable"; +import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; +import type { KubeApiResourceDescriptor } from "../../common/rbac"; +import { formatKubeApiResource } from "../../common/rbac"; + +const shouldShowResourceInjectable = getInjectable({ + id: "should-show-resource", + instantiate: (di, resource) => { + const cluster = di.inject(hostedClusterInjectable); + + return cluster + ? computed(() => cluster.shouldShowResource(resource)) + : computed(() => false); + }, + injectionToken: shouldShowResourceInjectionToken, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, resource: KubeApiResourceDescriptor) => formatKubeApiResource(resource), + }), +}); + +export default shouldShowResourceInjectable; diff --git a/src/renderer/cluster/accessible-namespaces.injectable.ts b/src/renderer/cluster/accessible-namespaces.injectable.ts new file mode 100644 index 0000000000..50743cfa83 --- /dev/null +++ b/src/renderer/cluster/accessible-namespaces.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import hostedClusterInjectable from "../cluster-frame-context/hosted-cluster.injectable"; + +const clusterConfiguredAccessibleNamespacesInjectable = getInjectable({ + id: "cluster-configured-accessible-namespaces", + instantiate: (di) => { + const hostedCluster = di.inject(hostedClusterInjectable); + + return computed(() => [...hostedCluster?.accessibleNamespaces ?? []]); + }, +}); + +export default clusterConfiguredAccessibleNamespacesInjectable; diff --git a/src/renderer/create-cluster/create-cluster.injectable.ts b/src/renderer/cluster/create-cluster.injectable.ts similarity index 92% rename from src/renderer/create-cluster/create-cluster.injectable.ts rename to src/renderer/cluster/create-cluster.injectable.ts index e0a9f51656..385dfe8d66 100644 --- a/src/renderer/create-cluster/create-cluster.injectable.ts +++ b/src/renderer/cluster/create-cluster.injectable.ts @@ -27,9 +27,9 @@ const createClusterInjectable = getInjectable({ createKubectl: () => { throw new Error("Tried to access back-end feature in front-end.");}, createContextHandler: () => undefined as never, createAuthorizationReview: () => { throw new Error("Tried to access back-end feature in front-end."); }, - createAuthorizationNamespaceReview: () => { throw new Error("Tried to access back-end feature in front-end."); }, + requestNamespaceListPermissionsFor: () => { throw new Error("Tried to access back-end feature in front-end."); }, createListNamespaces: () => { throw new Error("Tried to access back-end feature in front-end."); }, - createListApiResources: ()=> { throw new Error("Tried to access back-end feature in front-end."); }, + requestApiResources: ()=> { throw new Error("Tried to access back-end feature in front-end."); }, detectorRegistry: undefined as never, createVersionDetector: () => { throw new Error("Tried to access back-end feature in front-end."); }, }; diff --git a/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.injectable.ts b/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.injectable.ts index 6389009785..a4666adcef 100644 --- a/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.injectable.ts +++ b/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.injectable.ts @@ -13,6 +13,7 @@ import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-cre import assert from "assert"; import nodeStoreInjectable from "../../+nodes/store.injectable"; import requestClusterMetricsByNodeNamesInjectable from "../../../../common/k8s-api/endpoints/metrics.api/request-cluster-metrics-by-node-names.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; const clusterOverviewStoreInjectable = getInjectable({ id: "cluster-overview-store", @@ -32,6 +33,7 @@ const clusterOverviewStoreInjectable = getInjectable({ ), nodeStore: di.inject(nodeStoreInjectable), requestClusterMetricsByNodeNames: di.inject(requestClusterMetricsByNodeNamesInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, clusterApi); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts b/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts index 2669cb71a6..943b14dbf9 100644 --- a/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts +++ b/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts @@ -4,6 +4,7 @@ */ import { action, observable, reaction, when, makeObservable } from "mobx"; +import type { KubeObjectStoreDependencies } from "../../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store"; import type { Cluster, ClusterApi } from "../../../../common/k8s-api/endpoints"; import type { StorageLayer } from "../../../utils"; @@ -28,7 +29,7 @@ export interface ClusterOverviewStorageState { metricNodeRole: MetricNodeRole; } -interface ClusterOverviewStoreDependencies { +interface ClusterOverviewStoreDependencies extends KubeObjectStoreDependencies { readonly storage: StorageLayer; readonly nodeStore: NodeStore; requestClusterMetricsByNodeNames: RequestClusterMetricsByNodeNames; @@ -58,7 +59,7 @@ export class ClusterOverviewStore extends KubeObjectStore i } constructor(protected readonly dependencies: ClusterOverviewStoreDependencies, api: ClusterApi) { - super(api); + super(dependencies, api); makeObservable(this); autoBind(this); diff --git a/src/renderer/components/+config-autoscalers/store.injectable.ts b/src/renderer/components/+config-autoscalers/store.injectable.ts index a2d271df5d..4d4a3adc21 100644 --- a/src/renderer/components/+config-autoscalers/store.injectable.ts +++ b/src/renderer/components/+config-autoscalers/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import horizontalPodAutoscalerApiInjectable from "../../../common/k8s-api/endpoints/horizontal-pod-autoscaler.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { HorizontalPodAutoscalerStore } from "./store"; @@ -16,7 +17,9 @@ const horizontalPodAutoscalerStoreInjectable = getInjectable({ const api = di.inject(horizontalPodAutoscalerApiInjectable); - return new HorizontalPodAutoscalerStore(api); + return new HorizontalPodAutoscalerStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-leases/store.injectable.ts b/src/renderer/components/+config-leases/store.injectable.ts index b9dda24b07..eb9676ce90 100644 --- a/src/renderer/components/+config-leases/store.injectable.ts +++ b/src/renderer/components/+config-leases/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import leaseApiInjectable from "../../../common/k8s-api/endpoints/lease.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { LeaseStore } from "./store"; @@ -16,7 +17,9 @@ const leaseStoreInjectable = getInjectable({ const api = di.inject(leaseApiInjectable); - return new LeaseStore(api); + return new LeaseStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-limit-ranges/store.injectable.ts b/src/renderer/components/+config-limit-ranges/store.injectable.ts index 4f1224131a..494968b60c 100644 --- a/src/renderer/components/+config-limit-ranges/store.injectable.ts +++ b/src/renderer/components/+config-limit-ranges/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import limitRangeApiInjectable from "../../../common/k8s-api/endpoints/limit-range.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { LimitRangeStore } from "./store"; @@ -16,7 +17,9 @@ const limitRangeStoreInjectable = getInjectable({ const api = di.inject(limitRangeApiInjectable); - return new LimitRangeStore(api); + return new LimitRangeStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-maps/store.injectable.ts b/src/renderer/components/+config-maps/store.injectable.ts index d16da8318e..7cbd59e4a5 100644 --- a/src/renderer/components/+config-maps/store.injectable.ts +++ b/src/renderer/components/+config-maps/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import configMapApiInjectable from "../../../common/k8s-api/endpoints/config-map.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { ConfigMapStore } from "./store"; @@ -16,7 +17,9 @@ const configMapStoreInjectable = getInjectable({ const api = di.inject(configMapApiInjectable); - return new ConfigMapStore(api); + return new ConfigMapStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-pod-disruption-budgets/store.injectable.ts b/src/renderer/components/+config-pod-disruption-budgets/store.injectable.ts index 5314584274..697fd5c444 100644 --- a/src/renderer/components/+config-pod-disruption-budgets/store.injectable.ts +++ b/src/renderer/components/+config-pod-disruption-budgets/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import podDisruptionBudgetApiInjectable from "../../../common/k8s-api/endpoints/pod-disruption-budget.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { PodDisruptionBudgetStore } from "./store"; @@ -16,7 +17,9 @@ const podDisruptionBudgetStoreInjectable = getInjectable({ const api = di.inject(podDisruptionBudgetApiInjectable); - return new PodDisruptionBudgetStore(api); + return new PodDisruptionBudgetStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-priority-classes/store.injectable.ts b/src/renderer/components/+config-priority-classes/store.injectable.ts index 68677a3b5f..b01e69602c 100644 --- a/src/renderer/components/+config-priority-classes/store.injectable.ts +++ b/src/renderer/components/+config-priority-classes/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import priorityClassApiInjectable from "../../../common/k8s-api/endpoints/priority-class.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { PriorityClassStore } from "./store"; @@ -16,7 +17,9 @@ const priorityClassStoreInjectable = getInjectable({ const api = di.inject(priorityClassApiInjectable); - return new PriorityClassStore(api); + return new PriorityClassStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-resource-quotas/store.injectable.ts b/src/renderer/components/+config-resource-quotas/store.injectable.ts index d8294c8132..4999ca9517 100644 --- a/src/renderer/components/+config-resource-quotas/store.injectable.ts +++ b/src/renderer/components/+config-resource-quotas/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import resourceQuotaApiInjectable from "../../../common/k8s-api/endpoints/resource-quota.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { ResourceQuotaStore } from "./store"; @@ -16,7 +17,9 @@ const resourceQuotaStoreInjectable = getInjectable({ const api = di.inject(resourceQuotaApiInjectable); - return new ResourceQuotaStore(api); + return new ResourceQuotaStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-runtime-classes/store.injectable.ts b/src/renderer/components/+config-runtime-classes/store.injectable.ts index 0a4a21d716..63e8d82526 100644 --- a/src/renderer/components/+config-runtime-classes/store.injectable.ts +++ b/src/renderer/components/+config-runtime-classes/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import runtimeClassApiInjectable from "../../../common/k8s-api/endpoints/runtime-class.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { RuntimeClassStore } from "./store"; @@ -16,7 +17,9 @@ const runtimeClassStoreInjectable = getInjectable({ const api = di.inject(runtimeClassApiInjectable); - return new RuntimeClassStore(api); + return new RuntimeClassStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx b/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx index f1e3aad2b4..6a586bdd0c 100644 --- a/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx +++ b/src/renderer/components/+config-secrets/__tests__/secret-details.test.tsx @@ -9,6 +9,10 @@ import { Secret, SecretType } from "../../../../common/k8s-api/endpoints"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import { renderFor } from "../../test-utils/renderFor"; import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable"; +import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import hostedClusterInjectable from "../../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../../cluster/create-cluster.injectable"; +import directoryForKubeConfigsInjectable from "../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; jest.mock("../../kube-object-meta/kube-object-meta", () => ({ KubeObjectMeta: () => null, @@ -19,8 +23,20 @@ describe("SecretDetails tests", () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); const render = renderFor(di); + di.override(directoryForUserDataInjectable, () => "/some-user-data"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + const secret = new Secret({ apiVersion: "v1", kind: "secret", diff --git a/src/renderer/components/+config-secrets/store.injectable.ts b/src/renderer/components/+config-secrets/store.injectable.ts index 9e84f20696..2af7bf13f6 100644 --- a/src/renderer/components/+config-secrets/store.injectable.ts +++ b/src/renderer/components/+config-secrets/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import secretApiInjectable from "../../../common/k8s-api/endpoints/secret.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { SecretStore } from "./store"; @@ -16,7 +17,9 @@ const secretStoreInjectable = getInjectable({ const api = di.inject(secretApiInjectable); - return new SecretStore(api); + return new SecretStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+custom-resources/definition.store.injectable.ts b/src/renderer/components/+custom-resources/definition.store.injectable.ts index ee4719cc97..b93f656df9 100644 --- a/src/renderer/components/+custom-resources/definition.store.injectable.ts +++ b/src/renderer/components/+custom-resources/definition.store.injectable.ts @@ -7,6 +7,7 @@ import assert from "assert"; import autoRegistrationEmitterInjectable from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import customResourceDefinitionApiInjectable from "../../../common/k8s-api/endpoints/custom-resource-definition.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { CustomResourceDefinitionStore } from "./definition.store"; @@ -19,6 +20,7 @@ const customResourceDefinitionStoreInjectable = getInjectable({ return new CustomResourceDefinitionStore({ autoRegistration: di.inject(autoRegistrationEmitterInjectable), + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+custom-resources/definition.store.ts b/src/renderer/components/+custom-resources/definition.store.ts index 778f895214..bef5c2323e 100644 --- a/src/renderer/components/+custom-resources/definition.store.ts +++ b/src/renderer/components/+custom-resources/definition.store.ts @@ -4,7 +4,7 @@ */ import { computed, reaction, makeObservable } from "mobx"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import { autoBind } from "../../utils"; import type { CustomResourceDefinition, CustomResourceDefinitionApi } from "../../../common/k8s-api/endpoints/custom-resource-definition.api"; @@ -12,7 +12,7 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object"; import type TypedEventEmitter from "typed-emitter"; import type { LegacyAutoRegistration } from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable"; -export interface CustomResourceDefinitionStoreDependencies { +export interface CustomResourceDefinitionStoreDependencies extends KubeObjectStoreDependencies { readonly autoRegistration: TypedEventEmitter; } @@ -22,7 +22,7 @@ export class CustomResourceDefinitionStore extends KubeObjectStore { constructor( - protected readonly dependencies: Dependencies, + protected readonly dependencies: EventStoreDependencies, api: KubeEventApi, opts: KubeObjectStoreOptions = {}, ) { - super(api, { limit: 1000, ...opts }); + super(dependencies, api, { limit: 1000, ...opts }); autoBind(this); } diff --git a/src/renderer/components/+helm-releases/releases.injectable.ts b/src/renderer/components/+helm-releases/releases.injectable.ts index ed3d8d6b8e..7f4d2d286b 100644 --- a/src/renderer/components/+helm-releases/releases.injectable.ts +++ b/src/renderer/components/+helm-releases/releases.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { asyncComputed } from "@ogre-tools/injectable-react"; -import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import releaseSecretsInjectable from "./release-secrets.injectable"; import requestHelmReleasesInjectable from "../../../common/k8s-api/endpoints/helm-releases.api/request-releases.injectable"; import toHelmReleaseInjectable from "./to-helm-release.injectable"; @@ -13,7 +13,7 @@ const releasesInjectable = getInjectable({ id: "releases", instantiate: (di) => { - const clusterContext = di.inject(clusterFrameContextInjectable); + const clusterContext = di.inject(clusterFrameContextForNamespacedResourcesInjectable); const releaseSecrets = di.inject(releaseSecretsInjectable); const requestHelmReleases = di.inject(requestHelmReleasesInjectable); const toHelmRelease = di.inject(toHelmReleaseInjectable); diff --git a/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap b/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap index 664b0daece..335cc1d159 100644 --- a/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap +++ b/src/renderer/components/+namespaces/__snapshots__/namespace-select-filter.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` renders 1`] = ` +exports[` once the subscribe resolves renders 1`] = `
renders 1`] = ` `; -exports[` when clicked renders 1`] = ` +exports[` once the subscribe resolves when clicked renders 1`] = `
when clicked renders 1`] = ` class="Select__option Select__option--is-selected css-tr4s17-option" id="react-select-namespace-select-filter-option-2" tabindex="-1" - > -
- - - layers - - - - test-2 - - - - check - - -
-
-
-
- - - layers - - - - test-3 - - - - check - - -
-
-
-
- - - layers - - - - test-4 - - - - check - - -
-
-
-
- - - layers - - - - test-5 - - - - check - - -
-
-
-
- - - layers - - - - test-6 - - - - check - - -
-
-
-
- - - layers - - - - test-7 - - - - check - - -
-
-
-
- - - layers - - - - test-8 - - - - check - - -
-
-
-
- - - layers - - - - test-9 - - - - check - - -
-
-
when clicked renders 1`] = `
when clicked renders 1`] = `
when clicked renders 1`] = `
when clicked renders 1`] = `
+
+
+ + + layers + + + + test-2 + + + + check + + +
+
+
+
+ + + layers + + + + test-3 + + + + check + + +
+
+
+
+ + + layers + + + + test-4 + + + + check + + +
+
+
+
+ + + layers + + + + test-5 + + + + check + + +
+
+
+
+ + + layers + + + + test-6 + + + + check + + +
+
+
+
+ + + layers + + + + test-7 + + + + check + + +
+
+
+
+ + + layers + + + + test-8 + + + + check + + +
+
+
+
+ + + layers + + + + test-9 + + + + check + + +
+
`; -exports[` when clicked when 'test-2' is clicked renders 1`] = ` +exports[` once the subscribe resolves when clicked when 'test-2' is clicked renders 1`] = `
when clicked when 'test-2' is clicked renders `; -exports[` when clicked when 'test-2' is clicked when clicked again renders 1`] = ` +exports[` once the subscribe resolves when clicked when 'test-2' is clicked when clicked again renders 1`] = `
when clicked when 'test-2' is clicked when cl - test-3 + test-10
@@ -942,7 +942,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-4 + test-11
@@ -966,7 +966,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-5 + test-12
@@ -990,7 +990,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-6 + test-13
@@ -1014,7 +1014,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-7 + test-3
@@ -1038,7 +1038,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-8 + test-4 @@ -1062,7 +1062,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-9 + test-5 @@ -1086,7 +1086,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-10 + test-6 @@ -1110,7 +1110,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-11 + test-7 @@ -1134,7 +1134,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-12 + test-8 @@ -1158,7 +1158,7 @@ exports[` when clicked when 'test-2' is clicked when cl - test-13 + test-9 @@ -1168,7 +1168,7 @@ exports[` when clicked when 'test-2' is clicked when cl `; -exports[` when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked renders 1`] = ` +exports[` once the subscribe resolves when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked renders 1`] = `
when clicked when 'test-2' is clicked when cl `; -exports[` when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked when clicked again, then holding down multi select key when 'test-3' is clicked renders 1`] = ` +exports[` once the subscribe resolves when clicked when 'test-2' is clicked when clicked again when 'test-1' is clicked when clicked again, then holding down multi select key when 'test-3' is clicked renders 1`] = `
when clicked when 'test-2' is clicked when cl class="Select__option css-10wo9uf-option" id="react-select-namespace-select-filter-option-2" tabindex="-1" + > +
+ + + layers + + + + test-10 + +
+
+
+
+ + + layers + + + + test-11 + +
+
+
+
+ + + layers + + + + test-12 + +
+
+
+
+ + + layers + + + + test-13 + +
+
+
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
when clicked when 'test-2' is clicked when cl
-
-
- - - layers - - - - test-10 - -
-
-
-
- - - layers - - - - test-11 - -
-
-
-
- - - layers - - - - test-12 - -
-
-
-
- - - layers - - - - test-13 - -
-
diff --git a/src/renderer/components/+namespaces/namespace-select-filter.test.tsx b/src/renderer/components/+namespaces/namespace-select-filter.test.tsx index a81e32e263..0a4626b251 100644 --- a/src/renderer/components/+namespaces/namespace-select-filter.test.tsx +++ b/src/renderer/components/+namespaces/namespace-select-filter.test.tsx @@ -3,14 +3,25 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { AsyncFnMock } from "@async-fn/jest"; +import asyncFn from "@async-fn/jest"; import type { DiContainer } from "@ogre-tools/injectable"; import type { RenderResult } from "@testing-library/react"; import { fireEvent } from "@testing-library/react"; import React from "react"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import type { Fetch } from "../../../common/fetch/fetch.injectable"; +import fetchInjectable from "../../../common/fetch/fetch.injectable"; import { Namespace } from "../../../common/k8s-api/endpoints"; +import { createMockResponseFromString } from "../../../test-utils/mock-responses"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; +import type { Disposer } from "../../utils"; +import { disposer } from "../../utils"; import { renderFor } from "../test-utils/renderFor"; import { NamespaceSelectFilter } from "./namespace-select-filter"; import type { NamespaceStore } from "./store"; @@ -32,147 +43,197 @@ function createNamespace(name: string): Namespace { describe("", () => { let di: DiContainer; let namespaceStore: NamespaceStore; + let fetchMock: AsyncFnMock; let result: RenderResult; + let cleanup: Disposer; beforeEach(() => { di = getDiForUnitTesting({ doGeneralOverrides: true }); - di.override(directoryForUserDataInjectable, () => "/some-directory"); + di.unoverride(subscribeStoresInjectable); + + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + namespaceStore = di.inject(namespaceStoreInjectable); - const render = renderFor(di); + const subscribeStores = di.inject(subscribeStoresInjectable); - namespaceStore.items.replace([ - createNamespace("test-1"), - createNamespace("test-2"), - createNamespace("test-3"), - createNamespace("test-4"), - createNamespace("test-5"), - createNamespace("test-6"), - createNamespace("test-7"), - createNamespace("test-8"), - createNamespace("test-9"), - createNamespace("test-10"), - createNamespace("test-11"), - createNamespace("test-12"), - createNamespace("test-13"), - ]); + cleanup = disposer(subscribeStores([namespaceStore])); + + fetchMock = asyncFn(); + di.override(fetchInjectable, () => fetchMock); + + const render = renderFor(di); result = render(( )); }); - it("renders", () => { - expect(result.baseElement).toMatchSnapshot(); + afterEach(() => { + cleanup(); }); - describe("when clicked", () => { - beforeEach(() => { - result.getByTestId("namespace-select-filter").click(); + describe("once the subscribe resolves", () => { + beforeEach(async () => { + await fetchMock.resolveSpecific([ + "http://127.0.0.1:12345/api-kube/api/v1/namespaces", + ], createMockResponseFromString("http://127.0.0.1:12345/api-kube/api/v1/namespaces", JSON.stringify({ + apiVersion: "v1", + kind: "NamespaceList", + metadata: {}, + items: [ + createNamespace("test-1"), + createNamespace("test-2"), + createNamespace("test-3"), + createNamespace("test-4"), + createNamespace("test-5"), + createNamespace("test-6"), + createNamespace("test-7"), + createNamespace("test-8"), + createNamespace("test-9"), + createNamespace("test-10"), + createNamespace("test-11"), + createNamespace("test-12"), + createNamespace("test-13"), + ], + }))); }); it("renders", () => { expect(result.baseElement).toMatchSnapshot(); }); - it("opens menu", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); - }); - - describe("when 'test-2' is clicked", () => { + describe("when clicked", () => { beforeEach(() => { - result.getByText("test-2").click(); + result.getByTestId("namespace-select-filter").click(); }); it("renders", () => { expect(result.baseElement).toMatchSnapshot(); }); - it("has only 'test-2' is selected in the store", () => { - expect(namespaceStore.contextNamespaces).toEqual(["test-2"]); + it("opens menu", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); }); - it("closes menu", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); - }); - - describe("when clicked again", () => { + describe("when 'test-2' is clicked", () => { beforeEach(() => { - result.getByTestId("namespace-select-filter").click(); + result.getByText("test-2").click(); }); it("renders", () => { expect(result.baseElement).toMatchSnapshot(); }); - it("shows 'test-2' as selected", () => { - expect(result.queryByTestId("namespace-select-filter-option-test-2-selected")).not.toBeNull(); + it("has only 'test-2' is selected in the store", () => { + expect(namespaceStore.contextNamespaces).toEqual(["test-2"]); }); - it("does not show 'test-1' as selected", () => { - expect(result.queryByTestId("namespace-select-filter-option-test-1-selected")).toBeNull(); + it("closes menu", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); }); - describe("when 'test-1' is clicked", () => { + describe("when clicked again", () => { beforeEach(() => { - result.getByText("test-1").click(); + result.getByTestId("namespace-select-filter").click(); }); it("renders", () => { expect(result.baseElement).toMatchSnapshot(); }); - it("has only 'test-1' is selected in the store", () => { - expect(namespaceStore.contextNamespaces).toEqual(["test-1"]); + it("shows 'test-2' as selected", () => { + expect(result.queryByTestId("namespace-select-filter-option-test-2-selected")).not.toBeNull(); }); - it("closes menu", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); + it("does not show 'test-1' as selected", () => { + expect(result.queryByTestId("namespace-select-filter-option-test-1-selected")).toBeNull(); }); - describe("when clicked again, then holding down multi select key", () => { + describe("when 'test-1' is clicked", () => { beforeEach(() => { - const filter = result.getByTestId("namespace-select-filter"); - - filter.click(); - fireEvent.keyDown(filter, { key: "Meta" }); + result.getByText("test-1").click(); }); - describe("when 'test-3' is clicked", () => { + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); + }); + + it("has only 'test-1' is selected in the store", () => { + expect(namespaceStore.contextNamespaces).toEqual(["test-1"]); + }); + + it("closes menu", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); + }); + + describe("when clicked again, then holding down multi select key", () => { beforeEach(() => { - result.getByText("test-3").click(); + const filter = result.getByTestId("namespace-select-filter"); + + filter.click(); + fireEvent.keyDown(filter, { key: "Meta" }); }); - it("renders", () => { - expect(result.baseElement).toMatchSnapshot(); - }); - - it("has both 'test-1' and 'test-3' as selected in the store", () => { - expect(new Set(namespaceStore.contextNamespaces)).toEqual(new Set(["test-1", "test-3"])); - }); - - it("keeps menu open", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); - }); - - it("does not show 'kube-system' as selected", () => { - expect(result.queryByTestId("namespace-select-filter-option-kube-system-selected")).toBeNull(); - }); - - describe("when 'test-13' is clicked", () => { + describe("when 'test-3' is clicked", () => { beforeEach(() => { - result.getByText("test-13").click(); + result.getByText("test-3").click(); }); - it("has all of 'test-1', 'test-3', and 'test-13' selected in the store", () => { - expect(new Set(namespaceStore.contextNamespaces)).toEqual(new Set(["test-1", "test-3", "test-13"])); + it("renders", () => { + expect(result.baseElement).toMatchSnapshot(); }); - it("'test-13' is not sorted to the top of the list", () => { - const topLevelElement = result.getByText("test-13").parentElement?.parentElement as HTMLElement; + it("has both 'test-1' and 'test-3' as selected in the store", () => { + expect(new Set(namespaceStore.contextNamespaces)).toEqual(new Set(["test-1", "test-3"])); + }); - expect(topLevelElement.nextSibling).toBe(null); + it("keeps menu open", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); + }); + + it("does not show 'kube-system' as selected", () => { + expect(result.queryByTestId("namespace-select-filter-option-kube-system-selected")).toBeNull(); + }); + + describe("when 'test-13' is clicked", () => { + beforeEach(() => { + result.getByText("test-13").click(); + }); + + it("has all of 'test-1', 'test-3', and 'test-13' selected in the store", () => { + expect(new Set(namespaceStore.contextNamespaces)).toEqual(new Set(["test-1", "test-3", "test-13"])); + }); + + it("'test-13' is not sorted to the top of the list", () => { + const topLevelElement = result.getByText("test-13").parentElement?.parentElement as HTMLElement; + + expect(topLevelElement.previousSibling).not.toBe(null); + }); + }); + + describe("when releasing multi select key", () => { + beforeEach(() => { + const filter = result.getByTestId("namespace-select-filter"); + + fireEvent.keyUp(filter, { key: "Meta" }); + }); + + it("closes menu", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); + }); }); }); @@ -183,46 +244,24 @@ describe("", () => { fireEvent.keyUp(filter, { key: "Meta" }); }); - it("closes menu", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).toBeNull(); + it("keeps menu open", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); }); }); }); - - describe("when releasing multi select key", () => { - beforeEach(() => { - const filter = result.getByTestId("namespace-select-filter"); - - fireEvent.keyUp(filter, { key: "Meta" }); - }); - - it("keeps menu open", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-listbox")).not.toBeNull(); - }); - }); }); }); }); - }); - describe("when multi-selection key is pressed", () => { - beforeEach(() => { - const filter = result.getByTestId("namespace-select-filter"); - - fireEvent.keyDown(filter, { key: "Meta" }); - }); - - it("should show placeholder text as 'All namespaces'", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).toHaveTextContent("All namespaces"); - }); - - describe("when 'test-2' is clicked", () => { + describe("when multi-selection key is pressed", () => { beforeEach(() => { - result.getByText("test-2").click(); + const filter = result.getByTestId("namespace-select-filter"); + + fireEvent.keyDown(filter, { key: "Meta" }); }); - it("should not show placeholder text as 'All namespaces'", () => { - expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces"); + it("should show placeholder text as 'All namespaces'", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).toHaveTextContent("All namespaces"); }); describe("when 'test-2' is clicked", () => { @@ -230,20 +269,30 @@ describe("", () => { result.getByText("test-2").click(); }); - it("should not show placeholder as 'All namespaces'", () => { + it("should not show placeholder text as 'All namespaces'", () => { expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces"); }); - describe("when multi-selection key is raised", () => { + describe("when 'test-2' is clicked", () => { beforeEach(() => { - const filter = result.getByTestId("namespace-select-filter"); - - fireEvent.keyUp(filter, { key: "Meta" }); + result.getByText("test-2").click(); }); - it("should show placeholder text as 'All namespaces'", () => { + it("should not show placeholder as 'All namespaces'", () => { expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces"); }); + + describe("when multi-selection key is raised", () => { + beforeEach(() => { + const filter = result.getByTestId("namespace-select-filter"); + + fireEvent.keyUp(filter, { key: "Meta" }); + }); + + it("should show placeholder text as 'All namespaces'", () => { + expect(result.baseElement.querySelector("#react-select-namespace-select-filter-placeholder")).not.toHaveTextContent("All namespaces"); + }); + }); }); }); }); diff --git a/src/renderer/components/+namespaces/store.injectable.ts b/src/renderer/components/+namespaces/store.injectable.ts index 68d611e837..eb2a7e4a35 100644 --- a/src/renderer/components/+namespaces/store.injectable.ts +++ b/src/renderer/components/+namespaces/store.injectable.ts @@ -9,6 +9,8 @@ import createStorageInjectable from "../../utils/create-storage/create-storage.i import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable"; import assert from "assert"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; +import clusterConfiguredAccessibleNamespacesInjectable from "../../cluster/accessible-namespaces.injectable"; const namespaceStoreInjectable = getInjectable({ id: "namespace-store", @@ -20,7 +22,9 @@ const namespaceStoreInjectable = getInjectable({ const api = di.inject(namespaceApiInjectable); return new NamespaceStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), storage: createStorage("selected_namespaces", undefined), + clusterConfiguredAccessibleNamespaces: di.inject(clusterConfiguredAccessibleNamespacesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+namespaces/store.ts b/src/renderer/components/+namespaces/store.ts index 1b2342facf..4c178a8a96 100644 --- a/src/renderer/components/+namespaces/store.ts +++ b/src/renderer/components/+namespaces/store.ts @@ -3,22 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { IReactionDisposer } from "mobx"; +import type { IComputedValue, IReactionDisposer } from "mobx"; import { action, comparer, computed, makeObservable, reaction } from "mobx"; import type { StorageLayer } from "../../utils"; import { autoBind, noop, toggle } from "../../utils"; -import type { KubeObjectStoreLoadingParams } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreLoadingParams } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import type { NamespaceApi } from "../../../common/k8s-api/endpoints/namespace.api"; import { Namespace } from "../../../common/k8s-api/endpoints/namespace.api"; -interface Dependencies { - storage: StorageLayer; +interface Dependencies extends KubeObjectStoreDependencies { + readonly storage: StorageLayer; + readonly clusterConfiguredAccessibleNamespaces: IComputedValue; } export class NamespaceStore extends KubeObjectStore { constructor(protected readonly dependencies: Dependencies, api: NamespaceApi) { - super(api); + super(dependencies, api); makeObservable(this); autoBind(this); @@ -26,11 +27,21 @@ export class NamespaceStore extends KubeObjectStore { } private async init() { - await this.contextReady; await this.dependencies.storage.whenReady; - this.selectNamespaces(this.initialNamespaces); - this.autoLoadAllowedNamespaces(); + const { allowedNamespaces } = this; + const selectedNamespaces = this.dependencies.storage.get(); // raw namespaces, undefined on first load + + // return previously saved namespaces from local-storage (if any) + if (Array.isArray(selectedNamespaces)) { + this.selectNamespaces(selectedNamespaces.filter(namespace => allowedNamespaces.includes(namespace))); + } else if (allowedNamespaces.includes("default")) { + this.selectNamespaces(["default"]); + } else if (allowedNamespaces.length) { + this.selectNamespaces([allowedNamespaces[0]]); + } else { + this.selectNamespaces([]); + } } public onContextChange(callback: (namespaces: string[]) => void, opts: { fireImmediately?: boolean } = {}): IReactionDisposer { @@ -40,32 +51,6 @@ export class NamespaceStore extends KubeObjectStore { }); } - private autoLoadAllowedNamespaces(): IReactionDisposer { - return reaction(() => this.allowedNamespaces, namespaces => this.loadAll({ namespaces }), { - fireImmediately: true, - equals: comparer.shallow, - }); - } - - private get initialNamespaces(): string[] { - const { allowedNamespaces } = this; - const selectedNamespaces = this.dependencies.storage.get(); // raw namespaces, undefined on first load - - // return previously saved namespaces from local-storage (if any) - if (Array.isArray(selectedNamespaces)) { - return selectedNamespaces.filter(namespace => allowedNamespaces.includes(namespace)); - } - - // otherwise select "default" or first allowed namespace - if (allowedNamespaces.includes("default")) { - return ["default"]; - } else if (allowedNamespaces.length) { - return [allowedNamespaces[0]]; - } - - return []; - } - /** * @private * The current value (list of namespaces names) in the storage layer @@ -75,10 +60,7 @@ export class NamespaceStore extends KubeObjectStore { } @computed get allowedNamespaces(): string[] { - return Array.from(new Set([ - ...(this.context?.allNamespaces ?? []), // allowed namespaces from cluster (main), updating every 30s - ...this.items.map(item => item.getName()), // loaded namespaces from k8s api - ].flat())); + return this.items.map(item => item.getName()); } /** @@ -110,11 +92,13 @@ export class NamespaceStore extends KubeObjectStore { } subscribe() { + const clusterConfiguredAccessibleNamespaces = this.dependencies.clusterConfiguredAccessibleNamespaces.get(); + /** * if user has given static list of namespaces let's not start watches * because watch adds stuff that's not wanted or will just fail */ - if ((this.context?.cluster.accessibleNamespaces.length ?? 0) > 0) { + if (clusterConfiguredAccessibleNamespaces.length > 0) { return noop; } @@ -122,17 +106,13 @@ export class NamespaceStore extends KubeObjectStore { } protected async loadItems(params: KubeObjectStoreLoadingParams): Promise { - const { allowedNamespaces } = this; + const clusterConfiguredAccessibleNamespaces = this.dependencies.clusterConfiguredAccessibleNamespaces.get(); - let namespaces = await super.loadItems(params).catch(() => []); - - namespaces = namespaces.filter(namespace => allowedNamespaces.includes(namespace.getName())); - - if (!namespaces.length && allowedNamespaces.length > 0) { - return allowedNamespaces.map(getDummyNamespace); + if (clusterConfiguredAccessibleNamespaces.length > 0) { + return clusterConfiguredAccessibleNamespaces.map(getDummyNamespace); } - return namespaces; + return super.loadItems(params); } @action selectNamespaces = (namespace: string | string[]) => { diff --git a/src/renderer/components/+network-endpoints/store.injectable.ts b/src/renderer/components/+network-endpoints/store.injectable.ts index a63014ea70..7531b933b3 100644 --- a/src/renderer/components/+network-endpoints/store.injectable.ts +++ b/src/renderer/components/+network-endpoints/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import endpointsApiInjectable from "../../../common/k8s-api/endpoints/endpoint.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { EndpointsStore } from "./store"; @@ -16,7 +17,9 @@ const endpointsStoreInjectable = getInjectable({ const api = di.inject(endpointsApiInjectable); - return new EndpointsStore(api); + return new EndpointsStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+network-ingresses/store.injectable.ts b/src/renderer/components/+network-ingresses/store.injectable.ts index 9b95939e9f..0084a006a2 100644 --- a/src/renderer/components/+network-ingresses/store.injectable.ts +++ b/src/renderer/components/+network-ingresses/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import ingressApiInjectable from "../../../common/k8s-api/endpoints/ingress.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { IngressStore } from "./store"; @@ -16,7 +17,9 @@ const ingressStoreInjectable = getInjectable({ const api = di.inject(ingressApiInjectable); - return new IngressStore(api); + return new IngressStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+network-policies/store.injectable.ts b/src/renderer/components/+network-policies/store.injectable.ts index a666c04cb4..ad79a89b54 100644 --- a/src/renderer/components/+network-policies/store.injectable.ts +++ b/src/renderer/components/+network-policies/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import networkPolicyApiInjectable from "../../../common/k8s-api/endpoints/network-policy.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { NetworkPolicyStore } from "./store"; @@ -16,7 +17,9 @@ const networkPolicyStoreInjectable = getInjectable({ const api = di.inject(networkPolicyApiInjectable); - return new NetworkPolicyStore(api); + return new NetworkPolicyStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+network-services/store.injectable.ts b/src/renderer/components/+network-services/store.injectable.ts index 6013a97f15..e54f994162 100644 --- a/src/renderer/components/+network-services/store.injectable.ts +++ b/src/renderer/components/+network-services/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import serviceApiInjectable from "../../../common/k8s-api/endpoints/service.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { ServiceStore } from "./store"; @@ -16,7 +17,9 @@ const serviceStoreInjectable = getInjectable({ const api = di.inject(serviceApiInjectable); - return new ServiceStore(api); + return new ServiceStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+nodes/store.injectable.ts b/src/renderer/components/+nodes/store.injectable.ts index adb69a99ad..ac149210bd 100644 --- a/src/renderer/components/+nodes/store.injectable.ts +++ b/src/renderer/components/+nodes/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import nodeApiInjectable from "../../../common/k8s-api/endpoints/node.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { NodeStore } from "./store"; @@ -16,7 +17,9 @@ const nodeStoreInjectable = getInjectable({ const api = di.inject(nodeApiInjectable); - return new NodeStore(api); + return new NodeStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+nodes/store.ts b/src/renderer/components/+nodes/store.ts index 9f4be4b620..87fafa9256 100644 --- a/src/renderer/components/+nodes/store.ts +++ b/src/renderer/components/+nodes/store.ts @@ -6,13 +6,13 @@ import { sum } from "lodash"; import { computed, makeObservable } from "mobx"; import type { Node, NodeApi } from "../../../common/k8s-api/endpoints"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import { autoBind } from "../../utils"; export class NodeStore extends KubeObjectStore { - constructor(api: NodeApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + constructor(dependencies: KubeObjectStoreDependencies, api: NodeApi, opts?: KubeObjectStoreOptions) { + super(dependencies, api, opts); makeObservable(this); autoBind(this); diff --git a/src/renderer/components/+pod-security-policies/store.injectable.ts b/src/renderer/components/+pod-security-policies/store.injectable.ts index b010b60c97..44ab372d70 100644 --- a/src/renderer/components/+pod-security-policies/store.injectable.ts +++ b/src/renderer/components/+pod-security-policies/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import podSecurityPolicyApiInjectable from "../../../common/k8s-api/endpoints/pod-security-policy.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { PodSecurityPolicyStore } from "./store"; @@ -16,7 +17,9 @@ const podSecurityPolicyStoreInjectable = getInjectable({ const api = di.inject(podSecurityPolicyApiInjectable); - return new PodSecurityPolicyStore(api); + return new PodSecurityPolicyStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+storage-classes/store.injectable.ts b/src/renderer/components/+storage-classes/store.injectable.ts index ab4ae8c784..17f6da93db 100644 --- a/src/renderer/components/+storage-classes/store.injectable.ts +++ b/src/renderer/components/+storage-classes/store.injectable.ts @@ -7,6 +7,7 @@ import assert from "assert"; import getPersistentVolumesByStorageClassInjectable from "../+storage-volumes/get-persisten-volumes-by-storage-class.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import storageClassApiInjectable from "../../../common/k8s-api/endpoints/storage-class.api.injectable"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { StorageClassStore } from "./store"; @@ -19,6 +20,7 @@ const storageClassStoreInjectable = getInjectable({ return new StorageClassStore({ getPersistentVolumesByStorageClass: di.inject(getPersistentVolumesByStorageClassInjectable), + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+storage-classes/store.ts b/src/renderer/components/+storage-classes/store.ts index 8037a8d67d..8c01c145b3 100644 --- a/src/renderer/components/+storage-classes/store.ts +++ b/src/renderer/components/+storage-classes/store.ts @@ -3,12 +3,12 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import type { StorageClass, StorageClassApi, StorageClassData } from "../../../common/k8s-api/endpoints/storage-class.api"; import type { GetPersistentVolumesByStorageClass } from "../+storage-volumes/get-persisten-volumes-by-storage-class.injectable"; -export interface StorageClassStoreDependencies { +export interface StorageClassStoreDependencies extends KubeObjectStoreDependencies { getPersistentVolumesByStorageClass: GetPersistentVolumesByStorageClass; } @@ -18,7 +18,7 @@ export class StorageClassStore extends KubeObjectStore { let render: DiRender; @@ -20,8 +23,19 @@ describe("ClusterRoleBindingDialog tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); - di.override(directoryForUserDataInjectable, () => "/some-path-for-user-data"); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); render = renderFor(di); diff --git a/src/renderer/components/+user-management/+cluster-role-bindings/store.injectable.ts b/src/renderer/components/+user-management/+cluster-role-bindings/store.injectable.ts index afaf3e7400..a2e3223fa7 100644 --- a/src/renderer/components/+user-management/+cluster-role-bindings/store.injectable.ts +++ b/src/renderer/components/+user-management/+cluster-role-bindings/store.injectable.ts @@ -8,6 +8,7 @@ import { storesAndApisCanBeCreatedInjectionToken } from "../../../../common/k8s- import clusterRoleBindingApiInjectable from "../../../../common/k8s-api/endpoints/cluster-role-binding.api.injectable"; import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable"; import { ClusterRoleBindingStore } from "./store"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../../cluster-frame-context/for-cluster-scoped-resources.injectable"; const clusterRoleBindingStoreInjectable = getInjectable({ id: "cluster-role-binding-store", @@ -16,7 +17,9 @@ const clusterRoleBindingStoreInjectable = getInjectable({ const api = di.inject(clusterRoleBindingApiInjectable); - return new ClusterRoleBindingStore(api); + return new ClusterRoleBindingStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+user-management/+cluster-roles/store.injectable.ts b/src/renderer/components/+user-management/+cluster-roles/store.injectable.ts index bfd83f3b3b..9ae877f635 100644 --- a/src/renderer/components/+user-management/+cluster-roles/store.injectable.ts +++ b/src/renderer/components/+user-management/+cluster-roles/store.injectable.ts @@ -8,6 +8,7 @@ import { storesAndApisCanBeCreatedInjectionToken } from "../../../../common/k8s- import clusterRoleApiInjectable from "../../../../common/k8s-api/endpoints/cluster-role.api.injectable"; import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable"; import { ClusterRoleStore } from "./store"; +import clusterFrameContextForClusterScopedResourcesInjectable from "../../../cluster-frame-context/for-cluster-scoped-resources.injectable"; const clusterRoleStoreInjectable = getInjectable({ id: "cluster-role-store", @@ -16,7 +17,9 @@ const clusterRoleStoreInjectable = getInjectable({ const api = di.inject(clusterRoleApiInjectable); - return new ClusterRoleStore(api); + return new ClusterRoleStore({ + context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx b/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx index 82e3a7c9d7..08067ed817 100644 --- a/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx +++ b/src/renderer/components/+user-management/+role-bindings/__tests__/dialog.test.tsx @@ -13,6 +13,9 @@ import { renderFor } from "../../../test-utils/renderFor"; import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import clusterRoleStoreInjectable from "../../+cluster-roles/store.injectable"; import storesAndApisCanBeCreatedInjectable from "../../../../stores-apis-can-be-created.injectable"; +import directoryForKubeConfigsInjectable from "../../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../../../cluster/create-cluster.injectable"; describe("RoleBindingDialog tests", () => { let render: DiRender; @@ -20,8 +23,19 @@ describe("RoleBindingDialog tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); - di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); render = renderFor(di); diff --git a/src/renderer/components/+user-management/+role-bindings/store.injectable.ts b/src/renderer/components/+user-management/+role-bindings/store.injectable.ts index 20122b47d6..a3f33e76bc 100644 --- a/src/renderer/components/+user-management/+role-bindings/store.injectable.ts +++ b/src/renderer/components/+user-management/+role-bindings/store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable"; import roleBindingApiInjectable from "../../../../common/k8s-api/endpoints/role-binding.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable"; import { RoleBindingStore } from "./store"; @@ -16,7 +17,9 @@ const roleBindingStoreInjectable = getInjectable({ const api = di.inject(roleBindingApiInjectable); - return new RoleBindingStore(api); + return new RoleBindingStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+user-management/+roles/store.injectable.ts b/src/renderer/components/+user-management/+roles/store.injectable.ts index ac10dc831b..8b86ddb0ab 100644 --- a/src/renderer/components/+user-management/+roles/store.injectable.ts +++ b/src/renderer/components/+user-management/+roles/store.injectable.ts @@ -8,6 +8,7 @@ import roleApiInjectable from "../../../../common/k8s-api/endpoints/role.api.inj import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable"; import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable"; import { RoleStore } from "./store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; const roleStoreInjectable = getInjectable({ id: "role-store", @@ -16,7 +17,9 @@ const roleStoreInjectable = getInjectable({ const api = di.inject(roleApiInjectable); - return new RoleStore(api); + return new RoleStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+user-management/+service-accounts/store.injectable.ts b/src/renderer/components/+user-management/+service-accounts/store.injectable.ts index 75b4b791d5..98570c9017 100644 --- a/src/renderer/components/+user-management/+service-accounts/store.injectable.ts +++ b/src/renderer/components/+user-management/+service-accounts/store.injectable.ts @@ -8,6 +8,7 @@ import serviceAccountApiInjectable from "../../../../common/k8s-api/endpoints/se import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable"; import { kubeObjectStoreInjectionToken } from "../../../../common/k8s-api/api-manager/manager.injectable"; import { ServiceAccountStore } from "./store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../../cluster-frame-context/for-namespaced-resources.injectable"; const serviceAccountStoreInjectable = getInjectable({ id: "service-account-store", @@ -16,7 +17,9 @@ const serviceAccountStoreInjectable = getInjectable({ const api = di.inject(serviceAccountApiInjectable); - return new ServiceAccountStore(api); + return new ServiceAccountStore({ + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }, api); }, injectionToken: kubeObjectStoreInjectionToken, }); diff --git a/src/renderer/components/+workloads-cronjobs/store.injectable.ts b/src/renderer/components/+workloads-cronjobs/store.injectable.ts index a71352685f..6b0fab2c9c 100644 --- a/src/renderer/components/+workloads-cronjobs/store.injectable.ts +++ b/src/renderer/components/+workloads-cronjobs/store.injectable.ts @@ -7,6 +7,7 @@ import assert from "assert"; import getJobsByOwnerInjectable from "../+workloads-jobs/get-jobs-by-owner.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import cronJobApiInjectable from "../../../common/k8s-api/endpoints/cron-job.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { CronJobStore } from "./store"; @@ -19,6 +20,7 @@ const cronJobStoreInjectable = getInjectable({ return new CronJobStore({ getJobsByOwner: di.inject(getJobsByOwnerInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-cronjobs/store.ts b/src/renderer/components/+workloads-cronjobs/store.ts index 67e006ac90..fe6c765723 100644 --- a/src/renderer/components/+workloads-cronjobs/store.ts +++ b/src/renderer/components/+workloads-cronjobs/store.ts @@ -3,18 +3,18 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import type { CronJob, CronJobApi } from "../../../common/k8s-api/endpoints/cron-job.api"; import type { GetJobsByOwner } from "../+workloads-jobs/get-jobs-by-owner.injectable"; -interface Dependencies { +interface Dependencies extends KubeObjectStoreDependencies { getJobsByOwner: GetJobsByOwner; } export class CronJobStore extends KubeObjectStore { constructor(protected readonly dependencies: Dependencies, api: CronJobApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + super(dependencies, api, opts); } getStatuses(cronJobs?: CronJob[]) { diff --git a/src/renderer/components/+workloads-daemonsets/store.injectable.ts b/src/renderer/components/+workloads-daemonsets/store.injectable.ts index 3b7611dc43..5711ad625f 100644 --- a/src/renderer/components/+workloads-daemonsets/store.injectable.ts +++ b/src/renderer/components/+workloads-daemonsets/store.injectable.ts @@ -9,6 +9,7 @@ import daemonSetApiInjectable from "../../../common/k8s-api/endpoints/daemon-set import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import { DaemonSetStore } from "./store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const daemonSetStoreInjectable = getInjectable({ id: "daemon-set-store", @@ -19,6 +20,7 @@ const daemonSetStoreInjectable = getInjectable({ return new DaemonSetStore({ getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-daemonsets/store.ts b/src/renderer/components/+workloads-daemonsets/store.ts index 66216e3f76..1345468b62 100644 --- a/src/renderer/components/+workloads-daemonsets/store.ts +++ b/src/renderer/components/+workloads-daemonsets/store.ts @@ -6,16 +6,16 @@ import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable"; import type { DaemonSet, DaemonSetApi, Pod } from "../../../common/k8s-api/endpoints"; import { PodStatusPhase } from "../../../common/k8s-api/endpoints"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -export interface DaemonSetStoreDependencies { +export interface DaemonSetStoreDependencies extends KubeObjectStoreDependencies { readonly getPodsByOwnerId: GetPodsByOwnerId; } export class DaemonSetStore extends KubeObjectStore { constructor(protected readonly dependencies: DaemonSetStoreDependencies, api: DaemonSetApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + super(dependencies, api, opts); } getChildPods(daemonSet: DaemonSet): Pod[] { diff --git a/src/renderer/components/+workloads-deployments/store.injectable.ts b/src/renderer/components/+workloads-deployments/store.injectable.ts index 02eb3da48c..b77d9aa496 100644 --- a/src/renderer/components/+workloads-deployments/store.injectable.ts +++ b/src/renderer/components/+workloads-deployments/store.injectable.ts @@ -9,6 +9,7 @@ import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manag import { storesAndApisCanBeCreatedInjectionToken } from "../../../common/k8s-api/stores-apis-can-be-created.token"; import deploymentApiInjectable from "../../../common/k8s-api/endpoints/deployment.api.injectable"; import { DeploymentStore } from "./store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const deploymentStoreInjectable = getInjectable({ id: "deployment-store", @@ -19,6 +20,7 @@ const deploymentStoreInjectable = getInjectable({ return new DeploymentStore({ podStore: di.inject(podStoreInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-deployments/store.ts b/src/renderer/components/+workloads-deployments/store.ts index 014f1c2abc..af5d085594 100644 --- a/src/renderer/components/+workloads-deployments/store.ts +++ b/src/renderer/components/+workloads-deployments/store.ts @@ -6,7 +6,7 @@ import type { PodStore } from "../+workloads-pods/store"; import type { Deployment, DeploymentApi } from "../../../common/k8s-api/endpoints"; import { PodStatusPhase } from "../../../common/k8s-api/endpoints"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; // This needs to be disables because of https://github.com/microsoft/TypeScript/issues/15300 @@ -17,13 +17,13 @@ export type DeploymentStatuses = { pending: number; }; -export interface DeploymentStoreDependencies { +export interface DeploymentStoreDependencies extends KubeObjectStoreDependencies { readonly podStore: PodStore; } export class DeploymentStore extends KubeObjectStore { constructor(protected readonly dependencies: DeploymentStoreDependencies, api: DeploymentApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + super(dependencies, api, opts); } protected sortItems(items: Deployment[]) { diff --git a/src/renderer/components/+workloads-jobs/store.injectable.ts b/src/renderer/components/+workloads-jobs/store.injectable.ts index 46e4d26fb4..8f394a1bf5 100644 --- a/src/renderer/components/+workloads-jobs/store.injectable.ts +++ b/src/renderer/components/+workloads-jobs/store.injectable.ts @@ -7,6 +7,7 @@ import assert from "assert"; import getPodsByOwnerIdInjectable from "../+workloads-pods/get-pods-by-owner-id.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import jobApiInjectable from "../../../common/k8s-api/endpoints/job.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { JobStore } from "./store"; @@ -19,6 +20,7 @@ const jobStoreInjectable = getInjectable({ return new JobStore({ getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-jobs/store.ts b/src/renderer/components/+workloads-jobs/store.ts index 47fc09d931..2f6f671aad 100644 --- a/src/renderer/components/+workloads-jobs/store.ts +++ b/src/renderer/components/+workloads-jobs/store.ts @@ -3,20 +3,20 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import type { Job, JobApi } from "../../../common/k8s-api/endpoints/job.api"; import type { CronJob, Pod } from "../../../common/k8s-api/endpoints"; import { PodStatusPhase } from "../../../common/k8s-api/endpoints"; import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable"; -interface Dependencies { +interface Dependencies extends KubeObjectStoreDependencies { getPodsByOwnerId: GetPodsByOwnerId; } export class JobStore extends KubeObjectStore { constructor(protected readonly dependencies: Dependencies, api: JobApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + super(dependencies, api, opts); } getChildPods(job: Job): Pod[] { diff --git a/src/renderer/components/+workloads-overview/overview-statuses.tsx b/src/renderer/components/+workloads-overview/overview-statuses.tsx index 2430409d30..19c81712a9 100644 --- a/src/renderer/components/+workloads-overview/overview-statuses.tsx +++ b/src/renderer/components/+workloads-overview/overview-statuses.tsx @@ -12,6 +12,7 @@ import { withInjectables } from "@ogre-tools/injectable-react"; import type { IComputedValue } from "mobx"; import workloadsInjectable from "./workloads/workloads.injectable"; import type { Workload } from "./workloads/workload-injection-token"; +import { formatKubeApiResource } from "../../../common/rbac"; export interface OverviewStatusesProps {} @@ -24,7 +25,7 @@ const NonInjectedOverviewStatuses = observer(
{workloads.get().map((workload) => ( -
+
{`${workload.title} (${workload.amountOfItems.get()})`} diff --git a/src/renderer/components/+workloads-overview/overview.tsx b/src/renderer/components/+workloads-overview/overview.tsx index 7c93bd429c..8b60676da2 100644 --- a/src/renderer/components/+workloads-overview/overview.tsx +++ b/src/renderer/components/+workloads-overview/overview.tsx @@ -17,8 +17,7 @@ import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter"; import { Icon } from "../icon"; import { TooltipPosition } from "../tooltip"; import { withInjectables } from "@ogre-tools/injectable-react"; -import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable"; -import type { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api"; import workloadOverviewDetailsInjectable from "./workload-overview-details/workload-overview-details.injectable"; import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; @@ -35,10 +34,11 @@ import jobStoreInjectable from "../+workloads-jobs/store.injectable"; import statefulSetStoreInjectable from "../+workloads-statefulsets/store.injectable"; import type { EventStore } from "../+events/store"; import eventStoreInjectable from "../+events/store.injectable"; +import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context"; interface Dependencies { detailComponents: IComputedValue[]>; - clusterFrameContext: ClusterFrameContext; + clusterFrameContext: ClusterContext; subscribeStores: SubscribeStores; podStore: PodStore; daemonSetStore: DaemonSetStore; @@ -123,7 +123,7 @@ class NonInjectedWorkloadsOverview extends React.Component { export const WorkloadsOverview = withInjectables(NonInjectedWorkloadsOverview, { getProps: (di) => ({ detailComponents: di.inject(workloadOverviewDetailsInjectable), - clusterFrameContext: di.inject(clusterFrameContextInjectable), + clusterFrameContext: di.inject(clusterFrameContextForNamespacedResourcesInjectable), subscribeStores: di.inject(subscribeStoresInjectable), daemonSetStore: di.inject(daemonSetStoreInjectable), podStore: di.inject(podStoreInjectable), diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts index b4e22d64ba..7e8ab5cf6e 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts @@ -19,17 +19,17 @@ const cronJobsWorkloadInjectable = getInjectable({ const store = di.inject(cronJobsStoreInjectable); return { - resourceName: "cronjobs", + resource: { + apiName: "cronjobs", + group: "batch", + }, open: navigate, - amountOfItems: computed( () => store.getAllByNs(namespaceStore.contextNamespaces).length, ), - status: computed(() => store.getStatuses(store.getAllByNs(namespaceStore.contextNamespaces)), ), - title: ResourceNames.cronjobs, orderNumber: 70, }; diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts index 0124c0d1ce..7b6d7d750f 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts @@ -19,7 +19,10 @@ const daemonsetsWorkloadInjectable = getInjectable({ const store = di.inject(daemonsetsStoreInjectable); return { - resourceName: "daemonsets", + resource: { + apiName: "daemonsets", + group: "apps", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts index 27ca74fe97..b41e4ea5ca 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts @@ -19,7 +19,10 @@ const deploymentsWorkloadInjectable = getInjectable({ const store = di.inject(deploymentsStoreInjectable); return { - resourceName: "deployments", + resource: { + apiName: "deployments", + group: "apps", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts index 3442aabaf7..e68fd88c1c 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts @@ -19,7 +19,10 @@ const jobsWorkloadInjectable = getInjectable({ const store = di.inject(jobStoreInjectable); return { - resourceName: "jobs", + resource: { + apiName: "jobs", + group: "batch", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts index 2a2afefe3a..452f7903aa 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts @@ -19,7 +19,10 @@ const podsWorkloadInjectable = getInjectable({ const store = di.inject(podStoreInjectable); return { - resourceName: "pods", + resource: { + apiName: "pods", + group: "v1", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts index 6102e70b19..86189e4634 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts @@ -19,7 +19,10 @@ const replicasetsWorkloadInjectable = getInjectable({ const store = di.inject(replicasetsStoreInjectable); return { - resourceName: "replicasets", + resource: { + apiName: "replicasets", + group: "apps", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts index 8a12f3c0af..19b85c4fa0 100644 --- a/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts @@ -19,7 +19,10 @@ const statefulsetsWorkloadInjectable = getInjectable({ const store = di.inject(statefulsetsStoreInjectable); return { - resourceName: "statefulsets", + resource: { + apiName: "statefulsets", + group: "apps", + }, open: navigate, amountOfItems: computed( diff --git a/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts b/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts index decb5a1f7c..fca19148a7 100644 --- a/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts +++ b/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts @@ -4,10 +4,11 @@ */ import { getInjectionToken } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; +import type { KubeApiResourceDescriptor } from "../../../../common/rbac"; import type { WorkloadStatus } from "../overview-workload-status"; export interface Workload { - resourceName: string; + resource: KubeApiResourceDescriptor; open: () => void; amountOfItems: IComputedValue; status: IComputedValue; diff --git a/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts b/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts index aeaa62ed4a..d6b23b582f 100644 --- a/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts +++ b/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts @@ -2,18 +2,11 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { pipeline } from "@ogre-tools/fp"; import { getInjectable } from "@ogre-tools/injectable"; -import { filter, sortBy as sortByWithBadTyping } from "lodash/fp"; import { computed } from "mobx"; -import type { Workload } from "./workload-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../common/cluster-store/allowed-resources-injection-token"; +import { byOrderNumber } from "../../../../common/utils/composable-responsibilities/orderable/orderable"; import { workloadInjectionToken } from "./workload-injection-token"; -import isAllowedResourceInjectable from "../../../../common/utils/is-allowed-resource.injectable"; - -const sortBy = - (propertyPath: string) => - (collection: Collection[]) => - sortByWithBadTyping(propertyPath, collection); const workloadsInjectable = getInjectable({ id: "workloads", @@ -21,22 +14,11 @@ const workloadsInjectable = getInjectable({ instantiate: (di) => { const workloads = di.injectMany(workloadInjectionToken); - const isAllowedResource = (resourceName: string) => - di.inject(isAllowedResourceInjectable, resourceName); - - return computed(() => - pipeline( - workloads, - - filter((workload: Workload) => { - const isAllowed = isAllowedResource(workload.resourceName); - - return isAllowed.get(); - }), - - sortBy("orderNumber"), - ), - ); + return computed(() => ( + workloads + .filter(w => di.inject(shouldShowResourceInjectionToken, w.resource).get()) + .sort(byOrderNumber) + )); }, }); diff --git a/src/renderer/components/+workloads-pods/store.injectable.ts b/src/renderer/components/+workloads-pods/store.injectable.ts index 59b91b8c27..a1bf527bef 100644 --- a/src/renderer/components/+workloads-pods/store.injectable.ts +++ b/src/renderer/components/+workloads-pods/store.injectable.ts @@ -9,6 +9,7 @@ import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-create import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import { PodStore } from "./store"; import podMetricsApiInjectable from "../../../common/k8s-api/endpoints/pod-metrics.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const podStoreInjectable = getInjectable({ id: "pod-store", @@ -19,6 +20,7 @@ const podStoreInjectable = getInjectable({ return new PodStore({ podMetricsApi: di.inject(podMetricsApiInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-pods/store.ts b/src/renderer/components/+workloads-pods/store.ts index 146eb0b390..5ce9dca041 100644 --- a/src/renderer/components/+workloads-pods/store.ts +++ b/src/renderer/components/+workloads-pods/store.ts @@ -5,13 +5,13 @@ import countBy from "lodash/countBy"; import { observable } from "mobx"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import { cpuUnitsToNumber, unitsToBytes } from "../../utils"; import type { Pod, PodMetrics, PodApi, PodMetricsApi } from "../../../common/k8s-api/endpoints"; import type { KubeObject, NamespaceScopedMetadata } from "../../../common/k8s-api/kube-object"; -export interface PodStoreDependencies { +export interface PodStoreDependencies extends KubeObjectStoreDependencies { readonly podMetricsApi: PodMetricsApi; } @@ -21,7 +21,7 @@ export class PodStore extends KubeObjectStore { api: PodApi, opts?: KubeObjectStoreOptions, ) { - super(api, opts); + super(dependencies, api, opts); } readonly kubeMetrics = observable.array([]); diff --git a/src/renderer/components/+workloads-replicasets/store.injectable.ts b/src/renderer/components/+workloads-replicasets/store.injectable.ts index 73da498071..c961e504ad 100644 --- a/src/renderer/components/+workloads-replicasets/store.injectable.ts +++ b/src/renderer/components/+workloads-replicasets/store.injectable.ts @@ -9,6 +9,7 @@ import replicaSetApiInjectable from "../../../common/k8s-api/endpoints/replica-s import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import { ReplicaSetStore } from "./store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; const replicaSetStoreInjectable = getInjectable({ id: "replica-set-store", @@ -19,6 +20,7 @@ const replicaSetStoreInjectable = getInjectable({ return new ReplicaSetStore({ getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-replicasets/store.ts b/src/renderer/components/+workloads-replicasets/store.ts index d782846cd0..1a6ac96610 100644 --- a/src/renderer/components/+workloads-replicasets/store.ts +++ b/src/renderer/components/+workloads-replicasets/store.ts @@ -6,16 +6,16 @@ import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable"; import type { Deployment, ReplicaSet, ReplicaSetApi } from "../../../common/k8s-api/endpoints"; import { PodStatusPhase } from "../../../common/k8s-api/endpoints/pod.api"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -interface Dependencies { +export interface ReplicaSetStoreDependencies extends KubeObjectStoreDependencies { getPodsByOwnerId: GetPodsByOwnerId; } export class ReplicaSetStore extends KubeObjectStore { - constructor(protected readonly dependencies: Dependencies, api: ReplicaSetApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + constructor(protected readonly dependencies: ReplicaSetStoreDependencies, api: ReplicaSetApi, opts?: KubeObjectStoreOptions) { + super(dependencies, api, opts); } getChildPods(replicaSet: ReplicaSet) { diff --git a/src/renderer/components/+workloads-statefulsets/store.injectable.ts b/src/renderer/components/+workloads-statefulsets/store.injectable.ts index 0e440d969e..5931b5dfaf 100644 --- a/src/renderer/components/+workloads-statefulsets/store.injectable.ts +++ b/src/renderer/components/+workloads-statefulsets/store.injectable.ts @@ -7,6 +7,7 @@ import assert from "assert"; import getPodsByOwnerIdInjectable from "../+workloads-pods/get-pods-by-owner-id.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/manager.injectable"; import statefulSetApiInjectable from "../../../common/k8s-api/endpoints/stateful-set.api.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { StatefulSetStore } from "./store"; @@ -19,6 +20,7 @@ const statefulSetStoreInjectable = getInjectable({ return new StatefulSetStore({ getPodsByOwnerId: di.inject(getPodsByOwnerIdInjectable), + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/src/renderer/components/+workloads-statefulsets/store.ts b/src/renderer/components/+workloads-statefulsets/store.ts index 0732ae5680..bdedcdcaf7 100644 --- a/src/renderer/components/+workloads-statefulsets/store.ts +++ b/src/renderer/components/+workloads-statefulsets/store.ts @@ -6,16 +6,16 @@ import type { GetPodsByOwnerId } from "../+workloads-pods/get-pods-by-owner-id.injectable"; import type { StatefulSet, StatefulSetApi } from "../../../common/k8s-api/endpoints"; import { PodStatusPhase } from "../../../common/k8s-api/endpoints"; -import type { KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; +import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -interface Dependencies { +interface Dependencies extends KubeObjectStoreDependencies { getPodsByOwnerId: GetPodsByOwnerId; } export class StatefulSetStore extends KubeObjectStore { constructor(protected readonly dependencies: Dependencies, api: StatefulSetApi, opts?: KubeObjectStoreOptions) { - super(api, opts); + super(dependencies, api, opts); } getChildPods(statefulSet: StatefulSet) { diff --git a/src/renderer/components/__tests__/cronjob.store.test.ts b/src/renderer/components/__tests__/cronjob.store.test.ts index 307c005aec..322e609953 100644 --- a/src/renderer/components/__tests__/cronjob.store.test.ts +++ b/src/renderer/components/__tests__/cronjob.store.test.ts @@ -7,6 +7,10 @@ import cronJobStoreInjectable from "../+workloads-cronjobs/store.injectable"; import { CronJob } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const scheduledCronJob = new CronJob({ apiVersion: "foo", @@ -119,8 +123,20 @@ describe("CronJob Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + cronJobStore = di.inject(cronJobStoreInjectable); }); diff --git a/src/renderer/components/__tests__/daemonset.store.test.ts b/src/renderer/components/__tests__/daemonset.store.test.ts index 76afb3188a..da5a167ffd 100644 --- a/src/renderer/components/__tests__/daemonset.store.test.ts +++ b/src/renderer/components/__tests__/daemonset.store.test.ts @@ -10,6 +10,10 @@ import podStoreInjectable from "../+workloads-pods/store.injectable"; import { DaemonSet, Pod } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const runningDaemonSet = new DaemonSet({ apiVersion: "foo", @@ -136,8 +140,20 @@ describe("DaemonSet Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + const podStore = di.inject(podStoreInjectable); daemonSetStore = di.inject(daemonSetStoreInjectable); diff --git a/src/renderer/components/__tests__/deployments.store.test.ts b/src/renderer/components/__tests__/deployments.store.test.ts index a478c03d2a..dc50f85ccf 100644 --- a/src/renderer/components/__tests__/deployments.store.test.ts +++ b/src/renderer/components/__tests__/deployments.store.test.ts @@ -11,6 +11,10 @@ import type { PodSpec } from "../../../common/k8s-api/endpoints"; import { Deployment, Pod } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const spec: PodSpec = { containers: [{ @@ -208,8 +212,20 @@ describe("Deployment Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + const podStore = di.inject(podStoreInjectable); // Add pods to pod store diff --git a/src/renderer/components/__tests__/job.store.test.ts b/src/renderer/components/__tests__/job.store.test.ts index 6fdc63bd50..5a000d18ae 100644 --- a/src/renderer/components/__tests__/job.store.test.ts +++ b/src/renderer/components/__tests__/job.store.test.ts @@ -10,6 +10,10 @@ import podStoreInjectable from "../+workloads-pods/store.injectable"; import { Job, Pod } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const runningJob = new Job({ apiVersion: "foo", @@ -173,8 +177,20 @@ describe("Job Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + jobStore = di.inject(jobStoreInjectable); const podStore = di.inject(podStoreInjectable); diff --git a/src/renderer/components/__tests__/pods.store.test.ts b/src/renderer/components/__tests__/pods.store.test.ts index 72e124a686..229c94c2d8 100644 --- a/src/renderer/components/__tests__/pods.store.test.ts +++ b/src/renderer/components/__tests__/pods.store.test.ts @@ -8,6 +8,10 @@ import type { PodStore } from "../+workloads-pods/store"; import podStoreInjectable from "../+workloads-pods/store.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const runningPod = new Pod({ apiVersion: "foo", @@ -119,8 +123,20 @@ describe("Pod Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + podStore = di.inject(podStoreInjectable); }); diff --git a/src/renderer/components/__tests__/replicaset.store.test.ts b/src/renderer/components/__tests__/replicaset.store.test.ts index 29352c34e1..3186725981 100644 --- a/src/renderer/components/__tests__/replicaset.store.test.ts +++ b/src/renderer/components/__tests__/replicaset.store.test.ts @@ -10,6 +10,10 @@ import type { ReplicaSetStore } from "../+workloads-replicasets/store"; import { ReplicaSet, Pod } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const runningReplicaSet = new ReplicaSet({ apiVersion: "foo", @@ -136,8 +140,20 @@ describe("ReplicaSet Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + const podStore = di.inject(podStoreInjectable); replicaSetStore = di.inject(replicasetsStoreInjectable); diff --git a/src/renderer/components/__tests__/statefulset.store.test.ts b/src/renderer/components/__tests__/statefulset.store.test.ts index 00471c8805..742955884c 100644 --- a/src/renderer/components/__tests__/statefulset.store.test.ts +++ b/src/renderer/components/__tests__/statefulset.store.test.ts @@ -10,6 +10,10 @@ import statefulSetStoreInjectable from "../+workloads-statefulsets/store.injecta import { StatefulSet, Pod } from "../../../common/k8s-api/endpoints"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; const runningStatefulSet = new StatefulSet({ apiVersion: "foo", @@ -136,8 +140,20 @@ describe("StatefulSet Store tests", () => { beforeEach(() => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + const createCluster = di.inject(createClusterInjectable); + + di.override(hostedClusterInjectable, () => createCluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + statefulSetStore = di.inject(statefulSetStoreInjectable); const podStore = di.inject(podStoreInjectable); diff --git a/src/renderer/components/cluster-settings/accessible-namespaces.tsx b/src/renderer/components/cluster-settings/accessible-namespaces.tsx index 479cc8f443..2f3b9b930d 100644 --- a/src/renderer/components/cluster-settings/accessible-namespaces.tsx +++ b/src/renderer/components/cluster-settings/accessible-namespaces.tsx @@ -32,13 +32,13 @@ export class ClusterAccessibleNamespaces extends React.Component { this.namespaces.add(newNamespace); - this.props.cluster.accessibleNamespaces = Array.from(this.namespaces); + this.props.cluster.accessibleNamespaces.replace([...this.namespaces]); }} validators={systemName} items={Array.from(this.namespaces)} remove={({ oldItem: oldNamespace }) => { this.namespaces.delete(oldNamespace); - this.props.cluster.accessibleNamespaces = Array.from(this.namespaces); + this.props.cluster.accessibleNamespaces.replace([...this.namespaces]); }} inputTheme="round-black" /> diff --git a/src/renderer/components/dock/create-resource/view.tsx b/src/renderer/components/dock/create-resource/view.tsx index d84a823259..00221a7b32 100644 --- a/src/renderer/components/dock/create-resource/view.tsx +++ b/src/renderer/components/dock/create-resource/view.tsx @@ -13,8 +13,8 @@ import { observer } from "mobx-react"; import type { CreateResourceTabStore } from "./store"; import { EditorPanel } from "../editor-panel"; import { InfoPanel } from "../info-panel"; -import { Notifications } from "../../notifications"; -import logger from "../../../../common/logger"; +import type { ShowNotification } from "../../notifications"; +import type { Logger } from "../../../../common/logger"; import type { ApiManager } from "../../../../common/k8s-api/api-manager"; import { isObject, prevDefault } from "../../../utils"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -29,6 +29,10 @@ import getDetailsUrlInjectable from "../../kube-detail-params/get-details-url.in import navigateInjectable from "../../../navigation/navigate.injectable"; import type { RequestKubeObjectCreation } from "../../../../common/k8s-api/endpoints/resource-applier.api/request-update.injectable"; import requestKubeObjectCreationInjectable from "../../../../common/k8s-api/endpoints/resource-applier.api/request-update.injectable"; +import loggerInjectable from "../../../../common/logger.injectable"; +import type { ShowCheckedErrorNotification } from "../../notifications/show-checked-error.injectable"; +import showSuccessNotificationInjectable from "../../notifications/show-success-notification.injectable"; +import showCheckedErrorNotificationInjectable from "../../notifications/show-checked-error.injectable"; export interface CreateResourceProps { tabId: string; @@ -38,9 +42,12 @@ interface Dependencies { createResourceTemplates: IComputedValue[]>; createResourceTabStore: CreateResourceTabStore; apiManager: ApiManager; + logger: Logger; navigate: Navigate; getDetailsUrl: GetDetailsUrl; requestKubeObjectCreation: RequestKubeObjectCreation; + showSuccessNotification: ShowNotification; + showCheckedErrorNotification: ShowCheckedErrorNotification; } @observer @@ -81,34 +88,38 @@ class NonInjectedCreateResource extends React.Component { - try { - const data = await requestKubeObjectCreation(dump(resource)); - const { kind, apiVersion, metadata: { name, namespace }} = data; + const result = await requestKubeObjectCreation(dump(resource)); - const showDetails = () => { - const resourceLink = apiManager.lookupApiLink({ kind, apiVersion, name, namespace }); + if (!result.callWasSuccessful) { + this.props.logger.warn("Failed to create resource", { resource }, result.error); + this.props.showCheckedErrorNotification(result.error, "Unknown error occured while creating resources"); - navigate(getDetailsUrl(resourceLink)); - close(); - }; - - const close = Notifications.ok( -

- {kind} - {" "} - - {name} - - {" successfully created."} -

, - ); - } catch (error) { - Notifications.checkedError(error, "Unknown error occured while creating resources"); + return; } + + const { kind, apiVersion, metadata: { name, namespace }} = result.response; + + const close = this.props.showSuccessNotification(( +

+ {kind} + {" "} + { + const resourceLink = apiManager.lookupApiLink({ kind, apiVersion, name, namespace }); + + navigate(getDetailsUrl(resourceLink)); + close(); + })} + > + {name} + + {" successfully created."} +

+ )); }); await Promise.allSettled(creatingResources); @@ -168,8 +179,11 @@ export const CreateResource = withInjectables createResourceTabStore: di.inject(createResourceTabStoreInjectable), createResourceTemplates: await di.inject(createResourceTemplatesInjectable), apiManager: di.inject(apiManagerInjectable), + logger: di.inject(loggerInjectable), getDetailsUrl: di.inject(getDetailsUrlInjectable), navigate: di.inject(navigateInjectable), requestKubeObjectCreation: di.inject(requestKubeObjectCreationInjectable), + showSuccessNotification: di.inject(showSuccessNotificationInjectable), + showCheckedErrorNotification: di.inject(showCheckedErrorNotificationInjectable), }), }); diff --git a/src/renderer/components/input/search-input.tsx b/src/renderer/components/input/search-input.tsx index 1c5a66bdc2..4aff4a6ffa 100644 --- a/src/renderer/components/input/search-input.tsx +++ b/src/renderer/components/input/search-input.tsx @@ -11,7 +11,8 @@ import { cssNames, autoBind } from "../../utils"; import { Icon } from "../icon"; import type { InputProps } from "./input"; import { Input } from "./input"; -import { isMac } from "../../../common/vars"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import isMacInjectable from "../../../common/vars/is-mac.injectable"; export interface SearchInputProps extends InputProps { compact?: boolean; // show only search-icon when not focused @@ -27,13 +28,17 @@ const defaultProps: Partial = { placeholder: "Search...", }; +interface Dependencies { + isMac: boolean; +} + @observer -export class SearchInput extends React.Component { +class NonInjectedSearchInput extends React.Component { static defaultProps = defaultProps as object; private inputRef = createRef(); - constructor(props: SearchInputProps) { + constructor(props: SearchInputProps & Dependencies) { super(props); autoBind(this); } @@ -48,7 +53,7 @@ export class SearchInput extends React.Component { } onGlobalKey(evt: KeyboardEvent) { - if (evt.key === "f" && (isMac ? evt.metaKey : evt.ctrlKey)) { + if (evt.key === "f" && (this.props.isMac ? evt.metaKey : evt.ctrlKey)) { this.inputRef.current?.focus(); } } @@ -71,7 +76,7 @@ export class SearchInput extends React.Component { } render() { - const { className, compact, onClear, showClearIcon, bindGlobalFocusHotkey, value, ...inputProps } = this.props; + const { className, compact, onClear, showClearIcon, bindGlobalFocusHotkey, value, isMac, ...inputProps } = this.props; let rightIcon = ; if (showClearIcon && value) { @@ -97,3 +102,10 @@ export class SearchInput extends React.Component { ); } } + +export const SearchInput = withInjectables(NonInjectedSearchInput, { + getProps: (di, props) => ({ + ...props, + isMac: di.inject(isMacInjectable), + }), +}); diff --git a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index 707351c231..bd4fa3d81d 100644 --- a/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -20,8 +20,7 @@ import { ResourceKindMap, ResourceNames } from "../../utils/rbac"; import { Icon } from "../icon"; import { TooltipPosition } from "../tooltip"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context"; -import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; import type { SubscribableStore, SubscribeStores } from "../../kube-watch-api/kube-watch-api"; import type { KubeApi } from "../../../common/k8s-api/kube-api"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; @@ -29,6 +28,7 @@ import type { PageParam } from "../../navigation"; import type { ToggleKubeDetailsPane } from "../kube-detail-params/toggle-details.injectable"; import kubeSelectedUrlParamInjectable from "../kube-detail-params/kube-selected-url.injectable"; import toggleKubeDetailsPaneInjectable from "../kube-detail-params/toggle-details.injectable"; +import type { ClusterContext } from "../../cluster-frame-context/cluster-frame-context"; export interface KubeObjectListLayoutProps< K extends KubeObject, @@ -43,7 +43,7 @@ export interface KubeObjectListLayoutProps< } interface Dependencies { - clusterFrameContext: ClusterFrameContext; + clusterFrameContext: ClusterContext; subscribeToStores: SubscribeStores; kubeSelectedUrlParam: PageParam; toggleKubeDetailsPane: ToggleKubeDetailsPane; @@ -177,7 +177,7 @@ export const KubeObjectListLayout = withInjectables< >(NonInjectedKubeObjectListLayout, { getProps: (di, props) => ({ ...props, - clusterFrameContext: di.inject(clusterFrameContextInjectable), + clusterFrameContext: di.inject(clusterFrameContextForNamespacedResourcesInjectable), subscribeToStores: di.inject(subscribeStoresInjectable), kubeSelectedUrlParam: di.inject(kubeSelectedUrlParamInjectable), toggleKubeDetailsPane: di.inject(toggleKubeDetailsPaneInjectable), diff --git a/src/renderer/components/layout/top-bar/start-state-sync.injectable.ts b/src/renderer/components/layout/top-bar/start-state-sync.injectable.ts index 666966ddad..92488851c5 100644 --- a/src/renderer/components/layout/top-bar/start-state-sync.injectable.ts +++ b/src/renderer/components/layout/top-bar/start-state-sync.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { action } from "mobx"; -import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../before-frame-starts/tokens"; import ipcRendererInjectable from "../../../utils/channel/ipc-renderer.injectable"; import topBarStateInjectable from "./state.injectable"; @@ -26,7 +26,7 @@ const startTopbarStateSyncInjectable = getInjectable({ })); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, causesSideEffects: true, }); diff --git a/src/renderer/components/monaco-editor/monaco-themes/clouds-midnight.json b/src/renderer/components/monaco-editor/monaco-themes/clouds-midnight.json deleted file mode 100644 index 48696d8d0a..0000000000 --- a/src/renderer/components/monaco-editor/monaco-themes/clouds-midnight.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "name": "clouds-midnight", - "base": "vs-dark", - "inherit": true, - "rules": [ - { - "background": "191919", - "token": "" - }, - { - "foreground": "3c403b", - "token": "comment" - }, - { - "foreground": "5d90cd", - "token": "string" - }, - { - "foreground": "46a609", - "token": "constant.numeric" - }, - { - "foreground": "39946a", - "token": "constant.language" - }, - { - "foreground": "927c5d", - "token": "keyword" - }, - { - "foreground": "927c5d", - "token": "support.constant.property-value" - }, - { - "foreground": "927c5d", - "token": "constant.other.color" - }, - { - "foreground": "366f1a", - "token": "keyword.other.unit" - }, - { - "foreground": "a46763", - "token": "entity.other.attribute-name.html" - }, - { - "foreground": "4b4b4b", - "token": "keyword.operator" - }, - { - "foreground": "e92e2e", - "token": "storage" - }, - { - "foreground": "858585", - "token": "entity.other.inherited-class" - }, - { - "foreground": "606060", - "token": "entity.name.tag" - }, - { - "foreground": "a165ac", - "token": "constant.character.entity" - }, - { - "foreground": "a165ac", - "token": "support.class.js" - }, - { - "foreground": "606060", - "token": "entity.other.attribute-name" - }, - { - "foreground": "e92e2e", - "token": "meta.selector.css" - }, - { - "foreground": "e92e2e", - "token": "entity.name.tag.css" - }, - { - "foreground": "e92e2e", - "token": "entity.other.attribute-name.id.css" - }, - { - "foreground": "e92e2e", - "token": "entity.other.attribute-name.class.css" - }, - { - "foreground": "616161", - "token": "meta.property-name.css" - }, - { - "foreground": "e92e2e", - "token": "support.function" - }, - { - "foreground": "ffffff", - "background": "e92e2e", - "token": "invalid" - }, - { - "foreground": "e92e2e", - "token": "punctuation.section.embedded" - }, - { - "foreground": "606060", - "token": "punctuation.definition.tag" - }, - { - "foreground": "a165ac", - "token": "constant.other.color.rgb-value.css" - }, - { - "foreground": "a165ac", - "token": "support.constant.property-value.css" - } - ], - "colors": { - "editor.foreground": "#929292", - "editor.background": "#191919", - "editor.selectionBackground": "#000000", - "editor.lineHighlightBackground": "#D7D7D708", - "editorCursor.foreground": "#7DA5DC", - "editorWhitespace.foreground": "#BFBFBF" - } -} \ No newline at end of file diff --git a/src/renderer/components/test-utils/get-application-builder.tsx b/src/renderer/components/test-utils/get-application-builder.tsx index 7e2dde5296..405a4e3179 100644 --- a/src/renderer/components/test-utils/get-application-builder.tsx +++ b/src/renderer/components/test-utils/get-application-builder.tsx @@ -9,10 +9,10 @@ import type { IComputedValue, ObservableMap } from "mobx"; import { action, computed, observable, runInAction } from "mobx"; import React from "react"; import { Router } from "react-router"; -import allowedResourcesInjectable from "../../cluster-frame-context/allowed-resources.injectable"; import type { RenderResult } from "@testing-library/react"; import { fireEvent, queryByText } from "@testing-library/react"; -import type { KubeResource } from "../../../common/rbac"; +import type { KubeApiResourceDescriptor } from "../../../common/rbac"; +import { formatKubeApiResource } from "../../../common/rbac"; import type { DiContainer, Injectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable"; import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable"; @@ -22,10 +22,7 @@ import navigateToPreferencesInjectable from "../../../features/preferences/commo import type { NavigateToHelmCharts } from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable"; import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable"; import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; -import { ClusterFrameContext } from "../../cluster-frame-context/cluster-frame-context"; import type { Cluster } from "../../../common/cluster/cluster"; -import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import clusterFrameContextInjectable from "../../cluster-frame-context/cluster-frame-context.injectable"; import startMainApplicationInjectable from "../../../main/start-main-application/start-main-application.injectable"; import startFrameInjectable from "../../start-frame/start-frame.injectable"; import type { NamespaceStore } from "../+namespaces/store"; @@ -52,7 +49,6 @@ import hostedClusterIdInjectable from "../../cluster-frame-context/hosted-cluste import activeKubernetesClusterInjectable from "../../cluster-frame-context/active-kubernetes-cluster.injectable"; import { catalogEntityFromCluster } from "../../../main/cluster/manager"; import namespaceStoreInjectable from "../+namespaces/store.injectable"; -import { isAllowedResource } from "../../../common/cluster/is-allowed-resource"; import createApplicationWindowInjectable from "../../../main/start-main-application/lens-window/application-window/create-application-window.injectable"; import type { CreateElectronWindow } from "../../../main/start-main-application/lens-window/application-window/create-electron-window.injectable"; import createElectronWindowInjectable from "../../../main/start-main-application/lens-window/application-window/create-electron-window.injectable"; @@ -115,7 +111,7 @@ export interface ApplicationBuilder { create: (id: string) => LensWindowWithHelpers; }; - allowKubeResource: (resourceName: KubeResource) => ApplicationBuilder; + allowKubeResource: (resource: KubeApiResourceDescriptor) => ApplicationBuilder; beforeApplicationStart: (callback: Callback) => ApplicationBuilder; afterApplicationStart: (callback: Callback) => ApplicationBuilder; beforeWindowStart: (callback: Callback) => ApplicationBuilder; @@ -214,7 +210,7 @@ export const getApplicationBuilder = () => { }, })); - const allowedResourcesState = observable.array(); + const allowedResourcesState = observable.set(); const windowHelpers = new Map RenderResult }>(); @@ -502,15 +498,11 @@ export const getApplicationBuilder = () => { environment = environments.clusterFrame; builder.beforeWindowStart((windowDi) => { - windowDi.override(allowedResourcesInjectable, () => - computed(() => new Set([...allowedResourcesState])), - ); - const clusterStub = { id: "some-cluster-id", - accessibleNamespaces: [], - isAllowedResource: isAllowedResource(allowedResourcesState), - } as unknown as Cluster; + accessibleNamespaces: observable.array(), + shouldShowResource: (kind) => allowedResourcesState.has(formatKubeApiResource(kind)), + } as Partial as Cluster; windowDi.override(activeKubernetesClusterInjectable, () => computed(() => catalogEntityFromCluster(clusterStub)), @@ -538,20 +530,8 @@ export const getApplicationBuilder = () => { getTotalCount: () => namespaceItems.length, } as Partial as NamespaceStore; - const clusterFrameContextFake = new ClusterFrameContext( - clusterStub, - - { - namespaceStore: namespaceStoreStub, - }, - ); - windowDi.override(namespaceStoreInjectable, () => namespaceStoreStub); windowDi.override(hostedClusterInjectable, () => clusterStub); - windowDi.override(clusterFrameContextInjectable, () => clusterFrameContextFake); - - // Todo: get rid of global state. - KubeObjectStore.defaultContext.set(clusterFrameContextFake); }); return builder; @@ -635,11 +615,11 @@ export const getApplicationBuilder = () => { }, }, - allowKubeResource: (resourceName) => { + allowKubeResource: (resource) => { environment.onAllowKubeResource(); runInAction(() => { - allowedResourcesState.push(resourceName); + allowedResourcesState.add(formatKubeApiResource(resource)); }); return builder; diff --git a/src/renderer/frames/cluster-frame/cluster-frame.test.tsx b/src/renderer/frames/cluster-frame/cluster-frame.test.tsx index f95a80fee2..b969291fd2 100644 --- a/src/renderer/frames/cluster-frame/cluster-frame.test.tsx +++ b/src/renderer/frames/cluster-frame/cluster-frame.test.tsx @@ -15,13 +15,12 @@ import { ClusterFrame } from "./cluster-frame"; import historyInjectable from "../../navigation/history.injectable"; import { computed } from "mobx"; import type { Cluster } from "../../../common/cluster/cluster"; -import createClusterInjectable from "../../create-cluster/create-cluster.injectable"; +import createClusterInjectable from "../../cluster/create-cluster.injectable"; import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import legacyOnChannelListenInjectable from "../../ipc/legacy-channel-listen.injectable"; import currentRouteComponentInjectable from "../../routes/current-route-component.injectable"; -import allowedResourcesInjectable from "../../cluster-frame-context/allowed-resources.injectable"; import hostedClusterIdInjectable from "../../cluster-frame-context/hosted-cluster-id.injectable"; import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; import { useFakeTime } from "../../../common/test-utils/use-fake-time"; @@ -69,7 +68,8 @@ describe("", () => { describe("given cluster with list nodes and namespaces permissions", () => { beforeEach(() => { - di.override(allowedResourcesInjectable, () => computed(() => new Set(["nodes", "namespaces"]))); + // TODO: replace with not using private info + (cluster as any).allowedResources.replace(["v1/nodes", "v1/namespaces"]); }); it("renders", () => { @@ -110,7 +110,7 @@ describe("", () => { describe("given cluster without list nodes, but with namespaces permissions", () => { beforeEach(() => { - di.override(allowedResourcesInjectable, () => computed(() => new Set(["namespaces"]))); + (cluster as any).allowedResources.replace(["v1/namespaces"]); }); it("renders", () => { diff --git a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts index 909834a047..c640264ee3 100644 --- a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts +++ b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts @@ -7,10 +7,11 @@ import { initClusterFrame } from "./init-cluster-frame"; import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable"; import frameRoutingIdInjectable from "./frame-routing-id/frame-routing-id.injectable"; import hostedClusterInjectable from "../../../cluster-frame-context/hosted-cluster.injectable"; -import clusterFrameContextInjectable from "../../../cluster-frame-context/cluster-frame-context.injectable"; import assert from "assert"; import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable"; import loadExtensionsInjectable from "../../load-extensions.injectable"; +import loggerInjectable from "../../../../common/logger.injectable"; +import showErrorNotificationInjectable from "../../../components/notifications/show-error-notification.injectable"; const initClusterFrameInjectable = getInjectable({ id: "init-cluster-frame", @@ -26,7 +27,8 @@ const initClusterFrameInjectable = getInjectable({ catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable), frameRoutingId: di.inject(frameRoutingIdInjectable), emitAppEvent: di.inject(emitAppEventInjectable), - clusterFrameContext: di.inject(clusterFrameContextInjectable), + logger: di.inject(loggerInjectable), + showErrorNotification: di.inject(showErrorNotificationInjectable), }); }, }); diff --git a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts index 9476393366..109ae0f0bc 100644 --- a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts +++ b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts @@ -4,13 +4,11 @@ */ import type { Cluster } from "../../../../common/cluster/cluster"; import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry"; -import logger from "../../../../main/logger"; -import { Notifications } from "../../../components/notifications"; +import type { ShowNotification } from "../../../components/notifications"; import { when } from "mobx"; -import type { ClusterFrameContext } from "../../../cluster-frame-context/cluster-frame-context"; -import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store"; import { requestSetClusterFrameId } from "../../../ipc"; import type { EmitAppEvent } from "../../../../common/app-event-bus/emit-event.injectable"; +import type { Logger } from "../../../../common/logger"; interface Dependencies { hostedCluster: Cluster; @@ -18,9 +16,8 @@ interface Dependencies { catalogEntityRegistry: CatalogEntityRegistry; frameRoutingId: number; emitAppEvent: EmitAppEvent; - - // TODO: This dependency belongs to KubeObjectStore - clusterFrameContext: ClusterFrameContext; + logger: Logger; + showErrorNotification: ShowNotification; } const logPrefix = "[CLUSTER-FRAME]:"; @@ -31,7 +28,8 @@ export const initClusterFrame = ({ catalogEntityRegistry, frameRoutingId, emitAppEvent, - clusterFrameContext, + logger, + showErrorNotification, }: Dependencies) => async (unmountRoot: () => void) => { // TODO: Make catalogEntityRegistry already initialized when passed as dependency @@ -55,14 +53,12 @@ export const initClusterFrame = ({ { timeout: 15_000, onError: (error) => { - console.warn( + logger.warn( "[CLUSTER-FRAME]: error from activeEntity when()", error, ); - Notifications.error( - "Failed to get KubernetesCluster for this view. Extensions will not be loaded.", - ); + showErrorNotification("Failed to get KubernetesCluster for this view. Extensions will not be loaded."); }, }, ); @@ -84,6 +80,4 @@ export const initClusterFrame = ({ unmountRoot(); }; - // TODO: Make context dependency of KubeObjectStore - KubeObjectStore.defaultContext.set(clusterFrameContext); }; diff --git a/src/renderer/frames/root-frame/setup-system-ca.injectable.ts b/src/renderer/frames/root-frame/setup-system-ca.injectable.ts index 698a769be7..557d87c81f 100644 --- a/src/renderer/frames/root-frame/setup-system-ca.injectable.ts +++ b/src/renderer/frames/root-frame/setup-system-ca.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import injectSystemCAsInjectable from "../../../common/certificate-authorities/inject-system-cas.injectable"; const setupSystemCaInjectable = getInjectable({ @@ -12,7 +12,7 @@ const setupSystemCaInjectable = getInjectable({ id: "setup-system-ca", run: di.inject(injectSystemCAsInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupSystemCaInjectable; diff --git a/src/renderer/initializers/workload-events.tsx b/src/renderer/initializers/workload-events.tsx index 734b5105f0..735b4e2c74 100644 --- a/src/renderer/initializers/workload-events.tsx +++ b/src/renderer/initializers/workload-events.tsx @@ -7,7 +7,7 @@ import { withInjectables } from "@ogre-tools/injectable-react"; import type { IComputedValue } from "mobx"; import { observer } from "mobx-react"; import React from "react"; -import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable"; +import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; import { Events } from "../components/+events/events"; export interface WorkloadEventsProps {} @@ -32,7 +32,10 @@ const NonInjectedWorkloadEvents = observer(({ workloadEventsAreAllowed }: Depend export const WorkloadEvents = withInjectables(NonInjectedWorkloadEvents, { getProps: (di, props) => ({ - workloadEventsAreAllowed: di.inject(isAllowedResourceInjectable, "events"), + workloadEventsAreAllowed: di.inject(shouldShowResourceInjectionToken, { + apiName: "events", + group: "v1", + }), ...props, }), }); diff --git a/src/renderer/kube-watch-api/kube-watch-api.injectable.ts b/src/renderer/kube-watch-api/kube-watch-api.injectable.ts index 3fc8aa60d3..23e53bf7cc 100644 --- a/src/renderer/kube-watch-api/kube-watch-api.injectable.ts +++ b/src/renderer/kube-watch-api/kube-watch-api.injectable.ts @@ -3,14 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import clusterFrameContextInjectable from "../cluster-frame-context/cluster-frame-context.injectable"; +import clusterFrameContextForNamespacedResourcesInjectable from "../cluster-frame-context/for-namespaced-resources.injectable"; import { KubeWatchApi } from "./kube-watch-api"; const kubeWatchApiInjectable = getInjectable({ id: "kube-watch-api", instantiate: (di) => new KubeWatchApi({ - clusterFrameContext: di.inject(clusterFrameContextInjectable), + clusterContext: di.inject(clusterFrameContextForNamespacedResourcesInjectable), }), }); diff --git a/src/renderer/kube-watch-api/kube-watch-api.ts b/src/renderer/kube-watch-api/kube-watch-api.ts index c3eca74d29..4fc31bd1d5 100644 --- a/src/renderer/kube-watch-api/kube-watch-api.ts +++ b/src/renderer/kube-watch-api/kube-watch-api.ts @@ -6,10 +6,10 @@ import { comparer, reaction } from "mobx"; import type { Disposer } from "../../common/utils"; import { disposer, getOrInsert, noop, WrappedAbortController } from "../../common/utils"; import { once } from "lodash"; -import type { ClusterFrameContext } from "../cluster-frame-context/cluster-frame-context"; import logger from "../../common/logger"; import type { KubeObjectStoreLoadAllParams, KubeObjectStoreSubscribeParams } from "../../common/k8s-api/kube-object.store"; import AbortController from "abort-controller"; +import type { ClusterContext } from "../cluster-frame-context/cluster-frame-context"; // Kubernetes watch-api client // API: https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams @@ -68,7 +68,7 @@ export interface KubeWatchSubscribeStoreOptions { } interface Dependencies { - clusterFrameContext: ClusterFrameContext; + readonly clusterContext: ClusterContext; } export interface SubscribableStore { @@ -86,7 +86,7 @@ export type SubscribeStores = (stores: SubscribableStore[], opts?: KubeWatchSubs export class KubeWatchApi { readonly #watch = new WatchCount(); - constructor(private dependencies: Dependencies) {} + constructor(private readonly dependencies: Dependencies) {} private subscribeStore({ store, parent, namespaces, onLoadFailure }: SubscribeStoreParams): Disposer { const isNamespaceFilterWatch = !namespaces; @@ -96,7 +96,7 @@ export class KubeWatchApi { return () => this.#watch.dec(store); } - namespaces ??= this.dependencies.clusterFrameContext?.contextNamespaces ?? []; + namespaces ??= this.dependencies.clusterContext?.contextNamespaces ?? []; let childController = new WrappedAbortController(parent); const unsubscribe = disposer(); @@ -123,7 +123,7 @@ export class KubeWatchApi { const cancelReloading = isNamespaceFilterWatch && store.api.isNamespaced ? reaction( // Note: must slice because reaction won't fire if it isn't there - () => [this.dependencies.clusterFrameContext.contextNamespaces.slice(), this.dependencies.clusterFrameContext.hasSelectedAll] as const, + () => [this.dependencies.clusterContext.contextNamespaces.slice(), this.dependencies.clusterContext.hasSelectedAll] as const, ([namespaces, curSelectedAll], [prevNamespaces, prevSelectedAll]) => { if (curSelectedAll && prevSelectedAll) { const action = namespaces.length > prevNamespaces.length ? "created" : "deleted"; diff --git a/src/renderer/kube-watch-api/subscribe-stores.injectable.ts b/src/renderer/kube-watch-api/subscribe-stores.injectable.ts index f34a3d21f4..81c38c387e 100644 --- a/src/renderer/kube-watch-api/subscribe-stores.injectable.ts +++ b/src/renderer/kube-watch-api/subscribe-stores.injectable.ts @@ -7,7 +7,6 @@ import kubeWatchApiInjectable from "./kube-watch-api.injectable"; const subscribeStoresInjectable = getInjectable({ id: "subscribe-stores", - causesSideEffects: true, instantiate: (di) => di.inject(kubeWatchApiInjectable).subscribeStores, }); diff --git a/src/renderer/navigation/events.ts b/src/renderer/navigation/events.ts index 7cacd1b313..452d16af9b 100644 --- a/src/renderer/navigation/events.ts +++ b/src/renderer/navigation/events.ts @@ -3,39 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { ipcRenderer } from "electron"; -import { reaction } from "mobx"; -import { broadcastMessage } from "../../common/ipc"; -import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; -import matchedClusterIdInjectable from "./matched-cluster-id.injectable"; - +// export const enum IpcRendererNavigationEvents { - CLUSTER_VIEW_CURRENT_ID = "renderer:cluster-id-of-active-view", NAVIGATE_IN_APP = "renderer:navigate", NAVIGATE_IN_CLUSTER = "renderer:navigate-in-cluster", LOADED = "renderer:loaded", } - -export function bindEvents() { - if (!ipcRenderer) { - return; - } - - if (process.isMainFrame) { - bindClusterManagerRouteEvents(); - } -} - -// Handle events only in main window renderer process (see also: cluster-manager.tsx) -function bindClusterManagerRouteEvents() { - const di = getLegacyGlobalDiForExtensionApi(); - - const matchedClusterId = di.inject(matchedClusterIdInjectable); - - // Keep track of active cluster-id for handling IPC/menus/etc. - reaction(() => matchedClusterId.get(), clusterId => { - broadcastMessage(IpcRendererNavigationEvents.CLUSTER_VIEW_CURRENT_ID, clusterId); - }, { - fireImmediately: true, - }); -} diff --git a/src/renderer/start-frame/start-frame.injectable.ts b/src/renderer/start-frame/start-frame.injectable.ts index 60e8d590d0..3f1934aa1c 100644 --- a/src/renderer/start-frame/start-frame.injectable.ts +++ b/src/renderer/start-frame/start-frame.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { runManyFor } from "../../common/runnable/run-many-for"; -import { beforeFrameStartsInjectionToken, beforeClusterFrameStartsInjectionToken, beforeFrameStartsFirstInjectionToken, beforeMainFrameStartsInjectionToken } from "../before-frame-starts/tokens"; +import * as tokens from "../before-frame-starts/tokens"; import currentlyInClusterFrameInjectable from "../routes/currently-in-cluster-frame.injectable"; const startFrameInjectable = getInjectable({ @@ -13,22 +13,30 @@ const startFrameInjectable = getInjectable({ // TODO: Consolidate contents of bootstrap.tsx here instantiate: (di) => { const runMany = runManyFor(di); - const beforeFrameStartsFirst = runMany(beforeFrameStartsFirstInjectionToken); - const beforeMainFrameStarts = runMany(beforeMainFrameStartsInjectionToken); - const beforeClusterFrameStarts = runMany(beforeClusterFrameStartsInjectionToken); - const beforeFrameStarts = runMany(beforeFrameStartsInjectionToken); + const beforeFrameStartsFirst = runMany(tokens.beforeFrameStartsFirstInjectionToken); + const beforeMainFrameStartsFirst = runMany(tokens.beforeMainFrameStartsFirstInjectionToken); + const beforeClusterFrameStartsFirst = runMany(tokens.beforeClusterFrameStartsFirstInjectionToken); + const beforeFrameStartsSecond = runMany(tokens.beforeFrameStartsSecondInjectionToken); + const beforeMainFrameStartsSecond = runMany(tokens.beforeMainFrameStartsSecondInjectionToken); + const beforeClusterFrameStartsSecond = runMany(tokens.beforeClusterFrameStartsSecondInjectionToken); const currentlyInClusterFrame = di.inject(currentlyInClusterFrameInjectable); return async () => { await beforeFrameStartsFirst(); if (currentlyInClusterFrame) { - await beforeClusterFrameStarts(); + await beforeClusterFrameStartsFirst(); } else { - await beforeMainFrameStarts(); + await beforeMainFrameStartsFirst(); } - await beforeFrameStarts(); + await beforeFrameStartsSecond(); + + if (currentlyInClusterFrame) { + await beforeClusterFrameStartsSecond(); + } else { + await beforeMainFrameStartsSecond(); + } }; }, }); diff --git a/src/renderer/stores/init-user-store.injectable.ts b/src/renderer/stores/init-user-store.injectable.ts index a65181ca67..fba2287fe0 100644 --- a/src/renderer/stores/init-user-store.injectable.ts +++ b/src/renderer/stores/init-user-store.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import userStoreInjectable from "../../common/user-store/user-store.injectable"; -import { beforeFrameStartsInjectionToken } from "../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens"; import initDefaultUpdateChannelInjectable from "../vars/default-update-channel/init.injectable"; const initUserStoreInjectable = getInjectable({ @@ -19,7 +19,7 @@ const initUserStoreInjectable = getInjectable({ }, runAfter: di.inject(initDefaultUpdateChannelInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initUserStoreInjectable; diff --git a/src/renderer/themes/setup-apply-active-theme.injectable.ts b/src/renderer/themes/setup-apply-active-theme.injectable.ts index 17b2e655a5..960f5f8764 100644 --- a/src/renderer/themes/setup-apply-active-theme.injectable.ts +++ b/src/renderer/themes/setup-apply-active-theme.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { reaction } from "mobx"; import initializeSystemThemeTypeInjectable from "../../features/theme/system-type/renderer/initialize.injectable"; -import { beforeFrameStartsInjectionToken } from "../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens"; import initUserStoreInjectable from "../stores/init-user-store.injectable"; import activeThemeInjectable from "./active.injectable"; import applyLensThemeInjectable from "./apply-lens-theme.injectable"; @@ -31,7 +31,7 @@ const setupApplyActiveThemeInjectable = getInjectable({ di.inject(initUserStoreInjectable), ], }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupApplyActiveThemeInjectable; diff --git a/src/renderer/utils/channel/channel-listeners/start-listening-of-channels.injectable.ts b/src/renderer/utils/channel/channel-listeners/start-listening-of-channels.injectable.ts index cbcf63ea76..aea98b3360 100644 --- a/src/renderer/utils/channel/channel-listeners/start-listening-of-channels.injectable.ts +++ b/src/renderer/utils/channel/channel-listeners/start-listening-of-channels.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../../before-frame-starts/tokens"; import listeningOnMessageChannelsInjectable from "../../../../common/utils/channel/listening-on-message-channels.injectable"; const startListeningOfChannelsInjectable = getInjectable({ @@ -18,7 +18,7 @@ const startListeningOfChannelsInjectable = getInjectable({ }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default startListeningOfChannelsInjectable; diff --git a/src/renderer/utils/rbac.ts b/src/renderer/utils/rbac.ts index 46219febf6..93c153435f 100644 --- a/src/renderer/utils/rbac.ts +++ b/src/renderer/utils/rbac.ts @@ -5,6 +5,7 @@ import type { KubeResource } from "../../common/rbac"; import { apiResourceRecord } from "../../common/rbac"; +import { object } from "../../common/utils"; export const ResourceNames: Record = { "namespaces": "Namespaces", @@ -42,7 +43,7 @@ export const ResourceNames: Record = { "serviceaccounts": "Service Accounts", }; -export const ResourceKindMap: Record = Object.fromEntries( - Object.entries(apiResourceRecord) - .map(([resource, { kind }]) => [kind, resource as KubeResource]), +export const ResourceKindMap = object.fromEntries( + object.entries(apiResourceRecord) + .map(([resource, { kind }]) => [kind, resource]), ); diff --git a/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts b/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts index f813445855..3666bf0840 100644 --- a/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts +++ b/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import { syncBoxInitialValueChannel } from "../../../common/utils/sync-box/channels"; import createSyncBoxStateInjectable from "../../../common/utils/sync-box/sync-box-state.injectable"; import { requestFromChannelInjectionToken } from "../../../common/utils/channel/request-from-channel-injection-token"; @@ -32,7 +32,7 @@ const provideInitialValuesForSyncBoxesInjectable = getInjectable({ }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default provideInitialValuesForSyncBoxesInjectable; diff --git a/src/renderer/vars/build-version/init.injectable.ts b/src/renderer/vars/build-version/init.injectable.ts index 7e7b7e9876..0734960289 100644 --- a/src/renderer/vars/build-version/init.injectable.ts +++ b/src/renderer/vars/build-version/init.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import buildVersionInjectable from "./build-version.injectable"; const initializeBuildVersionInjectable = getInjectable({ @@ -16,7 +16,7 @@ const initializeBuildVersionInjectable = getInjectable({ await buildVersion.init(); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initializeBuildVersionInjectable; diff --git a/src/renderer/vars/default-update-channel/init.injectable.ts b/src/renderer/vars/default-update-channel/init.injectable.ts index c7435230c8..a349769815 100644 --- a/src/renderer/vars/default-update-channel/init.injectable.ts +++ b/src/renderer/vars/default-update-channel/init.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import initReleaseChannelInjectable from "../release-channel/init.injectable"; import defaultUpdateChannelInjectable from "../../../features/application-update/common/selected-update-channel/default-update-channel.injectable"; @@ -18,7 +18,7 @@ const initDefaultUpdateChannelInjectable = getInjectable({ }, runAfter: di.inject(initReleaseChannelInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initDefaultUpdateChannelInjectable; diff --git a/src/renderer/vars/release-channel/init.injectable.ts b/src/renderer/vars/release-channel/init.injectable.ts index 016ec0826d..e0092abf55 100644 --- a/src/renderer/vars/release-channel/init.injectable.ts +++ b/src/renderer/vars/release-channel/init.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import releaseChannelInjectable from "../../../common/vars/release-channel.injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import initSemanticBuildVersionInjectable from "../semantic-build-version/init.injectable"; const initReleaseChannelInjectable = getInjectable({ @@ -18,7 +18,7 @@ const initReleaseChannelInjectable = getInjectable({ }, runAfter: di.inject(initSemanticBuildVersionInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initReleaseChannelInjectable; diff --git a/src/renderer/vars/semantic-build-version/init.injectable.ts b/src/renderer/vars/semantic-build-version/init.injectable.ts index bf9d3e02d4..62a9a3387f 100644 --- a/src/renderer/vars/semantic-build-version/init.injectable.ts +++ b/src/renderer/vars/semantic-build-version/init.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import buildSemanticVersionInjectable from "../../../common/vars/build-semantic-version.injectable"; -import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../../before-frame-starts/tokens"; import initializeBuildVersionInjectable from "../build-version/init.injectable"; const initSemanticBuildVersionInjectable = getInjectable({ @@ -18,7 +18,7 @@ const initSemanticBuildVersionInjectable = getInjectable({ }, runAfter: di.inject(initializeBuildVersionInjectable), }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default initSemanticBuildVersionInjectable; diff --git a/src/test-utils/mock-responses.ts b/src/test-utils/mock-responses.ts new file mode 100644 index 0000000000..ce016cabc7 --- /dev/null +++ b/src/test-utils/mock-responses.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { Response, Headers as NodeFetchHeaders } from "node-fetch"; +import { PassThrough } from "stream"; + +export const createMockResponseFromString = (url: string, data: string, statusCode = 200) => { + const res: jest.Mocked = { + buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }), + clone: jest.fn(() => res), + arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }), + blob: jest.fn(async () => { throw new Error("blob() is not supported"); }), + body: new PassThrough(), + bodyUsed: false, + headers: new Headers() as NodeFetchHeaders, + json: jest.fn(async () => JSON.parse(await res.text())), + ok: 200 <= statusCode && statusCode < 300, + redirected: 300 <= statusCode && statusCode < 400, + size: data.length, + status: statusCode, + statusText: "some-text", + text: jest.fn(async () => data), + type: "basic", + url, + formData: jest.fn(async () => { throw new Error("formData() is not supported"); }), + }; + + return res; +}; + +export const createMockResponseFromStream = (url: string, stream: NodeJS.ReadableStream, statusCode = 200) => { + const res: jest.Mocked = { + buffer: jest.fn(async () => { throw new Error("buffer() is not supported"); }), + clone: jest.fn(() => res), + arrayBuffer: jest.fn(async () => { throw new Error("arrayBuffer() is not supported"); }), + blob: jest.fn(async () => { throw new Error("blob() is not supported"); }), + body: stream, + bodyUsed: false, + headers: new Headers() as NodeFetchHeaders, + json: jest.fn(async () => JSON.parse(await res.text())), + ok: 200 <= statusCode && statusCode < 300, + redirected: 300 <= statusCode && statusCode < 400, + size: 10, + status: statusCode, + statusText: "some-text", + text: jest.fn(() => { + const chunks: Buffer[] = []; + + return new Promise((resolve, reject) => { + stream.on("data", (chunk) => chunks.push(Buffer.from(chunk))); + stream.on("error", (err) => reject(err)); + stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); + }); + }), + type: "basic", + url, + formData: jest.fn(async () => { throw new Error("formData() is not supported"); }), + }; + + return res; +}; From 24240655c01f615fa6b4baea8b6496427536614b Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 20 Dec 2022 08:00:15 -0800 Subject: [PATCH 11/21] Split out use of httpsProxy preference (#6771) * Split out use of httpsProxy preference Signed-off-by: Sebastian Malton * Rename file Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- src/common/fetch/download-json.injectable.ts | 55 ------------------- src/common/fetch/download-json/impl.ts | 46 ++++++++++++++++ .../fetch/download-json/normal.injectable.ts | 14 +++++ .../fetch/download-json/proxy.injectable.ts | 14 +++++ src/common/fetch/fetch-module.injectable.ts | 19 +++++++ src/common/fetch/fetch.injectable.ts | 23 ++------ src/common/fetch/proxy-fetch.injectable.ts | 30 ++++++++++ .../navigation-using-application-menu.test.ts | 8 --- ...est-public-helm-repositories.injectable.ts | 4 +- .../+extensions/__tests__/extensions.test.tsx | 6 -- .../attempt-install-by-info.injectable.tsx | 2 +- 11 files changed, 130 insertions(+), 91 deletions(-) delete mode 100644 src/common/fetch/download-json.injectable.ts create mode 100644 src/common/fetch/download-json/impl.ts create mode 100644 src/common/fetch/download-json/normal.injectable.ts create mode 100644 src/common/fetch/download-json/proxy.injectable.ts create mode 100644 src/common/fetch/fetch-module.injectable.ts create mode 100644 src/common/fetch/proxy-fetch.injectable.ts diff --git a/src/common/fetch/download-json.injectable.ts b/src/common/fetch/download-json.injectable.ts deleted file mode 100644 index 78a7d030d7..0000000000 --- a/src/common/fetch/download-json.injectable.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import type { RequestInit, Response } from "node-fetch"; -import type { AsyncResult } from "../utils/async-result"; -import fetchInjectable from "./fetch.injectable"; - -export interface DownloadJsonOptions { - signal?: AbortSignal | null | undefined; -} - -export type DownloadJson = (url: string, opts?: DownloadJsonOptions) => Promise>; - -const downloadJsonInjectable = getInjectable({ - id: "download-json", - instantiate: (di): DownloadJson => { - const fetch = di.inject(fetchInjectable); - - return async (url, opts) => { - let result: Response; - - try { - result = await fetch(url, opts as RequestInit); - } catch (error) { - return { - callWasSuccessful: false, - error: String(error), - }; - } - - if (result.status < 200 || 300 <= result.status) { - return { - callWasSuccessful: false, - error: result.statusText, - }; - } - - try { - return { - callWasSuccessful: true, - response: await result.json(), - }; - } catch (error) { - return { - callWasSuccessful: false, - error: String(error), - }; - } - }; - }, -}); - -export default downloadJsonInjectable; diff --git a/src/common/fetch/download-json/impl.ts b/src/common/fetch/download-json/impl.ts new file mode 100644 index 0000000000..9faf9af124 --- /dev/null +++ b/src/common/fetch/download-json/impl.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { AsyncResult } from "../../utils/async-result"; +import type { Fetch } from "../fetch.injectable"; +import type { RequestInit, Response } from "node-fetch"; + +export interface DownloadJsonOptions { + signal?: AbortSignal | null | undefined; +} + +export type DownloadJson = (url: string, opts?: DownloadJsonOptions) => Promise>; + +export const downloadJsonWith = (fetch: Fetch): DownloadJson => async (url, opts) => { + let result: Response; + + try { + result = await fetch(url, opts as RequestInit); + } catch (error) { + return { + callWasSuccessful: false, + error: String(error), + }; + } + + if (result.status < 200 || 300 <= result.status) { + return { + callWasSuccessful: false, + error: result.statusText, + }; + } + + try { + return { + callWasSuccessful: true, + response: await result.json(), + }; + } catch (error) { + return { + callWasSuccessful: false, + error: String(error), + }; + } +}; + diff --git a/src/common/fetch/download-json/normal.injectable.ts b/src/common/fetch/download-json/normal.injectable.ts new file mode 100644 index 0000000000..adb5e35d82 --- /dev/null +++ b/src/common/fetch/download-json/normal.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import fetchInjectable from "../fetch.injectable"; +import { downloadJsonWith } from "./impl"; + +const downloadJsonInjectable = getInjectable({ + id: "download-json", + instantiate: (di) => downloadJsonWith(di.inject(fetchInjectable)), +}); + +export default downloadJsonInjectable; diff --git a/src/common/fetch/download-json/proxy.injectable.ts b/src/common/fetch/download-json/proxy.injectable.ts new file mode 100644 index 0000000000..46268d4ddb --- /dev/null +++ b/src/common/fetch/download-json/proxy.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import proxyFetchInjectable from "../proxy-fetch.injectable"; +import { downloadJsonWith } from "./impl"; + +const proxyDownloadJsonInjectable = getInjectable({ + id: "proxy-download-json", + instantiate: (di) => downloadJsonWith(di.inject(proxyFetchInjectable)), +}); + +export default proxyDownloadJsonInjectable; diff --git a/src/common/fetch/fetch-module.injectable.ts b/src/common/fetch/fetch-module.injectable.ts new file mode 100644 index 0000000000..444333f196 --- /dev/null +++ b/src/common/fetch/fetch-module.injectable.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type * as FetchModule from "node-fetch"; + +const { NodeFetch } = require("../../../build/webpack/node-fetch.bundle") as { NodeFetch: typeof FetchModule }; + +/** + * NOTE: while using this module can cause side effects, this specific injectable is not marked as + * such since sometimes the request can be wholely within the perview of unit test + */ +const nodeFetchModuleInjectable = getInjectable({ + id: "node-fetch-module", + instantiate: () => NodeFetch, +}); + +export default nodeFetchModuleInjectable; diff --git a/src/common/fetch/fetch.injectable.ts b/src/common/fetch/fetch.injectable.ts index bd1ba89db7..d4f51efe0d 100644 --- a/src/common/fetch/fetch.injectable.ts +++ b/src/common/fetch/fetch.injectable.ts @@ -3,32 +3,17 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { HttpsProxyAgent } from "hpagent"; -import type * as FetchModule from "node-fetch"; -import userStoreInjectable from "../user-store/user-store.injectable"; - -const { NodeFetch: { default: fetch }} = require("../../../build/webpack/node-fetch.bundle") as { NodeFetch: typeof FetchModule }; - -type Response = FetchModule.Response; -type RequestInit = FetchModule.RequestInit; +import type { RequestInit, Response } from "node-fetch"; +import nodeFetchModuleInjectable from "./fetch-module.injectable"; export type Fetch = (url: string, init?: RequestInit) => Promise; const fetchInjectable = getInjectable({ id: "fetch", instantiate: (di): Fetch => { - const { httpsProxy, allowUntrustedCAs } = di.inject(userStoreInjectable); - const agent = httpsProxy - ? new HttpsProxyAgent({ - proxy: httpsProxy, - rejectUnauthorized: !allowUntrustedCAs, - }) - : undefined; + const { default: fetch } = di.inject(nodeFetchModuleInjectable); - return (url, init = {}) => fetch(url, { - agent, - ...init, - }); + return (url, init) => fetch(url, init); }, causesSideEffects: true, }); diff --git a/src/common/fetch/proxy-fetch.injectable.ts b/src/common/fetch/proxy-fetch.injectable.ts new file mode 100644 index 0000000000..f13842c410 --- /dev/null +++ b/src/common/fetch/proxy-fetch.injectable.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { HttpsProxyAgent } from "hpagent"; +import userStoreInjectable from "../user-store/user-store.injectable"; +import type { Fetch } from "./fetch.injectable"; +import fetchInjectable from "./fetch.injectable"; + +const proxyFetchInjectable = getInjectable({ + id: "proxy-fetch", + instantiate: (di): Fetch => { + const fetch = di.inject(fetchInjectable); + const { httpsProxy, allowUntrustedCAs } = di.inject(userStoreInjectable); + const agent = httpsProxy + ? new HttpsProxyAgent({ + proxy: httpsProxy, + rejectUnauthorized: !allowUntrustedCAs, + }) + : undefined; + + return (url, init = {}) => fetch(url, { + agent, + ...init, + }); + }, +}); + +export default proxyFetchInjectable; diff --git a/src/features/extensions/navigation-using-application-menu.test.ts b/src/features/extensions/navigation-using-application-menu.test.ts index c17653d7a3..237e0a93a1 100644 --- a/src/features/extensions/navigation-using-application-menu.test.ts +++ b/src/features/extensions/navigation-using-application-menu.test.ts @@ -6,8 +6,6 @@ import type { RenderResult } from "@testing-library/react"; import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; -import downloadBinaryInjectable, { type DownloadBinary } from "../../common/fetch/download-binary.injectable"; -import downloadJsonInjectable, { type DownloadJson } from "../../common/fetch/download-json.injectable"; import focusWindowInjectable from "../../renderer/navigation/focus-window.injectable"; // TODO: Make components free of side effects by making them deterministic @@ -17,20 +15,14 @@ describe("extensions - navigation using application menu", () => { let builder: ApplicationBuilder; let rendered: RenderResult; let focusWindowMock: jest.Mock; - let downloadJson: jest.MockedFunction; - let downloadBinary: jest.MockedFunction; beforeEach(async () => { builder = getApplicationBuilder(); builder.beforeWindowStart((windowDi) => { focusWindowMock = jest.fn(); - downloadJson = jest.fn().mockImplementation((url) => { throw new Error(`Unexpected call to downloadJson for url=${url}`); }); - downloadBinary = jest.fn().mockImplementation((url) => { throw new Error(`Unexpected call to downloadJson for url=${url}`); }); windowDi.override(focusWindowInjectable, () => focusWindowMock); - windowDi.override(downloadJsonInjectable, () => downloadJson); - windowDi.override(downloadBinaryInjectable, () => downloadBinary); }); rendered = await builder.render(); diff --git a/src/features/helm-charts/child-features/preferences/renderer/adding-of-public-helm-repository/public-helm-repositories/request-public-helm-repositories.injectable.ts b/src/features/helm-charts/child-features/preferences/renderer/adding-of-public-helm-repository/public-helm-repositories/request-public-helm-repositories.injectable.ts index 64e130aa09..325f3e305d 100644 --- a/src/features/helm-charts/child-features/preferences/renderer/adding-of-public-helm-repository/public-helm-repositories/request-public-helm-repositories.injectable.ts +++ b/src/features/helm-charts/child-features/preferences/renderer/adding-of-public-helm-repository/public-helm-repositories/request-public-helm-repositories.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { sortBy } from "lodash/fp"; -import downloadJsonInjectable from "../../../../../../../common/fetch/download-json.injectable"; +import proxyDownloadJsonInjectable from "../../../../../../../common/fetch/download-json/proxy.injectable"; import { withTimeout } from "../../../../../../../common/fetch/timeout-controller"; import type { HelmRepo } from "../../../../../../../common/helm/helm-repo"; import loggerInjectable from "../../../../../../../common/logger.injectable"; @@ -15,7 +15,7 @@ const requestPublicHelmRepositoriesInjectable = getInjectable({ id: "request-public-helm-repositories", instantiate: (di) => { - const downloadJson = di.inject(downloadJsonInjectable); + const downloadJson = di.inject(proxyDownloadJsonInjectable); const logger = di.inject(loggerInjectable); return async (): Promise => { diff --git a/src/renderer/components/+extensions/__tests__/extensions.test.tsx b/src/renderer/components/+extensions/__tests__/extensions.test.tsx index b17b6bc782..751c1a38cc 100644 --- a/src/renderer/components/+extensions/__tests__/extensions.test.tsx +++ b/src/renderer/components/+extensions/__tests__/extensions.test.tsx @@ -25,9 +25,7 @@ import extensionInstallationStateStoreInjectable from "../../../../extensions/ex import { observable, when } from "mobx"; import type { RemovePath } from "../../../../common/fs/remove.injectable"; import removePathInjectable from "../../../../common/fs/remove.injectable"; -import type { DownloadJson } from "../../../../common/fetch/download-json.injectable"; import type { DownloadBinary } from "../../../../common/fetch/download-binary.injectable"; -import downloadJsonInjectable from "../../../../common/fetch/download-json.injectable"; import downloadBinaryInjectable from "../../../../common/fetch/download-binary.injectable"; import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable"; @@ -38,7 +36,6 @@ describe("Extensions", () => { let extensionInstallationStateStore: ExtensionInstallationStateStore; let render: DiRender; let deleteFileMock: jest.MockedFunction; - let downloadJson: jest.MockedFunction; let downloadBinary: jest.MockedFunction; beforeEach(() => { @@ -56,9 +53,6 @@ describe("Extensions", () => { deleteFileMock = jest.fn(); di.override(removePathInjectable, () => deleteFileMock); - downloadJson = jest.fn().mockImplementation((url) => { throw new Error(`Unexpected call to downloadJson for url=${url}`); }); - di.override(downloadJsonInjectable, () => downloadJson); - downloadBinary = jest.fn().mockImplementation((url) => { throw new Error(`Unexpected call to downloadJson for url=${url}`); }); di.override(downloadBinaryInjectable, () => downloadBinary); diff --git a/src/renderer/components/+extensions/attempt-install-by-info.injectable.tsx b/src/renderer/components/+extensions/attempt-install-by-info.injectable.tsx index 317162cc9f..1f3d65c802 100644 --- a/src/renderer/components/+extensions/attempt-install-by-info.injectable.tsx +++ b/src/renderer/components/+extensions/attempt-install-by-info.injectable.tsx @@ -15,7 +15,7 @@ import { reduce } from "lodash"; import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable"; import { withTimeout } from "../../../common/fetch/timeout-controller"; import downloadBinaryInjectable from "../../../common/fetch/download-binary.injectable"; -import downloadJsonInjectable from "../../../common/fetch/download-json.injectable"; +import downloadJsonInjectable from "../../../common/fetch/download-json/normal.injectable"; import type { PackageJson } from "type-fest"; import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable"; import loggerInjectable from "../../../common/logger.injectable"; From 4dac9a8b2b66cbb15946e3c4d5c2c8879b925f1e Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 20 Dec 2022 08:07:29 -0800 Subject: [PATCH 12/21] Fix broken import after PR merge (#6803) Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- .../navigation/setup-logging-for-navigation.injectable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/navigation/setup-logging-for-navigation.injectable.ts b/src/renderer/navigation/setup-logging-for-navigation.injectable.ts index b769b92db1..e6a1bce903 100644 --- a/src/renderer/navigation/setup-logging-for-navigation.injectable.ts +++ b/src/renderer/navigation/setup-logging-for-navigation.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import loggerInjectable from "../../common/logger.injectable"; -import { beforeFrameStartsInjectionToken } from "../before-frame-starts/tokens"; +import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens"; import observableHistoryInjectable from "./observable-history.injectable"; const setupLoggingForNavigationInjectable = getInjectable({ @@ -27,7 +27,7 @@ const setupLoggingForNavigationInjectable = getInjectable({ }); }, }), - injectionToken: beforeFrameStartsInjectionToken, + injectionToken: beforeFrameStartsSecondInjectionToken, }); export default setupLoggingForNavigationInjectable; From a83259b70a0d01fdaa6c6be8ec4c0563383ab922 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 21 Dec 2022 06:31:31 -0800 Subject: [PATCH 13/21] Make request-api-resources flatter in implementation (#6802) * Make request-api-resources flatter in implementation Signed-off-by: Sebastian Malton * More improvements to requestApiResources - Also move files to better places Signed-off-by: Sebastian Malton * Rename iter.pipeline as iter.chain Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- src/common/cluster/cluster.ts | 2 +- .../request-api-resources.injectable.ts | 83 ------------------- src/common/utils/iter.ts | 13 +-- src/common/utils/with-concurrency-limit.ts | 13 +++ .../kubeconfig-sync/manager.ts | 2 +- .../request-api-resources.injectable.ts | 49 +++++++++++ src/main/cluster/request-api-versions.ts | 18 ++++ .../request-core-api-versions.injectable.ts | 27 ++++++ ...quest-kube-api-resources-for.injectable.ts | 34 ++++++++ ...equest-non-core-api-versions.injectable.ts | 30 +++++++ .../create-cluster.injectable.ts | 2 +- src/main/k8s-request.injectable.ts | 4 +- .../+config-secrets/add-secret-dialog.tsx | 2 +- .../command-palette/command-dialog.tsx | 2 +- src/renderer/utils/cssNames.ts | 2 +- 15 files changed, 186 insertions(+), 97 deletions(-) delete mode 100644 src/common/cluster/request-api-resources.injectable.ts create mode 100644 src/common/utils/with-concurrency-limit.ts create mode 100644 src/main/cluster/request-api-resources.injectable.ts create mode 100644 src/main/cluster/request-api-versions.ts create mode 100644 src/main/cluster/request-core-api-versions.injectable.ts create mode 100644 src/main/cluster/request-kube-api-resources-for.injectable.ts create mode 100644 src/main/cluster/request-non-core-api-versions.injectable.ts diff --git a/src/common/cluster/cluster.ts b/src/common/cluster/cluster.ts index 303ee89361..c8e7e69e90 100644 --- a/src/common/cluster/cluster.ts +++ b/src/common/cluster/cluster.ts @@ -26,7 +26,7 @@ import type { Logger } from "../logger"; import type { BroadcastMessage } from "../ipc/broadcast-message.injectable"; import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable"; import type { CanListResource, RequestNamespaceListPermissions, RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable"; -import type { RequestApiResources } from "./request-api-resources.injectable"; +import type { RequestApiResources } from "../../main/cluster/request-api-resources.injectable"; export interface ClusterDependencies { readonly directoryForKubeConfigs: string; diff --git a/src/common/cluster/request-api-resources.injectable.ts b/src/common/cluster/request-api-resources.injectable.ts deleted file mode 100644 index 3e1a621f39..0000000000 --- a/src/common/cluster/request-api-resources.injectable.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { V1APIGroupList, V1APIResourceList, V1APIVersions } from "@kubernetes/client-node"; -import { getInjectable } from "@ogre-tools/injectable"; -import k8SRequestInjectable from "../../main/k8s-request.injectable"; -import loggerInjectable from "../logger.injectable"; -import type { KubeApiResource } from "../rbac"; -import type { Cluster } from "./cluster"; -import plimit from "p-limit"; - -export type RequestApiResources = (cluster: Cluster) => Promise; - -interface KubeResourceListGroup { - group: string; - path: string; -} - -const requestApiResourcesInjectable = getInjectable({ - id: "request-api-resources", - instantiate: (di): RequestApiResources => { - const k8sRequest = di.inject(k8SRequestInjectable); - const logger = di.inject(loggerInjectable); - - return async (cluster) => { - const apiLimit = plimit(5); - const kubeApiResources: KubeApiResource[] = []; - const resourceListGroups: KubeResourceListGroup[] = []; - - try { - await Promise.all([ - (async () => { - const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions; - - for (const version of versions) { - resourceListGroups.push({ - group: version, - path: `/api/${version}`, - }); - } - })(), - (async () => { - const { groups } = await k8sRequest(cluster, "/apis") as V1APIGroupList; - - for (const { preferredVersion, name } of groups) { - const { groupVersion } = preferredVersion ?? {}; - - if (groupVersion) { - resourceListGroups.push({ - group: name, - path: `/apis/${groupVersion}`, - }); - } - } - })(), - ]); - - await Promise.all( - resourceListGroups.map(({ group, path }) => apiLimit(async () => { - const { resources } = await k8sRequest(cluster, path) as V1APIResourceList; - - for (const resource of resources) { - kubeApiResources.push({ - apiName: resource.name, - kind: resource.kind, - group, - namespaced: resource.namespaced, - }); - } - })), - ); - } catch (error) { - logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`); - } - - return kubeApiResources; - }; - }, -}); - -export default requestApiResourcesInjectable; diff --git a/src/common/utils/iter.ts b/src/common/utils/iter.ts index 9752e56dc5..5b5593d2d4 100644 --- a/src/common/utils/iter.ts +++ b/src/common/utils/iter.ts @@ -5,7 +5,7 @@ export type Falsey = false | 0 | "" | null | undefined; -interface Iterator { +interface Iterator extends Iterable { filter(fn: (val: T) => unknown): Iterator; filterMap(fn: (val: T) => Falsey | U): Iterator; find(fn: (val: T) => unknown): T | undefined; @@ -15,15 +15,16 @@ interface Iterator { join(sep?: string): string; } -export function pipeline(src: IterableIterator): Iterator { +export function chain(src: IterableIterator): Iterator { return { - filter: (fn) => pipeline(filter(src, fn)), - filterMap: (fn) => pipeline(filterMap(src, fn)), - map: (fn) => pipeline(map(src, fn)), - flatMap: (fn) => pipeline(flatMap(src, fn)), + filter: (fn) => chain(filter(src, fn)), + filterMap: (fn) => chain(filterMap(src, fn)), + map: (fn) => chain(map(src, fn)), + flatMap: (fn) => chain(flatMap(src, fn)), find: (fn) => find(src, fn), join: (sep) => join(src, sep), collect: (fn) => fn(src), + [Symbol.iterator]: () => src, }; } diff --git a/src/common/utils/with-concurrency-limit.ts b/src/common/utils/with-concurrency-limit.ts new file mode 100644 index 0000000000..284bb334e1 --- /dev/null +++ b/src/common/utils/with-concurrency-limit.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import plimit from "p-limit"; + +export type ConcurrencyLimiter = (fn: (...args: Args) => Res) => (...args: Args) => Promise; + +export function withConcurrencyLimit(limit: number): ConcurrencyLimiter { + const limiter = plimit(limit); + + return fn => (...args) => limiter(() => fn(...args)); +} diff --git a/src/main/catalog-sources/kubeconfig-sync/manager.ts b/src/main/catalog-sources/kubeconfig-sync/manager.ts index 305f25a909..cf05577ae7 100644 --- a/src/main/catalog-sources/kubeconfig-sync/manager.ts +++ b/src/main/catalog-sources/kubeconfig-sync/manager.ts @@ -34,7 +34,7 @@ export class KubeconfigSyncManager { const seenIds = new Set(); return ( - iter.pipeline(this.sources.values()) + iter.chain(this.sources.values()) .flatMap(([entities]) => entities.get()) .filter(entity => { const alreadySeen = seenIds.has(entity.getId()); diff --git a/src/main/cluster/request-api-resources.injectable.ts b/src/main/cluster/request-api-resources.injectable.ts new file mode 100644 index 0000000000..bc996add60 --- /dev/null +++ b/src/main/cluster/request-api-resources.injectable.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectable } from "@ogre-tools/injectable"; +import loggerInjectable from "../../common/logger.injectable"; +import type { KubeApiResource } from "../../common/rbac"; +import type { Cluster } from "../../common/cluster/cluster"; +import { requestApiVersionsInjectionToken } from "./request-api-versions"; +import { withConcurrencyLimit } from "../../common/utils/with-concurrency-limit"; +import requestKubeApiResourcesForInjectable from "./request-kube-api-resources-for.injectable"; + +export type RequestApiResources = (cluster: Cluster) => Promise; + +export interface KubeResourceListGroup { + group: string; + path: string; +} + +const requestApiResourcesInjectable = getInjectable({ + id: "request-api-resources", + instantiate: (di): RequestApiResources => { + const logger = di.inject(loggerInjectable); + const apiVersionRequesters = di.injectMany(requestApiVersionsInjectionToken); + const requestKubeApiResourcesFor = di.inject(requestKubeApiResourcesForInjectable); + + return async (cluster) => { + const requestKubeApiResources = withConcurrencyLimit(5)(requestKubeApiResourcesFor(cluster)); + + try { + const requests = await Promise.all(apiVersionRequesters.map(fn => fn(cluster))); + const resources = await Promise.all(( + requests + .flat() + .map(requestKubeApiResources) + )); + + return resources.flat(); + } catch (error) { + logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`); + + return []; + } + }; + }, +}); + +export default requestApiResourcesInjectable; diff --git a/src/main/cluster/request-api-versions.ts b/src/main/cluster/request-api-versions.ts new file mode 100644 index 0000000000..af7bc7f232 --- /dev/null +++ b/src/main/cluster/request-api-versions.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Cluster } from "../../common/cluster/cluster"; + +export interface KubeResourceListGroup { + group: string; + path: string; +} + +export type RequestApiVersions = (cluster: Cluster) => Promise; + +export const requestApiVersionsInjectionToken = getInjectionToken({ + id: "request-api-versions-token", +}); diff --git a/src/main/cluster/request-core-api-versions.injectable.ts b/src/main/cluster/request-core-api-versions.injectable.ts new file mode 100644 index 0000000000..e92def914e --- /dev/null +++ b/src/main/cluster/request-core-api-versions.injectable.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { V1APIVersions } from "@kubernetes/client-node"; +import { getInjectable } from "@ogre-tools/injectable"; +import k8sRequestInjectable from "../k8s-request.injectable"; +import { requestApiVersionsInjectionToken } from "./request-api-versions"; + +const requestCoreApiVersionsInjectable = getInjectable({ + id: "request-core-api-versions", + instantiate: (di) => { + const k8sRequest = di.inject(k8sRequestInjectable); + + return async (cluster) => { + const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions; + + return versions.map(version => ({ + group: version, + path: `/api/${version}`, + })); + }; + }, + injectionToken: requestApiVersionsInjectionToken, +}); + +export default requestCoreApiVersionsInjectable; diff --git a/src/main/cluster/request-kube-api-resources-for.injectable.ts b/src/main/cluster/request-kube-api-resources-for.injectable.ts new file mode 100644 index 0000000000..dfe1345db1 --- /dev/null +++ b/src/main/cluster/request-kube-api-resources-for.injectable.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { V1APIResourceList } from "@kubernetes/client-node"; +import { getInjectable } from "@ogre-tools/injectable"; +import type { Cluster } from "../../common/cluster/cluster"; +import type { KubeApiResource } from "../../common/rbac"; +import k8sRequestInjectable from "../k8s-request.injectable"; +import type { KubeResourceListGroup } from "./request-api-versions"; + +export type RequestKubeApiResources = (grouping: KubeResourceListGroup) => Promise; + +export type RequestKubeApiResourcesFor = (cluster: Cluster) => RequestKubeApiResources; + +const requestKubeApiResourcesForInjectable = getInjectable({ + id: "request-kube-api-resources-for", + instantiate: (di): RequestKubeApiResourcesFor => { + const k8sRequest = di.inject(k8sRequestInjectable); + + return (cluster) => async ({ group, path }) => { + const { resources } = await k8sRequest(cluster, path) as V1APIResourceList; + + return resources.map(resource => ({ + apiName: resource.name, + kind: resource.kind, + group, + namespaced: resource.namespaced, + })); + }; + }, +}); + +export default requestKubeApiResourcesForInjectable; diff --git a/src/main/cluster/request-non-core-api-versions.injectable.ts b/src/main/cluster/request-non-core-api-versions.injectable.ts new file mode 100644 index 0000000000..5ca9e1495b --- /dev/null +++ b/src/main/cluster/request-non-core-api-versions.injectable.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { V1APIGroupList } from "@kubernetes/client-node"; +import { getInjectable } from "@ogre-tools/injectable"; +import { chain } from "../../common/utils/iter"; +import k8sRequestInjectable from "../k8s-request.injectable"; +import { requestApiVersionsInjectionToken } from "./request-api-versions"; + +const requestNonCoreApiVersionsInjectable = getInjectable({ + id: "request-non-core-api-versions", + instantiate: (di) => { + const k8sRequest = di.inject(k8sRequestInjectable); + + return async (cluster) => { + const { groups } = await k8sRequest(cluster, "/apis") as V1APIGroupList; + + return chain(groups.values()) + .filterMap(group => group.preferredVersion?.groupVersion && ({ + group: group.name, + path: `/apis/${group.preferredVersion.groupVersion}`, + })) + .collect(v => [...v]); + }; + }, + injectionToken: requestApiVersionsInjectionToken, +}); + +export default requestNonCoreApiVersionsInjectable; diff --git a/src/main/create-cluster/create-cluster.injectable.ts b/src/main/create-cluster/create-cluster.injectable.ts index f5b302300c..760e8e2e75 100644 --- a/src/main/create-cluster/create-cluster.injectable.ts +++ b/src/main/create-cluster/create-cluster.injectable.ts @@ -12,7 +12,7 @@ import createContextHandlerInjectable from "../context-handler/create-context-ha import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token"; import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable"; import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable"; -import createListApiResourcesInjectable from "../../common/cluster/request-api-resources.injectable"; +import createListApiResourcesInjectable from "../cluster/request-api-resources.injectable"; import loggerInjectable from "../../common/logger.injectable"; import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable"; import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable"; diff --git a/src/main/k8s-request.injectable.ts b/src/main/k8s-request.injectable.ts index a556cf62f2..a1a56be7dd 100644 --- a/src/main/k8s-request.injectable.ts +++ b/src/main/k8s-request.injectable.ts @@ -11,7 +11,7 @@ import lensProxyPortInjectable from "./lens-proxy/lens-proxy-port.injectable"; export type K8sRequest = (cluster: Cluster, path: string, options?: RequestPromiseOptions) => Promise; -const k8SRequestInjectable = getInjectable({ +const k8sRequestInjectable = getInjectable({ id: "k8s-request", instantiate: (di) => { @@ -34,4 +34,4 @@ const k8SRequestInjectable = getInjectable({ }, }); -export default k8SRequestInjectable; +export default k8sRequestInjectable; diff --git a/src/renderer/components/+config-secrets/add-secret-dialog.tsx b/src/renderer/components/+config-secrets/add-secret-dialog.tsx index 2d35a79160..c839025f02 100644 --- a/src/renderer/components/+config-secrets/add-secret-dialog.tsx +++ b/src/renderer/components/+config-secrets/add-secret-dialog.tsx @@ -86,7 +86,7 @@ export class AddSecretDialog extends React.Component { }; private getDataFromFields = (fields: SecretTemplateField[] = [], processValue: (val: string) => string = (val => val)) => { - return iter.pipeline(fields.values()) + return iter.chain(fields.values()) .filterMap(({ key, value }) => ( value ? [key, processValue(value)] as const diff --git a/src/renderer/components/command-palette/command-dialog.tsx b/src/renderer/components/command-palette/command-dialog.tsx index 06ed612b93..db33bde1cd 100644 --- a/src/renderer/components/command-palette/command-dialog.tsx +++ b/src/renderer/components/command-palette/command-dialog.tsx @@ -67,7 +67,7 @@ const NonInjectedCommandDialog = observer(({ } }; - const activeCommands = iter.pipeline(commands.get().values()) + const activeCommands = iter.chain(commands.get().values()) .filter(command => { try { return command.isActive(context); diff --git a/src/renderer/utils/cssNames.ts b/src/renderer/utils/cssNames.ts index 68fc8c1749..cd1fa37a4a 100755 --- a/src/renderer/utils/cssNames.ts +++ b/src/renderer/utils/cssNames.ts @@ -27,7 +27,7 @@ export function cssNames(...classNames: IClassName[]): string { } } - return iter.pipeline(classNamesEnabled.entries()) + return iter.chain(classNamesEnabled.entries()) .filter(([, isActive]) => !!isActive) .filterMap(([className]) => className.trim()) .join(" "); From 2000d9b32e4069d8256b83fdd2570a38b1ec6ca6 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 21 Dec 2022 06:46:51 -0800 Subject: [PATCH 14/21] Massively simplify bundled extension loading (#6787) * Switch bundled extension declarations to injection token Signed-off-by: Sebastian Malton * Change how bundled extensions are loaded Signed-off-by: Sebastian Malton * Fix token file name Signed-off-by: Sebastian Malton * Fix spelling Signed-off-by: Sebastian Malton * Improve interface name Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- package.json | 3 +- .../bundled-extension-token.ts | 17 +++ .../extension-discovery.injectable.ts | 2 - .../extension-discovery.ts | 34 +---- .../extension-loader/entry-point-name.ts | 10 ++ .../extension-loader.injectable.ts | 4 + .../extension-loader/extension-loader.ts | 142 +++++++++++++----- .../entry-point-name.injectable.ts | 14 ++ .../entry-point-name.injectable.ts | 14 ++ 9 files changed, 164 insertions(+), 76 deletions(-) create mode 100644 src/extensions/extension-discovery/bundled-extension-token.ts create mode 100644 src/extensions/extension-loader/entry-point-name.ts create mode 100644 src/main/extension-loader/entry-point-name.injectable.ts create mode 100644 src/renderer/extension-loader/entry-point-name.injectable.ts diff --git a/package.json b/package.json index b0b341bd64..8eeef8787d 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,7 @@ "bundledHelmVersion": "3.7.2", "sentryDsn": "", "contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:", - "welcomeRoute": "/welcome", - "extensions": [] + "welcomeRoute": "/welcome" }, "engines": { "node": ">=16 <17" diff --git a/src/extensions/extension-discovery/bundled-extension-token.ts b/src/extensions/extension-discovery/bundled-extension-token.ts new file mode 100644 index 0000000000..1a1a40f9fa --- /dev/null +++ b/src/extensions/extension-discovery/bundled-extension-token.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { LensExtensionConstructor, LensExtensionManifest } from "../lens-extension"; + +export interface BundledExtension { + readonly manifest: LensExtensionManifest; + main: () => LensExtensionConstructor | null; + renderer: () => LensExtensionConstructor | null; +} + +export const bundledExtensionInjectionToken = getInjectionToken({ + id: "bundled-extension-path", +}); diff --git a/src/extensions/extension-discovery/extension-discovery.injectable.ts b/src/extensions/extension-discovery/extension-discovery.injectable.ts index 971850c585..378f519bb7 100644 --- a/src/extensions/extension-discovery/extension-discovery.injectable.ts +++ b/src/extensions/extension-discovery/extension-discovery.injectable.ts @@ -27,7 +27,6 @@ import getRelativePathInjectable from "../../common/path/get-relative-path.injec import joinPathsInjectable from "../../common/path/join-paths.injectable"; import removePathInjectable from "../../common/fs/remove.injectable"; import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable"; -import applicationInformationInjectable from "../../common/vars/application-information.injectable"; import lensResourcesDirInjectable from "../../common/vars/lens-resources-dir.injectable"; const extensionDiscoveryInjectable = getInjectable({ @@ -58,7 +57,6 @@ const extensionDiscoveryInjectable = getInjectable({ getRelativePath: di.inject(getRelativePathInjectable), joinPaths: di.inject(joinPathsInjectable), homeDirectoryPath: di.inject(homeDirectoryPathInjectable), - applicationInformation: di.inject(applicationInformationInjectable), }), }); diff --git a/src/extensions/extension-discovery/extension-discovery.ts b/src/extensions/extension-discovery/extension-discovery.ts index 1d14c427d0..c9646a1c6c 100644 --- a/src/extensions/extension-discovery/extension-discovery.ts +++ b/src/extensions/extension-discovery/extension-discovery.ts @@ -30,7 +30,6 @@ import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable" import type { GetRelativePath } from "../../common/path/get-relative-path.injectable"; import type { RemovePath } from "../../common/fs/remove.injectable"; import type TypedEventEmitter from "typed-emitter"; -import type { ApplicationInformation } from "../../common/vars/application-information.injectable"; interface Dependencies { readonly extensionLoader: ExtensionLoader; @@ -42,7 +41,6 @@ interface Dependencies { readonly isProduction: boolean; readonly fileSystemSeparator: string; readonly homeDirectoryPath: string; - readonly applicationInformation: ApplicationInformation; isCompatibleExtension: (manifest: LensExtensionManifest) => boolean; installExtension: (name: string) => Promise; readJsonFile: ReadJson; @@ -384,42 +382,16 @@ export class ExtensionDiscovery { } async ensureExtensions(): Promise> { - const bundledExtensions = await this.loadBundledExtensions(); - const userExtensions = await this.loadFromFolder(this.localFolderPath, bundledExtensions.map((extension) => extension.manifest.name)); - const extensions = bundledExtensions.concat(userExtensions); + const userExtensions = await this.loadFromFolder(this.localFolderPath); - return this.extensions = new Map(extensions.map(extension => [extension.id, extension])); + return this.extensions = new Map(userExtensions.map(extension => [extension.id, extension])); } - async loadBundledExtensions(): Promise { - const extensions: InstalledExtension[] = []; - const extensionNames = this.dependencies.applicationInformation.config.extensions || []; - - for (const dirName of extensionNames) { - const absPath = this.dependencies.joinPaths(__dirname, "..", "..", "node_modules", dirName); - const extension = await this.loadExtensionFromFolder(absPath, { isBundled: true }); - - if (!extension) { - throw new Error(`Couldn't load bundled extension: ${dirName}`); - } - - extensions.push(extension); - } - this.dependencies.logger.debug(`${logModule}: ${extensions.length} extensions loaded`, { extensions }); - - return extensions; - } - - async loadFromFolder(folderPath: string, bundledExtensions: string[]): Promise { + async loadFromFolder(folderPath: string): Promise { const extensions: InstalledExtension[] = []; const paths = await this.dependencies.readDirectory(folderPath); for (const fileName of paths) { - // do not allow to override bundled extensions - if (bundledExtensions.includes(fileName)) { - continue; - } - const absPath = this.dependencies.joinPaths(folderPath, fileName); try { diff --git a/src/extensions/extension-loader/entry-point-name.ts b/src/extensions/extension-loader/entry-point-name.ts new file mode 100644 index 0000000000..390da4a34a --- /dev/null +++ b/src/extensions/extension-loader/entry-point-name.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; + +export const extensionEntryPointNameInjectionToken = getInjectionToken<"main" | "renderer">({ + id: "extension-entry-point-name-token", +}); diff --git a/src/extensions/extension-loader/extension-loader.injectable.ts b/src/extensions/extension-loader/extension-loader.injectable.ts index 48edce4446..67f8434043 100644 --- a/src/extensions/extension-loader/extension-loader.injectable.ts +++ b/src/extensions/extension-loader/extension-loader.injectable.ts @@ -12,6 +12,8 @@ import extensionInjectable from "./extension/extension.injectable"; import loggerInjectable from "../../common/logger.injectable"; import joinPathsInjectable from "../../common/path/join-paths.injectable"; import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable"; +import { bundledExtensionInjectionToken } from "../extension-discovery/bundled-extension-token"; +import { extensionEntryPointNameInjectionToken } from "./entry-point-name"; const extensionLoaderInjectable = getInjectable({ id: "extension-loader", @@ -21,6 +23,8 @@ const extensionLoaderInjectable = getInjectable({ createExtensionInstance: di.inject(createExtensionInstanceInjectionToken), extensionInstances: di.inject(extensionInstancesInjectable), getExtension: (instance: LensExtension) => di.inject(extensionInjectable, instance), + bundledExtensions: di.injectMany(bundledExtensionInjectionToken), + extensionEntryPointName: di.inject(extensionEntryPointNameInjectionToken), logger: di.inject(loggerInjectable), joinPaths: di.inject(joinPathsInjectable), getDirnameOfPath: di.inject(getDirnameOfPathInjectable), diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index c00414e88d..2c186ff38f 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -21,12 +21,15 @@ import type { Extension } from "./extension/extension.injectable"; import type { Logger } from "../../common/logger"; import type { JoinPaths } from "../../common/path/join-paths.injectable"; import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable"; +import type { BundledExtension } from "../extension-discovery/bundled-extension-token"; const logModule = "[EXTENSIONS-LOADER]"; interface Dependencies { readonly extensionInstances: ObservableMap; + readonly bundledExtensions: BundledExtension[]; readonly logger: Logger; + readonly extensionEntryPointName: "main" | "renderer"; updateExtensionsState: (extensionsState: Record) => void; createExtensionInstance: CreateExtensionInstance; getExtension: (instance: LensExtension) => Extension; @@ -34,6 +37,12 @@ interface Dependencies { getDirnameOfPath: GetDirnameOfPath; } +interface ExtensionBeingActivated { + instance: LensExtension; + installedExtension: InstalledExtension; + activated: Promise; +} + export interface ExtensionLoading { isBundled: boolean; loaded: Promise; @@ -248,14 +257,84 @@ export class ExtensionLoader { }); } - protected async loadExtensions(installedExtensions: Map) { + protected async loadBundledExtensions() { + return this.dependencies.bundledExtensions + .map(extension => { + try { + const LensExtensionClass = extension[this.dependencies.extensionEntryPointName](); + + if (!LensExtensionClass) { + return null; + } + + const installedExtension: InstalledExtension = { + absolutePath: "irrelevant", + id: extension.manifest.name, + isBundled: true, + isCompatible: true, + isEnabled: true, + manifest: extension.manifest, + manifestPath: "irrelevant", + }; + const instance = this.dependencies.createExtensionInstance( + LensExtensionClass, + installedExtension, + ); + + this.dependencies.extensionInstances.set(extension.manifest.name, instance); + + return { + instance, + installedExtension, + activated: instance.activate(), + } as ExtensionBeingActivated; + } catch (err) { + this.dependencies.logger.error(`${logModule}: error loading extension`, { ext: extension, err }); + + return null; + } + }) + .filter(isDefined); + } + + protected async loadExtensions(extensions: ExtensionBeingActivated[]): Promise { + // We first need to wait until each extension's `onActivate` is resolved or rejected, + // as this might register new catalog categories. Afterwards we can safely .enable the extension. + await Promise.all( + extensions.map(extension => + // If extension activation fails, log error + extension.activated.catch((error) => { + this.dependencies.logger.error(`${logModule}: activation extension error`, { ext: extension.installedExtension, error }); + }), + ), + ); + + extensions.forEach(({ instance }) => { + const extension = this.dependencies.getExtension(instance); + + extension.register(); + }); + + return extensions.map(extension => { + const loaded = extension.instance.enable().catch((err) => { + this.dependencies.logger.error(`${logModule}: failed to enable`, { ext: extension, err }); + }); + + return { + isBundled: extension.installedExtension.isBundled, + loaded, + }; + }); + } + + protected async loadUserExtensions(installedExtensions: Map) { // Steps of the function: // 1. require and call .activate for each Extension // 2. Wait until every extension's onActivate has been resolved // 3. Call .enable for each extension // 4. Return ExtensionLoading[] - const extensions = [...installedExtensions.entries()] + return [...installedExtensions.entries()] .map(([extId, extension]) => { const alreadyInit = this.dependencies.extensionInstances.has(extId) || this.nonInstancesByName.has(extension.manifest.name); @@ -280,7 +359,7 @@ export class ExtensionLoader { instance, installedExtension: extension, activated: instance.activate(), - }; + } as ExtensionBeingActivated; } catch (err) { this.dependencies.logger.error(`${logModule}: error loading extension`, { ext: extension, err }); } @@ -290,52 +369,33 @@ export class ExtensionLoader { return null; }) - // Remove null values .filter(isDefined); - - // We first need to wait until each extension's `onActivate` is resolved or rejected, - // as this might register new catalog categories. Afterwards we can safely .enable the extension. - await Promise.all( - extensions.map(extension => - // If extension activation fails, log error - extension.activated.catch((error) => { - this.dependencies.logger.error(`${logModule}: activation extension error`, { ext: extension.installedExtension, error }); - }), - ), - ); - - extensions.forEach(({ instance }) => { - const extension = this.dependencies.getExtension(instance); - - extension.register(); - }); - - // Return ExtensionLoading[] - return extensions.map(extension => { - const loaded = extension.instance.enable().catch((err) => { - this.dependencies.logger.error(`${logModule}: failed to enable`, { ext: extension, err }); - }); - - return { - isBundled: extension.installedExtension.isBundled, - loaded, - }; - }); } - autoInitExtensions() { + async autoInitExtensions() { this.dependencies.logger.info(`${logModule}: auto initializing extensions`); - // Setup reaction to load extensions on JSON changes - reaction(() => this.toJSON(), installedExtensions => this.loadExtensions(installedExtensions)); + const bundledExtensions = await this.loadBundledExtensions(); + const userExtensions = await this.loadUserExtensions(this.toJSON()); + const loadedExtensions = await this.loadExtensions([ + ...bundledExtensions, + ...userExtensions, + ]); - // Load initial extensions - return this.loadExtensions(this.toJSON()); + // Setup reaction to load extensions on JSON changes + reaction(() => this.toJSON(), installedExtensions => { + void (async () => { + const userExtensions = await this.loadUserExtensions(installedExtensions); + + await this.loadExtensions(userExtensions); + })(); + }); + + return loadedExtensions; } protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null { - const entryPointName = ipcRenderer ? "renderer" : "main"; - const extRelativePath = extension.manifest[entryPointName]; + const extRelativePath = extension.manifest[this.dependencies.extensionEntryPointName]; if (!extRelativePath) { return null; @@ -348,7 +408,7 @@ export class ExtensionLoader { } catch (error) { const message = (error instanceof Error ? error.stack : undefined) || error; - this.dependencies.logger.error(`${logModule}: can't load ${entryPointName} for "${extension.manifest.name}": ${message}`, { extension }); + this.dependencies.logger.error(`${logModule}: can't load ${this.dependencies.extensionEntryPointName} for "${extension.manifest.name}": ${message}`, { extension }); } return null; diff --git a/src/main/extension-loader/entry-point-name.injectable.ts b/src/main/extension-loader/entry-point-name.injectable.ts new file mode 100644 index 0000000000..0d209211f2 --- /dev/null +++ b/src/main/extension-loader/entry-point-name.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { extensionEntryPointNameInjectionToken } from "../../extensions/extension-loader/entry-point-name"; + +const extensionEntryPointNameInjectable = getInjectable({ + id: "extension-entry-point-name", + instantiate: () => "main" as const, + injectionToken: extensionEntryPointNameInjectionToken, +}); + +export default extensionEntryPointNameInjectable; diff --git a/src/renderer/extension-loader/entry-point-name.injectable.ts b/src/renderer/extension-loader/entry-point-name.injectable.ts new file mode 100644 index 0000000000..55315d6d73 --- /dev/null +++ b/src/renderer/extension-loader/entry-point-name.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { extensionEntryPointNameInjectionToken } from "../../extensions/extension-loader/entry-point-name"; + +const extensionEntryPointNameInjectable = getInjectable({ + id: "extension-entry-point-name", + instantiate: () => "renderer" as const, + injectionToken: extensionEntryPointNameInjectionToken, +}); + +export default extensionEntryPointNameInjectable; From 6aee907dc5dcc1c68e802f3a9147df2727299b77 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 21 Dec 2022 09:57:53 -0800 Subject: [PATCH 15/21] Release 6.3.0 (#6804) Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8eeef8787d..9a02b8bbea 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "6.3.0-alpha.0", + "version": "6.3.0", "main": "static/build/main.js", "copyright": "© 2022 OpenLens Authors", "license": "MIT", From 6781b4657f1245545598a9a5b62a78871e047cc5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Dec 2022 15:03:48 -0500 Subject: [PATCH 16/21] Update version to next preminor (#6810) Signed-off-by: GitHub Signed-off-by: GitHub Co-authored-by: Nokel81 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9a02b8bbea..225bc82cbe 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "6.3.0", + "version": "6.4.0-alpha.0", "main": "static/build/main.js", "copyright": "© 2022 OpenLens Authors", "license": "MIT", From da267a99f4c7dc26bc1d72946c8bdd108b0b88c3 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 22 Dec 2022 15:21:44 +0300 Subject: [PATCH 17/21] Revert #6733 to avoid breaking changes for extensions (#6821) This reverts commit 4af8fbaa345ddf714ea68947e265c9d2842bc214. Signed-off-by: Alex Andreev Signed-off-by: Alex Andreev --- .../update-button/styles.module.scss | 1 + .../+namespaces/namespace-select-filter.scss | 1 + src/renderer/components/+welcome/welcome.scss | 2 +- .../add-remove-buttons.scss | 1 + src/renderer/components/app.scss | 19 +++++++++++++------ .../cluster-manager/cluster-manager.scss | 2 +- .../cluster-status.module.scss | 1 + src/renderer/components/dialog/dialog.scss | 2 +- .../components/dock/dock-tabs.module.scss | 2 +- src/renderer/components/dock/dock.scss | 2 +- src/renderer/components/drawer/drawer.scss | 2 +- .../editable-list/editable-list.scss | 1 + .../components/hotbar/hotbar-menu.scss | 1 - .../hotbar/hotbar-selector.module.scss | 2 +- .../kube-object-details.scss | 2 ++ .../components/layout/main-layout.module.scss | 2 +- .../components/layout/setting-layout.scss | 2 +- .../layout/top-bar/top-bar.module.scss | 2 +- .../markdown-viewer/markdown-viewer.scss | 2 +- src/renderer/components/menu/menu.scss | 2 +- .../notifications/notifications.scss | 2 +- .../resizing-anchor/resizing-anchor.scss | 2 +- src/renderer/components/select/select.scss | 2 +- src/renderer/components/table/table-head.scss | 2 ++ src/renderer/components/tooltip/tooltip.scss | 2 +- src/renderer/components/vars.scss | 6 ++++++ 26 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/styles.module.scss b/src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/styles.module.scss index ade34c8a76..14b607327b 100644 --- a/src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/styles.module.scss +++ b/src/features/application-update/child-features/application-update-using-top-bar/renderer/update-application-top-bar-item/update-button/styles.module.scss @@ -33,6 +33,7 @@ height: 100%; left: 0; opacity: 0.15; + z-index: -1; transition: opacity 0.1s; } } diff --git a/src/renderer/components/+namespaces/namespace-select-filter.scss b/src/renderer/components/+namespaces/namespace-select-filter.scss index de82c192a0..f227976b46 100644 --- a/src/renderer/components/+namespaces/namespace-select-filter.scss +++ b/src/renderer/components/+namespaces/namespace-select-filter.scss @@ -33,6 +33,7 @@ &::before, &::after { content: ' '; position: absolute; + z-index: 20; display: block; width: 8px; height: var(--font-size); diff --git a/src/renderer/components/+welcome/welcome.scss b/src/renderer/components/+welcome/welcome.scss index 6e1ebbbced..0c87c062df 100644 --- a/src/renderer/components/+welcome/welcome.scss +++ b/src/renderer/components/+welcome/welcome.scss @@ -7,7 +7,7 @@ text-align: center; width: 100%; height: 100%; - z-index: var(--z-index-base); + z-index: 1; h2 { color: var(--textColorAccent); diff --git a/src/renderer/components/add-remove-buttons/add-remove-buttons.scss b/src/renderer/components/add-remove-buttons/add-remove-buttons.scss index ef3951c5e3..b2b462c027 100644 --- a/src/renderer/components/add-remove-buttons/add-remove-buttons.scss +++ b/src/renderer/components/add-remove-buttons/add-remove-buttons.scss @@ -9,6 +9,7 @@ margin-right: $padding * 4; bottom: 0; right: 0; + z-index: 99; .Button { &.remove-button { diff --git a/src/renderer/components/app.scss b/src/renderer/components/app.scss index 49ec7426ea..f44d1ee165 100755 --- a/src/renderer/components/app.scss +++ b/src/renderer/components/app.scss @@ -26,12 +26,6 @@ --font-weight-normal: 400; --font-weight-bold: 500; --main-layout-header: 40px; - - --z-index-base: 0; - --z-index-above: 1; - --z-index-modals: 2; - --z-index-menus: 3; - --z-index-topmost: 9999; } *, *:before, *:after { @@ -254,6 +248,19 @@ iframe { } } +// app's common loading indicator, displaying on the route transitions +#loading { + position: absolute; + left: 50%; + top: 50%; + margin: -15px; + z-index: 1000; + + &.hidden { + display: none; + } +} + // hack-fix: remove crappy yellow background from auto-filled inputs in chrome @keyframes autofill-remove-bgc { to { diff --git a/src/renderer/components/cluster-manager/cluster-manager.scss b/src/renderer/components/cluster-manager/cluster-manager.scss index 67d17676d0..7644d1f4d3 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.scss +++ b/src/renderer/components/cluster-manager/cluster-manager.scss @@ -27,7 +27,7 @@ } .error { - z-index: var(--z-index-base); + z-index: 1; } #lens-views { diff --git a/src/renderer/components/cluster-manager/cluster-status.module.scss b/src/renderer/components/cluster-manager/cluster-status.module.scss index a009434196..194ef467db 100644 --- a/src/renderer/components/cluster-manager/cluster-status.module.scss +++ b/src/renderer/components/cluster-manager/cluster-status.module.scss @@ -5,6 +5,7 @@ min-width: 350px; margin: auto; text-align: center; + z-index: 1; background: var(--mainBackground); width: 100%; height: 100%; diff --git a/src/renderer/components/dialog/dialog.scss b/src/renderer/components/dialog/dialog.scss index ddc7590915..4efb628881 100644 --- a/src/renderer/components/dialog/dialog.scss +++ b/src/renderer/components/dialog/dialog.scss @@ -12,7 +12,7 @@ width: 100%; height: 100%; padding: $unit * 5; - z-index: var(--z-index-modals); + z-index: $zIndex-dialog; overscroll-behavior: none; // prevent swiping with touch-pad on MacOSX overflow: auto; diff --git a/src/renderer/components/dock/dock-tabs.module.scss b/src/renderer/components/dock/dock-tabs.module.scss index 48edc4d22c..4a68a00c8a 100644 --- a/src/renderer/components/dock/dock-tabs.module.scss +++ b/src/renderer/components/dock/dock-tabs.module.scss @@ -44,7 +44,7 @@ content: "\00A0"; position: sticky; min-width: 8px; - z-index: var(--z-index-base); + z-index: 1; } &::before { diff --git a/src/renderer/components/dock/dock.scss b/src/renderer/components/dock/dock.scss index 2db21de66f..365d74bad1 100644 --- a/src/renderer/components/dock/dock.scss +++ b/src/renderer/components/dock/dock.scss @@ -26,7 +26,7 @@ right: 0; left: 0; bottom: 0; - z-index: var(--z-index-base); + z-index: 100; } } diff --git a/src/renderer/components/drawer/drawer.scss b/src/renderer/components/drawer/drawer.scss index cb2944beeb..2ef81a82dc 100644 --- a/src/renderer/components/drawer/drawer.scss +++ b/src/renderer/components/drawer/drawer.scss @@ -11,7 +11,7 @@ position: absolute; background: var(--contentColor); box-shadow: 0 0 $unit * 2 var(--boxShadow); - z-index: var(--z-index-above); + z-index: $zIndex-drawer; height: 100%; &.left { diff --git a/src/renderer/components/editable-list/editable-list.scss b/src/renderer/components/editable-list/editable-list.scss index cb61178099..089c92bce0 100644 --- a/src/renderer/components/editable-list/editable-list.scss +++ b/src/renderer/components/editable-list/editable-list.scss @@ -37,6 +37,7 @@ &::before, &::after { content: ' '; position: absolute; + z-index: 20; display: block; width: 8px; height: var(--font-size); diff --git a/src/renderer/components/hotbar/hotbar-menu.scss b/src/renderer/components/hotbar/hotbar-menu.scss index 9d09e04c1a..d92e325350 100644 --- a/src/renderer/components/hotbar/hotbar-menu.scss +++ b/src/renderer/components/hotbar/hotbar-menu.scss @@ -12,7 +12,6 @@ padding-top: 1px; width: var(--hotbar-width); overflow: hidden; - isolation: isolate; &.draggingOver::after { content: " "; diff --git a/src/renderer/components/hotbar/hotbar-selector.module.scss b/src/renderer/components/hotbar/hotbar-selector.module.scss index 1318b30d1f..a8154c4579 100644 --- a/src/renderer/components/hotbar/hotbar-selector.module.scss +++ b/src/renderer/components/hotbar/hotbar-selector.module.scss @@ -40,7 +40,7 @@ } &:focus-visible { - z-index: var(--z-index-base); + z-index: 1; } } } diff --git a/src/renderer/components/kube-object-details/kube-object-details.scss b/src/renderer/components/kube-object-details/kube-object-details.scss index 58676aff2e..678652a244 100644 --- a/src/renderer/components/kube-object-details/kube-object-details.scss +++ b/src/renderer/components/kube-object-details/kube-object-details.scss @@ -4,6 +4,8 @@ */ .KubeObjectDetails { + z-index: $zIndex-drawer + 1 !important; + .drawer-title { .Menu { border: none; diff --git a/src/renderer/components/layout/main-layout.module.scss b/src/renderer/components/layout/main-layout.module.scss index d12331cb3b..e882310427 100644 --- a/src/renderer/components/layout/main-layout.module.scss +++ b/src/renderer/components/layout/main-layout.module.scss @@ -11,7 +11,7 @@ grid-template-rows: [contents] 1fr [footer] auto; grid-template-columns: [sidebar] var(--sidebar-width) [contents] 1fr; width: 100%; - z-index: var(--z-index-base); + z-index: 1; height: 100%; } diff --git a/src/renderer/components/layout/setting-layout.scss b/src/renderer/components/layout/setting-layout.scss index 611fd0aa7b..b37a68a569 100644 --- a/src/renderer/components/layout/setting-layout.scss +++ b/src/renderer/components/layout/setting-layout.scss @@ -10,7 +10,7 @@ display: grid; color: var(--settingsColor); position: fixed; - z-index: var(--z-index-modals); + z-index: 13!important; left: 0; top: 0; right: 0; diff --git a/src/renderer/components/layout/top-bar/top-bar.module.scss b/src/renderer/components/layout/top-bar/top-bar.module.scss index acb0d51b09..7b3646dec3 100644 --- a/src/renderer/components/layout/top-bar/top-bar.module.scss +++ b/src/renderer/components/layout/top-bar/top-bar.module.scss @@ -5,7 +5,7 @@ .topBar { background-color: var(--layoutBackground); - z-index: var(--z-index-above); + z-index: 2; grid-area: topbar; height: var(--main-layout-header); diff --git a/src/renderer/components/markdown-viewer/markdown-viewer.scss b/src/renderer/components/markdown-viewer/markdown-viewer.scss index e38ccda5c8..62397dba24 100644 --- a/src/renderer/components/markdown-viewer/markdown-viewer.scss +++ b/src/renderer/components/markdown-viewer/markdown-viewer.scss @@ -658,7 +658,7 @@ :checked + .radio-label { position: relative; - z-index: var(--z-index-base); + z-index: 1; border-color: #4078c0; } diff --git a/src/renderer/components/menu/menu.scss b/src/renderer/components/menu/menu.scss index baee399b7f..30e92d2a75 100644 --- a/src/renderer/components/menu/menu.scss +++ b/src/renderer/components/menu/menu.scss @@ -12,7 +12,7 @@ background: var(--bgc); list-style: none; border: 1px solid var(--borderColor); - z-index: var(--z-index-menus); + z-index: 101; box-shadow: rgb(0 0 0 / 17%) 0px 6px 9px 0px; border-radius: 4px; diff --git a/src/renderer/components/notifications/notifications.scss b/src/renderer/components/notifications/notifications.scss index 82d1246b4f..f91ebde44c 100644 --- a/src/renderer/components/notifications/notifications.scss +++ b/src/renderer/components/notifications/notifications.scss @@ -11,7 +11,7 @@ top: 0; padding: $padding * 2; max-height: 100vh; - z-index: var(--z-index-topmost); + z-index: 100000; height: min-content!important; &:empty { diff --git a/src/renderer/components/resizing-anchor/resizing-anchor.scss b/src/renderer/components/resizing-anchor/resizing-anchor.scss index 4abfd4e933..bcdb159a43 100644 --- a/src/renderer/components/resizing-anchor/resizing-anchor.scss +++ b/src/renderer/components/resizing-anchor/resizing-anchor.scss @@ -13,7 +13,7 @@ body.resizing { $dimension: 12px; position: absolute; - z-index: var(--z-index-above); + z-index: 10; &::after { content: " "; diff --git a/src/renderer/components/select/select.scss b/src/renderer/components/select/select.scss index 68532e844c..1cc50a2c00 100644 --- a/src/renderer/components/select/select.scss +++ b/src/renderer/components/select/select.scss @@ -145,7 +145,7 @@ html { //-- Themes .Select__menu { - z-index: var(--z-index-menus); // render at the top when used inside dialog + z-index: $zIndex-select-portal; // render at the top when used inside dialog } .Select, .Select__menu { diff --git a/src/renderer/components/table/table-head.scss b/src/renderer/components/table/table-head.scss index e926d5e1f0..ff9c6afec7 100644 --- a/src/renderer/components/table/table-head.scss +++ b/src/renderer/components/table/table-head.scss @@ -15,7 +15,9 @@ } &.sticky { + position: -webkit-sticky; // safari position: sticky; + z-index: 1; top: 0; } diff --git a/src/renderer/components/tooltip/tooltip.scss b/src/renderer/components/tooltip/tooltip.scss index 38af5f709e..10b7669679 100644 --- a/src/renderer/components/tooltip/tooltip.scss +++ b/src/renderer/components/tooltip/tooltip.scss @@ -24,7 +24,7 @@ text-align: center; pointer-events: none; transition: opacity 150ms 150ms ease-in-out; - z-index: var(--z-index-topmost); + z-index: 100000; box-shadow: 0 8px 16px rgba(0,0,0,0.24); left: 0; top: 0; diff --git a/src/renderer/components/vars.scss b/src/renderer/components/vars.scss index b22341d572..2a4deb6f30 100755 --- a/src/renderer/components/vars.scss +++ b/src/renderer/components/vars.scss @@ -21,6 +21,12 @@ $font-weight-thin: var(--font-weight-thin); $font-weight-normal: var(--font-weight-normal); $font-weight-bold: var(--font-weight-bold); +// Z-index correlations +$zIndex-sidebar-hover: 500; +$zIndex-select-portal: 300; +$zIndex-dialog: 200; +$zIndex-drawer: 100; + // Animation timing functions $animation-curve-fast-out-slow-in: cubic-bezier(0.4, 0, 0.2, 1); $animation-curve-linear-out-slow-in: cubic-bezier(0, 0, 0.2, 1); From ba908c860dc532b8d0f3626d5eaa27ca987b0be2 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 22 Dec 2022 16:35:04 +0300 Subject: [PATCH 18/21] Fix terminal window paddings (#6822) Signed-off-by: Alex Andreev Signed-off-by: Alex Andreev --- src/renderer/components/dock/dock.scss | 4 ---- src/renderer/components/dock/terminal/terminal-window.scss | 7 ++++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/renderer/components/dock/dock.scss b/src/renderer/components/dock/dock.scss index 365d74bad1..216923523f 100644 --- a/src/renderer/components/dock/dock.scss +++ b/src/renderer/components/dock/dock.scss @@ -71,10 +71,6 @@ transition: flex-basis 25ms ease-in; background: var(--dockInfoBackground); - &.terminal { - background: var(--terminalBackground); - } - > *:not(.Spinner) { position: absolute; left: 0; diff --git a/src/renderer/components/dock/terminal/terminal-window.scss b/src/renderer/components/dock/terminal/terminal-window.scss index b0f8e76950..6602b595ef 100644 --- a/src/renderer/components/dock/terminal/terminal-window.scss +++ b/src/renderer/components/dock/terminal/terminal-window.scss @@ -10,7 +10,8 @@ flex: 1; overflow: hidden; - left: var(--spacing) !important; - top: var(--spacing) !important; - bottom: var(--spacing) !important; + + .terminal.xterm { + padding: calc(var(--padding) * 2); + } } From 97551bb7f079af1476524d72d48ca5e8523ad721 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Fri, 23 Dec 2022 13:33:35 +0200 Subject: [PATCH 19/21] Allow to import app as a library (#6722) * unify build fs layout Signed-off-by: Jari Kolehmainen * use currentApp path for static files Signed-off-by: Jari Kolehmainen * lint fix Signed-off-by: Jari Kolehmainen * allow to import open-lens Signed-off-by: Jari Kolehmainen * allow to customize both main & renderer Signed-off-by: Jari Kolehmainen * fix compile-library script Signed-off-by: Jari Kolehmainen * remove bundled extensions Signed-off-by: Jari Kolehmainen * similar interface for both main & renderer Signed-off-by: Jari Kolehmainen * use startApp on both sides Signed-off-by: Jari Kolehmainen * fix startApp import Signed-off-by: Jari Kolehmainen * fix startApp import Signed-off-by: Jari Kolehmainen * Fix injection cycle (somehow) Signed-off-by: Sebastian Malton * use cwd in download_binaries Signed-off-by: Jari Kolehmainen * introduce applicationInformationToken Signed-off-by: Jari Kolehmainen * register applicationInformationInjectable in main & renderer Signed-off-by: Jari Kolehmainen * allow to define bundled extensions via appStart Signed-off-by: Jari Kolehmainen * compile node-fetch automatically via prepare Signed-off-by: Jari Kolehmainen * define peerDependencies Signed-off-by: Jari Kolehmainen * webpack fixes Signed-off-by: Jari Kolehmainen * rename application-information-token.injectable.ts -> application-information-token.ts Signed-off-by: Jari Kolehmainen * monaco-editor as externals Signed-off-by: Jari Kolehmainen * refactor application-information Signed-off-by: Jari Kolehmainen * introduce bundledExtensionInjectionToken Signed-off-by: Jari Kolehmainen * mark library exports as experimental Signed-off-by: Jari Kolehmainen * move extension npm package files & add release automation Signed-off-by: Jari Kolehmainen * add missing build files to package Signed-off-by: Jari Kolehmainen * fix bad merge conflict resolve Signed-off-by: Jari Kolehmainen * fix package.json name Signed-off-by: Jari Kolehmainen * allow to set mode via startApp Signed-off-by: Jari Kolehmainen * revert unnecessary changes Signed-off-by: Jari Kolehmainen * webpack: fix extensionOutDir Signed-off-by: Jari Kolehmainen * remove unnecessary peerDependencies Signed-off-by: Jari Kolehmainen * introduce nodeEnvInjectionToken Signed-off-by: Jari Kolehmainen * remove NODE_ENV from environmentVariablesInjectable Signed-off-by: Jari Kolehmainen * fix jest modulePathIgnorePatterns Signed-off-by: Jari Kolehmainen * fix duplicate injectable registration Signed-off-by: Jari Kolehmainen * fix build executableName Signed-off-by: Jari Kolehmainen Signed-off-by: Jari Kolehmainen Signed-off-by: Sebastian Malton Co-authored-by: Sebastian Malton --- .github/workflows/publish-master-npm.yml | 15 ++- .github/workflows/publish-release-npm.yml | 37 +++++- .gitignore | 4 - Makefile | 33 ++++-- build/download_binaries.ts | 2 +- build/generate-tray-icons.ts | 6 +- ...rsion.ts => set_extensions_npm_version.ts} | 4 +- package.json | 81 +++++++++++-- .../npm => packages}/extensions/.gitignore | 0 .../npm => packages}/extensions/package.json | 0 .../welcome-route-config.injectable.ts | 4 +- src/common/library.ts | 14 +++ .../utils/environment-variables.injectable.ts | 4 - .../vars/application-copyright.injectable.ts | 4 +- .../application-description.injectable.ts | 4 +- ... => application-information-injectable.ts} | 8 +- .../vars/application-information-token.ts | 17 +++ .../bundled-kubectl-version.injectable.ts | 4 +- .../content-security-policy.injectable.ts | 4 +- .../vars/extension-api-version.injectable.ts | 4 +- src/common/vars/is-production.injectable.ts | 4 +- src/common/vars/node-env-injection-token.ts | 11 ++ src/common/vars/product-name.injectable.ts | 4 +- src/common/vars/sentry-dsn-url.injectable.ts | 4 +- ...irectory.global-override-for-injectable.ts | 1 - .../store-migration-version.injectable.ts | 4 +- .../extension-loader/extension-loader.ts | 2 +- .../publish-is-configured.injectable.ts | 4 +- .../store-migrations/snap.injectable.ts | 4 +- src/main/create-app.ts | 36 ++++++ ...s-in-development-environment.injectable.ts | 4 +- src/main/extension-api.ts | 26 +++++ src/main/getDi.ts | 18 +-- src/main/getDiForUnitTesting.ts | 11 +- src/main/index.ts | 39 ++----- src/main/library.ts | 21 ++++ src/main/register-injectables.ts | 29 +++++ .../routes/files/development.injectable.ts | 6 +- .../routes/files/production.injectable.ts | 4 +- .../create-electron-window.injectable.ts | 4 +- ...ormation.global-override-for-injectable.ts | 7 +- .../migrations/currentVersion.injectable.ts | 4 +- src/renderer/bootstrap.tsx | 34 +----- src/renderer/create-app.ts | 34 ++++++ src/renderer/extension-api.ts | 33 ++++++ ...rame-layout-child-component.injectable.tsx | 81 +++++++------ src/renderer/getDi.tsx | 18 +-- src/renderer/getDiForUnitTesting.tsx | 13 ++- src/renderer/index.ts | 32 +++++ src/renderer/library.ts | 14 +++ src/renderer/register-injectables.ts | 30 +++++ ...ormation.global-override-for-injectable.ts | 24 ++++ webpack/library-bundle.ts | 109 ++++++++++++++++++ webpack/main.ts | 25 ++-- webpack/renderer.ts | 9 +- webpack/vars.ts | 2 +- yarn.lock | 21 ---- 57 files changed, 712 insertions(+), 263 deletions(-) rename build/{set_npm_version.ts => set_extensions_npm_version.ts} (75%) rename {src/extensions/npm => packages}/extensions/.gitignore (100%) rename {src/extensions/npm => packages}/extensions/package.json (100%) create mode 100644 src/common/library.ts rename src/common/vars/{application-information.injectable.ts => application-information-injectable.ts} (69%) create mode 100644 src/common/vars/application-information-token.ts create mode 100644 src/common/vars/node-env-injection-token.ts create mode 100644 src/main/create-app.ts create mode 100644 src/main/extension-api.ts create mode 100644 src/main/library.ts create mode 100644 src/main/register-injectables.ts rename src/{common => main}/vars/application-information.global-override-for-injectable.ts (76%) create mode 100644 src/renderer/create-app.ts create mode 100644 src/renderer/extension-api.ts create mode 100644 src/renderer/index.ts create mode 100644 src/renderer/library.ts create mode 100644 src/renderer/register-injectables.ts create mode 100644 src/renderer/vars/application-information.global-override-for-injectable.ts create mode 100644 webpack/library-bundle.ts diff --git a/.github/workflows/publish-master-npm.yml b/.github/workflows/publish-master-npm.yml index 620245dc70..7824dd1c84 100644 --- a/.github/workflows/publish-master-npm.yml +++ b/.github/workflows/publish-master-npm.yml @@ -1,17 +1,16 @@ name: Publish NPM Package `master` on: - push: - branches: - - master + pull_request: + types: + - closed concurrency: group: publish-master-npm cancel-in-progress: true jobs: publish: - name: Publish NPM Package `master` + name: Publish Extensions NPM Package `master` runs-on: ubuntu-latest - if: | - ${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'area/extension') }} + if: ${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'area/extension') }} strategy: matrix: node-version: [16.x] @@ -28,11 +27,11 @@ jobs: - name: Generate NPM package run: | - make build-npm + make build-extensions-npm - name: publish new release run: | - make publish-npm + make publish-extensions-npm env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_RELEASE_TAG: master diff --git a/.github/workflows/publish-release-npm.yml b/.github/workflows/publish-release-npm.yml index 8dbf9f52ba..18be26f4f3 100644 --- a/.github/workflows/publish-release-npm.yml +++ b/.github/workflows/publish-release-npm.yml @@ -12,8 +12,8 @@ on: type: string description: The version to release manually jobs: - publish: - name: Publish NPM Package Release + publish-extensions: + name: Publish Extensions NPM Package Release runs-on: ubuntu-latest strategy: matrix: @@ -32,10 +32,37 @@ jobs: - name: Generate NPM package run: | - make build-npm + make build-extensions-npm - - name: publish new release + - name: Publish NPM package run: | - make publish-npm + make publish-extensions-npm + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + publish-library: + name: Publish Library NPM Package Release + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] + steps: + - name: Checkout Release + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ inputs.version }} + + - name: Using Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - name: Generate NPM package + run: | + make build-library-npm + + - name: Publish NPM package + run: | + make publish-library-npm env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 57139f691b..8e9d6f3fe4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,10 +10,6 @@ static/build static/types binaries/client/ binaries/server/ -src/extensions/*/*.js -src/extensions/*/*.d.ts -types/extension-api.d.ts -types/extension-renderer-api.d.ts docs/extensions/api site/ build/webpack/ diff --git a/Makefile b/Makefile index 07caefd5d2..a6e79dbd12 100644 --- a/Makefile +++ b/Makefile @@ -61,24 +61,32 @@ endif src/extensions/npm/extensions/__mocks__: cp -r __mocks__ src/extensions/npm/extensions/ -src/extensions/npm/extensions/dist: src/extensions/npm/extensions/node_modules +packages/extensions/dist: packages/extensions/node_modules yarn compile:extension-types -src/extensions/npm/extensions/node_modules: src/extensions/npm/extensions/package.json - cd src/extensions/npm/extensions/ && ../../../../node_modules/.bin/npm install --no-audit --no-fund --no-save +packages/extensions/node_modules: packages/extensions/package.json + cd packages/extensions/ && ../../node_modules/.bin/npm install --no-audit --no-fund --no-save -.PHONY: build-npm -build-npm: build-extension-types src/extensions/npm/extensions/__mocks__ - yarn npm:fix-package-version +.PHONY: build-extensions-npm +build-extensions-npm: build-extension-types packages/extensions/__mocks__ + yarn npm:fix-extensions-package-version + +.PHONY: build-library-npm +build-library-npm: + yarn compile-library .PHONY: build-extension-types -build-extension-types: node_modules src/extensions/npm/extensions/dist +build-extension-types: node_modules packages/extensions/dist -.PHONY: publish-npm -publish-npm: node_modules build-npm +.PHONY: publish-extensions-npm +publish-extensions-npm: node_modules build-extensions-npm ./node_modules/.bin/npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" - cd src/extensions/npm/extensions && npm publish --access=public --tag=$(NPM_RELEASE_TAG) - git restore src/extensions/npm/extensions/package.json + cd packages/extensions && npm publish --access=public --tag=$(NPM_RELEASE_TAG) && git restore package.json + +.PHONY: publish-library-npm +publish-library-npm: node_modules build-library-npm + ./node_modules/.bin/npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" + npm publish --access=public --tag=$(NPM_RELEASE_TAG) .PHONY: build-docs build-docs: @@ -90,7 +98,8 @@ docs: build-docs .PHONY: clean-npm clean-npm: - rm -rf src/extensions/npm/extensions/{dist,__mocks__,node_modules} + rm -rf packages/extensions/{dist,__mocks__,node_modules} + rm -rf static/build/library/ .PHONY: clean clean: clean-npm diff --git a/build/download_binaries.ts b/build/download_binaries.ts index 826c713fd3..66b90dea95 100644 --- a/build/download_binaries.ts +++ b/build/download_binaries.ts @@ -208,7 +208,7 @@ async function main() { noTTYOutput: true, format: "[{bar}] {percentage}% | {downloadArch} {binaryName}", }); - const baseDir = path.join(__dirname, "..", "binaries", "client"); + const baseDir = path.join(process.cwd(), "binaries", "client"); const downloaders: BinaryDownloader[] = [ new LensK8sProxyDownloader(deps, { version: packageInfo.config.k8sProxyVersion, diff --git a/build/generate-tray-icons.ts b/build/generate-tray-icons.ts index e0471a3153..3e363ad13d 100644 --- a/build/generate-tray-icons.ts +++ b/build/generate-tray-icons.ts @@ -10,9 +10,9 @@ import sharp from "sharp"; const size = Number(process.env.OUTPUT_SIZE || "16"); const outputFolder = process.env.OUTPUT_DIR || "./static/build/tray"; -const inputFile = process.env.INPUT_SVG_PATH || "./src/renderer/components/icon/logo-lens.svg"; -const noticeFile = process.env.NOTICE_SVG_PATH || "./src/renderer/components/icon/notice.svg"; -const spinnerFile = process.env.SPINNER_SVG_PATH || "./src/renderer/components/icon/arrow-spinner.svg"; +const inputFile = process.env.INPUT_SVG_PATH || path.resolve(__dirname, "../src/renderer/components/icon/logo-lens.svg"); +const noticeFile = process.env.NOTICE_SVG_PATH || path.resolve(__dirname, "../src/renderer/components/icon/notice.svg"); +const spinnerFile = process.env.SPINNER_SVG_PATH || path.resolve(__dirname, "../src/renderer/components/icon/arrow-spinner.svg"); async function ensureOutputFoler() { await ensureDir(outputFolder); diff --git a/build/set_npm_version.ts b/build/set_extensions_npm_version.ts similarity index 75% rename from build/set_npm_version.ts rename to build/set_extensions_npm_version.ts index d2f7c9a0b6..ee04257f48 100644 --- a/build/set_npm_version.ts +++ b/build/set_extensions_npm_version.ts @@ -4,7 +4,7 @@ */ import * as fs from "fs"; import * as path from "path"; -import packageInfo from "../src/extensions/npm/extensions/package.json"; +import packageInfo from "../packages/extensions/package.json"; import appInfo from "../package.json"; import { SemVer } from "semver"; import { execSync } from "child_process"; @@ -22,4 +22,4 @@ if (NPM_RELEASE_TAG !== "latest") { packageInfo.version = version.format(); -fs.writeFileSync(path.join(__dirname, "../src/extensions/npm/extensions/package.json"), `${JSON.stringify(packageInfo, null, 2)}\n`); +fs.writeFileSync(path.join(__dirname, "../packages/extensions/package.json"), `${JSON.stringify(packageInfo, null, 2)}\n`); diff --git a/package.json b/package.json index 225bc82cbe..8e1c99811f 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,51 @@ { - "name": "open-lens", + "name": "@k8slens/open-lens", "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", "version": "6.4.0-alpha.0", + "repository": { + "type": "git", + "url": "git+https://github.com/lensapp/lens.git" + }, + "keywords": [], + "bugs": { + "url": "https://github.com/lensapp/lens/issues" + }, "main": "static/build/main.js", + "exports": { + "./main": "./static/build/library/main.js", + "./renderer": "./static/build/library/renderer.js", + "./common": "./static/build/library/common.js", + "./styles": "./static/build/library/renderer.css" + }, + "typesVersions": { + "*": { + "main": [ + "./src/main/library.ts" + ], + "renderer": [ + "./src/renderer/library.ts" + ], + "common": [ + "./src/common/library.ts" + ] + } + }, + "files": [ + "build/download_binaries.ts", + "build/*.plist", + "build/installer.nsh", + "build/notarize.js", + "src/**/*", + "static/build/library/**/*", + "templates/**/*", + "types/*", + "tsconfig.json" + ], "copyright": "© 2022 OpenLens Authors", "license": "MIT", - "author": { - "name": "OpenLens Authors", - "email": "info@k8slens.dev" - }, + "author": "OpenLens Authors ", "scripts": { "adr:create": "echo \"What is the title?\"; read title; adr new \"$title\"", "adr:change-status": "echo \"Decision number?:\"; read decision; adr status $decision", @@ -22,13 +57,14 @@ "dev-run": "nodemon --watch ./static/build/main.js --exec \"electron --remote-debugging-port=9223 --inspect .\"", "dev:main": "yarn run compile:main --watch --progress", "dev:renderer": "yarn run ts-node webpack/dev-server.ts", + "compile-library": "env NODE_ENV=production yarn run webpack --config webpack/library-bundle.ts", "compile": "env NODE_ENV=production concurrently yarn:compile:*", "compile:main": "yarn run webpack --config webpack/main.ts", "compile:renderer": "yarn run webpack --config webpack/renderer.ts", "compile:extension-types": "yarn run webpack --config webpack/extensions.ts", "compile:node-fetch": "yarn run webpack --config ./webpack/node-fetch.ts", - "postinstall": "yarn run compile:node-fetch", - "npm:fix-package-version": "yarn run ts-node build/set_npm_version.ts", + "prepare": "yarn run compile:node-fetch", + "npm:fix-extensions-package-version": "yarn run ts-node build/set_extensions_npm_version.ts", "build:linux": "yarn run compile && electron-builder --linux --dir", "build:mac": "yarn run compile && electron-builder --mac --dir", "build:win": "yarn run compile && electron-builder --win --dir", @@ -79,7 +115,7 @@ }, "modulePathIgnorePatterns": [ "/dist", - "/src/extensions/npm" + "/packages" ], "setupFiles": [ "/src/jest.setup.ts", @@ -106,6 +142,7 @@ "LICENSE" ], "linux": { + "executableName": "open-lens", "category": "Network", "artifactName": "${productName}-${version}.${arch}.${ext}", "target": [ @@ -134,6 +171,7 @@ ] }, "mac": { + "executableName": "OpenLens", "hardenedRuntime": true, "gatekeeperAssess": false, "entitlements": "build/entitlements.mac.plist", @@ -154,6 +192,7 @@ ] }, "win": { + "executableName": "OpenLens.exe", "target": [ "nsis" ], @@ -204,7 +243,6 @@ "@sentry/electron": "^3.0.8", "@sentry/integrations": "^6.19.3", "@side/jest-runtime": "^1.0.1", - "@types/circular-dependency-plugin": "5.0.5", "abort-controller": "^3.0.0", "auto-bind": "^4.0.0", "await-lock": "^2.2.2", @@ -238,8 +276,6 @@ "mock-fs": "^5.2.0", "moment": "^2.29.4", "moment-timezone": "^0.5.40", - "monaco-editor": "^0.29.1", - "monaco-editor-webpack-plugin": "^5.0.0", "node-fetch": "^3.3.0", "node-pty": "0.10.1", "npm": "^8.19.3", @@ -375,6 +411,8 @@ "memorystream": "^0.3.1", "mini-css-extract-plugin": "^2.7.2", "mock-http": "^1.1.0", + "monaco-editor": "^0.29.1", + "monaco-editor-webpack-plugin": "^5.0.0", "node-gyp": "^8.3.0", "node-loader": "^2.0.0", "nodemon": "^2.0.20", @@ -411,5 +449,26 @@ "webpack-node-externals": "^3.0.0", "xterm": "^4.19.0", "xterm-addon-fit": "^0.5.0" + }, + "peerDependencies": { + "@types/byline": "^4.2.33", + "@types/chart.js": "^2.9.36", + "@types/color": "^3.0.3", + "@types/crypto-js": "^3.1.47", + "@types/lodash": "^4.14.191", + "@types/proper-lockfile": "^4.1.2", + "@types/react-dom": "^17.0.16", + "@types/react-router-dom": "^5.3.3", + "@types/react-virtualized-auto-sizer": "^1.0.1", + "@types/react-window": "^1.8.5", + "@types/request-promise-native": "^1.0.18", + "@types/tar": "^6.1.3", + "@types/tcp-port-used": "^1.0.1", + "@types/url-parse": "^1.4.8", + "@types/uuid": "^8.3.4", + "monaco-editor": "^0.29.1", + "react-select": "^5.7.0", + "typed-emitter": "^1.4.0", + "xterm-addon-fit": "^0.5.0" } } diff --git a/src/extensions/npm/extensions/.gitignore b/packages/extensions/.gitignore similarity index 100% rename from src/extensions/npm/extensions/.gitignore rename to packages/extensions/.gitignore diff --git a/src/extensions/npm/extensions/package.json b/packages/extensions/package.json similarity index 100% rename from src/extensions/npm/extensions/package.json rename to packages/extensions/package.json diff --git a/src/common/front-end-routing/routes/welcome/welcome-route-config.injectable.ts b/src/common/front-end-routing/routes/welcome/welcome-route-config.injectable.ts index 4e16df5bb4..d46b816c50 100644 --- a/src/common/front-end-routing/routes/welcome/welcome-route-config.injectable.ts +++ b/src/common/front-end-routing/routes/welcome/welcome-route-config.injectable.ts @@ -3,12 +3,12 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationInformationInjectable from "../../../vars/application-information.injectable"; +import applicationInformationToken from "../../../vars/application-information-token"; const welcomeRouteConfigInjectable = getInjectable({ id: "welcome-route-config", - instantiate: (di) => di.inject(applicationInformationInjectable).config.welcomeRoute, + instantiate: (di) => di.inject(applicationInformationToken).config.welcomeRoute, }); export default welcomeRouteConfigInjectable; diff --git a/src/common/library.ts b/src/common/library.ts new file mode 100644 index 0000000000..960cb47ca2 --- /dev/null +++ b/src/common/library.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import applicationInformationToken from "./vars/application-information-token"; +import type { ApplicationInformation } from "./vars/application-information-token"; +import { bundledExtensionInjectionToken } from "../extensions/extension-discovery/bundled-extension-token"; + +// @experimental +export { + applicationInformationToken, + ApplicationInformation, + bundledExtensionInjectionToken, +}; diff --git a/src/common/utils/environment-variables.injectable.ts b/src/common/utils/environment-variables.injectable.ts index 897b349d56..899d012f78 100644 --- a/src/common/utils/environment-variables.injectable.ts +++ b/src/common/utils/environment-variables.injectable.ts @@ -8,15 +8,11 @@ const environmentVariablesInjectable = getInjectable({ id: "environment-variables", instantiate: () => { - // IMPORTANT: The syntax needs to be exactly this in order to make environment variable values - // hard-coded at compile-time by Webpack. - const NODE_ENV = process.env.NODE_ENV; const JEST_WORKER_ID = process.env.JEST_WORKER_ID; const CICD = process.env.CICD; return { // Compile-time environment variables - NODE_ENV, JEST_WORKER_ID, CICD, diff --git a/src/common/vars/application-copyright.injectable.ts b/src/common/vars/application-copyright.injectable.ts index cdac64855c..0233584225 100644 --- a/src/common/vars/application-copyright.injectable.ts +++ b/src/common/vars/application-copyright.injectable.ts @@ -3,11 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationInformationInjectable from "./application-information.injectable"; +import applicationInformationToken from "./application-information-token"; const applicationCopyrightInjectable = getInjectable({ id: "application-copyright", - instantiate: (di) => di.inject(applicationInformationInjectable).copyright, + instantiate: (di) => di.inject(applicationInformationToken).copyright, }); export default applicationCopyrightInjectable; diff --git a/src/common/vars/application-description.injectable.ts b/src/common/vars/application-description.injectable.ts index d6c4c9f79b..5d63a67aa5 100644 --- a/src/common/vars/application-description.injectable.ts +++ b/src/common/vars/application-description.injectable.ts @@ -3,11 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationInformationInjectable from "./application-information.injectable"; +import applicationInformationToken from "./application-information-token"; const applicationDescriptionInjectable = getInjectable({ id: "application-description", - instantiate: (di) => di.inject(applicationInformationInjectable).description, + instantiate: (di) => di.inject(applicationInformationToken).description, }); export default applicationDescriptionInjectable; diff --git a/src/common/vars/application-information.injectable.ts b/src/common/vars/application-information-injectable.ts similarity index 69% rename from src/common/vars/application-information.injectable.ts rename to src/common/vars/application-information-injectable.ts index 56dfcf9d7e..a73e311233 100644 --- a/src/common/vars/application-information.injectable.ts +++ b/src/common/vars/application-information-injectable.ts @@ -4,14 +4,12 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import packageJson from "../../../package.json"; - -export type ApplicationInformation = Pick & { - build: Partial & { publish?: unknown[] }; -}; +import applicationInformationToken from "../../common/vars/application-information-token"; const applicationInformationInjectable = getInjectable({ id: "application-information", - instantiate: (): ApplicationInformation => { + injectionToken: applicationInformationToken, + instantiate: () => { const { version, config, productName, build, copyright, description, name } = packageJson; return { version, config, productName, build, copyright, description, name }; diff --git a/src/common/vars/application-information-token.ts b/src/common/vars/application-information-token.ts new file mode 100644 index 0000000000..dcd56a3146 --- /dev/null +++ b/src/common/vars/application-information-token.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; +import type packageJson from "../../../package.json"; + +export type ApplicationInformation = Pick & { + build: Partial & { publish?: unknown[] }; +}; + +const applicationInformationToken = getInjectionToken({ + id: "application-information-token", +}); + +export default applicationInformationToken; diff --git a/src/common/vars/bundled-kubectl-version.injectable.ts b/src/common/vars/bundled-kubectl-version.injectable.ts index 9542a79834..f5817426fb 100644 --- a/src/common/vars/bundled-kubectl-version.injectable.ts +++ b/src/common/vars/bundled-kubectl-version.injectable.ts @@ -3,11 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationInformationInjectable from "./application-information.injectable"; +import applicationInformationToken from "./application-information-token"; const bundledKubectlVersionInjectable = getInjectable({ id: "bundled-kubectl-version", - instantiate: (di) => di.inject(applicationInformationInjectable).config.bundledKubectlVersion, + instantiate: (di) => di.inject(applicationInformationToken).config.bundledKubectlVersion, }); export default bundledKubectlVersionInjectable; diff --git a/src/common/vars/content-security-policy.injectable.ts b/src/common/vars/content-security-policy.injectable.ts index b6e1e0eb30..01ccee0980 100644 --- a/src/common/vars/content-security-policy.injectable.ts +++ b/src/common/vars/content-security-policy.injectable.ts @@ -3,11 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationInformationInjectable from "./application-information.injectable"; +import applicationInformationToken from "./application-information-token"; const contentSecurityPolicyInjectable = getInjectable({ id: "content-security-policy", - instantiate: (di) => di.inject(applicationInformationInjectable).config.contentSecurityPolicy, + instantiate: (di) => di.inject(applicationInformationToken).config.contentSecurityPolicy, }); export default contentSecurityPolicyInjectable; diff --git a/src/common/vars/extension-api-version.injectable.ts b/src/common/vars/extension-api-version.injectable.ts index 4f7f4d9930..9b84cf1177 100644 --- a/src/common/vars/extension-api-version.injectable.ts +++ b/src/common/vars/extension-api-version.injectable.ts @@ -4,12 +4,12 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { SemVer } from "semver"; -import applicationInformationInjectable from "./application-information.injectable"; +import applicationInformationToken from "./application-information-token"; const extensionApiVersionInjectable = getInjectable({ id: "extension-api-version", instantiate: (di) => { - const { major, minor, patch } = new SemVer(di.inject(applicationInformationInjectable).version); + const { major, minor, patch } = new SemVer(di.inject(applicationInformationToken).version); return `${major}.${minor}.${patch}`; }, diff --git a/src/common/vars/is-production.injectable.ts b/src/common/vars/is-production.injectable.ts index 085b091dfa..661cb397d1 100644 --- a/src/common/vars/is-production.injectable.ts +++ b/src/common/vars/is-production.injectable.ts @@ -3,13 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import environmentVariablesInjectable from "../utils/environment-variables.injectable"; +import nodeEnvInjectionToken from "./node-env-injection-token"; const isProductionInjectable = getInjectable({ id: "is-production", instantiate: (di) => { - const { NODE_ENV: nodeEnv } = di.inject(environmentVariablesInjectable); + const nodeEnv = di.inject(nodeEnvInjectionToken); return nodeEnv === "production"; }, diff --git a/src/common/vars/node-env-injection-token.ts b/src/common/vars/node-env-injection-token.ts new file mode 100644 index 0000000000..9de463c1cb --- /dev/null +++ b/src/common/vars/node-env-injection-token.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; + +const nodeEnvInjectionToken = getInjectionToken({ + id: "node-env-injection-token", +}); + +export default nodeEnvInjectionToken; diff --git a/src/common/vars/product-name.injectable.ts b/src/common/vars/product-name.injectable.ts index 910c6afa48..7a5ba73f2d 100644 --- a/src/common/vars/product-name.injectable.ts +++ b/src/common/vars/product-name.injectable.ts @@ -3,11 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationInformationInjectable from "./application-information.injectable"; +import applicationInformationToken from "./application-information-token"; const productNameInjectable = getInjectable({ id: "product-name", - instantiate: (di) => di.inject(applicationInformationInjectable).productName, + instantiate: (di) => di.inject(applicationInformationToken).productName, }); export default productNameInjectable; diff --git a/src/common/vars/sentry-dsn-url.injectable.ts b/src/common/vars/sentry-dsn-url.injectable.ts index e33c2fd0de..7fd138ab0a 100644 --- a/src/common/vars/sentry-dsn-url.injectable.ts +++ b/src/common/vars/sentry-dsn-url.injectable.ts @@ -3,11 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationInformationInjectable from "./application-information.injectable"; +import applicationInformationToken from "./application-information-token"; const sentryDataSourceNameInjectable = getInjectable({ id: "sentry-data-source-name", - instantiate: (di) => di.inject(applicationInformationInjectable).config.sentryDsn, + instantiate: (di) => di.inject(applicationInformationToken).config.sentryDsn, }); export default sentryDataSourceNameInjectable; diff --git a/src/common/vars/static-files-directory.global-override-for-injectable.ts b/src/common/vars/static-files-directory.global-override-for-injectable.ts index cb4275caf6..3b8ec43046 100644 --- a/src/common/vars/static-files-directory.global-override-for-injectable.ts +++ b/src/common/vars/static-files-directory.global-override-for-injectable.ts @@ -5,5 +5,4 @@ import { getGlobalOverride } from "../test-utils/get-global-override"; import staticFilesDirectoryInjectable from "./static-files-directory.injectable"; - export default getGlobalOverride(staticFilesDirectoryInjectable, () => "/some-static-directory"); diff --git a/src/common/vars/store-migration-version.injectable.ts b/src/common/vars/store-migration-version.injectable.ts index 79ab1578a3..eb2b7aa8cc 100644 --- a/src/common/vars/store-migration-version.injectable.ts +++ b/src/common/vars/store-migration-version.injectable.ts @@ -3,11 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationInformationInjectable from "./application-information.injectable"; +import applicationInformationToken from "./application-information-token"; const storeMigrationVersionInjectable = getInjectable({ id: "store-migration-version", - instantiate: (di) => di.inject(applicationInformationInjectable).version, + instantiate: (di) => di.inject(applicationInformationToken).version, }); export default storeMigrationVersionInjectable; diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index 2c186ff38f..5dce874e54 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -404,7 +404,7 @@ export class ExtensionLoader { const extAbsolutePath = this.dependencies.joinPaths(this.dependencies.getDirnameOfPath(extension.manifestPath), extRelativePath); try { - return __non_webpack_require__(extAbsolutePath).default; + return require(/* webpackIgnore: true */ extAbsolutePath).default; } catch (error) { const message = (error instanceof Error ? error.stack : undefined) || error; diff --git a/src/features/application-update/main/updating-is-enabled/publish-is-configured/publish-is-configured.injectable.ts b/src/features/application-update/main/updating-is-enabled/publish-is-configured/publish-is-configured.injectable.ts index 4e4b01ef84..6b2f31380f 100644 --- a/src/features/application-update/main/updating-is-enabled/publish-is-configured/publish-is-configured.injectable.ts +++ b/src/features/application-update/main/updating-is-enabled/publish-is-configured/publish-is-configured.injectable.ts @@ -3,11 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import applicationInformationInjectable from "../../../../../common/vars/application-information.injectable"; +import applicationInformationToken from "../../../../../common/vars/application-information-token"; const publishIsConfiguredInjectable = getInjectable({ id: "publish-is-configured", - instantiate: (di) => Boolean(di.inject(applicationInformationInjectable).build.publish?.length), + instantiate: (di) => Boolean(di.inject(applicationInformationToken).build.publish?.length), }); export default publishIsConfiguredInjectable; diff --git a/src/main/cluster/store-migrations/snap.injectable.ts b/src/main/cluster/store-migrations/snap.injectable.ts index b99de8a49f..f87ff4b6c2 100644 --- a/src/main/cluster/store-migrations/snap.injectable.ts +++ b/src/main/cluster/store-migrations/snap.injectable.ts @@ -6,7 +6,7 @@ // Fix embedded kubeconfig paths under snap config import { getInjectable } from "@ogre-tools/injectable"; -import applicationInformationInjectable from "../../../common/vars/application-information.injectable"; +import applicationInformationToken from "../../../common/vars/application-information-token"; import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token"; import loggerInjectable from "../../../common/logger.injectable"; import isSnapPackageInjectable from "../../../common/vars/is-snap-package.injectable"; @@ -16,7 +16,7 @@ import pathExistsSyncInjectable from "../../../common/fs/path-exists-sync.inject const clusterStoreSnapMigrationInjectable = getInjectable({ id: "cluster-store-snap-migration", instantiate: (di) => { - const { version } = di.inject(applicationInformationInjectable); + const { version } = di.inject(applicationInformationToken); const logger = di.inject(loggerInjectable); const isSnapPackage = di.inject(isSnapPackageInjectable); const pathExistsSync = di.inject(pathExistsSyncInjectable); diff --git a/src/main/create-app.ts b/src/main/create-app.ts new file mode 100644 index 0000000000..d0cccae5f8 --- /dev/null +++ b/src/main/create-app.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { DiContainer } from "@ogre-tools/injectable"; +import { getInjectable } from "@ogre-tools/injectable"; +import { runInAction } from "mobx"; +import nodeEnvInjectionToken from "../common/vars/node-env-injection-token"; +import { registerInjectables } from "./register-injectables"; +import startMainApplicationInjectable from "./start-main-application/start-main-application.injectable"; + +interface AppConfig { + di: DiContainer; + mode: string; +} + +export function createApp(conf: AppConfig) { + const { di, mode } = conf; + + runInAction(() => { + di.register(getInjectable({ + id: "node-env", + instantiate: () => mode, + injectionToken: nodeEnvInjectionToken, + })); + + registerInjectables(di); + }); + + const startMainApplication = di.inject(startMainApplicationInjectable); + + return { + start: () => startMainApplication(), + }; +} diff --git a/src/main/electron-app/runnables/setup-developer-tools-in-development-environment.injectable.ts b/src/main/electron-app/runnables/setup-developer-tools-in-development-environment.injectable.ts index 07807a7965..5572887b06 100644 --- a/src/main/electron-app/runnables/setup-developer-tools-in-development-environment.injectable.ts +++ b/src/main/electron-app/runnables/setup-developer-tools-in-development-environment.injectable.ts @@ -3,6 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import nodeEnvInjectionToken from "../../../common/vars/node-env-injection-token"; import loggerInjectable from "../../../common/logger.injectable"; import { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token"; @@ -11,11 +12,12 @@ const setupDeveloperToolsInDevelopmentEnvironmentInjectable = getInjectable({ instantiate: (di) => { const logger = di.inject(loggerInjectable); + const nodeEnv = di.inject(nodeEnvInjectionToken); return { id: "setup-developer-tools-in-development-environment", run: () => { - if (process.env.NODE_ENV !== "development") { + if (nodeEnv !== "development") { return; } diff --git a/src/main/extension-api.ts b/src/main/extension-api.ts new file mode 100644 index 0000000000..2fd6bfb5f1 --- /dev/null +++ b/src/main/extension-api.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import * as Mobx from "mobx"; +import { spawn } from "node-pty"; +import * as LensExtensionsCommonApi from "../extensions/common-api"; +import * as LensExtensionsMainApi from "../extensions/main-api"; + + +/** + * 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 = { + Common: LensExtensionsCommonApi, + Main: LensExtensionsMainApi, +}; + +const Pty = { + spawn, +}; + +export { Mobx, LensExtensions, Pty }; diff --git a/src/main/getDi.ts b/src/main/getDi.ts index 8b71cca39b..db7decece8 100644 --- a/src/main/getDi.ts +++ b/src/main/getDi.ts @@ -3,28 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { createContainer } from "@ogre-tools/injectable"; -import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration"; -import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx"; import { runInAction } from "mobx"; -import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import applicationInformationInjectable from "../common/vars/application-information-injectable"; export const getDi = () => { const di = createContainer("main"); - setLegacyGlobalDiForExtensionApi(di, Environments.main); - runInAction(() => { - registerMobX(di); - - autoRegister({ - di, - requireContexts: [ - require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES), - require.context("../extensions", true, CONTEXT_MATCHER_FOR_NON_FEATURES), - require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES), - require.context("../features", true, CONTEXT_MATCHER_FOR_FEATURES), - ], - }); + di.register(applicationInformationInjectable); }); return di; diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index 58bb386810..51c99b9ca3 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -5,7 +5,7 @@ import { kebabCase, noop, chunk } from "lodash/fp"; import type { DiContainer, Injectable } from "@ogre-tools/injectable"; -import { createContainer, isInjectable } from "@ogre-tools/injectable"; +import { createContainer, isInjectable, getInjectable } from "@ogre-tools/injectable"; import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import writeJsonFileInjectable from "../common/fs/write-json-file.injectable"; import readJsonFileInjectable from "../common/fs/read-json-file.injectable"; @@ -71,6 +71,8 @@ import kubectlDownloadingNormalizedArchInjectable from "./kubectl/normalized-arc import initializeClusterManagerInjectable from "./cluster/initialize-manager.injectable"; import addKubeconfigSyncAsEntitySourceInjectable from "./start-main-application/runnables/kube-config-sync/add-source.injectable"; import type { GlobalOverride } from "../common/test-utils/get-global-override"; +import applicationInformationInjectable from "../common/vars/application-information-injectable"; +import nodeEnvInjectionToken from "../common/vars/node-env-injection-token"; export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) { const { @@ -79,6 +81,12 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) const di = createContainer("main"); + di.register(getInjectable({ + id: "node-env", + instantiate: () => "test", + injectionToken: nodeEnvInjectionToken, + })); + setLegacyGlobalDiForExtensionApi(di, Environments.main); di.preventSideEffects(); @@ -93,6 +101,7 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) runInAction(() => { registerMobX(di); + di.register(applicationInformationInjectable); chunk(100)(injectables).forEach(chunkInjectables => { di.register(...chunkInjectables); diff --git a/src/main/index.ts b/src/main/index.ts index 0d30c2ac56..e7e5aad291 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,38 +5,19 @@ // Main process -import * as Mobx from "mobx"; -import { spawn } from "node-pty"; -import process from "process"; -import * as LensExtensionsCommonApi from "../extensions/common-api"; -import * as LensExtensionsMainApi from "../extensions/main-api"; import { getDi } from "./getDi"; -import startMainApplicationInjectable from "./start-main-application/start-main-application.injectable"; +import { Mobx, LensExtensions, Pty } from "./extension-api"; +import { createApp } from "./create-app"; const di = getDi(); -const startMainApplication = di.inject(startMainApplicationInjectable); +const app = createApp({ + di, + mode: process.env.NODE_ENV || "development", +}); -(async () => { - try { - await startMainApplication(); - } catch (error) { - console.error(error); - process.exit(1); - } -})(); - -/** - * 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 = { - Common: LensExtensionsCommonApi, - Main: LensExtensionsMainApi, -}; - -const Pty = { - spawn, -}; +app.start().catch((error) => { + console.error(error); + process.exit(1); +}); export { Mobx, LensExtensions, Pty }; diff --git a/src/main/library.ts b/src/main/library.ts new file mode 100644 index 0000000000..f10d36929d --- /dev/null +++ b/src/main/library.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { afterApplicationIsLoadedInjectionToken } from "./start-main-application/runnable-tokens/after-application-is-loaded-injection-token"; +import { beforeApplicationIsLoadingInjectionToken } from "./start-main-application/runnable-tokens/before-application-is-loading-injection-token"; +import { beforeElectronIsReadyInjectionToken } from "./start-main-application/runnable-tokens/before-electron-is-ready-injection-token"; +import { onLoadOfApplicationInjectionToken } from "./start-main-application/runnable-tokens/on-load-of-application-injection-token"; +import * as extensionApi from "./extension-api"; +import { createApp } from "./create-app"; + +// @experimental +export { + createApp, + extensionApi, + afterApplicationIsLoadedInjectionToken, + beforeApplicationIsLoadingInjectionToken, + beforeElectronIsReadyInjectionToken, + onLoadOfApplicationInjectionToken, +}; diff --git a/src/main/register-injectables.ts b/src/main/register-injectables.ts new file mode 100644 index 0000000000..6d0117c512 --- /dev/null +++ b/src/main/register-injectables.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { DiContainer } from "@ogre-tools/injectable"; +import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration"; +import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx"; +import { runInAction } from "mobx"; +import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; + +export function registerInjectables(di: DiContainer) { + setLegacyGlobalDiForExtensionApi(di, Environments.main); + + runInAction(() => { + registerMobX(di); + + autoRegister({ + di, + requireContexts: [ + require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../extensions", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../features", true, CONTEXT_MATCHER_FOR_FEATURES), + ], + }); + }); + + return di; +} diff --git a/src/main/routes/files/development.injectable.ts b/src/main/routes/files/development.injectable.ts index dcc6fce649..b632e9151d 100644 --- a/src/main/routes/files/development.injectable.ts +++ b/src/main/routes/files/development.injectable.ts @@ -6,19 +6,17 @@ import { getInjectable } from "@ogre-tools/injectable"; import httpProxy from "http-proxy"; import { webpackDevServerPort } from "../../../../webpack/vars"; import { publicPath } from "../../../common/vars"; -import appNameInjectable from "../../../common/vars/app-name.injectable"; import type { LensApiRequest, RouteResponse } from "../../router/route"; const devStaticFileRouteHandlerInjectable = getInjectable({ id: "dev-static-file-route-handler", - instantiate: (di) => { + instantiate: () => { const proxy = httpProxy.createProxy(); - const appName = di.inject(appNameInjectable); const proxyTarget = `http://127.0.0.1:${webpackDevServerPort}`; return async ({ raw: { req, res }}: LensApiRequest<"/{path*}">): Promise> => { if (req.url === "/" || !req.url) { - req.url = `${publicPath}/${appName}.html`; + req.url = `${publicPath}/index.html`; } else if (!req.url.startsWith("/build/")) { return { statusCode: 404 }; } diff --git a/src/main/routes/files/production.injectable.ts b/src/main/routes/files/production.injectable.ts index b6f9ca5e40..1e4c360138 100644 --- a/src/main/routes/files/production.injectable.ts +++ b/src/main/routes/files/production.injectable.ts @@ -6,7 +6,6 @@ import { getInjectable } from "@ogre-tools/injectable"; import readFileBufferInjectable from "../../../common/fs/read-file-buffer.injectable"; import joinPathsInjectable from "../../../common/path/join-paths.injectable"; import staticFilesDirectoryInjectable from "../../../common/vars/static-files-directory.injectable"; -import appNameInjectable from "../../../common/vars/app-name.injectable"; import type { LensApiRequest } from "../../router/route"; import path from "path"; import type { SupportedFileExtension } from "../../router/router-content-types"; @@ -20,7 +19,6 @@ const prodStaticFileRouteHandlerInjectable = getInjectable({ const readFileBuffer = di.inject(readFileBufferInjectable); const joinPaths = di.inject(joinPathsInjectable); const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable); - const appName = di.inject(appNameInjectable); const logger = di.inject(loggerInjectable); return async ({ params }: LensApiRequest<"/{path*}">) => { @@ -49,7 +47,7 @@ const prodStaticFileRouteHandlerInjectable = getInjectable({ return { statusCode: 404 }; } - filePath = `${publicPath}/${appName}.html`; + filePath = `${publicPath}/index.html`; } } diff --git a/src/main/start-main-application/lens-window/application-window/create-electron-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/create-electron-window.injectable.ts index bc2cbb889f..2d763c6361 100644 --- a/src/main/start-main-application/lens-window/application-window/create-electron-window.injectable.ts +++ b/src/main/start-main-application/lens-window/application-window/create-electron-window.injectable.ts @@ -12,7 +12,7 @@ import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-b import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.injectable"; import lensResourcesDirInjectable from "../../../../common/vars/lens-resources-dir.injectable"; import isLinuxInjectable from "../../../../common/vars/is-linux.injectable"; -import applicationInformationInjectable from "../../../../common/vars/application-information.injectable"; +import applicationInformationToken from "../../../../common/vars/application-information-token"; import pathExistsSyncInjectable from "../../../../common/fs/path-exists-sync.injectable"; @@ -54,7 +54,7 @@ const createElectronWindowInjectable = getInjectable({ const getAbsolutePath = di.inject(getAbsolutePathInjectable); const lensResourcesDir = di.inject(lensResourcesDirInjectable); const isLinux = di.inject(isLinuxInjectable); - const applicationInformation = di.inject(applicationInformationInjectable); + const applicationInformation = di.inject(applicationInformationToken); const pathExistsSync = di.inject(pathExistsSyncInjectable); return (configuration) => { diff --git a/src/common/vars/application-information.global-override-for-injectable.ts b/src/main/vars/application-information.global-override-for-injectable.ts similarity index 76% rename from src/common/vars/application-information.global-override-for-injectable.ts rename to src/main/vars/application-information.global-override-for-injectable.ts index 83b9559d73..acc47ce83d 100644 --- a/src/common/vars/application-information.global-override-for-injectable.ts +++ b/src/main/vars/application-information.global-override-for-injectable.ts @@ -3,14 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getGlobalOverride } from "../test-utils/get-global-override"; -import applicationInformationInjectable from "./application-information.injectable"; +import { getGlobalOverride } from "../../common/test-utils/get-global-override"; +import applicationInformationInjectable from "../../common/vars/application-information-injectable"; export default getGlobalOverride(applicationInformationInjectable, () => ({ name: "some-product-name", productName: "some-product-name", version: "6.0.0", - build: {}, + build: {} as any, config: { k8sProxyVersion: "0.2.1", bundledKubectlVersion: "1.23.3", @@ -18,7 +18,6 @@ export default getGlobalOverride(applicationInformationInjectable, () => ({ sentryDsn: "", contentSecurityPolicy: "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:", welcomeRoute: "/welcome", - extensions: [], }, copyright: "some-copyright-information", description: "some-descriptive-text", diff --git a/src/main/weblinks-store/migrations/currentVersion.injectable.ts b/src/main/weblinks-store/migrations/currentVersion.injectable.ts index 9a23a1a326..6c58de3b48 100644 --- a/src/main/weblinks-store/migrations/currentVersion.injectable.ts +++ b/src/main/weblinks-store/migrations/currentVersion.injectable.ts @@ -7,13 +7,13 @@ import { docsUrl, slackUrl } from "../../../common/vars"; import type { WeblinkData } from "../../../common/weblinks-store/weblink-store"; import { getInjectable } from "@ogre-tools/injectable"; import { weblinkStoreMigrationInjectionToken } from "../../../common/weblinks-store/migration-token"; -import applicationInformationInjectable from "../../../common/vars/application-information.injectable"; +import applicationInformationToken from "../../../common/vars/application-information-token"; import { lensDocumentationWeblinkId, lensSlackWeblinkId } from "../links"; const currentVersionWeblinkStoreMigrationInjectable = getInjectable({ id: "current-version-weblink-store-migration", instantiate: (di) => { - const { version } = di.inject(applicationInformationInjectable); + const { version } = di.inject(applicationInformationToken); return { version, // Run always after upgrade diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 34474515fe..d37f561c05 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -6,15 +6,8 @@ import "./components/app.scss"; import React from "react"; -import ReactDOM, { render, unmountComponentAtNode } from "react-dom"; -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 LensExtensionsCommonApi from "../extensions/common-api"; -import * as LensExtensionsRendererApi from "../extensions/renderer-api"; +import { render, unmountComponentAtNode } from "react-dom"; import { DefaultProps } from "./mui-base-theme"; -import { getDi } from "./getDi"; import { DiContextProvider } from "@ogre-tools/injectable-react"; import type { DiContainer } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../extensions/extension-loader/extension-loader.injectable"; @@ -80,28 +73,3 @@ export async function bootstrap(di: DiContainer) { rootElem, ); } - -const di = getDi(); - -// run -bootstrap(di); - -/** - * 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 = { - Common: LensExtensionsCommonApi, - Renderer: LensExtensionsRendererApi, -}; - -export { - React, - ReactDOM, - ReactRouter, - ReactRouterDom, - Mobx, - MobxReact, - LensExtensions, -}; diff --git a/src/renderer/create-app.ts b/src/renderer/create-app.ts new file mode 100644 index 0000000000..3a511af5ec --- /dev/null +++ b/src/renderer/create-app.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import "./components/app.scss"; + +import { bootstrap } from "./bootstrap"; +import type { DiContainer } from "@ogre-tools/injectable"; +import { getInjectable } from "@ogre-tools/injectable"; +import nodeEnvInjectionToken from "../common/vars/node-env-injection-token"; +import { runInAction } from "mobx"; +import { registerInjectables } from "./register-injectables"; + +interface AppConfig { + di: DiContainer; + mode: string; +} + +export function createApp(conf: AppConfig) { + const { di, mode } = conf; + + runInAction(() => { + di.register(getInjectable({ + id: "node-env", + instantiate: () => mode, + injectionToken: nodeEnvInjectionToken, + })); + registerInjectables(di); + }); + + return { + start: () => bootstrap(di), + }; +} diff --git a/src/renderer/extension-api.ts b/src/renderer/extension-api.ts new file mode 100644 index 0000000000..12cdcef5f9 --- /dev/null +++ b/src/renderer/extension-api.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import ReactDOM from "react-dom"; +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 LensExtensionsCommonApi from "../extensions/common-api"; +import * as LensExtensionsRendererApi from "../extensions/renderer-api"; + +/** + * 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 = { + Common: LensExtensionsCommonApi, + Renderer: LensExtensionsRendererApi, +}; + +export { + React, + ReactDOM, + ReactRouter, + ReactRouterDom, + Mobx, + MobxReact, + LensExtensions, +}; diff --git a/src/renderer/frames/cluster-frame/cluster-frame-layout-child-component.injectable.tsx b/src/renderer/frames/cluster-frame/cluster-frame-layout-child-component.injectable.tsx index 3f1f5c4a03..0cd23d67c5 100644 --- a/src/renderer/frames/cluster-frame/cluster-frame-layout-child-component.injectable.tsx +++ b/src/renderer/frames/cluster-frame/cluster-frame-layout-child-component.injectable.tsx @@ -9,51 +9,62 @@ import { MainLayout } from "../../components/layout/main-layout"; import { Sidebar } from "../../components/layout/sidebar"; import { Dock } from "../../components/dock"; import styles from "./cluster-frame.module.css"; +import type { IComputedValue } from "mobx"; import { computed } from "mobx"; import currentRouteComponentInjectable from "../../routes/current-route-component.injectable"; import { Redirect } from "react-router"; import startUrlInjectable from "./start-url.injectable"; import currentPathInjectable from "../../routes/current-path.injectable"; import { observer } from "mobx-react"; +import { withInjectables } from "@ogre-tools/injectable-react"; + +interface Dependencies { + currentRouteComponent: IComputedValue | undefined>; + startUrl: IComputedValue; + currentPath: IComputedValue; +} + +const NonInjectedClusterFrameLayout = observer((props: Dependencies) => { + const Component = props.currentRouteComponent.get(); + const starting = props.startUrl.get(); + const current = props.currentPath.get(); + + return ( + } footer={}> + {Component ? ( + + ) : // NOTE: this check is to prevent an infinite loop + starting !== current ? ( + + ) : ( +
+
+ An error has occured. No route can be found matching the + current route, which is also the starting route. +
+
+ )} +
+ ); +}); + +const ClusterFrameLayout = withInjectables(NonInjectedClusterFrameLayout, { + getProps: (di, props) => ({ + ...props, + currentRouteComponent: di.inject(currentRouteComponentInjectable), + startUrl: di.inject(startUrlInjectable), + currentPath: di.inject(currentPathInjectable), + }), +}); const clusterFrameLayoutChildComponentInjectable = getInjectable({ id: "cluster-frame-layout-child-component", - instantiate: (di) => { - const currentRouteComponent = di.inject(currentRouteComponentInjectable); - const startUrl = di.inject(startUrlInjectable); - const currentPath = di.inject(currentPathInjectable); - - return { - id: "cluster-frame-layout", - - shouldRender: computed(() => true), - - Component: observer(() => { - const Component = currentRouteComponent.get(); - const starting = startUrl.get(); - const current = currentPath.get(); - - return ( - } footer={}> - {Component ? ( - - ) : // NOTE: this check is to prevent an infinite loop - starting !== current ? ( - - ) : ( -
-
- An error has occured. No route can be found matching the - current route, which is also the starting route. -
-
- )} -
- ); - }), - }; - }, + instantiate: () => ({ + id: "cluster-frame-layout", + shouldRender: computed(() => true), + Component: ClusterFrameLayout, + }), injectionToken: clusterFrameChildComponentInjectionToken, }); diff --git a/src/renderer/getDi.tsx b/src/renderer/getDi.tsx index 28e111bc97..de61d5ab67 100644 --- a/src/renderer/getDi.tsx +++ b/src/renderer/getDi.tsx @@ -4,28 +4,14 @@ */ import { createContainer } from "@ogre-tools/injectable"; -import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration"; -import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx"; import { runInAction } from "mobx"; -import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import applicationInformationInjectable from "../common/vars/application-information-injectable"; export const getDi = () => { const di = createContainer("renderer"); - setLegacyGlobalDiForExtensionApi(di, Environments.renderer); - runInAction(() => { - registerMobX(di); - - autoRegister({ - di, - requireContexts: [ - require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES), - require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES), - require.context("../extensions", true, CONTEXT_MATCHER_FOR_NON_FEATURES), - require.context("../features", true, CONTEXT_MATCHER_FOR_FEATURES), - ], - }); + di.register(applicationInformationInjectable); }); return di; diff --git a/src/renderer/getDiForUnitTesting.tsx b/src/renderer/getDiForUnitTesting.tsx index ffc531ec77..a2d7f30e0d 100644 --- a/src/renderer/getDiForUnitTesting.tsx +++ b/src/renderer/getDiForUnitTesting.tsx @@ -5,7 +5,7 @@ import { noop, chunk } from "lodash/fp"; import type { DiContainer, Injectable } from "@ogre-tools/injectable"; -import { createContainer, isInjectable } from "@ogre-tools/injectable"; +import { createContainer, isInjectable, getInjectable } from "@ogre-tools/injectable"; import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import requestFromChannelInjectable from "./utils/channel/request-from-channel.injectable"; import loggerInjectable from "../common/logger.injectable"; @@ -41,6 +41,8 @@ import legacyOnChannelListenInjectable from "./ipc/legacy-channel-listen.injecta import storageSaveDelayInjectable from "./utils/create-storage/storage-save-delay.injectable"; import environmentVariablesInjectable from "../common/utils/environment-variables.injectable"; import type { GlobalOverride } from "../common/test-utils/get-global-override"; +import applicationInformationInjectable from "../common/vars/application-information-injectable"; +import nodeEnvInjectionToken from "../common/vars/node-env-injection-token"; export const getDiForUnitTesting = ( opts: { doGeneralOverrides?: boolean } = {}, @@ -49,6 +51,12 @@ export const getDiForUnitTesting = ( const di = createContainer("renderer"); + di.register(getInjectable({ + id: "node-env", + instantiate: () => "test", + injectionToken: nodeEnvInjectionToken, + })); + di.preventSideEffects(); setLegacyGlobalDiForExtensionApi(di, Environments.renderer); @@ -63,7 +71,8 @@ export const getDiForUnitTesting = ( runInAction(() => { registerMobX(di); - + di.register(applicationInformationInjectable); + chunk(100)(injectables).forEach((chunkInjectables) => { di.register(...chunkInjectables); }); diff --git a/src/renderer/index.ts b/src/renderer/index.ts new file mode 100644 index 0000000000..4ccc568a2e --- /dev/null +++ b/src/renderer/index.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import "./components/app.scss"; + +import { getDi } from "./getDi"; +import { + React, ReactDOM, ReactRouter, + ReactRouterDom, Mobx, MobxReact, LensExtensions, +} from "./extension-api"; +import { createApp } from "./create-app"; + +const di = getDi(); +const app = createApp({ + di, + mode: process.env.NODE_ENV || "development", +}); + +// run +app.start(); + +export { + React, + ReactDOM, + ReactRouter, + ReactRouterDom, + Mobx, + MobxReact, + LensExtensions, +}; diff --git a/src/renderer/library.ts b/src/renderer/library.ts new file mode 100644 index 0000000000..6e3b2eada1 --- /dev/null +++ b/src/renderer/library.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import "./components/app.scss"; + +import * as extensionApi from "./extension-api"; +import { createApp } from "./create-app"; + +// @experimental +export { + createApp, + extensionApi, +}; diff --git a/src/renderer/register-injectables.ts b/src/renderer/register-injectables.ts new file mode 100644 index 0000000000..d801e04625 --- /dev/null +++ b/src/renderer/register-injectables.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { DiContainer } from "@ogre-tools/injectable"; +import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration"; +import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx"; +import { runInAction } from "mobx"; +import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; + +export function registerInjectables(di: DiContainer) { + setLegacyGlobalDiForExtensionApi(di, Environments.renderer); + + runInAction(() => { + registerMobX(di); + + autoRegister({ + di, + requireContexts: [ + require.context("./", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../common", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../extensions", true, CONTEXT_MATCHER_FOR_NON_FEATURES), + require.context("../features", true, CONTEXT_MATCHER_FOR_FEATURES), + ], + }); + }); + + return di; +} diff --git a/src/renderer/vars/application-information.global-override-for-injectable.ts b/src/renderer/vars/application-information.global-override-for-injectable.ts new file mode 100644 index 0000000000..acc47ce83d --- /dev/null +++ b/src/renderer/vars/application-information.global-override-for-injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getGlobalOverride } from "../../common/test-utils/get-global-override"; +import applicationInformationInjectable from "../../common/vars/application-information-injectable"; + +export default getGlobalOverride(applicationInformationInjectable, () => ({ + name: "some-product-name", + productName: "some-product-name", + version: "6.0.0", + build: {} as any, + config: { + k8sProxyVersion: "0.2.1", + bundledKubectlVersion: "1.23.3", + bundledHelmVersion: "3.7.2", + sentryDsn: "", + contentSecurityPolicy: "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:", + welcomeRoute: "/welcome", + }, + copyright: "some-copyright-information", + description: "some-descriptive-text", +})); diff --git a/webpack/library-bundle.ts b/webpack/library-bundle.ts new file mode 100644 index 0000000000..51a37877b0 --- /dev/null +++ b/webpack/library-bundle.ts @@ -0,0 +1,109 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import MiniCssExtractPlugin from "mini-css-extract-plugin"; +import { platform } from "os"; +import path from "path"; +import type { WebpackPluginInstance } from "webpack"; +import { DefinePlugin, optimize } from "webpack"; +import main from "./main"; +import renderer from "./renderer"; +import { buildDir } from "./vars"; +import CircularDependencyPlugin from "circular-dependency-plugin"; +import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin"; + +const rendererConfig = renderer({ showVars: false }); + +const config = [ + { + ...main(), + entry: { + main: path.resolve(__dirname, "..", "src", "main", "library.ts"), + }, + output: { + library: { + type: "commonjs2", + }, + path: path.resolve(buildDir, "library"), + }, + optimization: { + minimize: false, + }, + plugins: [ + new DefinePlugin({ + CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`, + CONTEXT_MATCHER_FOR_FEATURES: `/\\/(main|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`, + }), + new CircularDependencyPlugin({ + cwd: __dirname, + exclude: /node_modules/, + failOnError: true, + }) as unknown as WebpackPluginInstance, + ], + }, + { + ...rendererConfig, + name: "lens-app-common", + entry: { + common: path.resolve(__dirname, "..", "src", "common", "library.ts"), + }, + output: { + library: { + type: "commonjs2", + }, + path: path.resolve(buildDir, "library"), + }, + optimization: { + minimize: false, + }, + plugins: [ + new ForkTsCheckerPlugin(), + new CircularDependencyPlugin({ + cwd: __dirname, + exclude: /node_modules/, + failOnError: true, + }) as unknown as WebpackPluginInstance, + ], + }, + { + ...rendererConfig, + entry: { + renderer: path.resolve(__dirname, "..", "src", "renderer", "library.ts"), + }, + output: { + library: { + type: "commonjs2", + }, + path: path.resolve(buildDir, "library"), + }, + optimization: { + minimize: false, + }, + externals: { + ...(rendererConfig.externals as any), + "monaco-editor": "commonjs monaco-editor", + }, + plugins: [ + new DefinePlugin({ + CONTEXT_MATCHER_FOR_NON_FEATURES: `/\\.injectable(\\.${platform})?\\.tsx?$/`, + CONTEXT_MATCHER_FOR_FEATURES: `/\\/(renderer|common)\\/.+\\.injectable(\\.${platform})?\\.tsx?$/`, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + runtime: false, + }), + new optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + new ForkTsCheckerPlugin(), + new CircularDependencyPlugin({ + cwd: __dirname, + exclude: /node_modules/, + failOnError: true, + }) as unknown as WebpackPluginInstance, + ], + }, +]; + +export default config; diff --git a/webpack/main.ts b/webpack/main.ts index 84efd63f22..a57e9ccd9c 100755 --- a/webpack/main.ts +++ b/webpack/main.ts @@ -15,14 +15,14 @@ import { DefinePlugin } from "webpack"; import { buildDir, isDevelopment, mainDir } from "./vars"; import { platform } from "process"; -const configs: { (): webpack.Configuration }[] = []; - -configs.push((): webpack.Configuration => { - console.info("WEBPACK:main", { - isDevelopment, - mainDir, - buildDir, - }); +const main = ({ showVars = true } = {}): webpack.Configuration => { + if (showVars) { + console.info("WEBPACK:main", { + isDevelopment, + mainDir, + buildDir, + }); + } return { name: "lens-app-main", @@ -45,6 +45,11 @@ configs.push((): webpack.Configuration => { nodeExternals(), ], module: { + parser: { + javascript: { + commonjsMagicComments: true, + }, + }, rules: [ { test: /\.node$/, @@ -67,6 +72,6 @@ configs.push((): webpack.Configuration => { }) as unknown as WebpackPluginInstance, ], }; -}); +}; -export default configs; +export default main; diff --git a/webpack/renderer.ts b/webpack/renderer.ts index 6bad203ca7..6836591add 100755 --- a/webpack/renderer.ts +++ b/webpack/renderer.ts @@ -38,7 +38,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura devtool: isDevelopment ? "cheap-module-source-map" : "source-map", cache: isDevelopment ? { type: "filesystem" } : false, entry: { - [appName]: path.resolve(rendererDir, "bootstrap.tsx"), + [appName]: path.resolve(rendererDir, "index.ts"), }, output: { libraryTarget: "global", @@ -70,6 +70,11 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura minimize: false, }, module: { + parser: { + javascript: { + commonjsMagicComments: true, + }, + }, rules: [ { test: /\.node$/, @@ -102,7 +107,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura }), new HtmlWebpackPlugin({ - filename: `${appName}.html`, + filename: "index.html", template: htmlTemplate, inject: true, hash: true, diff --git a/webpack/vars.ts b/webpack/vars.ts index fd0b4bcee9..b0245dedae 100644 --- a/webpack/vars.ts +++ b/webpack/vars.ts @@ -11,7 +11,7 @@ export const isDevelopment = process.env.NODE_ENV !== "production"; export const mainDir = path.join(process.cwd(), "src", "main"); export const buildDir = path.join(process.cwd(), "static", "build"); export const extensionEntry = path.join(process.cwd(), "src", "extensions", "extension-api.ts"); -export const extensionOutDir = path.join(process.cwd(), "src", "extensions", "npm", "extensions", "dist"); +export const extensionOutDir = path.join(process.cwd(), "packages", "extensions", "dist"); export const assetsFolderName = "assets"; export const rendererDir = path.join(process.cwd(), "src", "renderer"); export const appName = isDevelopment diff --git a/yarn.lock b/yarn.lock index 9a876289b3..27ab4129b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8415,9 +8415,6 @@ klona@^2.0.4, klona@^2.0.5: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== -"kube-object-event-status@file:./extensions/kube-object-event-status": - version "6.1.1" - kuler@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" @@ -8435,15 +8432,6 @@ lazy-val@^1.0.4, lazy-val@^1.0.5: resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== -"lens-metrics-cluster-feature@file:./extensions/metrics-cluster-feature": - version "6.1.0" - -"lens-node-menu@file:./extensions/node-menu": - version "6.1.0" - -"lens-pod-menu@file:./extensions/pod-menu": - version "6.1.0" - less@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/less/-/less-4.1.2.tgz#6099ee584999750c2624b65f80145f8674e4b4b0" @@ -9004,9 +8992,6 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -"metrics-cluster-feature@file:./extensions/metrics-cluster-feature": - version "6.1.0" - micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -9453,9 +9438,6 @@ node-loader@^2.0.0: dependencies: loader-utils "^2.0.0" -"node-menu@file:./extensions/node-menu": - version "6.1.0" - node-pty@0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.1.tgz#cd05d03a2710315ec40221232ec04186f6ac2c6d" @@ -10261,9 +10243,6 @@ plist@^3.0.1, plist@^3.0.4: base64-js "^1.5.1" xmlbuilder "^9.0.7" -"pod-menu@file:./extensions/pod-menu": - version "6.1.0" - popper.js@1.16.1-lts: version "1.16.1-lts" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" From ce280e1d074b6afb3f1033cb3365b16d9c9acc4a Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 26 Dec 2022 11:30:39 +0000 Subject: [PATCH 20/21] add restart button to daemonset menu (#6826) Signed-off-by: Dan Bryant Signed-off-by: Dan Bryant --- .../k8s-api/endpoints/daemon-set.api.ts | 21 ++++++ .../+workloads-daemonsets/daemonset-menu.tsx | 69 +++++++++++++++++++ .../daemonset-menu.injectable.ts | 25 +++++++ 3 files changed, 115 insertions(+) create mode 100644 src/renderer/components/+workloads-daemonsets/daemonset-menu.tsx create mode 100644 src/renderer/components/kube-object-menu/kube-object-menu-items/daemonset-menu.injectable.ts diff --git a/src/common/k8s-api/endpoints/daemon-set.api.ts b/src/common/k8s-api/endpoints/daemon-set.api.ts index 49719b04f9..00f9cfb2d9 100644 --- a/src/common/k8s-api/endpoints/daemon-set.api.ts +++ b/src/common/k8s-api/endpoints/daemon-set.api.ts @@ -3,6 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import moment from "moment"; + import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; @@ -87,4 +89,23 @@ export class DaemonSetApi extends KubeApi { objectConstructor: DaemonSet, }); } + + restart(params: { namespace: string; name: string }) { + return this.request.patch(this.getUrl(params), { + data: { + spec: { + template: { + metadata: { + annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() }, + }, + }, + }, + }, + }, + { + headers: { + "content-type": "application/strategic-merge-patch+json", + }, + }); + } } diff --git a/src/renderer/components/+workloads-daemonsets/daemonset-menu.tsx b/src/renderer/components/+workloads-daemonsets/daemonset-menu.tsx new file mode 100644 index 0000000000..24a9c042b3 --- /dev/null +++ b/src/renderer/components/+workloads-daemonsets/daemonset-menu.tsx @@ -0,0 +1,69 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import React from "react"; +import type { KubeObjectMenuProps } from "../kube-object-menu"; +import type { DaemonSet, DaemonSetApi } from "../../../common/k8s-api/endpoints"; +import { MenuItem } from "../menu"; +import { Icon } from "../icon"; +import { Notifications } from "../notifications"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import daemonSetApiInjectable from "../../../common/k8s-api/endpoints/daemon-set.api.injectable"; +import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable"; +import openConfirmDialogInjectable from "../confirm-dialog/open.injectable"; + +export interface DaemonSetMenuProps extends KubeObjectMenuProps {} + +interface Dependencies { + daemonsetApi: DaemonSetApi; + openConfirmDialog: OpenConfirmDialog; +} + +const NonInjectedDaemonSetMenu = ({ + daemonsetApi, + object, + toolbar, + openConfirmDialog, +}: Dependencies & DaemonSetMenuProps) => ( + <> + openConfirmDialog({ + ok: async () => + { + try { + await daemonsetApi.restart({ + namespace: object.getNs(), + name: object.getName(), + }); + } catch (err) { + Notifications.checkedError(err, "Unknown error occured while restarting daemonset"); + } + }, + labelOk: "Restart", + message: ( +

+ {"Are you sure you want to restart daemonset "} + {object.getName()} + ? +

+ ), + })} + > + + Restart +
+ +); + +export const DaemonSetMenu = withInjectables(NonInjectedDaemonSetMenu, { + getProps: (di, props) => ({ + ...props, + daemonsetApi: di.inject(daemonSetApiInjectable), + openConfirmDialog: di.inject(openConfirmDialogInjectable), + }), +}); diff --git a/src/renderer/components/kube-object-menu/kube-object-menu-items/daemonset-menu.injectable.ts b/src/renderer/components/kube-object-menu/kube-object-menu-items/daemonset-menu.injectable.ts new file mode 100644 index 0000000000..a2607bd783 --- /dev/null +++ b/src/renderer/components/kube-object-menu/kube-object-menu-items/daemonset-menu.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { KubeObjectMenuItemComponent } from "../kube-object-menu-item-injection-token"; +import { kubeObjectMenuItemInjectionToken } from "../kube-object-menu-item-injection-token"; +import { computed } from "mobx"; +import { DaemonSetMenu } from "../../+workloads-daemonsets/daemonset-menu"; + +const daemonsetMenuInjectable = getInjectable({ + id: "daemonset-menu-kube-object-menu", + + instantiate: () => ({ + kind: "DaemonSet", + apiVersions: ["apps/v1"], + Component: DaemonSetMenu as KubeObjectMenuItemComponent, + enabled: computed(() => true), + orderNumber: 30, + }), + + injectionToken: kubeObjectMenuItemInjectionToken, +}); + +export default daemonsetMenuInjectable; From 44bc70360692edd42c9965abdc3d70263a66e248 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 27 Dec 2022 10:40:44 +0300 Subject: [PATCH 21/21] Remove error overlay in dev mode (#6831) Signed-off-by: Alex Andreev Signed-off-by: Alex Andreev --- webpack/renderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack/renderer.ts b/webpack/renderer.ts index 6836591add..ec66e22b70 100755 --- a/webpack/renderer.ts +++ b/webpack/renderer.ts @@ -128,7 +128,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura ...( isDevelopment - ? [new ReactRefreshWebpackPlugin()] + ? [new ReactRefreshWebpackPlugin({ overlay: false })] : [] ), ],