mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add copy buttons for string DrawerItem's
- Changes <LocaleDate> to localizeDate() so that the string check passes - Changes the rendering of booleans in CRDs to manually stringify them Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
169f443c3b
commit
deefdc9af2
@ -155,8 +155,11 @@ export function isMetricsEmpty(metrics: Record<string, IMetrics>) {
|
||||
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
|
||||
}
|
||||
|
||||
export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: string): Record<string, IMetrics> | void {
|
||||
if (!metrics) return;
|
||||
export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: string): Record<string, IMetrics> | undefined {
|
||||
if (!metrics) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const itemMetrics = { ...metrics };
|
||||
|
||||
for (const metric in metrics) {
|
||||
|
||||
@ -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<Props> {
|
||||
renderAdditionalColumns(resource: KubeObject, columns: AdditionalPrinterColumnsV1[]) {
|
||||
return columns.map(({ name, jsonPath: jp }) => (
|
||||
<DrawerItem key={name} name={name} renderBoolean>
|
||||
<DrawerItem key={name} name={name}>
|
||||
{convertSpecValue(jsonPath.value(resource, parseJsonPath(jp.slice(1))))}
|
||||
</DrawerItem>
|
||||
));
|
||||
|
||||
@ -100,32 +100,25 @@ export class CrdResources extends React.Component<Props> {
|
||||
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(),
|
||||
]}
|
||||
|
||||
@ -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<KubeEvent> {
|
||||
}
|
||||
@ -70,10 +70,10 @@ export class EventDetails extends React.Component<Props> {
|
||||
{event.getSource()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="First seen">
|
||||
{event.getFirstSeenTime()} ago (<LocaleDate date={event.firstTimestamp} />)
|
||||
{event.getFirstSeenTime()} ago ({localizeDate(event.firstTimestamp)})
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Last seen">
|
||||
{event.getLastSeenTime()} ago (<LocaleDate date={event.lastTimestamp} />)
|
||||
{event.getLastSeenTime()} ago ({localizeDate(event.lastTimestamp)})
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Count">
|
||||
{count}
|
||||
|
||||
@ -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<KubeEventDetailsProps> {
|
||||
{involvedObject.fieldPath}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Last seen">
|
||||
<LocaleDate date={lastTimestamp} />
|
||||
{localizeDate(lastTimestamp)}
|
||||
</DrawerItem>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -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<Props> {
|
||||
<span>
|
||||
{lastState}<br/>
|
||||
Reason: {status.lastState.terminated.reason} - exit code: {status.lastState.terminated.exitCode}<br/>
|
||||
Started at: {<LocaleDate date={status.lastState.terminated.startedAt} />}<br/>
|
||||
Finished at: {<LocaleDate date={status.lastState.terminated.finishedAt} />}<br/>
|
||||
Started at: {localizeDate(status.lastState.terminated.startedAt)}<br/>
|
||||
Finished at: {localizeDate(status.lastState.terminated.finishedAt)}<br/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<any> {
|
||||
name: React.ReactNode;
|
||||
@ -29,22 +31,49 @@ export interface DrawerItemProps extends React.HTMLAttributes<any> {
|
||||
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<DrawerItemProps> {
|
||||
interface State {
|
||||
isCopied: boolean;
|
||||
}
|
||||
|
||||
export class DrawerItem extends React.Component<DrawerItemProps, State> {
|
||||
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 (
|
||||
<div {...elemProps} className={classNames} title={title}>
|
||||
<span className="name">{name}</span>
|
||||
<span className="value">{content}</span>
|
||||
<div {...elemProps} className={cssNames("DrawerItem", className, { labelsOnly })} title={title}>
|
||||
<span className="name">
|
||||
{name}
|
||||
{canCopy && (
|
||||
<Icon
|
||||
material={isCopied ? "done" : "content_copy"}
|
||||
tooltip={isCopied ? "Copied!" : "Copy"}
|
||||
onClick={() => this.copyValue(stringChildren.join(""))}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span className={cssNames("value", { noCopy: canCopy })}>{children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<KubeObjectMetaProps> {
|
||||
return (
|
||||
<>
|
||||
<DrawerItem name="Created" hidden={this.isHidden("creationTimestamp")}>
|
||||
{getAge(true, false)} ago ({<LocaleDate date={creationTimestamp} />})
|
||||
{getAge(true, false)} ago ({localizeDate(creationTimestamp)})
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Name" hidden={this.isHidden("name")}>
|
||||
{getName()}
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import moment from "moment-timezone";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
|
||||
interface Props {
|
||||
date: string
|
||||
}
|
||||
|
||||
@observer
|
||||
export class LocaleDate extends React.Component<Props> {
|
||||
render() {
|
||||
const { date } = this.props;
|
||||
|
||||
return moment.tz(date, UserStore.getInstance().localeTimezone).format();
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,7 @@ import "./table-cell.scss";
|
||||
import type { TableSortBy, TableSortParams } from "./table";
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import { boundMethod, cssNames, displayBooleans } from "../../utils";
|
||||
import { boundMethod, cssNames } from "../../utils";
|
||||
import { Icon } from "../icon";
|
||||
import { Checkbox } from "../checkbox";
|
||||
|
||||
@ -35,7 +35,6 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
|
||||
title?: ReactNode;
|
||||
checkbox?: boolean; // render cell with a checkbox
|
||||
isChecked?: boolean; // mark checkbox as checked or not
|
||||
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
|
||||
sortBy?: TableSortBy; // column name, must be same as key in sortable object <Table sortable={}/>
|
||||
showWithColumn?: string // id of the column which follow same visibility rules
|
||||
_sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!)
|
||||
@ -88,13 +87,13 @@ export class TableCell extends React.Component<TableCellProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, checkbox, isChecked, sortBy, _sort, _sorting, _nowrap, children, title, renderBoolean: displayBoolean, showWithColumn, ...cellProps } = this.props;
|
||||
const { className, checkbox, isChecked, sortBy, _sort, _sorting, _nowrap, children, title, showWithColumn, ...cellProps } = this.props;
|
||||
const classNames = cssNames("TableCell", className, {
|
||||
checkbox,
|
||||
nowrap: _nowrap,
|
||||
sorting: this.isSortable,
|
||||
});
|
||||
const content = displayBooleans(displayBoolean, title || children);
|
||||
const content = title || children;
|
||||
|
||||
return (
|
||||
<div {...cellProps} className={classNames} onClick={this.onClick}>
|
||||
|
||||
@ -37,3 +37,4 @@ export * from "./name-parts";
|
||||
export * from "./prevDefault";
|
||||
export * from "./saveFile";
|
||||
export * from "./storageHelper";
|
||||
export * from "./locale-date";
|
||||
|
||||
@ -19,4 +19,13 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export * from "./locale-date";
|
||||
import moment from "moment";
|
||||
import { UserStore } from "../../common/user-store";
|
||||
|
||||
/**
|
||||
* Parse `date` and reformat it based upon the settings from `UserStore`
|
||||
* @param date The date string
|
||||
*/
|
||||
export function localizeDate(date: string): string {
|
||||
return moment.tz(date, UserStore.getInstance().localeTimezone).format();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user