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/release.yml b/.github/workflows/release.yml index ab7ee4c273..2c5d84bf0e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,6 @@ jobs: publish-npm: uses: ./.github/workflows/publish-release-npm.yml needs: release - if: ${{ jobs.release.outputs.version != '' }} + if: ${{ needs.release.outputs.version != '' }} with: - version: ${{ jobs.release.outputs.version }} + version: ${{ needs.release.outputs.version }} 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/.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 0308d23b9a..a6870a4816 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,28 +58,8 @@ 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): packages/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 $?);) - -packages/extensions/__mocks__: - cp -r __mocks__ packages/extensions/ +src/extensions/npm/extensions/__mocks__: + cp -r __mocks__ src/extensions/npm/extensions/ packages/extensions/dist: packages/extensions/node_modules yarn compile:extension-types @@ -121,17 +96,13 @@ 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 packages/extensions/{dist,__mocks__,node_modules} rm -rf static/build/library/ .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 ac5a76103c..c9b037d6a8 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,12 @@ "bundledHelmVersion": "3.7.2", "sentryDsn": "", "contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:", +<<<<<<< HEAD "welcomeRoute": "/welcome" +======= + "welcomeRoute": "/welcome", + "extensions": [] +>>>>>>> master }, "engines": { "node": ">=16 <17" @@ -233,7 +238,7 @@ "@astronautlabs/jsonpath": "^1.1.0", "@hapi/call": "^9.0.0", "@hapi/subtext": "^7.0.4", - "@kubernetes/client-node": "^0.17.1", + "@kubernetes/client-node": "^0.18.0", "@material-ui/styles": "^4.11.5", "@ogre-tools/fp": "^12.0.1", "@ogre-tools/injectable": "^12.0.1", @@ -256,7 +261,7 @@ "filehound": "^1.17.6", "fs-extra": "^9.0.1", "glob-to-regexp": "^0.4.1", - "got": "^11.8.5", + "got": "^11.8.6", "grapheme-splitter": "^1.0.4", "handlebars": "^4.7.7", "history": "^4.10.1", @@ -275,7 +280,7 @@ "mobx-utils": "^6.0.4", "mock-fs": "^5.2.0", "moment": "^2.29.4", - "moment-timezone": "^0.5.39", + "moment-timezone": "^0.5.40", "node-fetch": "^3.3.0", "node-pty": "0.10.1", "npm": "^8.19.3", @@ -290,7 +295,7 @@ "readable-stream": "^3.6.0", "request": "^2.88.2", "request-promise-native": "^1.0.9", - "rfc6902": "^5.0.0", + "rfc6902": "^5.0.1", "selfsigned": "^2.1.1", "semver": "^7.3.8", "tar": "^6.1.13", @@ -344,7 +349,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", @@ -388,9 +393,9 @@ "electron": "^19.1.9", "electron-builder": "^23.6.0", "electron-notarize": "^0.3.0", - "esbuild": "^0.16.7", + "esbuild": "^0.16.10", "esbuild-loader": "^2.20.0", - "eslint": "^8.29.0", + "eslint": "^8.30.0", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.26.0", @@ -416,7 +421,7 @@ "node-gyp": "^8.3.0", "node-loader": "^2.0.0", "nodemon": "^2.0.20", - "playwright": "^1.28.1", + "playwright": "^1.29.0", "postcss": "^8.4.20", "postcss-loader": "^6.2.1", "query-string": "^7.1.3", @@ -429,7 +434,7 @@ "react-select-event": "^5.5.1", "react-table": "^7.8.0", "react-window": "^1.8.8", - "sass": "^1.56.1", + "sass": "^1.57.1", "sass-loader": "^12.6.0", "sharp": "^0.31.2", "style-loader": "^3.3.1", @@ -439,7 +444,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/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/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/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-base-configs.ts b/src/common/k8s-api/api-base-configs.ts new file mode 100644 index 0000000000..5ac67229ec --- /dev/null +++ b/src/common/k8s-api/api-base-configs.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 { getInjectionToken } from "@ogre-tools/injectable"; + +export const apiBaseServerAddressInjectionToken = getInjectionToken({ + id: "api-base-config-server-address-token", +}); + +export const apiBaseHostHeaderInjectionToken = getInjectionToken({ + id: "api-base-host-header-token", +}); diff --git a/src/common/k8s-api/api-base.injectable.ts b/src/common/k8s-api/api-base.injectable.ts new file mode 100644 index 0000000000..b340882672 --- /dev/null +++ b/src/common/k8s-api/api-base.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 { apiPrefix } from "../vars"; +import isDebuggingInjectable from "../vars/is-debugging.injectable"; +import isDevelopmentInjectable from "../vars/is-development.injectable"; +import { apiBaseHostHeaderInjectionToken, apiBaseServerAddressInjectionToken } from "./api-base-configs"; +import createJsonApiInjectable from "./create-json-api.injectable"; + +const apiBaseInjectable = getInjectable({ + id: "api-base", + instantiate: (di) => { + const createJsonApi = di.inject(createJsonApiInjectable); + const isDebugging = di.inject(isDebuggingInjectable); + const isDevelopment = di.inject(isDevelopmentInjectable); + const serverAddress = di.inject(apiBaseServerAddressInjectionToken); + const hostHeaderValue = di.inject(apiBaseHostHeaderInjectionToken); + + return createJsonApi({ + serverAddress, + apiBase: apiPrefix, + debug: isDevelopment || isDebugging, + }, { + headers: { + "Host": hostHeaderValue, + }, + }); + }, +}); + +export default apiBaseInjectable; diff --git a/src/common/k8s-api/api-base.ts b/src/common/k8s-api/api-base.ts deleted file mode 100644 index 9544e4f60c..0000000000 --- a/src/common/k8s-api/api-base.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { JsonApi } from "./json-api"; -import { getInjectionToken } from "@ogre-tools/injectable"; - -export const apiBaseInjectionToken = getInjectionToken({ - id: "api-base-token", -}); 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/create-kube-api-for-cluster.injectable.ts b/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts index e7509dcbc7..eec3752e3a 100644 --- a/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts +++ b/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { apiKubePrefix } from "../vars"; import isDevelopmentInjectable from "../vars/is-development.injectable"; -import { apiBaseInjectionToken } from "./api-base"; +import apiBaseInjectable from "./api-base.injectable"; import createKubeJsonApiInjectable from "./create-kube-json-api.injectable"; import type { KubeApiOptions } from "./kube-api"; import { KubeApi } from "./kube-api"; @@ -33,7 +33,7 @@ export interface CreateKubeApiForCluster { const createKubeApiForClusterInjectable = getInjectable({ id: "create-kube-api-for-cluster", instantiate: (di): CreateKubeApiForCluster => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); const isDevelopment = di.inject(isDevelopmentInjectable); const createKubeJsonApi = di.inject(createKubeJsonApiInjectable); diff --git a/src/common/k8s-api/create-kube-json-api-for-cluster.injectable.ts b/src/common/k8s-api/create-kube-json-api-for-cluster.injectable.ts index 9901731c94..799b0bf963 100644 --- a/src/common/k8s-api/create-kube-json-api-for-cluster.injectable.ts +++ b/src/common/k8s-api/create-kube-json-api-for-cluster.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { apiKubePrefix } from "../vars"; import isDebuggingInjectable from "../vars/is-debugging.injectable"; -import { apiBaseInjectionToken } from "./api-base"; +import { apiBaseHostHeaderInjectionToken, apiBaseServerAddressInjectionToken } from "./api-base-configs"; import createKubeJsonApiInjectable from "./create-kube-json-api.injectable"; import type { KubeJsonApi } from "./kube-json-api"; @@ -14,19 +14,18 @@ export type CreateKubeJsonApiForCluster = (clusterId: string) => KubeJsonApi; const createKubeJsonApiForClusterInjectable = getInjectable({ id: "create-kube-json-api-for-cluster", instantiate: (di): CreateKubeJsonApiForCluster => { - const apiBase = di.inject(apiBaseInjectionToken); const createKubeJsonApi = di.inject(createKubeJsonApiInjectable); const isDebugging = di.inject(isDebuggingInjectable); return (clusterId) => createKubeJsonApi( { - serverAddress: apiBase.config.serverAddress, + serverAddress: di.inject(apiBaseServerAddressInjectionToken), apiBase: apiKubePrefix, debug: isDebugging, }, { headers: { - "Host": `${clusterId}.localhost:${new URL(apiBase.config.serverAddress).port}`, + "Host": `${clusterId}.${di.inject(apiBaseHostHeaderInjectionToken)}`, }, }, ); diff --git a/src/common/k8s-api/endpoints/helm-charts.api/request-charts.injectable.ts b/src/common/k8s-api/endpoints/helm-charts.api/request-charts.injectable.ts index 89de5f9d17..4d9bfc55b1 100644 --- a/src/common/k8s-api/endpoints/helm-charts.api/request-charts.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-charts.api/request-charts.injectable.ts @@ -3,10 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { apiBaseInjectionToken } from "../../api-base"; import type { RawHelmChart } from "../helm-charts.api"; import { HelmChart } from "../helm-charts.api"; import { isDefined } from "../../../utils"; +import apiBaseInjectable from "../../api-base.injectable"; export type RequestHelmCharts = () => Promise; export type RepoHelmChartList = Record; @@ -17,7 +17,7 @@ export type RepoHelmChartList = Record; const requestHelmChartsInjectable = getInjectable({ id: "request-helm-charts", instantiate: (di) => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return async () => { const data = await apiBase.get>("/v2/charts"); diff --git a/src/common/k8s-api/endpoints/helm-charts.api/request-readme.injectable.ts b/src/common/k8s-api/endpoints/helm-charts.api/request-readme.injectable.ts index c6815c4b93..fb8eaafa10 100644 --- a/src/common/k8s-api/endpoints/helm-charts.api/request-readme.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-charts.api/request-readme.injectable.ts @@ -3,17 +3,18 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import type { AsyncResult } from "../../../utils/async-result"; import { urlBuilderFor } from "../../../utils/buildUrl"; -import { apiBaseInjectionToken } from "../../api-base"; +import apiBaseInjectable from "../../api-base.injectable"; const requestReadmeEndpoint = urlBuilderFor("/v2/charts/:repo/:name/readme"); -export type RequestHelmChartReadme = (repo: string, name: string, version?: string) => Promise; +export type RequestHelmChartReadme = (repo: string, name: string, version?: string) => Promise>; const requestHelmChartReadmeInjectable = getInjectable({ id: "request-helm-chart-readme", instantiate: (di): RequestHelmChartReadme => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return (repo, name, version) => ( apiBase.get(requestReadmeEndpoint.compile({ name, repo }, { version })) diff --git a/src/common/k8s-api/endpoints/helm-charts.api/request-values.injectable.ts b/src/common/k8s-api/endpoints/helm-charts.api/request-values.injectable.ts index ec927fc37a..71105c9ff9 100644 --- a/src/common/k8s-api/endpoints/helm-charts.api/request-values.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-charts.api/request-values.injectable.ts @@ -3,17 +3,18 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import type { AsyncResult } from "../../../utils/async-result"; import { urlBuilderFor } from "../../../utils/buildUrl"; -import { apiBaseInjectionToken } from "../../api-base"; +import apiBaseInjectable from "../../api-base.injectable"; const requestValuesEndpoint = urlBuilderFor("/v2/charts/:repo/:name/values"); -export type RequestHelmChartValues = (repo: string, name: string, version: string) => Promise; +export type RequestHelmChartValues = (repo: string, name: string, version: string) => Promise>; const requestHelmChartValuesInjectable = getInjectable({ id: "request-helm-chart-values", instantiate: (di): RequestHelmChartValues => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return (repo, name, version) => ( apiBase.get(requestValuesEndpoint.compile({ repo, name }, { version })) diff --git a/src/common/k8s-api/endpoints/helm-charts.api/request-versions.injectable.ts b/src/common/k8s-api/endpoints/helm-charts.api/request-versions.injectable.ts index 410d8ea596..ab85594ec6 100644 --- a/src/common/k8s-api/endpoints/helm-charts.api/request-versions.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-charts.api/request-versions.injectable.ts @@ -4,10 +4,10 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { urlBuilderFor } from "../../../utils/buildUrl"; -import { apiBaseInjectionToken } from "../../api-base"; import { HelmChart } from "../helm-charts.api"; import type { RawHelmChart } from "../helm-charts.api"; import { isDefined } from "../../../utils"; +import apiBaseInjectable from "../../api-base.injectable"; const requestVersionsEndpoint = urlBuilderFor("/v2/charts/:repo/:name/versions"); @@ -16,7 +16,7 @@ export type RequestHelmChartVersions = (repo: string, chartName: string) => Prom const requestHelmChartVersionsInjectable = getInjectable({ id: "request-helm-chart-versions", instantiate: (di): RequestHelmChartVersions => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return async (repo, name) => { const rawVersions = await apiBase.get(requestVersionsEndpoint.compile({ name, repo })) as RawHelmChart[]; diff --git a/src/common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable.ts b/src/common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable.ts index 1bfe168b11..e1581c5d76 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { urlBuilderFor } from "../../../utils/buildUrl"; -import { apiBaseInjectionToken } from "../../api-base"; +import apiBaseInjectable from "../../api-base.injectable"; export type RequestHelmReleaseConfiguration = ( name: string, @@ -18,7 +18,7 @@ const requestHelmReleaseConfigurationInjectable = getInjectable({ id: "request-helm-release-configuration", instantiate: (di): RequestHelmReleaseConfiguration => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return (name, namespace, all: boolean) => ( apiBase.get(requestConfigurationEnpoint.compile({ name, namespace }, { all })) diff --git a/src/common/k8s-api/endpoints/helm-releases.api/request-create.injectable.ts b/src/common/k8s-api/endpoints/helm-releases.api/request-create.injectable.ts index bad802d3cc..c1cd09d40f 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api/request-create.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api/request-create.injectable.ts @@ -5,8 +5,8 @@ import yaml from "js-yaml"; import { getInjectable } from "@ogre-tools/injectable"; import type { HelmReleaseUpdateDetails } from "../helm-releases.api"; -import { apiBaseInjectionToken } from "../../api-base"; import { urlBuilderFor } from "../../../utils/buildUrl"; +import apiBaseInjectable from "../../api-base.injectable"; interface HelmReleaseCreatePayload { name?: string; @@ -25,7 +25,7 @@ const requestCreateHelmReleaseInjectable = getInjectable({ id: "request-create-helm-release", instantiate: (di): RequestCreateHelmRelease => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return ({ repo, chart, values, ...data }) => { return apiBase.post(requestCreateEndpoint.compile({}), { diff --git a/src/common/k8s-api/endpoints/helm-releases.api/request-delete.injectable.ts b/src/common/k8s-api/endpoints/helm-releases.api/request-delete.injectable.ts index 66b2013770..44af4311a9 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api/request-delete.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api/request-delete.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { urlBuilderFor } from "../../../utils/buildUrl"; -import { apiBaseInjectionToken } from "../../api-base"; +import apiBaseInjectable from "../../api-base.injectable"; export type RequestDeleteHelmRelease = (name: string, namespace: string) => Promise; @@ -13,7 +13,7 @@ const requestDeleteEndpoint = urlBuilderFor("/v2/releases/:namespace/:name"); const requestDeleteHelmReleaseInjectable = getInjectable({ id: "request-delete-helm-release", instantiate: (di): RequestDeleteHelmRelease => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return (name, namespace) => apiBase.del(requestDeleteEndpoint.compile({ name, namespace })); }, diff --git a/src/common/k8s-api/endpoints/helm-releases.api/request-details.injectable.ts b/src/common/k8s-api/endpoints/helm-releases.api/request-details.injectable.ts index 0b712cd78e..37f2287377 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api/request-details.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api/request-details.injectable.ts @@ -4,8 +4,8 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type { KubeJsonApiData } from "../../kube-json-api"; -import { apiBaseInjectionToken } from "../../api-base"; import { urlBuilderFor } from "../../../utils/buildUrl"; +import apiBaseInjectable from "../../api-base.injectable"; export interface HelmReleaseDetails { resources: KubeJsonApiData[]; @@ -32,7 +32,7 @@ const requestHelmReleaseDetailsInjectable = getInjectable({ id: "call-for-helm-release-details", instantiate: (di): CallForHelmReleaseDetails => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return (name, namespace) => apiBase.get(requestDetailsEnpoint.compile({ name, namespace })); }, diff --git a/src/common/k8s-api/endpoints/helm-releases.api/request-history.injectable.ts b/src/common/k8s-api/endpoints/helm-releases.api/request-history.injectable.ts index b6e9794fe7..58b6a37dbb 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api/request-history.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api/request-history.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { urlBuilderFor } from "../../../utils/buildUrl"; -import { apiBaseInjectionToken } from "../../api-base"; +import apiBaseInjectable from "../../api-base.injectable"; export interface HelmReleaseRevision { revision: number; @@ -22,7 +22,7 @@ const requestHistoryEnpoint = urlBuilderFor("/v2/releases/:namespace/:name/histo const requestHelmReleaseHistoryInjectable = getInjectable({ id: "request-helm-release-history", instantiate: (di): RequestHelmReleaseHistory => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return (name, namespace) => apiBase.get(requestHistoryEnpoint.compile({ name, namespace })); }, diff --git a/src/common/k8s-api/endpoints/helm-releases.api/request-releases.injectable.ts b/src/common/k8s-api/endpoints/helm-releases.api/request-releases.injectable.ts index 2619e289c6..ee6503ca99 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api/request-releases.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api/request-releases.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { urlBuilderFor } from "../../../utils/buildUrl"; -import { apiBaseInjectionToken } from "../../api-base"; +import apiBaseInjectable from "../../api-base.injectable"; import type { HelmReleaseDto } from "../helm-releases.api"; export type RequestHelmReleases = (namespace?: string) => Promise; @@ -15,7 +15,7 @@ const requestHelmReleasesInjectable = getInjectable({ id: "request-helm-releases", instantiate: (di): RequestHelmReleases => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return (namespace) => apiBase.get(requestHelmReleasesEndpoint.compile({ namespace })); }, diff --git a/src/common/k8s-api/endpoints/helm-releases.api/request-rollback.injectable.ts b/src/common/k8s-api/endpoints/helm-releases.api/request-rollback.injectable.ts index 1a9229b73a..036b399ef2 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api/request-rollback.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api/request-rollback.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { urlBuilderFor } from "../../../utils/buildUrl"; -import { apiBaseInjectionToken } from "../../api-base"; +import apiBaseInjectable from "../../api-base.injectable"; export type RequestHelmReleaseRollback = (name: string, namespace: string, revision: number) => Promise; @@ -13,7 +13,7 @@ const requestRollbackEndpoint = urlBuilderFor("/v2/releases/:namespace/:name"); const requestHelmReleaseRollbackInjectable = getInjectable({ id: "request-helm-release-rollback", instantiate: (di): RequestHelmReleaseRollback => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return async (name, namespace, revision) => { await apiBase.put( diff --git a/src/common/k8s-api/endpoints/helm-releases.api/request-update.injectable.ts b/src/common/k8s-api/endpoints/helm-releases.api/request-update.injectable.ts index 8f46a3a210..715a21cea9 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api/request-update.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api/request-update.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { apiBaseInjectionToken } from "../../api-base"; import { urlBuilderFor } from "../../../utils/buildUrl"; import type { AsyncResult } from "../../../utils/async-result"; +import apiBaseInjectable from "../../api-base.injectable"; interface HelmReleaseUpdatePayload { repo: string; @@ -26,7 +26,7 @@ const requestHelmReleaseUpdateInjectable = getInjectable({ id: "request-helm-release-update", instantiate: (di): RequestHelmReleaseUpdate => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return async (name, namespace, { repo, chart, values, version }) => { try { diff --git a/src/common/k8s-api/endpoints/helm-releases.api/request-values.injectable.ts b/src/common/k8s-api/endpoints/helm-releases.api/request-values.injectable.ts index d30f9973c4..99f1cc17d0 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api/request-values.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api/request-values.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { urlBuilderFor } from "../../../utils/buildUrl"; -import { apiBaseInjectionToken } from "../../api-base"; +import apiBaseInjectable from "../../api-base.injectable"; export type RequestHelmReleaseValues = (name: string, namespace: string, all?: boolean) => Promise; @@ -13,7 +13,7 @@ const requestValuesEndpoint = urlBuilderFor("/v2/release/:namespace/:name/values const requestHelmReleaseValuesInjectable = getInjectable({ id: "request-helm-release-values", instantiate: (di): RequestHelmReleaseValues => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return (name, namespace, all) => apiBase.get(requestValuesEndpoint.compile({ name, namespace }, { all })); }, diff --git a/src/common/k8s-api/endpoints/metrics.api/request-metrics.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/request-metrics.injectable.ts index 0da0bc95ec..e83c52b9aa 100644 --- a/src/common/k8s-api/endpoints/metrics.api/request-metrics.injectable.ts +++ b/src/common/k8s-api/endpoints/metrics.api/request-metrics.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { getSecondsFromUnixEpoch } from "../../../utils/date/get-current-date-time"; -import { apiBaseInjectionToken } from "../../api-base"; +import apiBaseInjectable from "../../api-base.injectable"; import type { MetricData } from "../metrics.api"; @@ -46,7 +46,7 @@ export interface RequestMetrics { const requestMetricsInjectable = getInjectable({ id: "request-metrics", instantiate: (di) => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return (async (query: object, params: RequestMetricsParams = {}) => { const { range = 3600, step = 60, namespace } = params; diff --git a/src/common/k8s-api/endpoints/metrics.api/request-providers.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/request-providers.injectable.ts index 4711333ca6..0c74c4d58d 100644 --- a/src/common/k8s-api/endpoints/metrics.api/request-providers.injectable.ts +++ b/src/common/k8s-api/endpoints/metrics.api/request-providers.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; -import { apiBaseInjectionToken } from "../../api-base"; +import apiBaseInjectable from "../../api-base.injectable"; export interface MetricProviderInfo { name: string; @@ -17,7 +17,7 @@ export type RequestMetricsProviders = () => Promise; const requestMetricsProvidersInjectable = getInjectable({ id: "request-metrics-providers", instantiate: (di): RequestMetricsProviders => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); return () => apiBase.get("/metrics/providers"); }, 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 c8ad3435fd..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 @@ -4,26 +4,45 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type { Patch } from "rfc6902"; -import { apiBaseInjectionToken } from "../../api-base"; +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(apiBaseInjectionToken); + 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 52824cd86c..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 @@ -3,17 +3,38 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { apiBaseInjectionToken } from "../../api-base"; +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(apiBaseInjectionToken); + 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/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/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/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/features/helm-charts/installing-chart/__snapshots__/opening-dock-tab-for-installing-helm-chart.test.ts.snap b/src/features/helm-charts/installing-chart/__snapshots__/opening-dock-tab-for-installing-helm-chart.test.ts.snap index bdd6779b76..e949e6c3c6 100644 --- a/src/features/helm-charts/installing-chart/__snapshots__/opening-dock-tab-for-installing-helm-chart.test.ts.snap +++ b/src/features/helm-charts/installing-chart/__snapshots__/opening-dock-tab-for-installing-helm-chart.test.ts.snap @@ -4631,6 +4631,919 @@ exports[`opening dock tab for installing helm chart given application is started `; +exports[`opening dock tab for installing helm chart given application is started, when navigating to helm charts when charts resolve when opening details of a chart when chart versions resolve when readme resolves when selecting different version when readme rejects renders 1`] = ` + +
+
+
+
@@ -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/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/+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/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; 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/dock/install-chart/install-chart-model.injectable.tsx b/src/renderer/components/dock/install-chart/install-chart-model.injectable.tsx index 0db94fbd59..b0bfcfcacc 100644 --- a/src/renderer/components/dock/install-chart/install-chart-model.injectable.tsx +++ b/src/renderer/components/dock/install-chart/install-chart-model.injectable.tsx @@ -122,14 +122,18 @@ export class InstallChartModel { this.configuration.isLoading.set(true); }); - const configuration = await this.dependencies.requestHelmChartValues( + const chartValuesRequest = await this.dependencies.requestHelmChartValues( this.chart.repo, this.chart.name, version, ); + if (!chartValuesRequest.callWasSuccessful) { + throw chartValuesRequest.error; + } + runInAction(() => { - this.configuration.onChange(configuration); + this.configuration.onChange(chartValuesRequest.response); this.configuration.isLoading.set(false); }); }, @@ -187,7 +191,7 @@ export class InstallChartModel { load = async () => { await this.dependencies.waitForChart(); - const [defaultConfiguration, versions] = await Promise.all([ + const [defaultConfigurationRequest, versions] = await Promise.all([ this.dependencies.requestHelmChartValues( this.chart.repo, this.chart.name, @@ -203,13 +207,14 @@ export class InstallChartModel { runInAction(() => { // TODO: Make "default" not hard-coded const namespace = this.chart.namespace || "default"; + const values = this.chart.values || (defaultConfigurationRequest.callWasSuccessful ? defaultConfigurationRequest.response : ""); this.versions.replace(versions); this.save({ version: this.chart.version, namespace, - values: this.chart.values || defaultConfiguration, + values, releaseName: this.chart.releaseName, }); }); 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/kubeconfig-dialog/open-service-account-kube-config-dialog.injectable.ts b/src/renderer/components/kubeconfig-dialog/open-service-account-kube-config-dialog.injectable.ts index 3a612dec2e..4c0e4255b1 100644 --- a/src/renderer/components/kubeconfig-dialog/open-service-account-kube-config-dialog.injectable.ts +++ b/src/renderer/components/kubeconfig-dialog/open-service-account-kube-config-dialog.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import apiBaseInjectable from "../../../common/k8s-api/api-base.injectable"; import type { ServiceAccount } from "../../../common/k8s-api/endpoints"; import { urlBuilderFor } from "../../../common/utils/buildUrl"; -import apiBaseInjectable from "../../k8s/api-base.injectable"; import openKubeconfigDialogInjectable from "./open.injectable"; export type OpenServiceAccountKubeConfigDialog = (account: ServiceAccount) => void; 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/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: " "; 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/k8s/api-base-host-header.injectable.ts b/src/renderer/k8s/api-base-host-header.injectable.ts new file mode 100644 index 0000000000..6ca954c85c --- /dev/null +++ b/src/renderer/k8s/api-base-host-header.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { apiBaseHostHeaderInjectionToken } from "../../common/k8s-api/api-base-configs"; +import windowLocationInjectable from "../../common/k8s-api/window-location.injectable"; + +const apiBaseHostHeaderInjectable = getInjectable({ + id: "api-base-host-header", + instantiate: (di) => di.inject(windowLocationInjectable).host, + injectionToken: apiBaseHostHeaderInjectionToken, +}); + +export default apiBaseHostHeaderInjectable; diff --git a/src/renderer/k8s/api-base-server-address.injectable.ts b/src/renderer/k8s/api-base-server-address.injectable.ts new file mode 100644 index 0000000000..acb1f525d3 --- /dev/null +++ b/src/renderer/k8s/api-base-server-address.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 { apiBaseServerAddressInjectionToken } from "../../common/k8s-api/api-base-configs"; +import windowLocationInjectable from "../../common/k8s-api/window-location.injectable"; + +const apiBaseServerAddressInjectable = getInjectable({ + id: "api-base-server-address", + instantiate: (di) => { + const { port } = di.inject(windowLocationInjectable); + + return `http://127.0.0.1:${port}`; + }, + injectionToken: apiBaseServerAddressInjectionToken, +}); + +export default apiBaseServerAddressInjectable; diff --git a/src/renderer/k8s/api-base.injectable.ts b/src/renderer/k8s/api-base.injectable.ts deleted file mode 100644 index 9ce5ab5f98..0000000000 --- a/src/renderer/k8s/api-base.injectable.ts +++ /dev/null @@ -1,36 +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 { apiBaseInjectionToken } from "../../common/k8s-api/api-base"; -import createJsonApiInjectable from "../../common/k8s-api/create-json-api.injectable"; -import windowLocationInjectable from "../../common/k8s-api/window-location.injectable"; -import { apiPrefix } from "../../common/vars"; -import isDebuggingInjectable from "../../common/vars/is-debugging.injectable"; -import isDevelopmentInjectable from "../../common/vars/is-development.injectable"; - -const apiBaseInjectable = getInjectable({ - id: "api-base", - instantiate: (di) => { - const createJsonApi = di.inject(createJsonApiInjectable); - const isDebugging = di.inject(isDebuggingInjectable); - const { port, host } = di.inject(windowLocationInjectable); - - return createJsonApi( - { - serverAddress: `http://127.0.0.1:${port}`, - apiBase: apiPrefix, - debug: di.inject(isDevelopmentInjectable) || isDebugging, - }, - { - headers: { - "Host": host, - }, - }, - ); - }, - injectionToken: apiBaseInjectionToken, -}); - -export default apiBaseInjectable; 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/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..e6a1bce903 --- /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 { beforeFrameStartsSecondInjectionToken } 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: beforeFrameStartsSecondInjectionToken, +}); + +export default setupLoggingForNavigationInjectable; diff --git a/src/renderer/port-forward/port-forward-store/port-forward-store.injectable.ts b/src/renderer/port-forward/port-forward-store/port-forward-store.injectable.ts index 5339a3e4e7..2d309c315d 100644 --- a/src/renderer/port-forward/port-forward-store/port-forward-store.injectable.ts +++ b/src/renderer/port-forward/port-forward-store/port-forward-store.injectable.ts @@ -7,8 +7,8 @@ import { PortForwardStore } from "./port-forward-store"; import type { ForwardedPort } from "../port-forward-item"; import createStorageInjectable from "../../utils/create-storage/create-storage.injectable"; import notifyErrorPortForwardingInjectable from "../notify-error-port-forwarding.injectable"; -import { apiBaseInjectionToken } from "../../../common/k8s-api/api-base"; import requestActivePortForwardInjectable from "./request-active-port-forward.injectable"; +import apiBaseInjectable from "../../../common/k8s-api/api-base.injectable"; const portForwardStoreInjectable = getInjectable({ id: "port-forward-store", @@ -22,7 +22,7 @@ const portForwardStoreInjectable = getInjectable({ undefined, ), notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable), - apiBase: di.inject(apiBaseInjectionToken), + apiBase: di.inject(apiBaseInjectable), requestActivePortForward: di.inject(requestActivePortForwardInjectable), }); }, diff --git a/src/renderer/port-forward/port-forward-store/request-active-port-forward.injectable.ts b/src/renderer/port-forward/port-forward-store/request-active-port-forward.injectable.ts index 4939f4d955..90f91320c8 100644 --- a/src/renderer/port-forward/port-forward-store/request-active-port-forward.injectable.ts +++ b/src/renderer/port-forward/port-forward-store/request-active-port-forward.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; -import { apiBaseInjectionToken } from "../../../common/k8s-api/api-base"; +import apiBaseInjectable from "../../../common/k8s-api/api-base.injectable"; import loggerInjectable from "../../../common/logger.injectable"; import { urlBuilderFor } from "../../../common/utils/buildUrl"; import type { ForwardedPort } from "../port-forward-item"; @@ -16,7 +16,7 @@ const requestActiveEndpoint = urlBuilderFor("/pods/port-forward/:namespace/:kind const requestActivePortForwardInjectable = getInjectable({ id: "request-active-port-forward", instantiate: (di): RequestActivePortForward => { - const apiBase = di.inject(apiBaseInjectionToken); + const apiBase = di.inject(apiBaseInjectable); const logger = di.inject(loggerInjectable); return async ({ port, forwardPort, namespace, kind, name, ...rest }) => { 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; +}; diff --git a/yarn.lock b/yarn.lock index 3287538dd2..27ab4129b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -598,135 +598,135 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@esbuild/android-arm64@0.16.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.7.tgz#2df472016c77dba3e79596a84da74c541698910f" - integrity sha512-tYFw0lBJSEvLoGzzYh1kXuzoX1iPkbOk3O29VqzQb0HbOy7t/yw1hGkvwoJhXHwzQUPsShyYcTgRf6bDBcfnTw== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.7.tgz#15f1a9b27b1637c38377b3e1f2d90b9782cdc141" - integrity sha512-yhzDbiVcmq6T1/XEvdcJIVcXHdLjDJ5cQ0Dp9R9p9ERMBTeO1dR5tc8YYv8zwDeBw1xZm+Eo3MRo8cwclhBS0g== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.7.tgz#cb34b7d666bf52266061cfb1a19c1d788b6c5ac1" - integrity sha512-3P2OuTxwAtM3k/yEWTNUJRjMPG1ce8rXs51GTtvEC5z1j8fC1plHeVVczdeHECU7aM2/Buc0MwZ6ciM/zysnWg== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.7.tgz#be1fabd0c2f6af111c16e9e9b18bf336c1e11634" - integrity sha512-VUb9GK23z8jkosHU9yJNUgQpsfJn+7ZyBm6adi2Ec5/U241eR1tAn82QicnUzaFDaffeixiHwikjmnec/YXEZg== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.7.tgz#2206042ac4396bb18dd53b379df83bec47eeb5fb" - integrity sha512-duterlv3tit3HI9vhzMWnSVaB1B6YsXpFq1Ntd6Fou82BB1l4tucYy3FI9dHv3tvtDuS0NiGf/k6XsdBqPZ01w== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.7.tgz#ca52bd64b0bba69ae4063245366f25838357c332" - integrity sha512-9kkycpBFes/vhi7B7o0cf+q2WdJi+EpVzpVTqtWFNiutARWDFFLcB93J8PR1cG228sucsl3B+7Ts27izE6qiaQ== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.7.tgz#bc01c146e6af5430c5eb325844de43f01e0264c4" - integrity sha512-5Ahf6jzWXJ4J2uh9dpy5DKOO+PeRUE/9DMys6VuYfwgQzd6n5+pVFm58L2Z2gRe611RX6SdydnNaiIKM3svY7g== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.7.tgz#23267ff1cdd2a8f150d5aca1d6d2a4dfd4be7909" - integrity sha512-2wv0xYDskk2+MzIm/AEprDip39a23Chptc4mL7hsHg26P0gD8RUhzmDu0KCH2vMThUI1sChXXoK9uH0KYQKaDg== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.7.tgz#2c7cf7244e4b8a6f757a87a113d83d8a0c1f5297" - integrity sha512-QqJnyCfu5OF78Olt7JJSZ7OSv/B4Hf+ZJWp4kkq9xwMsgu7yWq3crIic8gGOpDYTqVKKMDAVDgRXy5Wd/nWZyQ== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.7.tgz#a15dc3edf6953c5414add4264fd8335f48775490" - integrity sha512-APVYbEilKbD5ptmKdnIcXej2/+GdV65TfTjxR2Uk8t1EsOk49t6HapZW6DS/Bwlvh5hDwtLapdSumIVNGxgqLg== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.7.tgz#b3ce8539cf307b543796530839cf62507d9c7e84" - integrity sha512-5wPUAGclplQrAW7EFr3F84Y/d++7G0KykohaF4p54+iNWhUnMVU8Bh2sxiEOXUy4zKIdpHByMgJ5/Ko6QhtTUw== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.7.tgz#7c1c8f3de254b4e975ac2580bba187b87b959256" - integrity sha512-hxzlXtWF6yWfkE/SMTscNiVqLOAn7fOuIF3q/kiZaXxftz1DhZW/HpnTmTTWrzrS7zJWQxHHT4QSxyAj33COmA== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.7.tgz#72a00c9788f3ca5df56ecec060d5b92f945c9a2d" - integrity sha512-WM83Dac0LdXty5xPhlOuCD5Egfk1xLND/oRLYeB7Jb/tY4kzFSDgLlq91wYbHua/s03tQGA9iXvyjgymMw62Vw== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.7.tgz#545fd57e44dc3331a86956889f2a5b42bd116c9b" - integrity sha512-3nkNnNg4Ax6MS/l8O8Ynq2lGEVJYyJ2EoY3PHjNJ4PuZ80EYLMrFTFZ4L/Hc16AxgtXKwmNP9TM0YKNiBzBiJQ== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.7.tgz#a36fd4605904c49310616dd648c0c25a267a19c0" - integrity sha512-3SA/2VJuv0o1uD7zuqxEP+RrAyRxnkGddq0bwHQ98v1KNlzXD/JvxwTO3T6GM5RH6JUd29RTVQTOJfyzMkkppA== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.7.tgz#104f3f8f3f25f1f26b70cee05470974861ca5a5e" - integrity sha512-xi/tbqCqvPIzU+zJVyrpz12xqciTAPMi2fXEWGnapZymoGhuL2GIWIRXg4O2v5BXaYA5TSaiKYE14L0QhUTuQg== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.7.tgz#0fd59fea5e6b94ee82e81b3b389e561efe77b347" - integrity sha512-NUsYbq3B+JdNKn8SXkItFvdes9qTwEoS3aLALtiWciW/ystiCKM20Fgv9XQBOXfhUHyh5CLEeZDXzLOrwBXuCQ== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.7.tgz#c04072a70f31be1bd47204955d2c71ca393c9eb4" - integrity sha512-qjwzsgeve9I8Tbsko2FEkdSk2iiezuNGFgipQxY/736NePXDaDZRodIejYGWOlbYXugdxb0nif5yvypH6lKBmA== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.7.tgz#11c4cd341be1de93cb5e3bf096f3b63ae1497626" - integrity sha512-mFWDz4RoBTzPphTCkM7Kc7Qpa0o/Z01acajR+Ai7LdfKgcP/C6jYOaKwv7nKzD0+MjOT20j7You9g4ozYy1dKQ== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.7.tgz#95091269394f16352e318124790a3906bf370141" - integrity sha512-m39UmX19RvEIuC8sYZ0M+eQtdXw4IePDSZ78ZQmYyFaXY9krq4YzQCK2XWIJomNLtg4q+W5aXr8bW3AbqWNoVg== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.7.tgz#2bda285a0b7084a93417472c460b0209bef0c39d" - integrity sha512-1cbzSEZA1fANwmT6rjJ4G1qQXHxCxGIcNYFYR9ctI82/prT38lnwSRZ0i5p/MVXksw9eMlHlet6pGu2/qkXFCg== +"@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.7": - version "0.16.7" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.7.tgz#966ac3fc41758e6843cbd5844b2466bbdc34dada" - integrity sha512-QaQ8IH0JLacfGf5cf0HCCPnQuCTd/dAI257vXBgb/cccKGbH/6pVtI1gwhdAQ0Y48QSpTIFrh9etVyNdZY+zzw== +"@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.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" - integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg== +"@eslint/eslintrc@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.0.tgz#8ec64e0df3e7a1971ee1ff5158da87389f167a63" + integrity sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A== dependencies: ajv "^6.12.4" debug "^4.3.2" espree "^9.4.0" - globals "^13.15.0" + globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" @@ -861,14 +861,14 @@ "@hapi/bourne" "2.x.x" "@hapi/hoek" "9.x.x" -"@humanwhocodes/config-array@^0.11.6": - version "0.11.6" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.6.tgz#6a51d603a3aaf8d4cf45b42b3f2ac9318a4adc4b" - integrity sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg== +"@humanwhocodes/config-array@^0.11.8": + version "0.11.8" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" + integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" - minimatch "^3.0.4" + minimatch "^3.0.5" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" @@ -1184,11 +1184,15 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@kubernetes/client-node@^0.17.1": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.17.1.tgz#a5740712848d77823e7d0eee70229936398b4142" - integrity sha512-qXANjukuTq/drb1hq1NCYZafpdRTvbyTzbliWO6RwW7eEb2b9qwINbw0DiVHpBQg3e9DeQd8+brI1sR1Fck5kQ== +"@kubernetes/client-node@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.18.0.tgz#b1002f3c19fb7509ce521ea78f500589f8e3e047" + integrity sha512-Mp6q0OkZQBp+HslIgvHYpsPJk8z6mch231QWtIZQHvs+PaTE6mkUfusYE8fNw3jMjru5mVO/JDz6PTjB9YT2rQ== dependencies: + "@types/js-yaml" "^4.0.1" + "@types/node" "^10.12.0" + "@types/request" "^2.47.1" + "@types/ws" "^6.0.1" byline "^5.0.0" execa "5.0.0" isomorphic-ws "^4.0.1" @@ -1200,11 +1204,11 @@ stream-buffers "^3.0.2" tar "^6.1.11" tmp-promise "^3.0.2" - tslib "^1.9.3" - underscore "^1.9.1" + tslib "^2.4.1" + underscore "^1.13.6" ws "^7.3.1" optionalDependencies: - openid-client "^5.1.6" + openid-client "^5.3.0" "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" @@ -2249,7 +2253,7 @@ jest-matcher-utils "^28.0.0" pretty-format "^28.0.0" -"@types/js-yaml@^4.0.5": +"@types/js-yaml@^4.0.1", "@types/js-yaml@^4.0.5": version "4.0.5" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== @@ -2348,10 +2352,15 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.24.tgz#20ba1bf69c1b4ab405c7a01e950c4f446b05029f" integrity sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g== -"@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@^10.12.0": + version "10.17.60" + 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.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" @@ -2510,7 +2519,7 @@ dependencies: "@types/request" "*" -"@types/request@*", "@types/request@^2.48.7": +"@types/request@*", "@types/request@^2.47.1", "@types/request@^2.48.7": version "2.48.8" resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.8.tgz#0b90fde3b655ab50976cb8c5ac00faca22f5a82c" integrity sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ== @@ -2712,6 +2721,13 @@ tapable "^2.2.0" webpack "^5" +"@types/ws@^6.0.1": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.4.tgz#7797707c8acce8f76d8c34b370d4645b70421ff1" + integrity sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg== + dependencies: + "@types/node" "*" + "@types/ws@^8.5.1": version "8.5.3" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" @@ -5568,33 +5584,33 @@ esbuild@^0.15.6: esbuild-windows-64 "0.15.18" esbuild-windows-arm64 "0.15.18" -esbuild@^0.16.7: - version "0.16.7" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.7.tgz#3288f685a83c6097dea8ddf1759ca30d6e06535b" - integrity sha512-P6OBFYFSQOGzfApqCeYKqfKRRbCIRsdppTXFo4aAvtiW3o8TTyiIplBvHJI171saPAiy3WlawJHCveJVIOIx1A== +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.7" - "@esbuild/android-arm64" "0.16.7" - "@esbuild/android-x64" "0.16.7" - "@esbuild/darwin-arm64" "0.16.7" - "@esbuild/darwin-x64" "0.16.7" - "@esbuild/freebsd-arm64" "0.16.7" - "@esbuild/freebsd-x64" "0.16.7" - "@esbuild/linux-arm" "0.16.7" - "@esbuild/linux-arm64" "0.16.7" - "@esbuild/linux-ia32" "0.16.7" - "@esbuild/linux-loong64" "0.16.7" - "@esbuild/linux-mips64el" "0.16.7" - "@esbuild/linux-ppc64" "0.16.7" - "@esbuild/linux-riscv64" "0.16.7" - "@esbuild/linux-s390x" "0.16.7" - "@esbuild/linux-x64" "0.16.7" - "@esbuild/netbsd-x64" "0.16.7" - "@esbuild/openbsd-x64" "0.16.7" - "@esbuild/sunos-x64" "0.16.7" - "@esbuild/win32-arm64" "0.16.7" - "@esbuild/win32-ia32" "0.16.7" - "@esbuild/win32-x64" "0.16.7" + "@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" @@ -5769,13 +5785,13 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.29.0: - version "8.29.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.29.0.tgz#d74a88a20fb44d59c51851625bc4ee8d0ec43f87" - integrity sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg== +eslint@^8.30.0: + version "8.30.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.30.0.tgz#83a506125d089eef7c5b5910eeea824273a33f50" + integrity sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ== dependencies: - "@eslint/eslintrc" "^1.3.3" - "@humanwhocodes/config-array" "^0.11.6" + "@eslint/eslintrc" "^1.4.0" + "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" @@ -5794,7 +5810,7 @@ eslint@^8.29.0: file-entry-cache "^6.0.1" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.15.0" + globals "^13.19.0" grapheme-splitter "^1.0.4" ignore "^5.2.0" import-fresh "^3.0.0" @@ -6582,10 +6598,10 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.15.0: - version "13.15.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" - integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== +globals@^13.19.0: + version "13.19.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8" + integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ== dependencies: type-fest "^0.20.2" @@ -6629,10 +6645,10 @@ globrex@^0.1.2: resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== -got@^11.8.5: - version "11.8.5" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046" - integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ== +got@^11.8.6: + version "11.8.6" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" @@ -8045,10 +8061,10 @@ joi@^17.7.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" -jose@^4.1.4: - version "4.9.3" - resolved "https://registry.yarnpkg.com/jose/-/jose-4.9.3.tgz#890abd3f26725fe0f2aa720bc2f7835702b624db" - integrity sha512-f8E/z+T3Q0kA9txzH2DKvH/ds2uggcw0m3vVPSB9HrSkrQ7mojjifvS7aR8cw+lQl2Fcmx9npwaHpM/M3GD8UQ== +jose@^4.10.0: + version "4.11.1" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.11.1.tgz#8f7443549befe5bddcf4bae664a9cbc1a62da4fa" + integrity sha512-YRv4Tk/Wlug8qicwqFNFVEZSdbROCHRAC6qu/i0dyNKr5JQdoa2pIGoS04lLO/jXQX7Z9omoNewYIVIxqZBd9Q== joycon@^3.0.1: version "3.1.1" @@ -8894,7 +8910,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== @@ -9050,17 +9066,17 @@ minimatch@3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.1.2: +minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 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" @@ -9226,10 +9242,10 @@ mock-http@^1.1.0: dependencies: mergee "^1.0.0" -moment-timezone@^0.5.39: - version "0.5.39" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.39.tgz#342625a3b98810f04c8f4ea917e448d3525e600b" - integrity sha512-hoB6suq4ISDj7BDgctiOy6zljBsdYT0++0ZzZm9rtxIvJhIbQ3nmbgSWe7dNFGurl6/7b1OUkHlmN9JWgXVz7w== +moment-timezone@^0.5.40: + version "0.5.40" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.40.tgz#c148f5149fd91dd3e29bf481abc8830ecba16b89" + integrity sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg== dependencies: moment ">= 2.9.0" @@ -9875,12 +9891,12 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== -openid-client@^5.1.6: - version "5.1.8" - resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.1.8.tgz#3a24910288b32c32f548fb6e391f44178ce6370f" - integrity sha512-EPxJY6bT7YIYQEXSGxRC5flQ3GUhLy98ufdto6+BVBrFGPmwjUpy4xBcYuU/Wt9nPkO/3EgljBrr6Ezx4lp1RQ== +openid-client@^5.3.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.3.1.tgz#69a5fa7d2b5ad479032f576852d40b4d4435488a" + integrity sha512-RLfehQiHch9N6tRWNx68cicf3b1WR0x74bJWHRc25uYIbSRwjxYcTFaRnzbbpls5jroLAaB/bFIodTgA5LJMvw== dependencies: - jose "^4.1.4" + jose "^4.10.0" lru-cache "^6.0.0" object-hash "^2.0.1" oidc-token-hash "^5.0.1" @@ -10207,17 +10223,17 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright-core@1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.28.1.tgz#8400be9f4a8d1c0489abdb9e75a4cc0ffc3c00cb" - integrity sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag== +playwright-core@1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.29.0.tgz#314653618395afef5031156d691c7aa0826e5d05" + integrity sha512-pboOm1m0RD6z1GtwAbEH60PYRfF87vKdzOSRw2RyO0Y0a7utrMyWN2Au1ojGvQr4umuBMODkKTv607YIRypDSQ== -playwright@^1.28.1: - version "1.28.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.28.1.tgz#f23247f1de466ff73d7230d94df96271e5da6583" - integrity sha512-92Sz6XBlfHlb9tK5UCDzIFAuIkHHpemA9zwUaqvo+w7sFMSmVMGmvKcbptof/eJObq63PGnMhM75x7qxhTR78Q== +playwright@^1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.29.0.tgz#14b70f373a7d433b9e304ee1666cb70e93336364" + integrity sha512-vtXgX3FPNcAJq1QoIVCvmiHHKOLVTZkSYEo60n+EnX5MrNznAJzGquxT8c2sv+BG3CDyLeKm351e491HnF7yjw== dependencies: - playwright-core "1.28.1" + playwright-core "1.29.0" plist@^3.0.1, plist@^3.0.4: version "3.0.5" @@ -11168,10 +11184,10 @@ rfc4648@^1.3.0: resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.1.tgz#b0b16756e33d9de8c0c7833e94b28e627ec372a4" integrity sha512-60e/YWs2/D3MV1ErdjhJHcmlgnyLUiG4X/14dgsfm9/zmCWLN16xI6YqJYSCd/OANM7bUNzJqPY5B8/02S9Ibw== -rfc6902@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/rfc6902/-/rfc6902-5.0.0.tgz#cb53ac1cb8a700edc1ddc6c19afbb93422f9e385" - integrity sha512-6d0Q7DgN+RWHhvY/jb7QWG6ol+XreFqQn4NzvHB8CSALA70fRLRmbJaMvxlFf/JIzfkiNWq0lQF0l5yWb4yuLA== +rfc6902@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/rfc6902/-/rfc6902-5.0.1.tgz#e16f18dc322c755d6dd948423ddf52bb451eca3d" + integrity sha512-tYGfLpKIq9X7lrt4o3IkD9w9bpeAtsejfAqWNR98AoxfTsZqCepKa8eDlRiX8QMiCOD9vMx0/YbKLx0G1nPi5w== rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" @@ -11262,10 +11278,10 @@ sass-loader@^12.6.0: klona "^2.0.4" neo-async "^2.6.2" -sass@^1.32.13, sass@^1.56.1: - version "1.56.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.56.1.tgz#94d3910cd468fd075fa87f5bb17437a0b617d8a7" - integrity sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ== +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" @@ -12447,12 +12463,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - -tslib@^2.4.0: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== @@ -12560,14 +12571,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: @@ -12619,10 +12630,10 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== -underscore@1.7.0, underscore@^1.12.1, underscore@^1.9.1: - version "1.13.4" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee" - integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ== +underscore@1.7.0, underscore@^1.12.1, underscore@^1.13.6: + version "1.13.6" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" + integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== unique-filename@^1.1.1: version "1.1.1"