1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'master' into extensions-api

This commit is contained in:
Jari Kolehmainen 2020-09-30 19:47:13 +03:00
commit de3849d22c
53 changed files with 1273 additions and 584 deletions

View File

@ -1,69 +1,446 @@
/*
Cluster tests are run if there is a pre-existing minikube cluster. Before running cluster tests the TEST_NAMESPACE
namespace is removed, if it exists, from the minikube cluster. Resources are created as part of the cluster tests in the
TEST_NAMESPACE namespace. This is done to minimize destructive impact of the cluster tests on an existing minikube
cluster and vice versa.
*/
import { Application } from "spectron" import { Application } from "spectron"
import * as util from "../helpers/utils" import * as util from "../helpers/utils"
import { spawnSync } from "child_process" import { spawnSync } from "child_process"
jest.setTimeout(30000) const describeif = (condition : boolean) => condition ? describe : describe.skip
const itif = (condition : boolean) => condition ? it : it.skip
jest.setTimeout(60000)
describe("Lens integration tests", () => {
const TEST_NAMESPACE = "integration-tests"
const BACKSPACE = "\uE003" const BACKSPACE = "\uE003"
describe("app start", () => {
let app: Application let app: Application
const appStart = async () => {
app = util.setup()
await app.start()
// Wait for splash screen to be closed
while (await app.client.getWindowCount() > 1);
await app.client.windowByIndex(0)
await app.client.waitUntilWindowLoaded()
}
const clickWhatsNew = async (app: Application) => { const clickWhatsNew = async (app: Application) => {
await app.client.waitUntilTextExists("h1", "What's new") await app.client.waitUntilTextExists("h1", "What's new")
await app.client.click("button.primary") await app.client.click("button.primary")
await app.client.waitUntilTextExists("h1", "Welcome") await app.client.waitUntilTextExists("h1", "Welcome")
} }
const addMinikubeCluster = async (app: Application) => { describe("app start", () => {
await app.client.click("div.add-cluster") beforeAll(appStart, 20000)
await app.client.waitUntilTextExists("div", "Select kubeconfig file")
await app.client.click("button.primary")
}
const waitForMinikubeDashboard = async (app: Application) => { afterAll(async () => {
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started") if (app && app.isRunning()) {
await app.client.getWindowCount() return util.tearDown(app)
await app.client.waitForExist(`iframe[name="minikube"]`)
await app.client.frame("minikube")
await app.client.waitUntilTextExists("span.link-text", "Cluster")
} }
})
beforeEach(async () => {
app = util.setup()
await app.start()
await app.client.waitUntilWindowLoaded()
// Wait for splash screen to be closed
while (await app.client.getWindowCount() > 1);
await app.client.windowByIndex(0)
await app.client.waitUntilWindowLoaded()
}, 20000)
it('shows "whats new"', async () => { it('shows "whats new"', async () => {
await clickWhatsNew(app) await clickWhatsNew(app)
}) })
it('allows to add a cluster', async () => { // Todo figure out how to access main menu to get these to work
const status = spawnSync("minikube status", { shell: true }) it.skip('shows "add cluster"', async () => {
await app.client.keys(['Shift', 'Meta', 'A'])
await app.client.waitUntilTextExists("h2", "Add Cluster")
await app.client.keys(['Shift', 'Meta'])
})
it.skip('shows "preferences"', async () => {
await app.client.keys(['Meta', ','])
await app.client.waitUntilTextExists("h2", "Preferences")
await app.client.keys('Meta')
})
it.skip('quits Lens"', async () => {
await app.client.keys(['Meta', 'Q'])
await app.client.keys('Meta')
})
})
const minikubeReady = (): boolean => {
// determine if minikube is running
let status = spawnSync("minikube status", { shell: true })
if (status.status !== 0) { if (status.status !== 0) {
console.warn("minikube not running, skipping test") console.warn("minikube not running")
return return false
} }
// Remove TEST_NAMESPACE if it already exists
status = spawnSync(`minikube kubectl -- get namespace ${TEST_NAMESPACE}`, { shell: true })
if (status.status === 0) {
console.warn(`Removing existing ${TEST_NAMESPACE} namespace`)
status = spawnSync(`minikube kubectl -- delete namespace ${TEST_NAMESPACE}`, { shell: true })
if (status.status !== 0) {
console.warn(`Error removing ${TEST_NAMESPACE} namespace: ${status.stderr.toString()}`)
return false
}
console.log(status.stdout.toString())
}
return true
}
const ready = minikubeReady()
const addMinikubeCluster = async (app: Application) => {
await app.client.click("div.add-cluster")
await app.client.waitUntilTextExists("div", "Select kubeconfig file")
await app.client.click("div.Select__control") // show the context drop-down list
await app.client.waitUntilTextExists("div", "minikube")
if (!await app.client.$("button.primary").isEnabled()) {
await app.client.click("div.minikube") // select minikube context
} // else the only context, which must be 'minikube', is automatically selected
await app.client.click("div.Select__control") // hide the context drop-down list (it might be obscuring the Add cluster(s) button)
await app.client.click("button.primary") // add minikube cluster
}
const waitForMinikubeDashboard = async (app: Application) => {
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started")
await app.client.waitForExist(`iframe[name="minikube"]`)
await app.client.frame("minikube")
await app.client.waitUntilTextExists("span.link-text", "Cluster")
}
describeif(ready)("cluster tests", () => {
let clusterAdded = false
const addCluster = async () => {
await clickWhatsNew(app) await clickWhatsNew(app)
await addMinikubeCluster(app) await addMinikubeCluster(app)
await waitForMinikubeDashboard(app) await waitForMinikubeDashboard(app)
await app.client.click('a[href="/nodes"]') await app.client.click('a[href="/nodes"]')
await app.client.waitUntilTextExists("div.TableCell", "Ready") await app.client.waitUntilTextExists("div.TableCell", "Ready")
}
describe("cluster add", () => {
beforeAll(appStart, 20000)
afterAll(async () => {
if (app && app.isRunning()) {
return util.tearDown(app)
}
}) })
it('allows to create a pod', async () => { it('allows to add a cluster', async () => {
const status = spawnSync("minikube status", { shell: true }) await addCluster()
if (status.status !== 0) { clusterAdded = true
console.warn("minikube not running, skipping test") })
return })
const appStartAddCluster = async () => {
if (clusterAdded) {
await appStart()
await addCluster()
} }
await clickWhatsNew(app) }
await addMinikubeCluster(app)
await waitForMinikubeDashboard(app) describe("cluster pages", () => {
beforeAll(appStartAddCluster, 40000)
afterAll(async () => {
if (app && app.isRunning()) {
return util.tearDown(app)
}
})
const tests : {
drawer?: string
drawerId?: string
pages: {
name: string,
href: string,
expectedSelector: string,
expectedText: string
}[]
}[] = [
{
drawer: "",
drawerId: "",
pages: [ {
name: "Cluster",
href: "cluster",
expectedSelector: "div.ClusterNoMetrics p",
expectedText: "Metrics are not available due"
}]
},
{
drawer: "",
drawerId: "",
pages: [ {
name: "Nodes",
href: "nodes",
expectedSelector: "h5.title",
expectedText: "Nodes"
}]
},
{
drawer: "Workloads",
drawerId: "workloads",
pages: [ {
name: "Overview",
href: "workloads",
expectedSelector: "h5.box",
expectedText: "Overview"
},
{
name: "Pods",
href: "pods",
expectedSelector: "h5.title",
expectedText: "Pods"
},
{
name: "Deployments",
href: "deployments",
expectedSelector: "h5.title",
expectedText: "Deployments"
},
{
name: "DaemonSets",
href: "daemonsets",
expectedSelector: "h5.title",
expectedText: "Daemon Sets"
},
{
name: "StatefulSets",
href: "statefulsets",
expectedSelector: "h5.title",
expectedText: "Stateful Sets"
},
{
name: "Jobs",
href: "jobs",
expectedSelector: "h5.title",
expectedText: "Jobs"
},
{
name: "CronJobs",
href: "cronjobs",
expectedSelector: "h5.title",
expectedText: "Cron Jobs"
} ]
},
{
drawer: "Configuration",
drawerId: "config",
pages: [ {
name: "ConfigMaps",
href: "configmaps",
expectedSelector: "h5.title",
expectedText: "Config Maps"
},
{
name: "Secrets",
href: "secrets",
expectedSelector: "h5.title",
expectedText: "Secrets"
},
{
name: "Resource Quotas",
href: "resourcequotas",
expectedSelector: "h5.title",
expectedText: "Resource Quotas"
},
{
name: "HPA",
href: "hpa",
expectedSelector: "h5.title",
expectedText: "Horizontal Pod Autoscalers"
},
{
name: "Pod Disruption Budgets",
href: "poddisruptionbudgets",
expectedSelector: "h5.title",
expectedText: "Pod Disruption Budgets"
} ]
},
{
drawer: "Network",
drawerId: "networks",
pages: [ {
name: "Services",
href: "services",
expectedSelector: "h5.title",
expectedText: "Services"
},
{
name: "Endpoints",
href: "endpoints",
expectedSelector: "h5.title",
expectedText: "Endpoints"
},
{
name: "Ingresses",
href: "ingresses",
expectedSelector: "h5.title",
expectedText: "Ingresses"
},
{
name: "Network Policies",
href: "network-policies",
expectedSelector: "h5.title",
expectedText: "Network Policies"
} ]
},
{
drawer: "Storage",
drawerId: "storage",
pages: [ {
name: "Persistent Volume Claims",
href: "persistent-volume-claims",
expectedSelector: "h5.title",
expectedText: "Persistent Volume Claims"
},
{
name: "Persistent Volumes",
href: "persistent-volumes",
expectedSelector: "h5.title",
expectedText: "Persistent Volumes"
},
{
name: "Storage Classes",
href: "storage-classes",
expectedSelector: "h5.title",
expectedText: "Storage Classes"
} ]
},
{
drawer: "",
drawerId: "",
pages: [ {
name: "Namespaces",
href: "namespaces",
expectedSelector: "h5.title",
expectedText: "Namespaces"
}]
},
{
drawer: "",
drawerId: "",
pages: [ {
name: "Events",
href: "events",
expectedSelector: "h5.title",
expectedText: "Events"
}]
},
{
drawer: "Apps",
drawerId: "apps",
pages: [ {
name: "Charts",
href: "apps/charts",
expectedSelector: "div.HelmCharts input",
expectedText: ""
},
{
name: "Releases",
href: "apps/releases",
expectedSelector: "h5.title",
expectedText: "Releases"
} ]
},
{
drawer: "Access Control",
drawerId: "users",
pages: [ {
name: "Service Accounts",
href: "service-accounts",
expectedSelector: "h5.title",
expectedText: "Service Accounts"
},
{
name: "Role Bindings",
href: "role-bindings",
expectedSelector: "h5.title",
expectedText: "Role Bindings"
},
{
name: "Roles",
href: "roles",
expectedSelector: "h5.title",
expectedText: "Roles"
},
{
name: "Pod Security Policies",
href: "pod-security-policies",
expectedSelector: "h5.title",
expectedText: "Pod Security Policies"
} ]
},
{
drawer: "Custom Resources",
drawerId: "custom-resources",
pages: [ {
name: "Definitions",
href: "crd/definitions",
expectedSelector: "h5.title",
expectedText: "Custom Resources"
} ]
},
];
tests.forEach(({ drawer = "", drawerId = "", pages }) => {
if (drawer !== "") {
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)
})
}
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.waitUntilTextExists(expectedSelector, expectedText)
})
})
if (drawer !== "") {
// hide the drawer
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()
})
}
})
})
describe("cluster operations", () => {
beforeEach(appStartAddCluster, 40000)
afterEach(async () => {
if (app && app.isRunning()) {
return util.tearDown(app)
}
})
it('shows default namespace', async () => {
expect(clusterAdded).toBe(true)
await app.client.click('a[href="/namespaces"]')
await app.client.waitUntilTextExists("div.TableCell", "default")
await app.client.waitUntilTextExists("div.TableCell", "kube-system")
})
it(`creates ${TEST_NAMESPACE} namespace`, async () => {
expect(clusterAdded).toBe(true)
await app.client.click('a[href="/namespaces"]')
await app.client.waitUntilTextExists("div.TableCell", "default")
await app.client.waitUntilTextExists("div.TableCell", "kube-system")
await app.client.click("button.add-button")
await app.client.waitUntilTextExists("div.AddNamespaceDialog", "Create Namespace")
await app.client.keys(`${TEST_NAMESPACE}\n`)
await app.client.waitForExist(`.name=${TEST_NAMESPACE}`)
})
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.click(".sidebar-nav #workloads span.link-text")
await app.client.waitUntilTextExists('a[href="/pods"]', "Pods") await app.client.waitUntilTextExists('a[href="/pods"]', "Pods")
await app.client.click('a[href="/pods"]') await app.client.click('a[href="/pods"]')
@ -76,24 +453,21 @@ describe("app start", () => {
await app.client.keys("apiVersion: v1\n") await app.client.keys("apiVersion: v1\n")
await app.client.keys("kind: Pod\n") await app.client.keys("kind: Pod\n")
await app.client.keys("metadata:\n") await app.client.keys("metadata:\n")
await app.client.keys(" name: nginx\n") await app.client.keys(" name: nginx-create-pod-test\n")
await app.client.keys(`namespace: ${TEST_NAMESPACE}\n`)
await app.client.keys(BACKSPACE + "spec:\n") await app.client.keys(BACKSPACE + "spec:\n")
await app.client.keys(" containers:\n") await app.client.keys(" containers:\n")
await app.client.keys("- name: nginx\n") await app.client.keys("- name: nginx-create-pod-test\n")
await app.client.keys(" image: nginx:alpine\n") await app.client.keys(" image: nginx:alpine\n")
// Create deployent // Create deployment
await app.client.waitForEnabled("button.Button=Create & Close") await app.client.waitForEnabled("button.Button=Create & Close")
await app.client.click("button.Button=Create & Close") await app.client.click("button.Button=Create & Close")
// Wait until first bits of pod appears on dashboard // Wait until first bits of pod appears on dashboard
await app.client.waitForExist(".name=nginx") await app.client.waitForExist(".name=nginx-create-pod-test")
// Open pod details // Open pod details
await app.client.click(".name=nginx") await app.client.click(".name=nginx-create-pod-test")
await app.client.waitUntilTextExists("div.drawer-title-text", "Pod: nginx") await app.client.waitUntilTextExists("div.drawer-title-text", "Pod: nginx-create-pod-test")
}) })
})
afterEach(async () => {
if (app && app.isRunning()) {
return util.tearDown(app)
}
}) })
}) })

View File

