mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into extensions_register_feature_clean_up
# Conflicts: # extensions/support-page/main.ts
This commit is contained in:
commit
e2aaaa1e26
8
Makefile
8
Makefile
@ -56,7 +56,13 @@ else
|
||||
endif
|
||||
|
||||
build-extensions:
|
||||
$(foreach file, $(wildcard $(EXTENSIONS_DIR)/*), $(MAKE) -C $(file) build;)
|
||||
$(foreach dir, $(wildcard $(EXTENSIONS_DIR)/*), $(MAKE) -C $(dir) build;)
|
||||
|
||||
build-npm:
|
||||
yarn npm:fix-package-version
|
||||
|
||||
publish-npm: build-npm
|
||||
cd src/extensions/npm/extensions && npm publish
|
||||
|
||||
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
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 });
|
||||
});
|
||||
9
build/set_npm_version.ts
Normal file
9
build/set_npm_version.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import packageInfo from "../src/extensions/npm/extensions/package.json"
|
||||
import appInfo from "../package.json"
|
||||
|
||||
const packagePath = path.join(__dirname, "../src/extensions/npm/extensions/package.json")
|
||||
|
||||
packageInfo.version = appInfo.version
|
||||
fs.writeFileSync(packagePath, JSON.stringify(packageInfo, null, 2))
|
||||
BIN
build/tray/tray_icon.png
Normal file
BIN
build/tray/tray_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 448 B |
BIN
build/tray/tray_icon@2x.png
Normal file
BIN
build/tray/tray_icon@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 973 B |
BIN
build/tray/tray_icon@3x.png
Normal file
BIN
build/tray/tray_icon@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
build/tray/tray_icon_dark.png
Normal file
BIN
build/tray/tray_icon_dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 478 B |
BIN
build/tray/tray_icon_dark@2x.png
Normal file
BIN
build/tray/tray_icon_dark@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
build/tray/tray_icon_dark@3x.png
Normal file
BIN
build/tray/tray_icon_dark@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
4
extensions/example-extension/package-lock.json
generated
4
extensions/example-extension/package-lock.json
generated
@ -4,6 +4,10 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@k8slens/extensions": {
|
||||
"version": "file:../../src/extensions/npm/extensions",
|
||||
"dev": true
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
"react-open-doodles": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"ts-loader": "^8.0.4",
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^4.44.2"
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
"../../src/extensions/npm/**/*.d.ts",
|
||||
"./*.ts",
|
||||
"./*.tsx"
|
||||
],
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@k8slens/extensions": {
|
||||
"version": "file:../../src/extensions/npm/extensions",
|
||||
"dev": true
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"ts-loader": "^8.0.4",
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^4.44.2",
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
"../../src/extensions/npm/**/*.d.ts",
|
||||
"./*.ts",
|
||||
"./*.tsx"
|
||||
],
|
||||
|
||||
4
extensions/node-menu/package-lock.json
generated
4
extensions/node-menu/package-lock.json
generated
@ -4,6 +4,10 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@k8slens/extensions": {
|
||||
"version": "file:../../src/extensions/npm/extensions",
|
||||
"dev": true
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"ts-loader": "^8.0.4",
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^4.44.2",
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
"../../src/extensions/npm/**/*.d.ts",
|
||||
"./*.ts",
|
||||
"./*.tsx"
|
||||
],
|
||||
|
||||
4
extensions/pod-menu/package-lock.json
generated
4
extensions/pod-menu/package-lock.json
generated
@ -4,6 +4,10 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@k8slens/extensions": {
|
||||
"version": "file:../../src/extensions/npm/extensions",
|
||||
"dev": true
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^4.44.2",
|
||||
"mobx": "^5.15.5",
|
||||
"react": "^16.13.1"
|
||||
"react": "^16.13.1",
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions"
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
|
||||
selectedContainer: container,
|
||||
showTimestamps: false,
|
||||
previous: false,
|
||||
tailLines: 1000
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,6 @@ export class PodShellMenu extends React.Component<PodShellMenuProps> {
|
||||
|
||||
render() {
|
||||
const { object, toolbar } = this.props
|
||||
console.log(Object.keys(object))
|
||||
const containers = object.getRunningContainers();
|
||||
if (!containers.length) return;
|
||||
return (
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
"../../src/extensions/npm/**/*.d.ts",
|
||||
"./*.ts",
|
||||
"./*.tsx"
|
||||
],
|
||||
|
||||
@ -7,10 +7,7 @@ export default class SupportPageMainExtension extends LensMainExtension {
|
||||
parentId: "help",
|
||||
label: "Support",
|
||||
click() {
|
||||
windowManager.navigate({
|
||||
channel: "menu:navigate", // fixme: use windowManager.ensureMainWindow from Tray's PR
|
||||
url: supportPageURL(),
|
||||
});
|
||||
windowManager.navigate(supportPageURL());
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
4
extensions/support-page/package-lock.json
generated
4
extensions/support-page/package-lock.json
generated
@ -4,6 +4,10 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@k8slens/extensions": {
|
||||
"version": "file:../../src/extensions/npm/extensions",
|
||||
"dev": true
|
||||
},
|
||||
"@types/anymatch": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-router": "^5.1.8",
|
||||
"@types/webpack": "^4.41.17",
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"mobx": "^5.15.5",
|
||||
"react": "^16.13.1",
|
||||
"ts-loader": "^8.0.4",
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -24,7 +24,6 @@
|
||||
},
|
||||
"include": [
|
||||
"renderer.tsx",
|
||||
"../../src/extensions/npm/**/*.d.ts",
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
4
extensions/telemetry/package-lock.json
generated
4
extensions/telemetry/package-lock.json
generated
@ -4,6 +4,10 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@k8slens/extensions": {
|
||||
"version": "file:../../src/extensions/npm/extensions",
|
||||
"dev": true
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"ts-loader": "^8.0.4",
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^4.44.2",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { EventBus, Util } from "@k8slens/extensions"
|
||||
import { EventBus, Util, Store, App } from "@k8slens/extensions"
|
||||
import ua from "universal-analytics"
|
||||
import { machineIdSync } from "node-machine-id"
|
||||
import { telemetryPreferencesStore } from "./telemetry-preferences-store"
|
||||
@ -15,6 +15,8 @@ export class Tracker extends Util.Singleton {
|
||||
protected locale: string;
|
||||
protected electronUA: string;
|
||||
|
||||
protected reportInterval: NodeJS.Timeout
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
try {
|
||||
@ -23,7 +25,7 @@ export class Tracker extends Util.Singleton {
|
||||
this.visitor = ua(Tracker.GA_ID)
|
||||
}
|
||||
this.visitor.set("dl", "https://telemetry.k8slens.dev")
|
||||
|
||||
this.visitor.set("ua", `Lens ${App.version} (${this.getOS()})`)
|
||||
}
|
||||
|
||||
start() {
|
||||
@ -36,6 +38,8 @@ 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
|
||||
}
|
||||
|
||||
stop() {
|
||||
@ -46,12 +50,59 @@ export class Tracker extends Util.Singleton {
|
||||
for (const handler of this.eventHandlers) {
|
||||
EventBus.appEventBus.removeListener(handler)
|
||||
}
|
||||
if (this.reportInterval) {
|
||||
clearInterval(this.reportInterval)
|
||||
}
|
||||
}
|
||||
|
||||
protected async isTelemetryAllowed(): Promise<boolean> {
|
||||
return telemetryPreferencesStore.enabled
|
||||
}
|
||||
|
||||
protected reportData() {
|
||||
const clustersList = Store.clusterStore.clustersList
|
||||
|
||||
this.event("generic-data", "report", {
|
||||
appVersion: App.version,
|
||||
clustersCount: clustersList.length,
|
||||
workspacesCount: Store.workspaceStore.workspacesList.length
|
||||
})
|
||||
|
||||
clustersList.forEach((cluster) => {
|
||||
if (!cluster?.metadata.lastSeen) { return }
|
||||
this.reportClusterData(cluster)
|
||||
})
|
||||
}
|
||||
|
||||
protected reportClusterData(cluster: Store.ClusterModel) {
|
||||
this.event("cluster-data", "report", {
|
||||
id: cluster.metadata.id,
|
||||
kubernetesVersion: cluster.metadata.version,
|
||||
distribution: cluster.metadata.distribution,
|
||||
nodesCount: cluster.metadata.nodes,
|
||||
lastSeen: cluster.metadata.lastSeen
|
||||
})
|
||||
}
|
||||
|
||||
protected getOS() {
|
||||
let os = ""
|
||||
if (App.isMac) {
|
||||
os = "MacOS"
|
||||
} else if(App.isWindows) {
|
||||
os = "Windows"
|
||||
} else if (App.isLinux) {
|
||||
os = "Linux"
|
||||
if (App.isSnap) {
|
||||
os += "; Snap"
|
||||
} else {
|
||||
os += "; AppImage"
|
||||
}
|
||||
} else {
|
||||
os = "Unknown"
|
||||
}
|
||||
return os
|
||||
}
|
||||
|
||||
protected async event(eventCategory: string, eventAction: string, otherParams = {}) {
|
||||
try {
|
||||
const allowed = await this.isTelemetryAllowed();
|
||||
|
||||
@ -24,7 +24,6 @@
|
||||
},
|
||||
"include": [
|
||||
"renderer.ts",
|
||||
"../../src/extensions/npm/**/*.d.ts",
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
@ -166,8 +166,8 @@ describe("Lens integration tests", () => {
|
||||
pages: [{
|
||||
name: "Cluster",
|
||||
href: "cluster",
|
||||
expectedSelector: "div.ClusterNoMetrics p",
|
||||
expectedText: "Metrics are not available due"
|
||||
expectedSelector: "div.Cluster div.label",
|
||||
expectedText: "Master"
|
||||
}]
|
||||
},
|
||||
{
|
||||
@ -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 ""
|
||||
|
||||
|
||||
31
package.json
31
package.json
@ -16,12 +16,13 @@
|
||||
"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",
|
||||
"build:win": "yarn compile && electron-builder --win --dir -c.productName=Lens",
|
||||
@ -35,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": {
|
||||
@ -96,6 +98,11 @@
|
||||
"to": "static/",
|
||||
"filter": "!**/main.js"
|
||||
},
|
||||
{
|
||||
"from": "build/tray",
|
||||
"to": "static/icons",
|
||||
"filter": "*.png"
|
||||
},
|
||||
{
|
||||
"from": "extensions/",
|
||||
"to": "./extensions/",
|
||||
@ -185,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",
|
||||
@ -197,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",
|
||||
@ -275,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",
|
||||
@ -326,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",
|
||||
@ -338,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
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}` : "")
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,9 @@ import { defineGlobal } from "./utils/defineGlobal";
|
||||
|
||||
export const isMac = process.platform === "darwin"
|
||||
export const isWindows = process.platform === "win32"
|
||||
export const isLinux = process.platform === "linux"
|
||||
export const isDebugging = process.env.DEBUG === "true";
|
||||
export const isSnap = !!process.env["SNAP"]
|
||||
export const isProduction = process.env.NODE_ENV === "production"
|
||||
export const isTestEnv = !!process.env.JEST_WORKER_ID;
|
||||
export const isDevelopment = !isTestEnv && !isProduction;
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
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;
|
||||
|
||||
@ -56,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
|
||||
@ -79,6 +73,9 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
}
|
||||
if (existingWorkspace) {
|
||||
Object.assign(existingWorkspace, workspace);
|
||||
appEventBus.emit({name: "workspace", action: "update"})
|
||||
} else {
|
||||
appEventBus.emit({name: "workspace", action: "add"})
|
||||
}
|
||||
this.workspaces.set(id, workspace);
|
||||
return workspace;
|
||||
@ -95,6 +92,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
this.currentWorkspaceId = WorkspaceStore.defaultId; // reset to default
|
||||
}
|
||||
this.workspaces.delete(id);
|
||||
appEventBus.emit({name: "workspace", action: "remove"})
|
||||
clusterStore.removeByWorkspaceId(id)
|
||||
}
|
||||
|
||||
|
||||
4
src/extensions/core-api/app.ts
Normal file
4
src/extensions/core-api/app.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { getAppVersion } from "../../common/utils";
|
||||
|
||||
export const version = getAppVersion()
|
||||
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars"
|
||||
@ -5,20 +5,21 @@ export * from "../lens-renderer-extension"
|
||||
import type { WindowManager } from "../../main/window-manager";
|
||||
|
||||
// APIs
|
||||
import * as App from "./app"
|
||||
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 {
|
||||
App,
|
||||
EventBus,
|
||||
ClusterFeature,
|
||||
Store,
|
||||
Util,
|
||||
Registry,
|
||||
CommonVars,
|
||||
}
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export { ExtensionStore } from "../extension-store"
|
||||
export { clusterStore, ClusterModel } from "../../common/cluster-store"
|
||||
export { workspaceStore} from "../../common/workspace-store"
|
||||
export type { Cluster } from "../../main/cluster"
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ExtensionManifest } from "./lens-extension"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import fs from "fs-extra"
|
||||
import logger from "../main/logger"
|
||||
import { extensionPackagesRoot, InstalledExtension } from "./extension-loader"
|
||||
@ -24,10 +25,14 @@ export class ExtensionManager {
|
||||
return extensionPackagesRoot()
|
||||
}
|
||||
|
||||
get folderPath(): string {
|
||||
get inTreeFolderPath(): string {
|
||||
return path.resolve(__static, "../extensions");
|
||||
}
|
||||
|
||||
get localFolderPath(): string {
|
||||
return path.join(os.homedir(), ".k8slens", "extensions");
|
||||
}
|
||||
|
||||
get npmPath() {
|
||||
return __non_webpack_require__.resolve('npm/bin/npm-cli')
|
||||
}
|
||||
@ -35,7 +40,7 @@ export class ExtensionManager {
|
||||
async load() {
|
||||
logger.info("[EXTENSION-MANAGER] loading extensions from " + this.extensionPackagesRoot)
|
||||
await fs.ensureDir(path.join(this.extensionPackagesRoot, "node_modules"))
|
||||
|
||||
await fs.ensureDir(this.localFolderPath)
|
||||
return await this.loadExtensions();
|
||||
}
|
||||
|
||||
@ -74,14 +79,17 @@ export class ExtensionManager {
|
||||
}
|
||||
|
||||
async loadExtensions() {
|
||||
const extensions = await this.loadFromFolder(this.folderPath);
|
||||
const bundledExtensions = await this.loadBundledExtensions()
|
||||
const localExtendions = await this.loadFromFolder(this.localFolderPath)
|
||||
const extensions = bundledExtensions.concat(localExtendions)
|
||||
return new Map(extensions.map(ext => [ext.id, ext]));
|
||||
}
|
||||
|
||||
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
|
||||
const paths = await fs.readdir(folderPath);
|
||||
async loadBundledExtensions() {
|
||||
const extensions: InstalledExtension[] = []
|
||||
const folderPath = this.inTreeFolderPath
|
||||
const bundledExtensions = getBundledExtensions()
|
||||
const paths = await fs.readdir(folderPath);
|
||||
for (const fileName of paths) {
|
||||
if (!bundledExtensions.includes(fileName)) {
|
||||
continue
|
||||
@ -94,10 +102,33 @@ export class ExtensionManager {
|
||||
extensions.push(ext)
|
||||
}
|
||||
}
|
||||
logger.debug(`[EXTENSION-MANAGER]: ${extensions.length} extensions loaded`, { folderPath, extensions });
|
||||
await fs.writeFile(path.join(this.extensionPackagesRoot, "package.json"), JSON.stringify(this.packagesJson), {mode: 0o600})
|
||||
await this.installPackages()
|
||||
return extensions
|
||||
}
|
||||
|
||||
async loadFromFolder(folderPath: string): Promise<InstalledExtension[]> {
|
||||
const bundledExtensions = getBundledExtensions()
|
||||
const extensions: InstalledExtension[] = []
|
||||
const paths = await fs.readdir(folderPath);
|
||||
for (const fileName of paths) {
|
||||
if (bundledExtensions.includes(fileName)) { // do no allow to override bundled extensions
|
||||
continue
|
||||
}
|
||||
const absPath = path.resolve(folderPath, fileName);
|
||||
const manifestPath = path.resolve(absPath, "package.json");
|
||||
await fs.access(manifestPath, fs.constants.F_OK)
|
||||
const ext = await this.getExtensionByManifest(manifestPath).catch(() => null)
|
||||
if (ext) {
|
||||
extensions.push(ext)
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`[EXTENSION-MANAGER]: ${extensions.length} extensions loaded`, { folderPath, extensions });
|
||||
await fs.writeFile(path.join(this.extensionPackagesRoot, "package.json"), JSON.stringify(this.packagesJson), {mode: 0o600})
|
||||
await this.installPackages()
|
||||
|
||||
logger.debug(`[EXTENSION-MANAGER]: ${extensions.length} extensions loaded`, { folderPath, extensions });
|
||||
return extensions;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,9 +5,7 @@
|
||||
"version": "0.0.0",
|
||||
"copyright": "© 2020, Mirantis, Inc.",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"api.d.ts"
|
||||
],
|
||||
"types": "api.d.ts",
|
||||
"author": {
|
||||
"name": "Mirantis, Inc.",
|
||||
"email": "info@k8slens.dev"
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,12 +37,12 @@ export class HelmRepoManager extends Singleton {
|
||||
|
||||
async loadAvailableRepos(): Promise<HelmRepo[]> {
|
||||
const res = await customRequestPromise({
|
||||
uri: "https://hub.helm.sh/assets/js/repos.json",
|
||||
uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json",
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
timeout: 10000,
|
||||
});
|
||||
return orderBy<HelmRepo>(res.body.data, repo => repo.name);
|
||||
return orderBy<HelmRepo>(res.body, repo => repo.name);
|
||||
}
|
||||
|
||||
async init() {
|
||||
|
||||
@ -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' },
|
||||
|
||||
126
src/main/tray.ts
Normal file
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]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ export class HelmRelease implements ItemObject {
|
||||
chart: string
|
||||
status: string
|
||||
updated: string
|
||||
revision: number
|
||||
revision: string
|
||||
|
||||
getId() {
|
||||
return this.namespace + this.name;
|
||||
@ -165,7 +165,7 @@ export class HelmRelease implements ItemObject {
|
||||
}
|
||||
|
||||
getRevision() {
|
||||
return this.revision;
|
||||
return parseInt(this.revision, 10);
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
|
||||
@ -109,6 +109,14 @@ export class Ingress extends KubeObject {
|
||||
}
|
||||
return ports.join(", ")
|
||||
}
|
||||
|
||||
getLoadBalancers() {
|
||||
const { status: { loadBalancer = { ingress: [] } } } = this;
|
||||
|
||||
return (loadBalancer.ingress ?? []).map(address => (
|
||||
address.hostname || address.ip
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
export const ingressApi = new IngressApi({
|
||||
|
||||
@ -152,7 +152,16 @@ export interface IPodContainerStatus {
|
||||
reason: string;
|
||||
};
|
||||
};
|
||||
lastState: {};
|
||||
lastState: {
|
||||
[index: string]: object;
|
||||
terminated?: {
|
||||
startedAt: string;
|
||||
finishedAt: string;
|
||||
exitCode: number;
|
||||
reason: string;
|
||||
containerID: string;
|
||||
};
|
||||
};
|
||||
ready: boolean;
|
||||
restartCount: number;
|
||||
image: string;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -39,12 +39,14 @@ export class Ingresses extends React.Component<Props> {
|
||||
renderTableHeader={[
|
||||
{ title: <Trans>Name</Trans>, className: "name", sortBy: sortBy.name },
|
||||
{ title: <Trans>Namespace</Trans>, className: "namespace", sortBy: sortBy.namespace },
|
||||
{ title: <Trans>LoadBalancers</Trans>, className: "loadbalancers" },
|
||||
{ title: <Trans>Rules</Trans>, className: "rules" },
|
||||
{ title: <Trans>Age</Trans>, className: "age", sortBy: sortBy.age },
|
||||
]}
|
||||
renderTableContents={(ingress: Ingress) => [
|
||||
ingress.getName(),
|
||||
ingress.getNs(),
|
||||
ingress.getLoadBalancers().map(lb => <p key={lb}>{lb}</p>),
|
||||
ingress.getRoutes().map(route => <p key={route}>{route}</p>),
|
||||
ingress.getAge(),
|
||||
]}
|
||||
|
||||
@ -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,6 @@
|
||||
import "./pod-container-env.scss";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import flatten from "lodash/flatten";
|
||||
import { observer } from "mobx-react";
|
||||
import { Trans } from "@lingui/macro";
|
||||
import { IPodContainer, Secret } from "../../api/endpoints";
|
||||
@ -11,6 +10,7 @@ import { secretsStore } from "../+config-secrets/secrets.store";
|
||||
import { configMapsStore } from "../+config-maps/config-maps.store";
|
||||
import { Icon } from "../icon";
|
||||
import { base64, cssNames } from "../../utils";
|
||||
import _ from "lodash";
|
||||
|
||||
interface Props {
|
||||
container: IPodContainer;
|
||||
@ -40,7 +40,9 @@ export const ContainerEnvironment = observer((props: Props) => {
|
||||
)
|
||||
|
||||
const renderEnv = () => {
|
||||
return env.map(variable => {
|
||||
const orderedEnv = _.sortBy(env, 'name');
|
||||
|
||||
return orderedEnv.map(variable => {
|
||||
const { name, value, valueFrom } = variable
|
||||
let secretValue = null
|
||||
|
||||
@ -89,7 +91,7 @@ export const ContainerEnvironment = observer((props: Props) => {
|
||||
</div>
|
||||
))
|
||||
})
|
||||
return flatten(envVars)
|
||||
return _.flatten(envVars)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -2,7 +2,7 @@ import "./pod-details-container.scss"
|
||||
|
||||
import React from "react";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { IPodContainer, Pod } from "../../api/endpoints";
|
||||
import { IPodContainer, IPodContainerStatus, Pod } from "../../api/endpoints";
|
||||
import { DrawerItem } from "../drawer";
|
||||
import { cssNames } from "../../utils";
|
||||
import { StatusBrick } from "../status-brick";
|
||||
@ -21,12 +21,37 @@ interface Props {
|
||||
}
|
||||
|
||||
export class PodDetailsContainer extends React.Component<Props> {
|
||||
|
||||
renderStatus(state: string, status: IPodContainerStatus) {
|
||||
const ready = status ? status.ready : ""
|
||||
return (
|
||||
<span className={cssNames("status", state)}>
|
||||
{state}{ready ? `, ${_i18n._(t`ready`)}` : ""}
|
||||
{state === 'terminated' ? ` - ${status.state.terminated.reason} (${_i18n._(t`exit code`)}: ${status.state.terminated.exitCode})` : ''}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderLastState(lastState: string, status: IPodContainerStatus) {
|
||||
if (lastState === 'terminated') {
|
||||
return (
|
||||
<span>
|
||||
{lastState}<br/>
|
||||
{_i18n._(t`Reason`)}: {status.lastState.terminated.reason} - {_i18n._(t`exit code`)}: {status.lastState.terminated.exitCode}<br/>
|
||||
{_i18n._(t`Started at`)}: {status.lastState.terminated.startedAt}<br/>
|
||||
{_i18n._(t`Finished at`)}: {status.lastState.terminated.finishedAt}<br/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { pod, container, metrics } = this.props
|
||||
if (!pod || !container) return null
|
||||
const { name, image, imagePullPolicy, ports, volumeMounts, command, args } = container
|
||||
const status = pod.getContainerStatuses().find(status => status.name === container.name)
|
||||
const state = status ? Object.keys(status.state)[0] : ""
|
||||
const lastState = status ? Object.keys(status.lastState)[0] : ""
|
||||
const ready = status ? status.ready : ""
|
||||
const liveness = pod.getLivenessProbe(container)
|
||||
const readiness = pod.getReadinessProbe(container)
|
||||
@ -48,10 +73,12 @@ export class PodDetailsContainer extends React.Component<Props> {
|
||||
}
|
||||
{status &&
|
||||
<DrawerItem name={<Trans>Status</Trans>}>
|
||||
<span className={cssNames("status", state)}>
|
||||
{state}{ready ? `, ${_i18n._(t`ready`)}` : ""}
|
||||
{state === 'terminated' ? ` - ${status.state.terminated.reason} (${_i18n._(t`exit code`)}: ${status.state.terminated.exitCode})` : ''}
|
||||
</span>
|
||||
{this.renderStatus(state, status)}
|
||||
</DrawerItem>
|
||||
}
|
||||
{lastState &&
|
||||
<DrawerItem name={<Trans>Last Status</Trans>}>
|
||||
{this.renderLastState(lastState, status)}
|
||||
</DrawerItem>
|
||||
}
|
||||
<DrawerItem name={<Trans>Image</Trans>}>
|
||||
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user