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