mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
add more typing to sorting of tables
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
a62e1fc4e5
commit
f66d84ae4a
@ -269,7 +269,6 @@ export class ExtensionDiscovery {
|
|||||||
// fs.remove won't throw if path is missing
|
// fs.remove won't throw if path is missing
|
||||||
await fs.remove(path.join(extensionInstaller.extensionPackagesRoot, "package-lock.json"));
|
await fs.remove(path.join(extensionInstaller.extensionPackagesRoot, "package-lock.json"));
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Verify write access to static/extensions, which is needed for symlinking
|
// Verify write access to static/extensions, which is needed for symlinking
|
||||||
await fs.access(this.inTreeFolderPath, fs.constants.W_OK);
|
await fs.access(this.inTreeFolderPath, fs.constants.W_OK);
|
||||||
@ -293,8 +292,8 @@ export class ExtensionDiscovery {
|
|||||||
this.bundledFolderPath = this.inTreeTargetPath;
|
this.bundledFolderPath = this.inTreeTargetPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.ensureDir(this.nodeModulesPath);
|
|
||||||
await fs.ensureDir(this.localFolderPath);
|
await Promise.all([fs.ensureDir(this.nodeModulesPath), fs.ensureDir(this.localFolderPath)]);
|
||||||
|
|
||||||
const extensions = await this.loadExtensions();
|
const extensions = await this.loadExtensions();
|
||||||
|
|
||||||
@ -305,7 +304,7 @@ export class ExtensionDiscovery {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the symlinked path to the extension folder,
|
* Returns the symlinked path to the extension folder,
|
||||||
* e.g. "/Users/<username>/Library/Application Support/Lens/node_modules/@publisher/extension"
|
* e.g. `/Users/<username>/Library/Application Support/Lens/node_modules/@publisher/extension`
|
||||||
*/
|
*/
|
||||||
protected getInstalledPath(name: string) {
|
protected getInstalledPath(name: string) {
|
||||||
return path.join(this.nodeModulesPath, name);
|
return path.join(this.nodeModulesPath, name);
|
||||||
|
|||||||
@ -10,12 +10,12 @@ export class ApiManager {
|
|||||||
private apis = observable.map<string, KubeApi>();
|
private apis = observable.map<string, KubeApi>();
|
||||||
private stores = observable.map<KubeApi, KubeObjectStore<any>>();
|
private stores = observable.map<KubeApi, KubeObjectStore<any>>();
|
||||||
|
|
||||||
getApi(pathOrCallback: string | ((api: KubeApi) => boolean) = () => true) {
|
getApi<T extends KubeObject = KubeObject>(pathOrCallback: string | ((api: KubeApi) => boolean) = () => true): KubeApi<T> {
|
||||||
if (typeof pathOrCallback === "string") {
|
if (typeof pathOrCallback === "string") {
|
||||||
return this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase);
|
return (this.apis.get(pathOrCallback) || this.apis.get(KubeApi.parseApi(pathOrCallback).apiBase)) as KubeApi<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(this.apis.values()).find(pathOrCallback);
|
return Array.from(this.apis.values()).find(pathOrCallback) as KubeApi<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerApi(apiBase: string, api: KubeApi) {
|
registerApi(apiBase: string, api: KubeApi) {
|
||||||
|
|||||||
@ -3,31 +3,29 @@ import { KubeObject } from "../kube-object";
|
|||||||
import { KubeJsonApiData } from "../kube-json-api";
|
import { KubeJsonApiData } from "../kube-json-api";
|
||||||
import { apiBase } from "../index";
|
import { apiBase } from "../index";
|
||||||
import { apiManager } from "../api-manager";
|
import { apiManager } from "../api-manager";
|
||||||
|
import { CancelablePromise } from "../../utils/cancelableFetch";
|
||||||
|
import { filter } from "lodash";
|
||||||
|
|
||||||
export const resourceApplierApi = {
|
export const resourceApplierApi = {
|
||||||
annotations: [
|
annotations: [
|
||||||
"kubectl.kubernetes.io/last-applied-configuration"
|
"kubectl.kubernetes.io/last-applied-configuration"
|
||||||
],
|
],
|
||||||
|
|
||||||
async update<D extends KubeObject>(resource: object | string): Promise<D> {
|
update<D extends KubeObject>(resource: object | string): CancelablePromise<D[]> {
|
||||||
if (typeof resource === "string") {
|
if (typeof resource === "string") {
|
||||||
resource = jsYaml.safeLoad(resource);
|
resource = jsYaml.safeLoad(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiBase
|
return apiBase
|
||||||
.post<KubeJsonApiData[]>("/stack", { data: resource })
|
.post<KubeJsonApiData[]>("/stack", { data: resource })
|
||||||
.then(data => {
|
.then(data => filter(
|
||||||
const items = data.map(obj => {
|
data.map(obj => {
|
||||||
const api = apiManager.getApi(obj.metadata.selfLink);
|
const api = apiManager.getApi<D>(obj.metadata.selfLink);
|
||||||
|
|
||||||
if (api) {
|
if (api?.objectConstructor) {
|
||||||
return new api.objectConstructor(obj);
|
return new api.objectConstructor(obj);
|
||||||
} else {
|
|
||||||
return new KubeObject(obj);
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
));
|
||||||
return items.length === 1 ? items[0] : items;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,11 +4,8 @@ import { KubeObjectStore } from "../../kube-object.store";
|
|||||||
import { KubeObject } from "../../api/kube-object";
|
import { KubeObject } from "../../api/kube-object";
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
export class CRDResourceStore<T extends KubeObject = any> extends KubeObjectStore<T> {
|
export class CRDResourceStore<T extends KubeObject = KubeObject> extends KubeObjectStore<T> {
|
||||||
api: KubeApi;
|
constructor(public api: KubeApi<T>) {
|
||||||
|
|
||||||
constructor(api: KubeApi<T>) {
|
|
||||||
super();
|
super();
|
||||||
this.api = api;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ enum sortBy {
|
|||||||
age = "age",
|
age = "age",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props extends Partial<KubeObjectListLayoutProps> {
|
interface Props extends Partial<KubeObjectListLayoutProps<KubeEvent>> {
|
||||||
className?: IClassName;
|
className?: IClassName;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
compactLimit?: number;
|
compactLimit?: number;
|
||||||
@ -45,17 +45,17 @@ export class Events extends React.Component<Props> {
|
|||||||
store={eventStore}
|
store={eventStore}
|
||||||
isSelectable={false}
|
isSelectable={false}
|
||||||
sortingCallbacks={{
|
sortingCallbacks={{
|
||||||
[sortBy.namespace]: (event: KubeEvent) => event.getNs(),
|
[sortBy.namespace]: event => event.getNs(),
|
||||||
[sortBy.type]: (event: KubeEvent) => event.involvedObject.kind,
|
[sortBy.type]: event => event.involvedObject.kind,
|
||||||
[sortBy.object]: (event: KubeEvent) => event.involvedObject.name,
|
[sortBy.object]: event => event.involvedObject.name,
|
||||||
[sortBy.count]: (event: KubeEvent) => event.count,
|
[sortBy.count]: event => event.count,
|
||||||
[sortBy.age]: (event: KubeEvent) => event.metadata.creationTimestamp,
|
[sortBy.age]: event => event.metadata.creationTimestamp,
|
||||||
}}
|
}}
|
||||||
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,
|
||||||
]}
|
]}
|
||||||
renderHeaderTitle={<Trans>Events</Trans>}
|
renderHeaderTitle={<Trans>Events</Trans>}
|
||||||
customizeHeader={({ title, info }) => (
|
customizeHeader={({ title, info }) => (
|
||||||
@ -82,7 +82,7 @@ export class Events extends React.Component<Props> {
|
|||||||
{ title: <Trans>Count</Trans>, className: "count", sortBy: sortBy.count },
|
{ title: <Trans>Count</Trans>, className: "count", sortBy: sortBy.count },
|
||||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
||||||
]}
|
]}
|
||||||
renderTableContents={(event: KubeEvent) => {
|
renderTableContents={event => {
|
||||||
const { involvedObject, type, message } = event;
|
const { involvedObject, type, message } = event;
|
||||||
const { kind, name } = involvedObject;
|
const { kind, name } = involvedObject;
|
||||||
const tooltipId = `message-${event.getId()}`;
|
const tooltipId = `message-${event.getId()}`;
|
||||||
|
|||||||
@ -35,16 +35,21 @@ export class ServiceAccountsDetails extends React.Component<Props> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const namespace = serviceAccount.getNs();
|
const namespace = serviceAccount.getNs();
|
||||||
const secrets = serviceAccount.getSecrets().map(({ name }) => {
|
|
||||||
return secretsStore.load({ name, namespace });
|
|
||||||
});
|
|
||||||
|
|
||||||
this.secrets = await Promise.all(secrets);
|
this.secrets = await Promise.all(
|
||||||
const imagePullSecrets = serviceAccount.getImagePullSecrets().map(async({ name }) => {
|
serviceAccount
|
||||||
return secretsStore.load({ name, namespace }).catch(() => this.generateDummySecretObject(name));
|
.getSecrets()
|
||||||
});
|
.map(({ name }) => secretsStore.load({ name, namespace }))
|
||||||
|
);
|
||||||
this.imagePullSecrets = await Promise.all(imagePullSecrets);
|
this.imagePullSecrets = await Promise.all(
|
||||||
|
serviceAccount
|
||||||
|
.getImagePullSecrets()
|
||||||
|
.map(({ name }) => (
|
||||||
|
secretsStore
|
||||||
|
.load({ name, namespace })
|
||||||
|
.catch(() => this.generateDummySecretObject(name))
|
||||||
|
))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
renderSecrets() {
|
renderSecrets() {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import "./overview.scss";
|
import "./overview.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { observable, when } from "mobx";
|
import { computed, observable } from "mobx";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { OverviewStatuses } from "./overview-statuses";
|
import { OverviewStatuses } from "./overview-statuses";
|
||||||
import { RouteComponentProps } from "react-router";
|
import { RouteComponentProps } from "react-router";
|
||||||
@ -18,60 +18,36 @@ import { Spinner } from "../spinner";
|
|||||||
import { Events } from "../+events";
|
import { Events } from "../+events";
|
||||||
import { KubeObjectStore } from "../../kube-object.store";
|
import { KubeObjectStore } from "../../kube-object.store";
|
||||||
import { isAllowedResource } from "../../../common/rbac";
|
import { isAllowedResource } from "../../../common/rbac";
|
||||||
|
import { filter } from "lodash";
|
||||||
|
|
||||||
interface Props extends RouteComponentProps<IWorkloadsOverviewRouteParams> {
|
interface Props extends RouteComponentProps<IWorkloadsOverviewRouteParams> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class WorkloadsOverview extends React.Component<Props> {
|
export class WorkloadsOverview extends React.Component<Props> {
|
||||||
@observable isReady = false;
|
@observable stores: KubeObjectStore<any>[] = [];
|
||||||
@observable isUnmounting = false;
|
unsubscribeList: (() => void)[] = [];
|
||||||
|
|
||||||
|
@computed get isReady() {
|
||||||
|
return this.stores.every(store => store.isLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const stores: KubeObjectStore[] = [];
|
this.stores = filter([
|
||||||
|
isAllowedResource("pods") && podsStore,
|
||||||
if (isAllowedResource("pods")) {
|
isAllowedResource("deployments") && deploymentStore,
|
||||||
stores.push(podsStore);
|
isAllowedResource("daemonsets") && daemonSetStore,
|
||||||
}
|
isAllowedResource("statefulsets") && statefulSetStore,
|
||||||
|
isAllowedResource("replicasets") && replicaSetStore,
|
||||||
if (isAllowedResource("deployments")) {
|
isAllowedResource("jobs") && jobStore,
|
||||||
stores.push(deploymentStore);
|
isAllowedResource("cronjobs") && cronJobStore,
|
||||||
}
|
isAllowedResource("events") && eventStore,
|
||||||
|
]);
|
||||||
if (isAllowedResource("daemonsets")) {
|
this.unsubscribeList = this.stores.map(store => store.subscribe());
|
||||||
stores.push(daemonSetStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAllowedResource("statefulsets")) {
|
|
||||||
stores.push(statefulSetStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAllowedResource("replicasets")) {
|
|
||||||
stores.push(replicaSetStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAllowedResource("jobs")) {
|
|
||||||
stores.push(jobStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAllowedResource("cronjobs")) {
|
|
||||||
stores.push(cronJobStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAllowedResource("events")) {
|
|
||||||
stores.push(eventStore);
|
|
||||||
}
|
|
||||||
this.isReady = stores.every(store => store.isLoaded);
|
|
||||||
await Promise.all(stores.map(store => store.loadAll()));
|
|
||||||
this.isReady = true;
|
|
||||||
const unsubscribeList = stores.map(store => store.subscribe());
|
|
||||||
|
|
||||||
await when(() => this.isUnmounting);
|
|
||||||
unsubscribeList.forEach(dispose => dispose());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.isUnmounting = true;
|
this.unsubscribeList.forEach(dispose => dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContents() {
|
renderContents() {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
|
|||||||
import { KubeResource } from "../../../common/rbac";
|
import { KubeResource } from "../../../common/rbac";
|
||||||
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
import { replicaSetStore } from "../+workloads-replicasets/replicasets.store";
|
||||||
|
|
||||||
export const workloadStores: Partial<Record<KubeResource, KubeObjectStore>> = {
|
export const workloadStores: Partial<Record<KubeResource, KubeObjectStore<any>>> = {
|
||||||
"pods": podsStore,
|
"pods": podsStore,
|
||||||
"deployments": deploymentStore,
|
"deployments": deploymentStore,
|
||||||
"daemonsets": daemonSetStore,
|
"daemonsets": daemonSetStore,
|
||||||
|
|||||||
@ -46,11 +46,11 @@ export class CreateResource extends React.Component<Props> {
|
|||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
resources.map(data => {
|
resources.map(data => (
|
||||||
return resourceApplierApi.update(data)
|
resourceApplierApi.update(data)
|
||||||
.then(item => createdResources.push(item.getName()))
|
.then(item => createdResources.push(...item.map(item => item.getName())))
|
||||||
.catch((err: JsonApiErrorParsed) => errors.push(err.toString()));
|
.catch((err: JsonApiErrorParsed) => errors.push(err.toString()))
|
||||||
})
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
|
|||||||
@ -32,19 +32,19 @@ interface IHeaderPlaceholders {
|
|||||||
info: ReactNode;
|
info: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
export interface ItemListLayoutProps<Entry extends ItemObject = ItemObject, SortingOption extends string = string> {
|
||||||
className: IClassName;
|
className: IClassName;
|
||||||
store: ItemStore<T>;
|
store: ItemStore<Entry>;
|
||||||
dependentStores?: ItemStore[];
|
dependentStores?: ItemStore[];
|
||||||
isClusterScoped?: boolean;
|
isClusterScoped?: boolean;
|
||||||
hideFilters?: boolean;
|
hideFilters?: boolean;
|
||||||
searchFilters?: SearchFilter<T>[];
|
searchFilters?: SearchFilter<Entry>[];
|
||||||
filterItems?: ItemsFilter<T>[];
|
filterItems?: ItemsFilter<Entry>[];
|
||||||
|
|
||||||
// header (title, filtering, searching, etc.)
|
// header (title, filtering, searching, etc.)
|
||||||
showHeader?: boolean;
|
showHeader?: boolean;
|
||||||
headerClassName?: IClassName;
|
headerClassName?: IClassName;
|
||||||
renderHeaderTitle?: ReactNode | ((parent: ItemListLayout<T>) => ReactNode);
|
renderHeaderTitle?: ReactNode | ((parent: ItemListLayout<Entry>) => ReactNode);
|
||||||
customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode;
|
customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode;
|
||||||
|
|
||||||
// items list configuration
|
// items list configuration
|
||||||
@ -52,23 +52,23 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
|||||||
isSelectable?: boolean; // show checkbox in rows for selecting items
|
isSelectable?: boolean; // show checkbox in rows for selecting items
|
||||||
isSearchable?: boolean; // apply search-filter & add search-input
|
isSearchable?: boolean; // apply search-filter & add search-input
|
||||||
copyClassNameFromHeadCells?: boolean;
|
copyClassNameFromHeadCells?: boolean;
|
||||||
sortingCallbacks?: { [sortBy: string]: TableSortCallback<T> };
|
sortingCallbacks?: Record<SortingOption, TableSortCallback<Entry>>;
|
||||||
tableProps?: Partial<TableProps<T>>; // low-level table configuration
|
tableProps?: Partial<TableProps<Entry>>; // low-level table configuration
|
||||||
renderTableHeader: TableCellProps[] | null;
|
renderTableHeader: TableCellProps<SortingOption>[] | null;
|
||||||
renderTableContents: (item: T) => (ReactNode | TableCellProps)[];
|
renderTableContents: (item: Entry) => (ReactNode | TableCellProps)[];
|
||||||
renderItemMenu?: (item: T, store: ItemStore<T>) => ReactNode;
|
renderItemMenu?: (item: Entry, store: ItemStore<Entry>) => ReactNode;
|
||||||
customizeTableRowProps?: (item: T) => Partial<TableRowProps>;
|
customizeTableRowProps?: (item: Entry) => 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?: Entry;
|
||||||
onDetails?: (item: T) => void;
|
onDetails?: (item: Entry) => void;
|
||||||
|
|
||||||
// other
|
// other
|
||||||
customizeRemoveDialog?: (selectedItems: T[]) => Partial<ConfirmDialogParams>;
|
customizeRemoveDialog?: (selectedItems: Entry[]) => Partial<ConfirmDialogParams>;
|
||||||
renderFooter?: (parent: ItemListLayout<T>) => React.ReactNode;
|
renderFooter?: (parent: ItemListLayout<Entry>) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProps: Partial<ItemListLayoutProps> = {
|
const defaultProps: Partial<ItemListLayoutProps> = {
|
||||||
|
|||||||
@ -13,6 +13,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";
|
||||||
|
|
||||||
export interface KubeObjectDetailsProps<T = KubeObject> {
|
export interface KubeObjectDetailsProps<T = KubeObject> {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -81,7 +82,7 @@ export class KubeObjectDetails extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isCrdInstance && details.length === 0) {
|
if (isCrdInstance && details.length === 0) {
|
||||||
details.push(<CrdResourceDetails object={object} />);
|
details.push(<CrdResourceDetails object={object as CustomResourceDefinition} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { KubeObjectStore } from "../../kube-object.store";
|
|||||||
import { KubeObjectMenu } from "./kube-object-menu";
|
import { KubeObjectMenu } from "./kube-object-menu";
|
||||||
import { ItemObject } from "../../item.store";
|
import { ItemObject } from "../../item.store";
|
||||||
|
|
||||||
export interface KubeObjectListLayoutProps<T extends ItemObject & KubeObject> extends ItemListLayoutProps<T> {
|
export interface KubeObjectListLayoutProps<T extends ItemObject & KubeObject, SortOrder extends string> extends ItemListLayoutProps<T, SortOrder> {
|
||||||
store: KubeObjectStore<T>;
|
store: KubeObjectStore<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ function showItemDetails(item: KubeObject) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class KubeObjectListLayout<T extends ItemObject & KubeObject> extends React.Component<KubeObjectListLayoutProps<T>> {
|
export class KubeObjectListLayout<T extends ItemObject & KubeObject, SortOrder extends string> extends React.Component<KubeObjectListLayoutProps<T, SortOrder>> {
|
||||||
@computed get selectedItem() {
|
@computed get selectedItem() {
|
||||||
return this.props.store.getByPath(getSelectedDetails());
|
return this.props.store.getByPath(getSelectedDetails());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import "./table-cell.scss";
|
import "./table-cell.scss";
|
||||||
import type { TableSortBy, TableSortParams } from "./table";
|
import type { TableSortParams } from "./table";
|
||||||
|
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { autobind, cssNames, displayBooleans } from "../../utils";
|
import { autobind, cssNames, displayBooleans } from "../../utils";
|
||||||
@ -8,15 +8,15 @@ import { Checkbox } from "../checkbox";
|
|||||||
|
|
||||||
export type TableCellElem = React.ReactElement<TableCellProps>;
|
export type TableCellElem = React.ReactElement<TableCellProps>;
|
||||||
|
|
||||||
export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
|
export interface TableCellProps<SortingOption extends string = string> extends React.DOMAttributes<HTMLDivElement> {
|
||||||
className?: string;
|
className?: string;
|
||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
checkbox?: boolean; // render cell with a checkbox
|
checkbox?: boolean; // render cell with a checkbox
|
||||||
isChecked?: boolean; // mark checkbox as checked or not
|
isChecked?: boolean; // mark checkbox as checked or not
|
||||||
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
|
renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean"
|
||||||
sortBy?: TableSortBy; // column name, must be same as key in sortable object <Table sortable={}/>
|
sortBy?: SortingOption; // column name, must be same as key in sortable object <Table sortable={}/>
|
||||||
_sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!)
|
_sorting?: Partial<TableSortParams<SortingOption>>; // <Table> sorting state, don't use this prop outside (!)
|
||||||
_sort?(sortBy: TableSortBy): void; // <Table> sort function, don't use this prop outside (!)
|
_sort?(sortBy: SortingOption): void; // <Table> sort function, don't use this prop outside (!)
|
||||||
_nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!)
|
_nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,12 +14,11 @@ import { ItemObject } from "../../item.store";
|
|||||||
|
|
||||||
// todo: refactor + decouple search from location
|
// todo: refactor + decouple search from location
|
||||||
|
|
||||||
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<SortingOption extends string> = { sortBy: SortingOption; orderBy: TableOrderBy };
|
||||||
export type TableSortCallback<D> = (data: D) => string | number | (string | number)[];
|
export type TableSortCallback<D> = (data: D) => string | number | (string | number)[];
|
||||||
|
|
||||||
export interface TableProps<T> extends React.DOMAttributes<HTMLDivElement> {
|
export interface TableProps<T, SortingOption extends string = string> extends React.DOMAttributes<HTMLDivElement> {
|
||||||
items?: ItemObject[]; // Raw items data
|
items?: ItemObject[]; // Raw items data
|
||||||
className?: string;
|
className?: string;
|
||||||
autoSize?: boolean; // Setup auto-sizing for all columns (flex: 1 0)
|
autoSize?: boolean; // Setup auto-sizing for all columns (flex: 1 0)
|
||||||
@ -32,8 +31,8 @@ export interface TableProps<T> extends React.DOMAttributes<HTMLDivElement> {
|
|||||||
[sortBy: string]: TableSortCallback<T>;
|
[sortBy: string]: TableSortCallback<T>;
|
||||||
};
|
};
|
||||||
sortSyncWithUrl?: boolean; // sorting state is managed globally from url params
|
sortSyncWithUrl?: boolean; // sorting state is managed globally from url params
|
||||||
sortByDefault?: Partial<TableSortParams>; // default sorting params
|
sortByDefault?: Partial<TableSortParams<SortingOption>>; // default sorting params
|
||||||
onSort?: (params: TableSortParams) => void; // callback on sort change, default: global sync with url
|
onSort?: (params: TableSortParams<SortingOption>) => void; // callback on sort change, default: global sync with url
|
||||||
noItems?: React.ReactNode; // Show no items state table list is empty
|
noItems?: React.ReactNode; // Show no items state table list is empty
|
||||||
selectedItemId?: string; // Allows to scroll list to selected item
|
selectedItemId?: string; // Allows to scroll list to selected item
|
||||||
virtual?: boolean; // Use virtual list component to render only visible rows
|
virtual?: boolean; // Use virtual list component to render only visible rows
|
||||||
|
|||||||
@ -136,12 +136,8 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
return this.load({ name, namespace });
|
return this.load({ name, namespace });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async createItem(params: { name: string; namespace?: string }, data?: Partial<T>): Promise<T> {
|
|
||||||
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<T>): Promise<T> {
|
||||||
const newItem = await this.createItem(params, data);
|
const newItem = await this.api.create(params, data);
|
||||||
const items = this.sortItems([...this.items, newItem]);
|
const items = this.sortItems([...this.items, newItem]);
|
||||||
|
|
||||||
this.items.replace(items);
|
this.items.replace(items);
|
||||||
@ -150,7 +146,7 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(item: T, data: Partial<T>): Promise<T> {
|
async update(item: T, data: Partial<T>): Promise<T> {
|
||||||
const newItem = await item.update<T>(data);
|
const [newItem] = await item.update<T>(data);
|
||||||
const index = this.items.findIndex(item => item.getId() === newItem.getId());
|
const index = this.items.findIndex(item => item.getId() === newItem.getId());
|
||||||
|
|
||||||
this.items.splice(index, 1, newItem);
|
this.items.splice(index, 1, newItem);
|
||||||
@ -172,9 +168,11 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
protected eventsBuffer = observable<IKubeWatchEvent<KubeJsonApiData>>([], { deep: false });
|
protected eventsBuffer = observable<IKubeWatchEvent<KubeJsonApiData>>([], { deep: false });
|
||||||
|
|
||||||
protected bindWatchEventsUpdater(delay = 1000) {
|
protected bindWatchEventsUpdater(delay = 1000) {
|
||||||
return reaction(() => this.eventsBuffer.toJS()[0], this.updateFromEventsBuffer, {
|
return reaction(
|
||||||
delay
|
() => this.eventsBuffer.toJS()[0],
|
||||||
});
|
this.updateFromEventsBuffer,
|
||||||
|
{ delay }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(apis = [this.api]) {
|
subscribe(apis = [this.api]) {
|
||||||
@ -196,23 +194,22 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
|
|
||||||
for (const {type, object} of this.eventsBuffer.clear()) {
|
for (const {type, object} of this.eventsBuffer.clear()) {
|
||||||
const { uid, selfLink } = object.metadata;
|
const { uid, selfLink } = object.metadata;
|
||||||
const index = items.findIndex(item => item.getId() === uid);
|
const index = items.map(item => item.getId()).indexOf(uid);
|
||||||
const item = items[index];
|
const api = apiManager.getApi<T>(selfLink);
|
||||||
const api = apiManager.getApi(selfLink);
|
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "ADDED":
|
case "ADDED":
|
||||||
case "MODIFIED":
|
case "MODIFIED":
|
||||||
const newItem = new api.objectConstructor(object);
|
const newItem = new api.objectConstructor(object);
|
||||||
|
|
||||||
if (!item) {
|
if (index < 0) {
|
||||||
items.push(newItem);
|
items.push(newItem);
|
||||||
} else {
|
} else {
|
||||||
items.splice(index, 1, newItem);
|
items.splice(index, 1, newItem);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "DELETED":
|
case "DELETED":
|
||||||
if (item) {
|
if (index >= 0) {
|
||||||
items.splice(index, 1);
|
items.splice(index, 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -12,7 +12,7 @@ interface WrappingFunction {
|
|||||||
<T>(result: T): T;
|
<T>(result: T): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cancelableFetch(reqInfo: RequestInfo, reqInit: RequestInit = {}) {
|
export function cancelableFetch(reqInfo: RequestInfo, reqInit: RequestInit = {}): CancelablePromise<any> {
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const signal = abortController.signal;
|
const signal = abortController.signal;
|
||||||
const cancel = abortController.abort.bind(abortController);
|
const cancel = abortController.abort.bind(abortController);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user