@ -25,7 +25,7 @@ msgstr ""
msgid "(as a percentage of request)" msgid "(as a percentage of request)"
msgstr "(as a percentage of request)" msgstr "(as a percentage of request)"
#: src/renderer/components/+workspaces/workspaces.tsx:108 #: src/renderer/components/+workspaces/workspaces.tsx:121
msgid "(current)" msgid "(current)"
msgstr "(current)" msgstr "(current)"
@ -57,11 +57,11 @@ msgstr "<0>{0}</0> successfully created"
#~ msgid "A HTTP proxy server URL (format: http://<address>:<port>)" #~ msgid "A HTTP proxy server URL (format: http://<address>:<port>)"
#~ msgstr "A HTTP proxy server URL (format: http://<address>:<port>)" #~ msgstr "A HTTP proxy server URL (format: http://<address>:<port>)"
#: src/renderer/components/input/input.validators.ts:40 #: src/renderer/components/input/input.validators.ts:46
msgid "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics." msgid "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics."
msgstr "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics." msgstr "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics."
#: src/renderer/components/+workspaces/workspaces.tsx:84 #: src/renderer/components/+workspaces/workspaces.tsx:93
msgid "A single workspaces contains a list of clusters and their full configuration." msgid "A single workspaces contains a list of clusters and their full configuration."
msgstr "A single workspaces contains a list of clusters and their full configuration." msgstr "A single workspaces contains a list of clusters and their full configuration."
@ -87,8 +87,8 @@ msgstr "Account Name"
msgid "Active" msgid "Active"
msgstr "Active" msgstr "Active"
#: src/renderer/components/+add-cluster/add-cluster.tsx:303 #: src/renderer/components/+add-cluster/add-cluster.tsx:288
#: src/renderer/components/cluster-manager/clusters-menu.tsx:118 #: src/renderer/components/cluster-manager/clusters-menu.tsx:130
msgid "Add Cluster" msgid "Add Cluster"
msgstr "Add Cluster" msgstr "Add Cluster"
@ -100,7 +100,7 @@ msgstr "Add Namespace"
msgid "Add RoleBinding" msgid "Add RoleBinding"
msgstr "Add RoleBinding" msgstr "Add RoleBinding"
#: src/renderer/components/+workspaces/workspaces.tsx:125 #: src/renderer/components/+workspaces/workspaces.tsx:138
msgid "Add Workspace" msgid "Add Workspace"
msgstr "Add Workspace" msgstr "Add Workspace"
@ -112,7 +112,7 @@ msgstr "Add bindings to {name}"
#~ msgid "Add cluster" #~ msgid "Add cluster"
#~ msgstr "Add cluster" #~ msgstr "Add cluster"
#: src/renderer/components/+add-cluster/add-cluster.tsx:320 #: src/renderer/components/+add-cluster/add-cluster.tsx:305
msgid "Add cluster(s)" msgid "Add cluster(s)"
msgstr "Add cluster(s)" msgstr "Add cluster(s)"
@ -136,7 +136,7 @@ msgstr "Add field"
#~ msgid "Adding clusters: <0>{0}</0>" #~ msgid "Adding clusters: <0>{0}</0>"
#~ msgstr "Adding clusters: <0>{0}</0>" #~ msgstr "Adding clusters: <0>{0}</0>"
#: src/renderer/components/+preferences/preferences.tsx:103 #: src/renderer/components/+preferences/preferences.tsx:111
msgid "Adding helm branch <0>{0}</0> has failed: {1}" msgid "Adding helm branch <0>{0}</0> has failed: {1}"
msgstr "Adding helm branch <0>{0}</0> has failed: {1}" msgstr "Adding helm branch <0>{0}</0> has failed: {1}"
@ -191,7 +191,7 @@ msgstr "Affinities"
msgid "Age" msgid "Age"
msgstr "Age" msgstr "Age"
#: src/renderer/components/+workspaces/workspaces.tsx:64 #: src/renderer/components/+workspaces/workspaces.tsx:65
msgid "All clusters within workspace will be cleared as well" msgid "All clusters within workspace will be cleared as well"
msgstr "All clusters within workspace will be cleared as well" msgstr "All clusters within workspace will be cleared as well"
@ -219,11 +219,11 @@ msgstr "Allocatable"
msgid "Allow Privilege Escalation" msgid "Allow Privilege Escalation"
msgstr "Allow Privilege Escalation" msgstr "Allow Privilege Escalation"
#: src/renderer/components/+preferences/preferences.tsx:162 #: src/renderer/components/+preferences/preferences.tsx:169
msgid "Allow telemetry & usage tracking" msgid "Allow telemetry & usage tracking"
msgstr "Allow telemetry & usage tracking" msgstr "Allow telemetry & usage tracking"
#: src/renderer/components/+preferences/preferences.tsx:154 #: src/renderer/components/+preferences/preferences.tsx:161
msgid "Allow untrusted Certificate Authorities" msgid "Allow untrusted Certificate Authorities"
msgstr "Allow untrusted Certificate Authorities" msgstr "Allow untrusted Certificate Authorities"
@ -281,7 +281,7 @@ msgstr "Applying.."
msgid "Apps" msgid "Apps"
msgstr "Apps" msgstr "Apps"
#: src/renderer/components/+workspaces/workspaces.tsx:61 #: src/renderer/components/+workspaces/workspaces.tsx:62
msgid "Are you sure you want remove workspace <0>{0}</0>?" msgid "Are you sure you want remove workspace <0>{0}</0>?"
msgstr "Are you sure you want remove workspace <0>{0}</0>?" msgstr "Are you sure you want remove workspace <0>{0}</0>?"
@ -293,7 +293,7 @@ msgstr "Are you sure you want to drain <0>{nodeName}</0>?"
msgid "Arguments" msgid "Arguments"
msgstr "Arguments" msgstr "Arguments"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:108 #: src/renderer/components/+landing-page/landing-page.tsx:27
msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button."
msgstr "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." msgstr "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button."
@ -323,7 +323,7 @@ msgstr "Binding targets"
msgid "Bindings" msgid "Bindings"
msgstr "Bindings" msgstr "Bindings"
#: src/renderer/components/+add-cluster/add-cluster.tsx:251 #: src/renderer/components/+add-cluster/add-cluster.tsx:236
msgid "Browse" msgid "Browse"
msgstr "Browse" msgstr "Browse"
@ -402,7 +402,7 @@ msgstr "CPU requests"
msgid "CPU:" msgid "CPU:"
msgstr "CPU:" msgstr "CPU:"
#: src/renderer/components/+workspaces/workspaces.tsx:119 #: src/renderer/components/+workspaces/workspaces.tsx:133
#: src/renderer/components/confirm-dialog/confirm-dialog.tsx:44 #: src/renderer/components/confirm-dialog/confirm-dialog.tsx:44
#: src/renderer/components/dock/info-panel.tsx:97 #: src/renderer/components/dock/info-panel.tsx:97
#: src/renderer/components/wizard/wizard.tsx:130 #: src/renderer/components/wizard/wizard.tsx:130
@ -422,7 +422,7 @@ msgstr "Cancel"
msgid "Capacity" msgid "Capacity"
msgstr "Capacity" msgstr "Capacity"
#: src/renderer/components/+preferences/preferences.tsx:153 #: src/renderer/components/+preferences/preferences.tsx:160
msgid "Certificate Trust" msgid "Certificate Trust"
msgstr "Certificate Trust" msgstr "Certificate Trust"
@ -501,7 +501,7 @@ msgstr "Cluster IP"
msgid "Cluster Issuers" msgid "Cluster Issuers"
msgstr "Cluster Issuers" msgstr "Cluster Issuers"
#: src/renderer/components/+preferences/preferences.tsx:126 #: src/renderer/components/+preferences/preferences.tsx:134
msgid "Color Theme" msgid "Color Theme"
msgstr "Color Theme" msgstr "Color Theme"
@ -712,7 +712,6 @@ msgid "Cron Jobs"
msgstr "Cron Jobs" msgstr "Cron Jobs"
#: src/renderer/components/+workloads/workloads.tsx:77 #: src/renderer/components/+workloads/workloads.tsx:77
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:67
msgid "CronJobs" msgid "CronJobs"
msgstr "CronJobs" msgstr "CronJobs"
@ -759,7 +758,6 @@ msgid "Daemon Sets"
msgstr "Daemon Sets" msgstr "Daemon Sets"
#: src/renderer/components/+workloads/workloads.tsx:53 #: src/renderer/components/+workloads/workloads.tsx:53
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:57
msgid "DaemonSets" msgid "DaemonSets"
msgstr "DaemonSets" msgstr "DaemonSets"
@ -784,11 +782,15 @@ msgstr "Default Add Capabilities"
msgid "Default Runtime Class Name" msgid "Default Runtime Class Name"
msgstr "Default Runtime Class Name" msgstr "Default Runtime Class Name"
#: src/renderer/components/+preferences/kubectl-binaries.tsx:30
msgid "Default:"
msgstr "Default:"
#: src/renderer/components/+custom-resources/custom-resources.tsx:22 #: src/renderer/components/+custom-resources/custom-resources.tsx:22
msgid "Definitions" msgid "Definitions"
msgstr "Definitions" msgstr "Definitions"
#: src/renderer/components/+workspaces/workspaces.tsx:113 #: src/renderer/components/+workspaces/workspaces.tsx:126
#: src/renderer/components/menu/menu-actions.tsx:84 #: src/renderer/components/menu/menu-actions.tsx:84
msgid "Delete" msgid "Delete"
msgstr "Delete" msgstr "Delete"
@ -799,12 +801,11 @@ msgstr "Deploy Revisions"
#: src/renderer/components/+workloads/workloads.tsx:45 #: src/renderer/components/+workloads/workloads.tsx:45
#: src/renderer/components/+workloads-deployments/deployments.tsx:57 #: src/renderer/components/+workloads-deployments/deployments.tsx:57
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:47
msgid "Deployments" msgid "Deployments"
msgstr "Deployments" msgstr "Deployments"
#: src/renderer/components/+apps-helm-charts/helm-charts.tsx:65 #: src/renderer/components/+apps-helm-charts/helm-charts.tsx:65
#: src/renderer/components/+workspaces/workspaces.tsx:118 #: src/renderer/components/+workspaces/workspaces.tsx:131
msgid "Description" msgid "Description"
msgstr "Description" msgstr "Description"
@ -817,7 +818,7 @@ msgstr "Desired Healthy"
msgid "Desired number of replicas" msgid "Desired number of replicas"
msgstr "Desired number of replicas" msgstr "Desired number of replicas"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:64 #: src/renderer/components/cluster-manager/clusters-menu.tsx:65
msgid "Disconnect" msgid "Disconnect"
msgstr "Disconnect" msgstr "Disconnect"
@ -831,7 +832,7 @@ msgstr "Disk"
msgid "Disk:" msgid "Disk:"
msgstr "Disk:" msgstr "Disk:"
#: src/renderer/components/+preferences/preferences.tsx:158 #: src/renderer/components/+preferences/preferences.tsx:165
msgid "Does not affect cluster communications!" msgid "Does not affect cluster communications!"
msgstr "Does not affect cluster communications!" msgstr "Does not affect cluster communications!"
@ -840,14 +841,22 @@ msgid "Domains"
msgstr "Domains" msgstr "Domains"
#: src/renderer/components/+preferences/preferences.tsx:129 #: src/renderer/components/+preferences/preferences.tsx:129
msgid "Download Mirror" #~ msgid "Download Mirror"
msgstr "Download Mirror" #~ msgstr "Download Mirror"
#: src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx:90 #: src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx:90
msgid "Download file" msgid "Download file"
msgstr "Download file" msgstr "Download file"
#: src/renderer/components/+preferences/preferences.tsx:130 #: src/renderer/components/+preferences/kubectl-binaries.tsx:39
msgid "Download kubectl binaries"
msgstr "Download kubectl binaries"
#: src/renderer/components/+preferences/kubectl-binaries.tsx:37
msgid "Download kubectl binaries matching to Kubernetes cluster verison."
msgstr "Download kubectl binaries matching to Kubernetes cluster verison."
#: src/renderer/components/+preferences/kubectl-binaries.tsx:41
msgid "Download mirror for kubectl" msgid "Download mirror for kubectl"
msgstr "Download mirror for kubectl" msgstr "Download mirror for kubectl"
@ -873,7 +882,7 @@ msgstr "Duration"
msgid "E-mail" msgid "E-mail"
msgstr "E-mail" msgstr "E-mail"
#: src/renderer/components/+workspaces/workspaces.tsx:112 #: src/renderer/components/+workspaces/workspaces.tsx:125
#: src/renderer/components/menu/menu-actions.tsx:80 #: src/renderer/components/menu/menu-actions.tsx:80
#: src/renderer/components/menu/menu-actions.tsx:81 #: src/renderer/components/menu/menu-actions.tsx:81
msgid "Edit" msgid "Edit"
@ -1000,7 +1009,7 @@ msgstr "From <0>{from}</0> to <1>{to}</1>"
msgid "Fs Group" msgid "Fs Group"
msgstr "Fs Group" msgstr "Fs Group"
#: src/renderer/components/+landing-page/landing-page.tsx:23 #: src/renderer/components/+landing-page/landing-page.tsx:37
msgid "Get started by associating one or more clusters to Lens." msgid "Get started by associating one or more clusters to Lens."
msgstr "Get started by associating one or more clusters to Lens." msgstr "Get started by associating one or more clusters to Lens."
@ -1022,7 +1031,7 @@ msgstr "Groups"
msgid "HPA" msgid "HPA"
msgstr "HPA" msgstr "HPA"
#: src/renderer/components/+preferences/preferences.tsx:147 #: src/renderer/components/+preferences/preferences.tsx:137
msgid "HTTP Proxy" msgid "HTTP Proxy"
msgstr "HTTP Proxy" msgstr "HTTP Proxy"
@ -1030,7 +1039,7 @@ msgstr "HTTP Proxy"
#~ msgid "HTTP Proxy server. Used for communicating with Kubernetes API." #~ msgid "HTTP Proxy server. Used for communicating with Kubernetes API."
#~ msgstr "HTTP Proxy server. Used for communicating with Kubernetes API." #~ msgstr "HTTP Proxy server. Used for communicating with Kubernetes API."
#: src/renderer/components/+preferences/preferences.tsx:132 #: src/renderer/components/+preferences/preferences.tsx:145
msgid "Helm" msgid "Helm"
msgstr "Helm" msgstr "Helm"
@ -1050,7 +1059,7 @@ msgstr "Helm Install: {repo}/{name}"
msgid "Helm Upgrade: {0}" msgid "Helm Upgrade: {0}"
msgstr "Helm Upgrade: {0}" msgstr "Helm Upgrade: {0}"
#: src/renderer/components/+preferences/preferences.tsx:47 #: src/renderer/components/+preferences/preferences.tsx:51
msgid "Helm branch <0>{0}</0> already in use" msgid "Helm branch <0>{0}</0> already in use"
msgstr "Helm branch <0>{0}</0> already in use" msgstr "Helm branch <0>{0}</0> already in use"
@ -1157,11 +1166,11 @@ msgstr "Installation complete!"
msgid "Installing..." msgid "Installing..."
msgstr "Installing..." msgstr "Installing..."
#: src/renderer/components/input/input.validators.ts:44 #: src/renderer/components/input/input.validators.ts:50
msgid "Invalid account ID" msgid "Invalid account ID"
msgstr "Invalid account ID" msgstr "Invalid account ID"
#: src/renderer/components/input/input.validators.ts:15 #: src/renderer/components/input/input.validators.ts:16
msgid "Invalid number" msgid "Invalid number"
msgstr "Invalid number" msgstr "Invalid number"
@ -1197,7 +1206,6 @@ msgstr "Job name"
#: src/renderer/components/+workloads/workloads.tsx:69 #: src/renderer/components/+workloads/workloads.tsx:69
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:62 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:62
#: src/renderer/components/+workloads-jobs/jobs.tsx:36 #: src/renderer/components/+workloads-jobs/jobs.tsx:36
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:62
msgid "Jobs" msgid "Jobs"
msgstr "Jobs" msgstr "Jobs"
@ -1241,6 +1249,10 @@ msgstr "Kubeconfig"
msgid "Kubeconfig File" msgid "Kubeconfig File"
msgstr "Kubeconfig File" msgstr "Kubeconfig File"
#: src/renderer/components/+preferences/kubectl-binaries.tsx:35
msgid "Kubectl Binary"
msgstr "Kubectl Binary"
#: src/renderer/components/+nodes/node-details.tsx:98 #: src/renderer/components/+nodes/node-details.tsx:98
msgid "Kubelet version" msgid "Kubelet version"
msgstr "Kubelet version" msgstr "Kubelet version"
@ -1357,7 +1369,7 @@ msgstr "Max Pods"
msgid "Max Unavailable" msgid "Max Unavailable"
msgstr "Max Unavailable" msgstr "Max Unavailable"
#: src/renderer/components/input/input.validators.ts:35 #: src/renderer/components/input/input.validators.ts:41
msgid "Maximum length is {maxLength}" msgid "Maximum length is {maxLength}"
msgstr "Maximum length is {maxLength}" msgstr "Maximum length is {maxLength}"
@ -1433,7 +1445,7 @@ msgstr "Min Pods"
msgid "Minimize" msgid "Minimize"
msgstr "Minimize" msgstr "Minimize"
#: src/renderer/components/input/input.validators.ts:30 #: src/renderer/components/input/input.validators.ts:36
msgid "Minimum length is {minLength}" msgid "Minimum length is {minLength}"
msgstr "Minimum length is {minLength}" msgstr "Minimum length is {minLength}"
@ -1497,7 +1509,7 @@ msgstr "Mounts"
#: src/renderer/components/+workloads-pods/pods.tsx:74 #: src/renderer/components/+workloads-pods/pods.tsx:74
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:50 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:50
#: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:40 #: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:40
#: src/renderer/components/+workspaces/workspaces.tsx:117 #: src/renderer/components/+workspaces/workspaces.tsx:130
#: src/renderer/components/dock/edit-resource.tsx:90 #: src/renderer/components/dock/edit-resource.tsx:90
#: src/renderer/components/kube-object/kube-object-meta.tsx:20 #: src/renderer/components/kube-object/kube-object-meta.tsx:20
msgid "Name" msgid "Name"
@ -1565,7 +1577,7 @@ msgstr "Namespaces"
msgid "Namespaces: {0}" msgid "Namespaces: {0}"
msgstr "Namespaces: {0}" msgstr "Namespaces: {0}"
#: src/renderer/components/+preferences/preferences.tsx:157 #: src/renderer/components/+preferences/preferences.tsx:164
msgid "Needed with some corporate proxies that do certificate re-writing." msgid "Needed with some corporate proxies that do certificate re-writing."
msgstr "Needed with some corporate proxies that do certificate re-writing." msgstr "Needed with some corporate proxies that do certificate re-writing."
@ -1626,7 +1638,7 @@ msgstr "No Nodes Available."
#~ msgid "No contexts available or they already added" #~ msgid "No contexts available or they already added"
#~ msgstr "No contexts available or they already added" #~ msgstr "No contexts available or they already added"
#: src/renderer/components/+add-cluster/add-cluster.tsx:275 #: src/renderer/components/+add-cluster/add-cluster.tsx:260
msgid "No contexts available or they have been added already" msgid "No contexts available or they have been added already"
msgstr "No contexts available or they have been added already" msgstr "No contexts available or they have been added already"
@ -1742,7 +1754,7 @@ msgid "Organization"
msgstr "Organization" msgstr "Organization"
#: src/renderer/components/+workloads/workloads.tsx:29 #: src/renderer/components/+workloads/workloads.tsx:29
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:35 #: src/renderer/components/+workloads-overview/overview-statuses.tsx:45
msgid "Overview" msgid "Overview"
msgstr "Overview" msgstr "Overview"
@ -1758,7 +1770,7 @@ msgstr "Parallelism"
msgid "Parameters" msgid "Parameters"
msgstr "Parameters" msgstr "Parameters"
#: src/renderer/components/+add-cluster/add-cluster.tsx:245 #: src/renderer/components/+add-cluster/add-cluster.tsx:230
msgid "Paste as text" msgid "Paste as text"
msgstr "Paste as text" msgstr "Paste as text"
@ -1848,7 +1860,6 @@ msgstr "Pod shell"
#: src/renderer/components/+workloads/workloads.tsx:37 #: src/renderer/components/+workloads/workloads.tsx:37
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:47 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:47
#: src/renderer/components/+workloads-deployments/deployments.tsx:60 #: src/renderer/components/+workloads-deployments/deployments.tsx:60
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:42
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:89 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:89
#: src/renderer/components/+workloads-pods/pods.tsx:73 #: src/renderer/components/+workloads-pods/pods.tsx:73
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:52 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:52
@ -1899,7 +1910,7 @@ msgstr "Privileged"
#~ msgid "Pro-Tip: paste kubeconfig to collect available contexts" #~ msgid "Pro-Tip: paste kubeconfig to collect available contexts"
#~ msgstr "Pro-Tip: paste kubeconfig to collect available contexts" #~ msgstr "Pro-Tip: paste kubeconfig to collect available contexts"
#: src/renderer/components/+add-cluster/add-cluster.tsx:263 #: src/renderer/components/+add-cluster/add-cluster.tsx:248
msgid "Pro-Tip: paste kubeconfig to get available contexts" msgid "Pro-Tip: paste kubeconfig to get available contexts"
msgstr "Pro-Tip: paste kubeconfig to get available contexts" msgstr "Pro-Tip: paste kubeconfig to get available contexts"
@ -1907,7 +1918,7 @@ msgstr "Pro-Tip: paste kubeconfig to get available contexts"
#~ msgid "Pro-Tip: paste kubeconfig to parse available contexts" #~ msgid "Pro-Tip: paste kubeconfig to parse available contexts"
#~ msgstr "Pro-Tip: paste kubeconfig to parse available contexts" #~ msgstr "Pro-Tip: paste kubeconfig to parse available contexts"
#: src/renderer/components/+add-cluster/add-cluster.tsx:254 #: src/renderer/components/+add-cluster/add-cluster.tsx:239
msgid "Pro-Tip: you can also drag-n-drop kubeconfig file to this area" msgid "Pro-Tip: you can also drag-n-drop kubeconfig file to this area"
msgstr "Pro-Tip: you can also drag-n-drop kubeconfig file to this area" msgstr "Pro-Tip: you can also drag-n-drop kubeconfig file to this area"
@ -1924,11 +1935,11 @@ msgstr "Pro-Tip: you can also drag-n-drop kubeconfig file to this area"
msgid "Provisioner" msgid "Provisioner"
msgstr "Provisioner" msgstr "Provisioner"
#: src/renderer/components/+preferences/preferences.tsx:150 #: src/renderer/components/+preferences/preferences.tsx:140
msgid "Proxy is used only for non-cluster communication." msgid "Proxy is used only for non-cluster communication."
msgstr "Proxy is used only for non-cluster communication." msgstr "Proxy is used only for non-cluster communication."
#: src/renderer/components/+add-cluster/add-cluster.tsx:308 #: src/renderer/components/+add-cluster/add-cluster.tsx:293
msgid "Proxy settings" msgid "Proxy settings"
msgstr "Proxy settings" msgstr "Proxy settings"
@ -2008,10 +2019,10 @@ msgstr "Release: {0}"
msgid "Releases" msgid "Releases"
msgstr "Releases" msgstr "Releases"
#: src/renderer/components/+preferences/preferences.tsx:139 #: src/renderer/components/+preferences/preferences.tsx:152
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60
#: src/renderer/components/cluster-manager/clusters-menu.tsx:74 #: src/renderer/components/cluster-manager/clusters-menu.tsx:76
#: src/renderer/components/cluster-manager/clusters-menu.tsx:80 #: src/renderer/components/cluster-manager/clusters-menu.tsx:82
#: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/item-object-list/item-list-layout.tsx:179
#: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:49
#: src/renderer/components/menu/menu-actions.tsx:85 #: src/renderer/components/menu/menu-actions.tsx:85
@ -2022,7 +2033,7 @@ msgstr "Remove"
msgid "Remove <0>{releaseNames}</0>?" msgid "Remove <0>{releaseNames}</0>?"
msgstr "Remove <0>{releaseNames}</0>?" msgstr "Remove <0>{releaseNames}</0>?"
#: src/renderer/components/+workspaces/workspaces.tsx:51 #: src/renderer/components/+workspaces/workspaces.tsx:52
msgid "Remove Workspace" msgid "Remove Workspace"
msgstr "Remove Workspace" msgstr "Remove Workspace"
@ -2050,7 +2061,7 @@ msgstr "Remove selected items ({0})"
msgid "Remove {resourceKind} <0>{resourceName}</0>?" msgid "Remove {resourceKind} <0>{resourceName}</0>?"
msgstr "Remove {resourceKind} <0>{resourceName}</0>?" msgstr "Remove {resourceKind} <0>{resourceName}</0>?"
#: src/renderer/components/+preferences/preferences.tsx:114 #: src/renderer/components/+preferences/preferences.tsx:122
msgid "Removing helm branch <0>{0}</0> has failed: {1}" msgid "Removing helm branch <0>{0}</0> has failed: {1}"
msgstr "Removing helm branch <0>{0}</0> has failed: {1}" msgstr "Removing helm branch <0>{0}</0> has failed: {1}"
@ -2074,7 +2085,7 @@ msgstr "Replicas"
msgid "Repo/Name" msgid "Repo/Name"
msgstr "Repo/Name" msgstr "Repo/Name"
#: src/renderer/components/+preferences/preferences.tsx:133 #: src/renderer/components/+preferences/preferences.tsx:146
msgid "Repositories" msgid "Repositories"
msgstr "Repositories" msgstr "Repositories"
@ -2109,7 +2120,7 @@ msgstr "Required Drop Capabilities"
msgid "Required field" msgid "Required field"
msgstr "Required field" msgstr "Required field"
#: src/renderer/components/+add-cluster/add-cluster.tsx:250 #: src/renderer/components/+add-cluster/add-cluster.tsx:235
#: src/renderer/components/item-object-list/page-filters-list.tsx:31 #: src/renderer/components/item-object-list/page-filters-list.tsx:31
msgid "Reset" msgid "Reset"
msgstr "Reset" msgstr "Reset"
@ -2252,7 +2263,7 @@ msgstr "Runtime Class"
#: src/renderer/components/+config-maps/config-map-details.tsx:78 #: src/renderer/components/+config-maps/config-map-details.tsx:78
#: src/renderer/components/+config-secrets/secret-details.tsx:97 #: src/renderer/components/+config-secrets/secret-details.tsx:97
#: src/renderer/components/+workloads-pods/pod-logs-dialog.tsx:216 #: src/renderer/components/+workloads-pods/pod-logs-dialog.tsx:216
#: src/renderer/components/+workspaces/workspaces.tsx:120 #: src/renderer/components/+workspaces/workspaces.tsx:132
#: src/renderer/components/dock/edit-resource.tsx:88 #: src/renderer/components/dock/edit-resource.tsx:88
msgid "Save" msgid "Save"
msgstr "Save" msgstr "Save"
@ -2341,13 +2352,13 @@ msgstr "Select a quota.."
#~ msgid "Select context(s)" #~ msgid "Select context(s)"
#~ msgstr "Select context(s)" #~ msgstr "Select context(s)"
#: src/renderer/components/+add-cluster/add-cluster.tsx:272 #: src/renderer/components/+add-cluster/add-cluster.tsx:257
#~ msgid "Select contexts" msgid "Select contexts"
#~ msgstr "Select contexts" msgstr "Select contexts"
#: src/renderer/components/+add-cluster/add-cluster.tsx:272 #: src/renderer/components/+add-cluster/add-cluster.tsx:272
msgid "Select contexts (available: {0})" #~ msgid "Select contexts (available: {0})"
msgstr "Select contexts (available: {0})" #~ msgstr "Select contexts (available: {0})"
#: src/renderer/components/+add-cluster/add-cluster.tsx:76 #: src/renderer/components/+add-cluster/add-cluster.tsx:76
#: src/renderer/components/+add-cluster/add-cluster.tsx:76 #: src/renderer/components/+add-cluster/add-cluster.tsx:76
@ -2371,7 +2382,7 @@ msgstr "Select custom kubeconfig file"
#~ msgid "Select kubeconfig" #~ msgid "Select kubeconfig"
#~ msgstr "Select kubeconfig" #~ msgstr "Select kubeconfig"
#: src/renderer/components/+add-cluster/add-cluster.tsx:244 #: src/renderer/components/+add-cluster/add-cluster.tsx:229
msgid "Select kubeconfig file" msgid "Select kubeconfig file"
msgstr "Select kubeconfig file" msgstr "Select kubeconfig file"
@ -2399,7 +2410,7 @@ msgstr "Select service accounts"
#~ msgid "Selected contexts ({0}): <0>{1}</0>" #~ msgid "Selected contexts ({0}): <0>{1}</0>"
#~ msgstr "Selected contexts ({0}): <0>{1}</0>" #~ msgstr "Selected contexts ({0}): <0>{1}</0>"
#: src/renderer/components/+add-cluster/add-cluster.tsx:271 #: src/renderer/components/+add-cluster/add-cluster.tsx:256
msgid "Selected contexts: <0>{0}</0>" msgid "Selected contexts: <0>{0}</0>"
msgstr "Selected contexts: <0>{0}</0>" msgstr "Selected contexts: <0>{0}</0>"
@ -2503,7 +2514,6 @@ msgid "Stateful Sets"
msgstr "Stateful Sets" msgstr "Stateful Sets"
#: src/renderer/components/+workloads/workloads.tsx:61 #: src/renderer/components/+workloads/workloads.tsx:61
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:52
msgid "StatefulSets" msgid "StatefulSets"
msgstr "StatefulSets" msgstr "StatefulSets"
@ -2605,11 +2615,11 @@ msgstr "TLS"
msgid "Taints" msgid "Taints"
msgstr "Taints" msgstr "Taints"
#: src/renderer/components/+preferences/preferences.tsx:161 #: src/renderer/components/+preferences/preferences.tsx:168
msgid "Telemetry & Usage Tracking" msgid "Telemetry & Usage Tracking"
msgstr "Telemetry & Usage Tracking" msgstr "Telemetry & Usage Tracking"
#: src/renderer/components/+preferences/preferences.tsx:164 #: src/renderer/components/+preferences/preferences.tsx:171
msgid "Telemetry & usage data is collected to continuously improve the Lens experience." msgid "Telemetry & usage data is collected to continuously improve the Lens experience."
msgstr "Telemetry & usage data is collected to continuously improve the Lens experience." msgstr "Telemetry & usage data is collected to continuously improve the Lens experience."
@ -2629,15 +2639,19 @@ msgstr "There are no logs available for container."
msgid "There are no logs available." msgid "There are no logs available."
msgstr "There are no logs available." msgstr "There are no logs available."
#: src/renderer/components/input/input.validators.ts:5 #: src/renderer/components/input/input.validators.ts:6
msgid "This field is required" msgid "This field is required"
msgstr "This field is required" msgstr "This field is required"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:106 #: src/renderer/components/input/input.validators.ts:31
msgid "This field must be a valid path"
msgstr "This field must be a valid path"
#: src/renderer/components/+landing-page/landing-page.tsx:25
msgid "This is the quick launch menu." msgid "This is the quick launch menu."
msgstr "This is the quick launch menu." msgstr "This is the quick launch menu."
#: src/renderer/components/+preferences/preferences.tsx:156 #: src/renderer/components/+preferences/preferences.tsx:163
msgid "This will make Lens to trust ANY certificate authority without any validations." msgid "This will make Lens to trust ANY certificate authority without any validations."
msgstr "This will make Lens to trust ANY certificate authority without any validations." msgstr "This will make Lens to trust ANY certificate authority without any validations."
@ -2661,13 +2675,13 @@ msgstr "Tolerations"
msgid "Transmit" msgid "Transmit"
msgstr "Transmit" msgstr "Transmit"
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:107 #: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:106
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80
msgid "Trigger" msgid "Trigger"
msgstr "Trigger" msgstr "Trigger"
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:103 #: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:102
msgid "Trigger CronJob <0>{cronjobName}</0>" msgid "Trigger CronJob <0>{cronjobName}</0>"
msgstr "Trigger CronJob <0>{cronjobName}</0>" msgstr "Trigger CronJob <0>{cronjobName}</0>"
@ -2690,7 +2704,7 @@ msgstr "Trigger CronJob <0>{cronjobName}</0>"
msgid "Type" msgid "Type"
msgstr "Type" msgstr "Type"
#: src/renderer/components/+preferences/preferences.tsx:148 #: src/renderer/components/+preferences/preferences.tsx:138
msgid "Type HTTP proxy url (example: http://proxy.acme.org:8080)" msgid "Type HTTP proxy url (example: http://proxy.acme.org:8080)"
msgstr "Type HTTP proxy url (example: http://proxy.acme.org:8080)" msgstr "Type HTTP proxy url (example: http://proxy.acme.org:8080)"
@ -2827,11 +2841,11 @@ msgstr "Waiting services to be running"
msgid "Warnings: {0}" msgid "Warnings: {0}"
msgstr "Warnings: {0}" msgstr "Warnings: {0}"
#: src/renderer/components/+landing-page/landing-page.tsx:20 #: src/renderer/components/+landing-page/landing-page.tsx:34
msgid "Welcome!" msgid "Welcome!"
msgstr "Welcome!" msgstr "Welcome!"
#: src/renderer/components/+workspaces/workspaces.tsx:79 #: src/renderer/components/+workspaces/workspaces.tsx:88
msgid "What is a Workspace?" msgid "What is a Workspace?"
msgstr "What is a Workspace?" msgstr "What is a Workspace?"
@ -2844,19 +2858,19 @@ msgid "Workloads"
msgstr "Workloads" msgstr "Workloads"
#: src/renderer/components/+workspaces/workspace-menu.tsx:39 #: src/renderer/components/+workspaces/workspace-menu.tsx:39
#: src/renderer/components/+workspaces/workspaces.tsx:91 #: src/renderer/components/+workspaces/workspaces.tsx:100
msgid "Workspaces" msgid "Workspaces"
msgstr "Workspaces" msgstr "Workspaces"
#: src/renderer/components/+workspaces/workspaces.tsx:81 #: src/renderer/components/+workspaces/workspaces.tsx:90
msgid "Workspaces are used to organize number of clusters into logical groups." msgid "Workspaces are used to organize number of clusters into logical groups."
msgstr "Workspaces are used to organize number of clusters into logical groups." msgstr "Workspaces are used to organize number of clusters into logical groups."
#: src/renderer/components/input/input.validators.ts:10 #: src/renderer/components/input/input.validators.ts:11
msgid "Wrong email format" msgid "Wrong email format"
msgstr "Wrong email format" msgstr "Wrong email format"
#: src/renderer/components/input/input.validators.ts:25 #: src/renderer/components/input/input.validators.ts:26
msgid "Wrong url format" msgid "Wrong url format"
msgstr "Wrong url format" msgstr "Wrong url format"
@ -2915,7 +2929,7 @@ msgstr "listKind"
msgid "never" msgid "never"
msgstr "never" msgstr "never"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:121 #: src/renderer/components/cluster-manager/clusters-menu.tsx:133
msgid "new" msgid "new"
msgstr "new" msgstr "new"

