From 8f778ea379ac39cfaa0f57f3198eaa5ae92bd4f8 Mon Sep 17 00:00:00 2001 From: Nox Date: Tue, 25 Aug 2020 09:55:12 +0200 Subject: [PATCH 1/7] Add cronjob trigger (#694) Signed-off-by: Nox Co-authored-by: Lauri Nevala --- locales/en/messages.po | 32 +++-- locales/fi/messages.po | 32 +++-- locales/ru/messages.po | 32 +++-- src/renderer/api/endpoints/cron-job.api.ts | 9 +- src/renderer/api/endpoints/job.api.ts | 11 +- .../cronjob-trigger-dialog.scss | 18 +++ .../cronjob-trigger-dialog.tsx | 128 ++++++++++++++++++ .../+workloads-cronjobs/cronjob.store.ts | 6 +- .../+workloads-cronjobs/cronjobs.tsx | 14 +- .../overview-workload-status.scss | 2 + .../+workloads/workloads-mixins.scss | 10 ++ src/renderer/components/app.tsx | 2 + 12 files changed, 259 insertions(+), 37 deletions(-) create mode 100644 src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.scss create mode 100644 src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx diff --git a/locales/en/messages.po b/locales/en/messages.po index 46d7fdae36..8859dde6a8 100644 --- a/locales/en/messages.po +++ b/locales/en/messages.po @@ -79,7 +79,7 @@ msgid "Account Name" msgstr "Account Name" #: 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" msgstr "Active" @@ -173,7 +173,7 @@ msgstr "Affinities" #: 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-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-deployments/deployments.tsx:63 #: src/renderer/components/+workloads-jobs/jobs.tsx:41 @@ -675,7 +675,7 @@ msgstr "Created at" msgid "Credentials Ref" msgstr "Credentials Ref" -#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:40 +#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:44 msgid "Cron Jobs" msgstr "Cron Jobs" @@ -1154,6 +1154,10 @@ msgstr "Item list is empty" msgid "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-cronjobs/cronjob-details.tsx:62 #: src/renderer/components/+workloads-jobs/jobs.tsx:36 @@ -1218,7 +1222,7 @@ msgid "Last Failure Time" msgstr "Last Failure Time" #: 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" msgstr "Last schedule" @@ -1448,7 +1452,7 @@ msgstr "Mounts" #: 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.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-deployments/deployments.tsx:58 #: src/renderer/components/+workloads-jobs/jobs.tsx:37 @@ -1498,7 +1502,7 @@ msgstr "Names" #: 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/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-deployments/deployments.tsx:59 #: src/renderer/components/+workloads-jobs/jobs.tsx:38 @@ -2155,7 +2159,7 @@ msgid "Scale Deployment <0>{deploymentName}" msgstr "Scale Deployment <0>{deploymentName}" #: 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" msgstr "Schedule" @@ -2413,7 +2417,7 @@ msgid "Supplemental Groups" msgstr "Supplemental Groups" #: 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" msgstr "Suspend" @@ -2482,6 +2486,16 @@ msgstr "Tolerations" msgid "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}" +msgstr "Trigger CronJob <0>{cronjobName}" + #: src/renderer/components/+cluster/cluster-issues.tsx:102 #: src/renderer/components/+config-secrets/secret-details.tsx:74 #: src/renderer/components/+config-secrets/secrets.tsx:45 @@ -2717,7 +2731,7 @@ msgid "listKind" msgstr "listKind" #: 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" msgstr "never" diff --git a/locales/fi/messages.po b/locales/fi/messages.po index 8e41f2b6a2..464a49bf9e 100644 --- a/locales/fi/messages.po +++ b/locales/fi/messages.po @@ -79,7 +79,7 @@ msgid "Account Name" msgstr "" #: 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" msgstr "" @@ -173,7 +173,7 @@ msgstr "" #: 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-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-deployments/deployments.tsx:63 #: src/renderer/components/+workloads-jobs/jobs.tsx:41 @@ -671,7 +671,7 @@ msgstr "" msgid "Credentials Ref" msgstr "" -#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:40 +#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:44 msgid "Cron Jobs" msgstr "" @@ -1145,6 +1145,10 @@ msgstr "" msgid "JSON Path" 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-cronjobs/cronjob-details.tsx:62 #: src/renderer/components/+workloads-jobs/jobs.tsx:36 @@ -1209,7 +1213,7 @@ msgid "Last Failure Time" msgstr "" #: 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" msgstr "" @@ -1439,7 +1443,7 @@ msgstr "" #: 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.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-deployments/deployments.tsx:58 #: src/renderer/components/+workloads-jobs/jobs.tsx:37 @@ -1489,7 +1493,7 @@ msgstr "" #: 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/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-deployments/deployments.tsx:59 #: src/renderer/components/+workloads-jobs/jobs.tsx:38 @@ -2138,7 +2142,7 @@ msgid "Scale Deployment <0>{deploymentName}" msgstr "" #: 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" msgstr "" @@ -2396,7 +2400,7 @@ msgid "Supplemental Groups" msgstr "" #: 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" msgstr "" @@ -2465,6 +2469,16 @@ msgstr "" msgid "Transmit" 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}" +msgstr "" + #: src/renderer/components/+cluster/cluster-issues.tsx:102 #: src/renderer/components/+config-secrets/secret-details.tsx:74 #: src/renderer/components/+config-secrets/secrets.tsx:45 @@ -2700,7 +2714,7 @@ msgid "listKind" msgstr "" #: 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" msgstr "" diff --git a/locales/ru/messages.po b/locales/ru/messages.po index adcbd19aed..0200d47b72 100644 --- a/locales/ru/messages.po +++ b/locales/ru/messages.po @@ -80,7 +80,7 @@ msgid "Account Name" msgstr "Название аккаунта" #: 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" msgstr "Активный" @@ -174,7 +174,7 @@ msgstr "Аффинитеты" #: 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-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-deployments/deployments.tsx:63 #: src/renderer/components/+workloads-jobs/jobs.tsx:41 @@ -676,7 +676,7 @@ msgstr "Создано" msgid "Credentials Ref" msgstr "Credentials Ref" -#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:40 +#: src/renderer/components/+workloads-cronjobs/cronjobs.tsx:44 msgid "Cron Jobs" msgstr "" @@ -1155,6 +1155,10 @@ msgstr "Список пуст" msgid "JSON Path" 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-cronjobs/cronjob-details.tsx:62 #: src/renderer/components/+workloads-jobs/jobs.tsx:36 @@ -1219,7 +1223,7 @@ msgid "Last Failure Time" msgstr "Время последнего сбоя" #: 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" msgstr "Последний запуск" @@ -1449,7 +1453,7 @@ msgstr "Установки" #: 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.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-deployments/deployments.tsx:58 #: src/renderer/components/+workloads-jobs/jobs.tsx:37 @@ -1499,7 +1503,7 @@ msgstr "" #: 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/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-deployments/deployments.tsx:59 #: src/renderer/components/+workloads-jobs/jobs.tsx:38 @@ -2156,7 +2160,7 @@ msgid "Scale Deployment <0>{deploymentName}" msgstr "Масштабировать Deployment <0>{deploymentName}" #: 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" msgstr "Расписание" @@ -2414,7 +2418,7 @@ msgid "Supplemental Groups" msgstr "" #: 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" msgstr "Заморозка" @@ -2483,6 +2487,16 @@ msgstr "Толерантности" msgid "Transmit" 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}" +msgstr "" + #: src/renderer/components/+cluster/cluster-issues.tsx:102 #: src/renderer/components/+config-secrets/secret-details.tsx:74 #: src/renderer/components/+config-secrets/secrets.tsx:45 @@ -2718,7 +2732,7 @@ msgid "listKind" msgstr "" #: 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" msgstr "" diff --git a/src/renderer/api/endpoints/cron-job.api.ts b/src/renderer/api/endpoints/cron-job.api.ts index e48a8636b8..887ec9833c 100644 --- a/src/renderer/api/endpoints/cron-job.api.ts +++ b/src/renderer/api/endpoints/cron-job.api.ts @@ -32,6 +32,12 @@ export class CronJob extends KubeObject { jobTemplate: { metadata: { creationTimestamp?: string; + labels?: { + [key: string]: string; + }; + annotations?: { + [key: string]: string; + }; }; spec: { template: { @@ -53,7 +59,7 @@ export class CronJob extends KubeObject { failedJobsHistoryLimit: number; } status: { - lastScheduleTime: string; + lastScheduleTime?: string; } getSuspendFlag() { @@ -61,6 +67,7 @@ export class CronJob extends KubeObject { } getLastScheduleTime() { + if (!this.status.lastScheduleTime) return "-" const diff = moment().diff(this.status.lastScheduleTime) return formatDuration(diff, true) } diff --git a/src/renderer/api/endpoints/job.api.ts b/src/renderer/api/endpoints/job.api.ts index d4657605f6..6c6c1967c1 100644 --- a/src/renderer/api/endpoints/job.api.ts +++ b/src/renderer/api/endpoints/job.api.ts @@ -13,7 +13,7 @@ export class Job extends WorkloadKubeObject { parallelism?: number; completions?: number; backoffLimit?: number; - selector: { + selector?: { matchLabels: { [name: string]: string; }; @@ -21,8 +21,11 @@ export class Job extends WorkloadKubeObject { template: { metadata: { creationTimestamp?: string; - labels: { - name: string; + labels?: { + [name: string]: string; + }; + annotations?: { + [name: string]: string; }; }; spec: { @@ -35,7 +38,7 @@ export class Job extends WorkloadKubeObject { nodeSelector?: { [selector: string]: string; }; - tolerations: { + tolerations?: { key: string; operator: string; effect: string; diff --git a/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.scss b/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.scss new file mode 100644 index 0000000000..ef52eebb08 --- /dev/null +++ b/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.scss @@ -0,0 +1,18 @@ +.CronJobTriggerDialog { + .Wizard { + .header { + span { + color: #a0a0a0; + white-space: nowrap; + text-overflow: ellipsis; + } + } + + .WizardStep { + .step-content { + min-height: 90px; + overflow: hidden; + } + } + } +} \ No newline at end of file diff --git a/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx b/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx new file mode 100644 index 0000000000..5075e2fe9c --- /dev/null +++ b/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx @@ -0,0 +1,128 @@ +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 { +} + +@observer +export class CronJobTriggerDialog extends Component { + @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 ( + <> +
+ Job name: +
+
+ this.jobName = v.toLowerCase()} + className="box grow" + /> +
+ + ) + } + + render() { + const { className, ...dialogProps } = this.props; + const cronjobName = this.cronjob ? this.cronjob.getName() : ""; + console.log(cronjobName); + const header = ( +
+ Trigger CronJob {cronjobName} +
+ ); + return ( + + + Trigger} + disabledNext={!this.ready} + > + {this.renderContents()} + + + + ); + } +} \ No newline at end of file diff --git a/src/renderer/components/+workloads-cronjobs/cronjob.store.ts b/src/renderer/components/+workloads-cronjobs/cronjob.store.ts index 9e75c25121..e8fa3719ae 100644 --- a/src/renderer/components/+workloads-cronjobs/cronjob.store.ts +++ b/src/renderer/components/+workloads-cronjobs/cronjob.store.ts @@ -9,13 +9,13 @@ export class CronJobStore extends KubeObjectStore { api = cronJobApi getStatuses(cronJobs?: CronJob[]) { - const status = { failed: 0, running: 0 } + const status = { suspended: 0, scheduled: 0 } cronJobs.forEach(cronJob => { if (cronJob.spec.suspend) { - status.failed++ + status.suspended++ } else { - status.running++ + status.scheduled++ } }) return status diff --git a/src/renderer/components/+workloads-cronjobs/cronjobs.tsx b/src/renderer/components/+workloads-cronjobs/cronjobs.tsx index 4221302116..32a2dc50dc 100644 --- a/src/renderer/components/+workloads-cronjobs/cronjobs.tsx +++ b/src/renderer/components/+workloads-cronjobs/cronjobs.tsx @@ -3,8 +3,10 @@ import "./cronjobs.scss"; import React from "react"; import { observer } from "mobx-react"; 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 { MenuItem } from "../menu"; +import { Icon } from "../icon"; import { cronJobStore } from "./cronjob.store"; import { jobStore } from "../+workloads-jobs/job.store"; import { eventStore } from "../+events/event.store"; @@ -12,7 +14,9 @@ import { KubeObjectMenu, KubeObjectMenuProps } from "../kube-object/kube-object- import { ICronJobsRouteParams } from "../+workloads"; import { KubeObjectListLayout } from "../kube-object"; import { KubeEventIcon } from "../+events/kube-event-icon"; +import { _i18n } from "../../i18n"; import { apiManager } from "../../api/api-manager"; +import { CronJobTriggerDialog } from "./cronjob-trigger-dialog"; enum sortBy { name = "name", @@ -79,8 +83,14 @@ export class CronJobs extends React.Component { } export function CronJobMenu(props: KubeObjectMenuProps) { + const { object, toolbar } = props; return ( - + + CronJobTriggerDialog.open(object)}> + + Trigger + + ) } diff --git a/src/renderer/components/+workloads-overview/overview-workload-status.scss b/src/renderer/components/+workloads-overview/overview-workload-status.scss index c06ed5b4c8..4d47570973 100644 --- a/src/renderer/components/+workloads-overview/overview-workload-status.scss +++ b/src/renderer/components/+workloads-overview/overview-workload-status.scss @@ -3,6 +3,8 @@ --workload-status-pending: #{$pod-status-pending-color}; --workload-status-evicted: #{$pod-status-evicted-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-terminated: #{$pod-status-terminated-color}; --workload-status-unknown: #{$pod-status-unknown-color}; diff --git a/src/renderer/components/+workloads/workloads-mixins.scss b/src/renderer/components/+workloads/workloads-mixins.scss index 84bcca8219..8ba7f76665 100644 --- a/src/renderer/components/+workloads/workloads-mixins.scss +++ b/src/renderer/components/+workloads/workloads-mixins.scss @@ -26,6 +26,10 @@ $deployment-replicafailure: $colorError; $job-complete: $colorSuccess; $job-failed: $colorError; +// Cronjob +$cronjob-scheduled: $colorSuccess; +$cronjob-suspended: $colorTerminated; + // Pod Statuses $pod-status-color-list: ( running: $pod-status-running-color, @@ -48,6 +52,12 @@ $job-condition-color-list: ( failed: $job-failed, ); +// Cronjob Conditions +$cronjob-condition-color-list: ( + scheduled: $cronjob-scheduled, + suspended: $cronjob-suspended, +); + @mixin pod-status-bgs { @each $status, $color in $pod-status-color-list { &.#{$status} { diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index abdef9b7f4..23e71e2be9 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -25,6 +25,7 @@ import { KubeObjectDetails } from "./kube-object/kube-object-details"; import { AddRoleBindingDialog } from "./+user-management-roles-bindings"; import { PodLogsDialog } from "./+workloads-pods/pod-logs-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 { crdRoute } from "./+custom-resources"; import { isAllowedResource } from "../../common/rbac"; @@ -80,6 +81,7 @@ export class App extends React.Component { + From b75cac45461a6380d098ca500985330bcceda681 Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Tue, 25 Aug 2020 11:08:46 +0300 Subject: [PATCH 2/7] Fix windows dev env (#722) Signed-off-by: Lauri Nevala Co-authored-by: Trevor Nichols --- Makefile | 14 +++++++++++--- package.json | 7 ++++--- yarn.lock | 9 ++++++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 86cdf24e95..068116b3ba 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,10 @@ compile-dev: yarn compile:renderer --cache dev: - test -f out/main.js || make init - yarn dev # run electron and watch files +ifeq ("$(wildcard static/build/main.js)","") + make init +endif + yarn dev lint: yarn lint @@ -53,6 +55,12 @@ else endif 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 dist/* - rm -rf out/* + rm -rf static/build/* +endif \ No newline at end of file diff --git a/package.json b/package.json index d4581123b0..72e1405b4d 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ }, "scripts": { "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:main": "env DEBUG=true yarn compile:main --watch $@", - "dev:renderer": "env DEBUG=true yarn compile:renderer --watch $@", + "dev-run": "cross-env DEBUG=true nodemon --watch static/build/main.js --exec \"electron --inspect .\"", + "dev:main": "cross-env DEBUG=true yarn compile:main --watch", + "dev:renderer": "cross-env DEBUG=true yarn compile:renderer --watch", "compile": "env NODE_ENV=production concurrently yarn:compile:*", "compile:main": "webpack --config webpack.main.ts", "compile:renderer": "webpack --config webpack.renderer.ts", @@ -264,6 +264,7 @@ "circular-dependency-plugin": "^5.2.0", "color": "^3.1.2", "concurrently": "^5.2.0", + "cross-env": "^7.0.2", "css-element-queries": "^1.2.3", "css-loader": "^3.5.3", "dompurify": "^2.0.11", diff --git a/yarn.lock b/yarn.lock index d8881d0f1d..d00e634c95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3997,6 +3997,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" 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: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -4016,7 +4023,7 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" 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" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== From a12aa8a04f12bd6b3df65f5dbd1d02f88171ad54 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 25 Aug 2020 09:42:30 -0400 Subject: [PATCH 3/7] add cluster icon migration code (#673) Signed-off-by: Sebastian Malton --- package.json | 2 + src/common/cluster-store.ts | 8 +- src/common/cluster-store_test.ts | 27 +++++-- src/migrations/cluster-store/2.7.0-beta.0.ts | 2 +- src/migrations/cluster-store/3.6.0-beta.1.ts | 71 +++++++++++++----- .../components/cluster-icon-setting.tsx | 2 +- test-data/cluster-store-migration-icon.png | Bin 0 -> 29988 bytes yarn.lock | 65 +++++++++++++++- 8 files changed, 143 insertions(+), 34 deletions(-) create mode 100644 test-data/cluster-store-migration-icon.png diff --git a/package.json b/package.json index 72e1405b4d..54e4038c77 100644 --- a/package.json +++ b/package.json @@ -175,6 +175,7 @@ "crypto-js": "^4.0.0", "electron-updater": "^4.3.1", "electron-window-state": "^5.0.3", + "file-type": "^14.7.1", "filenamify": "^4.1.0", "fs-extra": "^9.0.1", "handlebars": "^4.7.6", @@ -184,6 +185,7 @@ "jsonpath": "^1.0.2", "lodash": "^4.17.15", "mac-ca": "^1.0.4", + "make-synchronous": "^0.1.1", "marked": "^1.1.0", "md5-file": "^5.0.0", "mobx": "^5.15.5", diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index ad25f43f26..6e9930f5ad 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -1,6 +1,5 @@ import type { WorkspaceId } from "./workspace-store"; -import path from "path"; -import { app, ipcRenderer, remote } from "electron"; +import { ipcRenderer } from "electron"; import { unlink } from "fs-extra"; import { action, computed, observable, toJS } from "mobx"; import { BaseStore } from "./base-store"; @@ -50,11 +49,6 @@ export interface ClusterPreferences { } export class ClusterStore extends BaseStore { - static get iconsDir() { - // TODO: remove remote cheat - return path.join((app || remote.app).getPath("userData"), "icons"); - } - private constructor() { super({ configName: "lens-cluster-store", diff --git a/src/common/cluster-store_test.ts b/src/common/cluster-store_test.ts index 3fc3e0b60e..6e5b99dbaf 100644 --- a/src/common/cluster-store_test.ts +++ b/src/common/cluster-store_test.ts @@ -6,6 +6,10 @@ import { ClusterStore } from "./cluster-store"; import { workspaceStore } from "./workspace-store"; import { saveConfigToAppFiles } from "./kube-helpers"; +const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png") + +console.log("") // fix bug + let clusterStore: ClusterStore; describe("empty config", () => { @@ -236,12 +240,13 @@ describe("pre 2.6.0 config with a cluster icon", () => { }, cluster1: { kubeConfig: "foo", - icon: "icon path", + icon: "icon_path", preferences: { terminalCWD: "/tmp" } }, - }) + }), + "icon_path": testDataIcon, } } mockFs(mockOpts); @@ -257,7 +262,7 @@ describe("pre 2.6.0 config with a cluster icon", () => { const storedClusterData = clusterStore.clustersList[0]; expect(storedClusterData.hasOwnProperty('icon')).toBe(false); 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); }) }) @@ -274,7 +279,6 @@ describe("for a pre 2.7.0-beta.0 config without a workspace", () => { }, cluster1: { kubeConfig: "foo", - icon: "icon path", preferences: { terminalCWD: "/tmp" } @@ -305,16 +309,20 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => { 'lens-cluster-store.json': JSON.stringify({ __internal__: { migrations: { - version: "2.7.0" + version: "3.5.0" } }, clusters: [ { id: 'cluster1', - kubeConfig: 'kubeconfig content' + kubeConfig: 'kubeconfig content', + preferences: { + icon: "store://icon_path", + } } ] - }) + }), + "icon_path": testDataIcon, } }; mockFs(mockOpts); @@ -330,4 +338,9 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => { const config = clusterStore.clustersList[0].kubeConfigPath; 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); + }) }) \ No newline at end of file diff --git a/src/migrations/cluster-store/2.7.0-beta.0.ts b/src/migrations/cluster-store/2.7.0-beta.0.ts index 22c4e6bba9..3e0ae9337f 100644 --- a/src/migrations/cluster-store/2.7.0-beta.0.ts +++ b/src/migrations/cluster-store/2.7.0-beta.0.ts @@ -6,7 +6,7 @@ export default migration({ run(store, log) { for (const value of store) { const clusterKey = value[0]; - if(clusterKey === "__internal__") continue + if (clusterKey === "__internal__") continue const cluster = value[1]; cluster.workspace = "default" store.set(clusterKey, cluster) diff --git a/src/migrations/cluster-store/3.6.0-beta.1.ts b/src/migrations/cluster-store/3.6.0-beta.1.ts index adc354d9ef..db39cb741d 100644 --- a/src/migrations/cluster-store/3.6.0-beta.1.ts +++ b/src/migrations/cluster-store/3.6.0-beta.1.ts @@ -1,34 +1,71 @@ // 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 { ensureDirSync } from "fs-extra" +import fse from "fs-extra" import { ClusterModel } from "../../common/cluster-store"; import { loadConfig, saveConfigToAppFiles } 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({ version: "3.6.0-beta.1", run(store, printLog) { - const migratedClusters: ClusterModel[] = [] - const storedClusters: ClusterModel[] = store.get("clusters"); - const kubeConfigBase = path.join((app || remote.app).getPath("userData"), "kubeconfigs") + const userDataPath = (app || remote.app).getPath("userData") + const kubeConfigBase = path.join(userDataPath, "kubeconfigs") + const storedClusters: ClusterModel[] = store.get("clusters") || []; - if (!storedClusters) return; - ensureDirSync(kubeConfigBase); + if (!storedClusters.length) return; + fse.ensureDirSync(kubeConfigBase); printLog("Number of clusters to migrate: ", storedClusters.length) - for (const cluster of storedClusters) { - try { - // take the embedded kubeconfig and dump it into a file - cluster.kubeConfigPath = saveConfigToAppFiles(cluster.id, cluster.kubeConfig) - cluster.contextName = loadConfig(cluster.kubeConfigPath).getCurrentContext(); - delete cluster.kubeConfig; - migratedClusters.push(cluster) - } catch (error) { - printLog(`Failed to migrate Kubeconfig for cluster "${cluster.id}"`, error) - } - } + const migratedClusters = storedClusters + .map(cluster => { + /** + * migrate kubeconfig + */ + try { + // take the embedded kubeconfig and dump it into a file + cluster.kubeConfigPath = saveConfigToAppFiles(cluster.id, cluster.kubeConfig) + 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 if (migratedClusters.length > 0) { diff --git a/src/renderer/components/+cluster-settings/components/cluster-icon-setting.tsx b/src/renderer/components/+cluster-settings/components/cluster-icon-setting.tsx index 20b4e6d6d5..04b7ef1fc6 100644 --- a/src/renderer/components/+cluster-settings/components/cluster-icon-setting.tsx +++ b/src/renderer/components/+cluster-settings/components/cluster-icon-setting.tsx @@ -28,7 +28,7 @@ export class ClusterIconSetting extends React.Component { try { if (file) { 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 { // this has to be done as a seperate branch (and not always) because `cluster` // is observable and triggers an update loop. diff --git a/test-data/cluster-store-migration-icon.png b/test-data/cluster-store-migration-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..623a0fea775940b03fdad9cdee23d8734251d2fa GIT binary patch literal 29988 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 literal 0 HcmV?d00001 diff --git a/yarn.lock b/yarn.lock index d00e634c95..9e75e68c2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1588,6 +1588,11 @@ dependencies: 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@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -5196,6 +5201,16 @@ file-loader@^6.0.0: loader-utils "^2.0.0" 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: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -6074,7 +6089,7 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.4: +ieee754@^1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== @@ -7632,6 +7647,14 @@ make-plural@^6.2.1: resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-6.2.1.tgz#2790af1d05fb2fc35a111ce759ffdb0aca1339a3" 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: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -8943,6 +8966,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" 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: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -9536,6 +9564,11 @@ readable-stream@^3.1.1, readable-stream@^3.6.0: string_decoder "^1.1.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: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -10655,6 +10688,15 @@ strip-outer@^1.0.1: dependencies: 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: version "1.2.1" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.2.1.tgz#c5cbbfbf1170d076cfdd86e0109c5bba114baa1a" @@ -10663,6 +10705,14 @@ style-loader@^1.2.1: loader-utils "^2.0.0" 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: version "3.0.1" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" @@ -10984,6 +11034,14 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" 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: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -11157,6 +11215,11 @@ type-fest@^0.13.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" 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: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" From 886fba136ac24d4d7ba831539954076e8d9fe149 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 25 Aug 2020 20:09:00 +0300 Subject: [PATCH 4/7] Fixing minor light theme issues (#744) * Cluster Menu scrolling & tooltip fixes Signed-off-by: Alex Andreev * Removing forgotten console.log() Signed-off-by: Alex Andreev * Fix Landing Page in light theme Signed-off-by: Alex Andreev * Updating Chart on every componentDidUpdate Signed-off-by: Alex Andreev --- .../+landing-page/landing-page.scss | 24 ++++++++++++++--- .../cronjob-trigger-dialog.tsx | 1 - src/renderer/components/chart/chart.tsx | 6 ++--- .../cluster-manager/clusters-menu.scss | 17 +++++++++--- .../cluster-manager/clusters-menu.tsx | 26 ++++++++++--------- 5 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/renderer/components/+landing-page/landing-page.scss b/src/renderer/components/+landing-page/landing-page.scss index 6cc726f5d9..0d05c418d8 100644 --- a/src/renderer/components/+landing-page/landing-page.scss +++ b/src/renderer/components/+landing-page/landing-page.scss @@ -1,8 +1,24 @@ .LandingPage { 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; + 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; + } + } } \ No newline at end of file diff --git a/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx b/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx index 5075e2fe9c..7f29c0478d 100644 --- a/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx +++ b/src/renderer/components/+workloads-cronjobs/cronjob-trigger-dialog.tsx @@ -97,7 +97,6 @@ export class CronJobTriggerDialog extends Component { render() { const { className, ...dialogProps } = this.props; const cronjobName = this.cronjob ? this.cronjob.getName() : ""; - console.log(cronjobName); const header = (
Trigger CronJob {cronjobName} diff --git a/src/renderer/components/chart/chart.tsx b/src/renderer/components/chart/chart.tsx index 92e3619031..a5fa4b9706 100644 --- a/src/renderer/components/chart/chart.tsx +++ b/src/renderer/components/chart/chart.tsx @@ -63,14 +63,14 @@ export class Chart extends React.Component { this.renderChart() } - componentDidUpdate(prevProps: ChartProps) { - const { data, showChart, redraw } = this.props + componentDidUpdate() { + const { showChart, redraw } = this.props if (redraw) { this.chart.destroy() this.renderChart() return } - if (!isEqual(prevProps.data, data) && showChart) { + if (showChart) { if (!this.chart) this.renderChart() else this.updateChart() } diff --git a/src/renderer/components/cluster-manager/clusters-menu.scss b/src/renderer/components/cluster-manager/clusters-menu.scss index 0ee1f59d7e..9b412e5035 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.scss +++ b/src/renderer/components/cluster-manager/clusters-menu.scss @@ -1,5 +1,4 @@ .ClustersMenu { - @include hidden-scrollbar; $spacing: $padding * 2; position: relative; @@ -23,8 +22,7 @@ padding: $spacing; width: 320px; background: $bgc; - z-index: 100; - color: white; + color: $textColorAccent; filter: drop-shadow(0 0px 2px #ffffff33); pointer-events: none; @@ -38,6 +36,19 @@ border-right: $arrowSize solid $bgc; right: 100%; } + + .theme-light & { + filter: drop-shadow(0 0px 2px #777); + background: white; + + &:before { + border-right-color: white; + } + } + } + + .clusters { + @include hidden-scrollbar; } > .add-cluster { diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index 23d85507c3..d4fb4cefb1 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -110,18 +110,20 @@ export class ClustersMenu extends React.Component {

)} - {clusters.map(cluster => { - return ( - this.showCluster(cluster.id)} - onContextMenu={() => this.showContextMenu(cluster)} - /> - ) - })} +
+ {clusters.map(cluster => { + return ( + this.showCluster(cluster.id)} + onContextMenu={() => this.showContextMenu(cluster)} + /> + ) + })} +
Add Cluster From 510d3d476032116cc430c47e355c82de5ab481b2 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Wed, 26 Aug 2020 11:32:15 +0300 Subject: [PATCH 5/7] Thinner bottom bar (#747) * Making bottom bar slightly thinner Signed-off-by: Alex Andreev * Moving workspace selector to left Signed-off-by: Alex Andreev --- src/renderer/components/cluster-manager/bottom-bar.scss | 2 +- src/renderer/components/cluster-manager/bottom-bar.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/cluster-manager/bottom-bar.scss b/src/renderer/components/cluster-manager/bottom-bar.scss index 974585723a..359c123836 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.scss +++ b/src/renderer/components/cluster-manager/bottom-bar.scss @@ -3,7 +3,7 @@ font-size: $font-size-small; background-color: #3d90ce; - padding: $spacing $padding; + padding: $padding / 4 $padding; color: white; #current-workspace { diff --git a/src/renderer/components/cluster-manager/bottom-bar.tsx b/src/renderer/components/cluster-manager/bottom-bar.tsx index 14809618f5..abbe81ec92 100644 --- a/src/renderer/components/cluster-manager/bottom-bar.tsx +++ b/src/renderer/components/cluster-manager/bottom-bar.tsx @@ -11,7 +11,7 @@ export class BottomBar extends React.Component { const { currentWorkspace } = workspaceStore; return (
-
+
{currentWorkspace.name}
From c31004e6776360524259876865214463e15c1348 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 27 Aug 2020 08:42:18 +0300 Subject: [PATCH 6/7] Fixing cluster icon badge overflow (#749) Signed-off-by: Alex Andreev --- src/renderer/components/cluster-icon/cluster-icon.scss | 2 +- src/renderer/components/cluster-manager/clusters-menu.scss | 4 ++-- src/renderer/components/cluster-manager/clusters-menu.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/cluster-icon/cluster-icon.scss b/src/renderer/components/cluster-icon/cluster-icon.scss index b8a87a07de..540cecf9eb 100644 --- a/src/renderer/components/cluster-icon/cluster-icon.scss +++ b/src/renderer/components/cluster-icon/cluster-icon.scss @@ -30,7 +30,7 @@ position: absolute; right: 0; bottom: 0; - margin: -$padding * 1.5; + margin: -$padding; font-size: $font-size-small; background: $colorError; color: white; diff --git a/src/renderer/components/cluster-manager/clusters-menu.scss b/src/renderer/components/cluster-manager/clusters-menu.scss index 9b412e5035..5e5f08664d 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.scss +++ b/src/renderer/components/cluster-manager/clusters-menu.scss @@ -3,9 +3,9 @@ position: relative; text-align: center; - padding: $spacing; background: $clusterMenuBackground; border-right: 1px solid $clusterMenuBorderColor; + padding-bottom: $spacing; .is-mac & { padding-top: $spacing * 2; @@ -49,11 +49,11 @@ .clusters { @include hidden-scrollbar; + padding: 0 $spacing $spacing; } > .add-cluster { position: relative; - margin-top: $padding; min-width: 43px; .Icon { diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index d4fb4cefb1..9be42facc8 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -97,7 +97,7 @@ export class ClustersMenu extends React.Component { const showStartupHint = this.showHint && isLanding && noClustersInScope; return (
this.showHint = false} > {showStartupHint && ( From 659d8bd0ab14f3bec2950a7872ed708ce4731cde Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Thu, 27 Aug 2020 08:43:17 +0300 Subject: [PATCH 7/7] Wait until cluster is ready before initializing cluster dashboard (#748) * Wait until cluster is ready before initializing cluster dashboard Signed-off-by: Lauri Nevala --- integration/specs/app_spec.ts | 11 ++++------- src/main/cluster.ts | 6 ++++++ src/renderer/components/cluster-manager/lens-views.ts | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/integration/specs/app_spec.ts b/integration/specs/app_spec.ts index f3cb922740..9c9a7361fb 100644 --- a/integration/specs/app_spec.ts +++ b/integration/specs/app_spec.ts @@ -26,11 +26,8 @@ describe("app start", () => { const waitForMinikubeDashboard = async (app: Application) => { await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started") let windowCount = await app.client.getWindowCount() - // wait for webview to appear on window count - while (windowCount == 1) { - windowCount = await app.client.getWindowCount() - } - await app.client.windowByIndex(windowCount - 1) + await app.client.waitForExist(`iframe[name="minikube"]`) + await app.client.frame("minikube") await app.client.waitUntilTextExists("span.link-text", "Cluster") } @@ -39,10 +36,10 @@ describe("app start", () => { await app.start() await app.client.waitUntilWindowLoaded() let windowCount = await app.client.getWindowCount() - while (windowCount > 1) { + while (windowCount > 1) { // Wait for splash screen to be closed windowCount = await app.client.getWindowCount() } - await app.client.windowByIndex(windowCount - 1) + await app.client.windowByIndex(0) await app.client.waitUntilWindowLoaded() }, 20000) diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 6ba0fb1f48..5176846ba4 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -27,6 +27,7 @@ export interface ClusterState extends ClusterModel { online: boolean; disconnected: boolean; accessible: boolean; + ready: boolean; failureReason: string; nodes: number; eventCount: number; @@ -47,6 +48,7 @@ export class Cluster implements ClusterModel { protected eventDisposers: Function[] = []; whenInitialized = when(() => this.initialized); + whenReady = when(() => this.ready); @observable initialized = false; @observable contextName: string; @@ -56,6 +58,7 @@ export class Cluster implements ClusterModel { @observable kubeProxyUrl: string; // lens-proxy to kube-api url @observable online: boolean; @observable accessible: boolean; + @observable ready: boolean; @observable disconnected: boolean; @observable failureReason: string; @observable nodes = 0; @@ -149,6 +152,7 @@ export class Cluster implements ClusterModel { this.disconnected = true; this.online = false; this.accessible = false; + this.ready = false; this.pushState(); } @@ -172,6 +176,7 @@ export class Cluster implements ClusterModel { this.refreshEvents(), this.refreshAllowedResources(), ]); + this.ready = true } } @@ -370,6 +375,7 @@ export class Cluster implements ClusterModel { initialized: this.initialized, apiUrl: this.apiUrl, online: this.online, + ready: this.ready, disconnected: this.disconnected, accessible: this.accessible, failureReason: this.failureReason, diff --git a/src/renderer/components/cluster-manager/lens-views.ts b/src/renderer/components/cluster-manager/lens-views.ts index 67a0ac68e2..25cb2d5f3a 100644 --- a/src/renderer/components/cluster-manager/lens-views.ts +++ b/src/renderer/components/cluster-manager/lens-views.ts @@ -21,10 +21,10 @@ export async function initView(clusterId: ClusterId) { } logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`) const cluster = clusterStore.getById(clusterId); - await cluster.whenInitialized; + await cluster.whenReady; const parentElem = document.getElementById("lens-views"); const iframe = document.createElement("iframe"); - iframe.name = cluster.preferences.clusterName; + iframe.name = cluster.contextName; iframe.setAttribute("src", `//${clusterId}.${location.host}`) iframe.addEventListener("load", async () => { logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`)