1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Catch and display load errors for WorkloadsOverview (#4393)

This commit is contained in:
Sebastian Malton 2021-11-22 12:27:54 -05:00 committed by GitHub
parent 0e5fc65806
commit 78a4e2a126
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 29 deletions

View File

@ -19,11 +19,12 @@
* 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 { orderBy } from "lodash";
import type React from "react"; import type React from "react";
import { BaseRegistry } from "./base-registry"; import { BaseRegistry } from "./base-registry";
export interface WorkloadsOverviewDetailComponents { export interface WorkloadsOverviewDetailComponents {
Details: React.ComponentType<any>; Details: React.ComponentType<{}>;
} }
export interface WorkloadsOverviewDetailRegistration { export interface WorkloadsOverviewDetailRegistration {
@ -31,10 +32,16 @@ export interface WorkloadsOverviewDetailRegistration {
priority?: number; priority?: number;
} }
export class WorkloadsOverviewDetailRegistry extends BaseRegistry<WorkloadsOverviewDetailRegistration> { type RegisteredWorkloadsOverviewDetail = Required<WorkloadsOverviewDetailRegistration>;
getItems() {
const items = super.getItems();
return items.sort((a, b) => (b.priority ?? 50) - (a.priority ?? 50)); export class WorkloadsOverviewDetailRegistry extends BaseRegistry<WorkloadsOverviewDetailRegistration, RegisteredWorkloadsOverviewDetail> {
getItems() {
return orderBy(super.getItems(), "priority", "desc");
}
protected getRegisteredItem(item: WorkloadsOverviewDetailRegistration): RegisteredWorkloadsOverviewDetail {
const { priority = 50, ...rest } = item;
return { priority, ...rest };
} }
} }

View File

@ -25,15 +25,6 @@
min-width: $unit * 75; min-width: $unit * 75;
background: var(--contentColor); background: var(--contentColor);
> .header {
position: relative;
padding: $padding * 2;
h5 {
color: var(--textColorPrimary);
}
}
.workloads { .workloads {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, 180px); grid-template-columns: repeat(auto-fit, 180px);

View File

@ -27,7 +27,6 @@ import { OverviewWorkloadStatus } from "./overview-workload-status";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { workloadStores } from "../+workloads"; import { workloadStores } from "../+workloads";
import { namespaceStore } from "../+namespaces/namespace.store"; import { namespaceStore } from "../+namespaces/namespace.store";
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
import type { KubeResource } from "../../../common/rbac"; import type { KubeResource } from "../../../common/rbac";
import { ResourceNames } from "../../utils/rbac"; import { ResourceNames } from "../../utils/rbac";
import { boundMethod } from "../../utils"; import { boundMethod } from "../../utils";
@ -73,10 +72,6 @@ export class OverviewStatuses extends React.Component {
return ( return (
<div className="OverviewStatuses"> <div className="OverviewStatuses">
<div className="header flex gaps align-center">
<h5 className="box grow">Overview</h5>
<NamespaceSelectFilter />
</div>
<div className="workloads"> <div className="workloads">
{workloads} {workloads}
</div> </div>

View File

@ -22,4 +22,18 @@
.WorkloadsOverview { .WorkloadsOverview {
--flex-gap: #{$padding * 2}; --flex-gap: #{$padding * 2};
min-height: 100%; min-height: 100%;
.header {
background: var(--contentColor);
position: relative;
padding: $padding * 2;
h5 {
color: var(--textColorPrimary);
}
.Icon.load-error {
color: var(--colorWarning);
}
}
} }

View File

@ -33,36 +33,77 @@ import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
import { jobStore } from "../+workloads-jobs/job.store"; import { jobStore } from "../+workloads-jobs/job.store";
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api"; import { kubeWatchApi } from "../../../common/k8s-api/kube-watch-api";
import { clusterContext } from "../context";
import { WorkloadsOverviewDetailRegistry } from "../../../extensions/registries"; import { WorkloadsOverviewDetailRegistry } from "../../../extensions/registries";
import type { WorkloadsOverviewRouteParams } from "../../../common/routes"; import type { WorkloadsOverviewRouteParams } from "../../../common/routes";
import { makeObservable, observable, reaction } from "mobx";
import { clusterContext } from "../context";
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
import { Icon } from "../icon";
import { TooltipPosition } from "../tooltip";
interface Props extends RouteComponentProps<WorkloadsOverviewRouteParams> { interface Props extends RouteComponentProps<WorkloadsOverviewRouteParams> {
} }
@observer @observer
export class WorkloadsOverview extends React.Component<Props> { export class WorkloadsOverview extends React.Component<Props> {
@observable loadErrors: string[] = [];
constructor(props: Props) {
super(props);
makeObservable(this);
}
componentDidMount() { componentDidMount() {
disposeOnUnmount(this, [ disposeOnUnmount(this, [
kubeWatchApi.subscribeStores([ kubeWatchApi.subscribeStores([
podsStore, deploymentStore, daemonSetStore, statefulSetStore, replicaSetStore, podsStore, deploymentStore, daemonSetStore, statefulSetStore, replicaSetStore,
jobStore, cronJobStore, eventStore, jobStore, cronJobStore, eventStore,
], { ], {
preload: true, onLoadFailure: error => this.loadErrors.push(String(error)),
namespaces: clusterContext.contextNamespaces, }),
reaction(() => clusterContext.contextNamespaces.slice(), () => {
// clear load errors
this.loadErrors.length = 0;
}), }),
]); ]);
} }
render() { renderLoadErrors() {
const items = WorkloadsOverviewDetailRegistry.getInstance().getItems().map((item, index) => { if (this.loadErrors.length === 0) {
return null;
}
return ( return (
<item.components.Details key={`workload-overview-${index}`}/> <Icon
material="warning"
className="load-error"
tooltip={{
children: (
<>
{this.loadErrors.map((error, index) => <p key={index}>{error}</p>)}
</>
),
preferredPositions: TooltipPosition.BOTTOM,
}}
/>
); );
}); }
render() {
const items = WorkloadsOverviewDetailRegistry
.getInstance()
.getItems()
.map(({ components: { Details }}, index) => (
<Details key={`workload-overview-${index}`}/>
));
return ( return (
<div className="WorkloadsOverview flex column gaps"> <div className="WorkloadsOverview flex column gaps">
<div className="header flex gaps align-center">
<h5 className="box grow">Overview</h5>
{this.renderLoadErrors()}
<NamespaceSelectFilter />
</div>
{items} {items}
</div> </div>
); );

View File

@ -67,7 +67,7 @@ export class KubeObjectListLayout<K extends KubeObject> extends React.Component<
const { store, dependentStores = [], subscribeStores } = this.props; const { store, dependentStores = [], subscribeStores } = this.props;
const stores = Array.from(new Set([store, ...dependentStores])); const stores = Array.from(new Set([store, ...dependentStores]));
const reactions: Disposer[] = [ const reactions: Disposer[] = [
reaction(() => clusterContext.contextNamespaces.length, () => { reaction(() => clusterContext.contextNamespaces.slice(), () => {
// clear load errors // clear load errors
this.loadErrors.length = 0; this.loadErrors.length = 0;
}), }),

View File

@ -30,7 +30,7 @@ export function initWorkloadsOverviewDetailRegistry() {
.add([ .add([
{ {
components: { components: {
Details: (props: any) => <OverviewStatuses {...props} />, Details: OverviewStatuses,
}, },
}, },
{ {