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

cleanup KubeObjectListLayout

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2020-12-14 21:33:51 -05:00
parent 3300a99a78
commit a62e1fc4e5
12 changed files with 89 additions and 103 deletions

View File

@ -3,18 +3,19 @@ import type { KubeObjectStore } from "../kube-object.store";
import { action, observable } from "mobx"; import { action, observable } from "mobx";
import { autobind } from "../utils"; import { autobind } from "../utils";
import { KubeApi } from "./kube-api"; import { KubeApi } from "./kube-api";
import { KubeObject } from "./kube-object";
@autobind() @autobind()
export class ApiManager { export class ApiManager {
private apis = observable.map<string, KubeApi>(); private apis = observable.map<string, KubeApi>();
private stores = observable.map<KubeApi, KubeObjectStore>(); private stores = observable.map<KubeApi, KubeObjectStore<any>>();
getApi(pathOrCallback: string | ((api: KubeApi) => boolean)) { getApi(pathOrCallback: string | ((api: KubeApi) => boolean) = () => true) {
if (typeof pathOrCallback === "string") { if (typeof pathOrCallback === "string") {
return this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase); return this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase);
} }
return Array.from(this.apis.values()).find(pathOrCallback ?? (() => true)); return Array.from(this.apis.values()).find(pathOrCallback);
} }
registerApi(apiBase: string, api: KubeApi) { registerApi(apiBase: string, api: KubeApi) {
@ -29,24 +30,26 @@ export class ApiManager {
return api; return api;
} }
unregisterApi(api: string | KubeApi) { unregisterApi(api: string | KubeApi): void {
if (typeof api === "string") this.apis.delete(api); if (typeof api === "string") {
else { return void this.apis.delete(api);
const apis = Array.from(this.apis.entries()); }
const entry = apis.find(entry => entry[1] === api);
if (entry) this.unregisterApi(entry[0]); for (const [apiPath, kubeApi] of this.apis) {
if (kubeApi === api) {
return void this.apis.delete(apiPath);
}
} }
} }
@action @action
registerStore(store: KubeObjectStore, apis: KubeApi[] = [store.api]) { registerStore<T extends KubeObject>(store: KubeObjectStore<T>, apis: KubeApi[] = [store.api]) {
apis.forEach(api => { apis.forEach(api => {
this.stores.set(api, store); this.stores.set(api, store);
}); });
} }
getStore(api: string | KubeApi): KubeObjectStore { getStore<T extends KubeObject>(api: string | KubeApi): KubeObjectStore<T> {
return this.stores.get(this.resolveApi(api)); return this.stores.get(this.resolveApi(api));
} }
} }

View File