View File

@ -25,7 +25,7 @@ msgstr ""
msgid "(as a percentage of request)" msgid "(as a percentage of request)"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:108 #: src/renderer/components/+workspaces/workspaces.tsx:121
msgid "(current)" msgid "(current)"
msgstr "" msgstr ""
@ -57,11 +57,11 @@ msgstr ""
#~ msgid "A HTTP proxy server URL (format: http://<address>:<port>)" #~ msgid "A HTTP proxy server URL (format: http://<address>:<port>)"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/input/input.validators.ts:40 #: src/renderer/components/input/input.validators.ts:46
msgid "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics." msgid "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics."
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:84 #: src/renderer/components/+workspaces/workspaces.tsx:93
msgid "A single workspaces contains a list of clusters and their full configuration." msgid "A single workspaces contains a list of clusters and their full configuration."
msgstr "" msgstr ""
@ -87,8 +87,8 @@ msgstr ""
msgid "Active" msgid "Active"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:303 #: src/renderer/components/+add-cluster/add-cluster.tsx:288
#: src/renderer/components/cluster-manager/clusters-menu.tsx:118 #: src/renderer/components/cluster-manager/clusters-menu.tsx:130
msgid "Add Cluster" msgid "Add Cluster"
msgstr "" msgstr ""
@ -100,7 +100,7 @@ msgstr ""
msgid "Add RoleBinding" msgid "Add RoleBinding"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:125 #: src/renderer/components/+workspaces/workspaces.tsx:138
msgid "Add Workspace" msgid "Add Workspace"
msgstr "" msgstr ""
@ -112,7 +112,7 @@ msgstr ""
#~ msgid "Add cluster" #~ msgid "Add cluster"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:320 #: src/renderer/components/+add-cluster/add-cluster.tsx:305
msgid "Add cluster(s)" msgid "Add cluster(s)"
msgstr "" msgstr ""
@ -136,7 +136,7 @@ msgstr ""
#~ msgid "Adding clusters: <0>{0}</0>" #~ msgid "Adding clusters: <0>{0}</0>"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:103 #: src/renderer/components/+preferences/preferences.tsx:111
msgid "Adding helm branch <0>{0}</0> has failed: {1}" msgid "Adding helm branch <0>{0}</0> has failed: {1}"
msgstr "" msgstr ""
@ -191,7 +191,7 @@ msgstr ""
msgid "Age" msgid "Age"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:64 #: src/renderer/components/+workspaces/workspaces.tsx:65
msgid "All clusters within workspace will be cleared as well" msgid "All clusters within workspace will be cleared as well"
msgstr "" msgstr ""
@ -219,11 +219,11 @@ msgstr ""
msgid "Allow Privilege Escalation" msgid "Allow Privilege Escalation"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:162 #: src/renderer/components/+preferences/preferences.tsx:169
msgid "Allow telemetry & usage tracking" msgid "Allow telemetry & usage tracking"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:154 #: src/renderer/components/+preferences/preferences.tsx:161
msgid "Allow untrusted Certificate Authorities" msgid "Allow untrusted Certificate Authorities"
msgstr "" msgstr ""
@ -281,7 +281,7 @@ msgstr ""
msgid "Apps" msgid "Apps"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:61 #: src/renderer/components/+workspaces/workspaces.tsx:62
msgid "Are you sure you want remove workspace <0>{0}</0>?" msgid "Are you sure you want remove workspace <0>{0}</0>?"
msgstr "" msgstr ""
@ -293,7 +293,7 @@ msgstr ""
msgid "Arguments" msgid "Arguments"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:108 #: src/renderer/components/+landing-page/landing-page.tsx:27
msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button."
msgstr "" msgstr ""
@ -323,7 +323,7 @@ msgstr ""
msgid "Bindings" msgid "Bindings"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:251 #: src/renderer/components/+add-cluster/add-cluster.tsx:236
msgid "Browse" msgid "Browse"
msgstr "" msgstr ""
@ -402,7 +402,7 @@ msgstr ""
msgid "CPU:" msgid "CPU:"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:119 #: src/renderer/components/+workspaces/workspaces.tsx:133
#: src/renderer/components/confirm-dialog/confirm-dialog.tsx:44 #: src/renderer/components/confirm-dialog/confirm-dialog.tsx:44
#: src/renderer/components/dock/info-panel.tsx:97 #: src/renderer/components/dock/info-panel.tsx:97
#: src/renderer/components/wizard/wizard.tsx:130 #: src/renderer/components/wizard/wizard.tsx:130
@ -422,7 +422,7 @@ msgstr ""
msgid "Capacity" msgid "Capacity"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:153 #: src/renderer/components/+preferences/preferences.tsx:160
msgid "Certificate Trust" msgid "Certificate Trust"
msgstr "" msgstr ""
@ -497,7 +497,7 @@ msgstr ""
msgid "Cluster Issuers" msgid "Cluster Issuers"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:126 #: src/renderer/components/+preferences/preferences.tsx:134
msgid "Color Theme" msgid "Color Theme"
msgstr "" msgstr ""
@ -708,7 +708,6 @@ msgid "Cron Jobs"
msgstr "" msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:77 #: src/renderer/components/+workloads/workloads.tsx:77
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:67
msgid "CronJobs" msgid "CronJobs"
msgstr "" msgstr ""
@ -755,7 +754,6 @@ msgid "Daemon Sets"
msgstr "" msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:53 #: src/renderer/components/+workloads/workloads.tsx:53
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:57
msgid "DaemonSets" msgid "DaemonSets"
msgstr "" msgstr ""
@ -780,11 +778,15 @@ msgstr ""
msgid "Default Runtime Class Name" msgid "Default Runtime Class Name"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/kubectl-binaries.tsx:30
msgid "Default:"
msgstr ""
#: src/renderer/components/+custom-resources/custom-resources.tsx:22 #: src/renderer/components/+custom-resources/custom-resources.tsx:22
msgid "Definitions" msgid "Definitions"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:113 #: src/renderer/components/+workspaces/workspaces.tsx:126
#: src/renderer/components/menu/menu-actions.tsx:84 #: src/renderer/components/menu/menu-actions.tsx:84
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
@ -795,12 +797,11 @@ msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:45 #: src/renderer/components/+workloads/workloads.tsx:45
#: src/renderer/components/+workloads-deployments/deployments.tsx:57 #: src/renderer/components/+workloads-deployments/deployments.tsx:57
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:47
msgid "Deployments" msgid "Deployments"
msgstr "" msgstr ""
#: src/renderer/components/+apps-helm-charts/helm-charts.tsx:65 #: src/renderer/components/+apps-helm-charts/helm-charts.tsx:65
#: src/renderer/components/+workspaces/workspaces.tsx:118 #: src/renderer/components/+workspaces/workspaces.tsx:131
msgid "Description" msgid "Description"
msgstr "" msgstr ""
@ -813,7 +814,7 @@ msgstr ""
msgid "Desired number of replicas" msgid "Desired number of replicas"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:64 #: src/renderer/components/cluster-manager/clusters-menu.tsx:65
msgid "Disconnect" msgid "Disconnect"
msgstr "" msgstr ""
@ -827,7 +828,7 @@ msgstr ""
msgid "Disk:" msgid "Disk:"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:158 #: src/renderer/components/+preferences/preferences.tsx:165
msgid "Does not affect cluster communications!" msgid "Does not affect cluster communications!"
msgstr "" msgstr ""
@ -836,14 +837,22 @@ msgid "Domains"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:129 #: src/renderer/components/+preferences/preferences.tsx:129
msgid "Download Mirror" #~ msgid "Download Mirror"
msgstr "" #~ msgstr ""
#: src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx:90 #: src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx:90
msgid "Download file" msgid "Download file"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:130 #: src/renderer/components/+preferences/kubectl-binaries.tsx:39
msgid "Download kubectl binaries"
msgstr ""
#: src/renderer/components/+preferences/kubectl-binaries.tsx:37
msgid "Download kubectl binaries matching to Kubernetes cluster verison."
msgstr ""
#: src/renderer/components/+preferences/kubectl-binaries.tsx:41
msgid "Download mirror for kubectl" msgid "Download mirror for kubectl"
msgstr "" msgstr ""
@ -869,7 +878,7 @@ msgstr ""
msgid "E-mail" msgid "E-mail"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:112 #: src/renderer/components/+workspaces/workspaces.tsx:125
#: src/renderer/components/menu/menu-actions.tsx:80 #: src/renderer/components/menu/menu-actions.tsx:80
#: src/renderer/components/menu/menu-actions.tsx:81 #: src/renderer/components/menu/menu-actions.tsx:81
msgid "Edit" msgid "Edit"
@ -991,7 +1000,7 @@ msgstr ""
msgid "Fs Group" msgid "Fs Group"
msgstr "" msgstr ""
#: src/renderer/components/+landing-page/landing-page.tsx:23 #: src/renderer/components/+landing-page/landing-page.tsx:37
msgid "Get started by associating one or more clusters to Lens." msgid "Get started by associating one or more clusters to Lens."
msgstr "" msgstr ""
@ -1013,7 +1022,7 @@ msgstr ""
msgid "HPA" msgid "HPA"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:147 #: src/renderer/components/+preferences/preferences.tsx:137
msgid "HTTP Proxy" msgid "HTTP Proxy"
msgstr "" msgstr ""
@ -1021,7 +1030,7 @@ msgstr ""
#~ msgid "HTTP Proxy server. Used for communicating with Kubernetes API." #~ msgid "HTTP Proxy server. Used for communicating with Kubernetes API."
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:132 #: src/renderer/components/+preferences/preferences.tsx:145
msgid "Helm" msgid "Helm"
msgstr "" msgstr ""
@ -1041,7 +1050,7 @@ msgstr ""
msgid "Helm Upgrade: {0}" msgid "Helm Upgrade: {0}"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:47 #: src/renderer/components/+preferences/preferences.tsx:51
msgid "Helm branch <0>{0}</0> already in use" msgid "Helm branch <0>{0}</0> already in use"
msgstr "" msgstr ""
@ -1148,11 +1157,11 @@ msgstr ""
msgid "Installing..." msgid "Installing..."
msgstr "" msgstr ""
#: src/renderer/components/input/input.validators.ts:44 #: src/renderer/components/input/input.validators.ts:50
msgid "Invalid account ID" msgid "Invalid account ID"
msgstr "" msgstr ""
#: src/renderer/components/input/input.validators.ts:15 #: src/renderer/components/input/input.validators.ts:16
msgid "Invalid number" msgid "Invalid number"
msgstr "" msgstr ""
@ -1188,7 +1197,6 @@ msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:69 #: src/renderer/components/+workloads/workloads.tsx:69
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:62 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:62
#: src/renderer/components/+workloads-jobs/jobs.tsx:36 #: src/renderer/components/+workloads-jobs/jobs.tsx:36
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:62
msgid "Jobs" msgid "Jobs"
msgstr "" msgstr ""
@ -1232,6 +1240,10 @@ msgstr ""
msgid "Kubeconfig File" msgid "Kubeconfig File"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/kubectl-binaries.tsx:35
msgid "Kubectl Binary"
msgstr ""
#: src/renderer/components/+nodes/node-details.tsx:98 #: src/renderer/components/+nodes/node-details.tsx:98
msgid "Kubelet version" msgid "Kubelet version"
msgstr "" msgstr ""
@ -1348,7 +1360,7 @@ msgstr ""
msgid "Max Unavailable" msgid "Max Unavailable"
msgstr "" msgstr ""
#: src/renderer/components/input/input.validators.ts:35 #: src/renderer/components/input/input.validators.ts:41
msgid "Maximum length is {maxLength}" msgid "Maximum length is {maxLength}"
msgstr "" msgstr ""
@ -1424,7 +1436,7 @@ msgstr ""
msgid "Minimize" msgid "Minimize"
msgstr "" msgstr ""
#: src/renderer/components/input/input.validators.ts:30 #: src/renderer/components/input/input.validators.ts:36
msgid "Minimum length is {minLength}" msgid "Minimum length is {minLength}"
msgstr "" msgstr ""
@ -1488,7 +1500,7 @@ msgstr ""
#: src/renderer/components/+workloads-pods/pods.tsx:74 #: src/renderer/components/+workloads-pods/pods.tsx:74
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:50 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:50
#: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:40 #: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:40
#: src/renderer/components/+workspaces/workspaces.tsx:117 #: src/renderer/components/+workspaces/workspaces.tsx:130
#: src/renderer/components/dock/edit-resource.tsx:90 #: src/renderer/components/dock/edit-resource.tsx:90
#: src/renderer/components/kube-object/kube-object-meta.tsx:20 #: src/renderer/components/kube-object/kube-object-meta.tsx:20
msgid "Name" msgid "Name"
@ -1556,7 +1568,7 @@ msgstr ""
msgid "Namespaces: {0}" msgid "Namespaces: {0}"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:157 #: src/renderer/components/+preferences/preferences.tsx:164
msgid "Needed with some corporate proxies that do certificate re-writing." msgid "Needed with some corporate proxies that do certificate re-writing."
msgstr "" msgstr ""
@ -1609,7 +1621,7 @@ msgstr ""
#~ msgid "No contexts available or they already added" #~ msgid "No contexts available or they already added"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:275 #: src/renderer/components/+add-cluster/add-cluster.tsx:260
msgid "No contexts available or they have been added already" msgid "No contexts available or they have been added already"
msgstr "" msgstr ""
@ -1725,7 +1737,7 @@ msgid "Organization"
msgstr "" msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:29 #: src/renderer/components/+workloads/workloads.tsx:29
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:35 #: src/renderer/components/+workloads-overview/overview-statuses.tsx:45
msgid "Overview" msgid "Overview"
msgstr "" msgstr ""
@ -1741,7 +1753,7 @@ msgstr ""
msgid "Parameters" msgid "Parameters"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:245 #: src/renderer/components/+add-cluster/add-cluster.tsx:230
msgid "Paste as text" msgid "Paste as text"
msgstr "" msgstr ""
@ -1831,7 +1843,6 @@ msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:37 #: src/renderer/components/+workloads/workloads.tsx:37
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:47 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:47
#: src/renderer/components/+workloads-deployments/deployments.tsx:60 #: src/renderer/components/+workloads-deployments/deployments.tsx:60
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:42
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:89 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:89
#: src/renderer/components/+workloads-pods/pods.tsx:73 #: src/renderer/components/+workloads-pods/pods.tsx:73
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:52 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:52
@ -1882,7 +1893,7 @@ msgstr ""
#~ msgid "Pro-Tip: paste kubeconfig to collect available contexts" #~ msgid "Pro-Tip: paste kubeconfig to collect available contexts"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:263 #: src/renderer/components/+add-cluster/add-cluster.tsx:248
msgid "Pro-Tip: paste kubeconfig to get available contexts" msgid "Pro-Tip: paste kubeconfig to get available contexts"
msgstr "" msgstr ""
@ -1890,7 +1901,7 @@ msgstr ""
#~ msgid "Pro-Tip: paste kubeconfig to parse available contexts" #~ msgid "Pro-Tip: paste kubeconfig to parse available contexts"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:254 #: src/renderer/components/+add-cluster/add-cluster.tsx:239
msgid "Pro-Tip: you can also drag-n-drop kubeconfig file to this area" msgid "Pro-Tip: you can also drag-n-drop kubeconfig file to this area"
msgstr "" msgstr ""
@ -1907,11 +1918,11 @@ msgstr ""
msgid "Provisioner" msgid "Provisioner"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:150 #: src/renderer/components/+preferences/preferences.tsx:140
msgid "Proxy is used only for non-cluster communication." msgid "Proxy is used only for non-cluster communication."
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:308 #: src/renderer/components/+add-cluster/add-cluster.tsx:293
msgid "Proxy settings" msgid "Proxy settings"
msgstr "" msgstr ""
@ -1991,10 +2002,10 @@ msgstr ""
msgid "Releases" msgid "Releases"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:139 #: src/renderer/components/+preferences/preferences.tsx:152
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60
#: src/renderer/components/cluster-manager/clusters-menu.tsx:74 #: src/renderer/components/cluster-manager/clusters-menu.tsx:76
#: src/renderer/components/cluster-manager/clusters-menu.tsx:80 #: src/renderer/components/cluster-manager/clusters-menu.tsx:82
#: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/item-object-list/item-list-layout.tsx:179
#: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:49
#: src/renderer/components/menu/menu-actions.tsx:85 #: src/renderer/components/menu/menu-actions.tsx:85
@ -2005,7 +2016,7 @@ msgstr ""
msgid "Remove <0>{releaseNames}</0>?" msgid "Remove <0>{releaseNames}</0>?"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:51 #: src/renderer/components/+workspaces/workspaces.tsx:52
msgid "Remove Workspace" msgid "Remove Workspace"
msgstr "" msgstr ""
@ -2033,7 +2044,7 @@ msgstr ""
msgid "Remove {resourceKind} <0>{resourceName}</0>?" msgid "Remove {resourceKind} <0>{resourceName}</0>?"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:114 #: src/renderer/components/+preferences/preferences.tsx:122
msgid "Removing helm branch <0>{0}</0> has failed: {1}" msgid "Removing helm branch <0>{0}</0> has failed: {1}"
msgstr "" msgstr ""
@ -2057,7 +2068,7 @@ msgstr ""
msgid "Repo/Name" msgid "Repo/Name"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:133 #: src/renderer/components/+preferences/preferences.tsx:146
msgid "Repositories" msgid "Repositories"
msgstr "" msgstr ""
@ -2092,7 +2103,7 @@ msgstr ""
msgid "Required field" msgid "Required field"
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:250 #: src/renderer/components/+add-cluster/add-cluster.tsx:235
#: src/renderer/components/item-object-list/page-filters-list.tsx:31 #: src/renderer/components/item-object-list/page-filters-list.tsx:31
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
@ -2235,7 +2246,7 @@ msgstr ""
#: src/renderer/components/+config-maps/config-map-details.tsx:78 #: src/renderer/components/+config-maps/config-map-details.tsx:78
#: src/renderer/components/+config-secrets/secret-details.tsx:97 #: src/renderer/components/+config-secrets/secret-details.tsx:97
#: src/renderer/components/+workloads-pods/pod-logs-dialog.tsx:216 #: src/renderer/components/+workloads-pods/pod-logs-dialog.tsx:216
#: src/renderer/components/+workspaces/workspaces.tsx:120 #: src/renderer/components/+workspaces/workspaces.tsx:132
#: src/renderer/components/dock/edit-resource.tsx:88 #: src/renderer/components/dock/edit-resource.tsx:88
msgid "Save" msgid "Save"
msgstr "" msgstr ""
@ -2324,13 +2335,13 @@ msgstr ""
#~ msgid "Select context(s)" #~ msgid "Select context(s)"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:272 #: src/renderer/components/+add-cluster/add-cluster.tsx:257
#~ msgid "Select contexts" msgid "Select contexts"
#~ msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:272 #: src/renderer/components/+add-cluster/add-cluster.tsx:272
msgid "Select contexts (available: {0})" #~ msgid "Select contexts (available: {0})"
msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:76 #: src/renderer/components/+add-cluster/add-cluster.tsx:76
#: src/renderer/components/+add-cluster/add-cluster.tsx:76 #: src/renderer/components/+add-cluster/add-cluster.tsx:76
@ -2354,7 +2365,7 @@ msgstr ""
#~ msgid "Select kubeconfig" #~ msgid "Select kubeconfig"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:244 #: src/renderer/components/+add-cluster/add-cluster.tsx:229
msgid "Select kubeconfig file" msgid "Select kubeconfig file"
msgstr "" msgstr ""
@ -2382,7 +2393,7 @@ msgstr ""
#~ msgid "Selected contexts ({0}): <0>{1}</0>" #~ msgid "Selected contexts ({0}): <0>{1}</0>"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:271 #: src/renderer/components/+add-cluster/add-cluster.tsx:256
msgid "Selected contexts: <0>{0}</0>" msgid "Selected contexts: <0>{0}</0>"
msgstr "" msgstr ""
@ -2486,7 +2497,6 @@ msgid "Stateful Sets"
msgstr "" msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:61 #: src/renderer/components/+workloads/workloads.tsx:61
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:52
msgid "StatefulSets" msgid "StatefulSets"
msgstr "" msgstr ""
@ -2588,11 +2598,11 @@ msgstr ""
msgid "Taints" msgid "Taints"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:161 #: src/renderer/components/+preferences/preferences.tsx:168
msgid "Telemetry & Usage Tracking" msgid "Telemetry & Usage Tracking"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:164 #: src/renderer/components/+preferences/preferences.tsx:171
msgid "Telemetry & usage data is collected to continuously improve the Lens experience." msgid "Telemetry & usage data is collected to continuously improve the Lens experience."
msgstr "" msgstr ""
@ -2612,15 +2622,19 @@ msgstr ""
msgid "There are no logs available." msgid "There are no logs available."
msgstr "" msgstr ""
#: src/renderer/components/input/input.validators.ts:5 #: src/renderer/components/input/input.validators.ts:6
msgid "This field is required" msgid "This field is required"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:106 #: src/renderer/components/input/input.validators.ts:31
msgid "This field must be a valid path"
msgstr ""
#: src/renderer/components/+landing-page/landing-page.tsx:25
msgid "This is the quick launch menu." msgid "This is the quick launch menu."
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:156 #: src/renderer/components/+preferences/preferences.tsx:163
msgid "This will make Lens to trust ANY certificate authority without any validations." msgid "This will make Lens to trust ANY certificate authority without any validations."
msgstr "" msgstr ""
@ -2644,13 +2658,13 @@ msgstr ""
msgid "Transmit" msgid "Transmit"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:107 #: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:106
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80
msgid "Trigger" msgid "Trigger"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:103 #: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:102
msgid "Trigger CronJob <0>{cronjobName}</0>" msgid "Trigger CronJob <0>{cronjobName}</0>"
msgstr "" msgstr ""
@ -2673,7 +2687,7 @@ msgstr ""
msgid "Type" msgid "Type"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:148 #: src/renderer/components/+preferences/preferences.tsx:138
msgid "Type HTTP proxy url (example: http://proxy.acme.org:8080)" msgid "Type HTTP proxy url (example: http://proxy.acme.org:8080)"
msgstr "" msgstr ""
@ -2810,11 +2824,11 @@ msgstr ""
msgid "Warnings: {0}" msgid "Warnings: {0}"
msgstr "" msgstr ""
#: src/renderer/components/+landing-page/landing-page.tsx:20 #: src/renderer/components/+landing-page/landing-page.tsx:34
msgid "Welcome!" msgid "Welcome!"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:79 #: src/renderer/components/+workspaces/workspaces.tsx:88
msgid "What is a Workspace?" msgid "What is a Workspace?"
msgstr "" msgstr ""
@ -2827,19 +2841,19 @@ msgid "Workloads"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspace-menu.tsx:39 #: src/renderer/components/+workspaces/workspace-menu.tsx:39
#: src/renderer/components/+workspaces/workspaces.tsx:91 #: src/renderer/components/+workspaces/workspaces.tsx:100
msgid "Workspaces" msgid "Workspaces"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:81 #: src/renderer/components/+workspaces/workspaces.tsx:90
msgid "Workspaces are used to organize number of clusters into logical groups." msgid "Workspaces are used to organize number of clusters into logical groups."
msgstr "" msgstr ""
#: src/renderer/components/input/input.validators.ts:10 #: src/renderer/components/input/input.validators.ts:11
msgid "Wrong email format" msgid "Wrong email format"
msgstr "" msgstr ""
#: src/renderer/components/input/input.validators.ts:25 #: src/renderer/components/input/input.validators.ts:26
msgid "Wrong url format" msgid "Wrong url format"
msgstr "" msgstr ""
@ -2898,7 +2912,7 @@ msgstr ""
msgid "never" msgid "never"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:121 #: src/renderer/components/cluster-manager/clusters-menu.tsx:133
msgid "new" msgid "new"
msgstr "" msgstr ""

