diff --git a/src/common/k8s-api/endpoints/metrics.api.ts b/src/common/k8s-api/endpoints/metrics.api.ts index b0e3c0bf9d..a30bf92a94 100644 --- a/src/common/k8s-api/endpoints/metrics.api.ts +++ b/src/common/k8s-api/endpoints/metrics.api.ts @@ -155,8 +155,11 @@ export function isMetricsEmpty(metrics: Record) { return Object.values(metrics).every(metric => !metric?.data?.result?.length); } -export function getItemMetrics(metrics: Record, itemName: string): Record | void { - if (!metrics) return; +export function getItemMetrics(metrics: Record, itemName: string): Record | undefined { + if (!metrics) { + return undefined; + } + const itemMetrics = { ...metrics }; for (const metric in metrics) { diff --git a/src/renderer/components/+custom-resources/crd-resource-details.tsx b/src/renderer/components/+custom-resources/crd-resource-details.tsx index 832cdf739c..e81d4e1c0e 100644 --- a/src/renderer/components/+custom-resources/crd-resource-details.tsx +++ b/src/renderer/components/+custom-resources/crd-resource-details.tsx @@ -56,6 +56,10 @@ function convertSpecValue(value: any): any { ); } + if (typeof value === "boolean") { + return value.toString(); + } + return value; } @@ -63,7 +67,7 @@ function convertSpecValue(value: any): any { export class CrdResourceDetails extends React.Component { renderAdditionalColumns(resource: KubeObject, columns: AdditionalPrinterColumnsV1[]) { return columns.map(({ name, jsonPath: jp }) => ( - + {convertSpecValue(jsonPath.value(resource, parseJsonPath(jp.slice(1))))} )); diff --git a/src/renderer/components/+custom-resources/crd-resources.tsx b/src/renderer/components/+custom-resources/crd-resources.tsx index b8645dfbd1..278a058ef2 100644 --- a/src/renderer/components/+custom-resources/crd-resources.tsx +++ b/src/renderer/components/+custom-resources/crd-resources.tsx @@ -100,32 +100,25 @@ export class CrdResources extends React.Component { renderTableHeader={[ { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, - ...extraColumns.map(column => { - const { name } = column; - - return { - title: name, - className: name.toLowerCase(), - sortBy: name, - id: name - }; - }), + ...extraColumns.map(({ name }) => ({ + title: name, + className: name.toLowerCase(), + sortBy: name, + id: name + })), { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, ]} renderTableContents={crdInstance => [ crdInstance.getName(), isNamespaced && crdInstance.getNs(), ...extraColumns.map((column) => { - let value = jsonPath.value(crdInstance, parseJsonPath(column.jsonPath.slice(1))); + const value = jsonPath.value(crdInstance, parseJsonPath(column.jsonPath.slice(1))); - if (Array.isArray(value) || typeof value === "object") { - value = JSON.stringify(value); + if (typeof value === "object") { + return JSON.stringify(value); } - return { - renderBoolean: true, - children: value, - }; + return value.toString(); }), crdInstance.getAge(), ]} diff --git a/src/renderer/components/+events/event-details.tsx b/src/renderer/components/+events/event-details.tsx index f70c44b656..30cc930182 100644 --- a/src/renderer/components/+events/event-details.tsx +++ b/src/renderer/components/+events/event-details.tsx @@ -30,10 +30,10 @@ import type { KubeObjectDetailsProps } from "../kube-object-details"; import { KubeEvent } from "../../../common/k8s-api/endpoints/events.api"; import { KubeObjectMeta } from "../kube-object-meta"; import { Table, TableCell, TableHead, TableRow } from "../table"; -import { LocaleDate } from "../locale-date"; import { getDetailsUrl } from "../kube-detail-params"; import { apiManager } from "../../../common/k8s-api/api-manager"; import logger from "../../../common/logger"; +import { localizeDate } from "../../utils"; interface Props extends KubeObjectDetailsProps { } @@ -70,10 +70,10 @@ export class EventDetails extends React.Component { {event.getSource()} - {event.getFirstSeenTime()} ago () + {event.getFirstSeenTime()} ago ({localizeDate(event.firstTimestamp)}) - {event.getLastSeenTime()} ago () + {event.getLastSeenTime()} ago ({localizeDate(event.lastTimestamp)}) {count} diff --git a/src/renderer/components/+events/kube-event-details.tsx b/src/renderer/components/+events/kube-event-details.tsx index a06911e08a..7a14535682 100644 --- a/src/renderer/components/+events/kube-event-details.tsx +++ b/src/renderer/components/+events/kube-event-details.tsx @@ -25,8 +25,7 @@ import React from "react"; import { observer } from "mobx-react"; import { KubeObject } from "../../../common/k8s-api/kube-object"; import { DrawerItem, DrawerTitle } from "../drawer"; -import { cssNames } from "../../utils"; -import { LocaleDate } from "../locale-date"; +import { cssNames, localizeDate } from "../../utils"; import { eventStore } from "./event.store"; import logger from "../../../common/logger"; @@ -87,7 +86,7 @@ export class KubeEventDetails extends React.Component { {involvedObject.fieldPath} - + {localizeDate(lastTimestamp)} ); diff --git a/src/renderer/components/+workloads-pods/pod-details-container.tsx b/src/renderer/components/+workloads-pods/pod-details-container.tsx index b4a4c5eaf2..72b34cf104 100644 --- a/src/renderer/components/+workloads-pods/pod-details-container.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-container.tsx @@ -24,7 +24,7 @@ import "./pod-details-container.scss"; import React from "react"; import type { IPodContainer, IPodContainerStatus, Pod } from "../../../common/k8s-api/endpoints"; import { DrawerItem } from "../drawer"; -import { cssNames } from "../../utils"; +import { cssNames, localizeDate } from "../../utils"; import { StatusBrick } from "../status-brick"; import { Badge } from "../badge"; import { ContainerEnvironment } from "./pod-container-env"; @@ -32,7 +32,6 @@ import { PodContainerPort } from "./pod-container-port"; import { ResourceMetrics } from "../resource-metrics"; import type { IMetrics } from "../../../common/k8s-api/endpoints/metrics.api"; import { ContainerCharts } from "./container-charts"; -import { LocaleDate } from "../locale-date"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { portForwardStore } from "../../port-forward/port-forward.store"; @@ -70,8 +69,8 @@ export class PodDetailsContainer extends React.Component { {lastState}
Reason: {status.lastState.terminated.reason} - exit code: {status.lastState.terminated.exitCode}
- Started at: {}
- Finished at: {}
+ Started at: {localizeDate(status.lastState.terminated.startedAt)}
+ Finished at: {localizeDate(status.lastState.terminated.finishedAt)}
); } diff --git a/src/renderer/components/drawer/drawer-item.scss b/src/renderer/components/drawer/drawer-item.scss index 16f35270a1..8fe31cfb16 100644 --- a/src/renderer/components/drawer/drawer-item.scss +++ b/src/renderer/components/drawer/drawer-item.scss @@ -32,6 +32,17 @@ overflow: hidden; text-overflow: ellipsis; color: $drawerItemNameColor; + + .Icon { + opacity: 0; + font-weight: normal; + margin-left: 8px; + } + + &:hover .Icon { + opacity: 1; + transition: opacity 250ms; + } } > .value { @@ -40,6 +51,10 @@ max-width: 100%; min-width: 0; + &.noCopy { + user-select: none; + } + &:empty:after { content: '—' } @@ -93,4 +108,4 @@ font-weight: $font-weight-bold; } } -} \ No newline at end of file +} diff --git a/src/renderer/components/drawer/drawer-item.tsx b/src/renderer/components/drawer/drawer-item.tsx index 4b5a276127..9082f41f90 100644 --- a/src/renderer/components/drawer/drawer-item.tsx +++ b/src/renderer/components/drawer/drawer-item.tsx @@ -21,7 +21,9 @@ import "./drawer-item.scss"; import React from "react"; -import { cssNames, displayBooleans } from "../../utils"; +import { clipboard } from "electron"; +import { cssNames } from "../../utils"; +import { Icon } from "../icon"; export interface DrawerItemProps extends React.HTMLAttributes { name: React.ReactNode; @@ -29,22 +31,49 @@ export interface DrawerItemProps extends React.HTMLAttributes { title?: string; labelsOnly?: boolean; hidden?: boolean; - renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean" } -export class DrawerItem extends React.Component { +interface State { + isCopied: boolean; +} + +export class DrawerItem extends React.Component { + state = { + isCopied: false, + }; + + copyValue = (content: string) => { + clipboard.writeText(content); + this.setState({ isCopied: true }); + setTimeout(() => { + this.setState({ isCopied: false }); + }, 3000); + }; + render() { - const { name, title, labelsOnly, children, hidden, className, renderBoolean, ...elemProps } = this.props; + const { name, title, labelsOnly, children, hidden, className, ...elemProps } = this.props; + const { isCopied } = this.state; - if (hidden) return null; + if (hidden) { + return null; + } - const classNames = cssNames("DrawerItem", className, { labelsOnly }); - const content = displayBooleans(renderBoolean, children); + const stringChildren = React.Children.toArray(children).filter(child => typeof child === "string"); + const canCopy = stringChildren.length > 0; return ( -
- {name} - {content} +
+ + {name} + {canCopy && ( + this.copyValue(stringChildren.join(""))} + /> + )} + + {children}
); } diff --git a/src/renderer/components/kube-object-meta/kube-object-meta.tsx b/src/renderer/components/kube-object-meta/kube-object-meta.tsx index 9db91061d2..856bfb76b1 100644 --- a/src/renderer/components/kube-object-meta/kube-object-meta.tsx +++ b/src/renderer/components/kube-object-meta/kube-object-meta.tsx @@ -25,9 +25,9 @@ import { DrawerItem, DrawerItemLabels } from "../drawer"; import { apiManager } from "../../../common/k8s-api/api-manager"; import { Link } from "react-router-dom"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import { LocaleDate } from "../locale-date"; import { getDetailsUrl } from "../kube-detail-params"; import logger from "../../../common/logger"; +import { localizeDate } from "../../utils"; export interface KubeObjectMetaProps { object: KubeObject; @@ -67,7 +67,7 @@ export class KubeObjectMeta extends React.Component { return ( <>