diff --git a/package.json b/package.json index 830939a7cb..d68bce842b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "devDependencies": { "adr": "^1.4.3", "cross-env": "^7.0.3", - "lerna": "^6.5.0", + "lerna": "^6.5.1", "rimraf": "^4.1.2" } } diff --git a/packages/core/package.json b/packages/core/package.json index 94ef65127c..e88c3642fc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -158,12 +158,12 @@ "hpagent": "^1.2.0", "http-proxy": "^1.18.1", "immer": "^9.0.19", - "joi": "^17.7.0", + "joi": "^17.7.1", "js-yaml": "^4.1.0", "lodash": "^4.17.15", "marked": "^4.2.12", "md5-file": "^5.0.0", - "mobx": "^6.7.0", + "mobx": "^6.8.0", "mobx-observable-history": "^2.0.3", "mobx-react": "^7.6.0", "mobx-utils": "^6.0.4", @@ -269,7 +269,7 @@ "electron": "^19.1.9", "electron-builder": "^23.6.0", "electron-notarize": "^0.3.0", - "esbuild": "^0.17.7", + "esbuild": "^0.17.8", "esbuild-loader": "^2.21.0", "eslint": "^8.33.0", "eslint-import-resolver-typescript": "^3.5.3", @@ -310,7 +310,7 @@ "react-table": "^7.8.0", "react-window": "^1.8.8", "rimraf": "^4.1.2", - "sass": "^1.58.0", + "sass": "^1.58.1", "sass-loader": "^12.6.0", "style-loader": "^3.3.1", "tailwindcss": "^3.2.4", diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/navigate-to-replication-controllers.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/navigate-to-replication-controllers.injectable.ts new file mode 100644 index 0000000000..240ad0f37e --- /dev/null +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/navigate-to-replication-controllers.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import replicationControllersRouteInjectable from "./replicationcontrollers-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToReplicationControllersInjectable = getInjectable({ + id: "navigate-to-replicationcontrollers", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(replicationControllersRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToReplicationControllersInjectable; diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts new file mode 100644 index 0000000000..77d87abc96 --- /dev/null +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; + +const replicationControllersRouteInjectable = getInjectable({ + id: "replicationcontrollers-route", + + instantiate: (di) => ({ + path: "/replicationcontrollers", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "replicationcontrollers", + group: "", // core + }), + }), + + injectionToken: frontEndRouteInjectionToken, +}); + +export default replicationControllersRouteInjectable; diff --git a/packages/core/src/common/k8s-api/endpoints/index.ts b/packages/core/src/common/k8s-api/endpoints/index.ts index 0314c6b282..8fb6cbdabd 100644 --- a/packages/core/src/common/k8s-api/endpoints/index.ts +++ b/packages/core/src/common/k8s-api/endpoints/index.ts @@ -33,6 +33,7 @@ export * from "./pod-metrics.api"; export * from "./pod-security-policy.api"; export * from "./priority-class.api"; export * from "./replica-set.api"; +export * from "./replication-controller.api"; export * from "./resource-quota.api"; export * from "./role.api"; export * from "./role-binding.api"; diff --git a/packages/core/src/common/k8s-api/endpoints/replication-controller.api.injectable.ts b/packages/core/src/common/k8s-api/endpoints/replication-controller.api.injectable.ts new file mode 100644 index 0000000000..6d65f446b5 --- /dev/null +++ b/packages/core/src/common/k8s-api/endpoints/replication-controller.api.injectable.ts @@ -0,0 +1,23 @@ +/** + * 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 { kubeApiInjectionToken } from "../kube-api/kube-api-injection-token"; +import loggerInjectable from "../../logger.injectable"; +import maybeKubeApiInjectable from "../maybe-kube-api.injectable"; +import { ReplicationControllerApi } from "./replication-controller.api"; + +const replicationControllerApiInjectable = getInjectable({ + id: "replication-controller-api", + instantiate: (di) => { + return new ReplicationControllerApi({ + logger: di.inject(loggerInjectable), + maybeKubeApi: di.inject(maybeKubeApiInjectable), + }); + }, + + injectionToken: kubeApiInjectionToken, +}); + +export default replicationControllerApiInjectable; diff --git a/packages/core/src/common/k8s-api/endpoints/replication-controller.api.ts b/packages/core/src/common/k8s-api/endpoints/replication-controller.api.ts new file mode 100644 index 0000000000..b87b7a81a8 --- /dev/null +++ b/packages/core/src/common/k8s-api/endpoints/replication-controller.api.ts @@ -0,0 +1,149 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; +import { KubeApi } from "../kube-api"; +import type { + BaseKubeObjectCondition, KubeObjectMetadata, + KubeObjectStatus, + NamespaceScopedMetadata, +} from "../kube-object"; +import { KubeObject } from "../kube-object"; +import type { PodTemplateSpec } from "./types"; + +export class ReplicationControllerApi extends KubeApi { + constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { + super(deps, { + ...opts ?? {}, + objectConstructor: ReplicationController, + }); + } + + protected getScaleApiUrl(params: { namespace: string; name: string }) { + return `${this.formatUrlForNotListing(params)}/scale`; + } + + getScale(params: { namespace: string; name: string }): Promise { + return this.request.get(this.getScaleApiUrl(params)); + } + + scale(params: { namespace: string; name: string }, replicas: number): Promise { + return this.request.patch(this.getScaleApiUrl(params), { + data: { + metadata: params, + spec: { + replicas, + }, + }, + }, { + headers: { + "content-type": "application/strategic-merge-patch+json", + }, + }); + } +} + +export interface Scale { + apiVersion: "autoscaling/v1"; + kind: "Scale"; + metadata: KubeObjectMetadata; + spec: { + replicas: number; + }; + status: { + replicas: number; + selector: string; + }; +} + +export interface ReplicationControllerSpec { + /** + * Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. + * Defaults to 0 (pod will be considered available as soon as it is ready) + */ + minReadySeconds?: number; + /** + * Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. + * Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller + */ + replicas?: number; + /** + * Selector is a label query over pods that should match the Replicas count. If Selector is empty, it is defaulted to the labels present on the Pod template. + * Label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template. + * More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + */ + selector?: Record; + /** + * Template is the object that describes the pod that will be created if insufficient replicas are detected. This takes precedence over a TemplateRef. + * More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + */ + template: PodTemplateSpec; +} + +export interface ReplicationControllerStatus extends KubeObjectStatus { + /** + * The number of available replicas (ready for at least minReadySeconds) for this replication controller. + */ + availableReplicas: number; + /** + * The number of pods that have labels matching the labels of the pod template of the replication controller. + */ + fullyLabeledReplicas: number; + /** + * ObservedGeneration reflects the generation of the most recently observed replication controller. + */ + observedGeneration: number; + /** + * The number of ready replicas for this replication controller. + */ + readyReplicas: number; + /** + * Replicas is the most recently observed number of replicas. + * More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller + */ + replicas: number; +} + +export class ReplicationController extends KubeObject< + NamespaceScopedMetadata, + ReplicationControllerStatus, + ReplicationControllerSpec +> { + static kind = "ReplicationController"; + static namespaced = true; + static apiBase = "/api/v1/replicationcontrollers"; + + getMinReadySeconds(): number { + return this.spec?.minReadySeconds ?? 0; + } + + getGeneration() { + return this.status?.observedGeneration; + } + + getSelectorLabels(): string[] { + return KubeObject.stringifyLabels(this.spec.selector); + } + + getReplicas(): number | undefined { + return this.status?.replicas; + } + + getDesiredReplicas(): number { + return this.spec?.replicas ?? 0; + } + + getAvailableReplicas(): number | undefined { + return this.status?.availableReplicas; + } + + getLabeledReplicas(): number | undefined { + return this.status?.fullyLabeledReplicas; + } + + getConditions(): BaseKubeObjectCondition[] { + return this.status?.conditions ?? []; + } +} diff --git a/packages/core/src/common/rbac.ts b/packages/core/src/common/rbac.ts index e2ccad3806..03b4fd1de9 100644 --- a/packages/core/src/common/rbac.ts +++ b/packages/core/src/common/rbac.ts @@ -6,7 +6,7 @@ export type KubeResource = "namespaces" | "nodes" | "events" | "resourcequotas" | "services" | "limitranges" | "leases" | "secrets" | "configmaps" | "ingresses" | "ingressclasses" | "networkpolicies" | "persistentvolumeclaims" | "persistentvolumes" | "storageclasses" | - "pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" | + "pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "replicationcontrollers" | "jobs" | "cronjobs" | "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "verticalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets" | "priorityclasses" | "runtimeclasses" | "roles" | "clusterroles" | "rolebindings" | "clusterrolebindings" | "serviceaccounts"; @@ -171,6 +171,11 @@ export const apiResourceRecord: Record = { group: "apps", namespaced: true, }, + replicationcontrollers: { + kind: "ReplicationController", + group: "", // core + namespaced: true, + }, roles: { kind: "Role", group: "rbac.authorization.k8s.io", diff --git a/packages/core/src/extensions/ipc/ipc-main.ts b/packages/core/src/extensions/ipc/ipc-main.ts index cd18bcc56c..8bb0ada63d 100644 --- a/packages/core/src/extensions/ipc/ipc-main.ts +++ b/packages/core/src/extensions/ipc/ipc-main.ts @@ -27,12 +27,12 @@ export abstract class IpcMain extends IpcRegistrar { listen(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer { const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`; const cleanup = once(() => { - this.extension[lensExtensionDependencies].logger.info(`[IPC-RENDERER]: removing extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + this.extension[lensExtensionDependencies].logger.debug(`[IPC-RENDERER]: removing extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); return ipcMain.removeListener(prefixedChannel, listener); }); - this.extension[lensExtensionDependencies].logger.info(`[IPC-RENDERER]: adding extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + this.extension[lensExtensionDependencies].logger.debug(`[IPC-RENDERER]: adding extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); ipcMain.addListener(prefixedChannel, listener); this.extension[Disposers].push(cleanup); @@ -47,10 +47,10 @@ export abstract class IpcMain extends IpcRegistrar { handle(channel: string, handler: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any): void { const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`; - this.extension[lensExtensionDependencies].logger.info(`[IPC-RENDERER]: adding extension handler`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + this.extension[lensExtensionDependencies].logger.debug(`[IPC-RENDERER]: adding extension handler`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); ipcMainHandle(prefixedChannel, handler); this.extension[Disposers].push(() => { - this.extension[lensExtensionDependencies].logger.info(`[IPC-RENDERER]: removing extension handler`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + this.extension[lensExtensionDependencies].logger.debug(`[IPC-RENDERER]: removing extension handler`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); return ipcMain.removeHandler(prefixedChannel); }); diff --git a/packages/core/src/extensions/ipc/ipc-renderer.ts b/packages/core/src/extensions/ipc/ipc-renderer.ts index 71b67779f5..ff61738113 100644 --- a/packages/core/src/extensions/ipc/ipc-renderer.ts +++ b/packages/core/src/extensions/ipc/ipc-renderer.ts @@ -28,12 +28,12 @@ export abstract class IpcRenderer extends IpcRegistrar { listen(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer { const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`; const cleanup = once(() => { - console.info(`[IPC-RENDERER]: removing extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + console.debug(`[IPC-RENDERER]: removing extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); return ipcRenderer.removeListener(prefixedChannel, listener); }); - console.info(`[IPC-RENDERER]: adding extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + console.debug(`[IPC-RENDERER]: adding extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); ipcRenderer.addListener(prefixedChannel, listener); this.extension[Disposers].push(cleanup); diff --git a/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/about-bundled-extensions.injectable.ts b/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/about-bundled-extensions.injectable.ts new file mode 100644 index 0000000000..449ce5f4a8 --- /dev/null +++ b/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/about-bundled-extensions.injectable.ts @@ -0,0 +1,23 @@ +/** + * 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 { bundledExtensionInjectionToken } from "../../../../../../common/library"; +import buildSemanticVersionInjectable from "../../../../../../common/vars/build-semantic-version.injectable"; + +const aboutBundledExtensionsInjectable = getInjectable({ + id: "about-bundled-extensions", + instantiate: (di) => { + const buildSemanticVersion = di.inject(buildSemanticVersionInjectable); + const bundledExtensions = di.injectMany(bundledExtensionInjectionToken); + + if (buildSemanticVersion.get().prerelease[0] === "latest") { + return []; + } + + return bundledExtensions.map(ext => `${ext.manifest.name}: ${ext.manifest.version}`); + }, +}); + +export default aboutBundledExtensionsInjectable; diff --git a/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts b/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts index a91e2af337..8bc78ae8c3 100644 --- a/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts +++ b/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts @@ -10,6 +10,7 @@ import productNameInjectable from "../../../../../../common/vars/product-name.in import buildVersionInjectable from "../../../../../../main/vars/build-version/build-version.injectable"; import extensionApiVersionInjectable from "../../../../../../common/vars/extension-api-version.injectable"; import applicationCopyrightInjectable from "../../../../../../common/vars/application-copyright.injectable"; +import aboutBundledExtensionsInjectable from "./about-bundled-extensions.injectable"; const showAboutInjectable = getInjectable({ id: "show-about", @@ -22,6 +23,7 @@ const showAboutInjectable = getInjectable({ const appName = di.inject(appNameInjectable); const productName = di.inject(productNameInjectable); const applicationCopyright = di.inject(applicationCopyrightInjectable); + const aboutBundledExtensions = di.inject(aboutBundledExtensionsInjectable); return () => { const appInfo = [ @@ -30,6 +32,7 @@ const showAboutInjectable = getInjectable({ `Electron: ${process.versions.electron}`, `Chrome: ${process.versions.chrome}`, `Node: ${process.versions.node}`, + ...aboutBundledExtensions, applicationCopyright, ]; diff --git a/packages/core/src/features/helm-releases/__snapshots__/showing-details-for-helm-release.test.ts.snap b/packages/core/src/features/helm-releases/__snapshots__/showing-details-for-helm-release.test.ts.snap index 86e80acc4c..c36ac69784 100644 --- a/packages/core/src/features/helm-releases/__snapshots__/showing-details-for-helm-release.test.ts.snap +++ b/packages/core/src/features/helm-releases/__snapshots__/showing-details-for-helm-release.test.ts.snap @@ -5818,7 +5818,7 @@ exports[`showing details for helm release given application is started when navi @@ -7100,7 +7100,7 @@ exports[`showing details for helm release given application is started when navi @@ -8382,7 +8382,7 @@ exports[`showing details for helm release given application is started when navi @@ -9469,7 +9469,7 @@ exports[`showing details for helm release given application is started when navi @@ -10557,7 +10557,7 @@ exports[`showing details for helm release given application is started when navi @@ -11840,7 +11840,7 @@ exports[`showing details for helm release given application is started when navi @@ -17047,3 +17047,2077 @@ exports[`showing details for helm release given application is started when navi `; + +exports[`showing details for helm release given application is started when navigating to helm releases when releases resolve when selecting release to see details when opening details for second release when details for second release resolve renders 1`] = ` + +
+
+
+
+
+ +
+ + + close + + +
+ Close +
+
+
+
+
+
+
+
+