Merge remote-tracking branch 'origin/master' into lens_restructure
# Conflicts: # package.json # src/main/index.ts # src/renderer/api/__test__/parseAPI.test.ts # src/renderer/components/chart/bar-chart.tsx # src/renderer/components/chart/chart.tsx # src/renderer/components/chart/pie-chart.tsx # src/renderer/utils/__tests__/arrays.test.ts # src/renderer/utils/arrays.ts # yarn.lock
@ -13,7 +13,6 @@ trigger:
|
|||||||
- "*"
|
- "*"
|
||||||
jobs:
|
jobs:
|
||||||
- job: Windows
|
- job: Windows
|
||||||
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: windows-2019
|
vmImage: windows-2019
|
||||||
strategy:
|
strategy:
|
||||||
@ -34,10 +33,14 @@ jobs:
|
|||||||
inputs:
|
inputs:
|
||||||
key: yarn | $(Agent.OS) | yarn.lock
|
key: yarn | $(Agent.OS) | yarn.lock
|
||||||
path: $(YARN_CACHE_FOLDER)
|
path: $(YARN_CACHE_FOLDER)
|
||||||
|
cacheHitVar: CACHE_RESTORED
|
||||||
displayName: Cache Yarn packages
|
displayName: Cache Yarn packages
|
||||||
- script: make deps
|
- script: make deps
|
||||||
displayName: Install dependencies
|
displayName: Install dependencies
|
||||||
|
- script: make integration-win
|
||||||
|
displayName: Run integration tests
|
||||||
- script: make build
|
- script: make build
|
||||||
|
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||||
displayName: Build
|
displayName: Build
|
||||||
env:
|
env:
|
||||||
WIN_CSC_LINK: $(WIN_CSC_LINK)
|
WIN_CSC_LINK: $(WIN_CSC_LINK)
|
||||||
@ -53,6 +56,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- script: CI_BUILD_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=CI_BUILD_TAG]$CI_BUILD_TAG"
|
- script: CI_BUILD_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=CI_BUILD_TAG]$CI_BUILD_TAG"
|
||||||
displayName: Set the tag name as an environment variable
|
displayName: Set the tag name as an environment variable
|
||||||
|
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||||
- task: NodeTool@0
|
- task: NodeTool@0
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: $(node_version)
|
versionSpec: $(node_version)
|
||||||
@ -138,6 +142,7 @@ jobs:
|
|||||||
SNAP_LOGIN: $(SNAP_LOGIN)
|
SNAP_LOGIN: $(SNAP_LOGIN)
|
||||||
- script: make build
|
- script: make build
|
||||||
displayName: Build
|
displayName: Build
|
||||||
|
condition: "and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))"
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: $(GH_TOKEN)
|
GH_TOKEN: $(GH_TOKEN)
|
||||||
- bash: |
|
- bash: |
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
# Lens | The Kubernetes IDE
|
# Lens | The Kubernetes IDE
|
||||||
|
|
||||||
[](https://dev.azure.com/lensapp/lensapp/_build/latest?definitionId=1&branchName=master)
|
[](https://dev.azure.com/lensapp/lensapp/_build/latest?definitionId=1&branchName=master)
|
||||||
|
[](https://github.com/lensapp/lens/releases)
|
||||||
|
[](https://join.slack.com/t/k8slens/shared_invite/enQtOTc5NjAyNjYyOTk4LWU1NDQ0ZGFkOWJkNTRhYTc2YjVmZDdkM2FkNGM5MjhiYTRhMDU2NDQ1MzIyMDA4ZGZlNmExOTc0N2JmY2M3ZGI)
|
||||||
|
|
||||||
Lens is the only IDE you’ll ever need to take control of your Kubernetes clusters. It is a standalone application for MacOS, Windows and Linux operating systems. It is open source and free.
|
Lens is the only IDE you’ll ever need to take control of your Kubernetes clusters. It is a standalone application for MacOS, Windows and Linux operating systems. It is open source and free.
|
||||||
|
|
||||||
|
|||||||
BIN
build/icon.ico
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 116 KiB |
BIN
build/icon.png
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 26 KiB |
@ -4,6 +4,12 @@
|
|||||||
"description": "Lens - The Kubernetes IDE",
|
"description": "Lens - The Kubernetes IDE",
|
||||||
"version": "3.5.0-beta.1",
|
"version": "3.5.0-beta.1",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
|
"copyright": "© 2020, Lakend Labs, Inc.",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": {
|
||||||
|
"name": "Lakend Labs, Inc.",
|
||||||
|
"email": "info@lakendlabs.com"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently 'yarn dev:main' 'yarn dev:renderer'",
|
"dev": "concurrently 'yarn dev:main' 'yarn dev:renderer'",
|
||||||
"dev-run": "electron --inspect .",
|
"dev-run": "electron --inspect .",
|
||||||
@ -30,9 +36,6 @@
|
|||||||
"download:kubectl": "yarn run ts-node build/download_kubectl.ts",
|
"download:kubectl": "yarn run ts-node build/download_kubectl.ts",
|
||||||
"download:helm": "yarn run ts-node build/download_helm.ts"
|
"download:helm": "yarn run ts-node build/download_helm.ts"
|
||||||
},
|
},
|
||||||
"author": "Lakend Labs, Inc. <info@lakendlabs.com>",
|
|
||||||
"copyright": "© 2020, Lakend Labs, Inc.",
|
|
||||||
"license": "MIT",
|
|
||||||
"config": {
|
"config": {
|
||||||
"bundledKubectlVersion": "1.17.4",
|
"bundledKubectlVersion": "1.17.4",
|
||||||
"bundledHelmVersion": "3.1.2"
|
"bundledHelmVersion": "3.1.2"
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Application } from "spectron";
|
|||||||
let appPath = ""
|
let appPath = ""
|
||||||
switch(process.platform) {
|
switch(process.platform) {
|
||||||
case "win32":
|
case "win32":
|
||||||
appPath = "./dist/win-unpacked/Lens.exe"
|
appPath = "./dist/win-unpacked/LensDev.exe"
|
||||||
break
|
break
|
||||||
case "linux":
|
case "linux":
|
||||||
appPath = "./dist/linux-unpacked/kontena-lens"
|
appPath = "./dist/linux-unpacked/kontena-lens"
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { stat } from "fs"
|
|||||||
|
|
||||||
jest.setTimeout(20000)
|
jest.setTimeout(20000)
|
||||||
|
|
||||||
|
const BACKSPACE = "\uE003"
|
||||||
|
|
||||||
describe("app start", () => {
|
describe("app start", () => {
|
||||||
let app: Application
|
let app: Application
|
||||||
const clickWhatsNew = async (app: Application) => {
|
const clickWhatsNew = async (app: Application) => {
|
||||||
@ -13,6 +15,24 @@ describe("app start", () => {
|
|||||||
await app.client.waitUntilTextExists("h1", "Welcome")
|
await app.client.waitUntilTextExists("h1", "Welcome")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addMinikubeCluster = async (app: Application) => {
|
||||||
|
await app.client.click("a#add-cluster")
|
||||||
|
await app.client.waitUntilTextExists("legend", "Choose config:")
|
||||||
|
await app.client.selectByVisibleText("select#kubecontext-select", "minikube (new)")
|
||||||
|
await app.client.click("button.btn-primary")
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitForMinikubeDashboard = async (app: Application) => {
|
||||||
|
await app.client.waitUntilTextExists("pre.auth-output", "Authentication proxy started")
|
||||||
|
let windowCount = await app.client.getWindowCount()
|
||||||
|
// wait for webview to appear on window count
|
||||||
|
while (windowCount == 1) {
|
||||||
|
windowCount = await app.client.getWindowCount()
|
||||||
|
}
|
||||||
|
await app.client.windowByIndex(windowCount - 1)
|
||||||
|
await app.client.waitUntilTextExists("span.link-text", "Cluster")
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
app = util.setup()
|
app = util.setup()
|
||||||
await app.start()
|
await app.start()
|
||||||
@ -32,22 +52,48 @@ describe("app start", () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
await clickWhatsNew(app)
|
await clickWhatsNew(app)
|
||||||
await app.client.click("a#add-cluster")
|
await addMinikubeCluster(app)
|
||||||
await app.client.waitUntilTextExists("legend", "Choose config:")
|
await waitForMinikubeDashboard(app)
|
||||||
await app.client.selectByVisibleText("select#kubecontext-select", "minikube (new)")
|
|
||||||
await app.client.click("button.btn-primary")
|
|
||||||
await app.client.waitUntilTextExists("pre.auth-output", "Authentication proxy started")
|
|
||||||
let windowCount = await app.client.getWindowCount()
|
|
||||||
// wait for webview to appear on window count
|
|
||||||
while (windowCount == 1) {
|
|
||||||
windowCount = await app.client.getWindowCount()
|
|
||||||
}
|
|
||||||
await app.client.windowByIndex(windowCount - 1)
|
|
||||||
await app.client.waitUntilTextExists("span.link-text", "Cluster")
|
|
||||||
await app.client.click('a[href="/nodes"]')
|
await app.client.click('a[href="/nodes"]')
|
||||||
await app.client.waitUntilTextExists("div.TableCell", "minikube")
|
await app.client.waitUntilTextExists("div.TableCell", "minikube")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('allows to create a pod', async () => {
|
||||||
|
const status = spawnSync("minikube status", {shell: true})
|
||||||
|
if (status.status !== 0) {
|
||||||
|
console.warn("minikube not running, skipping test")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await clickWhatsNew(app)
|
||||||
|
await addMinikubeCluster(app)
|
||||||
|
await waitForMinikubeDashboard(app)
|
||||||
|
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("div.TableCell", "kube-apiserver-minikube")
|
||||||
|
await app.client.click('.Icon.new-dock-tab')
|
||||||
|
await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource")
|
||||||
|
await app.client.click("li.MenuItem.create-resource-tab")
|
||||||
|
await app.client.waitForVisible(".CreateResource div.ace_content")
|
||||||
|
// Write pod manifest to editor
|
||||||
|
await app.client.keys("apiVersion: v1\n")
|
||||||
|
await app.client.keys("kind: Pod\n")
|
||||||
|
await app.client.keys("metadata:\n")
|
||||||
|
await app.client.keys(" name: nginx\n")
|
||||||
|
await app.client.keys(BACKSPACE + "spec:\n")
|
||||||
|
await app.client.keys(" containers:\n")
|
||||||
|
await app.client.keys("- name: nginx\n")
|
||||||
|
await app.client.keys(" image: nginx:alpine\n")
|
||||||
|
// Create deployent
|
||||||
|
await app.client.waitForEnabled("button.Button=Create & Close")
|
||||||
|
await app.client.click("button.Button=Create & Close")
|
||||||
|
// Wait until first bits of pod appears on dashboard
|
||||||
|
await app.client.waitForExist(".name=nginx")
|
||||||
|
// Open pod details
|
||||||
|
await app.client.click(".name=nginx")
|
||||||
|
await app.client.waitUntilTextExists("div.drawer-title-text", "Pod: nginx")
|
||||||
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (app && app.isRunning()) {
|
if (app && app.isRunning()) {
|
||||||
return util.tearDown(app)
|
return util.tearDown(app)
|
||||||
|
|||||||
@ -29,6 +29,7 @@ if (app.commandLine.getSwitchValue("proxy-server") !== "") {
|
|||||||
const promiseIpc = new PromiseIpc({ timeout: 2000 })
|
const promiseIpc = new PromiseIpc({ timeout: 2000 })
|
||||||
let windowManager: WindowManager = null;
|
let windowManager: WindowManager = null;
|
||||||
let clusterManager: ClusterManager = null;
|
let clusterManager: ClusterManager = null;
|
||||||
|
let proxyServer: proxy.LensProxy = null;
|
||||||
|
|
||||||
const vmURL = formatUrl({
|
const vmURL = formatUrl({
|
||||||
pathname: path.join(__dirname, `${vueAppName}.html`),
|
pathname: path.join(__dirname, `${vueAppName}.html`),
|
||||||
@ -64,10 +65,9 @@ async function main() {
|
|||||||
|
|
||||||
// create cluster manager
|
// create cluster manager
|
||||||
clusterManager = new ClusterManager(clusterStore.getAllClusterObjects(), port)
|
clusterManager = new ClusterManager(clusterStore.getAllClusterObjects(), port)
|
||||||
|
|
||||||
// run proxy
|
// run proxy
|
||||||
try {
|
try {
|
||||||
proxy.listen(port, clusterManager)
|
proxyServer = proxy.listen(port, clusterManager)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Could not start proxy (127.0.0:${port}): ${error.message}`)
|
logger.error(`Could not start proxy (127.0.0:${port}): ${error.message}`)
|
||||||
await dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${port}): ${error.message || "unknown error"}`)
|
await dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${port}): ${error.message || "unknown error"}`)
|
||||||
@ -87,7 +87,7 @@ async function main() {
|
|||||||
},
|
},
|
||||||
showPreferencesHook: async () => {
|
showPreferencesHook: async () => {
|
||||||
// IPC send needs webContents as we're sending it to renderer
|
// IPC send needs webContents as we're sending it to renderer
|
||||||
promiseIpc.send('navigate', findMainWebContents(), { name: 'preferences-page' }).then((data: any) => {
|
promiseIpc.send('navigate', findMainWebContents(), {name: 'preferences-page'}).then((data: any) => {
|
||||||
logger.debug("navigate: preferences IPC sent");
|
logger.debug("navigate: preferences IPC sent");
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -110,10 +110,10 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.on("ready", main)
|
app.on("ready", main)
|
||||||
app.on('window-all-closed', function () {
|
app.on('window-all-closed', function() {
|
||||||
// On OS X it is common for applications and their menu bar
|
// On OS X it is common for applications and their menu bar
|
||||||
// to stay active until the user quits explicitly with Cmd + Q
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
if (!isMac) {
|
if (process.platform != 'darwin') {
|
||||||
app.quit();
|
app.quit();
|
||||||
} else {
|
} else {
|
||||||
windowManager = null
|
windowManager = null
|
||||||
@ -130,5 +130,6 @@ app.on("activate", () => {
|
|||||||
app.on("will-quit", async (event) => {
|
app.on("will-quit", async (event) => {
|
||||||
event.preventDefault(); // To allow mixpanel sending to be executed
|
event.preventDefault(); // To allow mixpanel sending to be executed
|
||||||
if (clusterManager) clusterManager.stop()
|
if (clusterManager) clusterManager.stop()
|
||||||
|
if (proxyServer) proxyServer.close()
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
})
|
})
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export class PrometheusLens implements PrometheusProvider {
|
|||||||
port: service.spec.ports[0].port
|
port: service.spec.ports[0].port
|
||||||
}
|
}
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
logger.warn(`PrometheusLens: failed to list services: ${error.toString()}`)
|
logger.warn(`PrometheusLens: failed to list services: ${error.response.body.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,8 @@ export class LensProxy {
|
|||||||
protected clusterManager: ClusterManager
|
protected clusterManager: ClusterManager
|
||||||
protected retryCounters: Map<string, number> = new Map()
|
protected retryCounters: Map<string, number> = new Map()
|
||||||
protected router: Router
|
protected router: Router
|
||||||
|
protected proxyServer: http.Server
|
||||||
|
protected closed = false
|
||||||
|
|
||||||
constructor(port: number, clusterManager: ClusterManager) {
|
constructor(port: number, clusterManager: ClusterManager) {
|
||||||
this.port = port
|
this.port = port
|
||||||
@ -28,6 +30,13 @@ export class LensProxy {
|
|||||||
public run() {
|
public run() {
|
||||||
const proxyServer = this.buildProxyServer();
|
const proxyServer = this.buildProxyServer();
|
||||||
proxyServer.listen(this.port, "127.0.0.1")
|
proxyServer.listen(this.port, "127.0.0.1")
|
||||||
|
this.proxyServer = proxyServer
|
||||||
|
}
|
||||||
|
|
||||||
|
public close() {
|
||||||
|
logger.info("Closing proxy server")
|
||||||
|
this.proxyServer.close()
|
||||||
|
this.closed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
protected buildProxyServer() {
|
protected buildProxyServer() {
|
||||||
@ -68,6 +77,9 @@ export class LensProxy {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
proxy.on("error", (error, req, res, target) => {
|
proxy.on("error", (error, req, res, target) => {
|
||||||
|
if(this.closed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (target) {
|
if (target) {
|
||||||
logger.debug("Failed proxy to target: " + JSON.stringify(target))
|
logger.debug("Failed proxy to target: " + JSON.stringify(target))
|
||||||
if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) {
|
if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { helmCli } from "./helm-cli"
|
|||||||
import { isWindows } from "../common/vars";
|
import { isWindows } from "../common/vars";
|
||||||
|
|
||||||
export class ShellSession extends EventEmitter {
|
export class ShellSession extends EventEmitter {
|
||||||
static shellEnv: any
|
static shellEnvs: Map<string, any> = new Map()
|
||||||
|
|
||||||
protected websocket: WebSocket
|
protected websocket: WebSocket
|
||||||
protected shellProcess: pty.IPty
|
protected shellProcess: pty.IPty
|
||||||
@ -22,6 +22,7 @@ export class ShellSession extends EventEmitter {
|
|||||||
protected helmBinDir: string;
|
protected helmBinDir: string;
|
||||||
protected preferences: ClusterPreferences;
|
protected preferences: ClusterPreferences;
|
||||||
protected running = false;
|
protected running = false;
|
||||||
|
protected clusterId: string;
|
||||||
|
|
||||||
constructor(socket: WebSocket, pathToKubeconfig: string, cluster: Cluster) {
|
constructor(socket: WebSocket, pathToKubeconfig: string, cluster: Cluster) {
|
||||||
super()
|
super()
|
||||||
@ -29,6 +30,7 @@ export class ShellSession extends EventEmitter {
|
|||||||
this.kubeconfigPath = pathToKubeconfig
|
this.kubeconfigPath = pathToKubeconfig
|
||||||
this.kubectl = new Kubectl(cluster.version)
|
this.kubectl = new Kubectl(cluster.version)
|
||||||
this.preferences = cluster.preferences || {}
|
this.preferences = cluster.preferences || {}
|
||||||
|
this.clusterId = cluster.id
|
||||||
}
|
}
|
||||||
|
|
||||||
public async open() {
|
public async open() {
|
||||||
@ -77,16 +79,14 @@ export class ShellSession extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async getCachedShellEnv() {
|
protected async getCachedShellEnv() {
|
||||||
let env: any
|
let env = ShellSession.shellEnvs.get(this.clusterId)
|
||||||
if (!ShellSession.shellEnv) {
|
if (!env) {
|
||||||
env = await this.getShellEnv()
|
env = await this.getShellEnv()
|
||||||
ShellSession.shellEnv = env
|
ShellSession.shellEnvs.set(this.clusterId, env)
|
||||||
} else {
|
} else {
|
||||||
env = ShellSession.shellEnv
|
|
||||||
|
|
||||||
// refresh env in the background
|
// refresh env in the background
|
||||||
this.getShellEnv().then((shellEnv: any) => {
|
this.getShellEnv().then((shellEnv: any) => {
|
||||||
ShellSession.shellEnv = shellEnv
|
ShellSession.shellEnvs.set(this.clusterId, shellEnv)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -122,7 +122,6 @@
|
|||||||
id="checkbox-allow-telemetry"
|
id="checkbox-allow-telemetry"
|
||||||
switch
|
switch
|
||||||
v-model="preferences.allowTelemetry"
|
v-model="preferences.allowTelemetry"
|
||||||
:disabled="licenceData && licenceData.status === 'valid'"
|
|
||||||
@input="onSave"
|
@input="onSave"
|
||||||
>
|
>
|
||||||
Allow telemetry & usage tracking
|
Allow telemetry & usage tracking
|
||||||
|
|||||||
@ -47,6 +47,11 @@ export default new Vuex.Store({
|
|||||||
this.commit("savePreferences", userStore.getPreferences());
|
this.commit("savePreferences", userStore.getPreferences());
|
||||||
},
|
},
|
||||||
savePreferences(state, prefs) {
|
savePreferences(state, prefs) {
|
||||||
|
if (prefs.allowTelemetry) {
|
||||||
|
tracker.event("telemetry", "enabled")
|
||||||
|
} else {
|
||||||
|
tracker.event("telemetry", "disabled")
|
||||||
|
}
|
||||||
state.preferences = prefs;
|
state.preferences = prefs;
|
||||||
userStore.setPreferences(prefs);
|
userStore.setPreferences(prefs);
|
||||||
this.dispatch("destroyWebviews")
|
this.dispatch("destroyWebviews")
|
||||||
|
|||||||
0
src/renderer/api/__test__/parseAPI.test.ts
Normal file
@ -156,9 +156,12 @@ export class HelmRelease implements ItemObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getChart(withVersion = false) {
|
getChart(withVersion = false) {
|
||||||
return withVersion ?
|
let chart = this.chart
|
||||||
this.chart :
|
if(!withVersion && this.getVersion() != "" ) {
|
||||||
this.chart.substr(0, this.chart.lastIndexOf("-"));
|
const search = new RegExp(`-${this.getVersion()}`)
|
||||||
|
chart = chart.replace(search, "");
|
||||||
|
}
|
||||||
|
return chart
|
||||||
}
|
}
|
||||||
|
|
||||||
getRevision() {
|
getRevision() {
|
||||||
@ -170,7 +173,7 @@ export class HelmRelease implements ItemObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getVersion() {
|
getVersion() {
|
||||||
const versions = this.chart.match(/(\d+)[^-]*$/)
|
const versions = this.chart.match(/(v?\d+)[^-].*$/)
|
||||||
if (versions) {
|
if (versions) {
|
||||||
return versions[0]
|
return versions[0]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { autobind } from "../../utils";
|
|||||||
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
import { IAffinity, WorkloadKubeObject } from "../workload-kube-object";
|
||||||
import { IPodContainer } from "./pods.api";
|
import { IPodContainer } from "./pods.api";
|
||||||
import { KubeApi } from "../kube-api";
|
import { KubeApi } from "../kube-api";
|
||||||
|
import { JsonApiParams } from "../json-api";
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
export class Job extends WorkloadKubeObject {
|
export class Job extends WorkloadKubeObject {
|
||||||
@ -88,6 +89,13 @@ export class Job extends WorkloadKubeObject {
|
|||||||
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", [])
|
const containers: IPodContainer[] = get(this, "spec.template.spec.containers", [])
|
||||||
return [...containers].map(container => container.image)
|
return [...containers].map(container => container.image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
const params: JsonApiParams = {
|
||||||
|
query: { propagationPolicy: "Background" }
|
||||||
|
}
|
||||||
|
return super.delete(params)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const jobApi = new KubeApi({
|
export const jobApi = new KubeApi({
|
||||||
|
|||||||
@ -56,7 +56,21 @@ export const metricsApi = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
|
export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
|
||||||
|
if (!metrics?.data?.result) {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
resultType: "",
|
||||||
|
result: [{
|
||||||
|
metric: {},
|
||||||
|
values: []
|
||||||
|
} as IMetricsResult],
|
||||||
|
},
|
||||||
|
status: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { result } = metrics.data;
|
const { result } = metrics.data;
|
||||||
|
|
||||||
if (result.length) {
|
if (result.length) {
|
||||||
if (frames > 0) {
|
if (frames > 0) {
|
||||||
// fill the gaps
|
// fill the gaps
|
||||||
@ -81,13 +95,16 @@ export function normalizeMetrics(metrics: IMetrics, frames = 60): IMetrics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isMetricsEmpty(metrics: { [key: string]: IMetrics }) {
|
export function isMetricsEmpty(metrics: { [key: string]: IMetrics }) {
|
||||||
return Object.values(metrics).every(metric => !metric.data.result.length);
|
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getItemMetrics(metrics: { [key: string]: IMetrics }, itemName: string) {
|
export function getItemMetrics(metrics: { [key: string]: IMetrics }, itemName: string): { [key: string]: IMetrics } {
|
||||||
if (!metrics) return;
|
if (!metrics) return;
|
||||||
const itemMetrics = { ...metrics };
|
const itemMetrics = { ...metrics };
|
||||||
for (const metric in metrics) {
|
for (const metric in metrics) {
|
||||||
|
if (!metrics[metric]?.data?.result) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
const results = metrics[metric].data.result;
|
const results = metrics[metric].data.result;
|
||||||
const result = results.find(res => Object.values(res.metric)[0] == itemName);
|
const result = results.find(res => Object.values(res.metric)[0] == itemName);
|
||||||
itemMetrics[metric].data.result = result ? [result] : [];
|
itemMetrics[metric].data.result = result ? [result] : [];
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { IKubeObjectRef, KubeJsonApi, KubeJsonApiData, KubeJsonApiDataList } fro
|
|||||||
import { apiKube } from "./index";
|
import { apiKube } from "./index";
|
||||||
import { kubeWatchApi } from "./kube-watch-api";
|
import { kubeWatchApi } from "./kube-watch-api";
|
||||||
import { apiManager } from "./api-manager";
|
import { apiManager } from "./api-manager";
|
||||||
|
import { split } from "../utils/arrays";
|
||||||
|
|
||||||
export interface IKubeApiOptions<T extends KubeObject> {
|
export interface IKubeApiOptions<T extends KubeObject> {
|
||||||
kind: string; // resource type within api-group, e.g. "Namespace"
|
kind: string; // resource type within api-group, e.g. "Namespace"
|
||||||
@ -32,20 +33,45 @@ export interface IKubeApiLinkRef {
|
|||||||
namespace?: string;
|
namespace?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KubeApi<T extends KubeObject = any> {
|
export interface IKubeApiLinkBase extends IKubeApiLinkRef {
|
||||||
static matcher = /(\/apis?.*?)\/(?:(.*?)\/)?(v.*?)(?:\/namespaces\/(.+?))?\/([^\/]+)(?:\/([^\/?]+))?.*$/
|
apiBase: string;
|
||||||
|
apiGroup: string;
|
||||||
|
apiVersionWithGroup: string;
|
||||||
|
}
|
||||||
|
|
||||||
static parseApi(apiPath = "") {
|
export class KubeApi<T extends KubeObject = any> {
|
||||||
|
static parseApi(apiPath = ""): IKubeApiLinkBase {
|
||||||
apiPath = new URL(apiPath, location.origin).pathname;
|
apiPath = new URL(apiPath, location.origin).pathname;
|
||||||
const [, apiPrefix, apiGroup = "", apiVersion, namespace, resource, name] = apiPath.match(KubeApi.matcher) || [];
|
const [, prefix, ...parts] = apiPath.split("/");
|
||||||
|
const apiPrefix = `/${prefix}`;
|
||||||
|
|
||||||
|
const [left, right, found] = split(parts, "namespaces");
|
||||||
|
let apiGroup, apiVersion, namespace, resource, name;
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
if (left.length == 0) {
|
||||||
|
throw new Error(`invalid apiPath: ${apiPath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiVersion = left.pop();
|
||||||
|
apiGroup = left.join("/");
|
||||||
|
[namespace, resource, name] = right;
|
||||||
|
} else {
|
||||||
|
[apiGroup, apiVersion, resource] = left;
|
||||||
|
}
|
||||||
const apiVersionWithGroup = [apiGroup, apiVersion].filter(v => v).join("/");
|
const apiVersionWithGroup = [apiGroup, apiVersion].filter(v => v).join("/");
|
||||||
const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/");
|
const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/");
|
||||||
|
|
||||||
|
if (!apiBase) {
|
||||||
|
throw new Error(`invalid apiPath: ${apiPath}`)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
apiBase,
|
apiBase,
|
||||||
apiPrefix, apiGroup,
|
apiPrefix, apiGroup,
|
||||||
apiVersion, apiVersionWithGroup,
|
apiVersion, apiVersionWithGroup,
|
||||||
namespace, resource, name,
|
namespace, resource, name,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static createLink(ref: IKubeApiLinkRef): string {
|
static createLink(ref: IKubeApiLinkRef): string {
|
||||||
@ -55,7 +81,7 @@ export class KubeApi<T extends KubeObject = any> {
|
|||||||
namespace = `namespaces/${namespace}`
|
namespace = `namespaces/${namespace}`
|
||||||
}
|
}
|
||||||
return [apiPrefix, apiVersion, namespace, resource, name]
|
return [apiPrefix, apiVersion, namespace, resource, name]
|
||||||
.filter(v => !!v)
|
.filter(v => v)
|
||||||
.join("/")
|
.join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,8 +156,9 @@ export class KubeApi<T extends KubeObject = any> {
|
|||||||
if (KubeObject.isJsonApiData(data)) {
|
if (KubeObject.isJsonApiData(data)) {
|
||||||
return new KubeObjectConstructor(data);
|
return new KubeObjectConstructor(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// process items list response
|
// process items list response
|
||||||
else if (KubeObject.isJsonApiDataList(data)) {
|
if (KubeObject.isJsonApiDataList(data)) {
|
||||||
const { apiVersion, items, metadata } = data;
|
const { apiVersion, items, metadata } = data;
|
||||||
this.setResourceVersion(namespace, metadata.resourceVersion);
|
this.setResourceVersion(namespace, metadata.resourceVersion);
|
||||||
this.setResourceVersion("", metadata.resourceVersion);
|
this.setResourceVersion("", metadata.resourceVersion);
|
||||||
@ -141,10 +168,12 @@ export class KubeApi<T extends KubeObject = any> {
|
|||||||
...item,
|
...item,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom apis might return array for list response, e.g. users, groups, etc.
|
// custom apis might return array for list response, e.g. users, groups, etc.
|
||||||
else if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
return data.map(data => new KubeObjectConstructor(data));
|
return data.map(data => new KubeObjectConstructor(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,16 +191,19 @@ export class KubeApi<T extends KubeObject = any> {
|
|||||||
|
|
||||||
async create({ name = "", namespace = "default" } = {}, data?: Partial<T>): Promise<T> {
|
async create({ name = "", namespace = "default" } = {}, data?: Partial<T>): Promise<T> {
|
||||||
const apiUrl = this.getUrl({ namespace });
|
const apiUrl = this.getUrl({ namespace });
|
||||||
return this.request.post(apiUrl, {
|
|
||||||
data: merge({
|
return this.request
|
||||||
kind: this.kind,
|
.post(apiUrl, {
|
||||||
apiVersion: this.apiVersionWithGroup,
|
data: merge({
|
||||||
metadata: {
|
kind: this.kind,
|
||||||
name,
|
apiVersion: this.apiVersionWithGroup,
|
||||||
namespace
|
metadata: {
|
||||||
}
|
name,
|
||||||
}, data)
|
namespace
|
||||||
}).then(this.parseResponse);
|
}
|
||||||
|
}, data)
|
||||||
|
})
|
||||||
|
.then(this.parseResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update({ name = "", namespace = "default" } = {}, data?: Partial<T>): Promise<T> {
|
async update({ name = "", namespace = "default" } = {}, data?: Partial<T>): Promise<T> {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { KubeJsonApiData, KubeJsonApiDataList } from "./kube-json-api";
|
|||||||
import { autobind, formatDuration } from "../utils";
|
import { autobind, formatDuration } from "../utils";
|
||||||
import { ItemObject } from "../item.store";
|
import { ItemObject } from "../item.store";
|
||||||
import { apiKube } from "./index";
|
import { apiKube } from "./index";
|
||||||
|
import { JsonApiParams } from "./json-api";
|
||||||
import { resourceApplierApi } from "./endpoints/resource-applier.api";
|
import { resourceApplierApi } from "./endpoints/resource-applier.api";
|
||||||
|
|
||||||
export type IKubeObjectConstructor<T extends KubeObject = any> = (new (data: KubeJsonApiData | any) => T) & {
|
export type IKubeObjectConstructor<T extends KubeObject = any> = (new (data: KubeJsonApiData | any) => T) & {
|
||||||
@ -153,7 +154,7 @@ export class KubeObject implements ItemObject {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
delete() {
|
delete(params?: JsonApiParams) {
|
||||||
return apiKube.del(this.selfLink);
|
return apiKube.del(this.selfLink, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,15 +82,15 @@ export class ClusterStore extends KubeObjectStore<Cluster> {
|
|||||||
this.liveMetrics = await this.loadMetrics({ start, end, step, range });
|
this.liveMetrics = await this.loadMetrics({ start, end, step, range });
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetricsValues(source: Partial<IClusterMetrics>) {
|
getMetricsValues(source: Partial<IClusterMetrics>): [number, string][] {
|
||||||
const metrics =
|
switch (this.metricType) {
|
||||||
this.metricType === MetricType.CPU ? source.cpuUsage :
|
case MetricType.CPU:
|
||||||
this.metricType === MetricType.MEMORY ? source.memoryUsage
|
return normalizeMetrics(source.cpuUsage).data.result[0].values
|
||||||
: null;
|
case MetricType.MEMORY:
|
||||||
if (!metrics) {
|
return normalizeMetrics(source.memoryUsage).data.result[0].values
|
||||||
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return normalizeMetrics(metrics).data.result[0].values;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resetMetrics() {
|
resetMetrics() {
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export class NamespaceSelect extends React.Component<Props> {
|
|||||||
private unsubscribe = noop;
|
private unsubscribe = noop;
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
if (isAllowedResource("namespaces") && !namespaceStore.isLoaded) {
|
if (!namespaceStore.isLoaded) {
|
||||||
await namespaceStore.loadAll();
|
await namespaceStore.loadAll();
|
||||||
}
|
}
|
||||||
this.unsubscribe = namespaceStore.subscribe();
|
this.unsubscribe = namespaceStore.subscribe();
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { KubeObjectStore } from "../../kube-object.store";
|
|||||||
import { Namespace, namespacesApi } from "../../api/endpoints";
|
import { Namespace, namespacesApi } from "../../api/endpoints";
|
||||||
import { IQueryParams, navigation, setQueryParams } from "../../navigation";
|
import { IQueryParams, navigation, setQueryParams } from "../../navigation";
|
||||||
import { apiManager } from "../../api/api-manager";
|
import { apiManager } from "../../api/api-manager";
|
||||||
|
import { isAllowedResource } from "../..//api/rbac";
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
export class NamespaceStore extends KubeObjectStore<Namespace> {
|
export class NamespaceStore extends KubeObjectStore<Namespace> {
|
||||||
@ -43,6 +44,16 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected loadItems(namespaces?: string[]) {
|
protected loadItems(namespaces?: string[]) {
|
||||||
|
if (!isAllowedResource("namespaces")) {
|
||||||
|
if (namespaces) {
|
||||||
|
return Promise.all(namespaces.map(name => this.getDummyNamespace(name)))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new Promise<Namespace[]>(() => {
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
if (namespaces) {
|
if (namespaces) {
|
||||||
return Promise.all(namespaces.map(name => this.api.get({ name })))
|
return Promise.all(namespaces.map(name => this.api.get({ name })))
|
||||||
}
|
}
|
||||||
@ -51,6 +62,19 @@ export class NamespaceStore extends KubeObjectStore<Namespace> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getDummyNamespace(name: string) {
|
||||||
|
return new Namespace({
|
||||||
|
kind: "Namespace",
|
||||||
|
apiVersion: "v1",
|
||||||
|
metadata: {
|
||||||
|
name: name,
|
||||||
|
uid: "",
|
||||||
|
resourceVersion: "",
|
||||||
|
selfLink: `/api/v1/namespaces/${name}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
setContext(namespaces: string[]) {
|
setContext(namespaces: string[]) {
|
||||||
this.contextNs.replace(namespaces);
|
this.contextNs.replace(namespaces);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,9 +18,9 @@ export const IngressCharts = observer(() => {
|
|||||||
if (!metrics) return null;
|
if (!metrics) return null;
|
||||||
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
||||||
|
|
||||||
const values = Object.values(metrics).map(metric =>
|
const values = Object.values(metrics)
|
||||||
normalizeMetrics(metric).data.result[0].values
|
.map(normalizeMetrics)
|
||||||
);
|
.map(({ data }) => data.result[0].values);
|
||||||
const [
|
const [
|
||||||
bytesSentSuccess,
|
bytesSentSuccess,
|
||||||
bytesSentFailure,
|
bytesSentFailure,
|
||||||
|
|||||||
@ -96,7 +96,7 @@ export class DeploymentScaleDialog extends Component<Props> {
|
|||||||
<Trans>Desired number of replicas</Trans>: {desiredReplicas}
|
<Trans>Desired number of replicas</Trans>: {desiredReplicas}
|
||||||
</div>
|
</div>
|
||||||
<div className="slider-container">
|
<div className="slider-container">
|
||||||
<Slider value={desiredReplicas} max={scaleMax} onChange={onChange}/>
|
<Slider value={desiredReplicas} max={scaleMax} onChange={onChange as any /** see: https://github.com/mui-org/material-ui/issues/20191 */}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{warning &&
|
{warning &&
|
||||||
|
|||||||
@ -17,9 +17,9 @@ export const ContainerCharts = () => {
|
|||||||
if (!metrics) return null;
|
if (!metrics) return null;
|
||||||
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
||||||
|
|
||||||
const values = Object.values(metrics).map(metric =>
|
const values = Object.values(metrics)
|
||||||
normalizeMetrics(metric).data.result[0].values
|
.map(normalizeMetrics)
|
||||||
);
|
.map(({ data }) => data.result[0].values);
|
||||||
const [
|
const [
|
||||||
cpuUsage,
|
cpuUsage,
|
||||||
cpuRequests,
|
cpuRequests,
|
||||||
|
|||||||
@ -28,9 +28,9 @@ export const PodCharts = observer(() => {
|
|||||||
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
if (isMetricsEmpty(metrics)) return <NoMetrics/>;
|
||||||
|
|
||||||
const options = tabId == 0 ? cpuOptions : memoryOptions;
|
const options = tabId == 0 ? cpuOptions : memoryOptions;
|
||||||
const values = Object.values(metrics).map(metric =>
|
const values = Object.values(metrics)
|
||||||
normalizeMetrics(metric).data.result[0].values
|
.map(normalizeMetrics)
|
||||||
);
|
.map(({ data }) => data.result[0].values);
|
||||||
const [
|
const [
|
||||||
cpuUsage,
|
cpuUsage,
|
||||||
cpuRequests,
|
cpuRequests,
|
||||||
|
|||||||
@ -120,8 +120,11 @@ const SecretKey = (props: SecretKeyProps) => {
|
|||||||
setSecret(secret)
|
setSecret(secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!secret) {
|
if (secret?.data?.[key]) {
|
||||||
return (
|
return <>{base64.decode(secret.data[key])}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
secretKeyRef({name}.{key})
|
secretKeyRef({name}.{key})
|
||||||
<Icon
|
<Icon
|
||||||
@ -131,7 +134,5 @@ const SecretKey = (props: SecretKeyProps) => {
|
|||||||
onClick={showKey}
|
onClick={showKey}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return <>{base64.decode(secret.data[key])}</>
|
|
||||||
}
|
|
||||||
|
|||||||
@ -139,7 +139,7 @@ export function BarChart(props: Props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const options = merge(barOptions, customOptions);
|
const options = merge(barOptions, customOptions);
|
||||||
if (!chartData.datasets.length) {
|
if (chartData.datasets.length == 0) {
|
||||||
return <NoMetrics/>
|
return <NoMetrics/>
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@ -159,10 +159,15 @@ export const memoryOptions: ChartOptions = {
|
|||||||
scales: {
|
scales: {
|
||||||
yAxes: [{
|
yAxes: [{
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: value => {
|
callback: (value: number | string): string => {
|
||||||
value = parseFloat(String(value));
|
if (typeof value == "string") {
|
||||||
if (!value) return 0;
|
const float = parseFloat(value);
|
||||||
return value < 1 ? value.toFixed(3) : bytesToUnits(value);
|
if (float < 1) {
|
||||||
|
return float.toFixed(3);
|
||||||
|
}
|
||||||
|
return bytesToUnits(parseInt(value));
|
||||||
|
}
|
||||||
|
return `${value}`;
|
||||||
},
|
},
|
||||||
stepSize: 1
|
stepSize: 1
|
||||||
}
|
}
|
||||||
@ -184,11 +189,12 @@ export const cpuOptions: ChartOptions = {
|
|||||||
scales: {
|
scales: {
|
||||||
yAxes: [{
|
yAxes: [{
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: (value: number) => {
|
callback: (value: number | string): string => {
|
||||||
if (value == 0) return 0;
|
const float = parseFloat(`${value}`);
|
||||||
if (value < 10) return value.toFixed(3);
|
if (float == 0) return "0";
|
||||||
if (value < 100) return value.toFixed(2);
|
if (float < 10) return float.toFixed(3);
|
||||||
return value.toFixed(1);
|
if (float < 100) return float.toFixed(2);
|
||||||
|
return float.toFixed(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import "./chart.scss";
|
import "./chart.scss";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ChartJS from "chart.js";
|
import ChartJS, {ChartData, ChartOptions} from "chart.js";
|
||||||
import { isEqual, remove } from "lodash";
|
import { isEqual, remove } from "lodash";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { StatusBrick } from "../status-brick";
|
import { StatusBrick } from "../status-brick";
|
||||||
@ -17,7 +17,7 @@ export interface ChartDataSets extends ChartJS.ChartDataSets {
|
|||||||
|
|
||||||
export interface ChartProps {
|
export interface ChartProps {
|
||||||
data: ChartData;
|
data: ChartData;
|
||||||
options?: ChartJS.ChartOptions; // Passed to ChartJS instance
|
options?: ChartOptions; // Passed to ChartJS instance
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
height?: number | string;
|
height?: number | string;
|
||||||
type?: ChartKind;
|
type?: ChartKind;
|
||||||
|
|||||||
@ -5,7 +5,10 @@ import { Chart, ChartProps } from "./chart";
|
|||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { themeStore } from "../../theme.store";
|
import { themeStore } from "../../theme.store";
|
||||||
|
|
||||||
export class PieChart extends React.Component<ChartProps> {
|
interface Props extends ChartProps {
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PieChart extends React.Component<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { data, className, options, ...chartProps } = this.props
|
const { data, className, options, ...chartProps } = this.props
|
||||||
const { contentColor } = themeStore.activeTheme.colors;
|
const { contentColor } = themeStore.activeTheme.colors;
|
||||||
|
|||||||
@ -119,12 +119,12 @@ export class Dock extends React.Component<Props> {
|
|||||||
/>
|
/>
|
||||||
<div className="toolbar flex gaps align-center box grow">
|
<div className="toolbar flex gaps align-center box grow">
|
||||||
<div className="dock-menu box grow">
|
<div className="dock-menu box grow">
|
||||||
<MenuActions usePortal triggerIcon={{ material: "add", tooltip: <Trans>New tab</Trans> }} closeOnScroll={false}>
|
<MenuActions usePortal triggerIcon={{ material: "add", className: "new-dock-tab", tooltip: <Trans>New tab</Trans> }} closeOnScroll={false}>
|
||||||
<MenuItem onClick={() => createTerminalTab()}>
|
<MenuItem className="create-terminal-tab" onClick={() => createTerminalTab()}>
|
||||||
<Icon small svg="terminal" size={15}/>
|
<Icon small svg="terminal" size={15}/>
|
||||||
<Trans>Terminal session</Trans>
|
<Trans>Terminal session</Trans>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={() => createResourceTab()}>
|
<MenuItem className="create-resource-tab" onClick={() => createResourceTab()}>
|
||||||
<Icon small material="create"/>
|
<Icon small material="create"/>
|
||||||
<Trans>Create resource</Trans>
|
<Trans>Create resource</Trans>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@ -1,161 +1 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><style>.cls-2{fill:#3c90ce;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-2" d="M256,496C123.67,496,16,388.33,16,256S123.67,16,256,16,496,123.67,496,256,388.33,496,256,496Zm0-470C129.17,26,26,129.18,26,256S129.17,486,256,486,486,382.82,486,256,382.83,26,256,26Z"/><path class="cls-2" d="M403.22,282l65.66-81.68A220.52,220.52,0,0,0,351.61,57.81Z"/><path class="cls-2" d="M476,256a220.86,220.86,0,0,0-4-41.5L334.74,385.3H434A219,219,0,0,0,476,256Z"/><path class="cls-2" d="M247.63,121.17,139.38,69.48A220,220,0,0,0,38.81,221Z"/><path class="cls-2" d="M140.75,184.81,37.07,234.35C36.38,241.48,36,248.69,36,256A219.13,219.13,0,0,0,91,401.4Z"/><path class="cls-2" d="M210.57,396.65l63.2,78.57a219.52,219.52,0,0,0,151.38-78.57Z"/><path class="cls-2" d="M124.21,307.38l-23.91,104A219.33,219.33,0,0,0,256,476c1.26,0,2.5-.07,3.76-.1Z"/><path class="cls-2" d="M364.52,164.42,338.67,52.12A220.21,220.21,0,0,0,151.16,62.54Z"/><path class="cls-2" d="M351.71,276.74c-.46-.11-1.12-.29-1.58-.37a45.28,45.28,0,0,0-5.2-.42,50.71,50.71,0,0,1-9.73-1.6,6.13,6.13,0,0,1-2.35-2.36l-2.19-.63a70.52,70.52,0,0,0-11.31-48.85l1.93-1.73a4.3,4.3,0,0,1,1-3.08,50.88,50.88,0,0,1,8.07-5.67,43.85,43.85,0,0,0,4.51-2.63c.35-.26.83-.67,1.2-1,2.6-2.08,3.2-5.66,1.33-8s-5.49-2.57-8.09-.49c-.38.29-.88.67-1.21,1a43.42,43.42,0,0,0-3.58,3.79,51.27,51.27,0,0,1-7.32,6.63,6.1,6.1,0,0,1-3.3.36l-2.06,1.47a71.09,71.09,0,0,0-45.06-21.77c-.05-.72-.11-2-.12-2.42a4.33,4.33,0,0,1-1.78-2.72,50.16,50.16,0,0,1,.62-9.84,45.29,45.29,0,0,0,.74-5.17c0-.43,0-1.07,0-1.54,0-3.33-2.43-6-5.43-6s-5.43,2.7-5.43,6c0,.05,0,.1,0,.15,0,.45,0,1,0,1.39a45.29,45.29,0,0,0,.74,5.17,50.87,50.87,0,0,1,.61,9.84,5.9,5.9,0,0,1-1.77,2.81l-.12,2.29a71.38,71.38,0,0,0-9.82,1.51,69.83,69.83,0,0,0-35.46,20.26c-.6-.41-1.65-1.16-2-1.39a4.32,4.32,0,0,1-3.23-.31,50.46,50.46,0,0,1-7.31-6.61,45,45,0,0,0-3.58-3.79c-.33-.29-.83-.67-1.2-1a6.42,6.42,0,0,0-3.78-1.42,5.22,5.22,0,0,0-4.33,1.91c-1.87,2.34-1.27,5.93,1.33,8l.09.06c.35.29.79.66,1.12.91a46.81,46.81,0,0,0,4.5,2.63,49.61,49.61,0,0,1,8.07,5.67,6,6,0,0,1,1.09,3.13l1.74,1.55a70.25,70.25,0,0,0-11.07,49l-2.28.66a7.61,7.61,0,0,1-2.33,2.36,51.5,51.5,0,0,1-9.73,1.6,45.38,45.38,0,0,0-5.21.41c-.41.08-1,.23-1.45.34h0l-.08,0c-3.2.78-5.26,3.72-4.6,6.61a5.75,5.75,0,0,0,7,4h.08l.1,0c.45-.1,1-.21,1.41-.32a45.65,45.65,0,0,0,4.87-1.87,51.49,51.49,0,0,1,9.46-2.78,6.07,6.07,0,0,1,3.12,1.1l2.37-.4A70.59,70.59,0,0,0,225,322.21l-1,2.37a5.46,5.46,0,0,1,.48,3.07,53.5,53.5,0,0,1-4.92,8.83,46,46,0,0,0-2.91,4.33c-.21.41-.48,1-.69,1.47a5.73,5.73,0,0,0,2.31,7.71c2.69,1.3,6-.07,7.49-3.06v0h0c.21-.42.5-1,.67-1.38a45,45,0,0,0,1.57-5c1.43-3.61,2.22-7.4,4.2-9.75a4.31,4.31,0,0,1,2.34-1.14l1.23-2.23a70.15,70.15,0,0,0,40.78,2.93,71.11,71.11,0,0,0,9.31-2.8l1.16,2.09a4.26,4.26,0,0,1,2.77,1.68,50.16,50.16,0,0,1,3.72,9.12,44.6,44.6,0,0,0,1.58,5c.18.4.47,1,.67,1.39,1.45,3,4.81,4.38,7.51,3.07s3.7-4.72,2.3-7.71c-.2-.43-.49-1.05-.7-1.46A45.8,45.8,0,0,0,302,336.4a51.18,51.18,0,0,1-4.82-8.62,4.27,4.27,0,0,1,.42-3.2,20.73,20.73,0,0,1-.9-2.19A70.66,70.66,0,0,0,328,283c.7.11,1.92.33,2.32.41a4.28,4.28,0,0,1,3-1.13,50.68,50.68,0,0,1,9.47,2.79,45.66,45.66,0,0,0,4.87,1.88c.39.1,1,.2,1.4.3l.11,0h.08a5.72,5.72,0,0,0,7-4C357,280.45,354.91,277.51,351.71,276.74Zm-51.57-55.29-23.2,16.45-.08,0a4.79,4.79,0,0,1-6.56-.88,4.71,4.71,0,0,1-1.05-2.77h0l-1.61-28.43A56.44,56.44,0,0,1,300.14,221.45Zm-43.7,31.16h8.73l5.43,6.79-2,8.46-7.84,3.77L253,267.85,251,259.39Zm-8.22-45.84a56.58,56.58,0,0,1,5.79-1l-1.61,28.47-.12.06a4.79,4.79,0,0,1-7.59,3.67l-.05,0-23.35-16.55A55.92,55.92,0,0,1,248.22,206.77Zm-35.39,25.3,21.32,19.07,0,.12a4.79,4.79,0,0,1-1.88,8.22l0,.09-27.32,7.89A55.83,55.83,0,0,1,212.83,232.07Zm28.54,50.65L230.51,309A56.27,56.27,0,0,1,208,280.73L236,276l0,.06A4.37,4.37,0,0,1,237,276a4.78,4.78,0,0,1,4.33,6.67Zm32,33.74a56.07,56.07,0,0,1-30.62-1.58L256.56,290h0a4.77,4.77,0,0,1,4-2.53A4.86,4.86,0,0,1,265,290h.1l13.82,25Q276.24,315.82,273.41,316.46Zm17.81-7.4-11-26.5,0-.05a4.78,4.78,0,0,1,2.32-6.2,4.65,4.65,0,0,1,1.83-.48,5.05,5.05,0,0,1,1.1.08l.05-.05,28.26,4.77A56,56,0,0,1,291.22,309.06Zm25.59-41.69-27.46-7.91,0-.12a4.79,4.79,0,0,1-3.4-5.68,4.7,4.7,0,0,1,1.52-2.54v-.06l21.2-19a56.92,56.92,0,0,1,8.17,35.28Z"/></g></g></svg>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"> <image id="image0" width="512" height="512" x="0" y="0"
|
|
||||||
href="data:image/png;base64,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
|
|
||||||
Zuhs3tmBJknTq3Np53qaZzc/OJ9w/vaFyAutF0MvXr4UeOl8C6fl7GXPy6euuF85eZV9tf6ay7W6
|
|
||||||
687XT/zu/PuJVpfWuhuuNxpuut1sbJvSdqbdu/3cLf9bl25zb1/rmNbRdif2zt3OGZ1ddwV3n9/L
|
|
||||||
uvfmfu79wQfLHhIeFj5SfVTyWO9xxR9Wf9R2uXSd7vbvvv4k+smDHn7Pyz9lf37tzX9Kf1ryzPBZ
|
|
||||||
1XPH56f6Avtuvpj+ovel5OXgq4K/1P7a/try9fG/ff6+3p/Q3/tG+mbo7Zp32u/2v5/8vnkgYuDx
|
|
||||||
h+wPgx8LP2l/OvCZ/bnlS/yXZ4PzvpK+ln6z+tb4PfT7w6HsoSEJT8obkQI4bKCpqQBv9wPQEwEY
|
|
||||||
NwEo00c19Ygho/8DRgj+F4/q7hFzAUwaAMT7AIQ3Aewe1iDLsNwYR2C+GB9AnZwU498mS3VyHM1F
|
|
||||||
rcekScnQ0DtMP5KsAL51Dg0N1g8NfavEmr2P6ZgPo1p+RMeIMDmPFdPSba/ODoD/sn8BcJ0Q6U6Z
|
|
||||||
H8IAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAASNQTFRF////
|
|
||||||
hpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCb
|
|
||||||
hpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCb
|
|
||||||
hpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCb
|
|
||||||
hpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCb
|
|
||||||
hpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCbhpCb
|
|
||||||
////f/Q5igAAAF90Uk5TAAAVQ26QsMjc7vMka6bhKITbEmwnmfcmpP2lFJX7AWPvZCC/XPEHmxjK
|
|
||||||
GSw4PjfyLfAa4xwGnL5lE+2UmvZyc9cziSOn4kWxssvf4JFEF4o02CmWXckIIsCL5BZw3fSQaiOm
|
|
||||||
AAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+QDHAczCg2f+2gAAAxo
|
|
||||||
elRYdFJhdyBwcm9maWxlIHR5cGUgaWNjAABYha2Za64jKxKE/9cqZgk8kgSWAwkpzf43MB9lu9un
|
|
||||||
7dbVlcanUdkUBUk+IoLq679m13/45Jz1CuezQtNYQw1qIcW7S5fuKjWVJFVSCqWVXkYKoW7hNl/C
|
|
||||||
DCHyPXJlIo2aa65BYgkliIXn58/ff/ncqzqrXq8ft2E5rV+W/cvP9e+Gx6iipWbNj5/l2a/pUjnd
|
|
||||||
uh43pjyue7PhUFN9/M7jcQ0p14rnwqvf2nMBuXDn7cbHjWWvG1rf+z386n8fL0HeJxIi8zBV4mOF
|
|
||||||
FowgxJq03r8tvSwKVbG/Pi0yefbncOlm113340Z93WBrSkro84H5msgI0FJ/9e+npald+jeL6t8t
|
|
||||||
1S+WXveN+nnjR3R+f5pg/8k7Zcraf9/4l+H/++f/PxEutFr+3ErS5xfTVpO0kh4/46M/rqZTXWZJ
|
|
||||||
18eNrUm2+Ed/f0bqz/5GVFopnxNRwpKfCffeLxpFZH5Z2FjYPidS7aLSPy2qIr3IRz+xlVLyx0Rk
|
|
||||||
s5M/n1tTfJQlfvST/2/Ofpuo6CzlywOsLf2FT2/jhzZSt35OxNaKyv7ozyyQP310JsLS/WVrtZRU
|
|
||||||
yqcvdB/Q/bpl/Rb+Vd9y6cfWOlv7tFTO1qR9CT9hJgpfLYrfLK25tG/hx07y5evWypcFDlpK+rK1
|
|
||||||
U9e/2ePHRCb1s0RAAiUIn5l9c5l8eYCVIajPhBy4YpTPPDooFsLZwvHHuZ45z05PxPJpjAGQQrlz
|
|
||||||
rR7cYvlf1S9ZnVU7md0BunYiLU+OI7pBgGPNaSdLTJfvdj4vBv6BR56PRb8IExIL7RdVv33UHoSW
|
|
||||||
e3oEJfV5fR2Y9p3Bs7UH37n7t3GprsdE+5nZFnoCPgpZrT9G/kag88CBFn8Kh35Y9BDmYRDaup4O
|
|
||||||
ZII8H0+eHBIeOghwolR4qPDQQU9tB0tx8ZElOLySJg1XtCMiGhMc+OlM0JmgM8HgocFDg4cmD00e
|
|
||||||
mjxk8aDuIbqjhWg8s/i+j7M3P45PDgQ5g3zi7vNXaQuXZ1qneYg4Nybus4tIGsS82Sl9p0QisY2Y
|
|
||||||
f1IJhqNxs3ITs2NjEiyOnTFYGwcLDMZOrpMrFkbjuiITLb5sOjZX5wEf5GCmHfUmtBUSbJKS49sW
|
|
||||||
EgmbwCXyn8Y9Fk+ViVLlwUZn2+QDA4lxGkwyeZgVE/5I+CKdIOGC5CWcXMxIv8w2Mloxn6hlVoE3
|
|
||||||
aB4Oe2TlJlHJdVMN/O78ZvI8+D35bRX5w/h9tIYheTrBJmqCM08ZSVrUyQyAFBkAABBuqZG2gzSj
|
|
||||||
RgZZwT0sFUMZMZngkpMl4iRkITIlWihEo+RB6nTqtYXC3gu5UpqSSrRBmwAAKFPYaiHahSjffxGL
|
|
||||||
NFGXR15giRI91RG0TnLPgvYVdPitTXXFoDsH2CqcWq6kRGXxmhcQ4BcSjIxBYFVWrp2bOLpOD3Uh
|
|
||||||
z1i1ekMXTRLYQyN/mpxSHiTzDq3xG2vbGBeJTQcPtV1DIxk7yXuqurPNjgMO53eUUG98R5R2HN6N
|
|
||||||
7yzSGT9iDCPBawPTBjkx2NKgbgYpMIjQwBcDcTr8BJo/5P/MPUwAbSpXKmgyds5TPbDIxPOTEFok
|
|
||||||
BdNAYOZgxdCTJRi5ZaMGO2VG4hqTrvNHgi45pTbCwi2rEbVFyi9jwNph4Y9NOmyybxM9hGrY1cPu
|
|
||||||
k3LUsHHB3puy7MHZvpPlDjJ4xUeOs3zm4Ozb9ylvvJSIgGTChAMqJneKa9K/6nkyUsj3vygOjK4Y
|
|
||||||
m10xDtxlPcbd2FyN1FVMUmJSiZROTCPHZLSdsZWWaCyCUAUAaIOUtnKBBIXShFOoPBQQ2n5EaROO
|
|
||||||
WaDDZlHAIaYIeUG0GuEMwGLGMldkV6fajl6+0OQ9KnCuzaPOhKXkqY9YwYYqMcJ/sbYe69iR3IrV
|
|
||||||
a2zgCHUeoc3Y2oht+BXb4gcP3pDDlrrO2Nlpn+TuWpEKiyONOHDNqBU42pHUiAN/Ynececap+YqT
|
|
||||||
GSeWzDXiQSBSIFrhWmc0/GNm0XDwArQWVbzqgbAeiXRcTLZR/Bwqr7jxye4SNwm1d49OODzv6Mp3
|
|
||||||
+n169D3SDaX4nsJLpDlJB2aAdUQK/GsXm2b5gTdtJ55KKdWE0Eqp8XV4SmtBOiOBWwmsSrlLypYS
|
|
||||||
Ry/A0pPI4thrV5Ixk6xxqisVBhelwdzFaA6DMzH0no5i10nbjfjSpKdaqTKer4uJGgnfsqdGCrWB
|
|
||||||
IUsSYJI6k+L41PtKoAnFg4G5JDRjGn2Cxg6IpDSzgsr9SpOBpEQ60GawuSlCgQltVTDT0sIvq2pa
|
|
||||||
rLw2eUnWINAThgHhNVEyiQ1eYPlI3mNytuIOcmW0jFJ8g+pdk0IglYXw4aI4uXrKxzZIPiMewH3J
|
|
||||||
DLxyBpxz9Zxny5mQCcbKEWF9Z1ktl5hzAaAL4qxYysVXPucM1GzWsbPuDlfIhXKDMLrlipxtUXKD
|
|
||||||
bVqz3KyBPuUOUmehPtm4A3dMM6pkdgqxeGbDeeq8YJiR525EtGYkVTa2ZDvnRYSplkyeZhAm73RO
|
|
||||||
ujQYCSQhPDE7953M8J0umDFTs3Ig8oAPriULlZIeRpZu7IGpsD3NIskxhXLHfFaDuWBDQcZLG5dQ
|
|
||||||
4Qc0oDI0KKBc1uZwj5pX5OGYog614aVaJ3kHxUFYTaZQx9KAR4oFWZgveA9Fz+CRqwxWwhCY3eQ+
|
|
||||||
eTDx3IlgDyGoYnMIKSWMktWJxeKshgjYui8hFx5kyQEBX4qvQ/QAdkUozgP1YAVcCBwUCrOQBCVV
|
|
||||||
yhpQB9YKYYRXx1XwWDnaXZoTZY5YiYMlBxtCXQDholCrDk5MjjbkSFCpJFi8NDKwISKbzdIjE3WO
|
|
||||||
VX0eyqtl8NgYNC9l4rXZaZwcLNNAMVtaFtau2sqyXkCmwlG07LmuAuRD4bFAAEgtjChkFrPBBuAv
|
|
||||||
dwYgjmEJl6ZuikxSMFtJLs1Qi5yYd7k443Wyh6VQtmWfdyiLs19Sqlzhda2dJGYMha3Qs7ZtHLiy
|
|
||||||
9j60+0kUjhHdLzRC1cnKc3B1x9CqNlzPe7eF0FgTzQ+nbE7428gGPO4IBQfSUWPwPLIFMVrhE8C5
|
|
||||||
VDQCAaacB4agIDOeoP7IUHm8ejLCBNig3CvOOfuqCrCrt8pB+CLPMgQDUeiujcE9jdobsmEDW6J1
|
|
||||||
jILIkQrA10kdAazVGu2QCqcVSoggxqtuIHAzAHRAwUj1jcqSipJoR1I0OKpFW4BIAOkAYBcgoTaK
|
|
||||||
FPpcjVRvsvPFgZRYjok6Ot5N51Db7j11b8zYOMsCBrt1QLt3bPXdKMs2bDSYvuHCNt2uZgpi0LmA
|
|
||||||
rsUzy73tOzi7eYY2O7oZ7YZcQ21ALajPOFqHPgDt0aHyUyxXB0yAHDRVo+iok1K8F+vUIYkDAFUU
|
|
||||||
E97sOKJzCO5tYhhJhbjr3Y0kaH2scvUJ1s/hHRrsxhjz2hcpBgt1iK/vsUmXxUZndx8ITOTtglWl
|
|
||||||
wqwKutO6XhxflMKnwZRCVVHmA1oaZbAYUgUPDJKGLNsDq0aDSJrJAGAZ0dEyNkbza0ApY1Jmc3co
|
|
||||||
3YahBJegReDyDXdsaP8kMBad0yAytqC6xkSGzLjgK4HwLV2TYEzAlx1Sp51SjnmWNtBnGF77BNjI
|
|
||||||
ujaxhtKmvBfHGVi1W4JT5hyzMNO++HbyLE7r8yieuZC27AAYB9Mc5odKOYmimTnlbBSFVpQDEF8c
|
|
||||||
0oMRAJK85DJw1oBbKzLZGdkgHKuNE4ccQeJwybI2zeAP1uoIlGb4wgALm1ONmYx9XAZjGXgGcaiB
|
|
||||||
CgjXas4GOM2sMJFt2dgMMjT74rS0gPsF3eDGvsDeBdCuwnkN/ChLFzDOfPjhvB1B7ZKbiiAm7oRr
|
|
||||||
DZ/4HvHLKcTaQH4hgrvC4LZQPMtTv5YPh6U4D0z6hfOOoW8KNLYmRuYNeG1oZIsbVQjOh7XPy13g
|
|
||||||
YpMAMCIyYNqFxiL3FwEFVMcGNUGXib+B1o3w3asbMqFt4rod5nNDRxfOE7t4RC5Ed6SMXZ5jdyDJ
|
|
||||||
BQKBd0BrsHdtV12uJ2gozcaYRtYhdSiB6iCLD5T8rNUJJ4XSLjzYfVEjW6bvZWAkZevOueu8z45v
|
|
||||||
rxNeb7qAYb1fNDjQdfes0u83EZD7Ywjb+/Hi+/cnvr0zee/7db3+duPP/h8v0H8beVt2XjfcE5Wn
|
|
||||||
AQTmYdhrD/X5fwPP/wooz5cl5fGGHn2a8+t6vXX8+KCkEGD5/fUM55r58AmsevzoQO5jcHr4CLqv
|
|
||||||
9w0G3tc1Hm9tEEfnYuHE8O1d/+vDZm/T2ni8qz1niwWYkPcl/MPn+h8EJEi9UWMHFQAACKJJREFU
|
|
||||||
eNrt3GlfVWUYRvEehBBkiEQJGUIGy8LEMMPScKqktGywefj+3yJ72S9z733Ofro8Z63/J7ge7vXG
|
|
||||||
w5FXXpEkSZIkSZIkSZIkSWOvtDRxanLq1enTM7N/Kmh25vT0q1OTpyba3q2XAM7MzS+kX65/Wpif
|
|
||||||
O/P/BLD42lL6sXq+pdcWawfw+tnp9Cv1ItNnX68YwPK58+kHqsn5c8uVAlh5YzX9OLWxemGlQgBr
|
|
||||||
6xvph6mtjfW1ngPYfHMr/Sh1sXVxs88AtnfSD1JXO9v9BbC7l36Nutvb7SmAS2+ln6LBvHWpjwDe
|
|
||||||
vpx+hwZ1+Z3hA3jXD31H2MK7wwawP5N+g4Yxsz9cAFf8Zd+Im70yTADvef+Rd/W9wQM4uJZer+Fd
|
|
||||||
Oxg0gPcP09vVh8PrgwXwgb/5HRPTHwwUgJ//jI0bgwSwm16t/ux2D2Dbz//HyN521wA2P0xvVp92
|
|
||||||
NjsGcDG9WP066hbAzY/Sg9WvmZudAvg4vVd9W+8SwIrf/xs7GysdAriQXqv+zbcPYNnvf4+h1cXW
|
|
||||||
AdxKb1UNt9oGsHY7PVU13F5rGcAn6aWq47hlAHfSQ1XHnXYB3E3vVC13WwVwLz1TtdxrFYB//2Fs
|
|
||||||
LbUJ4H56peq53yKAufRI1TPXIoAH6ZGq50GLAD5Nj1Q9h80BTKQ3qqaJxgBOpSeqps8aA5hMT1RN
|
|
||||||
k40BTKUnqqapxgA+T09UTQ8bAzhJT1RNJ40BfJGeqJq+bAzAvwc41rYaA7ianqiaZhsDSC9UXQYA
|
|
||||||
ZwBwBgBnAHAGAGcAcAYAZwBwBgBnAHAGAGcAcAYAZwBwBgBnAHAGAGcAcAYAZwBwBgBnAHAGAGcA
|
|
||||||
cAYAZwBwBgBnAHAGAGcAcAYAZwBwBgBnAHAGAGcAcAYAZwBwBgBnAHAGAGcAcAYAZwBwBgBnAHAG
|
|
||||||
AGcAcAYAZwBwBgBnAHAGAGcAcAYAZwBwBgBnAHAGAGcAcAYAZwBwBgBnAHAGAGcAcAYAZwBwBgBn
|
|
||||||
AHAGADeCAZT+pJ/yEjAAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgD
|
|
||||||
gDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4Az
|
|
||||||
ADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4
|
|
||||||
A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOA
|
|
||||||
MwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMA
|
|
||||||
OAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgD
|
|
||||||
gDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4Az
|
|
||||||
ADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4
|
|
||||||
A4BjB/D/S//w/sUADMAADMAADMAADMAADMAADMAADMAADMAADMAADMAADCA98N/SNzSAsPQNDSAs
|
|
||||||
fUMDCEvf0ADC0jc0gLD0DQ0gLH1DAwhL39AAwtI3NICw9A0NICx9QwMIS9/QAMLSNzSAsPQNDSAs
|
|
||||||
fUMDCEvf0ADC0jc0gLD0DQ0gLH1DAwhL39AAwtI3NICw9A0NICx9QwMIS9/QAMLSNzSAsPQNDSAs
|
|
||||||
fUMDCEvf0ADC0jc0gLD0DQ0gLH1DAwhL39AAwtI3NICw9A0NICx9QwMIS9/QAMLSNzSAsPQNDSAs
|
|
||||||
fUMDCEvf0ADC0jc0gLD0DQ0gLH1DAwhL39AAwtI3NICw9A0NICx9QwPQGDEAOAOAMwA4A4AzADgD
|
|
||||||
gDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4Az
|
|
||||||
ADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4
|
|
||||||
A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzADgDgDMAOAOAMwA4A4AzALjGAK6mF6qm
|
|
||||||
2cYAttITVdNWYwBfpCeqpi8bAzhJT1RNJ40BfJ6eqJoeNgYwlZ6omh41BjCZnqiaJhsDeJyeqJo+
|
|
||||||
awxgIj1RNX3VGEA5TG9UPYelOYAH6ZGq50GLAObSI1XP2RYB3E+PVD33WwRQTqdXqpbTpU0AX6dn
|
|
||||||
qpZ7rQK4m56pWp60CqDcSe9UHd+UdgEcp4eqjuOWAazdTi9VDbfXWgZQbqWnqoZbpW0Ai6vprerf
|
|
||||||
6retAyjz6bHq33xpH8DKRnqt+rax3CGAsp6eq759V7oEcHMmvVf92rrZKYBylB6sfh2VbgFs7qQX
|
|
||||||
q087mx0DKNt76c3qz9526RpA2U2PVn++L90DKDfSq9WXG2WQAC49Te9WP04uDRRAue4XhMfC4fUy
|
|
||||||
WADl4Fp6u4Z37aAMGkDZn02v17Bm98vgAZQrFjDiZq+UYQIo+/7FkJG2tV+GC6AcLKTfoMEtHJRh
|
|
||||||
AyjvXE6/QoO6/H4ZPoDyg58IjagbP5Q+Aihl198LjKC97593y4ECKD/+lH6Nuvrpx9JfAGXzyH8N
|
|
||||||
jJSto83SZwClrK37PcGRsbG+9l93HDiAUpYv+G3xkbA6v/zfVxwigGcJnDuffpyanD+3+KIbDhVA
|
|
||||||
KRPH/s/Rl9qd44kXX3DIAJ558vNS+pV6vqWfnzSeb/gAnjkzN+9XBV4yv8zPnWlzu14C+Nuvj397
|
|
||||||
9PDp7zN/pF/O9sfM708fPvrt8a9t79YYgCRJkiRJkiRJkiRJGnl/AWNYpuT+rvp8AAAAJXRFWHRk
|
|
||||||
YXRlOmNyZWF0ZQAyMDIwLTAzLTI4VDA3OjUxOjEwKzAwOjAwJ6kuaQAAACV0RVh0ZGF0ZTptb2Rp
|
|
||||||
ZnkAMjAyMC0wMy0yOFQwNzo1MToxMCswMDowMFb0ltUAAAAodEVYdGljYzpjb3B5cmlnaHQAQ29w
|
|
||||||
eXJpZ2h0IEFwcGxlIEluYy4sIDIwMjAKut6wAAAAF3RFWHRpY2M6ZGVzY3JpcHRpb24ARGlzcGxh
|
|
||||||
eRcblbgAAAAASUVORK5CYII=" />
|
|
||||||
</svg>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.2 KiB |
@ -117,12 +117,11 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
try {
|
try {
|
||||||
await Promise.all(stores.map(store => store.loadAll()));
|
await Promise.all(stores.map(store => store.loadAll()));
|
||||||
const subscriptions = stores.map(store => store.subscribe());
|
const subscriptions = stores.map(store => store.subscribe());
|
||||||
|
await when(() => this.isUnmounting);
|
||||||
subscriptions.forEach(dispose => dispose()); // unsubscribe all
|
subscriptions.forEach(dispose => dispose()); // unsubscribe all
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
console.log("catched", error)
|
console.log("catched", error)
|
||||||
}
|
}
|
||||||
await when(() => this.isUnmounting);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
|||||||
@ -226,7 +226,7 @@ class SidebarNavItem extends React.Component<SidebarNavItemProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isHidden, subMenus = [], icon, text, url, children, className } = this.props;
|
const { id, isHidden, subMenus = [], icon, text, url, children, className } = this.props;
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -234,7 +234,7 @@ class SidebarNavItem extends React.Component<SidebarNavItemProps> {
|
|||||||
if (extendedView) {
|
if (extendedView) {
|
||||||
const isActive = this.isActive();
|
const isActive = this.isActive();
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("SidebarNavItem", className)}>
|
<div id={id} className={cssNames("SidebarNavItem", className)}>
|
||||||
<div className={cssNames("nav-item", { active: isActive })} onClick={this.toggleSubMenu}>
|
<div className={cssNames("nav-item", { active: isActive })} onClick={this.toggleSubMenu}>
|
||||||
{icon}
|
{icon}
|
||||||
<span className="link-text">{text}</span>
|
<span className="link-text">{text}</span>
|
||||||
|
|||||||
0
src/renderer/utils/__tests__/arrays.test.ts
Normal file
0
src/renderer/utils/arrays.ts
Normal file
@ -2,15 +2,25 @@
|
|||||||
|
|
||||||
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where your might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where your might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
||||||
|
|
||||||
## 3.5.0-beta.1 (current version)
|
## 3.5.0 (current version)
|
||||||
|
|
||||||
- Dynamic dashboard UI based on RBAC rules
|
- Dynamic dashboard UI based on RBAC rules (hides non-accessible menus)
|
||||||
- Show object reference for all objects
|
- Show object reference for all objects
|
||||||
- Unify scrollbars/paddings
|
- Unify scrollbars/paddings
|
||||||
|
- New logo
|
||||||
|
- Remove Helm release update checker
|
||||||
|
- Improve Helm release version detection
|
||||||
|
- Show owner reference on all resource details
|
||||||
- Fix: add arch node selector for hybrid clusters
|
- Fix: add arch node selector for hybrid clusters
|
||||||
- Fix pod shell command on Windows
|
- Fix pod shell command on Windows
|
||||||
|
- Fix app freeze after closing terminal on Windows
|
||||||
|
- Fix: use correct kubeconfig context on terminal when switching cluster
|
||||||
|
- Fix error when closing Lens on Windows
|
||||||
|
- Fix: deploy kube-state-metrics component only to amd64 nodes
|
||||||
- Translation correction: transit to transmit
|
- Translation correction: transit to transmit
|
||||||
- Remove Kontena reference from Lens logo
|
- Remove Kontena reference from Lens logo
|
||||||
|
- Track telemetry pref changed event
|
||||||
|
- Integration tests using spectron
|
||||||
|
|
||||||
## 3.4.0
|
## 3.4.0
|
||||||
|
|
||||||
@ -280,4 +290,3 @@ Here you can find description of changes we've built into each release. While we
|
|||||||
## 2.0.0
|
## 2.0.0
|
||||||
|
|
||||||
Initial release of the Lens desktop application. Basic functionality with auto-import of users local kubeconfig for cluster access.
|
Initial release of the Lens desktop application. Basic functionality with auto-import of users local kubeconfig for cluster access.
|
||||||
|
|
||||||
|
|||||||