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

Add common app routes to the protocol renderer router (#2272)

This commit is contained in:
Sebastian Malton 2021-03-17 13:57:49 -04:00 committed by GitHub
parent ca39379b3a
commit 0561f6bfd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 129 additions and 33 deletions

View File

@ -2,7 +2,7 @@
Add clusters by clicking the **Add Cluster** button in the left-side menu. Add clusters by clicking the **Add Cluster** button in the left-side menu.
1. Click the **Add Cluster** button (indicated with a '+' icon). 1. Click the **Add Cluster** button (indicated with a '+' icon). Or [click here](lens://app/cluster).
2. Enter the path to your kubeconfig file. You'll need to have a kubeconfig file for the cluster you want to add. You can either browse for the path from the file system or or enter it directly. 2. Enter the path to your kubeconfig file. You'll need to have a kubeconfig file for the cluster you want to add. You can either browse for the path from the file system or or enter it directly.
Selected [cluster contexts](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#context) are added as a separate item in the left-side cluster menu to allow you to operate easily on multiple clusters and/or contexts. Selected [cluster contexts](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#context) are added as a separate item in the left-side cluster menu to allow you to operate easily on multiple clusters and/or contexts.

View File

@ -1,6 +1,9 @@
# Lens Extension API # Lens Extension API
Customize and enhance the Lens experience with the Lens Extension API. Use the extension API to create menus or page content. The same extension API was used to create many of Lens's core features. Customize and enhance the Lens experience with the Lens Extension API.
Use the extension API to create menus or page content.
The same extension API was used to create many of Lens's core features.
To install your first extension you should goto the [extension page](lens://app/extensions) in lens.
This documentation describes: This documentation describes:

View File

@ -72,5 +72,6 @@ To stay current with the Lens features, you can review the [release notes](https
## Next Steps ## Next Steps
- [Launch Lens](lens://app/landing)
- [Add clusters](../clusters/adding-clusters.md) - [Add clusters](../clusters/adding-clusters.md)
- [Watch introductory videos](./introductory-videos.md) - [Watch introductory videos](./introductory-videos.md)

View File

@ -1,12 +1,11 @@
# Preferences # Preferences
## Color Themes ## Color Themes
The Color Themes option in Lens preferences lets you set the colors in the Lens user interface to suit your liking. The Color Themes option in Lens preferences lets you set the colors in the Lens user interface to suit your liking.
1. Go to **File** > **Preferences** (**Lens** > **Preferences** on Mac). 1. Go to **File** > **Preferences** (**Lens** > **Preferences** on Mac). Or follow [this link](lens://app/preferences?highlight=appearance).
2. Select your preferred theme from the **Color Theme** dropdown. 2. Select your preferred theme from the **Color Theme** dropdown.
![Color Theme](images/color-theme.png) ![Color Theme](images/color-theme.png)
@ -19,10 +18,9 @@ Lens collects telemetry data, which is used to help us understand how to improve
If you don't wish to send usage data to Mirantis, you can disable the "Telemetry & Usage Tracking" in the Lens preferences. If you don't wish to send usage data to Mirantis, you can disable the "Telemetry & Usage Tracking" in the Lens preferences.
1. Go to **File** > **Preferences** (**Lens** > **Preferences** on Mac). 1. Go to **File** > **Preferences** (**Lens** > **Preferences** on Mac). Or follow [this link](lens://app/preferences?highlight=telemetry-tracking).
2. Scroll down to **Telemetry & Usage Tracking** 2. Scroll down to **Telemetry & Usage Tracking**
3. Uncheck **Allow Telemetry & Usage Tracking**. 3. Uncheck **Allow Telemetry & Usage Tracking**.
This will silence all telemetry events from Lens going forward. Telemetry information may have been collected and sent up until the point when you disable this setting. This will silence all telemetry events from Lens going forward. Telemetry information may have been collected and sent up until the point when you disable this setting.
![Disable Telemetry & Usage Tracking](images/disabled-telemetry-usage-tracking.png) ![Disable Telemetry & Usage Tracking](images/disabled-telemetry-usage-tracking.png)

View File

@ -8,6 +8,7 @@ export default class TelemetryRendererExtension extends LensRendererExtension {
appPreferences = [ appPreferences = [
{ {
title: "Telemetry & Usage Tracking", title: "Telemetry & Usage Tracking",
id: "telemetry-tracking",
components: { components: {
Hint: () => <TelemetryPreferenceHint/>, Hint: () => <TelemetryPreferenceHint/>,
Input: () => <TelemetryPreferenceInput telemetry={telemetryPreferencesStore}/> Input: () => <TelemetryPreferenceInput telemetry={telemetryPreferencesStore}/>

View File

@ -184,10 +184,12 @@ export abstract class LensProtocolRouter extends Singleton {
* @param pathSchema the URI path schema to match against for this handler * @param pathSchema the URI path schema to match against for this handler
* @param handler a function that will be called if a protocol path matches * @param handler a function that will be called if a protocol path matches
*/ */
public addInternalHandler(urlSchema: string, handler: RouteHandler): void { public addInternalHandler(urlSchema: string, handler: RouteHandler): this {
pathToRegexp(urlSchema); // verify now that the schema is valid pathToRegexp(urlSchema); // verify now that the schema is valid
logger.info(`${LensProtocolRouter.LoggingPrefix}: internal registering ${urlSchema}`); logger.info(`${LensProtocolRouter.LoggingPrefix}: internal registering ${urlSchema}`);
this.internalRoutes.set(urlSchema, handler); this.internalRoutes.set(urlSchema, handler);
return this;
} }
/** /**

View File

@ -3,14 +3,20 @@ import { compile } from "path-to-regexp";
export interface IURLParams<P extends object = {}, Q extends object = {}> { export interface IURLParams<P extends object = {}, Q extends object = {}> {
params?: P; params?: P;
query?: Q; query?: Q;
fragment?: string;
} }
export function buildURL<P extends object = {}, Q extends object = {}>(path: string | any) { export function buildURL<P extends object = {}, Q extends object = {}>(path: string | any) {
const pathBuilder = compile(String(path)); const pathBuilder = compile(String(path));
return function ({ params, query }: IURLParams<P, Q> = {}) { return function ({ params, query, fragment }: IURLParams<P, Q> = {}): string {
const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : ""; const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : "";
const parts = [
pathBuilder(params),
queryParams && `?${queryParams}`,
fragment && `#${fragment}`,
];
return pathBuilder(params) + (queryParams ? `?${queryParams}` : ""); return parts.filter(Boolean).join("");
}; };
} }

View File

@ -8,10 +8,21 @@ export interface AppPreferenceComponents {
export interface AppPreferenceRegistration { export interface AppPreferenceRegistration {
title: string; title: string;
id?: string;
components: AppPreferenceComponents; components: AppPreferenceComponents;
} }
export class AppPreferenceRegistry extends BaseRegistry<AppPreferenceRegistration> { export interface RegisteredAppPreference extends AppPreferenceRegistration {
id: string;
}
export class AppPreferenceRegistry extends BaseRegistry<AppPreferenceRegistration, RegisteredAppPreference> {
getRegisteredItem(item: AppPreferenceRegistration): RegisteredAppPreference {
return {
id: item.id || item.title.toLowerCase().replace(/[^0-9a-zA-Z]+/g, "-"),
...item,
};
}
} }
export const appPreferenceRegistry = new AppPreferenceRegistry(); export const appPreferenceRegistry = new AppPreferenceRegistry();

View File

@ -1,8 +1,8 @@
import "./preferences.scss"; import "./preferences.scss";
import React from "react"; import React from "react";
import { computed, observable } from "mobx"; import { computed, observable, reaction } from "mobx";
import { observer } from "mobx-react"; import { disposeOnUnmount, observer } from "mobx-react";
import { userStore } from "../../../common/user-store"; import { userStore } from "../../../common/user-store";
import { isWindows } from "../../../common/vars"; import { isWindows } from "../../../common/vars";
@ -16,6 +16,7 @@ import { Select, SelectOption } from "../select";
import { HelmCharts } from "./helm-charts"; import { HelmCharts } from "./helm-charts";
import { KubectlBinaries } from "./kubectl-binaries"; import { KubectlBinaries } from "./kubectl-binaries";
import { ScrollSpy } from "../scroll-spy/scroll-spy"; import { ScrollSpy } from "../scroll-spy/scroll-spy";
import { navigation } from "../../navigation";
@observer @observer
export class Preferences extends React.Component { export class Preferences extends React.Component {
@ -29,6 +30,21 @@ export class Preferences extends React.Component {
})); }));
} }
componentDidMount() {
disposeOnUnmount(this, [
reaction(() => navigation.location.hash, hash => {
const fragment = hash.slice(1); // hash is /^(#\w.)?$/
if (fragment) {
// ignore empty framents
document.getElementById(fragment)?.scrollIntoView();
}
}, {
fireImmediately: true
})
]);
}
render() { render() {
const { preferences } = userStore; const { preferences } = userStore;
const header = <h2>Preferences</h2>; const header = <h2>Preferences</h2>;
@ -133,10 +149,10 @@ export class Preferences extends React.Component {
<section> <section>
<h1>Extensions</h1> <h1>Extensions</h1>
</section> </section>
{appPreferenceRegistry.getItems().map(({ title, components: { Hint, Input } }, index) => { {appPreferenceRegistry.getItems().map(({ title, id, components: { Hint, Input } }, index) => {
return ( return (
<section key={index} id={title}> <section key={index} id={title}>
<h2>{title}</h2> <h2 id={id}>{title}</h2>
<Input/> <Input/>
<small className="hint"> <small className="hint">
<Hint/> <Hint/>

View File

@ -12,7 +12,7 @@ import { ConfirmDialog } from "./components/confirm-dialog";
import { extensionLoader } from "../extensions/extension-loader"; import { extensionLoader } from "../extensions/extension-loader";
import { broadcastMessage } from "../common/ipc"; import { broadcastMessage } from "../common/ipc";
import { CommandContainer } from "./components/command-palette/command-container"; import { CommandContainer } from "./components/command-palette/command-container";
import { LensProtocolRouterRenderer } from "./protocol-handler/router"; import { LensProtocolRouterRenderer, bindProtocolAddRouteHandlers } from "./protocol-handler";
import { registerIpcHandlers } from "./ipc"; import { registerIpcHandlers } from "./ipc";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
@ -21,6 +21,7 @@ export class LensApp extends React.Component {
static async init() { static async init() {
extensionLoader.loadOnClusterManagerRenderer(); extensionLoader.loadOnClusterManagerRenderer();
LensProtocolRouterRenderer.getInstance<LensProtocolRouterRenderer>().init(); LensProtocolRouterRenderer.getInstance<LensProtocolRouterRenderer>().init();
bindProtocolAddRouteHandlers();
window.addEventListener("offline", () => { window.addEventListener("offline", () => {
broadcastMessage("network:offline"); broadcastMessage("network:offline");
}); });

View File

@ -5,11 +5,11 @@ import { clusterViewRoute, IClusterViewRouteParams } from "../components/cluster
import { navigation } from "./history"; import { navigation } from "./history";
export function navigate(location: LocationDescriptor) { export function navigate(location: LocationDescriptor) {
const currentLocation = navigation.getPath(); const currentLocation = navigation.location.pathname;
navigation.push(location); navigation.push(location);
if (currentLocation === navigation.getPath()) { if (currentLocation === navigation.location.pathname) {
navigation.goBack(); // prevent sequences of same url in history navigation.goBack(); // prevent sequences of same url in history
} }
} }

View File

@ -10,5 +10,5 @@ navigation.listen((location, action) => {
const isClusterView = !process.isMainFrame; const isClusterView = !process.isMainFrame;
const domain = global.location.href; const domain = global.location.href;
logger.debug(`[NAVIGATION]: ${action}`, { isClusterView, domain, location }); logger.debug(`[NAVIGATION]: ${action}-ing. Current is now:`, { isClusterView, domain, location });
}); });

View File

@ -1,10 +1,8 @@
// Navigation (renderer) // Navigation (renderer)
import { bindEvents } from "./events"; import { bindEvents } from "./events";
import { bindProtocolHandlers } from "./protocol-handlers";
export * from "./history"; export * from "./history";
export * from "./helpers"; export * from "./helpers";
bindEvents(); bindEvents();
bindProtocolHandlers();

View File

@ -1,10 +0,0 @@
import { LensProtocolRouterRenderer } from "../protocol-handler/router";
import { navigate } from "./helpers";
export function bindProtocolHandlers() {
const lprr = LensProtocolRouterRenderer.getInstance<LensProtocolRouterRenderer>();
lprr.addInternalHandler("/preferences", () => {
navigate("/preferences");
});
}

View File

@ -0,0 +1,58 @@
import { addClusterURL } from "../components/+add-cluster";
import { clusterSettingsURL } from "../components/+cluster-settings";
import { extensionsURL } from "../components/+extensions";
import { landingURL } from "../components/+landing-page";
import { preferencesURL } from "../components/+preferences";
import { clusterViewURL } from "../components/cluster-manager/cluster-view.route";
import { LensProtocolRouterRenderer } from "./router";
import { navigate } from "../navigation/helpers";
import { clusterStore } from "../../common/cluster-store";
import { workspaceStore } from "../../common/workspace-store";
export function bindProtocolAddRouteHandlers() {
LensProtocolRouterRenderer
.getInstance<LensProtocolRouterRenderer>()
.addInternalHandler("/preferences", ({ search: { highlight }}) => {
navigate(preferencesURL({ fragment: highlight }));
})
.addInternalHandler("/", () => {
navigate(landingURL());
})
.addInternalHandler("/landing", () => {
navigate(landingURL());
})
.addInternalHandler("/landing/:workspaceId", ({ pathname: { workspaceId } }) => {
if (workspaceStore.getById(workspaceId)) {
workspaceStore.setActive(workspaceId);
navigate(landingURL());
} else {
console.log("[APP-HANDLER]: workspace with given ID does not exist", { workspaceId });
}
})
.addInternalHandler("/cluster", () => {
navigate(addClusterURL());
})
.addInternalHandler("/cluster/:clusterId", ({ pathname: { clusterId } }) => {
const cluster = clusterStore.getById(clusterId);
if (cluster) {
workspaceStore.setActive(cluster.workspace);
navigate(clusterViewURL({ params: { clusterId } }));
} else {
console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId });
}
})
.addInternalHandler("/cluster/:clusterId/settings", ({ pathname: { clusterId } }) => {
const cluster = clusterStore.getById(clusterId);
if (cluster) {
workspaceStore.setActive(cluster.workspace);
navigate(clusterSettingsURL({ params: { clusterId } }));
} else {
console.log("[APP-HANDLER]: cluster with given ID does not exist", { clusterId });
}
})
.addInternalHandler("/extensions", () => {
navigate(extensionsURL());
});
}

View File

@ -1 +1,2 @@
export * from "./router.ts"; export * from "./router";
export * from "./app-handlers";

View File

@ -53,6 +53,16 @@ export default function generateExtensionTypes(): webpack.Configuration {
} }
} }
}, },
{
test: /\.(jpg|png|svg|map|ico)$/,
use: {
loader: "file-loader",
options: {
name: "images/[name]-[hash:6].[ext]",
esModule: false, // handle media imports in <template>, e.g <img src="../assets/logo.svg"> (react)
}
}
},
// for import scss files // for import scss files
{ {
test: /\.s?css$/, test: /\.s?css$/,