View File

@ -26,7 +26,7 @@ msgstr ""
msgid "(as a percentage of request)" msgid "(as a percentage of request)"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:108 #: src/renderer/components/+workspaces/workspaces.tsx:121
msgid "(current)" msgid "(current)"
msgstr "" msgstr ""
@ -58,11 +58,11 @@ msgstr ""
#~ msgid "A HTTP proxy server URL (format: http://<address>:<port>)" #~ msgid "A HTTP proxy server URL (format: http://<address>:<port>)"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/input/input.validators.ts:40 #: src/renderer/components/input/input.validators.ts:46
msgid "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics." msgid "A System Name must be lowercase DNS labels separated by dots. DNS labels are alphanumerics and dashes enclosed by alphanumerics."
msgstr "Это поле может содержать только латинские буквы в нижнем регистре, номера и дефис." msgstr "Это поле может содержать только латинские буквы в нижнем регистре, номера и дефис."
#: src/renderer/components/+workspaces/workspaces.tsx:84 #: src/renderer/components/+workspaces/workspaces.tsx:93
msgid "A single workspaces contains a list of clusters and their full configuration." msgid "A single workspaces contains a list of clusters and their full configuration."
msgstr "" msgstr ""
@ -88,8 +88,8 @@ msgstr "Название аккаунта"
msgid "Active" msgid "Active"
msgstr "Активный" msgstr "Активный"
#: src/renderer/components/+add-cluster/add-cluster.tsx:303 #: src/renderer/components/+add-cluster/add-cluster.tsx:288
#: src/renderer/components/cluster-manager/clusters-menu.tsx:118 #: src/renderer/components/cluster-manager/clusters-menu.tsx:130
msgid "Add Cluster" msgid "Add Cluster"
msgstr "" msgstr ""
@ -101,7 +101,7 @@ msgstr "Добавить Namespace"
msgid "Add RoleBinding" msgid "Add RoleBinding"
msgstr "Добавить привязку ролей" msgstr "Добавить привязку ролей"
#: src/renderer/components/+workspaces/workspaces.tsx:125 #: src/renderer/components/+workspaces/workspaces.tsx:138
msgid "Add Workspace" msgid "Add Workspace"
msgstr "" msgstr ""
@ -113,7 +113,7 @@ msgstr "Добавить привязки к {name}"
#~ msgid "Add cluster" #~ msgid "Add cluster"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:320 #: src/renderer/components/+add-cluster/add-cluster.tsx:305
msgid "Add cluster(s)" msgid "Add cluster(s)"
msgstr "" msgstr ""
@ -137,7 +137,7 @@ msgstr "Добавить поле"
#~ msgid "Adding clusters: <0>{0}</0>" #~ msgid "Adding clusters: <0>{0}</0>"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:103 #: src/renderer/components/+preferences/preferences.tsx:111
msgid "Adding helm branch <0>{0}</0> has failed: {1}" msgid "Adding helm branch <0>{0}</0> has failed: {1}"
msgstr "" msgstr ""
@ -192,7 +192,7 @@ msgstr "Аффинитеты"
msgid "Age" msgid "Age"
msgstr "Возраст" msgstr "Возраст"
#: src/renderer/components/+workspaces/workspaces.tsx:64 #: src/renderer/components/+workspaces/workspaces.tsx:65
msgid "All clusters within workspace will be cleared as well" msgid "All clusters within workspace will be cleared as well"
msgstr "" msgstr ""
@ -220,11 +220,11 @@ msgstr ""
msgid "Allow Privilege Escalation" msgid "Allow Privilege Escalation"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:162 #: src/renderer/components/+preferences/preferences.tsx:169
msgid "Allow telemetry & usage tracking" msgid "Allow telemetry & usage tracking"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:154 #: src/renderer/components/+preferences/preferences.tsx:161
msgid "Allow untrusted Certificate Authorities" msgid "Allow untrusted Certificate Authorities"
msgstr "" msgstr ""
@ -282,7 +282,7 @@ msgstr "Применение.."
msgid "Apps" msgid "Apps"
msgstr "Приложения" msgstr "Приложения"
#: src/renderer/components/+workspaces/workspaces.tsx:61 #: src/renderer/components/+workspaces/workspaces.tsx:62
msgid "Are you sure you want remove workspace <0>{0}</0>?" msgid "Are you sure you want remove workspace <0>{0}</0>?"
msgstr "" msgstr ""
@ -294,7 +294,7 @@ msgstr "Выполнить команду drain для ноды <0>{nodeName}</0
msgid "Arguments" msgid "Arguments"
msgstr "Аргументы" msgstr "Аргументы"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:108 #: src/renderer/components/+landing-page/landing-page.tsx:27
msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button." msgid "Associate clusters and choose the ones you want to access via quick launch menu by clicking the + button."
msgstr "" msgstr ""
@ -324,7 +324,7 @@ msgstr "Цели привязки"
msgid "Bindings" msgid "Bindings"
msgstr "Привязки" msgstr "Привязки"
#: src/renderer/components/+add-cluster/add-cluster.tsx:251 #: src/renderer/components/+add-cluster/add-cluster.tsx:236
msgid "Browse" msgid "Browse"
msgstr "" msgstr ""
@ -403,7 +403,7 @@ msgstr "Запросы к процессору"
msgid "CPU:" msgid "CPU:"
msgstr "CPU:" msgstr "CPU:"
#: src/renderer/components/+workspaces/workspaces.tsx:119 #: src/renderer/components/+workspaces/workspaces.tsx:133
#: src/renderer/components/confirm-dialog/confirm-dialog.tsx:44 #: src/renderer/components/confirm-dialog/confirm-dialog.tsx:44
#: src/renderer/components/dock/info-panel.tsx:97 #: src/renderer/components/dock/info-panel.tsx:97
#: src/renderer/components/wizard/wizard.tsx:130 #: src/renderer/components/wizard/wizard.tsx:130
@ -423,7 +423,7 @@ msgstr "Отмена"
msgid "Capacity" msgid "Capacity"
msgstr "Емкость" msgstr "Емкость"
#: src/renderer/components/+preferences/preferences.tsx:153 #: src/renderer/components/+preferences/preferences.tsx:160
msgid "Certificate Trust" msgid "Certificate Trust"
msgstr "" msgstr ""
@ -502,7 +502,7 @@ msgstr "IP-адрес кластера"
msgid "Cluster Issuers" msgid "Cluster Issuers"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:126 #: src/renderer/components/+preferences/preferences.tsx:134
msgid "Color Theme" msgid "Color Theme"
msgstr "" msgstr ""
@ -713,7 +713,6 @@ msgid "Cron Jobs"
msgstr "" msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:77 #: src/renderer/components/+workloads/workloads.tsx:77
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:67
msgid "CronJobs" msgid "CronJobs"
msgstr "CronJobs" msgstr "CronJobs"
@ -760,7 +759,6 @@ msgid "Daemon Sets"
msgstr "" msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:53 #: src/renderer/components/+workloads/workloads.tsx:53
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:57
msgid "DaemonSets" msgid "DaemonSets"
msgstr "DaemonSets" msgstr "DaemonSets"
@ -785,11 +783,15 @@ msgstr ""
msgid "Default Runtime Class Name" msgid "Default Runtime Class Name"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/kubectl-binaries.tsx:30
msgid "Default:"
msgstr ""
#: src/renderer/components/+custom-resources/custom-resources.tsx:22 #: src/renderer/components/+custom-resources/custom-resources.tsx:22
msgid "Definitions" msgid "Definitions"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:113 #: src/renderer/components/+workspaces/workspaces.tsx:126
#: src/renderer/components/menu/menu-actions.tsx:84 #: src/renderer/components/menu/menu-actions.tsx:84
msgid "Delete" msgid "Delete"
msgstr "Удалить" msgstr "Удалить"
@ -800,12 +802,11 @@ msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:45 #: src/renderer/components/+workloads/workloads.tsx:45
#: src/renderer/components/+workloads-deployments/deployments.tsx:57 #: src/renderer/components/+workloads-deployments/deployments.tsx:57
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:47
msgid "Deployments" msgid "Deployments"
msgstr "Deployments" msgstr "Deployments"
#: src/renderer/components/+apps-helm-charts/helm-charts.tsx:65 #: src/renderer/components/+apps-helm-charts/helm-charts.tsx:65
#: src/renderer/components/+workspaces/workspaces.tsx:118 #: src/renderer/components/+workspaces/workspaces.tsx:131
msgid "Description" msgid "Description"
msgstr "Описание" msgstr "Описание"
@ -818,7 +819,7 @@ msgstr ""
msgid "Desired number of replicas" msgid "Desired number of replicas"
msgstr "Нужный уровень реплик" msgstr "Нужный уровень реплик"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:64 #: src/renderer/components/cluster-manager/clusters-menu.tsx:65
msgid "Disconnect" msgid "Disconnect"
msgstr "" msgstr ""
@ -832,7 +833,7 @@ msgstr "Диск"
msgid "Disk:" msgid "Disk:"
msgstr "Диск:" msgstr "Диск:"
#: src/renderer/components/+preferences/preferences.tsx:158 #: src/renderer/components/+preferences/preferences.tsx:165
msgid "Does not affect cluster communications!" msgid "Does not affect cluster communications!"
msgstr "" msgstr ""
@ -841,14 +842,22 @@ msgid "Domains"
msgstr "Домены" msgstr "Домены"
#: src/renderer/components/+preferences/preferences.tsx:129 #: src/renderer/components/+preferences/preferences.tsx:129
msgid "Download Mirror" #~ msgid "Download Mirror"
msgstr "" #~ msgstr ""
#: src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx:90 #: src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx:90
msgid "Download file" msgid "Download file"
msgstr "Скачать файл" msgstr "Скачать файл"
#: src/renderer/components/+preferences/preferences.tsx:130 #: src/renderer/components/+preferences/kubectl-binaries.tsx:39
msgid "Download kubectl binaries"
msgstr ""
#: src/renderer/components/+preferences/kubectl-binaries.tsx:37
msgid "Download kubectl binaries matching to Kubernetes cluster verison."
msgstr ""
#: src/renderer/components/+preferences/kubectl-binaries.tsx:41
msgid "Download mirror for kubectl" msgid "Download mirror for kubectl"
msgstr "" msgstr ""
@ -874,7 +883,7 @@ msgstr "Продолжительность"
msgid "E-mail" msgid "E-mail"
msgstr "Эл. почта" msgstr "Эл. почта"
#: src/renderer/components/+workspaces/workspaces.tsx:112 #: src/renderer/components/+workspaces/workspaces.tsx:125
#: src/renderer/components/menu/menu-actions.tsx:80 #: src/renderer/components/menu/menu-actions.tsx:80
#: src/renderer/components/menu/menu-actions.tsx:81 #: src/renderer/components/menu/menu-actions.tsx:81
msgid "Edit" msgid "Edit"
@ -1001,7 +1010,7 @@ msgstr "От <0>{from}</0> до <1>{to}</1>"
msgid "Fs Group" msgid "Fs Group"
msgstr "" msgstr ""
#: src/renderer/components/+landing-page/landing-page.tsx:23 #: src/renderer/components/+landing-page/landing-page.tsx:37
msgid "Get started by associating one or more clusters to Lens." msgid "Get started by associating one or more clusters to Lens."
msgstr "" msgstr ""
@ -1023,7 +1032,7 @@ msgstr "Группы"
msgid "HPA" msgid "HPA"
msgstr "HPA" msgstr "HPA"
#: src/renderer/components/+preferences/preferences.tsx:147 #: src/renderer/components/+preferences/preferences.tsx:137
msgid "HTTP Proxy" msgid "HTTP Proxy"
msgstr "" msgstr ""
@ -1031,7 +1040,7 @@ msgstr ""
#~ msgid "HTTP Proxy server. Used for communicating with Kubernetes API." #~ msgid "HTTP Proxy server. Used for communicating with Kubernetes API."
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:132 #: src/renderer/components/+preferences/preferences.tsx:145
msgid "Helm" msgid "Helm"
msgstr "" msgstr ""
@ -1051,7 +1060,7 @@ msgstr "Helm установка: {repo}/{name}"
msgid "Helm Upgrade: {0}" msgid "Helm Upgrade: {0}"
msgstr "Helm обновление: {0}" msgstr "Helm обновление: {0}"
#: src/renderer/components/+preferences/preferences.tsx:47 #: src/renderer/components/+preferences/preferences.tsx:51
msgid "Helm branch <0>{0}</0> already in use" msgid "Helm branch <0>{0}</0> already in use"
msgstr "" msgstr ""
@ -1158,11 +1167,11 @@ msgstr "Установка завершена!"
msgid "Installing..." msgid "Installing..."
msgstr "Установка.." msgstr "Установка.."
#: src/renderer/components/input/input.validators.ts:44 #: src/renderer/components/input/input.validators.ts:50
msgid "Invalid account ID" msgid "Invalid account ID"
msgstr "Неверный ID аккаунта" msgstr "Неверный ID аккаунта"
#: src/renderer/components/input/input.validators.ts:15 #: src/renderer/components/input/input.validators.ts:16
msgid "Invalid number" msgid "Invalid number"
msgstr "Неверный номер" msgstr "Неверный номер"
@ -1198,7 +1207,6 @@ msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:69 #: src/renderer/components/+workloads/workloads.tsx:69
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:62 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:62
#: src/renderer/components/+workloads-jobs/jobs.tsx:36 #: src/renderer/components/+workloads-jobs/jobs.tsx:36
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:62
msgid "Jobs" msgid "Jobs"
msgstr "Jobs" msgstr "Jobs"
@ -1242,6 +1250,10 @@ msgstr "Файл конфигурации"
msgid "Kubeconfig File" msgid "Kubeconfig File"
msgstr "Файл конфигурации" msgstr "Файл конфигурации"
#: src/renderer/components/+preferences/kubectl-binaries.tsx:35
msgid "Kubectl Binary"
msgstr ""
#: src/renderer/components/+nodes/node-details.tsx:98 #: src/renderer/components/+nodes/node-details.tsx:98
msgid "Kubelet version" msgid "Kubelet version"
msgstr "Версия Kubelet" msgstr "Версия Kubelet"
@ -1358,7 +1370,7 @@ msgstr "Макс. подов"
msgid "Max Unavailable" msgid "Max Unavailable"
msgstr "" msgstr ""
#: src/renderer/components/input/input.validators.ts:35 #: src/renderer/components/input/input.validators.ts:41
msgid "Maximum length is {maxLength}" msgid "Maximum length is {maxLength}"
msgstr "Максимальная длина {maxLength}" msgstr "Максимальная длина {maxLength}"
@ -1434,7 +1446,7 @@ msgstr "Мин. подов"
msgid "Minimize" msgid "Minimize"
msgstr "Минимизировать" msgstr "Минимизировать"
#: src/renderer/components/input/input.validators.ts:30 #: src/renderer/components/input/input.validators.ts:36
msgid "Minimum length is {minLength}" msgid "Minimum length is {minLength}"
msgstr "Минимальная длина {minLength}" msgstr "Минимальная длина {minLength}"
@ -1498,7 +1510,7 @@ msgstr "Установки"
#: src/renderer/components/+workloads-pods/pods.tsx:74 #: src/renderer/components/+workloads-pods/pods.tsx:74
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:50 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:50
#: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:40 #: src/renderer/components/+workloads-statefulsets/statefulsets.tsx:40
#: src/renderer/components/+workspaces/workspaces.tsx:117 #: src/renderer/components/+workspaces/workspaces.tsx:130
#: src/renderer/components/dock/edit-resource.tsx:90 #: src/renderer/components/dock/edit-resource.tsx:90
#: src/renderer/components/kube-object/kube-object-meta.tsx:20 #: src/renderer/components/kube-object/kube-object-meta.tsx:20
msgid "Name" msgid "Name"
@ -1566,7 +1578,7 @@ msgstr "Namespaces"
msgid "Namespaces: {0}" msgid "Namespaces: {0}"
msgstr "Namespaces: {0}" msgstr "Namespaces: {0}"
#: src/renderer/components/+preferences/preferences.tsx:157 #: src/renderer/components/+preferences/preferences.tsx:164
msgid "Needed with some corporate proxies that do certificate re-writing." msgid "Needed with some corporate proxies that do certificate re-writing."
msgstr "" msgstr ""
@ -1627,7 +1639,7 @@ msgstr "Нет доступных нод."
#~ msgid "No contexts available or they already added" #~ msgid "No contexts available or they already added"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:275 #: src/renderer/components/+add-cluster/add-cluster.tsx:260
msgid "No contexts available or they have been added already" msgid "No contexts available or they have been added already"
msgstr "" msgstr ""
@ -1743,7 +1755,7 @@ msgid "Organization"
msgstr "Организация" msgstr "Организация"
#: src/renderer/components/+workloads/workloads.tsx:29 #: src/renderer/components/+workloads/workloads.tsx:29
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:35 #: src/renderer/components/+workloads-overview/overview-statuses.tsx:45
msgid "Overview" msgid "Overview"
msgstr "Обзор" msgstr "Обзор"
@ -1759,7 +1771,7 @@ msgstr "Параллелизм"
msgid "Parameters" msgid "Parameters"
msgstr "Параметры" msgstr "Параметры"
#: src/renderer/components/+add-cluster/add-cluster.tsx:245 #: src/renderer/components/+add-cluster/add-cluster.tsx:230
msgid "Paste as text" msgid "Paste as text"
msgstr "" msgstr ""
@ -1849,7 +1861,6 @@ msgstr "Командная строка пода"
#: src/renderer/components/+workloads/workloads.tsx:37 #: src/renderer/components/+workloads/workloads.tsx:37
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:47 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:47
#: src/renderer/components/+workloads-deployments/deployments.tsx:60 #: src/renderer/components/+workloads-deployments/deployments.tsx:60
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:42
#: src/renderer/components/+workloads-pods/pod-details-list.tsx:89 #: src/renderer/components/+workloads-pods/pod-details-list.tsx:89
#: src/renderer/components/+workloads-pods/pods.tsx:73 #: src/renderer/components/+workloads-pods/pods.tsx:73
#: src/renderer/components/+workloads-replicasets/replicasets.tsx:52 #: src/renderer/components/+workloads-replicasets/replicasets.tsx:52
@ -1900,7 +1911,7 @@ msgstr ""
#~ msgid "Pro-Tip: paste kubeconfig to collect available contexts" #~ msgid "Pro-Tip: paste kubeconfig to collect available contexts"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:263 #: src/renderer/components/+add-cluster/add-cluster.tsx:248
msgid "Pro-Tip: paste kubeconfig to get available contexts" msgid "Pro-Tip: paste kubeconfig to get available contexts"
msgstr "" msgstr ""
@ -1908,7 +1919,7 @@ msgstr ""
#~ msgid "Pro-Tip: paste kubeconfig to parse available contexts" #~ msgid "Pro-Tip: paste kubeconfig to parse available contexts"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:254 #: src/renderer/components/+add-cluster/add-cluster.tsx:239
msgid "Pro-Tip: you can also drag-n-drop kubeconfig file to this area" msgid "Pro-Tip: you can also drag-n-drop kubeconfig file to this area"
msgstr "" msgstr ""
@ -1925,11 +1936,11 @@ msgstr ""
msgid "Provisioner" msgid "Provisioner"
msgstr "Комиссия" msgstr "Комиссия"
#: src/renderer/components/+preferences/preferences.tsx:150 #: src/renderer/components/+preferences/preferences.tsx:140
msgid "Proxy is used only for non-cluster communication." msgid "Proxy is used only for non-cluster communication."
msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:308 #: src/renderer/components/+add-cluster/add-cluster.tsx:293
msgid "Proxy settings" msgid "Proxy settings"
msgstr "" msgstr ""
@ -2009,10 +2020,10 @@ msgstr "Установка: {0}"
msgid "Releases" msgid "Releases"
msgstr "Релизы" msgstr "Релизы"
#: src/renderer/components/+preferences/preferences.tsx:139 #: src/renderer/components/+preferences/preferences.tsx:152
#: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60 #: src/renderer/components/+user-management-roles-bindings/role-binding-details.tsx:60
#: src/renderer/components/cluster-manager/clusters-menu.tsx:74 #: src/renderer/components/cluster-manager/clusters-menu.tsx:76
#: src/renderer/components/cluster-manager/clusters-menu.tsx:80 #: src/renderer/components/cluster-manager/clusters-menu.tsx:82
#: src/renderer/components/item-object-list/item-list-layout.tsx:179 #: src/renderer/components/item-object-list/item-list-layout.tsx:179
#: src/renderer/components/menu/menu-actions.tsx:49 #: src/renderer/components/menu/menu-actions.tsx:49
#: src/renderer/components/menu/menu-actions.tsx:85 #: src/renderer/components/menu/menu-actions.tsx:85
@ -2023,7 +2034,7 @@ msgstr "Удалить"
msgid "Remove <0>{releaseNames}</0>?" msgid "Remove <0>{releaseNames}</0>?"
msgstr "Удалить <0>{releaseNames}</0>?" msgstr "Удалить <0>{releaseNames}</0>?"
#: src/renderer/components/+workspaces/workspaces.tsx:51 #: src/renderer/components/+workspaces/workspaces.tsx:52
msgid "Remove Workspace" msgid "Remove Workspace"
msgstr "" msgstr ""
@ -2051,7 +2062,7 @@ msgstr "Удалить выбранные элементы ({0})"
msgid "Remove {resourceKind} <0>{resourceName}</0>?" msgid "Remove {resourceKind} <0>{resourceName}</0>?"
msgstr "Удалить {resourceKind} <0>{resourceName}</0>?" msgstr "Удалить {resourceKind} <0>{resourceName}</0>?"
#: src/renderer/components/+preferences/preferences.tsx:114 #: src/renderer/components/+preferences/preferences.tsx:122
msgid "Removing helm branch <0>{0}</0> has failed: {1}" msgid "Removing helm branch <0>{0}</0> has failed: {1}"
msgstr "" msgstr ""
@ -2075,7 +2086,7 @@ msgstr "Реплики"
msgid "Repo/Name" msgid "Repo/Name"
msgstr "Репозиторий/Имя" msgstr "Репозиторий/Имя"
#: src/renderer/components/+preferences/preferences.tsx:133 #: src/renderer/components/+preferences/preferences.tsx:146
msgid "Repositories" msgid "Repositories"
msgstr "" msgstr ""
@ -2110,7 +2121,7 @@ msgstr ""
msgid "Required field" msgid "Required field"
msgstr "Обязательное поле" msgstr "Обязательное поле"
#: src/renderer/components/+add-cluster/add-cluster.tsx:250 #: src/renderer/components/+add-cluster/add-cluster.tsx:235
#: src/renderer/components/item-object-list/page-filters-list.tsx:31 #: src/renderer/components/item-object-list/page-filters-list.tsx:31
msgid "Reset" msgid "Reset"
msgstr "Сбросить" msgstr "Сбросить"
@ -2253,7 +2264,7 @@ msgstr ""
#: src/renderer/components/+config-maps/config-map-details.tsx:78 #: src/renderer/components/+config-maps/config-map-details.tsx:78
#: src/renderer/components/+config-secrets/secret-details.tsx:97 #: src/renderer/components/+config-secrets/secret-details.tsx:97
#: src/renderer/components/+workloads-pods/pod-logs-dialog.tsx:216 #: src/renderer/components/+workloads-pods/pod-logs-dialog.tsx:216
#: src/renderer/components/+workspaces/workspaces.tsx:120 #: src/renderer/components/+workspaces/workspaces.tsx:132
#: src/renderer/components/dock/edit-resource.tsx:88 #: src/renderer/components/dock/edit-resource.tsx:88
msgid "Save" msgid "Save"
msgstr "Сохранить" msgstr "Сохранить"
@ -2342,13 +2353,13 @@ msgstr "Выберите квоту..."
#~ msgid "Select context(s)" #~ msgid "Select context(s)"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:272 #: src/renderer/components/+add-cluster/add-cluster.tsx:257
#~ msgid "Select contexts" msgid "Select contexts"
#~ msgstr "" msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:272 #: src/renderer/components/+add-cluster/add-cluster.tsx:272
msgid "Select contexts (available: {0})" #~ msgid "Select contexts (available: {0})"
msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:76 #: src/renderer/components/+add-cluster/add-cluster.tsx:76
#: src/renderer/components/+add-cluster/add-cluster.tsx:76 #: src/renderer/components/+add-cluster/add-cluster.tsx:76
@ -2372,7 +2383,7 @@ msgstr ""
#~ msgid "Select kubeconfig" #~ msgid "Select kubeconfig"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:244 #: src/renderer/components/+add-cluster/add-cluster.tsx:229
msgid "Select kubeconfig file" msgid "Select kubeconfig file"
msgstr "" msgstr ""
@ -2400,7 +2411,7 @@ msgstr "Выбрать сервисные аккаунты"
#~ msgid "Selected contexts ({0}): <0>{1}</0>" #~ msgid "Selected contexts ({0}): <0>{1}</0>"
#~ msgstr "" #~ msgstr ""
#: src/renderer/components/+add-cluster/add-cluster.tsx:271 #: src/renderer/components/+add-cluster/add-cluster.tsx:256
msgid "Selected contexts: <0>{0}</0>" msgid "Selected contexts: <0>{0}</0>"
msgstr "" msgstr ""
@ -2504,7 +2515,6 @@ msgid "Stateful Sets"
msgstr "" msgstr ""
#: src/renderer/components/+workloads/workloads.tsx:61 #: src/renderer/components/+workloads/workloads.tsx:61
#: src/renderer/components/+workloads-overview/overview-statuses.tsx:52
msgid "StatefulSets" msgid "StatefulSets"
msgstr "StatefulSets" msgstr "StatefulSets"
@ -2606,11 +2616,11 @@ msgstr "TLS"
msgid "Taints" msgid "Taints"
msgstr "Метки блокировки" msgstr "Метки блокировки"
#: src/renderer/components/+preferences/preferences.tsx:161 #: src/renderer/components/+preferences/preferences.tsx:168
msgid "Telemetry & Usage Tracking" msgid "Telemetry & Usage Tracking"
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:164 #: src/renderer/components/+preferences/preferences.tsx:171
msgid "Telemetry & usage data is collected to continuously improve the Lens experience." msgid "Telemetry & usage data is collected to continuously improve the Lens experience."
msgstr "" msgstr ""
@ -2630,15 +2640,19 @@ msgstr "Для контейнера нет логов."
msgid "There are no logs available." msgid "There are no logs available."
msgstr "Логи отсутствуют." msgstr "Логи отсутствуют."
#: src/renderer/components/input/input.validators.ts:5 #: src/renderer/components/input/input.validators.ts:6
msgid "This field is required" msgid "This field is required"
msgstr "Это обязательное поле" msgstr "Это обязательное поле"
#: src/renderer/components/cluster-manager/clusters-menu.tsx:106 #: src/renderer/components/input/input.validators.ts:31
msgid "This field must be a valid path"
msgstr ""
#: src/renderer/components/+landing-page/landing-page.tsx:25
msgid "This is the quick launch menu." msgid "This is the quick launch menu."
msgstr "" msgstr ""
#: src/renderer/components/+preferences/preferences.tsx:156 #: src/renderer/components/+preferences/preferences.tsx:163
msgid "This will make Lens to trust ANY certificate authority without any validations." msgid "This will make Lens to trust ANY certificate authority without any validations."
msgstr "" msgstr ""
@ -2662,13 +2676,13 @@ msgstr "Толерантности"
msgid "Transmit" msgid "Transmit"
msgstr "Транзит" msgstr "Транзит"
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:107 #: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:106
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80
msgid "Trigger" msgid "Trigger"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:103 #: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:102
msgid "Trigger CronJob <0>{cronjobName}</0>" msgid "Trigger CronJob <0>{cronjobName}</0>"
msgstr "" msgstr ""
@ -2691,7 +2705,7 @@ msgstr ""
msgid "Type" msgid "Type"
msgstr "Тип" msgstr "Тип"
#: src/renderer/components/+preferences/preferences.tsx:148 #: src/renderer/components/+preferences/preferences.tsx:138
msgid "Type HTTP proxy url (example: http://proxy.acme.org:8080)" msgid "Type HTTP proxy url (example: http://proxy.acme.org:8080)"
msgstr "" msgstr ""
@ -2828,11 +2842,11 @@ msgstr "Ожидание запуска сервисов"
msgid "Warnings: {0}" msgid "Warnings: {0}"
msgstr "Предупреждения: {0}" msgstr "Предупреждения: {0}"
#: src/renderer/components/+landing-page/landing-page.tsx:20 #: src/renderer/components/+landing-page/landing-page.tsx:34
msgid "Welcome!" msgid "Welcome!"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:79 #: src/renderer/components/+workspaces/workspaces.tsx:88
msgid "What is a Workspace?" msgid "What is a Workspace?"
msgstr "" msgstr ""
@ -2845,19 +2859,19 @@ msgid "Workloads"
msgstr "Ресурсы" msgstr "Ресурсы"
#: src/renderer/components/+workspaces/workspace-menu.tsx:39 #: src/renderer/components/+workspaces/workspace-menu.tsx:39
#: src/renderer/components/+workspaces/workspaces.tsx:91 #: src/renderer/components/+workspaces/workspaces.tsx:100
msgid "Workspaces" msgid "Workspaces"
msgstr "" msgstr ""
#: src/renderer/components/+workspaces/workspaces.tsx:81 #: src/renderer/components/+workspaces/workspaces.tsx:90
msgid "Workspaces are used to organize number of clusters into logical groups." msgid "Workspaces are used to organize number of clusters into logical groups."
msgstr "" msgstr ""
#: src/renderer/components/input/input.validators.ts:10 #: src/renderer/components/input/input.validators.ts:11
msgid "Wrong email format" msgid "Wrong email format"
msgstr "Неверный формат электронной почты" msgstr "Неверный формат электронной почты"
#: src/renderer/components/input/input.validators.ts:25 #: src/renderer/components/input/input.validators.ts:26
msgid "Wrong url format" msgid "Wrong url format"
msgstr "Неверный url формат" msgstr "Неверный url формат"
@ -2916,7 +2930,7 @@ msgstr ""
msgid "never" msgid "never"
msgstr "" msgstr ""
#: src/renderer/components/cluster-manager/clusters-menu.tsx:121 #: src/renderer/components/cluster-manager/clusters-menu.tsx:133
msgid "new" msgid "new"
msgstr "" msgstr ""

