1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
* Tray icon #833 -- part 1

Signed-off-by: Roman <ixrock@gmail.com>

* Tray icon #833 -- part 2

Signed-off-by: Roman <ixrock@gmail.com>

* Tray icon #833 -- part 3

Signed-off-by: Roman <ixrock@gmail.com>

* Tray icon #833 -- part 4

Signed-off-by: Roman <ixrock@gmail.com>

* fix: lint / linux build failed

Signed-off-by: Roman <ixrock@gmail.com>

* allow to disable tray from preferences

Signed-off-by: Roman <ixrock@gmail.com>

* allow to tweak svg-icon before applying as tray-icon

Signed-off-by: Roman <ixrock@gmail.com>

* add checkbox indication, setActive workspace on cluster select

Signed-off-by: Roman <ixrock@gmail.com>

* fix build version (cannon find module 'react')

Signed-off-by: Roman <ixrock@gmail.com>

* - switching dark/light icon depending on os-x theme settings
- optimization: don't re-create tray icon on menu udpates (avoid blinking)

Signed-off-by: Roman <ixrock@gmail.com>

* fix: refresh icon after turning on/off + switching dark-mode

Signed-off-by: Roman <ixrock@gmail.com>

* allow to close main window and re-open from dock or tray icon

Signed-off-by: Roman <ixrock@gmail.com>

* small fix

Signed-off-by: Roman <ixrock@gmail.com>

* fix: ensure main-window from global menu

Signed-off-by: Roman <ixrock@gmail.com>

* chore

Signed-off-by: Roman <ixrock@gmail.com>

* fix: hide traffic-light buttons for tray window

Signed-off-by: Roman <ixrock@gmail.com>

* removed redundant tray window

Signed-off-by: Roman <ixrock@gmail.com>

* removed delay from base-store

Signed-off-by: Roman <ixrock@gmail.com>

* adding cluster fix (reverted changes from master)

Signed-off-by: Roman <ixrock@gmail.com>

* - hide icon in dock when main-window closed (mac-os only)
- added preferences checkbox to open app at system start-up

Signed-off-by: Roman <ixrock@gmail.com>

* handle quit app action from tray menu

Signed-off-by: Roman <ixrock@gmail.com>

* moved generating tray icons to build step

Signed-off-by: Roman <ixrock@gmail.com>

* Fix integration tests (#1080)

* Fix integration tests

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* Update integration/helpers/utils.ts

Co-authored-by: Sebastian Malton <sebastian@malton.name>
Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

Co-authored-by: Sebastian Malton <sebastian@malton.name>

* fix-build: invisible app icon when there are more files within "build/icons/*.png"

Signed-off-by: Roman <ixrock@gmail.com>

* chore

Signed-off-by: Roman <ixrock@gmail.com>

* yarn i18n.extract

Signed-off-by: Roman <ixrock@gmail.com>

* clean-up

Signed-off-by: Roman <ixrock@gmail.com>

* navigation refactoring, move out `buildUrl` to common/utils so `react` and `react-router` not required as package.json dependecies in runtime (main)

Signed-off-by: Roman <ixrock@gmail.com>

* Ignore namespace query param on integration tests (#1109)

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>

* merge-conflicts fixes

Signed-off-by: Roman <ixrock@gmail.com>

* support page fixes

Signed-off-by: Roman <ixrock@gmail.com>

* make eslint happy again

Signed-off-by: Roman <ixrock@gmail.com>

Co-authored-by: Lauri Nevala <lauri.nevala@gmail.com>
Co-authored-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Roman 2020-10-27 15:25:29 +02:00 committed by GitHub
parent e2a1bac0ed
commit 334815f71a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 939 additions and 417 deletions

View File

@ -40,9 +40,10 @@ brew cask install lens
Allows for faster separate re-runs of some of the more involved processes: Allows for faster separate re-runs of some of the more involved processes:
1. `yarn dev:main` compiles electron's main process part and start watching files 1. `yarn dev:main` compiles electron's main process app part
1. `yarn dev:renderer` compiles electron's renderer part and start watching files 1. `yarn dev:renderer` compiles electron's renderer app part
1. `yarn dev-run` runs app in dev-mode and restarts when electron's main process file has changed 1. `yarn dev:extension-types` compile declaration types for `@k8slens/extensions`
1. `yarn dev-run` runs app in dev-mode and auto-restart when main process file has changed
## Developer's ~~RTFM~~ recommended list: ## Developer's ~~RTFM~~ recommended list:

51
build/build_tray_icon.ts Normal file
View File

@ -0,0 +1,51 @@
// Generate tray icons from SVG to PNG + different sizes and colors (B&W)
// Command: `yarn build:tray-icons`
import path from "path"
import sharp from "sharp";
import jsdom from "jsdom"
import fs from "fs-extra"
export async function generateTrayIcon(
{
outputFilename = "tray_icon", // e.g. output tray_icon_dark@2x.png
svgIconPath = path.resolve(__dirname, "../src/renderer/components/icon/logo-lens.svg"),
outputFolder = path.resolve(__dirname, "./tray"),
dpiSuffix = "2x",
pixelSize = 32,
shouldUseDarkColors = false, // managed by electron.nativeTheme.shouldUseDarkColors
} = {}) {
outputFilename += shouldUseDarkColors ? "_dark" : ""
dpiSuffix = dpiSuffix !== "1x" ? `@${dpiSuffix}` : ""
const pngIconDestPath = path.resolve(outputFolder, `${outputFilename}${dpiSuffix}.png`)
try {
// Modify .SVG colors
const trayIconColor = shouldUseDarkColors ? "white" : "black";
const svgDom = await jsdom.JSDOM.fromFile(svgIconPath);
const svgRoot = svgDom.window.document.body.getElementsByTagName("svg")[0];
svgRoot.innerHTML += `<style>* {fill: ${trayIconColor} !important;}</style>`
const svgIconBuffer = Buffer.from(svgRoot.outerHTML);
// Resize and convert to .PNG
const pngIconBuffer: Buffer = await sharp(svgIconBuffer)
.resize({ width: pixelSize, height: pixelSize })
.png()
.toBuffer();
// Save icon
await fs.writeFile(pngIconDestPath, pngIconBuffer);
console.info(`[DONE]: Tray icon saved at "${pngIconDestPath}"`);
} catch (err) {
console.error(`[ERROR]: ${err}`);
}
}
// Run
const iconSizes: Record<string, number> = {
"1x": 16,
"2x": 32,
"3x": 48,
};
Object.entries(iconSizes).forEach(([dpiSuffix, pixelSize]) => {
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: false });
generateTrayIcon({ dpiSuffix, pixelSize, shouldUseDarkColors: true });
});

BIN
build/tray/tray_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 B

BIN
build/tray/tray_icon@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

BIN
build/tray/tray_icon@3x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -12,10 +12,7 @@ export default class SupportPageMainExtension extends LensMainExtension {
parentId: "help", parentId: "help",
label: "Support", label: "Support",
click() { click() {
windowManager.navigate({ windowManager.navigate(supportPageURL());
channel: "menu:navigate", // fixme: use windowManager.ensureMainWindow from Tray's PR
url: supportPageURL(),
});
} }
}) })
) )

View File

