mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
fixes & refactoring
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
31f1ebca61
commit
6cdf21e44b
@ -84,6 +84,15 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
|||||||
return semver.gt(getAppVersion(), this.lastSeenAppVersion);
|
return semver.gt(getAppVersion(), this.lastSeenAppVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setHiddenTableColumns(tableId: string, names: Set<string> | string[]) {
|
||||||
|
this.preferences.hiddenTableColumns[tableId] = Array.from(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
getHiddenTableColumns(tableId: string): Set<string> {
|
||||||
|
return new Set(this.preferences.hiddenTableColumns[tableId]);
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
resetKubeConfigPath() {
|
resetKubeConfigPath() {
|
||||||
this.kubeConfigPath = kubeConfigDefaultPath;
|
this.kubeConfigPath = kubeConfigDefaultPath;
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export class HelmChart {
|
|||||||
tillerVersion?: string;
|
tillerVersion?: string;
|
||||||
|
|
||||||
getId() {
|
getId() {
|
||||||
return `${this.apiVersion}/${this.name}@${this.getAppVersion()}`;
|
return this.digest;
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
|
|||||||
@ -19,8 +19,7 @@ import { lookupApiLink } from "../../api/kube-api";
|
|||||||
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
|
||||||
import { Badge } from "../badge";
|
import { Badge } from "../badge";
|
||||||
|
|
||||||
|
enum columnId {
|
||||||
enum sortBy {
|
|
||||||
name = "name",
|
name = "name",
|
||||||
namespace = "namespace",
|
namespace = "namespace",
|
||||||
containers = "containers",
|
containers = "containers",
|
||||||
@ -77,15 +76,15 @@ export class Pods extends React.Component<Props> {
|
|||||||
tableId = "workloads_pods"
|
tableId = "workloads_pods"
|
||||||
isConfigurable
|
isConfigurable
|
||||||
sortingCallbacks={{
|
sortingCallbacks={{
|
||||||
[sortBy.name]: (pod: Pod) => pod.getName(),
|
[columnId.name]: (pod: Pod) => pod.getName(),
|
||||||
[sortBy.namespace]: (pod: Pod) => pod.getNs(),
|
[columnId.namespace]: (pod: Pod) => pod.getNs(),
|
||||||
[sortBy.containers]: (pod: Pod) => pod.getContainers().length,
|
[columnId.containers]: (pod: Pod) => pod.getContainers().length,
|
||||||
[sortBy.restarts]: (pod: Pod) => pod.getRestartsCount(),
|
[columnId.restarts]: (pod: Pod) => pod.getRestartsCount(),
|
||||||
[sortBy.owners]: (pod: Pod) => pod.getOwnerRefs().map(ref => ref.kind),
|
[columnId.owners]: (pod: Pod) => pod.getOwnerRefs().map(ref => ref.kind),
|
||||||
[sortBy.qos]: (pod: Pod) => pod.getQosClass(),
|
[columnId.qos]: (pod: Pod) => pod.getQosClass(),
|
||||||
[sortBy.node]: (pod: Pod) => pod.getNodeName(),
|
[columnId.node]: (pod: Pod) => pod.getNodeName(),
|
||||||
[sortBy.age]: (pod: Pod) => pod.metadata.creationTimestamp,
|
[columnId.age]: (pod: Pod) => pod.metadata.creationTimestamp,
|
||||||
[sortBy.status]: (pod: Pod) => pod.getStatusMessage(),
|
[columnId.status]: (pod: Pod) => pod.getStatusMessage(),
|
||||||
}}
|
}}
|
||||||
searchFilters={[
|
searchFilters={[
|
||||||
(pod: Pod) => pod.getSearchFields(),
|
(pod: Pod) => pod.getSearchFields(),
|
||||||
@ -95,16 +94,16 @@ export class Pods extends React.Component<Props> {
|
|||||||
]}
|
]}
|
||||||
renderHeaderTitle="Pods"
|
renderHeaderTitle="Pods"
|
||||||
renderTableHeader={[
|
renderTableHeader={[
|
||||||
{ title: "Name", className: "name", sortBy: sortBy.name },
|
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
||||||
{ className: "warning", showWithColumn: "name" },
|
{ className: "warning", showWithColumn: columnId.name },
|
||||||
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace },
|
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
|
||||||
{ title: "Containers", className: "containers", sortBy: sortBy.containers },
|
{ title: "Containers", className: "containers", sortBy: columnId.containers, id: columnId.containers },
|
||||||
{ title: "Restarts", className: "restarts", sortBy: sortBy.restarts },
|
{ title: "Restarts", className: "restarts", sortBy: columnId.restarts, id: columnId.restarts },
|
||||||
{ title: "Controlled By", className: "owners", sortBy: sortBy.owners },
|
{ title: "Controlled By", className: "owners", sortBy: columnId.owners, id: columnId.owners },
|
||||||
{ title: "Node", className: "node", sortBy: sortBy.node },
|
{ title: "Node", className: "node", sortBy: columnId.node, id: columnId.node },
|
||||||
{ title: "QoS", className: "qos", sortBy: sortBy.qos },
|
{ title: "QoS", className: "qos", sortBy: columnId.qos, id: columnId.qos },
|
||||||
{ title: "Age", className: "age", sortBy: sortBy.age },
|
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
|
||||||
{ title: "Status", className: "status", sortBy: sortBy.status },
|
{ title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
|
||||||
]}
|
]}
|
||||||
renderTableContents={(pod: Pod) => [
|
renderTableContents={(pod: Pod) => [
|
||||||
<Badge flat key="name" label={pod.getName()} tooltip={pod.getName()} />,
|
<Badge flat key="name" label={pod.getName()} tooltip={pod.getName()} />,
|
||||||
|
|||||||
@ -36,3 +36,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ItemListLayoutVisibilityMenu {
|
||||||
|
.MenuItem {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Checkbox {
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import "./item-list-layout.scss";
|
import "./item-list-layout.scss";
|
||||||
import "./table-menu.scss";
|
|
||||||
import groupBy from "lodash/groupBy";
|
import groupBy from "lodash/groupBy";
|
||||||
|
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
@ -23,7 +22,6 @@ import { MenuActions } from "../menu/menu-actions";
|
|||||||
import { MenuItem } from "../menu";
|
import { MenuItem } from "../menu";
|
||||||
import { Checkbox } from "../checkbox";
|
import { Checkbox } from "../checkbox";
|
||||||
import { userStore } from "../../../common/user-store";
|
import { userStore } from "../../../common/user-store";
|
||||||
import logger from "../../../main/logger";
|
|
||||||
|
|
||||||
// todo: refactor, split to small re-usable components
|
// todo: refactor, split to small re-usable components
|
||||||
|
|
||||||
@ -101,11 +99,10 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
|
|
||||||
private watchDisposers: IReactionDisposer[] = [];
|
private watchDisposers: IReactionDisposer[] = [];
|
||||||
|
|
||||||
@observable hiddenColumnNames = new Set<string>();
|
|
||||||
@observable isUnmounting = false;
|
@observable isUnmounting = false;
|
||||||
|
|
||||||
@observable userSettings: ItemListLayoutUserSettings = {
|
@observable userSettings: ItemListLayoutUserSettings = {
|
||||||
showAppliedFilters: true,
|
showAppliedFilters: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: ItemListLayoutProps) {
|
constructor(props: ItemListLayoutProps) {
|
||||||
@ -122,9 +119,15 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
|
const { isClusterScoped, isConfigurable, tableId } = this.props;
|
||||||
|
|
||||||
|
if (isConfigurable && !tableId) {
|
||||||
|
throw new Error("[ItemListLayout]: configurable list require props.tableId to be specified");
|
||||||
|
}
|
||||||
|
|
||||||
this.loadStores();
|
this.loadStores();
|
||||||
|
|
||||||
if (!this.props.isClusterScoped) {
|
if (!isClusterScoped) {
|
||||||
disposeOnUnmount(this, [
|
disposeOnUnmount(this, [
|
||||||
namespaceStore.onContextChange(() => this.loadStores())
|
namespaceStore.onContextChange(() => this.loadStores())
|
||||||
]);
|
]);
|
||||||
@ -137,11 +140,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get stores() {
|
@computed get stores() {
|
||||||
const { store, dependentStores, tableId } = this.props;
|
const { store, dependentStores } = this.props;
|
||||||
|
|
||||||
if (this.canBeConfigured) {
|
|
||||||
this.hiddenColumnNames = new Set(userStore.preferences?.hiddenTableColumns?.[tableId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Set([store, ...dependentStores]);
|
return new Set([store, ...dependentStores]);
|
||||||
}
|
}
|
||||||
@ -249,42 +248,6 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
return this.applyFilters(filterItems, allItems);
|
return this.applyFilters(filterItems, allItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateColumnFilter(checkboxValue: boolean, columnName: string) {
|
|
||||||
if (checkboxValue) {
|
|
||||||
this.hiddenColumnNames.delete(columnName);
|
|
||||||
} else {
|
|
||||||
this.hiddenColumnNames.add(columnName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.canBeConfigured) {
|
|
||||||
userStore.preferences.hiddenTableColumns[this.props.tableId] = Array.from(this.hiddenColumnNames);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
columnIsVisible(index: number): boolean {
|
|
||||||
const { renderTableHeader } = this.props;
|
|
||||||
|
|
||||||
if (!this.canBeConfigured) return true;
|
|
||||||
|
|
||||||
return !this.hiddenColumnNames.has(renderTableHeader[index].showWithColumn ?? renderTableHeader[index].className);
|
|
||||||
}
|
|
||||||
|
|
||||||
get canBeConfigured(): boolean {
|
|
||||||
const { isConfigurable, tableId, renderTableHeader } = this.props;
|
|
||||||
|
|
||||||
if (!isConfigurable || !tableId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!renderTableHeader?.every(({ className }) => className)) {
|
|
||||||
logger.warning("[ItemObjectList]: cannot configure an object list without all headers being identifiable");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
getRow(uid: string) {
|
getRow(uid: string) {
|
||||||
const {
|
const {
|
||||||
@ -316,20 +279,18 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{
|
{
|
||||||
renderTableContents(item)
|
renderTableContents(item).map((content, index) => {
|
||||||
.map((content, index) => {
|
const cellProps: TableCellProps = isReactNode(content) ? { children: content } : content;
|
||||||
const cellProps: TableCellProps = isReactNode(content) ? { children: content } : content;
|
const headCell = renderTableHeader?.[index];
|
||||||
|
|
||||||
if (copyClassNameFromHeadCells && renderTableHeader) {
|
if (copyClassNameFromHeadCells && headCell) {
|
||||||
const headCell = renderTableHeader[index];
|
cellProps.className = cssNames(cellProps.className, headCell.className);
|
||||||
|
}
|
||||||
|
|
||||||
if (headCell) {
|
if (!headCell || !this.isHiddenColumn(headCell)) {
|
||||||
cellProps.className = cssNames(cellProps.className, headCell.className);
|
return <TableCell key={index} {...cellProps} />;
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
return this.columnIsVisible(index) ? <TableCell key={index} {...cellProps} /> : null;
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
{renderItemMenu && (
|
{renderItemMenu && (
|
||||||
<TableCell className="menu" onClick={stopPropagation}>
|
<TableCell className="menu" onClick={stopPropagation}>
|
||||||
@ -458,10 +419,40 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderTableHeader() {
|
||||||
|
const { renderTableHeader, isSelectable, isConfigurable, store } = this.props;
|
||||||
|
|
||||||
|
if (!renderTableHeader) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableHead showTopLine nowrap>
|
||||||
|
{isSelectable && (
|
||||||
|
<TableCell
|
||||||
|
checkbox
|
||||||
|
isChecked={store.isSelectedAll(this.items)}
|
||||||
|
onClick={prevDefault(() => store.toggleSelectionAll(this.items))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{renderTableHeader.map((cellProps, index) => {
|
||||||
|
if (!this.isHiddenColumn(cellProps)) {
|
||||||
|
return <TableCell key={cellProps.id ?? index} {...cellProps} />;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
{isConfigurable && (
|
||||||
|
<TableCell className="menu">
|
||||||
|
{this.renderColumnVisibilityMenu()}
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderList() {
|
renderList() {
|
||||||
const {
|
const {
|
||||||
isSelectable, tableProps = {}, renderTableHeader, renderItemMenu,
|
store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem,
|
||||||
store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem
|
tableProps = {},
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { isReady, removeItemsDialog, items } = this;
|
const { isReady, removeItemsDialog, items } = this;
|
||||||
const { selectedItems } = store;
|
const { selectedItems } = store;
|
||||||
@ -486,22 +477,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
className: cssNames("box grow", tableProps.className, themeStore.activeTheme.type),
|
className: cssNames("box grow", tableProps.className, themeStore.activeTheme.type),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{renderTableHeader && (
|
{this.renderTableHeader()}
|
||||||
<TableHead showTopLine nowrap>
|
|
||||||
{isSelectable && (
|
|
||||||
<TableCell
|
|
||||||
checkbox
|
|
||||||
isChecked={store.isSelectedAll(items)}
|
|
||||||
onClick={prevDefault(() => store.toggleSelectionAll(items))}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{renderTableHeader.map((cellProps, index) => this.columnIsVisible(index) ? <TableCell key={index} {...cellProps} /> : null)}
|
|
||||||
{renderItemMenu && <TableCell className="menu">
|
|
||||||
{this.canBeConfigured && this.renderColumnMenu()}
|
|
||||||
</TableCell>
|
|
||||||
}
|
|
||||||
</TableHead>
|
|
||||||
)}
|
|
||||||
{
|
{
|
||||||
!virtual && items.map(item => this.getRow(item.getId()))
|
!virtual && items.map(item => this.getRow(item.getId()))
|
||||||
}
|
}
|
||||||
@ -517,23 +493,44 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderColumnMenu() {
|
@computed get hiddenColumns() {
|
||||||
|
return userStore.getHiddenTableColumns(this.props.tableId);
|
||||||
|
}
|
||||||
|
|
||||||
|
isHiddenColumn({ id: columnId, showWithColumn }: TableCellProps): boolean {
|
||||||
|
if (!this.props.isConfigurable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.hiddenColumns.has(columnId) || (
|
||||||
|
showWithColumn && this.hiddenColumns.has(showWithColumn)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateColumnVisibility({ id: columnId }: TableCellProps, isVisible: boolean) {
|
||||||
|
const hiddenColumns = new Set(this.hiddenColumns);
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
hiddenColumns.add(columnId);
|
||||||
|
} else {
|
||||||
|
hiddenColumns.delete(columnId);
|
||||||
|
}
|
||||||
|
|
||||||
|
userStore.setHiddenTableColumns(this.props.tableId, hiddenColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderColumnVisibilityMenu() {
|
||||||
const { renderTableHeader } = this.props;
|
const { renderTableHeader } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuActions
|
<MenuActions className="ItemListLayoutVisibilityMenu" toolbar={false} autoCloseOnSelect={false}>
|
||||||
toolbar={false}
|
|
||||||
autoCloseOnSelect={false}
|
|
||||||
className={cssNames("KubeObjectMenu")}
|
|
||||||
>
|
|
||||||
{renderTableHeader.map((cellProps, index) => (
|
{renderTableHeader.map((cellProps, index) => (
|
||||||
!cellProps.showWithColumn && (
|
!cellProps.showWithColumn && (
|
||||||
<MenuItem key={index} className="input">
|
<MenuItem key={index} className="input">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={cellProps.title ?? `<${cellProps.className}>`}
|
label={cellProps.title ?? `<${cellProps.className}>`}
|
||||||
className="MenuCheckbox"
|
value={!this.isHiddenColumn(cellProps)}
|
||||||
value={!this.hiddenColumnNames.has(cellProps.className)}
|
onChange={isVisible => this.updateColumnVisibility(cellProps, isVisible)}
|
||||||
onChange={(v) => this.updateColumnFilter(v, cellProps.className)}
|
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
.MenuCheckbox {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
@ -9,13 +9,14 @@ 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 extends React.DOMAttributes<HTMLDivElement> {
|
||||||
|
id?: string; // used for configuration visibility of columns
|
||||||
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?: TableSortBy; // column name, must be same as key in sortable object <Table sortable={}/>
|
||||||
showWithColumn?: string // className of another column, if it is not empty the current column is not shown in the filter menu, visibility of this one is the same as a specified column, applicable to headers only
|
showWithColumn?: string // id of the column which follow same visibility rules
|
||||||
_sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!)
|
_sorting?: Partial<TableSortParams>; // <Table> sorting state, don't use this prop outside (!)
|
||||||
_sort?(sortBy: TableSortBy): void; // <Table> sort function, don't use this prop outside (!)
|
_sort?(sortBy: TableSortBy): 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 (!)
|
||||||
@ -73,7 +74,7 @@ export class TableCell extends React.Component<TableCellProps> {
|
|||||||
const content = displayBooleans(displayBoolean, title || children);
|
const content = displayBooleans(displayBoolean, title || children);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...cellProps} id={className} className={classNames} onClick={this.onClick}>
|
<div {...cellProps} className={classNames} onClick={this.onClick}>
|
||||||
{this.renderCheckbox()}
|
{this.renderCheckbox()}
|
||||||
{_nowrap ? <div className="content">{content}</div> : content}
|
{_nowrap ? <div className="content">{content}</div> : content}
|
||||||
{this.renderSortIcon()}
|
{this.renderSortIcon()}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user