mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into catalog-expandable-menu
This commit is contained in:
commit
679cf7e550
@ -45,7 +45,7 @@ function getBuildChannel(): string {
|
||||
async function writeOutExtensionVersion(manifestPath: string) {
|
||||
const extensionPackageJson = await fse.readJson(manifestPath);
|
||||
|
||||
extensionPackageJson.version = `${versionInfo.format()}.${buildNumber}`;
|
||||
extensionPackageJson.version = appInfo.version;
|
||||
|
||||
return fse.writeJson(manifestPath, extensionPackageJson, {
|
||||
spaces: 2,
|
||||
|
||||
@ -42,6 +42,7 @@ export type KubernetesClusterPrometheusMetrics = {
|
||||
export type KubernetesClusterSpec = {
|
||||
kubeconfigPath: string;
|
||||
kubeconfigContext: string;
|
||||
iconData?: string;
|
||||
metrics?: {
|
||||
source: string;
|
||||
prometheus?: KubernetesClusterPrometheusMetrics;
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
import "../common/cluster-ipc";
|
||||
import type http from "http";
|
||||
import { action, autorun, makeObservable, reaction } from "mobx";
|
||||
import { action, autorun, makeObservable, reaction, toJS } from "mobx";
|
||||
import { ClusterStore, getClusterIdFromHost } from "../common/cluster-store";
|
||||
import type { Cluster } from "./cluster";
|
||||
import logger from "./logger";
|
||||
@ -45,7 +45,14 @@ export class ClusterManager extends Singleton {
|
||||
reaction(
|
||||
() => this.store.clustersList.map(c => c.getState()),
|
||||
() => this.updateCatalog(this.store.clustersList),
|
||||
{ fireImmediately: true, }
|
||||
{ fireImmediately: false, }
|
||||
);
|
||||
|
||||
// reacting to every cluster's preferences change and total amount of items
|
||||
reaction(
|
||||
() => this.store.clustersList.map(c => toJS(c.preferences)),
|
||||
() => this.updateCatalog(this.store.clustersList),
|
||||
{ fireImmediately: false, }
|
||||
);
|
||||
|
||||
reaction(() => catalogEntityRegistry.getItemsForApiKind<KubernetesCluster>("entity.k8slens.dev/v1alpha1", "KubernetesCluster"), (entities) => {
|
||||
@ -74,32 +81,40 @@ export class ClusterManager extends Singleton {
|
||||
@action
|
||||
protected updateCatalog(clusters: Cluster[]) {
|
||||
for (const cluster of clusters) {
|
||||
const index = catalogEntityRegistry.items.findIndex((entity) => entity.metadata.uid === cluster.id);
|
||||
|
||||
if (index !== -1) {
|
||||
const entity = catalogEntityRegistry.items[index] as KubernetesCluster;
|
||||
|
||||
this.updateEntityStatus(entity, cluster);
|
||||
|
||||
if (cluster.preferences?.clusterName) {
|
||||
entity.metadata.name = cluster.preferences.clusterName;
|
||||
}
|
||||
|
||||
entity.spec.metrics ||= { source: "local" };
|
||||
|
||||
if (entity.spec.metrics.source === "local") {
|
||||
const prometheus: KubernetesClusterPrometheusMetrics = entity.spec?.metrics?.prometheus || {};
|
||||
|
||||
prometheus.type = cluster.preferences.prometheusProvider?.type;
|
||||
prometheus.address = cluster.preferences.prometheus;
|
||||
entity.spec.metrics.prometheus = prometheus;
|
||||
}
|
||||
|
||||
catalogEntityRegistry.items.splice(index, 1, entity);
|
||||
}
|
||||
this.updateEntityFromCluster(cluster);
|
||||
}
|
||||
}
|
||||
|
||||
protected updateEntityFromCluster(cluster: Cluster) {
|
||||
const index = catalogEntityRegistry.items.findIndex((entity) => entity.metadata.uid === cluster.id);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entity = catalogEntityRegistry.items[index] as KubernetesCluster;
|
||||
|
||||
this.updateEntityStatus(entity, cluster);
|
||||
|
||||
if (cluster.preferences?.clusterName) {
|
||||
entity.metadata.name = cluster.preferences.clusterName;
|
||||
}
|
||||
|
||||
entity.spec.metrics ||= { source: "local" };
|
||||
|
||||
if (entity.spec.metrics.source === "local") {
|
||||
const prometheus: KubernetesClusterPrometheusMetrics = entity.spec?.metrics?.prometheus || {};
|
||||
|
||||
prometheus.type = cluster.preferences.prometheusProvider?.type;
|
||||
prometheus.address = cluster.preferences.prometheus;
|
||||
entity.spec.metrics.prometheus = prometheus;
|
||||
}
|
||||
|
||||
entity.spec.iconData = cluster.preferences.icon;
|
||||
|
||||
catalogEntityRegistry.items.splice(index, 1, entity);
|
||||
}
|
||||
|
||||
protected updateEntityStatus(entity: KubernetesCluster, cluster: Cluster) {
|
||||
entity.status.phase = cluster.accessible ? "connected" : "disconnected";
|
||||
}
|
||||
@ -121,7 +136,7 @@ export class ClusterManager extends Singleton {
|
||||
cluster.kubeConfigPath = entity.spec.kubeconfigPath;
|
||||
cluster.contextName = entity.spec.kubeconfigContext;
|
||||
|
||||
this.updateEntityStatus(entity, cluster);
|
||||
this.updateEntityFromCluster(cluster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,6 +79,7 @@ export class CatalogEntityDetails extends Component<Props> {
|
||||
uid={entity.metadata.uid}
|
||||
title={entity.metadata.name}
|
||||
source={entity.metadata.source}
|
||||
icon={entity.spec.iconData}
|
||||
onClick={() => this.openEntity()}
|
||||
size={128} />
|
||||
<div className="IconHint">
|
||||
|
||||
@ -36,13 +36,13 @@ import { catalogCategoryRegistry } from "../../../common/catalog";
|
||||
import { CatalogAddButton } from "./catalog-add-button";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import { Notifications } from "../notifications";
|
||||
import { Avatar } from "../avatar/avatar";
|
||||
import { MainLayout } from "../layout/main-layout";
|
||||
import { cssNames } from "../../utils";
|
||||
import { makeCss } from "../../../common/utils/makeCss";
|
||||
import { CatalogEntityDetails } from "./catalog-entity-details";
|
||||
import type { CatalogViewRouteParam } from "../../../common/routes";
|
||||
import { CatalogMenu } from "./catalog-menu";
|
||||
import { HotbarIcon } from "../hotbar/hotbar-icon";
|
||||
|
||||
enum sortBy {
|
||||
name = "name",
|
||||
@ -169,13 +169,13 @@ export class Catalog extends React.Component<Props> {
|
||||
|
||||
renderIcon(item: CatalogEntityItem) {
|
||||
return (
|
||||
<Avatar
|
||||
title={item.name}
|
||||
colorHash={`${item.name}-${item.source}`}
|
||||
width={24}
|
||||
height={24}
|
||||
className={css.catalogIcon}
|
||||
/>
|
||||
<HotbarIcon
|
||||
uid={item.getId()}
|
||||
title={item.getName()}
|
||||
source={item.source}
|
||||
icon={item.entity.spec.iconData}
|
||||
onClick={() => this.onDetails(item)}
|
||||
size={24} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -34,10 +34,11 @@ export class Welcome extends React.Component {
|
||||
<div className="box">
|
||||
<Icon svg="logo-lens" className="logo" />
|
||||
|
||||
<h2>Welcome to {productName} 5 Beta!</h2>
|
||||
<h2>Welcome to {productName} 5!</h2>
|
||||
|
||||
<p>
|
||||
Here are some steps to help you get started with {productName} 5 Beta.
|
||||
To get you started we have auto-detected your clusters in your kubeconfig file and added them to the catalog, your centralized view for managing all your cloud-native resources.
|
||||
<br/><br/>
|
||||
If you have any questions or feedback, please join our <a href={slackUrl} target="_blank" rel="noreferrer">Lens Community slack channel</a>.
|
||||
</p>
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import type { KubernetesCluster } from "../../../common/catalog-entities";
|
||||
import { ClusterStore } from "../../../common/cluster-store";
|
||||
import type { EntitySettingViewProps } from "../../../extensions/registries";
|
||||
import type { CatalogEntity } from "../../api/catalog-entity";
|
||||
@ -41,6 +42,9 @@ export function GeneralSettings({ entity }: EntitySettingViewProps) {
|
||||
<section>
|
||||
<components.ClusterNameSetting cluster={cluster} />
|
||||
</section>
|
||||
<section>
|
||||
<components.ClusterIconSetting cluster={cluster} entity={entity as KubernetesCluster} />
|
||||
</section>
|
||||
<section>
|
||||
<components.ClusterKubeconfig cluster={cluster} />
|
||||
</section>
|
||||
|
||||
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2021 OpenLens Authors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
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";
|
||||
|
||||
enum GeneralInputStatus {
|
||||
CLEAN = "clean",
|
||||
ERROR = "error",
|
||||
}
|
||||
|
||||
interface Props {
|
||||
cluster: Cluster;
|
||||
entity: KubernetesCluster
|
||||
}
|
||||
|
||||
@observer
|
||||
export class ClusterIconSetting extends React.Component<Props> {
|
||||
@observable status = GeneralInputStatus.CLEAN;
|
||||
@observable errorText?: string;
|
||||
|
||||
@boundMethod
|
||||
async onIconPick([file]: File[]) {
|
||||
const { cluster } = this.props;
|
||||
|
||||
try {
|
||||
if (file) {
|
||||
const buf = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
cluster.preferences.icon = `data:${file.type};base64,${buf.toString("base64")}`;
|
||||
} else {
|
||||
// this has to be done as a seperate branch (and not always) because `cluster`
|
||||
// is observable and triggers an update loop.
|
||||
cluster.preferences.icon = undefined;
|
||||
}
|
||||
} catch (e) {
|
||||
this.errorText = e.toString();
|
||||
this.status = GeneralInputStatus.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
getClearButton() {
|
||||
if (this.props.cluster.preferences.icon) {
|
||||
return <Button
|
||||
label="Clear"
|
||||
tooltip="Revert back to default icon"
|
||||
onClick={() => this.onIconPick([])}
|
||||
/>;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { entity } = this.props;
|
||||
const label = (
|
||||
<>
|
||||
<HotbarIcon
|
||||
uid={entity.metadata.uid}
|
||||
title={entity.metadata.name}
|
||||
source={entity.metadata.source}
|
||||
icon={entity.spec.iconData}
|
||||
/>
|
||||
<span style={{marginRight: "var(--unit)"}}>Browse for new icon...</span>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SubTitle title="Cluster Icon" />
|
||||
<div className="file-loader">
|
||||
<FilePicker
|
||||
accept="image/*"
|
||||
label={label}
|
||||
onOverSizeLimit={OverSizeLimitStyle.FILTER}
|
||||
handler={this.onIconPick}
|
||||
/>
|
||||
{this.getClearButton()}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -27,3 +27,4 @@ export * from "./cluster-name-setting";
|
||||
export * from "./cluster-prometheus-setting";
|
||||
export * from "./cluster-proxy-setting";
|
||||
export * from "./cluster-show-metrics";
|
||||
export * from "./cluster-icon-settings";
|
||||
|
||||
@ -129,6 +129,7 @@ export class HotbarEntityIcon extends React.Component<Props> {
|
||||
uid={entity.metadata.uid}
|
||||
title={entity.metadata.name}
|
||||
source={entity.metadata.source}
|
||||
icon={entity.spec.iconData}
|
||||
className={className}
|
||||
active={isActive}
|
||||
onMenuOpen={onOpen}
|
||||
|
||||
@ -113,7 +113,17 @@
|
||||
}
|
||||
|
||||
img {
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
padding: 3px;
|
||||
border-radius: 6px;
|
||||
|
||||
&.active {
|
||||
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px var(--textColorAccent);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:not(.active) {
|
||||
box-shadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px #ffffff50;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ export interface HotbarIconProps extends DOMAttributes<HTMLElement> {
|
||||
uid: string;
|
||||
title: string;
|
||||
source: string;
|
||||
icon?: string;
|
||||
onMenuOpen?: () => void;
|
||||
className?: IClassName;
|
||||
active?: boolean;
|
||||
@ -61,7 +62,7 @@ function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
|
||||
}
|
||||
|
||||
export const HotbarIcon = observer(({menuItems = [], size = 40, ...props}: HotbarIconProps) => {
|
||||
const { uid, title, active, className, source, disabled, onMenuOpen, children, ...rest } = props;
|
||||
const { uid, title, icon, active, className, source, disabled, onMenuOpen, children, ...rest } = props;
|
||||
const id = `hotbarIcon-${uid}`;
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
||||
@ -69,18 +70,31 @@ export const HotbarIcon = observer(({menuItems = [], size = 40, ...props}: Hotba
|
||||
setMenuOpen(!menuOpen);
|
||||
};
|
||||
|
||||
const renderIcon = () => {
|
||||
if (icon) {
|
||||
return <img
|
||||
{...rest}
|
||||
src={icon}
|
||||
className={active ? "active" : "default"}
|
||||
width={size}
|
||||
height={size} />;
|
||||
} else {
|
||||
return <Avatar
|
||||
{...rest}
|
||||
title={title}
|
||||
colorHash={`${title}-${source}`}
|
||||
className={active ? "active" : "default"}
|
||||
width={size}
|
||||
height={size}
|
||||
/>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cssNames("HotbarIcon flex inline", className, { disabled })}>
|
||||
<MaterialTooltip title={`${title || "unknown"} (${source || "unknown"})`} placement="right">
|
||||
<div id={id}>
|
||||
<Avatar
|
||||
{...rest}
|
||||
title={title}
|
||||
colorHash={`${title}-${source}`}
|
||||
className={active ? "active" : "default"}
|
||||
width={size}
|
||||
height={size}
|
||||
/>
|
||||
{renderIcon()}
|
||||
{children}
|
||||
</div>
|
||||
</MaterialTooltip>
|
||||
|
||||
@ -38,13 +38,13 @@ body.resizing {
|
||||
height: 100%;
|
||||
margin-left: 50%;
|
||||
background: transparent;
|
||||
transition: all 0.2s 0s;
|
||||
transition: background 0.2s 0s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
background: var(--blue);
|
||||
transition: all 0.2s 0.5s;
|
||||
transition: background 0.2s 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,8 +58,20 @@ body.resizing {
|
||||
cursor: row-resize;
|
||||
height: $dimension;
|
||||
|
||||
&.leading {
|
||||
top: -$dimension / 2;
|
||||
&::after {
|
||||
height: 3px;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.resizing & {
|
||||
$expandedHeight: 200px;
|
||||
height: $expandedHeight;
|
||||
margin-top: -$expandedHeight / 2;
|
||||
|
||||
&::after {
|
||||
margin-top: $expandedHeight / 2;
|
||||
}
|
||||
}
|
||||
|
||||
&.trailing {
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { catalogURL, preferencesURL } from "../../common/routes";
|
||||
import { catalogURL } from "../../common/routes";
|
||||
import { WelcomeMenuRegistry } from "../../extensions/registries";
|
||||
import { navigate } from "../navigation";
|
||||
|
||||
@ -27,14 +27,9 @@ export function initWelcomeMenuRegistry() {
|
||||
WelcomeMenuRegistry.getInstance()
|
||||
.add([
|
||||
{
|
||||
title: "Browse Your Catalog",
|
||||
title: "Browse Clusters",
|
||||
icon: "view_list",
|
||||
click: () => navigate(catalogURL())
|
||||
},
|
||||
{
|
||||
title: "Configure Preferences",
|
||||
icon: "settings",
|
||||
click: () => navigate(preferencesURL())
|
||||
click: () => navigate(catalogURL({ params: { group: "entity.k8slens.dev", kind: "KubernetesCluster" } } ))
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user