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 { autobind } from "../utils";
import { KubeApi } from "./kube-api";
import { KubeObject } from "./kube-object";
@autobind()
export class ApiManager {
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") {
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) {
@ -29,24 +30,26 @@ export class ApiManager {
return api;
}
unregisterApi(api: string | KubeApi) {
if (typeof api === "string") this.apis.delete(api);
else {
const apis = Array.from(this.apis.entries());
const entry = apis.find(entry => entry[1] === api);
unregisterApi(api: string | KubeApi): void {
if (typeof api === "string") {
return void this.apis.delete(api);
}
if (entry) this.unregisterApi(entry[0]);
for (const [apiPath, kubeApi] of this.apis) {
if (kubeApi === api) {
return void this.apis.delete(apiPath);
}
}
}
@action
registerStore(store: KubeObjectStore, apis: KubeApi[] = [store.api]) {
registerStore<T extends KubeObject>(store: KubeObjectStore<T>, apis: KubeApi[] = [store.api]) {
apis.forEach(api => {
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));
}
}

View File

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

View File

@ -9,6 +9,7 @@ import { KubeApi } from "./kube-api";
import { apiManager } from "./api-manager";
import { apiPrefix, isDevelopment } from "../../common/vars";
import { getHostedCluster } from "../../common/cluster-store";
import { KubeObject } from "./kube-object";
export interface IKubeWatchEvent<T = any> {
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 { selfLink, namespace, resourceVersion } = evt.object.metadata;
const api = apiManager.getApi(selfLink);

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
// header (title, filtering, searching, etc.)
showHeader?: boolean;
headerClassName?: IClassName;
renderHeaderTitle?: ReactNode | ((parent: ItemListLayout) => ReactNode);
renderHeaderTitle?: ReactNode | ((parent: ItemListLayout<T>) => ReactNode);
customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode;
// items list configuration
@ -52,8 +52,8 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
isSelectable?: boolean; // show checkbox in rows for selecting items
isSearchable?: boolean; // apply search-filter & add search-input
copyClassNameFromHeadCells?: boolean;
sortingCallbacks?: { [sortBy: string]: TableSortCallback };
tableProps?: Partial<TableProps>; // low-level table configuration
sortingCallbacks?: { [sortBy: string]: TableSortCallback<T> };
tableProps?: Partial<TableProps<T>>; // low-level table configuration
renderTableHeader: TableCellProps[] | null;
renderTableContents: (item: T) => (ReactNode | TableCellProps)[];
renderItemMenu?: (item: T, store: ItemStore<T>) => ReactNode;
@ -68,7 +68,7 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
// other
customizeRemoveDialog?: (selectedItems: T[]) => Partial<ConfirmDialogParams>;
renderFooter?: (parent: ItemListLayout) => React.ReactNode;
renderFooter?: (parent: ItemListLayout<T>) => React.ReactNode;
}
const defaultProps: Partial<ItemListLayoutProps> = {
@ -88,7 +88,7 @@ interface ItemListLayoutUserSettings {
}
@observer
export class ItemListLayout extends React.Component<ItemListLayoutProps> {
export class ItemListLayout<T extends ItemObject> extends React.Component<ItemListLayoutProps<T>> {
static defaultProps = defaultProps as object;
@observable isUnmounting = false;
@ -98,7 +98,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
showAppliedFilters: false,
};
constructor(props: ItemListLayoutProps) {
constructor(props: ItemListLayoutProps<T>) {
super(props);
// 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 { KubeObjectStore } from "../../kube-object.store";
import { KubeObjectMenu } from "./kube-object-menu";
import { ItemObject } from "../../item.store";
export interface KubeObjectListLayoutProps extends ItemListLayoutProps {
store: KubeObjectStore;
export interface KubeObjectListLayoutProps<T extends ItemObject & KubeObject> extends ItemListLayoutProps<T> {
store: KubeObjectStore<T>;
}
function showItemDetails(item: KubeObject) {
return showDetails(item.selfLink);
}
@observer
export class KubeObjectListLayout extends React.Component<KubeObjectListLayoutProps> {
export class KubeObjectListLayout<T extends ItemObject & KubeObject> extends React.Component<KubeObjectListLayoutProps<T>> {
@computed get selectedItem() {
return this.props.store.getByPath(getSelectedDetails());
}
onDetails = (item: KubeObject) => {
if (this.props.onDetails) {
this.props.onDetails(item);
}
else {
showDetails(item.selfLink);
}
static defaultProps = {
onDetails: showItemDetails
};
render() {
@ -35,7 +35,7 @@ export class KubeObjectListLayout extends React.Component<KubeObjectListLayoutPr
{...layoutProps}
className={cssNames("KubeObjectListLayout", className)}
detailsItem={this.selectedItem}
onDetails={this.onDetails}
onDetails={this.props.onDetails}
renderItemMenu={(item) => {
return <KubeObjectMenu object={item}/>;
}}

View File

@ -17,9 +17,9 @@ import { ItemObject } from "../../item.store";
export type TableSortBy = string;
export type TableOrderBy = "asc" | "desc" | string;
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
className?: string;
autoSize?: boolean; // Setup auto-sizing for all columns (flex: 1 0)
@ -29,7 +29,7 @@ export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
sortable?: {
// 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}/>
[sortBy: string]: TableSortCallback;
[sortBy: string]: TableSortCallback<T>;
};
sortSyncWithUrl?: boolean; // sorting state is managed globally from url params
sortByDefault?: Partial<TableSortParams>; // default sorting params
@ -44,8 +44,8 @@ export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
}
@observer
export class Table extends React.Component<TableProps> {
static defaultProps: TableProps = {
export class Table<T> extends React.Component<TableProps<T>> {
static defaultProps: TableProps<any> = {
scrollable: true,
autoSize: true,
rowPadding: "8px",

View File

@ -9,7 +9,7 @@ import { KubeJsonApiData } from "./api/kube-json-api";
import { getHostedCluster } from "../common/cluster-store";
@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>;
public readonly limit?: number;
public readonly bufferSize: number = 50000;