View File

@ -2,7 +2,7 @@
"name": "kontena-lens", "name": "kontena-lens",
"productName": "Lens", "productName": "Lens",
"description": "Lens - The Kubernetes IDE", "description": "Lens - The Kubernetes IDE",
"version": "3.6.4", "version": "3.6.5-rc.1",
"main": "static/build/main.js", "main": "static/build/main.js",
"copyright": "© 2020, Mirantis, Inc.", "copyright": "© 2020, Mirantis, Inc.",
"license": "MIT", "license": "MIT",
@ -39,7 +39,7 @@
}, },
"config": { "config": {
"bundledKubectlVersion": "1.17.11", "bundledKubectlVersion": "1.17.11",
"bundledHelmVersion": "3.3.1" "bundledHelmVersion": "3.3.4"
}, },
"engines": { "engines": {
"node": ">=12.0 <13.0" "node": ">=12.0 <13.0"

View File

@ -93,6 +93,10 @@ export class BaseStore<T = any> extends Singleton {
} }
} }
unregisterIpcListener() {
ipcRenderer.removeAllListeners(this.syncChannel)
}
disableSync() { disableSync() {
this.syncDisposers.forEach(dispose => dispose()); this.syncDisposers.forEach(dispose => dispose());
this.syncDisposers.length = 0; this.syncDisposers.length = 0;

View File

@ -9,7 +9,7 @@ export const clusterIpc = {
const cluster = clusterStore.getById(clusterId); const cluster = clusterStore.getById(clusterId);
if (cluster) { if (cluster) {
if (frameId) cluster.frameId = frameId; // save cluster's webFrame.routingId to be able to send push-updates if (frameId) cluster.frameId = frameId; // save cluster's webFrame.routingId to be able to send push-updates
return cluster.activate(true); return cluster.activate();
} }
}, },
}), }),

