diff --git a/open-lens/package.json b/open-lens/package.json index 838c37f897..ffb87c2d82 100644 --- a/open-lens/package.json +++ b/open-lens/package.json @@ -214,6 +214,7 @@ "@k8slens/routing": "^1.0.0", "@k8slens/run-many": "^1.0.0", "@k8slens/startable-stoppable": "^1.0.0", + "@k8slens/table": "6.5.0", "@k8slens/tooltip": "^1.0.0", "@k8slens/utilities": "^1.0.0", "@kubernetes/client-node": "^0.18.1", diff --git a/open-lens/src/renderer/add-remove-buttons.injectable.ts b/open-lens/src/renderer/add-remove-buttons.injectable.ts new file mode 100644 index 0000000000..e75843851c --- /dev/null +++ b/open-lens/src/renderer/add-remove-buttons.injectable.ts @@ -0,0 +1,11 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import { AddRemoveButtons } from "@k8slens/core/renderer"; +import { addOrRemoveButtonsInjectionToken } from "@k8slens/table"; + +const addRemoveButtonsInjectable = getInjectable({ + id: "add-remove-buttons-component", + instantiate: () => ({ Component: AddRemoveButtons }), + injectionToken: addOrRemoveButtonsInjectionToken, +}); + +export default addRemoveButtonsInjectable \ No newline at end of file diff --git a/open-lens/src/renderer/create-table-state.injectable.ts b/open-lens/src/renderer/create-table-state.injectable.ts new file mode 100644 index 0000000000..a183a92b64 --- /dev/null +++ b/open-lens/src/renderer/create-table-state.injectable.ts @@ -0,0 +1,10 @@ +import { createTableStateInjectionToken } from "@k8slens/table"; +import { getInjectable } from "@ogre-tools/injectable"; + +const createTableStateInjectable = getInjectable({ + id: "open-lens-table-state", + instantiate: () => () => {}, + injectionToken: createTableStateInjectionToken, +}); + +export default createTableStateInjectable \ No newline at end of file diff --git a/open-lens/src/renderer/table.injectable.ts b/open-lens/src/renderer/table.injectable.ts new file mode 100644 index 0000000000..c07f60ea6d --- /dev/null +++ b/open-lens/src/renderer/table.injectable.ts @@ -0,0 +1,11 @@ +import { tableComponentInjectionToken } from "@k8slens/table"; +import { getInjectable } from "@ogre-tools/injectable"; +import { Table } from "@k8slens/core/renderer"; + +const tableComponentInjectable = getInjectable({ + id: "table-component", + instantiate: () => ({ Component: Table }), + injectionToken: tableComponentInjectionToken, +}); + +export default tableComponentInjectable \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9278ce31fd..396e39a131 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3883,6 +3883,10 @@ "resolved": "packages/utility-features/startable-stoppable", "link": true }, + "node_modules/@k8slens/table": { + "resolved": "packages/table", + "link": true + }, "node_modules/@k8slens/test-utils": { "resolved": "packages/utility-features/test-utils", "link": true @@ -34013,6 +34017,7 @@ "@k8slens/routing": "^1.0.0", "@k8slens/run-many": "^1.0.0", "@k8slens/startable-stoppable": "^1.0.0", + "@k8slens/table": "6.5.0", "@k8slens/tooltip": "^1.0.0", "@k8slens/utilities": "^1.0.0", "@kubernetes/client-node": "^0.18.1", @@ -34216,6 +34221,7 @@ "packages/core": { "name": "@k8slens/core", "version": "6.5.0", + "hasInstallScript": true, "license": "MIT", "devDependencies": { "@async-fn/jest": "1.6.4", @@ -34357,6 +34363,7 @@ "@k8slens/routing": "^1.0.0-alpha.5", "@k8slens/run-many": "^1.0.0-alpha.1", "@k8slens/startable-stoppable": "^1.0.0-alpha.1", + "@k8slens/table": "6.5.0", "@k8slens/tooltip": "^1.0.0-alpha.5", "@k8slens/utilities": "^1.0.0-alpha.1", "@kubernetes/client-node": "^0.18.1", @@ -35383,6 +35390,18 @@ "rimraf": "^4.4.1" } }, + "packages/table": { + "name": "@k8slens/table", + "version": "6.5.0", + "license": "MIT", + "devDependencies": { + "@k8slens/webpack": "^6.5.0-alpha.8", + "rimraf": "^4.4.1" + }, + "peerDependencies": { + "@ogre-tools/injectable": "^17.1.0" + } + }, "packages/technical-features/application/agnostic": { "name": "@k8slens/application", "version": "6.5.0", diff --git a/packages/core/package.json b/packages/core/package.json index e830c28b8d..815cdc936b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -55,7 +55,8 @@ "test:unit": "jest --testPathIgnorePatterns integration", "test:watch": "func() { jest ${1} --watch --testPathIgnorePatterns integration; }; func", "lint": "PROD=true eslint --ext js,ts,tsx --max-warnings=0 .", - "lint:fix": "npm run lint -- --fix" + "lint:fix": "npm run lint -- --fix", + "postinstall": "linkable" }, "config": { "k8sProxyVersion": "0.3.0", @@ -238,6 +239,7 @@ "@k8slens/run-many": "^1.0.0-alpha.1", "@k8slens/spinner": "^1.0.0", "@k8slens/startable-stoppable": "^1.0.0-alpha.1", + "@k8slens/table": "6.5.0", "@k8slens/tooltip": "^1.0.0-alpha.5", "@k8slens/utilities": "^1.0.0-alpha.1", "@kubernetes/client-node": "^0.18.1", diff --git a/packages/core/src/renderer/components/item-object-list/content.tsx b/packages/core/src/renderer/components/item-object-list/content.tsx index dde58ca7ee..514bd000f3 100644 --- a/packages/core/src/renderer/components/item-object-list/content.tsx +++ b/packages/core/src/renderer/components/item-object-list/content.tsx @@ -11,14 +11,12 @@ import { computed, makeObservable } from "mobx"; import { Observer, observer } from "mobx-react"; import type { ConfirmDialogParams } from "../confirm-dialog"; import type { TableProps, TableRowProps, TableSortCallbacks } from "../table"; -import { Table, TableCell, TableHead, TableRow } from "../table"; +import { TableCell, TableHead, TableRow } from "../table"; import type { IClassName, StrictReactNode } from "@k8slens/utilities"; import { cssNames, isDefined, isReactNode, noop, prevDefault, stopPropagation } from "@k8slens/utilities"; -import type { AddRemoveButtonsProps } from "../add-remove-buttons"; -import { AddRemoveButtons } from "../add-remove-buttons"; import { NoItems } from "../no-items"; import { Spinner } from "@k8slens/spinner"; -import type { ItemObject, TableCellProps } from "@k8slens/list-layout"; +import type { GeneralKubeObjectListLayoutColumn, ItemObject, TableCellProps } from "@k8slens/list-layout"; import type { Filter, PageFiltersStore } from "./page-filters/store"; import type { LensTheme } from "../../themes/lens-theme"; import { MenuActions } from "../menu/menu-actions"; @@ -35,6 +33,9 @@ import type { ToggleTableColumnVisibility } from "../../../features/user-prefere import toggleTableColumnVisibilityInjectable from "../../../features/user-preferences/common/toggle-table-column-visibility.injectable"; import type { IsTableColumnHidden } from "../../../features/user-preferences/common/is-table-column-hidden.injectable"; import isTableColumnHiddenInjectable from "../../../features/user-preferences/common/is-table-column-hidden.injectable"; +import type { AddOrRemoveButtons, AddRemoveButtonsProps, TableComponent } from "@k8slens/table"; +import { addOrRemoveButtonsInjectionToken, tableComponentInjectionToken } from "@k8slens/table"; +import { tableStateInjectable } from "../table/table-state.injectable"; export interface ItemListLayoutContentProps { getFilters: () => Filter[]; @@ -54,6 +55,7 @@ export interface ItemListLayoutContentProps Partial>; addRemoveButtons?: Partial; virtual?: boolean; + columns?: GeneralKubeObjectListLayoutColumn[]; // item details view hasDetailsView?: boolean; @@ -79,6 +81,9 @@ interface Dependencies { openConfirmDialog: OpenConfirmDialog; toggleTableColumnVisibility: ToggleTableColumnVisibility; isTableColumnHidden: IsTableColumnHidden; + table: TableComponent; + addOrRemoveButtons: AddOrRemoveButtons; + tableState: object; } @observer @@ -299,6 +304,7 @@ class NonInjectedItemListLayoutContent< const { store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem, className, tableProps = {}, tableId, getItems, activeTheme, + table, addOrRemoveButtons, tableState, } = this.props; const selectedItemId = detailsItem && detailsItem.getId(); const classNames = cssNames(className, "box", "grow", activeTheme.get().type); @@ -307,7 +313,7 @@ class NonInjectedItemListLayoutContent< return (
- {this.renderTableHeader()} {this.renderItems()} -
+ {() => ( - 0 ? () => this.removeItemsDialog(selectedItems) @@ -385,5 +392,8 @@ export const ItemListLayoutContent = withInjectables(props: ItemListLayoutContentProps) => React.ReactElement; diff --git a/packages/core/src/renderer/components/item-object-list/list-layout.tsx b/packages/core/src/renderer/components/item-object-list/list-layout.tsx index 32f46e7a2a..2bbf95e45c 100644 --- a/packages/core/src/renderer/components/item-object-list/list-layout.tsx +++ b/packages/core/src/renderer/components/item-object-list/list-layout.tsx @@ -13,7 +13,7 @@ import type { TableProps, TableRowProps, TableSortCallbacks } from "../table"; import type { IClassName, StrictReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities"; import type { AddRemoveButtonsProps } from "../add-remove-buttons"; -import type { ItemObject, TableCellProps } from "@k8slens/list-layout"; +import type { ItemObject, TableCellProps, GeneralKubeObjectListLayoutColumn } from "@k8slens/list-layout"; import type { SearchInputUrlProps } from "../input"; import type { PageFiltersStore } from "./page-filters/store"; import { FilterType } from "./page-filters/store"; @@ -98,6 +98,7 @@ export type ItemListLayoutProps; customizeHeader?: HeaderCustomizer | HeaderCustomizer[]; + columns?: GeneralKubeObjectListLayoutColumn[]; // items list configuration isReady?: boolean; // show loading indicator while not ready diff --git a/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index 9280d55ac8..2bbadc144e 100644 --- a/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -42,7 +42,7 @@ export interface KubeObjectListLayoutProps< // eslint-disable-next-line unused-imports/no-unused-vars-ts, @typescript-eslint/no-unused-vars A extends KubeApi, D extends KubeJsonApiDataFor, -> extends Omit, "getItems" | "dependentStores" | "preloadStores"> { +> extends Omit, "getItems" | "dependentStores" | "preloadStores" | "columns"> { items?: K[]; getItems?: () => K[]; store: KubeItemListStore; @@ -193,6 +193,7 @@ class NonInjectedKubeObjectListLayout< getItems={() => this.props.items || store.contextItems} preloadStores={false} // loading handled in kubeWatchApi.subscribeStores() detailsItem={this.selectedItem} + columns={targetColumns as GeneralKubeObjectListLayoutColumn[]} customizeHeader={[ ({ filters, searchProps, info, ...headerPlaceHolders }) => ({ filters: ( diff --git a/packages/core/src/renderer/components/table/table-data-context.ts b/packages/core/src/renderer/components/table/table-data-context.ts new file mode 100644 index 0000000000..162ad6f668 --- /dev/null +++ b/packages/core/src/renderer/components/table/table-data-context.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import React from "react"; +import type { KubeObject } from "@k8slens/kube-object"; +import type { + BaseKubeObjectListLayoutColumn, + GeneralKubeObjectListLayoutColumn, + SpecificKubeListLayoutColumn, +} from "@k8slens/list-layout"; +import type { ItemListLayoutContentProps } from "../item-object-list/content"; + +export type TableContextRequiredDataFromComponentsLayerAbove< + K extends KubeObject, +> = Pick< + ItemListLayoutContentProps, + | "tableId" + | "getFilters" + | "renderItemMenu" + | "store" + | "onDetails" + | "hasDetailsView" + | "getItems" + | "renderTableHeader" + | "renderTableContents" + | "sortingCallbacks" + | "isSelectable" +>; + +export interface TableDataContextValue + extends TableContextRequiredDataFromComponentsLayerAbove { + columns?: ( + | BaseKubeObjectListLayoutColumn + | SpecificKubeListLayoutColumn + | GeneralKubeObjectListLayoutColumn + )[]; +} + +export const TableDataContext = React.createContext< + TableDataContextValue +>({} as any); diff --git a/packages/core/src/renderer/components/table/table-state.injectable.tsx b/packages/core/src/renderer/components/table/table-state.injectable.tsx new file mode 100644 index 0000000000..ee38d81bdc --- /dev/null +++ b/packages/core/src/renderer/components/table/table-state.injectable.tsx @@ -0,0 +1,197 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { MenuActions, MenuItem } from "../menu"; +import React from "react"; +import { action, computed } from "mobx"; +import { isReactNode, stopPropagation } from "@k8slens/utilities"; +import type { KubeObject } from "@k8slens/kube-object"; +import { Checkbox } from "../checkbox"; +import type { TableCellProps } from "@k8slens/list-layout"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import type { TableDataContextValue } from "./table-data-context"; +import type { CreateTableState } from "@k8slens/table"; +import { createTableStateInjectionToken } from "@k8slens/table"; + +interface TableDataRow { + id: string; + data: DataItem; + index?: number; + className?: string; +} + +interface TableDataColumn { + id: string; + title: React.ReactNode; + className?: string; + style?: React.CSSProperties; + size?: string; + minSize?: number; + resizable?: boolean; + draggable?: boolean; + sortable?: boolean; + renderValue?: (row: TableDataRow) => React.ReactNode; + sortValue?: (row: TableDataRow, col: TableDataColumn) => string | number; + searchValue?: (row: TableDataRow) => string; +} + +export function createLensTableState({ + tableId, + getFilters, + renderItemMenu, + store, + onDetails, + hasDetailsView, + getItems, + renderTableHeader, + renderTableContents, + sortingCallbacks, + isSelectable, + columns: contextColumns, +}: TableDataContextValue, createState: CreateTableState) { + const headers = renderTableHeader as Required[]; + + let headingColumns: TableDataColumn[] = headers.map( + ({ id: columnId, className, title }, index) => ({ + id: columnId ?? className, + className, + resizable: !!columnId, + sortable: !!columnId, + draggable: !!columnId, // e.g. warning-icon column in pods + title, + renderValue(row: TableDataRow) { + const content = + renderTableContents?.(row.data)[index] ?? + contextColumns + ?.find((col) => col.id === columnId) + ?.content?.(row.data); + + if (isReactNode(content)) { + return content; + } else { + const { className, title } = content as TableCellProps; + + return
{title}
; + } + }, + sortValue(row: TableDataRow, col: any) { + return sortingCallbacks?.[col.id]?.(row.data) as string; + }, + }), + ); + + const checkboxColumn: TableDataColumn = { + id: "checkbox", + className: "checkbox", + draggable: false, + sortable: false, + resizable: false, + get title() { + return ( +
+ tableState.toggleRowSelectionAll()} + /> +
+ ); + }, + renderValue({ id: rowId }: { id: string | number | symbol }) { + return ( +
+ { + if (tableState.selectedRowsId.has(rowId)) { + tableState.selectedRowsId.delete(rowId); + } else { + tableState.selectedRowsId.add(rowId); + } + })} + /> +
+ ); + }, + }; + + const menuColumn: TableDataColumn = { + id: "menu", + className: "menu", + resizable: false, + sortable: false, + draggable: false, + get title() { + return ( + + {headers + .filter((headerCell) => !headerCell.showWithColumn) + .map(({ id: columnId, title, className }) => ( + + { + if (tableState.hiddenColumns.has(columnId)) { + tableState.hiddenColumns.delete(columnId); + } else { + tableState.hiddenColumns.add(columnId); + } + })} + /> + + ))} + + ); + }, + renderValue(row: any) { + return ( +
{renderItemMenu?.(row.data, store)}
+ ); + }, + }; + + if (isSelectable) { + headingColumns = [checkboxColumn, ...headingColumns]; + } + + const tableState = createState({ + dataItems: computed(getItems), + headingColumns: [...headingColumns, menuColumn], + + searchBox: computed(() => { + return getFilters().find((item) => item.type === "search")?.value ?? ""; + }), + + customizeRows: () => ({ + className: `${hasDetailsView ? "withDetails" : ""}`, + onSelect(row: any, evt: any) { + if (evt.isPropagationStopped()) { + return; // e.g. click on `checkbox` (== select row) + } + + evt.stopPropagation(); + evt.preventDefault(); + onDetails?.(row.data); + }, + }), + }); + + return tableState; +} + +export const tableStateInjectable = getInjectable({ + id: "table-state", + instantiate(di, context: TableDataContextValue) { + const createState = di.inject(createTableStateInjectionToken); + + return createLensTableState(context, createState); + }, + lifecycle: lifecycleEnum.transient, +}); diff --git a/packages/core/src/renderer/library.ts b/packages/core/src/renderer/library.ts index 8ed023c559..ba2c7c2300 100644 --- a/packages/core/src/renderer/library.ts +++ b/packages/core/src/renderer/library.ts @@ -22,3 +22,5 @@ export * as ReactRouterDom from "react-router-dom"; export * as rendererExtensionApi from "../extensions/renderer-api"; export * as commonExtensionApi from "../extensions/common-api"; export { metricsFeature } from "../features/metrics/metrics-feature"; +export * from "./components/table"; +export * from "./components/add-remove-buttons"; diff --git a/packages/core/src/test-env/add-remove-buttons.injectable.ts b/packages/core/src/test-env/add-remove-buttons.injectable.ts new file mode 100644 index 0000000000..c1f0284331 --- /dev/null +++ b/packages/core/src/test-env/add-remove-buttons.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { addOrRemoveButtonsInjectionToken } from "@k8slens/table"; +import { AddRemoveButtons } from "../renderer/components/add-remove-buttons"; + +const addRemoveButtonsInjectable = getInjectable({ + id: "add-remove-buttons-component", + instantiate: () => ({ Component: AddRemoveButtons }), + injectionToken: addOrRemoveButtonsInjectionToken, +}); + +export default addRemoveButtonsInjectable; diff --git a/packages/core/src/test-env/create-table-state.injectable.ts b/packages/core/src/test-env/create-table-state.injectable.ts new file mode 100644 index 0000000000..c8eee23150 --- /dev/null +++ b/packages/core/src/test-env/create-table-state.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { createTableStateInjectionToken } from "@k8slens/table"; +import { getInjectable } from "@ogre-tools/injectable"; + +const createTableStateInjectable = getInjectable({ + id: "open-lens-table-state", + instantiate: () => () => {}, + injectionToken: createTableStateInjectionToken, +}); + +export default createTableStateInjectable; diff --git a/packages/core/src/test-env/table.injectable.ts b/packages/core/src/test-env/table.injectable.ts new file mode 100644 index 0000000000..1c2e603535 --- /dev/null +++ b/packages/core/src/test-env/table.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { tableComponentInjectionToken } from "@k8slens/table"; +import { getInjectable } from "@ogre-tools/injectable"; +import { Table } from "../renderer/components/table"; + +const tableComponentInjectable = getInjectable({ + id: "table-component", + instantiate: () => ({ Component: Table }), + injectionToken: tableComponentInjectionToken, +}); + +export default tableComponentInjectable; diff --git a/packages/table/README.md b/packages/table/README.md new file mode 100644 index 0000000000..bd25eb4ad7 --- /dev/null +++ b/packages/table/README.md @@ -0,0 +1,3 @@ +# Description + +The package exports tokens needed for table configuration. \ No newline at end of file diff --git a/packages/table/index.ts b/packages/table/index.ts new file mode 100644 index 0000000000..88958b6738 --- /dev/null +++ b/packages/table/index.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeObject } from "@k8slens/kube-object"; +import type { + BaseKubeObjectListLayoutColumn, + GeneralKubeObjectListLayoutColumn, + SpecificKubeListLayoutColumn, +} from "@k8slens/list-layout"; +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { IComputedValue, IObservableValue } from "mobx"; + +type Column = ( + | BaseKubeObjectListLayoutColumn + | SpecificKubeListLayoutColumn + | GeneralKubeObjectListLayoutColumn +); + +export interface TableComponentProps { + tableId?: string; + columns?: Column[]; + save?: (state: object) => void; + load?: (tableId: string) => object; +} + +export interface TableComponent { + Component: React.ComponentType; +} + +export interface AddRemoveButtonsProps extends React.HTMLAttributes { + onAdd?: () => void; + onRemove?: () => void; + addTooltip?: React.ReactNode; + removeTooltip?: React.ReactNode; +} + +export interface AddOrRemoveButtons { + Component: React.ComponentType; +} + +export type CreateTableState = (params: { + dataItems: IComputedValue; + headingColumns: object[]; + customizeRows?: (row: object) => object; + getRowId?: (dataItem: any) => string | number | symbol; + searchBox?: IComputedValue | IObservableValue; +}) => any; + +export const tableComponentInjectionToken = getInjectionToken({ + id: "table-component-injection-token", +}); + +export const addOrRemoveButtonsInjectionToken = getInjectionToken({ + id: "add-or-remove-buttons-injection-token", +}); + +export const createTableStateInjectionToken = getInjectionToken({ + id: "create-table-state-injection-token", +}) \ No newline at end of file diff --git a/packages/table/package.json b/packages/table/package.json new file mode 100644 index 0000000000..bb8a327735 --- /dev/null +++ b/packages/table/package.json @@ -0,0 +1,28 @@ +{ + "name": "@k8slens/table", + "version": "6.5.0", + "description": "Injection tokens for table and related components", + "license": "MIT", + "type": "commonjs", + "private": false, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "clean": "rimraf dist/", + "build": "lens-webpack-build" + }, + "devDependencies": { + "@k8slens/webpack": "^6.5.0-alpha.8", + "rimraf": "^4.4.1" + }, + "peerDependencies": { + "@ogre-tools/injectable": "^17.1.0" + } +} \ No newline at end of file diff --git a/packages/table/tsconfig.json b/packages/table/tsconfig.json new file mode 100644 index 0000000000..c440e25379 --- /dev/null +++ b/packages/table/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@k8slens/typescript/config/base.json", + "include": ["**/*.ts"] +} \ No newline at end of file diff --git a/packages/table/webpack.config.js b/packages/table/webpack.config.js new file mode 100644 index 0000000000..c1089b1b44 --- /dev/null +++ b/packages/table/webpack.config.js @@ -0,0 +1 @@ +module.exports = require("@k8slens/webpack").configForNode; \ No newline at end of file