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

Cleaning settings page view (#3156)

* Making inputs consistent

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fixing hover effect in inputs

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding separators to sidebar menu

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fine-tuning general section

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fine-tuning hidden metrics area

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* EntityIcon in Settings sidebar

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Moving cluster icon settings on top

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Shrink Apply button a big

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
Alex Andreev 2021-06-23 11:27:26 +03:00 committed by GitHub
parent 1cc5607987
commit 7739b387cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 159 additions and 100 deletions

View File

@ -281,7 +281,9 @@ export class MetricsSettings extends React.Component<Props> {
waiting={this.inProgress}
onClick={() => this.save()}
primary
disabled={!this.changed} />
disabled={!this.changed}
className="w-60 h-14"
/>
{this.canUpgrade && (<small className="hint">
An update is available for enabled metrics components.

View File

@ -33,6 +33,7 @@ import { EntitySettingRegistry } from "../../../extensions/registries";
import type { EntitySettingsRouteParams } from "../../../common/routes";
import { groupBy } from "lodash";
import { SettingLayout } from "../layout/setting-layout";
import { HotbarIcon } from "../hotbar/hotbar-icon";
interface Props extends RouteComponentProps<EntitySettingsRouteParams> {
}
@ -83,10 +84,19 @@ export class EntitySettings extends React.Component<Props> {
return (
<>
<div className="flex items-center pb-8">
<HotbarIcon
uid={this.entity.metadata.uid}
title={this.entity.metadata.name}
source={this.entity.metadata.source}
src={this.entity.spec.icon?.src}
/>
<h2>{this.entity.metadata.name}</h2>
</div>
<Tabs className="flex column" scrollable={false} onChange={this.onTabChange} value={this.activeTab}>
{ groups.map((group, groupIndex) => (
<React.Fragment key={`group-${groupIndex}`}>
<hr/>
<div className="header">{group[0]}</div>
{ group[1].map((setting, index) => (
<Tab

View File

@ -45,6 +45,7 @@ import { Install } from "./install";
import { InstalledExtensions } from "./installed-extensions";
import { Notice } from "./notice";
import { SettingLayout } from "../layout/setting-layout";
import { docsUrl } from "../../../common/vars";
function getMessageFromError(error: any): string {
if (!error || typeof error !== "object") {
@ -514,7 +515,13 @@ export class Extensions extends React.Component<Props> {
<section>
<h1>Extensions</h1>
<Notice/>
<Notice>
<p>
Add new features via Lens Extensions.{" "}
Check out <a href={`${docsUrl}/extensions/`} target="_blank" rel="noreferrer">docs</a>{" "}
and list of <a href="https://github.com/lensapp/lens-extensions/blob/main/README.md" target="_blank" rel="noreferrer">available extensions</a>.
</p>
</Notice>
<Install
supportedFormats={supportedFormats}

View File

@ -20,17 +20,14 @@
*/
import styles from "./notice.module.css";
import React from "react";
import { docsUrl } from "../../../common/vars";
import React, { DOMAttributes } from "react";
export function Notice() {
interface Props extends DOMAttributes<any> {}
export function Notice(props: Props) {
return (
<div className={styles.notice}>
<p>
Add new features via Lens Extensions.{" "}
Check out <a href={`${docsUrl}/extensions/`} target="_blank" rel="noreferrer">docs</a>{" "}
and list of <a href="https://github.com/lensapp/lens-extensions/blob/main/README.md" target="_blank" rel="noreferrer">available extensions</a>.
</p>
{props.children}
</div>
);
}

View File

@ -40,12 +40,16 @@ export function GeneralSettings({ entity }: EntitySettingViewProps) {
return (
<section>
<section>
<div className="flex">
<div className="flex-grow pr-8">
<components.ClusterNameSetting cluster={cluster} />
</section>
<section>
</div>
<div>
<components.ClusterIconSetting cluster={cluster} entity={entity as KubernetesCluster} />
</div>
</div>
</section>
<section>
<section className="small">
<components.ClusterKubeconfig cluster={cluster} />
</section>
</section>
@ -106,10 +110,9 @@ export function MetricsSettings({ entity }: EntitySettingViewProps) {
<section>
<components.ClusterPrometheusSetting cluster={cluster} />
</section>
<hr/>
<section>
<components.ClusterMetricsSetting cluster={cluster} />
</section>
<section>
<components.ShowMetricsSetting cluster={cluster} />
</section>
</section>

View File

@ -54,6 +54,7 @@ export class ClusterAccessibleNamespaces extends React.Component<Props> {
this.namespaces.delete(oldNamesapce);
this.props.cluster.accessibleNamespaces = Array.from(this.namespaces);
}}
inputTheme="round-black"
/>
<small className="hint">
This setting is useful for manually specifying which namespaces you have access to. This is useful when you do not have permissions to list namespaces.

View File

@ -21,15 +21,13 @@
import React from "react";
import type { Cluster } from "../../../../main/cluster";
//import { FilePicker, OverSizeLimitStyle } from "../../file-picker";
import { boundMethod } from "../../../utils";
import { Button } from "../../button";
import { observable } from "mobx";
import { observer } from "mobx-react";
import { SubTitle } from "../../layout/sub-title";
import { HotbarIcon } from "../../hotbar/hotbar-icon";
import type { KubernetesCluster } from "../../../../common/catalog-entities";
import { FilePicker, OverSizeLimitStyle } from "../../file-picker";
import { MenuActions, MenuItem } from "../../menu";
enum GeneralInputStatus {
CLEAN = "clean",
@ -46,6 +44,8 @@ export class ClusterIconSetting extends React.Component<Props> {
@observable status = GeneralInputStatus.CLEAN;
@observable errorText?: string;
private element = React.createRef<HTMLDivElement>();
@boundMethod
async onIconPick([file]: File[]) {
const { cluster } = this.props;
@ -66,16 +66,11 @@ export class ClusterIconSetting extends React.Component<Props> {
}
}
getClearButton() {
if (this.props.cluster.preferences.icon) {
return <Button
label="Clear"
tooltip="Revert back to default icon"
onClick={() => this.onIconPick([])}
/>;
}
@boundMethod
onUploadClick() {
const input = this.element.current.querySelector("input[type=file]") as HTMLInputElement;
return null;
input.click();
}
render() {
@ -87,24 +82,32 @@ export class ClusterIconSetting extends React.Component<Props> {
title={entity.metadata.name}
source={entity.metadata.source}
src={entity.spec.icon?.src}
size={53}
/>
<span style={{marginRight: "var(--unit)"}}>Browse for new icon...</span>
</>
);
return (
<>
<SubTitle title="Cluster Icon" />
<div className="file-loader">
<div ref={this.element}>
<div className="file-loader flex flex-row items-center">
<div className="mr-5">
<FilePicker
accept="image/*"
label={label}
onOverSizeLimit={OverSizeLimitStyle.FILTER}
handler={this.onIconPick}
/>
{this.getClearButton()}
</div>
</>
<MenuActions toolbar={false} autoCloseOnSelect={true} triggerIcon={{ material: "more_horiz" }}>
<MenuItem onClick={this.onUploadClick}>
Upload Icon
</MenuItem>
<MenuItem onClick={() => this.onIconPick([])} disabled={!this.props.cluster.preferences.icon}>
Clear
</MenuItem>
</MenuActions>
</div>
</div>
);
}
}

View File

@ -25,6 +25,7 @@ import { observer } from "mobx-react";
import { SubTitle } from "../../layout/sub-title";
import { boundMethod } from "../../../../common/utils";
import { shell } from "electron";
import { Notice } from "../../+extensions/notice";
interface Props {
cluster: Cluster;
@ -42,14 +43,12 @@ export class ClusterKubeconfig extends React.Component<Props> {
render() {
return (
<>
<Notice>
<SubTitle title="Kubeconfig" />
<span>
<a className="link value" onClick={this.openKubeconfig}>{this.props.cluster.kubeConfigPath}</a>
</span>
</>
</Notice>
);
}
}

View File

@ -100,6 +100,7 @@ export class ClusterMetricsSetting extends React.Component<Props> {
options={Object.values(ClusterMetricsResourceType)}
onChange={this.onChangeSelect}
formatOptionLabel={this.formatOptionLabel}
themeName="lens"
/>
<Button
primary
@ -118,7 +119,7 @@ export class ClusterMetricsSetting extends React.Component<Props> {
render() {
return (
<div className="MetricsSelect">
<div className="MetricsSelec0 mb-5">
<SubTitle title={"Hide metrics from the UI"}/>
<div className="flex gaps">
{this.renderMetricsSelect()}

View File

@ -138,14 +138,17 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
this.onSaveProvider();
}}
options={this.options}
themeName="lens"
/>
<small className="hint">What query format is used to fetch metrics from Prometheus</small>
</>
}
</section>
{this.canEditPrometheusPath && (
<>
<hr/>
<section>
<p>Prometheus service address.</p>
<SubTitle title="Prometheus service address" />
<Input
theme="round-black"
value={this.path}
@ -158,6 +161,7 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
({"<namespace>/<service>:<port>"}). {productName} tries to auto-detect address if left empty.
</small>
</section>
</>
)}
</>
);

View File

@ -25,6 +25,7 @@ import type { Cluster } from "../../../../main/cluster";
import { observable, reaction, makeObservable } from "mobx";
import { Badge } from "../../badge/badge";
import { Icon } from "../../icon/icon";
import { Notice } from "../../+extensions/notice";
interface Props {
cluster: Cluster;
@ -55,14 +56,20 @@ export class ShowMetricsSetting extends React.Component<Props> {
}
renderMetrics() {
const metrics = Array.from(this.hiddenMetrics);
if (!metrics.length) {
return (
<div className="flex-grow text-center">All metrics are visible on the UI</div>
);
}
return (
Array.from(this.hiddenMetrics).map(name => {
metrics.map(name => {
const tooltipId = `${name}`;
return (
<Badge key={name}>
<Badge key={name} flat>
<span id={tooltipId}>{name}</span>
<Icon
smallest
@ -79,9 +86,11 @@ export class ShowMetricsSetting extends React.Component<Props> {
render() {
return (
<div className="MetricsSelect flex wrap gaps">
<Notice>
<div className="MetricsSelect flex wrap gaps leading-relaxed">
{this.renderMetrics()}
</div>
</Notice>
);
}
}

View File

@ -23,7 +23,10 @@
.el-contents {
display: flex;
flex-direction: column;
&:not(:empty) {
margin: $padding 0px;
}
.el-value-remove {
.Icon {

View File

@ -25,7 +25,7 @@ import { observer } from "mobx-react";
import React from "react";
import { Icon } from "../icon";
import { Input } from "../input";
import { Input, InputProps } from "../input";
import { boundMethod } from "../../utils";
export interface Props<T> {
@ -37,11 +37,13 @@ export interface Props<T> {
// An optional prop used to convert T to a displayable string
// defaults to `String`
renderItem?: (item: T, index: number) => React.ReactNode,
inputTheme?: InputProps["theme"];
}
const defaultProps: Partial<Props<any>> = {
placeholder: "Add new item...",
renderItem: (item: any, index: number) => <React.Fragment key={index}>{item}</React.Fragment>
renderItem: (item: any, index: number) => <React.Fragment key={index}>{item}</React.Fragment>,
inputTheme: "round"
};
@observer
@ -59,13 +61,13 @@ export class EditableList<T> extends React.Component<Props<T>> {
}
render() {
const { items, remove, renderItem, placeholder } = this.props;
const { items, remove, renderItem, placeholder, inputTheme } = this.props;
return (
<div className="EditableList">
<div className="el-header">
<Input
theme="round"
theme={inputTheme}
onSubmit={this.onSubmit}
placeholder={placeholder}
/>

View File

@ -227,12 +227,12 @@ export class FilePicker extends React.Component<Props> {
getIconRight(): React.ReactNode {
switch (this.status) {
case FileInputStatus.CLEAR:
return <Icon className="clean" material="cloud_upload"></Icon>;
case FileInputStatus.PROCESSING:
return <Spinner />;
case FileInputStatus.ERROR:
return <Icon material="error" title={this.errorText}></Icon>;
default:
return null;
}
}
}

View File

@ -136,10 +136,15 @@
border-color: var(--inputControlBorder);
color: var(--textColorTertiary);
padding: $padding;
transition: border-color 0.1s;
&:hover {
border-color: var(--inputControlHoverBorder);
}
&:focus-within {
border-color: $colorInfo;
}
}
}
}

View File

@ -20,17 +20,19 @@
*/
.SettingLayout {
--width: 75%;
--nav-width: 180px;
--nav-column-width: 30vw;
width: 100%;
height: 100%;
display: grid !important;
display: grid;
color: var(--settingsColor);
@include media("<1000px") {
--width: 85%;
}
position: fixed;
z-index: 13!important;
left: 0;
top: 0;
right: 0;
bottom: 0;
height: unset;
background-color: var(--settingsBackground);
&.showNavigation {
grid-template-columns: var(--nav-column-width) 1fr;
@ -40,18 +42,6 @@
}
}
// covers whole app view area
&.showOnTop {
position: fixed !important; // allow to cover ClustersMenu
z-index: 13;
left: 0;
top: 0;
right: 0;
bottom: 0;
height: unset;
background-color: var(--settingsBackground);
}
> .sidebarRegion {
display: flex;
justify-content: flex-end;
@ -63,12 +53,25 @@
padding: 60px 0 60px 20px;
h2 {
margin-bottom: 10px;
font-size: 18px;
padding: 6px 10px;
font-size: 15px;
overflow-wrap: anywhere;
color: var(--textColorAccent);
font-weight: 600;
padding-right: 20px;
word-break: break-word;
}
hr {
margin-top: 10px;
margin-bottom: 10px;
margin-left: 10px;
margin-right: 20px;
height: 1px;
border-top: thin solid var(--hrColor);
&:first-child {
display: none;
}
}
.Tabs {
@ -78,6 +81,7 @@
font-weight: 800;
line-height: 16px;
text-transform: uppercase;
color: var(--textColorPrimary);
&:first-child {
padding-top: 0;
@ -112,9 +116,14 @@
> .label {
width: 100%;
font-weight: 500;
}
}
}
.HotbarIcon {
margin: 0 11px;
}
}
}
@ -124,7 +133,9 @@
justify-content: center;
> .content {
width: var(--width);
width: 100%;
max-width: 740px;
min-width: 460px;
padding: 60px 40px 80px;
> section {
@ -157,7 +168,7 @@
}
.Icon {
color: var(--textColorSecondary);
color: var(--textColorTertiary);
}
}
@ -200,7 +211,7 @@
font-size: 18px;
line-height: 20px;
font-weight: 600;
margin-bottom: 20px;
margin-bottom: 30px;
}
a {
@ -210,6 +221,7 @@
.hint {
margin-top: 8px;
font-size: 14px;
line-height: 20px;
}
.SubTitle {

View File

@ -81,7 +81,7 @@ export class SettingLayout extends React.Component<SettingLayoutProps> {
contentClass, provideBackButtonNavigation,
contentGaps, navigation, children, ...elemProps
} = this.props;
const className = cssNames("SettingLayout", "showOnTop", { showNavigation: navigation }, this.props.className);
const className = cssNames("SettingLayout", { showNavigation: navigation }, this.props.className);
return (
<div {...elemProps} className={className}>

View File

@ -213,6 +213,7 @@ html {
:hover {
&.Select__control {
box-shadow: 0 0 0 1px var(--inputControlHoverBorder);
transition: box-shadow 0.1s;
}
}