View File

@ -1,6 +1,6 @@
import type { WorkspaceId } from "./workspace-store"; import type { WorkspaceId } from "./workspace-store";
import path from "path"; import path from "path";
import { app, ipcRenderer, remote } from "electron"; import { app, ipcRenderer, remote, webFrame, webContents } from "electron";
import { unlink } from "fs-extra"; import { unlink } from "fs-extra";
import { action, computed, observable, toJS } from "mobx"; import { action, computed, observable, toJS } from "mobx";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";
@ -73,20 +73,27 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
migrations: migrations, migrations: migrations,
}); });
if (ipcRenderer) {
ipcRenderer.on("cluster:state", (event, model: ClusterState) => {
this.applyWithoutSync(() => {
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host}`, model);
this.getById(model.id)?.updateModel(model);
})
})
}
} }
@observable activeClusterId: ClusterId; @observable activeClusterId: ClusterId;
@observable removedClusters = observable.map<ClusterId, Cluster>(); @observable removedClusters = observable.map<ClusterId, Cluster>();
@observable clusters = observable.map<ClusterId, Cluster>(); @observable clusters = observable.map<ClusterId, Cluster>();
registerIpcListener() {
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`)
ipcRenderer.on("cluster:state", (event, model: ClusterState) => {
this.applyWithoutSync(() => {
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, model);
this.getById(model.id)?.updateModel(model);
})
})
}
unregisterIpcListener() {
super.unregisterIpcListener()
ipcRenderer.removeAllListeners("cluster:state")
}
@computed get activeCluster(): Cluster | null { @computed get activeCluster(): Cluster | null {
return this.getById(this.activeClusterId); return this.getById(this.activeClusterId);
} }
@ -122,10 +129,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
return this.clusters.size > 0; return this.clusters.size > 0;
} }
hasContext(name: string) {
return this.clustersList.some(cluster => cluster.contextName === name);
}
getById(id: ClusterId): Cluster { getById(id: ClusterId): Cluster {
return this.clusters.get(id); return this.clusters.get(id);
} }

View File

@ -146,14 +146,22 @@ describe("config with existing clusters", () => {
id: 'cluster1', id: 'cluster1',
kubeConfig: 'foo', kubeConfig: 'foo',
contextName: 'foo', contextName: 'foo',
preferences: { terminalCWD: '/foo' } preferences: { terminalCWD: '/foo' },
workspace: 'default'
}, },
{ {
id: 'cluster2', id: 'cluster2',
kubeConfig: 'foo2', kubeConfig: 'foo2',
contextName: 'foo2', contextName: 'foo2',
preferences: { terminalCWD: '/foo2' } preferences: { terminalCWD: '/foo2' }
} },
{
id: 'cluster3',
kubeConfig: 'foo',
contextName: 'foo',
preferences: { terminalCWD: '/foo' },
workspace: 'foo'
},
] ]
}) })
} }
@ -183,10 +191,12 @@ describe("config with existing clusters", () => {
it("allows getting all of the clusters", async () => { it("allows getting all of the clusters", async () => {
const storedClusters = clusterStore.clustersList; const storedClusters = clusterStore.clustersList;
expect(storedClusters.length).toBe(3)
expect(storedClusters[0].id).toBe('cluster1') expect(storedClusters[0].id).toBe('cluster1')
expect(storedClusters[0].preferences.terminalCWD).toBe('/foo') expect(storedClusters[0].preferences.terminalCWD).toBe('/foo')
expect(storedClusters[1].id).toBe('cluster2') expect(storedClusters[1].id).toBe('cluster2')
expect(storedClusters[1].preferences.terminalCWD).toBe('/foo2') expect(storedClusters[1].preferences.terminalCWD).toBe('/foo2')
expect(storedClusters[2].id).toBe('cluster3')
}) })
}) })

View File

@ -1,9 +1,10 @@
import { PrometheusLens } from "../main/prometheus/lens"; import { PrometheusLens } from "../main/prometheus/lens";
import { PrometheusHelm } from "../main/prometheus/helm"; import { PrometheusHelm } from "../main/prometheus/helm";
import { PrometheusOperator } from "../main/prometheus/operator"; import { PrometheusOperator } from "../main/prometheus/operator";
import { PrometheusStacklight } from "../main/prometheus/stacklight";
import { PrometheusProviderRegistry } from "../main/prometheus/provider-registry"; import { PrometheusProviderRegistry } from "../main/prometheus/provider-registry";
[PrometheusLens, PrometheusHelm, PrometheusOperator].forEach(providerClass => { [PrometheusLens, PrometheusHelm, PrometheusOperator, PrometheusStacklight].forEach(providerClass => {
const provider = new providerClass() const provider = new providerClass()
PrometheusProviderRegistry.registerProvider(provider.id, provider) PrometheusProviderRegistry.registerProvider(provider.id, provider)
}); });

View File

