diff --git a/package.json b/package.json index 490f9accda..e85cd73760 100644 --- a/package.json +++ b/package.json @@ -229,7 +229,7 @@ "proper-lockfile": "^4.1.2", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-material-ui-carousel": "^2.3.1", + "react-material-ui-carousel": "^2.3.5", "react-router": "^5.2.0", "react-virtualized-auto-sizer": "^1.0.6", "readable-stream": "^3.6.0", @@ -376,7 +376,7 @@ "ts-node": "^10.2.1", "type-fest": "^1.0.2", "typed-emitter": "^1.3.1", - "typedoc": "0.22.5", + "typedoc": "0.22.6", "typedoc-plugin-markdown": "^3.11.3", "typeface-roboto": "^1.1.13", "typescript": "^4.4.3", diff --git a/src/common/catalog/catalog-category-registry.ts b/src/common/catalog/catalog-category-registry.ts index e6da48a815..735911aa51 100644 --- a/src/common/catalog/catalog-category-registry.ts +++ b/src/common/catalog/catalog-category-registry.ts @@ -58,7 +58,7 @@ export class CatalogCategoryRegistry { iter.reduce( this.filters, iter.filter, - this.items, + this.items.values(), ) ); } diff --git a/src/common/catalog/catalog-entity.ts b/src/common/catalog/catalog-entity.ts index d59a21686c..cbe59bae57 100644 --- a/src/common/catalog/catalog-entity.ts +++ b/src/common/catalog/catalog-entity.ts @@ -105,7 +105,7 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm iter.reduce( this.filters, iter.filter, - menuItems, + menuItems.values(), ) ); } diff --git a/src/common/k8s-api/endpoints/nodes.api.ts b/src/common/k8s-api/endpoints/nodes.api.ts index 01571a7d16..bd09044776 100644 --- a/src/common/k8s-api/endpoints/nodes.api.ts +++ b/src/common/k8s-api/endpoints/nodes.api.ts @@ -20,7 +20,7 @@ */ import { KubeObject } from "../kube-object"; -import { autoBind, cpuUnitsToNumber, unitsToBytes } from "../../../renderer/utils"; +import { autoBind, cpuUnitsToNumber, iter, unitsToBytes } from "../../../renderer/utils"; import { IMetrics, metricsApi } from "./metrics.api"; import { KubeApi } from "../kube-api"; import type { KubeJsonApiData } from "../kube-json-api"; @@ -71,6 +71,15 @@ export function formatNodeTaint(taint: NodeTaint): string { return `${taint.key}:${taint.effect}`; } +export interface NodeCondition { + type: string; + status: string; + lastHeartbeatTime?: string; + lastTransitionTime?: string; + reason?: string; + message?: string; +} + export interface Node { spec: { podCIDR?: string; @@ -100,14 +109,7 @@ export interface Node { memory: string; pods: string; }; - conditions?: { - type: string; - status: string; - lastHeartbeatTime?: string; - lastTransitionTime?: string; - reason?: string; - message?: string; - }[]; + conditions?: NodeCondition[]; addresses?: { type: string; address: string; @@ -141,6 +143,19 @@ export interface Node { }; } +/** + * Iterate over `conditions` yielding the `type` field if the `status` field is + * the string `"True"` + * @param conditions An iterator of some conditions + */ +function* getTrueConditionTypes(conditions: IterableIterator | Iterable): IterableIterator { + for (const { status, type } of conditions) { + if (status === "True") { + yield type; + } + } +} + export class Node extends KubeObject { static kind = "Node"; static namespaced = false; @@ -151,16 +166,15 @@ export class Node extends KubeObject { autoBind(this); } - getNodeConditionText() { - const { conditions } = this.status; - - if (!conditions) return ""; - - return conditions.reduce((types, current) => { - if (current.status !== "True") return ""; - - return types += ` ${current.type}`; - }, ""); + /** + * Returns the concatination of all current condition types which have a status + * of `"True"` + */ + getNodeConditionText(): string { + return iter.join( + getTrueConditionTypes(this.status?.conditions ?? []), + " ", + ); } getTaints() { diff --git a/src/common/utils/iter.ts b/src/common/utils/iter.ts index 65b0937726..dd97c8feef 100644 --- a/src/common/utils/iter.ts +++ b/src/common/utils/iter.ts @@ -25,7 +25,7 @@ export type Falsey = false | 0 | "" | null | undefined; * Create a new type safe empty Iterable * @returns An `Iterable` that yields 0 items */ -export function* newEmpty(): Iterable { +export function* newEmpty(): IterableIterator { return; } @@ -35,7 +35,7 @@ export function* newEmpty(): Iterable { * @param src An initial iterator * @param n The maximum number of elements to take from src. Yields up to the floor of `n` and 0 items if n < 0 */ -export function* take(src: Iterable, n: number): Iterable { +export function* take(src: Iterable, n: number): IterableIterator { outer: for (let i = 0; i < n; i += 1) { for (const item of src) { yield item; @@ -53,7 +53,7 @@ export function* take(src: Iterable, n: number): Iterable { * @param src A type that can be iterated over * @param fn The function that is called for each value */ -export function* map(src: Iterable, fn: (from: T) => U): Iterable { +export function* map(src: Iterable, fn: (from: T) => U): IterableIterator { for (const from of src) { yield fn(from); } @@ -64,7 +64,7 @@ export function* map(src: Iterable, fn: (from: T) => U): Iterable { * @param src A type that can be iterated over * @param fn The function that returns either an iterable over items that should be filtered out or a `Falsey` value indicating that it should be ignored */ -export function* filterFlatMap(src: Iterable, fn: (from: T) => Iterable | Falsey): Iterable { +export function* filterFlatMap(src: Iterable, fn: (from: T) => Iterable | Falsey): IterableIterator { for (const from of src) { if (!from) { continue; @@ -89,7 +89,7 @@ export function* filterFlatMap(src: Iterable, fn: (from: T) => Iterable * @param src A type that can be iterated over * @param fn A function that returns an iterator */ -export function* flatMap(src: Iterable, fn: (from: T) => Iterable): Iterable { +export function* flatMap(src: Iterable, fn: (from: T) => Iterable): IterableIterator { for (const from of src) { yield* fn(from); } @@ -101,7 +101,7 @@ export function* flatMap(src: Iterable, fn: (from: T) => Iterable): * @param src A type that can be iterated over * @param fn The function that is called for each value */ -export function* filter(src: Iterable, fn: (from: T) => any): Iterable { +export function* filter(src: Iterable, fn: (from: T) => any): IterableIterator { for (const from of src) { if (fn(from)) { yield from; @@ -115,7 +115,7 @@ export function* filter(src: Iterable, fn: (from: T) => any): Iterable * @param src A type that can be iterated over * @param fn The function that is called for each value */ -export function* filterMap(src: Iterable, fn: (from: T) => U | Falsey): Iterable { +export function* filterMap(src: Iterable, fn: (from: T) => U | Falsey): IterableIterator { for (const from of src) { const res = fn(from); @@ -131,7 +131,7 @@ export function* filterMap(src: Iterable, fn: (from: T) => U | Falsey): * @param src A type that can be iterated over * @param fn The function that is called for each value */ -export function* filterMapStrict(src: Iterable, fn: (from: T) => U | null | undefined): Iterable { +export function* filterMapStrict(src: Iterable, fn: (from: T) => U | null | undefined): IterableIterator { for (const from of src) { const res = fn(from); @@ -164,7 +164,7 @@ export function find(src: Iterable, match: (i: T) => any): T | undefined { * @param reducer A function for producing the next item from an accumilation and the current item * @param initial The initial value for the iteration */ -export function reduce(src: Iterable, reducer: (acc: Iterable, cur: T) => Iterable, initial: Iterable): Iterable; +export function reduce>(src: Iterable, reducer: (acc: R, cur: T) => R, initial: R): R; export function reduce(src: Iterable, reducer: (acc: R, cur: T) => R, initial: R): R { let acc = initial; @@ -175,3 +175,13 @@ export function reduce(src: Iterable, reducer: (acc: R, cur: T) => return acc; } + +/** + * A convenience function for reducing over an iterator of strings and concatenating them together + * @param src The value to iterate over + * @param connector The string value to intersperse between the yielded values + * @returns The concatenated entries of `src` interspersed with copies of `connector` + */ +export function join(src: Iterable, connector = ","): string { + return reduce(src, (acc, cur) => `${acc}${connector}${cur}`, ""); +} diff --git a/src/renderer/api/catalog-entity-registry.ts b/src/renderer/api/catalog-entity-registry.ts index 1b7e79e67c..acf65219a5 100644 --- a/src/renderer/api/catalog-entity-registry.ts +++ b/src/renderer/api/catalog-entity-registry.ts @@ -130,7 +130,7 @@ export class CatalogEntityRegistry { iter.reduce( this.filters, iter.filter, - this.items, + this.items.values(), ) ); } diff --git a/src/renderer/components/+cluster/cluster-issues.tsx b/src/renderer/components/+cluster/cluster-issues.tsx index fb5b98bc28..3f9b5c6b90 100644 --- a/src/renderer/components/+cluster/cluster-issues.tsx +++ b/src/renderer/components/+cluster/cluster-issues.tsx @@ -22,7 +22,7 @@ import "./cluster-issues.scss"; import React from "react"; -import { disposeOnUnmount, observer } from "mobx-react"; +import { observer } from "mobx-react"; import { computed, makeObservable } from "mobx"; import { Icon } from "../icon"; import { SubHeader } from "../layout/sub-header"; @@ -34,7 +34,6 @@ import type { ItemObject } from "../../../common/item.store"; import { Spinner } from "../spinner"; import { ThemeStore } from "../../theme.store"; import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params"; -import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; import { apiManager } from "../../../common/k8s-api/api-manager"; interface Props { @@ -66,10 +65,6 @@ export class ClusterIssues extends React.Component { constructor(props: Props) { super(props); makeObservable(this); - - disposeOnUnmount(this, [ - kubeWatchApi.subscribeStores([eventStore, nodesStore]) - ]); } @computed get warnings() { diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx index e68c2f752f..248c7de427 100644 --- a/src/renderer/components/+cluster/cluster-overview.tsx +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -36,6 +36,8 @@ import { ClusterPieCharts } from "./cluster-pie-charts"; import { getActiveClusterEntity } from "../../api/catalog-entity-registry"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { ClusterStore } from "../../../common/cluster-store"; +import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; +import { eventStore } from "../+events/event.store"; @observer export class ClusterOverview extends React.Component { @@ -53,6 +55,9 @@ export class ClusterOverview extends React.Component { this.metricPoller.start(true); disposeOnUnmount(this, [ + kubeWatchApi.subscribeStores([podsStore, eventStore, nodesStore], { + preload: true, + }), reaction( () => clusterOverviewStore.metricNodeRole, // Toggle Master/Worker node switcher () => this.metricPoller.restart(true) @@ -91,7 +96,7 @@ export class ClusterOverview extends React.Component { } render() { - const isLoaded = nodesStore.isLoaded && podsStore.isLoaded; + const isLoaded = nodesStore.isLoaded && eventStore.isLoaded; const isMetricHidden = getActiveClusterEntity()?.isMetricHidden(ClusterMetricsResourceType.Cluster); return ( diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 769c9fe9c4..59bf2ea065 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -42,9 +42,6 @@ import whatInput from "what-input"; import { clusterSetFrameIdHandler } from "../../common/cluster-ipc"; import { ClusterPageMenuRegistration, ClusterPageMenuRegistry } from "../../extensions/registries"; import { StatefulSetScaleDialog } from "./+workloads-statefulsets/statefulset-scale-dialog"; -import { eventStore } from "./+events/event.store"; -import { nodesStore } from "./+nodes/nodes.store"; -import { podsStore } from "./+workloads-pods/pods.store"; import { kubeWatchApi } from "../../common/k8s-api/kube-watch-api"; import { ReplicaSetScaleDialog } from "./+workloads-replicasets/replicaset-scale-dialog"; import { CommandContainer } from "./command-palette/command-container"; @@ -137,7 +134,7 @@ export class App extends React.Component { componentDidMount() { disposeOnUnmount(this, [ - kubeWatchApi.subscribeStores([podsStore, nodesStore, eventStore, namespaceStore], { + kubeWatchApi.subscribeStores([namespaceStore], { preload: true, }), diff --git a/yarn.lock b/yarn.lock index b736310b49..2ead9306c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11569,10 +11569,10 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== -react-material-ui-carousel@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/react-material-ui-carousel/-/react-material-ui-carousel-2.3.1.tgz#73b516f831d45df70bb9219f56fee14a2788e332" - integrity sha512-QV3z+x10x19rSAPSHMmkHtdx/PSiwqo6FeAaY8Y04LtuKM0ZxHsgeMslNdcI8M1G2B48391YqN+ouWFPYqAbTA== +react-material-ui-carousel@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/react-material-ui-carousel/-/react-material-ui-carousel-2.3.5.tgz#c6b4514333564ff4d49d2447f6d67f6c7651e813" + integrity sha512-laiysIaA47rX0/lIVjprX5MHrDyjLxgzAsyBnG8OKgO2ad5o3Pg88PAmu6RlMy/L/H6PKFYQ8H5bAqpeZETeYw== dependencies: auto-bind "^2.1.1" react-swipeable "^6.1.0" @@ -13851,10 +13851,10 @@ typedoc-plugin-markdown@^3.11.3: dependencies: handlebars "^4.7.7" -typedoc@0.22.5: - version "0.22.5" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.22.5.tgz#c1a7c33fcdc808f57c766584a6afb47762944229" - integrity sha512-KFrWGU1iKiTGw0RcyjLNYDmhd7uICU14HgBNPmFKY/sT4Pm/fraaLyWyisst9vGTUAKxqibqoDITR7+ZcAkhHg== +typedoc@0.22.6: + version "0.22.6" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.22.6.tgz#1122f83a6eb5cd7dbb26d1924de1f9de9e8c7c7e" + integrity sha512-ePbJqOaz0GNkU2ehRwFwBpLD4Gp6m7jbJfHysXmDdjVKc1g8DFJ83r/LOZ9TZrkC661vgpoIY3FjSPEtUilHNA== dependencies: glob "^7.2.0" lunr "^2.3.9"