mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/master' into load_resources_per_namespaces
# Conflicts: # src/renderer/components/item-object-list/item-list-layout.tsx
This commit is contained in:
commit
ff89638787
18 files changed
+203
-139
No files matched your search
@@ -4,7 +4,7 @@ import React, { Fragment } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { TabLayout } from "../layout/tab-layout";
|
||||
import { eventStore } from "./event.store";
|
||||
import { KubeObjectListLayout, KubeObjectListLayoutProps, getDetailsUrl } from "../kube-object";
|
||||
import { getDetailsUrl, KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object";
|
||||
import { KubeEvent } from "../../api/endpoints/events.api";
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -65,7 +65,7 @@ export class Events extends React.Component<Props> {
|
||||
small
|
||||
material="help_outline"
|
||||
className="help-icon"
|
||||
tooltip="Limited to {eventStore.limit}"
|
||||
tooltip={`Limited to ${eventStore.limit}`}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { IResourceMetricsValue, ResourceMetricsContext } from "../resource-metri
|
||||
import { observer } from "mobx-react";
|
||||
import { ChartOptions, ChartPoint } from "chart.js";
|
||||
import { themeStore } from "../../theme.store";
|
||||
import { mapValues } from "lodash";
|
||||
|
||||
type IContext = IResourceMetricsValue<Node, { metrics: IClusterMetrics }>;
|
||||
|
||||
@@ -15,26 +16,26 @@ export const NodeCharts = observer(() => {
|
||||
const id = object.getId();
|
||||
const { chartCapacityColor } = themeStore.activeTheme.colors;
|
||||
|
||||
if (!metrics) return null;
|
||||
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
||||
if (!metrics) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const values = Object.values(metrics).map(metric =>
|
||||
normalizeMetrics(metric).data.result[0].values
|
||||
);
|
||||
const [
|
||||
if (isMetricsEmpty(metrics)) {
|
||||
return <NoMetrics />;
|
||||
}
|
||||
|
||||
const {
|
||||
memoryUsage,
|
||||
memoryRequests,
|
||||
_memoryLimits, // eslint-disable-line unused-imports/no-unused-vars-ts
|
||||
memoryCapacity,
|
||||
cpuUsage,
|
||||
cpuRequests,
|
||||
_cpuLimits, // eslint-disable-line unused-imports/no-unused-vars-ts
|
||||
cpuCapacity,
|
||||
podUsage,
|
||||
podCapacity,
|
||||
fsSize,
|
||||
fsUsage
|
||||
] = values;
|
||||
} = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0].values);
|
||||
|
||||
const datasets = [
|
||||
// CPU
|
||||
|
||||
@@ -74,6 +74,8 @@ export class Pods extends React.Component<Props> {
|
||||
<KubeObjectListLayout
|
||||
className="Pods" store={podsStore}
|
||||
dependentStores={[volumeClaimStore, eventStore]}
|
||||
tableId = "workloads_pods"
|
||||
isConfigurable
|
||||
sortingCallbacks={{
|
||||
[sortBy.name]: (pod: Pod) => pod.getName(),
|
||||
[sortBy.namespace]: (pod: Pod) => pod.getNs(),
|
||||
@@ -94,7 +96,7 @@ export class Pods extends React.Component<Props> {
|
||||
renderHeaderTitle="Pods"
|
||||
renderTableHeader={[
|
||||
{ title: "Name", className: "name", sortBy: sortBy.name },
|
||||
{ className: "warning" },
|
||||
{ className: "warning", showWithColumn: "name" },
|
||||
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace },
|
||||
{ title: "Containers", className: "containers", sortBy: sortBy.containers },
|
||||
{ title: "Restarts", className: "restarts", sortBy: sortBy.restarts },
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
height: 18px; // Must be equal to lineHeight variable in pod-log-list.tsx
|
||||
font-family: var(--font-monospace);
|
||||
font-size: smaller;
|
||||
white-space: pre;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: var(--logRowHoverBackground);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "./item-list-layout.scss";
|
||||
import "./table-menu.scss";
|
||||
import groupBy from "lodash/groupBy";
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
@@ -18,6 +19,11 @@ import { PageFiltersList } from "./page-filters-list";
|
||||
import { PageFiltersSelect } from "./page-filters-select";
|
||||
import { NamespaceSelectFilter } from "../+namespaces/namespace-select";
|
||||
import { themeStore } from "../../theme.store";
|
||||
import { MenuActions} from "../menu/menu-actions";
|
||||
import { MenuItem } from "../menu";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import { userStore } from "../../../common/user-store";
|
||||
import logger from "../../../main/logger";
|
||||
|
||||
// todo: refactor, split to small re-usable components
|
||||
|
||||
@@ -32,6 +38,7 @@ interface IHeaderPlaceholders {
|
||||
}
|
||||
|
||||
export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
||||
tableId?: string;
|
||||
className: IClassName;
|
||||
store: ItemStore<T>;
|
||||
dependentStores?: ItemStore[];
|
||||
@@ -50,6 +57,7 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
||||
isReady?: boolean; // show loading indicator while not ready
|
||||
isSelectable?: boolean; // show checkbox in rows for selecting items
|
||||
isSearchable?: boolean; // apply search-filter & add search-input
|
||||
isConfigurable?: boolean;
|
||||
copyClassNameFromHeadCells?: boolean;
|
||||
sortingCallbacks?: { [sortBy: string]: TableSortCallback };
|
||||
tableProps?: Partial<TableProps>; // low-level table configuration
|
||||
@@ -74,6 +82,7 @@ const defaultProps: Partial<ItemListLayoutProps> = {
|
||||
showHeader: true,
|
||||
isSearchable: true,
|
||||
isSelectable: true,
|
||||
isConfigurable: false,
|
||||
copyClassNameFromHeadCells: true,
|
||||
dependentStores: [],
|
||||
filterItems: [],
|
||||
@@ -92,6 +101,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
|
||||
private watchDisposers: IReactionDisposer[] = [];
|
||||
|
||||
@observable hiddenColumnNames = new Set<string>();
|
||||
@observable isUnmounting = false;
|
||||
|
||||
@observable userSettings: ItemListLayoutUserSettings = {
|
||||
@@ -127,7 +137,10 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
}
|
||||
|
||||
@computed get stores() {
|
||||
const { store, dependentStores, isClusterScoped } = this.props;
|
||||
const { store, dependentStores, isClusterScoped, tableId } = this.props;
|
||||
|
||||
if (this.canBeConfigured) this.hiddenColumnNames = new Set(userStore.preferences?.hiddenTableColumns?.[tableId]);
|
||||
|
||||
const stores = new Set([store, ...dependentStores]);
|
||||
|
||||
if (!isClusterScoped) {
|
||||
@@ -240,6 +253,42 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
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()
|
||||
getRow(uid: string) {
|
||||
const {
|
||||
@@ -283,7 +332,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
}
|
||||
}
|
||||
|
||||
return <TableCell key={index} {...cellProps} />;
|
||||
return this.columnIsVisible(index) ? <TableCell key={index} {...cellProps} /> : null;
|
||||
})
|
||||
}
|
||||
{renderItemMenu && (
|
||||
@@ -450,14 +499,18 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
onClick={prevDefault(() => store.toggleSelectionAll(items))}
|
||||
/>
|
||||
)}
|
||||
{renderTableHeader.map((cellProps, index) => <TableCell key={index} {...cellProps} />)}
|
||||
{renderItemMenu && <TableCell className="menu"/>}
|
||||
{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()))
|
||||
}
|
||||
</Table>
|
||||
|
||||
)}
|
||||
<AddRemoveButtons
|
||||
onRemove={selectedItems.length ? removeItemsDialog : null}
|
||||
@@ -468,6 +521,29 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||
);
|
||||
}
|
||||
|
||||
renderColumnMenu() {
|
||||
const { renderTableHeader} = this.props;
|
||||
|
||||
return (
|
||||
<MenuActions
|
||||
toolbar = {false}
|
||||
autoCloseOnSelect = {false}
|
||||
className={cssNames("KubeObjectMenu")}
|
||||
>
|
||||
{renderTableHeader.map((cellProps, index) => (
|
||||
!cellProps.showWithColumn &&
|
||||
<MenuItem key={index} className="input">
|
||||
<Checkbox label = {cellProps.title ?? `<${cellProps.className}>`}
|
||||
className = "MenuCheckbox"
|
||||
value ={!this.hiddenColumnNames.has(cellProps.className)}
|
||||
onChange = {(v) => this.updateColumnFilter(v, cellProps.className)}
|
||||
/>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuActions>
|
||||
);
|
||||
}
|
||||
|
||||
renderFooter() {
|
||||
if (this.props.renderFooter) {
|
||||
return this.props.renderFooter(this);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.MenuCheckbox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import isString from "lodash/isString";
|
||||
export interface MenuActionsProps extends Partial<MenuProps> {
|
||||
className?: string;
|
||||
toolbar?: boolean; // display menu as toolbar with icons
|
||||
autoCloseOnSelect?: boolean;
|
||||
triggerIcon?: string | IconProps | React.ReactNode;
|
||||
removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode);
|
||||
updateAction?(): void;
|
||||
@@ -80,7 +81,7 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
||||
|
||||
render() {
|
||||
const {
|
||||
className, toolbar, children, updateAction, removeAction, triggerIcon, removeConfirmationMessage,
|
||||
className, toolbar, autoCloseOnSelect, children, updateAction, removeAction, triggerIcon, removeConfirmationMessage,
|
||||
...menuProps
|
||||
} = this.props;
|
||||
const menuClassName = cssNames("MenuActions flex", className, {
|
||||
@@ -98,7 +99,7 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
||||
className={menuClassName}
|
||||
usePortal={autoClose}
|
||||
closeOnScroll={autoClose}
|
||||
closeOnClickItem={autoClose}
|
||||
closeOnClickItem={autoCloseOnSelect ?? autoClose }
|
||||
closeOnClickOutside={autoClose}
|
||||
{...menuProps}
|
||||
>
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
|
||||
isChecked?: boolean; // mark checkbox as checked or not
|
||||
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={}/>
|
||||
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
|
||||
_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 (!)
|
||||
_nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!)
|
||||
@@ -63,7 +64,7 @@ export class TableCell extends React.Component<TableCellProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, checkbox, isChecked, sortBy, _sort, _sorting, _nowrap, children, title, renderBoolean: displayBoolean, ...cellProps } = this.props;
|
||||
const { className, checkbox, isChecked, sortBy, _sort, _sorting, _nowrap, children, title, renderBoolean: displayBoolean, showWithColumn, ...cellProps } = this.props;
|
||||
const classNames = cssNames("TableCell", className, {
|
||||
checkbox,
|
||||
nowrap: _nowrap,
|
||||
|
||||
Reference in new issue
Block a user