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

Improve Table typing and remove annotations on callbacks

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2021-05-21 10:17:00 -04:00
parent 22c9d03edb
commit defb0ea0dd
55 changed files with 498 additions and 490 deletions

View File

@ -22,8 +22,9 @@
import { ingressStore } from "../../components/+network-ingresses/ingress.store"; import { ingressStore } from "../../components/+network-ingresses/ingress.store";
import { apiManager } from "../api-manager"; import { apiManager } from "../api-manager";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
class TestApi extends KubeApi { class TestApi extends KubeApi<KubeObject> {
protected async checkPreferredVersion() { protected async checkPreferredVersion() {
return; return;
@ -36,6 +37,7 @@ describe("ApiManager", () => {
const apiBase = "apis/v1/foo"; const apiBase = "apis/v1/foo";
const fallbackApiBase = "/apis/extensions/v1beta1/foo"; const fallbackApiBase = "/apis/extensions/v1beta1/foo";
const kubeApi = new TestApi({ const kubeApi = new TestApi({
objectConstructor: KubeObject,
apiBase, apiBase,
fallbackApiBases: [fallbackApiBase], fallbackApiBases: [fallbackApiBase],
checkPreferredVersion: true, checkPreferredVersion: true,

View File

@ -20,6 +20,7 @@
*/ */
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object";
describe("KubeApi", () => { describe("KubeApi", () => {
it("uses url from apiBase if apiBase contains the resource", async () => { it("uses url from apiBase if apiBase contains the resource", async () => {
@ -29,7 +30,7 @@ describe("KubeApi", () => {
body: JSON.stringify({ body: JSON.stringify({
resources: [{ resources: [{
name: "ingresses" name: "ingresses"
}] as any [] }] as any[]
}) })
}; };
} else if (request.url === "/api-kube/apis/extensions/v1beta1") { } else if (request.url === "/api-kube/apis/extensions/v1beta1") {
@ -38,13 +39,13 @@ describe("KubeApi", () => {
body: JSON.stringify({ body: JSON.stringify({
resources: [{ resources: [{
name: "ingresses" name: "ingresses"
}] as any [] }] as any[]
}) })
}; };
} else { } else {
return { return {
body: JSON.stringify({ body: JSON.stringify({
resources: [] as any [] resources: [] as any[]
}) })
}; };
} }
@ -53,6 +54,7 @@ describe("KubeApi", () => {
const apiBase = "/apis/networking.k8s.io/v1/ingresses"; const apiBase = "/apis/networking.k8s.io/v1/ingresses";
const fallbackApiBase = "/apis/extensions/v1beta1/ingresses"; const fallbackApiBase = "/apis/extensions/v1beta1/ingresses";
const kubeApi = new KubeApi({ const kubeApi = new KubeApi({
objectConstructor: KubeObject,
apiBase, apiBase,
fallbackApiBases: [fallbackApiBase], fallbackApiBases: [fallbackApiBase],
checkPreferredVersion: true, checkPreferredVersion: true,
@ -68,7 +70,7 @@ describe("KubeApi", () => {
if (request.url === "/api-kube/apis/networking.k8s.io/v1") { if (request.url === "/api-kube/apis/networking.k8s.io/v1") {
return { return {
body: JSON.stringify({ body: JSON.stringify({
resources: [] as any [] resources: [] as any[]
}) })
}; };
} else if (request.url === "/api-kube/apis/extensions/v1beta1") { } else if (request.url === "/api-kube/apis/extensions/v1beta1") {
@ -76,13 +78,13 @@ describe("KubeApi", () => {
body: JSON.stringify({ body: JSON.stringify({
resources: [{ resources: [{
name: "ingresses" name: "ingresses"
}] as any [] }] as any[]
}) })
}; };
} else { } else {
return { return {
body: JSON.stringify({ body: JSON.stringify({
resources: [] as any [] resources: [] as any[]
}) })
}; };
} }
@ -91,6 +93,7 @@ describe("KubeApi", () => {
const apiBase = "apis/networking.k8s.io/v1/ingresses"; const apiBase = "apis/networking.k8s.io/v1/ingresses";
const fallbackApiBase = "/apis/extensions/v1beta1/ingresses"; const fallbackApiBase = "/apis/extensions/v1beta1/ingresses";
const kubeApi = new KubeApi({ const kubeApi = new KubeApi({
objectConstructor: KubeObject,
apiBase, apiBase,
fallbackApiBases: [fallbackApiBase], fallbackApiBases: [fallbackApiBase],
checkPreferredVersion: true, checkPreferredVersion: true,

View File

@ -24,17 +24,18 @@ import type { KubeObjectStore } from "../kube-object.store";
import { action, observable, makeObservable } from "mobx"; import { action, observable, makeObservable } from "mobx";
import { autoBind } from "../utils"; import { autoBind } from "../utils";
import { KubeApi, parseKubeApi } from "./kube-api"; import { KubeApi, parseKubeApi } from "./kube-api";
import type { KubeObject } from "./kube-object";
export class ApiManager { export class ApiManager {
private apis = observable.map<string, KubeApi>(); private apis = observable.map<string, KubeApi<KubeObject>>();
private stores = observable.map<string, KubeObjectStore>(); private stores = observable.map<string, KubeObjectStore<KubeObject>>();
constructor() { constructor() {
makeObservable(this); makeObservable(this);
autoBind(this); autoBind(this);
} }
getApi(pathOrCallback: string | ((api: KubeApi) => boolean)) { getApi(pathOrCallback: string | ((api: KubeApi<KubeObject>) => boolean)) {
if (typeof pathOrCallback === "string") { if (typeof pathOrCallback === "string") {
return this.apis.get(pathOrCallback) || this.apis.get(parseKubeApi(pathOrCallback).apiBase); return this.apis.get(pathOrCallback) || this.apis.get(parseKubeApi(pathOrCallback).apiBase);
} }
@ -46,10 +47,10 @@ export class ApiManager {
return Array.from(this.apis.values()).find((api) => api.kind === kind && api.apiVersionWithGroup === apiVersion); return Array.from(this.apis.values()).find((api) => api.kind === kind && api.apiVersionWithGroup === apiVersion);
} }
registerApi(apiBase: string, api: KubeApi) { registerApi(apiBase: string, api: KubeApi<KubeObject>) {
if (!this.apis.has(apiBase)) { if (!this.apis.has(apiBase)) {
this.stores.forEach((store) => { this.stores.forEach((store) => {
if(store.api === api) { if (store.api === api) {
this.stores.set(apiBase, store); this.stores.set(apiBase, store);
} }
}); });
@ -58,13 +59,13 @@ export class ApiManager {
} }
} }
protected resolveApi(api: string | KubeApi): KubeApi { protected resolveApi<K extends KubeObject>(api: string | KubeApi<K>): KubeApi<K> {
if (typeof api === "string") return this.getApi(api); if (typeof api === "string") return this.getApi(api) as KubeApi<K>;
return api; return api;
} }
unregisterApi(api: string | KubeApi) { unregisterApi(api: string | KubeApi<KubeObject>) {
if (typeof api === "string") this.apis.delete(api); if (typeof api === "string") this.apis.delete(api);
else { else {
const apis = Array.from(this.apis.entries()); const apis = Array.from(this.apis.entries());
@ -75,13 +76,13 @@ export class ApiManager {
} }
@action @action
registerStore(store: KubeObjectStore, apis: KubeApi[] = [store.api]) { registerStore(store: KubeObjectStore<KubeObject>, apis: KubeApi<KubeObject>[] = [store.api]) {
apis.forEach(api => { apis.forEach(api => {
this.stores.set(api.apiBase, store); this.stores.set(api.apiBase, store);
}); });
} }
getStore<S extends KubeObjectStore>(api: string | KubeApi): S { getStore<S extends KubeObjectStore<KubeObject>>(api: string | KubeApi<KubeObject>): S {
return this.stores.get(this.resolveApi(api)?.apiBase) as S; return this.stores.get(this.resolveApi(api)?.apiBase) as S;
} }
} }

View File

@ -30,7 +30,7 @@ export const resourceApplierApi = {
"kubectl.kubernetes.io/last-applied-configuration" "kubectl.kubernetes.io/last-applied-configuration"
], ],
async update<D extends KubeObject>(resource: object | string): Promise<D> { async update<K extends KubeObject>(resource: object | string): Promise<K | null> {
if (typeof resource === "string") { if (typeof resource === "string") {
resource = jsYaml.safeLoad(resource); resource = jsYaml.safeLoad(resource);
} }
@ -48,7 +48,7 @@ export const resourceApplierApi = {
} }
}); });
return items.length === 1 ? items[0] : items; return items[0] as K ?? null;
}); });
} }
}; };

View File

@ -49,7 +49,7 @@ export interface IKubeApiOptions<T extends KubeObject> {
*/ */
fallbackApiBases?: string[]; fallbackApiBases?: string[];
objectConstructor?: IKubeObjectConstructor<T>; objectConstructor: IKubeObjectConstructor<T>;
request?: KubeJsonApi; request?: KubeJsonApi;
isNamespaced?: boolean; isNamespaced?: boolean;
kind?: string; kind?: string;
@ -115,7 +115,7 @@ export function forCluster<T extends KubeObject>(cluster: IKubeApiCluster, kubeC
}); });
} }
export function ensureObjectSelfLink(api: KubeApi, object: KubeJsonApiData) { export function ensureObjectSelfLink(api: KubeApi<KubeObject>, object: KubeJsonApiData) {
if (!object.metadata.selfLink) { if (!object.metadata.selfLink) {
object.metadata.selfLink = createKubeApiURL({ object.metadata.selfLink = createKubeApiURL({
apiPrefix: api.apiPrefix, apiPrefix: api.apiPrefix,
@ -127,7 +127,7 @@ export function ensureObjectSelfLink(api: KubeApi, object: KubeJsonApiData) {
} }
} }
export type KubeApiWatchCallback = (data: IKubeWatchEvent, error: any) => void; export type KubeApiWatchCallback = (data: IKubeWatchEvent<KubeJsonApiData>, error: any) => void;
export type KubeApiWatchOptions = { export type KubeApiWatchOptions = {
namespace: string; namespace: string;
@ -135,7 +135,7 @@ export type KubeApiWatchOptions = {
abortController?: AbortController abortController?: AbortController
}; };
export class KubeApi<T extends KubeObject = any> { export class KubeApi<T extends KubeObject> {
readonly kind: string; readonly kind: string;
readonly apiBase: string; readonly apiBase: string;
readonly apiPrefix: string; readonly apiPrefix: string;
@ -152,7 +152,7 @@ export class KubeApi<T extends KubeObject = any> {
constructor(protected options: IKubeApiOptions<T>) { constructor(protected options: IKubeApiOptions<T>) {
const { const {
objectConstructor = KubeObject as IKubeObjectConstructor, objectConstructor,
request = apiKube, request = apiKube,
kind = options.objectConstructor?.kind, kind = options.objectConstructor?.kind,
isNamespaced = options.objectConstructor?.namespaced isNamespaced = options.objectConstructor?.namespaced
@ -456,14 +456,14 @@ export class KubeApi<T extends KubeObject = any> {
clearTimeout(timedRetry); clearTimeout(timedRetry);
timedRetry = setTimeout(() => { // we did not get any kubernetes errors so let's retry timedRetry = setTimeout(() => { // we did not get any kubernetes errors so let's retry
this.watch({...opts, namespace, callback}); this.watch({ ...opts, namespace, callback });
}, 1000); }, 1000);
}); });
}); });
byline(nodeStream).on("data", (line) => { byline(nodeStream).on("data", (line) => {
try { try {
const event: IKubeWatchEvent = JSON.parse(line); const event: IKubeWatchEvent<KubeJsonApiData> = JSON.parse(line);
if (event.type === "ERROR" && event.object.kind === "Status") { if (event.type === "ERROR" && event.object.kind === "Status") {
errorReceived = true; errorReceived = true;
@ -474,7 +474,7 @@ export class KubeApi<T extends KubeObject = any> {
this.modifyWatchEvent(event); this.modifyWatchEvent(event);
callback(event, null); callback(event, null);
} catch (ignore) { } catch (ignore) {
// ignore parse errors // ignore parse errors
} }
}); });
}) })
@ -487,7 +487,7 @@ export class KubeApi<T extends KubeObject = any> {
return abort; return abort;
} }
protected modifyWatchEvent(event: IKubeWatchEvent) { protected modifyWatchEvent(event: IKubeWatchEvent<KubeJsonApiData>) {
switch (event.type) { switch (event.type) {
case "ADDED": case "ADDED":

View File

@ -30,7 +30,7 @@ import type { JsonApiParams } from "./json-api";
import { resourceApplierApi } from "./endpoints/resource-applier.api"; import { resourceApplierApi } from "./endpoints/resource-applier.api";
import { hasOptionalProperty, hasTypedProperty, isObject, isString, bindPredicate, isTypedArray, isRecord } from "../../common/utils/type-narrowing"; import { hasOptionalProperty, hasTypedProperty, isObject, isString, bindPredicate, isTypedArray, isRecord } from "../../common/utils/type-narrowing";
export type IKubeObjectConstructor<T extends KubeObject = any> = (new (data: KubeJsonApiData | any) => T) & { export type IKubeObjectConstructor<K extends KubeObject = KubeObject> = (new (data: KubeJsonApiData | any) => K) & {
kind?: string; kind?: string;
namespaced?: boolean; namespaced?: boolean;
apiBase?: string; apiBase?: string;
@ -266,8 +266,8 @@ export class KubeObject<Metadata extends IKubeObjectMetadata = IKubeObjectMetada
} }
// use unified resource-applier api for updating all k8s objects // use unified resource-applier api for updating all k8s objects
async update<T extends KubeObject>(data: Partial<T>): Promise<T> { async update<K extends KubeObject>(data: Partial<K>): Promise<K> {
return resourceApplierApi.update<T>({ return resourceApplierApi.update<K>({
...this.toPlainObject(), ...this.toPlainObject(),
...data, ...data,
}); });

View File

@ -31,8 +31,9 @@ import { autoBind, Disposer, noop } from "../utils";
import type { KubeApi } from "./kube-api"; import type { KubeApi } from "./kube-api";
import type { KubeJsonApiData } from "./kube-json-api"; import type { KubeJsonApiData } from "./kube-json-api";
import { isDebugging, isProduction } from "../../common/vars"; import { isDebugging, isProduction } from "../../common/vars";
import type { KubeObject } from "./kube-object";
export interface IKubeWatchEvent<T = KubeJsonApiData> { export interface IKubeWatchEvent<T extends KubeJsonApiData> {
type: "ADDED" | "MODIFIED" | "DELETED" | "ERROR"; type: "ADDED" | "MODIFIED" | "DELETED" | "ERROR";
object?: T; object?: T;
} }
@ -58,11 +59,11 @@ export class KubeWatchApi {
autoBind(this); autoBind(this);
} }
isAllowedApi(api: KubeApi): boolean { isAllowedApi(api: KubeApi<KubeObject>): boolean {
return Boolean(this.context?.cluster.isAllowedResource(api.kind)); return Boolean(this.context?.cluster.isAllowedResource(api.kind));
} }
preloadStores(stores: KubeObjectStore[], opts: { namespaces?: string[], loadOnce?: boolean } = {}) { preloadStores(stores: KubeObjectStore<KubeObject>[], opts: { namespaces?: string[], loadOnce?: boolean } = {}) {
const limitRequests = plimit(1); // load stores one by one to allow quick skipping when fast clicking btw pages const limitRequests = plimit(1); // load stores one by one to allow quick skipping when fast clicking btw pages
const preloading: Promise<any>[] = []; const preloading: Promise<any>[] = [];
@ -80,7 +81,7 @@ export class KubeWatchApi {
}; };
} }
subscribeStores(stores: KubeObjectStore[], opts: IKubeWatchSubscribeStoreOptions = {}): Disposer { subscribeStores(stores: KubeObjectStore<KubeObject>[], opts: IKubeWatchSubscribeStoreOptions = {}): Disposer {
const { preload = true, waitUntilLoaded = true, loadOnce = false, } = opts; const { preload = true, waitUntilLoaded = true, loadOnce = false, } = opts;
const subscribingNamespaces = opts.namespaces ?? this.context?.allNamespaces ?? []; const subscribingNamespaces = opts.namespaces ?? this.context?.allNamespaces ?? [];
const unsubscribeList: Function[] = []; const unsubscribeList: Function[] = [];

View File

@ -83,14 +83,14 @@ export class HelmCharts extends Component<Props> {
store={helmChartStore} store={helmChartStore}
isSelectable={false} isSelectable={false}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (chart: HelmChart) => chart.getName(), [columnId.name]: chart => chart.getName(),
[columnId.repo]: (chart: HelmChart) => chart.getRepository(), [columnId.repo]: chart => chart.getRepository(),
}} }}
searchFilters={[ searchFilters={[
(chart: HelmChart) => chart.getName(), chart => chart.getName(),
(chart: HelmChart) => chart.getVersion(), chart => chart.getVersion(),
(chart: HelmChart) => chart.getAppVersion(), chart => chart.getAppVersion(),
(chart: HelmChart) => chart.getKeywords(), chart => chart.getKeywords(),
]} ]}
customizeHeader={() => ( customizeHeader={() => (
<SearchInputUrl placeholder="Search Helm Charts" /> <SearchInputUrl placeholder="Search Helm Charts" />
@ -103,7 +103,7 @@ export class HelmCharts extends Component<Props> {
{ title: "App Version", className: "app-version", id: columnId.appVersion }, { title: "App Version", className: "app-version", id: columnId.appVersion },
{ title: "Repository", className: "repository", sortBy: columnId.repo, id: columnId.repo }, { title: "Repository", className: "repository", sortBy: columnId.repo, id: columnId.repo },
]} ]}
renderTableContents={(chart: HelmChart) => [ renderTableContents={chart => [
<figure key="image"> <figure key="image">
<img <img
src={chart.getIcon() || require("./helm-placeholder.svg")} src={chart.getIcon() || require("./helm-placeholder.svg")}

View File

@ -103,19 +103,19 @@ export class HelmReleases extends Component<Props> {
store={releaseStore} store={releaseStore}
dependentStores={[secretsStore]} dependentStores={[secretsStore]}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (release: HelmRelease) => release.getName(), [columnId.name]: release => release.getName(),
[columnId.namespace]: (release: HelmRelease) => release.getNs(), [columnId.namespace]: release => release.getNs(),
[columnId.revision]: (release: HelmRelease) => release.getRevision(), [columnId.revision]: release => release.getRevision(),
[columnId.chart]: (release: HelmRelease) => release.getChart(), [columnId.chart]: release => release.getChart(),
[columnId.status]: (release: HelmRelease) => release.getStatus(), [columnId.status]: release => release.getStatus(),
[columnId.updated]: (release: HelmRelease) => release.getUpdated(false, false), [columnId.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="Releases" renderHeaderTitle="Releases"
customizeHeader={({ filters, ...headerPlaceholders }) => ({ customizeHeader={({ filters, ...headerPlaceholders }) => ({
@ -137,7 +137,7 @@ export class HelmReleases extends Component<Props> {
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
{ title: "Updated", className: "updated", sortBy: columnId.updated, id: columnId.updated }, { title: "Updated", className: "updated", sortBy: columnId.updated, id: columnId.updated },
]} ]}
renderTableContents={(release: HelmRelease) => [ renderTableContents={release => [
release.getName(), release.getName(),
release.getNs(), release.getNs(),
release.getChart(), release.getChart(),
@ -147,13 +147,13 @@ export class HelmReleases extends Component<Props> {
{ title: release.getStatus(), className: kebabCase(release.getStatus()) }, { title: release.getStatus(), className: kebabCase(release.getStatus()) },
release.getUpdated(), release.getUpdated(),
]} ]}
renderItemMenu={(release: HelmRelease) => ( renderItemMenu={release => (
<HelmReleaseMenu <HelmReleaseMenu
release={release} release={release}
removeConfirmationMessage={this.renderRemoveDialogMessage([release])} removeConfirmationMessage={this.renderRemoveDialogMessage([release])}
/> />
)} )}
customizeRemoveDialog={(selectedItems: HelmRelease[]) => ({ customizeRemoveDialog={selectedItems => ({
message: this.renderRemoveDialogMessage(selectedItems) message: this.renderRemoveDialogMessage(selectedItems)
})} })}
detailsItem={this.selectedRelease} detailsItem={this.selectedRelease}

View File

@ -55,7 +55,7 @@ enum sortBy {
status = "status" status = "status"
} }
interface Props extends RouteComponentProps<ICatalogViewRouteParam> {} interface Props extends RouteComponentProps<ICatalogViewRouteParam> { }
@observer @observer
export class Catalog extends React.Component<Props> { export class Catalog extends React.Component<Props> {
@ -182,7 +182,7 @@ export class Catalog extends React.Component<Props> {
</MenuItem> </MenuItem>
)) ))
} }
<MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item) }> <MenuItem key="add-to-hotbar" onClick={() => this.addToHotbar(item)}>
Pin to Hotbar Pin to Hotbar
</MenuItem> </MenuItem>
</MenuActions> </MenuActions>
@ -211,12 +211,12 @@ export class Catalog extends React.Component<Props> {
store={this.catalogEntityStore} store={this.catalogEntityStore}
tableId="catalog-items" tableId="catalog-items"
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: CatalogEntityItem) => item.name, [sortBy.name]: entity => entity.name,
[sortBy.source]: (item: CatalogEntityItem) => item.source, [sortBy.source]: entity => entity.source,
[sortBy.status]: (item: CatalogEntityItem) => item.phase, [sortBy.status]: entity => entity.phase,
}} }}
searchFilters={[ searchFilters={[
(entity: CatalogEntityItem) => entity.searchFields, entity => entity.searchFields,
]} ]}
renderTableHeader={[ renderTableHeader={[
{ title: "", className: styles.iconCell }, { title: "", className: styles.iconCell },
@ -225,14 +225,14 @@ export class Catalog extends React.Component<Props> {
{ title: "Labels", className: styles.labelsCell }, { title: "Labels", className: styles.labelsCell },
{ title: "Status", className: styles.statusCell, sortBy: sortBy.status }, { title: "Status", className: styles.statusCell, sortBy: sortBy.status },
]} ]}
renderTableContents={(item: CatalogEntityItem) => [ renderTableContents={entity => [
this.renderIcon(item), this.renderIcon(entity),
item.name, entity.name,
item.source, entity.source,
item.labels.map((label) => <Badge key={label} label={label} title={label} />), entity.labels.map((label) => <Badge key={label} label={label} title={label} />),
{ title: item.phase, className: kebabCase(item.phase) } { title: entity.phase, className: kebabCase(entity.phase) }
]} ]}
onDetails={(item: CatalogEntityItem) => this.onDetails(item) } onDetails={entity => this.onDetails(entity) }
renderItemMenu={this.renderItemMenu} renderItemMenu={this.renderItemMenu}
/> />
); );
@ -248,13 +248,13 @@ export class Catalog extends React.Component<Props> {
store={this.catalogEntityStore} store={this.catalogEntityStore}
tableId="catalog-items" tableId="catalog-items"
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: CatalogEntityItem) => item.name, [sortBy.name]: entity => entity.name,
[sortBy.kind]: (item: CatalogEntityItem) => item.kind, [sortBy.kind]: entity => entity.kind,
[sortBy.source]: (item: CatalogEntityItem) => item.source, [sortBy.source]: entity => entity.source,
[sortBy.status]: (item: CatalogEntityItem) => item.phase, [sortBy.status]: entity => entity.phase,
}} }}
searchFilters={[ searchFilters={[
(entity: CatalogEntityItem) => entity.searchFields, entity => entity.searchFields,
]} ]}
renderTableHeader={[ renderTableHeader={[
{ title: "", className: styles.iconCell }, { title: "", className: styles.iconCell },
@ -263,16 +263,16 @@ export class Catalog extends React.Component<Props> {
{ title: "Labels", className: styles.labelsCell }, { title: "Labels", className: styles.labelsCell },
{ title: "Status", className: styles.statusCell, sortBy: sortBy.status }, { title: "Status", className: styles.statusCell, sortBy: sortBy.status },
]} ]}
renderTableContents={(item: CatalogEntityItem) => [ renderTableContents={entity => [
this.renderIcon(item), this.renderIcon(entity),
item.name, entity.name,
item.kind, entity.kind,
item.source, entity.source,
item.labels.map((label) => <Badge key={label} label={label} title={label} />), entity.labels.map((label) => <Badge key={label} label={label} title={label} />),
{ title: item.phase, className: kebabCase(item.phase) } { title: entity.phase, className: kebabCase(entity.phase) }
]} ]}
detailsItem={this.selectedItem} detailsItem={this.selectedItem}
onDetails={(item: CatalogEntityItem) => this.onDetails(item) } onDetails={entity => this.onDetails(entity) }
renderItemMenu={this.renderItemMenu} renderItemMenu={this.renderItemMenu}
/> />
); );

View File

@ -64,15 +64,15 @@ export class HorizontalPodAutoscalers extends React.Component<Props> {
tableId="configuration_hpa" tableId="configuration_hpa"
className="HorizontalPodAutoscalers" store={hpaStore} className="HorizontalPodAutoscalers" store={hpaStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (item: HorizontalPodAutoscaler) => item.getName(), [columnId.name]: item => item.getName(),
[columnId.namespace]: (item: HorizontalPodAutoscaler) => item.getNs(), [columnId.namespace]: item => item.getNs(),
[columnId.minPods]: (item: HorizontalPodAutoscaler) => item.getMinPods(), [columnId.minPods]: item => item.getMinPods(),
[columnId.maxPods]: (item: HorizontalPodAutoscaler) => item.getMaxPods(), [columnId.maxPods]: item => item.getMaxPods(),
[columnId.replicas]: (item: HorizontalPodAutoscaler) => item.getReplicas(), [columnId.replicas]: item => item.getReplicas(),
[columnId.age]: (item: HorizontalPodAutoscaler) => item.getTimeDiffFromNow(), [columnId.age]: item => item.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(item: HorizontalPodAutoscaler) => item.getSearchFields() item => item.getSearchFields()
]} ]}
renderHeaderTitle="Horizontal Pod Autoscalers" renderHeaderTitle="Horizontal Pod Autoscalers"
renderTableHeader={[ renderTableHeader={[
@ -86,7 +86,7 @@ export class HorizontalPodAutoscalers extends React.Component<Props> {
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", id: columnId.status }, { title: "Status", className: "status", id: columnId.status },
]} ]}
renderTableContents={(hpa: HorizontalPodAutoscaler) => [ renderTableContents={hpa => [
hpa.getName(), hpa.getName(),
<KubeObjectStatusIcon key="icon" object={hpa} />, <KubeObjectStatusIcon key="icon" object={hpa} />,
hpa.getNs(), hpa.getNs(),

View File

@ -28,7 +28,6 @@ import { limitRangeStore } from "./limit-ranges.store";
import type { LimitRangeRouteParams } from "./limit-ranges.route"; import type { LimitRangeRouteParams } from "./limit-ranges.route";
import React from "react"; import React from "react";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { LimitRange } from "../../api/endpoints/limit-range.api";
enum columnId { enum columnId {
name = "name", name = "name",
@ -49,22 +48,22 @@ export class LimitRanges extends React.Component<Props> {
className="LimitRanges" className="LimitRanges"
store={limitRangeStore} store={limitRangeStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (item: LimitRange) => item.getName(), [columnId.name]: item => item.getName(),
[columnId.namespace]: (item: LimitRange) => item.getNs(), [columnId.namespace]: item => item.getNs(),
[columnId.age]: (item: LimitRange) => item.getTimeDiffFromNow(), [columnId.age]: item => item.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(item: LimitRange) => item.getName(), item => item.getName(),
(item: LimitRange) => item.getNs(), item => item.getNs(),
]} ]}
renderHeaderTitle={"Limit Ranges"} renderHeaderTitle="Limit Ranges"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(limitRange: LimitRange) => [ renderTableContents={limitRange => [
limitRange.getName(), limitRange.getName(),
<KubeObjectStatusIcon key="icon" object={limitRange}/>, <KubeObjectStatusIcon key="icon" object={limitRange}/>,
limitRange.getNs(), limitRange.getNs(),

View File

@ -25,7 +25,6 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import { configMapsStore } from "./config-maps.store"; import { configMapsStore } from "./config-maps.store";
import type { ConfigMap } from "../../api/endpoints/configmap.api";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import type { IConfigMapsRouteParams } from "./config-maps.route"; import type { IConfigMapsRouteParams } from "./config-maps.route";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
@ -49,14 +48,14 @@ export class ConfigMaps extends React.Component<Props> {
tableId="configuration_configmaps" tableId="configuration_configmaps"
className="ConfigMaps" store={configMapsStore} className="ConfigMaps" store={configMapsStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (item: ConfigMap) => item.getName(), [columnId.name]: item => item.getName(),
[columnId.namespace]: (item: ConfigMap) => item.getNs(), [columnId.namespace]: item => item.getNs(),
[columnId.keys]: (item: ConfigMap) => item.getKeys(), [columnId.keys]: item => item.getKeys(),
[columnId.age]: (item: ConfigMap) => item.getTimeDiffFromNow(), [columnId.age]: item => item.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(item: ConfigMap) => item.getSearchFields(), item => item.getSearchFields(),
(item: ConfigMap) => item.getKeys() item => item.getKeys()
]} ]}
renderHeaderTitle="Config Maps" renderHeaderTitle="Config Maps"
renderTableHeader={[ renderTableHeader={[
@ -66,7 +65,7 @@ export class ConfigMaps extends React.Component<Props> {
{ title: "Keys", className: "keys", sortBy: columnId.keys, id: columnId.keys }, { title: "Keys", className: "keys", sortBy: columnId.keys, id: columnId.keys },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(configMap: ConfigMap) => [ renderTableContents={configMap => [
configMap.getName(), configMap.getName(),
<KubeObjectStatusIcon key="icon" object={configMap}/>, <KubeObjectStatusIcon key="icon" object={configMap}/>,
configMap.getNs(), configMap.getNs(),

View File

@ -51,16 +51,16 @@ export class PodDisruptionBudgets extends React.Component<Props> {
className="PodDisruptionBudgets" className="PodDisruptionBudgets"
store={podDisruptionBudgetsStore} store={podDisruptionBudgetsStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (pdb: PodDisruptionBudget) => pdb.getName(), [columnId.name]: pdb => pdb.getName(),
[columnId.namespace]: (pdb: PodDisruptionBudget) => pdb.getNs(), [columnId.namespace]: pdb => pdb.getNs(),
[columnId.minAvailable]: (pdb: PodDisruptionBudget) => pdb.getMinAvailable(), [columnId.minAvailable]: pdb => pdb.getMinAvailable(),
[columnId.maxUnavailable]: (pdb: PodDisruptionBudget) => pdb.getMaxUnavailable(), [columnId.maxUnavailable]: pdb => pdb.getMaxUnavailable(),
[columnId.currentHealthy]: (pdb: PodDisruptionBudget) => pdb.getCurrentHealthy(), [columnId.currentHealthy]: pdb => pdb.getCurrentHealthy(),
[columnId.desiredHealthy]: (pdb: PodDisruptionBudget) => pdb.getDesiredHealthy(), [columnId.desiredHealthy]: pdb => pdb.getDesiredHealthy(),
[columnId.age]: (pdb: PodDisruptionBudget) => pdb.getAge(), [columnId.age]: pdb => pdb.getAge(),
}} }}
searchFilters={[ searchFilters={[
(pdb: PodDisruptionBudget) => pdb.getSearchFields(), pdb => pdb.getSearchFields(),
]} ]}
renderHeaderTitle="Pod Disruption Budgets" renderHeaderTitle="Pod Disruption Budgets"
renderTableHeader={[ renderTableHeader={[
@ -73,7 +73,7 @@ export class PodDisruptionBudgets extends React.Component<Props> {
{ title: "Desired Healthy", className: "desired-healthy", sortBy: columnId.desiredHealthy, id: columnId.desiredHealthy }, { title: "Desired Healthy", className: "desired-healthy", sortBy: columnId.desiredHealthy, id: columnId.desiredHealthy },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(pdb: PodDisruptionBudget) => { renderTableContents={pdb => {
return [ return [
pdb.getName(), pdb.getName(),
<KubeObjectStatusIcon key="icon" object={pdb} />, <KubeObjectStatusIcon key="icon" object={pdb} />,

View File

@ -25,7 +25,6 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import type { ResourceQuota } from "../../api/endpoints/resource-quota.api";
import { AddQuotaDialog } from "./add-quota-dialog"; import { AddQuotaDialog } from "./add-quota-dialog";
import { resourceQuotaStore } from "./resource-quotas.store"; import { resourceQuotaStore } from "./resource-quotas.store";
import type { IResourceQuotaRouteParams } from "./resource-quotas.route"; import type { IResourceQuotaRouteParams } from "./resource-quotas.route";
@ -50,13 +49,13 @@ export class ResourceQuotas extends React.Component<Props> {
tableId="configuration_quotas" tableId="configuration_quotas"
className="ResourceQuotas" store={resourceQuotaStore} className="ResourceQuotas" store={resourceQuotaStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (item: ResourceQuota) => item.getName(), [columnId.name]: item => item.getName(),
[columnId.namespace]: (item: ResourceQuota) => item.getNs(), [columnId.namespace]: item => item.getNs(),
[columnId.age]: (item: ResourceQuota) => item.getTimeDiffFromNow(), [columnId.age]: item => item.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(item: ResourceQuota) => item.getSearchFields(), item => item.getSearchFields(),
(item: ResourceQuota) => item.getName(), item => item.getName(),
]} ]}
renderHeaderTitle="Resource Quotas" renderHeaderTitle="Resource Quotas"
renderTableHeader={[ renderTableHeader={[
@ -65,7 +64,7 @@ export class ResourceQuotas extends React.Component<Props> {
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(resourceQuota: ResourceQuota) => [ renderTableContents={resourceQuota => [
resourceQuota.getName(), resourceQuota.getName(),
<KubeObjectStatusIcon key="icon" object={resourceQuota}/>, <KubeObjectStatusIcon key="icon" object={resourceQuota}/>,
resourceQuota.getNs(), resourceQuota.getNs(),

View File

@ -24,7 +24,6 @@ import "./secrets.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import type { Secret } from "../../api/endpoints";
import { AddSecretDialog } from "./add-secret-dialog"; import { AddSecretDialog } from "./add-secret-dialog";
import type { ISecretsRouteParams } from "./secrets.route"; import type { ISecretsRouteParams } from "./secrets.route";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
@ -54,16 +53,16 @@ export class Secrets extends React.Component<Props> {
tableId="configuration_secrets" tableId="configuration_secrets"
className="Secrets" store={secretsStore} className="Secrets" store={secretsStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (item: Secret) => item.getName(), [columnId.name]: item => item.getName(),
[columnId.namespace]: (item: Secret) => item.getNs(), [columnId.namespace]: item => item.getNs(),
[columnId.labels]: (item: Secret) => item.getLabels(), [columnId.labels]: item => item.getLabels(),
[columnId.keys]: (item: Secret) => item.getKeys(), [columnId.keys]: item => item.getKeys(),
[columnId.type]: (item: Secret) => item.type, [columnId.type]: item => item.type,
[columnId.age]: (item: Secret) => item.getTimeDiffFromNow(), [columnId.age]: item => item.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(item: Secret) => item.getSearchFields(), item => item.getSearchFields(),
(item: Secret) => item.getKeys(), item => item.getKeys(),
]} ]}
renderHeaderTitle="Secrets" renderHeaderTitle="Secrets"
renderTableHeader={[ renderTableHeader={[
@ -75,7 +74,7 @@ export class Secrets extends React.Component<Props> {
{ title: "Type", className: "type", sortBy: columnId.type, id: columnId.type }, { title: "Type", className: "type", sortBy: columnId.type, id: columnId.type },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.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

@ -32,6 +32,7 @@ import type { CustomResourceDefinition } from "../../api/endpoints/crd.api";
import { Select, SelectOption } from "../select"; import { Select, SelectOption } from "../select";
import { createPageParam } from "../../navigation"; import { createPageParam } from "../../navigation";
import { Icon } from "../icon"; import { Icon } from "../icon";
import type { TableSortCallbacks } from "../table";
export const crdGroupsUrlParam = createPageParam<string[]>({ export const crdGroupsUrlParam = createPageParam<string[]>({
name: "groups", name: "groups",
@ -78,11 +79,11 @@ export class CrdList extends React.Component {
render() { render() {
const { items, selectedGroups } = this; const { items, selectedGroups } = this;
const sortingCallbacks = { const sortingCallbacks: TableSortCallbacks<CustomResourceDefinition> = {
[columnId.kind]: (crd: CustomResourceDefinition) => crd.getResourceKind(), [columnId.kind]: crd => crd.getResourceKind(),
[columnId.group]: (crd: CustomResourceDefinition) => crd.getGroup(), [columnId.group]: crd => crd.getGroup(),
[columnId.version]: (crd: CustomResourceDefinition) => crd.getVersion(), [columnId.version]: crd => crd.getVersion(),
[columnId.scope]: (crd: CustomResourceDefinition) => crd.getScope(), [columnId.scope]: crd => crd.getScope(),
}; };
return ( return (
@ -133,7 +134,7 @@ export class CrdList extends React.Component {
{ title: "Scope", className: "scope", sortBy: columnId.scope, id: columnId.scope }, { title: "Scope", className: "scope", sortBy: columnId.scope, id: columnId.scope },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(crd: CustomResourceDefinition) => [ renderTableContents={crd => [
<Link key="link" to={crd.getResourceUrl()} onClick={stopPropagation}> <Link key="link" to={crd.getResourceUrl()} onClick={stopPropagation}>
{crd.getResourceTitle()} {crd.getResourceTitle()}
</Link>, </Link>,

View File

@ -23,10 +23,10 @@ import type { KubeApi } from "../../api/kube-api";
import { KubeObjectStore } from "../../kube-object.store"; import { KubeObjectStore } from "../../kube-object.store";
import type { KubeObject } from "../../api/kube-object"; import type { KubeObject } from "../../api/kube-object";
export class CRDResourceStore<T extends KubeObject = any> extends KubeObjectStore<T> { export class CRDResourceStore<K extends KubeObject> extends KubeObjectStore<K> {
api: KubeApi; api: KubeApi<K>;
constructor(api: KubeApi<T>) { constructor(api: KubeApi<K>) {
super(); super();
this.api = api; this.api = api;
} }

View File

@ -30,7 +30,7 @@ import type { KubeObject } from "../../api/kube-object";
import type { ICRDRouteParams } from "./crd.route"; import type { ICRDRouteParams } from "./crd.route";
import { autorun, computed, makeObservable } from "mobx"; import { autorun, computed, makeObservable } from "mobx";
import { crdStore } from "./crd.store"; import { crdStore } from "./crd.store";
import type { TableSortCallback } from "../table"; import type { TableSortCallbacks } from "../table";
import { apiManager } from "../../api/api-manager"; import { apiManager } from "../../api/api-manager";
import { parseJsonPath } from "../../utils/jsonPath"; import { parseJsonPath } from "../../utils/jsonPath";
@ -80,14 +80,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: TableSortCallbacks<KubeObject> = {
[columnId.name]: (item: KubeObject) => item.getName(), [columnId.name]: item => item.getName(),
[columnId.namespace]: (item: KubeObject) => item.getNs(), [columnId.namespace]: item => item.getNs(),
[columnId.age]: (item: KubeObject) => item.getTimeDiffFromNow(), [columnId.age]: item => item.getTimeDiffFromNow(),
}; };
extraColumns.forEach(column => { extraColumns.forEach(column => {
sortingCallbacks[column.name] = (item: KubeObject) => jsonPath.value(item, parseJsonPath(column.jsonPath.slice(1))); sortingCallbacks[column.name] = item => jsonPath.value(item, parseJsonPath(column.jsonPath.slice(1)));
}); });
return ( return (
@ -98,7 +98,7 @@ export class CrdResources extends React.Component<Props> {
store={store} store={store}
sortingCallbacks={sortingCallbacks} sortingCallbacks={sortingCallbacks}
searchFilters={[ searchFilters={[
(item: KubeObject) => item.getSearchFields(), item => item.getSearchFields(),
]} ]}
renderHeaderTitle={crd.getResourceTitle()} renderHeaderTitle={crd.getResourceTitle()}
renderTableHeader={[ renderTableHeader={[
@ -116,7 +116,7 @@ export class CrdResources extends React.Component<Props> {
}), }),
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(crdInstance: KubeObject) => [ renderTableContents={crdInstance => [
crdInstance.getName(), crdInstance.getName(),
isNamespaced && crdInstance.getNs(), isNamespaced && crdInstance.getNs(),
...extraColumns.map((column) => { ...extraColumns.map((column) => {

View File

@ -26,13 +26,18 @@ import { crdApi, CustomResourceDefinition } from "../../api/endpoints/crd.api";
import { apiManager } from "../../api/api-manager"; import { apiManager } from "../../api/api-manager";
import { KubeApi } from "../../api/kube-api"; import { KubeApi } from "../../api/kube-api";
import { CRDResourceStore } from "./crd-resource.store"; import { CRDResourceStore } from "./crd-resource.store";
import type { KubeObject } from "../../api/kube-object"; import { KubeObject } from "../../api/kube-object";
function initStore(crd: CustomResourceDefinition) { function initStore(crd: CustomResourceDefinition) {
const apiBase = crd.getResourceApiBase(); const apiBase = crd.getResourceApiBase();
const kind = crd.getResourceKind(); const kind = crd.getResourceKind();
const isNamespaced = crd.isNamespaced(); const isNamespaced = crd.isNamespaced();
const api = apiManager.getApi(apiBase) || new KubeApi({ apiBase, kind, isNamespaced }); const api = apiManager.getApi(apiBase) ?? new KubeApi({
objectConstructor: KubeObject,
apiBase,
kind,
isNamespaced,
});
if (!apiManager.getStore(api)) { if (!apiManager.getStore(api)) {
apiManager.registerStore(new CRDResourceStore(api)); apiManager.registerStore(new CRDResourceStore(api));

View File

@ -29,7 +29,7 @@ import { TabLayout } from "../layout/tab-layout";
import { EventStore, eventStore } from "./event.store"; import { EventStore, eventStore } from "./event.store";
import { getDetailsUrl, KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object"; import { getDetailsUrl, KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object";
import type { KubeEvent } from "../../api/endpoints/events.api"; import type { KubeEvent } from "../../api/endpoints/events.api";
import type { TableSortCallbacks, TableSortParams, TableProps } from "../table"; import type { TableSortCallbacks, TableSortParams } from "../table";
import type { IHeaderPlaceholders } from "../item-object-list"; import type { IHeaderPlaceholders } from "../item-object-list";
import { Tooltip } from "../tooltip"; import { Tooltip } from "../tooltip";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@ -49,7 +49,7 @@ enum columnId {
lastSeen = "last-seen", lastSeen = "last-seen",
} }
interface Props extends Partial<KubeObjectListLayoutProps> { interface Props extends Partial<KubeObjectListLayoutProps<KubeEvent>> {
className?: IClassName; className?: IClassName;
compact?: boolean; compact?: boolean;
compactLimit?: number; compactLimit?: number;
@ -69,19 +69,13 @@ export class Events extends React.Component<Props> {
orderBy: "asc", orderBy: "asc",
}; };
private sortingCallbacks: TableSortCallbacks = { private sortingCallbacks: TableSortCallbacks<KubeEvent> = {
[columnId.namespace]: (event: KubeEvent) => event.getNs(), [columnId.namespace]: event => event.getNs(),
[columnId.type]: (event: KubeEvent) => event.type, [columnId.type]: event => event.type,
[columnId.object]: (event: KubeEvent) => event.involvedObject.name, [columnId.object]: event => event.involvedObject.name,
[columnId.count]: (event: KubeEvent) => event.count, [columnId.count]: event => event.count,
[columnId.age]: (event: KubeEvent) => event.getTimeDiffFromNow(), [columnId.age]: event => event.getTimeDiffFromNow(),
[columnId.lastSeen]: (event: KubeEvent) => this.now - new Date(event.lastTimestamp).getTime(), [columnId.lastSeen]: event => this.now - new Date(event.lastTimestamp).getTime(),
};
private tableConfiguration: TableProps = {
sortSyncWithUrl: false,
sortByDefault: this.sorting,
onSort: params => this.sorting = params,
}; };
constructor(props: Props) { constructor(props: Props) {
@ -156,13 +150,17 @@ export class Events extends React.Component<Props> {
isSelectable={false} isSelectable={false}
items={visibleItems} items={visibleItems}
virtual={!compact} virtual={!compact}
tableProps={this.tableConfiguration} tableProps={{
sortSyncWithUrl: false,
sortByDefault: this.sorting,
onSort: params => this.sorting = params,
}}
sortingCallbacks={this.sortingCallbacks} sortingCallbacks={this.sortingCallbacks}
searchFilters={[ searchFilters={[
(event: KubeEvent) => event.getSearchFields(), event => event.getSearchFields(),
(event: KubeEvent) => event.message, event => event.message,
(event: KubeEvent) => event.getSource(), event => event.getSource(),
(event: KubeEvent) => event.involvedObject.name, event => event.involvedObject.name,
]} ]}
renderTableHeader={[ renderTableHeader={[
{ title: "Type", className: "type", sortBy: columnId.type, id: columnId.type }, { title: "Type", className: "type", sortBy: columnId.type, id: columnId.type },
@ -174,7 +172,7 @@ export class Events extends React.Component<Props> {
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Last Seen", className: "last-seen", sortBy: columnId.lastSeen, id: columnId.lastSeen }, { title: "Last Seen", className: "last-seen", sortBy: columnId.lastSeen, id: columnId.lastSeen },
]} ]}
renderTableContents={(event: KubeEvent) => { renderTableContents={event => {
const { involvedObject, type, message } = event; const { involvedObject, type, message } = event;
const tooltipId = `message-${event.getId()}`; const tooltipId = `message-${event.getId()}`;
const isWarning = event.isWarning(); const isWarning = event.isWarning();

View File

@ -106,7 +106,7 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
return super.getSubscribeApis(); return super.getSubscribeApis();
} }
protected async loadItems(params: KubeObjectStoreLoadingParams) { protected async loadItems(params: KubeObjectStoreLoadingParams<Namespace>) {
const { allowedNamespaces } = this; const { allowedNamespaces } = this;
let namespaces = (await super.loadItems(params)) || []; let namespaces = (await super.loadItems(params)) || [];

View File

@ -22,7 +22,7 @@
import "./namespaces.scss"; import "./namespaces.scss";
import React from "react"; import React from "react";
import { Namespace, NamespaceStatus } from "../../api/endpoints"; import { NamespaceStatus } from "../../api/endpoints";
import { AddNamespaceDialog } from "./add-namespace-dialog"; import { AddNamespaceDialog } from "./add-namespace-dialog";
import { TabLayout } from "../layout/tab-layout"; import { TabLayout } from "../layout/tab-layout";
import { Badge } from "../badge"; import { Badge } from "../badge";
@ -51,14 +51,14 @@ export class Namespaces extends React.Component<Props> {
tableId="namespaces" tableId="namespaces"
className="Namespaces" store={namespaceStore} className="Namespaces" store={namespaceStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (ns: Namespace) => ns.getName(), [columnId.name]: ns => ns.getName(),
[columnId.labels]: (ns: Namespace) => ns.getLabels(), [columnId.labels]: ns => ns.getLabels(),
[columnId.age]: (ns: Namespace) => ns.getTimeDiffFromNow(), [columnId.age]: ns => ns.getTimeDiffFromNow(),
[columnId.status]: (ns: Namespace) => ns.getStatus(), [columnId.status]: ns => ns.getStatus(),
}} }}
searchFilters={[ searchFilters={[
(item: Namespace) => item.getSearchFields(), item => item.getSearchFields(),
(item: Namespace) => item.getStatus() item => item.getStatus()
]} ]}
renderHeaderTitle="Namespaces" renderHeaderTitle="Namespaces"
renderTableHeader={[ renderTableHeader={[
@ -68,7 +68,7 @@ export class Namespaces extends React.Component<Props> {
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]} ]}
renderTableContents={(item: Namespace) => [ renderTableContents={item => [
item.getName(), item.getName(),
<KubeObjectStatusIcon key="icon" object={item} />, <KubeObjectStatusIcon key="icon" object={item} />,
item.getLabels().map(label => <Badge key={label} label={label}/>), item.getLabels().map(label => <Badge key={label} label={label}/>),
@ -79,7 +79,7 @@ export class Namespaces extends React.Component<Props> {
addTooltip: "Add Namespace", addTooltip: "Add Namespace",
onAdd: () => AddNamespaceDialog.open(), onAdd: () => AddNamespaceDialog.open(),
}} }}
customizeTableRowProps={(item: Namespace) => ({ customizeTableRowProps={item => ({
disabled: item.getStatus() === NamespaceStatus.TERMINATING, disabled: item.getStatus() === NamespaceStatus.TERMINATING,
})} })}
/> />

View File

@ -25,7 +25,6 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router-dom"; import type { RouteComponentProps } from "react-router-dom";
import type { EndpointRouteParams } from "./endpoints.route"; import type { EndpointRouteParams } from "./endpoints.route";
import type { Endpoint } from "../../api/endpoints/endpoint.api";
import { endpointStore } from "./endpoints.store"; import { endpointStore } from "./endpoints.store";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
@ -49,12 +48,12 @@ export class Endpoints extends React.Component<Props> {
tableId="network_endpoints" tableId="network_endpoints"
className="Endpoints" store={endpointStore} className="Endpoints" store={endpointStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (endpoint: Endpoint) => endpoint.getName(), [columnId.name]: endpoint => endpoint.getName(),
[columnId.namespace]: (endpoint: Endpoint) => endpoint.getNs(), [columnId.namespace]: endpoint => endpoint.getNs(),
[columnId.age]: (endpoint: Endpoint) => endpoint.getTimeDiffFromNow(), [columnId.age]: endpoint => endpoint.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(endpoint: Endpoint) => endpoint.getSearchFields() endpoint => endpoint.getSearchFields()
]} ]}
renderHeaderTitle="Endpoints" renderHeaderTitle="Endpoints"
renderTableHeader={[ renderTableHeader={[
@ -64,7 +63,7 @@ export class Endpoints extends React.Component<Props> {
{ title: "Endpoints", className: "endpoints", id: columnId.endpoints }, { title: "Endpoints", className: "endpoints", id: columnId.endpoints },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(endpoint: Endpoint) => [ renderTableContents={endpoint => [
endpoint.getName(), endpoint.getName(),
<KubeObjectStatusIcon key="icon" object={endpoint} />, <KubeObjectStatusIcon key="icon" object={endpoint} />,
endpoint.getNs(), endpoint.getNs(),
@ -72,7 +71,7 @@ export class Endpoints extends React.Component<Props> {
endpoint.getAge(), endpoint.getAge(),
]} ]}
tableProps={{ tableProps={{
customRowHeights: (item: Endpoint, lineHeight, paddings) => { customRowHeights: (item, lineHeight, paddings) => {
const lines = item.getEndpointSubsets().length || 1; const lines = item.getEndpointSubsets().length || 1;
return lines * lineHeight + paddings; return lines * lineHeight + paddings;

View File

@ -25,7 +25,6 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router-dom"; import type { RouteComponentProps } from "react-router-dom";
import type { IngressRouteParams } from "./ingresses.route"; import type { IngressRouteParams } from "./ingresses.route";
import type { Ingress } from "../../api/endpoints/ingress.api";
import { ingressStore } from "./ingress.store"; import { ingressStore } from "./ingress.store";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
@ -50,13 +49,13 @@ export class Ingresses extends React.Component<Props> {
tableId="network_ingresses" tableId="network_ingresses"
className="Ingresses" store={ingressStore} className="Ingresses" store={ingressStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (ingress: Ingress) => ingress.getName(), [columnId.name]: ingress => ingress.getName(),
[columnId.namespace]: (ingress: Ingress) => ingress.getNs(), [columnId.namespace]: ingress => ingress.getNs(),
[columnId.age]: (ingress: Ingress) => ingress.getTimeDiffFromNow(), [columnId.age]: ingress => ingress.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(ingress: Ingress) => ingress.getSearchFields(), ingress => ingress.getSearchFields(),
(ingress: Ingress) => ingress.getPorts(), ingress => ingress.getPorts(),
]} ]}
renderHeaderTitle="Ingresses" renderHeaderTitle="Ingresses"
renderTableHeader={[ renderTableHeader={[
@ -67,7 +66,7 @@ export class Ingresses extends React.Component<Props> {
{ title: "Rules", className: "rules", id: columnId.rules }, { title: "Rules", className: "rules", id: columnId.rules },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(ingress: Ingress) => [ renderTableContents={ingress => [
ingress.getName(), ingress.getName(),
<KubeObjectStatusIcon key="icon" object={ingress} />, <KubeObjectStatusIcon key="icon" object={ingress} />,
ingress.getNs(), ingress.getNs(),
@ -76,7 +75,7 @@ export class Ingresses extends React.Component<Props> {
ingress.getAge(), ingress.getAge(),
]} ]}
tableProps={{ tableProps={{
customRowHeights: (item: Ingress, lineHeight, paddings) => { customRowHeights: (item, lineHeight, paddings) => {
const lines = item.getRoutes().length || 1; const lines = item.getRoutes().length || 1;
return lines * lineHeight + paddings; return lines * lineHeight + paddings;

View File

@ -24,7 +24,6 @@ import "./network-policies.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router-dom"; import type { RouteComponentProps } from "react-router-dom";
import type { NetworkPolicy } from "../../api/endpoints/network-policy.api";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import type { INetworkPoliciesRouteParams } from "./network-policies.route"; import type { INetworkPoliciesRouteParams } from "./network-policies.route";
import { networkPolicyStore } from "./network-policy.store"; import { networkPolicyStore } from "./network-policy.store";
@ -49,12 +48,12 @@ export class NetworkPolicies extends React.Component<Props> {
tableId="network_policies" tableId="network_policies"
className="NetworkPolicies" store={networkPolicyStore} className="NetworkPolicies" store={networkPolicyStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (item: NetworkPolicy) => item.getName(), [columnId.name]: item => item.getName(),
[columnId.namespace]: (item: NetworkPolicy) => item.getNs(), [columnId.namespace]: item => item.getNs(),
[columnId.age]: (item: NetworkPolicy) => item.getTimeDiffFromNow(), [columnId.age]: item => item.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(item: NetworkPolicy) => item.getSearchFields(), item => item.getSearchFields(),
]} ]}
renderHeaderTitle="Network Policies" renderHeaderTitle="Network Policies"
renderTableHeader={[ renderTableHeader={[
@ -64,7 +63,7 @@ export class NetworkPolicies extends React.Component<Props> {
{ title: "Policy Types", className: "type", id: columnId.types }, { title: "Policy Types", className: "type", id: columnId.types },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(item: NetworkPolicy) => [ renderTableContents={item => [
item.getName(), item.getName(),
<KubeObjectStatusIcon key="icon" object={item} />, <KubeObjectStatusIcon key="icon" object={item} />,
item.getNs(), item.getNs(),

View File

@ -25,7 +25,6 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import type { IServicesRouteParams } from "./services.route"; import type { IServicesRouteParams } from "./services.route";
import type { Service } from "../../api/endpoints/service.api";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { Badge } from "../badge"; import { Badge } from "../badge";
import { serviceStore } from "./services.store"; import { serviceStore } from "./services.store";
@ -55,19 +54,19 @@ export class Services extends React.Component<Props> {
tableId="network_services" tableId="network_services"
className="Services" store={serviceStore} className="Services" store={serviceStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (service: Service) => service.getName(), [columnId.name]: service => service.getName(),
[columnId.namespace]: (service: Service) => service.getNs(), [columnId.namespace]: service => service.getNs(),
[columnId.selector]: (service: Service) => service.getSelector(), [columnId.selector]: service => service.getSelector(),
[columnId.ports]: (service: Service) => (service.spec.ports || []).map(({ port }) => port)[0], [columnId.ports]: service => (service.spec.ports || []).map(({ port }) => port)[0],
[columnId.clusterIp]: (service: Service) => service.getClusterIp(), [columnId.clusterIp]: service => service.getClusterIp(),
[columnId.type]: (service: Service) => service.getType(), [columnId.type]: service => service.getType(),
[columnId.age]: (service: Service) => service.getTimeDiffFromNow(), [columnId.age]: service => service.getTimeDiffFromNow(),
[columnId.status]: (service: Service) => service.getStatus(), [columnId.status]: service => service.getStatus(),
}} }}
searchFilters={[ searchFilters={[
(service: Service) => service.getSearchFields(), service => service.getSearchFields(),
(service: Service) => service.getSelector().join(" "), service => service.getSelector().join(" "),
(service: Service) => service.getPorts().join(" "), service => service.getPorts().join(" "),
]} ]}
renderHeaderTitle="Services" renderHeaderTitle="Services"
renderTableHeader={[ renderTableHeader={[
@ -82,7 +81,7 @@ export class Services extends React.Component<Props> {
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]} ]}
renderTableContents={(service: Service) => [ renderTableContents={service => [
service.getName(), service.getName(),
<KubeObjectStatusIcon key="icon" object={service} />, <KubeObjectStatusIcon key="icon" object={service} />,
service.getNs(), service.getNs(),

View File

@ -171,21 +171,21 @@ export class Nodes extends React.Component<Props> {
dependentStores={[podsStore]} dependentStores={[podsStore]}
isSelectable={false} isSelectable={false}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (node: Node) => node.getName(), [columnId.name]: node => node.getName(),
[columnId.cpu]: (node: Node) => nodesStore.getLastMetricValues(node, ["cpuUsage"]), [columnId.cpu]: node => nodesStore.getLastMetricValues(node, ["cpuUsage"]),
[columnId.memory]: (node: Node) => nodesStore.getLastMetricValues(node, ["memoryUsage"]), [columnId.memory]: node => nodesStore.getLastMetricValues(node, ["memoryUsage"]),
[columnId.disk]: (node: Node) => nodesStore.getLastMetricValues(node, ["fsUsage"]), [columnId.disk]: node => nodesStore.getLastMetricValues(node, ["fsUsage"]),
[columnId.conditions]: (node: Node) => node.getNodeConditionText(), [columnId.conditions]: node => node.getNodeConditionText(),
[columnId.taints]: (node: Node) => node.getTaints().length, [columnId.taints]: node => node.getTaints().length,
[columnId.roles]: (node: Node) => node.getRoleLabels(), [columnId.roles]: node => node.getRoleLabels(),
[columnId.age]: (node: Node) => node.getTimeDiffFromNow(), [columnId.age]: node => node.getTimeDiffFromNow(),
[columnId.version]: (node: Node) => node.getKubeletVersion(), [columnId.version]: node => node.getKubeletVersion(),
}} }}
searchFilters={[ searchFilters={[
(node: Node) => node.getSearchFields(), node => node.getSearchFields(),
(node: Node) => node.getRoleLabels(), node => node.getRoleLabels(),
(node: Node) => node.getKubeletVersion(), node => node.getKubeletVersion(),
(node: Node) => node.getNodeConditionText(), node => node.getNodeConditionText(),
]} ]}
renderHeaderTitle="Nodes" renderHeaderTitle="Nodes"
renderTableHeader={[ renderTableHeader={[
@ -200,7 +200,7 @@ export class Nodes extends React.Component<Props> {
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Conditions", className: "conditions", sortBy: columnId.conditions, id: columnId.conditions }, { title: "Conditions", className: "conditions", sortBy: columnId.conditions, id: columnId.conditions },
]} ]}
renderTableContents={(node: Node) => { renderTableContents={node => {
const tooltipId = `node-taints-${node.getId()}`; const tooltipId = `node-taints-${node.getId()}`;
return [ return [

View File

@ -25,7 +25,6 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { podSecurityPoliciesStore } from "./pod-security-policies.store"; import { podSecurityPoliciesStore } from "./pod-security-policies.store";
import type { PodSecurityPolicy } from "../../api/endpoints";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum columnId { enum columnId {
@ -45,15 +44,15 @@ export class PodSecurityPolicies extends React.Component {
className="PodSecurityPolicies" className="PodSecurityPolicies"
store={podSecurityPoliciesStore} store={podSecurityPoliciesStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (item: PodSecurityPolicy) => item.getName(), [columnId.name]: item => item.getName(),
[columnId.volumes]: (item: PodSecurityPolicy) => item.getVolumes(), [columnId.volumes]: item => item.getVolumes(),
[columnId.privileged]: (item: PodSecurityPolicy) => +item.isPrivileged(), [columnId.privileged]: item => +item.isPrivileged(),
[columnId.age]: (item: PodSecurityPolicy) => item.getTimeDiffFromNow(), [columnId.age]: item => item.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(item: PodSecurityPolicy) => item.getSearchFields(), item => item.getSearchFields(),
(item: PodSecurityPolicy) => item.getVolumes(), item => item.getVolumes(),
(item: PodSecurityPolicy) => Object.values(item.getRules()), item => Object.values(item.getRules()),
]} ]}
renderHeaderTitle="Pod Security Policies" renderHeaderTitle="Pod Security Policies"
renderTableHeader={[ renderTableHeader={[
@ -63,7 +62,7 @@ export class PodSecurityPolicies extends React.Component {
{ title: "Volumes", className: "volumes", sortBy: columnId.volumes, id: columnId.volumes }, { title: "Volumes", className: "volumes", sortBy: columnId.volumes, id: columnId.volumes },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(item: PodSecurityPolicy) => { renderTableContents={item => {
return [ return [
item.getName(), item.getName(),
<KubeObjectStatusIcon key="icon" object={item} />, <KubeObjectStatusIcon key="icon" object={item} />,

View File

@ -24,7 +24,6 @@ import "./storage-classes.scss";
import React from "react"; import React from "react";
import type { RouteComponentProps } from "react-router-dom"; import type { RouteComponentProps } from "react-router-dom";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { StorageClass } from "../../api/endpoints/storage-class.api";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import type { IStorageClassesRouteParams } from "./storage-classes.route"; import type { IStorageClassesRouteParams } from "./storage-classes.route";
import { storageClassStore } from "./storage-class.store"; import { storageClassStore } from "./storage-class.store";
@ -51,14 +50,14 @@ export class StorageClasses extends React.Component<Props> {
className="StorageClasses" className="StorageClasses"
store={storageClassStore} store={storageClassStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (item: StorageClass) => item.getName(), [columnId.name]: item => item.getName(),
[columnId.age]: (item: StorageClass) => item.getTimeDiffFromNow(), [columnId.age]: item => item.getTimeDiffFromNow(),
[columnId.provisioner]: (item: StorageClass) => item.provisioner, [columnId.provisioner]: item => item.provisioner,
[columnId.reclaimPolicy]: (item: StorageClass) => item.reclaimPolicy, [columnId.reclaimPolicy]: item => item.reclaimPolicy,
}} }}
searchFilters={[ searchFilters={[
(item: StorageClass) => item.getSearchFields(), item => item.getSearchFields(),
(item: StorageClass) => item.provisioner, item => item.provisioner,
]} ]}
renderHeaderTitle="Storage Classes" renderHeaderTitle="Storage Classes"
renderTableHeader={[ renderTableHeader={[
@ -69,7 +68,7 @@ export class StorageClasses extends React.Component<Props> {
{ title: "Default", className: "is-default", id: columnId.default }, { title: "Default", className: "is-default", id: columnId.default },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(storageClass: StorageClass) => [ renderTableContents={storageClass => [
storageClass.getName(), storageClass.getName(),
<KubeObjectStatusIcon key="icon" object={storageClass} />, <KubeObjectStatusIcon key="icon" object={storageClass} />,
storageClass.provisioner, storageClass.provisioner,

View File

@ -25,7 +25,6 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Link, RouteComponentProps } from "react-router-dom"; import { Link, RouteComponentProps } from "react-router-dom";
import { volumeClaimStore } from "./volume-claim.store"; import { volumeClaimStore } from "./volume-claim.store";
import type { PersistentVolumeClaim } from "../../api/endpoints/persistent-volume-claims.api";
import { podsStore } from "../+workloads-pods/pods.store"; import { podsStore } from "../+workloads-pods/pods.store";
import { getDetailsUrl, KubeObjectListLayout } from "../kube-object"; import { getDetailsUrl, KubeObjectListLayout } from "../kube-object";
import type { IVolumeClaimsRouteParams } from "./volume-claims.route"; import type { IVolumeClaimsRouteParams } from "./volume-claims.route";
@ -58,17 +57,17 @@ export class PersistentVolumeClaims extends React.Component<Props> {
store={volumeClaimStore} store={volumeClaimStore}
dependentStores={[podsStore]} dependentStores={[podsStore]}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (pvc: PersistentVolumeClaim) => pvc.getName(), [columnId.name]: pvc => pvc.getName(),
[columnId.namespace]: (pvc: PersistentVolumeClaim) => pvc.getNs(), [columnId.namespace]: pvc => pvc.getNs(),
[columnId.pods]: (pvc: PersistentVolumeClaim) => pvc.getPods(podsStore.items).map(pod => pod.getName()), [columnId.pods]: pvc => pvc.getPods(podsStore.items).map(pod => pod.getName()),
[columnId.status]: (pvc: PersistentVolumeClaim) => pvc.getStatus(), [columnId.status]: pvc => pvc.getStatus(),
[columnId.size]: (pvc: PersistentVolumeClaim) => unitsToBytes(pvc.getStorage()), [columnId.size]: pvc => unitsToBytes(pvc.getStorage()),
[columnId.storageClass]: (pvc: PersistentVolumeClaim) => pvc.spec.storageClassName, [columnId.storageClass]: pvc => pvc.spec.storageClassName,
[columnId.age]: (pvc: PersistentVolumeClaim) => pvc.getTimeDiffFromNow(), [columnId.age]: pvc => pvc.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(item: PersistentVolumeClaim) => item.getSearchFields(), item => item.getSearchFields(),
(item: PersistentVolumeClaim) => item.getPods(podsStore.items).map(pod => pod.getName()), item => item.getPods(podsStore.items).map(pod => pod.getName()),
]} ]}
renderHeaderTitle="Persistent Volume Claims" renderHeaderTitle="Persistent Volume Claims"
renderTableHeader={[ renderTableHeader={[
@ -81,7 +80,7 @@ export class PersistentVolumeClaims extends React.Component<Props> {
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]} ]}
renderTableContents={(pvc: PersistentVolumeClaim) => { renderTableContents={pvc => {
const pods = pvc.getPods(podsStore.items); const pods = pvc.getPods(podsStore.items);
const { storageClassName } = pvc.spec; const { storageClassName } = pvc.spec;
const storageClassDetailsUrl = getDetailsUrl(storageClassApi.getUrl({ const storageClassDetailsUrl = getDetailsUrl(storageClassApi.getUrl({

View File

@ -24,7 +24,6 @@ import "./volumes.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Link, RouteComponentProps } from "react-router-dom"; import { Link, RouteComponentProps } from "react-router-dom";
import type { PersistentVolume } from "../../api/endpoints/persistent-volume.api";
import { getDetailsUrl, KubeObjectListLayout } from "../kube-object"; import { getDetailsUrl, KubeObjectListLayout } from "../kube-object";
import type { IVolumesRouteParams } from "./volumes.route"; import type { IVolumesRouteParams } from "./volumes.route";
import { stopPropagation } from "../../utils"; import { stopPropagation } from "../../utils";
@ -54,15 +53,15 @@ export class PersistentVolumes extends React.Component<Props> {
className="PersistentVolumes" className="PersistentVolumes"
store={volumesStore} store={volumesStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (item: PersistentVolume) => item.getName(), [columnId.name]: item => item.getName(),
[columnId.storageClass]: (item: PersistentVolume) => item.getStorageClass(), [columnId.storageClass]: item => item.getStorageClass(),
[columnId.capacity]: (item: PersistentVolume) => item.getCapacity(true), [columnId.capacity]: item => item.getCapacity(true),
[columnId.status]: (item: PersistentVolume) => item.getStatus(), [columnId.status]: item => item.getStatus(),
[columnId.age]: (item: PersistentVolume) => item.getTimeDiffFromNow(), [columnId.age]: item => item.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(item: PersistentVolume) => item.getSearchFields(), item => item.getSearchFields(),
(item: PersistentVolume) => item.getClaimRefName(), item => item.getClaimRefName(),
]} ]}
renderHeaderTitle="Persistent Volumes" renderHeaderTitle="Persistent Volumes"
renderTableHeader={[ renderTableHeader={[
@ -74,7 +73,7 @@ export class PersistentVolumes extends React.Component<Props> {
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]} ]}
renderTableContents={(volume: PersistentVolume) => { renderTableContents={volume => {
const { claimRef, storageClassName } = volume.spec; const { claimRef, storageClassName } = volume.spec;
const storageClassDetailsUrl = getDetailsUrl(storageClassApi.getUrl({ const storageClassDetailsUrl = getDetailsUrl(storageClassApi.getUrl({
name: storageClassName name: storageClassName

View File

@ -40,7 +40,6 @@ import { namespaceStore } from "../+namespaces/namespace.store";
import { serviceAccountsStore } from "../+user-management-service-accounts/service-accounts.store"; import { serviceAccountsStore } from "../+user-management-service-accounts/service-accounts.store";
import { roleBindingsStore } from "./role-bindings.store"; import { roleBindingsStore } from "./role-bindings.store";
import { showDetails } from "../kube-object"; import { showDetails } from "../kube-object";
import type { KubeObjectStore } from "../../kube-object.store";
interface BindingSelectOption extends SelectOption { interface BindingSelectOption extends SelectOption {
value: string; // binding name value: string; // binding name
@ -102,14 +101,12 @@ export class AddRoleBindingDialog extends React.Component<Props> {
}; };
async loadData() { async loadData() {
const stores: KubeObjectStore[] = [
namespaceStore,
rolesStore,
serviceAccountsStore,
];
this.isLoading = true; this.isLoading = true;
await Promise.all(stores.map(store => store.reloadAll())); await Promise.all([
namespaceStore.reloadAll(),
rolesStore.reloadAll(),
serviceAccountsStore.reloadAll(),
]);
this.isLoading = false; this.isLoading = false;
} }

View File

@ -51,7 +51,7 @@ export class RoleBindingsStore extends KubeObjectStore<RoleBinding> {
return clusterRoleBindingApi.get(params); return clusterRoleBindingApi.get(params);
} }
protected async loadItems(params: KubeObjectStoreLoadingParams): Promise<RoleBinding[]> { protected async loadItems(params: KubeObjectStoreLoadingParams<RoleBinding>): Promise<RoleBinding[]> {
const items = await Promise.all([ const items = await Promise.all([
super.loadItems({ ...params, api: clusterRoleBindingApi }), super.loadItems({ ...params, api: clusterRoleBindingApi }),
super.loadItems({ ...params, api: roleBindingApi }), super.loadItems({ ...params, api: roleBindingApi }),

View File

@ -25,7 +25,6 @@ import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import type { IRoleBindingsRouteParams } from "../+user-management/user-management.route"; import type { IRoleBindingsRouteParams } from "../+user-management/user-management.route";
import type { RoleBinding } from "../../api/endpoints";
import { roleBindingsStore } from "./role-bindings.store"; import { roleBindingsStore } from "./role-bindings.store";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { AddRoleBindingDialog } from "./add-role-binding-dialog"; import { AddRoleBindingDialog } from "./add-role-binding-dialog";
@ -51,14 +50,14 @@ export class RoleBindings extends React.Component<Props> {
className="RoleBindings" className="RoleBindings"
store={roleBindingsStore} store={roleBindingsStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (binding: RoleBinding) => binding.getName(), [columnId.name]: binding => binding.getName(),
[columnId.namespace]: (binding: RoleBinding) => binding.getNs(), [columnId.namespace]: binding => binding.getNs(),
[columnId.bindings]: (binding: RoleBinding) => binding.getSubjectNames(), [columnId.bindings]: binding => binding.getSubjectNames(),
[columnId.age]: (binding: RoleBinding) => binding.getTimeDiffFromNow(), [columnId.age]: binding => binding.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(binding: RoleBinding) => binding.getSearchFields(), binding => binding.getSearchFields(),
(binding: RoleBinding) => binding.getSubjectNames(), binding => binding.getSubjectNames(),
]} ]}
renderHeaderTitle="Role Bindings" renderHeaderTitle="Role Bindings"
renderTableHeader={[ renderTableHeader={[
@ -68,7 +67,7 @@ export class RoleBindings extends React.Component<Props> {
{ title: "Bindings", className: "bindings", sortBy: columnId.bindings, id: columnId.bindings }, { title: "Bindings", className: "bindings", sortBy: columnId.bindings, id: columnId.bindings },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(binding: RoleBinding) => [ renderTableContents={binding => [
binding.getName(), binding.getName(),
<KubeObjectStatusIcon key="icon" object={binding} />, <KubeObjectStatusIcon key="icon" object={binding} />,
binding.getNs() || "-", binding.getNs() || "-",

View File

@ -49,7 +49,7 @@ export class RolesStore extends KubeObjectStore<Role> {
return clusterRoleApi.get(params); return clusterRoleApi.get(params);
} }
protected async loadItems(params: KubeObjectStoreLoadingParams): Promise<Role[]> { protected async loadItems(params: KubeObjectStoreLoadingParams<Role>): Promise<Role[]> {
const items = await Promise.all([ const items = await Promise.all([
super.loadItems({ ...params, api: clusterRoleApi }), super.loadItems({ ...params, api: clusterRoleApi }),
super.loadItems({ ...params, api: roleApi }), super.loadItems({ ...params, api: roleApi }),

View File

@ -26,7 +26,6 @@ import { observer } from "mobx-react";
import type { RouteComponentProps } from "react-router"; import type { RouteComponentProps } from "react-router";
import type { IRolesRouteParams } from "../+user-management/user-management.route"; import type { IRolesRouteParams } from "../+user-management/user-management.route";
import { rolesStore } from "./roles.store"; import { rolesStore } from "./roles.store";
import type { Role } from "../../api/endpoints";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { AddRoleDialog } from "./add-role-dialog"; import { AddRoleDialog } from "./add-role-dialog";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
@ -51,12 +50,12 @@ export class Roles extends React.Component<Props> {
className="Roles" className="Roles"
store={rolesStore} store={rolesStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (role: Role) => role.getName(), [columnId.name]: role => role.getName(),
[columnId.namespace]: (role: Role) => role.getNs(), [columnId.namespace]: role => role.getNs(),
[columnId.age]: (role: Role) => role.getTimeDiffFromNow(), [columnId.age]: role => role.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(role: Role) => role.getSearchFields(), role => role.getSearchFields(),
]} ]}
renderHeaderTitle="Roles" renderHeaderTitle="Roles"
renderTableHeader={[ renderTableHeader={[
@ -65,7 +64,7 @@ export class Roles extends React.Component<Props> {
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(role: Role) => [ renderTableContents={role => [
role.getName(), role.getName(),
<KubeObjectStatusIcon key="icon" object={role} />, <KubeObjectStatusIcon key="icon" object={role} />,
role.getNs() || "-", role.getNs() || "-",

View File

@ -55,12 +55,12 @@ export class ServiceAccounts extends React.Component<Props> {
tableId="access_service_accounts" tableId="access_service_accounts"
className="ServiceAccounts" store={serviceAccountsStore} className="ServiceAccounts" store={serviceAccountsStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (account: ServiceAccount) => account.getName(), [columnId.name]: account => account.getName(),
[columnId.namespace]: (account: ServiceAccount) => account.getNs(), [columnId.namespace]: account => account.getNs(),
[columnId.age]: (account: ServiceAccount) => account.getTimeDiffFromNow(), [columnId.age]: account => account.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(account: ServiceAccount) => account.getSearchFields(), account => account.getSearchFields(),
]} ]}
renderHeaderTitle="Service Accounts" renderHeaderTitle="Service Accounts"
renderTableHeader={[ renderTableHeader={[
@ -69,7 +69,7 @@ export class ServiceAccounts extends React.Component<Props> {
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(account: ServiceAccount) => [ renderTableContents={account => [
account.getName(), account.getName(),
<KubeObjectStatusIcon key="icon" object={account} />, <KubeObjectStatusIcon key="icon" object={account} />,
account.getNs(), account.getNs(),

View File

@ -62,16 +62,16 @@ export class CronJobs extends React.Component<Props> {
className="CronJobs" store={cronJobStore} className="CronJobs" store={cronJobStore}
dependentStores={[jobStore, eventStore]} dependentStores={[jobStore, eventStore]}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (cronJob: CronJob) => cronJob.getName(), [columnId.name]: cronJob => cronJob.getName(),
[columnId.namespace]: (cronJob: CronJob) => cronJob.getNs(), [columnId.namespace]: cronJob => cronJob.getNs(),
[columnId.suspend]: (cronJob: CronJob) => cronJob.getSuspendFlag(), [columnId.suspend]: cronJob => cronJob.getSuspendFlag(),
[columnId.active]: (cronJob: CronJob) => cronJobStore.getActiveJobsNum(cronJob), [columnId.active]: cronJob => cronJobStore.getActiveJobsNum(cronJob),
[columnId.lastSchedule]: (cronJob: CronJob) => cronJob.getLastScheduleTime(), [columnId.lastSchedule]: cronJob => cronJob.getLastScheduleTime(),
[columnId.age]: (cronJob: CronJob) => cronJob.getTimeDiffFromNow(), [columnId.age]: cronJob => cronJob.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(cronJob: CronJob) => cronJob.getSearchFields(), cronJob => cronJob.getSearchFields(),
(cronJob: CronJob) => cronJob.getSchedule(), cronJob => cronJob.getSchedule(),
]} ]}
renderHeaderTitle="Cron Jobs" renderHeaderTitle="Cron Jobs"
renderTableHeader={[ renderTableHeader={[
@ -84,7 +84,7 @@ export class CronJobs extends React.Component<Props> {
{ title: "Last schedule", className: "last-schedule", sortBy: columnId.lastSchedule, id: columnId.lastSchedule }, { title: "Last schedule", className: "last-schedule", sortBy: columnId.lastSchedule, id: columnId.lastSchedule },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(cronJob: CronJob) => [ renderTableContents={cronJob => [
cronJob.getName(), cronJob.getName(),
<KubeObjectStatusIcon key="icon" object={cronJob} />, <KubeObjectStatusIcon key="icon" object={cronJob} />,
cronJob.getNs(), cronJob.getNs(),

View File

@ -65,14 +65,14 @@ export class DaemonSets extends React.Component<Props> {
className="DaemonSets" store={daemonSetStore} className="DaemonSets" store={daemonSetStore}
dependentStores={[podsStore, nodesStore, eventStore]} dependentStores={[podsStore, nodesStore, eventStore]}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (daemonSet: DaemonSet) => daemonSet.getName(), [columnId.name]: daemonSet => daemonSet.getName(),
[columnId.namespace]: (daemonSet: DaemonSet) => daemonSet.getNs(), [columnId.namespace]: daemonSet => daemonSet.getNs(),
[columnId.pods]: (daemonSet: DaemonSet) => this.getPodsLength(daemonSet), [columnId.pods]: daemonSet => this.getPodsLength(daemonSet),
[columnId.age]: (daemonSet: DaemonSet) => daemonSet.getTimeDiffFromNow(), [columnId.age]: daemonSet => daemonSet.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(daemonSet: DaemonSet) => daemonSet.getSearchFields(), daemonSet => daemonSet.getSearchFields(),
(daemonSet: DaemonSet) => daemonSet.getLabels(), daemonSet => daemonSet.getLabels(),
]} ]}
renderHeaderTitle="Daemon Sets" renderHeaderTitle="Daemon Sets"
renderTableHeader={[ renderTableHeader={[
@ -83,7 +83,7 @@ export class DaemonSets extends React.Component<Props> {
{ title: "Node Selector", className: "labels", id: columnId.labels }, { title: "Node Selector", className: "labels", id: columnId.labels },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(daemonSet: DaemonSet) => [ renderTableContents={daemonSet => [
daemonSet.getName(), daemonSet.getName(),
daemonSet.getNs(), daemonSet.getNs(),
this.getPodsLength(daemonSet), this.getPodsLength(daemonSet),

View File

@ -82,15 +82,15 @@ export class Deployments extends React.Component<Props> {
className="Deployments" store={deploymentStore} className="Deployments" store={deploymentStore}
dependentStores={[replicaSetStore, podsStore, nodesStore, eventStore]} dependentStores={[replicaSetStore, podsStore, nodesStore, eventStore]}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (deployment: Deployment) => deployment.getName(), [columnId.name]: deployment => deployment.getName(),
[columnId.namespace]: (deployment: Deployment) => deployment.getNs(), [columnId.namespace]: deployment => deployment.getNs(),
[columnId.replicas]: (deployment: Deployment) => deployment.getReplicas(), [columnId.replicas]: deployment => deployment.getReplicas(),
[columnId.age]: (deployment: Deployment) => deployment.getTimeDiffFromNow(), [columnId.age]: deployment => deployment.getTimeDiffFromNow(),
[columnId.condition]: (deployment: Deployment) => deployment.getConditionsText(), [columnId.condition]: deployment => deployment.getConditionsText(),
}} }}
searchFilters={[ searchFilters={[
(deployment: Deployment) => deployment.getSearchFields(), deployment => deployment.getSearchFields(),
(deployment: Deployment) => deployment.getConditionsText(), deployment => deployment.getConditionsText(),
]} ]}
renderHeaderTitle="Deployments" renderHeaderTitle="Deployments"
renderTableHeader={[ renderTableHeader={[
@ -102,7 +102,7 @@ export class Deployments extends React.Component<Props> {
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Conditions", className: "conditions", sortBy: columnId.condition, id: columnId.condition }, { title: "Conditions", className: "conditions", sortBy: columnId.condition, id: columnId.condition },
]} ]}
renderTableContents={(deployment: Deployment) => [ renderTableContents={deployment => [
deployment.getName(), deployment.getName(),
<KubeObjectStatusIcon key="icon" object={deployment}/>, <KubeObjectStatusIcon key="icon" object={deployment}/>,
deployment.getNs(), deployment.getNs(),
@ -111,9 +111,7 @@ export class Deployments extends React.Component<Props> {
deployment.getAge(), deployment.getAge(),
this.renderConditions(deployment), this.renderConditions(deployment),
]} ]}
renderItemMenu={(item: Deployment) => { renderItemMenu={item => <DeploymentMenu object={item} />}
return <DeploymentMenu object={item}/>;
}}
/> />
); );
} }

View File

@ -27,7 +27,6 @@ import type { RouteComponentProps } from "react-router";
import { podsStore } from "../+workloads-pods/pods.store"; import { podsStore } from "../+workloads-pods/pods.store";
import { jobStore } from "./job.store"; import { jobStore } from "./job.store";
import { eventStore } from "../+events/event.store"; import { eventStore } from "../+events/event.store";
import type { Job } from "../../api/endpoints/job.api";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import type { IJobsRouteParams } from "../+workloads"; import type { IJobsRouteParams } from "../+workloads";
import kebabCase from "lodash/kebabCase"; import kebabCase from "lodash/kebabCase";
@ -54,13 +53,13 @@ export class Jobs extends React.Component<Props> {
className="Jobs" store={jobStore} className="Jobs" store={jobStore}
dependentStores={[podsStore, eventStore]} dependentStores={[podsStore, eventStore]}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (job: Job) => job.getName(), [columnId.name]: job => job.getName(),
[columnId.namespace]: (job: Job) => job.getNs(), [columnId.namespace]: job => job.getNs(),
[columnId.conditions]: (job: Job) => job.getCondition() != null ? job.getCondition().type : "", [columnId.conditions]: job => job.getCondition() != null ? job.getCondition().type : "",
[columnId.age]: (job: Job) => job.getTimeDiffFromNow(), [columnId.age]: job => job.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(job: Job) => job.getSearchFields(), job => job.getSearchFields(),
]} ]}
renderHeaderTitle="Jobs" renderHeaderTitle="Jobs"
renderTableHeader={[ renderTableHeader={[
@ -71,7 +70,7 @@ export class Jobs extends React.Component<Props> {
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Conditions", className: "conditions", sortBy: columnId.conditions, id: columnId.conditions }, { title: "Conditions", className: "conditions", sortBy: columnId.conditions, id: columnId.conditions },
]} ]}
renderTableContents={(job: Job) => { renderTableContents={job => {
const condition = job.getCondition(); const condition = job.getCondition();
return [ return [

View File

@ -46,7 +46,7 @@ const resources: KubeResource[] = [
export class OverviewStatuses extends React.Component { export class OverviewStatuses extends React.Component {
@boundMethod @boundMethod
renderWorkload(resource: KubeResource): React.ReactElement { renderWorkload(resource: KubeResource): React.ReactElement {
const store = workloadStores[resource]; const store = workloadStores.get(resource);
const items = store.getAllByNs(namespaceStore.contextNamespaces); const items = store.getAllByNs(namespaceStore.contextNamespaces);
return ( return (

View File

@ -37,13 +37,6 @@ enum sortBy {
Seconds = "seconds", Seconds = "seconds",
} }
const sortingCallbacks = {
[sortBy.Key]: (toleration: IToleration) => toleration.key,
[sortBy.Operator]: (toleration: IToleration) => toleration.operator,
[sortBy.Effect]: (toleration: IToleration) => toleration.effect,
[sortBy.Seconds]: (toleration: IToleration) => toleration.tolerationSeconds,
};
const getTableRow = (toleration: IToleration) => { const getTableRow = (toleration: IToleration) => {
const { key, operator, effect, tolerationSeconds } = toleration; const { key, operator, effect, tolerationSeconds } = toleration;
@ -66,10 +59,17 @@ export function PodTolerations({ tolerations }: Props) {
<Table <Table
tableId="workloads_pod_tolerations" tableId="workloads_pod_tolerations"
selectable selectable
items={tolerations}
scrollable={false} scrollable={false}
sortable={sortingCallbacks} sortable={{
[sortBy.Key]: toleration => toleration.key,
[sortBy.Operator]: toleration => toleration.operator,
[sortBy.Effect]: toleration => toleration.effect,
[sortBy.Seconds]: toleration => toleration.tolerationSeconds,
}}
sortSyncWithUrl={false} sortSyncWithUrl={false}
className="PodTolerations" className="PodTolerations"
renderRow={getTableRow}
> >
<TableHead sticky={false}> <TableHead sticky={false}>
<TableCell className="key" sortBy={sortBy.Key}>Key</TableCell> <TableCell className="key" sortBy={sortBy.Key}>Key</TableCell>
@ -77,9 +77,6 @@ export function PodTolerations({ tolerations }: Props) {
<TableCell className="effect" sortBy={sortBy.Effect}>Effect</TableCell> <TableCell className="effect" sortBy={sortBy.Effect}>Effect</TableCell>
<TableCell className="seconds" sortBy={sortBy.Seconds}>Seconds</TableCell> <TableCell className="seconds" sortBy={sortBy.Seconds}>Seconds</TableCell>
</TableHead> </TableHead>
{
tolerations.map(getTableRow)
}
</Table> </Table>
); );
} }

View File

@ -97,21 +97,21 @@ export class Pods extends React.Component<Props> {
tableId = "workloads_pods" tableId = "workloads_pods"
isConfigurable isConfigurable
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (pod: Pod) => pod.getName(), [columnId.name]: pod => pod.getName(),
[columnId.namespace]: (pod: Pod) => pod.getNs(), [columnId.namespace]: pod => pod.getNs(),
[columnId.containers]: (pod: Pod) => pod.getContainers().length, [columnId.containers]: pod => pod.getContainers().length,
[columnId.restarts]: (pod: Pod) => pod.getRestartsCount(), [columnId.restarts]: pod => pod.getRestartsCount(),
[columnId.owners]: (pod: Pod) => pod.getOwnerRefs().map(ref => ref.kind), [columnId.owners]: pod => pod.getOwnerRefs().map(ref => ref.kind),
[columnId.qos]: (pod: Pod) => pod.getQosClass(), [columnId.qos]: pod => pod.getQosClass(),
[columnId.node]: (pod: Pod) => pod.getNodeName(), [columnId.node]: pod => pod.getNodeName(),
[columnId.age]: (pod: Pod) => pod.getTimeDiffFromNow(), [columnId.age]: pod => pod.getTimeDiffFromNow(),
[columnId.status]: (pod: Pod) => pod.getStatusMessage(), [columnId.status]: pod => pod.getStatusMessage(),
}} }}
searchFilters={[ searchFilters={[
(pod: Pod) => pod.getSearchFields(), pod => pod.getSearchFields(),
(pod: Pod) => pod.getStatusMessage(), pod => pod.getStatusMessage(),
(pod: Pod) => pod.status.podIP, pod => pod.status.podIP,
(pod: Pod) => pod.getNodeName(), pod => pod.getNodeName(),
]} ]}
renderHeaderTitle="Pods" renderHeaderTitle="Pods"
renderTableHeader={[ renderTableHeader={[
@ -126,7 +126,7 @@ export class Pods extends React.Component<Props> {
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]} ]}
renderTableContents={(pod: Pod) => [ renderTableContents={pod => [
<Badge flat key="name" label={pod.getName()} tooltip={pod.getName()} />, <Badge flat key="name" label={pod.getName()} tooltip={pod.getName()} />,
<KubeObjectStatusIcon key="icon" object={pod} />, <KubeObjectStatusIcon key="icon" object={pod} />,
pod.getNs(), pod.getNs(),

View File

@ -56,15 +56,15 @@ export class ReplicaSets extends React.Component<Props> {
tableId="workload_replicasets" tableId="workload_replicasets"
className="ReplicaSets" store={replicaSetStore} className="ReplicaSets" store={replicaSetStore}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (replicaSet: ReplicaSet) => replicaSet.getName(), [columnId.name]: replicaSet => replicaSet.getName(),
[columnId.namespace]: (replicaSet: ReplicaSet) => replicaSet.getNs(), [columnId.namespace]: replicaSet => replicaSet.getNs(),
[columnId.desired]: (replicaSet: ReplicaSet) => replicaSet.getDesired(), [columnId.desired]: replicaSet => replicaSet.getDesired(),
[columnId.current]: (replicaSet: ReplicaSet) => replicaSet.getCurrent(), [columnId.current]: replicaSet => replicaSet.getCurrent(),
[columnId.ready]: (replicaSet: ReplicaSet) => replicaSet.getReady(), [columnId.ready]: replicaSet => replicaSet.getReady(),
[columnId.age]: (replicaSet: ReplicaSet) => replicaSet.getTimeDiffFromNow(), [columnId.age]: replicaSet => replicaSet.getTimeDiffFromNow(),
}} }}
searchFilters={[ searchFilters={[
(replicaSet: ReplicaSet) => replicaSet.getSearchFields(), replicaSet => replicaSet.getSearchFields(),
]} ]}
renderHeaderTitle="Replica Sets" renderHeaderTitle="Replica Sets"
renderTableHeader={[ renderTableHeader={[
@ -76,7 +76,7 @@ export class ReplicaSets extends React.Component<Props> {
{ title: "Ready", className: "ready", sortBy: columnId.ready, id: columnId.ready }, { title: "Ready", className: "ready", sortBy: columnId.ready, id: columnId.ready },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(replicaSet: ReplicaSet) => [ renderTableContents={replicaSet => [
replicaSet.getName(), replicaSet.getName(),
<KubeObjectStatusIcon key="icon" object={replicaSet}/>, <KubeObjectStatusIcon key="icon" object={replicaSet}/>,
replicaSet.getNs(), replicaSet.getNs(),

View File

@ -65,13 +65,13 @@ export class StatefulSets extends React.Component<Props> {
className="StatefulSets" store={statefulSetStore} className="StatefulSets" store={statefulSetStore}
dependentStores={[podsStore, nodesStore, eventStore]} dependentStores={[podsStore, nodesStore, eventStore]}
sortingCallbacks={{ sortingCallbacks={{
[columnId.name]: (statefulSet: StatefulSet) => statefulSet.getName(), [columnId.name]: statefulSet => statefulSet.getName(),
[columnId.namespace]: (statefulSet: StatefulSet) => statefulSet.getNs(), [columnId.namespace]: statefulSet => statefulSet.getNs(),
[columnId.age]: (statefulSet: StatefulSet) => statefulSet.getTimeDiffFromNow(), [columnId.age]: statefulSet => statefulSet.getTimeDiffFromNow(),
[columnId.replicas]: (statefulSet: StatefulSet) => statefulSet.getReplicas(), [columnId.replicas]: statefulSet => statefulSet.getReplicas(),
}} }}
searchFilters={[ searchFilters={[
(statefulSet: StatefulSet) => statefulSet.getSearchFields(), statefulSet => statefulSet.getSearchFields(),
]} ]}
renderHeaderTitle="Stateful Sets" renderHeaderTitle="Stateful Sets"
renderTableHeader={[ renderTableHeader={[
@ -82,7 +82,7 @@ export class StatefulSets extends React.Component<Props> {
{ className: "warning", showWithColumn: columnId.replicas }, { className: "warning", showWithColumn: columnId.replicas },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(statefulSet: StatefulSet) => [ renderTableContents={statefulSet => [
statefulSet.getName(), statefulSet.getName(),
statefulSet.getNs(), statefulSet.getNs(),
this.renderPods(statefulSet), this.renderPods(statefulSet),
@ -90,9 +90,7 @@ export class StatefulSets extends React.Component<Props> {
<KubeObjectStatusIcon key="icon" object={statefulSet}/>, <KubeObjectStatusIcon key="icon" object={statefulSet}/>,
statefulSet.getAge(), statefulSet.getAge(),
]} ]}
renderItemMenu={(item: StatefulSet) => { renderItemMenu={item => <StatefulSetMenu object={item} />}
return <StatefulSetMenu object={item}/>;
}}
/> />
); );
} }

View File

@ -28,13 +28,14 @@ import { jobStore } from "../+workloads-jobs/job.store";
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
import type { KubeResource } from "../../../common/rbac"; import type { KubeResource } from "../../../common/rbac";
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
import type { KubeObject } from "../../api/kube-object";
export const workloadStores: Partial<Record<KubeResource, KubeObjectStore>> = { export const workloadStores = new Map<KubeResource, KubeObjectStore<KubeObject>>([
"pods": podsStore, ["pods", podsStore],
"deployments": deploymentStore, ["deployments", deploymentStore],
"daemonsets": daemonSetStore, ["daemonsets", daemonSetStore],
"statefulsets": statefulSetStore, ["statefulsets", statefulSetStore],
"replicasets": replicaSetStore, ["replicasets", replicaSetStore],
"jobs": jobStore, ["jobs", jobStore],
"cronjobs": cronJobStore, ["cronjobs", cronJobStore],
}; ]);

View File

@ -84,7 +84,7 @@ export class EditResourceStore extends DockTabStore<EditingResource> {
return Boolean(tabDataReady && this.getResource(tabId)); // ready to edit resource return Boolean(tabDataReady && this.getResource(tabId)); // ready to edit resource
} }
getStore(tabId: TabId): KubeObjectStore | undefined { getStore(tabId: TabId): KubeObjectStore<KubeObject> | undefined {
return apiManager.getStore(this.getResourcePath(tabId)); return apiManager.getStore(this.getResourcePath(tabId));
} }

View File

@ -26,7 +26,7 @@ import React, { ReactNode } from "react";
import { computed, makeObservable } from "mobx"; import { computed, makeObservable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { ConfirmDialog, ConfirmDialogParams } from "../confirm-dialog"; import { ConfirmDialog, ConfirmDialogParams } from "../confirm-dialog";
import { Table, TableCell, TableCellProps, TableHead, TableProps, TableRow, TableRowProps, TableSortCallback } from "../table"; import { Table, TableCell, TableCellProps, TableHead, TableProps, TableRow, TableRowProps, TableSortCallbacks } from "../table";
import { boundMethod, createStorage, cssNames, IClassName, isReactNode, noop, ObservableToggleSet, prevDefault, stopPropagation } from "../../utils"; import { boundMethod, createStorage, cssNames, IClassName, isReactNode, noop, ObservableToggleSet, prevDefault, stopPropagation } from "../../utils";
import { AddRemoveButtons, AddRemoveButtonsProps } from "../add-remove-buttons"; import { AddRemoveButtons, AddRemoveButtonsProps } from "../add-remove-buttons";
import { NoItems } from "../no-items"; import { NoItems } from "../no-items";
@ -46,8 +46,10 @@ import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
// todo: refactor, split to small re-usable components // todo: refactor, split to small re-usable components
export type SearchFilter<T extends ItemObject = any> = (item: T) => string | number | (string | number)[]; export type SearchFilter<Item extends ItemObject> = (item: Item) => string | number | (string | number)[];
export type ItemsFilter<T extends ItemObject = any> = (items: T[]) => T[]; export type SearchFilters<Item extends ItemObject> = Record<string, SearchFilter<Item>>;
export type ItemsFilter<Item extends ItemObject> = (items: Item[]) => Item[];
export type ItemsFilters<Item extends ItemObject> = Record<string, ItemsFilter<Item>>;
export interface IHeaderPlaceholders { export interface IHeaderPlaceholders {
title: ReactNode; title: ReactNode;
@ -56,22 +58,22 @@ export interface IHeaderPlaceholders {
info: ReactNode; info: ReactNode;
} }
export interface ItemListLayoutProps<T extends ItemObject = ItemObject> { export interface ItemListLayoutProps<Item extends ItemObject> {
tableId?: string; tableId?: string;
className: IClassName; className: IClassName;
items?: T[]; items?: Item[];
store: ItemStore<T>; store: ItemStore<Item>;
dependentStores?: ItemStore[]; dependentStores?: ItemStore<ItemObject>[];
preloadStores?: boolean; preloadStores?: boolean;
hideFilters?: boolean; hideFilters?: boolean;
searchFilters?: SearchFilter<T>[]; searchFilters?: SearchFilter<Item>[];
/** @deprecated */ /** @deprecated */
filterItems?: ItemsFilter<T>[]; filterItems?: ItemsFilter<Item>[];
// 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<Item>) => ReactNode);
customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode; customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode;
// items list configuration // items list configuration
@ -80,26 +82,28 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
isSearchable?: boolean; // apply search-filter & add search-input isSearchable?: boolean; // apply search-filter & add search-input
isConfigurable?: boolean; isConfigurable?: boolean;
copyClassNameFromHeadCells?: boolean; copyClassNameFromHeadCells?: boolean;
sortingCallbacks?: { [sortBy: string]: TableSortCallback }; sortingCallbacks?: TableSortCallbacks<Item>;
tableProps?: Partial<TableProps>; // low-level table configuration tableProps?: Partial<TableProps<Item>>; // low-level table configuration
renderTableHeader: TableCellProps[] | null; renderTableHeader: TableCellProps[] | null;
renderTableContents: (item: T) => (ReactNode | TableCellProps)[]; renderTableContents: (item: Item) => (ReactNode | TableCellProps)[];
renderItemMenu?: (item: T, store: ItemStore<T>) => ReactNode; renderItemMenu?: (item: Item, store: ItemStore<Item>) => ReactNode;
customizeTableRowProps?: (item: T) => Partial<TableRowProps>; customizeTableRowProps?: (item: Item) => Partial<TableRowProps>;
addRemoveButtons?: Partial<AddRemoveButtonsProps>; addRemoveButtons?: Partial<AddRemoveButtonsProps>;
virtual?: boolean; virtual?: boolean;
// item details view // item details view
hasDetailsView?: boolean; hasDetailsView?: boolean;
detailsItem?: T; detailsItem?: Item;
onDetails?: (item: T) => void; onDetails?: (item: Item) => void;
// other // other
customizeRemoveDialog?: (selectedItems: T[]) => Partial<ConfirmDialogParams>; customizeRemoveDialog?: (selectedItems: Item[]) => Partial<ConfirmDialogParams>;
renderFooter?: (parent: ItemListLayout) => React.ReactNode; renderFooter?: (parent: ItemListLayout<Item>) => React.ReactNode;
filterCallbacks?: ItemsFilters<Item>;
} }
const defaultProps: Partial<ItemListLayoutProps> = { const defaultProps: Partial<ItemListLayoutProps<ItemObject>> = {
showHeader: true, showHeader: true,
isSearchable: true, isSearchable: true,
isSelectable: true, isSelectable: true,
@ -115,14 +119,14 @@ const defaultProps: Partial<ItemListLayoutProps> = {
}; };
@observer @observer
export class ItemListLayout extends React.Component<ItemListLayoutProps> { export class ItemListLayout<Item extends ItemObject> extends React.Component<ItemListLayoutProps<Item>> {
static defaultProps = defaultProps as object; static defaultProps = defaultProps as object;
private storage = createStorage("item_list_layout", { private storage = createStorage("item_list_layout", {
showFilters: false, // setup defaults showFilters: false, // setup defaults
}); });
constructor(props: ItemListLayoutProps) { constructor(props: ItemListLayoutProps<Item>) {
super(props); super(props);
makeObservable(this); makeObservable(this);
} }
@ -158,7 +162,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
stores.forEach(store => store.loadAll(namespaceStore.contextNamespaces)); stores.forEach(store => store.loadAll(namespaceStore.contextNamespaces));
} }
private filterCallbacks: { [type: string]: ItemsFilter } = { private filterCallbacks: ItemsFilters<Item> = {
[FilterType.SEARCH]: items => { [FilterType.SEARCH]: items => {
const { searchFilters, isSearchable } = this.props; const { searchFilters, isSearchable } = this.props;
const search = pageFilters.getValues(FilterType.SEARCH)[0] || ""; const search = pageFilters.getValues(FilterType.SEARCH)[0] || "";
@ -199,20 +203,20 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
return activeFilters; return activeFilters;
} }
applyFilters<T>(filters: ItemsFilter[], items: T[]): T[] { applyFilters(filters: ItemsFilter<Item>[], items: Item[]): Item[] {
if (!filters || !filters.length) return items; if (!filters || !filters.length) return items;
return filters.reduce((items, filter) => filter(items), items); return filters.reduce((items, filter) => filter(items), items);
} }
@computed get items() { @computed get items() {
const { filters, filterCallbacks } = this; const { filters, filterCallbacks, props } = this;
const filterGroups = groupBy<Filter>(filters, ({ type }) => type); const filterGroups = groupBy<Filter>(filters, ({ type }) => type);
const filterItems: ItemsFilter[] = []; const filterItems: ItemsFilter<Item>[] = [];
Object.entries(filterGroups).forEach(([type, filtersGroup]) => { Object.entries(filterGroups).forEach(([type, filtersGroup]) => {
const filterCallback = filterCallbacks[type]; const filterCallback = filterCallbacks[type] ?? props.filterCallbacks?.[type];
if (filterCallback && filtersGroup.length > 0) { if (filterCallback && filtersGroup.length > 0) {
filterItems.push(filterCallback); filterItems.push(filterCallback);

View File

@ -33,6 +33,7 @@ import { crdStore } from "../+custom-resources/crd.store";
import { CrdResourceDetails } from "../+custom-resources"; import { CrdResourceDetails } from "../+custom-resources";
import { KubeObjectMenu } from "./kube-object-menu"; import { KubeObjectMenu } from "./kube-object-menu";
import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry";
import { CustomResourceDefinition } from "../../api/endpoints";
/** /**
* Used to store `object.selfLink` to show more info about resource in the details panel. * Used to store `object.selfLink` to show more info about resource in the details panel.
@ -99,15 +100,7 @@ export class KubeObjectDetails extends React.Component {
} }
@computed get object() { @computed get object() {
const store = apiManager.getStore(this.path); return apiManager.getStore(this.path)?.getByPath(this.path);
if (store) {
return store.getByPath(this.path);
}
}
@computed get isCrdInstance() {
return !!crdStore.getByObject(this.object);
} }
@disposeOnUnmount @disposeOnUnmount
@ -137,7 +130,7 @@ export class KubeObjectDetails extends React.Component {
}); });
render() { render() {
const { object, isLoading, loadingError, isCrdInstance } = this; const { object, isLoading, loadingError } = this;
const isOpen = !!(object || isLoading || loadingError); const isOpen = !!(object || isLoading || loadingError);
let title = ""; let title = "";
let details: React.ReactNode[]; let details: React.ReactNode[];
@ -150,7 +143,7 @@ export class KubeObjectDetails extends React.Component {
return <item.components.Details object={object} key={`object-details-${index}`}/>; return <item.components.Details object={object} key={`object-details-${index}`}/>;
}); });
if (isCrdInstance && details.length === 0) { if (object instanceof CustomResourceDefinition && details.length === 0) {
details.push(<CrdResourceDetails object={object}/>); details.push(<CrdResourceDetails object={object}/>);
} }
} }

View File

@ -30,21 +30,22 @@ import { KubeObjectMenu } from "./kube-object-menu";
import { kubeSelectedUrlParam, showDetails } from "./kube-object-details"; import { kubeSelectedUrlParam, showDetails } from "./kube-object-details";
import { kubeWatchApi } from "../../api/kube-watch-api"; import { kubeWatchApi } from "../../api/kube-watch-api";
import { clusterContext } from "../context"; import { clusterContext } from "../context";
import { FilterType, pageFilters } from "../item-object-list/page-filters.store";
export interface KubeObjectListLayoutProps extends ItemListLayoutProps { export interface KubeObjectListLayoutProps<K extends KubeObject> extends ItemListLayoutProps<K> {
store: KubeObjectStore; store: KubeObjectStore<K>;
dependentStores?: KubeObjectStore[]; dependentStores?: KubeObjectStore<KubeObject>[];
} }
const defaultProps: Partial<KubeObjectListLayoutProps> = { const defaultProps: Partial<KubeObjectListLayoutProps<KubeObject>> = {
onDetails: (item: KubeObject) => showDetails(item.selfLink), onDetails: (item: KubeObject) => showDetails(item.selfLink),
}; };
@observer @observer
export class KubeObjectListLayout extends React.Component<KubeObjectListLayoutProps> { export class KubeObjectListLayout<K extends KubeObject> extends React.Component<KubeObjectListLayoutProps<K>> {
static defaultProps = defaultProps as object; static defaultProps = defaultProps as object;
constructor(props: KubeObjectListLayoutProps) { constructor(props: KubeObjectListLayoutProps<K>) {
super(props); super(props);
makeObservable(this); makeObservable(this);
} }
@ -76,7 +77,18 @@ export class KubeObjectListLayout extends React.Component<KubeObjectListLayoutPr
items={items} items={items}
preloadStores={false} // loading handled in kubeWatchApi.subscribeStores() preloadStores={false} // loading handled in kubeWatchApi.subscribeStores()
detailsItem={this.selectedItem} detailsItem={this.selectedItem}
renderItemMenu={(item: KubeObject) => <KubeObjectMenu object={item} />} // safe because we are dealing with KubeObjects here renderItemMenu={(item: K) => <KubeObjectMenu object={item} />} // safe because we are dealing with KubeObjects here
filterCallbacks={{
[FilterType.NAMESPACE]: items => {
const filterValues = pageFilters.getValues(FilterType.NAMESPACE);
if (filterValues.length > 0) {
return items.filter(item => filterValues.includes(item.getNs()));
}
return items;
},
}}
/> />
); );
} }

View File

@ -30,19 +30,18 @@ import { TableHead, TableHeadElem, TableHeadProps } from "./table-head";
import type { TableCellElem } from "./table-cell"; import type { TableCellElem } from "./table-cell";
import { VirtualList } from "../virtual-list"; import { VirtualList } from "../virtual-list";
import { createPageParam } from "../../navigation"; import { createPageParam } from "../../navigation";
import type { ItemObject } from "../../item.store";
import { getSortParams, setSortParams } from "./table.storage"; import { getSortParams, setSortParams } from "./table.storage";
import { computed, makeObservable } from "mobx"; import { computed, makeObservable } from "mobx";
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<Item> = (data: Item) => string | number | (string | number)[];
export type TableSortCallbacks = { [columnId: string]: TableSortCallback }; export type TableSortCallbacks<Item> = Record<string, TableSortCallback<Item>>;
export interface TableProps extends React.DOMAttributes<HTMLDivElement> { export interface TableProps<Item> extends React.DOMAttributes<HTMLDivElement> {
tableId?: string; tableId?: string;
items?: ItemObject[]; // Raw items data items?: Item[]; // 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)
selectable?: boolean; // Highlight rows on hover selectable?: boolean; // Highlight rows on hover
@ -52,7 +51,7 @@ export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
* 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}/>
*/ */
sortable?: TableSortCallbacks; sortable?: TableSortCallbacks<Item>;
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
onSort?: (params: TableSortParams) => void; // callback on sort change, default: global sync with url onSort?: (params: TableSortParams) => void; // callback on sort change, default: global sync with url
@ -61,8 +60,9 @@ export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
virtual?: boolean; // Use virtual list component to render only visible rows virtual?: boolean; // Use virtual list component to render only visible rows
rowPadding?: string; rowPadding?: string;
rowLineHeight?: string; rowLineHeight?: string;
customRowHeights?: (item: object, lineHeight: number, paddings: number) => number; customRowHeights?: (item: Item, lineHeight: number, paddings: number) => number;
getTableRow?: (uid: string) => React.ReactElement<TableRowProps>; getTableRow?: (uid: string) => React.ReactElement<TableRowProps>;
renderRow?: (item: Item) => React.ReactElement<TableRowProps>;
} }
export const sortByUrlParam = createPageParam({ export const sortByUrlParam = createPageParam({
@ -74,8 +74,8 @@ export const orderByUrlParam = createPageParam({
}); });
@observer @observer
export class Table extends React.Component<TableProps> { export class Table<Item> extends React.Component<TableProps<Item>> {
static defaultProps: TableProps = { static defaultProps: TableProps<any> = {
scrollable: true, scrollable: true,
autoSize: true, autoSize: true,
rowPadding: "8px", rowPadding: "8px",
@ -83,7 +83,7 @@ export class Table extends React.Component<TableProps> {
sortSyncWithUrl: true, sortSyncWithUrl: true,
}; };
constructor(props: TableProps) { constructor(props: TableProps<Item>) {
super(props); super(props);
makeObservable(this); makeObservable(this);
} }
@ -171,9 +171,20 @@ export class Table extends React.Component<TableProps> {
}); });
} }
renderRows() { getContent() {
const { sortable, noItems, children, virtual, customRowHeights, rowLineHeight, rowPadding, items, getTableRow, selectedItemId, className } = this.props; const { items, renderRow, children } = this.props;
const content = React.Children.toArray(children) as (TableRowElem | TableHeadElem)[]; const content = React.Children.toArray(children) as (TableRowElem | TableHeadElem)[];
if (renderRow) {
content.push(...items.map(renderRow));
}
return content;
}
renderRows() {
const { sortable, noItems, virtual, customRowHeights, rowLineHeight, rowPadding, items, getTableRow, selectedItemId, className } = this.props;
const content = this.getContent();
let rows: React.ReactElement<TableRowProps>[] = content.filter(elem => elem.type === TableRow); let rows: React.ReactElement<TableRowProps>[] = content.filter(elem => elem.type === TableRow);
let sortedItems = rows.length ? rows.map(row => row.props.sortItem) : [...items]; let sortedItems = rows.length ? rows.map(row => row.props.sortItem) : [...items];

View File

@ -28,15 +28,15 @@ export interface ItemObject {
getName(): string; getName(): string;
} }
export abstract class ItemStore<T extends ItemObject = ItemObject> { export abstract class ItemStore<Item extends ItemObject> {
abstract loadAll(...args: any[]): Promise<void | T[]>; abstract loadAll(...args: any[]): Promise<void | Item[]>;
protected defaultSorting = (item: T) => item.getName(); protected defaultSorting = (item: Item) => item.getName();
@observable failedLoading = false; @observable failedLoading = false;
@observable isLoading = false; @observable isLoading = false;
@observable isLoaded = false; @observable isLoaded = false;
@observable items = observable.array<T>([], { deep: false }); @observable items = observable.array<Item>([], { deep: false });
@observable selectedItemsIds = observable.map<string, boolean>(); @observable selectedItemsIds = observable.map<string, boolean>();
constructor() { constructor() {
@ -44,11 +44,11 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
autoBind(this); autoBind(this);
} }
@computed get selectedItems(): T[] { @computed get selectedItems(): Item[] {
return this.items.filter(item => this.selectedItemsIds.get(item.getId())); return this.items.filter(item => this.selectedItemsIds.get(item.getId()));
} }
public getItems(): T[] { public getItems(): Item[] {
return Array.from(this.items); return Array.from(this.items);
} }
@ -56,8 +56,8 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
return this.items.length; return this.items.length;
} }
getByName(name: string, ...args: any[]): T; getByName(name: string, ...args: any[]): Item;
getByName(name: string): T { getByName(name: string): Item {
return this.items.find(item => item.getName() === name); return this.items.find(item => item.getName() === name);
} }
@ -75,13 +75,13 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
* @param order whether to sort from least to greatest (`"asc"` (default)) or vice-versa (`"desc"`) * @param order whether to sort from least to greatest (`"asc"` (default)) or vice-versa (`"desc"`)
*/ */
@action @action
protected sortItems(items: T[] = this.items, sorting: ((item: T) => any)[] = [this.defaultSorting], order?: "asc" | "desc"): T[] { protected sortItems(items: Item[] = this.items, sorting: ((item: Item) => any)[] = [this.defaultSorting], order?: "asc" | "desc"): Item[] {
return orderBy(items, sorting, order); return orderBy(items, sorting, order);
} }
protected async createItem(...args: any[]): Promise<any>; protected async createItem(...args: any[]): Promise<any>;
@action @action
protected async createItem(request: () => Promise<T>) { protected async createItem(request: () => Promise<Item>) {
const newItem = await request(); const newItem = await request();
const item = this.items.find(item => item.getId() === newItem.getId()); const item = this.items.find(item => item.getId() === newItem.getId());
@ -98,7 +98,7 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
protected async loadItems(...args: any[]): Promise<any>; protected async loadItems(...args: any[]): Promise<any>;
@action @action
protected async loadItems(request: () => Promise<T[] | any>, sortItems = true) { protected async loadItems(request: () => Promise<Item[] | any>, sortItems = true) {
if (this.isLoading) { if (this.isLoading) {
await when(() => !this.isLoading); await when(() => !this.isLoading);
@ -117,9 +117,9 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
} }
} }
protected async loadItem(...args: any[]): Promise<T> protected async loadItem(...args: any[]): Promise<Item>
@action @action
protected async loadItem(request: () => Promise<T>, sortItems = true) { protected async loadItem(request: () => Promise<Item>, sortItems = true) {
const item = await Promise.resolve(request()).catch(() => null); const item = await Promise.resolve(request()).catch(() => null);
if (item) { if (item) {
@ -141,7 +141,7 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
} }
@action @action
protected async updateItem(item: T, request: () => Promise<T>) { protected async updateItem(item: Item, request: () => Promise<Item>) {
const updatedItem = await request(); const updatedItem = await request();
const index = this.items.findIndex(i => i.getId() === item.getId()); const index = this.items.findIndex(i => i.getId() === item.getId());
@ -151,28 +151,28 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
} }
@action @action
protected async removeItem(item: T, request: () => Promise<any>) { protected async removeItem(item: Item, request: () => Promise<any>) {
await request(); await request();
this.items.remove(item); this.items.remove(item);
this.selectedItemsIds.delete(item.getId()); this.selectedItemsIds.delete(item.getId());
} }
isSelected(item: T) { isSelected(item: Item) {
return !!this.selectedItemsIds.get(item.getId()); return !!this.selectedItemsIds.get(item.getId());
} }
@action @action
select(item: T) { select(item: Item) {
this.selectedItemsIds.set(item.getId(), true); this.selectedItemsIds.set(item.getId(), true);
} }
@action @action
unselect(item: T) { unselect(item: Item) {
this.selectedItemsIds.delete(item.getId()); this.selectedItemsIds.delete(item.getId());
} }
@action @action
toggleSelection(item: T) { toggleSelection(item: Item) {
if (this.isSelected(item)) { if (this.isSelected(item)) {
this.unselect(item); this.unselect(item);
} else { } else {
@ -181,7 +181,7 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
} }
@action @action
toggleSelectionAll(visibleItems: T[] = this.items) { toggleSelectionAll(visibleItems: Item[] = this.items) {
const allSelected = visibleItems.every(this.isSelected); const allSelected = visibleItems.every(this.isSelected);
if (allSelected) { if (allSelected) {
@ -191,7 +191,7 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
} }
} }
isSelectedAll(visibleItems: T[] = this.items) { isSelectedAll(visibleItems: Item[] = this.items) {
if (!visibleItems.length) return false; if (!visibleItems.length) return false;
return visibleItems.every(this.isSelected); return visibleItems.every(this.isSelected);
@ -218,7 +218,7 @@ export abstract class ItemStore<T extends ItemObject = ItemObject> {
return noop; return noop;
} }
* [Symbol.iterator]() { *[Symbol.iterator]() {
yield* this.items; yield* this.items;
} }
} }

View File

@ -31,16 +31,16 @@ import { ensureObjectSelfLink, IKubeApiQueryParams, KubeApi, parseKubeApi } from
import type { KubeJsonApiData } from "./api/kube-json-api"; import type { KubeJsonApiData } from "./api/kube-json-api";
import { Notifications } from "./components/notifications"; import { Notifications } from "./components/notifications";
export interface KubeObjectStoreLoadingParams { export interface KubeObjectStoreLoadingParams<K extends KubeObject> {
namespaces: string[]; namespaces: string[];
api?: KubeApi; api?: KubeApi<K>;
reqInit?: RequestInit; reqInit?: RequestInit;
} }
export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemStore<T> { export abstract class KubeObjectStore<K extends KubeObject> extends ItemStore<K> {
static defaultContext = observable.box<ClusterContext>(); // TODO: support multiple cluster contexts static defaultContext = observable.box<ClusterContext>(); // TODO: support multiple cluster contexts
abstract api: KubeApi<T>; abstract api: KubeApi<K>;
public readonly limit?: number; public readonly limit?: number;
public readonly bufferSize: number = 50000; public readonly bufferSize: number = 50000;
@observable private loadedNamespaces?: string[]; @observable private loadedNamespaces?: string[];
@ -64,7 +64,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
return KubeObjectStore.defaultContext.get(); return KubeObjectStore.defaultContext.get();
} }
@computed get contextItems(): T[] { @computed get contextItems(): K[] {
const namespaces = this.context?.contextNamespaces ?? []; const namespaces = this.context?.contextNamespaces ?? [];
return this.items.filter(item => { return this.items.filter(item => {
@ -88,9 +88,9 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
return { limit }; return { limit };
} }
getStatuses?(items: T[]): Record<string, number>; getStatuses?(items: K[]): Record<string, number>;
getAllByNs(namespace: string | string[], strict = false): T[] { getAllByNs(namespace: string | string[], strict = false): K[] {
const namespaces: string[] = [].concat(namespace); const namespaces: string[] = [].concat(namespace);
if (namespaces.length) { if (namespaces.length) {
@ -108,7 +108,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
return this.items.find(item => item.getId() === id); return this.items.find(item => item.getId() === id);
} }
getByName(name: string, namespace?: string): T { getByName(name: string, namespace?: string): K {
return this.items.find(item => { return this.items.find(item => {
return item.getName() === name && ( return item.getName() === name && (
namespace ? item.getNs() === namespace : true namespace ? item.getNs() === namespace : true
@ -116,19 +116,19 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
}); });
} }
getByPath(path: string): T { getByPath(path: string): K {
return this.items.find(item => item.selfLink === path); return this.items.find(item => item.selfLink === path);
} }
getByLabel(labels: string[] | { [label: string]: string }): T[] { getByLabel(labels: string[] | { [label: string]: string }): K[] {
if (Array.isArray(labels)) { if (Array.isArray(labels)) {
return this.items.filter((item: T) => { return this.items.filter((item: K) => {
const itemLabels = item.getLabels(); const itemLabels = item.getLabels();
return labels.every(label => itemLabels.includes(label)); return labels.every(label => itemLabels.includes(label));
}); });
} else { } else {
return this.items.filter((item: T) => { return this.items.filter((item: K) => {
const itemLabels = item.metadata.labels || {}; const itemLabels = item.metadata.labels || {};
return Object.entries(labels) return Object.entries(labels)
@ -137,7 +137,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
} }
} }
protected async loadItems({ namespaces, api, reqInit }: KubeObjectStoreLoadingParams): Promise<T[]> { protected async loadItems({ namespaces, api, reqInit }: KubeObjectStoreLoadingParams<K>): Promise<K[]> {
if (this.context?.cluster.isAllowedResource(api.kind)) { if (this.context?.cluster.isAllowedResource(api.kind)) {
if (!api.isNamespaced) { if (!api.isNamespaced) {
return api.list({ reqInit }, this.query); return api.list({ reqInit }, this.query);
@ -163,12 +163,12 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
return []; return [];
} }
protected filterItemsOnLoad(items: T[]) { protected filterItemsOnLoad(items: K[]) {
return items; return items;
} }
@action @action
async loadAll(options: { namespaces?: string[], merge?: boolean, reqInit?: RequestInit } = {}): Promise<void | T[]> { async loadAll(options: { namespaces?: string[], merge?: boolean, reqInit?: RequestInit } = {}): Promise<void | K[]> {
await this.contextReady; await this.contextReady;
this.isLoading = true; this.isLoading = true;
@ -215,7 +215,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
} }
@action @action
protected mergeItems(partialItems: T[], { replace = false, updateStore = true, sort = true, filter = true } = {}): T[] { protected mergeItems(partialItems: K[], { replace = false, updateStore = true, sort = true, filter = true } = {}): K[] {
let items = partialItems; let items = partialItems;
// update existing items // update existing items
@ -239,12 +239,12 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
if (error) this.reset(); if (error) this.reset();
} }
protected async loadItem(params: { name: string; namespace?: string }): Promise<T> { protected async loadItem(params: { name: string; namespace?: string }): Promise<K> {
return this.api.get(params); return this.api.get(params);
} }
@action @action
async load(params: { name: string; namespace?: string }): Promise<T> { async load(params: { name: string; namespace?: string }): Promise<K> {
const { name, namespace } = params; const { name, namespace } = params;
let item = this.getByName(name, namespace); let item = this.getByName(name, namespace);
@ -265,11 +265,11 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
return this.load({ name, namespace }); return this.load({ name, namespace });
} }
protected async createItem(params: { name: string; namespace?: string }, data?: Partial<T>): Promise<T> { protected async createItem(params: { name: string; namespace?: string }, data?: Partial<K>): Promise<K> {
return this.api.create(params, data); return this.api.create(params, data);
} }
async create(params: { name: string; namespace?: string }, data?: Partial<T>): Promise<T> { async create(params: { name: string; namespace?: string }, data?: Partial<K>): Promise<K> {
const newItem = await this.createItem(params, data); const newItem = await this.createItem(params, data);
const items = this.sortItems([...this.items, newItem]); const items = this.sortItems([...this.items, newItem]);
@ -278,9 +278,9 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
return newItem; return newItem;
} }
async update(item: T, data: Partial<T>): Promise<T> { async update(item: K, data: Partial<K>): Promise<K> {
const newItem = await item.update<T>(data); const newItem = await item.update(data);
ensureObjectSelfLink(this.api, newItem); ensureObjectSelfLink(this.api, newItem);
const index = this.items.findIndex(item => item.getId() === newItem.getId()); const index = this.items.findIndex(item => item.getId() === newItem.getId());
@ -290,7 +290,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
return newItem; return newItem;
} }
async remove(item: T) { async remove(item: K) {
await item.delete(); await item.delete();
this.items.remove(item); this.items.remove(item);
this.selectedItemsIds.delete(item.getId()); this.selectedItemsIds.delete(item.getId());
@ -309,7 +309,8 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
}); });
} }
getSubscribeApis(): KubeApi[] { getSubscribeApis(): KubeApi<KubeObject>[] {
// TODO remove this function, each Store should only be a single API
return [this.api]; return [this.api];
} }
@ -351,7 +352,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
}; };
} }
private watchNamespace(api: KubeApi<T>, namespace: string, abortController: AbortController) { private watchNamespace(api: KubeApi<K>, namespace: string, abortController: AbortController) {
let timedRetry: NodeJS.Timeout; let timedRetry: NodeJS.Timeout;
const watch = () => api.watch({ const watch = () => api.watch({
namespace, namespace,
@ -361,7 +362,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
const { signal } = abortController; const { signal } = abortController;
const callback = (data: IKubeWatchEvent, error: any) => { const callback = (data: IKubeWatchEvent<K>, error: any) => {
if (!this.isLoaded || error instanceof DOMException) return; if (!this.isLoaded || error instanceof DOMException) return;
if (error instanceof Response) { if (error instanceof Response) {
@ -409,7 +410,7 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
switch (type) { switch (type) {
case "ADDED": case "ADDED":
case "MODIFIED": case "MODIFIED":
const newItem = new api.objectConstructor(object); const newItem = new api.objectConstructor(object) as K;
if (!item) { if (!item) {
items.push(newItem); items.push(newItem);