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

Merge remote-tracking branch 'origin/master' into add_cluster_from_custom_config

# Conflicts:
#	src/common/cluster-store.ts
#	src/migrations/cluster-store/3.6.0-beta.1.ts
This commit is contained in:
Roman 2020-08-27 13:22:29 +03:00
commit 01be6e449e
31 changed files with 494 additions and 108 deletions

View File

@ -20,8 +20,10 @@ compile-dev:
yarn compile:renderer --cache yarn compile:renderer --cache
dev: dev:
test -f out/main.js || make init ifeq ("$(wildcard static/build/main.js)","")
yarn dev # run electron and watch files make init
endif
yarn dev
lint: lint:
yarn lint yarn lint
@ -53,6 +55,12 @@ else
endif endif
clean: clean:
ifeq "$(DETECTED_OS)" "Windows"
if exist binaries\client del /s /q binaries\client\*.*
if exist dist del /s /q dist\*.*
if exist static\build del /s /q static\build\*.*
else
rm -rf binaries/client/* rm -rf binaries/client/*
rm -rf dist/* rm -rf dist/*
rm -rf out/* rm -rf static/build/*
endif

View File

@ -26,11 +26,8 @@ describe("app start", () => {
const waitForMinikubeDashboard = async (app: Application) => { const waitForMinikubeDashboard = async (app: Application) => {
await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started") await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started")
let windowCount = await app.client.getWindowCount() let windowCount = await app.client.getWindowCount()
// wait for webview to appear on window count await app.client.waitForExist(`iframe[name="minikube"]`)
while (windowCount == 1) { await app.client.frame("minikube")
windowCount = await app.client.getWindowCount()
}
await app.client.windowByIndex(windowCount - 1)
await app.client.waitUntilTextExists("span.link-text", "Cluster") await app.client.waitUntilTextExists("span.link-text", "Cluster")
} }
@ -39,10 +36,10 @@ describe("app start", () => {
await app.start() await app.start()
await app.client.waitUntilWindowLoaded() await app.client.waitUntilWindowLoaded()
let windowCount = await app.client.getWindowCount() let windowCount = await app.client.getWindowCount()
while (windowCount > 1) { while (windowCount > 1) { // Wait for splash screen to be closed
windowCount = await app.client.getWindowCount() windowCount = await app.client.getWindowCount()
} }
await app.client.windowByIndex(windowCount - 1) await app.client.windowByIndex(0)
await app.client.waitUntilWindowLoaded() await app.client.waitUntilWindowLoaded()
}, 20000) }, 20000)

View File

@ -83,7 +83,7 @@ msgid "Account Name"
msgstr "Account Name" msgstr "Account Name"
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:51 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:51
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:46 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:50
msgid "Active" msgid "Active"
msgstr "Active" msgstr "Active"
@ -181,7 +181,7 @@ msgstr "Affinities"
#: src/renderer/components/+user-management-roles/roles.tsx:35 #: src/renderer/components/+user-management-roles/roles.tsx:35
#: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:38 #: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:38
#: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:38 #: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:38
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:48 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:52
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:50 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:50
#: src/renderer/components/+workloads-deployments/deployments.tsx:63 #: src/renderer/components/+workloads-deployments/deployments.tsx:63
#: src/renderer/components/+workloads-jobs/jobs.tsx:41 #: src/renderer/components/+workloads-jobs/jobs.tsx:41
@ -707,7 +707,7 @@ msgstr "Created at"
msgid "Credentials Ref" msgid "Credentials Ref"
msgstr "Credentials Ref" msgstr "Credentials Ref"
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:40 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:44
msgid "Cron Jobs" msgid "Cron Jobs"
msgstr "Cron Jobs" msgstr "Cron Jobs"
@ -1190,6 +1190,10 @@ msgstr "Item list is empty"
msgid "JSON Path" msgid "JSON Path"
msgstr "JSON Path" msgstr "JSON Path"
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:91
msgid "Job name"
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
@ -1254,7 +1258,7 @@ msgid "Last Failure Time"
msgstr "Last Failure Time" msgstr "Last Failure Time"
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:57 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:57
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:47 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:51
msgid "Last schedule" msgid "Last schedule"
msgstr "Last schedule" msgstr "Last schedule"
@ -1484,7 +1488,7 @@ msgstr "Mounts"
#: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:35 #: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:35
#: src/renderer/components/+user-management-service-accounts/service-accounts-secret.tsx:29 #: src/renderer/components/+user-management-service-accounts/service-accounts-secret.tsx:29
#: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:36 #: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:36
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:41 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:45
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:45 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:45
#: src/renderer/components/+workloads-deployments/deployments.tsx:58 #: src/renderer/components/+workloads-deployments/deployments.tsx:58
#: src/renderer/components/+workloads-jobs/jobs.tsx:37 #: src/renderer/components/+workloads-jobs/jobs.tsx:37
@ -1534,7 +1538,7 @@ msgstr "Names"
#: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:37 #: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:37
#: src/renderer/components/+user-management-service-accounts/create-service-account-dialog.tsx:79 #: src/renderer/components/+user-management-service-accounts/create-service-account-dialog.tsx:79
#: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:37 #: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:37
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:43 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:47
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:46 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:46
#: src/renderer/components/+workloads-deployments/deployments.tsx:59 #: src/renderer/components/+workloads-deployments/deployments.tsx:59
#: src/renderer/components/+workloads-jobs/jobs.tsx:38 #: src/renderer/components/+workloads-jobs/jobs.tsx:38
@ -2264,7 +2268,7 @@ msgid "Scale Deployment <0>{deploymentName}</0>"
msgstr "Scale Deployment <0>{deploymentName}</0>" msgstr "Scale Deployment <0>{deploymentName}</0>"
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:46 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:46
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:44 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:48
msgid "Schedule" msgid "Schedule"
msgstr "Schedule" msgstr "Schedule"
@ -2588,7 +2592,7 @@ msgid "Supplemental Groups"
msgstr "Supplemental Groups" msgstr "Supplemental Groups"
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:54 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:54
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:45 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:49
msgid "Suspend" msgid "Suspend"
msgstr "Suspend" msgstr "Suspend"
@ -2657,6 +2661,16 @@ 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/cronjobs.tsx:79
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80
msgid "Trigger"
msgstr "Trigger"
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:103
msgid "Trigger CronJob <0>{cronjobName}</0>"
msgstr "Trigger CronJob <0>{cronjobName}</0>"
#: src/renderer/components/+cluster/cluster-issues.tsx:102 #: src/renderer/components/+cluster/cluster-issues.tsx:102
#: src/renderer/components/+config-secrets/secret-details.tsx:74 #: src/renderer/components/+config-secrets/secret-details.tsx:74
#: src/renderer/components/+config-secrets/secrets.tsx:45 #: src/renderer/components/+config-secrets/secrets.tsx:45
@ -2897,7 +2911,7 @@ msgid "listKind"
msgstr "listKind" msgstr "listKind"
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:48 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:48
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:57 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:61
msgid "never" msgid "never"
msgstr "never" msgstr "never"

View File

@ -83,7 +83,7 @@ msgid "Account Name"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:51 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:51
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:46 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:50
msgid "Active" msgid "Active"
msgstr "" msgstr ""
@ -181,7 +181,7 @@ msgstr ""
#: src/renderer/components/+user-management-roles/roles.tsx:35 #: src/renderer/components/+user-management-roles/roles.tsx:35
#: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:38 #: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:38
#: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:38 #: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:38
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:48 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:52
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:50 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:50
#: src/renderer/components/+workloads-deployments/deployments.tsx:63 #: src/renderer/components/+workloads-deployments/deployments.tsx:63
#: src/renderer/components/+workloads-jobs/jobs.tsx:41 #: src/renderer/components/+workloads-jobs/jobs.tsx:41
@ -703,7 +703,7 @@ msgstr ""
msgid "Credentials Ref" msgid "Credentials Ref"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:40 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:44
msgid "Cron Jobs" msgid "Cron Jobs"
msgstr "" msgstr ""
@ -1181,6 +1181,10 @@ msgstr ""
msgid "JSON Path" msgid "JSON Path"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:91
msgid "Job name"
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
@ -1245,7 +1249,7 @@ msgid "Last Failure Time"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:57 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:57
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:47 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:51
msgid "Last schedule" msgid "Last schedule"
msgstr "" msgstr ""
@ -1475,7 +1479,7 @@ msgstr ""
#: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:35 #: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:35
#: src/renderer/components/+user-management-service-accounts/service-accounts-secret.tsx:29 #: src/renderer/components/+user-management-service-accounts/service-accounts-secret.tsx:29
#: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:36 #: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:36
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:41 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:45
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:45 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:45
#: src/renderer/components/+workloads-deployments/deployments.tsx:58 #: src/renderer/components/+workloads-deployments/deployments.tsx:58
#: src/renderer/components/+workloads-jobs/jobs.tsx:37 #: src/renderer/components/+workloads-jobs/jobs.tsx:37
@ -1525,7 +1529,7 @@ msgstr ""
#: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:37 #: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:37
#: src/renderer/components/+user-management-service-accounts/create-service-account-dialog.tsx:79 #: src/renderer/components/+user-management-service-accounts/create-service-account-dialog.tsx:79
#: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:37 #: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:37
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:43 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:47
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:46 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:46
#: src/renderer/components/+workloads-deployments/deployments.tsx:59 #: src/renderer/components/+workloads-deployments/deployments.tsx:59
#: src/renderer/components/+workloads-jobs/jobs.tsx:38 #: src/renderer/components/+workloads-jobs/jobs.tsx:38
@ -2247,7 +2251,7 @@ msgid "Scale Deployment <0>{deploymentName}</0>"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:46 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:46
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:44 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:48
msgid "Schedule" msgid "Schedule"
msgstr "" msgstr ""
@ -2571,7 +2575,7 @@ msgid "Supplemental Groups"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:54 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:54
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:45 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:49
msgid "Suspend" msgid "Suspend"
msgstr "" msgstr ""
@ -2640,6 +2644,16 @@ msgstr ""
msgid "Transmit" msgid "Transmit"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:107
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80
msgid "Trigger"
msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:103
msgid "Trigger CronJob <0>{cronjobName}</0>"
msgstr ""
#: src/renderer/components/+cluster/cluster-issues.tsx:102 #: src/renderer/components/+cluster/cluster-issues.tsx:102
#: src/renderer/components/+config-secrets/secret-details.tsx:74 #: src/renderer/components/+config-secrets/secret-details.tsx:74
#: src/renderer/components/+config-secrets/secrets.tsx:45 #: src/renderer/components/+config-secrets/secrets.tsx:45
@ -2880,7 +2894,7 @@ msgid "listKind"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:48 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:48
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:57 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:61
msgid "never" msgid "never"
msgstr "" msgstr ""

View File

@ -84,7 +84,7 @@ msgid "Account Name"
msgstr "Название аккаунта" msgstr "Название аккаунта"
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:51 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:51
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:46 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:50
msgid "Active" msgid "Active"
msgstr "Активный" msgstr "Активный"
@ -182,7 +182,7 @@ msgstr "Аффинитеты"
#: src/renderer/components/+user-management-roles/roles.tsx:35 #: src/renderer/components/+user-management-roles/roles.tsx:35
#: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:38 #: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:38
#: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:38 #: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:38
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:48 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:52
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:50 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:50
#: src/renderer/components/+workloads-deployments/deployments.tsx:63 #: src/renderer/components/+workloads-deployments/deployments.tsx:63
#: src/renderer/components/+workloads-jobs/jobs.tsx:41 #: src/renderer/components/+workloads-jobs/jobs.tsx:41
@ -708,7 +708,7 @@ msgstr "Создано"
msgid "Credentials Ref" msgid "Credentials Ref"
msgstr "Credentials Ref" msgstr "Credentials Ref"
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:40 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:44
msgid "Cron Jobs" msgid "Cron Jobs"
msgstr "" msgstr ""
@ -1191,6 +1191,10 @@ msgstr "Список пуст"
msgid "JSON Path" msgid "JSON Path"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:91
msgid "Job name"
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
@ -1255,7 +1259,7 @@ msgid "Last Failure Time"
msgstr "Время последнего сбоя" msgstr "Время последнего сбоя"
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:57 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:57
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:47 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:51
msgid "Last schedule" msgid "Last schedule"
msgstr "Последний запуск" msgstr "Последний запуск"
@ -1485,7 +1489,7 @@ msgstr "Установки"
#: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:35 #: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:35
#: src/renderer/components/+user-management-service-accounts/service-accounts-secret.tsx:29 #: src/renderer/components/+user-management-service-accounts/service-accounts-secret.tsx:29
#: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:36 #: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:36
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:41 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:45
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:45 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:45
#: src/renderer/components/+workloads-deployments/deployments.tsx:58 #: src/renderer/components/+workloads-deployments/deployments.tsx:58
#: src/renderer/components/+workloads-jobs/jobs.tsx:37 #: src/renderer/components/+workloads-jobs/jobs.tsx:37
@ -1535,7 +1539,7 @@ msgstr ""
#: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:37 #: src/renderer/components/+user-management-roles-bindings/role-bindings.tsx:37
#: src/renderer/components/+user-management-service-accounts/create-service-account-dialog.tsx:79 #: src/renderer/components/+user-management-service-accounts/create-service-account-dialog.tsx:79
#: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:37 #: src/renderer/components/+user-management-service-accounts/service-accounts.tsx:37
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:43 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:47
#: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:46 #: src/renderer/components/+workloads-daemonsets/daemonsets.tsx:46
#: src/renderer/components/+workloads-deployments/deployments.tsx:59 #: src/renderer/components/+workloads-deployments/deployments.tsx:59
#: src/renderer/components/+workloads-jobs/jobs.tsx:38 #: src/renderer/components/+workloads-jobs/jobs.tsx:38
@ -2265,7 +2269,7 @@ msgid "Scale Deployment <0>{deploymentName}</0>"
msgstr "Масштабировать Deployment <0>{deploymentName}</0>" msgstr "Масштабировать Deployment <0>{deploymentName}</0>"
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:46 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:46
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:44 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:48
msgid "Schedule" msgid "Schedule"
msgstr "Расписание" msgstr "Расписание"
@ -2589,7 +2593,7 @@ msgid "Supplemental Groups"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:54 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:54
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:45 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:49
msgid "Suspend" msgid "Suspend"
msgstr "Заморозка" msgstr "Заморозка"
@ -2658,6 +2662,16 @@ msgstr "Толерантности"
msgid "Transmit" msgid "Transmit"
msgstr "Транзит" msgstr "Транзит"
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:107
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:79
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:80
msgid "Trigger"
msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx:103
msgid "Trigger CronJob <0>{cronjobName}</0>"
msgstr ""
#: src/renderer/components/+cluster/cluster-issues.tsx:102 #: src/renderer/components/+cluster/cluster-issues.tsx:102
#: src/renderer/components/+config-secrets/secret-details.tsx:74 #: src/renderer/components/+config-secrets/secret-details.tsx:74
#: src/renderer/components/+config-secrets/secrets.tsx:45 #: src/renderer/components/+config-secrets/secrets.tsx:45
@ -2898,7 +2912,7 @@ msgid "listKind"
msgstr "" msgstr ""
#: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:48 #: src/renderer/components/+workloads-cronjobs/cronjob-details.tsx:48
#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:57 #: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:61
msgid "never" msgid "never"
msgstr "" msgstr ""

View File

@ -12,9 +12,9 @@
}, },
"scripts": { "scripts": {
"dev": "concurrently -k \"yarn dev-run -C\" \"yarn dev:main\" \"yarn dev:renderer\"", "dev": "concurrently -k \"yarn dev-run -C\" \"yarn dev:main\" \"yarn dev:renderer\"",
"dev-run": "nodemon --watch static/build/main.js --exec \"electron --inspect .\" $@", "dev-run": "cross-env DEBUG=true nodemon --watch static/build/main.js --exec \"electron --inspect .\"",
"dev:main": "env DEBUG=true yarn compile:main --watch $@", "dev:main": "cross-env DEBUG=true yarn compile:main --watch",
"dev:renderer": "env DEBUG=true yarn compile:renderer --watch $@", "dev:renderer": "cross-env DEBUG=true yarn compile:renderer --watch",
"compile": "env NODE_ENV=production concurrently yarn:compile:*", "compile": "env NODE_ENV=production concurrently yarn:compile:*",
"compile:main": "webpack --config webpack.main.ts", "compile:main": "webpack --config webpack.main.ts",
"compile:renderer": "webpack --config webpack.renderer.ts", "compile:renderer": "webpack --config webpack.renderer.ts",
@ -175,6 +175,7 @@
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"electron-updater": "^4.3.1", "electron-updater": "^4.3.1",
"electron-window-state": "^5.0.3", "electron-window-state": "^5.0.3",
"file-type": "^14.7.1",
"filenamify": "^4.1.0", "filenamify": "^4.1.0",
"fs-extra": "^9.0.1", "fs-extra": "^9.0.1",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
@ -184,6 +185,7 @@
"jsonpath": "^1.0.2", "jsonpath": "^1.0.2",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"mac-ca": "^1.0.4", "mac-ca": "^1.0.4",
"make-synchronous": "^0.1.1",
"marked": "^1.1.0", "marked": "^1.1.0",
"md5-file": "^5.0.0", "md5-file": "^5.0.0",
"mobx": "^5.15.5", "mobx": "^5.15.5",
@ -264,6 +266,7 @@
"circular-dependency-plugin": "^5.2.0", "circular-dependency-plugin": "^5.2.0",
"color": "^3.1.2", "color": "^3.1.2",
"concurrently": "^5.2.0", "concurrently": "^5.2.0",
"cross-env": "^7.0.2",
"css-element-queries": "^1.2.3", "css-element-queries": "^1.2.3",
"css-loader": "^3.5.3", "css-loader": "^3.5.3",
"dompurify": "^2.0.11", "dompurify": "^2.0.11",

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, remote, ipcRenderer } 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";

View File

@ -5,6 +5,10 @@ import { Cluster } from "../main/cluster";
import { ClusterStore } from "./cluster-store"; import { ClusterStore } from "./cluster-store";
import { workspaceStore } from "./workspace-store"; import { workspaceStore } from "./workspace-store";
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png")
console.log("") // fix bug
let clusterStore: ClusterStore; let clusterStore: ClusterStore;
describe("empty config", () => { describe("empty config", () => {
@ -231,12 +235,13 @@ describe("pre 2.6.0 config with a cluster icon", () => {
}, },
cluster1: { cluster1: {
kubeConfig: "foo", kubeConfig: "foo",
icon: "icon path", icon: "icon_path",
preferences: { preferences: {
terminalCWD: "/tmp" terminalCWD: "/tmp"
} }
}, },
}) }),
"icon_path": testDataIcon,
} }
} }
mockFs(mockOpts); mockFs(mockOpts);
@ -252,7 +257,7 @@ describe("pre 2.6.0 config with a cluster icon", () => {
const storedClusterData = clusterStore.clustersList[0]; const storedClusterData = clusterStore.clustersList[0];
expect(storedClusterData.hasOwnProperty('icon')).toBe(false); expect(storedClusterData.hasOwnProperty('icon')).toBe(false);
expect(storedClusterData.preferences.hasOwnProperty('icon')).toBe(true); expect(storedClusterData.preferences.hasOwnProperty('icon')).toBe(true);
expect(storedClusterData.preferences.icon).toBe("icon path"); expect(storedClusterData.preferences.icon.startsWith("data:image/jpeg;base64,")).toBe(true);
}) })
}) })
@ -269,7 +274,6 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
}, },
cluster1: { cluster1: {
kubeConfig: "foo", kubeConfig: "foo",
icon: "icon path",
preferences: { preferences: {
terminalCWD: "/tmp" terminalCWD: "/tmp"
} }
@ -300,16 +304,20 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
'lens-cluster-store.json': JSON.stringify({ 'lens-cluster-store.json': JSON.stringify({
__internal__: { __internal__: {
migrations: { migrations: {
version: "2.7.0" version: "3.5.0"
} }
}, },
clusters: [ clusters: [
{ {
id: 'cluster1', id: 'cluster1',
kubeConfig: 'kubeconfig content' kubeConfig: 'kubeconfig content',
preferences: {
icon: "store://icon_path",
}
} }
] ]
}) }),
"icon_path": testDataIcon,
} }
}; };
mockFs(mockOpts); mockFs(mockOpts);
@ -325,4 +333,9 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
const config = clusterStore.clustersList[0].kubeConfigPath; const config = clusterStore.clustersList[0].kubeConfigPath;
expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content"); expect(fs.readFileSync(config, "utf8")).toBe("kubeconfig content");
}) })
it("migrates to modern format with icon not in file", async () => {
const { icon } = clusterStore.clustersList[0].preferences;
expect(icon.startsWith("data:image/jpeg;base64, ")).toBe(true);
})
}) })

View File

@ -27,6 +27,7 @@ export interface ClusterState extends ClusterModel {
online: boolean; online: boolean;
disconnected: boolean; disconnected: boolean;
accessible: boolean; accessible: boolean;
ready: boolean;
failureReason: string; failureReason: string;
nodes: number; nodes: number;
eventCount: number; eventCount: number;
@ -47,6 +48,7 @@ export class Cluster implements ClusterModel {
protected eventDisposers: Function[] = []; protected eventDisposers: Function[] = [];
whenInitialized = when(() => this.initialized); whenInitialized = when(() => this.initialized);
whenReady = when(() => this.ready);
@observable initialized = false; @observable initialized = false;
@observable contextName: string; @observable contextName: string;
@ -56,6 +58,7 @@ export class Cluster implements ClusterModel {
@observable kubeProxyUrl: string; // lens-proxy to kube-api url @observable kubeProxyUrl: string; // lens-proxy to kube-api url
@observable online: boolean; @observable online: boolean;
@observable accessible: boolean; @observable accessible: boolean;
@observable ready: boolean;
@observable disconnected: boolean; @observable disconnected: boolean;
@observable failureReason: string; @observable failureReason: string;
@observable nodes = 0; @observable nodes = 0;
@ -149,6 +152,7 @@ export class Cluster implements ClusterModel {
this.disconnected = true; this.disconnected = true;
this.online = false; this.online = false;
this.accessible = false; this.accessible = false;
this.ready = false;
this.pushState(); this.pushState();
} }
@ -172,6 +176,7 @@ export class Cluster implements ClusterModel {
this.refreshEvents(), this.refreshEvents(),
this.refreshAllowedResources(), this.refreshAllowedResources(),
]); ]);
this.ready = true
} }
} }
@ -370,6 +375,7 @@ export class Cluster implements ClusterModel {
initialized: this.initialized, initialized: this.initialized,
apiUrl: this.apiUrl, apiUrl: this.apiUrl,
online: this.online, online: this.online,
ready: this.ready,
disconnected: this.disconnected, disconnected: this.disconnected,
accessible: this.accessible, accessible: this.accessible,
failureReason: this.failureReason, failureReason: this.failureReason,

View File

@ -6,7 +6,7 @@ export default migration({
run(store, log) { run(store, log) {
for (const value of store) { for (const value of store) {
const clusterKey = value[0]; const clusterKey = value[0];
if(clusterKey === "__internal__") continue if (clusterKey === "__internal__") continue
const cluster = value[1]; const cluster = value[1];
cluster.workspace = "default" cluster.workspace = "default"
store.set(clusterKey, cluster) store.set(clusterKey, cluster)

View File

@ -1,32 +1,71 @@
// Move embedded kubeconfig into separate file and add reference to it to cluster settings // Move embedded kubeconfig into separate file and add reference to it to cluster settings
// convert file path cluster icons to their base64 encoded versions
import path from "path"
import { app, remote } from "electron"
import { migration } from "../migration-wrapper"; import { migration } from "../migration-wrapper";
import { ensureDirSync } from "fs-extra" import fse from "fs-extra"
import { ClusterModel, ClusterStore } from "../../common/cluster-store"; import { ClusterModel, ClusterStore } from "../../common/cluster-store";
import { loadConfig } from "../../common/kube-helpers"; import { loadConfig } from "../../common/kube-helpers";
import makeSynchronous from "make-synchronous"
const AsyncFunction = Object.getPrototypeOf(async function () { return }).constructor;
const getFileTypeFnString = `return require("file-type").fromBuffer(fileData)`;
const getFileType = new AsyncFunction("fileData", getFileTypeFnString);
export default migration({ export default migration({
version: "3.6.0-beta.1", version: "3.6.0-beta.1",
run(store, printLog) { run(store, printLog) {
const migratedClusters: ClusterModel[] = [] const userDataPath = (app || remote.app).getPath("userData")
const storedClusters: ClusterModel[] = store.get("clusters");
const kubeConfigBase = ClusterStore.getCustomKubeConfigPath(""); const kubeConfigBase = ClusterStore.getCustomKubeConfigPath("");
const storedClusters: ClusterModel[] = store.get("clusters") || [];
if (!storedClusters) return; if (!storedClusters.length) return;
ensureDirSync(kubeConfigBase); fse.ensureDirSync(kubeConfigBase);
printLog("Number of clusters to migrate: ", storedClusters.length) printLog("Number of clusters to migrate: ", storedClusters.length)
for (const cluster of storedClusters) { const migratedClusters = storedClusters
try { .map(cluster => {
// take the embedded kubeconfig and dump it into a file] /**
cluster.kubeConfigPath = ClusterStore.embedCustomKubeConfig(cluster.id, cluster.kubeConfig); * migrate kubeconfig
cluster.contextName = loadConfig(cluster.kubeConfigPath).getCurrentContext(); */
delete cluster.kubeConfig; try {
migratedClusters.push(cluster) // take the embedded kubeconfig and dump it into a file
} catch (error) { cluster.kubeConfigPath = ClusterStore.embedCustomKubeConfig(cluster.id, cluster.kubeConfig);
printLog(`Failed to migrate Kubeconfig for cluster "${cluster.id}"`, error) cluster.contextName = loadConfig(cluster.kubeConfigPath).getCurrentContext();
} delete cluster.kubeConfig;
}
} catch (error) {
printLog(`Failed to migrate Kubeconfig for cluster "${cluster.id}", removing cluster...`, error)
return undefined;
}
/**
* migrate cluster icon
*/
try {
if (cluster.preferences?.icon) {
printLog(`migrating ${cluster.preferences.icon} for ${cluster.preferences.clusterName}`)
const iconPath = cluster.preferences.icon.replace("store://", "")
const fileData = fse.readFileSync(path.join(userDataPath, iconPath));
const { mime = "" } = makeSynchronous(getFileType)(fileData);
if (!mime) {
printLog(`mime type not detected for ${cluster.preferences.clusterName}'s icon: ${iconPath}`)
}
cluster.preferences.icon = `data:${mime};base64, ${fileData.toString('base64')}`;
} else {
delete cluster.preferences?.icon;
}
} catch (error) {
printLog(`Failed to migrate cluster icon for cluster "${cluster.id}"`, error)
delete cluster.preferences.icon;
}
return cluster;
})
.filter(c => c);
// "overwrite" the cluster configs // "overwrite" the cluster configs
if (migratedClusters.length > 0) { if (migratedClusters.length > 0) {

View File

@ -32,6 +32,12 @@ export class CronJob extends KubeObject {
jobTemplate: { jobTemplate: {
metadata: { metadata: {
creationTimestamp?: string; creationTimestamp?: string;
labels?: {
[key: string]: string;
};
annotations?: {
[key: string]: string;
};
}; };
spec: { spec: {
template: { template: {
@ -53,7 +59,7 @@ export class CronJob extends KubeObject {
failedJobsHistoryLimit: number; failedJobsHistoryLimit: number;
} }
status: { status: {
lastScheduleTime: string; lastScheduleTime?: string;
} }
getSuspendFlag() { getSuspendFlag() {
@ -61,6 +67,7 @@ export class CronJob extends KubeObject {
} }
getLastScheduleTime() { getLastScheduleTime() {
if (!this.status.lastScheduleTime) return "-"
const diff = moment().diff(this.status.lastScheduleTime) const diff = moment().diff(this.status.lastScheduleTime)
return formatDuration(diff, true) return formatDuration(diff, true)
} }

View File

@ -13,7 +13,7 @@ export class Job extends WorkloadKubeObject {
parallelism?: number; parallelism?: number;
completions?: number; completions?: number;
backoffLimit?: number; backoffLimit?: number;
selector: { selector?: {
matchLabels: { matchLabels: {
[name: string]: string; [name: string]: string;
}; };
@ -21,8 +21,11 @@ export class Job extends WorkloadKubeObject {
template: { template: {
metadata: { metadata: {
creationTimestamp?: string; creationTimestamp?: string;
labels: { labels?: {
name: string; [name: string]: string;
};
annotations?: {
[name: string]: string;
}; };
}; };
spec: { spec: {
@ -35,7 +38,7 @@ export class Job extends WorkloadKubeObject {
nodeSelector?: { nodeSelector?: {
[selector: string]: string; [selector: string]: string;
}; };
tolerations: { tolerations?: {
key: string; key: string;
operator: string; operator: string;
effect: string; effect: string;

View File

@ -28,7 +28,7 @@ export class ClusterIconSetting extends React.Component<Props> {
try { try {
if (file) { if (file) {
const buf = Buffer.from(await file.arrayBuffer()); const buf = Buffer.from(await file.arrayBuffer());
cluster.preferences.icon = `data:image/jpeg;base64, ${buf.toString('base64')}`; cluster.preferences.icon = `data:${file.type};base64, ${buf.toString('base64')}`;
} else { } else {
// this has to be done as a seperate branch (and not always) because `cluster` // this has to be done as a seperate branch (and not always) because `cluster`
// is observable and triggers an update loop. // is observable and triggers an update loop.

View File

@ -1,8 +1,24 @@
.LandingPage { .LandingPage {
height: 100%; height: 100%;
background: #282b2f url(../../components/icon/crane.svg) no-repeat;
background-position: 0 35%;
background-size: 85%;
background-clip: content-box;
text-align: center; text-align: center;
z-index: 0;
&::after {
content: "";
background: url(../../components/icon/crane.svg) no-repeat;
background-position: 0 35%;
background-size: 85%;
background-clip: content-box;
opacity: 1;
top: 0;
left: 0;
bottom: 0;
right: 0;
position: absolute;
z-index: -1;
.theme-light & {
opacity: 0.2;
}
}
} }

View File

@ -0,0 +1,18 @@
.CronJobTriggerDialog {
.Wizard {
.header {
span {
color: #a0a0a0;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.WizardStep {
.step-content {
min-height: 90px;
overflow: hidden;
}
}
}
}

View File

@ -0,0 +1,127 @@
import "./cronjob-trigger-dialog.scss";
import React, { Component } from "react";
import { observable } from "mobx";
import { observer } from "mobx-react";
import { Trans } from "@lingui/macro";
import { Dialog, DialogProps } from "../dialog";
import { Wizard, WizardStep } from "../wizard";
import { CronJob, cronJobApi, jobApi, Job } from "../../api/endpoints";
import { Notifications } from "../notifications";
import { cssNames } from "../../utils";
import { Input } from "../input";
import { systemName, maxLength } from "../input/input.validators";
interface Props extends Partial<DialogProps> {
}
@observer
export class CronJobTriggerDialog extends Component<Props> {
@observable static isOpen = false;
@observable static data: CronJob = null;
@observable jobName = "";
@observable ready = false;
static open(cronjob: CronJob) {
CronJobTriggerDialog.isOpen = true;
CronJobTriggerDialog.data = cronjob;
}
static close() {
CronJobTriggerDialog.isOpen = false;
}
get cronjob() {
return CronJobTriggerDialog.data;
}
close = () => {
CronJobTriggerDialog.close();
}
onOpen = async () => {
const { cronjob } = this;
this.jobName = cronjob ? cronjob.getName() + "-manual-" + Math.random().toString(36).slice(2, 7) : "";
this.jobName = this.jobName.slice(0, 63);
this.ready = true;
}
onClose = () => {
this.ready = false;
}
trigger = async () => {
const { cronjob } = this;
const { close } = this;
try {
const cronjobDefinition = await cronJobApi.get({
name: cronjob.getName(),
namespace: cronjob.getNs()
});
await jobApi.create({
name: this.jobName,
namespace: cronjob.getNs()
}, {
spec: cronjobDefinition.spec.jobTemplate.spec
});
close();
} catch (err) {
Notifications.error(err);
}
}
renderContents() {
return (
<>
<div className="flex gaps">
<Trans>Job name</Trans>:
</div>
<div className="flex gaps">
<Input
required autoFocus
placeholder={this.jobName}
validators={[systemName, maxLength]}
maxLength={63}
value={this.jobName} onChange={v => this.jobName = v.toLowerCase()}
className="box grow"
/>
</div>
</>
)
}
render() {
const { className, ...dialogProps } = this.props;
const cronjobName = this.cronjob ? this.cronjob.getName() : "";
const header = (
<h5>
<Trans>Trigger CronJob <span>{cronjobName}</span></Trans>
</h5>
);
return (
<Dialog
{...dialogProps}
isOpen={CronJobTriggerDialog.isOpen}
className={cssNames("CronJobTriggerDialog", className)}
onOpen={this.onOpen}
onClose={this.onClose}
close={this.close}
>
<Wizard header={header} done={this.close}>
<WizardStep
contentClass="flex gaps column"
next={this.trigger}
nextLabel={<Trans>Trigger</Trans>}
disabledNext={!this.ready}
>
{this.renderContents()}
</WizardStep>
</Wizard>
</Dialog>
);
}
}

View File

@ -9,13 +9,13 @@ export class CronJobStore extends KubeObjectStore<CronJob> {
api = cronJobApi api = cronJobApi
getStatuses(cronJobs?: CronJob[]) { getStatuses(cronJobs?: CronJob[]) {
const status = { failed: 0, running: 0 } const status = { suspended: 0, scheduled: 0 }
cronJobs.forEach(cronJob => { cronJobs.forEach(cronJob => {
if (cronJob.spec.suspend) { if (cronJob.spec.suspend) {
status.failed++ status.suspended++
} }
else { else {
status.running++ status.scheduled++
} }
}) })
return status return status

View File

@ -3,8 +3,10 @@ import "./cronjobs.scss";
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { RouteComponentProps } from "react-router"; import { RouteComponentProps } from "react-router";
import { Trans } from "@lingui/macro"; import { t, Trans } from "@lingui/macro";
import { CronJob, cronJobApi } from "../../api/endpoints/cron-job.api"; import { CronJob, cronJobApi } from "../../api/endpoints/cron-job.api";
import { MenuItem } from "../menu";
import { Icon } from "../icon";
import { cronJobStore } from "./cronjob.store"; import { cronJobStore } from "./cronjob.store";
import { jobStore } from "../+workloads-jobs/job.store"; import { jobStore } from "../+workloads-jobs/job.store";
import { eventStore } from "../+events/event.store"; import { eventStore } from "../+events/event.store";
@ -12,7 +14,9 @@ import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object-
import { ICronJobsRouteParams } from "../+workloads"; import { ICronJobsRouteParams } from "../+workloads";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { KubeEventIcon } from "../+events/kube-event-icon"; import { KubeEventIcon } from "../+events/kube-event-icon";
import { _i18n } from "../../i18n";
import { apiManager } from "../../api/api-manager"; import { apiManager } from "../../api/api-manager";
import { CronJobTriggerDialog } from "./cronjob-trigger-dialog";
enum sortBy { enum sortBy {
name = "name", name = "name",
@ -79,8 +83,14 @@ export class CronJobs extends React.Component<Props> {
} }
export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) { export function CronJobMenu(props: KubeObjectMenuProps<CronJob>) {
const { object, toolbar } = props;
return ( return (
<KubeObjectMenu {...props}/> <KubeObjectMenu {...props}>
<MenuItem onClick={() => CronJobTriggerDialog.open(object)}>
<Icon material="play_circle_filled" title={_i18n._(t`Trigger`)} interactive={toolbar}/>
<span className="title"><Trans>Trigger</Trans></span>
</MenuItem>
</KubeObjectMenu>
) )
} }

View File

@ -3,6 +3,8 @@
--workload-status-pending: #{$pod-status-pending-color}; --workload-status-pending: #{$pod-status-pending-color};
--workload-status-evicted: #{$pod-status-evicted-color}; --workload-status-evicted: #{$pod-status-evicted-color};
--workload-status-succeeded: #{$pod-status-succeeded-color}; --workload-status-succeeded: #{$pod-status-succeeded-color};
--workload-status-scheduled: #{$cronjob-scheduled};
--workload-status-suspended: #{$cronjob-suspended};
--workload-status-failed: #{$pod-status-failed-color}; --workload-status-failed: #{$pod-status-failed-color};
--workload-status-terminated: #{$pod-status-terminated-color}; --workload-status-terminated: #{$pod-status-terminated-color};
--workload-status-unknown: #{$pod-status-unknown-color}; --workload-status-unknown: #{$pod-status-unknown-color};

View File

@ -26,6 +26,10 @@ $deployment-replicafailure: $colorError;
$job-complete: $colorSuccess; $job-complete: $colorSuccess;
$job-failed: $colorError; $job-failed: $colorError;
// Cronjob
$cronjob-scheduled: $colorSuccess;
$cronjob-suspended: $colorTerminated;
// Pod Statuses // Pod Statuses
$pod-status-color-list: ( $pod-status-color-list: (
running: $pod-status-running-color, running: $pod-status-running-color,
@ -48,6 +52,12 @@ $job-condition-color-list: (
failed: $job-failed, failed: $job-failed,
); );
// Cronjob Conditions
$cronjob-condition-color-list: (
scheduled: $cronjob-scheduled,
suspended: $cronjob-suspended,
);
@mixin pod-status-bgs { @mixin pod-status-bgs {
@each $status, $color in $pod-status-color-list { @each $status, $color in $pod-status-color-list {
&.#{$status} { &.#{$status} {

View File

@ -25,6 +25,7 @@ import { KubeObjectDetails } from "./kube-object/kube-object-details";
import { AddRoleBindingDialog } from "./+user-management-roles-bindings"; import { AddRoleBindingDialog } from "./+user-management-roles-bindings";
import { PodLogsDialog } from "./+workloads-pods/pod-logs-dialog"; import { PodLogsDialog } from "./+workloads-pods/pod-logs-dialog";
import { DeploymentScaleDialog } from "./+workloads-deployments/deployment-scale-dialog"; import { DeploymentScaleDialog } from "./+workloads-deployments/deployment-scale-dialog";
import { CronJobTriggerDialog } from "./+workloads-cronjobs/cronjob-trigger-dialog";
import { CustomResources } from "./+custom-resources/custom-resources"; import { CustomResources } from "./+custom-resources/custom-resources";
import { crdRoute } from "./+custom-resources"; import { crdRoute } from "./+custom-resources";
import { isAllowedResource } from "../../common/rbac"; import { isAllowedResource } from "../../common/rbac";
@ -80,6 +81,7 @@ export class App extends React.Component {
<AddRoleBindingDialog/> <AddRoleBindingDialog/>
<PodLogsDialog/> <PodLogsDialog/>
<DeploymentScaleDialog/> <DeploymentScaleDialog/>
<CronJobTriggerDialog/>
</ErrorBoundary> </ErrorBoundary>
</Router> </Router>
</I18nProvider> </I18nProvider>

View File

@ -63,14 +63,14 @@ export class Chart extends React.Component<ChartProps> {
this.renderChart() this.renderChart()
} }
componentDidUpdate(prevProps: ChartProps) { componentDidUpdate() {
const { data, showChart, redraw } = this.props const { showChart, redraw } = this.props
if (redraw) { if (redraw) {
this.chart.destroy() this.chart.destroy()
this.renderChart() this.renderChart()
return return
} }
if (!isEqual(prevProps.data, data) && showChart) { if (showChart) {
if (!this.chart) this.renderChart() if (!this.chart) this.renderChart()
else this.updateChart() else this.updateChart()
} }

View File

@ -30,7 +30,7 @@
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 0; bottom: 0;
margin: -$padding * 1.5; margin: -$padding;
font-size: $font-size-small; font-size: $font-size-small;
background: $colorError; background: $colorError;
color: white; color: white;

View File

@ -3,7 +3,7 @@
font-size: $font-size-small; font-size: $font-size-small;
background-color: #3d90ce; background-color: #3d90ce;
padding: $spacing $padding; padding: $padding / 4 $padding;
color: white; color: white;
#current-workspace { #current-workspace {

View File

@ -11,7 +11,7 @@ export class BottomBar extends React.Component {
const { currentWorkspace } = workspaceStore; const { currentWorkspace } = workspaceStore;
return ( return (
<div className="BottomBar flex gaps"> <div className="BottomBar flex gaps">
<div id="current-workspace" className="flex gaps align-center box right"> <div id="current-workspace" className="flex gaps align-center box">
<Icon small material="layers"/> <Icon small material="layers"/>
<span className="workspace-name">{currentWorkspace.name}</span> <span className="workspace-name">{currentWorkspace.name}</span>
</div> </div>

View File

@ -1,12 +1,11 @@
.ClustersMenu { .ClustersMenu {
@include hidden-scrollbar;
$spacing: $padding * 2; $spacing: $padding * 2;
position: relative; position: relative;
text-align: center; text-align: center;
padding: $spacing;
background: $clusterMenuBackground; background: $clusterMenuBackground;
border-right: 1px solid $clusterMenuBorderColor; border-right: 1px solid $clusterMenuBorderColor;
padding-bottom: $spacing;
.is-mac & { .is-mac & {
padding-top: $spacing * 2; padding-top: $spacing * 2;
@ -23,8 +22,7 @@
padding: $spacing; padding: $spacing;
width: 320px; width: 320px;
background: $bgc; background: $bgc;
z-index: 100; color: $textColorAccent;
color: white;
filter: drop-shadow(0 0px 2px #ffffff33); filter: drop-shadow(0 0px 2px #ffffff33);
pointer-events: none; pointer-events: none;
@ -38,11 +36,24 @@
border-right: $arrowSize solid $bgc; border-right: $arrowSize solid $bgc;
right: 100%; right: 100%;
} }
.theme-light & {
filter: drop-shadow(0 0px 2px #777);
background: white;
&:before {
border-right-color: white;
}
}
}
.clusters {
@include hidden-scrollbar;
padding: 0 $spacing $spacing;
} }
> .add-cluster { > .add-cluster {
position: relative; position: relative;
margin-top: $padding;
min-width: 43px; min-width: 43px;
.Icon { .Icon {

View File

@ -99,7 +99,7 @@ export class ClustersMenu extends React.Component<Props> {
const showStartupHint = this.showHint && isLanding && noClustersInScope; // fixme: broken, move to landing.tsx const showStartupHint = this.showHint && isLanding && noClustersInScope; // fixme: broken, move to landing.tsx
return ( return (
<div <div
className={cssNames("ClustersMenu flex column gaps", className)} className={cssNames("ClustersMenu flex column", className)}
onMouseEnter={() => this.showHint = false} onMouseEnter={() => this.showHint = false}
> >
{showStartupHint && ( {showStartupHint && (
@ -112,18 +112,20 @@ export class ClustersMenu extends React.Component<Props> {
</p> </p>
</div> </div>
)} )}
{clusters.map(cluster => { <div className="clusters flex column gaps">
return ( {clusters.map(cluster => {
<ClusterIcon return (
key={cluster.id} <ClusterIcon
showErrors={true} key={cluster.id}
cluster={cluster} showErrors={true}
isActive={cluster.id === getMatchedClusterId()} cluster={cluster}
onClick={() => this.showCluster(cluster.id)} isActive={cluster.id === getMatchedClusterId()}
onContextMenu={() => this.showContextMenu(cluster)} onClick={() => this.showCluster(cluster.id)}
/> onContextMenu={() => this.showContextMenu(cluster)}
) />
})} )
})}
</div>
<div className="add-cluster" onClick={this.addCluster}> <div className="add-cluster" onClick={this.addCluster}>
<Tooltip targetId="add-cluster-icon"> <Tooltip targetId="add-cluster-icon">
<Trans>Add Cluster</Trans> <Trans>Add Cluster</Trans>

View File

@ -21,10 +21,10 @@ export async function initView(clusterId: ClusterId) {
} }
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`) logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
const cluster = clusterStore.getById(clusterId); const cluster = clusterStore.getById(clusterId);
await cluster.whenInitialized; await cluster.whenReady;
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.preferences.clusterName; iframe.name = cluster.contextName;
iframe.setAttribute("src", `//${clusterId}.${location.host}`) iframe.setAttribute("src", `//${clusterId}.${location.host}`)
iframe.addEventListener("load", async () => { iframe.addEventListener("load", async () => {
logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`) logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`)

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -1588,6 +1588,11 @@
dependencies: dependencies:
defer-to-connect "^1.0.1" defer-to-connect "^1.0.1"
"@tokenizer/token@^0.1.0", "@tokenizer/token@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.1.1.tgz#f0d92c12f87079ddfd1b29f614758b9696bc29e3"
integrity sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==
"@types/anymatch@*": "@types/anymatch@*":
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
@ -3997,6 +4002,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
sha.js "^2.4.8" sha.js "^2.4.8"
cross-env@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9"
integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==
dependencies:
cross-spawn "^7.0.1"
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5" version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@ -4016,7 +4028,7 @@ cross-spawn@^3.0.0:
lru-cache "^4.0.1" lru-cache "^4.0.1"
which "^1.2.9" which "^1.2.9"
cross-spawn@^7.0.0, cross-spawn@^7.0.2: cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@ -5189,6 +5201,16 @@ file-loader@^6.0.0:
loader-utils "^2.0.0" loader-utils "^2.0.0"
schema-utils "^2.6.5" schema-utils "^2.6.5"
file-type@^14.7.1:
version "14.7.1"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-14.7.1.tgz#f748732b3e70478bff530e1cf0ec2fe33608b1bb"
integrity sha512-sXAMgFk67fQLcetXustxfKX+PZgHIUFn96Xld9uH8aXPdX3xOp0/jg9OdouVTvQrf7mrn+wAa4jN/y9fUOOiRA==
dependencies:
readable-web-to-node-stream "^2.0.0"
strtok3 "^6.0.3"
token-types "^2.0.0"
typedarray-to-buffer "^3.1.5"
file-uri-to-path@1.0.0: file-uri-to-path@1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
@ -6067,7 +6089,7 @@ identity-obj-proxy@^3.0.0:
dependencies: dependencies:
harmony-reflect "^1.4.6" harmony-reflect "^1.4.6"
ieee754@^1.1.4: ieee754@^1.1.13, ieee754@^1.1.4:
version "1.1.13" version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
@ -7625,6 +7647,14 @@ make-plural@^6.2.1:
resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-6.2.1.tgz#2790af1d05fb2fc35a111ce759ffdb0aca1339a3" resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-6.2.1.tgz#2790af1d05fb2fc35a111ce759ffdb0aca1339a3"
integrity sha512-AmkruwJ9EjvyTv6AM8MBMK3TAeOJvhgTv5YQXzF0EP2qawhpvMjDpHvsdOIIT0Vn+BB0+IogmYZ1z+Ulm/m0Fg== integrity sha512-AmkruwJ9EjvyTv6AM8MBMK3TAeOJvhgTv5YQXzF0EP2qawhpvMjDpHvsdOIIT0Vn+BB0+IogmYZ1z+Ulm/m0Fg==
make-synchronous@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/make-synchronous/-/make-synchronous-0.1.1.tgz#0169f6ec769c3cf8948d66790da262740c1209e7"
integrity sha512-Y4SxxqhaoyMDokJQ0AZz0E+bLhRkOSR7Z/IQoTKPdS6HYi3aobal2kMHoHHoqBadPWjf07P4K1FQLXOx3wf9Yw==
dependencies:
subsume "^3.0.0"
type-fest "^0.16.0"
makeerror@1.0.x: makeerror@1.0.x:
version "1.0.11" version "1.0.11"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@ -8936,6 +8966,11 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
sha.js "^2.4.8" sha.js "^2.4.8"
peek-readable@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-3.1.0.tgz#250b08b7de09db8573d7fd8ea475215bbff14348"
integrity sha512-KGuODSTV6hcgdZvDrIDBUkN0utcAVj1LL7FfGbM0viKTtCHmtZcuEJ+lGqsp0fTFkGqesdtemV2yUSMeyy3ddA==
pend@~1.2.0: pend@~1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
@ -9529,6 +9564,11 @@ readable-stream@^3.1.1, readable-stream@^3.6.0:
string_decoder "^1.1.1" string_decoder "^1.1.1"
util-deprecate "^1.0.1" util-deprecate "^1.0.1"
readable-web-to-node-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-2.0.0.tgz#751e632f466552ac0d5c440cc01470352f93c4b7"
integrity sha512-+oZJurc4hXpaaqsN68GoZGQAQIA3qr09Or4fqEsargABnbe5Aau8hFn6ISVleT3cpY/0n/8drn7huyyEvTbghA==
readdirp@^2.2.1: readdirp@^2.2.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
@ -10648,6 +10688,15 @@ strip-outer@^1.0.1:
dependencies: dependencies:
escape-string-regexp "^1.0.2" escape-string-regexp "^1.0.2"
strtok3@^6.0.3:
version "6.0.4"
resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.0.4.tgz#ede0d20fde5aa9fda56417c3558eaafccc724694"
integrity sha512-rqWMKwsbN9APU47bQTMEYTPcwdpKDtmf1jVhHzNW2cL1WqAxaM9iBb9t5P2fj+RV2YsErUWgQzHD5JwV0uCTEQ==
dependencies:
"@tokenizer/token" "^0.1.1"
"@types/debug" "^4.1.5"
peek-readable "^3.1.0"
style-loader@^1.2.1: style-loader@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a"
@ -10656,6 +10705,14 @@ style-loader@^1.2.1:
loader-utils "^2.0.0" loader-utils "^2.0.0"
schema-utils "^2.6.6" schema-utils "^2.6.6"
subsume@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/subsume/-/subsume-3.0.0.tgz#22c92730f441ad72ee9af4bdad42dc4ff830cfaf"
integrity sha512-6n/UfV8UWKwJNO8OAOiKntwEMihuBeeoJfzpL542C+OuvT4iWG9SwjrXkOmsxjb4SteHUsos9SvrdqZ9+ICwTQ==
dependencies:
escape-string-regexp "^2.0.0"
unique-string "^2.0.0"
sumchecker@^3.0.1: sumchecker@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42"
@ -10977,6 +11034,14 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2" regex-not "^1.0.2"
safe-regex "^1.1.0" safe-regex "^1.1.0"
token-types@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/token-types/-/token-types-2.0.0.tgz#b23618af744818299c6fbf125e0fdad98bab7e85"
integrity sha512-WWvu8sGK8/ZmGusekZJJ5NM6rRVTTDO7/bahz4NGiSDb/XsmdYBn6a1N/bymUHuWYTWeuLUg98wUzvE4jPdCZw==
dependencies:
"@tokenizer/token" "^0.1.0"
ieee754 "^1.1.13"
touch@^3.1.0: touch@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
@ -11150,6 +11215,11 @@ type-fest@^0.13.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
type-fest@^0.16.0:
version "0.16.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860"
integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==
type-fest@^0.6.0: type-fest@^0.6.0:
version "0.6.0" version "0.6.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"