@ -1,5 +1,4 @@
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import { KubeJsonApiData } from "../kube-json-api";
import { autobind } from "../../utils"; import { autobind } from "../../utils";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
@ -29,12 +28,7 @@ export class Secret extends KubeObject {
data: { data: {
[prop: string]: string; [prop: string]: string;
token?: string; token?: string;
}; } = {};
constructor(data: KubeJsonApiData) {
super(data);
this.data = this.data || {};
}
getKeys(): string[] { getKeys(): string[] {
return Object.keys(this.data); return Object.keys(this.data);

View File

@ -79,7 +79,7 @@ export function forCluster<T extends KubeObject>(cluster: IKubeApiCluster, kubeC
}); });
} }
export class KubeApi<T extends KubeObject = any> { export class KubeApi<T extends KubeObject = KubeObject> {
static parseApi = parseKubeApi; static parseApi = parseKubeApi;
static watchAll(...apis: KubeApi[]) { static watchAll(...apis: KubeApi[]) {

View File

@ -9,6 +9,7 @@ import { KubeApi } from "./kube-api";
import { apiManager } from "./api-manager"; import { apiManager } from "./api-manager";
import { apiPrefix, isDevelopment } from "../../common/vars"; import { apiPrefix, isDevelopment } from "../../common/vars";
import { getHostedCluster } from "../../common/cluster-store"; import { getHostedCluster } from "../../common/cluster-store";
import { KubeObject } from "./kube-object";
export interface IKubeWatchEvent<T = any> { export interface IKubeWatchEvent<T = any> {
type: "ADDED" | "MODIFIED" | "DELETED"; type: "ADDED" | "MODIFIED" | "DELETED";
@ -156,7 +157,7 @@ export class KubeWatchApi {
} }
} }
addListener(store: KubeObjectStore, callback: (evt: IKubeWatchEvent) => void) { addListener<T extends KubeObject>(store: KubeObjectStore<T>, callback: (evt: IKubeWatchEvent) => void) {
const listener = (evt: IKubeWatchEvent<KubeJsonApiData>) => { const listener = (evt: IKubeWatchEvent<KubeJsonApiData>) => {
const { selfLink, namespace, resourceVersion } = evt.object.metadata; const { selfLink, namespace, resourceVersion } = evt.object.metadata;
const api = apiManager.getApi(selfLink); const api = apiManager.getApi(selfLink);

View File

@ -86,19 +86,19 @@ export class HelmReleases extends Component<Props> {
store={releaseStore} store={releaseStore}
dependentStores={[secretsStore]} dependentStores={[secretsStore]}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (release: HelmRelease) => release.getName(), [sortBy.name]: release => release.getName(),
[sortBy.namespace]: (release: HelmRelease) => release.getNs(), [sortBy.namespace]: release => release.getNs(),
[sortBy.revision]: (release: HelmRelease) => release.getRevision(), [sortBy.revision]: release => release.getRevision(),
[sortBy.chart]: (release: HelmRelease) => release.getChart(), [sortBy.chart]: release => release.getChart(),
[sortBy.status]: (release: HelmRelease) => release.getStatus(), [sortBy.status]: release => release.getStatus(),
[sortBy.updated]: (release: HelmRelease) => release.getUpdated(false, false), [sortBy.updated]: release => release.getUpdated(false, false),
}} }}
searchFilters={[ searchFilters={[
(release: HelmRelease) => release.getName(), release => release.getName(),
(release: HelmRelease) => release.getNs(), release => release.getNs(),
(release: HelmRelease) => release.getChart(), release => release.getChart(),
(release: HelmRelease) => release.getStatus(), release => release.getStatus(),
(release: HelmRelease) => release.getVersion(), release => release.getVersion(),
]} ]}
renderHeaderTitle={<Trans>Releases</Trans>} renderHeaderTitle={<Trans>Releases</Trans>}
renderTableHeader={[ renderTableHeader={[
@ -111,31 +111,23 @@ export class HelmReleases extends Component<Props> {
{ title: <Trans>Status</Trans>, className: "status", sortBy: sortBy.status }, { title: <Trans>Status</Trans>, className: "status", sortBy: sortBy.status },
{ title: <Trans>Updated</Trans>, className: "updated", sortBy: sortBy.updated }, { title: <Trans>Updated</Trans>, className: "updated", sortBy: sortBy.updated },
]} ]}
renderTableContents={(release: HelmRelease) => { renderTableContents={release => [
const version = release.getVersion(); release.getName(),
release.getNs(),
return [ release.getChart(),
release.getName(), release.getRevision(),
release.getNs(), release.getVersion(),
release.getChart(), release.appVersion,
release.getRevision(), { title: release.getStatus(), className: kebabCase(release.getStatus()) },
<> release.getUpdated(),
{version} ]}
</>, renderItemMenu={release => (
release.appVersion, <HelmReleaseMenu
{ title: release.getStatus(), className: kebabCase(release.getStatus()) }, release={release}
release.getUpdated(), removeConfirmationMessage={this.renderRemoveDialogMessage([release])}
]; />
}} )}
renderItemMenu={(release: HelmRelease) => { customizeRemoveDialog={selectedItems => ({
return (
<HelmReleaseMenu
release={release}
removeConfirmationMessage={this.renderRemoveDialogMessage([release])}
/>
);
}}
customizeRemoveDialog={(selectedItems: HelmRelease[]) => ({
message: this.renderRemoveDialogMessage(selectedItems) message: this.renderRemoveDialogMessage(selectedItems)
})} })}
detailsItem={this.selectedRelease} detailsItem={this.selectedRelease}

View File

@ -4,7 +4,6 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { RouteComponentProps } from "react-router"; import { RouteComponentProps } from "react-router";
import { Secret } from "../../api/endpoints";
import { AddSecretDialog } from "./add-secret-dialog"; import { AddSecretDialog } from "./add-secret-dialog";
import { ISecretsRouteParams } from "./secrets.route"; import { ISecretsRouteParams } from "./secrets.route";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
@ -30,18 +29,19 @@ export class Secrets extends React.Component<Props> {
return ( return (
<> <>
<KubeObjectListLayout <KubeObjectListLayout
className="Secrets" store={secretsStore} className="Secrets"
store={secretsStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: Secret) => item.getName(), [sortBy.name]: secret => secret.getName(),
[sortBy.namespace]: (item: Secret) => item.getNs(), [sortBy.namespace]: secret => secret.getNs(),
[sortBy.labels]: (item: Secret) => item.getLabels(), [sortBy.labels]: secret => secret.getLabels(),
[sortBy.keys]: (item: Secret) => item.getKeys(), [sortBy.keys]: secret => secret.getKeys(),
[sortBy.type]: (item: Secret) => item.type, [sortBy.type]: secret => secret.type,
[sortBy.age]: (item: Secret) => item.metadata.creationTimestamp, [sortBy.age]: secret => secret.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(item: Secret) => item.getSearchFields(), secret => secret.getSearchFields(),
(item: Secret) => item.getKeys(), secret => secret.getKeys(),
]} ]}
renderHeaderTitle={<Trans>Secrets</Trans>} renderHeaderTitle={<Trans>Secrets</Trans>}
renderTableHeader={[ renderTableHeader={[
@ -53,7 +53,7 @@ export class Secrets extends React.Component<Props> {
{ title: <Trans>Type</Trans>, className: "type", sortBy: sortBy.type }, { title: <Trans>Type</Trans>, className: "type", sortBy: sortBy.type },
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age }, { title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
]} ]}
renderTableContents={(secret: Secret) => [ renderTableContents={secret => [
secret.getName(), secret.getName(),
<KubeObjectStatusIcon key="icon" object={secret} />, <KubeObjectStatusIcon key="icon" object={secret} />,
secret.getNs(), secret.getNs(),

View File

@ -54,14 +54,14 @@ export class CrdResources extends React.Component<Props> {
if (!crd) return null; if (!crd) return null;
const isNamespaced = crd.isNamespaced(); const isNamespaced = crd.isNamespaced();
const extraColumns = crd.getPrinterColumns(false); // Cols with priority bigger than 0 are shown in details const extraColumns = crd.getPrinterColumns(false); // Cols with priority bigger than 0 are shown in details
const sortingCallbacks: { [sortBy: string]: TableSortCallback } = { const sortingCallbacks: { [sortBy: string]: TableSortCallback<KubeObject> } = {
[sortBy.name]: (item: KubeObject) => item.getName(), [sortBy.name]: item => item.getName(),
[sortBy.namespace]: (item: KubeObject) => item.getNs(), [sortBy.namespace]: item => item.getNs(),
[sortBy.age]: (item: KubeObject) => item.metadata.creationTimestamp, [sortBy.age]: item => item.metadata.creationTimestamp,
}; };
extraColumns.forEach(column => { extraColumns.forEach(column => {
sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.value(item, column.jsonPath.slice(1)); sortingCallbacks[column.name] = item => jsonPath.value(item, column.jsonPath.slice(1));
}); });
return ( return (

View File

@ -23,17 +23,13 @@ export const ContainerEnvironment = observer((props: Props) => {
useEffect( useEffect(
() => () =>
autorun(() => { autorun(() => {
env && env.forEach(variable => { env?.forEach(({ valueFrom }) => {
const { valueFrom } = variable; if (valueFrom?.configMapKeyRef) {
if (valueFrom && valueFrom.configMapKeyRef) {
configMapsStore.load({ name: valueFrom.configMapKeyRef.name, namespace }); configMapsStore.load({ name: valueFrom.configMapKeyRef.name, namespace });
} }
}); });
envFrom && envFrom.forEach(item => { envFrom?.forEach(({ configMapRef }) => {
const { configMapRef } = item; if (configMapRef?.name) {
if (configMapRef && configMapRef.name) {
configMapsStore.load({ name: configMapRef.name, namespace }); configMapsStore.load({ name: configMapRef.name, namespace });
} }
}); });

View File

@ -44,7 +44,7 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
// header (title, filtering, searching, etc.) // header (title, filtering, searching, etc.)
showHeader?: boolean; showHeader?: boolean;
headerClassName?: IClassName; headerClassName?: IClassName;
renderHeaderTitle?: ReactNode | ((parent: ItemListLayout) => ReactNode); renderHeaderTitle?: ReactNode | ((parent: ItemListLayout<T>) => ReactNode);
customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode; customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode;
// items list configuration // items list configuration
@ -52,8 +52,8 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
isSelectable?: boolean; // show checkbox in rows for selecting items isSelectable?: boolean; // show checkbox in rows for selecting items
isSearchable?: boolean; // apply search-filter & add search-input isSearchable?: boolean; // apply search-filter & add search-input
copyClassNameFromHeadCells?: boolean; copyClassNameFromHeadCells?: boolean;
sortingCallbacks?: { [sortBy: string]: TableSortCallback }; sortingCallbacks?: { [sortBy: string]: TableSortCallback<T> };
tableProps?: Partial<TableProps>; // low-level table configuration tableProps?: Partial<TableProps<T>>; // low-level table configuration
renderTableHeader: TableCellProps[] | null; renderTableHeader: TableCellProps[] | null;
renderTableContents: (item: T) => (ReactNode | TableCellProps)[]; renderTableContents: (item: T) => (ReactNode | TableCellProps)[];
renderItemMenu?: (item: T, store: ItemStore<T>) => ReactNode; renderItemMenu?: (item: T, store: ItemStore<T>) => ReactNode;
@ -68,7 +68,7 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
// other // other
customizeRemoveDialog?: (selectedItems: T[]) => Partial<ConfirmDialogParams>; customizeRemoveDialog?: (selectedItems: T[]) => Partial<ConfirmDialogParams>;
renderFooter?: (parent: ItemListLayout) => React.ReactNode; renderFooter?: (parent: ItemListLayout<T>) => React.ReactNode;
} }
const defaultProps: Partial<ItemListLayoutProps> = { const defaultProps: Partial<ItemListLayoutProps> = {
@ -88,7 +88,7 @@ interface ItemListLayoutUserSettings {
} }
@observer @observer
export class ItemListLayout extends React.Component<ItemListLayoutProps> { export class ItemListLayout<T extends ItemObject> extends React.Component<ItemListLayoutProps<T>> {
static defaultProps = defaultProps as object; static defaultProps = defaultProps as object;
@observable isUnmounting = false; @observable isUnmounting = false;
@ -98,7 +98,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
showAppliedFilters: false, showAppliedFilters: false,
}; };
constructor(props: ItemListLayoutProps) { constructor(props: ItemListLayoutProps<T>) {
super(props); super(props);
// keep ui user settings in local storage // keep ui user settings in local storage

View File

@ -7,24 +7,24 @@ import { getSelectedDetails, showDetails } from "../../navigation";
import { ItemListLayout, ItemListLayoutProps } from "../item-object-list/item-list-layout"; import { ItemListLayout, ItemListLayoutProps } from "../item-object-list/item-list-layout";
import { KubeObjectStore } from "../../kube-object.store"; import { KubeObjectStore } from "../../kube-object.store";
import { KubeObjectMenu } from "./kube-object-menu"; import { KubeObjectMenu } from "./kube-object-menu";
import { ItemObject } from "../../item.store";
export interface KubeObjectListLayoutProps extends ItemListLayoutProps { export interface KubeObjectListLayoutProps<T extends ItemObject & KubeObject> extends ItemListLayoutProps<T> {
store: KubeObjectStore; store: KubeObjectStore<T>;
}
function showItemDetails(item: KubeObject) {
return showDetails(item.selfLink);
} }
@observer @observer
export class KubeObjectListLayout extends React.Component<KubeObjectListLayoutProps> { export class KubeObjectListLayout<T extends ItemObject & KubeObject> extends React.Component<KubeObjectListLayoutProps<T>> {
@computed get selectedItem() { @computed get selectedItem() {
return this.props.store.getByPath(getSelectedDetails()); return this.props.store.getByPath(getSelectedDetails());
} }
onDetails = (item: KubeObject) => { static defaultProps = {
if (this.props.onDetails) { onDetails: showItemDetails
this.props.onDetails(item);
}
else {
showDetails(item.selfLink);
}
}; };
render() { render() {
@ -35,7 +35,7 @@ export class KubeObjectListLayout extends React.Component<KubeObjectListLayoutPr
{...layoutProps} {...layoutProps}
className={cssNames("KubeObjectListLayout", className)} className={cssNames("KubeObjectListLayout", className)}
detailsItem={this.selectedItem} detailsItem={this.selectedItem}
onDetails={this.onDetails} onDetails={this.props.onDetails}
renderItemMenu={(item) => { renderItemMenu={(item) => {
return <KubeObjectMenu object={item}/>; return <KubeObjectMenu object={item}/>;
}} }}

View File

@ -17,9 +17,9 @@ import { ItemObject } from "../../item.store";
export type TableSortBy = string; export type TableSortBy = string;
export type TableOrderBy = "asc" | "desc" | string; export type TableOrderBy = "asc" | "desc" | string;
export type TableSortParams = { sortBy: TableSortBy; orderBy: TableOrderBy }; export type TableSortParams = { sortBy: TableSortBy; orderBy: TableOrderBy };
export type TableSortCallback<D = any> = (data: D) => string | number | (string | number)[]; export type TableSortCallback<D> = (data: D) => string | number | (string | number)[];
export interface TableProps extends React.DOMAttributes<HTMLDivElement> { export interface TableProps<T> extends React.DOMAttributes<HTMLDivElement> {
items?: ItemObject[]; // Raw items data items?: ItemObject[]; // Raw items data
className?: string; className?: string;
autoSize?: boolean; // Setup auto-sizing for all columns (flex: 1 0) autoSize?: boolean; // Setup auto-sizing for all columns (flex: 1 0)
@ -29,7 +29,7 @@ export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
sortable?: { sortable?: {
// Define sortable callbacks for every column in <TableHead><TableCell sortBy="someCol"><TableHead> // Define sortable callbacks for every column in <TableHead><TableCell sortBy="someCol"><TableHead>
// @sortItem argument in the callback is an object, provided in <TableRow sortItem={someColDataItem}/> // @sortItem argument in the callback is an object, provided in <TableRow sortItem={someColDataItem}/>
[sortBy: string]: TableSortCallback; [sortBy: string]: TableSortCallback<T>;
}; };
sortSyncWithUrl?: boolean; // sorting state is managed globally from url params sortSyncWithUrl?: boolean; // sorting state is managed globally from url params
sortByDefault?: Partial<TableSortParams>; // default sorting params sortByDefault?: Partial<TableSortParams>; // default sorting params
@ -44,8 +44,8 @@ export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
} }
@observer @observer
export class Table extends React.Component<TableProps> { export class Table<T> extends React.Component<TableProps<T>> {
static defaultProps: TableProps = { static defaultProps: TableProps<any> = {
scrollable: true, scrollable: true,
autoSize: true, autoSize: true,
rowPadding: "8px", rowPadding: "8px",

View File

@ -9,7 +9,7 @@ import { KubeJsonApiData } from "./api/kube-json-api";
import { getHostedCluster } from "../common/cluster-store"; import { getHostedCluster } from "../common/cluster-store";
@autobind() @autobind()
export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemStore<T> { export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T> {
abstract api: KubeApi<T>; abstract api: KubeApi<T>;
public readonly limit?: number; public readonly limit?: number;
public readonly bufferSize: number = 50000; public readonly bufferSize: number = 50000;