mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
more work
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
1508aa9999
commit
70d8eeb3c8
@ -69,7 +69,6 @@ module.exports = {
|
||||
],
|
||||
"quotes": ["error", "double", {
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true,
|
||||
}],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"eol-last": ["error", "always"],
|
||||
@ -128,7 +127,6 @@ module.exports = {
|
||||
}],
|
||||
"quotes": ["error", "double", {
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true,
|
||||
}],
|
||||
"react/prop-types": "off",
|
||||
"semi": "off",
|
||||
@ -196,7 +194,6 @@ module.exports = {
|
||||
}],
|
||||
"quotes": ["error", "double", {
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true,
|
||||
}],
|
||||
"react/prop-types": "off",
|
||||
"semi": "off",
|
||||
|
||||
@ -23,10 +23,10 @@ import get from "lodash/get";
|
||||
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
||||
import { autoBind } from "../../utils";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { KubeJsonApiData } from "../kube-json-api";
|
||||
import type { IPodContainer, IPodMetrics } from "./pods.api";
|
||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
||||
import { getMetrics } from "./metrics.api";
|
||||
|
||||
export class DaemonSet extends WorkloadKubeObject {
|
||||
static kind = "DaemonSet";
|
||||
@ -106,7 +106,7 @@ export function getMetricsForDaemonSets(daemonsets: DaemonSet[], namespace: stri
|
||||
const podSelector = daemonsets.map(daemonset => `${daemonset.getName()}-[[:alnum:]]{5}`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
return getMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
|
||||
@ -22,9 +22,9 @@
|
||||
// Kubernetes apis
|
||||
// Docs: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/
|
||||
|
||||
export * from "./cluster.api";
|
||||
export * from "./cluster-role.api";
|
||||
export * from "./cluster-role-binding.api";
|
||||
export * from "./cluster-role.api";
|
||||
export * from "./cluster.api";
|
||||
export * from "./configmap.api";
|
||||
export * from "./crd.api";
|
||||
export * from "./cron-job.api";
|
||||
@ -36,22 +36,23 @@ export * from "./hpa.api";
|
||||
export * from "./ingress.api";
|
||||
export * from "./job.api";
|
||||
export * from "./limit-range.api";
|
||||
export * from "./metrics.api";
|
||||
export * from "./namespaces.api";
|
||||
export * from "./network-policy.api";
|
||||
export * from "./nodes.api";
|
||||
export * from "./persistent-volume.api";
|
||||
export * from "./persistent-volume-claims.api";
|
||||
export * from "./pods.api";
|
||||
export * from "./poddisruptionbudget.api";
|
||||
export * from "./persistent-volume.api";
|
||||
export * from "./pod-metrics.api";
|
||||
export * from "./poddisruptionbudget.api";
|
||||
export * from "./pods.api";
|
||||
export * from "./podsecuritypolicy.api";
|
||||
export * from "./replica-set.api";
|
||||
export * from "./resource-quota.api";
|
||||
export * from "./role.api";
|
||||
export * from "./role-binding.api";
|
||||
export * from "./role.api";
|
||||
export * from "./secret.api";
|
||||
export * from "./selfsubjectrulesreviews.api";
|
||||
export * from "./service.api";
|
||||
export * from "./service-accounts.api";
|
||||
export * from "./service.api";
|
||||
export * from "./stateful-set.api";
|
||||
export * from "./storage-class.api";
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
import { KubeObject } from "../kube-object";
|
||||
import { autoBind } from "../../utils";
|
||||
import { IMetrics, metricsApi } from "./metrics.api";
|
||||
import { IMetrics, getMetrics } from "./metrics.api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { KubeJsonApiData } from "../kube-json-api";
|
||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
||||
@ -32,7 +32,7 @@ export class IngressApi extends KubeApi<Ingress> {
|
||||
export function getMetricsForIngress(ingress: string, namespace: string): Promise<IIngressMetrics> {
|
||||
const opts = { category: "ingress", ingress };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
return getMetrics({
|
||||
bytesSentSuccess: opts,
|
||||
bytesSentFailure: opts,
|
||||
requestDurationSeconds: opts,
|
||||
@ -42,12 +42,12 @@ export function getMetricsForIngress(ingress: string, namespace: string): Promis
|
||||
});
|
||||
}
|
||||
|
||||
export interface IIngressMetrics<T = IMetrics> {
|
||||
[metric: string]: T;
|
||||
bytesSentSuccess: T;
|
||||
bytesSentFailure: T;
|
||||
requestDurationSeconds: T;
|
||||
responseDurationSeconds: T;
|
||||
export interface IIngressMetrics {
|
||||
[metric: string]: IMetrics;
|
||||
bytesSentSuccess: IMetrics;
|
||||
bytesSentFailure: IMetrics;
|
||||
requestDurationSeconds: IMetrics;
|
||||
responseDurationSeconds: IMetrics;
|
||||
}
|
||||
|
||||
export interface ILoadBalancerIngress {
|
||||
|
||||
@ -24,7 +24,9 @@
|
||||
import moment from "moment";
|
||||
import { apiBase } from "../index";
|
||||
import type { IMetricsQuery } from "../../../main/routes/metrics-route";
|
||||
import { iter, toJS } from "../../utils";
|
||||
import { iter } from "../../utils";
|
||||
import { mapValues } from "lodash";
|
||||
import type { Falsey } from "../../utils/iter";
|
||||
|
||||
export interface IMetrics {
|
||||
status: string;
|
||||
@ -70,33 +72,39 @@ export interface IResourceMetrics<T extends IMetrics> {
|
||||
networkTransmit: T;
|
||||
}
|
||||
|
||||
export const metricsApi = {
|
||||
async getMetrics<T = IMetricsQuery>(query: T, reqParams: IMetricsReqParams = {}): Promise<T extends object ? { [K in keyof T]: IMetrics } : IMetrics> {
|
||||
const { range = 3600, step = 60, namespace } = reqParams;
|
||||
let { start, end } = reqParams;
|
||||
export async function getMetrics<T = IMetricsQuery>(query: T, reqParams: IMetricsReqParams = {}): Promise<T extends object ? { [K in keyof T]: IMetrics } : IMetrics> {
|
||||
const { range = 3600, step = 60, namespace } = reqParams;
|
||||
let { start, end } = reqParams;
|
||||
|
||||
if (!start && !end) {
|
||||
const timeNow = Date.now() / 1000;
|
||||
const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes
|
||||
if (!start && !end) {
|
||||
const timeNow = Date.now() / 1000;
|
||||
const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes
|
||||
|
||||
start = now - range;
|
||||
end = now;
|
||||
}
|
||||
|
||||
return apiBase.post("/metrics", {
|
||||
data: query,
|
||||
query: {
|
||||
start, end, step,
|
||||
"kubernetes_namespace": namespace,
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async getMetricProviders(): Promise<MetricProviderInfo[]> {
|
||||
return apiBase.get("/metrics/providers");
|
||||
start = now - range;
|
||||
end = now;
|
||||
}
|
||||
|
||||
return apiBase.post("/metrics", {
|
||||
data: query,
|
||||
query: {
|
||||
start, end, step,
|
||||
"kubernetes_namespace": namespace,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function getMetricProviders(): Promise<MetricProviderInfo[]> {
|
||||
return apiBase.get("/metrics/providers");
|
||||
}
|
||||
|
||||
export type FlattenedMetrics<M extends Record<string, IMetrics>> = {
|
||||
[key in keyof M]: [number, string][];
|
||||
};
|
||||
|
||||
export function flattenMatricResults<M extends Record<string, IMetrics>>(metrics: M): FlattenedMetrics<M> {
|
||||
return mapValues(metrics, metric => normalizeMetrics(metric).data.result[0].values);
|
||||
}
|
||||
|
||||
export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
|
||||
if (!metrics?.data?.result) {
|
||||
return {
|
||||
@ -113,8 +121,6 @@ export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
|
||||
|
||||
const { result } = metrics.data;
|
||||
|
||||
console.log(toJS(result));
|
||||
|
||||
if (result.length) {
|
||||
if (frames > 0) {
|
||||
// fill the gaps
|
||||
@ -126,8 +132,8 @@ export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
|
||||
const now = moment().startOf("minute").subtract(1, "minute").unix();
|
||||
|
||||
for (
|
||||
let timestamp = res.values[0][0];
|
||||
timestamp <= now;
|
||||
let timestamp = res.values[0][0];
|
||||
timestamp <= now;
|
||||
timestamp = moment.unix(timestamp).add(1, "minute").unix()
|
||||
) {
|
||||
if (!res.values.find((value) => value[0] === timestamp)) {
|
||||
@ -155,8 +161,8 @@ export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
export function isMetricsEmpty(metrics: Record<string, IMetrics>) {
|
||||
return !Object.values(metrics).some(metric => metric?.data?.result?.length);
|
||||
export function isMetricsEmpty(metrics: Record<string, IMetrics> | Falsey) {
|
||||
return !metrics || !Object.values(metrics).some(metric => metric?.data?.result?.length);
|
||||
}
|
||||
|
||||
export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: string): Record<string, IMetrics> {
|
||||
@ -171,7 +177,7 @@ export function getItemMetrics(metrics: Record<string, IMetrics>, itemName: stri
|
||||
if (value?.data?.result) {
|
||||
const result = value.data.result.find(res => res.metric.container === itemName);
|
||||
|
||||
value.data.result = [result].filter(Boolean);
|
||||
value.data.result = result ? [result] : [];
|
||||
|
||||
return [key, value];
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
||||
import { autoBind } from "../../utils";
|
||||
import { IMetrics, metricsApi } from "./metrics.api";
|
||||
import { IMetrics, getMetrics } from "./metrics.api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { KubeJsonApiData } from "../kube-json-api";
|
||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
||||
@ -38,7 +38,7 @@ export function getMetricsForPods(pods: Pod[], namespace: string, selector = "po
|
||||
const podSelector = pods.map(pod => pod.getName()).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
return getMetrics({
|
||||
cpuUsage: opts,
|
||||
cpuRequests: opts,
|
||||
cpuLimits: opts,
|
||||
@ -53,17 +53,17 @@ export function getMetricsForPods(pods: Pod[], namespace: string, selector = "po
|
||||
});
|
||||
}
|
||||
|
||||
export interface IPodMetrics<T = IMetrics> {
|
||||
[metric: string]: T;
|
||||
cpuUsage: T;
|
||||
memoryUsage: T;
|
||||
fsUsage: T;
|
||||
networkReceive: T;
|
||||
networkTransmit: T;
|
||||
cpuRequests?: T;
|
||||
cpuLimits?: T;
|
||||
memoryRequests?: T;
|
||||
memoryLimits?: T;
|
||||
export interface IPodMetrics {
|
||||
[metric: string]: IMetrics;
|
||||
cpuUsage: IMetrics;
|
||||
memoryUsage: IMetrics;
|
||||
fsUsage: IMetrics;
|
||||
networkReceive: IMetrics;
|
||||
networkTransmit: IMetrics;
|
||||
cpuRequests?: IMetrics;
|
||||
cpuLimits?: IMetrics;
|
||||
memoryRequests?: IMetrics;
|
||||
memoryLimits?: IMetrics;
|
||||
}
|
||||
|
||||
// Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core
|
||||
|
||||
@ -59,6 +59,7 @@ export * from "./types";
|
||||
export * from "./convertMemory";
|
||||
export * from "./convertCpu";
|
||||
|
||||
import * as numbers from "./numbers";
|
||||
import * as iter from "./iter";
|
||||
|
||||
export { iter };
|
||||
export { iter, numbers };
|
||||
|
||||
30
src/common/utils/numbers.ts
Normal file
30
src/common/utils/numbers.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return the clamp of `x` by the bounds `min` and `max`.
|
||||
* @param x The number to clamp
|
||||
* @param min The lower bound
|
||||
* @param max The upper bound
|
||||
*/
|
||||
export function clamp(x: number, { min, max }: { min: number, max: number}): number {
|
||||
return Math.min(max, Math.max(x, min));
|
||||
}
|
||||
@ -22,16 +22,13 @@
|
||||
import "./cluster-overview.scss";
|
||||
|
||||
import React from "react";
|
||||
import { observable, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { nodesStore } from "../+nodes/nodes.store";
|
||||
import { podsStore } from "../+workloads-pods/pods.store";
|
||||
import { boundMethod, createStorage, interval } from "../../utils";
|
||||
import { boundMethod, createStorage } from "../../utils";
|
||||
import { TabLayout } from "../layout/tab-layout";
|
||||
import { Spinner } from "../spinner";
|
||||
import { ClusterIssues } from "./cluster-issues";
|
||||
import { ClusterMetrics } from "./cluster-metrics";
|
||||
import { kubeClusterStore } from "./cluster-overview.store";
|
||||
import { ClusterPieCharts } from "./cluster-pie-charts";
|
||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||
@ -88,7 +85,6 @@ export class ClusterOverview extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { metrics } = this;
|
||||
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Cluster);
|
||||
|
||||
return (
|
||||
@ -98,7 +94,7 @@ export class ClusterOverview extends React.Component {
|
||||
<ResourceMetrics
|
||||
loader={this.loadMetrics}
|
||||
tabs={[MetricType.CPU, MetricType.MEMORY]}
|
||||
params={{ metrics }}
|
||||
metrics={this.metrics}
|
||||
>
|
||||
<ClusterMetrics />
|
||||
<ClusterPieCharts />
|
||||
|
||||
@ -23,7 +23,7 @@ import React, { useContext } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import type { ChartOptions, ChartPoint } from "chart.js";
|
||||
import type { IIngressMetrics, Ingress } from "../../../common/k8s-api/endpoints";
|
||||
import { BarChart, memoryOptions } from "../chart";
|
||||
import { BarChart, defaultBarChartOptions } from "../chart";
|
||||
import { normalizeMetrics, isMetricsEmpty } from "../../../common/k8s-api/endpoints/metrics.api";
|
||||
import { NoMetrics } from "../resource-metrics/no-metrics";
|
||||
import { ResourceMetricsContext, IResourceMetricsValue } from "../resource-metrics";
|
||||
@ -52,15 +52,15 @@ export const IngressCharts = observer(() => {
|
||||
[
|
||||
{
|
||||
id: `${id}-bytesSentSuccess`,
|
||||
label: `Bytes sent, status 2xx`,
|
||||
tooltip: `Bytes sent by Ingress controller with successful status`,
|
||||
label: "Bytes sent, status 2xx",
|
||||
tooltip: "Bytes sent by Ingress controller with successful status",
|
||||
borderColor: "#46cd9e",
|
||||
data: bytesSentSuccess.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-bytesSentFailure`,
|
||||
label: `Bytes sent, status 5xx`,
|
||||
tooltip: `Bytes sent by Ingress controller with error status`,
|
||||
label: "Bytes sent, status 5xx",
|
||||
tooltip: "Bytes sent by Ingress controller with error status",
|
||||
borderColor: "#cd465a",
|
||||
data: bytesSentFailure.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
@ -69,15 +69,15 @@ export const IngressCharts = observer(() => {
|
||||
[
|
||||
{
|
||||
id: `${id}-requestDurationSeconds`,
|
||||
label: `Request`,
|
||||
tooltip: `Request duration in seconds`,
|
||||
label: "Request",
|
||||
tooltip: "Request duration in seconds",
|
||||
borderColor: "#48b18d",
|
||||
data: requestDurationSeconds.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-responseDurationSeconds`,
|
||||
label: `Response`,
|
||||
tooltip: `Response duration in seconds`,
|
||||
label: "Response",
|
||||
tooltip: "Response duration in seconds",
|
||||
borderColor: "#73ba3c",
|
||||
data: responseDurationSeconds.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
@ -97,7 +97,7 @@ export const IngressCharts = observer(() => {
|
||||
label: ({ datasetIndex, index }, { datasets }) => {
|
||||
const { label, data } = datasets[datasetIndex];
|
||||
const value = data[index] as ChartPoint;
|
||||
const chartTooltipSec = `sec`;
|
||||
const chartTooltipSec = "sec";
|
||||
|
||||
return `${label}: ${parseFloat(value.y as string).toFixed(3)} ${chartTooltipSec}`;
|
||||
}
|
||||
@ -105,7 +105,7 @@ export const IngressCharts = observer(() => {
|
||||
}
|
||||
};
|
||||
|
||||
const options = [memoryOptions, durationOptions];
|
||||
const options = [defaultBarChartOptions, durationOptions];
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
|
||||
@ -35,6 +35,7 @@ import { getBackendServiceNamePort, getMetricsForIngress, IIngressMetrics } from
|
||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||
import { boundMethod } from "../../utils";
|
||||
import { ingressMetricTabs } from "../metrics-helpers";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<Ingress> {
|
||||
}
|
||||
@ -63,42 +64,41 @@ export class IngressDetails extends React.Component<Props> {
|
||||
renderPaths(ingress: Ingress) {
|
||||
const { spec: { rules } } = ingress;
|
||||
|
||||
if (!rules || !rules.length) return null;
|
||||
if (!rules) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return rules.map((rule, index) => {
|
||||
return (
|
||||
<div className="rules" key={index}>
|
||||
{rule.host && (
|
||||
<div className="host-title">
|
||||
<>Host: {rule.host}</>
|
||||
</div>
|
||||
)}
|
||||
{rule.http && (
|
||||
<Table className="paths">
|
||||
<TableHead>
|
||||
<TableCell className="path">Path</TableCell>
|
||||
<TableCell className="backends">Backends</TableCell>
|
||||
</TableHead>
|
||||
{
|
||||
rule.http.paths.map((path, index) => {
|
||||
const { serviceName, servicePort } = getBackendServiceNamePort(path.backend);
|
||||
const backend = `${serviceName}:${servicePort}`;
|
||||
return rules.map((rule, index) => (
|
||||
<div className="rules" key={index}>
|
||||
{rule.host && (
|
||||
<div className="host-title">
|
||||
Host: {rule.host}
|
||||
</div>
|
||||
)}
|
||||
{rule.http && (
|
||||
<Table className="paths">
|
||||
<TableHead>
|
||||
<TableCell className="path">Path</TableCell>
|
||||
<TableCell className="backends">Backends</TableCell>
|
||||
</TableHead>
|
||||
{
|
||||
rule.http.paths.map((path, index) => {
|
||||
const { serviceName, servicePort } = getBackendServiceNamePort(path.backend);
|
||||
|
||||
return (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="path">{path.path || ""}</TableCell>
|
||||
<TableCell className="backends">
|
||||
<p key={backend}>{backend}</p>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="path">{path.path || ""}</TableCell>
|
||||
<TableCell className="backends">
|
||||
<p>{serviceName}:{servicePort}</p>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
renderIngressPoints(ingressPoints: ILoadBalancerIngress[]) {
|
||||
@ -134,11 +134,6 @@ export class IngressDetails extends React.Component<Props> {
|
||||
|
||||
const { spec, status } = ingress;
|
||||
const ingressPoints = status?.loadBalancer?.ingress;
|
||||
const { metrics } = this;
|
||||
const metricTabs = [
|
||||
"Network",
|
||||
"Duration",
|
||||
];
|
||||
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Ingress);
|
||||
const { serviceName, servicePort } = ingress.getServiceNamePort();
|
||||
|
||||
@ -147,7 +142,9 @@ export class IngressDetails extends React.Component<Props> {
|
||||
{!isMetricHidden && (
|
||||
<ResourceMetrics
|
||||
loader={this.loadMetrics}
|
||||
tabs={metricTabs} object={ingress} params={{ metrics }}
|
||||
tabs={ingressMetricTabs}
|
||||
object={ingress}
|
||||
metrics={this.metrics}
|
||||
>
|
||||
<IngressCharts/>
|
||||
</ResourceMetrics>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
import React, { useContext } from "react";
|
||||
import type { IClusterMetrics, Node } from "../../../common/k8s-api/endpoints";
|
||||
import { BarChart, cpuOptions, memoryOptions } from "../chart";
|
||||
import { BarChart, cpuOptions, defaultBarChartOptions } from "../chart";
|
||||
import { isMetricsEmpty, normalizeMetrics } from "../../../common/k8s-api/endpoints/metrics.api";
|
||||
import { NoMetrics } from "../resource-metrics/no-metrics";
|
||||
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
||||
@ -66,29 +66,29 @@ export const NodeCharts = observer(() => {
|
||||
[
|
||||
{
|
||||
id: `${id}-cpuUsage`,
|
||||
label: `Usage`,
|
||||
tooltip: `CPU cores usage`,
|
||||
label: "Usage",
|
||||
tooltip: "CPU cores usage",
|
||||
borderColor: "#3D90CE",
|
||||
data: cpuUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-cpuRequests`,
|
||||
label: `Requests`,
|
||||
tooltip: `CPU requests`,
|
||||
label: "Requests",
|
||||
tooltip: "CPU requests",
|
||||
borderColor: "#30b24d",
|
||||
data: cpuRequests.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-cpuAllocatableCapacity`,
|
||||
label: `Allocatable Capacity`,
|
||||
tooltip: `CPU allocatable capacity`,
|
||||
label: "Allocatable Capacity",
|
||||
tooltip: "CPU allocatable capacity",
|
||||
borderColor: "#032b4d",
|
||||
data: cpuAllocatableCapacity.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-cpuCapacity`,
|
||||
label: `Capacity`,
|
||||
tooltip: `CPU capacity`,
|
||||
label: "Capacity",
|
||||
tooltip: "CPU capacity",
|
||||
borderColor: chartCapacityColor,
|
||||
data: cpuCapacity.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
@ -97,36 +97,36 @@ export const NodeCharts = observer(() => {
|
||||
[
|
||||
{
|
||||
id: `${id}-memoryUsage`,
|
||||
label: `Usage`,
|
||||
tooltip: `Memory usage`,
|
||||
label: "Usage",
|
||||
tooltip: "Memory usage",
|
||||
borderColor: "#c93dce",
|
||||
data: memoryUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-workloadMemoryUsage`,
|
||||
label: `Workload Memory Usage`,
|
||||
tooltip: `Workload memory usage`,
|
||||
label: "Workload Memory Usage",
|
||||
tooltip: "Workload memory usage",
|
||||
borderColor: "#9cd3ce",
|
||||
data: workloadMemoryUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: "memoryRequests",
|
||||
label: `Requests`,
|
||||
tooltip: `Memory requests`,
|
||||
label: "Requests",
|
||||
tooltip: "Memory requests",
|
||||
borderColor: "#30b24d",
|
||||
data: memoryRequests.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-memoryAllocatableCapacity`,
|
||||
label: `Allocatable Capacity`,
|
||||
tooltip: `Memory allocatable capacity`,
|
||||
label: "Allocatable Capacity",
|
||||
tooltip: "Memory allocatable capacity",
|
||||
borderColor: "#032b4d",
|
||||
data: memoryAllocatableCapacity.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-memoryCapacity`,
|
||||
label: `Capacity`,
|
||||
tooltip: `Memory capacity`,
|
||||
label: "Capacity",
|
||||
tooltip: "Memory capacity",
|
||||
borderColor: chartCapacityColor,
|
||||
data: memoryCapacity.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
@ -135,15 +135,15 @@ export const NodeCharts = observer(() => {
|
||||
[
|
||||
{
|
||||
id: `${id}-fsUsage`,
|
||||
label: `Usage`,
|
||||
tooltip: `Node filesystem usage in bytes`,
|
||||
label: "Usage",
|
||||
tooltip: "Node filesystem usage in bytes",
|
||||
borderColor: "#ffc63d",
|
||||
data: fsUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-fsSize`,
|
||||
label: `Size`,
|
||||
tooltip: `Node filesystem size in bytes`,
|
||||
label: "Size",
|
||||
tooltip: "Node filesystem size in bytes",
|
||||
borderColor: chartCapacityColor,
|
||||
data: fsSize.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
@ -152,15 +152,15 @@ export const NodeCharts = observer(() => {
|
||||
[
|
||||
{
|
||||
id: `${id}-podUsage`,
|
||||
label: `Usage`,
|
||||
tooltip: `Number of running Pods`,
|
||||
label: "Usage",
|
||||
tooltip: "Number of running Pods",
|
||||
borderColor: "#30b24d",
|
||||
data: podUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-podCapacity`,
|
||||
label: `Capacity`,
|
||||
tooltip: `Node Pods capacity`,
|
||||
label: "Capacity",
|
||||
tooltip: "Node Pods capacity",
|
||||
borderColor: chartCapacityColor,
|
||||
data: podCapacity.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
@ -187,7 +187,7 @@ export const NodeCharts = observer(() => {
|
||||
}
|
||||
};
|
||||
|
||||
const options = [cpuOptions, memoryOptions, memoryOptions, podOptions];
|
||||
const options = [cpuOptions, defaultBarChartOptions, defaultBarChartOptions, podOptions];
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
|
||||
@ -40,6 +40,7 @@ import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||
import { NodeDetailsResources } from "./node-details-resources";
|
||||
import { DrawerTitle } from "../drawer/drawer-title";
|
||||
import { boundMethod } from "../../utils";
|
||||
import { nodeDetailsMetricTabs } from "../metrics-helpers";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<Node> {
|
||||
}
|
||||
@ -78,13 +79,6 @@ export class NodeDetails extends React.Component<Props> {
|
||||
const conditions = node.getActiveConditions();
|
||||
const taints = node.getTaints();
|
||||
const childPods = podsStore.getPodsByNode(node.getName());
|
||||
const { metrics } = this;
|
||||
const metricTabs = [
|
||||
"CPU",
|
||||
"Memory",
|
||||
"Disk",
|
||||
"Pods",
|
||||
];
|
||||
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Node);
|
||||
|
||||
return (
|
||||
@ -92,7 +86,9 @@ export class NodeDetails extends React.Component<Props> {
|
||||
{!isMetricHidden && podsStore.isLoaded && (
|
||||
<ResourceMetrics
|
||||
loader={this.loadMetrics}
|
||||
tabs={metricTabs} object={node} params={{ metrics }}
|
||||
tabs={nodeDetailsMetricTabs}
|
||||
object={node}
|
||||
metrics={this.metrics}
|
||||
>
|
||||
<NodeCharts/>
|
||||
</ResourceMetrics>
|
||||
|
||||
@ -69,7 +69,6 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
||||
return null;
|
||||
}
|
||||
const { storageClassName, accessModes } = volumeClaim.spec;
|
||||
const { metrics } = this;
|
||||
const pods = volumeClaim.getPods(podsStore.items);
|
||||
const metricTabs = [
|
||||
"Disk"
|
||||
@ -81,12 +80,12 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
||||
{!isMetricHidden && (
|
||||
<ResourceMetrics
|
||||
loader={this.loadMetrics}
|
||||
tabs={metricTabs} object={volumeClaim} params={{ metrics }}
|
||||
tabs={metricTabs} object={volumeClaim} metrics={this.metrics}
|
||||
>
|
||||
<VolumeClaimDiskChart/>
|
||||
<VolumeClaimDiskChart />
|
||||
</ResourceMetrics>
|
||||
)}
|
||||
<KubeObjectMeta object={volumeClaim}/>
|
||||
<KubeObjectMeta object={volumeClaim} />
|
||||
<DrawerItem name="Access Modes">
|
||||
{accessModes.join(", ")}
|
||||
</DrawerItem>
|
||||
@ -107,10 +106,10 @@ export class PersistentVolumeClaimDetails extends React.Component<Props> {
|
||||
{volumeClaim.getStatus()}
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerTitle title="Selector"/>
|
||||
<DrawerTitle title="Selector" />
|
||||
|
||||
<DrawerItem name="Match Labels" labelsOnly>
|
||||
{volumeClaim.getMatchLabels().map(label => <Badge key={label} label={label}/>)}
|
||||
{volumeClaim.getMatchLabels().map(label => <Badge key={label} label={label} />)}
|
||||
</DrawerItem>
|
||||
|
||||
<DrawerItem name="Match Expressions">
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
import React, { useContext } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import type { IPvcMetrics, PersistentVolumeClaim } from "../../../common/k8s-api/endpoints";
|
||||
import { BarChart, ChartDataSets, memoryOptions } from "../chart";
|
||||
import { BarChart, ChartDataSets, defaultBarChartOptions } from "../chart";
|
||||
import { isMetricsEmpty, normalizeMetrics } from "../../../common/k8s-api/endpoints/metrics.api";
|
||||
import { NoMetrics } from "../resource-metrics/no-metrics";
|
||||
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
||||
@ -45,15 +45,15 @@ export const VolumeClaimDiskChart = observer(() => {
|
||||
const datasets: ChartDataSets[] = [
|
||||
{
|
||||
id: `${id}-diskUsage`,
|
||||
label: `Usage`,
|
||||
tooltip: `Volume disk usage`,
|
||||
label: "Usage",
|
||||
tooltip: "Volume disk usage",
|
||||
borderColor: "#ffc63d",
|
||||
data: usage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-diskCapacity`,
|
||||
label: `Capacity`,
|
||||
tooltip: `Volume disk capacity`,
|
||||
label: "Capacity",
|
||||
tooltip: "Volume disk capacity",
|
||||
borderColor: chartCapacityColor,
|
||||
data: capacity.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
@ -64,7 +64,7 @@ export const VolumeClaimDiskChart = observer(() => {
|
||||
className="VolumeClaimDiskChart flex box grow column"
|
||||
name={`pvc-${object.getName()}-disk`}
|
||||
timeLabelStep={10}
|
||||
options={memoryOptions}
|
||||
options={defaultBarChartOptions}
|
||||
data={{ datasets }}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -33,13 +33,14 @@ import { podsStore } from "../+workloads-pods/pods.store";
|
||||
import type { KubeObjectDetailsProps } from "../kube-object-details";
|
||||
import { DaemonSet, getMetricsForDaemonSets, IPodMetrics } from "../../../common/k8s-api/endpoints";
|
||||
import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics";
|
||||
import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts";
|
||||
import { PodCharts } from "../+workloads-pods/pod-charts";
|
||||
import { makeObservable, observable, reaction } from "mobx";
|
||||
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
|
||||
import { KubeObjectMeta } from "../kube-object-meta";
|
||||
import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||
import { boundMethod } from "../../utils";
|
||||
import { podMetricTabs } from "../metrics-helpers";
|
||||
|
||||
interface Props extends KubeObjectDetailsProps<DaemonSet> {
|
||||
}
|
||||
@ -85,7 +86,9 @@ export class DaemonSetDetails extends React.Component<Props> {
|
||||
{!isMetricHidden && podsStore.isLoaded && (
|
||||
<ResourceMetrics
|
||||
loader={this.loadMetrics}
|
||||
tabs={podMetricTabs} object={daemonSet} params={{ metrics: this.metrics }}
|
||||
tabs={podMetricTabs}
|
||||
object={daemonSet}
|
||||
metrics={this.metrics}
|
||||
>
|
||||
<PodCharts/>
|
||||
</ResourceMetrics>
|
||||
|
||||
@ -22,22 +22,15 @@
|
||||
import React, { useContext } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import type { IPodMetrics } from "../../../common/k8s-api/endpoints";
|
||||
import { BarChart, cpuOptions, memoryOptions } from "../chart";
|
||||
import { isMetricsEmpty, normalizeMetrics } from "../../../common/k8s-api/endpoints/metrics.api";
|
||||
import { BarChart, ChartDataSets } from "../chart";
|
||||
import { FlattenedMetrics, flattenMatricResults, isMetricsEmpty } from "../../../common/k8s-api/endpoints/metrics.api";
|
||||
import { NoMetrics } from "../resource-metrics/no-metrics";
|
||||
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
||||
import { ResourceMetricsContext } from "../resource-metrics";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import { mapValues } from "lodash";
|
||||
import { ContainerMetricsTab, getBarChartOptions } from "../metrics-helpers";
|
||||
|
||||
type IContext = IResourceMetricsValue<any, { metrics: IPodMetrics }>;
|
||||
|
||||
export const ContainerCharts = observer(() => {
|
||||
const { params: { metrics }, tabId } = useContext<IContext>(ResourceMetricsContext);
|
||||
function getDatasets(tab: string, metrics: FlattenedMetrics<IPodMetrics>): ChartDataSets[] | null {
|
||||
const { chartCapacityColor } = ThemeStore.getInstance().activeTheme.colors;
|
||||
|
||||
if (!metrics) return null;
|
||||
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
||||
|
||||
const {
|
||||
cpuUsage,
|
||||
cpuRequests,
|
||||
@ -46,76 +39,90 @@ export const ContainerCharts = observer(() => {
|
||||
memoryRequests,
|
||||
memoryLimits,
|
||||
fsUsage
|
||||
} = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0].values);
|
||||
} = metrics;
|
||||
|
||||
const datasets = [
|
||||
// CPU
|
||||
[
|
||||
{
|
||||
id: "cpuUsage",
|
||||
label: `Usage`,
|
||||
tooltip: `CPU cores usage`,
|
||||
borderColor: "#3D90CE",
|
||||
data: cpuUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: "cpuRequests",
|
||||
label: `Requests`,
|
||||
tooltip: `CPU requests`,
|
||||
borderColor: "#30b24d",
|
||||
data: cpuRequests.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: "cpuLimits",
|
||||
label: `Limits`,
|
||||
tooltip: `CPU limits`,
|
||||
borderColor: chartCapacityColor,
|
||||
data: cpuLimits.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
],
|
||||
// Memory
|
||||
[
|
||||
{
|
||||
id: "memoryUsage",
|
||||
label: `Usage`,
|
||||
tooltip: `Memory usage`,
|
||||
borderColor: "#c93dce",
|
||||
data: memoryUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: "memoryRequests",
|
||||
label: `Requests`,
|
||||
tooltip: `Memory requests`,
|
||||
borderColor: "#30b24d",
|
||||
data: memoryRequests.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: "memoryLimits",
|
||||
label: `Limits`,
|
||||
tooltip: `Memory limits`,
|
||||
borderColor: chartCapacityColor,
|
||||
data: memoryLimits.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
],
|
||||
// Filesystem
|
||||
[
|
||||
{
|
||||
id: "fsUsage",
|
||||
label: `Usage`,
|
||||
tooltip: `Bytes consumed on this filesystem`,
|
||||
borderColor: "#ffc63d",
|
||||
data: fsUsage.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
]
|
||||
];
|
||||
switch (tab) {
|
||||
case ContainerMetricsTab.CPU:
|
||||
return [
|
||||
{
|
||||
id: "cpuUsage",
|
||||
label: "Usage",
|
||||
tooltip: "CPU cores usage",
|
||||
borderColor: "#3D90CE",
|
||||
data: cpuUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: "cpuRequests",
|
||||
label: "Requests",
|
||||
tooltip: "CPU requests",
|
||||
borderColor: "#30b24d",
|
||||
data: cpuRequests.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: "cpuLimits",
|
||||
label: "Limits",
|
||||
tooltip: "CPU limits",
|
||||
borderColor: chartCapacityColor,
|
||||
data: cpuLimits.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
];
|
||||
case ContainerMetricsTab.MEMORY:
|
||||
return [
|
||||
{
|
||||
id: "memoryUsage",
|
||||
label: "Usage",
|
||||
tooltip: "Memory usage",
|
||||
borderColor: "#c93dce",
|
||||
data: memoryUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: "memoryRequests",
|
||||
label: "Requests",
|
||||
tooltip: "Memory requests",
|
||||
borderColor: "#30b24d",
|
||||
data: memoryRequests.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: "memoryLimits",
|
||||
label: "Limits",
|
||||
tooltip: "Memory limits",
|
||||
borderColor: chartCapacityColor,
|
||||
data: memoryLimits.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
];
|
||||
case ContainerMetricsTab.FILESYSTEM:
|
||||
return [
|
||||
{
|
||||
id: "fsUsage",
|
||||
label: "Usage",
|
||||
tooltip: "Bytes consumed on this filesystem",
|
||||
borderColor: "#ffc63d",
|
||||
data: fsUsage.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
];
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const options = tabId == 0 ? cpuOptions : memoryOptions;
|
||||
export const ContainerCharts = observer(() => {
|
||||
const { metrics, tab } = useContext(ResourceMetricsContext);
|
||||
|
||||
if (isMetricsEmpty(metrics)) {
|
||||
return <NoMetrics />;
|
||||
}
|
||||
|
||||
const datasets = getDatasets(tab, flattenMatricResults(metrics as IPodMetrics));
|
||||
|
||||
if (!datasets) {
|
||||
return <NoMetrics />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
name={`metrics-${tabId}`}
|
||||
options={options}
|
||||
data={{ datasets: datasets[tabId] }}
|
||||
name={`metrics-${tab}`}
|
||||
options={getBarChartOptions(tab)}
|
||||
data={{ datasets }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@ -19,97 +19,97 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { mapValues } from "lodash";
|
||||
import { observer } from "mobx-react";
|
||||
import React, { useContext } from "react";
|
||||
import { isMetricsEmpty, normalizeMetrics } from "../../../common/k8s-api/endpoints/metrics.api";
|
||||
import { BarChart, cpuOptions, memoryOptions } from "../chart";
|
||||
import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metrics";
|
||||
import { FlattenedMetrics, flattenMatricResults, isMetricsEmpty } from "../../../common/k8s-api/endpoints/metrics.api";
|
||||
import { BarChart, ChartDataSets } from "../chart";
|
||||
import { ResourceMetricsContext } from "../resource-metrics";
|
||||
import { NoMetrics } from "../resource-metrics/no-metrics";
|
||||
|
||||
import type { WorkloadKubeObject } from "../../../common/k8s-api/workload-kube-object";
|
||||
import type { IPodMetrics } from "../../../common/k8s-api/endpoints";
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { getBarChartOptions, PodMetricsTab } from "../metrics-helpers";
|
||||
|
||||
export const podMetricTabs = [
|
||||
"CPU",
|
||||
"Memory",
|
||||
"Network",
|
||||
"Filesystem",
|
||||
];
|
||||
|
||||
type IContext = IResourceMetricsValue<WorkloadKubeObject, { metrics: IPodMetrics }>;
|
||||
|
||||
export const PodCharts = observer(() => {
|
||||
const { params: { metrics }, tabId, object } = useContext<IContext>(ResourceMetricsContext);
|
||||
function getDatasets(object: KubeObject, tab: string, metrics: FlattenedMetrics<IPodMetrics>): ChartDataSets[] | null {
|
||||
const id = object.getId();
|
||||
|
||||
if (!metrics) return null;
|
||||
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
||||
|
||||
const options = tabId == 0 ? cpuOptions : memoryOptions;
|
||||
const {
|
||||
cpuUsage,
|
||||
memoryUsage,
|
||||
fsUsage,
|
||||
networkReceive,
|
||||
networkTransmit
|
||||
} = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0].values);
|
||||
networkTransmit,
|
||||
fsUsage,
|
||||
} = metrics;
|
||||
|
||||
const datasets = [
|
||||
// CPU
|
||||
[
|
||||
{
|
||||
id: `${id}-cpuUsage`,
|
||||
label: `Usage`,
|
||||
tooltip: `Container CPU cores usage`,
|
||||
borderColor: "#3D90CE",
|
||||
data: cpuUsage.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
],
|
||||
// Memory
|
||||
[
|
||||
{
|
||||
id: `${id}-memoryUsage`,
|
||||
label: `Usage`,
|
||||
tooltip: `Container memory usage`,
|
||||
borderColor: "#c93dce",
|
||||
data: memoryUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
],
|
||||
// Network
|
||||
[
|
||||
{
|
||||
id: `${id}-networkReceive`,
|
||||
label: `Receive`,
|
||||
tooltip: `Bytes received by all containers`,
|
||||
borderColor: "#64c5d6",
|
||||
data: networkReceive.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-networkTransmit`,
|
||||
label: `Transmit`,
|
||||
tooltip: `Bytes transmitted from all containers`,
|
||||
borderColor: "#46cd9e",
|
||||
data: networkTransmit.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
],
|
||||
// Filesystem
|
||||
[
|
||||
{
|
||||
id: `${id}-fsUsage`,
|
||||
label: `Usage`,
|
||||
tooltip: `Bytes consumed on this filesystem`,
|
||||
borderColor: "#ffc63d",
|
||||
data: fsUsage.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
]
|
||||
];
|
||||
switch (tab) {
|
||||
case PodMetricsTab.CPU:
|
||||
return [
|
||||
{
|
||||
id: `${id}-cpuUsage`,
|
||||
label: "Usage",
|
||||
tooltip: "Container CPU cores usage",
|
||||
borderColor: "#3D90CE",
|
||||
data: cpuUsage.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
];
|
||||
case PodMetricsTab.MEMORY:
|
||||
return [
|
||||
{
|
||||
id: `${id}-memoryUsage`,
|
||||
label: "Usage",
|
||||
tooltip: "Container memory usage",
|
||||
borderColor: "#c93dce",
|
||||
data: memoryUsage.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
];
|
||||
case PodMetricsTab.NETWORK:
|
||||
return [
|
||||
{
|
||||
id: `${id}-networkReceive`,
|
||||
label: "Receive",
|
||||
tooltip: "Bytes received by all containers",
|
||||
borderColor: "#64c5d6",
|
||||
data: networkReceive.map(([x, y]) => ({ x, y }))
|
||||
},
|
||||
{
|
||||
id: `${id}-networkTransmit`,
|
||||
label: "Transmit",
|
||||
tooltip: "Bytes transmitted from all containers",
|
||||
borderColor: "#46cd9e",
|
||||
data: networkTransmit.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
];
|
||||
case PodMetricsTab.FILESYSTEM:
|
||||
return [
|
||||
{
|
||||
id: `${id}-fsUsage`,
|
||||
label: "Usage",
|
||||
tooltip: "Bytes consumed on this filesystem",
|
||||
borderColor: "#ffc63d",
|
||||
data: fsUsage.map(([x, y]) => ({ x, y }))
|
||||
}
|
||||
];
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const PodCharts = observer(() => {
|
||||
const { metrics, tab, object } = useContext(ResourceMetricsContext);
|
||||
|
||||
if (isMetricsEmpty(metrics)) {
|
||||
return <NoMetrics />;
|
||||
}
|
||||
|
||||
const datasets = getDatasets(object, tab, flattenMatricResults(metrics as IPodMetrics));
|
||||
|
||||
if (!datasets) {
|
||||
return <NoMetrics />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
name={`${object.getName()}-metric-${tabId}`}
|
||||
options={options}
|
||||
data={{ datasets: datasets[tabId] }}
|
||||
name={`${object.getName()}-metric-${tab}`}
|
||||
options={getBarChartOptions(tab)}
|
||||
data={{ datasets }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@ -37,6 +37,7 @@ import { getActiveClusterEntity } from "../../api/catalog-entity-registry";
|
||||
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
|
||||
import { portForwardStore } from "../../port-forward/port-forward.store";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { containerMetricTabs } from "../metrics-helpers";
|
||||
|
||||
interface Props {
|
||||
pod: Pod;
|
||||
@ -58,7 +59,7 @@ export class PodDetailsContainer extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<span className={cssNames("status", state)}>
|
||||
{state}{ready ? `, ready` : ""}
|
||||
{state}{ready ? ", ready" : ""}
|
||||
{state === "terminated" ? ` - ${status.state.terminated.reason} (exit code: ${status.state.terminated.exitCode})` : ""}
|
||||
</span>
|
||||
);
|
||||
@ -68,10 +69,10 @@ export class PodDetailsContainer extends React.Component<Props> {
|
||||
if (lastState === "terminated") {
|
||||
return (
|
||||
<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/>
|
||||
{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 />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -88,113 +89,111 @@ export class PodDetailsContainer extends React.Component<Props> {
|
||||
const state = status ? Object.keys(status.state)[0] : "";
|
||||
const lastState = status ? Object.keys(status.lastState)[0] : "";
|
||||
const ready = status ? status.ready : "";
|
||||
const imageId = status? status.imageID : "";
|
||||
const imageId = status ? status.imageID : "";
|
||||
const liveness = pod.getLivenessProbe(container);
|
||||
const readiness = pod.getReadinessProbe(container);
|
||||
const startup = pod.getStartupProbe(container);
|
||||
const isInitContainer = !!pod.getInitContainers().find(c => c.name == name);
|
||||
const metricTabs = [
|
||||
"CPU",
|
||||
"Memory",
|
||||
"Filesystem",
|
||||
];
|
||||
const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Container);
|
||||
|
||||
return (
|
||||
<div className="PodDetailsContainer">
|
||||
<div className="pod-container-title">
|
||||
<StatusBrick className={cssNames(state, { ready })}/>{name}
|
||||
<StatusBrick className={cssNames(state, { ready })} />{name}
|
||||
</div>
|
||||
{!isMetricHidden && !isInitContainer &&
|
||||
<ResourceMetrics tabs={metricTabs} params={{ metrics }}>
|
||||
<ContainerCharts/>
|
||||
</ResourceMetrics>
|
||||
<ResourceMetrics
|
||||
tabs={containerMetricTabs}
|
||||
metrics={metrics}
|
||||
>
|
||||
<ContainerCharts />
|
||||
</ResourceMetrics>
|
||||
}
|
||||
{status &&
|
||||
<DrawerItem name="Status">
|
||||
{this.renderStatus(state, status)}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Status">
|
||||
{this.renderStatus(state, status)}
|
||||
</DrawerItem>
|
||||
}
|
||||
{lastState &&
|
||||
<DrawerItem name="Last Status">
|
||||
{this.renderLastState(lastState, status)}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Last Status">
|
||||
{this.renderLastState(lastState, status)}
|
||||
</DrawerItem>
|
||||
}
|
||||
<DrawerItem name="Image">
|
||||
<Badge label={image} tooltip={imageId}/>
|
||||
<Badge label={image} tooltip={imageId} />
|
||||
</DrawerItem>
|
||||
{imagePullPolicy && imagePullPolicy !== "IfNotPresent" &&
|
||||
<DrawerItem name="ImagePullPolicy">
|
||||
{imagePullPolicy}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="ImagePullPolicy">
|
||||
{imagePullPolicy}
|
||||
</DrawerItem>
|
||||
}
|
||||
{ports && ports.length > 0 &&
|
||||
<DrawerItem name="Ports">
|
||||
{
|
||||
ports.map((port) => {
|
||||
const key = `${container.name}-port-${port.containerPort}-${port.protocol}`;
|
||||
<DrawerItem name="Ports">
|
||||
{
|
||||
ports.map((port) => {
|
||||
const key = `${container.name}-port-${port.containerPort}-${port.protocol}`;
|
||||
|
||||
return (
|
||||
<PodContainerPort pod={pod} port={port} key={key}/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</DrawerItem>
|
||||
return (
|
||||
<PodContainerPort pod={pod} port={port} key={key} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</DrawerItem>
|
||||
}
|
||||
{<ContainerEnvironment container={container} namespace={pod.getNs()}/>}
|
||||
{<ContainerEnvironment container={container} namespace={pod.getNs()} />}
|
||||
{volumeMounts && volumeMounts.length > 0 &&
|
||||
<DrawerItem name="Mounts">
|
||||
{
|
||||
volumeMounts.map(mount => {
|
||||
const { name, mountPath, readOnly } = mount;
|
||||
<DrawerItem name="Mounts">
|
||||
{
|
||||
volumeMounts.map(mount => {
|
||||
const { name, mountPath, readOnly } = mount;
|
||||
|
||||
return (
|
||||
<React.Fragment key={name + mountPath}>
|
||||
<span className="mount-path">{mountPath}</span>
|
||||
<span className="mount-from">from {name} ({readOnly ? "ro" : "rw"})</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
}
|
||||
</DrawerItem>
|
||||
return (
|
||||
<React.Fragment key={name + mountPath}>
|
||||
<span className="mount-path">{mountPath}</span>
|
||||
<span className="mount-from">from {name} ({readOnly ? "ro" : "rw"})</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
}
|
||||
</DrawerItem>
|
||||
}
|
||||
{liveness.length > 0 &&
|
||||
<DrawerItem name="Liveness" labelsOnly>
|
||||
{
|
||||
liveness.map((value, index) => (
|
||||
<Badge key={index} label={value}/>
|
||||
))
|
||||
}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Liveness" labelsOnly>
|
||||
{
|
||||
liveness.map((value, index) => (
|
||||
<Badge key={index} label={value} />
|
||||
))
|
||||
}
|
||||
</DrawerItem>
|
||||
}
|
||||
{readiness.length > 0 &&
|
||||
<DrawerItem name="Readiness" labelsOnly>
|
||||
{
|
||||
readiness.map((value, index) => (
|
||||
<Badge key={index} label={value}/>
|
||||
))
|
||||
}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Readiness" labelsOnly>
|
||||
{
|
||||
readiness.map((value, index) => (
|
||||
<Badge key={index} label={value} />
|
||||
))
|
||||
}
|
||||
</DrawerItem>
|
||||
}
|
||||
{startup.length > 0 &&
|
||||
<DrawerItem name="Startup" labelsOnly>
|
||||
{
|
||||
startup.map((value, index) => (
|
||||
<Badge key={index} label={value}/>
|
||||
))
|
||||
}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Startup" labelsOnly>
|
||||
{
|
||||
startup.map((value, index) => (
|
||||
<Badge key={index} label={value} />
|
||||
))
|
||||
}
|
||||
</DrawerItem>
|
||||
}
|
||||
{command &&
|
||||
<DrawerItem name="Command">
|
||||
{command.join(" ")}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Command">
|
||||
{command.join(" ")}
|
||||
</DrawerItem>
|
||||
}
|
||||
|
||||
{args &&
|
||||
<DrawerItem name="Arguments">
|
||||
{args.join(" ")}
|
||||
</DrawerItem>
|
||||
<DrawerItem name="Arguments">
|
||||
{args.join(" ")}
|
||||
</DrawerItem>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -82,7 +82,6 @@ export class PodDetails extends React.Component<Props> {
|
||||
}
|
||||
|
||||
const { status: { conditions, podIP }, spec: { nodeName } } = pod;
|
||||
const { metrics } = this;
|
||||
const podIPs = pod.getIPs();
|
||||
const nodeSelector = pod.getNodeSelectors();
|
||||
const volumes = pod.getVolumes();
|
||||
@ -96,7 +95,7 @@ export class PodDetails extends React.Component<Props> {
|
||||
loader={this.loadMetrics}
|
||||
tabs={podMetricTabs}
|
||||
object={pod}
|
||||
params={{ metrics }}
|
||||
metrics={this.metrics}
|
||||
>
|
||||
<PodCharts/>
|
||||
</ResourceMetrics>
|
||||
|
||||
@ -72,7 +72,6 @@ export class ReplicaSetDetails extends React.Component<Props> {
|
||||
const { object: replicaSet } = this.props;
|
||||
|
||||
if (!replicaSet) return null;
|
||||
const { metrics } = this;
|
||||
const { status } = replicaSet;
|
||||
const { availableReplicas, replicas } = status;
|
||||
const selectors = replicaSet.getSelectors();
|
||||
@ -86,7 +85,7 @@ export class ReplicaSetDetails extends React.Component<Props> {
|
||||
{!isMetricHidden && podsStore.isLoaded && (
|
||||
<ResourceMetrics
|
||||
loader={this.loadMetrics}
|
||||
tabs={podMetricTabs} object={replicaSet} params={{ metrics }}
|
||||
tabs={podMetricTabs} object={replicaSet} metrics={this.metrics}
|
||||
>
|
||||
<PodCharts/>
|
||||
</ResourceMetrics>
|
||||
@ -121,7 +120,7 @@ export class ReplicaSetDetails extends React.Component<Props> {
|
||||
<DrawerItem name="Pod Status" className="pod-status">
|
||||
<PodDetailsStatuses pods={childPods}/>
|
||||
</DrawerItem>
|
||||
<ResourceMetricsText metrics={metrics}/>
|
||||
<ResourceMetricsText metrics={this.metrics}/>
|
||||
<PodDetailsList pods={childPods} owner={replicaSet}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -26,7 +26,7 @@ import Color from "color";
|
||||
import { observer } from "mobx-react";
|
||||
import type { ChartData, ChartOptions, ChartPoint, ChartTooltipItem, Scriptable } from "chart.js";
|
||||
import { Chart, ChartKind, ChartProps } from "./chart";
|
||||
import { bytesToUnits, cssNames } from "../../utils";
|
||||
import { bytesToUnits, cssNames, numbers } from "../../utils";
|
||||
import { ZebraStripes } from "./zebra-stripes.plugin";
|
||||
import { ThemeStore } from "../../theme.store";
|
||||
import { NoMetrics } from "../resource-metrics/no-metrics";
|
||||
@ -144,12 +144,10 @@ export class BarChart extends React.Component<Props> {
|
||||
|
||||
return String(xLabel);
|
||||
},
|
||||
labelColor: ({ datasetIndex }) => {
|
||||
return {
|
||||
borderColor: "darkgray",
|
||||
backgroundColor: chartData.datasets[datasetIndex].borderColor as string
|
||||
};
|
||||
}
|
||||
labelColor: ({ datasetIndex }) => ({
|
||||
borderColor: "darkgray",
|
||||
backgroundColor: chartData.datasets[datasetIndex].borderColor as string
|
||||
})
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
@ -183,7 +181,7 @@ export class BarChart extends React.Component<Props> {
|
||||
}
|
||||
|
||||
// Default options for all charts containing memory units (network, disk, memory, etc)
|
||||
export const memoryOptions: ChartOptions = {
|
||||
const memoryUnits: ChartOptions = {
|
||||
scales: {
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
@ -217,18 +215,18 @@ export const memoryOptions: ChartOptions = {
|
||||
};
|
||||
|
||||
// Default options for all charts with cpu units or other decimal numbers
|
||||
export const cpuOptions: ChartOptions = {
|
||||
const decimalUnits: ChartOptions = {
|
||||
scales: {
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
callback: (value: number | string): string => {
|
||||
const float = parseFloat(`${value}`);
|
||||
const parsed = typeof value === "string"
|
||||
? parseFloat(value)
|
||||
: value;
|
||||
const magnitude = numbers.clamp(Math.floor(Math.log10(parsed)), { min: 0, max: 2 });
|
||||
const precision = 3 - magnitude;
|
||||
|
||||
if (float == 0) return "0";
|
||||
if (float < 10) return float.toFixed(3);
|
||||
if (float < 100) return float.toFixed(2);
|
||||
|
||||
return float.toFixed(1);
|
||||
return parsed.toFixed(precision);
|
||||
}
|
||||
}
|
||||
}]
|
||||
@ -244,3 +242,8 @@ export const cpuOptions: ChartOptions = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const barChartOptions = Object.freeze({
|
||||
decimalUnits,
|
||||
memoryUnits,
|
||||
});
|
||||
|
||||
118
src/renderer/components/metrics-helpers/index.ts
Normal file
118
src/renderer/components/metrics-helpers/index.ts
Normal file
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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 { barChartOptions } from "../chart";
|
||||
|
||||
/**
|
||||
* The CPU metrics tab
|
||||
*/
|
||||
enum CPUTab {
|
||||
CPU = "CPU"
|
||||
}
|
||||
|
||||
/**
|
||||
* The Memory metrics tab
|
||||
*/
|
||||
enum MemoryTab {
|
||||
MEMORY = "Memory"
|
||||
}
|
||||
|
||||
/**
|
||||
* The Filesystem metrics tab
|
||||
*/
|
||||
enum FilesystemTab {
|
||||
FILESYSTEM = "Filesystem"
|
||||
}
|
||||
|
||||
/**
|
||||
* The Disk metrics tab
|
||||
*/
|
||||
enum DiskTab {
|
||||
DISK = "Disk"
|
||||
}
|
||||
|
||||
/**
|
||||
* The Network metrics tab
|
||||
*/
|
||||
enum NetworkTab {
|
||||
NETWORK = "Network"
|
||||
}
|
||||
|
||||
/**
|
||||
* The Duration metrics tab
|
||||
*/
|
||||
enum DurationTab {
|
||||
DURATION = "Duration"
|
||||
}
|
||||
|
||||
/**
|
||||
* The Pods metrics tab
|
||||
*/
|
||||
enum PodsTab {
|
||||
PODS = "Pods"
|
||||
}
|
||||
|
||||
export type PodMetricsTab = typeof PodMetricsTab;
|
||||
export const PodMetricsTab = {
|
||||
...CPUTab,
|
||||
...MemoryTab,
|
||||
...NetworkTab,
|
||||
...FilesystemTab,
|
||||
};
|
||||
export const podMetricTabs = Object.values(PodMetricsTab);
|
||||
|
||||
export type ContainerMetricsTab = typeof ContainerMetricsTab;
|
||||
export const ContainerMetricsTab = {
|
||||
...CPUTab,
|
||||
...MemoryTab,
|
||||
...FilesystemTab,
|
||||
};
|
||||
export const containerMetricTabs = Object.values(ContainerMetricsTab);
|
||||
|
||||
export type NodeDetailsMetricsTab = typeof NodeDetailsMetricsTab;
|
||||
export const NodeDetailsMetricsTab = {
|
||||
...CPUTab,
|
||||
...MemoryTab,
|
||||
...DiskTab,
|
||||
...PodsTab,
|
||||
};
|
||||
export const nodeDetailsMetricTabs = Object.values(NodeDetailsMetricsTab);
|
||||
|
||||
export type IngressMetricsTab = typeof IngressMetricsTab;
|
||||
export const IngressMetricsTab = {
|
||||
...NetworkTab,
|
||||
...DurationTab,
|
||||
};
|
||||
export const ingressMetricTabs = Object.values(IngressMetricsTab);
|
||||
|
||||
/**
|
||||
* Get the bar chart options for a specific chart tab
|
||||
* @param tab The tab ID
|
||||
* @returns Bar chart options for that metrics tab
|
||||
*/
|
||||
export function getBarChartOptions(tab: string) {
|
||||
switch (tab) {
|
||||
case CPUTab.CPU:
|
||||
return barChartOptions.decimalUnits;
|
||||
default:
|
||||
return barChartOptions.memoryUnits;
|
||||
}
|
||||
}
|
||||
@ -27,33 +27,34 @@ import { useInterval } from "../../hooks";
|
||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
import { cssNames, IClassName } from "../../utils";
|
||||
import { Spinner } from "../spinner";
|
||||
import type { IMetrics } from "../../../common/k8s-api/endpoints";
|
||||
|
||||
interface Props<T> {
|
||||
tabs: string[] | string[][];
|
||||
interface Props {
|
||||
tabs: string[];
|
||||
object?: KubeObject;
|
||||
loader?: () => void;
|
||||
interval?: number;
|
||||
className?: IClassName;
|
||||
params?: T;
|
||||
metrics?: Record<string, IMetrics>;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export type IResourceMetricsValue<T extends KubeObject = any, P = any> = {
|
||||
object: T;
|
||||
tabId: number;
|
||||
params?: P;
|
||||
export type IResourceMetricsValue<K extends KubeObject = KubeObject, Metrics extends Record<string, IMetrics> = Record<string, IMetrics>> = {
|
||||
object?: K;
|
||||
tab: string;
|
||||
metrics?: Metrics;
|
||||
};
|
||||
|
||||
export const ResourceMetricsContext = createContext<IResourceMetricsValue>(null);
|
||||
|
||||
const defaultProps: Partial<Props<any>> = {
|
||||
const defaultProps: Partial<Props> = {
|
||||
interval: 60 // 1 min
|
||||
};
|
||||
|
||||
ResourceMetrics.defaultProps = defaultProps;
|
||||
|
||||
export function ResourceMetrics<T>({ object, loader, interval, tabs, children, className, params }: Props<T>) {
|
||||
const [tabId, setTabId] = useState<number>(0);
|
||||
export function ResourceMetrics({ object, loader, interval, tabs, children, className, metrics }: Props) {
|
||||
const [tab, setTab] = useState<string>(tabs[0]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loader) loader();
|
||||
@ -69,15 +70,15 @@ export function ResourceMetrics<T>({ object, loader, interval, tabs, children, c
|
||||
<RadioGroup
|
||||
asButtons
|
||||
className="flex box grow gaps"
|
||||
value={tabs[tabId]}
|
||||
onChange={value => setTabId(tabs.findIndex(tab => tab == value))}
|
||||
value={tab}
|
||||
onChange={value => setTab(value)}
|
||||
>
|
||||
{tabs.map((tab, index) => (
|
||||
<Radio key={index} className="box grow" label={tab} value={tab} />
|
||||
))}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<ResourceMetricsContext.Provider value={{ object, tabId, params }}>
|
||||
<ResourceMetricsContext.Provider value={{ object, tab, metrics }}>
|
||||
<div className="graph">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user