mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix all Age, LastSeen, and FirstSeen displays (#4990)
This commit is contained in:
parent
c9cab22558
commit
b08daa811d
@ -235,6 +235,7 @@
|
||||
"mobx": "^6.3.7",
|
||||
"mobx-observable-history": "^2.0.3",
|
||||
"mobx-react": "^7.2.1",
|
||||
"mobx-utils": "^6.0.4",
|
||||
"mock-fs": "^5.1.2",
|
||||
"moment": "^2.29.1",
|
||||
"moment-timezone": "^0.5.34",
|
||||
|
||||
@ -49,12 +49,18 @@ export class KubeEvent extends KubeObject {
|
||||
return `${component} ${host || ""}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function is not reactive to changing of time. If rendering use `<ReactiveDuration />` instead
|
||||
*/
|
||||
getFirstSeenTime() {
|
||||
const diff = moment().diff(this.firstTimestamp);
|
||||
|
||||
return formatDuration(diff, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function is not reactive to changing of time. If rendering use `<ReactiveDuration />` instead
|
||||
*/
|
||||
getLastSeenTime() {
|
||||
const diff = moment().diff(this.lastTimestamp);
|
||||
|
||||
|
||||
@ -261,10 +261,28 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
|
||||
return this.metadata.namespace || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function computes the number of milliseconds from the UNIX EPOCH to the
|
||||
* creation timestamp of this object.
|
||||
*/
|
||||
getCreationTimestamp() {
|
||||
return new Date(this.metadata.creationTimestamp).getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function computes a new "now" on every call which might cause subtle issues if called multiple times
|
||||
*
|
||||
* NOTE: Generally you can use `getCreationTimestamp` instead.
|
||||
*/
|
||||
getTimeDiffFromNow(): number {
|
||||
return Date.now() - new Date(this.metadata.creationTimestamp).getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function computes a new "now" on every call might cause subtle issues if called multiple times
|
||||
*
|
||||
* NOTE: this function also is not reactive to updates in the current time so it should not be used for renderering
|
||||
*/
|
||||
getAge(humanize = true, compact = true, fromNow = false): string | number {
|
||||
if (fromNow) {
|
||||
return moment(this.metadata.creationTimestamp).fromNow(); // "string", getTimeDiffFromNow() cannot be used
|
||||
|
||||
@ -19,6 +19,7 @@ import { Spinner } from "../spinner";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params";
|
||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
export interface ClusterIssuesProps {
|
||||
className?: string;
|
||||
@ -28,8 +29,8 @@ interface IWarning extends ItemObject {
|
||||
kind: string;
|
||||
message: string;
|
||||
selfLink: string;
|
||||
age: string | number;
|
||||
timeDiffFromNow: number;
|
||||
renderAge: () => React.ReactElement;
|
||||
ageMs: number;
|
||||
}
|
||||
|
||||
enum sortBy {
|
||||
@ -40,63 +41,42 @@ enum sortBy {
|
||||
|
||||
@observer
|
||||
export class ClusterIssues extends React.Component<ClusterIssuesProps> {
|
||||
private sortCallbacks = {
|
||||
[sortBy.type]: (warning: IWarning) => warning.kind,
|
||||
[sortBy.object]: (warning: IWarning) => warning.getName(),
|
||||
[sortBy.age]: (warning: IWarning) => warning.timeDiffFromNow,
|
||||
};
|
||||
|
||||
constructor(props: ClusterIssuesProps) {
|
||||
super(props);
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@computed get warnings() {
|
||||
const warnings: IWarning[] = [];
|
||||
|
||||
// Node bad conditions
|
||||
nodesStore.items.forEach(node => {
|
||||
const { kind, selfLink, getId, getName, getAge, getTimeDiffFromNow } = node;
|
||||
|
||||
node.getWarningConditions().forEach(({ message }) => {
|
||||
warnings.push({
|
||||
age: getAge(),
|
||||
getId,
|
||||
getName,
|
||||
timeDiffFromNow: getTimeDiffFromNow(),
|
||||
kind,
|
||||
@computed get warnings(): IWarning[] {
|
||||
return [
|
||||
...nodesStore.items.flatMap(node => (
|
||||
node.getWarningConditions()
|
||||
.map(({ message }) => ({
|
||||
selfLink: node.selfLink,
|
||||
getId: node.getId,
|
||||
getName: node.getName,
|
||||
kind: node.kind,
|
||||
message,
|
||||
selfLink,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Warning events for Workloads
|
||||
const events = eventStore.getWarnings();
|
||||
|
||||
events.forEach(error => {
|
||||
const { message, involvedObject, getAge, getTimeDiffFromNow } = error;
|
||||
const { uid, name, kind } = involvedObject;
|
||||
|
||||
warnings.push({
|
||||
getId: () => uid,
|
||||
getName: () => name,
|
||||
timeDiffFromNow: getTimeDiffFromNow(),
|
||||
age: getAge(),
|
||||
message,
|
||||
kind,
|
||||
selfLink: apiManager.lookupApiLink(involvedObject, error),
|
||||
});
|
||||
});
|
||||
|
||||
return warnings;
|
||||
renderAge: () => <KubeObjectAge key="age" object={node} />,
|
||||
ageMs: -node.getCreationTimestamp(),
|
||||
}))
|
||||
)),
|
||||
...eventStore.getWarnings().map(warning => ({
|
||||
getId: () => warning.involvedObject.uid,
|
||||
getName: () => warning.involvedObject.name,
|
||||
renderAge: () => <KubeObjectAge key="age" object={warning} />,
|
||||
ageMs: -warning.getCreationTimestamp(),
|
||||
message: warning.message,
|
||||
kind: warning.kind,
|
||||
selfLink: apiManager.lookupApiLink(warning.involvedObject, warning),
|
||||
})),
|
||||
];
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
getTableRow(uid: string) {
|
||||
const { warnings } = this;
|
||||
const warning = warnings.find(warn => warn.getId() == uid);
|
||||
const { getId, getName, message, kind, selfLink, age } = warning;
|
||||
const { getId, getName, message, kind, selfLink, renderAge } = warning;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
@ -115,7 +95,7 @@ export class ClusterIssues extends React.Component<ClusterIssuesProps> {
|
||||
{kind}
|
||||
</TableCell>
|
||||
<TableCell className="age">
|
||||
{age}
|
||||
{renderAge()}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
@ -143,15 +123,18 @@ export class ClusterIssues extends React.Component<ClusterIssuesProps> {
|
||||
return (
|
||||
<>
|
||||
<SubHeader className={styles.SubHeader}>
|
||||
<Icon material="error_outline"/>{" "}
|
||||
<>Warnings: {warnings.length}</>
|
||||
<Icon material="error_outline"/> Warnings: {warnings.length}
|
||||
</SubHeader>
|
||||
<Table
|
||||
tableId="cluster_issues"
|
||||
items={warnings}
|
||||
virtual
|
||||
selectable
|
||||
sortable={this.sortCallbacks}
|
||||
sortable={{
|
||||
[sortBy.type]: warning => warning.kind,
|
||||
[sortBy.object]: warning => warning.getName(),
|
||||
[sortBy.age]: warning => warning.ageMs,
|
||||
}}
|
||||
sortByDefault={{ sortBy: sortBy.object, orderBy: "asc" }}
|
||||
sortSyncWithUrl={false}
|
||||
getTableRow={this.getTableRow}
|
||||
|
||||
@ -15,6 +15,7 @@ import { Badge } from "../badge";
|
||||
import { cssNames } from "../../utils";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { HpaRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -49,17 +50,18 @@ export class HorizontalPodAutoscalers extends React.Component<HorizontalPodAutos
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="configuration_hpa"
|
||||
className="HorizontalPodAutoscalers" store={hpaStore}
|
||||
className="HorizontalPodAutoscalers"
|
||||
store={hpaStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: item => item.getName(),
|
||||
[columnId.namespace]: item => item.getNs(),
|
||||
[columnId.minPods]: item => item.getMinPods(),
|
||||
[columnId.maxPods]: item => item.getMaxPods(),
|
||||
[columnId.replicas]: item => item.getReplicas(),
|
||||
[columnId.age]: item => item.getTimeDiffFromNow(),
|
||||
[columnId.name]: hpa => hpa.getName(),
|
||||
[columnId.namespace]: hpa => hpa.getNs(),
|
||||
[columnId.minPods]: hpa => hpa.getMinPods(),
|
||||
[columnId.maxPods]: hpa => hpa.getMaxPods(),
|
||||
[columnId.replicas]: hpa => hpa.getReplicas(),
|
||||
[columnId.age]: hpa => -hpa.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
hpa => hpa.getSearchFields(),
|
||||
]}
|
||||
renderHeaderTitle="Horizontal Pod Autoscalers"
|
||||
renderTableHeader={[
|
||||
@ -81,11 +83,10 @@ export class HorizontalPodAutoscalers extends React.Component<HorizontalPodAutos
|
||||
hpa.getMinPods(),
|
||||
hpa.getMaxPods(),
|
||||
hpa.getReplicas(),
|
||||
hpa.getAge(),
|
||||
hpa.getConditions().map(({ type, tooltip, isReady }) => {
|
||||
if (!isReady) return null;
|
||||
|
||||
return (
|
||||
<KubeObjectAge key="age" object={hpa} />,
|
||||
hpa.getConditions()
|
||||
.filter(({ isReady }) => isReady)
|
||||
.map(({ type, tooltip }) => (
|
||||
<Badge
|
||||
key={type}
|
||||
label={type}
|
||||
@ -94,8 +95,7 @@ export class HorizontalPodAutoscalers extends React.Component<HorizontalPodAutos
|
||||
expandable={false}
|
||||
scrollable={true}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
)),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -12,6 +12,7 @@ import { limitRangeStore } from "./limit-ranges.store";
|
||||
import React from "react";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { LimitRangeRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -32,9 +33,9 @@ export class LimitRanges extends React.Component<LimitRangesProps> {
|
||||
className="LimitRanges"
|
||||
store={limitRangeStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: item => item.getName(),
|
||||
[columnId.namespace]: item => item.getNs(),
|
||||
[columnId.age]: item => item.getTimeDiffFromNow(),
|
||||
[columnId.name]: limitRange => limitRange.getName(),
|
||||
[columnId.namespace]: limitRange => limitRange.getNs(),
|
||||
[columnId.age]: limitRange => -limitRange.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getName(),
|
||||
@ -51,7 +52,7 @@ export class LimitRanges extends React.Component<LimitRangesProps> {
|
||||
limitRange.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={limitRange}/>,
|
||||
limitRange.getNs(),
|
||||
limitRange.getAge(),
|
||||
<KubeObjectAge key="age" object={limitRange} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -12,6 +12,7 @@ import { configMapsStore } from "./config-maps.store";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { ConfigMapsRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -30,16 +31,17 @@ export class ConfigMaps extends React.Component<ConfigMapsProps> {
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="configuration_configmaps"
|
||||
className="ConfigMaps" store={configMapsStore}
|
||||
className="ConfigMaps"
|
||||
store={configMapsStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: item => item.getName(),
|
||||
[columnId.namespace]: item => item.getNs(),
|
||||
[columnId.keys]: item => item.getKeys(),
|
||||
[columnId.age]: item => item.getTimeDiffFromNow(),
|
||||
[columnId.name]: configMap => configMap.getName(),
|
||||
[columnId.namespace]: configMap => configMap.getNs(),
|
||||
[columnId.keys]: configMap => configMap.getKeys(),
|
||||
[columnId.age]: configMap => -configMap.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
item => item.getKeys(),
|
||||
configMap => configMap.getSearchFields(),
|
||||
configMap => configMap.getKeys(),
|
||||
]}
|
||||
renderHeaderTitle="Config Maps"
|
||||
renderTableHeader={[
|
||||
@ -54,7 +56,7 @@ export class ConfigMaps extends React.Component<ConfigMapsProps> {
|
||||
<KubeObjectStatusIcon key="icon" object={configMap}/>,
|
||||
configMap.getNs(),
|
||||
configMap.getKeys().join(", "),
|
||||
configMap.getAge(),
|
||||
<KubeObjectAge key="age" object={configMap} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -12,6 +12,7 @@ import type { PodDisruptionBudget } from "../../../common/k8s-api/endpoints/podd
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -42,7 +43,7 @@ export class PodDisruptionBudgets extends React.Component<PodDisruptionBudgetsPr
|
||||
[columnId.maxUnavailable]: pdb => pdb.getMaxUnavailable(),
|
||||
[columnId.currentHealthy]: pdb => pdb.getCurrentHealthy(),
|
||||
[columnId.desiredHealthy]: pdb => pdb.getDesiredHealthy(),
|
||||
[columnId.age]: pdb => pdb.getAge(),
|
||||
[columnId.age]: pdb => -pdb.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
pdb => pdb.getSearchFields(),
|
||||
@ -58,8 +59,7 @@ export class PodDisruptionBudgets extends React.Component<PodDisruptionBudgetsPr
|
||||
{ title: "Desired Healthy", className: "desired-healthy", sortBy: columnId.desiredHealthy, id: columnId.desiredHealthy },
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
]}
|
||||
renderTableContents={pdb => {
|
||||
return [
|
||||
renderTableContents={pdb => [
|
||||
pdb.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={pdb} />,
|
||||
pdb.getNs(),
|
||||
@ -67,9 +67,8 @@ export class PodDisruptionBudgets extends React.Component<PodDisruptionBudgetsPr
|
||||
pdb.getMaxUnavailable(),
|
||||
pdb.getCurrentHealthy(),
|
||||
pdb.getDesiredHealthy(),
|
||||
pdb.getAge(),
|
||||
];
|
||||
}}
|
||||
<KubeObjectAge key="age" object={pdb} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import { AddQuotaDialog } from "./add-quota-dialog";
|
||||
import { resourceQuotaStore } from "./resource-quotas.store";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { ResourceQuotaRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -31,15 +32,16 @@ export class ResourceQuotas extends React.Component<ResourceQuotasProps> {
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="configuration_quotas"
|
||||
className="ResourceQuotas" store={resourceQuotaStore}
|
||||
className="ResourceQuotas"
|
||||
store={resourceQuotaStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: item => item.getName(),
|
||||
[columnId.namespace]: item => item.getNs(),
|
||||
[columnId.age]: item => item.getTimeDiffFromNow(),
|
||||
[columnId.name]: resourceQuota => resourceQuota.getName(),
|
||||
[columnId.namespace]: resourceQuota => resourceQuota.getNs(),
|
||||
[columnId.age]: resourceQuota => -resourceQuota.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
item => item.getName(),
|
||||
resourceQuota => resourceQuota.getSearchFields(),
|
||||
resourceQuota => resourceQuota.getName(),
|
||||
]}
|
||||
renderHeaderTitle="Resource Quotas"
|
||||
renderTableHeader={[
|
||||
@ -52,7 +54,7 @@ export class ResourceQuotas extends React.Component<ResourceQuotasProps> {
|
||||
resourceQuota.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={resourceQuota}/>,
|
||||
resourceQuota.getNs(),
|
||||
resourceQuota.getAge(),
|
||||
<KubeObjectAge key="age" object={resourceQuota} />,
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
onAdd: () => AddQuotaDialog.open(),
|
||||
|
||||
@ -14,6 +14,7 @@ import { Badge } from "../badge";
|
||||
import { secretsStore } from "./secrets.store";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { SecretsRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -35,18 +36,19 @@ export class Secrets extends React.Component<SecretsProps> {
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="configuration_secrets"
|
||||
className="Secrets" store={secretsStore}
|
||||
className="Secrets"
|
||||
store={secretsStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: item => item.getName(),
|
||||
[columnId.namespace]: item => item.getNs(),
|
||||
[columnId.labels]: item => item.getLabels(),
|
||||
[columnId.keys]: item => item.getKeys(),
|
||||
[columnId.type]: item => item.type,
|
||||
[columnId.age]: item => item.getTimeDiffFromNow(),
|
||||
[columnId.name]: secret => secret.getName(),
|
||||
[columnId.namespace]: secret => secret.getNs(),
|
||||
[columnId.labels]: secret => secret.getLabels(),
|
||||
[columnId.keys]: secret => secret.getKeys(),
|
||||
[columnId.type]: secret => secret.type,
|
||||
[columnId.age]: secret => -secret.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
item => item.getKeys(),
|
||||
secret => secret.getSearchFields(),
|
||||
secret => secret.getKeys(),
|
||||
]}
|
||||
renderHeaderTitle="Secrets"
|
||||
renderTableHeader={[
|
||||
@ -65,7 +67,7 @@ export class Secrets extends React.Component<SecretsProps> {
|
||||
secret.getLabels().map(label => <Badge scrollable key={label} label={label} expandable={false}/>),
|
||||
secret.getKeys().join(", "),
|
||||
secret.type,
|
||||
secret.getAge(),
|
||||
<KubeObjectAge key="age" object={secret} />,
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
onAdd: () => AddSecretDialog.open(),
|
||||
|
||||
@ -12,11 +12,10 @@ import { Link } from "react-router-dom";
|
||||
import { stopPropagation } from "../../utils";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { crdStore } from "./crd.store";
|
||||
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints/crd.api";
|
||||
import { Select, SelectOption } from "../select";
|
||||
import { createPageParam } from "../../navigation";
|
||||
import { Icon } from "../icon";
|
||||
import type { TableSortCallbacks } from "../table";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
export const crdGroupsUrlParam = createPageParam<string[]>({
|
||||
name: "groups",
|
||||
@ -63,12 +62,6 @@ export class CustomResourceDefinitions extends React.Component {
|
||||
|
||||
render() {
|
||||
const { items, selectedGroups } = this;
|
||||
const sortingCallbacks: TableSortCallbacks<CustomResourceDefinition> = {
|
||||
[columnId.kind]: crd => crd.getResourceKind(),
|
||||
[columnId.group]: crd => crd.getGroup(),
|
||||
[columnId.version]: crd => crd.getVersion(),
|
||||
[columnId.scope]: crd => crd.getScope(),
|
||||
};
|
||||
|
||||
return (
|
||||
<KubeObjectListLayout
|
||||
@ -79,8 +72,20 @@ export class CustomResourceDefinitions extends React.Component {
|
||||
// Don't subscribe the `crdStore` because <Sidebar> already has and is always mounted
|
||||
subscribeStores={false}
|
||||
items={items}
|
||||
sortingCallbacks={sortingCallbacks}
|
||||
searchFilters={Object.values(sortingCallbacks)}
|
||||
sortingCallbacks={{
|
||||
[columnId.kind]: crd => crd.getResourceKind(),
|
||||
[columnId.group]: crd => crd.getGroup(),
|
||||
[columnId.version]: crd => crd.getVersion(),
|
||||
[columnId.scope]: crd => crd.getScope(),
|
||||
[columnId.age]: crd => -crd.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
crd => crd.getResourceKind(),
|
||||
crd => crd.getGroup(),
|
||||
crd => crd.getVersion(),
|
||||
crd => crd.getScope(),
|
||||
crd => -crd.getCreationTimestamp(),
|
||||
]}
|
||||
renderHeaderTitle="Custom Resources"
|
||||
customizeHeader={({ filters, ...headerPlaceholders }) => {
|
||||
let placeholder = <>All groups</>;
|
||||
@ -131,7 +136,7 @@ export class CustomResourceDefinitions extends React.Component {
|
||||
crd.getGroup(),
|
||||
crd.getVersion(),
|
||||
crd.getScope(),
|
||||
crd.getAge(),
|
||||
<KubeObjectAge key="age" object={crd} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -6,17 +6,16 @@
|
||||
import "./crd-resources.scss";
|
||||
|
||||
import React from "react";
|
||||
import jsonPath from "jsonpath";
|
||||
import { value } from "jsonpath";
|
||||
import { observer } from "mobx-react";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { computed, makeObservable } from "mobx";
|
||||
import { crdStore } from "./crd.store";
|
||||
import type { TableSortCallbacks } from "../table";
|
||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||
import { parseJsonPath } from "../../utils/jsonPath";
|
||||
import type { CRDRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
export interface CustomResourceDefinitionResourcesProps extends RouteComponentProps<CRDRouteParams> {
|
||||
}
|
||||
@ -47,29 +46,13 @@ export class CustomResourceDefinitionResources extends React.Component<CustomRes
|
||||
render() {
|
||||
const { crd, store } = this;
|
||||
|
||||
if (!crd) return null;
|
||||
if (!crd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isNamespaced = crd.isNamespaced();
|
||||
const extraColumns = crd.getPrinterColumns(false); // Cols with priority bigger than 0 are shown in details
|
||||
const sortingCallbacks: TableSortCallbacks<KubeObject> = {
|
||||
[columnId.name]: item => item.getName(),
|
||||
[columnId.namespace]: item => item.getNs(),
|
||||
[columnId.age]: item => item.getTimeDiffFromNow(),
|
||||
};
|
||||
|
||||
extraColumns.forEach(column => {
|
||||
sortingCallbacks[column.name] = item => jsonPath.value(item, parseJsonPath(column.jsonPath.slice(1)));
|
||||
});
|
||||
|
||||
const version = crd.getPreferedVersion();
|
||||
const loadFailedPrefix = <p>Failed to load {crd.getPluralName()}</p>;
|
||||
const failedToLoadMessage = version.served
|
||||
? loadFailedPrefix
|
||||
: (
|
||||
<>
|
||||
{loadFailedPrefix}
|
||||
<p>Prefered version ({crd.getGroup()}/{version.name}) is not served</p>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<KubeObjectListLayout
|
||||
@ -78,9 +61,17 @@ export class CustomResourceDefinitionResources extends React.Component<CustomRes
|
||||
tableId="crd_resources"
|
||||
className="CrdResources"
|
||||
store={store}
|
||||
sortingCallbacks={sortingCallbacks}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: customResource => customResource.getName(),
|
||||
[columnId.namespace]: customResource => customResource.getNs(),
|
||||
[columnId.age]: customResource => -customResource.getCreationTimestamp(),
|
||||
...Object.fromEntries(extraColumns.map(({ name, jsonPath }) => [
|
||||
name,
|
||||
customResource => value(customResource, parseJsonPath(jsonPath.slice(1))),
|
||||
])),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
customResource => customResource.getSearchFields(),
|
||||
]}
|
||||
renderHeaderTitle={crd.getResourceKind()}
|
||||
customizeHeader={({ searchProps, ...headerPlaceholders }) => ({
|
||||
@ -93,36 +84,39 @@ export class CustomResourceDefinitionResources extends React.Component<CustomRes
|
||||
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 {
|
||||
...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)));
|
||||
let rawValue = value(crdInstance, parseJsonPath(column.jsonPath.slice(1)));
|
||||
|
||||
if (Array.isArray(value) || typeof value === "object") {
|
||||
value = JSON.stringify(value);
|
||||
if (Array.isArray(rawValue) || typeof rawValue === "object") {
|
||||
rawValue = JSON.stringify(rawValue);
|
||||
}
|
||||
|
||||
return {
|
||||
renderBoolean: true,
|
||||
children: value,
|
||||
children: rawValue,
|
||||
};
|
||||
}),
|
||||
crdInstance.getAge(),
|
||||
<KubeObjectAge key="age" object={crdInstance} />,
|
||||
]}
|
||||
failedToLoadMessage={failedToLoadMessage}
|
||||
failedToLoadMessage={(
|
||||
<>
|
||||
<p>Failed to load {crd.getPluralName()}</p>
|
||||
{!version.served && (
|
||||
<p>Prefered version ({crd.getGroup()}/{version.name}) is not served</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ 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 { ReactiveDuration } from "../duration/reactive-duration";
|
||||
|
||||
export interface EventDetailsProps extends KubeObjectDetailsProps<KubeEvent> {
|
||||
}
|
||||
@ -54,10 +55,14 @@ export class EventDetails extends React.Component<EventDetailsProps> {
|
||||
{event.getSource()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="First seen">
|
||||
{event.getFirstSeenTime()} ago (<LocaleDate date={event.firstTimestamp} />)
|
||||
<ReactiveDuration timestamp={event.firstTimestamp} />
|
||||
{" ago "}
|
||||
(<LocaleDate date={event.firstTimestamp} />)
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Last seen">
|
||||
{event.getLastSeenTime()} ago (<LocaleDate date={event.lastTimestamp} />)
|
||||
<ReactiveDuration timestamp={event.lastTimestamp} />
|
||||
{" ago "}
|
||||
(<LocaleDate date={event.lastTimestamp} />)
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Count">
|
||||
{count}
|
||||
|
||||
@ -29,7 +29,7 @@ export class EventStore extends KubeObjectStore<KubeEvent> {
|
||||
|
||||
protected sortItems(items: KubeEvent[]) {
|
||||
return super.sortItems(items, [
|
||||
event => event.getTimeDiffFromNow(), // keep events order as timeline ("fresh" on top)
|
||||
event => -event.getCreationTimestamp(), // keep events order as timeline ("fresh" on top)
|
||||
], "asc");
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,8 @@ import { Icon } from "../icon";
|
||||
import { eventsURL } from "../../../common/routes";
|
||||
import { getDetailsUrl } from "../kube-detail-params";
|
||||
import { apiManager } from "../../../common/k8s-api/api-manager";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
import { ReactiveDuration } from "../duration/reactive-duration";
|
||||
|
||||
enum columnId {
|
||||
message = "message",
|
||||
@ -47,7 +49,6 @@ const defaultProps: Partial<EventsProps> = {
|
||||
@observer
|
||||
export class Events extends React.Component<EventsProps> {
|
||||
static defaultProps = defaultProps as object;
|
||||
now = Date.now();
|
||||
|
||||
@observable sorting: TableSortParams = {
|
||||
sortBy: columnId.age,
|
||||
@ -59,8 +60,8 @@ export class Events extends React.Component<EventsProps> {
|
||||
[columnId.type]: event => event.type,
|
||||
[columnId.object]: event => event.involvedObject.name,
|
||||
[columnId.count]: event => event.count,
|
||||
[columnId.age]: event => event.getTimeDiffFromNow(),
|
||||
[columnId.lastSeen]: event => this.now - new Date(event.lastTimestamp).getTime(),
|
||||
[columnId.age]: event => -event.getCreationTimestamp(),
|
||||
[columnId.lastSeen]: event => -new Date(event.lastTimestamp).getTime(),
|
||||
};
|
||||
|
||||
constructor(props: EventsProps) {
|
||||
@ -185,8 +186,8 @@ export class Events extends React.Component<EventsProps> {
|
||||
</Link>,
|
||||
event.getSource(),
|
||||
event.count,
|
||||
event.getAge(),
|
||||
event.getLastSeenTime(),
|
||||
<KubeObjectAge key="age" object={event} />,
|
||||
<ReactiveDuration key="last-seen" timestamp={event.lastTimestamp} />,
|
||||
];
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -51,44 +51,34 @@ class NonInjectedKubeEventDetails extends React.Component<KubeEventDetailsProps
|
||||
|
||||
const events = eventStore.getEventsByObject(object);
|
||||
|
||||
if (!events.length) {
|
||||
return (
|
||||
<DrawerTitle className="flex gaps align-center">
|
||||
<span>Events</span>
|
||||
</DrawerTitle>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DrawerTitle className="flex gaps align-center">
|
||||
<span>Events</span>
|
||||
</DrawerTitle>
|
||||
{events.length > 0 && (
|
||||
<div className="KubeEventDetails">
|
||||
{events.map(evt => {
|
||||
const { message, count, lastTimestamp, involvedObject } = evt;
|
||||
|
||||
return (
|
||||
<div className="event" key={evt.getId()}>
|
||||
<div className={cssNames("title", { warning: evt.isWarning() })}>
|
||||
{message}
|
||||
{events.map(event => (
|
||||
<div className="event" key={event.getId()}>
|
||||
<div className={cssNames("title", { warning: event.isWarning() })}>
|
||||
{event.message}
|
||||
</div>
|
||||
<DrawerItem name="Source">
|
||||
{evt.getSource()}
|
||||
{event.getSource()}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Count">
|
||||
{count}
|
||||
{event.count}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Sub-object">
|
||||
{involvedObject.fieldPath}
|
||||
{event.involvedObject.fieldPath}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Last seen">
|
||||
<LocaleDate date={lastTimestamp} />
|
||||
<LocaleDate date={event.lastTimestamp} />
|
||||
</DrawerItem>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { eventStore } from "./event.store";
|
||||
import { cssNames } from "../../utils";
|
||||
import type { KubeEvent } from "../../../common/k8s-api/endpoints/events.api";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
export interface KubeEventIconProps {
|
||||
object: KubeObject;
|
||||
@ -48,7 +49,7 @@ export class KubeEventIcon extends React.Component<KubeEventIconProps> {
|
||||
<div className="msg">{event.message}</div>
|
||||
<div className="age">
|
||||
<Icon material="access_time"/>
|
||||
{event.getAge(undefined, undefined, true)}
|
||||
<KubeObjectAge object={event} />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
|
||||
@ -33,6 +33,8 @@ import releaseInjectable from "./release.injectable";
|
||||
import releaseDetailsInjectable from "./release-details.injectable";
|
||||
import releaseValuesInjectable from "./release-values.injectable";
|
||||
import userSuppliedValuesAreShownInjectable from "./user-supplied-values-are-shown.injectable";
|
||||
import type { KubeObject } from "../../../../common/k8s-api/kube-object";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
export interface ReleaseDetailsProps {
|
||||
hideDetails(): void;
|
||||
@ -150,15 +152,14 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
|
||||
);
|
||||
}
|
||||
|
||||
renderResources() {
|
||||
const { resources } = this.details;
|
||||
|
||||
if (!resources) return null;
|
||||
const groups = groupBy(resources, item => item.kind);
|
||||
const tables = Object.entries(groups).map(([kind, items]) => {
|
||||
renderResources(resources: KubeObject[]) {
|
||||
return (
|
||||
<div className="resources">
|
||||
{
|
||||
Object.entries(groupBy(resources, item => item.kind))
|
||||
.map(([kind, items]) => (
|
||||
<React.Fragment key={kind}>
|
||||
<SubTitle title={kind}/>
|
||||
<SubTitle title={kind} />
|
||||
<Table scrollable={false}>
|
||||
<TableHead sticky={false}>
|
||||
<TableCell className="name">Name</TableCell>
|
||||
@ -176,19 +177,21 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
|
||||
<TableCell className="name">
|
||||
{detailsUrl ? <Link to={detailsUrl}>{name}</Link> : name}
|
||||
</TableCell>
|
||||
{namespace && <TableCell className="namespace">{namespace}</TableCell>}
|
||||
<TableCell className="age">{item.getAge()}</TableCell>
|
||||
{namespace && (
|
||||
<TableCell className="namespace">
|
||||
{namespace}
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell className="age">
|
||||
<KubeObjectAge key="age" object={item} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="resources">
|
||||
{tables}
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -200,6 +203,8 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
|
||||
return <Spinner center/>;
|
||||
}
|
||||
|
||||
const { resources } = this.details;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DrawerItem name="Chart" className="chart">
|
||||
@ -236,7 +241,7 @@ class NonInjectedReleaseDetails extends Component<ReleaseDetailsProps & Dependen
|
||||
<DrawerTitle title="Notes"/>
|
||||
{this.renderNotes()}
|
||||
<DrawerTitle title="Resources"/>
|
||||
{this.renderResources()}
|
||||
{resources && this.renderResources(resources)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable";
|
||||
import addNamespaceDialogModelInjectable
|
||||
from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -43,14 +44,14 @@ export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDia
|
||||
className="Namespaces"
|
||||
store={namespaceStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: ns => ns.getName(),
|
||||
[columnId.labels]: ns => ns.getLabels(),
|
||||
[columnId.age]: ns => ns.getTimeDiffFromNow(),
|
||||
[columnId.status]: ns => ns.getStatus(),
|
||||
[columnId.name]: namespace => namespace.getName(),
|
||||
[columnId.labels]: namespace => namespace.getLabels(),
|
||||
[columnId.age]: namespace => -namespace.getCreationTimestamp(),
|
||||
[columnId.status]: namespace => namespace.getStatus(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
item => item.getStatus(),
|
||||
namespace => namespace.getSearchFields(),
|
||||
namespace => namespace.getStatus(),
|
||||
]}
|
||||
renderHeaderTitle="Namespaces"
|
||||
renderTableHeader={[
|
||||
@ -60,12 +61,12 @@ export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDia
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
|
||||
]}
|
||||
renderTableContents={item => [
|
||||
item.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={item} />,
|
||||
item.getLabels().map(label => <Badge scrollable key={label} label={label}/>),
|
||||
item.getAge(),
|
||||
{ title: item.getStatus(), className: item.getStatus().toLowerCase() },
|
||||
renderTableContents={namespace => [
|
||||
namespace.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={namespace} />,
|
||||
namespace.getLabels().map(label => <Badge scrollable key={label} label={label}/>),
|
||||
<KubeObjectAge key="age" object={namespace} />,
|
||||
{ title: namespace.getStatus(), className: namespace.getStatus().toLowerCase() },
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
addTooltip: "Add Namespace",
|
||||
|
||||
@ -12,6 +12,7 @@ import { endpointStore } from "./endpoints.store";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { EndpointRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -34,7 +35,7 @@ export class Endpoints extends React.Component<EndpointsProps> {
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: endpoint => endpoint.getName(),
|
||||
[columnId.namespace]: endpoint => endpoint.getNs(),
|
||||
[columnId.age]: endpoint => endpoint.getTimeDiffFromNow(),
|
||||
[columnId.age]: endpoint => -endpoint.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
endpoint => endpoint.getSearchFields(),
|
||||
@ -52,7 +53,7 @@ export class Endpoints extends React.Component<EndpointsProps> {
|
||||
<KubeObjectStatusIcon key="icon" object={endpoint} />,
|
||||
endpoint.getNs(),
|
||||
endpoint.toString(),
|
||||
endpoint.getAge(),
|
||||
<KubeObjectAge key="age" object={endpoint} />,
|
||||
]}
|
||||
tableProps={{
|
||||
customRowHeights: (item, lineHeight, paddings) => {
|
||||
|
||||
@ -12,6 +12,7 @@ import { ingressStore } from "./ingress.store";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { IngressRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -35,7 +36,7 @@ export class Ingresses extends React.Component<IngressesProps> {
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: ingress => ingress.getName(),
|
||||
[columnId.namespace]: ingress => ingress.getNs(),
|
||||
[columnId.age]: ingress => ingress.getTimeDiffFromNow(),
|
||||
[columnId.age]: ingress => -ingress.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
ingress => ingress.getSearchFields(),
|
||||
@ -56,7 +57,7 @@ export class Ingresses extends React.Component<IngressesProps> {
|
||||
ingress.getNs(),
|
||||
ingress.getLoadBalancers().map(lb => <p key={lb}>{lb}</p>),
|
||||
ingress.getRoutes().map(route => <p key={route}>{route}</p>),
|
||||
ingress.getAge(),
|
||||
<KubeObjectAge key="age" object={ingress} />,
|
||||
]}
|
||||
tableProps={{
|
||||
customRowHeights: (item, lineHeight, paddings) => {
|
||||
|
||||
@ -12,6 +12,7 @@ import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { networkPolicyStore } from "./network-policy.store";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { NetworkPoliciesRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -30,14 +31,15 @@ export class NetworkPolicies extends React.Component<NetworkPoliciesProps> {
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="network_policies"
|
||||
className="NetworkPolicies" store={networkPolicyStore}
|
||||
className="NetworkPolicies"
|
||||
store={networkPolicyStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: item => item.getName(),
|
||||
[columnId.namespace]: item => item.getNs(),
|
||||
[columnId.age]: item => item.getTimeDiffFromNow(),
|
||||
[columnId.name]: networkPolicy => networkPolicy.getName(),
|
||||
[columnId.namespace]: networkPolicy => networkPolicy.getNs(),
|
||||
[columnId.age]: networkPolicy => -networkPolicy.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
networkPolicy => networkPolicy.getSearchFields(),
|
||||
]}
|
||||
renderHeaderTitle="Network Policies"
|
||||
renderTableHeader={[
|
||||
@ -47,12 +49,12 @@ export class NetworkPolicies extends React.Component<NetworkPoliciesProps> {
|
||||
{ title: "Policy Types", className: "type", id: columnId.types },
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
]}
|
||||
renderTableContents={item => [
|
||||
item.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={item} />,
|
||||
item.getNs(),
|
||||
item.getTypes().join(", "),
|
||||
item.getAge(),
|
||||
renderTableContents={networkPolicy => [
|
||||
networkPolicy.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={networkPolicy} />,
|
||||
networkPolicy.getNs(),
|
||||
networkPolicy.getTypes().join(", "),
|
||||
<KubeObjectAge key="age" object={networkPolicy} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -13,6 +13,7 @@ import { Badge } from "../badge";
|
||||
import { serviceStore } from "./services.store";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { ServicesRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -44,7 +45,7 @@ export class Services extends React.Component<ServicesProps> {
|
||||
[columnId.ports]: service => (service.spec.ports || []).map(({ port }) => port)[0],
|
||||
[columnId.clusterIp]: service => service.getClusterIp(),
|
||||
[columnId.type]: service => service.getType(),
|
||||
[columnId.age]: service => service.getTimeDiffFromNow(),
|
||||
[columnId.age]: service => -service.getCreationTimestamp(),
|
||||
[columnId.status]: service => service.getStatus(),
|
||||
}}
|
||||
searchFilters={[
|
||||
@ -81,7 +82,7 @@ export class Services extends React.Component<ServicesProps> {
|
||||
service.getPorts().join(", "),
|
||||
externalIps.join(", ") || "-",
|
||||
service.getSelector().map(label => <Badge key={label} label={label} />),
|
||||
service.getAge(),
|
||||
<KubeObjectAge key="age" object={service} />,
|
||||
{ title: service.getStatus(), className: service.getStatus().toLowerCase() },
|
||||
];
|
||||
}}
|
||||
|
||||
@ -23,6 +23,7 @@ import { eventStore } from "../+events/event.store";
|
||||
import type { NodesRouteParams } from "../../../common/routes";
|
||||
import { makeObservable, observable } from "mobx";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -191,7 +192,7 @@ export class NodesRoute extends React.Component<NodesRouteProps> {
|
||||
[columnId.conditions]: node => node.getNodeConditionText(),
|
||||
[columnId.taints]: node => node.getTaints().length,
|
||||
[columnId.roles]: node => node.getRoleLabels(),
|
||||
[columnId.age]: node => node.getTimeDiffFromNow(),
|
||||
[columnId.age]: node => -node.getCreationTimestamp(),
|
||||
[columnId.version]: node => node.getKubeletVersion(),
|
||||
}}
|
||||
searchFilters={[
|
||||
@ -231,7 +232,7 @@ export class NodesRoute extends React.Component<NodesRouteProps> {
|
||||
</>,
|
||||
node.getRoleLabels(),
|
||||
node.status.nodeInfo.kubeletVersion,
|
||||
node.getAge(),
|
||||
<KubeObjectAge key="age" object={node} />,
|
||||
this.renderConditions(node),
|
||||
];
|
||||
}}
|
||||
|
||||
@ -10,6 +10,7 @@ import { observer } from "mobx-react";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { podSecurityPoliciesStore } from "./pod-security-policies.store";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -28,15 +29,15 @@ export class PodSecurityPolicies extends React.Component {
|
||||
className="PodSecurityPolicies"
|
||||
store={podSecurityPoliciesStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: item => item.getName(),
|
||||
[columnId.volumes]: item => item.getVolumes(),
|
||||
[columnId.privileged]: item => +item.isPrivileged(),
|
||||
[columnId.age]: item => item.getTimeDiffFromNow(),
|
||||
[columnId.name]: podSecurityPolicy => podSecurityPolicy.getName(),
|
||||
[columnId.volumes]: podSecurityPolicy => podSecurityPolicy.getVolumes(),
|
||||
[columnId.privileged]: podSecurityPolicy => +podSecurityPolicy.isPrivileged(),
|
||||
[columnId.age]: podSecurityPolicy => -podSecurityPolicy.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
item => item.getVolumes(),
|
||||
item => Object.values(item.getRules()),
|
||||
podSecurityPolicy => podSecurityPolicy.getSearchFields(),
|
||||
podSecurityPolicy => podSecurityPolicy.getVolumes(),
|
||||
podSecurityPolicy => Object.values(podSecurityPolicy.getRules()),
|
||||
]}
|
||||
renderHeaderTitle="Pod Security Policies"
|
||||
renderTableHeader={[
|
||||
@ -46,15 +47,13 @@ export class PodSecurityPolicies extends React.Component {
|
||||
{ title: "Volumes", className: "volumes", sortBy: columnId.volumes, id: columnId.volumes },
|
||||
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||
]}
|
||||
renderTableContents={item => {
|
||||
return [
|
||||
item.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={item} />,
|
||||
item.isPrivileged() ? "Yes" : "No",
|
||||
item.getVolumes().join(", "),
|
||||
item.getAge(),
|
||||
];
|
||||
}}
|
||||
renderTableContents={podSecurityPolicy => [
|
||||
podSecurityPolicy.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={podSecurityPolicy} />,
|
||||
podSecurityPolicy.isPrivileged() ? "Yes" : "No",
|
||||
podSecurityPolicy.getVolumes().join(", "),
|
||||
<KubeObjectAge key="age" object={podSecurityPolicy} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { storageClassStore } from "./storage-class.store";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { StorageClassesRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -34,14 +35,14 @@ export class StorageClasses extends React.Component<StorageClassesProps> {
|
||||
className="StorageClasses"
|
||||
store={storageClassStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: item => item.getName(),
|
||||
[columnId.age]: item => item.getTimeDiffFromNow(),
|
||||
[columnId.provisioner]: item => item.provisioner,
|
||||
[columnId.reclaimPolicy]: item => item.reclaimPolicy,
|
||||
[columnId.name]: storageClass => storageClass.getName(),
|
||||
[columnId.age]: storageClass => -storageClass.getCreationTimestamp(),
|
||||
[columnId.provisioner]: storageClass => storageClass.provisioner,
|
||||
[columnId.reclaimPolicy]: storageClass => storageClass.reclaimPolicy,
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
item => item.provisioner,
|
||||
storageClass => storageClass.getSearchFields(),
|
||||
storageClass => storageClass.provisioner,
|
||||
]}
|
||||
renderHeaderTitle="Storage Classes"
|
||||
renderTableHeader={[
|
||||
@ -58,7 +59,7 @@ export class StorageClasses extends React.Component<StorageClassesProps> {
|
||||
storageClass.provisioner,
|
||||
storageClass.getReclaimPolicy(),
|
||||
storageClass.isDefault() ? "Yes" : null,
|
||||
storageClass.getAge(),
|
||||
<KubeObjectAge key="age" object={storageClass} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -17,6 +17,7 @@ import { storageClassApi } from "../../../common/k8s-api/endpoints";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { VolumeClaimsRouteParams } from "../../../common/routes";
|
||||
import { getDetailsUrl } from "../kube-detail-params";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -48,11 +49,11 @@ export class PersistentVolumeClaims extends React.Component<PersistentVolumeClai
|
||||
[columnId.status]: pvc => pvc.getStatus(),
|
||||
[columnId.size]: pvc => unitsToBytes(pvc.getStorage()),
|
||||
[columnId.storageClass]: pvc => pvc.spec.storageClassName,
|
||||
[columnId.age]: pvc => pvc.getTimeDiffFromNow(),
|
||||
[columnId.age]: pvc => -pvc.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
item => item.getPods(podsStore.items).map(pod => pod.getName()),
|
||||
pvc => pvc.getSearchFields(),
|
||||
pvc => pvc.getPods(podsStore.items).map(pod => pod.getName()),
|
||||
]}
|
||||
renderHeaderTitle="Persistent Volume Claims"
|
||||
renderTableHeader={[
|
||||
@ -85,7 +86,7 @@ export class PersistentVolumeClaims extends React.Component<PersistentVolumeClai
|
||||
{pod.getName()}
|
||||
</Link>
|
||||
)),
|
||||
pvc.getAge(),
|
||||
<KubeObjectAge key="age" object={pvc} />,
|
||||
{ title: pvc.getStatus(), className: pvc.getStatus().toLowerCase() },
|
||||
];
|
||||
}}
|
||||
|
||||
@ -15,6 +15,7 @@ import { volumesStore } from "./volumes.store";
|
||||
import { pvcApi, storageClassApi } from "../../../common/k8s-api/endpoints";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { VolumesRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -38,15 +39,15 @@ export class PersistentVolumes extends React.Component<PersistentVolumesProps> {
|
||||
className="PersistentVolumes"
|
||||
store={volumesStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: item => item.getName(),
|
||||
[columnId.storageClass]: item => item.getStorageClass(),
|
||||
[columnId.capacity]: item => item.getCapacity(true),
|
||||
[columnId.status]: item => item.getStatus(),
|
||||
[columnId.age]: item => item.getTimeDiffFromNow(),
|
||||
[columnId.name]: volume => volume.getName(),
|
||||
[columnId.storageClass]: volume => volume.getStorageClass(),
|
||||
[columnId.capacity]: volume => volume.getCapacity(true),
|
||||
[columnId.status]: volume => volume.getStatus(),
|
||||
[columnId.age]: volume => -volume.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
item => item.getSearchFields(),
|
||||
item => item.getClaimRefName(),
|
||||
volume => volume.getSearchFields(),
|
||||
volume => volume.getClaimRefName(),
|
||||
]}
|
||||
renderHeaderTitle="Persistent Volumes"
|
||||
renderTableHeader={[
|
||||
@ -76,7 +77,7 @@ export class PersistentVolumes extends React.Component<PersistentVolumesProps> {
|
||||
{claimRef.name}
|
||||
</Link>
|
||||
),
|
||||
volume.getAge(),
|
||||
<KubeObjectAge key="age" object={volume} />,
|
||||
{ title: volume.getStatus(), className: volume.getStatus().toLowerCase() },
|
||||
];
|
||||
}}
|
||||
|
||||
@ -15,6 +15,7 @@ import { clusterRoleBindingsStore } from "./store";
|
||||
import { clusterRolesStore } from "../+cluster-roles/store";
|
||||
import { serviceAccountsStore } from "../+service-accounts/store";
|
||||
import type { ClusterRoleBindingsRouteParams } from "../../../../common/routes";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -40,7 +41,7 @@ export class ClusterRoleBindings extends React.Component<ClusterRoleBindingsProp
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: binding => binding.getName(),
|
||||
[columnId.bindings]: binding => binding.getSubjectNames(),
|
||||
[columnId.age]: binding => binding.getTimeDiffFromNow(),
|
||||
[columnId.age]: binding => -binding.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
binding => binding.getSearchFields(),
|
||||
@ -57,7 +58,7 @@ export class ClusterRoleBindings extends React.Component<ClusterRoleBindingsProp
|
||||
binding.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={binding} />,
|
||||
binding.getSubjectNames(),
|
||||
binding.getAge(),
|
||||
<KubeObjectAge key="age" object={binding} />,
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
onAdd: () => ClusterRoleBindingDialog.open(),
|
||||
|
||||
@ -13,6 +13,7 @@ import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import { AddClusterRoleDialog } from "./add-dialog";
|
||||
import { clusterRolesStore } from "./store";
|
||||
import type { ClusterRolesRouteParams } from "../../../../common/routes";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -35,7 +36,7 @@ export class ClusterRoles extends React.Component<ClusterRolesProps> {
|
||||
store={clusterRolesStore}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: clusterRole => clusterRole.getName(),
|
||||
[columnId.age]: clusterRole => clusterRole.getTimeDiffFromNow(),
|
||||
[columnId.age]: clusterRole => -clusterRole.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
clusterRole => clusterRole.getSearchFields(),
|
||||
@ -49,7 +50,7 @@ export class ClusterRoles extends React.Component<ClusterRolesProps> {
|
||||
renderTableContents={clusterRole => [
|
||||
clusterRole.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={clusterRole} />,
|
||||
clusterRole.getAge(),
|
||||
<KubeObjectAge key="age" object={clusterRole} />,
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
onAdd: () => AddClusterRoleDialog.open(),
|
||||
|
||||
@ -15,6 +15,7 @@ import { rolesStore } from "../+roles/store";
|
||||
import { clusterRolesStore } from "../+cluster-roles/store";
|
||||
import { serviceAccountsStore } from "../+service-accounts/store";
|
||||
import type { RoleBindingsRouteParams } from "../../../../common/routes";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -41,7 +42,7 @@ export class RoleBindings extends React.Component<RoleBindingsProps> {
|
||||
[columnId.name]: binding => binding.getName(),
|
||||
[columnId.namespace]: binding => binding.getNs(),
|
||||
[columnId.bindings]: binding => binding.getSubjectNames(),
|
||||
[columnId.age]: binding => binding.getTimeDiffFromNow(),
|
||||
[columnId.age]: binding => -binding.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
binding => binding.getSearchFields(),
|
||||
@ -60,7 +61,7 @@ export class RoleBindings extends React.Component<RoleBindingsProps> {
|
||||
<KubeObjectStatusIcon key="icon" object={binding} />,
|
||||
binding.getNs(),
|
||||
binding.getSubjectNames(),
|
||||
binding.getAge(),
|
||||
<KubeObjectAge key="age" object={binding} />,
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
onAdd: () => RoleBindingDialog.open(),
|
||||
|
||||
@ -13,6 +13,7 @@ import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import { AddRoleDialog } from "./add-dialog";
|
||||
import { rolesStore } from "./store";
|
||||
import type { RolesRouteParams } from "../../../../common/routes";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -36,7 +37,7 @@ export class Roles extends React.Component<RolesProps> {
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: role => role.getName(),
|
||||
[columnId.namespace]: role => role.getNs(),
|
||||
[columnId.age]: role => role.getTimeDiffFromNow(),
|
||||
[columnId.age]: role => -role.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
role => role.getSearchFields(),
|
||||
@ -52,7 +53,7 @@ export class Roles extends React.Component<RolesProps> {
|
||||
role.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={role} />,
|
||||
role.getNs(),
|
||||
role.getAge(),
|
||||
<KubeObjectAge key="age" object={role} />,
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
onAdd: () => AddRoleDialog.open(),
|
||||
|
||||
@ -13,6 +13,7 @@ import { KubeObjectStatusIcon } from "../../kube-object-status-icon";
|
||||
import { CreateServiceAccountDialog } from "./create-dialog";
|
||||
import { serviceAccountsStore } from "./store";
|
||||
import type { ServiceAccountsRouteParams } from "../../../../common/routes";
|
||||
import { KubeObjectAge } from "../../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -35,7 +36,7 @@ export class ServiceAccounts extends React.Component<ServiceAccountsProps> {
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: account => account.getName(),
|
||||
[columnId.namespace]: account => account.getNs(),
|
||||
[columnId.age]: account => account.getTimeDiffFromNow(),
|
||||
[columnId.age]: account => -account.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
account => account.getSearchFields(),
|
||||
@ -51,7 +52,7 @@ export class ServiceAccounts extends React.Component<ServiceAccountsProps> {
|
||||
account.getName(),
|
||||
<KubeObjectStatusIcon key="icon" object={account} />,
|
||||
account.getNs(),
|
||||
account.getAge(),
|
||||
<KubeObjectAge key="age" object={account} />,
|
||||
]}
|
||||
addRemoveButtons={{
|
||||
onAdd: () => CreateServiceAccountDialog.open(),
|
||||
|
||||
@ -15,6 +15,7 @@ import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { CronJobsRouteParams } from "../../../common/routes";
|
||||
import moment from "moment";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -36,7 +37,8 @@ export class CronJobs extends React.Component<CronJobsProps> {
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="workload_cronjobs"
|
||||
className="CronJobs" store={cronJobStore}
|
||||
className="CronJobs"
|
||||
store={cronJobStore}
|
||||
dependentStores={[jobStore, eventStore]}
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: cronJob => cronJob.getName(),
|
||||
@ -48,7 +50,7 @@ export class CronJobs extends React.Component<CronJobsProps> {
|
||||
? moment().diff(cronJob.status.lastScheduleTime)
|
||||
: 0
|
||||
),
|
||||
[columnId.age]: cronJob => cronJob.getTimeDiffFromNow(),
|
||||
[columnId.age]: cronJob => -cronJob.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
cronJob => cronJob.getSearchFields(),
|
||||
@ -73,7 +75,7 @@ export class CronJobs extends React.Component<CronJobsProps> {
|
||||
cronJob.getSuspendFlag(),
|
||||
cronJobStore.getActiveJobsNum(cronJob),
|
||||
cronJob.getLastScheduleTime(),
|
||||
cronJob.getAge(),
|
||||
<KubeObjectAge key="age" object={cronJob} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -16,6 +16,7 @@ import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { Badge } from "../badge";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { DaemonSetsRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -39,13 +40,14 @@ export class DaemonSets extends React.Component<DaemonSetsProps> {
|
||||
<KubeObjectListLayout
|
||||
isConfigurable
|
||||
tableId="workload_daemonsets"
|
||||
className="DaemonSets" store={daemonSetStore}
|
||||
className="DaemonSets"
|
||||
store={daemonSetStore}
|
||||
dependentStores={[podsStore, eventStore]} // status icon component uses event store
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: daemonSet => daemonSet.getName(),
|
||||
[columnId.namespace]: daemonSet => daemonSet.getNs(),
|
||||
[columnId.pods]: daemonSet => this.getPodsLength(daemonSet),
|
||||
[columnId.age]: daemonSet => daemonSet.getTimeDiffFromNow(),
|
||||
[columnId.age]: daemonSet => -daemonSet.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
daemonSet => daemonSet.getSearchFields(),
|
||||
@ -68,7 +70,7 @@ export class DaemonSets extends React.Component<DaemonSetsProps> {
|
||||
daemonSet.getNodeSelectors().map(selector => (
|
||||
<Badge key={selector} label={selector} scrollable/>
|
||||
)),
|
||||
daemonSet.getAge(),
|
||||
<KubeObjectAge key="age" object={daemonSet} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -16,6 +16,7 @@ import { Table, TableCell, TableHead, TableRow } from "../table";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
||||
import { showDetails } from "../kube-detail-params";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
|
||||
enum sortBy {
|
||||
@ -71,8 +72,7 @@ export class DeploymentReplicaSets extends React.Component<DeploymentReplicaSets
|
||||
<TableCell className="actions"/>
|
||||
</TableHead>
|
||||
{
|
||||
replicaSets.map(replica => {
|
||||
return (
|
||||
replicaSets.map(replica => (
|
||||
<TableRow
|
||||
key={replica.getId()}
|
||||
sortItem={replica}
|
||||
@ -80,16 +80,15 @@ export class DeploymentReplicaSets extends React.Component<DeploymentReplicaSets
|
||||
onClick={prevDefault(() => showDetails(replica.selfLink, false))}
|
||||
>
|
||||
<TableCell className="name">{replica.getName()}</TableCell>
|
||||
<TableCell className="warning"><KubeObjectStatusIcon key="icon" object={replica}/></TableCell>
|
||||
<TableCell className="warning"><KubeObjectStatusIcon key="icon" object={replica} /></TableCell>
|
||||
<TableCell className="namespace">{replica.getNs()}</TableCell>
|
||||
<TableCell className="pods">{this.getPodsLength(replica)}</TableCell>
|
||||
<TableCell className="age">{replica.getAge()}</TableCell>
|
||||
<TableCell className="age"><KubeObjectAge key="age" object={replica} /></TableCell>
|
||||
<TableCell className="actions" onClick={stopPropagation}>
|
||||
<ReplicaSetMenu object={replica}/>
|
||||
<ReplicaSetMenu object={replica} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
))
|
||||
}
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
@ -87,6 +87,7 @@ const dummyDeployment: Deployment = {
|
||||
getName: jest.fn(),
|
||||
getNs: jest.fn(),
|
||||
getAge: jest.fn(),
|
||||
getCreationTimestamp: jest.fn(),
|
||||
getTimeDiffFromNow: jest.fn(),
|
||||
getFinalizers: jest.fn(),
|
||||
getLabels: jest.fn(),
|
||||
|
||||
@ -17,6 +17,7 @@ import kebabCase from "lodash/kebabCase";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { DeploymentsRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -59,7 +60,7 @@ export class Deployments extends React.Component<DeploymentsProps> {
|
||||
[columnId.name]: deployment => deployment.getName(),
|
||||
[columnId.namespace]: deployment => deployment.getNs(),
|
||||
[columnId.replicas]: deployment => deployment.getReplicas(),
|
||||
[columnId.age]: deployment => deployment.getTimeDiffFromNow(),
|
||||
[columnId.age]: deployment => -deployment.getCreationTimestamp(),
|
||||
[columnId.condition]: deployment => deployment.getConditionsText(),
|
||||
}}
|
||||
searchFilters={[
|
||||
@ -82,7 +83,7 @@ export class Deployments extends React.Component<DeploymentsProps> {
|
||||
deployment.getNs(),
|
||||
this.renderPods(deployment),
|
||||
deployment.getReplicas(),
|
||||
deployment.getAge(),
|
||||
<KubeObjectAge key="age" object={deployment} />,
|
||||
this.renderConditions(deployment),
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -14,6 +14,7 @@ import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import kebabCase from "lodash/kebabCase";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { JobsRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -39,7 +40,7 @@ export class Jobs extends React.Component<JobsProps> {
|
||||
[columnId.name]: job => job.getName(),
|
||||
[columnId.namespace]: job => job.getNs(),
|
||||
[columnId.conditions]: job => job.getCondition() != null ? job.getCondition().type : "",
|
||||
[columnId.age]: job => job.getTimeDiffFromNow(),
|
||||
[columnId.age]: job => -job.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
job => job.getSearchFields(),
|
||||
@ -61,7 +62,7 @@ export class Jobs extends React.Component<JobsProps> {
|
||||
job.getNs(),
|
||||
`${job.getCompletions()} / ${job.getDesiredCompletions()}`,
|
||||
<KubeObjectStatusIcon key="icon" object={job}/>,
|
||||
job.getAge(),
|
||||
<KubeObjectAge key="age" object={job} />,
|
||||
condition && {
|
||||
title: condition.type,
|
||||
className: kebabCase(condition.type),
|
||||
|
||||
@ -23,6 +23,7 @@ import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import { Badge } from "../badge";
|
||||
import type { PodsRouteParams } from "../../../common/routes";
|
||||
import { getDetailsUrl } from "../kube-detail-params";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -89,7 +90,7 @@ export class Pods extends React.Component<PodsProps> {
|
||||
[columnId.owners]: pod => pod.getOwnerRefs().map(ref => ref.kind),
|
||||
[columnId.qos]: pod => pod.getQosClass(),
|
||||
[columnId.node]: pod => pod.getNodeName(),
|
||||
[columnId.age]: pod => pod.getTimeDiffFromNow(),
|
||||
[columnId.age]: pod => -pod.getCreationTimestamp(),
|
||||
[columnId.status]: pod => pod.getStatusMessage(),
|
||||
}}
|
||||
searchFilters={[
|
||||
@ -137,7 +138,7 @@ export class Pods extends React.Component<PodsProps> {
|
||||
</Badge>
|
||||
: "",
|
||||
pod.getQosClass(),
|
||||
pod.getAge(),
|
||||
<KubeObjectAge key="age" object={pod} />,
|
||||
{ title: pod.getStatusMessage(), className: kebabCase(pod.getStatusMessage()) },
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -82,6 +82,7 @@ const dummyReplicaSet: ReplicaSet = {
|
||||
getName: jest.fn(),
|
||||
getNs: jest.fn(),
|
||||
getAge: jest.fn(),
|
||||
getCreationTimestamp: jest.fn(),
|
||||
getTimeDiffFromNow: jest.fn(),
|
||||
getFinalizers: jest.fn(),
|
||||
getLabels: jest.fn(),
|
||||
|
||||
@ -13,6 +13,7 @@ import type { RouteComponentProps } from "react-router";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import type { ReplicaSetsRouteParams } from "../../../common/routes";
|
||||
import { eventStore } from "../+events/event.store";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -41,7 +42,7 @@ export class ReplicaSets extends React.Component<ReplicaSetsProps> {
|
||||
[columnId.desired]: replicaSet => replicaSet.getDesired(),
|
||||
[columnId.current]: replicaSet => replicaSet.getCurrent(),
|
||||
[columnId.ready]: replicaSet => replicaSet.getReady(),
|
||||
[columnId.age]: replicaSet => replicaSet.getTimeDiffFromNow(),
|
||||
[columnId.age]: replicaSet => -replicaSet.getCreationTimestamp(),
|
||||
}}
|
||||
searchFilters={[
|
||||
replicaSet => replicaSet.getSearchFields(),
|
||||
@ -63,7 +64,7 @@ export class ReplicaSets extends React.Component<ReplicaSetsProps> {
|
||||
replicaSet.getDesired(),
|
||||
replicaSet.getCurrent(),
|
||||
replicaSet.getReady(),
|
||||
replicaSet.getAge(),
|
||||
<KubeObjectAge key="age" object={replicaSet} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -92,6 +92,7 @@ const dummyStatefulSet: StatefulSet = {
|
||||
getName: jest.fn(),
|
||||
getNs: jest.fn(),
|
||||
getAge: jest.fn(),
|
||||
getCreationTimestamp: jest.fn(),
|
||||
getTimeDiffFromNow: jest.fn(),
|
||||
getFinalizers: jest.fn(),
|
||||
getLabels: jest.fn(),
|
||||
|
||||
@ -15,6 +15,7 @@ import { eventStore } from "../+events/event.store";
|
||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import type { StatefulSetsRouteParams } from "../../../common/routes";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
enum columnId {
|
||||
name = "name",
|
||||
@ -45,7 +46,7 @@ export class StatefulSets extends React.Component<StatefulSetsProps> {
|
||||
sortingCallbacks={{
|
||||
[columnId.name]: statefulSet => statefulSet.getName(),
|
||||
[columnId.namespace]: statefulSet => statefulSet.getNs(),
|
||||
[columnId.age]: statefulSet => statefulSet.getTimeDiffFromNow(),
|
||||
[columnId.age]: statefulSet => -statefulSet.getCreationTimestamp(),
|
||||
[columnId.replicas]: statefulSet => statefulSet.getReplicas(),
|
||||
}}
|
||||
searchFilters={[
|
||||
@ -66,7 +67,7 @@ export class StatefulSets extends React.Component<StatefulSetsProps> {
|
||||
this.renderPods(statefulSet),
|
||||
statefulSet.getReplicas(),
|
||||
<KubeObjectStatusIcon key="icon" object={statefulSet}/>,
|
||||
statefulSet.getAge(),
|
||||
<KubeObjectAge key="age" object={statefulSet} />,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
44
src/renderer/components/duration/reactive-duration.tsx
Normal file
44
src/renderer/components/duration/reactive-duration.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { now } from "mobx-utils";
|
||||
import React from "react";
|
||||
import { formatDuration } from "../../utils";
|
||||
|
||||
export interface ReactiveDurationProps {
|
||||
timestamp: string;
|
||||
|
||||
/**
|
||||
* Whether the display string should prefer length over precision
|
||||
* @default true
|
||||
*/
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function computes a resonable update
|
||||
*/
|
||||
function computeUpdateInterval(creationTimestampEpoch: number): number {
|
||||
const seconds = Math.floor((Date.now() - creationTimestampEpoch) / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
|
||||
if (minutes < 10) {
|
||||
// Update every second
|
||||
return 1000;
|
||||
}
|
||||
|
||||
return 60 * 1000;
|
||||
}
|
||||
|
||||
export const ReactiveDuration = observer(({ timestamp, compact = true }: ReactiveDurationProps) => {
|
||||
const creationTimestamp = new Date(timestamp).getTime();
|
||||
|
||||
return (
|
||||
<>
|
||||
{formatDuration(now(computeUpdateInterval(creationTimestamp)) - creationTimestamp, compact)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@ -12,6 +12,7 @@ import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||
import { LocaleDate } from "../locale-date";
|
||||
import { getDetailsUrl } from "../kube-detail-params";
|
||||
import logger from "../../../common/logger";
|
||||
import { KubeObjectAge } from "../kube-object/age";
|
||||
|
||||
export interface KubeObjectMetaProps {
|
||||
object: KubeObject;
|
||||
@ -44,14 +45,16 @@ export class KubeObjectMeta extends React.Component<KubeObjectMetaProps> {
|
||||
|
||||
const {
|
||||
getNs, getLabels, getResourceVersion, selfLink, getAnnotations,
|
||||
getFinalizers, getId, getAge, getName, metadata: { creationTimestamp },
|
||||
getFinalizers, getId, getName, metadata: { creationTimestamp },
|
||||
} = object;
|
||||
const ownerRefs = object.getOwnerRefs();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DrawerItem name="Created" hidden={this.isHidden("creationTimestamp")}>
|
||||
{getAge(true, false)} ago ({<LocaleDate date={creationTimestamp} />})
|
||||
<KubeObjectAge object={object} compact={false} />
|
||||
{" ago "}
|
||||
({<LocaleDate date={creationTimestamp} />})
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Name" hidden={this.isHidden("name")}>
|
||||
{getName()}
|
||||
|
||||
22
src/renderer/components/kube-object/age.tsx
Normal file
22
src/renderer/components/kube-object/age.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { ReactiveDuration } from "../duration/reactive-duration";
|
||||
|
||||
export interface KubeObjectAgeProps {
|
||||
object: KubeObject;
|
||||
|
||||
/**
|
||||
* Whether the display string should prefer length over precision
|
||||
* @default true
|
||||
*/
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export const KubeObjectAge = ({ object, compact = true }: KubeObjectAgeProps) => (
|
||||
<ReactiveDuration timestamp={object.metadata.creationTimestamp} compact={compact} />
|
||||
);
|
||||
@ -9285,6 +9285,11 @@ mobx-react@^7.2.1:
|
||||
dependencies:
|
||||
mobx-react-lite "^3.2.0"
|
||||
|
||||
mobx-utils@^6.0.4:
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mobx-utils/-/mobx-utils-6.0.4.tgz#5283a466ece8de0ac36ae3cfa1b1c032ec302b37"
|
||||
integrity sha512-CcTgFcCWN78eyRXU7OiKfhIVDEWFFoKdpfj49GIVcWykIQ4deXnaRnnKHElbVYFFgz1TOs8a3bDAq7qsSe864A==
|
||||
|
||||
mobx@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.0.tgz#a8fb693c3047bdfcb1eaff9aa48e36a7eb084f96"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user