mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
release v4.2.0 (#2432)
Signed-off-by: Sebastian Malton<sebastian@malton.name>
This commit is contained in:
1 parent
34712dd51f
commit
86b0fa6479
9 files changed
+139
-57
No files matched your search
@@ -0,0 +1,40 @@
|
||||
import { Router } from "../router";
|
||||
|
||||
const staticRoot = __dirname;
|
||||
|
||||
class TestRouter extends Router {
|
||||
protected resolveStaticRootPath() {
|
||||
return staticRoot;
|
||||
}
|
||||
}
|
||||
|
||||
describe("Router", () => {
|
||||
it("blocks path traversal attacks", async () => {
|
||||
const router = new TestRouter();
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
end: jest.fn()
|
||||
};
|
||||
|
||||
await router.handleStaticFile("../index.ts", res as any, {} as any, 0);
|
||||
|
||||
expect(res.statusCode).toEqual(404);
|
||||
});
|
||||
|
||||
it("serves files under static root", async () => {
|
||||
const router = new TestRouter();
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
write: jest.fn(),
|
||||
setHeader: jest.fn(),
|
||||
end: jest.fn()
|
||||
};
|
||||
const req = {
|
||||
url: ""
|
||||
};
|
||||
|
||||
await router.handleStaticFile("router.test.ts", res as any, req as any, 0);
|
||||
|
||||
expect(res.statusCode).toEqual(200);
|
||||
});
|
||||
});
|
||||
@@ -28,7 +28,7 @@ export class LensProxy {
|
||||
}
|
||||
|
||||
listen(port = this.port): this {
|
||||
this.proxyServer = this.buildCustomProxy().listen(port);
|
||||
this.proxyServer = this.buildCustomProxy().listen(port, "127.0.0.1");
|
||||
logger.info(`[LENS-PROXY]: Proxy server has started at ${this.origin}`);
|
||||
|
||||
return this;
|
||||
|
||||
+15
-1
@@ -40,10 +40,16 @@ export interface LensApiRequest<P = any> {
|
||||
|
||||
export class Router {
|
||||
protected router: any;
|
||||
protected staticRootPath: string;
|
||||
|
||||
public constructor() {
|
||||
this.router = new Call.Router();
|
||||
this.addRoutes();
|
||||
this.staticRootPath = this.resolveStaticRootPath();
|
||||
}
|
||||
|
||||
protected resolveStaticRootPath() {
|
||||
return path.resolve(__static);
|
||||
}
|
||||
|
||||
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
|
||||
@@ -102,7 +108,15 @@ export class Router {
|
||||
}
|
||||
|
||||
async handleStaticFile(filePath: string, res: http.ServerResponse, req: http.IncomingMessage, retryCount = 0) {
|
||||
const asset = path.join(__static, filePath);
|
||||
const asset = path.join(this.staticRootPath, filePath);
|
||||
const normalizedFilePath = path.resolve(asset);
|
||||
|
||||
if (!normalizedFilePath.startsWith(this.staticRootPath)) {
|
||||
res.statusCode = 404;
|
||||
res.end();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const filename = path.basename(req.url);
|
||||
|
||||
@@ -32,6 +32,7 @@ const defaultProps: Partial<Props> = {
|
||||
useWorker: false,
|
||||
onBlur: noop,
|
||||
onFocus: noop,
|
||||
cursorPos: { row: 0, column: 0 },
|
||||
};
|
||||
|
||||
@observer
|
||||
|
||||
@@ -26,7 +26,6 @@ interface SidebarItemProps {
|
||||
* this item should be shown as active
|
||||
*/
|
||||
isActive?: boolean;
|
||||
subMenus?: React.ReactNode | React.ComponentType<SidebarItemProps>[];
|
||||
}
|
||||
|
||||
@observer
|
||||
@@ -53,11 +52,9 @@ export class SidebarItem extends React.Component<SidebarItemProps> {
|
||||
}
|
||||
|
||||
@computed get isExpandable(): boolean {
|
||||
if (this.compact) {
|
||||
return false; // not available currently
|
||||
}
|
||||
if (this.compact) return false; // not available in compact-mode currently
|
||||
|
||||
return Boolean(this.props.subMenus || this.props.children);
|
||||
return Boolean(this.props.children);
|
||||
}
|
||||
|
||||
toggleExpand = () => {
|
||||
@@ -66,8 +63,22 @@ export class SidebarItem extends React.Component<SidebarItemProps> {
|
||||
});
|
||||
};
|
||||
|
||||
renderSubMenu() {
|
||||
const { isExpandable, expanded, isActive } = this;
|
||||
|
||||
if (!isExpandable || !expanded) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className={cssNames("sub-menu", { active: isActive })}>
|
||||
{this.props.children}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isHidden, icon, text, children, url, className, subMenus } = this.props;
|
||||
const { isHidden, icon, text, url, className } = this.props;
|
||||
|
||||
if (isHidden) return null;
|
||||
|
||||
@@ -90,12 +101,7 @@ export class SidebarItem extends React.Component<SidebarItemProps> {
|
||||
material={expanded ? "keyboard_arrow_up" : "keyboard_arrow_down"}
|
||||
/>}
|
||||
</NavLink>
|
||||
{isExpandable && expanded && (
|
||||
<ul className={cssNames("sub-menu", { active: isActive })}>
|
||||
{subMenus}
|
||||
{children}
|
||||
</ul>
|
||||
)}
|
||||
{this.renderSubMenu()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,25 +51,20 @@ export class Sidebar extends React.Component<Props> {
|
||||
}
|
||||
|
||||
return Object.entries(crdStore.groups).map(([group, crds]) => {
|
||||
const crdGroupSubMenu: React.ReactNode = crds.map((crd) => {
|
||||
return (
|
||||
<SidebarItem
|
||||
key={crd.getResourceApiBase()}
|
||||
id={`crd-resource:${crd.getResourceApiBase()}`}
|
||||
url={crd.getResourceUrl()}
|
||||
text={crd.getResourceTitle()}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const id = `crd-group:${group}`;
|
||||
const crdGroupsPageUrl = crdURL({ query: { groups: group } });
|
||||
|
||||
return (
|
||||
<SidebarItem
|
||||
key={group}
|
||||
text={group}
|
||||
id={`crd-group:${group}`}
|
||||
url={crdURL({ query: { groups: group } })}
|
||||
subMenus={crdGroupSubMenu}
|
||||
/>
|
||||
<SidebarItem key={id} id={id} text={group} url={crdGroupsPageUrl}>
|
||||
{crds.map((crd) => (
|
||||
<SidebarItem
|
||||
key={crd.getResourceApiBase()}
|
||||
id={`crd-resource:${crd.getResourceApiBase()}`}
|
||||
url={crd.getResourceUrl()}
|
||||
text={crd.getResourceTitle()}
|
||||
/>
|
||||
))}
|
||||
</SidebarItem>
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -147,8 +142,9 @@ export class Sidebar extends React.Component<Props> {
|
||||
isActive={isActive}
|
||||
text={menuItem.title}
|
||||
icon={<menuItem.components.Icon/>}
|
||||
subMenus={this.renderTreeFromTabRoutes(tabRoutes)}
|
||||
/>
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(tabRoutes)}
|
||||
</SidebarItem>
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -175,88 +171,94 @@ export class Sidebar extends React.Component<Props> {
|
||||
<div className={cssNames("sidebar-nav flex column box grow-fixed", { compact })}>
|
||||
<SidebarItem
|
||||
id="cluster"
|
||||
text="Cluster"
|
||||
isActive={isActiveRoute(clusterRoute)}
|
||||
isHidden={!isAllowedResource("nodes")}
|
||||
url={clusterURL()}
|
||||
text="Cluster"
|
||||
icon={<Icon svg="kube"/>}
|
||||
/>
|
||||
<SidebarItem
|
||||
id="nodes"
|
||||
text="Nodes"
|
||||
isActive={isActiveRoute(nodesRoute)}
|
||||
isHidden={!isAllowedResource("nodes")}
|
||||
url={nodesURL()}
|
||||
text="Nodes"
|
||||
icon={<Icon svg="nodes"/>}
|
||||
/>
|
||||
<SidebarItem
|
||||
id="workloads"
|
||||
text="Workloads"
|
||||
isActive={isActiveRoute(workloadsRoute)}
|
||||
isHidden={Workloads.tabRoutes.length == 0}
|
||||
url={workloadsURL({ query })}
|
||||
subMenus={this.renderTreeFromTabRoutes(Workloads.tabRoutes)}
|
||||
text="Workloads"
|
||||
icon={<Icon svg="workloads"/>}
|
||||
/>
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(Workloads.tabRoutes)}
|
||||
</SidebarItem>
|
||||
<SidebarItem
|
||||
id="config"
|
||||
text="Configuration"
|
||||
isActive={isActiveRoute(configRoute)}
|
||||
isHidden={Config.tabRoutes.length == 0}
|
||||
url={configURL({ query })}
|
||||
subMenus={this.renderTreeFromTabRoutes(Config.tabRoutes)}
|
||||
text="Configuration"
|
||||
icon={<Icon material="list"/>}
|
||||
/>
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(Config.tabRoutes)}
|
||||
</SidebarItem>
|
||||
<SidebarItem
|
||||
id="networks"
|
||||
text="Network"
|
||||
isActive={isActiveRoute(networkRoute)}
|
||||
isHidden={Network.tabRoutes.length == 0}
|
||||
url={networkURL({ query })}
|
||||
subMenus={this.renderTreeFromTabRoutes(Network.tabRoutes)}
|
||||
text="Network"
|
||||
icon={<Icon material="device_hub"/>}
|
||||
/>
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(Network.tabRoutes)}
|
||||
</SidebarItem>
|
||||
<SidebarItem
|
||||
id="storage"
|
||||
text="Storage"
|
||||
isActive={isActiveRoute(storageRoute)}
|
||||
isHidden={Storage.tabRoutes.length == 0}
|
||||
url={storageURL({ query })}
|
||||
subMenus={this.renderTreeFromTabRoutes(Storage.tabRoutes)}
|
||||
icon={<Icon svg="storage"/>}
|
||||
text="Storage"
|
||||
/>
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(Storage.tabRoutes)}
|
||||
</SidebarItem>
|
||||
<SidebarItem
|
||||
id="namespaces"
|
||||
text="Namespaces"
|
||||
isActive={isActiveRoute(namespacesRoute)}
|
||||
isHidden={!isAllowedResource("namespaces")}
|
||||
url={namespacesURL()}
|
||||
icon={<Icon material="layers"/>}
|
||||
text="Namespaces"
|
||||
/>
|
||||
<SidebarItem
|
||||
id="events"
|
||||
text="Events"
|
||||
isActive={isActiveRoute(eventRoute)}
|
||||
isHidden={!isAllowedResource("events")}
|
||||
url={eventsURL({ query })}
|
||||
icon={<Icon material="access_time"/>}
|
||||
text="Events"
|
||||
/>
|
||||
<SidebarItem
|
||||
id="apps"
|
||||
text="Apps" // helm charts
|
||||
isActive={isActiveRoute(appsRoute)}
|
||||
url={appsURL({ query })}
|
||||
subMenus={this.renderTreeFromTabRoutes(Apps.tabRoutes)}
|
||||
icon={<Icon material="apps"/>}
|
||||
text="Apps"
|
||||
/>
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(Apps.tabRoutes)}
|
||||
</SidebarItem>
|
||||
<SidebarItem
|
||||
id="users"
|
||||
text="Access Control"
|
||||
isActive={isActiveRoute(usersManagementRoute)}
|
||||
url={usersManagementURL({ query })}
|
||||
subMenus={this.renderTreeFromTabRoutes(UserManagement.tabRoutes)}
|
||||
icon={<Icon material="security"/>}
|
||||
text="Access Control"
|
||||
/>
|
||||
>
|
||||
{this.renderTreeFromTabRoutes(UserManagement.tabRoutes)}
|
||||
</SidebarItem>
|
||||
<SidebarItem
|
||||
id="custom-resources"
|
||||
text="Custom Resources"
|
||||
|
||||
Reference in new issue
Block a user