mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
commit
a64579dc08
@ -3,7 +3,7 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "5.2.6-beta.1",
|
"version": "5.2.6",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021 OpenLens Authors",
|
"copyright": "© 2021 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -237,6 +237,7 @@
|
|||||||
"readable-stream": "^3.6.0",
|
"readable-stream": "^3.6.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"request-promise-native": "^1.0.9",
|
"request-promise-native": "^1.0.9",
|
||||||
|
"rfc6902": "^4.0.2",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
"serializr": "^2.0.5",
|
"serializr": "^2.0.5",
|
||||||
"shell-env": "^3.0.1",
|
"shell-env": "^3.0.1",
|
||||||
|
|||||||
@ -20,92 +20,108 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { CustomResourceDefinition } from "../endpoints";
|
import { CustomResourceDefinition } from "../endpoints";
|
||||||
import type { KubeObjectMetadata } from "../kube-object";
|
|
||||||
|
|
||||||
describe("Crds", () => {
|
describe("Crds", () => {
|
||||||
describe("getVersion", () => {
|
describe("getVersion", () => {
|
||||||
it("should get the first version name from the list of versions", () => {
|
it("should throw if none of the versions are served", () => {
|
||||||
const crd = new CustomResourceDefinition({
|
const crd = new CustomResourceDefinition({
|
||||||
apiVersion: "foo",
|
apiVersion: "apiextensions.k8s.io/v1",
|
||||||
kind: "CustomResourceDefinition",
|
kind: "CustomResourceDefinition",
|
||||||
metadata: {} as KubeObjectMetadata,
|
metadata: {
|
||||||
|
name: "foo",
|
||||||
|
resourceVersion: "12345",
|
||||||
|
uid: "12345",
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
name: "123",
|
||||||
|
served: false,
|
||||||
|
storage: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1234",
|
||||||
|
served: false,
|
||||||
|
storage: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
crd.spec = {
|
expect(() => crd.getVersion()).toThrowError("Failed to find a version for CustomResourceDefinition foo");
|
||||||
versions: [
|
});
|
||||||
{
|
|
||||||
name: "123",
|
it("should should get the version that is both served and stored", () => {
|
||||||
served: false,
|
const crd = new CustomResourceDefinition({
|
||||||
storage: false,
|
apiVersion: "apiextensions.k8s.io/v1",
|
||||||
}
|
kind: "CustomResourceDefinition",
|
||||||
]
|
metadata: {
|
||||||
} as any;
|
name: "foo",
|
||||||
|
resourceVersion: "12345",
|
||||||
|
uid: "12345",
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
name: "123",
|
||||||
|
served: true,
|
||||||
|
storage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1234",
|
||||||
|
served: false,
|
||||||
|
storage: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
expect(crd.getVersion()).toBe("123");
|
expect(crd.getVersion()).toBe("123");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get the first version name from the list of versions (length 2)", () => {
|
it("should should get the version that is both served and stored even with version field", () => {
|
||||||
const crd = new CustomResourceDefinition({
|
const crd = new CustomResourceDefinition({
|
||||||
apiVersion: "foo",
|
apiVersion: "apiextensions.k8s.io/v1",
|
||||||
kind: "CustomResourceDefinition",
|
kind: "CustomResourceDefinition",
|
||||||
metadata: {} as KubeObjectMetadata,
|
metadata: {
|
||||||
|
name: "foo",
|
||||||
|
resourceVersion: "12345",
|
||||||
|
uid: "12345",
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
version: "abc",
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
name: "123",
|
||||||
|
served: true,
|
||||||
|
storage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1234",
|
||||||
|
served: false,
|
||||||
|
storage: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
crd.spec = {
|
|
||||||
versions: [
|
|
||||||
{
|
|
||||||
name: "123",
|
|
||||||
served: false,
|
|
||||||
storage: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1234",
|
|
||||||
served: false,
|
|
||||||
storage: false,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
expect(crd.getVersion()).toBe("123");
|
expect(crd.getVersion()).toBe("123");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get the first version name from the list of versions (length 2) even with version field", () => {
|
it("should get the version name from the version field", () => {
|
||||||
const crd = new CustomResourceDefinition({
|
const crd = new CustomResourceDefinition({
|
||||||
apiVersion: "foo",
|
apiVersion: "apiextensions.k8s.io/v1beta1",
|
||||||
kind: "CustomResourceDefinition",
|
kind: "CustomResourceDefinition",
|
||||||
metadata: {} as KubeObjectMetadata,
|
metadata: {
|
||||||
|
name: "foo",
|
||||||
|
resourceVersion: "12345",
|
||||||
|
uid: "12345",
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
version: "abc",
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
crd.spec = {
|
|
||||||
version: "abc",
|
|
||||||
versions: [
|
|
||||||
{
|
|
||||||
name: "123",
|
|
||||||
served: false,
|
|
||||||
storage: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1234",
|
|
||||||
served: false,
|
|
||||||
storage: false,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
expect(crd.getVersion()).toBe("123");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should get the first version name from the version field", () => {
|
|
||||||
const crd = new CustomResourceDefinition({
|
|
||||||
apiVersion: "foo",
|
|
||||||
kind: "CustomResourceDefinition",
|
|
||||||
metadata: {} as KubeObjectMetadata,
|
|
||||||
});
|
|
||||||
|
|
||||||
crd.spec = {
|
|
||||||
version: "abc"
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
expect(crd.getVersion()).toBe("abc");
|
expect(crd.getVersion()).toBe("abc");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,10 +19,11 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { KubeObject } from "../kube-object";
|
import { KubeCreationError, KubeObject } from "../kube-object";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
import { crdResourcesURL } from "../../routes";
|
import { crdResourcesURL } from "../../routes";
|
||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
||||||
|
import type { KubeJsonApiData } from "../kube-json-api";
|
||||||
|
|
||||||
type AdditionalPrinterColumnsCommon = {
|
type AdditionalPrinterColumnsCommon = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -39,10 +40,21 @@ type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & {
|
|||||||
JSONPath: string;
|
JSONPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface CRDVersion {
|
||||||
|
name: string;
|
||||||
|
served: boolean;
|
||||||
|
storage: boolean;
|
||||||
|
schema?: object; // required in v1 but not present in v1beta
|
||||||
|
additionalPrinterColumns?: AdditionalPrinterColumnsV1[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface CustomResourceDefinition {
|
export interface CustomResourceDefinition {
|
||||||
spec: {
|
spec: {
|
||||||
group: string;
|
group: string;
|
||||||
version?: string; // deprecated in v1 api
|
/**
|
||||||
|
* @deprecated for apiextensions.k8s.io/v1 but used previously
|
||||||
|
*/
|
||||||
|
version?: string;
|
||||||
names: {
|
names: {
|
||||||
plural: string;
|
plural: string;
|
||||||
singular: string;
|
singular: string;
|
||||||
@ -50,19 +62,19 @@ export interface CustomResourceDefinition {
|
|||||||
listKind: string;
|
listKind: string;
|
||||||
};
|
};
|
||||||
scope: "Namespaced" | "Cluster" | string;
|
scope: "Namespaced" | "Cluster" | string;
|
||||||
validation?: any;
|
/**
|
||||||
versions?: {
|
* @deprecated for apiextensions.k8s.io/v1 but used previously
|
||||||
name: string;
|
*/
|
||||||
served: boolean;
|
validation?: object;
|
||||||
storage: boolean;
|
versions?: CRDVersion[];
|
||||||
schema?: unknown; // required in v1 but not present in v1beta
|
|
||||||
additionalPrinterColumns?: AdditionalPrinterColumnsV1[]
|
|
||||||
}[];
|
|
||||||
conversion: {
|
conversion: {
|
||||||
strategy?: string;
|
strategy?: string;
|
||||||
webhook?: any;
|
webhook?: any;
|
||||||
};
|
};
|
||||||
additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[]; // removed in v1
|
/**
|
||||||
|
* @deprecated for apiextensions.k8s.io/v1 but used previously
|
||||||
|
*/
|
||||||
|
additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[];
|
||||||
};
|
};
|
||||||
status: {
|
status: {
|
||||||
conditions: {
|
conditions: {
|
||||||
@ -83,11 +95,23 @@ export interface CustomResourceDefinition {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CRDApiData extends KubeJsonApiData {
|
||||||
|
spec: object; // TODO: make better
|
||||||
|
}
|
||||||
|
|
||||||
export class CustomResourceDefinition extends KubeObject {
|
export class CustomResourceDefinition extends KubeObject {
|
||||||
static kind = "CustomResourceDefinition";
|
static kind = "CustomResourceDefinition";
|
||||||
static namespaced = false;
|
static namespaced = false;
|
||||||
static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions";
|
static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions";
|
||||||
|
|
||||||
|
constructor(data: CRDApiData) {
|
||||||
|
super(data);
|
||||||
|
|
||||||
|
if (!data.spec || typeof data.spec !== "object") {
|
||||||
|
throw new KubeCreationError("Cannot create a CustomResourceDefinition from an object without spec", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getResourceUrl() {
|
getResourceUrl() {
|
||||||
return crdResourcesURL({
|
return crdResourcesURL({
|
||||||
params: {
|
params: {
|
||||||
@ -125,9 +149,36 @@ export class CustomResourceDefinition extends KubeObject {
|
|||||||
return this.spec.scope;
|
return this.spec.scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPreferedVersion(): CRDVersion {
|
||||||
|
// Prefer the modern `versions` over the legacy `version`
|
||||||
|
if (this.spec.versions) {
|
||||||
|
for (const version of this.spec.versions) {
|
||||||
|
/**
|
||||||
|
* If the version is not served then 404 errors will occur
|
||||||
|
* We should also prefer the storage version
|
||||||
|
*/
|
||||||
|
if (version.served && version.storage) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (this.spec.version) {
|
||||||
|
const { additionalPrinterColumns: apc } = this.spec;
|
||||||
|
const additionalPrinterColumns = apc?.map(({ JSONPath, ...apc}) => ({ ...apc, jsonPath: JSONPath }));
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: this.spec.version,
|
||||||
|
served: true,
|
||||||
|
storage: true,
|
||||||
|
schema: this.spec.validation,
|
||||||
|
additionalPrinterColumns,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to find a version for CustomResourceDefinition ${this.metadata.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
getVersion() {
|
getVersion() {
|
||||||
// v1 has removed the spec.version property, if it is present it must match the first version
|
return this.getPreferedVersion().name;
|
||||||
return this.spec.versions?.[0]?.name ?? this.spec.version;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isNamespaced() {
|
isNamespaced() {
|
||||||
@ -147,17 +198,14 @@ export class CustomResourceDefinition extends KubeObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] {
|
getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] {
|
||||||
const columns = this.spec.versions?.find(a => this.getVersion() == a.name)?.additionalPrinterColumns
|
const columns = this.getPreferedVersion().additionalPrinterColumns ?? [];
|
||||||
?? this.spec.additionalPrinterColumns?.map(({ JSONPath, ...rest }) => ({ ...rest, jsonPath: JSONPath })) // map to V1 shape
|
|
||||||
?? [];
|
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
.filter(column => column.name != "Age")
|
.filter(column => column.name != "Age" && (ignorePriority || !column.priority));
|
||||||
.filter(column => ignorePriority ? true : !column.priority);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getValidation() {
|
getValidation() {
|
||||||
return JSON.stringify(this.spec.validation ?? this.spec.versions?.[0]?.schema, null, 2);
|
return JSON.stringify(this.getPreferedVersion().schema, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
getConditions() {
|
getConditions() {
|
||||||
|
|||||||
@ -22,19 +22,27 @@
|
|||||||
import jsYaml from "js-yaml";
|
import jsYaml from "js-yaml";
|
||||||
import type { KubeJsonApiData } from "../kube-json-api";
|
import type { KubeJsonApiData } from "../kube-json-api";
|
||||||
import { apiBase } from "../index";
|
import { apiBase } from "../index";
|
||||||
|
import type { Patch } from "rfc6902";
|
||||||
|
|
||||||
export const resourceApplierApi = {
|
export const annotations = [
|
||||||
annotations: [
|
"kubectl.kubernetes.io/last-applied-configuration"
|
||||||
"kubectl.kubernetes.io/last-applied-configuration"
|
];
|
||||||
],
|
|
||||||
|
|
||||||
async update(resource: object | string): Promise<KubeJsonApiData | null> {
|
export async function update(resource: object | string): Promise<KubeJsonApiData> {
|
||||||
if (typeof resource === "string") {
|
if (typeof resource === "string") {
|
||||||
resource = jsYaml.safeLoad(resource);
|
resource = jsYaml.safeLoad(resource);
|
||||||
}
|
|
||||||
|
|
||||||
const [data = null] = await apiBase.post<KubeJsonApiData[]>("/stack", { data: resource });
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
return apiBase.post<KubeJsonApiData>("/stack", { data: resource });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function patch(name: string, kind: string, ns: string, patch: Patch): Promise<KubeJsonApiData> {
|
||||||
|
return apiBase.patch<KubeJsonApiData>("/stack", {
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
kind,
|
||||||
|
ns,
|
||||||
|
patch,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -27,8 +27,7 @@ import { stringify } from "querystring";
|
|||||||
import { EventEmitter } from "../../common/event-emitter";
|
import { EventEmitter } from "../../common/event-emitter";
|
||||||
import logger from "../../common/logger";
|
import logger from "../../common/logger";
|
||||||
|
|
||||||
export interface JsonApiData {
|
export interface JsonApiData {}
|
||||||
}
|
|
||||||
|
|
||||||
export interface JsonApiError {
|
export interface JsonApiError {
|
||||||
code?: number;
|
code?: number;
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import { parseKubeApi } from "./kube-api-parse";
|
|||||||
import type { KubeJsonApiData } from "./kube-json-api";
|
import type { KubeJsonApiData } from "./kube-json-api";
|
||||||
import type { RequestInit } from "node-fetch";
|
import type { RequestInit } from "node-fetch";
|
||||||
import AbortController from "abort-controller";
|
import AbortController from "abort-controller";
|
||||||
|
import type { Patch } from "rfc6902";
|
||||||
|
|
||||||
export interface KubeObjectStoreLoadingParams<K extends KubeObject> {
|
export interface KubeObjectStoreLoadingParams<K extends KubeObject> {
|
||||||
namespaces: string[];
|
namespaces: string[];
|
||||||
@ -279,19 +280,29 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
return newItem;
|
return newItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(item: T, data: Partial<T>): Promise<T> {
|
private postUpdate(rawItem: KubeJsonApiData): T {
|
||||||
const rawItem = await item.update(data);
|
|
||||||
const newItem = new this.api.objectConstructor(rawItem);
|
const newItem = new this.api.objectConstructor(rawItem);
|
||||||
|
const index = this.items.findIndex(item => item.getId() === newItem.getId());
|
||||||
|
|
||||||
ensureObjectSelfLink(this.api, newItem);
|
ensureObjectSelfLink(this.api, newItem);
|
||||||
|
|
||||||
const index = this.items.findIndex(item => item.getId() === newItem.getId());
|
if (index < 0) {
|
||||||
|
this.items.push(newItem);
|
||||||
this.items.splice(index, 1, newItem);
|
} else {
|
||||||
|
this.items[index] = newItem;
|
||||||
|
}
|
||||||
|
|
||||||
return newItem;
|
return newItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async patch(item: T, patch: Patch): Promise<T> {
|
||||||
|
return this.postUpdate(await item.patch(patch));
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(item: T, data: Partial<T>): Promise<T> {
|
||||||
|
return this.postUpdate(await item.update(data));
|
||||||
|
}
|
||||||
|
|
||||||
async remove(item: T) {
|
async remove(item: T) {
|
||||||
await item.delete();
|
await item.delete();
|
||||||
this.items.remove(item);
|
this.items.remove(item);
|
||||||
|
|||||||
@ -27,9 +27,9 @@ import { autoBind, formatDuration } from "../utils";
|
|||||||
import type { ItemObject } from "../item.store";
|
import type { ItemObject } from "../item.store";
|
||||||
import { apiKube } from "./index";
|
import { apiKube } from "./index";
|
||||||
import type { JsonApiParams } from "./json-api";
|
import type { JsonApiParams } from "./json-api";
|
||||||
import { resourceApplierApi } from "./endpoints/resource-applier.api";
|
import * as resourceApplierApi from "./endpoints/resource-applier.api";
|
||||||
import { hasOptionalProperty, hasTypedProperty, isObject, isString, bindPredicate, isTypedArray, isRecord } from "../../common/utils/type-narrowing";
|
import { hasOptionalProperty, hasTypedProperty, isObject, isString, bindPredicate, isTypedArray, isRecord } from "../../common/utils/type-narrowing";
|
||||||
import _ from "lodash";
|
import type { Patch } from "rfc6902";
|
||||||
|
|
||||||
export type KubeObjectConstructor<K extends KubeObject> = (new (data: KubeJsonApiData | any) => K) & {
|
export type KubeObjectConstructor<K extends KubeObject> = (new (data: KubeJsonApiData | any) => K) & {
|
||||||
kind?: string;
|
kind?: string;
|
||||||
@ -98,6 +98,12 @@ export interface KubeObjectStatus {
|
|||||||
|
|
||||||
export type KubeMetaField = keyof KubeObjectMetadata;
|
export type KubeMetaField = keyof KubeObjectMetadata;
|
||||||
|
|
||||||
|
export class KubeCreationError extends Error {
|
||||||
|
constructor(message: string, public data: any) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata, Status = any, Spec = any> implements ItemObject {
|
export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata, Status = any, Spec = any> implements ItemObject {
|
||||||
static readonly kind: string;
|
static readonly kind: string;
|
||||||
static readonly namespaced: boolean;
|
static readonly namespaced: boolean;
|
||||||
@ -191,18 +197,32 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
|
|||||||
return Object.entries(labels).map(([name, value]) => `${name}=${value}`);
|
return Object.entries(labels).map(([name, value]) => `${name}=${value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static readonly nonEditableFields = [
|
/**
|
||||||
"apiVersion",
|
* These must be RFC6902 compliant paths
|
||||||
"kind",
|
*/
|
||||||
"metadata.name",
|
private static readonly nonEditiablePathPrefixes = [
|
||||||
"metadata.selfLink",
|
"/metadata/managedFields",
|
||||||
"metadata.resourceVersion",
|
"/status",
|
||||||
"metadata.uid",
|
|
||||||
"managedFields",
|
|
||||||
"status",
|
|
||||||
];
|
];
|
||||||
|
private static readonly nonEditablePaths = new Set([
|
||||||
|
"/apiVersion",
|
||||||
|
"/kind",
|
||||||
|
"/metadata/name",
|
||||||
|
"/metadata/selfLink",
|
||||||
|
"/metadata/resourceVersion",
|
||||||
|
"/metadata/uid",
|
||||||
|
...KubeObject.nonEditiablePathPrefixes,
|
||||||
|
]);
|
||||||
|
|
||||||
constructor(data: KubeJsonApiData) {
|
constructor(data: KubeJsonApiData) {
|
||||||
|
if (typeof data !== "object") {
|
||||||
|
throw new TypeError(`Cannot create a KubeObject from ${typeof data}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.metadata || typeof data.metadata !== "object") {
|
||||||
|
throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata`, data);
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(this, data);
|
Object.assign(this, data);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
@ -264,7 +284,7 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOwnerRefs() {
|
getOwnerRefs() {
|
||||||
const refs = this.metadata?.ownerReferences || [];
|
const refs = this.metadata.ownerReferences || [];
|
||||||
const namespace = this.getNs();
|
const namespace = this.getNs();
|
||||||
|
|
||||||
return refs.map(ownerRef => ({ ...ownerRef, namespace }));
|
return refs.map(ownerRef => ({ ...ownerRef, namespace }));
|
||||||
@ -286,14 +306,31 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
|
|||||||
return JSON.parse(JSON.stringify(this));
|
return JSON.parse(JSON.stringify(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// use unified resource-applier api for updating all k8s objects
|
async patch(patch: Patch): Promise<KubeJsonApiData | null> {
|
||||||
async update(data: Partial<this>): Promise<KubeJsonApiData | null> {
|
for (const op of patch) {
|
||||||
for (const field of KubeObject.nonEditableFields) {
|
if (KubeObject.nonEditablePaths.has(op.path)) {
|
||||||
if (!_.isEqual(_.get(this, field), _.get(data, field))) {
|
throw new Error(`Failed to update ${this.kind}: JSON pointer ${op.path} has been modified`);
|
||||||
throw new Error(`Failed to update Kube Object: ${field} has been modified`);
|
}
|
||||||
|
|
||||||
|
for (const pathPrefix of KubeObject.nonEditiablePathPrefixes) {
|
||||||
|
if (op.path.startsWith(`${pathPrefix}/`)) {
|
||||||
|
throw new Error(`Failed to update ${this.kind}: Child JSON pointer of ${op.path} has been modified`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return resourceApplierApi.patch(this.getName(), this.kind, this.getNs(), patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a full update (or more specifically a replace)
|
||||||
|
*
|
||||||
|
* Note: this is brittle if `data` is not actually partial (but instead whole).
|
||||||
|
* As fields such as `resourceVersion` will probably out of date. This is a
|
||||||
|
* common race condition.
|
||||||
|
*/
|
||||||
|
async update(data: Partial<this>): Promise<KubeJsonApiData | null> {
|
||||||
|
// use unified resource-applier api for updating all k8s objects
|
||||||
return resourceApplierApi.update({
|
return resourceApplierApi.update({
|
||||||
...this.toPlainObject(),
|
...this.toPlainObject(),
|
||||||
...data,
|
...data,
|
||||||
|
|||||||
@ -22,55 +22,96 @@
|
|||||||
import type { Cluster } from "./cluster";
|
import type { Cluster } from "./cluster";
|
||||||
import type { KubernetesObject } from "@kubernetes/client-node";
|
import type { KubernetesObject } from "@kubernetes/client-node";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
import fs from "fs";
|
import fs from "fs-extra";
|
||||||
import * as yaml from "js-yaml";
|
import * as yaml from "js-yaml";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as tempy from "tempy";
|
import * as tempy from "tempy";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { appEventBus } from "../common/event-bus";
|
import { appEventBus } from "../common/event-bus";
|
||||||
import { cloneJsonObject } from "../common/utils";
|
import { cloneJsonObject } from "../common/utils";
|
||||||
|
import type { Patch } from "rfc6902";
|
||||||
|
import { promiseExecFile } from "./promise-exec";
|
||||||
|
|
||||||
export class ResourceApplier {
|
export class ResourceApplier {
|
||||||
constructor(protected cluster: Cluster) {
|
constructor(protected cluster: Cluster) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch a kube resource's manifest, throwing any error that occurs.
|
||||||
|
* @param name The name of the kube resource
|
||||||
|
* @param kind The kind of the kube resource
|
||||||
|
* @param patch The list of JSON operations
|
||||||
|
* @param ns The optional namespace of the kube resource
|
||||||
|
*/
|
||||||
|
async patch(name: string, kind: string, patch: Patch, ns?: string): Promise<string> {
|
||||||
|
appEventBus.emit({ name: "resource", action: "patch" });
|
||||||
|
|
||||||
|
const kubectl = await this.cluster.ensureKubectl();
|
||||||
|
const kubectlPath = await kubectl.getPath();
|
||||||
|
const proxyKubeconfigPath = await this.cluster.getProxyKubeconfigPath();
|
||||||
|
const args = [
|
||||||
|
"--kubeconfig", proxyKubeconfigPath,
|
||||||
|
"patch",
|
||||||
|
kind,
|
||||||
|
name,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (ns) {
|
||||||
|
args.push("--namespace", ns);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(
|
||||||
|
"--type", "json",
|
||||||
|
"--patch", JSON.stringify(patch),
|
||||||
|
"-o", "json"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout } = await promiseExecFile(kubectlPath, args);
|
||||||
|
|
||||||
|
return stdout;
|
||||||
|
} catch (error) {
|
||||||
|
throw error.stderr ?? error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async apply(resource: KubernetesObject | any): Promise<string> {
|
async apply(resource: KubernetesObject | any): Promise<string> {
|
||||||
resource = this.sanitizeObject(resource);
|
resource = this.sanitizeObject(resource);
|
||||||
appEventBus.emit({ name: "resource", action: "apply" });
|
appEventBus.emit({ name: "resource", action: "apply" });
|
||||||
|
|
||||||
return await this.kubectlApply(yaml.safeDump(resource));
|
return this.kubectlApply(yaml.safeDump(resource));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async kubectlApply(content: string): Promise<string> {
|
protected async kubectlApply(content: string): Promise<string> {
|
||||||
const kubectl = await this.cluster.ensureKubectl();
|
const kubectl = await this.cluster.ensureKubectl();
|
||||||
const kubectlPath = await kubectl.getPath();
|
const kubectlPath = await kubectl.getPath();
|
||||||
const proxyKubeconfigPath = await this.cluster.getProxyKubeconfigPath();
|
const proxyKubeconfigPath = await this.cluster.getProxyKubeconfigPath();
|
||||||
|
const fileName = tempy.file({ name: "resource.yaml" });
|
||||||
|
const args = [
|
||||||
|
"apply",
|
||||||
|
"--kubeconfig", proxyKubeconfigPath,
|
||||||
|
"-o", "json",
|
||||||
|
"-f", fileName,
|
||||||
|
];
|
||||||
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
logger.debug(`shooting manifests with ${kubectlPath}`, { args });
|
||||||
const fileName = tempy.file({ name: "resource.yaml" });
|
|
||||||
|
|
||||||
fs.writeFileSync(fileName, content);
|
const execEnv = { ...process.env };
|
||||||
const cmd = `"${kubectlPath}" apply --kubeconfig "${proxyKubeconfigPath}" -o json -f "${fileName}"`;
|
const httpsProxy = this.cluster.preferences?.httpsProxy;
|
||||||
|
|
||||||
logger.debug(`shooting manifests with: ${cmd}`);
|
if (httpsProxy) {
|
||||||
const execEnv: NodeJS.ProcessEnv = Object.assign({}, process.env);
|
execEnv.HTTPS_PROXY = httpsProxy;
|
||||||
const httpsProxy = this.cluster.preferences?.httpsProxy;
|
}
|
||||||
|
|
||||||
if (httpsProxy) {
|
try {
|
||||||
execEnv["HTTPS_PROXY"] = httpsProxy;
|
await fs.writeFile(fileName, content);
|
||||||
}
|
const { stdout } = await promiseExecFile(kubectlPath, args);
|
||||||
exec(cmd, { env: execEnv },
|
|
||||||
(error, stdout, stderr) => {
|
|
||||||
if (stderr != "") {
|
|
||||||
fs.unlinkSync(fileName);
|
|
||||||
reject(stderr);
|
|
||||||
|
|
||||||
return;
|
return stdout;
|
||||||
}
|
} catch (error) {
|
||||||
fs.unlinkSync(fileName);
|
throw error?.stderr ?? error;
|
||||||
resolve(JSON.parse(stdout));
|
} finally {
|
||||||
});
|
await fs.unlink(fileName);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async kubectlApplyAll(resources: string[], extraArgs = ["-o", "json"]): Promise<string> {
|
public async kubectlApplyAll(resources: string[], extraArgs = ["-o", "json"]): Promise<string> {
|
||||||
|
|||||||
@ -198,5 +198,6 @@ export class Router {
|
|||||||
|
|
||||||
// Resource Applier API
|
// Resource Applier API
|
||||||
this.router.add({ method: "post", path: `${apiPrefix}/stack` }, ResourceApplierApiRoute.applyResource);
|
this.router.add({ method: "post", path: `${apiPrefix}/stack` }, ResourceApplierApiRoute.applyResource);
|
||||||
|
this.router.add({ method: "patch", path: `${apiPrefix}/stack` }, ResourceApplierApiRoute.patchResource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,19 @@ export class ResourceApplierApiRoute {
|
|||||||
try {
|
try {
|
||||||
const resource = await new ResourceApplier(cluster).apply(payload);
|
const resource = await new ResourceApplier(cluster).apply(payload);
|
||||||
|
|
||||||
respondJson(response, [resource], 200);
|
respondJson(response, resource, 200);
|
||||||
|
} catch (error) {
|
||||||
|
respondText(response, error, 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async patchResource(request: LensApiRequest) {
|
||||||
|
const { response, cluster, payload } = request;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resource = await new ResourceApplier(cluster).patch(payload.name, payload.kind, payload.patch, payload.ns);
|
||||||
|
|
||||||
|
respondJson(response, resource, 200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
respondText(response, error, 422);
|
respondText(response, error, 422);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,14 +21,37 @@
|
|||||||
|
|
||||||
import type http from "http";
|
import type http from "http";
|
||||||
|
|
||||||
export function respondJson(res: http.ServerResponse, content: any, status = 200) {
|
/**
|
||||||
respond(res, JSON.stringify(content), "application/json", status);
|
* Respond to a HTTP request with a body of JSON data
|
||||||
|
* @param res The HTTP response to write data to
|
||||||
|
* @param content The data or its JSON stringified version of it
|
||||||
|
* @param status [200] The status code to respond with
|
||||||
|
*/
|
||||||
|
export function respondJson(res: http.ServerResponse, content: Object | string, status = 200) {
|
||||||
|
const normalizedContent = typeof content === "object"
|
||||||
|
? JSON.stringify(content)
|
||||||
|
: content;
|
||||||
|
|
||||||
|
respond(res, normalizedContent, "application/json", status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond to a HTTP request with a body of plain text data
|
||||||
|
* @param res The HTTP response to write data to
|
||||||
|
* @param content The string data to respond with
|
||||||
|
* @param status [200] The status code to respond with
|
||||||
|
*/
|
||||||
export function respondText(res: http.ServerResponse, content: string, status = 200) {
|
export function respondText(res: http.ServerResponse, content: string, status = 200) {
|
||||||
respond(res, content, "text/plain", status);
|
respond(res, content, "text/plain", status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond to a HTTP request with a body of plain text data
|
||||||
|
* @param res The HTTP response to write data to
|
||||||
|
* @param content The string data to respond with
|
||||||
|
* @param contentType The HTTP Content-Type header value
|
||||||
|
* @param status [200] The status code to respond with
|
||||||
|
*/
|
||||||
export function respond(res: http.ServerResponse, content: string, contentType: string, status = 200) {
|
export function respond(res: http.ServerResponse, content: string, contentType: string, status = 200) {
|
||||||
res.setHeader("Content-Type", contentType);
|
res.setHeader("Content-Type", contentType);
|
||||||
res.statusCode = status;
|
res.statusCode = status;
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import { Table, TableCell, TableHead, TableRow } from "../table";
|
|||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
export interface HpaDetailsProps extends KubeObjectDetailsProps<HorizontalPodAutoscaler> {
|
export interface HpaDetailsProps extends KubeObjectDetailsProps<HorizontalPodAutoscaler> {
|
||||||
}
|
}
|
||||||
@ -80,17 +81,13 @@ export class HpaDetails extends React.Component<HpaDetailsProps> {
|
|||||||
<TableCell className="metrics">Current / Target</TableCell>
|
<TableCell className="metrics">Current / Target</TableCell>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
{
|
{
|
||||||
hpa.getMetrics().map((metric, index) => {
|
hpa.getMetrics()
|
||||||
const name = renderName(metric);
|
.map((metric, index) => (
|
||||||
const values = hpa.getMetricValues(metric);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow key={index}>
|
<TableRow key={index}>
|
||||||
<TableCell className="name">{name}</TableCell>
|
<TableCell className="name">{renderName(metric)}</TableCell>
|
||||||
<TableCell className="metrics">{values}</TableCell>
|
<TableCell className="metrics">{hpa.getMetricValues(metric)}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</Table>
|
</Table>
|
||||||
);
|
);
|
||||||
@ -99,7 +96,16 @@ export class HpaDetails extends React.Component<HpaDetailsProps> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: hpa } = this.props;
|
const { object: hpa } = this.props;
|
||||||
|
|
||||||
if (!hpa) return null;
|
if (!hpa) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(hpa instanceof HorizontalPodAutoscaler)) {
|
||||||
|
logger.error("[HpaDetails]: passed object that is not an instanceof HorizontalPodAutoscaler", hpa);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { scaleTargetRef } = hpa.spec;
|
const { scaleTargetRef } = hpa.spec;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import { LimitPart, LimitRange, LimitRangeItem, Resource } from "../../../common
|
|||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { DrawerItem } from "../drawer/drawer-item";
|
import { DrawerItem } from "../drawer/drawer-item";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<LimitRange> {
|
interface Props extends KubeObjectDetailsProps<LimitRange> {
|
||||||
}
|
}
|
||||||
@ -57,15 +58,13 @@ function renderResourceLimits(limit: LimitRangeItem, resource: Resource) {
|
|||||||
|
|
||||||
function renderLimitDetails(limits: LimitRangeItem[], resources: Resource[]) {
|
function renderLimitDetails(limits: LimitRangeItem[], resources: Resource[]) {
|
||||||
|
|
||||||
return resources.map(resource =>
|
return resources.map(resource => (
|
||||||
<DrawerItem key={resource} name={resource}>
|
<DrawerItem key={resource} name={resource}>
|
||||||
{
|
{
|
||||||
limits.map(limit =>
|
limits.map(limit => renderResourceLimits(limit, resource))
|
||||||
renderResourceLimits(limit, resource)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -73,7 +72,16 @@ export class LimitRangeDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: limitRange } = this.props;
|
const { object: limitRange } = this.props;
|
||||||
|
|
||||||
if (!limitRange) return null;
|
if (!limitRange) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(limitRange instanceof LimitRange)) {
|
||||||
|
logger.error("[LimitRangeDetails]: passed object that is not an instanceof LimitRange", limitRange);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const containerLimits = limitRange.getContainerLimits();
|
const containerLimits = limitRange.getContainerLimits();
|
||||||
const podLimits = limitRange.getPodLimits();
|
const podLimits = limitRange.getPodLimits();
|
||||||
const pvcLimits = limitRange.getPVCLimits();
|
const pvcLimits = limitRange.getPVCLimits();
|
||||||
|
|||||||
@ -30,8 +30,9 @@ import { Input } from "../input";
|
|||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { configMapsStore } from "./config-maps.store";
|
import { configMapsStore } from "./config-maps.store";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { ConfigMap } from "../../../common/k8s-api/endpoints";
|
import { ConfigMap } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<ConfigMap> {
|
interface Props extends KubeObjectDetailsProps<ConfigMap> {
|
||||||
}
|
}
|
||||||
@ -72,6 +73,8 @@ export class ConfigMapDetails extends React.Component<Props> {
|
|||||||
<>ConfigMap <b>{configMap.getName()}</b> successfully updated.</>
|
<>ConfigMap <b>{configMap.getName()}</b> successfully updated.</>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
Notifications.error(`Failed to save config map: ${error}`);
|
||||||
} finally {
|
} finally {
|
||||||
this.isSaving = false;
|
this.isSaving = false;
|
||||||
}
|
}
|
||||||
@ -80,7 +83,16 @@ export class ConfigMapDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: configMap } = this.props;
|
const { object: configMap } = this.props;
|
||||||
|
|
||||||
if (!configMap) return null;
|
if (!configMap) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(configMap instanceof ConfigMap)) {
|
||||||
|
logger.error("[ConfigMapDetails]: passed object that is not an instanceof ConfigMap", configMap);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const data = Array.from(this.data.entries());
|
const data = Array.from(this.data.entries());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -91,22 +103,20 @@ export class ConfigMapDetails extends React.Component<Props> {
|
|||||||
<>
|
<>
|
||||||
<DrawerTitle title="Data"/>
|
<DrawerTitle title="Data"/>
|
||||||
{
|
{
|
||||||
data.map(([name, value]) => {
|
data.map(([name, value]) => (
|
||||||
return (
|
<div key={name} className="data">
|
||||||
<div key={name} className="data">
|
<div className="name">{name}</div>
|
||||||
<div className="name">{name}</div>
|
<div className="flex gaps align-flex-start">
|
||||||
<div className="flex gaps align-flex-start">
|
<Input
|
||||||
<Input
|
multiLine
|
||||||
multiLine
|
theme="round-black"
|
||||||
theme="round-black"
|
className="box grow"
|
||||||
className="box grow"
|
value={value}
|
||||||
value={value}
|
onChange={v => this.data.set(name, v)}
|
||||||
onChange={v => this.data.set(name, v)}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
})
|
))
|
||||||
}
|
}
|
||||||
<Button
|
<Button
|
||||||
primary
|
primary
|
||||||
|
|||||||
@ -26,8 +26,9 @@ import { observer } from "mobx-react";
|
|||||||
import { DrawerItem } from "../drawer";
|
import { DrawerItem } from "../drawer";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { PodDisruptionBudget } from "../../../common/k8s-api/endpoints";
|
import { PodDisruptionBudget } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PodDisruptionBudget> {
|
interface Props extends KubeObjectDetailsProps<PodDisruptionBudget> {
|
||||||
}
|
}
|
||||||
@ -38,7 +39,16 @@ export class PodDisruptionBudgetDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: pdb } = this.props;
|
const { object: pdb } = this.props;
|
||||||
|
|
||||||
if (!pdb) return null;
|
if (!pdb) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(pdb instanceof PodDisruptionBudget)) {
|
||||||
|
logger.error("[PodDisruptionBudgetDetails]: passed object that is not an instanceof PodDisruptionBudget", pdb);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const selectors = pdb.getSelectors();
|
const selectors = pdb.getSelectors();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -26,10 +26,11 @@ import { observer } from "mobx-react";
|
|||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { cpuUnitsToNumber, cssNames, unitsToBytes, metricUnitsToNumber } from "../../utils";
|
import { cpuUnitsToNumber, cssNames, unitsToBytes, metricUnitsToNumber } from "../../utils";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { ResourceQuota } from "../../../common/k8s-api/endpoints/resource-quota.api";
|
import { ResourceQuota } from "../../../common/k8s-api/endpoints/resource-quota.api";
|
||||||
import { LineProgress } from "../line-progress";
|
import { LineProgress } from "../line-progress";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<ResourceQuota> {
|
interface Props extends KubeObjectDetailsProps<ResourceQuota> {
|
||||||
}
|
}
|
||||||
@ -77,7 +78,15 @@ export class ResourceQuotaDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: quota } = this.props;
|
const { object: quota } = this.props;
|
||||||
|
|
||||||
if (!quota) return null;
|
if (!quota) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(quota instanceof ResourceQuota)) {
|
||||||
|
logger.error("[ResourceQuotaDetails]: passed object that is not an instanceof ResourceQuota", quota);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ResourceQuotaDetails">
|
<div className="ResourceQuotaDetails">
|
||||||
|
|||||||
@ -33,8 +33,9 @@ import { base64 } from "../../utils";
|
|||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { secretsStore } from "./secrets.store";
|
import { secretsStore } from "./secrets.store";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { Secret } from "../../../common/k8s-api/endpoints";
|
import { Secret } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Secret> {
|
interface Props extends KubeObjectDetailsProps<Secret> {
|
||||||
}
|
}
|
||||||
@ -84,7 +85,15 @@ export class SecretDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: secret } = this.props;
|
const { object: secret } = this.props;
|
||||||
|
|
||||||
if (!secret) return null;
|
if (!secret) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(secret instanceof Secret)) {
|
||||||
|
logger.error("[SecretDetails]: passed object that is not an instanceof Secret", secret);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="SecretDetails">
|
<div className="SecretDetails">
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import "./crd-details.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
|
import { CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { ThemeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
@ -35,6 +35,7 @@ import { Input } from "../input";
|
|||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import MonacoEditor from "react-monaco-editor";
|
import MonacoEditor from "react-monaco-editor";
|
||||||
import { UserStore } from "../../../common/user-store";
|
import { UserStore } from "../../../common/user-store";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<CustomResourceDefinition> {
|
interface Props extends KubeObjectDetailsProps<CustomResourceDefinition> {
|
||||||
}
|
}
|
||||||
@ -44,7 +45,16 @@ export class CRDDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: crd } = this.props;
|
const { object: crd } = this.props;
|
||||||
|
|
||||||
if (!crd) return null;
|
if (!crd) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(crd instanceof CustomResourceDefinition)) {
|
||||||
|
logger.error("[CRDDetails]: passed object that is not an instanceof CustomResourceDefinition", crd);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { plural, singular, kind, listKind } = crd.getNames();
|
const { plural, singular, kind, listKind } = crd.getNames();
|
||||||
const printerColumns = crd.getPrinterColumns();
|
const printerColumns = crd.getPrinterColumns();
|
||||||
const validation = crd.getValidation();
|
const validation = crd.getValidation();
|
||||||
|
|||||||
@ -30,9 +30,10 @@ import { DrawerItem } from "../drawer";
|
|||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { Input } from "../input";
|
import { Input } from "../input";
|
||||||
import type { AdditionalPrinterColumnsV1, CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
|
import { AdditionalPrinterColumnsV1, CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
|
||||||
import { parseJsonPath } from "../../utils/jsonPath";
|
import { parseJsonPath } from "../../utils/jsonPath";
|
||||||
import type { KubeObject, KubeObjectMetadata, KubeObjectStatus } from "../../../common/k8s-api/kube-object";
|
import { KubeObject, KubeObjectMetadata, KubeObjectStatus } from "../../../common/k8s-api/kube-object";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<KubeObject> {
|
interface Props extends KubeObjectDetailsProps<KubeObject> {
|
||||||
crd: CustomResourceDefinition;
|
crd: CustomResourceDefinition;
|
||||||
@ -101,6 +102,18 @@ export class CrdResourceDetails extends React.Component<Props> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(object instanceof KubeObject)) {
|
||||||
|
logger.error("[CrdResourceDetails]: passed object that is not an instanceof KubeObject", object);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(crd instanceof CustomResourceDefinition)) {
|
||||||
|
logger.error("[CrdResourceDetails]: passed crd that is not an instanceof CustomResourceDefinition", crd);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const extraColumns = crd.getPrinterColumns();
|
const extraColumns = crd.getPrinterColumns();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -27,12 +27,13 @@ import { DrawerItem, DrawerTitle } from "../drawer";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { KubeEvent } from "../../../common/k8s-api/endpoints/events.api";
|
import { KubeEvent } from "../../../common/k8s-api/endpoints/events.api";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { LocaleDate } from "../locale-date";
|
import { LocaleDate } from "../locale-date";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<KubeEvent> {
|
interface Props extends KubeObjectDetailsProps<KubeEvent> {
|
||||||
}
|
}
|
||||||
@ -42,7 +43,16 @@ export class EventDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: event } = this.props;
|
const { object: event } = this.props;
|
||||||
|
|
||||||
if (!event) return null;
|
if (!event) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(event instanceof KubeEvent)) {
|
||||||
|
logger.error("[EventDetails]: passed object that is not an instanceof KubeEvent", event);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { message, reason, count, type, involvedObject } = event;
|
const { message, reason, count, type, involvedObject } = event;
|
||||||
const { kind, name, namespace, fieldPath } = involvedObject;
|
const { kind, name, namespace, fieldPath } = involvedObject;
|
||||||
|
|
||||||
|
|||||||
@ -23,10 +23,11 @@ import "./kube-event-details.scss";
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { eventStore } from "./event.store";
|
import { eventStore } from "./event.store";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
export interface KubeEventDetailsProps {
|
export interface KubeEventDetailsProps {
|
||||||
object: KubeObject;
|
object: KubeObject;
|
||||||
@ -40,6 +41,17 @@ export class KubeEventDetails extends React.Component<KubeEventDetailsProps> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { object } = this.props;
|
const { object } = this.props;
|
||||||
|
|
||||||
|
if (!object) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(object instanceof KubeObject)) {
|
||||||
|
logger.error("[KubeEventDetails]: passed object that is not an instanceof KubeObject", object);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const events = eventStore.getEventsByObject(object);
|
const events = eventStore.getEventsByObject(object);
|
||||||
|
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
|
|||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Namespace> {
|
interface Props extends KubeObjectDetailsProps<Namespace> {
|
||||||
}
|
}
|
||||||
@ -81,7 +82,16 @@ export class NamespaceDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: namespace } = this.props;
|
const { object: namespace } = this.props;
|
||||||
|
|
||||||
if (!namespace) return null;
|
if (!namespace) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(namespace instanceof Namespace)) {
|
||||||
|
logger.error("[NamespaceDetails]: passed object that is not an instanceof Namespace", namespace);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const status = namespace.getStatus();
|
const status = namespace.getStatus();
|
||||||
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Namespace);
|
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Namespace);
|
||||||
|
|
||||||
|
|||||||
@ -25,9 +25,10 @@ import React from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { DrawerTitle } from "../drawer";
|
import { DrawerTitle } from "../drawer";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { Endpoint } from "../../../common/k8s-api/endpoints";
|
import { Endpoint } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { EndpointSubsetList } from "./endpoint-subset-list";
|
import { EndpointSubsetList } from "./endpoint-subset-list";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Endpoint> {
|
interface Props extends KubeObjectDetailsProps<Endpoint> {
|
||||||
}
|
}
|
||||||
@ -37,7 +38,15 @@ export class EndpointDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: endpoint } = this.props;
|
const { object: endpoint } = this.props;
|
||||||
|
|
||||||
if (!endpoint) return null;
|
if (!endpoint) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(endpoint instanceof Endpoint)) {
|
||||||
|
logger.error("[EndpointDetails]: passed object that is not an instanceof Endpoint", endpoint);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="EndpointDetails">
|
<div className="EndpointDetails">
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import React from "react";
|
|||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
import { makeObservable, observable, reaction } from "mobx";
|
import { makeObservable, observable, reaction } from "mobx";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import type { ILoadBalancerIngress, Ingress } from "../../../common/k8s-api/endpoints";
|
import { ILoadBalancerIngress, Ingress } from "../../../common/k8s-api/endpoints";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { ResourceMetrics } from "../resource-metrics";
|
import { ResourceMetrics } from "../resource-metrics";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
@ -35,6 +35,7 @@ import { getBackendServiceNamePort, getMetricsForIngress, IIngressMetrics } from
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Ingress> {
|
interface Props extends KubeObjectDetailsProps<Ingress> {
|
||||||
}
|
}
|
||||||
@ -132,6 +133,12 @@ export class IngressDetails extends React.Component<Props> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(ingress instanceof Ingress)) {
|
||||||
|
logger.error("[IngressDetails]: passed object that is not an instanceof Ingress", ingress);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { spec, status } = ingress;
|
const { spec, status } = ingress;
|
||||||
const ingressPoints = status?.loadBalancer?.ingress;
|
const ingressPoints = status?.loadBalancer?.ingress;
|
||||||
const { metrics } = this;
|
const { metrics } = this;
|
||||||
|
|||||||
@ -24,12 +24,13 @@ import "./network-policy-details.scss";
|
|||||||
import get from "lodash/get";
|
import get from "lodash/get";
|
||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import type { IPolicyEgress, IPolicyIngress, IPolicyIpBlock, IPolicySelector, NetworkPolicy } from "../../../common/k8s-api/endpoints/network-policy.api";
|
import { IPolicyEgress, IPolicyIngress, IPolicyIpBlock, IPolicySelector, NetworkPolicy } from "../../../common/k8s-api/endpoints/network-policy.api";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { SubTitle } from "../layout/sub-title";
|
import { SubTitle } from "../layout/sub-title";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<NetworkPolicy> {
|
interface Props extends KubeObjectDetailsProps<NetworkPolicy> {
|
||||||
}
|
}
|
||||||
@ -117,6 +118,13 @@ export class NetworkPolicyDetails extends React.Component<Props> {
|
|||||||
if (!policy) {
|
if (!policy) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(policy instanceof NetworkPolicy)) {
|
||||||
|
logger.error("[NetworkPolicyDetails]: passed object that is not an instanceof NetworkPolicy", policy);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { ingress, egress } = policy.spec;
|
const { ingress, egress } = policy.spec;
|
||||||
const selector = policy.getMatchLabels();
|
const selector = policy.getMatchLabels();
|
||||||
|
|
||||||
|
|||||||
@ -26,12 +26,13 @@ import { disposeOnUnmount, observer } from "mobx-react";
|
|||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { Service } from "../../../common/k8s-api/endpoints";
|
import { Service } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { ServicePortComponent } from "./service-port-component";
|
import { ServicePortComponent } from "./service-port-component";
|
||||||
import { endpointStore } from "../+network-endpoints/endpoints.store";
|
import { endpointStore } from "../+network-endpoints/endpoints.store";
|
||||||
import { ServiceDetailsEndpoint } from "./service-details-endpoint";
|
import { ServiceDetailsEndpoint } from "./service-details-endpoint";
|
||||||
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Service> {
|
interface Props extends KubeObjectDetailsProps<Service> {
|
||||||
}
|
}
|
||||||
@ -52,7 +53,16 @@ export class ServiceDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: service } = this.props;
|
const { object: service } = this.props;
|
||||||
|
|
||||||
if (!service) return null;
|
if (!service) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(service instanceof Service)) {
|
||||||
|
logger.error("[ServiceDetails]: passed object that is not an instanceof Service", service);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { spec } = service;
|
const { spec } = service;
|
||||||
const endpoint = endpointStore.getByName(service.getName(), service.getNs());
|
const endpoint = endpointStore.getByName(service.getName(), service.getNs());
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
|||||||
import { NodeDetailsResources } from "./node-details-resources";
|
import { NodeDetailsResources } from "./node-details-resources";
|
||||||
import { DrawerTitle } from "../drawer/drawer-title";
|
import { DrawerTitle } from "../drawer/drawer-title";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Node> {
|
interface Props extends KubeObjectDetailsProps<Node> {
|
||||||
}
|
}
|
||||||
@ -72,7 +73,16 @@ export class NodeDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: node } = this.props;
|
const { object: node } = this.props;
|
||||||
|
|
||||||
if (!node) return null;
|
if (!node) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(node instanceof Node)) {
|
||||||
|
logger.error("[NodeDetails]: passed object that is not an instanceof Node", node);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { status } = node;
|
const { status } = node;
|
||||||
const { nodeInfo, addresses } = status;
|
const { nodeInfo, addresses } = status;
|
||||||
const conditions = node.getActiveConditions();
|
const conditions = node.getActiveConditions();
|
||||||
|
|||||||
@ -25,22 +25,26 @@ import React from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { DrawerItem, DrawerTitle } from "../drawer";
|
import { DrawerItem, DrawerTitle } from "../drawer";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { PodSecurityPolicy } from "../../../common/k8s-api/endpoints";
|
import { PodSecurityPolicy } from "../../../common/k8s-api/endpoints";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { Table, TableCell, TableHead, TableRow } from "../table";
|
import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PodSecurityPolicy> {
|
interface Props extends KubeObjectDetailsProps<PodSecurityPolicy> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RuleGroup {
|
||||||
|
rule: string;
|
||||||
|
ranges?: {
|
||||||
|
max: number;
|
||||||
|
min: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class PodSecurityPolicyDetails extends React.Component<Props> {
|
export class PodSecurityPolicyDetails extends React.Component<Props> {
|
||||||
renderRuleGroup(
|
renderRuleGroup( title: React.ReactNode, group: RuleGroup) {
|
||||||
title: React.ReactNode,
|
|
||||||
group: {
|
|
||||||
rule: string;
|
|
||||||
ranges?: { max: number; min: number }[];
|
|
||||||
}) {
|
|
||||||
if (!group) return null;
|
if (!group) return null;
|
||||||
const { rule, ranges } = group;
|
const { rule, ranges } = group;
|
||||||
|
|
||||||
@ -67,6 +71,13 @@ export class PodSecurityPolicyDetails extends React.Component<Props> {
|
|||||||
if (!psp) {
|
if (!psp) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(psp instanceof PodSecurityPolicy)) {
|
||||||
|
logger.error("[PodSecurityPolicyDetails]: passed object that is not an instanceof PodSecurityPolicy", psp);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
allowedHostPaths, allowedCapabilities, allowedCSIDrivers, allowedFlexVolumes, allowedProcMountTypes,
|
allowedHostPaths, allowedCapabilities, allowedCSIDrivers, allowedFlexVolumes, allowedProcMountTypes,
|
||||||
allowedUnsafeSysctls, allowPrivilegeEscalation, defaultAddCapabilities, forbiddenSysctls, fsGroup,
|
allowedUnsafeSysctls, allowPrivilegeEscalation, defaultAddCapabilities, forbiddenSysctls, fsGroup,
|
||||||
@ -172,14 +183,14 @@ export class PodSecurityPolicyDetails extends React.Component<Props> {
|
|||||||
<TableCell>Path Prefix</TableCell>
|
<TableCell>Path Prefix</TableCell>
|
||||||
<TableCell>Read-only</TableCell>
|
<TableCell>Read-only</TableCell>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
{allowedHostPaths.map(({ pathPrefix, readOnly }, index) => {
|
{
|
||||||
return (
|
allowedHostPaths.map(({ pathPrefix, readOnly }, index) => (
|
||||||
<TableRow key={index}>
|
<TableRow key={index}>
|
||||||
<TableCell>{pathPrefix}</TableCell>
|
<TableCell>{pathPrefix}</TableCell>
|
||||||
<TableCell>{readOnly ? "Yes" : "No"}</TableCell>
|
<TableCell>{readOnly ? "Yes" : "No"}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
))
|
||||||
})}
|
}
|
||||||
</Table>
|
</Table>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -193,7 +204,7 @@ export class PodSecurityPolicyDetails extends React.Component<Props> {
|
|||||||
<>
|
<>
|
||||||
<DrawerTitle title="Runtime Class"/>
|
<DrawerTitle title="Runtime Class"/>
|
||||||
<DrawerItem name="Allowed Runtime Class Names">
|
<DrawerItem name="Allowed Runtime Class Names">
|
||||||
{(runtimeClass.allowedRuntimeClassNames || []).join(", ") || "-"}
|
{runtimeClass.allowedRuntimeClassNames?.join(", ") || "-"}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Default Runtime Class Name">
|
<DrawerItem name="Default Runtime Class Name">
|
||||||
{runtimeClass.defaultRuntimeClassName || "-"}
|
{runtimeClass.defaultRuntimeClassName || "-"}
|
||||||
|
|||||||
@ -27,11 +27,12 @@ import { DrawerItem, DrawerTitle } from "../drawer";
|
|||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import type { StorageClass } from "../../../common/k8s-api/endpoints";
|
import { StorageClass } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { storageClassStore } from "./storage-class.store";
|
import { storageClassStore } from "./storage-class.store";
|
||||||
import { VolumeDetailsList } from "../+storage-volumes/volume-details-list";
|
import { VolumeDetailsList } from "../+storage-volumes/volume-details-list";
|
||||||
import { volumesStore } from "../+storage-volumes/volumes.store";
|
import { volumesStore } from "../+storage-volumes/volumes.store";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<StorageClass> {
|
interface Props extends KubeObjectDetailsProps<StorageClass> {
|
||||||
}
|
}
|
||||||
@ -44,9 +45,18 @@ export class StorageClassDetails extends React.Component<Props> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { object: storageClass } = this.props;
|
const { object: storageClass } = this.props;
|
||||||
const persistentVolumes = storageClassStore.getPersistentVolumes(storageClass);
|
|
||||||
|
|
||||||
if (!storageClass) return null;
|
if (!storageClass) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(storageClass instanceof StorageClass)) {
|
||||||
|
logger.error("[StorageClassDetails]: passed object that is not an instanceof StorageClass", storageClass);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistentVolumes = storageClassStore.getPersistentVolumes(storageClass);
|
||||||
const { provisioner, parameters, mountOptions } = storageClass;
|
const { provisioner, parameters, mountOptions } = storageClass;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
|||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PersistentVolumeClaim> {
|
interface Props extends KubeObjectDetailsProps<PersistentVolumeClaim> {
|
||||||
}
|
}
|
||||||
@ -68,6 +69,13 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
|||||||
if (!volumeClaim) {
|
if (!volumeClaim) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(volumeClaim instanceof PersistentVolumeClaim)) {
|
||||||
|
logger.error("[PersistentVolumeClaimDetails]: passed object that is not an instanceof PersistentVolumeClaim", volumeClaim);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { storageClassName, accessModes } = volumeClaim.spec;
|
const { storageClassName, accessModes } = volumeClaim.spec;
|
||||||
const { metrics } = this;
|
const { metrics } = this;
|
||||||
const pods = volumeClaim.getPods(podsStore.items);
|
const pods = volumeClaim.getPods(podsStore.items);
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import { PersistentVolume, pvcApi } from "../../../common/k8s-api/endpoints";
|
|||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<PersistentVolume> {
|
interface Props extends KubeObjectDetailsProps<PersistentVolume> {
|
||||||
}
|
}
|
||||||
@ -43,6 +44,13 @@ export class PersistentVolumeDetails extends React.Component<Props> {
|
|||||||
if (!volume) {
|
if (!volume) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(volume instanceof PersistentVolume)) {
|
||||||
|
logger.error("[PersistentVolumeDetails]: passed object that is not an instanceof PersistentVolume", volume);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { accessModes, capacity, persistentVolumeReclaimPolicy, storageClassName, claimRef, flexVolume, mountOptions, nfs } = volume.spec;
|
const { accessModes, capacity, persistentVolumeReclaimPolicy, storageClassName, claimRef, flexVolume, mountOptions, nfs } = volume.spec;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -31,8 +31,9 @@ import { Link } from "react-router-dom";
|
|||||||
import { cronJobStore } from "./cronjob.store";
|
import { cronJobStore } from "./cronjob.store";
|
||||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import type { CronJob, Job } from "../../../common/k8s-api/endpoints";
|
import { CronJob, Job } from "../../../common/k8s-api/endpoints";
|
||||||
import { KubeObjectMeta } from "../kube-object-meta";
|
import { KubeObjectMeta } from "../kube-object-meta";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<CronJob> {
|
interface Props extends KubeObjectDetailsProps<CronJob> {
|
||||||
}
|
}
|
||||||
@ -46,18 +47,27 @@ export class CronJobDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: cronJob } = this.props;
|
const { object: cronJob } = this.props;
|
||||||
|
|
||||||
if (!cronJob) return null;
|
if (!cronJob) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(cronJob instanceof CronJob)) {
|
||||||
|
logger.error("[CronJobDetails]: passed object that is not an instanceof CronJob", cronJob);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const childJobs = jobStore.getJobsByOwner(cronJob);
|
const childJobs = jobStore.getJobsByOwner(cronJob);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="CronJobDetails">
|
<div className="CronJobDetails">
|
||||||
<KubeObjectMeta object={cronJob}/>
|
<KubeObjectMeta object={cronJob}/>
|
||||||
<DrawerItem name="Schedule">
|
<DrawerItem name="Schedule">
|
||||||
{cronJob.isNeverRun() ? (
|
{
|
||||||
<>
|
cronJob.isNeverRun()
|
||||||
never ({cronJob.getSchedule()})
|
? `never (${cronJob.getSchedule()})`
|
||||||
</>
|
: cronJob.getSchedule()
|
||||||
) : cronJob.getSchedule()}
|
}
|
||||||
</DrawerItem>
|
</DrawerItem>
|
||||||
<DrawerItem name="Active">
|
<DrawerItem name="Active">
|
||||||
{cronJobStore.getActiveJobsNum(cronJob)}
|
{cronJobStore.getActiveJobsNum(cronJob)}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import { KubeObjectMeta } from "../kube-object-meta";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
||||||
}
|
}
|
||||||
@ -72,7 +73,16 @@ export class DaemonSetDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: daemonSet } = this.props;
|
const { object: daemonSet } = this.props;
|
||||||
|
|
||||||
if (!daemonSet) return null;
|
if (!daemonSet) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(daemonSet instanceof DaemonSet)) {
|
||||||
|
logger.error("[DaemonSetDetails]: passed object that is not an instanceof DaemonSet", daemonSet);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { spec } = daemonSet;
|
const { spec } = daemonSet;
|
||||||
const selectors = daemonSet.getSelectors();
|
const selectors = daemonSet.getSelectors();
|
||||||
const images = daemonSet.getImages();
|
const images = daemonSet.getImages();
|
||||||
|
|||||||
@ -42,6 +42,7 @@ import { DeploymentReplicaSets } from "./deployment-replicasets";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Deployment> {
|
interface Props extends KubeObjectDetailsProps<Deployment> {
|
||||||
}
|
}
|
||||||
@ -75,7 +76,16 @@ export class DeploymentDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: deployment } = this.props;
|
const { object: deployment } = this.props;
|
||||||
|
|
||||||
if (!deployment) return null;
|
if (!deployment) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(deployment instanceof Deployment)) {
|
||||||
|
logger.error("[DeploymentDetails]: passed object that is not an instanceof Deployment", deployment);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { status, spec } = deployment;
|
const { status, spec } = deployment;
|
||||||
const nodeSelector = deployment.getNodeSelectors();
|
const nodeSelector = deployment.getNodeSelectors();
|
||||||
const selectors = deployment.getSelectors();
|
const selectors = deployment.getSelectors();
|
||||||
|
|||||||
@ -112,6 +112,7 @@ const dummyDeployment: Deployment = {
|
|||||||
toPlainObject: jest.fn(),
|
toPlainObject: jest.fn(),
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
delete: jest.fn(),
|
delete: jest.fn(),
|
||||||
|
patch: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("<DeploymentScaleDialog />", () => {
|
describe("<DeploymentScaleDialog />", () => {
|
||||||
|
|||||||
@ -44,6 +44,7 @@ import { ResourceMetrics } from "../resource-metrics";
|
|||||||
import { boundMethod } from "autobind-decorator";
|
import { boundMethod } from "autobind-decorator";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Job> {
|
interface Props extends KubeObjectDetailsProps<Job> {
|
||||||
}
|
}
|
||||||
@ -71,7 +72,16 @@ export class JobDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: job } = this.props;
|
const { object: job } = this.props;
|
||||||
|
|
||||||
if (!job) return null;
|
if (!job) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(job instanceof Job)) {
|
||||||
|
logger.error("[JobDetails]: passed object that is not an instanceof Job", job);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const selectors = job.getSelectors();
|
const selectors = job.getSelectors();
|
||||||
const nodeSelector = job.getNodeSelectors();
|
const nodeSelector = job.getNodeSelectors();
|
||||||
const images = job.getImages();
|
const images = job.getImages();
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import { KubeObjectMeta } from "../kube-object-meta";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<Pod> {
|
interface Props extends KubeObjectDetailsProps<Pod> {
|
||||||
}
|
}
|
||||||
@ -77,7 +78,16 @@ export class PodDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: pod } = this.props;
|
const { object: pod } = this.props;
|
||||||
|
|
||||||
if (!pod) return null;
|
if (!pod) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(pod instanceof Pod)) {
|
||||||
|
logger.error("[PodDetails]: passed object that is not an instanceof Pod", pod);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { status, spec } = pod;
|
const { status, spec } = pod;
|
||||||
const { conditions, podIP } = status;
|
const { conditions, podIP } = status;
|
||||||
const podIPs = pod.getIPs();
|
const podIPs = pod.getIPs();
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import { KubeObjectMeta } from "../kube-object-meta";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<ReplicaSet> {
|
interface Props extends KubeObjectDetailsProps<ReplicaSet> {
|
||||||
}
|
}
|
||||||
@ -71,7 +72,16 @@ export class ReplicaSetDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: replicaSet } = this.props;
|
const { object: replicaSet } = this.props;
|
||||||
|
|
||||||
if (!replicaSet) return null;
|
if (!replicaSet) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(replicaSet instanceof ReplicaSet)) {
|
||||||
|
logger.error("[ReplicaSetDetails]: passed object that is not an instanceof ReplicaSet", replicaSet);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const { metrics } = this;
|
const { metrics } = this;
|
||||||
const { status } = replicaSet;
|
const { status } = replicaSet;
|
||||||
const { availableReplicas, replicas } = status;
|
const { availableReplicas, replicas } = status;
|
||||||
|
|||||||
@ -107,6 +107,7 @@ const dummyReplicaSet: ReplicaSet = {
|
|||||||
toPlainObject: jest.fn(),
|
toPlainObject: jest.fn(),
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
delete: jest.fn(),
|
delete: jest.fn(),
|
||||||
|
patch: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("<ReplicaSetScaleDialog />", () => {
|
describe("<ReplicaSetScaleDialog />", () => {
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import { KubeObjectMeta } from "../kube-object-meta";
|
|||||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||||
import { boundMethod } from "../../utils";
|
import { boundMethod } from "../../utils";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props extends KubeObjectDetailsProps<StatefulSet> {
|
interface Props extends KubeObjectDetailsProps<StatefulSet> {
|
||||||
}
|
}
|
||||||
@ -72,7 +73,16 @@ export class StatefulSetDetails extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { object: statefulSet } = this.props;
|
const { object: statefulSet } = this.props;
|
||||||
|
|
||||||
if (!statefulSet) return null;
|
if (!statefulSet) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(statefulSet instanceof StatefulSet)) {
|
||||||
|
logger.error("[StatefulSetDetails]: passed object that is not an instanceof StatefulSet", statefulSet);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const images = statefulSet.getImages();
|
const images = statefulSet.getImages();
|
||||||
const selectors = statefulSet.getSelectors();
|
const selectors = statefulSet.getSelectors();
|
||||||
const nodeSelector = statefulSet.getNodeSelectors();
|
const nodeSelector = statefulSet.getNodeSelectors();
|
||||||
|
|||||||
@ -117,6 +117,7 @@ const dummyStatefulSet: StatefulSet = {
|
|||||||
toPlainObject: jest.fn(),
|
toPlainObject: jest.fn(),
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
delete: jest.fn(),
|
delete: jest.fn(),
|
||||||
|
patch: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("<StatefulSetScaleDialog />", () => {
|
describe("<StatefulSetScaleDialog />", () => {
|
||||||
|
|||||||
@ -25,9 +25,11 @@ import { Pod } from "../../../../common/k8s-api/endpoints";
|
|||||||
import { ThemeStore } from "../../../theme.store";
|
import { ThemeStore } from "../../../theme.store";
|
||||||
import { dockStore } from "../dock.store";
|
import { dockStore } from "../dock.store";
|
||||||
import { logTabStore } from "../log-tab.store";
|
import { logTabStore } from "../log-tab.store";
|
||||||
import { TerminalStore } from "../terminal.store";
|
|
||||||
import { deploymentPod1, deploymentPod2, deploymentPod3, dockerPod } from "./pod.mock";
|
import { deploymentPod1, deploymentPod2, deploymentPod3, dockerPod } from "./pod.mock";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
|
import { mockWindow } from "../../../../../__mocks__/windowMock";
|
||||||
|
|
||||||
|
mockWindow();
|
||||||
|
|
||||||
jest.mock("react-monaco-editor", () => null);
|
jest.mock("react-monaco-editor", () => null);
|
||||||
|
|
||||||
@ -45,7 +47,6 @@ describe("log tab store", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
UserStore.createInstance();
|
UserStore.createInstance();
|
||||||
ThemeStore.createInstance();
|
ThemeStore.createInstance();
|
||||||
TerminalStore.createInstance();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -53,7 +54,6 @@ describe("log tab store", () => {
|
|||||||
dockStore.reset();
|
dockStore.reset();
|
||||||
UserStore.resetInstance();
|
UserStore.resetInstance();
|
||||||
ThemeStore.resetInstance();
|
ThemeStore.resetInstance();
|
||||||
TerminalStore.resetInstance();
|
|
||||||
fse.remove("tmp");
|
fse.remove("tmp");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -135,11 +135,11 @@ describe("log tab store", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// FIXME: this is failed when it's not .only == depends on something above
|
// FIXME: this is failed when it's not .only == depends on something above
|
||||||
it.only("closes tab if no pods left in store", () => {
|
it.only("closes tab if no pods left in store", async () => {
|
||||||
const selectedPod = new Pod(deploymentPod1);
|
const selectedPod = new Pod(deploymentPod1);
|
||||||
const selectedContainer = selectedPod.getInitContainers()[0];
|
const selectedContainer = selectedPod.getInitContainers()[0];
|
||||||
|
|
||||||
logTabStore.createPodTab({
|
const id = logTabStore.createPodTab({
|
||||||
selectedPod,
|
selectedPod,
|
||||||
selectedContainer
|
selectedContainer
|
||||||
});
|
});
|
||||||
@ -147,6 +147,7 @@ describe("log tab store", () => {
|
|||||||
podsStore.items.clear();
|
podsStore.items.clear();
|
||||||
|
|
||||||
expect(logTabStore.getData(dockStore.selectedTabId)).toBeUndefined();
|
expect(logTabStore.getData(dockStore.selectedTabId)).toBeUndefined();
|
||||||
expect(dockStore.getTabById(dockStore.selectedTabId)).toBeUndefined();
|
expect(logTabStore.getData(id)).toBeUndefined();
|
||||||
|
expect(dockStore.getTabById(id)).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -33,10 +33,10 @@ import { createResourceStore } from "./create-resource.store";
|
|||||||
import type { DockTab } from "./dock.store";
|
import type { DockTab } from "./dock.store";
|
||||||
import { EditorPanel } from "./editor-panel";
|
import { EditorPanel } from "./editor-panel";
|
||||||
import { InfoPanel } from "./info-panel";
|
import { InfoPanel } from "./info-panel";
|
||||||
import { resourceApplierApi } from "../../../common/k8s-api/endpoints/resource-applier.api";
|
import * as resourceApplierApi from "../../../common/k8s-api/endpoints/resource-applier.api";
|
||||||
import type { JsonApiErrorParsed } from "../../../common/k8s-api/json-api";
|
|
||||||
import { Notifications } from "../notifications";
|
import { Notifications } from "../notifications";
|
||||||
import { monacoModelsManager } from "./monaco-model-manager";
|
import { monacoModelsManager } from "./monaco-model-manager";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -95,7 +95,7 @@ export class CreateResource extends React.Component<Props> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
create = async () => {
|
create = async (): Promise<undefined> => {
|
||||||
if (this.error || !this.data.trim()) {
|
if (this.error || !this.data.trim()) {
|
||||||
// do not save when field is empty or there is an error
|
// do not save when field is empty or there is an error
|
||||||
return null;
|
return null;
|
||||||
@ -103,31 +103,31 @@ export class CreateResource extends React.Component<Props> {
|
|||||||
|
|
||||||
// skip empty documents if "---" pasted at the beginning or end
|
// skip empty documents if "---" pasted at the beginning or end
|
||||||
const resources = jsYaml.safeLoadAll(this.data).filter(Boolean);
|
const resources = jsYaml.safeLoadAll(this.data).filter(Boolean);
|
||||||
const createdResources: string[] = [];
|
|
||||||
const errors: string[] = [];
|
|
||||||
|
|
||||||
await Promise.all(
|
if (resources.length === 0) {
|
||||||
resources.map(data => {
|
return void logger.info("Nothing to create");
|
||||||
return resourceApplierApi.update(data)
|
|
||||||
.then(item => createdResources.push(item.metadata.name))
|
|
||||||
.catch((err: JsonApiErrorParsed) => errors.push(err.toString()));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (errors.length) {
|
|
||||||
errors.forEach(error => Notifications.error(error));
|
|
||||||
if (!createdResources.length) throw errors[0];
|
|
||||||
}
|
}
|
||||||
const successMessage = (
|
|
||||||
<p>
|
|
||||||
{createdResources.length === 1 ? "Resource" : "Resources"}{" "}
|
|
||||||
<b>{createdResources.join(", ")}</b> successfully created
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
|
|
||||||
Notifications.ok(successMessage);
|
const createdResources: string[] = [];
|
||||||
|
|
||||||
return successMessage;
|
for (const result of await Promise.allSettled(resources.map(resourceApplierApi.update))) {
|
||||||
|
if (result.status === "fulfilled") {
|
||||||
|
createdResources.push(result.value.metadata.name);
|
||||||
|
} else {
|
||||||
|
Notifications.error(result.reason.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createdResources.length > 0) {
|
||||||
|
Notifications.ok((
|
||||||
|
<p>
|
||||||
|
{createdResources.length === 1 ? "Resource" : "Resources"}{" "}
|
||||||
|
<b>{createdResources.join(", ")}</b> successfully created
|
||||||
|
</p>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderControls(){
|
renderControls(){
|
||||||
|
|||||||
@ -160,7 +160,7 @@ export class DockStore implements DockStorageState {
|
|||||||
window.addEventListener("resize", throttle(this.adjustHeight, 250));
|
window.addEventListener("resize", throttle(this.adjustHeight, 250));
|
||||||
// create monaco models
|
// create monaco models
|
||||||
this.whenReady.then(() => {this.tabs.forEach(tab => {
|
this.whenReady.then(() => {this.tabs.forEach(tab => {
|
||||||
if (this.usesMonacoEditor(tab)) {
|
if (this.usesMonacoEditor(tab)) {
|
||||||
monacoModelsManager.addModel(tab.id);
|
monacoModelsManager.addModel(tab.id);
|
||||||
}
|
}
|
||||||
});});
|
});});
|
||||||
@ -274,7 +274,7 @@ export class DockStore implements DockStorageState {
|
|||||||
title
|
title
|
||||||
};
|
};
|
||||||
|
|
||||||
// add monaco model
|
// add monaco model
|
||||||
if (this.usesMonacoEditor(tab)) {
|
if (this.usesMonacoEditor(tab)) {
|
||||||
monacoModelsManager.addModel(id);
|
monacoModelsManager.addModel(id);
|
||||||
}
|
}
|
||||||
@ -287,14 +287,14 @@ export class DockStore implements DockStorageState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async closeTab(tabId: TabId) {
|
closeTab(tabId: TabId) {
|
||||||
const tab = this.getTabById(tabId);
|
const tab = this.getTabById(tabId);
|
||||||
|
|
||||||
if (!tab || tab.pinned) {
|
if (!tab || tab.pinned) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove monaco model
|
// remove monaco model
|
||||||
if (this.usesMonacoEditor(tab)) {
|
if (this.usesMonacoEditor(tab)) {
|
||||||
monacoModelsManager.removeModel(tabId);
|
monacoModelsManager.removeModel(tabId);
|
||||||
}
|
}
|
||||||
@ -305,12 +305,6 @@ export class DockStore implements DockStorageState {
|
|||||||
if (this.tabs.length) {
|
if (this.tabs.length) {
|
||||||
const newTab = this.tabs.slice(-1)[0]; // last
|
const newTab = this.tabs.slice(-1)[0]; // last
|
||||||
|
|
||||||
if (newTab?.kind === TabKind.TERMINAL) {
|
|
||||||
// close the dock when selected sibling inactive terminal tab
|
|
||||||
const { TerminalStore } = await import("./terminal.store");
|
|
||||||
|
|
||||||
if (!TerminalStore.getInstance(false)?.isConnected(newTab.id)) this.close();
|
|
||||||
}
|
|
||||||
this.selectTab(newTab.id);
|
this.selectTab(newTab.id);
|
||||||
} else {
|
} else {
|
||||||
this.selectedTabId = null;
|
this.selectedTabId = null;
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import {monacoModelsManager} from "./monaco-model-manager";
|
|||||||
export interface EditingResource {
|
export interface EditingResource {
|
||||||
resource: string; // resource path, e.g. /api/v1/namespaces/default
|
resource: string; // resource path, e.g. /api/v1/namespaces/default
|
||||||
draft?: string; // edited draft in yaml
|
draft?: string; // edited draft in yaml
|
||||||
|
firstDraft?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EditResourceStore extends DockTabStore<EditingResource> {
|
export class EditResourceStore extends DockTabStore<EditingResource> {
|
||||||
@ -106,6 +107,10 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
|
|||||||
return dockStore.getTabById(tabId);
|
return dockStore.getTabById(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearInitialDraft(tabId: TabId): void {
|
||||||
|
delete this.getData(tabId)?.firstDraft;
|
||||||
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
super.reset();
|
super.reset();
|
||||||
Array.from(this.watchers).forEach(([tabId, dispose]) => {
|
Array.from(this.watchers).forEach(([tabId, dispose]) => {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import "./edit-resource.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { action, computed, makeObservable, observable } from "mobx";
|
import { action, computed, makeObservable, observable } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import jsYaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import type { DockTab } from "./dock.store";
|
import type { DockTab } from "./dock.store";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { editResourceStore } from "./edit-resource.store";
|
import { editResourceStore } from "./edit-resource.store";
|
||||||
@ -33,6 +33,7 @@ import { Badge } from "../badge";
|
|||||||
import { EditorPanel } from "./editor-panel";
|
import { EditorPanel } from "./editor-panel";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
|
import { createPatch } from "rfc6902";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -71,16 +72,17 @@ export class EditResource extends React.Component<Props> {
|
|||||||
return draft;
|
return draft;
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsYaml.safeDump(this.resource.toPlainObject()); // dump resource first time
|
return yaml.safeDump(this.resource.toPlainObject()); // dump resource first time
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
saveDraft(draft: string | object) {
|
saveDraft(draft: string | object) {
|
||||||
if (typeof draft === "object") {
|
if (typeof draft === "object") {
|
||||||
draft = draft ? jsYaml.safeDump(draft) : undefined;
|
draft = draft ? yaml.safeDump(draft) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
editResourceStore.setData(this.tabId, {
|
editResourceStore.setData(this.tabId, {
|
||||||
|
firstDraft: draft, // this must be before the next line
|
||||||
...editResourceStore.getData(this.tabId),
|
...editResourceStore.getData(this.tabId),
|
||||||
draft,
|
draft,
|
||||||
});
|
});
|
||||||
@ -95,16 +97,18 @@ export class EditResource extends React.Component<Props> {
|
|||||||
if (this.error) {
|
if (this.error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const store = editResourceStore.getStore(this.tabId);
|
|
||||||
const updatedResource: KubeObject = await store.update(this.resource, jsYaml.safeLoad(this.draft));
|
|
||||||
|
|
||||||
this.saveDraft(updatedResource.toPlainObject()); // update with new resourceVersion to avoid further errors on save
|
const store = editResourceStore.getStore(this.tabId);
|
||||||
const resourceType = updatedResource.kind;
|
const currentVersion = yaml.safeLoad(this.draft);
|
||||||
const resourceName = updatedResource.getName();
|
const firstVersion = yaml.safeLoad(editResourceStore.getData(this.tabId).firstDraft ?? this.draft);
|
||||||
|
const patches = createPatch(firstVersion, currentVersion);
|
||||||
|
const updatedResource = await store.patch(this.resource, patches);
|
||||||
|
|
||||||
|
editResourceStore.clearInitialDraft(this.tabId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
{resourceType} <b>{resourceName}</b> updated.
|
{updatedResource.kind} <b>{updatedResource.getName()}</b> updated.
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -126,9 +130,9 @@ export class EditResource extends React.Component<Props> {
|
|||||||
submittingMessage="Applying.."
|
submittingMessage="Applying.."
|
||||||
controls={(
|
controls={(
|
||||||
<div className="resource-info flex gaps align-center">
|
<div className="resource-info flex gaps align-center">
|
||||||
<span>Kind:</span> <Badge label={resource.kind}/>
|
<span>Kind:</span><Badge label={resource.kind}/>
|
||||||
<span>Name:</span><Badge label={resource.getName()}/>
|
<span>Name:</span><Badge label={resource.getName()}/>
|
||||||
<span>Namespace:</span> <Badge label={resource.getNs() || "global"}/>
|
<span>Namespace:</span><Badge label={resource.getNs() || "global"}/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -91,10 +91,7 @@ export class EditorPanel extends React.Component<Props> {
|
|||||||
|
|
||||||
onChange = (value: string) => {
|
onChange = (value: string) => {
|
||||||
this.validate(value);
|
this.validate(value);
|
||||||
|
this.props.onChange?.(value, this.yamlError);
|
||||||
if (this.props.onChange) {
|
|
||||||
this.props.onChange(value, this.yamlError);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import { podsStore } from "../+workloads-pods/pods.store";
|
|||||||
|
|
||||||
import { IPodContainer, Pod } from "../../../common/k8s-api/endpoints";
|
import { IPodContainer, Pod } from "../../../common/k8s-api/endpoints";
|
||||||
import type { WorkloadKubeObject } from "../../../common/k8s-api/workload-kube-object";
|
import type { WorkloadKubeObject } from "../../../common/k8s-api/workload-kube-object";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
import { DockTabStore } from "./dock-tab.store";
|
import { DockTabStore } from "./dock-tab.store";
|
||||||
import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store";
|
import { dockStore, DockTabCreateSpecific, TabKind } from "./dock.store";
|
||||||
|
|
||||||
@ -51,17 +52,15 @@ export class LogTabStore extends DockTabStore<LogTabData> {
|
|||||||
storageKey: "pod_logs"
|
storageKey: "pod_logs"
|
||||||
});
|
});
|
||||||
|
|
||||||
reaction(() => podsStore.items.length, () => {
|
reaction(() => podsStore.items.length, () => this.updateTabsData());
|
||||||
this.updateTabsData();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createPodTab({ selectedPod, selectedContainer }: PodLogsTabData): void {
|
createPodTab({ selectedPod, selectedContainer }: PodLogsTabData): string {
|
||||||
const podOwner = selectedPod.getOwnerRefs()[0];
|
const podOwner = selectedPod.getOwnerRefs()[0];
|
||||||
const pods = podsStore.getPodsByOwnerId(podOwner?.uid);
|
const pods = podsStore.getPodsByOwnerId(podOwner?.uid);
|
||||||
const title = `Pod ${selectedPod.getName()}`;
|
const title = `Pod ${selectedPod.getName()}`;
|
||||||
|
|
||||||
this.createLogsTab(title, {
|
return this.createLogsTab(title, {
|
||||||
pods: pods.length ? pods : [selectedPod],
|
pods: pods.length ? pods : [selectedPod],
|
||||||
selectedPod,
|
selectedPod,
|
||||||
selectedContainer
|
selectedContainer
|
||||||
@ -95,9 +94,9 @@ export class LogTabStore extends DockTabStore<LogTabData> {
|
|||||||
...tabParams,
|
...tabParams,
|
||||||
kind: TabKind.POD_LOGS,
|
kind: TabKind.POD_LOGS,
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createLogsTab(title: string, data: LogTabData) {
|
private createLogsTab(title: string, data: LogTabData): string {
|
||||||
const id = uniqueId("log-tab-");
|
const id = uniqueId("log-tab-");
|
||||||
|
|
||||||
this.createDockTab({ id, title });
|
this.createDockTab({ id, title });
|
||||||
@ -106,38 +105,45 @@ export class LogTabStore extends DockTabStore<LogTabData> {
|
|||||||
showTimestamps: false,
|
showTimestamps: false,
|
||||||
previous: false
|
previous: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateTabsData() {
|
private updateTabsData() {
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
|
|
||||||
for (const [tabId, tabData] of this.data) {
|
for (const [tabId, tabData] of this.data) {
|
||||||
const pod = new Pod(tabData.selectedPod);
|
try {
|
||||||
const pods = podsStore.getPodsByOwnerId(pod.getOwnerRefs()[0]?.uid);
|
if (!tabData.selectedPod) {
|
||||||
const isSelectedPodInList = pods.find(item => item.getId() == pod.getId());
|
tabData.selectedPod = tabData.pods[0];
|
||||||
const selectedPod = isSelectedPodInList ? pod : pods[0];
|
}
|
||||||
const selectedContainer = isSelectedPodInList ? tabData.selectedContainer : pod.getAllContainers()[0];
|
|
||||||
|
|
||||||
if (pods.length) {
|
const pod = new Pod(tabData.selectedPod);
|
||||||
this.setData(tabId, {
|
const pods = podsStore.getPodsByOwnerId(pod.getOwnerRefs()[0]?.uid);
|
||||||
...tabData,
|
const isSelectedPodInList = pods.find(item => item.getId() == pod.getId());
|
||||||
selectedPod,
|
const selectedPod = isSelectedPodInList ? pod : pods[0];
|
||||||
selectedContainer,
|
const selectedContainer = isSelectedPodInList ? tabData.selectedContainer : pod.getAllContainers()[0];
|
||||||
pods
|
|
||||||
});
|
if (pods.length > 0) {
|
||||||
|
this.setData(tabId, {
|
||||||
this.renameTab(tabId);
|
...tabData,
|
||||||
} else {
|
selectedPod,
|
||||||
promises.push(this.closeTab(tabId));
|
selectedContainer,
|
||||||
|
pods
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renameTab(tabId);
|
||||||
|
} else {
|
||||||
|
this.closeTab(tabId);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[LOG-TAB-STORE]: failed to set data for tabId=${tabId} deleting`, error,);
|
||||||
|
this.data.delete(tabId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async closeTab(tabId: string) {
|
private closeTab(tabId: string) {
|
||||||
this.clearData(tabId);
|
this.clearData(tabId);
|
||||||
await dockStore.closeTab(tabId);
|
dockStore.closeTab(tabId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,15 +44,11 @@ export class KubeObjectMenu<T extends KubeObject> extends React.Component<KubeOb
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isEditable() {
|
get isEditable() {
|
||||||
const { editable } = this.props;
|
return this.props.editable ?? Boolean(this.store?.patch);
|
||||||
|
|
||||||
return editable !== undefined ? editable : !!(this.store && this.store.update);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isRemovable() {
|
get isRemovable() {
|
||||||
const { removable } = this.props;
|
return this.props.removable ?? Boolean(this.store?.remove);
|
||||||
|
|
||||||
return removable !== undefined ? removable : !!(this.store && this.store.remove);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
|
|||||||
@ -20,13 +20,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { KubeMetaField, KubeObject } from "../../../common/k8s-api/kube-object";
|
import { KubeMetaField, KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import { DrawerItem, DrawerItemLabels } from "../drawer";
|
import { DrawerItem, DrawerItemLabels } from "../drawer";
|
||||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||||
import { LocaleDate } from "../locale-date";
|
import { LocaleDate } from "../locale-date";
|
||||||
import { getDetailsUrl } from "../kube-detail-params";
|
import { getDetailsUrl } from "../kube-detail-params";
|
||||||
|
import logger from "../../../common/logger";
|
||||||
|
|
||||||
export interface KubeObjectMetaProps {
|
export interface KubeObjectMetaProps {
|
||||||
object: KubeObject;
|
object: KubeObject;
|
||||||
@ -46,6 +47,17 @@ export class KubeObjectMeta extends React.Component<KubeObjectMetaProps> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { object } = this.props;
|
const { object } = this.props;
|
||||||
|
|
||||||
|
if (!object) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(object instanceof KubeObject)) {
|
||||||
|
logger.error("[KubeObjectMeta]: passed object that is not an instanceof KubeObject", object);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getNs, getLabels, getResourceVersion, selfLink, getAnnotations,
|
getNs, getLabels, getResourceVersion, selfLink, getAnnotations,
|
||||||
getFinalizers, getId, getAge, getName, metadata: { creationTimestamp },
|
getFinalizers, getId, getAge, getName, metadata: { creationTimestamp },
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import { StorageHelper } from "./storageHelper";
|
|||||||
import { ClusterStore } from "../../common/cluster-store";
|
import { ClusterStore } from "../../common/cluster-store";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { getHostedClusterId, getPath } from "../../common/utils";
|
import { getHostedClusterId, getPath } from "../../common/utils";
|
||||||
|
import { isTestEnv } from "../../common/vars";
|
||||||
|
|
||||||
const storage = observable({
|
const storage = observable({
|
||||||
initialized: false,
|
initialized: false,
|
||||||
@ -62,7 +63,9 @@ export function createAppStorage<T>(key: string, defaultValue: T, clusterId?: st
|
|||||||
.then(data => storage.data = data)
|
.then(data => storage.data = data)
|
||||||
.catch(() => null) // ignore empty / non-existing / invalid json files
|
.catch(() => null) // ignore empty / non-existing / invalid json files
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
logger.info(`${logPrefix} loading finished for ${filePath}`);
|
if (!isTestEnv) {
|
||||||
|
logger.info(`${logPrefix} loading finished for ${filePath}`);
|
||||||
|
}
|
||||||
storage.loaded = true;
|
storage.loaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -12446,6 +12446,11 @@ rfc4648@^1.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.3.0.tgz#2a69c76f05bc0e388feab933672de9b492af95f1"
|
resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.3.0.tgz#2a69c76f05bc0e388feab933672de9b492af95f1"
|
||||||
integrity sha512-x36K12jOflpm1V8QjPq3I+pt7Z1xzeZIjiC8J2Oxd7bE1efTrOG241DTYVJByP/SxR9jl1t7iZqYxDX864jgBQ==
|
integrity sha512-x36K12jOflpm1V8QjPq3I+pt7Z1xzeZIjiC8J2Oxd7bE1efTrOG241DTYVJByP/SxR9jl1t7iZqYxDX864jgBQ==
|
||||||
|
|
||||||
|
rfc6902@^4.0.2:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/rfc6902/-/rfc6902-4.0.2.tgz#ce99d3562b9e3287d403462e6bcc81eead8fcea0"
|
||||||
|
integrity sha512-MJOC4iDSv3Qn5/QvhPbrNoRongti6moXSShcRmtbNqOk0WPxlviEdMV4bb9PaULhSxLUXzWd4AjAMKQ3j3y54w==
|
||||||
|
|
||||||
rimraf@2, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1:
|
rimraf@2, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1:
|
||||||
version "2.7.1"
|
version "2.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user