@ -3,13 +3,13 @@
import React from "react" import React from "react"
import { observer } from "mobx-react" import { observer } from "mobx-react"
import { CommonVars, Component } from "@k8slens/extensions"; import { App, Component } from "@k8slens/extensions";
@observer @observer
export class Support extends React.Component { export class Support extends React.Component {
render() { render() {
const { PageLayout } = Component; const { PageLayout } = Component;
const { slackUrl, issuesTrackerUrl } = CommonVars; const { slackUrl, issuesTrackerUrl } = App;
return ( return (
<PageLayout showOnTop className="Support" header={<h2>Support</h2>}> <PageLayout showOnTop className="Support" header={<h2>Support</h2>}>
<h2>Community Slack Channel</h2> <h2>Community Slack Channel</h2>

View File

@ -389,13 +389,13 @@ describe("Lens integration tests", () => {
it(`shows ${drawer} drawer`, async () => { it(`shows ${drawer} drawer`, async () => {
expect(clusterAdded).toBe(true) expect(clusterAdded).toBe(true)
await app.client.click(`.sidebar-nav #${drawerId} span.link-text`) await app.client.click(`.sidebar-nav #${drawerId} span.link-text`)
await app.client.waitUntilTextExists(`a[href="/${pages[0].href}"]`, pages[0].name) await app.client.waitUntilTextExists(`a[href^="/${pages[0].href}"]`, pages[0].name)
}) })
} }
pages.forEach(({ name, href, expectedSelector, expectedText }) => { pages.forEach(({ name, href, expectedSelector, expectedText }) => {
it(`shows ${drawer}->${name} page`, async () => { it(`shows ${drawer}->${name} page`, async () => {
expect(clusterAdded).toBe(true) expect(clusterAdded).toBe(true)
await app.client.click(`a[href="/${href}"]`) await app.client.click(`a[href^="/${href}"]`)
await app.client.waitUntilTextExists(expectedSelector, expectedText) await app.client.waitUntilTextExists(expectedSelector, expectedText)
}) })
}) })
@ -404,7 +404,7 @@ describe("Lens integration tests", () => {
it(`hides ${drawer} drawer`, async () => { it(`hides ${drawer} drawer`, async () => {
expect(clusterAdded).toBe(true) expect(clusterAdded).toBe(true)
await app.client.click(`.sidebar-nav #${drawerId} span.link-text`) await app.client.click(`.sidebar-nav #${drawerId} span.link-text`)
await expect(app.client.waitUntilTextExists(`a[href="/${pages[0].href}"]`, pages[0].name, 100)).rejects.toThrow() await expect(app.client.waitUntilTextExists(`a[href^="/${pages[0].href}"]`, pages[0].name, 100)).rejects.toThrow()
}) })
} }
}) })
@ -440,8 +440,8 @@ describe("Lens integration tests", () => {
it(`creates a pod in ${TEST_NAMESPACE} namespace`, async () => { it(`creates a pod in ${TEST_NAMESPACE} namespace`, async () => {
expect(clusterAdded).toBe(true) expect(clusterAdded).toBe(true)
await app.client.click(".sidebar-nav #workloads span.link-text") await app.client.click(".sidebar-nav #workloads span.link-text")
await app.client.waitUntilTextExists('a[href="/pods"]', "Pods") await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods")
await app.client.click('a[href="/pods"]') await app.client.click('a[href^="/pods"]')
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver") await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver")
await app.client.click('.Icon.new-dock-tab') await app.client.click('.Icon.new-dock-tab')
await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource") await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource")

View File

@ -13,7 +13,6 @@ export function setup(): Application {
path: AppPaths[process.platform], path: AppPaths[process.platform],
startTimeout: 30000, startTimeout: 30000,
waitTimeout: 60000, waitTimeout: 60000,
chromeDriverArgs: ['remote-debugging-port=9222'],
env: { env: {
CICD: "true" CICD: "true"
} }
@ -27,6 +26,6 @@ export async function tearDown(app: Application) {
try { try {
process.kill(pid, "SIGKILL"); process.kill(pid, "SIGKILL");
} catch (e) { } catch (e) {
return console.error(e)
} }
} }

View File

@ -88,7 +88,7 @@ msgid "Active"
msgstr "Active" msgstr "Active"
#: src/renderer/components/+add-cluster/add-cluster.tsx:310 #: src/renderer/components/+add-cluster/add-cluster.tsx:310
#: src/renderer/components/cluster-manager/clusters-menu.tsx:130 #: src/renderer/components/cluster-manager/clusters-menu.tsx:127
msgid "Add Cluster" msgid "Add Cluster"
msgstr "Add Cluster" msgstr "Add Cluster"
@ -219,11 +219,11 @@ msgstr "Allocatable"
msgid "Allow Privilege Escalation" msgid "Allow Privilege Escalation"
msgstr "Allow Privilege Escalation" msgstr "Allow Privilege Escalation"
#: src/renderer/components/+preferences/preferences.tsx:169 #: src/renderer/components/+preferences/preferences.tsx:172
msgid "Allow telemetry & usage tracking" msgid "Allow telemetry & usage tracking"
msgstr "Allow telemetry & usage tracking" msgstr "Allow telemetry & usage tracking"
#: src/renderer/components/+preferences/preferences.tsx:161 #: src/renderer/components/+preferences/preferences.tsx:164
msgid "Allow untrusted Certificate Authorities" msgid "Allow untrusted Certificate Authorities"
msgstr "Allow untrusted Certificate Authorities" msgstr "Allow untrusted Certificate Authorities"
@ -301,6 +301,14 @@ msgstr "Associate clusters and choose the ones you want to access via quick laun
msgid "Auth App Role" msgid "Auth App Role"
msgstr "Auth App Role" msgstr "Auth App Role"
#: src/renderer/components/+preferences/preferences.tsx:160
msgid "Auto start-up"
msgstr "Auto start-up"
#: src/renderer/components/+preferences/preferences.tsx:161
msgid "Automatically start Lens on login"
msgstr "Automatically start Lens on login"
#: src/renderer/components/error-boundary/error-boundary.tsx:53 #: src/renderer/components/error-boundary/error-boundary.tsx:53
#: src/renderer/components/wizard/wizard.tsx:130 #: src/renderer/components/wizard/wizard.tsx:130
msgid "Back" msgid "Back"
@ -422,7 +430,7 @@ msgstr "Cancel"
msgid "Capacity" msgid "Capacity"
msgstr "Capacity" msgstr "Capacity"
#: src/renderer/components/+preferences/preferences.tsx:160 #: src/renderer/components/+preferences/preferences.tsx:163
msgid "Certificate Trust" msgid "Certificate Trust"
msgstr "Certificate Trust" msgstr "Certificate Trust"
@ -817,7 +825,7 @@ msgstr "Desired Healthy"
msgid "Desired number of replicas" msgid "Desired number of replicas"
msgstr "Desired number of replicas" msgstr "Desired number of replicas"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:65 #: src/renderer/components/cluster-manager/clusters-menu.tsx:62
msgid "Disconnect" msgid "Disconnect"
msgstr "Disconnect" msgstr "Disconnect"
@ -831,7 +839,7 @@ msgstr "Disk"
msgid "Disk:" msgid "Disk:"
msgstr "Disk:" msgstr "Disk:"
#: src/renderer/components/+preferences/preferences.tsx:165 #: src/renderer/components/+preferences/preferences.tsx:168
msgid "Does not affect cluster communications!" msgid "Does not affect cluster communications!"
msgstr "Does not affect cluster communications!" msgstr "Does not affect cluster communications!"
@ -927,8 +935,8 @@ msgstr "Environment"
msgid "Error stack" msgid "Error stack"
msgstr "Error stack" msgstr "Error stack"
#: src/renderer/components/+add-cluster/add-cluster.tsx:89 #: src/renderer/components/+add-cluster/add-cluster.tsx:88
#: src/renderer/components/+add-cluster/add-cluster.tsx:130 #: src/renderer/components/+add-cluster/add-cluster.tsx:129
msgid "Error while adding cluster(s): {0}" msgid "Error while adding cluster(s): {0}"
msgstr "Error while adding cluster(s): {0}" msgstr "Error while adding cluster(s): {0}"
@ -1581,7 +1589,7 @@ msgstr "Namespaces"
msgid "Namespaces: {0}" msgid "Namespaces: {0}"
msgstr "Namespaces: {0}" msgstr "Namespaces: {0}"
#: src/renderer/components/+preferences/preferences.tsx:164 #: src/renderer/components/+preferences/preferences.tsx:167
msgid "Needed with some corporate proxies that do certificate re-writing." msgid "Needed with some corporate proxies that do certificate re-writing."
msgstr "Needed with some corporate proxies that do certificate re-writing." msgstr "Needed with some corporate proxies that do certificate re-writing."
@ -1798,7 +1806,7 @@ msgstr "Persistent Volume Claims"
msgid "Persistent Volumes" msgid "Persistent Volumes"
msgstr "Persistent Volumes" msgstr "Persistent Volumes"
#: src/renderer/components/+add-cluster/add-cluster.tsx:75 #: src/renderer/components/+add-cluster/add-cluster.tsx:74
msgid "Please select at least one cluster context" msgid "Please select at least one cluster context"
msgstr "Please select at least one cluster context" msgstr "Please select at least one cluster context"
@ -2025,8 +2033,8 @@ msgstr "Releases"
#: src/renderer/components/+preferences/preferences.tsx:152 #: src/renderer/components/+preferences/preferences.tsx:152
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60
#: src/renderer/components/cluster-manager/clusters-menu.tsx:76 #: src/renderer/components/cluster-manager/clusters-menu.tsx:73
#: src/renderer/components/cluster-manager/clusters-menu.tsx:82 #: src/renderer/components/cluster-manager/clusters-menu.tsx:79
#: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/item-object-list/item-list-layout.tsx:179
#: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:49
#: src/renderer/components/menu/menu-actions.tsx:85 #: src/renderer/components/menu/menu-actions.tsx:85
@ -2470,7 +2478,7 @@ msgstr "Set"
msgid "Set quota" msgid "Set quota"
msgstr "Set quota" msgstr "Set quota"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:53 #: src/renderer/components/cluster-manager/clusters-menu.tsx:51
msgid "Settings" msgid "Settings"
msgstr "Settings" msgstr "Settings"
@ -2613,7 +2621,7 @@ msgstr "Submitting.."
msgid "Subsets" msgid "Subsets"
msgstr "Subsets" msgstr "Subsets"
#: src/renderer/components/+add-cluster/add-cluster.tsx:122 #: src/renderer/components/+add-cluster/add-cluster.tsx:121
msgid "Successfully imported <0>{0}</0> cluster(s)" msgid "Successfully imported <0>{0}</0> cluster(s)"
msgstr "Successfully imported <0>{0}</0> cluster(s)" msgstr "Successfully imported <0>{0}</0> cluster(s)"
@ -2635,11 +2643,11 @@ msgstr "TLS"
msgid "Taints" msgid "Taints"
msgstr "Taints" msgstr "Taints"
#: src/renderer/components/+preferences/preferences.tsx:168 #: src/renderer/components/+preferences/preferences.tsx:171
msgid "Telemetry & Usage Tracking" msgid "Telemetry & Usage Tracking"
msgstr "Telemetry & Usage Tracking" msgstr "Telemetry & Usage Tracking"
#: src/renderer/components/+preferences/preferences.tsx:171 #: src/renderer/components/+preferences/preferences.tsx:174
msgid "Telemetry & usage data is collected to continuously improve the Lens experience." msgid "Telemetry & usage data is collected to continuously improve the Lens experience."
msgstr "Telemetry & usage data is collected to continuously improve the Lens experience." msgstr "Telemetry & usage data is collected to continuously improve the Lens experience."
@ -2675,7 +2683,7 @@ msgstr "This field must be a valid path"
msgid "This is the quick launch menu." msgid "This is the quick launch menu."
msgstr "This is the quick launch menu." msgstr "This is the quick launch menu."
#: src/renderer/components/+preferences/preferences.tsx:163 #: src/renderer/components/+preferences/preferences.tsx:166
msgid "This will make Lens to trust ANY certificate authority without any validations." msgid "This will make Lens to trust ANY certificate authority without any validations."
msgstr "This will make Lens to trust ANY certificate authority without any validations." msgstr "This will make Lens to trust ANY certificate authority without any validations."
@ -2953,7 +2961,7 @@ msgstr "listKind"
msgid "never" msgid "never"
msgstr "never" msgstr "never"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:133 #: src/renderer/components/cluster-manager/clusters-menu.tsx:130
msgid "new" msgid "new"
msgstr "new" msgstr "new"

View File

@ -88,7 +88,7 @@ msgid "Active"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:310 #: src/renderer/components/+add-cluster/add-cluster.tsx:310
#: src/renderer/components/cluster-manager/clusters-menu.tsx:130 #: src/renderer/components/cluster-manager/clusters-menu.tsx:127
msgid "Add Cluster" msgid "Add Cluster"
msgstr "" msgstr ""
@ -219,11 +219,11 @@ msgstr ""
msgid "Allow Privilege Escalation" msgid "Allow Privilege Escalation"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:169 #: src/renderer/components/+preferences/preferences.tsx:172
msgid "Allow telemetry & usage tracking" msgid "Allow telemetry & usage tracking"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:161 #: src/renderer/components/+preferences/preferences.tsx:164
msgid "Allow untrusted Certificate Authorities" msgid "Allow untrusted Certificate Authorities"
msgstr "" msgstr ""
@ -301,6 +301,14 @@ msgstr ""
msgid "Auth App Role" msgid "Auth App Role"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:160
msgid "Auto start-up"
msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:161
msgid "Automatically start Lens on login"
msgstr ""
#: src/renderer/components/error-boundary/error-boundary.tsx:53 #: src/renderer/components/error-boundary/error-boundary.tsx:53
#: src/renderer/components/wizard/wizard.tsx:130 #: src/renderer/components/wizard/wizard.tsx:130
msgid "Back" msgid "Back"
@ -422,7 +430,7 @@ msgstr ""
msgid "Capacity" msgid "Capacity"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:160 #: src/renderer/components/+preferences/preferences.tsx:163
msgid "Certificate Trust" msgid "Certificate Trust"
msgstr "" msgstr ""
@ -813,7 +821,7 @@ msgstr ""
msgid "Desired number of replicas" msgid "Desired number of replicas"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:65 #: src/renderer/components/cluster-manager/clusters-menu.tsx:62
msgid "Disconnect" msgid "Disconnect"
msgstr "" msgstr ""
@ -827,7 +835,7 @@ msgstr ""
msgid "Disk:" msgid "Disk:"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:165 #: src/renderer/components/+preferences/preferences.tsx:168
msgid "Does not affect cluster communications!" msgid "Does not affect cluster communications!"
msgstr "" msgstr ""
@ -923,8 +931,8 @@ msgstr ""
msgid "Error stack" msgid "Error stack"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:89 #: src/renderer/components/+add-cluster/add-cluster.tsx:88
#: src/renderer/components/+add-cluster/add-cluster.tsx:130 #: src/renderer/components/+add-cluster/add-cluster.tsx:129
msgid "Error while adding cluster(s): {0}" msgid "Error while adding cluster(s): {0}"
msgstr "" msgstr ""
@ -1572,7 +1580,7 @@ msgstr ""
msgid "Namespaces: {0}" msgid "Namespaces: {0}"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:164 #: src/renderer/components/+preferences/preferences.tsx:167
msgid "Needed with some corporate proxies that do certificate re-writing." msgid "Needed with some corporate proxies that do certificate re-writing."
msgstr "" msgstr ""
@ -1781,7 +1789,7 @@ msgstr ""
msgid "Persistent Volumes" msgid "Persistent Volumes"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:75 #: src/renderer/components/+add-cluster/add-cluster.tsx:74
msgid "Please select at least one cluster context" msgid "Please select at least one cluster context"
msgstr "" msgstr ""
@ -2008,8 +2016,8 @@ msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:152 #: src/renderer/components/+preferences/preferences.tsx:152
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60
#: src/renderer/components/cluster-manager/clusters-menu.tsx:76 #: src/renderer/components/cluster-manager/clusters-menu.tsx:73
#: src/renderer/components/cluster-manager/clusters-menu.tsx:82 #: src/renderer/components/cluster-manager/clusters-menu.tsx:79
#: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/item-object-list/item-list-layout.tsx:179
#: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:49
#: src/renderer/components/menu/menu-actions.tsx:85 #: src/renderer/components/menu/menu-actions.tsx:85
@ -2453,7 +2461,7 @@ msgstr ""
msgid "Set quota" msgid "Set quota"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:53 #: src/renderer/components/cluster-manager/clusters-menu.tsx:51
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
@ -2596,7 +2604,7 @@ msgstr ""
msgid "Subsets" msgid "Subsets"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:122 #: src/renderer/components/+add-cluster/add-cluster.tsx:121
msgid "Successfully imported <0>{0}</0> cluster(s)" msgid "Successfully imported <0>{0}</0> cluster(s)"
msgstr "" msgstr ""
@ -2618,11 +2626,11 @@ msgstr ""
msgid "Taints" msgid "Taints"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:168 #: src/renderer/components/+preferences/preferences.tsx:171
msgid "Telemetry & Usage Tracking" msgid "Telemetry & Usage Tracking"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:171 #: src/renderer/components/+preferences/preferences.tsx:174
msgid "Telemetry & usage data is collected to continuously improve the Lens experience." msgid "Telemetry & usage data is collected to continuously improve the Lens experience."
msgstr "" msgstr ""
@ -2658,7 +2666,7 @@ msgstr ""
msgid "This is the quick launch menu." msgid "This is the quick launch menu."
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:163 #: src/renderer/components/+preferences/preferences.tsx:166
msgid "This will make Lens to trust ANY certificate authority without any validations." msgid "This will make Lens to trust ANY certificate authority without any validations."
msgstr "" msgstr ""
@ -2936,7 +2944,7 @@ msgstr ""
msgid "never" msgid "never"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:133 #: src/renderer/components/cluster-manager/clusters-menu.tsx:130
msgid "new" msgid "new"
msgstr "" msgstr ""

View File

@ -89,7 +89,7 @@ msgid "Active"
msgstr "Активный" msgstr "Активный"
#: src/renderer/components/+add-cluster/add-cluster.tsx:310 #: src/renderer/components/+add-cluster/add-cluster.tsx:310
#: src/renderer/components/cluster-manager/clusters-menu.tsx:130 #: src/renderer/components/cluster-manager/clusters-menu.tsx:127
msgid "Add Cluster" msgid "Add Cluster"
msgstr "" msgstr ""
@ -220,11 +220,11 @@ msgstr ""
msgid "Allow Privilege Escalation" msgid "Allow Privilege Escalation"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:169 #: src/renderer/components/+preferences/preferences.tsx:172
msgid "Allow telemetry & usage tracking" msgid "Allow telemetry & usage tracking"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:161 #: src/renderer/components/+preferences/preferences.tsx:164
msgid "Allow untrusted Certificate Authorities" msgid "Allow untrusted Certificate Authorities"
msgstr "" msgstr ""
@ -302,6 +302,14 @@ msgstr ""
msgid "Auth App Role" msgid "Auth App Role"
msgstr "Auth App Role" msgstr "Auth App Role"
#: src/renderer/components/+preferences/preferences.tsx:160
msgid "Auto start-up"
msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:161
msgid "Automatically start Lens on login"
msgstr ""
#: src/renderer/components/error-boundary/error-boundary.tsx:53 #: src/renderer/components/error-boundary/error-boundary.tsx:53
#: src/renderer/components/wizard/wizard.tsx:130 #: src/renderer/components/wizard/wizard.tsx:130
msgid "Back" msgid "Back"
@ -423,7 +431,7 @@ msgstr "Отмена"
msgid "Capacity" msgid "Capacity"
msgstr "Емкость" msgstr "Емкость"
#: src/renderer/components/+preferences/preferences.tsx:160 #: src/renderer/components/+preferences/preferences.tsx:163
msgid "Certificate Trust" msgid "Certificate Trust"
msgstr "" msgstr ""
@ -818,7 +826,7 @@ msgstr ""
msgid "Desired number of replicas" msgid "Desired number of replicas"
msgstr "Нужный уровень реплик" msgstr "Нужный уровень реплик"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:65 #: src/renderer/components/cluster-manager/clusters-menu.tsx:62
msgid "Disconnect" msgid "Disconnect"
msgstr "" msgstr ""
@ -832,7 +840,7 @@ msgstr "Диск"
msgid "Disk:" msgid "Disk:"
msgstr "Диск:" msgstr "Диск:"
#: src/renderer/components/+preferences/preferences.tsx:165 #: src/renderer/components/+preferences/preferences.tsx:168
msgid "Does not affect cluster communications!" msgid "Does not affect cluster communications!"
msgstr "" msgstr ""
@ -928,8 +936,8 @@ msgstr "Среда"
msgid "Error stack" msgid "Error stack"
msgstr "Стэк ошибки" msgstr "Стэк ошибки"
#: src/renderer/components/+add-cluster/add-cluster.tsx:89 #: src/renderer/components/+add-cluster/add-cluster.tsx:88
#: src/renderer/components/+add-cluster/add-cluster.tsx:130 #: src/renderer/components/+add-cluster/add-cluster.tsx:129
msgid "Error while adding cluster(s): {0}" msgid "Error while adding cluster(s): {0}"
msgstr "" msgstr ""
@ -1582,7 +1590,7 @@ msgstr "Namespaces"
msgid "Namespaces: {0}" msgid "Namespaces: {0}"
msgstr "Namespaces: {0}" msgstr "Namespaces: {0}"
#: src/renderer/components/+preferences/preferences.tsx:164 #: src/renderer/components/+preferences/preferences.tsx:167
msgid "Needed with some corporate proxies that do certificate re-writing." msgid "Needed with some corporate proxies that do certificate re-writing."
msgstr "" msgstr ""
@ -1799,7 +1807,7 @@ msgstr "Persistent Volume Claims"
msgid "Persistent Volumes" msgid "Persistent Volumes"
msgstr "Persistent Volumes" msgstr "Persistent Volumes"
#: src/renderer/components/+add-cluster/add-cluster.tsx:75 #: src/renderer/components/+add-cluster/add-cluster.tsx:74
msgid "Please select at least one cluster context" msgid "Please select at least one cluster context"
msgstr "" msgstr ""
@ -2026,8 +2034,8 @@ msgstr "Релизы"
#: src/renderer/components/+preferences/preferences.tsx:152 #: src/renderer/components/+preferences/preferences.tsx:152
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60
#: src/renderer/components/cluster-manager/clusters-menu.tsx:76 #: src/renderer/components/cluster-manager/clusters-menu.tsx:73
#: src/renderer/components/cluster-manager/clusters-menu.tsx:82 #: src/renderer/components/cluster-manager/clusters-menu.tsx:79
#: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/item-object-list/item-list-layout.tsx:179
#: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:49
#: src/renderer/components/menu/menu-actions.tsx:85 #: src/renderer/components/menu/menu-actions.tsx:85
@ -2471,7 +2479,7 @@ msgstr "Установлено"
msgid "Set quota" msgid "Set quota"
msgstr "Установить квоту" msgstr "Установить квоту"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:53 #: src/renderer/components/cluster-manager/clusters-menu.tsx:51
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
@ -2614,7 +2622,7 @@ msgstr "Применение.."
msgid "Subsets" msgid "Subsets"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:122 #: src/renderer/components/+add-cluster/add-cluster.tsx:121
msgid "Successfully imported <0>{0}</0> cluster(s)" msgid "Successfully imported <0>{0}</0> cluster(s)"
msgstr "" msgstr ""
@ -2636,11 +2644,11 @@ msgstr "TLS"
msgid "Taints" msgid "Taints"
msgstr "Метки блокировки" msgstr "Метки блокировки"
#: src/renderer/components/+preferences/preferences.tsx:168 #: src/renderer/components/+preferences/preferences.tsx:171
msgid "Telemetry & Usage Tracking" msgid "Telemetry & Usage Tracking"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:171 #: src/renderer/components/+preferences/preferences.tsx:174
msgid "Telemetry & usage data is collected to continuously improve the Lens experience." msgid "Telemetry & usage data is collected to continuously improve the Lens experience."
msgstr "" msgstr ""
@ -2676,7 +2684,7 @@ msgstr ""
msgid "This is the quick launch menu." msgid "This is the quick launch menu."
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:163 #: src/renderer/components/+preferences/preferences.tsx:166
msgid "This will make Lens to trust ANY certificate authority without any validations." msgid "This will make Lens to trust ANY certificate authority without any validations."
msgstr "" msgstr ""
@ -2954,7 +2962,7 @@ msgstr ""
msgid "never" msgid "never"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:133 #: src/renderer/components/cluster-manager/clusters-menu.tsx:130
msgid "new" msgid "new"
msgstr "" msgstr ""

View File

@ -16,12 +16,12 @@
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"", "dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"",
"dev:main": "yarn compile:main --watch", "dev:main": "yarn compile:main --watch",
"dev:renderer": "yarn compile:renderer --watch", "dev:renderer": "yarn compile:renderer --watch",
"dev:extension-rollup": "yarn compile:extension-rollup --watch", "dev:extension-types": "yarn compile:extension-types --watch",
"compile": "env NODE_ENV=production concurrently yarn:compile:*", "compile": "env NODE_ENV=production concurrently yarn:compile:*",
"compile:main": "webpack --config webpack.main.ts", "compile:main": "webpack --config webpack.main.ts",
"compile:renderer": "webpack --config webpack.renderer.ts", "compile:renderer": "webpack --config webpack.renderer.ts",
"compile:i18n": "lingui compile", "compile:i18n": "lingui compile",
"compile:extension-rollup": "rollup --config src/extensions/rollup.config.js", "compile:extension-types": "rollup --config src/extensions/rollup.config.js",
"npm:fix-package-version": "ts-node build/set_npm_version.ts", "npm:fix-package-version": "ts-node build/set_npm_version.ts",
"build:linux": "yarn compile && electron-builder --linux --dir -c.productName=Lens", "build:linux": "yarn compile && electron-builder --linux --dir -c.productName=Lens",
"build:mac": "yarn compile && electron-builder --mac --dir -c.productName=Lens", "build:mac": "yarn compile && electron-builder --mac --dir -c.productName=Lens",
@ -36,6 +36,7 @@
"download-bins": "concurrently yarn:download:*", "download-bins": "concurrently yarn:download:*",
"download:kubectl": "yarn run ts-node build/download_kubectl.ts", "download:kubectl": "yarn run ts-node build/download_kubectl.ts",
"download:helm": "yarn run ts-node build/download_helm.ts", "download:helm": "yarn run ts-node build/download_helm.ts",
"build:tray-icons": "yarn run ts-node build/build_tray_icon.ts",
"lint": "eslint $@ --ext js,ts,tsx --max-warnings=0 src/" "lint": "eslint $@ --ext js,ts,tsx --max-warnings=0 src/"
}, },
"config": { "config": {
@ -97,6 +98,11 @@
"to": "static/", "to": "static/",
"filter": "!**/main.js" "filter": "!**/main.js"
}, },
{
"from": "build/tray",
"to": "static/icons",
"filter": "*.png"
},
{ {
"from": "extensions/", "from": "extensions/",
"to": "./extensions/", "to": "./extensions/",
@ -186,6 +192,20 @@
"@hapi/call": "^8.0.0", "@hapi/call": "^8.0.0",
"@hapi/subtext": "^7.0.3", "@hapi/subtext": "^7.0.3",
"@kubernetes/client-node": "^0.12.0", "@kubernetes/client-node": "^0.12.0",
"@types/crypto-js": "^3.1.47",
"@types/electron-window-state": "^2.0.34",
"@types/fs-extra": "^9.0.1",
"@types/http-proxy": "^1.17.4",
"@types/js-yaml": "^3.12.4",
"@types/jsdom": "^16.2.4",
"@types/jsonpath": "^0.2.0",
"@types/lodash": "^4.14.155",
"@types/marked": "^0.7.4",
"@types/mock-fs": "^4.10.0",
"@types/node": "^12.12.45",
"@types/proper-lockfile": "^4.1.1",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/tar": "^4.0.3",
"array-move": "^3.0.0", "array-move": "^3.0.0",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"command-exists": "1.2.9", "command-exists": "1.2.9",
@ -198,8 +218,8 @@
"fs-extra": "^9.0.1", "fs-extra": "^9.0.1",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"immer": "^7.0.5",
"js-yaml": "^3.14.0", "js-yaml": "^3.14.0",
"jsdom": "^16.4.0",
"jsonpath": "^1.0.2", "jsonpath": "^1.0.2",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"mac-ca": "^1.0.4", "mac-ca": "^1.0.4",
@ -276,6 +296,7 @@
"@types/request": "^2.48.5", "@types/request": "^2.48.5",
"@types/request-promise-native": "^1.0.17", "@types/request-promise-native": "^1.0.17",
"@types/semver": "^7.2.0", "@types/semver": "^7.2.0",
"@types/sharp": "^0.26.0",
"@types/shelljs": "^0.8.8", "@types/shelljs": "^0.8.8",
"@types/spdy": "^3.4.4", "@types/spdy": "^3.4.4",
"@types/tar": "^4.0.3", "@types/tar": "^4.0.3",
@ -327,7 +348,7 @@
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"progress-bar-webpack-plugin": "^2.1.0", "progress-bar-webpack-plugin": "^2.1.0",
"raw-loader": "^4.0.1", "raw-loader": "^4.0.1",
"react": "^16.13.1", "react": "^16.14.0",
"react-beautiful-dnd": "^13.0.0", "react-beautiful-dnd": "^13.0.0",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-router": "^5.2.0", "react-router": "^5.2.0",
@ -339,6 +360,7 @@
"rollup-plugin-ignore-import": "^1.3.2", "rollup-plugin-ignore-import": "^1.3.2",
"rollup-pluginutils": "^2.8.2", "rollup-pluginutils": "^2.8.2",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"sharp": "^0.26.1",
"spectron": "11.0.0", "spectron": "11.0.0",
"style-loader": "^1.2.1", "style-loader": "^1.2.1",
"terser-webpack-plugin": "^3.0.3", "terser-webpack-plugin": "^3.0.3",

View File

@ -1,6 +1,6 @@
import type { WorkspaceId } from "./workspace-store"; import type { WorkspaceId } from "./workspace-store";
import path from "path"; import path from "path";
import { app, ipcRenderer, remote, webFrame, webContents } from "electron"; import { app, ipcRenderer, remote, webFrame } from "electron";
import { unlink } from "fs-extra"; import { unlink } from "fs-extra";
import { action, computed, observable, toJS } from "mobx"; import { action, computed, observable, toJS } from "mobx";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";
@ -113,7 +113,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
@action @action
setActive(id: ClusterId) { setActive(id: ClusterId) {
this.activeClusterId = id; this.activeClusterId = this.clusters.has(id) ? id : null;
} }
@action @action
@ -160,7 +160,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
if (cluster) { if (cluster) {
this.clusters.delete(clusterId); this.clusters.delete(clusterId);
if (this.activeClusterId === clusterId) { if (this.activeClusterId === clusterId) {
this.activeClusterId = null; this.setActive(null);
} }
// remove only custom kubeconfigs (pasted as text) // remove only custom kubeconfigs (pasted as text)
if (cluster.kubeConfigPath == ClusterStore.getCustomKubeConfigPath(clusterId)) { if (cluster.kubeConfigPath == ClusterStore.getCustomKubeConfigPath(clusterId)) {

View File

@ -27,6 +27,7 @@ export interface UserPreferences {
downloadKubectlBinaries?: boolean; downloadKubectlBinaries?: boolean;
downloadBinariesPath?: string; downloadBinariesPath?: string;
kubectlBinariesPath?: string; kubectlBinariesPath?: string;
openAtLogin?: boolean;
} }
export class UserStore extends BaseStore<UserStoreModel> { export class UserStore extends BaseStore<UserStoreModel> {
@ -38,14 +39,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
migrations: migrations, migrations: migrations,
}); });
// track telemetry availability this.handleOnLoad();
reaction(() => this.preferences.allowTelemetry, allowed => {
appEventBus.emit({name: "telemetry", action: allowed ? "enabled" : "disabled"})
});
// refresh new contexts
this.whenLoaded.then(this.refreshNewContexts);
reaction(() => this.kubeConfigPath, this.refreshNewContexts);
} }
@observable lastSeenAppVersion = "0.0.0" @observable lastSeenAppVersion = "0.0.0"
@ -59,8 +53,31 @@ export class UserStore extends BaseStore<UserStoreModel> {
colorTheme: UserStore.defaultTheme, colorTheme: UserStore.defaultTheme,
downloadMirror: "default", downloadMirror: "default",
downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version
openAtLogin: true,
}; };
protected async handleOnLoad() {
await this.whenLoaded;
// refresh new contexts
this.refreshNewContexts();
reaction(() => this.kubeConfigPath, this.refreshNewContexts);
if (app) {
// track telemetry availability
reaction(() => this.preferences.allowTelemetry, allowed => {
appEventBus.emit({name: "telemetry", action: allowed ? "enabled" : "disabled"})
});
// open at system start-up
reaction(() => this.preferences.openAtLogin, open => {
app.setLoginItemSettings({ openAtLogin: open });
}, {
fireImmediately: true,
});
}
}
get isNewVersion() { get isNewVersion() {
return semver.gt(getAppVersion(), this.lastSeenAppVersion); return semver.gt(getAppVersion(), this.lastSeenAppVersion);
} }

View File

@ -0,0 +1,14 @@
import { compile } from "path-to-regexp"
export interface IURLParams<P extends object = {}, Q extends object = {}> {
params?: P;
query?: Q;
}
export function buildURL<P extends object = {}, Q extends object = {}>(path: string | any) {
const pathBuilder = compile(String(path));
return function ({ params, query }: IURLParams<P, Q> = {}) {
const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : ""
return pathBuilder(params) + (queryParams ? `?${queryParams}` : "")
}
}

View File

@ -1,8 +1,6 @@
import { action, computed, observable, toJS } from "mobx"; import { action, computed, observable, toJS } from "mobx";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";
import { clusterStore } from "./cluster-store" import { clusterStore } from "./cluster-store"
import { landingURL } from "../renderer/components/+landing-page/landing-page.route";
import { navigate } from "../renderer/navigation";
import { appEventBus } from "./event-bus"; import { appEventBus } from "./event-bus";
export type WorkspaceId = string; export type WorkspaceId = string;
@ -57,18 +55,13 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
} }
@action @action
setActive(id = WorkspaceStore.defaultId, { redirectToLanding = true, resetActiveCluster = true } = {}) { setActive(id = WorkspaceStore.defaultId, reset = true) {
if (id === this.currentWorkspaceId) return; if (id === this.currentWorkspaceId) return;
if (!this.getById(id)) { if (!this.getById(id)) {
throw new Error(`workspace ${id} doesn't exist`); throw new Error(`workspace ${id} doesn't exist`);
} }
this.currentWorkspaceId = id; this.currentWorkspaceId = id;
if (resetActiveCluster) { clusterStore.activeClusterId = null; // fixme: handle previously selected cluster from current workspace
clusterStore.setActive(null)
}
if (redirectToLanding) {
navigate(landingURL())
}
} }
@action @action

View File

@ -1,5 +1,4 @@
import { app } from "electron";
import { getAppVersion } from "../../common/utils"; import { getAppVersion } from "../../common/utils";
export const version = getAppVersion() export const version = getAppVersion()
export { isSnap, isWindows, isMac, isLinux, appName } from "../../common/vars" export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars"

View File

@ -10,9 +10,9 @@ import * as EventBus from "./event-bus"
import * as Store from "./stores" import * as Store from "./stores"
import * as Util from "./utils" import * as Util from "./utils"
import * as Registry from "../registries" import * as Registry from "../registries"
import * as CommonVars from "../../common/vars";
import * as ClusterFeature from "./cluster-feature" import * as ClusterFeature from "./cluster-feature"
// TODO: allow to expose windowManager.navigate() as Navigation.navigate() in runtime
export let windowManager: WindowManager; export let windowManager: WindowManager;
export { export {
@ -22,5 +22,4 @@ export {
Store, Store,
Util, Util,
Registry, Registry,
CommonVars,
} }

View File

@ -1,19 +1,19 @@
import { autoUpdater } from "electron-updater" import { autoUpdater } from "electron-updater"
import logger from "./logger" import logger from "./logger"
export default class AppUpdater { export class AppUpdater {
static readonly defaultUpdateIntervalMs = 1000 * 60 * 60 * 24 // once a day
protected updateInterval: number = (1000 * 60 * 60 * 24) // once a day static checkForUpdates() {
return autoUpdater.checkForUpdatesAndNotify()
}
constructor() { constructor(protected updateInterval = AppUpdater.defaultUpdateIntervalMs) {
autoUpdater.logger = logger autoUpdater.logger = logger
} }
public start() { public start() {
setInterval(() => { setInterval(AppUpdater.checkForUpdates, this.updateInterval)
autoUpdater.checkForUpdatesAndNotify() return AppUpdater.checkForUpdates();
}, this.updateInterval)
return autoUpdater.checkForUpdatesAndNotify()
} }
} }

View File

@ -10,7 +10,7 @@ import path from "path"
import { LensProxy } from "./lens-proxy" import { LensProxy } from "./lens-proxy"
import { WindowManager } from "./window-manager"; import { WindowManager } from "./window-manager";
import { ClusterManager } from "./cluster-manager"; import { ClusterManager } from "./cluster-manager";
import AppUpdater from "./app-updater" import { AppUpdater } from "./app-updater"
import { shellSync } from "./shell-sync" import { shellSync } from "./shell-sync"
import { getFreePort } from "./port" import { getFreePort } from "./port"
import { mangleProxyEnv } from "./proxy-env" import { mangleProxyEnv } from "./proxy-env"
@ -24,45 +24,46 @@ import { extensionLoader } from "../extensions/extension-loader";
import logger from "./logger" import logger from "./logger"
const workingDir = path.join(app.getPath("appData"), appName); const workingDir = path.join(app.getPath("appData"), appName);
let proxyPort: number;
let proxyServer: LensProxy;
let clusterManager: ClusterManager;
let windowManager: WindowManager;
app.setName(appName); app.setName(appName);
if (!process.env.CICD) { if (!process.env.CICD) {
app.setPath("userData", workingDir); app.setPath("userData", workingDir);
} }
let clusterManager: ClusterManager;
let proxyServer: LensProxy;
mangleProxyEnv() mangleProxyEnv()
if (app.commandLine.getSwitchValue("proxy-server") !== "") { if (app.commandLine.getSwitchValue("proxy-server") !== "") {
process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server") process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server")
} }
async function main() { app.on("ready", async () => {
await shellSync();
logger.info(`🚀 Starting Lens from "${workingDir}"`) logger.info(`🚀 Starting Lens from "${workingDir}"`)
await shellSync();
const updater = new AppUpdater() const updater = new AppUpdater()
updater.start(); updater.start();
registerFileProtocol("static", __static); registerFileProtocol("static", __static);
// find free port // preload isomorphic stores
let proxyPort: number
try {
proxyPort = await getFreePort()
} catch (error) {
logger.error(error)
dialog.showErrorBox("Lens Error", "Could not find a free port for the cluster proxy")
app.quit();
}
// preload configuration from stores
await Promise.all([ await Promise.all([
userStore.load(), userStore.load(),
clusterStore.load(), clusterStore.load(),
workspaceStore.load(), workspaceStore.load(),
]); ]);
// find free port
try {
proxyPort = await getFreePort()
} catch (error) {
logger.error(error)
dialog.showErrorBox("Lens Error", "Could not find a free port for the cluster proxy")
app.exit();
}
// create cluster manager // create cluster manager
clusterManager = new ClusterManager(proxyPort); clusterManager = new ClusterManager(proxyPort);
@ -72,12 +73,12 @@ async function main() {
} catch (error) { } catch (error) {
logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error.message}`) logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error.message}`)
dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error.message || "unknown error"}`) dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error.message || "unknown error"}`)
app.quit(); app.exit();
} }
// create window manager and open app windowManager = new WindowManager(proxyPort);
LensExtensionsApi.windowManager = new WindowManager(proxyPort);
LensExtensionsApi.windowManager = windowManager; // expose to extensions
extensionLoader.loadOnMain() extensionLoader.loadOnMain()
extensionLoader.extensions.replace(await extensionManager.load()) extensionLoader.extensions.replace(await extensionManager.load())
extensionLoader.broadcastExtensions() extensionLoader.broadcastExtensions()
@ -85,15 +86,21 @@ async function main() {
setTimeout(() => { setTimeout(() => {
appEventBus.emit({ name: "app", action: "start" }) appEventBus.emit({ name: "app", action: "start" })
}, 1000) }, 1000)
});
app.on("activate", (event, hasVisibleWindows) => {
logger.info('APP:ACTIVATE', { hasVisibleWindows })
if (!hasVisibleWindows) {
windowManager.initMainWindow();
} }
});
app.on("ready", main); // Quit app on Cmd+Q (MacOS)
app.on("will-quit", (event) => {
app.on("will-quit", async (event) => { logger.info('APP:QUIT');
event.preventDefault(); // To allow mixpanel sending to be executed event.preventDefault(); // prevent app's default shutdown (e.g. required for telemetry, etc.)
if (proxyServer) proxyServer.close() clusterManager?.stop(); // close cluster connections
if (clusterManager) clusterManager.stop() return; // skip exit to make tray work, to quit go to app's global menu or tray's menu
app.exit();
}) })
// Extensions-api runtime exports // Extensions-api runtime exports