@ -59,8 +59,6 @@ export class UserStore extends BaseStore<UserStoreModel> {
colorTheme: UserStore.defaultTheme, colorTheme: UserStore.defaultTheme,
downloadMirror: "default", downloadMirror: "default",
downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version
downloadBinariesPath: this.getDefaultKubectlPath(),
kubectlBinariesPath: ""
}; };
get isNewVersion() { get isNewVersion() {

View File

@ -56,10 +56,10 @@ export class Cluster implements ClusterModel {
@observable kubeConfigPath: string; @observable kubeConfigPath: string;
@observable apiUrl: string; // cluster server url @observable apiUrl: string; // cluster server url
@observable kubeProxyUrl: string; // lens-proxy to kube-api url @observable kubeProxyUrl: string; // lens-proxy to kube-api url
@observable online: boolean; @observable online = false;
@observable accessible: boolean; @observable accessible = false;
@observable ready: boolean; @observable ready = false;
@observable disconnected: boolean; @observable disconnected = true;
@observable failureReason: string; @observable failureReason: string;
@observable nodes = 0; @observable nodes = 0;
@observable version: string; @observable version: string;
@ -124,13 +124,14 @@ export class Cluster implements ClusterModel {
this.eventDisposers.length = 0; this.eventDisposers.length = 0;
} }
async activate(init = false) { @action
async activate() {
logger.info(`[CLUSTER]: activate`, this.getMeta()); logger.info(`[CLUSTER]: activate`, this.getMeta());
await this.whenInitialized; await this.whenInitialized;
if (!this.eventDisposers.length) { if (!this.eventDisposers.length) {
this.bindEvents(); this.bindEvents();
} }
if (this.disconnected || (!init && !this.accessible)) { if (this.disconnected || !this.accessible) {
await this.reconnect(); await this.reconnect();
} }
await this.refreshConnectionStatus() await this.refreshConnectionStatus()
@ -143,6 +144,7 @@ export class Cluster implements ClusterModel {
return this.pushState(); return this.pushState();
} }
@action
async reconnect() { async reconnect() {
logger.info(`[CLUSTER]: reconnect`, this.getMeta()); logger.info(`[CLUSTER]: reconnect`, this.getMeta());
this.contextHandler.stopServer(); this.contextHandler.stopServer();

View File

@ -36,7 +36,7 @@ const packageMirrors: Map<string, string> = new Map([
let bundledPath: string let bundledPath: string
const initScriptVersionString = "# lens-initscript v3\n" const initScriptVersionString = "# lens-initscript v3\n"
if (isDevelopment || isTestEnv) { if (isDevelopment || isTestEnv) {
const platformName = isWindows ? "windows" : process.platform const platformName = isWindows ? "windows" : process.platform
bundledPath = path.join(process.cwd(), "binaries", "client", platformName, process.arch, "kubectl") bundledPath = path.join(process.cwd(), "binaries", "client", platformName, process.arch, "kubectl")
} else { } else {
@ -110,7 +110,11 @@ export class Kubectl {
} }
protected getDownloadDir() { protected getDownloadDir() {
return userStore.preferences?.downloadBinariesPath || Kubectl.kubectlDir if (userStore.preferences?.downloadBinariesPath) {
return path.join(userStore.preferences.downloadBinariesPath, "kubectl")
}
return Kubectl.kubectlDir
} }
public async getPath(bundled = false): Promise<string> { public async getPath(bundled = false): Promise<string> {
@ -279,6 +283,12 @@ export class Kubectl {
bashScript += "fi\n" bashScript += "fi\n"
bashScript += `export PATH="${helmPath}:${kubectlPath}:$PATH"\n` bashScript += `export PATH="${helmPath}:${kubectlPath}:$PATH"\n`
bashScript += "export KUBECONFIG=\"$tempkubeconfig\"\n" bashScript += "export KUBECONFIG=\"$tempkubeconfig\"\n"
bashScript += "NO_PROXY=\",${NO_PROXY:-localhost},\"\n"
bashScript += "NO_PROXY=\"${NO_PROXY//,localhost,/,}\"\n"
bashScript += "NO_PROXY=\"${NO_PROXY//,127.0.0.1,/,}\"\n"
bashScript += "NO_PROXY=\"localhost,127.0.0.1${NO_PROXY%,}\"\n"
bashScript += "export NO_PROXY\n"
bashScript += "unset tempkubeconfig\n" bashScript += "unset tempkubeconfig\n"
await fsPromises.writeFile(bashScriptPath, bashScript.toString(), { mode: 0o644 }) await fsPromises.writeFile(bashScriptPath, bashScript.toString(), { mode: 0o644 })
@ -304,6 +314,11 @@ export class Kubectl {
zshScript += "d=${d/#:/}\n" zshScript += "d=${d/#:/}\n"
zshScript += "export PATH=\"$helmpath:$kubectlpath:${d/%:/}\"\n" zshScript += "export PATH=\"$helmpath:$kubectlpath:${d/%:/}\"\n"
zshScript += "export KUBECONFIG=\"$tempkubeconfig\"\n" zshScript += "export KUBECONFIG=\"$tempkubeconfig\"\n"
zshScript += "NO_PROXY=\",${NO_PROXY:-localhost},\"\n"
zshScript += "NO_PROXY=\"${NO_PROXY//,localhost,/,}\"\n"
zshScript += "NO_PROXY=\"${NO_PROXY//,127.0.0.1,/,}\"\n"
zshScript += "NO_PROXY=\"localhost,127.0.0.1${NO_PROXY%,}\"\n"
zshScript += "export NO_PROXY\n"
zshScript += "unset tempkubeconfig\n" zshScript += "unset tempkubeconfig\n"
zshScript += "unset OLD_ZDOTDIR\n" zshScript += "unset OLD_ZDOTDIR\n"
await fsPromises.writeFile(zshScriptPath, zshScript.toString(), { mode: 0o644 }) await fsPromises.writeFile(zshScriptPath, zshScript.toString(), { mode: 0o644 })

View File

@ -44,9 +44,7 @@ export class LensProxy {
const spdyProxy = spdy.createServer({ const spdyProxy = spdy.createServer({
spdy: { spdy: {
plain: true, plain: true,
connection: { protocols: ["http/1.1", "spdy/3.1"]
autoSpdy31: true
}
} }
}, (req: http.IncomingMessage, res: http.ServerResponse) => { }, (req: http.IncomingMessage, res: http.ServerResponse) => {
this.handleRequest(proxy, req, res) this.handleRequest(proxy, req, res)
@ -55,11 +53,7 @@ export class LensProxy {
if (req.url.startsWith(`${apiPrefix}?`)) { if (req.url.startsWith(`${apiPrefix}?`)) {
this.handleWsUpgrade(req, socket, head) this.handleWsUpgrade(req, socket, head)
} else { } else {
if (req.headers.upgrade?.startsWith("SPDY")) { this.handleProxyUpgrade(proxy, req, socket, head)
this.handleSpdyProxy(proxy, req, socket, head)
} else {
socket.end()
}
} }
}) })
spdyProxy.on("error", (err) => { spdyProxy.on("error", (err) => {
@ -68,17 +62,45 @@ export class LensProxy {
return spdyProxy return spdyProxy
} }
protected async handleSpdyProxy(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) { protected async handleProxyUpgrade(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) {
const cluster = this.clusterManager.getClusterForRequest(req) const cluster = this.clusterManager.getClusterForRequest(req)
if (cluster) { if (cluster) {
const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "") const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "")
const apiUrl = url.parse(cluster.apiUrl) const apiUrl = url.parse(cluster.apiUrl)
const res = new http.ServerResponse(req) const pUrl = url.parse(proxyUrl)
res.assignSocket(socket) const connectOpts = { port: parseInt(pUrl.port), host: pUrl.hostname }
res.setHeader("Location", proxyUrl) const proxySocket = new net.Socket()
res.setHeader("Host", apiUrl.hostname) proxySocket.connect(connectOpts, () => {
res.statusCode = 302 proxySocket.write(`${req.method} ${pUrl.path} HTTP/1.1\r\n`)
res.end() proxySocket.write(`Host: ${apiUrl.host}\r\n`)
for (let i = 0; i < req.rawHeaders.length; i += 2) {
const key = req.rawHeaders[i]
if (key !== "Host" && key !== "Authorization") {
proxySocket.write(`${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}\r\n`)
}
}
proxySocket.write("\r\n")
proxySocket.write(head)
})
proxySocket.on('data', function (chunk) {
socket.write(chunk)
})
proxySocket.on('end', function () {
socket.end()
})
proxySocket.on('error', function (err) {
socket.write("HTTP/" + req.httpVersion + " 500 Connection error\r\n\r\n");
socket.end()
})
socket.on('data', function (chunk) {
proxySocket.write(chunk)
})
socket.on('end', function () {
proxySocket.end()
})
socket.on('error', function () {
proxySocket.end()
})
} }
} }
@ -134,7 +156,6 @@ export class LensProxy {
protected async handleRequest(proxy: httpProxy, req: http.IncomingMessage, res: http.ServerResponse) { protected async handleRequest(proxy: httpProxy, req: http.IncomingMessage, res: http.ServerResponse) {
const cluster = this.clusterManager.getClusterForRequest(req) const cluster = this.clusterManager.getClusterForRequest(req)
if (cluster) { if (cluster) {
await cluster.contextHandler.ensureServer();
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler) const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler)
if (proxyTarget) { if (proxyTarget) {
// allow to fetch apis in "clusterId.localhost:port" from "localhost:port" // allow to fetch apis in "clusterId.localhost:port" from "localhost:port"

View File

@ -37,19 +37,19 @@ export class PrometheusOperator implements PrometheusProvider {
memoryUsage: ` memoryUsage: `
sum( sum(
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes) node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
) by (node) )
`.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`), `.replace(/_bytes/g, `_bytes * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}`),
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`, memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"})`,
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`, memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"})`,
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`, memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"})`,
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}) by (node)`, cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`,
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`, cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`,
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`, cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"})`,
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`, cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"})`,
podUsage: `sum(kubelet_running_pod_count{node=~"${opts.nodes}"})`, podUsage: `sum(kubelet_running_pod_count{node=~"${opts.nodes}"})`,
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`, podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"})`,
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}) by (node)`, fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`,
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"}) by (node)` fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"} - node_filesystem_avail_bytes{mountpoint="/"} * on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`
} }
case 'nodes': case 'nodes':
return { return {
@ -62,10 +62,10 @@ export class PrometheusOperator implements PrometheusProvider {
} }
case 'pods': case 'pods':
return { return {
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`, cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`, cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",image!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`, memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`, fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,

View File

@ -0,0 +1,83 @@
import { PrometheusProvider, PrometheusQueryOpts, PrometheusQuery, PrometheusService } from "./provider-registry";
import { CoreV1Api } from "@kubernetes/client-node";
import logger from "../logger"
export class PrometheusStacklight implements PrometheusProvider {
id = "stacklight"
name = "Stacklight"
rateAccuracy = "1m"
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService> {
try {
const resp = await client.readNamespacedService("prometheus-server", "stacklight")
const service = resp.body
return {
id: this.id,
namespace: service.metadata.namespace,
service: service.metadata.name,
port: service.spec.ports[0].port
}
} catch(error) {
logger.warn(`PrometheusLens: failed to list services: ${error.response.body.message}`)
}
}
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery {
switch(opts.category) {
case 'cluster':
return {
memoryUsage: `
sum(
node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)
) by (kubernetes_name)
`.replace(/_bytes/g, `_bytes{node=~"${opts.nodes}"}`),
memoryRequests: `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="memory"}) by (component)`,
memoryLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="memory"}) by (component)`,
memoryCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="memory"}) by (component)`,
cpuUsage: `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`,
cpuRequests:`sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
cpuLimits: `sum(kube_pod_container_resource_limits{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
cpuCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="cpu"}) by (component)`,
podUsage: `sum(kubelet_running_pod_count{instance=~"${opts.nodes}"})`,
podCapacity: `sum(kube_node_status_capacity{node=~"${opts.nodes}", resource="pods"}) by (component)`,
fsSize: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`,
fsUsage: `sum(node_filesystem_size_bytes{node=~"${opts.nodes}", mountpoint="/"} - node_filesystem_avail_bytes{node=~"${opts.nodes}", mountpoint="/"}) by (node)`
}
case 'nodes':
return {
memoryUsage: `sum (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) by (node)`,
memoryCapacity: `sum(kube_node_status_capacity{resource="memory"}) by (node)`,
cpuUsage: `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`,
cpuCapacity: `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`,
fsSize: `sum(node_filesystem_size_bytes{mountpoint="/"}) by (node)`,
fsUsage: `sum(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) by (node)`
}
case 'pods':
return {
cpuUsage: `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
cpuRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
cpuLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryUsage: `sum(container_memory_working_set_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryRequests: `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
memoryLimits: `sum(kube_pod_container_resource_limits{pod=~"${opts.pods}",resource="memory",namespace="${opts.namespace}"}) by (${opts.selector})`,
fsUsage: `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`,
networkReceive: `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`,
networkTransmit: `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`
}
case 'pvc':
return {
diskUsage: `sum(kubelet_volume_stats_used_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`,
diskCapacity: `sum(kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="${opts.pvc}"}) by (persistentvolumeclaim, namespace)`
}
case 'ingress':
const bytesSent = (ingress: string, statuses: string) =>
`sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}", status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress)`
return {
bytesSentSuccess: bytesSent(opts.igress, "^2\\\\d*"),
bytesSentFailure: bytesSent(opts.ingres, "^5\\\\d*"),
requestDurationSeconds: `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`,
responseDurationSeconds: `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}"}[${this.rateAccuracy}])) by (ingress)`
}
}
}
}

View File

@ -125,6 +125,8 @@ export class ShellSession extends EventEmitter {
if (this.preferences.httpsProxy) { if (this.preferences.httpsProxy) {
env["HTTPS_PROXY"] = this.preferences.httpsProxy env["HTTPS_PROXY"] = this.preferences.httpsProxy
} }
const no_proxy = ["localhost", "127.0.0.1", env["NO_PROXY"]]
env["NO_PROXY"] = no_proxy.filter(address => !!address).join()
if (env.DEBUG) { // do not pass debug option to bash if (env.DEBUG) { // do not pass debug option to bash
delete env["DEBUG"] delete env["DEBUG"]
} }

View File

@ -7,6 +7,7 @@ import version260Beta3 from "./2.6.0-beta.3"
import version270Beta0 from "./2.7.0-beta.0" import version270Beta0 from "./2.7.0-beta.0"
import version270Beta1 from "./2.7.0-beta.1" import version270Beta1 from "./2.7.0-beta.1"
import version360Beta1 from "./3.6.0-beta.1" import version360Beta1 from "./3.6.0-beta.1"
import snap from "./snap"
export default { export default {
...version200Beta2, ...version200Beta2,
@ -16,4 +17,5 @@ export default {
...version270Beta0, ...version270Beta0,
...version270Beta1, ...version270Beta1,
...version360Beta1, ...version360Beta1,
...snap
} }

View File

@ -0,0 +1,33 @@
// Fix embedded kubeconfig paths under snap config
import { migration } from "../migration-wrapper";
import { ClusterModel, ClusterStore } from "../../common/cluster-store";
import { getAppVersion } from "../../common/utils/app-version";
import fs from "fs"
export default migration({
version: getAppVersion(), // Run always after upgrade
run(store, printLog) {
if (!process.env["SNAP"]) return;
printLog("Migrating embedded kubeconfig paths")
const storedClusters: ClusterModel[] = store.get("clusters") || [];
if (!storedClusters.length) return;
printLog("Number of clusters to migrate: ", storedClusters.length)
const migratedClusters = storedClusters
.map(cluster => {
/**
* replace snap version with 'current' in kubeconfig path
*/
if (!fs.existsSync(cluster.kubeConfigPath)) {
const kubeconfigPath = cluster.kubeConfigPath.replace(/\/snap\/kontena-lens\/[0-9]*\//, "/snap/kontena-lens/current/")
cluster.kubeConfigPath = kubeconfigPath
}
return cluster;
})
store.set("clusters", migratedClusters)
}
})

View File

@ -223,6 +223,7 @@ export class Issuer extends KubeObject {
} }
getConditions() { getConditions() {
if (!this.status?.conditions) return [];
const { conditions = [] } = this.status; const { conditions = [] } = this.status;
return conditions.map(condition => { return conditions.map(condition => {
const { message, reason, lastTransitionTime, status } = condition; const { message, reason, lastTransitionTime, status } = condition;

View File

@ -50,7 +50,7 @@ export class CustomResourceDefinition extends KubeObject {
message: string; message: string;
reason: string; reason: string;
status: string; status: string;
type: string; type?: string;
}[]; }[];
acceptedNames: { acceptedNames: {
plural: string; plural: string;
@ -132,7 +132,7 @@ export class CustomResourceDefinition extends KubeObject {
} }
getConditions() { getConditions() {
if (!this.status.conditions) return []; if (!this.status?.conditions) return [];
return this.status.conditions.map(condition => { return this.status.conditions.map(condition => {
const { message, reason, lastTransitionTime, status } = condition; const { message, reason, lastTransitionTime, status } = condition;
return { return {

View File

@ -115,12 +115,12 @@ export class KubeObject implements ItemObject {
return KubeObject.stringifyLabels(this.metadata.labels); return KubeObject.stringifyLabels(this.metadata.labels);
} }
getAnnotations(): string[] { getAnnotations(filter = false): string[] {
const labels = KubeObject.stringifyLabels(this.metadata.annotations); const labels = KubeObject.stringifyLabels(this.metadata.annotations);
return labels.filter(label => { return filter ? labels.filter(label => {
const skip = resourceApplierApi.annotations.some(key => label.startsWith(key)); const skip = resourceApplierApi.annotations.some(key => label.startsWith(key));
return !skip; return !skip;
}) }) : labels;
} }
getOwnerRefs() { getOwnerRefs() {
@ -138,7 +138,7 @@ export class KubeObject implements ItemObject {
getNs(), getNs(),
getId(), getId(),
...getLabels(), ...getLabels(),
...getAnnotations(), ...getAnnotations(true),
] ]
} }

View File

@ -1,6 +1,6 @@
import "./components/app.scss" import "./components/app.scss"
import React from "react"; import React from "react";
import { render } from "react-dom"; import { render, unmountComponentAtNode } from "react-dom";
import { isMac } from "../common/vars"; import { isMac } from "../common/vars";
import { userStore } from "../common/user-store"; import { userStore } from "../common/user-store";
import { workspaceStore } from "../common/workspace-store"; import { workspaceStore } from "../common/workspace-store";
@ -30,12 +30,27 @@ export async function bootstrap(App: AppComponent) {
themeStore.init(), themeStore.init(),
]); ]);
// Register additional store listeners
clusterStore.registerIpcListener();
// init app's dependencies if any // init app's dependencies if any
if (App.init) { if (App.init) {
await App.init(); await App.init();
extensionStore.autoEnableOnLoad(getLensRuntime); extensionStore.autoEnableOnLoad(getLensRuntime);
} }
render(<App/>, rootElem); window.addEventListener("message", (ev: MessageEvent) => {
if (ev.data === "teardown") {
userStore.unregisterIpcListener()
workspaceStore.unregisterIpcListener()
clusterStore.unregisterIpcListener()
unmountComponentAtNode(rootElem)
window.location.href = "about:blank"
}
})
render(<>
{isMac && <div id="draggable-top" />}
<App />
</>, rootElem);
} }
// run // run

View File

@ -99,10 +99,7 @@ export class AddCluster extends React.Component {
getContexts(config: KubeConfig): Map<string, KubeConfig> { getContexts(config: KubeConfig): Map<string, KubeConfig> {
const contexts = new Map(); const contexts = new Map();
splitConfig(config).forEach(config => { splitConfig(config).forEach(config => {
const isExists = clusterStore.hasContext(config.currentContext);
if (!isExists) {
contexts.set(config.currentContext, config); contexts.set(config.currentContext, config);
}
}) })
return contexts return contexts
} }

View File

@ -3,8 +3,8 @@ import "./helm-chart-details.scss";
import React, { Component } from "react"; import React, { Component } from "react";
import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api"; import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api";
import { t, Trans } from "@lingui/macro"; import { t, Trans } from "@lingui/macro";
import { autorun, observable } from "mobx"; import { observable, toJS } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react"; import { observer } from "mobx-react";
import { Drawer, DrawerItem } from "../drawer"; import { Drawer, DrawerItem } from "../drawer";
import { autobind, stopPropagation } from "../../utils"; import { autobind, stopPropagation } from "../../utils";
import { MarkdownViewer } from "../markdown-viewer"; import { MarkdownViewer } from "../markdown-viewer";
@ -25,39 +25,41 @@ interface Props {
export class HelmChartDetails extends Component<Props> { export class HelmChartDetails extends Component<Props> {
@observable chartVersions: HelmChart[]; @observable chartVersions: HelmChart[];
@observable selectedChart: HelmChart; @observable selectedChart: HelmChart;
@observable description: string = null; @observable readme: string = null;
@observable error: string = null;
private chartPromise: CancelablePromise<{ readme: string; versions: HelmChart[] }>; private chartPromise: CancelablePromise<{ readme: string; versions: HelmChart[] }>;
@disposeOnUnmount async componentDidMount() {
chartSelector = autorun(async () => { const { chart: { name, repo, version } } = this.props
if (!this.props.chart) return;
this.chartVersions = null;
this.selectedChart = null;
this.description = null;
this.loadChartData();
this.chartPromise.then(data => {
this.description = data.readme;
this.chartVersions = data.versions;
this.selectedChart = data.versions[0];
});
});
loadChartData(version?: string) { try {
const { chart: { name, repo } } = this.props; const { readme, versions } = await (this.chartPromise = helmChartsApi.get(repo, name, version))
if (this.chartPromise) this.chartPromise.cancel(); this.readme = readme
this.chartPromise = helmChartsApi.get(repo, name, version); this.chartVersions = versions
this.selectedChart = versions[0]
} catch (error) {
this.error = error
}
}
componentWillUnmount() {
this.chartPromise?.cancel();
} }
@autobind() @autobind()
onVersionChange(opt: SelectOption) { async onVersionChange({ value: version }: SelectOption) {
const version = opt.value;
this.selectedChart = this.chartVersions.find(chart => chart.version === version); this.selectedChart = this.chartVersions.find(chart => chart.version === version);
this.description = null; this.readme = null;
this.loadChartData(version);
this.chartPromise.then(data => { try {
this.description = data.readme this.chartPromise?.cancel();
}); const { chart: { name, repo } } = this.props;
const { readme } = await (this.chartPromise = helmChartsApi.get(repo, name, version))
this.readme = readme;
} catch (error) {
this.error = error;
}
} }
@autobind() @autobind()
@ -95,7 +97,7 @@ export class HelmChartDetails extends Component<Props> {
</DrawerItem> </DrawerItem>
<DrawerItem name={_i18n._(t`Maintainers`)} className="maintainers"> <DrawerItem name={_i18n._(t`Maintainers`)} className="maintainers">
{selectedChart.getMaintainers().map(({ name, email, url }) => {selectedChart.getMaintainers().map(({ name, email, url }) =>
<a key={name} href={url ? url : `mailto:${email}`} target="_blank">{name}</a> <a key={name} href={url || `mailto:${email}`} target="_blank">{name}</a>
)} )}
</DrawerItem> </DrawerItem>
{selectedChart.getKeywords().length > 0 && ( {selectedChart.getKeywords().length > 0 && (
@ -108,14 +110,35 @@ export class HelmChartDetails extends Component<Props> {
); );
} }
renderReadme() {
if (this.readme === null) {
return <Spinner center />
}
return (
<div className="chart-description">
<MarkdownViewer markdown={this.readme} />
</div>
)
}
renderContent() { renderContent() {
if (this.selectedChart === null || this.description === null) return <Spinner center/>; if (!this.selectedChart) {
return <Spinner center />;
}
if (this.error) {
return (
<div className="box grow">
<p className="error">{this.error}</p>
</div>
)
}
return ( return (
<div className="box grow"> <div className="box grow">
{this.renderIntroduction()} {this.renderIntroduction()}
<div className="chart-description"> {this.renderReadme()}
<MarkdownViewer markdown={this.description}/>
</div>
</div> </div>
); );
} }

View File

@ -99,10 +99,12 @@ export class HelmCharts extends Component<Props> {
detailsItem={this.selectedChart} detailsItem={this.selectedChart}
onDetails={this.showDetails} onDetails={this.showDetails}
/> />
{this.selectedChart && (
<HelmChartDetails <HelmChartDetails
chart={this.selectedChart} chart={this.selectedChart}
hideDetails={this.hideDetails} hideDetails={this.hideDetails}
/> />
)}
</> </>
); );
} }

View File

@ -13,8 +13,9 @@ import { apiManager } from "../../api/api-manager";
import { crdStore } from "./crd.store"; import { crdStore } from "./crd.store";
import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { KubeObjectMeta } from "../kube-object/kube-object-meta";
import { Input } from "../input"; import { Input } from "../input";
import { CustomResourceDefinition } from "../../api/endpoints/crd.api";
interface Props extends KubeObjectDetailsProps { interface Props extends KubeObjectDetailsProps<CustomResourceDefinition> {
} }
function CrdColumnValue({ value }: { value: any[] | {} | string }) { function CrdColumnValue({ value }: { value: any[] | {} | string }) {
@ -66,12 +67,14 @@ export class CrdResourceDetails extends React.Component<Props> {
})} })}
{showStatus && ( {showStatus && (
<DrawerItem name={<Trans>Status</Trans>} className="status" labelsOnly> <DrawerItem name={<Trans>Status</Trans>} className="status" labelsOnly>
{object.status.conditions.map((condition: { type: string; message: string; status: string }) => { {object.status.conditions.map((condition, index) => {
const { type, message, status } = condition; const { type, reason, message, status } = condition;
const kind = type || reason;
if (!kind) return null;
return ( return (
<Badge <Badge
key={type} label={type} key={kind + index} label={kind}
className={cssNames({ disabled: status === "False" }, type.toLowerCase())} className={cssNames({ disabled: status === "False" }, kind.toLowerCase())}
tooltip={message} tooltip={message}
/> />
); );

View File

@ -91,7 +91,8 @@ export class NamespaceSelectFilter extends React.Component {
closeMenuOnSelect={false} closeMenuOnSelect={false}
isOptionSelected={() => false} isOptionSelected={() => false}
controlShouldRenderValue={false} controlShouldRenderValue={false}
onChange={({ value: namespace }: SelectOption) => toggleContext(namespace)} isMulti
onChange={([{ value }]: SelectOption[]) => toggleContext(value)}
formatOptionLabel={({ value: namespace }: SelectOption) => { formatOptionLabel={({ value: namespace }: SelectOption) => {
const isSelected = hasContext(namespace); const isSelected = hasContext(namespace);
return ( return (

View File

@ -18,44 +18,22 @@ export const KubectlBinaries = observer(({ preferences }: { preferences: UserPre
{ value: "china", label: "China (Azure)" }, { value: "china", label: "China (Azure)" },
] ]
const save = () => { const save = () => {
preferences.downloadBinariesPath = downloadPath; preferences.downloadBinariesPath = downloadPath;
preferences.kubectlBinariesPath = binariesPath; preferences.kubectlBinariesPath = binariesPath;
} }
const renderPath = () => {
if (preferences.downloadKubectlBinaries) {
return null;
}
return (
<>
<SubTitle title="Path to Kubectl binary"/>
<Input
theme="round-black"
value={binariesPath}
validators={isPath}
onChange={setBinariesPath}
onBlur={save}
/>
<small className="hint">
<Trans>Default:</Trans>{" "}{Kubectl.bundledKubectlPath}
</small>
</>
);
}
return ( return (
<> <>
<h2><Trans>Kubectl Binary</Trans></h2> <h2><Trans>Kubectl Binary</Trans></h2>
<small className="hint">
<Trans>Download kubectl binaries matching to Kubernetes cluster verison.</Trans>
</small>
<Checkbox <Checkbox
label={<Trans>Download kubectl binaries</Trans>} label={<Trans>Download kubectl binaries</Trans>}
value={preferences.downloadKubectlBinaries} value={preferences.downloadKubectlBinaries}
onChange={downloadKubectlBinaries => preferences.downloadKubectlBinaries = downloadKubectlBinaries} onChange={downloadKubectlBinaries => preferences.downloadKubectlBinaries = downloadKubectlBinaries}
/> />
<small className="hint">
<Trans>Download kubectl binaries matching to Kubernetes cluster version.</Trans>
</small>
<SubTitle title="Download mirror" /> <SubTitle title="Download mirror" />
<Select <Select
placeholder={<Trans>Download mirror for kubectl</Trans>} placeholder={<Trans>Download mirror for kubectl</Trans>}
@ -68,16 +46,28 @@ export const KubectlBinaries = observer(({ preferences }: { preferences: UserPre
<Input <Input
theme="round-black" theme="round-black"
value={downloadPath} value={downloadPath}
placeholder={`Directory to download binaries into`} placeholder={userStore.getDefaultKubectlPath()}
validators={isPath} validators={isPath}
onChange={setDownloadPath} onChange={setDownloadPath}
onBlur={save} onBlur={save}
disabled={!preferences.downloadKubectlBinaries} disabled={!preferences.downloadKubectlBinaries}
/> />
<small> <small className="hint">
Default: {userStore.getDefaultKubectlPath()} The directory to download binaries into.
</small>
<SubTitle title="Path to Kubectl binary" />
<Input
theme="round-black"
placeholder={Kubectl.bundledKubectlPath}
value={binariesPath}
validators={isPath}
onChange={setBinariesPath}
onBlur={save}
disabled={preferences.downloadKubectlBinaries}
/>
<small className="hint">
<Trans>The path to the kubectl binary on the system.</Trans>
</small> </small>
{renderPath()}
</> </>
); );
}); });

View File

@ -11,11 +11,9 @@ export class DaemonSetStore extends KubeObjectStore<DaemonSet> {
@observable metrics: IPodMetrics = null; @observable metrics: IPodMetrics = null;
loadMetrics(daemonSet: DaemonSet) { async loadMetrics(daemonSet: DaemonSet) {
const pods = this.getChildPods(daemonSet); const pods = this.getChildPods(daemonSet);
return podsApi.getMetrics(pods, daemonSet.getNs(), "").then(metrics => this.metrics = await podsApi.getMetrics(pods, daemonSet.getNs(), "");
this.metrics = metrics
);
} }
getChildPods(daemonSet: DaemonSet): Pod[] { getChildPods(daemonSet: DaemonSet): Pod[] {

View File

@ -16,11 +16,9 @@ export class DeploymentStore extends KubeObjectStore<Deployment> {
], "desc"); ], "desc");
} }
loadMetrics(deployment: Deployment) { async loadMetrics(deployment: Deployment) {
const pods = this.getChildPods(deployment); const pods = this.getChildPods(deployment);
return podsApi.getMetrics(pods, deployment.getNs(), "").then(metrics => this.metrics = await podsApi.getMetrics(pods, deployment.getNs(), "");
this.metrics = metrics
);
} }
getStatuses(deployments?: Deployment[]) { getStatuses(deployments?: Deployment[]) {

View File

@ -5,28 +5,45 @@ import { observer } from "mobx-react";
import { Trans } from "@lingui/macro"; import { Trans } from "@lingui/macro";
import { OverviewWorkloadStatus } from "./overview-workload-status"; import { OverviewWorkloadStatus } from "./overview-workload-status";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { cronJobsURL, daemonSetsURL, deploymentsURL, jobsURL, podsURL, statefulSetsURL } from "../+workloads"; import { workloadURL, workloadStores } from "../+workloads";
import { podsStore } from "../+workloads-pods/pods.store";
import { deploymentStore } from "../+workloads-deployments/deployments.store";
import { daemonSetStore } from "../+workloads-daemonsets/daemonsets.store";
import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store";
import { jobStore } from "../+workloads-jobs/job.store";
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
import { namespaceStore } from "../+namespaces/namespace.store"; import { namespaceStore } from "../+namespaces/namespace.store";
import { PageFiltersList } from "../item-object-list/page-filters-list"; import { PageFiltersList } from "../item-object-list/page-filters-list";
import { NamespaceSelectFilter } from "../+namespaces/namespace-select"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select";
import { isAllowedResource } from "../../../common/rbac"; import { isAllowedResource, KubeResource } from "../../../common/rbac";
import { ResourceNames } from "../../../renderer/utils/rbac";
import { autobind } from "../../utils";
import { _i18n } from "../../i18n";
const resources: KubeResource[] = [
"pods",
"deployments",
"statefulsets",
"daemonsets",
"jobs",
"cronjobs",
]
@observer @observer
export class OverviewStatuses extends React.Component { export class OverviewStatuses extends React.Component {
@autobind()
renderWorkload(resource: KubeResource): React.ReactElement {
const store = workloadStores[resource];
const items = store.getAllByNs(namespaceStore.contextNs)
return (
<div className="workload" key={resource}>
<div className="title">
<Link to={workloadURL[resource]()}>{ResourceNames[resource]} ({items.length})</Link>
</div>
<OverviewWorkloadStatus status={store.getStatuses(items)} />
</div>
)
}
render() { render() {
const { contextNs } = namespaceStore; const workloads = resources
const pods = isAllowedResource("pods") ? podsStore.getAllByNs(contextNs) : []; .filter(isAllowedResource)
const deployments = isAllowedResource("deployments") ? deploymentStore.getAllByNs(contextNs) : []; .map(this.renderWorkload);
const statefulSets = isAllowedResource("statefulsets") ? statefulSetStore.getAllByNs(contextNs) : [];
const daemonSets = isAllowedResource("daemonsets") ? daemonSetStore.getAllByNs(contextNs) : [];
const jobs = isAllowedResource("jobs") ? jobStore.getAllByNs(contextNs) : [];
const cronJobs = isAllowedResource("cronjobs") ? cronJobStore.getAllByNs(contextNs) : [];
return ( return (
<div className="OverviewStatuses"> <div className="OverviewStatuses">
<div className="header flex gaps align-center"> <div className="header flex gaps align-center">
@ -35,42 +52,7 @@ export class OverviewStatuses extends React.Component {
</div> </div>
<PageFiltersList /> <PageFiltersList />
<div className="workloads"> <div className="workloads">
{isAllowedResource("pods") && {workloads}
<div className="workload">
<div className="title"><Link to={podsURL()}><Trans>Pods</Trans> ({pods.length})</Link></div>
<OverviewWorkloadStatus status={podsStore.getStatuses(pods)}/>
</div>
}
{isAllowedResource("deployments") &&
<div className="workload">
<div className="title"><Link to={deploymentsURL()}><Trans>Deployments</Trans> ({deployments.length})</Link></div>
<OverviewWorkloadStatus status={deploymentStore.getStatuses(deployments)}/>
</div>
}
{isAllowedResource("statefulsets") &&
<div className="workload">
<div className="title"><Link to={statefulSetsURL()}><Trans>StatefulSets</Trans> ({statefulSets.length})</Link></div>
<OverviewWorkloadStatus status={statefulSetStore.getStatuses(statefulSets)}/>
</div>
}
{isAllowedResource("daemonsets") &&
<div className="workload">
<div className="title"><Link to={daemonSetsURL()}><Trans>DaemonSets</Trans> ({daemonSets.length})</Link></div>
<OverviewWorkloadStatus status={daemonSetStore.getStatuses(daemonSets)}/>
</div>
}
{isAllowedResource("jobs") &&
<div className="workload">
<div className="title"><Link to={jobsURL()}><Trans>Jobs</Trans> ({jobs.length})</Link></div>
<OverviewWorkloadStatus status={jobStore.getStatuses(jobs)}/>
</div>
}
{isAllowedResource("cronjobs") &&
<div className="workload">
<div className="title"><Link to={cronJobsURL()}><Trans>CronJobs</Trans> ({cronJobs.length})</Link></div>
<OverviewWorkloadStatus status={cronJobStore.getStatuses(cronJobs)}/>
</div>
}
</div> </div>
</div> </div>
) )

View File

@ -113,7 +113,8 @@ const SecretKey = (props: SecretKeyProps) => {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [secret, setSecret] = useState<Secret>() const [secret, setSecret] = useState<Secret>()
const showKey = async () => { const showKey = async (evt: React.MouseEvent) => {
evt.preventDefault()
setLoading(true) setLoading(true)
const secret = await secretsStore.load({ name, namespace }); const secret = await secretsStore.load({ name, namespace });
setLoading(false) setLoading(false)

View File

@ -10,11 +10,9 @@ export class ReplicaSetStore extends KubeObjectStore<ReplicaSet> {
api = replicaSetApi api = replicaSetApi
@observable metrics: IPodMetrics = null; @observable metrics: IPodMetrics = null;
loadMetrics(replicaSet: ReplicaSet) { async loadMetrics(replicaSet: ReplicaSet) {
const pods = this.getChildPods(replicaSet); const pods = this.getChildPods(replicaSet);
return podsApi.getMetrics(pods, replicaSet.getNs(), "").then(metrics => this.metrics = await podsApi.getMetrics(pods, replicaSet.getNs(), "");
this.metrics = metrics
);
} }
getChildPods(replicaSet: ReplicaSet) { getChildPods(replicaSet: ReplicaSet) {

View File

@ -10,11 +10,9 @@ export class StatefulSetStore extends KubeObjectStore<StatefulSet> {
api = statefulSetApi api = statefulSetApi
@observable metrics: IPodMetrics = null; @observable metrics: IPodMetrics = null;
loadMetrics(statefulSet: StatefulSet) { async loadMetrics(statefulSet: StatefulSet) {
const pods = this.getChildPods(statefulSet); const pods = this.getChildPods(statefulSet);
return podsApi.getMetrics(pods, statefulSet.getNs(), "").then(metrics => this.metrics = await podsApi.getMetrics(pods, statefulSet.getNs(), "");
this.metrics = metrics
);
} }
getChildPods(statefulSet: StatefulSet) { getChildPods(statefulSet: StatefulSet) {

View File

@ -1,3 +1,3 @@
export * from "./workloads.route" export * from "./workloads.route"
export * from "./workloads" export * from "./workloads"
export * from "./workloads.stores"

View File

@ -1,6 +1,7 @@
import { RouteProps } from "react-router" import { RouteProps } from "react-router"
import { Workloads } from "./workloads"; import { Workloads } from "./workloads";
import { buildURL, IURLParams } from "../../navigation"; import { buildURL, IURLParams } from "../../navigation";
import { KubeResource } from "../../../common/rbac";
export const workloadsRoute: RouteProps = { export const workloadsRoute: RouteProps = {
get path() { get path() {
@ -62,3 +63,12 @@ export const daemonSetsURL = buildURL<IDaemonSetsRouteParams>(daemonSetsRoute.pa
export const statefulSetsURL = buildURL<IStatefulSetsRouteParams>(statefulSetsRoute.path) export const statefulSetsURL = buildURL<IStatefulSetsRouteParams>(statefulSetsRoute.path)
export const jobsURL = buildURL<IJobsRouteParams>(jobsRoute.path) export const jobsURL = buildURL<IJobsRouteParams>(jobsRoute.path)
export const cronJobsURL = buildURL<ICronJobsRouteParams>(cronJobsRoute.path) export const cronJobsURL = buildURL<ICronJobsRouteParams>(cronJobsRoute.path)
export const workloadURL: Partial<Record<KubeResource, ReturnType<typeof buildURL>>> = {
"pods": podsURL,
"deployments": deploymentsURL,
"daemonsets": daemonSetsURL,
"statefulsets": statefulSetsURL,
"jobs": jobsURL,
"cronjobs": cronJobsURL,
}

View File

@ -0,0 +1,17 @@
import { KubeObjectStore } from "../../kube-object.store";
import { podsStore } from "../+workloads-pods/pods.store";
import { deploymentStore } from "../+workloads-deployments/deployments.store";
import { daemonSetStore } from "../+workloads-daemonsets/daemonsets.store";
import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store";
import { jobStore } from "../+workloads-jobs/job.store";
import { cronJobStore } from "../+workloads-cronjobs/cronjob.store";
import { KubeResource } from "../../../common/rbac";
export const workloadStores: Partial<Record<KubeResource, KubeObjectStore>> = {
"pods": podsStore,
"deployments": deploymentStore,
"daemonsets": daemonSetStore,
"statefulsets": statefulSetStore,
"jobs": jobStore,
"cronjobs": cronJobStore,
}

View File

@ -13,6 +13,7 @@
:root { :root {
--mainBackground: #1e2124; --mainBackground: #1e2124;
--main-layout-header: 40px; --main-layout-header: 40px;
--drag-region-height: 22px
} }
::selection { ::selection {
@ -47,7 +48,7 @@ html, body {
left: 0; left: 0;
top: 0; top: 0;
width: 100%; width: 100%;
height: var(--main-layout-header); height: var(--drag-region-height);
z-index: 1000; z-index: 1000;
pointer-events: none; pointer-events: none;
} }

View File

@ -165,7 +165,7 @@ export const memoryOptions: ChartOptions = {
} }
return bytesToUnits(parseInt(value)); return bytesToUnits(parseInt(value));
} }
return `${value}`; return bytesToUnits(value);
}, },
stepSize: 1 stepSize: 1
} }

View File

@ -55,7 +55,6 @@ export class ClusterManager extends React.Component {
render() { render() {
return ( return (
<div className="ClusterManager"> <div className="ClusterManager">
<div id="draggable-top"/>
<main> <main>
<div id="lens-views" /> <div id="lens-views" />
<Switch> <Switch>

View File

@ -19,8 +19,11 @@ export async function initView(clusterId: ClusterId) {
if (!clusterId || lensViews.has(clusterId)) { if (!clusterId || lensViews.has(clusterId)) {
return; return;
} }
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
const cluster = clusterStore.getById(clusterId); const cluster = clusterStore.getById(clusterId);
if (!cluster) {
return;
}
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
const parentElem = document.getElementById("lens-views"); const parentElem = document.getElementById("lens-views");
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
iframe.name = cluster.contextName; iframe.name = cluster.contextName;
@ -42,9 +45,9 @@ export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrame
// Keep frame in DOM to avoid possible bugs when same cluster re-created after being removed. // Keep frame in DOM to avoid possible bugs when same cluster re-created after being removed.
// In that case for some reasons `webFrame.routingId` returns some previous frameId (usage in app.tsx) // In that case for some reasons `webFrame.routingId` returns some previous frameId (usage in app.tsx)
// Issue: https://github.com/lensapp/lens/issues/811 // Issue: https://github.com/lensapp/lens/issues/811
iframe.dataset.meta = `${iframe.name} was removed at ${new Date().toLocaleString()}`; iframe.dataset.meta = `${iframe.name} was removed at ${new Date().toLocaleString()}`
iframe.removeAttribute("src")
iframe.removeAttribute("name") iframe.removeAttribute("name")
iframe.contentWindow.postMessage("teardown", "*")
} }
export function refreshViews() { export function refreshViews() {

View File

@ -5,6 +5,7 @@ import { t } from "@lingui/macro";
import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api"; import { HelmChart, helmChartsApi } from "../../api/endpoints/helm-charts.api";
import { IReleaseUpdateDetails } from "../../api/endpoints/helm-releases.api"; import { IReleaseUpdateDetails } from "../../api/endpoints/helm-releases.api";
import { _i18n } from "../../i18n"; import { _i18n } from "../../i18n";
import { Notifications } from "../notifications";
export interface IChartInstallData { export interface IChartInstallData {
name: string; name: string;
@ -27,45 +28,50 @@ export class InstallChartStore extends DockTabStore<IChartInstallData> {
}); });
autorun(() => { autorun(() => {
const { selectedTab, isOpen } = dockStore; const { selectedTab, isOpen } = dockStore;
if (!isInstallChartTab(selectedTab)) return; if (isInstallChartTab(selectedTab) && isOpen) {
if (isOpen) { this.loadData()
this.loadData(); .catch(err => Notifications.error(String(err)))
} }
}, { delay: 250 }) }, { delay: 250 })
} }
@action @action
async loadData(tabId = dockStore.selectedTabId) { async loadData(tabId = dockStore.selectedTabId) {
const { values } = this.getData(tabId); const promises = []
const versions = this.versions.getData(tabId);
return Promise.all([ if (!this.getData(tabId).values) {
!versions && this.loadVersions(tabId), promises.push(this.loadValues(tabId))
!values && this.loadValues(tabId), }
])
if (!this.versions.getData(tabId)) {
promises.push(this.loadVersions(tabId))
}
await Promise.all(promises)
} }
@action @action
async loadVersions(tabId: TabId) { async loadVersions(tabId: TabId) {
const { repo, name } = this.getData(tabId); const { repo, name, version } = this.getData(tabId);
this.versions.clearData(tabId); // reset this.versions.clearData(tabId); // reset
const charts = await helmChartsApi.get(repo, name); const charts = await helmChartsApi.get(repo, name, version);
const versions = charts.versions.map(chartVersion => chartVersion.version); const versions = charts.versions.map(chartVersion => chartVersion.version);
this.versions.setData(tabId, versions); this.versions.setData(tabId, versions);
} }
@action @action
async loadValues(tabId: TabId) { async loadValues(tabId: TabId) {
const data = this.getData(tabId); const data = this.getData(tabId)
const { repo, name, version } = data; const { repo, name, version } = data
let values = "";
const fetchValues = async (retry = 1, maxRetries = 3) => { // This loop is for "retrying" the "getValues" call
values = await helmChartsApi.getValues(repo, name, version); for (const _ of Array(3)) {
if (values || retry == maxRetries) return; const values = await helmChartsApi.getValues(repo, name, version)
await fetchValues(retry + 1); if (values) {
}; this.setData(tabId, { ...data, values })
this.setData(tabId, { ...data, values: undefined }); // reset return
await fetchValues(); }
this.setData(tabId, { ...data, values }); }
} }
} }

View File

@ -107,7 +107,7 @@ export class InstallChart extends Component<Props> {
render() { render() {
const { tabId, chartData, values, versions, install } = this; const { tabId, chartData, values, versions, install } = this;
if (!chartData || chartData.values === undefined || !versions) { if (chartData?.values === undefined || !versions) {
return <Spinner center />; return <Spinner center />;
} }

View File

@ -282,7 +282,6 @@ export class Input extends React.Component<InputProps, State> {
onKeyDown: this.onKeyDown, onKeyDown: this.onKeyDown,
rows: multiLine ? (rows || 1) : null, rows: multiLine ? (rows || 1) : null,
ref: this.bindRef, ref: this.bindRef,
type: "text",
spellCheck: "false", spellCheck: "false",
}); });

View File

@ -116,6 +116,6 @@ export function openServiceAccountKubeConfig(account: ServiceAccount) {
const namespace = account.getNs() const namespace = account.getNs()
KubeConfigDialog.open({ KubeConfigDialog.open({
title: <Trans>{accountName} kubeconfig</Trans>, title: <Trans>{accountName} kubeconfig</Trans>,
loader: () => apiBase.get(`/kubeconfig/service-account/${namespace}/${account}`) loader: () => apiBase.get(`/kubeconfig/service-account/${namespace}/${accountName}`)
}) })
} }

View File

@ -8,10 +8,16 @@ import { JsonApiErrorParsed } from "../../api/json-api";
export type IMessageId = string | number; export type IMessageId = string | number;
export type IMessage = React.ReactNode | React.ReactNode[] | JsonApiErrorParsed; export type IMessage = React.ReactNode | React.ReactNode[] | JsonApiErrorParsed;
export enum NotificationStatus {
OK = "ok",
ERROR = "error",
INFO = "info",
}
export interface INotification { export interface INotification {
id?: IMessageId; id?: IMessageId;
message: IMessage; message: IMessage;
status?: "ok" | "error" | "info"; status?: NotificationStatus;
timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide
} }

View File

@ -5,7 +5,7 @@ import { reaction } from "mobx";
import { disposeOnUnmount, observer } from "mobx-react" import { disposeOnUnmount, observer } from "mobx-react"
import { JsonApiErrorParsed } from "../../api/json-api"; import { JsonApiErrorParsed } from "../../api/json-api";
import { cssNames, prevDefault } from "../../utils"; import { cssNames, prevDefault } from "../../utils";
import { IMessage, INotification, notificationsStore } from "./notifications.store"; import { IMessage, INotification, notificationsStore, NotificationStatus } from "./notifications.store";
import { Animate } from "../animate"; import { Animate } from "../animate";
import { Icon } from "../icon" import { Icon } from "../icon"
@ -17,7 +17,7 @@ export class Notifications extends React.Component {
notificationsStore.add({ notificationsStore.add({
message: message, message: message,
timeout: 2500, timeout: 2500,
status: "ok" status: NotificationStatus.OK
}) })
} }
@ -25,13 +25,13 @@ export class Notifications extends React.Component {
notificationsStore.add({ notificationsStore.add({
message: message, message: message,
timeout: 10000, timeout: 10000,
status: "error" status: NotificationStatus.ERROR
}); });
} }
static info(message: IMessage, customOpts: Partial<INotification> = {}) { static info(message: IMessage, customOpts: Partial<INotification> = {}) {
return notificationsStore.add({ return notificationsStore.add({
status: "info", status: NotificationStatus.INFO,
timeout: 0, timeout: 0,
message: message, message: message,
...customOpts, ...customOpts,

View File

@ -32,6 +32,7 @@ html {
background: transparent; background: transparent;
min-height: 0; min-height: 0;
box-shadow: 0 0 0 1px $halfGray; box-shadow: 0 0 0 1px $halfGray;
cursor: pointer;
&--is-focused { &--is-focused {
box-shadow: 0 0 0 2px $primary; box-shadow: 0 0 0 2px $primary;
@ -88,6 +89,7 @@ html {
&__option { &__option {
white-space: nowrap; white-space: nowrap;
cursor: pointer;
&:active { &:active {
background: $primary; background: $primary;

View File

@ -19,6 +19,8 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
kubeWatchApi.addListener(this, this.onWatchApiEvent); kubeWatchApi.addListener(this, this.onWatchApiEvent);
} }
getStatuses?(items: T[]): Record<string, number>;
getAllByNs(namespace: string | string[], strict = false): T[] { getAllByNs(namespace: string | string[], strict = false): T[] {
const namespaces: string[] = [].concat(namespace); const namespaces: string[] = [].concat(namespace);
if (namespaces.length) { if (namespaces.length) {

View File

@ -0,0 +1,28 @@
import { KubeResource } from "../../common/rbac";
import { _i18n } from "../i18n";
export const ResourceNames: Record<KubeResource, string> = {
"namespaces": _i18n._("Namespaces"),
"nodes": _i18n._("Nodes"),
"events": _i18n._("Events"),
"resourcequotas": _i18n._("Resource Quotas"),
"services": _i18n._("Services"),
"secrets": _i18n._("Secrets"),
"configmaps": _i18n._("Config Maps"),
"ingresses": _i18n._("Ingresses"),
"networkpolicies": _i18n._("Network Policies"),
"persistentvolumes": _i18n._("Persistent Volumes"),
"storageclasses": _i18n._("Storage Classes"),
"pods": _i18n._("Pods"),
"daemonsets": _i18n._("Daemon Sets"),
"deployments": _i18n._("Deployments"),
"statefulsets": _i18n._("Stateful Sets"),
"replicasets": _i18n._("Replica Sets"),
"jobs": _i18n._("Jobs"),
"cronjobs": _i18n._("Cron Jobs"),
"endpoints": _i18n._("Endpoints"),
"customresourcedefinitions": _i18n._("Custom Resource Definitions"),
"horizontalpodautoscalers": _i18n._("Horizontal Pod Autoscalers"),
"podsecuritypolicies": _i18n._("Pod Security Policies"),
"poddisruptionbudgets": _i18n._("Pod Disruption Budgets"),
}

View File

@ -2,7 +2,26 @@
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights! Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
## 3.6.4 (current version) ## 3.6.5-rc.1 (current version)
- Fix Notifications not to block items not visually under them from being interacted with
- Fix side bar not to scroll after clicking on lower menu item
- Auto-select context if only one context is present in pasted Kubeconfig
- Fix background image of What's New page on white theme
- Reduce height on draggable-top and only render it on macos
- Download dir option is now consistent with other settings
- Allow to add the same cluster multiple times
- Convert bytes in memory chart properly
- Fix empty dashboard screen after cluster is removed and added multiple times in a row to application
- Dropdowns have pointer cursor now
- Proxy kubectl exec requests properly
- Pass always chart version information when dealing with helm commands
- Fix app crash when conditions are not yet present in CRD objects
- Fix kubeconfig generating for service account
- Update bundled Helm binary to version 3.3.4
- Fix clusters' kubeconfig paths that point to snap config dir to use current snap config path
## 3.6.4
- Fix: deleted namespace does not get auto unselected - Fix: deleted namespace does not get auto unselected
- Get focus to dock tab (terminal & resource editor) content after resize - Get focus to dock tab (terminal & resource editor) content after resize
- Downloading kubectl binary does not block dashboard opening anymore - Downloading kubectl binary does not block dashboard opening anymore