Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Views management refactoring #669

Merged
merged 54 commits into from
Aug 19, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
7d3e876
part 1
ixrock Aug 5, 2020
8925186
part 2
ixrock Aug 6, 2020
a78d5a0
cluster-view battling -- part 1 (iframe failing support nodeintegration)
ixrock Aug 6, 2020
5452fdb
ipc-refactoring, fixes
ixrock Aug 6, 2020
09c765c
cluster-view battling -- part 2
ixrock Aug 7, 2020
2287332
cluster-view battling -- part 3
ixrock Aug 7, 2020
2894f31
fixes
ixrock Aug 7, 2020
1204e9e
fix menu navigation
ixrock Aug 7, 2020
3e86729
cluster-view bugfixing, attempt 100500..
ixrock Aug 7, 2020
9818c07
Fixing Cluster Settings layout (#651)
aleksfront Aug 7, 2020
795a98c
base-migration-branch merge-fix
ixrock Aug 7, 2020
7364cb7
cluster-view reworks
ixrock Aug 8, 2020
0b80032
todo/fixme: hide active view on disconnect
ixrock Aug 8, 2020
ceca2d7
moved some runtime deps from dev-deps-list in package.json
ixrock Aug 10, 2020
8e6d886
cluster-view fixes
ixrock Aug 10, 2020
7b5092d
cluster-view more fixes & UX optimizations
ixrock Aug 10, 2020
6f6680b
fix: cluster-manager initial loading, redirect to startPage from any …
ixrock Aug 10, 2020
4ef26be
fix: handle navigation from global menu
ixrock Aug 10, 2020
d2be0e4
more fixes
ixrock Aug 10, 2020
8e626ee
Fix CRD api parsing (#622)
nevalla Jul 31, 2020
b3f97a6
Fix Resource Quota Rendering (#624)
Nokel81 Aug 4, 2020
6293dad
adding port-forward for containers in pods (#528)
jim-docker Aug 7, 2020
3a50255
use the Kubernetes regex for matching system names (#659)
Nokel81 Aug 7, 2020
df764ca
Removing legacy browserCheck() utility
aleksfront Aug 10, 2020
26e93e7
Revert select box-shadow (outline) color
aleksfront Aug 10, 2020
aa63624
Allowing to save prometheus service address
aleksfront Aug 10, 2020
bb382d2
Improving code readabilty
aleksfront Aug 10, 2020
503888a
Setting overflow:hidden on lens-view elem (#664)
aleksfront Aug 10, 2020
39391e7
Fixing broken/missing icons (#665)
aleksfront Aug 10, 2020
6c40d33
fix menu/navigation
ixrock Aug 10, 2020
93b42ff
moar random fixes
ixrock Aug 10, 2020
631826a
clean up
ixrock Aug 10, 2020
916a101
Fix Cluster Settings style specificity
aleksfront Aug 11, 2020
ae3fdea
fix: navigate to cluster-settings page from clusters-menu
ixrock Aug 11, 2020
260bbbd
Revert system menu layout (#671)
nevalla Aug 12, 2020
e7d4317
fix: navigating to cluster-view from common-view
ixrock Aug 12, 2020
7903eba
revering user-store refactoring
ixrock Aug 12, 2020
f5a978e
moving cluster-settings view lens-app views (common)
ixrock Aug 12, 2020
2228957
reverted cluster-manager.ts/getClusterForRequest()
ixrock Aug 12, 2020
77066ee
Adding unit tests to ClusterStore (#678)
aleksfront Aug 13, 2020
c4ae765
Navigate to landing page on cluster disconnect
aleksfront Aug 13, 2020
bc339e4
Add Cluster page visual fixes
aleksfront Aug 13, 2020
877fe18
Improve isUrl validator
aleksfront Aug 13, 2020
1987c12
Fixing Cluster Settings typo
aleksfront Aug 13, 2020
161b356
fix: use `env NODE_ENV=production` for `yarn compile`
ixrock Aug 13, 2020
8275e15
prod-build fix
ixrock Aug 13, 2020
a28d671
extracting missing localization
ixrock Aug 13, 2020
38aefc5
Reconnecting cluster after selection (#685)
aleksfront Aug 14, 2020
50a2e1f
fix: skip redefining global `__static` in tests
ixrock Aug 14, 2020
fb124ad
Preferences section restyling (#700)
aleksfront Aug 17, 2020
0353ee7
Replace webview to iframe (#701)
ixrock Aug 17, 2020
948421b
Fix integration tests (#698)
nevalla Aug 18, 2020
07c3143
Minor WizardLayout fix
aleksfront Aug 18, 2020
fe42219
Merge branch 'vue_react_migration' into views_management_refactoring
nevalla Aug 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
part 1
Signed-off-by: Roman <ixrock@gmail.com>
Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>
  • Loading branch information
ixrock authored and nevalla committed Aug 18, 2020
commit 7d3e87685baf3ceb5278635dd5397185641e71e7
15 changes: 6 additions & 9 deletions src/common/cluster-store.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import type { WorkspaceId } from "./workspace-store";
import path from "path";
import filenamify from "filenamify";
import { app, ipcRenderer, remote } from "electron";
import { copyFile, ensureDir, unlink } from "fs-extra";
import { unlink } from "fs-extra";
import { action, computed, observable, toJS } from "mobx";
import { appProto, noClustersHost } from "./vars";
import { BaseStore } from "./base-store";
import { Cluster, ClusterState } from "../main/cluster";
import migrations from "../migrations/cluster-store"
Expand Down Expand Up @@ -177,12 +175,11 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {

export const clusterStore = ClusterStore.getInstance<ClusterStore>();

export function isNoClustersView() {
return location.hostname === noClustersHost
}

export function getHostedClusterId() {
return location.hostname.split(".")[0];
export function getHostedClusterId(): ClusterId {
const clusterHost = location.hostname.match(/^(.*?)\.localhost/);
if (clusterHost) {
return clusterHost[1]
}
}

export function getHostedCluster(): Cluster {
Expand Down
2 changes: 1 addition & 1 deletion src/common/kube-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export function saveConfigToAppFiles(clusterId: string, kubeConfig: KubeConfig |

export async function getKubeConfigLocal(): Promise<string> {
try {
const configFile = path.join(process.env.HOME, '.kube', 'config');
const configFile = path.join(os.homedir(), '.kube', 'config');
const file = await readFile(configFile, "utf8");
const obj = yaml.safeLoad(file);
if (obj.contexts) {
Expand Down
11 changes: 6 additions & 5 deletions src/common/user-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,12 @@ export class UserStore extends BaseStore<UserStoreModel> {
if (kubeConfig) {
this.newContexts.clear();
const localContexts = loadConfig(kubeConfig).getContexts();
console.log(localContexts)
localContexts
.filter(ctx => ctx.cluster)
.filter(ctx => !this.seenContexts.has(ctx.name))
.forEach(ctx => this.newContexts.add(ctx.name));
localContexts.forEach(({ cluster, name }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change, the older version is more clear

Copy link
Contributor Author

@ixrock ixrock Aug 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for me, sorry. Older version more like functional-style, but I would not say it's more readable (for humans) + there is no need to run multiple cycles on what's can be updated in one run.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is matter of taste. I would revert this change as it's not related to views management at all, right?

if (!cluster) return;
if (!this.seenContexts.has(name)) {
this.newContexts.add(name)
}
})
}
}

Expand Down
5 changes: 0 additions & 5 deletions src/common/vars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ export const isDevelopment = isDebugging || !isProduction;
export const isTestEnv = !!process.env.JEST_WORKER_ID;

export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`
export const appProto = "lens" // app.getPath("userData") folder
export const staticProto = "static" // static folder (e.g. "static://RELEASE_NOTES.md")

// System paths
export const contextDir = process.cwd();
Expand All @@ -22,9 +20,6 @@ export const rendererDir = path.join(contextDir, "src/renderer");
export const htmlTemplate = path.resolve(rendererDir, "template.html");
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");

// System pages
export const noClustersHost = "no-clusters.localhost"

// Apis
export const apiPrefix = "/api" // local router apis
export const apiKubePrefix = "/api-kube" // k8s cluster apis
Expand Down
11 changes: 10 additions & 1 deletion src/common/workspace-store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { action, computed, observable, toJS } from "mobx";
import { action, computed, observable, reaction, toJS } from "mobx";
import { BaseStore } from "./base-store";
import { clusterStore } from "./cluster-store"

Expand All @@ -22,6 +22,15 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
super({
configName: "lens-workspace-store",
});

// switch to first available cluster in current workspace
reaction(() => this.currentWorkspaceId, workspaceId => {
const clusters = clusterStore.getByWorkspaceId(workspaceId);
const activeClusterInWorkspace = clusters.some(cluster => cluster.id === clusterStore.activeClusterId);
if (!activeClusterInWorkspace) {
clusterStore.activeClusterId = clusters.length ? clusters[0].id : null;
}
})
}

@observable currentWorkspaceId = WorkspaceStore.defaultId;
Expand Down
22 changes: 3 additions & 19 deletions src/main/cluster-manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type http from "http"
import { autorun } from "mobx";
import { apiKubePrefix } from "../common/vars";
import { ClusterId, clusterStore } from "../common/cluster-store"
import { Cluster } from "./cluster"
import { clusterIpc } from "../common/cluster-ipc";
Expand Down Expand Up @@ -50,23 +49,8 @@ export class ClusterManager {
}

getClusterForRequest(req: http.IncomingMessage): Cluster {
let cluster: Cluster = null

// lens-server is connecting to 127.0.0.1:<port>/<uid>
if (req.headers.host.startsWith("127.0.0.1")) {
const clusterId = req.url.split("/")[1]
if (clusterId) {
cluster = this.getCluster(clusterId)
if (cluster) {
// we need to swap path prefix so that request is proxied to kube api
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix)
}
}
} else {
const id = req.headers.host.split(".")[0]
cluster = this.getCluster(id)
}

return cluster;
logger.info(`getClusterForRequest(): ${req.headers.host}${req.url}`)
const clusterId = req.headers.host.split(".")[0]
return this.getCluster(clusterId)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why logic has been removed? Don't we use 127.0.0.1:<port>/uuid anymore anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems so.. Looks like outdated code from very beginning of lens-app.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked because I port/uuid addresses were used when doing requests outside of renderer (wildcard dns to localhost works only within browser scope). Anyway I don't like to see changes that are totally outside of PR scope, it makes review unnecessary complicated.

Copy link
Contributor Author

@ixrock ixrock Aug 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, reverted back. Someone should check if this is actually works, cause top comment:
// lens-server is connecting to 127.0.0.1:<port>/<uid>
which unclear in terms of what you saying how this might be used outside.

}
}
42 changes: 24 additions & 18 deletions src/main/cluster.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store"
import type { FeatureStatusMap } from "./feature"
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
import type { WorkspaceId } from "../common/workspace-store";
import { action, observable, reaction, toJS, when } from "mobx";
import type { FeatureStatusMap } from "./feature"
import { action, computed, observable, reaction, toJS, when } from "mobx";
import { apiKubePrefix } from "../common/vars";
import { broadcastIpc } from "../common/ipc";
import { ContextHandler } from "./context-handler"
Expand Down Expand Up @@ -52,7 +53,6 @@ export class Cluster implements ClusterModel {
@observable kubeConfigPath: string;
@observable apiUrl: string; // cluster server url
@observable kubeProxyUrl: string; // lens-proxy to kube-api url
@observable webContentUrl: string; // page content url for loading in renderer
@observable online: boolean;
@observable accessible: boolean;
@observable disconnected: boolean;
Expand All @@ -67,6 +67,11 @@ export class Cluster implements ClusterModel {
@observable allowedNamespaces: string[] = [];
@observable allowedResources: string[] = [];

@computed get host() {
const proxyHost = new URL(this.kubeProxyUrl).host;
return `${this.id}.${proxyHost}`
}

constructor(model: ClusterModel) {
this.updateModel(model);
}
Expand All @@ -80,20 +85,15 @@ export class Cluster implements ClusterModel {

@action
async init(port: number) {
if (this.initialized) {
return;
}
try {
this.contextHandler = new ContextHandler(this);
this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler);
this.kubeProxyUrl = `http://localhost:${port}${apiKubePrefix}`;
this.webContentUrl = `http://${this.id}.localhost:${port}`;
this.initialized = true;
logger.info(`[CLUSTER]: init success`, {
logger.info(`[CLUSTER]: "${this.contextName}" init success`, {
id: this.id,
serverUrl: this.apiUrl,
webContentUrl: this.webContentUrl,
kubeProxyUrl: this.kubeProxyUrl,
context: this.contextName,
apiUrl: this.apiUrl
});
} catch (err) {
logger.error(`[CLUSTER]: init failed: ${err}`, {
Expand Down Expand Up @@ -155,7 +155,7 @@ export class Cluster implements ClusterModel {
@action
async refresh() {
logger.info(`[CLUSTER]: refresh`, this.getMeta());
await this.refreshConnectionStatus();
await this.refreshConnectionStatus(); // refresh "version", "online", etc.
if (this.accessible) {
this.kubeCtl = new Kubectl(this.version)
this.distribution = this.detectKubernetesDistribution(this.version)
Expand Down Expand Up @@ -217,22 +217,28 @@ export class Cluster implements ClusterModel {
return uninstallFeature(name, this)
}

getPrometheusApiPrefix() {
return this.preferences.prometheus?.prefix || ""
}

protected async k8sRequest(path: string, options: RequestPromiseOptions = {}) {
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
const apiUrl = this.kubeProxyUrl + path;
return request(apiUrl, {
json: true,
timeout: 5000,
headers: {
Host: this.host, // provide cluster-id for ClusterManager.getClusterForRequest()
...(options.headers || {}),
Host: new URL(this.webContentUrl).host,
},
})
}

getMetrics(prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) {
const prometheusPrefix = this.preferences.prometheus?.prefix || "";
const metricsPath = `/api/v1/namespaces/${prometheusPath}/proxy${prometheusPrefix}/api/v1/query_range`;
return this.k8sRequest(metricsPath, {
resolveWithFullResponse: false,
json: true,
qs: queryParams,
})
}

protected async getConnectionStatus(): Promise<ClusterStatus> {
try {
const response = await this.k8sRequest("/version")
Expand Down
7 changes: 2 additions & 5 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import "../common/system-ca"
import "../common/prometheus-providers"
import { app, dialog } from "electron"
import { appName, appProto, staticDir, staticProto } from "../common/vars";
import { appName, staticDir } from "../common/vars";
import path from "path"
import { initMenu } from "./menu"
import { LensProxy } from "./lens-proxy"
import { WindowManager } from "./window-manager";
import { ClusterManager } from "./cluster-manager";
Expand Down Expand Up @@ -41,8 +40,7 @@ async function main() {
const updater = new AppUpdater()
updater.start();

registerFileProtocol(appProto, app.getPath("userData"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why appProto has been removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's never used

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a dumb question but how do we display uploaded cluster icons then?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to saving the base64 encoding of them in the user store

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It removes the filename issue that we were seeing with some unicode characters, and it removes the security problems when running on dev mode.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to saving the base64 encoding of them in the user store

Ok, do we have a migration for that?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not code one up, I should do that then

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not code one up, I should do that then

Yes we should have a migration. Otherwise Lens will drop all the existing cluster icons on upgrade.

registerFileProtocol(staticProto, staticDir);
registerFileProtocol("static", staticDir);

// find free port
let proxyPort: number
Expand Down Expand Up @@ -74,7 +72,6 @@ async function main() {

// create window manager and open app
windowManager = new WindowManager(proxyPort);
initMenu(windowManager);
}

app.on("ready", main);
Expand Down
33 changes: 13 additions & 20 deletions src/main/lens-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { openShell } from "./node-shell-session";
import { Router } from "./router"
import { ClusterManager } from "./cluster-manager"
import { ContextHandler } from "./context-handler";
import { apiKubePrefix, noClustersHost } from "../common/vars";
import { apiKubePrefix } from "../common/vars";
import logger from "./logger"

export class LensProxy {
protected origin: string
protected proxyServer: http.Server
protected router: Router
protected closed = false
Expand All @@ -21,12 +22,13 @@ export class LensProxy {
}

private constructor(protected port: number, protected clusterManager: ClusterManager) {
this.origin = `http://localhost:${port}`
this.router = new Router();
}

listen(port = this.port): this {
this.proxyServer = this.buildCustomProxy().listen(port);
logger.info(`LensProxy server has started http://localhost:${port}`);
logger.info(`LensProxy server has started at ${this.origin}`);
return this;
}

Expand Down Expand Up @@ -117,26 +119,17 @@ export class LensProxy {
}

protected async handleRequest(proxy: httpProxy, req: http.IncomingMessage, res: http.ServerResponse) {
if (req.headers.host.split(":")[0] === noClustersHost) {
this.router.handleStaticFile(req.url, res);
return;
}
const cluster = this.clusterManager.getClusterForRequest(req)
if (!cluster) {
const reqId = this.getRequestId(req);
logger.error("Got request to unknown cluster", { reqId })
res.statusCode = 503
res.end()
return
}
const contextHandler = cluster.contextHandler
await contextHandler.ensureServer();
const proxyTarget = await this.getProxyTarget(req, contextHandler)
if (proxyTarget) {
proxy.web(req, res, proxyTarget)
} else {
this.router.route(cluster, req, res);
if (cluster) {
await cluster.contextHandler.ensureServer();
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler)
if (proxyTarget) {
// allow to fetch apis in "clusterId.localhost:port" from "localhost:port"
res.setHeader("Access-Control-Allow-Origin", this.origin);
return proxy.web(req, res, proxyTarget);
}
}
this.router.route(cluster, req, res);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why route on an undefined cluster? Wouldn't it be better to change line 123 to be if (!cluster) throw... or if (!cluster) return ...?

}

protected async handleWsUpgrade(req: http.IncomingMessage, socket: net.Socket, head: Buffer) {
Expand Down
25 changes: 6 additions & 19 deletions src/main/menu.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { WindowManager } from "./window-manager";
import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, shell, webContents } from "electron"
import { autorun } from "mobx";
import { broadcastIpc } from "../common/ipc";
import { appName, isMac, issuesTrackerUrl, isWindows, slackUrl } from "../common/vars";
import { clusterStore } from "../common/cluster-store";
import { addClusterURL } from "../renderer/components/+add-cluster/add-cluster.route";
Expand All @@ -17,19 +16,7 @@ export function initMenu(windowManager: WindowManager) {
});
}

function buildMenu(windowManager: WindowManager) {
const hasClusters = clusterStore.hasClusters();
const activeClusterId = clusterStore.activeClusterId;

function navigate(url: string) {
const clusterView = windowManager.getClusterView(activeClusterId);
broadcastIpc({
channel: "menu:navigate",
webContentId: clusterView ? clusterView.id : undefined /*no-clusters*/,
args: [url],
});
}

export function buildMenu(windowManager: WindowManager) {
function macOnly(menuItems: MenuItemConstructorOptions[]): MenuItemConstructorOptions[] {
if (!isMac) return [];
return menuItems;
Expand All @@ -41,20 +28,20 @@ function buildMenu(windowManager: WindowManager) {
{
label: 'Add Cluster',
click() {
navigate(addClusterURL())
windowManager.navigateMain(addClusterURL())
}
},
...(hasClusters ? [{
...(clusterStore.activeCluster ? [{
label: 'Cluster Settings',
click() {
navigate(clusterSettingsURL())
windowManager.navigateMain(clusterSettingsURL())
}
}] : []),
{ type: 'separator' },
{
label: 'Preferences',
click() {
navigate(preferencesURL())
windowManager.navigateMain(preferencesURL())
}
},
...macOnly([
Expand Down Expand Up @@ -125,7 +112,7 @@ function buildMenu(windowManager: WindowManager) {
{
label: "What's new?",
click() {
navigate(whatsNewURL())
windowManager.navigateMain(whatsNewURL())
},
},
{
Expand Down
Loading