View File

@ -12,11 +12,27 @@ import logger from "./logger";
export type MenuTopId = "mac" | "file" | "edit" | "view" | "help" export type MenuTopId = "mac" | "file" | "edit" | "view" | "help"
export function initMenu(windowManager: WindowManager) { export function initMenu(windowManager: WindowManager) {
autorun(() => buildMenu(windowManager), { return autorun(() => buildMenu(windowManager), {
delay: 100 delay: 100
}); });
} }
export function showAbout(browserWindow: BrowserWindow) {
const appInfo = [
`${appName}: ${app.getVersion()}`,
`Electron: ${process.versions.electron}`,
`Chrome: ${process.versions.chrome}`,
`Copyright 2020 Mirantis, Inc.`,
]
dialog.showMessageBoxSync(browserWindow, {
title: `${isWindows ? " ".repeat(2) : ""}${appName}`,
type: "info",
buttons: ["Close"],
message: `Lens`,
detail: appInfo.join("\r\n")
})
}
export function buildMenu(windowManager: WindowManager) { export function buildMenu(windowManager: WindowManager) {
function ignoreOnMac(menuItems: MenuItemConstructorOptions[]) { function ignoreOnMac(menuItems: MenuItemConstructorOptions[]) {
if (isMac) return []; if (isMac) return [];
@ -32,28 +48,9 @@ export function buildMenu(windowManager: WindowManager) {
return menuItems; return menuItems;
} }
function navigate(url: string) { async function navigate(url: string) {
logger.info(`[MENU]: navigating to ${url}`); logger.info(`[MENU]: navigating to ${url}`);
windowManager.navigate({ await windowManager.navigate(url);
channel: "menu:navigate",
url: url,
})
}
function showAbout(browserWindow: BrowserWindow) {
const appInfo = [
`${appName}: ${app.getVersion()}`,
`Electron: ${process.versions.electron}`,
`Chrome: ${process.versions.chrome}`,
`Copyright 2020 Mirantis, Inc.`,
]
dialog.showMessageBoxSync(browserWindow, {
title: `${isWindows ? " ".repeat(2) : ""}${appName}`,
type: "info",
buttons: ["Close"],
message: `Lens`,
detail: appInfo.join("\r\n")
})
} }
const macAppMenu: MenuItemConstructorOptions = { const macAppMenu: MenuItemConstructorOptions = {
@ -80,7 +77,13 @@ export function buildMenu(windowManager: WindowManager) {
{ role: 'hideOthers' }, { role: 'hideOthers' },
{ role: 'unhide' }, { role: 'unhide' },
{ type: 'separator' }, { type: 'separator' },
{ role: 'quit' } {
label: 'Quit',
accelerator: 'Cmd+Q',
click() {
app.exit(); // force quit since might be blocked within app.on("will-quit")
}
}
] ]
}; };
@ -118,7 +121,9 @@ export function buildMenu(windowManager: WindowManager) {
}, },
{ type: 'separator' }, { type: 'separator' },
{ role: 'quit' } { role: 'quit' }
]) ]),
{ type: 'separator' },
{ role: 'close' } // close current window
] ]
}; };
@ -158,7 +163,7 @@ export function buildMenu(windowManager: WindowManager) {
label: 'Reload', label: 'Reload',
accelerator: 'CmdOrCtrl+R', accelerator: 'CmdOrCtrl+R',
click() { click() {
windowManager.reload({ channel: "menu:reload" }); windowManager.reload();
} }
}, },
{ role: 'toggleDevTools' }, { role: 'toggleDevTools' },
@ -209,7 +214,7 @@ export function buildMenu(windowManager: WindowManager) {
// Modify menu from extensions-api // Modify menu from extensions-api
menuRegistry.getItems().forEach(({ parentId, ...menuItem }) => { menuRegistry.getItems().forEach(({ parentId, ...menuItem }) => {
try { try {
const topMenu = appMenu[parentId].submenu as MenuItemConstructorOptions[]; const topMenu = appMenu[parentId as MenuTopId].submenu as MenuItemConstructorOptions[];
topMenu.push(menuItem); topMenu.push(menuItem);
} catch (err) { } catch (err) {
logger.error(`[MENU]: can't register menu item, parentId=${parentId}`, { menuItem }) logger.error(`[MENU]: can't register menu item, parentId=${parentId}`, { menuItem })

126
src/main/tray.ts Normal file
View File

@ -0,0 +1,126 @@
import path from "path"
import packageInfo from "../../package.json"
import { app, dialog, Menu, NativeImage, nativeTheme, Tray } from "electron"
import { autorun } from "mobx";
import { showAbout } from "./menu";
import { AppUpdater } from "./app-updater";
import { WindowManager } from "./window-manager";
import { clusterStore } from "../common/cluster-store";
import { workspaceStore } from "../common/workspace-store";
import { preferencesURL } from "../renderer/components/+preferences/preferences.route";
import { clusterViewURL } from "../renderer/components/cluster-manager/cluster-view.route";
import logger from "./logger";
import { isDevelopment } from "../common/vars";
// note: instance of Tray should be saved somewhere, otherwise it disappears
export let tray: Tray;
// refresh icon when MacOS dark/light theme has changed
nativeTheme.on("updated", () => tray?.setImage(getTrayIcon()));
export function getTrayIcon(isDark = nativeTheme.shouldUseDarkColors): string {
return path.resolve(
__static,
isDevelopment ? "../build/tray" : "icons", // copied within electron-builder extras
`tray_icon${isDark ? "_dark" : ""}.png`
)
}
export function initTray(windowManager: WindowManager) {
const dispose = autorun(() => {
try {
const menu = createTrayMenu(windowManager);
buildTray(getTrayIcon(), menu);
} catch (err) {
logger.error(`[TRAY]: building failed: ${err}`);
}
})
return () => {
dispose();
tray?.destroy();
tray = null;
}
}
export function buildTray(icon: string | NativeImage, menu: Menu) {
if (!tray) {
tray = new Tray(icon)
tray.setToolTip(packageInfo.description)
tray.setIgnoreDoubleClickEvents(true);
}
tray.setImage(icon);
tray.setContextMenu(menu);
return tray;
}
export function createTrayMenu(windowManager: WindowManager): Menu {
return Menu.buildFromTemplate([
{
label: "About Lens",
async click() {
// note: argument[1] (browserWindow) not available when app is not focused / hidden
const browserWindow = await windowManager.ensureMainWindow();
showAbout(browserWindow);
},
},
{ type: 'separator' },
{
label: "Open Lens",
async click() {
await windowManager.ensureMainWindow()
},
},
{
label: "Preferences",
click() {
windowManager.navigate(preferencesURL());
},
},
{
label: "Clusters",
submenu: workspaceStore.workspacesList
.filter(workspace => clusterStore.getByWorkspaceId(workspace.id).length > 0) // hide empty workspaces
.map(workspace => {
const clusters = clusterStore.getByWorkspaceId(workspace.id);
return {
label: workspace.name,
toolTip: workspace.description,
submenu: clusters.map(cluster => {
const { id: clusterId, preferences: { clusterName: label }, online, workspace } = cluster;
return {
label: `${online ? '✓' : '\x20'.repeat(3)/*offset*/}${label}`,
toolTip: clusterId,
async click() {
workspaceStore.setActive(workspace);
clusterStore.setActive(clusterId);
windowManager.navigate(clusterViewURL({ params: { clusterId } }));
}
}
})
}
}),
},
{
label: "Check for updates",
async click() {
const result = await AppUpdater.checkForUpdates();
if (!result) {
const browserWindow = await windowManager.ensureMainWindow();
dialog.showMessageBoxSync(browserWindow, {
message: "No updates available",
type: "info",
})
}
},
},
{ type: 'separator' },
{
label: 'Quit App',
click() {
app.exit();
}
}
]);
}

View File

@ -1,28 +1,46 @@
import type { ClusterId } from "../common/cluster-store"; import type { ClusterId } from "../common/cluster-store";
import { clusterStore } from "../common/cluster-store"; import { clusterStore } from "../common/cluster-store";
import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"
import windowStateKeeper from "electron-window-state"
import { observable } from "mobx"; import { observable } from "mobx";
import { initMenu } from "./menu"; import { app, BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"
import windowStateKeeper from "electron-window-state"
import { extensionLoader } from "../extensions/extension-loader"; import { extensionLoader } from "../extensions/extension-loader";
import { appEventBus } from "../common/event-bus" import { appEventBus } from "../common/event-bus"
import { initMenu } from "./menu";
import { initTray } from "./tray";
export class WindowManager { export class WindowManager {
protected mainView: BrowserWindow; protected mainWindow: BrowserWindow;
protected splashWindow: BrowserWindow; protected splashWindow: BrowserWindow;
protected windowState: windowStateKeeper.State; protected windowState: windowStateKeeper.State;
protected disposers: Record<string, Function> = {};
@observable activeClusterId: ClusterId; @observable activeClusterId: ClusterId;
constructor(protected proxyPort: number) { constructor(protected proxyPort: number) {
this.bindEvents();
this.initMenu();
this.initTray();
this.initMainWindow();
}
get mainUrl() {
return `http://localhost:${this.proxyPort}`
}
async initMainWindow(showSplash = true) {
// Manage main window size and position with state persistence // Manage main window size and position with state persistence
if (!this.windowState) {
this.windowState = windowStateKeeper({ this.windowState = windowStateKeeper({
defaultHeight: 900, defaultHeight: 900,
defaultWidth: 1440, defaultWidth: 1440,
}); });
}
if (!this.mainWindow) {
// show icon in dock (mac-os only)
app.dock?.show();
const { width, height, x, y } = this.windowState; const { width, height, x, y } = this.windowState;
this.mainView = new BrowserWindow({ this.mainWindow = new BrowserWindow({
x, y, width, height, x, y, width, height,
show: false, show: false,
minWidth: 700, // accommodate 800 x 600 display minimum minWidth: 700, // accommodate 800 x 600 display minimum
@ -35,61 +53,88 @@ export class WindowManager {
enableRemoteModule: true, enableRemoteModule: true,
}, },
}); });
this.windowState.manage(this.mainView); this.windowState.manage(this.mainWindow);
// open external links in default browser (target=_blank, window.open) // open external links in default browser (target=_blank, window.open)
this.mainView.webContents.on("new-window", (event, url) => { this.mainWindow.webContents.on("new-window", (event, url) => {
event.preventDefault(); event.preventDefault();
shell.openExternal(url); shell.openExternal(url);
}); });
this.mainView.webContents.on("dom-ready", () => { this.mainWindow.webContents.on("dom-ready", () => {
extensionLoader.broadcastExtensions() extensionLoader.broadcastExtensions()
}) })
this.mainView.on("focus", () => { this.mainWindow.on("focus", () => {
appEventBus.emit({name: "app", action: "focus"}) appEventBus.emit({name: "app", action: "focus"})
}) })
this.mainView.on("blur", () => { this.mainWindow.on("blur", () => {
appEventBus.emit({name: "app", action: "blur"}) appEventBus.emit({name: "app", action: "blur"})
}) })
// clean up
this.mainWindow.on("closed", () => {
this.windowState.unmanage();
this.mainWindow = null;
this.splashWindow = null;
app.dock?.hide(); // hide icon in dock (mac-os)
})
}
try {
if (showSplash) await this.showSplash();
await this.mainWindow.loadURL(this.mainUrl);
this.mainWindow.show();
this.splashWindow?.close();
} catch (err) {
dialog.showErrorBox("ERROR!", err.toString())
}
}
protected async initMenu() {
this.disposers.menuAutoUpdater = initMenu(this);
}
protected initTray() {
this.disposers.trayAutoUpdater = initTray(this);
}
protected bindEvents() {
// track visible cluster from ui // track visible cluster from ui
ipcMain.on("cluster-view:current-id", (event, clusterId: ClusterId) => { ipcMain.on("cluster-view:current-id", (event, clusterId: ClusterId) => {
this.activeClusterId = clusterId; this.activeClusterId = clusterId;
}); });
// load & show app
this.showMain();
initMenu(this);
} }
navigate({ url, channel, frameId }: { url: string, channel: string, frameId?: number }) { async ensureMainWindow(): Promise<BrowserWindow> {
if (!this.mainWindow) await this.initMainWindow();
this.mainWindow.show();
return this.mainWindow;
}
sendToView({ channel, frameId, data = [] }: { channel: string, frameId?: number, data?: any[] }) {
if (frameId) { if (frameId) {
this.mainView.webContents.sendToFrame(frameId, channel, url); this.mainWindow.webContents.sendToFrame(frameId, channel, ...data);
} else { } else {
this.mainView.webContents.send(channel, url); this.mainWindow.webContents.send(channel, ...data);
} }
} }
reload({ channel }: { channel: string }) { async navigate(url: string, frameId?: number) {
await this.ensureMainWindow();
this.sendToView({
channel: "menu:navigate",
frameId: frameId,
data: [url],
})
}
reload() {
const frameId = clusterStore.getById(this.activeClusterId)?.frameId; const frameId = clusterStore.getById(this.activeClusterId)?.frameId;
if (frameId) { if (frameId) {
this.mainView.webContents.sendToFrame(frameId, channel); this.sendToView({ channel: "menu:reload", frameId });
} else { } else {
webContents.getFocusedWebContents()?.reload(); webContents.getFocusedWebContents()?.reload();
} }
} }
async showMain() {
try {
await this.showSplash();
await this.mainView.loadURL(`http://localhost:${this.proxyPort}`)
this.mainView.show()
this.splashWindow.close()
} catch (err) {
dialog.showErrorBox("ERROR!", err.toString())
}
}
async showSplash() { async showSplash() {
if (!this.splashWindow) { if (!this.splashWindow) {
this.splashWindow = new BrowserWindow({ this.splashWindow = new BrowserWindow({
@ -110,8 +155,13 @@ export class WindowManager {
} }
destroy() { destroy() {
this.windowState.unmanage(); this.mainWindow.destroy();
this.splashWindow.destroy(); this.splashWindow.destroy();
this.mainView.destroy(); this.mainWindow = null;
this.splashWindow = null;
Object.entries(this.disposers).forEach(([name, dispose]) => {
dispose();
delete this.disposers[name]
});
} }
} }

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const addClusterRoute: RouteProps = { export const addClusterRoute: RouteProps = {
path: "/add-cluster" path: "/add-cluster"

View File

@ -46,6 +46,7 @@ export class AddCluster extends React.Component {
@observable dropAreaActive = false; @observable dropAreaActive = false;
componentDidMount() { componentDidMount() {
clusterStore.setActive(null);
this.setKubeConfig(userStore.kubeConfigPath); this.setKubeConfig(userStore.kubeConfigPath);
} }
@ -118,10 +119,9 @@ export class AddCluster extends React.Component {
} }
} }
@action
addClusters = () => { addClusters = () => {
const configValidationErrors:string[] = [];
let newClusters: ClusterModel[] = []; let newClusters: ClusterModel[] = [];
try { try {
if (!this.selectedContexts.length) { if (!this.selectedContexts.length) {
this.error = <Trans>Please select at least one cluster context</Trans> this.error = <Trans>Please select at least one cluster context</Trans>

View File

@ -1,6 +1,6 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../../common/utils/buildUrl";
import { appsRoute } from "../+apps/apps.route"; import { appsRoute } from "../+apps/apps.route";
import { buildURL } from "../../navigation";
export const helmChartsRoute: RouteProps = { export const helmChartsRoute: RouteProps = {
path: appsRoute.path + "/charts/:repo?/:chartName?" path: appsRoute.path + "/charts/:repo?/:chartName?"

View File

@ -1,6 +1,6 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../../common/utils/buildUrl";
import { appsRoute } from "../+apps/apps.route"; import { appsRoute } from "../+apps/apps.route";
import { buildURL } from "../../navigation";
export const releaseRoute: RouteProps = { export const releaseRoute: RouteProps = {
path: appsRoute.path + "/releases/:namespace?/:name?" path: appsRoute.path + "/releases/:namespace?/:name?"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const appsRoute: RouteProps = { export const appsRoute: RouteProps = {
path: "/apps", path: "/apps",

View File

@ -1,6 +1,6 @@
import type { IClusterViewRouteParams } from "../cluster-manager/cluster-view.route"; import type { IClusterViewRouteParams } from "../cluster-manager/cluster-view.route";
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export interface IClusterSettingsRouteParams extends IClusterViewRouteParams { export interface IClusterSettingsRouteParams extends IClusterViewRouteParams {
} }

View File

@ -1,8 +1,9 @@
import "./cluster-settings.scss"; import "./cluster-settings.scss";
import React from "react"; import React from "react";
import { autorun } from "mobx"; import { reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { RouteComponentProps } from "react-router";
import { observer, disposeOnUnmount } from "mobx-react";
import { Features } from "./features"; import { Features } from "./features";
import { Removal } from "./removal"; import { Removal } from "./removal";
import { Status } from "./status"; import { Status } from "./status";
@ -11,7 +12,6 @@ import { Cluster } from "../../../main/cluster";
import { ClusterIcon } from "../cluster-icon"; import { ClusterIcon } from "../cluster-icon";
import { IClusterSettingsRouteParams } from "./cluster-settings.route"; import { IClusterSettingsRouteParams } from "./cluster-settings.route";
import { clusterStore } from "../../../common/cluster-store"; import { clusterStore } from "../../../common/cluster-store";
import { RouteComponentProps } from "react-router";
import { clusterIpc } from "../../../common/cluster-ipc"; import { clusterIpc } from "../../../common/cluster-ipc";
import { PageLayout } from "../layout/page-layout"; import { PageLayout } from "../layout/page-layout";
@ -20,16 +20,23 @@ interface Props extends RouteComponentProps<IClusterSettingsRouteParams> {
@observer @observer
export class ClusterSettings extends React.Component<Props> { export class ClusterSettings extends React.Component<Props> {
get cluster(): Cluster { get clusterId() {
return clusterStore.getById(this.props.match.params.clusterId); return this.props.match.params.clusterId
} }
async componentDidMount() { get cluster(): Cluster {
disposeOnUnmount(this, return clusterStore.getById(this.clusterId);
autorun(() => { }
this.refreshCluster();
componentDidMount() {
disposeOnUnmount(this, [
reaction(() => this.cluster, this.refreshCluster, {
fireImmediately: true,
}),
reaction(() => this.clusterId, clusterId => clusterStore.setActive(clusterId), {
fireImmediately: true,
}) })
) ])
} }
refreshCluster = async () => { refreshCluster = async () => {

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const clusterRoute: RouteProps = { export const clusterRoute: RouteProps = {
path: "/cluster" path: "/cluster"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const hpaRoute: RouteProps = { export const hpaRoute: RouteProps = {
path: "/hpa" path: "/hpa"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const configMapsRoute: RouteProps = { export const configMapsRoute: RouteProps = {
path: "/configmaps" path: "/configmaps"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const pdbRoute: RouteProps = { export const pdbRoute: RouteProps = {
path: "/poddisruptionbudgets" path: "/poddisruptionbudgets"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const resourceQuotaRoute: RouteProps = { export const resourceQuotaRoute: RouteProps = {
path: "/resourcequotas" path: "/resourcequotas"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const secretsRoute: RouteProps = { export const secretsRoute: RouteProps = {
path: "/secrets" path: "/secrets"

View File

@ -1,7 +1,7 @@
import { RouteProps } from "react-router"; import { RouteProps } from "react-router";
import { configMapsURL } from "../+config-maps";
import { Config } from "./config"; import { Config } from "./config";
import { IURLParams } from "../../navigation"; import { IURLParams } from "../../../common/utils/buildUrl";
import { configMapsURL } from "../+config-maps/config-maps.route";
export const configRoute: RouteProps = { export const configRoute: RouteProps = {
get path() { get path() {

View File

@ -10,8 +10,8 @@ import { resourceQuotaRoute, ResourceQuotas, resourceQuotaURL } from "../+config
import { PodDisruptionBudgets, pdbRoute, pdbURL } from "../+config-pod-disruption-budgets"; import { PodDisruptionBudgets, pdbRoute, pdbURL } from "../+config-pod-disruption-budgets";
import { configURL } from "./config.route"; import { configURL } from "./config.route";
import { HorizontalPodAutoscalers, hpaRoute, hpaURL } from "../+config-autoscalers"; import { HorizontalPodAutoscalers, hpaRoute, hpaURL } from "../+config-autoscalers";
import { buildURL } from "../../navigation";
import { isAllowedResource } from "../../../common/rbac" import { isAllowedResource } from "../../../common/rbac"
import { buildURL } from "../../../common/utils/buildUrl";
export const certificatesURL = buildURL("/certificates"); export const certificatesURL = buildURL("/certificates");
export const issuersURL = buildURL("/issuers"); export const issuersURL = buildURL("/issuers");

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const crdRoute: RouteProps = { export const crdRoute: RouteProps = {
path: "/crd" path: "/crd"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const eventRoute: RouteProps = { export const eventRoute: RouteProps = {
path: "/events" path: "/events"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const landingRoute: RouteProps = { export const landingRoute: RouteProps = {
path: "/landing" path: "/landing"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const namespacesRoute: RouteProps = { export const namespacesRoute: RouteProps = {
path: "/namespaces" path: "/namespaces"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const endpointRoute: RouteProps = { export const endpointRoute: RouteProps = {
path: "/endpoints" path: "/endpoints"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const ingressRoute: RouteProps = { export const ingressRoute: RouteProps = {
path: "/ingresses" path: "/ingresses"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const networkPoliciesRoute: RouteProps = { export const networkPoliciesRoute: RouteProps = {
path: "/network-policies" path: "/network-policies"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const servicesRoute: RouteProps = { export const servicesRoute: RouteProps = {
path: "/services" path: "/services"

View File

@ -1,7 +1,7 @@
import { RouteProps } from "react-router" import { RouteProps } from "react-router"
import { Network } from "./network"; import { Network } from "./network";
import { servicesURL } from "../+network-services"; import { servicesURL } from "../+network-services";
import { IURLParams } from "../../navigation"; import { IURLParams } from "../../../common/utils/buildUrl";
export const networkRoute: RouteProps = { export const networkRoute: RouteProps = {
get path() { get path() {

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const nodesRoute: RouteProps = { export const nodesRoute: RouteProps = {
path: "/nodes" path: "/nodes"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const podSecurityPoliciesRoute: RouteProps = { export const podSecurityPoliciesRoute: RouteProps = {
path: "/pod-security-policies" path: "/pod-security-policies"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const preferencesRoute: RouteProps = { export const preferencesRoute: RouteProps = {
path: "/preferences" path: "/preferences"

View File

@ -21,4 +21,8 @@
display: none; display: none;
} }
} }
.Checkbox {
align-self: start; // limit clickable area to checkbox + text
}
} }

View File

@ -156,6 +156,13 @@ export class Preferences extends React.Component {
})} })}
</div> </div>
<h2><Trans>Auto start-up</Trans></h2>
<Checkbox
label={<Trans>Automatically start Lens on login</Trans>}
value={preferences.openAtLogin}
onChange={v => preferences.openAtLogin = v}
/>
<h2><Trans>Certificate Trust</Trans></h2> <h2><Trans>Certificate Trust</Trans></h2>
<Checkbox <Checkbox
label={<Trans>Allow untrusted Certificate Authorities</Trans>} label={<Trans>Allow untrusted Certificate Authorities</Trans>}

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const storageClassesRoute: RouteProps = { export const storageClassesRoute: RouteProps = {
path: "/storage-classes" path: "/storage-classes"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const volumeClaimsRoute: RouteProps = { export const volumeClaimsRoute: RouteProps = {
path: "/persistent-volume-claims" path: "/persistent-volume-claims"

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const volumesRoute: RouteProps = { export const volumesRoute: RouteProps = {
path: "/persistent-volumes" path: "/persistent-volumes"

View File

@ -1,7 +1,7 @@
import { RouteProps } from "react-router" import { RouteProps } from "react-router"
import { volumeClaimsURL } from "../+storage-volume-claims"; import { volumeClaimsURL } from "../+storage-volume-claims";
import { Storage } from "./storage"; import { Storage } from "./storage";
import { IURLParams } from "../../navigation"; import { IURLParams } from "../../../common/utils/buildUrl";
export const storageRoute: RouteProps = { export const storageRoute: RouteProps = {
get path() { get path() {

View File

@ -1,6 +1,6 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL, IURLParams } from "../../../common/utils/buildUrl";
import { UserManagement } from "./user-management" import { UserManagement } from "./user-management"
import { buildURL, IURLParams } from "../../navigation";
export const usersManagementRoute: RouteProps = { export const usersManagementRoute: RouteProps = {
get path() { get path() {
@ -30,9 +30,7 @@ export interface IRolesRouteParams {
} }
// URL-builders // URL-builders
export const usersManagementURL = (params?: IURLParams) => serviceAccountsURL(params);
export const serviceAccountsURL = buildURL<IServiceAccountsRouteParams>(serviceAccountsRoute.path) export const serviceAccountsURL = buildURL<IServiceAccountsRouteParams>(serviceAccountsRoute.path)
export const roleBindingsURL = buildURL<IRoleBindingsRouteParams>(roleBindingsRoute.path) export const roleBindingsURL = buildURL<IRoleBindingsRouteParams>(roleBindingsRoute.path)
export const rolesURL = buildURL<IRoleBindingsRouteParams>(rolesRoute.path) export const rolesURL = buildURL<IRoleBindingsRouteParams>(rolesRoute.path)
export const usersManagementURL = (params?: IURLParams) => {
return serviceAccountsURL(params);
};

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const whatsNewRoute: RouteProps = { export const whatsNewRoute: RouteProps = {
path: "/what-s-new" path: "/what-s-new"

View File

@ -1,7 +1,7 @@
import { RouteProps } from "react-router" import type { RouteProps } from "react-router";
import { Workloads } from "./workloads"; import { buildURL, IURLParams } from "../../../common/utils/buildUrl";
import { buildURL, IURLParams } from "../../navigation";
import { KubeResource } from "../../../common/rbac"; import { KubeResource } from "../../../common/rbac";
import { Workloads } from "./workloads";
export const workloadsRoute: RouteProps = { export const workloadsRoute: RouteProps = {
get path() { get path() {

View File

@ -1,5 +1,5 @@
import { RouteProps } from "react-router"; import type { RouteProps } from "react-router";
import { buildURL } from "../../navigation"; import { buildURL } from "../../../common/utils/buildUrl";
export const workspacesRoute: RouteProps = { export const workspacesRoute: RouteProps = {
path: "/workspaces" path: "/workspaces"

View File

@ -1,4 +1,5 @@
import "./cluster-manager.scss" import "./cluster-manager.scss"
import React from "react"; import React from "react";
import { Redirect, Route, Switch } from "react-router"; import { Redirect, Route, Switch } from "react-router";
import { comparer, reaction } from "mobx"; import { comparer, reaction } from "mobx";
@ -11,14 +12,17 @@ import { Workspaces, workspacesRoute } from "../+workspaces";
import { AddCluster, addClusterRoute } from "../+add-cluster"; import { AddCluster, addClusterRoute } from "../+add-cluster";
import { ClusterView } from "./cluster-view"; import { ClusterView } from "./cluster-view";
import { ClusterSettings, clusterSettingsRoute } from "../+cluster-settings"; import { ClusterSettings, clusterSettingsRoute } from "../+cluster-settings";
import { clusterViewRoute, clusterViewURL, getMatchedCluster, getMatchedClusterId } from "./cluster-view.route"; import { clusterViewRoute, clusterViewURL } from "./cluster-view.route";
import { clusterStore } from "../../../common/cluster-store"; import { clusterStore } from "../../../common/cluster-store";
import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views"; import { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
import { globalPageRegistry } from "../../../extensions/registries/page-registry"; import { globalPageRegistry } from "../../../extensions/registries/page-registry";
import { getMatchedClusterId } from "../../navigation";
@observer @observer
export class ClusterManager extends React.Component { export class ClusterManager extends React.Component {
componentDidMount() { componentDidMount() {
const getMatchedCluster = () => clusterStore.getById(getMatchedClusterId());
disposeOnUnmount(this, [ disposeOnUnmount(this, [
reaction(getMatchedClusterId, initView, { reaction(getMatchedClusterId, initView, {
fireImmediately: true fireImmediately: true

View File

@ -1,8 +1,5 @@
import { reaction } from "mobx"; import type { RouteProps } from "react-router";
import { ipcRenderer } from "electron"; import { buildURL } from "../../../common/utils/buildUrl";
import { matchPath, RouteProps } from "react-router";
import { buildURL, navigation } from "../../navigation";
import { clusterStore } from "../../../common/cluster-store";
export interface IClusterViewRouteParams { export interface IClusterViewRouteParams {
clusterId: string; clusterId: string;
@ -14,33 +11,3 @@ export const clusterViewRoute: RouteProps = {
} }
export const clusterViewURL = buildURL<IClusterViewRouteParams>(clusterViewRoute.path) export const clusterViewURL = buildURL<IClusterViewRouteParams>(clusterViewRoute.path)
export function getMatchedClusterId(): string {
const matched = matchPath<IClusterViewRouteParams>(navigation.location.pathname, {
exact: true,
path: clusterViewRoute.path
})
if (matched) {
return matched.params.clusterId;
}
}
export function getMatchedCluster() {
return clusterStore.getById(getMatchedClusterId())
}
if (ipcRenderer) {
if (process.isMainFrame) {
// Keep track of active cluster-id for handling IPC/menus/etc.
reaction(() => getMatchedClusterId(), clusterId => {
ipcRenderer.send("cluster-view:current-id", clusterId);
}, {
fireImmediately: true
})
}
// Reload dashboard
ipcRenderer.on("menu:reload", () => {
location.reload();
});
}

View File

@ -1,14 +1,37 @@
import "./cluster-view.scss" import "./cluster-view.scss"
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { reaction } from "mobx";
import { getMatchedCluster } from "./cluster-view.route"; import { disposeOnUnmount, observer } from "mobx-react";
import { RouteComponentProps } from "react-router";
import { IClusterViewRouteParams } from "./cluster-view.route";
import { ClusterStatus } from "./cluster-status"; import { ClusterStatus } from "./cluster-status";
import { hasLoadedView } from "./lens-views"; import { hasLoadedView } from "./lens-views";
import { Cluster } from "../../../main/cluster";
import { clusterStore } from "../../../common/cluster-store";
interface Props extends RouteComponentProps<IClusterViewRouteParams> {
}
@observer @observer
export class ClusterView extends React.Component { export class ClusterView extends React.Component<Props> {
get clusterId() {
return this.props.match.params.clusterId;
}
get cluster(): Cluster {
return clusterStore.getById(this.clusterId);
}
async componentDidMount() {
disposeOnUnmount(this, [
reaction(() => this.clusterId, clusterId => clusterStore.setActive(clusterId), {
fireImmediately: true,
})
])
}
render() { render() {
const cluster = getMatchedCluster(); const { cluster } = this;
const showStatus = cluster && (!cluster.available || !hasLoadedView(cluster.id) || !cluster.ready) const showStatus = cluster && (!cluster.available || !hasLoadedView(cluster.id) || !cluster.ready)
return ( return (
<div className="ClusterView"> <div className="ClusterView">

View File

@ -1,8 +1,9 @@
import type { Cluster } from "../../../main/cluster";
import "./clusters-menu.scss" import "./clusters-menu.scss"
import { remote } from "electron"
import React from "react"; import React from "react";
import { remote } from "electron"
import type { Cluster } from "../../../main/cluster";
import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { _i18n } from "../../i18n"; import { _i18n } from "../../i18n";
import { t, Trans } from "@lingui/macro"; import { t, Trans } from "@lingui/macro";
@ -21,7 +22,6 @@ import { Tooltip } from "../tooltip";
import { ConfirmDialog } from "../confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog";
import { clusterIpc } from "../../../common/cluster-ipc"; import { clusterIpc } from "../../../common/cluster-ipc";
import { clusterViewURL } from "./cluster-view.route"; import { clusterViewURL } from "./cluster-view.route";
import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd";
import { globalPageRegistry } from "../../../extensions/registries/page-registry"; import { globalPageRegistry } from "../../../extensions/registries/page-registry";
interface Props { interface Props {
@ -31,13 +31,11 @@ interface Props {
@observer @observer
export class ClustersMenu extends React.Component<Props> { export class ClustersMenu extends React.Component<Props> {
showCluster = (clusterId: ClusterId) => { showCluster = (clusterId: ClusterId) => {
clusterStore.setActive(clusterId);
navigate(clusterViewURL({ params: { clusterId } })); navigate(clusterViewURL({ params: { clusterId } }));
} }
addCluster = () => { addCluster = () => {
navigate(addClusterURL()); navigate(addClusterURL());
clusterStore.setActive(null);
} }
showContextMenu = (cluster: Cluster) => { showContextMenu = (cluster: Cluster) => {
@ -47,7 +45,6 @@ export class ClustersMenu extends React.Component<Props> {
menu.append(new MenuItem({ menu.append(new MenuItem({
label: _i18n._(t`Settings`), label: _i18n._(t`Settings`),
click: () => { click: () => {
clusterStore.setActive(cluster.id);
navigate(clusterSettingsURL({ navigate(clusterSettingsURL({
params: { params: {
clusterId: cluster.id clusterId: cluster.id
@ -112,21 +109,14 @@ export class ClustersMenu extends React.Component<Props> {
<div className="clusters flex column gaps"> <div className="clusters flex column gaps">
<DragDropContext onDragEnd={this.swapClusterIconOrder}> <DragDropContext onDragEnd={this.swapClusterIconOrder}>
<Droppable droppableId="cluster-menu" type="CLUSTER"> <Droppable droppableId="cluster-menu" type="CLUSTER">
{(provided: DroppableProvided) => ( {({ innerRef, droppableProps, placeholder }: DroppableProvided) => (
<div <div ref={innerRef} {...droppableProps}>
ref={provided.innerRef}
{...provided.droppableProps}
>
{clusters.map((cluster, index) => { {clusters.map((cluster, index) => {
const isActive = cluster.id === clusterStore.activeClusterId; const isActive = cluster.id === clusterStore.activeClusterId;
return ( return (
<Draggable draggableId={cluster.id} index={index} key={cluster.id}> <Draggable draggableId={cluster.id} index={index} key={cluster.id}>
{(provided: DraggableProvided) => ( {({ draggableProps, dragHandleProps, innerRef }: DraggableProvided) => (
<div <div ref={innerRef} {...draggableProps} {...dragHandleProps}>
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<ClusterIcon <ClusterIcon
key={cluster.id} key={cluster.id}
showErrors={true} showErrors={true}
@ -138,9 +128,9 @@ export class ClustersMenu extends React.Component<Props> {
</div> </div>
)} )}
</Draggable> </Draggable>
)} )
)} })}
{provided.placeholder} {placeholder}
</div> </div>
)} )}
</Droppable> </Droppable>
@ -157,9 +147,7 @@ export class ClustersMenu extends React.Component<Props> {
</div> </div>
<div className="extensions"> <div className="extensions">
{globalPageRegistry.getItems().map(({ path, url = String(path), hideInMenu, components: { MenuIcon } }) => { {globalPageRegistry.getItems().map(({ path, url = String(path), hideInMenu, components: { MenuIcon } }) => {
if (!MenuIcon || hideInMenu) { if (!MenuIcon || hideInMenu) return;
return;
}
return <MenuIcon key={url} onClick={() => navigate(url)}/> return <MenuIcon key={url} onClick={() => navigate(url)}/>
})} })}
</div> </div>

View File

@ -1,6 +1,6 @@
import { observable, when } from "mobx"; import { observable, when } from "mobx";
import { ClusterId, clusterStore, getClusterFrameUrl } from "../../../common/cluster-store"; import { ClusterId, clusterStore, getClusterFrameUrl } from "../../../common/cluster-store";
import { getMatchedCluster } from "./cluster-view.route" import { getMatchedClusterId } from "../../navigation";
import logger from "../../../main/logger"; import logger from "../../../main/logger";
export interface LensView { export interface LensView {
@ -51,7 +51,7 @@ export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrame
} }
export function refreshViews() { export function refreshViews() {
const cluster = getMatchedCluster(); const cluster = clusterStore.getById(getMatchedClusterId());
lensViews.forEach(({ clusterId, view, isLoaded }) => { lensViews.forEach(({ clusterId, view, isLoaded }) => {
const isCurrent = clusterId === cluster?.id; const isCurrent = clusterId === cluster?.id;
const isReady = cluster?.available && cluster?.ready; const isReady = cluster?.available && cluster?.ready;

View File

Before

Width:  |  Height:  |  Size: 730 B

After

Width:  |  Height:  |  Size: 730 B

View File

@ -1,7 +0,0 @@
<svg id="Layer_1" viewBox="0 0 194 219" xmlns="http://www.w3.org/2000/svg">
<g fill="#fff">
<polygon points="98 12.4 71 28.3 44 44.1 17 60 17 91.8 17 123.6 17 155.4 44 171.3 44 139.5 44 107.7 44 75.9 71 60 71 91.8 71 91.8 71 123.6 98 107.7 98 107.7 125 91.8 125 60 152 44.1 125 28.3 98 12.4"/>
<polygon points="152 44.1 152 75.9 152 107.7 125 123.6 152 139.5 152 171.3 179 155.4 179 123.6 179 91.8 179 60 152 44.1"/>
<polygon points="98 139.5 71 155.4 71 155.4 71 187.2 98 203.1 125 187.2 125 155.4 98 139.5"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 538 B

View File

@ -80,7 +80,7 @@ export class Sidebar extends React.Component<Props> {
<div className={cssNames("Sidebar flex column", className, { pinned: isPinned })}> <div className={cssNames("Sidebar flex column", className, { pinned: isPinned })}>
<div className="header flex align-center"> <div className="header flex align-center">
<NavLink exact to="/" className="box grow"> <NavLink exact to="/" className="box grow">
<Icon svg="logo-full" className="logo-icon"/> <Icon svg="logo-lens" className="logo-icon"/>
<div className="logo-text">Lens</div> <div className="logo-text">Lens</div>
</NavLink> </NavLink>
<Icon <Icon

View File

@ -1,22 +1,16 @@
// Navigation helpers // Navigation helpers
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import { compile } from "path-to-regexp" import { matchPath } from "react-router";
import { createBrowserHistory, createMemoryHistory, LocationDescriptor } from "history"; import { reaction } from "mobx";
import { createObservableHistory } from "mobx-observable-history"; import { createObservableHistory } from "mobx-observable-history";
import { createBrowserHistory, createMemoryHistory, LocationDescriptor } from "history";
import logger from "../main/logger"; import logger from "../main/logger";
import { clusterViewRoute, IClusterViewRouteParams } from "./components/cluster-manager/cluster-view.route";
export const history = typeof window !== "undefined" ? createBrowserHistory() : createMemoryHistory(); export const history = typeof window !== "undefined" ? createBrowserHistory() : createMemoryHistory();
export const navigation = createObservableHistory(history); export const navigation = createObservableHistory(history);
// handle navigation from other process (e.g. system menus in main, common->cluster view interactions)
if (ipcRenderer) {
ipcRenderer.on("menu:navigate", (event, location: LocationDescriptor) => {
logger.info(`[IPC]: ${event.type} ${JSON.stringify(location)}`, event);
navigate(location);
})
}
export function navigate(location: LocationDescriptor) { export function navigate(location: LocationDescriptor) {
const currentLocation = navigation.getPath(); const currentLocation = navigation.getPath();
navigation.push(location); navigation.push(location);
@ -25,20 +19,6 @@ export function navigate(location: LocationDescriptor) {
} }
} }
export interface IURLParams<P = {}, Q = {}> {
params?: P;
query?: IQueryParams & Q;
}
// todo: extract building urls to commons (also used in menu.ts)
// fixme: missing types validation for params & query
export function buildURL<P extends object, Q = object>(path: string | string[]) {
const pathBuilder = compile(path.toString());
return function ({ params, query }: IURLParams<P, Q> = {}) {
return pathBuilder(params) + (query ? getQueryString(query, false) : "")
}
}
// common params for all pages // common params for all pages
export interface IQueryParams { export interface IQueryParams {
namespaces?: string[]; // selected context namespaces namespaces?: string[]; // selected context namespaces
@ -100,3 +80,33 @@ export function setSearch(text: string) {
export function getSearch() { export function getSearch() {
return navigation.searchParams.get("search") || ""; return navigation.searchParams.get("search") || "";
} }
export function getMatchedClusterId(): string {
const matched = matchPath<IClusterViewRouteParams>(navigation.location.pathname, {
exact: true,
path: clusterViewRoute.path
});
return matched?.params.clusterId;
}
//-- EVENTS
if (process.isMainFrame) {
// Keep track of active cluster-id for handling IPC/menus/etc.
reaction(() => getMatchedClusterId(), clusterId => {
ipcRenderer.send("cluster-view:current-id", clusterId);
}, {
fireImmediately: true
})
}
// Handle navigation via IPC (e.g. from top menu)
ipcRenderer.on("menu:navigate", (event, location: LocationDescriptor) => {
logger.info(`[IPC]: ${event.type} ${JSON.stringify(location)}`, event);
navigate(location);
});
// Reload dashboard window
ipcRenderer.on("menu:reload", () => {
location.reload();
});

241
yarn.lock
View File

@ -1941,6 +1941,15 @@
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.4.tgz#7d3b534ec35a0585128e2d332db1403ebe057e25" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.4.tgz#7d3b534ec35a0585128e2d332db1403ebe057e25"
integrity sha512-fYMgzN+9e28R81weVN49inn/u798ruU91En1ZnGvSZzCRc5jXx9B2EDhlRaWmcO1RIxFHL8AajRXzxDuJu93+A== integrity sha512-fYMgzN+9e28R81weVN49inn/u798ruU91En1ZnGvSZzCRc5jXx9B2EDhlRaWmcO1RIxFHL8AajRXzxDuJu93+A==
"@types/jsdom@^16.2.4":
version "16.2.4"
resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.4.tgz#527ca99943e00561ca4056b1904fd5f4facebc3b"
integrity sha512-RssgLa5ptjVKRkHho/Ex0+DJWkVsYuV8oh2PSG3gKxFp8n/VNyB7kOrZGQkk2zgPlcBkIKOItUc/T5BXit9uhg==
dependencies:
"@types/node" "*"
"@types/parse5" "*"
"@types/tough-cookie" "*"
"@types/json-schema@^7.0.3": "@types/json-schema@^7.0.3":
version "7.0.5" version "7.0.5"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
@ -2059,6 +2068,11 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/parse5@*":
version "5.0.3"
resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109"
integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==
"@types/podium@*": "@types/podium@*":
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.0.tgz#bfaa2151be2b1d6109cc69f7faa9dac2cba3bb20" resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.0.tgz#bfaa2151be2b1d6109cc69f7faa9dac2cba3bb20"
@ -2199,6 +2213,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/sharp@^0.26.0":
version "0.26.0"
resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.26.0.tgz#2fa8419dbdaca8dd38f73888b27b207f188a8669"
integrity sha512-oJrR8eiwpL7qykn2IeFRduXM4za7z+7yOUEbKVtuDQ/F6htDLHYO6IbzhaJQHV5n6O3adIh4tJvtgPyLyyydqg==
dependencies:
"@types/node" "*"
"@types/shelljs@^0.8.8": "@types/shelljs@^0.8.8":
version "0.8.8" version "0.8.8"
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.8.tgz#e439c69929b88a2c8123c1a55e09eb708315addf" resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.8.tgz#e439c69929b88a2c8123c1a55e09eb708315addf"
@ -3247,6 +3268,15 @@ bl@^1.0.0:
readable-stream "^2.3.5" readable-stream "^2.3.5"
safe-buffer "^5.1.1" safe-buffer "^5.1.1"
bl@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489"
integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
block-stream@*: block-stream@*:
version "0.0.9" version "0.0.9"
resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
@ -4647,6 +4677,20 @@ decompress-response@^3.3.0:
dependencies: dependencies:
mimic-response "^1.0.0" mimic-response "^1.0.0"
decompress-response@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
dependencies:
mimic-response "^2.0.0"
decompress-response@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
dependencies:
mimic-response "^3.1.0"
deep-extend@^0.6.0: deep-extend@^0.6.0:
version "0.6.0" version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
@ -4741,6 +4785,11 @@ detect-indent@~5.0.0:
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
detect-newline@^2.1.0: detect-newline@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
@ -5124,7 +5173,7 @@ encoding@^0.1.11:
dependencies: dependencies:
iconv-lite "^0.6.2" iconv-lite "^0.6.2"
end-of-stream@^1.0.0, end-of-stream@^1.1.0: end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4" version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
@ -5485,6 +5534,11 @@ expand-brackets@^2.1.4:
snapdragon "^0.8.1" snapdragon "^0.8.1"
to-regex "^3.0.1" to-regex "^3.0.1"
expand-template@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
expand-tilde@^2.0.0, expand-tilde@^2.0.2: expand-tilde@^2.0.0, expand-tilde@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
@ -6131,6 +6185,11 @@ getpass@^0.1.1:
dependencies: dependencies:
assert-plus "^1.0.0" assert-plus "^1.0.0"
github-from-package@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
glob-parent@^3.1.0: glob-parent@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
@ -6739,11 +6798,6 @@ ignore@^5.1.4:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
immer@^7.0.5:
version "7.0.5"
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.5.tgz#8af347db5b60b40af8ae7baf1784ea4d35b5208e"
integrity sha512-TtRAKZyuqld2eYjvWgXISLJ0ZlOl1OOTzRmrmiY8SlB0dnAhZ1OiykIDL5KDFNaPHDXiLfGQFNJGtet8z8AEmg==
import-fresh@^3.0.0, import-fresh@^3.1.0: import-fresh@^3.0.0, import-fresh@^3.1.0:
version "3.2.1" version "3.2.1"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
@ -7867,6 +7921,38 @@ jsdom@^16.2.2:
ws "^7.2.3" ws "^7.2.3"
xml-name-validator "^3.0.0" xml-name-validator "^3.0.0"
jsdom@^16.4.0:
version "16.4.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb"
integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==
dependencies:
abab "^2.0.3"
acorn "^7.1.1"
acorn-globals "^6.0.0"
cssom "^0.4.4"
cssstyle "^2.2.0"
data-urls "^2.0.0"
decimal.js "^10.2.0"
domexception "^2.0.1"
escodegen "^1.14.1"
html-encoding-sniffer "^2.0.1"
is-potential-custom-element-name "^1.0.0"
nwsapi "^2.2.0"
parse5 "5.1.1"
request "^2.88.2"
request-promise-native "^1.0.8"
saxes "^5.0.0"
symbol-tree "^3.2.4"
tough-cookie "^3.0.1"
w3c-hr-time "^1.0.2"
w3c-xmlserializer "^2.0.0"
webidl-conversions "^6.1.0"
whatwg-encoding "^1.0.5"
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
ws "^7.2.3"
xml-name-validator "^3.0.0"
jsesc@^2.5.1: jsesc@^2.5.1:
version "2.5.2" version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@ -8810,6 +8896,16 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
mimic-response@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
mimic-response@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
mini-create-react-context@^0.4.0: mini-create-react-context@^0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040" resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040"
@ -8845,7 +8941,7 @@ minimatch@^3.0.4, minimatch@~3.0.2:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5:
version "1.2.5" version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
@ -8930,6 +9026,11 @@ mixin-deep@^1.2.0:
for-in "^1.0.2" for-in "^1.0.2"
is-extendable "^1.0.1" is-extendable "^1.0.1"
mkdirp-classic@^0.5.2:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4: mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
@ -9037,6 +9138,11 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1" snapdragon "^0.8.1"
to-regex "^3.0.1" to-regex "^3.0.1"
napi-build-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
natural-compare@^1.4.0: natural-compare@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@ -9060,6 +9166,18 @@ no-case@^3.0.3:
lower-case "^2.0.1" lower-case "^2.0.1"
tslib "^1.10.0" tslib "^1.10.0"
node-abi@^2.7.0:
version "2.19.1"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.19.1.tgz#6aa32561d0a5e2fdb6810d8c25641b657a8cea85"
integrity sha512-HbtmIuByq44yhAzK7b9j/FelKlHYISKQn0mtvcBrU5QBkhoCMp5bu8Hv5AI34DcKfOAcJBcOEMwLlwO62FFu9A==
dependencies:
semver "^5.4.1"
node-addon-api@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.2.tgz#04bc7b83fd845ba785bb6eae25bc857e1ef75681"
integrity sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==
node-fetch-npm@^2.0.2: node-fetch-npm@^2.0.2:
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4" resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4"
@ -9237,6 +9355,11 @@ nodemon@^2.0.4:
undefsafe "^2.0.2" undefsafe "^2.0.2"
update-notifier "^4.0.0" update-notifier "^4.0.0"
noop-logger@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=
"nopt@2 || 3": "nopt@2 || 3":
version "3.0.6" version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
@ -9559,7 +9682,7 @@ npm@^6.14.8:
worker-farm "^1.7.0" worker-farm "^1.7.0"
write-file-atomic "^2.4.3" write-file-atomic "^2.4.3"
"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.1.2, npmlog@~4.1.2: "npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.1, npmlog@^4.1.2, npmlog@~4.1.2:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@ -10371,6 +10494,27 @@ postinstall-postinstall@^2.1.0:
resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3"
integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==
prebuild-install@^5.3.5:
version "5.3.5"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.5.tgz#e7e71e425298785ea9d22d4f958dbaccf8bb0e1b"
integrity sha512-YmMO7dph9CYKi5IR/BzjOJlRzpxGGVo1EsLSUZ0mt/Mq0HWZIHOKHHcHdT69yG54C9m6i45GpItwRHpk0Py7Uw==
dependencies:
detect-libc "^1.0.3"
expand-template "^2.0.3"
github-from-package "0.0.0"
minimist "^1.2.3"
mkdirp "^0.5.1"
napi-build-utils "^1.0.1"
node-abi "^2.7.0"
noop-logger "^0.1.1"
npmlog "^4.0.1"
pump "^3.0.0"
rc "^1.2.7"
simple-get "^3.0.3"
tar-fs "^2.0.0"
tunnel-agent "^0.6.0"
which-pm-runs "^1.0.0"
prelude-ls@^1.2.1: prelude-ls@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -10680,7 +10824,7 @@ raw-loader@^4.0.1:
loader-utils "^2.0.0" loader-utils "^2.0.0"
schema-utils "^2.6.5" schema-utils "^2.6.5"
rc@^1.0.1, rc@^1.1.6, rc@^1.2.8: rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8:
version "1.2.8" version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
@ -10802,7 +10946,16 @@ react-zlib-js@^1.0.4:
resolved "https://registry.yarnpkg.com/react-zlib-js/-/react-zlib-js-1.0.4.tgz#dd2b9fbf56d5ab224fa7a99affbbedeba9aa3dc7" resolved "https://registry.yarnpkg.com/react-zlib-js/-/react-zlib-js-1.0.4.tgz#dd2b9fbf56d5ab224fa7a99affbbedeba9aa3dc7"
integrity sha512-ynXD9DFxpE7vtGoa3ZwBtPmZrkZYw2plzHGbanUjBOSN4RtuXdektSfABykHtTiWEHMh7WdYj45LHtp228ZF1A== integrity sha512-ynXD9DFxpE7vtGoa3ZwBtPmZrkZYw2plzHGbanUjBOSN4RtuXdektSfABykHtTiWEHMh7WdYj45LHtp228ZF1A==
react@^16.13.1, react@^16.8.0: react@^16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
react@^16.8.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
@ -10944,7 +11097,7 @@ read@1, read@~1.0.1, read@~1.0.7:
string_decoder "~1.1.1" string_decoder "~1.1.1"
util-deprecate "~1.0.1" util-deprecate "~1.0.1"
readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@ -11649,6 +11802,21 @@ shallow-clone@^3.0.0:
dependencies: dependencies:
kind-of "^6.0.2" kind-of "^6.0.2"
sharp@^0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.26.1.tgz#084e3447ba17f1baf3e3f2e08305ed7aec236ce9"
integrity sha512-9MhwS4ys8pnwBH7MtnBdLzUv+cb24QC4xbzzQL6A+1MQ4Se2V6oPHEX8TIGIZUPRKi6S1kJPVNzt/Xqqp6/H3Q==
dependencies:
color "^3.1.2"
detect-libc "^1.0.3"
node-addon-api "^3.0.2"
npmlog "^4.1.2"
prebuild-install "^5.3.5"
semver "^7.3.2"
simple-get "^4.0.0"
tar-fs "^2.1.0"
tunnel-agent "^0.6.0"
shebang-command@^1.2.0: shebang-command@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@ -11701,6 +11869,29 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
simple-concat@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
simple-get@^3.0.3:
version "3.1.0"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
dependencies:
decompress-response "^4.2.0"
once "^1.3.1"
simple-concat "^1.0.0"
simple-get@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675"
integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==
dependencies:
decompress-response "^6.0.0"
once "^1.3.1"
simple-concat "^1.0.0"
simple-swizzle@^0.2.2: simple-swizzle@^0.2.2:
version "0.2.2" version "0.2.2"
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
@ -12354,6 +12545,16 @@ tapable@^1.0.0, tapable@^1.1.3:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tar-fs@^2.0.0, tar-fs@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5"
integrity sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.0.0"
tar-stream@^1.5.0: tar-stream@^1.5.0:
version "1.6.2" version "1.6.2"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
@ -12367,6 +12568,17 @@ tar-stream@^1.5.0:
to-buffer "^1.1.1" to-buffer "^1.1.1"
xtend "^4.0.0" xtend "^4.0.0"
tar-stream@^2.0.0:
version "2.1.4"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa"
integrity sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==
dependencies:
bl "^4.0.3"
end-of-stream "^1.4.1"
fs-constants "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.1.1"
tar@^2.0.0: tar@^2.0.0:
version "2.2.2" version "2.2.2"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40"
@ -13272,7 +13484,7 @@ webidl-conversions@^5.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
webidl-conversions@^6.0.0: webidl-conversions@^6.0.0, webidl-conversions@^6.1.0:
version "6.1.0" version "6.1.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
@ -13367,6 +13579,11 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which-pm-runs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
which@1, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: which@1, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"