Merge branch 'master' into extensions-docs
@ -149,6 +149,11 @@ jobs:
|
||||
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||
env:
|
||||
GH_TOKEN: $(GH_TOKEN)
|
||||
- script: make publish-npm
|
||||
displayName: Publish npm package
|
||||
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||
env:
|
||||
NPM_TOKEN: $(NPM_TOKEN)
|
||||
- bash: |
|
||||
mkdir -p "$AZURE_CACHE_FOLDER"
|
||||
tar -czf "$AZURE_CACHE_FOLDER/yarn-cache.tar.gz" "$YARN_CACHE_FOLDER"
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
module.exports = {
|
||||
ignorePatterns: ["src/extensions/npm/extensions/api.d.ts"],
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
|
||||
11
LICENSE
@ -1,8 +1,13 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Mirantis, Inc.
|
||||
|
||||
All rights reserved.
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
* All content residing under the "docs/" directory of this repository, if that
|
||||
directory exists, is licensed under "Creative Commons: CC BY-SA 4.0 license".
|
||||
* All third party components incorporated into the Lens Software are licensed
|
||||
under the original license provided by the owner of the applicable component.
|
||||
* Content outside of the above mentioned directories or restrictions above is
|
||||
available under the "MIT" license as defined below.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
4
Makefile
@ -59,10 +59,12 @@ build-extensions:
|
||||
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), $(MAKE) -C $(dir) build;)
|
||||
|
||||
build-npm:
|
||||
yarn compile:extension-types
|
||||
yarn npm:fix-package-version
|
||||
|
||||
publish-npm: build-npm
|
||||
cd src/extensions/npm/extensions && npm publish
|
||||
npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
|
||||
cd src/extensions/npm/extensions && npm publish --access=public
|
||||
|
||||
clean:
|
||||
ifeq "$(DETECTED_OS)" "Windows"
|
||||
|
||||
@ -40,9 +40,10 @@ brew cask install lens
|
||||
|
||||
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:renderer` compiles electron's renderer part and start watching files
|
||||
1. `yarn dev-run` runs app in dev-mode and restarts when electron's main process file has changed
|
||||
1. `yarn dev:main` compiles electron's main process app part
|
||||
1. `yarn dev:renderer` compiles electron's renderer app part
|
||||
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:
|
||||
|
||||
|
||||
51
build/build_tray_icon.ts
Normal 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
|
After Width: | Height: | Size: 448 B |
BIN
build/tray/tray_icon@2x.png
Normal file
|
After Width: | Height: | Size: 973 B |
BIN
build/tray/tray_icon@3x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
build/tray/tray_icon_dark.png
Normal file
|
After Width: | Height: | Size: 478 B |
BIN
build/tray/tray_icon_dark@2x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
build/tray/tray_icon_dark@3x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
@ -1,26 +1,16 @@
|
||||
import { LensRendererExtension, Registry } from "@k8slens/extensions";
|
||||
import { ExamplePage, ExampleIcon } from "./page"
|
||||
import { LensRendererExtension } from "@k8slens/extensions";
|
||||
import { ExampleIcon, ExamplePage } from "./page"
|
||||
import React from "react"
|
||||
|
||||
export default class ExampleExtension extends LensRendererExtension {
|
||||
onActivate() {
|
||||
console.log('EXAMPLE EXTENSION RENDERER: ACTIVATED', this.getMeta());
|
||||
}
|
||||
|
||||
registerClusterPage(registry: Registry.ClusterPageRegistry) {
|
||||
this.disposers.push(
|
||||
registry.add({
|
||||
path: "/extension-example",
|
||||
title: "Example Extension",
|
||||
components: {
|
||||
Page: () => <ExamplePage extension={this} />,
|
||||
MenuIcon: ExampleIcon,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
onDeactivate() {
|
||||
console.log('EXAMPLE EXTENSION RENDERER: DEACTIVATED', this.getMeta());
|
||||
}
|
||||
clusterPages = [
|
||||
{
|
||||
path: "/extension-example",
|
||||
title: "Example Extension",
|
||||
components: {
|
||||
Page: () => <ExamplePage extension={this}/>,
|
||||
MenuIcon: ExampleIcon,
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,25 +1,23 @@
|
||||
import { Registry, LensRendererExtension } from "@k8slens/extensions"
|
||||
import { LensRendererExtension } from "@k8slens/extensions"
|
||||
import { MetricsFeature } from "./src/metrics-feature"
|
||||
import React from "react"
|
||||
|
||||
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
||||
registerClusterFeatures(registry: Registry.ClusterFeatureRegistry) {
|
||||
this.disposers.push(
|
||||
registry.add({
|
||||
title: "Metrics Stack",
|
||||
components: {
|
||||
Description: () => {
|
||||
return (
|
||||
<span>
|
||||
clusterFeatures = [
|
||||
{
|
||||
title: "Metrics Stack",
|
||||
components: {
|
||||
Description: () => {
|
||||
return (
|
||||
<span>
|
||||
Enable timeseries data visualization (Prometheus stack) for your cluster.
|
||||
Install this only if you don't have existing Prometheus stack installed.
|
||||
You can see preview of manifests <a href="https://github.com/lensapp/lens/tree/master/extensions/lens-metrics/resources" target="_blank">here</a>.
|
||||
</span>
|
||||
)
|
||||
}
|
||||
},
|
||||
feature: new MetricsFeature()
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
feature: new MetricsFeature()
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,21 +1,15 @@
|
||||
import { Registry, LensRendererExtension } from "@k8slens/extensions";
|
||||
import { LensRendererExtension } from "@k8slens/extensions";
|
||||
import React from "react"
|
||||
import { NodeMenu } from "./src/node-menu"
|
||||
import { NodeMenu, NodeMenuProps } from "./src/node-menu"
|
||||
|
||||
export default class NodeMenuRendererExtension extends LensRendererExtension {
|
||||
async onActivate() {
|
||||
console.log("node-menu extension activated")
|
||||
}
|
||||
|
||||
registerKubeObjectMenus(registry: Registry.KubeObjectMenuRegistry) {
|
||||
this.disposers.push(
|
||||
registry.add({
|
||||
kind: "Node",
|
||||
apiVersions: ["v1"],
|
||||
components: {
|
||||
MenuItem: (props) => <NodeMenu {...props} />
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
kubeObjectMenuItems = [
|
||||
{
|
||||
kind: "Node",
|
||||
apiVersions: ["v1"],
|
||||
components: {
|
||||
MenuItem: (props: NodeMenuProps) => <NodeMenu {...props} />
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import React from "react";
|
||||
import { Component, K8sApi, Navigation} from "@k8slens/extensions"
|
||||
|
||||
export function NodeMenu(props: Component.KubeObjectMenuProps<K8sApi.Node>) {
|
||||
export interface NodeMenuProps extends Component.KubeObjectMenuProps<K8sApi.Node> {
|
||||
}
|
||||
|
||||
export function NodeMenu(props: NodeMenuProps) {
|
||||
const { object: node, toolbar } = props;
|
||||
if (!node) return null;
|
||||
const nodeName = node.getName();
|
||||
|
||||
@ -1,31 +1,23 @@
|
||||
import { Registry, LensRendererExtension } from "@k8slens/extensions";
|
||||
import { PodShellMenu } from "./src/shell-menu"
|
||||
import { PodLogsMenu } from "./src/logs-menu"
|
||||
import { LensRendererExtension } from "@k8slens/extensions";
|
||||
import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu"
|
||||
import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu"
|
||||
import React from "react"
|
||||
|
||||
export default class PodMenuRendererExtension extends LensRendererExtension {
|
||||
async onActivate() {
|
||||
console.log("pod-menu extension activated")
|
||||
}
|
||||
|
||||
registerKubeObjectMenus(registry: Registry.KubeObjectMenuRegistry) {
|
||||
this.disposers.push(
|
||||
registry.add({
|
||||
kind: "Pod",
|
||||
apiVersions: ["v1"],
|
||||
components: {
|
||||
MenuItem: (props) => <PodShellMenu {...props} />
|
||||
}
|
||||
})
|
||||
)
|
||||
this.disposers.push(
|
||||
registry.add({
|
||||
kind: "Pod",
|
||||
apiVersions: ["v1"],
|
||||
components: {
|
||||
MenuItem: (props) => <PodLogsMenu {...props} />
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
kubeObjectMenuItems = [
|
||||
{
|
||||
kind: "Pod",
|
||||
apiVersions: ["v1"],
|
||||
components: {
|
||||
MenuItem: (props: PodShellMenuProps) => <PodShellMenu {...props} />
|
||||
}
|
||||
},
|
||||
{
|
||||
kind: "Pod",
|
||||
apiVersions: ["v1"],
|
||||
components: {
|
||||
MenuItem: (props: PodLogsMenuProps) => <PodLogsMenu {...props} />
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions";
|
||||
|
||||
interface Props extends Component.KubeObjectMenuProps<K8sApi.Pod> {
|
||||
export interface PodLogsMenuProps extends Component.KubeObjectMenuProps<K8sApi.Pod> {
|
||||
}
|
||||
|
||||
export class PodLogsMenu extends React.Component<Props> {
|
||||
export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
|
||||
showLogs(container: K8sApi.IPodContainer) {
|
||||
Navigation.hideDetails();
|
||||
const pod = this.props.object;
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
import React from "react";
|
||||
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions";
|
||||
|
||||
interface Props extends Component.KubeObjectMenuProps<K8sApi.Pod> {
|
||||
export interface PodShellMenuProps extends Component.KubeObjectMenuProps<K8sApi.Pod> {
|
||||
}
|
||||
|
||||
export class PodShellMenu extends React.Component<Props> {
|
||||
export class PodShellMenu extends React.Component<PodShellMenuProps> {
|
||||
async execShell(container?: string) {
|
||||
Navigation.hideDetails();
|
||||
const { object: pod } = this.props
|
||||
|
||||
@ -1,23 +1,14 @@
|
||||
import { LensMainExtension, Registry, windowManager } from "@k8slens/extensions";
|
||||
import { LensMainExtension, windowManager } from "@k8slens/extensions";
|
||||
import { supportPageURL } from "./src/support.route";
|
||||
|
||||
export default class SupportPageMainExtension extends LensMainExtension {
|
||||
async onActivate() {
|
||||
console.log("support page extension activated")
|
||||
}
|
||||
|
||||
async registerAppMenus(registry: Registry.MenuRegistry) {
|
||||
this.disposers.push(
|
||||
registry.add({
|
||||
parentId: "help",
|
||||
label: "Support",
|
||||
click() {
|
||||
windowManager.navigate({
|
||||
channel: "menu:navigate", // fixme: use windowManager.ensureMainWindow from Tray's PR
|
||||
url: supportPageURL(),
|
||||
});
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
appMenus = [
|
||||
{
|
||||
parentId: "help",
|
||||
label: "Support",
|
||||
click() {
|
||||
windowManager.navigate(supportPageURL());
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,39 +1,31 @@
|
||||
import React from "react";
|
||||
import { Component, LensRendererExtension, Navigation, Registry } from "@k8slens/extensions";
|
||||
import { Component, LensRendererExtension, Navigation } from "@k8slens/extensions";
|
||||
import { supportPageRoute, supportPageURL } from "./src/support.route";
|
||||
import { Support } from "./src/support";
|
||||
|
||||
export default class SupportPageRendererExtension extends LensRendererExtension {
|
||||
async onActivate() {
|
||||
console.log("support page extension activated")
|
||||
}
|
||||
globalPages = [
|
||||
{
|
||||
...supportPageRoute,
|
||||
url: supportPageURL(),
|
||||
hideInMenu: true,
|
||||
components: {
|
||||
Page: Support,
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
registerGlobalPage(registry: Registry.GlobalPageRegistry) {
|
||||
this.disposers.push(
|
||||
registry.add({
|
||||
...supportPageRoute,
|
||||
url: supportPageURL(),
|
||||
hideInMenu: true,
|
||||
components: {
|
||||
Page: Support,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
registerStatusBarItem(registry: Registry.StatusBarRegistry) {
|
||||
this.disposers.push(
|
||||
registry.add({
|
||||
item: (
|
||||
<div
|
||||
className="flex align-center gaps hover-highlight"
|
||||
onClick={() => Navigation.navigate(supportPageURL())}
|
||||
>
|
||||
<Component.Icon material="help_outline" small />
|
||||
<span>Support</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
statusBarItems = [
|
||||
{
|
||||
item: (
|
||||
<div
|
||||
className="flex align-center gaps hover-highlight"
|
||||
onClick={() => Navigation.navigate(supportPageURL())}
|
||||
>
|
||||
<Component.Icon material="help_outline" small/>
|
||||
<span>Support</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
|
||||
import React from "react"
|
||||
import { observer } from "mobx-react"
|
||||
import { CommonVars, Component } from "@k8slens/extensions";
|
||||
import { App, Component } from "@k8slens/extensions";
|
||||
|
||||
@observer
|
||||
export class Support extends React.Component {
|
||||
render() {
|
||||
const { PageLayout } = Component;
|
||||
const { slackUrl, issuesTrackerUrl } = CommonVars;
|
||||
const { slackUrl, issuesTrackerUrl } = App;
|
||||
return (
|
||||
<PageLayout showOnTop className="Support" header={<h2>Support</h2>}>
|
||||
<h2>Community Slack Channel</h2>
|
||||
|
||||
@ -1,29 +1,23 @@
|
||||
import { LensRendererExtension, Registry } from "@k8slens/extensions";
|
||||
import { LensRendererExtension } from "@k8slens/extensions";
|
||||
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store"
|
||||
import { TelemetryPreferenceHint, TelemetryPreferenceInput } from "./src/telemetry-preference"
|
||||
import { tracker } from "./src/tracker"
|
||||
import React from "react"
|
||||
|
||||
export default class TelemetryRendererExtension extends LensRendererExtension {
|
||||
appPreferences = [
|
||||
{
|
||||
title: "Telemetry & Usage Tracking",
|
||||
components: {
|
||||
Hint: () => <TelemetryPreferenceHint/>,
|
||||
Input: () => <TelemetryPreferenceInput telemetry={telemetryPreferencesStore}/>
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
async onActivate() {
|
||||
console.log("telemetry extension activated")
|
||||
tracker.start()
|
||||
await telemetryPreferencesStore.loadExtension(this)
|
||||
}
|
||||
|
||||
registerAppPreferences(registry: Registry.AppPreferenceRegistry) {
|
||||
this.disposers.push(
|
||||
registry.add({
|
||||
title: "Telemetry & Usage Tracking",
|
||||
components: {
|
||||
Hint: () => <TelemetryPreferenceHint />,
|
||||
Input: () => <TelemetryPreferenceInput telemetry={telemetryPreferencesStore} />
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
onDeactivate() {
|
||||
console.log("telemetry extension deactivated")
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,8 +38,9 @@ export class Tracker extends Util.Singleton {
|
||||
}
|
||||
this.eventHandlers.push(handler)
|
||||
EventBus.appEventBus.addListener(handler)
|
||||
|
||||
this.reportInterval = setInterval(this.reportData, 60 * 60 * 1000) // report every 1h
|
||||
this.reportInterval = setInterval(() => {
|
||||
this.reportData()
|
||||
}, 60 * 60 * 1000) // report every 1h
|
||||
}
|
||||
|
||||
stop() {
|
||||
|
||||
@ -389,13 +389,13 @@ describe("Lens integration tests", () => {
|
||||
it(`shows ${drawer} drawer`, async () => {
|
||||
expect(clusterAdded).toBe(true)
|
||||
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 }) => {
|
||||
it(`shows ${drawer}->${name} page`, async () => {
|
||||
expect(clusterAdded).toBe(true)
|
||||
await app.client.click(`a[href="/${href}"]`)
|
||||
await app.client.click(`a[href^="/${href}"]`)
|
||||
await app.client.waitUntilTextExists(expectedSelector, expectedText)
|
||||
})
|
||||
})
|
||||
@ -404,7 +404,7 @@ describe("Lens integration tests", () => {
|
||||
it(`hides ${drawer} drawer`, async () => {
|
||||
expect(clusterAdded).toBe(true)
|
||||
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 () => {
|
||||
expect(clusterAdded).toBe(true)
|
||||
await app.client.click(".sidebar-nav #workloads span.link-text")
|
||||
await app.client.waitUntilTextExists('a[href="/pods"]', "Pods")
|
||||
await app.client.click('a[href="/pods"]')
|
||||
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods")
|
||||
await app.client.click('a[href^="/pods"]')
|
||||
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver")
|
||||
await app.client.click('.Icon.new-dock-tab')
|
||||
await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource")
|
||||
|
||||
@ -13,7 +13,6 @@ export function setup(): Application {
|
||||
path: AppPaths[process.platform],
|
||||
startTimeout: 30000,
|
||||
waitTimeout: 60000,
|
||||
chromeDriverArgs: ['remote-debugging-port=9222'],
|
||||
env: {
|
||||
CICD: "true"
|
||||
}
|
||||
@ -27,6 +26,6 @@ export async function tearDown(app: Application) {
|
||||
try {
|
||||
process.kill(pid, "SIGKILL");
|
||||
} catch (e) {
|
||||
return
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ msgid "Active"
|
||||
msgstr "Active"
|
||||
|
||||
#: 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"
|
||||
msgstr "Add Cluster"
|
||||
|
||||
@ -219,11 +219,11 @@ msgstr "Allocatable"
|
||||
msgid "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"
|
||||
msgstr "Allow telemetry & usage tracking"
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:161
|
||||
#: src/renderer/components/+preferences/preferences.tsx:164
|
||||
msgid "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"
|
||||
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/wizard/wizard.tsx:130
|
||||
msgid "Back"
|
||||
@ -422,7 +430,7 @@ msgstr "Cancel"
|
||||
msgid "Capacity"
|
||||
msgstr "Capacity"
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:160
|
||||
#: src/renderer/components/+preferences/preferences.tsx:163
|
||||
msgid "Certificate Trust"
|
||||
msgstr "Certificate Trust"
|
||||
|
||||
@ -817,7 +825,7 @@ msgstr "Desired Healthy"
|
||||
msgid "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"
|
||||
msgstr "Disconnect"
|
||||
|
||||
@ -831,7 +839,7 @@ msgstr "Disk"
|
||||
msgid "Disk:"
|
||||
msgstr "Disk:"
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:165
|
||||
#: src/renderer/components/+preferences/preferences.tsx:168
|
||||
msgid "Does not affect cluster communications!"
|
||||
msgstr "Does not affect cluster communications!"
|
||||
|
||||
@ -927,8 +935,8 @@ msgstr "Environment"
|
||||
msgid "Error stack"
|
||||
msgstr "Error stack"
|
||||
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:89
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:130
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:88
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:129
|
||||
msgid "Error while adding cluster(s): {0}"
|
||||
msgstr "Error while adding cluster(s): {0}"
|
||||
|
||||
@ -1581,7 +1589,7 @@ msgstr "Namespaces"
|
||||
msgid "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."
|
||||
msgstr "Needed with some corporate proxies that do certificate re-writing."
|
||||
|
||||
@ -1798,7 +1806,7 @@ msgstr "Persistent Volume Claims"
|
||||
msgid "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"
|
||||
msgstr "Please select at least one cluster context"
|
||||
|
||||
@ -2025,8 +2033,8 @@ msgstr "Releases"
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:152
|
||||
#: 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:82
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:73
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:79
|
||||
#: 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:85
|
||||
@ -2470,7 +2478,7 @@ msgstr "Set"
|
||||
msgid "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"
|
||||
msgstr "Settings"
|
||||
|
||||
@ -2613,7 +2621,7 @@ msgstr "Submitting.."
|
||||
msgid "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)"
|
||||
msgstr "Successfully imported <0>{0}</0> cluster(s)"
|
||||
|
||||
@ -2635,11 +2643,11 @@ msgstr "TLS"
|
||||
msgid "Taints"
|
||||
msgstr "Taints"
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:168
|
||||
#: src/renderer/components/+preferences/preferences.tsx:171
|
||||
msgid "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."
|
||||
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."
|
||||
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."
|
||||
msgstr "This will make Lens to trust ANY certificate authority without any validations."
|
||||
|
||||
@ -2953,7 +2961,7 @@ msgstr "listKind"
|
||||
msgid "never"
|
||||
msgstr "never"
|
||||
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:133
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:130
|
||||
msgid "new"
|
||||
msgstr "new"
|
||||
|
||||
|
||||
@ -88,7 +88,7 @@ msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
msgstr ""
|
||||
|
||||
@ -219,11 +219,11 @@ msgstr ""
|
||||
msgid "Allow Privilege Escalation"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:169
|
||||
#: src/renderer/components/+preferences/preferences.tsx:172
|
||||
msgid "Allow telemetry & usage tracking"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:161
|
||||
#: src/renderer/components/+preferences/preferences.tsx:164
|
||||
msgid "Allow untrusted Certificate Authorities"
|
||||
msgstr ""
|
||||
|
||||
@ -301,6 +301,14 @@ msgstr ""
|
||||
msgid "Auth App Role"
|
||||
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/wizard/wizard.tsx:130
|
||||
msgid "Back"
|
||||
@ -422,7 +430,7 @@ msgstr ""
|
||||
msgid "Capacity"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:160
|
||||
#: src/renderer/components/+preferences/preferences.tsx:163
|
||||
msgid "Certificate Trust"
|
||||
msgstr ""
|
||||
|
||||
@ -813,7 +821,7 @@ msgstr ""
|
||||
msgid "Desired number of replicas"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:65
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:62
|
||||
msgid "Disconnect"
|
||||
msgstr ""
|
||||
|
||||
@ -827,7 +835,7 @@ msgstr ""
|
||||
msgid "Disk:"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:165
|
||||
#: src/renderer/components/+preferences/preferences.tsx:168
|
||||
msgid "Does not affect cluster communications!"
|
||||
msgstr ""
|
||||
|
||||
@ -923,8 +931,8 @@ msgstr ""
|
||||
msgid "Error stack"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:89
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:130
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:88
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:129
|
||||
msgid "Error while adding cluster(s): {0}"
|
||||
msgstr ""
|
||||
|
||||
@ -1572,7 +1580,7 @@ msgstr ""
|
||||
msgid "Namespaces: {0}"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@ -1781,7 +1789,7 @@ msgstr ""
|
||||
msgid "Persistent Volumes"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@ -2008,8 +2016,8 @@ msgstr ""
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:152
|
||||
#: 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:82
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:73
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:79
|
||||
#: 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:85
|
||||
@ -2453,7 +2461,7 @@ msgstr ""
|
||||
msgid "Set quota"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:53
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:51
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
@ -2596,7 +2604,7 @@ msgstr ""
|
||||
msgid "Subsets"
|
||||
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)"
|
||||
msgstr ""
|
||||
|
||||
@ -2618,11 +2626,11 @@ msgstr ""
|
||||
msgid "Taints"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:168
|
||||
#: src/renderer/components/+preferences/preferences.tsx:171
|
||||
msgid "Telemetry & Usage Tracking"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@ -2658,7 +2666,7 @@ msgstr ""
|
||||
msgid "This is the quick launch menu."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@ -2936,7 +2944,7 @@ msgstr ""
|
||||
msgid "never"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:133
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:130
|
||||
msgid "new"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@ msgid "Active"
|
||||
msgstr "Активный"
|
||||
|
||||
#: 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"
|
||||
msgstr ""
|
||||
|
||||
@ -220,11 +220,11 @@ msgstr ""
|
||||
msgid "Allow Privilege Escalation"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:169
|
||||
#: src/renderer/components/+preferences/preferences.tsx:172
|
||||
msgid "Allow telemetry & usage tracking"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:161
|
||||
#: src/renderer/components/+preferences/preferences.tsx:164
|
||||
msgid "Allow untrusted Certificate Authorities"
|
||||
msgstr ""
|
||||
|
||||
@ -302,6 +302,14 @@ msgstr ""
|
||||
msgid "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/wizard/wizard.tsx:130
|
||||
msgid "Back"
|
||||
@ -423,7 +431,7 @@ msgstr "Отмена"
|
||||
msgid "Capacity"
|
||||
msgstr "Емкость"
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:160
|
||||
#: src/renderer/components/+preferences/preferences.tsx:163
|
||||
msgid "Certificate Trust"
|
||||
msgstr ""
|
||||
|
||||
@ -818,7 +826,7 @@ msgstr ""
|
||||
msgid "Desired number of replicas"
|
||||
msgstr "Нужный уровень реплик"
|
||||
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:65
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:62
|
||||
msgid "Disconnect"
|
||||
msgstr ""
|
||||
|
||||
@ -832,7 +840,7 @@ msgstr "Диск"
|
||||
msgid "Disk:"
|
||||
msgstr "Диск:"
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:165
|
||||
#: src/renderer/components/+preferences/preferences.tsx:168
|
||||
msgid "Does not affect cluster communications!"
|
||||
msgstr ""
|
||||
|
||||
@ -928,8 +936,8 @@ msgstr "Среда"
|
||||
msgid "Error stack"
|
||||
msgstr "Стэк ошибки"
|
||||
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:89
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:130
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:88
|
||||
#: src/renderer/components/+add-cluster/add-cluster.tsx:129
|
||||
msgid "Error while adding cluster(s): {0}"
|
||||
msgstr ""
|
||||
|
||||
@ -1582,7 +1590,7 @@ msgstr "Namespaces"
|
||||
msgid "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."
|
||||
msgstr ""
|
||||
|
||||
@ -1799,7 +1807,7 @@ msgstr "Persistent Volume Claims"
|
||||
msgid "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"
|
||||
msgstr ""
|
||||
|
||||
@ -2026,8 +2034,8 @@ msgstr "Релизы"
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:152
|
||||
#: 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:82
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:73
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:79
|
||||
#: 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:85
|
||||
@ -2471,7 +2479,7 @@ msgstr "Установлено"
|
||||
msgid "Set quota"
|
||||
msgstr "Установить квоту"
|
||||
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:53
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:51
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
@ -2614,7 +2622,7 @@ msgstr "Применение.."
|
||||
msgid "Subsets"
|
||||
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)"
|
||||
msgstr ""
|
||||
|
||||
@ -2636,11 +2644,11 @@ msgstr "TLS"
|
||||
msgid "Taints"
|
||||
msgstr "Метки блокировки"
|
||||
|
||||
#: src/renderer/components/+preferences/preferences.tsx:168
|
||||
#: src/renderer/components/+preferences/preferences.tsx:171
|
||||
msgid "Telemetry & Usage Tracking"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@ -2676,7 +2684,7 @@ msgstr ""
|
||||
msgid "This is the quick launch menu."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@ -2954,7 +2962,7 @@ msgstr ""
|
||||
msgid "never"
|
||||
msgstr ""
|
||||
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:133
|
||||
#: src/renderer/components/cluster-manager/clusters-menu.tsx:130
|
||||
msgid "new"
|
||||
msgstr ""
|
||||
|
||||
|
||||
32
package.json
@ -2,7 +2,7 @@
|
||||
"name": "kontena-lens",
|
||||
"productName": "Lens",
|
||||
"description": "Lens - The Kubernetes IDE",
|
||||
"version": "3.6.6",
|
||||
"version": "4.0.0-alpha.2",
|
||||
"main": "static/build/main.js",
|
||||
"copyright": "© 2020, Mirantis, Inc.",
|
||||
"license": "MIT",
|
||||
@ -16,12 +16,12 @@
|
||||
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\"",
|
||||
"dev:main": "yarn compile:main --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:main": "webpack --config webpack.main.ts",
|
||||
"compile:renderer": "webpack --config webpack.renderer.ts",
|
||||
"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",
|
||||
"build:linux": "yarn compile && electron-builder --linux --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:kubectl": "yarn run ts-node build/download_kubectl.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/"
|
||||
},
|
||||
"config": {
|
||||
@ -97,6 +98,11 @@
|
||||
"to": "static/",
|
||||
"filter": "!**/main.js"
|
||||
},
|
||||
{
|
||||
"from": "build/tray",
|
||||
"to": "static/icons",
|
||||
"filter": "*.png"
|
||||
},
|
||||
{
|
||||
"from": "extensions/",
|
||||
"to": "./extensions/",
|
||||
@ -186,6 +192,20 @@
|
||||
"@hapi/call": "^8.0.0",
|
||||
"@hapi/subtext": "^7.0.3",
|
||||
"@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",
|
||||
"chalk": "^4.1.0",
|
||||
"command-exists": "1.2.9",
|
||||
@ -198,8 +218,8 @@
|
||||
"fs-extra": "^9.0.1",
|
||||
"handlebars": "^4.7.6",
|
||||
"http-proxy": "^1.18.1",
|
||||
"immer": "^7.0.5",
|
||||
"js-yaml": "^3.14.0",
|
||||
"jsdom": "^16.4.0",
|
||||
"jsonpath": "^1.0.2",
|
||||
"lodash": "^4.17.15",
|
||||
"mac-ca": "^1.0.4",
|
||||
@ -276,6 +296,7 @@
|
||||
"@types/request": "^2.48.5",
|
||||
"@types/request-promise-native": "^1.0.17",
|
||||
"@types/semver": "^7.2.0",
|
||||
"@types/sharp": "^0.26.0",
|
||||
"@types/shelljs": "^0.8.8",
|
||||
"@types/spdy": "^3.4.4",
|
||||
"@types/tar": "^4.0.3",
|
||||
@ -327,7 +348,7 @@
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"progress-bar-webpack-plugin": "^2.1.0",
|
||||
"raw-loader": "^4.0.1",
|
||||
"react": "^16.13.1",
|
||||
"react": "^16.14.0",
|
||||
"react-beautiful-dnd": "^13.0.0",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "^5.2.0",
|
||||
@ -339,6 +360,7 @@
|
||||
"rollup-plugin-ignore-import": "^1.3.2",
|
||||
"rollup-pluginutils": "^2.8.2",
|
||||
"sass-loader": "^8.0.2",
|
||||
"sharp": "^0.26.1",
|
||||
"spectron": "11.0.0",
|
||||
"style-loader": "^1.2.1",
|
||||
"terser-webpack-plugin": "^3.0.3",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { WorkspaceId } from "./workspace-store";
|
||||
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 { action, computed, observable, toJS } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
@ -113,7 +113,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
@action
|
||||
setActive(id: ClusterId) {
|
||||
this.activeClusterId = id;
|
||||
this.activeClusterId = this.clusters.has(id) ? id : null;
|
||||
}
|
||||
|
||||
@action
|
||||
@ -160,7 +160,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
if (cluster) {
|
||||
this.clusters.delete(clusterId);
|
||||
if (this.activeClusterId === clusterId) {
|
||||
this.activeClusterId = null;
|
||||
this.setActive(null);
|
||||
}
|
||||
// remove only custom kubeconfigs (pasted as text)
|
||||
if (cluster.kubeConfigPath == ClusterStore.getCustomKubeConfigPath(clusterId)) {
|
||||
|
||||
@ -27,6 +27,7 @@ export interface UserPreferences {
|
||||
downloadKubectlBinaries?: boolean;
|
||||
downloadBinariesPath?: string;
|
||||
kubectlBinariesPath?: string;
|
||||
openAtLogin?: boolean;
|
||||
}
|
||||
|
||||
export class UserStore extends BaseStore<UserStoreModel> {
|
||||
@ -38,14 +39,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
||||
migrations: migrations,
|
||||
});
|
||||
|
||||
// track telemetry availability
|
||||
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);
|
||||
this.handleOnLoad();
|
||||
}
|
||||
|
||||
@observable lastSeenAppVersion = "0.0.0"
|
||||
@ -59,8 +53,31 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
||||
colorTheme: UserStore.defaultTheme,
|
||||
downloadMirror: "default",
|
||||
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() {
|
||||
return semver.gt(getAppVersion(), this.lastSeenAppVersion);
|
||||
}
|
||||
|
||||
14
src/common/utils/buildUrl.ts
Normal 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}` : "")
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,6 @@
|
||||
import { action, computed, observable, toJS } from "mobx";
|
||||
import { BaseStore } from "./base-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";
|
||||
|
||||
export type WorkspaceId = string;
|
||||
@ -57,18 +55,13 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
}
|
||||
|
||||
@action
|
||||
setActive(id = WorkspaceStore.defaultId, { redirectToLanding = true, resetActiveCluster = true } = {}) {
|
||||
setActive(id = WorkspaceStore.defaultId, reset = true) {
|
||||
if (id === this.currentWorkspaceId) return;
|
||||
if (!this.getById(id)) {
|
||||
throw new Error(`workspace ${id} doesn't exist`);
|
||||
}
|
||||
this.currentWorkspaceId = id;
|
||||
if (resetActiveCluster) {
|
||||
clusterStore.setActive(null)
|
||||
}
|
||||
if (redirectToLanding) {
|
||||
navigate(landingURL())
|
||||
}
|
||||
clusterStore.activeClusterId = null; // fixme: handle previously selected cluster from current workspace
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { app } from "electron";
|
||||
import { getAppVersion } from "../../common/utils";
|
||||
|
||||
export const version = getAppVersion()
|
||||
export { isSnap, isWindows, isMac, isLinux, appName } from "../../common/vars"
|
||||
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars"
|
||||
@ -10,9 +10,9 @@ import * as EventBus from "./event-bus"
|
||||
import * as Store from "./stores"
|
||||
import * as Util from "./utils"
|
||||
import * as Registry from "../registries"
|
||||
import * as CommonVars from "../../common/vars";
|
||||
import * as ClusterFeature from "./cluster-feature"
|
||||
|
||||
// TODO: allow to expose windowManager.navigate() as Navigation.navigate() in runtime
|
||||
export let windowManager: WindowManager;
|
||||
|
||||
export {
|
||||
@ -22,5 +22,4 @@ export {
|
||||
Store,
|
||||
Util,
|
||||
Registry,
|
||||
CommonVars,
|
||||
}
|
||||
|
||||
@ -36,26 +36,26 @@ export class ExtensionLoader {
|
||||
|
||||
loadOnMain() {
|
||||
logger.info('[EXTENSIONS-LOADER]: load on main')
|
||||
this.autoloadExtensions((instance: LensMainExtension) => {
|
||||
instance.registerAppMenus(menuRegistry);
|
||||
this.autoloadExtensions((extension: LensMainExtension) => {
|
||||
extension.registerTo(menuRegistry, extension.appMenus)
|
||||
})
|
||||
}
|
||||
|
||||
loadOnClusterManagerRenderer() {
|
||||
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
||||
this.autoloadExtensions((instance: LensRendererExtension) => {
|
||||
instance.registerGlobalPage(globalPageRegistry)
|
||||
instance.registerAppPreferences(appPreferenceRegistry)
|
||||
instance.registerClusterFeatures(clusterFeatureRegistry)
|
||||
instance.registerStatusBarItem(statusBarRegistry)
|
||||
this.autoloadExtensions((extension: LensRendererExtension) => {
|
||||
extension.registerTo(globalPageRegistry, extension.globalPages)
|
||||
extension.registerTo(appPreferenceRegistry, extension.appPreferences)
|
||||
extension.registerTo(clusterFeatureRegistry, extension.clusterFeatures)
|
||||
extension.registerTo(statusBarRegistry, extension.statusBarItems)
|
||||
})
|
||||
}
|
||||
|
||||
loadOnClusterRenderer() {
|
||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
||||
this.autoloadExtensions((instance: LensRendererExtension) => {
|
||||
instance.registerClusterPage(clusterPageRegistry)
|
||||
instance.registerKubeObjectMenus(kubeObjectMenuRegistry)
|
||||
this.autoloadExtensions((extension: LensRendererExtension) => {
|
||||
extension.registerTo(clusterPageRegistry, extension.clusterPages)
|
||||
extension.registerTo(kubeObjectMenuRegistry, extension.kubeObjectMenuItems)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -25,10 +25,18 @@ export class ExtensionManager {
|
||||
return extensionPackagesRoot()
|
||||
}
|
||||
|
||||
get inTreeTargetPath() {
|
||||
return path.join(this.extensionPackagesRoot, "extensions")
|
||||
}
|
||||
|
||||
get inTreeFolderPath(): string {
|
||||
return path.resolve(__static, "../extensions");
|
||||
}
|
||||
|
||||
get nodeModulesPath(): string {
|
||||
return path.join(this.extensionPackagesRoot, "node_modules")
|
||||
}
|
||||
|
||||
get localFolderPath(): string {
|
||||
return path.join(os.homedir(), ".k8slens", "extensions");
|
||||
}
|
||||
@ -39,7 +47,13 @@ export class ExtensionManager {
|
||||
|
||||
async load() {
|
||||
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot)
|
||||
await fs.ensureDir(path.join(this.extensionPackagesRoot, "node_modules"))
|
||||
if (this.inTreeFolderPath !== this.inTreeTargetPath) {
|
||||
// we need to copy in-tree extensions so that we can symlink them properly on "npm install"
|
||||
await fs.remove(this.inTreeTargetPath)
|
||||
await fs.ensureDir(this.inTreeTargetPath)
|
||||
await fs.copy(this.inTreeFolderPath, this.inTreeTargetPath)
|
||||
}
|
||||
await fs.ensureDir(this.nodeModulesPath)
|
||||
await fs.ensureDir(this.localFolderPath)
|
||||
return await this.loadExtensions();
|
||||
}
|
||||
@ -55,7 +69,7 @@ export class ExtensionManager {
|
||||
id: manifestJson.name,
|
||||
version: manifestJson.version,
|
||||
name: manifestJson.name,
|
||||
manifestPath: path.join(this.extensionPackagesRoot, "node_modules", manifestJson.name, "package.json"),
|
||||
manifestPath: path.join(this.nodeModulesPath, manifestJson.name, "package.json"),
|
||||
manifest: manifestJson
|
||||
}
|
||||
} catch (err) {
|
||||
@ -80,14 +94,14 @@ export class ExtensionManager {
|
||||
|
||||
async loadExtensions() {
|
||||
const bundledExtensions = await this.loadBundledExtensions()
|
||||
const localExtendions = await this.loadFromFolder(this.localFolderPath)
|
||||
const extensions = bundledExtensions.concat(localExtendions)
|
||||
const localExtensions = await this.loadFromFolder(this.localFolderPath)
|
||||
const extensions = bundledExtensions.concat(localExtensions)
|
||||
return new Map(extensions.map(ext => [ext.id, ext]));
|
||||
}
|
||||
|
||||
async loadBundledExtensions() {
|
||||
const extensions: InstalledExtension[] = []
|
||||
const folderPath = this.inTreeFolderPath
|
||||
const folderPath = this.inTreeTargetPath
|
||||
const bundledExtensions = getBundledExtensions()
|
||||
const paths = await fs.readdir(folderPath);
|
||||
for (const fileName of paths) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { readJsonSync } from "fs-extra";
|
||||
import { action, observable, toJS } from "mobx";
|
||||
import logger from "../main/logger";
|
||||
import { BaseRegistry } from "./registries/base-registry";
|
||||
|
||||
export type ExtensionId = string | ExtensionPackageJsonPath;
|
||||
export type ExtensionPackageJsonPath = string;
|
||||
@ -25,7 +26,7 @@ export interface ExtensionManifest extends ExtensionModel {
|
||||
export class LensExtension implements ExtensionModel {
|
||||
public id: ExtensionId;
|
||||
public updateUrl: string;
|
||||
protected disposers: Function[] = [];
|
||||
protected disposers: (() => void)[] = [];
|
||||
|
||||
@observable name = "";
|
||||
@observable description = "";
|
||||
@ -77,6 +78,14 @@ export class LensExtension implements ExtensionModel {
|
||||
// mock
|
||||
}
|
||||
|
||||
registerTo<T = any>(registry: BaseRegistry<T>, items: T[] = []) {
|
||||
const disposers = items.map(item => registry.add(item));
|
||||
this.disposers.push(...disposers);
|
||||
return () => {
|
||||
this.disposers = this.disposers.filter(disposer => !disposers.includes(disposer))
|
||||
};
|
||||
}
|
||||
|
||||
getMeta() {
|
||||
return toJS({
|
||||
id: this.id,
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
import type { MenuRegistration } from "./registries/menu-registry";
|
||||
import { observable } from "mobx";
|
||||
import { LensExtension } from "./lens-extension"
|
||||
import type { MenuRegistry } from "./registries/menu-registry";
|
||||
|
||||
export class LensMainExtension extends LensExtension {
|
||||
registerAppMenus(registry: MenuRegistry) {
|
||||
//
|
||||
}
|
||||
|
||||
registerPrometheusProviders(registry: any) {
|
||||
//
|
||||
}
|
||||
@observable.shallow appMenus: MenuRegistration[] = []
|
||||
}
|
||||
|
||||
@ -1,28 +1,12 @@
|
||||
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectMenuRegistration, PageRegistration, StatusBarRegistration } from "./registries"
|
||||
import { observable } from "mobx";
|
||||
import { LensExtension } from "./lens-extension"
|
||||
import type { GlobalPageRegistry, ClusterPageRegistry, AppPreferenceRegistry, StatusBarRegistry, KubeObjectMenuRegistry, ClusterFeatureRegistry } from "./registries"
|
||||
|
||||
export class LensRendererExtension extends LensExtension {
|
||||
registerGlobalPage(registry: GlobalPageRegistry) {
|
||||
return
|
||||
}
|
||||
|
||||
registerClusterPage(registry: ClusterPageRegistry) {
|
||||
return
|
||||
}
|
||||
|
||||
registerAppPreferences(registry: AppPreferenceRegistry) {
|
||||
return
|
||||
}
|
||||
|
||||
registerClusterFeatures(registry: ClusterFeatureRegistry) {
|
||||
return
|
||||
}
|
||||
|
||||
registerStatusBarItem(registry: StatusBarRegistry) {
|
||||
return
|
||||
}
|
||||
|
||||
registerKubeObjectMenus(registry: KubeObjectMenuRegistry) {
|
||||
return
|
||||
}
|
||||
@observable.shallow globalPages: PageRegistration[] = []
|
||||
@observable.shallow clusterPages: PageRegistration[] = []
|
||||
@observable.shallow appPreferences: AppPreferenceRegistration[] = []
|
||||
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = []
|
||||
@observable.shallow statusBarItems: StatusBarRegistration[] = []
|
||||
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = []
|
||||
}
|
||||
|
||||
@ -6,6 +6,9 @@
|
||||
"copyright": "© 2020, Mirantis, Inc.",
|
||||
"license": "MIT",
|
||||
"types": "api.d.ts",
|
||||
"files": [
|
||||
"api.d.ts"
|
||||
],
|
||||
"author": {
|
||||
"name": "Mirantis, Inc.",
|
||||
"email": "info@k8slens.dev"
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
// Extensions API -> Global menu customizations
|
||||
|
||||
import type { MenuTopId } from "../../main/menu";
|
||||
import type { MenuItemConstructorOptions } from "electron";
|
||||
import { BaseRegistry } from "./base-registry";
|
||||
|
||||
export interface MenuRegistration extends MenuItemConstructorOptions {
|
||||
parentId?: MenuTopId;
|
||||
parentId: string;
|
||||
}
|
||||
|
||||
export class MenuRegistry extends BaseRegistry<MenuRegistration> {
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { autoUpdater } from "electron-updater"
|
||||
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
|
||||
}
|
||||
|
||||
public start() {
|
||||
setInterval(() => {
|
||||
autoUpdater.checkForUpdatesAndNotify()
|
||||
}, this.updateInterval)
|
||||
|
||||
return autoUpdater.checkForUpdatesAndNotify()
|
||||
setInterval(AppUpdater.checkForUpdates, this.updateInterval)
|
||||
return AppUpdater.checkForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import path from "path"
|
||||
import { LensProxy } from "./lens-proxy"
|
||||
import { WindowManager } from "./window-manager";
|
||||
import { ClusterManager } from "./cluster-manager";
|
||||
import AppUpdater from "./app-updater"
|
||||
import { AppUpdater } from "./app-updater"
|
||||
import { shellSync } from "./shell-sync"
|
||||
import { getFreePort } from "./port"
|
||||
import { mangleProxyEnv } from "./proxy-env"
|
||||
@ -24,45 +24,46 @@ import { extensionLoader } from "../extensions/extension-loader";
|
||||
import logger from "./logger"
|
||||
|
||||
const workingDir = path.join(app.getPath("appData"), appName);
|
||||
let proxyPort: number;
|
||||
let proxyServer: LensProxy;
|
||||
let clusterManager: ClusterManager;
|
||||
let windowManager: WindowManager;
|
||||
|
||||
app.setName(appName);
|
||||
if (!process.env.CICD) {
|
||||
app.setPath("userData", workingDir);
|
||||
}
|
||||
|
||||
let clusterManager: ClusterManager;
|
||||
let proxyServer: LensProxy;
|
||||
|
||||
mangleProxyEnv()
|
||||
if (app.commandLine.getSwitchValue("proxy-server") !== "") {
|
||||
process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server")
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await shellSync();
|
||||
app.on("ready", async () => {
|
||||
logger.info(`🚀 Starting Lens from "${workingDir}"`)
|
||||
await shellSync();
|
||||
|
||||
const updater = new AppUpdater()
|
||||
updater.start();
|
||||
|
||||
registerFileProtocol("static", __static);
|
||||
|
||||
// find free port
|
||||
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
|
||||
// preload isomorphic stores
|
||||
await Promise.all([
|
||||
userStore.load(),
|
||||
clusterStore.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
|
||||
clusterManager = new ClusterManager(proxyPort);
|
||||
|
||||
@ -72,28 +73,34 @@ async function main() {
|
||||
} catch (error) {
|
||||
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"}`)
|
||||
app.quit();
|
||||
app.exit();
|
||||
}
|
||||
|
||||
// create window manager and open app
|
||||
LensExtensionsApi.windowManager = new WindowManager(proxyPort);
|
||||
windowManager = new WindowManager(proxyPort);
|
||||
|
||||
LensExtensionsApi.windowManager = windowManager; // expose to extensions
|
||||
extensionLoader.loadOnMain()
|
||||
extensionLoader.extensions.replace(await extensionManager.load())
|
||||
extensionLoader.broadcastExtensions()
|
||||
|
||||
setTimeout(() => {
|
||||
appEventBus.emit({name: "app", action: "start"})
|
||||
appEventBus.emit({ name: "app", action: "start" })
|
||||
}, 1000)
|
||||
}
|
||||
});
|
||||
|
||||
app.on("ready", main);
|
||||
app.on("activate", (event, hasVisibleWindows) => {
|
||||
logger.info('APP:ACTIVATE', { hasVisibleWindows })
|
||||
if (!hasVisibleWindows) {
|
||||
windowManager.initMainWindow();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("will-quit", async (event) => {
|
||||
event.preventDefault(); // To allow mixpanel sending to be executed
|
||||
if (proxyServer) proxyServer.close()
|
||||
if (clusterManager) clusterManager.stop()
|
||||
app.exit();
|
||||
// Quit app on Cmd+Q (MacOS)
|
||||
app.on("will-quit", (event) => {
|
||||
logger.info('APP:QUIT');
|
||||
event.preventDefault(); // prevent app's default shutdown (e.g. required for telemetry, etc.)
|
||||
clusterManager?.stop(); // close cluster connections
|
||||
return; // skip exit to make tray work, to quit go to app's global menu or tray's menu
|
||||
})
|
||||
|
||||
// Extensions-api runtime exports
|
||||
|
||||
@ -12,11 +12,27 @@ import logger from "./logger";
|
||||
export type MenuTopId = "mac" | "file" | "edit" | "view" | "help"
|
||||
|
||||
export function initMenu(windowManager: WindowManager) {
|
||||
autorun(() => buildMenu(windowManager), {
|
||||
return autorun(() => buildMenu(windowManager), {
|
||||
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) {
|
||||
function ignoreOnMac(menuItems: MenuItemConstructorOptions[]) {
|
||||
if (isMac) return [];
|
||||
@ -32,28 +48,9 @@ export function buildMenu(windowManager: WindowManager) {
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
function navigate(url: string) {
|
||||
async function navigate(url: string) {
|
||||
logger.info(`[MENU]: navigating to ${url}`);
|
||||
windowManager.navigate({
|
||||
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")
|
||||
})
|
||||
await windowManager.navigate(url);
|
||||
}
|
||||
|
||||
const macAppMenu: MenuItemConstructorOptions = {
|
||||
@ -80,7 +77,13 @@ export function buildMenu(windowManager: WindowManager) {
|
||||
{ role: 'hideOthers' },
|
||||
{ role: 'unhide' },
|
||||
{ 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' },
|
||||
{ role: 'quit' }
|
||||
])
|
||||
]),
|
||||
{ type: 'separator' },
|
||||
{ role: 'close' } // close current window
|
||||
]
|
||||
};
|
||||
|
||||
@ -158,7 +163,7 @@ export function buildMenu(windowManager: WindowManager) {
|
||||
label: 'Reload',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
click() {
|
||||
windowManager.reload({ channel: "menu:reload" });
|
||||
windowManager.reload();
|
||||
}
|
||||
},
|
||||
{ role: 'toggleDevTools' },
|
||||
@ -209,7 +214,7 @@ export function buildMenu(windowManager: WindowManager) {
|
||||
// Modify menu from extensions-api
|
||||
menuRegistry.getItems().forEach(({ parentId, ...menuItem }) => {
|
||||
try {
|
||||
const topMenu = appMenu[parentId].submenu as MenuItemConstructorOptions[];
|
||||
const topMenu = appMenu[parentId as MenuTopId].submenu as MenuItemConstructorOptions[];
|
||||
topMenu.push(menuItem);
|
||||
} catch (err) {
|
||||
logger.error(`[MENU]: can't register menu item, parentId=${parentId}`, { menuItem })
|
||||
|
||||
126
src/main/tray.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
@ -1,95 +1,140 @@
|
||||
import type { ClusterId } 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 { 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 { appEventBus } from "../common/event-bus"
|
||||
import { initMenu } from "./menu";
|
||||
import { initTray } from "./tray";
|
||||
|
||||
export class WindowManager {
|
||||
protected mainView: BrowserWindow;
|
||||
protected mainWindow: BrowserWindow;
|
||||
protected splashWindow: BrowserWindow;
|
||||
protected windowState: windowStateKeeper.State;
|
||||
protected disposers: Record<string, Function> = {};
|
||||
|
||||
@observable activeClusterId: ClusterId;
|
||||
|
||||
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
|
||||
this.windowState = windowStateKeeper({
|
||||
defaultHeight: 900,
|
||||
defaultWidth: 1440,
|
||||
});
|
||||
if (!this.windowState) {
|
||||
this.windowState = windowStateKeeper({
|
||||
defaultHeight: 900,
|
||||
defaultWidth: 1440,
|
||||
});
|
||||
}
|
||||
if (!this.mainWindow) {
|
||||
// show icon in dock (mac-os only)
|
||||
app.dock?.show();
|
||||
|
||||
const { width, height, x, y } = this.windowState;
|
||||
this.mainView = new BrowserWindow({
|
||||
x, y, width, height,
|
||||
show: false,
|
||||
minWidth: 700, // accommodate 800 x 600 display minimum
|
||||
minHeight: 500, // accommodate 800 x 600 display minimum
|
||||
titleBarStyle: "hidden",
|
||||
backgroundColor: "#1e2124",
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInSubFrames: true,
|
||||
enableRemoteModule: true,
|
||||
},
|
||||
});
|
||||
this.windowState.manage(this.mainView);
|
||||
const { width, height, x, y } = this.windowState;
|
||||
this.mainWindow = new BrowserWindow({
|
||||
x, y, width, height,
|
||||
show: false,
|
||||
minWidth: 700, // accommodate 800 x 600 display minimum
|
||||
minHeight: 500, // accommodate 800 x 600 display minimum
|
||||
titleBarStyle: "hidden",
|
||||
backgroundColor: "#1e2124",
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInSubFrames: true,
|
||||
enableRemoteModule: true,
|
||||
},
|
||||
});
|
||||
this.windowState.manage(this.mainWindow);
|
||||
|
||||
// open external links in default browser (target=_blank, window.open)
|
||||
this.mainView.webContents.on("new-window", (event, url) => {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
});
|
||||
this.mainView.webContents.on("dom-ready", () => {
|
||||
extensionLoader.broadcastExtensions()
|
||||
})
|
||||
this.mainView.on("focus", () => {
|
||||
appEventBus.emit({name: "app", action: "focus"})
|
||||
})
|
||||
this.mainView.on("blur", () => {
|
||||
appEventBus.emit({name: "app", action: "blur"})
|
||||
})
|
||||
// open external links in default browser (target=_blank, window.open)
|
||||
this.mainWindow.webContents.on("new-window", (event, url) => {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
});
|
||||
this.mainWindow.webContents.on("dom-ready", () => {
|
||||
extensionLoader.broadcastExtensions()
|
||||
})
|
||||
this.mainWindow.on("focus", () => {
|
||||
appEventBus.emit({name: "app", action: "focus"})
|
||||
})
|
||||
this.mainWindow.on("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
|
||||
ipcMain.on("cluster-view:current-id", (event, clusterId: 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) {
|
||||
this.mainView.webContents.sendToFrame(frameId, channel, url);
|
||||
this.mainWindow.webContents.sendToFrame(frameId, channel, ...data);
|
||||
} 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;
|
||||
if (frameId) {
|
||||
this.mainView.webContents.sendToFrame(frameId, channel);
|
||||
this.sendToView({ channel: "menu:reload", frameId });
|
||||
} else {
|
||||
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() {
|
||||
if (!this.splashWindow) {
|
||||
this.splashWindow = new BrowserWindow({
|
||||
@ -110,8 +155,13 @@ export class WindowManager {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.windowState.unmanage();
|
||||
this.mainWindow.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]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const addClusterRoute: RouteProps = {
|
||||
path: "/add-cluster"
|
||||
|
||||
@ -46,6 +46,7 @@ export class AddCluster extends React.Component {
|
||||
@observable dropAreaActive = false;
|
||||
|
||||
componentDidMount() {
|
||||
clusterStore.setActive(null);
|
||||
this.setKubeConfig(userStore.kubeConfigPath);
|
||||
}
|
||||
|
||||
@ -118,17 +119,16 @@ export class AddCluster extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
addClusters = () => {
|
||||
const configValidationErrors:string[] = [];
|
||||
let newClusters: ClusterModel[] = [];
|
||||
|
||||
try {
|
||||
if (!this.selectedContexts.length) {
|
||||
this.error = <Trans>Please select at least one cluster context</Trans>
|
||||
return;
|
||||
}
|
||||
this.error = ""
|
||||
this.isWaiting = true
|
||||
this.isWaiting = true
|
||||
|
||||
newClusters = this.selectedContexts.filter(context => {
|
||||
try {
|
||||
@ -138,8 +138,8 @@ export class AddCluster extends React.Component {
|
||||
} catch (err) {
|
||||
this.error = String(err.message)
|
||||
if (err instanceof ExecValidationNotFoundError ) {
|
||||
Notifications.error(<Trans>Error while adding cluster(s): {this.error}</Trans>);
|
||||
return false;
|
||||
Notifications.error(<Trans>Error while adding cluster(s): {this.error}</Trans>);
|
||||
return false;
|
||||
} else {
|
||||
throw new Error(err);
|
||||
}
|
||||
@ -169,7 +169,7 @@ export class AddCluster extends React.Component {
|
||||
clusterStore.setActive(clusterId);
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
} else {
|
||||
if (newClusters.length > 1) {
|
||||
if (newClusters.length > 1) {
|
||||
Notifications.ok(
|
||||
<Trans>Successfully imported <b>{newClusters.length}</b> cluster(s)</Trans>
|
||||
);
|
||||
|
||||
@ -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 { buildURL } from "../../navigation";
|
||||
|
||||
export const helmChartsRoute: RouteProps = {
|
||||
path: appsRoute.path + "/charts/:repo?/:chartName?"
|
||||
|
||||
@ -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 { buildURL } from "../../navigation";
|
||||
|
||||
export const releaseRoute: RouteProps = {
|
||||
path: appsRoute.path + "/releases/:namespace?/:name?"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const appsRoute: RouteProps = {
|
||||
path: "/apps",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { IClusterViewRouteParams } from "../cluster-manager/cluster-view.route";
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export interface IClusterSettingsRouteParams extends IClusterViewRouteParams {
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import "./cluster-settings.scss";
|
||||
|
||||
import React from "react";
|
||||
import { autorun } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { reaction } from "mobx";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { Features } from "./features";
|
||||
import { Removal } from "./removal";
|
||||
import { Status } from "./status";
|
||||
@ -11,7 +12,6 @@ import { Cluster } from "../../../main/cluster";
|
||||
import { ClusterIcon } from "../cluster-icon";
|
||||
import { IClusterSettingsRouteParams } from "./cluster-settings.route";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||
import { PageLayout } from "../layout/page-layout";
|
||||
|
||||
@ -20,16 +20,23 @@ interface Props extends RouteComponentProps<IClusterSettingsRouteParams> {
|
||||
|
||||
@observer
|
||||
export class ClusterSettings extends React.Component<Props> {
|
||||
get cluster(): Cluster {
|
||||
return clusterStore.getById(this.props.match.params.clusterId);
|
||||
get clusterId() {
|
||||
return this.props.match.params.clusterId
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
this.refreshCluster();
|
||||
get cluster(): Cluster {
|
||||
return clusterStore.getById(this.clusterId);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this, [
|
||||
reaction(() => this.cluster, this.refreshCluster, {
|
||||
fireImmediately: true,
|
||||
}),
|
||||
reaction(() => this.clusterId, clusterId => clusterStore.setActive(clusterId), {
|
||||
fireImmediately: true,
|
||||
})
|
||||
)
|
||||
])
|
||||
}
|
||||
|
||||
refreshCluster = async () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const clusterRoute: RouteProps = {
|
||||
path: "/cluster"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const hpaRoute: RouteProps = {
|
||||
path: "/hpa"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const configMapsRoute: RouteProps = {
|
||||
path: "/configmaps"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const pdbRoute: RouteProps = {
|
||||
path: "/poddisruptionbudgets"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const resourceQuotaRoute: RouteProps = {
|
||||
path: "/resourcequotas"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const secretsRoute: RouteProps = {
|
||||
path: "/secrets"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { configMapsURL } from "../+config-maps";
|
||||
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 = {
|
||||
get path() {
|
||||
|
||||
@ -10,8 +10,8 @@ import { resourceQuotaRoute, ResourceQuotas, resourceQuotaURL } from "../+config
|
||||
import { PodDisruptionBudgets, pdbRoute, pdbURL } from "../+config-pod-disruption-budgets";
|
||||
import { configURL } from "./config.route";
|
||||
import { HorizontalPodAutoscalers, hpaRoute, hpaURL } from "../+config-autoscalers";
|
||||
import { buildURL } from "../../navigation";
|
||||
import { isAllowedResource } from "../../../common/rbac"
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const certificatesURL = buildURL("/certificates");
|
||||
export const issuersURL = buildURL("/issuers");
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const crdRoute: RouteProps = {
|
||||
path: "/crd"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const eventRoute: RouteProps = {
|
||||
path: "/events"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const landingRoute: RouteProps = {
|
||||
path: "/landing"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const namespacesRoute: RouteProps = {
|
||||
path: "/namespaces"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const endpointRoute: RouteProps = {
|
||||
path: "/endpoints"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const ingressRoute: RouteProps = {
|
||||
path: "/ingresses"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const networkPoliciesRoute: RouteProps = {
|
||||
path: "/network-policies"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const servicesRoute: RouteProps = {
|
||||
path: "/services"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { Network } from "./network";
|
||||
import { servicesURL } from "../+network-services";
|
||||
import { IURLParams } from "../../navigation";
|
||||
import { IURLParams } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const networkRoute: RouteProps = {
|
||||
get path() {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const nodesRoute: RouteProps = {
|
||||
path: "/nodes"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const podSecurityPoliciesRoute: RouteProps = {
|
||||
path: "/pod-security-policies"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const preferencesRoute: RouteProps = {
|
||||
path: "/preferences"
|
||||
|
||||
@ -21,4 +21,8 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.Checkbox {
|
||||
align-self: start; // limit clickable area to checkbox + text
|
||||
}
|
||||
}
|
||||
@ -156,6 +156,13 @@ export class Preferences extends React.Component {
|
||||
})}
|
||||
</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>
|
||||
<Checkbox
|
||||
label={<Trans>Allow untrusted Certificate Authorities</Trans>}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const storageClassesRoute: RouteProps = {
|
||||
path: "/storage-classes"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const volumeClaimsRoute: RouteProps = {
|
||||
path: "/persistent-volume-claims"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const volumesRoute: RouteProps = {
|
||||
path: "/persistent-volumes"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { volumeClaimsURL } from "../+storage-volume-claims";
|
||||
import { Storage } from "./storage";
|
||||
import { IURLParams } from "../../navigation";
|
||||
import { IURLParams } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const storageRoute: RouteProps = {
|
||||
get path() {
|
||||
|
||||
@ -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 { buildURL, IURLParams } from "../../navigation";
|
||||
|
||||
export const usersManagementRoute: RouteProps = {
|
||||
get path() {
|
||||
@ -30,9 +30,7 @@ export interface IRolesRouteParams {
|
||||
}
|
||||
|
||||
// URL-builders
|
||||
export const usersManagementURL = (params?: IURLParams) => serviceAccountsURL(params);
|
||||
export const serviceAccountsURL = buildURL<IServiceAccountsRouteParams>(serviceAccountsRoute.path)
|
||||
export const roleBindingsURL = buildURL<IRoleBindingsRouteParams>(roleBindingsRoute.path)
|
||||
export const rolesURL = buildURL<IRoleBindingsRouteParams>(rolesRoute.path)
|
||||
export const usersManagementURL = (params?: IURLParams) => {
|
||||
return serviceAccountsURL(params);
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const whatsNewRoute: RouteProps = {
|
||||
path: "/what-s-new"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { RouteProps } from "react-router"
|
||||
import { Workloads } from "./workloads";
|
||||
import { buildURL, IURLParams } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL, IURLParams } from "../../../common/utils/buildUrl";
|
||||
import { KubeResource } from "../../../common/rbac";
|
||||
import { Workloads } from "./workloads";
|
||||
|
||||
export const workloadsRoute: RouteProps = {
|
||||
get path() {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../navigation";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export const workspacesRoute: RouteProps = {
|
||||
path: "/workspaces"
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import "./cluster-manager.scss"
|
||||
|
||||
import React from "react";
|
||||
import { Redirect, Route, Switch } from "react-router";
|
||||
import { comparer, reaction } from "mobx";
|
||||
@ -11,14 +12,17 @@ import { Workspaces, workspacesRoute } from "../+workspaces";
|
||||
import { AddCluster, addClusterRoute } from "../+add-cluster";
|
||||
import { ClusterView } from "./cluster-view";
|
||||
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 { hasLoadedView, initView, lensViews, refreshViews } from "./lens-views";
|
||||
import { globalPageRegistry } from "../../../extensions/registries/page-registry";
|
||||
import { getMatchedClusterId } from "../../navigation";
|
||||
|
||||
@observer
|
||||
export class ClusterManager extends React.Component {
|
||||
componentDidMount() {
|
||||
const getMatchedCluster = () => clusterStore.getById(getMatchedClusterId());
|
||||
|
||||
disposeOnUnmount(this, [
|
||||
reaction(getMatchedClusterId, initView, {
|
||||
fireImmediately: true
|
||||
@ -55,7 +59,7 @@ export class ClusterManager extends React.Component {
|
||||
return (
|
||||
<div className="ClusterManager">
|
||||
<main>
|
||||
<div id="lens-views" />
|
||||
<div id="lens-views"/>
|
||||
<Switch>
|
||||
<Route component={LandingPage} {...landingRoute} />
|
||||
<Route component={Preferences} {...preferencesRoute} />
|
||||
@ -66,11 +70,11 @@ export class ClusterManager extends React.Component {
|
||||
{globalPageRegistry.getItems().map(({ path, url = String(path), components: { Page } }) => {
|
||||
return <Route key={url} path={path} component={Page}/>
|
||||
})}
|
||||
<Redirect exact to={this.startUrl} />
|
||||
<Redirect exact to={this.startUrl}/>
|
||||
</Switch>
|
||||
</main>
|
||||
<ClustersMenu />
|
||||
<BottomBar />
|
||||
<ClustersMenu/>
|
||||
<BottomBar/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import { reaction } from "mobx";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { matchPath, RouteProps } from "react-router";
|
||||
import { buildURL, navigation } from "../../navigation";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import type { RouteProps } from "react-router";
|
||||
import { buildURL } from "../../../common/utils/buildUrl";
|
||||
|
||||
export interface IClusterViewRouteParams {
|
||||
clusterId: string;
|
||||
@ -14,33 +11,3 @@ export const clusterViewRoute: RouteProps = {
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,14 +1,37 @@
|
||||
import "./cluster-view.scss"
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { getMatchedCluster } from "./cluster-view.route";
|
||||
import { reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { RouteComponentProps } from "react-router";
|
||||
import { IClusterViewRouteParams } from "./cluster-view.route";
|
||||
import { ClusterStatus } from "./cluster-status";
|
||||
import { hasLoadedView } from "./lens-views";
|
||||
import { Cluster } from "../../../main/cluster";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
|
||||
interface Props extends RouteComponentProps<IClusterViewRouteParams> {
|
||||
}
|
||||
|
||||
@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() {
|
||||
const cluster = getMatchedCluster();
|
||||
const { cluster } = this;
|
||||
const showStatus = cluster && (!cluster.available || !hasLoadedView(cluster.id) || !cluster.ready)
|
||||
return (
|
||||
<div className="ClusterView">
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import type { Cluster } from "../../../main/cluster";
|
||||
import "./clusters-menu.scss"
|
||||
|
||||
import { remote } from "electron"
|
||||
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 { _i18n } from "../../i18n";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
@ -21,7 +22,6 @@ import { Tooltip } from "../tooltip";
|
||||
import { ConfirmDialog } from "../confirm-dialog";
|
||||
import { clusterIpc } from "../../../common/cluster-ipc";
|
||||
import { clusterViewURL } from "./cluster-view.route";
|
||||
import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd";
|
||||
import { globalPageRegistry } from "../../../extensions/registries/page-registry";
|
||||
|
||||
interface Props {
|
||||
@ -31,13 +31,11 @@ interface Props {
|
||||
@observer
|
||||
export class ClustersMenu extends React.Component<Props> {
|
||||
showCluster = (clusterId: ClusterId) => {
|
||||
clusterStore.setActive(clusterId);
|
||||
navigate(clusterViewURL({ params: { clusterId } }));
|
||||
}
|
||||
|
||||
addCluster = () => {
|
||||
navigate(addClusterURL());
|
||||
clusterStore.setActive(null);
|
||||
}
|
||||
|
||||
showContextMenu = (cluster: Cluster) => {
|
||||
@ -47,7 +45,6 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
menu.append(new MenuItem({
|
||||
label: _i18n._(t`Settings`),
|
||||
click: () => {
|
||||
clusterStore.setActive(cluster.id);
|
||||
navigate(clusterSettingsURL({
|
||||
params: {
|
||||
clusterId: cluster.id
|
||||
@ -112,21 +109,14 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
<div className="clusters flex column gaps">
|
||||
<DragDropContext onDragEnd={this.swapClusterIconOrder}>
|
||||
<Droppable droppableId="cluster-menu" type="CLUSTER">
|
||||
{(provided: DroppableProvided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{({ innerRef, droppableProps, placeholder }: DroppableProvided) => (
|
||||
<div ref={innerRef} {...droppableProps}>
|
||||
{clusters.map((cluster, index) => {
|
||||
const isActive = cluster.id === clusterStore.activeClusterId;
|
||||
return (
|
||||
<Draggable draggableId={cluster.id} index={index} key={cluster.id}>
|
||||
{(provided: DraggableProvided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
>
|
||||
{({ draggableProps, dragHandleProps, innerRef }: DraggableProvided) => (
|
||||
<div ref={innerRef} {...draggableProps} {...dragHandleProps}>
|
||||
<ClusterIcon
|
||||
key={cluster.id}
|
||||
showErrors={true}
|
||||
@ -138,9 +128,9 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
)}
|
||||
)}
|
||||
{provided.placeholder}
|
||||
)
|
||||
})}
|
||||
{placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
@ -150,16 +140,14 @@ export class ClustersMenu extends React.Component<Props> {
|
||||
<Tooltip targetId="add-cluster-icon">
|
||||
<Trans>Add Cluster</Trans>
|
||||
</Tooltip>
|
||||
<Icon big material="add" id="add-cluster-icon" />
|
||||
<Icon big material="add" id="add-cluster-icon"/>
|
||||
{newContexts.size > 0 && (
|
||||
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>} />
|
||||
<Badge className="counter" label={newContexts.size} tooltip={<Trans>new</Trans>}/>
|
||||
)}
|
||||
</div>
|
||||
<div className="extensions">
|
||||
{globalPageRegistry.getItems().map(({ path, url = String(path), hideInMenu, components: { MenuIcon } }) => {
|
||||
if (!MenuIcon || hideInMenu) {
|
||||
return;
|
||||
}
|
||||
if (!MenuIcon || hideInMenu) return;
|
||||
return <MenuIcon key={url} onClick={() => navigate(url)}/>
|
||||
})}
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { observable, when } from "mobx";
|
||||
import { ClusterId, clusterStore, getClusterFrameUrl } from "../../../common/cluster-store";
|
||||
import { getMatchedCluster } from "./cluster-view.route"
|
||||
import { getMatchedClusterId } from "../../navigation";
|
||||
import logger from "../../../main/logger";
|
||||
|
||||
export interface LensView {
|
||||
@ -51,7 +51,7 @@ export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrame
|
||||
}
|
||||
|
||||
export function refreshViews() {
|
||||
const cluster = getMatchedCluster();
|
||||
const cluster = clusterStore.getById(getMatchedClusterId());
|
||||
lensViews.forEach(({ clusterId, view, isLoaded }) => {
|
||||
const isCurrent = clusterId === cluster?.id;
|
||||
const isReady = cluster?.available && cluster?.ready;
|
||||
|
||||
|
Before Width: | Height: | Size: 730 B After Width: | Height: | Size: 730 B |
@ -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 |
@ -80,7 +80,7 @@ export class Sidebar extends React.Component<Props> {
|
||||
<div className={cssNames("Sidebar flex column", className, { pinned: isPinned })}>
|
||||
<div className="header flex align-center">
|
||||
<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>
|
||||
</NavLink>
|
||||
<Icon
|
||||
|
||||
@ -1,22 +1,16 @@
|
||||
// Navigation helpers
|
||||
|
||||
import { ipcRenderer } from "electron";
|
||||
import { compile } from "path-to-regexp"
|
||||
import { createBrowserHistory, createMemoryHistory, LocationDescriptor } from "history";
|
||||
import { matchPath } from "react-router";
|
||||
import { reaction } from "mobx";
|
||||
import { createObservableHistory } from "mobx-observable-history";
|
||||
import { createBrowserHistory, createMemoryHistory, LocationDescriptor } from "history";
|
||||
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 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) {
|
||||
const currentLocation = navigation.getPath();
|
||||
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
|
||||
export interface IQueryParams {
|
||||
namespaces?: string[]; // selected context namespaces
|
||||
@ -100,3 +80,33 @@ export function setSearch(text: string) {
|
||||
export function getSearch() {
|
||||
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();
|
||||
});
|
||||
|
||||
@ -2,7 +2,17 @@
|
||||
|
||||
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
||||
|
||||
## 3.6.6 (current version)
|
||||
## 4.0.0-alpha.2 (current version)
|
||||
|
||||
- Extension API
|
||||
- Improved pod logs
|
||||
- Tray icon
|
||||
- Add last-status information for container
|
||||
- Add LoadBalancer information to Ingress view
|
||||
- Move tracker to an extension
|
||||
- Add support page (as an extension)
|
||||
|
||||
## 3.6.6
|
||||
- Fix labels' word boundary to cover only drawer badges
|
||||
- Fix cluster dashboard opening not to start authentication proxy twice
|
||||
- Fix: Refresh cluster connection status also when connection is disconnected
|
||||
|
||||
241
yarn.lock
@ -1941,6 +1941,15 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.4.tgz#7d3b534ec35a0585128e2d332db1403ebe057e25"
|
||||
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":
|
||||
version "7.0.5"
|
||||
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"
|
||||
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@*":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.0.tgz#bfaa2151be2b1d6109cc69f7faa9dac2cba3bb20"
|
||||
@ -2199,6 +2213,13 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
version "0.8.8"
|
||||
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"
|
||||
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@*:
|
||||
version "0.0.9"
|
||||
resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
|
||||
@ -4647,6 +4677,20 @@ decompress-response@^3.3.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "0.6.0"
|
||||
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"
|
||||
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:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
|
||||
@ -5124,7 +5173,7 @@ encoding@^0.1.11:
|
||||
dependencies:
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||
@ -5485,6 +5534,11 @@ expand-brackets@^2.1.4:
|
||||
snapdragon "^0.8.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:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
|
||||
@ -6131,6 +6185,11 @@ getpass@^0.1.1:
|
||||
dependencies:
|
||||
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:
|
||||
version "3.1.0"
|
||||
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"
|
||||
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:
|
||||
version "3.2.1"
|
||||
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"
|
||||
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:
|
||||
version "2.5.2"
|
||||
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"
|
||||
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:
|
||||
version "0.4.0"
|
||||
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:
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
@ -8930,6 +9026,11 @@ mixin-deep@^1.2.0:
|
||||
for-in "^1.0.2"
|
||||
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:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
@ -9037,6 +9138,11 @@ nanomatch@^1.2.9:
|
||||
snapdragon "^0.8.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:
|
||||
version "1.4.0"
|
||||
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"
|
||||
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:
|
||||
version "2.0.4"
|
||||
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"
|
||||
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":
|
||||
version "3.0.6"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
|
||||
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"
|
||||
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:
|
||||
version "1.2.1"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
||||
integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
|
||||
@ -10944,7 +11097,7 @@ read@1, read@~1.0.1, read@~1.0.7:
|
||||
string_decoder "~1.1.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"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||
@ -11649,6 +11802,21 @@ shallow-clone@^3.0.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "1.2.0"
|
||||
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"
|
||||
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:
|
||||
version "0.2.2"
|
||||
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"
|
||||
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:
|
||||
version "1.6.2"
|
||||
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"
|
||||
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:
|
||||
version "2.2.2"
|
||||
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"
|
||||
integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
|
||||
|
||||
webidl-conversions@^6.0.0:
|
||||
webidl-conversions@^6.0.0, webidl-conversions@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
|
||||
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"
|
||||
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:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
|
||||