{
-
+
Lens
{
Cluster}
- icon={}
+ icon={}
/>
Nodes}
- icon={}
+ icon={}
/>
{
routePath={workloadsRoute.path}
subMenus={Workloads.tabRoutes}
text={Workloads}
- icon={}
+ icon={}
/>
{
routePath={configRoute.path}
subMenus={Config.tabRoutes}
text={Configuration}
- icon={}
+ icon={}
/>
{
routePath={networkRoute.path}
subMenus={Network.tabRoutes}
text={Network}
- icon={}
+ icon={}
/>
{
url={storageURL({ query })}
routePath={storageRoute.path}
subMenus={Storage.tabRoutes}
- icon={}
+ icon={}
text={Storage}
/>
}
+ icon={}
text={Namespaces}
/>
}
+ icon={}
text={Events}
/>
{
url={appsURL({ query })}
subMenus={Apps.tabRoutes}
routePath={appsRoute.path}
- icon={}
+ icon={}
text={Apps}
/>
{
url={usersManagementURL({ query })}
routePath={usersManagementRoute.path}
subMenus={UserManagement.tabRoutes}
- icon={}
+ icon={}
text={Access Control}
/>
}
+ icon={}
text={Custom Resources}
>
{this.renderCustomResources()}
@@ -186,7 +186,7 @@ export class Sidebar extends React.Component {
- )
+ );
}
}
@@ -203,7 +203,10 @@ interface SidebarNavItemProps {
const navItemStorage = createStorage<[string, boolean][]>("sidebar_menu_item", []);
const navItemState = observable.map
(navItemStorage.get());
-reaction(() => [...navItemState], value => navItemStorage.set(value));
+reaction(
+ () => [...navItemState],
+ (value) => navItemStorage.set(value)
+);
@observer
class SidebarNavItem extends React.Component {
@@ -216,15 +219,15 @@ class SidebarNavItem extends React.Component {
toggleSubMenu = () => {
navItemState.set(this.props.id, !this.isExpanded);
- }
+ };
isActive = () => {
const { routePath, url } = this.props;
const { pathname } = navigation.location;
return !!matchPath(pathname, {
- path: routePath || url
+ path: routePath || url,
});
- }
+ };
render() {
const { id, isHidden, subMenus = [], icon, text, url, children, className } = this.props;
@@ -239,10 +242,7 @@ class SidebarNavItem extends React.Component {
{icon}
{text}
-
+
{subMenus.map(({ title, url }) => (
@@ -252,18 +252,18 @@ class SidebarNavItem extends React.Component {
))}
{React.Children.toArray(children).map((child: React.ReactElement) => {
return React.cloneElement(child, {
- className: cssNames(child.props.className, { visible: this.isExpanded })
+ className: cssNames(child.props.className, { visible: this.isExpanded }),
});
})}
- )
+ );
}
return (
{icon}
{text}
- )
+ );
}
}
diff --git a/src/renderer/components/layout/tab-layout.scss b/src/renderer/components/layout/tab-layout.scss
new file mode 100755
index 0000000000..e8b62558d7
--- /dev/null
+++ b/src/renderer/components/layout/tab-layout.scss
@@ -0,0 +1,25 @@
+
+.TabLayout {
+ display: contents;
+
+ > .Tabs {
+ grid-area: tabs;
+ background: $layoutTabsBackground;
+ }
+
+
+ main {
+ @include custom-scrollbar;
+ $spacing: $margin * 2;
+
+ .theme-light & {
+ @include custom-scrollbar(dark);
+ }
+
+ grid-area: main;
+ overflow-y: scroll; // always reserve space for scrollbar (17px)
+ overflow-x: auto;
+ margin: $spacing;
+ margin-right: 0;
+ }
+}
\ No newline at end of file
diff --git a/src/renderer/components/layout/tab-layout.tsx b/src/renderer/components/layout/tab-layout.tsx
new file mode 100644
index 0000000000..82c0cac6cf
--- /dev/null
+++ b/src/renderer/components/layout/tab-layout.tsx
@@ -0,0 +1,45 @@
+import "./tab-layout.scss";
+
+import React, { ReactNode } from "react";
+import { matchPath, RouteProps } from "react-router-dom";
+import { observer } from "mobx-react";
+import { cssNames } from "../../utils";
+import { Tab, Tabs } from "../tabs";
+import { ErrorBoundary } from "../error-boundary";
+import { navigate, navigation } from "../../navigation";
+import { getHostedCluster } from "../../../common/cluster-store";
+
+export interface TabRoute extends RouteProps {
+ title: React.ReactNode;
+ url: string;
+}
+
+interface Props {
+ children: ReactNode;
+ className?: any;
+ tabs?: TabRoute[];
+ contentClass?: string;
+}
+
+export const TabLayout = observer(({ className, contentClass, tabs, children }: Props) => {
+ const routePath = navigation.location.pathname;
+ const cluster = getHostedCluster();
+ if (!cluster) {
+ return null; // fix: skip render when removing active (visible) cluster
+ }
+ return (
+
+ {tabs && (
+ navigate(url)}>
+ {tabs.map(({ title, path, url, ...routeProps }) => {
+ const isActive = !!matchPath(routePath, { path, ...routeProps });
+ return ;
+ })}
+
+ )}
+
+ {children}
+
+
+ );
+});
diff --git a/src/renderer/components/notifications/notifications.scss b/src/renderer/components/notifications/notifications.scss
index e8a64378cc..37d4990ee5 100644
--- a/src/renderer/components/notifications/notifications.scss
+++ b/src/renderer/components/notifications/notifications.scss
@@ -3,10 +3,11 @@
position: absolute;
right: 0;
- bottom: 0;
+ top: 0;
padding: $padding * 2;
max-height: 100vh;
z-index: 100000;
+ height: min-content!important;
&:empty {
display: none;
@@ -42,4 +43,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/renderer/components/tooltip/tooltip.tsx b/src/renderer/components/tooltip/tooltip.tsx
index b985b04f02..3cbe0c8708 100644
--- a/src/renderer/components/tooltip/tooltip.tsx
+++ b/src/renderer/components/tooltip/tooltip.tsx
@@ -15,6 +15,7 @@ export enum TooltipPosition {
export interface TooltipProps {
targetId: string; // html-id of target element to bind for
+ tooltipOnParentHover?: boolean; // detect hover on parent of target
visible?: boolean; // initial visibility
offset?: number; // offset from target element in pixels (all sides)
usePortal?: boolean; // renders element outside of parent (in body), disable for "easy-styling", default: true
@@ -50,14 +51,22 @@ export class Tooltip extends React.Component {
return document.getElementById(this.props.targetId)
}
+ get hoverTarget(): HTMLElement {
+ if (this.props.tooltipOnParentHover) {
+ return this.targetElem.parentElement
+ }
+
+ return this.targetElem
+ }
+
componentDidMount() {
- this.targetElem.addEventListener("mouseenter", this.onEnterTarget)
- this.targetElem.addEventListener("mouseleave", this.onLeaveTarget)
+ this.hoverTarget.addEventListener("mouseenter", this.onEnterTarget)
+ this.hoverTarget.addEventListener("mouseleave", this.onLeaveTarget)
}
componentWillUnmount() {
- this.targetElem.removeEventListener("mouseenter", this.onEnterTarget)
- this.targetElem.removeEventListener("mouseleave", this.onLeaveTarget)
+ this.hoverTarget.removeEventListener("mouseenter", this.onEnterTarget)
+ this.hoverTarget.removeEventListener("mouseleave", this.onLeaveTarget)
}
@autobind()
diff --git a/src/renderer/components/tooltip/withTooltip.tsx b/src/renderer/components/tooltip/withTooltip.tsx
index f9b9a65f69..f8c3e3ea93 100644
--- a/src/renderer/components/tooltip/withTooltip.tsx
+++ b/src/renderer/components/tooltip/withTooltip.tsx
@@ -8,6 +8,11 @@ import uniqueId from "lodash/uniqueId"
export interface TooltipDecoratorProps {
tooltip?: ReactNode | Omit;
+ /**
+ * forces tooltip to detect the target's parent for mouse events. This is
+ * useful for displaying tooltips even when the target is "disabled"
+ */
+ tooltipOverrideDisabled?: boolean;
}
export function withTooltip>(Target: T): T {
@@ -17,22 +22,25 @@ export function withTooltip>(Target: T): T {
protected tooltipId = uniqueId("tooltip_target_");
render() {
- const { tooltip, ...targetProps } = this.props;
+ const { tooltip, tooltipOverrideDisabled, ...targetProps } = this.props;
if (tooltip) {
const tooltipId = targetProps.id || this.tooltipId;
const tooltipProps: TooltipProps = {
targetId: tooltipId,
+ tooltipOnParentHover: tooltipOverrideDisabled,
...(isReactNode(tooltip) ? { children: tooltip } : tooltip),
};
targetProps.id = tooltipId;
targetProps.children = (
<>
- {targetProps.children}
-
+
+ {targetProps.children}
+
+
>
)
}
- return ;
+ return ;
}
}
diff --git a/static/RELEASE_NOTES.md b/static/RELEASE_NOTES.md
index 4c968e3206..a11f954f31 100644
--- a/static/RELEASE_NOTES.md
+++ b/static/RELEASE_NOTES.md
@@ -2,7 +2,13 @@
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
-## 3.6.3 (current version)
+## 3.6.4 (current version)
+- Fix: deleted namespace does not get auto unselected
+- Get focus to dock tab (terminal & resource editor) content after resize
+- Downloading kubectl binary does not block dashboard opening anymore
+- Fix background image of What's New page on white theme
+
+## 3.6.3
- Fix app crash on certain situations when opening ingress details
- Reduce app minimum size to support >= 800 x 600 resolution displays
- Fix app crash when service account has imagePullSecrets defined but the actual secret is missing