From 64be4ee948344c427cd23aecd8c3ef144063def7 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 21 Jan 2021 10:38:49 +0300 Subject: [PATCH 1/5] Fixing log tab layout colors (#1995) * Making "since" date visible as bolded text Signed-off-by: Alex Andreev * Fixing colors in log tab elements Signed-off-by: Alex Andreev --- src/renderer/components/dock/info-panel.scss | 14 +++++++++----- src/renderer/components/dock/log-controls.tsx | 7 ++++++- src/renderer/components/select/select.scss | 5 +---- src/renderer/themes/lens-dark.json | 2 ++ src/renderer/themes/lens-light.json | 4 +++- src/renderer/themes/theme-vars.scss | 4 +++- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/renderer/components/dock/info-panel.scss b/src/renderer/components/dock/info-panel.scss index 482dbee02d..cf9b3268b2 100644 --- a/src/renderer/components/dock/info-panel.scss +++ b/src/renderer/components/dock/info-panel.scss @@ -1,12 +1,16 @@ .InfoPanel { @include hidden-scrollbar; - background: $dockInfoBackground; - padding: $padding $padding * 2; + background: var(--dockInfoBackground); + padding: var(--padding) calc(var(--padding) * 2); flex-shrink: 0; .Spinner { - margin-right: $padding; + margin-right: var(--padding); + } + + .Badge { + background-color: var(--dockBadgeBackground); } > .controls { @@ -15,8 +19,8 @@ &:not(:empty) + .info { min-height: 25px; - padding-left: $padding; - padding-right: $padding; + padding-left: var(--padding); + padding-right: var(--padding); } } } \ No newline at end of file diff --git a/src/renderer/components/dock/log-controls.tsx b/src/renderer/components/dock/log-controls.tsx index cedff7fbb9..06bbf12863 100644 --- a/src/renderer/components/dock/log-controls.tsx +++ b/src/renderer/components/dock/log-controls.tsx @@ -41,7 +41,12 @@ export const LogControls = observer((props: Props) => { return (
- {since && `Logs from ${new Date(since[0]).toLocaleString()}`} + {since && ( + + Logs from{" "} + {new Date(since[0]).toLocaleString()} + + )}
Date: Thu, 21 Jan 2021 08:09:41 -0500 Subject: [PATCH 2/5] enfore unix line endings and always ending files with line endings (#1997) Signed-off-by: Sebastian Malton --- .eslintrc.js | 6 ++++++ __mocks__/imageMock.ts | 2 +- __mocks__/styleMock.ts | 2 +- extensions/kube-object-event-status/src/resolver.tsx | 2 +- src/common/__tests__/search-store.test.ts | 2 +- src/common/__tests__/user-store.test.ts | 2 +- src/common/custom-errors.ts | 2 +- src/common/kube-helpers.ts | 2 +- src/common/prometheus-providers.ts | 2 +- src/common/search-store.ts | 2 +- src/common/utils/__tests__/splitArray.test.ts | 2 +- src/common/utils/singleton.ts | 2 +- src/extensions/interfaces/index.ts | 2 +- src/extensions/interfaces/registrations.ts | 2 +- src/extensions/renderer-api/kube-object-status.ts | 2 +- src/extensions/renderer-api/theming.ts | 2 +- src/main/cluster-detectors/base-cluster-detector.ts | 2 +- src/main/cluster-detectors/cluster-id-detector.ts | 2 +- src/main/cluster-detectors/detector-registry.ts | 2 +- src/main/cluster-detectors/last-seen-detector.ts | 2 +- src/main/cluster-detectors/nodes-count-detector.ts | 2 +- src/main/cluster-detectors/version-detector.ts | 2 +- src/migrations/cluster-store/index.ts | 2 +- src/renderer/api/__tests__/kube-api.test.ts | 2 +- src/renderer/api/workload-kube-object.ts | 2 +- .../components/+apps-helm-charts/helm-charts.route.ts | 2 +- src/renderer/components/+apps-helm-charts/index.ts | 2 +- src/renderer/components/+apps-releases/index.ts | 2 +- src/renderer/components/+apps/index.ts | 2 +- .../components/cluster-home-dir-setting.tsx | 2 +- .../+cluster-settings/components/cluster-name-setting.tsx | 2 +- .../+cluster-settings/components/cluster-proxy-setting.tsx | 2 +- src/renderer/components/+cluster-settings/general.tsx | 2 +- src/renderer/components/+cluster-settings/removal.tsx | 2 +- src/renderer/components/+cluster-settings/status.tsx | 2 +- src/renderer/components/+cluster/cluster-metrics.tsx | 2 +- src/renderer/components/+landing-page/index.tsx | 2 +- src/renderer/components/+pod-security-policies/index.ts | 2 +- .../components/+user-management-roles/roles.store.ts | 2 +- .../components/+user-management-service-accounts/index.ts | 2 +- src/renderer/components/+user-management/index.ts | 2 +- src/renderer/components/+workloads-pods/index.ts | 2 +- .../components/+workloads-pods/pod-details-statuses.tsx | 2 +- src/renderer/components/+workloads-statefulsets/index.ts | 2 +- src/renderer/components/ace-editor/ace-editor.tsx | 2 +- src/renderer/components/ace-editor/index.ts | 2 +- src/renderer/components/add-remove-buttons/index.ts | 2 +- src/renderer/components/animate/index.ts | 2 +- src/renderer/components/chart/background-block.plugin.ts | 2 +- src/renderer/components/chart/bar-chart.tsx | 2 +- src/renderer/components/chart/chart.tsx | 2 +- src/renderer/components/chart/index.ts | 2 +- src/renderer/components/chart/pie-chart.tsx | 2 +- src/renderer/components/chart/useRealTimeMetrics.ts | 2 +- src/renderer/components/chart/zebra-stripes.plugin.ts | 2 +- src/renderer/components/checkbox/checkbox.tsx | 2 +- src/renderer/components/checkbox/index.ts | 2 +- src/renderer/components/cluster-icon/index.ts | 2 +- src/renderer/components/confirm-dialog/index.ts | 2 +- src/renderer/components/dock/dock-tabs.tsx | 2 +- src/renderer/components/editable-list/index.ts | 2 +- src/renderer/components/error-boundary/index.ts | 2 +- src/renderer/components/file-picker/file-picker.tsx | 2 +- src/renderer/components/file-picker/index.ts | 2 +- src/renderer/components/icon/index.ts | 2 +- src/renderer/components/input/search-input-url.tsx | 2 +- src/renderer/components/item-object-list/index.tsx | 2 +- src/renderer/components/kube-object-status-icon/index.ts | 2 +- src/renderer/components/kubeconfig-dialog/index.ts | 2 +- .../components/layout/__test__/main-layout-header.test.tsx | 2 +- src/renderer/components/layout/login-layout.tsx | 2 +- src/renderer/components/layout/sidebar-context.ts | 2 +- src/renderer/components/line-progress/index.ts | 2 +- src/renderer/components/markdown-viewer/index.ts | 2 +- src/renderer/components/markdown-viewer/markdown-viewer.tsx | 2 +- src/renderer/components/radio/index.ts | 2 +- src/renderer/components/resource-metrics/index.ts | 2 +- src/renderer/components/slider/index.ts | 2 +- src/renderer/components/status-brick/index.ts | 2 +- src/renderer/components/status-brick/status-brick.tsx | 2 +- src/renderer/components/virtual-list/index.ts | 2 +- src/renderer/components/wizard/index.ts | 2 +- src/renderer/hooks/useInterval.ts | 2 +- src/renderer/hooks/useOnUnmount.ts | 2 +- src/renderer/hooks/useStorage.ts | 2 +- src/renderer/navigation/events.ts | 2 +- src/renderer/navigation/helpers.ts | 2 +- src/renderer/navigation/index.ts | 2 +- src/renderer/utils/__tests__/metricUnitsToNumber.test.ts | 2 +- src/renderer/utils/formatDuration.ts | 2 +- src/renderer/utils/jsonPath.ts | 2 +- types/command-exists.d.ts | 2 +- 92 files changed, 97 insertions(+), 91 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3fd52c2465..57ee07348f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -46,6 +46,8 @@ module.exports = { "avoidEscape": true, "allowTemplateLiterals": true, }], + "linebreak-style": ["error", "unix"], + "eol-last": ["error", "always"], "semi": ["error", "always"], "object-shorthand": "error", "prefer-template": "error", @@ -101,6 +103,8 @@ module.exports = { }], "semi": "off", "@typescript-eslint/semi": ["error"], + "linebreak-style": ["error", "unix"], + "eol-last": ["error", "always"], "object-shorthand": "error", "prefer-template": "error", "template-curly-spacing": "error", @@ -162,6 +166,8 @@ module.exports = { }], "semi": "off", "@typescript-eslint/semi": ["error"], + "linebreak-style": ["error", "unix"], + "eol-last": ["error", "always"], "object-shorthand": "error", "prefer-template": "error", "template-curly-spacing": "error", diff --git a/__mocks__/imageMock.ts b/__mocks__/imageMock.ts index a099545376..f053ebf797 100644 --- a/__mocks__/imageMock.ts +++ b/__mocks__/imageMock.ts @@ -1 +1 @@ -module.exports = {}; \ No newline at end of file +module.exports = {}; diff --git a/__mocks__/styleMock.ts b/__mocks__/styleMock.ts index a099545376..f053ebf797 100644 --- a/__mocks__/styleMock.ts +++ b/__mocks__/styleMock.ts @@ -1 +1 @@ -module.exports = {}; \ No newline at end of file +module.exports = {}; diff --git a/extensions/kube-object-event-status/src/resolver.tsx b/extensions/kube-object-event-status/src/resolver.tsx index 69691c2e79..5e9151288f 100644 --- a/extensions/kube-object-event-status/src/resolver.tsx +++ b/extensions/kube-object-event-status/src/resolver.tsx @@ -56,4 +56,4 @@ export function resolveStatusForCronJobs(cronJob: K8sApi.CronJob): K8sApi.KubeOb text: `${event.message}`, timestamp: event.metadata.creationTimestamp }; -} \ No newline at end of file +} diff --git a/src/common/__tests__/search-store.test.ts b/src/common/__tests__/search-store.test.ts index 7939ef1d8c..d361c858fd 100644 --- a/src/common/__tests__/search-store.test.ts +++ b/src/common/__tests__/search-store.test.ts @@ -77,4 +77,4 @@ describe("search store tests", () => { searchStore.onSearch(logs, "Starting"); expect(searchStore.totalFinds).toBe(2); }); -}); \ No newline at end of file +}); diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index 08ca359ce5..b74941a790 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -101,4 +101,4 @@ describe("user store tests", () => { expect(us.lastSeenAppVersion).toBe("0.0.0"); }); }); -}); \ No newline at end of file +}); diff --git a/src/common/custom-errors.ts b/src/common/custom-errors.ts index 9bcf3a998a..177ef7578f 100644 --- a/src/common/custom-errors.ts +++ b/src/common/custom-errors.ts @@ -10,4 +10,4 @@ export class ExecValidationNotFoundError extends Error { this.name = this.constructor.name; Error.captureStackTrace(this, this.constructor); } -} \ No newline at end of file +} diff --git a/src/common/kube-helpers.ts b/src/common/kube-helpers.ts index bb0e6b86d2..02a9faef92 100644 --- a/src/common/kube-helpers.ts +++ b/src/common/kube-helpers.ts @@ -175,4 +175,4 @@ export function validateKubeConfig (config: KubeConfig) { throw new ExecValidationNotFoundError(execCommand, isAbsolute); } } -} \ No newline at end of file +} diff --git a/src/common/prometheus-providers.ts b/src/common/prometheus-providers.ts index a5c515b338..5496163c38 100644 --- a/src/common/prometheus-providers.ts +++ b/src/common/prometheus-providers.ts @@ -10,4 +10,4 @@ import { PrometheusProviderRegistry } from "../main/prometheus/provider-registry PrometheusProviderRegistry.registerProvider(provider.id, provider); }); -export const prometheusProviders = PrometheusProviderRegistry.getProviders(); \ No newline at end of file +export const prometheusProviders = PrometheusProviderRegistry.getProviders(); diff --git a/src/common/search-store.ts b/src/common/search-store.ts index a3aba9dcbe..eb2517ca0f 100644 --- a/src/common/search-store.ts +++ b/src/common/search-store.ts @@ -133,4 +133,4 @@ export class SearchStore { } } -export const searchStore = new SearchStore; \ No newline at end of file +export const searchStore = new SearchStore; diff --git a/src/common/utils/__tests__/splitArray.test.ts b/src/common/utils/__tests__/splitArray.test.ts index a401e07701..1e1589fee2 100644 --- a/src/common/utils/__tests__/splitArray.test.ts +++ b/src/common/utils/__tests__/splitArray.test.ts @@ -28,4 +28,4 @@ describe("split array on element tests", () => { test("ten elements, in end array", () => { expect(splitArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 9)).toStrictEqual([[0, 1, 2, 3, 4, 5, 6, 7, 8], [], true]); }); -}); \ No newline at end of file +}); diff --git a/src/common/utils/singleton.ts b/src/common/utils/singleton.ts index 61269d10b1..caa5471072 100644 --- a/src/common/utils/singleton.ts +++ b/src/common/utils/singleton.ts @@ -26,4 +26,4 @@ class Singleton { } export { Singleton }; -export default Singleton; \ No newline at end of file +export default Singleton; diff --git a/src/extensions/interfaces/index.ts b/src/extensions/interfaces/index.ts index c91d8cdd19..7b1c601537 100644 --- a/src/extensions/interfaces/index.ts +++ b/src/extensions/interfaces/index.ts @@ -1 +1 @@ -export * from "./registrations"; \ No newline at end of file +export * from "./registrations"; diff --git a/src/extensions/interfaces/registrations.ts b/src/extensions/interfaces/registrations.ts index 47c63062ea..ff51d9a824 100644 --- a/src/extensions/interfaces/registrations.ts +++ b/src/extensions/interfaces/registrations.ts @@ -5,4 +5,4 @@ export type { KubeObjectMenuRegistration, KubeObjectMenuComponents } from "../re export type { KubeObjectStatusRegistration } from "../registries/kube-object-status-registry"; export type { PageRegistration, RegisteredPage, PageParams, PageComponentProps, PageComponents, PageTarget } from "../registries/page-registry"; export type { PageMenuRegistration, ClusterPageMenuRegistration, PageMenuComponents } from "../registries/page-menu-registry"; -export type { StatusBarRegistration } from "../registries/status-bar-registry"; \ No newline at end of file +export type { StatusBarRegistration } from "../registries/status-bar-registry"; diff --git a/src/extensions/renderer-api/kube-object-status.ts b/src/extensions/renderer-api/kube-object-status.ts index f609d736fe..616ead1bb2 100644 --- a/src/extensions/renderer-api/kube-object-status.ts +++ b/src/extensions/renderer-api/kube-object-status.ts @@ -8,4 +8,4 @@ export enum KubeObjectStatusLevel { INFO = 1, WARNING = 2, CRITICAL = 3 -} \ No newline at end of file +} diff --git a/src/extensions/renderer-api/theming.ts b/src/extensions/renderer-api/theming.ts index f819036803..b3da69bdbc 100644 --- a/src/extensions/renderer-api/theming.ts +++ b/src/extensions/renderer-api/theming.ts @@ -2,4 +2,4 @@ import { themeStore } from "../../renderer/theme.store"; export function getActiveTheme() { return themeStore.activeTheme; -} \ No newline at end of file +} diff --git a/src/main/cluster-detectors/base-cluster-detector.ts b/src/main/cluster-detectors/base-cluster-detector.ts index 9d52e1a70e..885f96c33e 100644 --- a/src/main/cluster-detectors/base-cluster-detector.ts +++ b/src/main/cluster-detectors/base-cluster-detector.ts @@ -31,4 +31,4 @@ export class BaseClusterDetector { }, }); } -} \ No newline at end of file +} diff --git a/src/main/cluster-detectors/cluster-id-detector.ts b/src/main/cluster-detectors/cluster-id-detector.ts index 2e0cc694ff..810955afae 100644 --- a/src/main/cluster-detectors/cluster-id-detector.ts +++ b/src/main/cluster-detectors/cluster-id-detector.ts @@ -23,4 +23,4 @@ export class ClusterIdDetector extends BaseClusterDetector { return response.metadata.uid; } -} \ No newline at end of file +} diff --git a/src/main/cluster-detectors/detector-registry.ts b/src/main/cluster-detectors/detector-registry.ts index 43c56153c9..b1d1b73447 100644 --- a/src/main/cluster-detectors/detector-registry.ts +++ b/src/main/cluster-detectors/detector-registry.ts @@ -48,4 +48,4 @@ detectorRegistry.add(ClusterIdDetector); detectorRegistry.add(LastSeenDetector); detectorRegistry.add(VersionDetector); detectorRegistry.add(DistributionDetector); -detectorRegistry.add(NodesCountDetector); \ No newline at end of file +detectorRegistry.add(NodesCountDetector); diff --git a/src/main/cluster-detectors/last-seen-detector.ts b/src/main/cluster-detectors/last-seen-detector.ts index e648d5f2f9..0a9bcf9f74 100644 --- a/src/main/cluster-detectors/last-seen-detector.ts +++ b/src/main/cluster-detectors/last-seen-detector.ts @@ -11,4 +11,4 @@ export class LastSeenDetector extends BaseClusterDetector { return { value: new Date().toJSON(), accuracy: 100 }; } -} \ No newline at end of file +} diff --git a/src/main/cluster-detectors/nodes-count-detector.ts b/src/main/cluster-detectors/nodes-count-detector.ts index 0ece5dd080..45584df5bd 100644 --- a/src/main/cluster-detectors/nodes-count-detector.ts +++ b/src/main/cluster-detectors/nodes-count-detector.ts @@ -16,4 +16,4 @@ export class NodesCountDetector extends BaseClusterDetector { return response.items.length; } -} \ No newline at end of file +} diff --git a/src/main/cluster-detectors/version-detector.ts b/src/main/cluster-detectors/version-detector.ts index 8080ef57a1..b19979db8a 100644 --- a/src/main/cluster-detectors/version-detector.ts +++ b/src/main/cluster-detectors/version-detector.ts @@ -16,4 +16,4 @@ export class VersionDetector extends BaseClusterDetector { return response.gitVersion; } -} \ No newline at end of file +} diff --git a/src/migrations/cluster-store/index.ts b/src/migrations/cluster-store/index.ts index c546fdaeda..4a71d4f7ad 100644 --- a/src/migrations/cluster-store/index.ts +++ b/src/migrations/cluster-store/index.ts @@ -18,4 +18,4 @@ export default { ...version270Beta1, ...version360Beta1, ...snap -}; \ No newline at end of file +}; diff --git a/src/renderer/api/__tests__/kube-api.test.ts b/src/renderer/api/__tests__/kube-api.test.ts index 41078e77a3..7481bd096a 100644 --- a/src/renderer/api/__tests__/kube-api.test.ts +++ b/src/renderer/api/__tests__/kube-api.test.ts @@ -79,4 +79,4 @@ describe("KubeApi", () => { expect(kubeApi.apiPrefix).toEqual("/apis"); expect(kubeApi.apiGroup).toEqual("extensions"); }); -}); \ No newline at end of file +}); diff --git a/src/renderer/api/workload-kube-object.ts b/src/renderer/api/workload-kube-object.ts index e0c6d3f121..c6786aa99f 100644 --- a/src/renderer/api/workload-kube-object.ts +++ b/src/renderer/api/workload-kube-object.ts @@ -82,4 +82,4 @@ export class WorkloadKubeObject extends KubeObject { return Object.keys(affinity).length; } -} \ No newline at end of file +} diff --git a/src/renderer/components/+apps-helm-charts/helm-charts.route.ts b/src/renderer/components/+apps-helm-charts/helm-charts.route.ts index 9b8aecc499..97a0923d97 100644 --- a/src/renderer/components/+apps-helm-charts/helm-charts.route.ts +++ b/src/renderer/components/+apps-helm-charts/helm-charts.route.ts @@ -11,4 +11,4 @@ export interface IHelmChartsRouteParams { repo?: string; } -export const helmChartsURL = buildURL(helmChartsRoute.path); \ No newline at end of file +export const helmChartsURL = buildURL(helmChartsRoute.path); diff --git a/src/renderer/components/+apps-helm-charts/index.ts b/src/renderer/components/+apps-helm-charts/index.ts index a9403c097c..c0649f3f38 100644 --- a/src/renderer/components/+apps-helm-charts/index.ts +++ b/src/renderer/components/+apps-helm-charts/index.ts @@ -1,2 +1,2 @@ export * from "./helm-charts"; -export * from "./helm-charts.route"; \ No newline at end of file +export * from "./helm-charts.route"; diff --git a/src/renderer/components/+apps-releases/index.ts b/src/renderer/components/+apps-releases/index.ts index 32a4871769..bd80c60404 100644 --- a/src/renderer/components/+apps-releases/index.ts +++ b/src/renderer/components/+apps-releases/index.ts @@ -1,2 +1,2 @@ export * from "./releases"; -export * from "./release.route"; \ No newline at end of file +export * from "./release.route"; diff --git a/src/renderer/components/+apps/index.ts b/src/renderer/components/+apps/index.ts index 70c0169777..330891b2b1 100644 --- a/src/renderer/components/+apps/index.ts +++ b/src/renderer/components/+apps/index.ts @@ -1,2 +1,2 @@ export * from "./apps"; -export * from "./apps.route"; \ No newline at end of file +export * from "./apps.route"; diff --git a/src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx b/src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx index 35c18cc5e5..10aabf3ff7 100644 --- a/src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx +++ b/src/renderer/components/+cluster-settings/components/cluster-home-dir-setting.tsx @@ -48,4 +48,4 @@ export class ClusterHomeDirSetting extends React.Component { ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx b/src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx index 631c6d54ef..9d953ef9ca 100644 --- a/src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx +++ b/src/renderer/components/+cluster-settings/components/cluster-name-setting.tsx @@ -45,4 +45,4 @@ export class ClusterNameSetting extends React.Component { ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/+cluster-settings/components/cluster-proxy-setting.tsx b/src/renderer/components/+cluster-settings/components/cluster-proxy-setting.tsx index 3887487816..eb122ac444 100644 --- a/src/renderer/components/+cluster-settings/components/cluster-proxy-setting.tsx +++ b/src/renderer/components/+cluster-settings/components/cluster-proxy-setting.tsx @@ -45,4 +45,4 @@ export class ClusterProxySetting extends React.Component { ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/+cluster-settings/general.tsx b/src/renderer/components/+cluster-settings/general.tsx index 1d498bc94b..91fce05164 100644 --- a/src/renderer/components/+cluster-settings/general.tsx +++ b/src/renderer/components/+cluster-settings/general.tsx @@ -25,4 +25,4 @@ export class General extends React.Component {
; } -} \ No newline at end of file +} diff --git a/src/renderer/components/+cluster-settings/removal.tsx b/src/renderer/components/+cluster-settings/removal.tsx index 7d97e9c515..495fb71fe8 100644 --- a/src/renderer/components/+cluster-settings/removal.tsx +++ b/src/renderer/components/+cluster-settings/removal.tsx @@ -17,4 +17,4 @@ export class Removal extends React.Component {
); } -} \ No newline at end of file +} diff --git a/src/renderer/components/+cluster-settings/status.tsx b/src/renderer/components/+cluster-settings/status.tsx index 7f21d19aba..d43cfe5c35 100644 --- a/src/renderer/components/+cluster-settings/status.tsx +++ b/src/renderer/components/+cluster-settings/status.tsx @@ -58,4 +58,4 @@ export class Status extends React.Component { ; } -} \ No newline at end of file +} diff --git a/src/renderer/components/+cluster/cluster-metrics.tsx b/src/renderer/components/+cluster/cluster-metrics.tsx index 6461bae7f3..2bdaded8b7 100644 --- a/src/renderer/components/+cluster/cluster-metrics.tsx +++ b/src/renderer/components/+cluster/cluster-metrics.tsx @@ -95,4 +95,4 @@ export const ClusterMetrics = observer(() => { {renderMetrics()} ); -}); \ No newline at end of file +}); diff --git a/src/renderer/components/+landing-page/index.tsx b/src/renderer/components/+landing-page/index.tsx index 4bdb2a706c..c7eacf1bd0 100644 --- a/src/renderer/components/+landing-page/index.tsx +++ b/src/renderer/components/+landing-page/index.tsx @@ -1,2 +1,2 @@ export * from "./landing-page.route"; -export * from "./landing-page"; \ No newline at end of file +export * from "./landing-page"; diff --git a/src/renderer/components/+pod-security-policies/index.ts b/src/renderer/components/+pod-security-policies/index.ts index d037873b5b..c9379d3381 100644 --- a/src/renderer/components/+pod-security-policies/index.ts +++ b/src/renderer/components/+pod-security-policies/index.ts @@ -1,3 +1,3 @@ export * from "./pod-security-policies.route"; export * from "./pod-security-policies"; -export * from "./pod-security-policy-details"; \ No newline at end of file +export * from "./pod-security-policy-details"; diff --git a/src/renderer/components/+user-management-roles/roles.store.ts b/src/renderer/components/+user-management-roles/roles.store.ts index 6af33deacb..7b6c6c2397 100644 --- a/src/renderer/components/+user-management-roles/roles.store.ts +++ b/src/renderer/components/+user-management-roles/roles.store.ts @@ -49,4 +49,4 @@ export const rolesStore = new RolesStore(); apiManager.registerStore(rolesStore, [ roleApi, clusterRoleApi, -]); \ No newline at end of file +]); diff --git a/src/renderer/components/+user-management-service-accounts/index.ts b/src/renderer/components/+user-management-service-accounts/index.ts index fd45e28288..bd81292bf1 100644 --- a/src/renderer/components/+user-management-service-accounts/index.ts +++ b/src/renderer/components/+user-management-service-accounts/index.ts @@ -1,3 +1,3 @@ export * from "./service-accounts"; export * from "./service-accounts-details"; -export * from "./create-service-account-dialog"; \ No newline at end of file +export * from "./create-service-account-dialog"; diff --git a/src/renderer/components/+user-management/index.ts b/src/renderer/components/+user-management/index.ts index 6f29869b9b..4ff825df97 100644 --- a/src/renderer/components/+user-management/index.ts +++ b/src/renderer/components/+user-management/index.ts @@ -1,2 +1,2 @@ export * from "./user-management"; -export * from "./user-management.route"; \ No newline at end of file +export * from "./user-management.route"; diff --git a/src/renderer/components/+workloads-pods/index.ts b/src/renderer/components/+workloads-pods/index.ts index f3181cb3a2..cc7782911c 100644 --- a/src/renderer/components/+workloads-pods/index.ts +++ b/src/renderer/components/+workloads-pods/index.ts @@ -1,2 +1,2 @@ export * from "./pods"; -export * from "./pod-details"; \ No newline at end of file +export * from "./pod-details"; diff --git a/src/renderer/components/+workloads-pods/pod-details-statuses.tsx b/src/renderer/components/+workloads-pods/pod-details-statuses.tsx index 5ce8465e72..1e0f765381 100644 --- a/src/renderer/components/+workloads-pods/pod-details-statuses.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-statuses.tsx @@ -27,4 +27,4 @@ export class PodDetailsStatuses extends React.Component { ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/+workloads-statefulsets/index.ts b/src/renderer/components/+workloads-statefulsets/index.ts index 1cb72d701a..af942b604f 100644 --- a/src/renderer/components/+workloads-statefulsets/index.ts +++ b/src/renderer/components/+workloads-statefulsets/index.ts @@ -1,2 +1,2 @@ export * from "./statefulsets"; -export * from "./statefulset-details"; \ No newline at end of file +export * from "./statefulset-details"; diff --git a/src/renderer/components/ace-editor/ace-editor.tsx b/src/renderer/components/ace-editor/ace-editor.tsx index 68ef92635a..2dceb48bba 100644 --- a/src/renderer/components/ace-editor/ace-editor.tsx +++ b/src/renderer/components/ace-editor/ace-editor.tsx @@ -154,4 +154,4 @@ export class AceEditor extends React.Component { ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/ace-editor/index.ts b/src/renderer/components/ace-editor/index.ts index 7bfc7c01ea..173845abab 100644 --- a/src/renderer/components/ace-editor/index.ts +++ b/src/renderer/components/ace-editor/index.ts @@ -1 +1 @@ -export * from "./ace-editor"; \ No newline at end of file +export * from "./ace-editor"; diff --git a/src/renderer/components/add-remove-buttons/index.ts b/src/renderer/components/add-remove-buttons/index.ts index 825c59d7d2..fa2deb84ec 100644 --- a/src/renderer/components/add-remove-buttons/index.ts +++ b/src/renderer/components/add-remove-buttons/index.ts @@ -1 +1 @@ -export * from "./add-remove-buttons"; \ No newline at end of file +export * from "./add-remove-buttons"; diff --git a/src/renderer/components/animate/index.ts b/src/renderer/components/animate/index.ts index 080c5446c8..36d812de20 100644 --- a/src/renderer/components/animate/index.ts +++ b/src/renderer/components/animate/index.ts @@ -1 +1 @@ -export * from "./animate"; \ No newline at end of file +export * from "./animate"; diff --git a/src/renderer/components/chart/background-block.plugin.ts b/src/renderer/components/chart/background-block.plugin.ts index 1d39f71aed..ff4816c4dd 100644 --- a/src/renderer/components/chart/background-block.plugin.ts +++ b/src/renderer/components/chart/background-block.plugin.ts @@ -39,4 +39,4 @@ export const BackgroundBlock = { ctx.stroke(); ctx.restore(); } -}; \ No newline at end of file +}; diff --git a/src/renderer/components/chart/bar-chart.tsx b/src/renderer/components/chart/bar-chart.tsx index 69b65b10c9..4f80258703 100644 --- a/src/renderer/components/chart/bar-chart.tsx +++ b/src/renderer/components/chart/bar-chart.tsx @@ -220,4 +220,4 @@ export const cpuOptions: ChartOptions = { } } } -}; \ No newline at end of file +}; diff --git a/src/renderer/components/chart/chart.tsx b/src/renderer/components/chart/chart.tsx index b7e621be22..e42a308848 100644 --- a/src/renderer/components/chart/chart.tsx +++ b/src/renderer/components/chart/chart.tsx @@ -213,4 +213,4 @@ export class Chart extends React.Component { ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/chart/index.ts b/src/renderer/components/chart/index.ts index a9db66c298..d75ddf7a2f 100644 --- a/src/renderer/components/chart/index.ts +++ b/src/renderer/components/chart/index.ts @@ -1,3 +1,3 @@ export * from "./chart"; export * from "./pie-chart"; -export * from "./bar-chart"; \ No newline at end of file +export * from "./bar-chart"; diff --git a/src/renderer/components/chart/pie-chart.tsx b/src/renderer/components/chart/pie-chart.tsx index 1c629ab505..939d6bb612 100644 --- a/src/renderer/components/chart/pie-chart.tsx +++ b/src/renderer/components/chart/pie-chart.tsx @@ -64,4 +64,4 @@ export class PieChart extends React.Component { ChartJS.Tooltip.positioners.cursor = function (elements: any, position: { x: number; y: number }) { return position; -}; \ No newline at end of file +}; diff --git a/src/renderer/components/chart/useRealTimeMetrics.ts b/src/renderer/components/chart/useRealTimeMetrics.ts index 69e4e3da7f..b01629e8e9 100644 --- a/src/renderer/components/chart/useRealTimeMetrics.ts +++ b/src/renderer/components/chart/useRealTimeMetrics.ts @@ -42,4 +42,4 @@ export function useRealTimeMetrics(metrics: IMetricValues, chartData: IChartData } return data; -} \ No newline at end of file +} diff --git a/src/renderer/components/chart/zebra-stripes.plugin.ts b/src/renderer/components/chart/zebra-stripes.plugin.ts index f934f88fb2..3a85f8d0a2 100644 --- a/src/renderer/components/chart/zebra-stripes.plugin.ts +++ b/src/renderer/components/chart/zebra-stripes.plugin.ts @@ -95,4 +95,4 @@ export const ZebraStripes = { cover.style.backgroundPositionX = `${-step * minutes}px`; } } -}; \ No newline at end of file +}; diff --git a/src/renderer/components/checkbox/checkbox.tsx b/src/renderer/components/checkbox/checkbox.tsx index f97740a874..8d452a1198 100644 --- a/src/renderer/components/checkbox/checkbox.tsx +++ b/src/renderer/components/checkbox/checkbox.tsx @@ -50,4 +50,4 @@ export class Checkbox extends React.PureComponent { ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/checkbox/index.ts b/src/renderer/components/checkbox/index.ts index 7af8873e06..057f167821 100644 --- a/src/renderer/components/checkbox/index.ts +++ b/src/renderer/components/checkbox/index.ts @@ -1 +1 @@ -export * from "./checkbox"; \ No newline at end of file +export * from "./checkbox"; diff --git a/src/renderer/components/cluster-icon/index.ts b/src/renderer/components/cluster-icon/index.ts index 4e1858939f..7879490b85 100644 --- a/src/renderer/components/cluster-icon/index.ts +++ b/src/renderer/components/cluster-icon/index.ts @@ -1 +1 @@ -export * from "./cluster-icon"; \ No newline at end of file +export * from "./cluster-icon"; diff --git a/src/renderer/components/confirm-dialog/index.ts b/src/renderer/components/confirm-dialog/index.ts index dfcd83ded3..4627fd6882 100644 --- a/src/renderer/components/confirm-dialog/index.ts +++ b/src/renderer/components/confirm-dialog/index.ts @@ -1 +1 @@ -export * from "./confirm-dialog"; \ No newline at end of file +export * from "./confirm-dialog"; diff --git a/src/renderer/components/dock/dock-tabs.tsx b/src/renderer/components/dock/dock-tabs.tsx index 6bf9280d59..554411024b 100644 --- a/src/renderer/components/dock/dock-tabs.tsx +++ b/src/renderer/components/dock/dock-tabs.tsx @@ -48,4 +48,4 @@ export const DockTabs = ({ tabs, autoFocus, selectedTab, onChangeTab }: Props) = {tabs.map(tab => {renderTab(tab)})} ); -}; \ No newline at end of file +}; diff --git a/src/renderer/components/editable-list/index.ts b/src/renderer/components/editable-list/index.ts index cc0293acd6..1dc93d5df7 100644 --- a/src/renderer/components/editable-list/index.ts +++ b/src/renderer/components/editable-list/index.ts @@ -1 +1 @@ -export * from "./editable-list"; \ No newline at end of file +export * from "./editable-list"; diff --git a/src/renderer/components/error-boundary/index.ts b/src/renderer/components/error-boundary/index.ts index cdcf838466..90e954fb2e 100644 --- a/src/renderer/components/error-boundary/index.ts +++ b/src/renderer/components/error-boundary/index.ts @@ -1 +1 @@ -export * from "./error-boundary"; \ No newline at end of file +export * from "./error-boundary"; diff --git a/src/renderer/components/file-picker/file-picker.tsx b/src/renderer/components/file-picker/file-picker.tsx index 14cd6c07e8..1a52fe4973 100644 --- a/src/renderer/components/file-picker/file-picker.tsx +++ b/src/renderer/components/file-picker/file-picker.tsx @@ -209,4 +209,4 @@ export class FilePicker extends React.Component { return ; } } -} \ No newline at end of file +} diff --git a/src/renderer/components/file-picker/index.ts b/src/renderer/components/file-picker/index.ts index f58aec1470..28c490afab 100644 --- a/src/renderer/components/file-picker/index.ts +++ b/src/renderer/components/file-picker/index.ts @@ -1 +1 @@ -export * from "./file-picker"; \ No newline at end of file +export * from "./file-picker"; diff --git a/src/renderer/components/icon/index.ts b/src/renderer/components/icon/index.ts index 5cdcefa69c..b975409af4 100644 --- a/src/renderer/components/icon/index.ts +++ b/src/renderer/components/icon/index.ts @@ -1 +1 @@ -export * from "./icon"; \ No newline at end of file +export * from "./icon"; diff --git a/src/renderer/components/input/search-input-url.tsx b/src/renderer/components/input/search-input-url.tsx index 2b1045ede2..c0e00d6e56 100644 --- a/src/renderer/components/input/search-input-url.tsx +++ b/src/renderer/components/input/search-input-url.tsx @@ -54,4 +54,4 @@ export class SearchInputUrl extends React.Component { /> ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/item-object-list/index.tsx b/src/renderer/components/item-object-list/index.tsx index b0b106b298..87ba0e908a 100644 --- a/src/renderer/components/item-object-list/index.tsx +++ b/src/renderer/components/item-object-list/index.tsx @@ -1 +1 @@ -export * from "./item-list-layout"; \ No newline at end of file +export * from "./item-list-layout"; diff --git a/src/renderer/components/kube-object-status-icon/index.ts b/src/renderer/components/kube-object-status-icon/index.ts index 36751596a0..3ef2e6b29c 100644 --- a/src/renderer/components/kube-object-status-icon/index.ts +++ b/src/renderer/components/kube-object-status-icon/index.ts @@ -1 +1 @@ -export * from "./kube-object-status-icon"; \ No newline at end of file +export * from "./kube-object-status-icon"; diff --git a/src/renderer/components/kubeconfig-dialog/index.ts b/src/renderer/components/kubeconfig-dialog/index.ts index fdd244fe98..cb8c90cc14 100644 --- a/src/renderer/components/kubeconfig-dialog/index.ts +++ b/src/renderer/components/kubeconfig-dialog/index.ts @@ -1 +1 @@ -export * from "./kubeconfig-dialog"; \ No newline at end of file +export * from "./kubeconfig-dialog"; diff --git a/src/renderer/components/layout/__test__/main-layout-header.test.tsx b/src/renderer/components/layout/__test__/main-layout-header.test.tsx index b2a7bb5d93..499839072c 100644 --- a/src/renderer/components/layout/__test__/main-layout-header.test.tsx +++ b/src/renderer/components/layout/__test__/main-layout-header.test.tsx @@ -46,4 +46,4 @@ describe("", () => { expect(getByText("minikube")).toBeInTheDocument(); }); -}); \ No newline at end of file +}); diff --git a/src/renderer/components/layout/login-layout.tsx b/src/renderer/components/layout/login-layout.tsx index 8aa9c08e0b..669f783769 100755 --- a/src/renderer/components/layout/login-layout.tsx +++ b/src/renderer/components/layout/login-layout.tsx @@ -34,4 +34,4 @@ export class LoginLayout extends React.Component { ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/layout/sidebar-context.ts b/src/renderer/components/layout/sidebar-context.ts index fff192cba3..7001bbc319 100644 --- a/src/renderer/components/layout/sidebar-context.ts +++ b/src/renderer/components/layout/sidebar-context.ts @@ -4,4 +4,4 @@ export const SidebarContext = React.createContext({ pinned: export type SidebarContextValue = { pinned: boolean; -}; \ No newline at end of file +}; diff --git a/src/renderer/components/line-progress/index.ts b/src/renderer/components/line-progress/index.ts index 91942d706a..bd76106dbb 100644 --- a/src/renderer/components/line-progress/index.ts +++ b/src/renderer/components/line-progress/index.ts @@ -1 +1 @@ -export * from "./line-progress"; \ No newline at end of file +export * from "./line-progress"; diff --git a/src/renderer/components/markdown-viewer/index.ts b/src/renderer/components/markdown-viewer/index.ts index e82c6ba3c3..3c42af15f4 100644 --- a/src/renderer/components/markdown-viewer/index.ts +++ b/src/renderer/components/markdown-viewer/index.ts @@ -1 +1 @@ -export * from "./markdown-viewer"; \ No newline at end of file +export * from "./markdown-viewer"; diff --git a/src/renderer/components/markdown-viewer/markdown-viewer.tsx b/src/renderer/components/markdown-viewer/markdown-viewer.tsx index b1a2334b97..08478cb5a9 100644 --- a/src/renderer/components/markdown-viewer/markdown-viewer.tsx +++ b/src/renderer/components/markdown-viewer/markdown-viewer.tsx @@ -34,4 +34,4 @@ export class MarkdownViewer extends Component { /> ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/radio/index.ts b/src/renderer/components/radio/index.ts index 577923ef9c..0df0f30e50 100644 --- a/src/renderer/components/radio/index.ts +++ b/src/renderer/components/radio/index.ts @@ -1 +1 @@ -export * from "./radio"; \ No newline at end of file +export * from "./radio"; diff --git a/src/renderer/components/resource-metrics/index.ts b/src/renderer/components/resource-metrics/index.ts index 5438f760b4..a50f74ea8e 100644 --- a/src/renderer/components/resource-metrics/index.ts +++ b/src/renderer/components/resource-metrics/index.ts @@ -1,2 +1,2 @@ export * from "./resource-metrics"; -export * from "./resource-metrics-text"; \ No newline at end of file +export * from "./resource-metrics-text"; diff --git a/src/renderer/components/slider/index.ts b/src/renderer/components/slider/index.ts index bc79daa3ff..67c45bb063 100644 --- a/src/renderer/components/slider/index.ts +++ b/src/renderer/components/slider/index.ts @@ -1 +1 @@ -export * from "./slider"; \ No newline at end of file +export * from "./slider"; diff --git a/src/renderer/components/status-brick/index.ts b/src/renderer/components/status-brick/index.ts index e16a2a8093..cc6d3e8879 100644 --- a/src/renderer/components/status-brick/index.ts +++ b/src/renderer/components/status-brick/index.ts @@ -1 +1 @@ -export * from "./status-brick"; \ No newline at end of file +export * from "./status-brick"; diff --git a/src/renderer/components/status-brick/status-brick.tsx b/src/renderer/components/status-brick/status-brick.tsx index 34c835c9fa..ced04acca4 100644 --- a/src/renderer/components/status-brick/status-brick.tsx +++ b/src/renderer/components/status-brick/status-brick.tsx @@ -19,4 +19,4 @@ export class StatusBrick extends React.Component { /> ); } -} \ No newline at end of file +} diff --git a/src/renderer/components/virtual-list/index.ts b/src/renderer/components/virtual-list/index.ts index 4e5b065f43..3fad81848e 100644 --- a/src/renderer/components/virtual-list/index.ts +++ b/src/renderer/components/virtual-list/index.ts @@ -1 +1 @@ -export * from "./virtual-list"; \ No newline at end of file +export * from "./virtual-list"; diff --git a/src/renderer/components/wizard/index.ts b/src/renderer/components/wizard/index.ts index b217e311a9..da693bd87f 100644 --- a/src/renderer/components/wizard/index.ts +++ b/src/renderer/components/wizard/index.ts @@ -1 +1 @@ -export * from "./wizard"; \ No newline at end of file +export * from "./wizard"; diff --git a/src/renderer/hooks/useInterval.ts b/src/renderer/hooks/useInterval.ts index d195fa279f..7ab604511b 100644 --- a/src/renderer/hooks/useInterval.ts +++ b/src/renderer/hooks/useInterval.ts @@ -16,4 +16,4 @@ export function useInterval(callback: () => void, delay: number) { return () => clearInterval(id); }, [delay]); -} \ No newline at end of file +} diff --git a/src/renderer/hooks/useOnUnmount.ts b/src/renderer/hooks/useOnUnmount.ts index 5af04e39b1..a8b6fdd1b4 100644 --- a/src/renderer/hooks/useOnUnmount.ts +++ b/src/renderer/hooks/useOnUnmount.ts @@ -2,4 +2,4 @@ import { useEffect } from "react"; export function useOnUnmount(callback: () => void) { useEffect(() => callback, []); -} \ No newline at end of file +} diff --git a/src/renderer/hooks/useStorage.ts b/src/renderer/hooks/useStorage.ts index 2af730fec8..97b0588d29 100644 --- a/src/renderer/hooks/useStorage.ts +++ b/src/renderer/hooks/useStorage.ts @@ -10,4 +10,4 @@ export function useStorage(key: string, initialValue?: T, options?: IStorageH }; return [storageValue, setValue] as [T, (value: T) => void]; -} \ No newline at end of file +} diff --git a/src/renderer/navigation/events.ts b/src/renderer/navigation/events.ts index 971465706d..1766a1e0d3 100644 --- a/src/renderer/navigation/events.ts +++ b/src/renderer/navigation/events.ts @@ -28,4 +28,4 @@ export function bindEvents() { subscribeToBroadcast("renderer:reload", () => { location.reload(); }); -} \ No newline at end of file +} diff --git a/src/renderer/navigation/helpers.ts b/src/renderer/navigation/helpers.ts index 378f6edb96..0eda77c629 100644 --- a/src/renderer/navigation/helpers.ts +++ b/src/renderer/navigation/helpers.ts @@ -33,4 +33,4 @@ export function getMatchedClusterId(): string { }); return matched?.params.clusterId; -} \ No newline at end of file +} diff --git a/src/renderer/navigation/index.ts b/src/renderer/navigation/index.ts index 70959c2dbd..94930fc994 100644 --- a/src/renderer/navigation/index.ts +++ b/src/renderer/navigation/index.ts @@ -5,4 +5,4 @@ import { bindEvents } from "./events"; export * from "./history"; export * from "./helpers"; -bindEvents(); \ No newline at end of file +bindEvents(); diff --git a/src/renderer/utils/__tests__/metricUnitsToNumber.test.ts b/src/renderer/utils/__tests__/metricUnitsToNumber.test.ts index e94c2f3b67..a22aa46790 100644 --- a/src/renderer/utils/__tests__/metricUnitsToNumber.test.ts +++ b/src/renderer/utils/__tests__/metricUnitsToNumber.test.ts @@ -12,4 +12,4 @@ describe("metricUnitsToNumber tests", () => { test("with m suffix", () => { expect(metricUnitsToNumber("124m")).toStrictEqual(124000000); }); -}); \ No newline at end of file +}); diff --git a/src/renderer/utils/formatDuration.ts b/src/renderer/utils/formatDuration.ts index 8864aba393..87da6cfa64 100644 --- a/src/renderer/utils/formatDuration.ts +++ b/src/renderer/utils/formatDuration.ts @@ -83,4 +83,4 @@ function getMeaningfulValues(values: number[], suffixes: string[], separator = " .filter(([dur]) => dur > 0) .map(([dur, suf]) => dur + suf) .join(separator); -} \ No newline at end of file +} diff --git a/src/renderer/utils/jsonPath.ts b/src/renderer/utils/jsonPath.ts index ea31ffa80e..79075500f9 100644 --- a/src/renderer/utils/jsonPath.ts +++ b/src/renderer/utils/jsonPath.ts @@ -32,4 +32,4 @@ function convertToIndexNotation(key: string, firstItem = false) { return `${prefix}${key}`; } -} \ No newline at end of file +} diff --git a/types/command-exists.d.ts b/types/command-exists.d.ts index b5375ae390..634d2a035e 100644 --- a/types/command-exists.d.ts +++ b/types/command-exists.d.ts @@ -13,4 +13,4 @@ declare function commandExists( declare namespace commandExists { function sync(commandName: string): boolean; -} \ No newline at end of file +} From a92ed46f0d0c0bb9fd247bb01094781de4efc243 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 22 Jan 2021 10:27:54 +0300 Subject: [PATCH 3/5] Fixing tolerations list layout (#2002) * Expanding tolerations div width Signed-off-by: Alex Andreev * Adding tolerations table Signed-off-by: Alex Andreev * Fixing tolerations table styles Signed-off-by: Alex Andreev * Adding tests Signed-off-by: Alex Andreev * Add new line at the end of the file for linter Signed-off-by: Alex Andreev --- src/renderer/api/workload-kube-object.ts | 2 +- .../__tests__/pod-tolerations.test.tsx | 59 +++++++++++++++++ .../pod-details-tolerations.scss | 22 ++++++- .../pod-details-tolerations.tsx | 20 ++---- .../+workloads-pods/pod-tolerations.scss | 14 +++++ .../+workloads-pods/pod-tolerations.tsx | 63 +++++++++++++++++++ .../components/drawer/drawer-item.scss | 4 +- .../drawer/drawer-param-toggler.tsx | 2 +- src/renderer/components/drawer/drawer.scss | 1 - 9 files changed, 165 insertions(+), 22 deletions(-) create mode 100644 src/renderer/components/+workloads-pods/__tests__/pod-tolerations.test.tsx create mode 100644 src/renderer/components/+workloads-pods/pod-tolerations.scss create mode 100644 src/renderer/components/+workloads-pods/pod-tolerations.tsx diff --git a/src/renderer/api/workload-kube-object.ts b/src/renderer/api/workload-kube-object.ts index c6786aa99f..185d3d502c 100644 --- a/src/renderer/api/workload-kube-object.ts +++ b/src/renderer/api/workload-kube-object.ts @@ -1,7 +1,7 @@ import get from "lodash/get"; import { KubeObject } from "./kube-object"; -interface IToleration { +export interface IToleration { key?: string; operator?: string; effect?: string; diff --git a/src/renderer/components/+workloads-pods/__tests__/pod-tolerations.test.tsx b/src/renderer/components/+workloads-pods/__tests__/pod-tolerations.test.tsx new file mode 100644 index 0000000000..dbde813e5a --- /dev/null +++ b/src/renderer/components/+workloads-pods/__tests__/pod-tolerations.test.tsx @@ -0,0 +1,59 @@ +/** + * @jest-environment jsdom + */ + +import React from "react"; +import "@testing-library/jest-dom/extend-expect"; +import { fireEvent, render } from "@testing-library/react"; +import { IToleration } from "../../../api/workload-kube-object"; +import { PodTolerations } from "../pod-tolerations"; + +const tolerations: IToleration[] =[ + { + key: "CriticalAddonsOnly", + operator: "Exist", + effect: "NoExecute", + tolerationSeconds: 3600 + }, + { + key: "node.kubernetes.io/not-ready", + operator: "NoExist", + effect: "NoSchedule", + tolerationSeconds: 7200 + }, +]; + +describe("", () => { + it("renders w/o errors", () => { + const { container } = render(); + + expect(container).toBeInstanceOf(HTMLElement); + }); + + it("shows all tolerations", () => { + const { container } = render(); + const rows = container.querySelectorAll(".TableRow"); + + expect(rows[0].querySelector(".key").textContent).toBe("CriticalAddonsOnly"); + expect(rows[0].querySelector(".operator").textContent).toBe("Exist"); + expect(rows[0].querySelector(".effect").textContent).toBe("NoExecute"); + expect(rows[0].querySelector(".seconds").textContent).toBe("3600"); + + expect(rows[1].querySelector(".key").textContent).toBe("node.kubernetes.io/not-ready"); + expect(rows[1].querySelector(".operator").textContent).toBe("NoExist"); + expect(rows[1].querySelector(".effect").textContent).toBe("NoSchedule"); + expect(rows[1].querySelector(".seconds").textContent).toBe("7200"); + }); + + it("sorts table properly", () => { + const { container, getByText } = render(); + const headCell = getByText("Key"); + + fireEvent.click(headCell); + fireEvent.click(headCell); + + const rows = container.querySelectorAll(".TableRow"); + + expect(rows[0].querySelector(".key").textContent).toBe("node.kubernetes.io/not-ready"); + }); +}); diff --git a/src/renderer/components/+workloads-pods/pod-details-tolerations.scss b/src/renderer/components/+workloads-pods/pod-details-tolerations.scss index 0aa68fa1d6..1ac932cd9d 100644 --- a/src/renderer/components/+workloads-pods/pod-details-tolerations.scss +++ b/src/renderer/components/+workloads-pods/pod-details-tolerations.scss @@ -1,5 +1,23 @@ .PodDetailsTolerations { - .toleration { - margin-bottom: $margin; + grid-template-columns: auto; + + .PodTolerations { + margin-top: var(--margin); + } + + // Expanding value cell to cover 2 columns (whole Drawer width) + + > .name { + grid-row-start: 1; + grid-column-start: 1; + } + + > .value { + grid-row-start: 1; + grid-column-start: 1; + } + + .DrawerParamToggler > .params { + margin-left: var(--drawer-item-title-width); } } \ No newline at end of file diff --git a/src/renderer/components/+workloads-pods/pod-details-tolerations.tsx b/src/renderer/components/+workloads-pods/pod-details-tolerations.tsx index 8b67502e26..67bd5a07d0 100644 --- a/src/renderer/components/+workloads-pods/pod-details-tolerations.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-tolerations.tsx @@ -1,10 +1,11 @@ import "./pod-details-tolerations.scss"; import React from "react"; -import { Pod, Deployment, DaemonSet, StatefulSet, ReplicaSet, Job } from "../../api/endpoints"; import { DrawerParamToggler, DrawerItem } from "../drawer"; +import { WorkloadKubeObject } from "../../api/workload-kube-object"; +import { PodTolerations } from "./pod-tolerations"; interface Props { - workload: Pod | Deployment | DaemonSet | StatefulSet | ReplicaSet | Job; + workload: WorkloadKubeObject; } export class PodDetailsTolerations extends React.Component { @@ -17,20 +18,7 @@ export class PodDetailsTolerations extends React.Component { return ( - { - tolerations.map((toleration, index) => { - const { key, operator, effect, tolerationSeconds } = toleration; - - return ( -
- {key} - {operator && {operator}} - {effect && {effect}} - {!!tolerationSeconds && {tolerationSeconds}} -
- ); - }) - } +
); diff --git a/src/renderer/components/+workloads-pods/pod-tolerations.scss b/src/renderer/components/+workloads-pods/pod-tolerations.scss new file mode 100644 index 0000000000..b840697685 --- /dev/null +++ b/src/renderer/components/+workloads-pods/pod-tolerations.scss @@ -0,0 +1,14 @@ +.PodTolerations { + .TableHead { + background-color: var(--drawerSubtitleBackground); + } + + .TableCell { + white-space: normal; + word-break: normal; + + &.key { + flex-grow: 3; + } + } +} \ No newline at end of file diff --git a/src/renderer/components/+workloads-pods/pod-tolerations.tsx b/src/renderer/components/+workloads-pods/pod-tolerations.tsx new file mode 100644 index 0000000000..e8d3d7d099 --- /dev/null +++ b/src/renderer/components/+workloads-pods/pod-tolerations.tsx @@ -0,0 +1,63 @@ +import "./pod-tolerations.scss"; +import React from "react"; +import uniqueId from "lodash/uniqueId"; + +import { IToleration } from "../../api/workload-kube-object"; +import { Table, TableCell, TableHead, TableRow } from "../table"; + +interface Props { + tolerations: IToleration[]; +} + +enum sortBy { + Key = "key", + Operator = "operator", + Effect = "effect", + Seconds = "seconds", +} + +const sortingCallbacks = { + [sortBy.Key]: (toleration: IToleration) => toleration.key, + [sortBy.Operator]: (toleration: IToleration) => toleration.operator, + [sortBy.Effect]: (toleration: IToleration) => toleration.effect, + [sortBy.Seconds]: (toleration: IToleration) => toleration.tolerationSeconds, +}; + +const getTableRow = (toleration: IToleration) => { + const { key, operator, effect, tolerationSeconds } = toleration; + + return ( + + {key} + {operator} + {effect} + {tolerationSeconds} + + ); +}; + +export function PodTolerations({ tolerations }: Props) { + return ( + + + Key + Operator + Effect + Seconds + + { + tolerations.map(getTableRow) + } +
+ ); +} diff --git a/src/renderer/components/drawer/drawer-item.scss b/src/renderer/components/drawer/drawer-item.scss index f7727414a3..a9c54120df 100644 --- a/src/renderer/components/drawer/drawer-item.scss +++ b/src/renderer/components/drawer/drawer-item.scss @@ -1,6 +1,8 @@ .DrawerItem { + --drawer-item-title-width: 30%; + display: grid; - grid-template-columns: minmax(30%, min-content) auto; + grid-template-columns: minmax(var(--drawer-item-title-width), min-content) auto; border-bottom: 1px solid $borderFaintColor; padding: $padding 0; diff --git a/src/renderer/components/drawer/drawer-param-toggler.tsx b/src/renderer/components/drawer/drawer-param-toggler.tsx index 85772d0855..df97a4d1bd 100644 --- a/src/renderer/components/drawer/drawer-param-toggler.tsx +++ b/src/renderer/components/drawer/drawer-param-toggler.tsx @@ -25,7 +25,7 @@ export class DrawerParamToggler extends React.Component -
+
{label}
{link} diff --git a/src/renderer/components/drawer/drawer.scss b/src/renderer/components/drawer/drawer.scss index b34b3b5965..8b7fc27993 100644 --- a/src/renderer/components/drawer/drawer.scss +++ b/src/renderer/components/drawer/drawer.scss @@ -69,7 +69,6 @@ padding: var(--spacing); .Table .TableHead { - background-color: $contentColor; border-bottom: 1px solid $borderFaintColor; } } From f8c111ddd8031f568fa2f6b97790541ee568b9a5 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 22 Jan 2021 13:18:46 +0200 Subject: [PATCH 4/5] Load k8s resources only for selected namespaces (#1918) * loading k8s resources into stores per selected namespaces -- part 1 Signed-off-by: Roman * loading k8s resources into stores per selected namespaces -- part 2 - fix: generating helm chart id Signed-off-by: Roman * loading k8s resources into stores per selected namespaces -- part 3 Signed-off-by: Roman * fixes Signed-off-by: Roman * fixes / responding to comments Signed-off-by: Roman * chore / small fixes Signed-off-by: Roman * fixes & refactoring Signed-off-by: Roman * make lint happy Signed-off-by: Roman * reset store on loading error Signed-off-by: Roman * added new cluster method: cluster.isAllowedResource Signed-off-by: Roman * fix: loading namespaces optimizations Signed-off-by: Roman * fixes & refactoring Signed-off-by: Roman --- src/common/rbac.ts | 53 ++-- src/common/user-store.ts | 9 + src/main/cluster.ts | 41 ++- src/renderer/api/kube-watch-api.ts | 34 ++- .../+apps-releases/release.store.ts | 27 +- .../components/+namespaces/namespace.store.ts | 140 ++++++---- .../role-bindings.store.ts | 18 +- .../+user-management-roles/roles.store.ts | 18 +- .../+workloads-overview/overview-statuses.tsx | 2 +- .../+workloads-overview/overview.tsx | 86 +++--- .../components/+workloads-pods/pods.tsx | 41 ++- .../item-object-list/item-list-layout.scss | 11 + .../item-object-list/item-list-layout.tsx | 250 +++++++++--------- .../item-object-list/page-filters.store.ts | 6 +- .../item-object-list/table-menu.scss | 4 - src/renderer/components/table/table-cell.tsx | 5 +- src/renderer/item.store.ts | 14 +- src/renderer/kube-object.store.ts | 66 +++-- 18 files changed, 465 insertions(+), 360 deletions(-) delete mode 100644 src/renderer/components/item-object-list/table-menu.scss diff --git a/src/common/rbac.ts b/src/common/rbac.ts index fbcf7c98d8..de242b114a 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -7,37 +7,38 @@ export type KubeResource = "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets"; export interface KubeApiResource { - resource: KubeResource; // valid resource name + kind: string; // resource type (e.g. "Namespace") + apiName: KubeResource; // valid api resource name (e.g. "namespaces") group?: string; // api-group } // TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7) export const apiResources: KubeApiResource[] = [ - { resource: "configmaps" }, - { resource: "cronjobs", group: "batch" }, - { resource: "customresourcedefinitions", group: "apiextensions.k8s.io" }, - { resource: "daemonsets", group: "apps" }, - { resource: "deployments", group: "apps" }, - { resource: "endpoints" }, - { resource: "events" }, - { resource: "horizontalpodautoscalers" }, - { resource: "ingresses", group: "networking.k8s.io" }, - { resource: "jobs", group: "batch" }, - { resource: "limitranges" }, - { resource: "namespaces" }, - { resource: "networkpolicies", group: "networking.k8s.io" }, - { resource: "nodes" }, - { resource: "persistentvolumes" }, - { resource: "persistentvolumeclaims" }, - { resource: "pods" }, - { resource: "poddisruptionbudgets" }, - { resource: "podsecuritypolicies" }, - { resource: "resourcequotas" }, - { resource: "replicasets", group: "apps" }, - { resource: "secrets" }, - { resource: "services" }, - { resource: "statefulsets", group: "apps" }, - { resource: "storageclasses", group: "storage.k8s.io" }, + { kind: "ConfigMap", apiName: "configmaps" }, + { kind: "CronJob", apiName: "cronjobs", group: "batch" }, + { kind: "CustomResourceDefinition", apiName: "customresourcedefinitions", group: "apiextensions.k8s.io" }, + { kind: "DaemonSet", apiName: "daemonsets", group: "apps" }, + { kind: "Deployment", apiName: "deployments", group: "apps" }, + { kind: "Endpoint", apiName: "endpoints" }, + { kind: "Event", apiName: "events" }, + { kind: "HorizontalPodAutoscaler", apiName: "horizontalpodautoscalers" }, + { kind: "Ingress", apiName: "ingresses", group: "networking.k8s.io" }, + { kind: "Job", apiName: "jobs", group: "batch" }, + { kind: "Namespace", apiName: "namespaces" }, + { kind: "LimitRange", apiName: "limitranges" }, + { kind: "NetworkPolicy", apiName: "networkpolicies", group: "networking.k8s.io" }, + { kind: "Node", apiName: "nodes" }, + { kind: "PersistentVolume", apiName: "persistentvolumes" }, + { kind: "PersistentVolumeClaim", apiName: "persistentvolumeclaims" }, + { kind: "Pod", apiName: "pods" }, + { kind: "PodDisruptionBudget", apiName: "poddisruptionbudgets" }, + { kind: "PodSecurityPolicy", apiName: "podsecuritypolicies" }, + { kind: "ResourceQuota", apiName: "resourcequotas" }, + { kind: "ReplicaSet", apiName: "replicasets", group: "apps" }, + { kind: "Secret", apiName: "secrets" }, + { kind: "Service", apiName: "services" }, + { kind: "StatefulSet", apiName: "statefulsets", group: "apps" }, + { kind: "StorageClass", apiName: "storageclasses", group: "storage.k8s.io" }, ]; export function isAllowedResource(resources: KubeResource | KubeResource[]) { diff --git a/src/common/user-store.ts b/src/common/user-store.ts index cf271a011d..b0294d9e5a 100644 --- a/src/common/user-store.ts +++ b/src/common/user-store.ts @@ -84,6 +84,15 @@ export class UserStore extends BaseStore { return semver.gt(getAppVersion(), this.lastSeenAppVersion); } + @action + setHiddenTableColumns(tableId: string, names: Set | string[]) { + this.preferences.hiddenTableColumns[tableId] = Array.from(names); + } + + getHiddenTableColumns(tableId: string): Set { + return new Set(this.preferences.hiddenTableColumns[tableId]); + } + @action resetKubeConfigPath() { this.kubeConfigPath = kubeConfigDefaultPath; diff --git a/src/main/cluster.ts b/src/main/cluster.ts index c6c14f6406..956164e10c 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -190,7 +190,7 @@ export class Cluster implements ClusterModel, ClusterState { */ @observable metadata: ClusterMetadata = {}; /** - * List of allowed namespaces + * List of allowed namespaces verified via K8S::SelfSubjectAccessReview api * * @observable */ @@ -203,7 +203,7 @@ export class Cluster implements ClusterModel, ClusterState { */ @observable allowedResources: string[] = []; /** - * List of accessible namespaces + * List of accessible namespaces provided by user in the Cluster Settings * * @observable */ @@ -224,7 +224,7 @@ export class Cluster implements ClusterModel, ClusterState { * @computed */ @computed get name() { - return this.preferences.clusterName || this.contextName; + return this.preferences.clusterName || this.contextName; } /** @@ -279,7 +279,8 @@ export class Cluster implements ClusterModel, ClusterState { * @param port port where internal auth proxy is listening * @internal */ - @action async init(port: number) { + @action + async init(port: number) { try { this.initializing = true; this.contextHandler = new ContextHandler(this); @@ -334,7 +335,8 @@ export class Cluster implements ClusterModel, ClusterState { * @param force force activation * @internal */ - @action async activate(force = false) { + @action + async activate(force = false) { if (this.activated && !force) { return this.pushState(); } @@ -373,7 +375,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async reconnect() { + @action + async reconnect() { logger.info(`[CLUSTER]: reconnect`, this.getMeta()); this.contextHandler?.stopServer(); await this.contextHandler?.ensureServer(); @@ -400,7 +403,8 @@ export class Cluster implements ClusterModel, ClusterState { * @internal * @param opts refresh options */ - @action async refresh(opts: ClusterRefreshOptions = {}) { + @action + async refresh(opts: ClusterRefreshOptions = {}) { logger.info(`[CLUSTER]: refresh`, this.getMeta()); await this.whenInitialized; await this.refreshConnectionStatus(); @@ -420,7 +424,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async refreshMetadata() { + @action + async refreshMetadata() { logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta()); const metadata = await detectorRegistry.detectForCluster(this); const existingMetadata = this.metadata; @@ -431,7 +436,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async refreshConnectionStatus() { + @action + async refreshConnectionStatus() { const connectionStatus = await this.getConnectionStatus(); this.online = connectionStatus > ClusterStatus.Offline; @@ -441,7 +447,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async refreshAllowedResources() { + @action + async refreshAllowedResources() { this.allowedNamespaces = await this.getAllowedNamespaces(); this.allowedResources = await this.getAllowedResources(); } @@ -668,7 +675,7 @@ export class Cluster implements ClusterModel, ClusterState { for (const namespace of this.allowedNamespaces.slice(0, 10)) { if (!this.resourceAccessStatuses.get(apiResource)) { const result = await this.canI({ - resource: apiResource.resource, + resource: apiResource.apiName, group: apiResource.group, verb: "list", namespace @@ -683,9 +690,19 @@ export class Cluster implements ClusterModel, ClusterState { return apiResources .filter((resource) => this.resourceAccessStatuses.get(resource)) - .map(apiResource => apiResource.resource); + .map(apiResource => apiResource.apiName); } catch (error) { return []; } } + + isAllowedResource(kind: string): boolean { + const apiResource = apiResources.find(resource => resource.kind === kind || resource.apiName === kind); + + if (apiResource) { + return this.allowedResources.includes(apiResource.apiName); + } + + return true; // allowed by default for other resources + } } diff --git a/src/renderer/api/kube-watch-api.ts b/src/renderer/api/kube-watch-api.ts index 78ca25256e..fe35a04baa 100644 --- a/src/renderer/api/kube-watch-api.ts +++ b/src/renderer/api/kube-watch-api.ts @@ -11,7 +11,7 @@ import { apiPrefix, isDevelopment } from "../../common/vars"; import { getHostedCluster } from "../../common/cluster-store"; export interface IKubeWatchEvent { - type: "ADDED" | "MODIFIED" | "DELETED"; + type: "ADDED" | "MODIFIED" | "DELETED" | "ERROR"; object?: T; } @@ -62,27 +62,41 @@ export class KubeWatchApi { }); } - protected getQuery(): Partial { - const { isAdmin, allowedNamespaces } = getHostedCluster(); + // FIXME: use POST to send apis for subscribing (list could be huge) + // TODO: try to use normal fetch res.body stream to consume watch-api updates + // https://github.com/lensapp/lens/issues/1898 + protected async getQuery() { + const { namespaceStore } = await import("../components/+namespaces/namespace.store"); + + await namespaceStore.whenReady; + const { isAdmin } = getHostedCluster(); return { api: this.activeApis.map(api => { - if (isAdmin) return api.getWatchUrl(); + if (isAdmin && !api.isNamespaced) { + return api.getWatchUrl(); + } - return allowedNamespaces.map(namespace => api.getWatchUrl(namespace)); + if (api.isNamespaced) { + return namespaceStore.getContextNamespaces().map(namespace => api.getWatchUrl(namespace)); + } + + return []; }).flat() }; } // todo: maybe switch to websocket to avoid often reconnects @autobind() - protected connect() { + protected async connect() { if (this.evtSource) this.disconnect(); // close previous connection - if (!this.activeApis.length) { + const query = await this.getQuery(); + + if (!this.activeApis.length || !query.api.length) { return; } - const query = this.getQuery(); + const apiUrl = `${apiPrefix}/watch?${stringify(query)}`; this.evtSource = new EventSource(apiUrl); @@ -158,6 +172,10 @@ export class KubeWatchApi { addListener(store: KubeObjectStore, callback: (evt: IKubeWatchEvent) => void) { const listener = (evt: IKubeWatchEvent) => { + if (evt.type === "ERROR") { + return; // e.g. evt.object.message == "too old resource version" + } + const { namespace, resourceVersion } = evt.object.metadata; const api = apiManager.getApiByKind(evt.object.kind, evt.object.apiVersion); diff --git a/src/renderer/components/+apps-releases/release.store.ts b/src/renderer/components/+apps-releases/release.store.ts index b6d5c2fb5f..6f7ed39fed 100644 --- a/src/renderer/components/+apps-releases/release.store.ts +++ b/src/renderer/components/+apps-releases/release.store.ts @@ -5,7 +5,7 @@ import { HelmRelease, helmReleasesApi, IReleaseCreatePayload, IReleaseUpdatePayl import { ItemStore } from "../../item.store"; import { Secret } from "../../api/endpoints"; import { secretsStore } from "../+config-secrets/secrets.store"; -import { getHostedCluster } from "../../../common/cluster-store"; +import { namespaceStore } from "../+namespaces/namespace.store"; @autobind() export class ReleaseStore extends ItemStore { @@ -60,30 +60,23 @@ export class ReleaseStore extends ItemStore { @action async loadAll() { this.isLoading = true; - let items; try { - const { isAdmin, allowedNamespaces } = getHostedCluster(); + const items = await this.loadItems(namespaceStore.getContextNamespaces()); - items = await this.loadItems(!isAdmin ? allowedNamespaces : null); - } finally { - if (items) { - items = this.sortItems(items); - this.items.replace(items); - } + this.items.replace(this.sortItems(items)); this.isLoaded = true; + } catch (error) { + console.error(`Loading Helm Chart releases has failed: ${error}`); + } finally { this.isLoading = false; } } - async loadItems(namespaces?: string[]) { - if (!namespaces) { - return helmReleasesApi.list(); - } else { - return Promise - .all(namespaces.map(namespace => helmReleasesApi.list(namespace))) - .then(items => items.flat()); - } + async loadItems(namespaces: string[]) { + return Promise + .all(namespaces.map(namespace => helmReleasesApi.list(namespace))) + .then(items => items.flat()); } async create(payload: IReleaseCreatePayload) { diff --git a/src/renderer/components/+namespaces/namespace.store.ts b/src/renderer/components/+namespaces/namespace.store.ts index ad02dd137c..50ec2c8038 100644 --- a/src/renderer/components/+namespaces/namespace.store.ts +++ b/src/renderer/components/+namespaces/namespace.store.ts @@ -1,53 +1,120 @@ -import { action, comparer, observable, reaction } from "mobx"; +import { action, comparer, IReactionDisposer, IReactionOptions, observable, reaction, toJS, when } from "mobx"; import { autobind, createStorage } from "../../utils"; -import { KubeObjectStore } from "../../kube-object.store"; -import { Namespace, namespacesApi } from "../../api/endpoints"; +import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store"; +import { Namespace, namespacesApi } from "../../api/endpoints/namespaces.api"; import { createPageParam } from "../../navigation"; import { apiManager } from "../../api/api-manager"; -import { isAllowedResource } from "../../../common/rbac"; -import { getHostedCluster } from "../../../common/cluster-store"; +import { clusterStore, getHostedCluster } from "../../../common/cluster-store"; -const storage = createStorage("context_namespaces", []); +const storage = createStorage("context_namespaces"); export const namespaceUrlParam = createPageParam({ name: "namespaces", isSystem: true, multiValues: true, get defaultValue() { - return storage.get(); // initial namespaces coming from URL or local-storage (default) + return storage.get() ?? []; // initial namespaces coming from URL or local-storage (default) } }); +export function getDummyNamespace(name: string) { + return new Namespace({ + kind: Namespace.kind, + apiVersion: "v1", + metadata: { + name, + uid: "", + resourceVersion: "", + selfLink: `/api/v1/namespaces/${name}` + } + }); +} + @autobind() export class NamespaceStore extends KubeObjectStore { api = namespacesApi; - contextNs = observable.array(); + + @observable contextNs = observable.array(); + @observable isReady = false; + + whenReady = when(() => this.isReady); constructor() { super(); this.init(); } - private init() { - this.setContext(this.initNamespaces); + private async init() { + await clusterStore.whenLoaded; + if (!getHostedCluster()) return; + await getHostedCluster().whenReady; // wait for cluster-state from main - return reaction(() => this.contextNs.toJS(), namespaces => { + this.setContext(this.initialNamespaces); + this.autoLoadAllowedNamespaces(); + this.autoUpdateUrlAndLocalStorage(); + + this.isReady = true; + } + + public onContextChange(callback: (contextNamespaces: string[]) => void, opts: IReactionOptions = {}): IReactionDisposer { + return reaction(() => this.contextNs.toJS(), callback, { + equals: comparer.shallow, + ...opts, + }); + } + + private autoUpdateUrlAndLocalStorage(): IReactionDisposer { + return this.onContextChange(namespaces => { storage.set(namespaces); // save to local-storage namespaceUrlParam.set(namespaces, { replaceHistory: true }); // update url }, { fireImmediately: true, - equals: comparer.identity, }); } - get initNamespaces() { - return namespaceUrlParam.get(); + private autoLoadAllowedNamespaces(): IReactionDisposer { + return reaction(() => this.allowedNamespaces, () => this.loadAll(), { + fireImmediately: true, + equals: comparer.shallow, + }); } - getContextParams() { - return { - namespaces: this.contextNs.toJS(), - }; + get allowedNamespaces(): string[] { + return toJS(getHostedCluster().allowedNamespaces); + } + + private get initialNamespaces(): string[] { + const allowed = new Set(this.allowedNamespaces); + const prevSelected = storage.get(); + + if (Array.isArray(prevSelected)) { + return prevSelected.filter(namespace => allowed.has(namespace)); + } + + // otherwise select "default" or first allowed namespace + if (allowed.has("default")) { + return ["default"]; + } else if (allowed.size) { + return [Array.from(allowed)[0]]; + } + + return []; + } + + getContextNamespaces(): string[] { + const namespaces = this.contextNs.toJS(); + + // show all namespaces when nothing selected + if (!namespaces.length) { + if (this.isLoaded) { + // return actual namespaces list since "allowedNamespaces" updating every 30s in cluster and thus might be stale + return this.items.map(namespace => namespace.getName()); + } + + return this.allowedNamespaces; + } + + return namespaces; } subscribe(apis = [this.api]) { @@ -61,31 +128,18 @@ export class NamespaceStore extends KubeObjectStore { return super.subscribe(apis); } - protected async loadItems(namespaces?: string[]) { - if (!isAllowedResource("namespaces")) { - if (namespaces) return namespaces.map(this.getDummyNamespace); + protected async loadItems(params: KubeObjectStoreLoadingParams) { + const { allowedNamespaces } = this; - return []; + let namespaces = await super.loadItems(params); + + namespaces = namespaces.filter(namespace => allowedNamespaces.includes(namespace.getName())); + + if (!namespaces.length && allowedNamespaces.length > 0) { + return allowedNamespaces.map(getDummyNamespace); } - if (namespaces) { - return Promise.all(namespaces.map(name => this.api.get({ name }))); - } else { - return super.loadItems(); - } - } - - protected getDummyNamespace(name: string) { - return new Namespace({ - kind: "Namespace", - apiVersion: "v1", - metadata: { - name, - uid: "", - resourceVersion: "", - selfLink: `/api/v1/namespaces/${name}` - } - }); + return namespaces; } @action @@ -105,12 +159,6 @@ export class NamespaceStore extends KubeObjectStore { else this.contextNs.push(namespace); } - @action - reset() { - super.reset(); - this.contextNs.clear(); - } - @action async remove(item: Namespace) { await super.remove(item); diff --git a/src/renderer/components/+user-management-roles-bindings/role-bindings.store.ts b/src/renderer/components/+user-management-roles-bindings/role-bindings.store.ts index f293dea6f0..71890acc44 100644 --- a/src/renderer/components/+user-management-roles-bindings/role-bindings.store.ts +++ b/src/renderer/components/+user-management-roles-bindings/role-bindings.store.ts @@ -1,7 +1,7 @@ import difference from "lodash/difference"; import uniqBy from "lodash/uniqBy"; import { clusterRoleBindingApi, IRoleBindingSubject, RoleBinding, roleBindingApi } from "../../api/endpoints"; -import { KubeObjectStore } from "../../kube-object.store"; +import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store"; import { autobind } from "../../utils"; import { apiManager } from "../../api/api-manager"; @@ -26,15 +26,13 @@ export class RoleBindingsStore extends KubeObjectStore { return clusterRoleBindingApi.get(params); } - protected loadItems(namespaces?: string[]) { - if (namespaces) { - return Promise.all( - namespaces.map(namespace => roleBindingApi.list({ namespace })) - ).then(items => items.flat()); - } else { - return Promise.all([clusterRoleBindingApi.list(), roleBindingApi.list()]) - .then(items => items.flat()); - } + protected async loadItems(params: KubeObjectStoreLoadingParams): Promise { + const items = await Promise.all([ + super.loadItems({ ...params, api: clusterRoleBindingApi }), + super.loadItems({ ...params, api: roleBindingApi }), + ]); + + return items.flat(); } protected async createItem(params: { name: string; namespace?: string }, data?: Partial) { diff --git a/src/renderer/components/+user-management-roles/roles.store.ts b/src/renderer/components/+user-management-roles/roles.store.ts index 7b6c6c2397..7d2e90dd38 100644 --- a/src/renderer/components/+user-management-roles/roles.store.ts +++ b/src/renderer/components/+user-management-roles/roles.store.ts @@ -1,6 +1,6 @@ import { clusterRoleApi, Role, roleApi } from "../../api/endpoints"; import { autobind } from "../../utils"; -import { KubeObjectStore } from "../../kube-object.store"; +import { KubeObjectStore, KubeObjectStoreLoadingParams } from "../../kube-object.store"; import { apiManager } from "../../api/api-manager"; @autobind() @@ -24,15 +24,13 @@ export class RolesStore extends KubeObjectStore { return clusterRoleApi.get(params); } - protected loadItems(namespaces?: string[]): Promise { - if (namespaces) { - return Promise.all( - namespaces.map(namespace => roleApi.list({ namespace })) - ).then(items => items.flat()); - } else { - return Promise.all([clusterRoleApi.list(), roleApi.list()]) - .then(items => items.flat()); - } + protected async loadItems(params: KubeObjectStoreLoadingParams): Promise { + const items = await Promise.all([ + super.loadItems({ ...params, api: clusterRoleApi }), + super.loadItems({ ...params, api: roleApi }), + ]); + + return items.flat(); } protected async createItem(params: { name: string; namespace?: string }, data?: Partial) { diff --git a/src/renderer/components/+workloads-overview/overview-statuses.tsx b/src/renderer/components/+workloads-overview/overview-statuses.tsx index 78adecb6df..33e5aa37c5 100644 --- a/src/renderer/components/+workloads-overview/overview-statuses.tsx +++ b/src/renderer/components/+workloads-overview/overview-statuses.tsx @@ -27,7 +27,7 @@ export class OverviewStatuses extends React.Component { @autobind() renderWorkload(resource: KubeResource): React.ReactElement { const store = workloadStores[resource]; - const items = store.getAllByNs(namespaceStore.contextNs); + const items = store.getAllByNs(namespaceStore.getContextNamespaces()); return (
diff --git a/src/renderer/components/+workloads-overview/overview.tsx b/src/renderer/components/+workloads-overview/overview.tsx index 318ad53f77..351b57462c 100644 --- a/src/renderer/components/+workloads-overview/overview.tsx +++ b/src/renderer/components/+workloads-overview/overview.tsx @@ -17,81 +17,65 @@ import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; import { Events } from "../+events"; import { KubeObjectStore } from "../../kube-object.store"; import { isAllowedResource } from "../../../common/rbac"; +import { namespaceStore } from "../+namespaces/namespace.store"; interface Props extends RouteComponentProps { } @observer export class WorkloadsOverview extends React.Component { + @observable isLoading = false; @observable isUnmounting = false; async componentDidMount() { - const stores: KubeObjectStore[] = []; + const stores: KubeObjectStore[] = [ + isAllowedResource("pods") && podsStore, + isAllowedResource("deployments") && deploymentStore, + isAllowedResource("daemonsets") && daemonSetStore, + isAllowedResource("statefulsets") && statefulSetStore, + isAllowedResource("replicasets") && replicaSetStore, + isAllowedResource("jobs") && jobStore, + isAllowedResource("cronjobs") && cronJobStore, + isAllowedResource("events") && eventStore, + ].filter(Boolean); - if (isAllowedResource("pods")) { - stores.push(podsStore); - } + const unsubscribeMap = new Map void>(); - if (isAllowedResource("deployments")) { - stores.push(deploymentStore); - } + const loadStores = async () => { + this.isLoading = true; - if (isAllowedResource("daemonsets")) { - stores.push(daemonSetStore); - } + for (const store of stores) { + if (this.isUnmounting) break; - if (isAllowedResource("statefulsets")) { - stores.push(statefulSetStore); - } + try { + await store.loadAll(); + unsubscribeMap.get(store)?.(); // unsubscribe previous watcher + unsubscribeMap.set(store, store.subscribe()); + } catch (error) { + console.error("loading store error", error); + } + } + this.isLoading = false; + }; - if (isAllowedResource("replicasets")) { - stores.push(replicaSetStore); - } + namespaceStore.onContextChange(loadStores, { + fireImmediately: true, + }); - if (isAllowedResource("jobs")) { - stores.push(jobStore); - } - - if (isAllowedResource("cronjobs")) { - stores.push(cronJobStore); - } - - if (isAllowedResource("events")) { - stores.push(eventStore); - } - - const unsubscribeList: Array<() => void> = []; - - for (const store of stores) { - await store.loadAll(); - unsubscribeList.push(store.subscribe()); - } - - await when(() => this.isUnmounting); - unsubscribeList.forEach(dispose => dispose()); + await when(() => this.isUnmounting && !this.isLoading); + unsubscribeMap.forEach(dispose => dispose()); + unsubscribeMap.clear(); } componentWillUnmount() { this.isUnmounting = true; } - get contents() { - return ( - <> - - { isAllowedResource("events") && } - - ); - } - render() { return (
- {this.contents} + + {isAllowedResource("events") && }
); } diff --git a/src/renderer/components/+workloads-pods/pods.tsx b/src/renderer/components/+workloads-pods/pods.tsx index 2296b98317..a59c9d79d2 100644 --- a/src/renderer/components/+workloads-pods/pods.tsx +++ b/src/renderer/components/+workloads-pods/pods.tsx @@ -19,8 +19,7 @@ import { lookupApiLink } from "../../api/kube-api"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { Badge } from "../badge"; - -enum sortBy { +enum columnId { name = "name", namespace = "namespace", containers = "containers", @@ -77,15 +76,15 @@ export class Pods extends React.Component { tableId = "workloads_pods" isConfigurable sortingCallbacks={{ - [sortBy.name]: (pod: Pod) => pod.getName(), - [sortBy.namespace]: (pod: Pod) => pod.getNs(), - [sortBy.containers]: (pod: Pod) => pod.getContainers().length, - [sortBy.restarts]: (pod: Pod) => pod.getRestartsCount(), - [sortBy.owners]: (pod: Pod) => pod.getOwnerRefs().map(ref => ref.kind), - [sortBy.qos]: (pod: Pod) => pod.getQosClass(), - [sortBy.node]: (pod: Pod) => pod.getNodeName(), - [sortBy.age]: (pod: Pod) => pod.metadata.creationTimestamp, - [sortBy.status]: (pod: Pod) => pod.getStatusMessage(), + [columnId.name]: (pod: Pod) => pod.getName(), + [columnId.namespace]: (pod: Pod) => pod.getNs(), + [columnId.containers]: (pod: Pod) => pod.getContainers().length, + [columnId.restarts]: (pod: Pod) => pod.getRestartsCount(), + [columnId.owners]: (pod: Pod) => pod.getOwnerRefs().map(ref => ref.kind), + [columnId.qos]: (pod: Pod) => pod.getQosClass(), + [columnId.node]: (pod: Pod) => pod.getNodeName(), + [columnId.age]: (pod: Pod) => pod.metadata.creationTimestamp, + [columnId.status]: (pod: Pod) => pod.getStatusMessage(), }} searchFilters={[ (pod: Pod) => pod.getSearchFields(), @@ -95,16 +94,16 @@ export class Pods extends React.Component { ]} renderHeaderTitle="Pods" renderTableHeader={[ - { title: "Name", className: "name", sortBy: sortBy.name }, - { className: "warning", showWithColumn: "name" }, - { title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, - { title: "Containers", className: "containers", sortBy: sortBy.containers }, - { title: "Restarts", className: "restarts", sortBy: sortBy.restarts }, - { title: "Controlled By", className: "owners", sortBy: sortBy.owners }, - { title: "Node", className: "node", sortBy: sortBy.node }, - { title: "QoS", className: "qos", sortBy: sortBy.qos }, - { title: "Age", className: "age", sortBy: sortBy.age }, - { title: "Status", className: "status", sortBy: sortBy.status }, + { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, + { className: "warning", showWithColumn: columnId.name }, + { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, + { title: "Containers", className: "containers", sortBy: columnId.containers, id: columnId.containers }, + { title: "Restarts", className: "restarts", sortBy: columnId.restarts, id: columnId.restarts }, + { title: "Controlled By", className: "owners", sortBy: columnId.owners, id: columnId.owners }, + { title: "Node", className: "node", sortBy: columnId.node, id: columnId.node }, + { title: "QoS", className: "qos", sortBy: columnId.qos, id: columnId.qos }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, ]} renderTableContents={(pod: Pod) => [ , diff --git a/src/renderer/components/item-object-list/item-list-layout.scss b/src/renderer/components/item-object-list/item-list-layout.scss index 9bdc2f943d..0008ffd527 100644 --- a/src/renderer/components/item-object-list/item-list-layout.scss +++ b/src/renderer/components/item-object-list/item-list-layout.scss @@ -36,3 +36,14 @@ } } +.ItemListLayoutVisibilityMenu { + .MenuItem { + padding: 0; + } + + .Checkbox { + width: 100%; + padding: var(--spacing); + cursor: pointer; + } +} diff --git a/src/renderer/components/item-object-list/item-list-layout.tsx b/src/renderer/components/item-object-list/item-list-layout.tsx index 38e0e0218d..aaeb7438ea 100644 --- a/src/renderer/components/item-object-list/item-list-layout.tsx +++ b/src/renderer/components/item-object-list/item-list-layout.tsx @@ -1,12 +1,11 @@ import "./item-list-layout.scss"; -import "./table-menu.scss"; import groupBy from "lodash/groupBy"; import React, { ReactNode } from "react"; -import { computed, observable, reaction, toJS, when } from "mobx"; +import { computed, IReactionDisposer, observable, reaction, toJS } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { ConfirmDialog, ConfirmDialogParams } from "../confirm-dialog"; -import { TableSortCallback, Table, TableCell, TableCellProps, TableHead, TableProps, TableRow, TableRowProps } from "../table"; +import { Table, TableCell, TableCellProps, TableHead, TableProps, TableRow, TableRowProps, TableSortCallback } from "../table"; import { autobind, createStorage, cssNames, IClassName, isReactNode, noop, prevDefault, stopPropagation } from "../../utils"; import { AddRemoveButtons, AddRemoveButtonsProps } from "../add-remove-buttons"; import { NoItems } from "../no-items"; @@ -19,11 +18,10 @@ import { PageFiltersList } from "./page-filters-list"; import { PageFiltersSelect } from "./page-filters-select"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select"; import { themeStore } from "../../theme.store"; -import { MenuActions} from "../menu/menu-actions"; +import { MenuActions } from "../menu/menu-actions"; import { MenuItem } from "../menu"; import { Checkbox } from "../checkbox"; import { userStore } from "../../../common/user-store"; -import logger from "../../../main/logger"; // todo: refactor, split to small re-usable components @@ -98,10 +96,11 @@ interface ItemListLayoutUserSettings { @observer export class ItemListLayout extends React.Component { static defaultProps = defaultProps as object; - @observable hiddenColumnNames = new Set(); + + private watchDisposers: IReactionDisposer[] = []; + @observable isUnmounting = false; - // default user settings (ui show-hide tweaks mostly) @observable userSettings: ItemListLayoutUserSettings = { showAppliedFilters: false, }; @@ -120,31 +119,54 @@ export class ItemListLayout extends React.Component { } async componentDidMount() { - const { store, dependentStores, isClusterScoped, tableId } = this.props; + const { isClusterScoped, isConfigurable, tableId } = this.props; - if (this.canBeConfigured) this.hiddenColumnNames = new Set(userStore.preferences?.hiddenTableColumns?.[tableId]); + if (isConfigurable && !tableId) { + throw new Error("[ItemListLayout]: configurable list require props.tableId to be specified"); + } - const stores = [store, ...dependentStores]; + this.loadStores(); - if (!isClusterScoped) stores.push(namespaceStore); - - try { - stores.map(store => store.reset()); - await Promise.all(stores.map(store => store.loadAll())); - const subscriptions = stores.map(store => store.subscribe()); - - await when(() => this.isUnmounting); - subscriptions.forEach(dispose => dispose()); // unsubscribe all - } catch (error) { - console.log("catched", error); + if (!isClusterScoped) { + disposeOnUnmount(this, [ + namespaceStore.onContextChange(() => this.loadStores()) + ]); } } - componentWillUnmount() { + async componentWillUnmount() { this.isUnmounting = true; - const { store, isSelectable } = this.props; + this.unsubscribeStores(); + } - if (isSelectable) store.resetSelection(); + @computed get stores() { + const { store, dependentStores } = this.props; + + return new Set([store, ...dependentStores]); + } + + async loadStores() { + this.unsubscribeStores(); // reset first + + // load + for (const store of this.stores) { + if (this.isUnmounting) { + this.unsubscribeStores(); + break; + } + + try { + await store.loadAll(); + this.watchDisposers.push(store.subscribe()); + } catch (error) { + console.error("loading store error", error); + } + } + } + + unsubscribeStores() { + this.watchDisposers.forEach(dispose => dispose()); + this.watchDisposers.length = 0; } private filterCallbacks: { [type: string]: ItemsFilter } = { @@ -180,9 +202,7 @@ export class ItemListLayout extends React.Component { }; @computed get isReady() { - const { isReady, store } = this.props; - - return typeof isReady == "boolean" ? isReady : store.isLoaded; + return this.props.isReady ?? this.props.store.isLoaded; } @computed get filters() { @@ -228,42 +248,6 @@ export class ItemListLayout extends React.Component { return this.applyFilters(filterItems, allItems); } - updateColumnFilter(checkboxValue: boolean, columnName: string) { - if (checkboxValue){ - this.hiddenColumnNames.delete(columnName); - } else { - this.hiddenColumnNames.add(columnName); - } - - if (this.canBeConfigured) { - userStore.preferences.hiddenTableColumns[this.props.tableId] = Array.from(this.hiddenColumnNames); - } - } - - columnIsVisible(index: number): boolean { - const {renderTableHeader} = this.props; - - if (!this.canBeConfigured) return true; - - return !this.hiddenColumnNames.has(renderTableHeader[index].showWithColumn ?? renderTableHeader[index].className); - } - - get canBeConfigured(): boolean { - const { isConfigurable, tableId, renderTableHeader } = this.props; - - if (!isConfigurable || !tableId) { - return false; - } - - if (!renderTableHeader?.every(({ className }) => className)) { - logger.warning("[ItemObjectList]: cannot configure an object list without all headers being identifiable"); - - return false; - } - - return true; - } - @autobind() getRow(uid: string) { const { @@ -295,20 +279,18 @@ export class ItemListLayout extends React.Component { /> )} { - renderTableContents(item) - .map((content, index) => { - const cellProps: TableCellProps = isReactNode(content) ? { children: content } : content; + renderTableContents(item).map((content, index) => { + const cellProps: TableCellProps = isReactNode(content) ? { children: content } : content; + const headCell = renderTableHeader?.[index]; - if (copyClassNameFromHeadCells && renderTableHeader) { - const headCell = renderTableHeader[index]; + if (copyClassNameFromHeadCells && headCell) { + cellProps.className = cssNames(cellProps.className, headCell.className); + } - if (headCell) { - cellProps.className = cssNames(cellProps.className, headCell.className); - } - } - - return this.columnIsVisible(index) ? : null; - }) + if (!headCell || !this.isHiddenColumn(headCell)) { + return ; + } + }) } {renderItemMenu && ( @@ -347,16 +329,11 @@ export class ItemListLayout extends React.Component { return; } - return ; + return ; } renderNoItems() { - const { allItems, items, filters } = this; - const allItemsCount = allItems.length; - const itemsCount = items.length; - const isFiltered = filters.length > 0 && allItemsCount > itemsCount; - - if (isFiltered) { + if (this.filters.length > 0) { return ( No items found. @@ -369,7 +346,7 @@ export class ItemListLayout extends React.Component { ); } - return ; + return ; } renderHeaderContent(placeholders: IHeaderPlaceholders): ReactNode { @@ -413,12 +390,12 @@ export class ItemListLayout extends React.Component { title:
{title}
, info: this.renderInfo(), filters: <> - {!isClusterScoped && } + {!isClusterScoped && } + }}/> , - search: , + search: , }; let header = this.renderHeaderContent(placeholders); @@ -442,10 +419,40 @@ export class ItemListLayout extends React.Component { ); } + renderTableHeader() { + const { renderTableHeader, isSelectable, isConfigurable, store } = this.props; + + if (!renderTableHeader) { + return; + } + + return ( + + {isSelectable && ( + store.toggleSelectionAll(this.items))} + /> + )} + {renderTableHeader.map((cellProps, index) => { + if (!this.isHiddenColumn(cellProps)) { + return ; + } + })} + {isConfigurable && ( + + {this.renderColumnVisibilityMenu()} + + )} + + ); + } + renderList() { const { - isSelectable, tableProps = {}, renderTableHeader, renderItemMenu, - store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem + store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem, + tableProps = {}, } = this.props; const { isReady, removeItemsDialog, items } = this; const { selectedItems } = store; @@ -454,7 +461,7 @@ export class ItemListLayout extends React.Component { return (
{!isReady && ( - + )} {isReady && ( { className: cssNames("box grow", tableProps.className, themeStore.activeTheme.type), })} > - {renderTableHeader && ( - - {isSelectable && ( - store.toggleSelectionAll(items))} - /> - )} - {renderTableHeader.map((cellProps, index) => this.columnIsVisible(index) ? : null)} - { renderItemMenu && - - {this.canBeConfigured && this.renderColumnMenu()} - - } - - )} + {this.renderTableHeader()} { !virtual && items.map(item => this.getRow(item.getId())) } @@ -502,24 +493,47 @@ export class ItemListLayout extends React.Component { ); } - renderColumnMenu() { - const { renderTableHeader} = this.props; + @computed get hiddenColumns() { + return userStore.getHiddenTableColumns(this.props.tableId); + } + + isHiddenColumn({ id: columnId, showWithColumn }: TableCellProps): boolean { + if (!this.props.isConfigurable) { + return false; + } + + return this.hiddenColumns.has(columnId) || ( + showWithColumn && this.hiddenColumns.has(showWithColumn) + ); + } + + updateColumnVisibility({ id: columnId }: TableCellProps, isVisible: boolean) { + const hiddenColumns = new Set(this.hiddenColumns); + + if (!isVisible) { + hiddenColumns.add(columnId); + } else { + hiddenColumns.delete(columnId); + } + + userStore.setHiddenTableColumns(this.props.tableId, hiddenColumns); + } + + renderColumnVisibilityMenu() { + const { renderTableHeader } = this.props; return ( - + {renderTableHeader.map((cellProps, index) => ( - !cellProps.showWithColumn && + !cellProps.showWithColumn && ( - `} - className = "MenuCheckbox" - value ={!this.hiddenColumnNames.has(cellProps.className)} - onChange = {(v) => this.updateColumnFilter(v, cellProps.className)} + `} + value={!this.isHiddenColumn(cellProps)} + onChange={isVisible => this.updateColumnVisibility(cellProps, isVisible)} /> + ) ))} ); diff --git a/src/renderer/components/item-object-list/page-filters.store.ts b/src/renderer/components/item-object-list/page-filters.store.ts index 9bff008aa6..d931cd2575 100644 --- a/src/renderer/components/item-object-list/page-filters.store.ts +++ b/src/renderer/components/item-object-list/page-filters.store.ts @@ -34,14 +34,14 @@ export class PageFiltersStore { namespaceStore.setContext(filteredNs); } }), - reaction(() => namespaceStore.contextNs.toJS(), contextNs => { + namespaceStore.onContextChange(namespaces => { const filteredNs = this.getValues(FilterType.NAMESPACE); - const isChanged = contextNs.length !== filteredNs.length; + const isChanged = namespaces.length !== filteredNs.length; if (isChanged) { this.filters.replace([ ...this.filters.filter(({ type }) => type !== FilterType.NAMESPACE), - ...contextNs.map(ns => ({ type: FilterType.NAMESPACE, value: ns })), + ...namespaces.map(ns => ({ type: FilterType.NAMESPACE, value: ns })), ]); } }, { diff --git a/src/renderer/components/item-object-list/table-menu.scss b/src/renderer/components/item-object-list/table-menu.scss deleted file mode 100644 index b7e41f54ca..0000000000 --- a/src/renderer/components/item-object-list/table-menu.scss +++ /dev/null @@ -1,4 +0,0 @@ -.MenuCheckbox { - width: 100%; - height: 100%; -} diff --git a/src/renderer/components/table/table-cell.tsx b/src/renderer/components/table/table-cell.tsx index 97335078f1..81e2f9f85f 100644 --- a/src/renderer/components/table/table-cell.tsx +++ b/src/renderer/components/table/table-cell.tsx @@ -9,13 +9,14 @@ import { Checkbox } from "../checkbox"; export type TableCellElem = React.ReactElement; export interface TableCellProps extends React.DOMAttributes { + id?: string; // used for configuration visibility of columns className?: string; title?: ReactNode; checkbox?: boolean; // render cell with a checkbox isChecked?: boolean; // mark checkbox as checked or not renderBoolean?: boolean; // show "true" or "false" for all of the children elements are "typeof boolean" sortBy?: TableSortBy; // column name, must be same as key in sortable object
- showWithColumn?: string // className of another column, if it is not empty the current column is not shown in the filter menu, visibility of this one is the same as a specified column, applicable to headers only + showWithColumn?: string // id of the column which follow same visibility rules _sorting?: Partial; //
sorting state, don't use this prop outside (!) _sort?(sortBy: TableSortBy): void; //
sort function, don't use this prop outside (!) _nowrap?: boolean; // indicator, might come from parent , don't use this prop outside (!) @@ -73,7 +74,7 @@ export class TableCell extends React.Component { const content = displayBooleans(displayBoolean, title || children); return ( -
+
{this.renderCheckbox()} {_nowrap ?
{content}
: content} {this.renderSortIcon()} diff --git a/src/renderer/item.store.ts b/src/renderer/item.store.ts index 2105954d32..eccd2b52df 100644 --- a/src/renderer/item.store.ts +++ b/src/renderer/item.store.ts @@ -9,7 +9,7 @@ export interface ItemObject { @autobind() export abstract class ItemStore { - abstract loadAll(): Promise; + abstract loadAll(...args: any[]): Promise; protected defaultSorting = (item: T) => item.getName(); @@ -40,8 +40,7 @@ export abstract class ItemStore { if (item) { return item; - } - else { + } else { const items = this.sortItems([...this.items, newItem]); this.items.replace(items); @@ -83,8 +82,7 @@ export abstract class ItemStore { const index = this.items.findIndex(item => item === existingItem); this.items.splice(index, 1, item); - } - else { + } else { let items = [...this.items, item]; if (sortItems) items = this.sortItems(items); @@ -130,8 +128,7 @@ export abstract class ItemStore { toggleSelection(item: T) { if (this.isSelected(item)) { this.unselect(item); - } - else { + } else { this.select(item); } } @@ -142,8 +139,7 @@ export abstract class ItemStore { if (allSelected) { visibleItems.forEach(this.unselect); - } - else { + } else { visibleItems.forEach(this.select); } } diff --git a/src/renderer/kube-object.store.ts b/src/renderer/kube-object.store.ts index bb2fffd819..956f5aa5f6 100644 --- a/src/renderer/kube-object.store.ts +++ b/src/renderer/kube-object.store.ts @@ -1,3 +1,4 @@ +import type { Cluster } from "../main/cluster"; import { action, observable, reaction } from "mobx"; import { autobind } from "./utils"; import { KubeObject } from "./api/kube-object"; @@ -6,7 +7,11 @@ import { ItemStore } from "./item.store"; import { apiManager } from "./api/api-manager"; import { IKubeApiQueryParams, KubeApi } from "./api/kube-api"; import { KubeJsonApiData } from "./api/kube-json-api"; -import { getHostedCluster } from "../common/cluster-store"; + +export interface KubeObjectStoreLoadingParams { + namespaces: string[]; + api?: KubeApi; +} @autobind() export abstract class KubeObjectStore extends ItemStore { @@ -71,14 +76,26 @@ export abstract class KubeObjectStore extends ItemSt } } - protected async loadItems(allowedNamespaces?: string[]): Promise { - if (!this.api.isNamespaced || !allowedNamespaces) { - return this.api.list({}, this.query); - } else { - return Promise - .all(allowedNamespaces.map(namespace => this.api.list({ namespace }))) - .then(items => items.flat()); + protected async resolveCluster(): Promise { + const { getHostedCluster } = await import("../common/cluster-store"); + + return getHostedCluster(); + } + + protected async loadItems({ namespaces, api }: KubeObjectStoreLoadingParams): Promise { + const cluster = await this.resolveCluster(); + + if (cluster.isAllowedResource(api.kind)) { + if (api.isNamespaced) { + return Promise + .all(namespaces.map(namespace => api.list({ namespace }))) + .then(items => items.flat()); + } + + return api.list({}, this.query); } + + return []; } protected filterItemsOnLoad(items: T[]) { @@ -86,30 +103,35 @@ export abstract class KubeObjectStore extends ItemSt } @action - async loadAll() { + async loadAll({ namespaces: contextNamespaces }: { namespaces?: string[] } = {}) { this.isLoading = true; - let items: T[]; try { - const { allowedNamespaces, accessibleNamespaces, isAdmin } = getHostedCluster(); + if (!contextNamespaces) { + const { namespaceStore } = await import("./components/+namespaces/namespace.store"); - if (isAdmin && accessibleNamespaces.length == 0) { - items = await this.loadItems(); - } else { - items = await this.loadItems(allowedNamespaces); + contextNamespaces = namespaceStore.getContextNamespaces(); } + let items = await this.loadItems({ namespaces: contextNamespaces, api: this.api }); + items = this.filterItemsOnLoad(items); - } finally { - if (items) { - items = this.sortItems(items); - this.items.replace(items); - } - this.isLoading = false; + items = this.sortItems(items); + + this.items.replace(items); this.isLoaded = true; + } catch (error) { + console.error("Loading store items failed", { error, store: this }); + this.resetOnError(error); + } finally { + this.isLoading = false; } } + protected resetOnError(error: any) { + if (error) this.reset(); + } + protected async loadItem(params: { name: string; namespace?: string }): Promise { return this.api.get(params); } @@ -194,7 +216,7 @@ export abstract class KubeObjectStore extends ItemSt // create latest non-observable copy of items to apply updates in one action (==single render) const items = this.items.toJS(); - for (const {type, object} of this.eventsBuffer.clear()) { + for (const { type, object } of this.eventsBuffer.clear()) { const index = items.findIndex(item => item.getId() === object.metadata?.uid); const item = items[index]; const api = apiManager.getApiByKind(object.kind, object.apiVersion); From 9da349ce42aedd714904e7a4eb17b093eb7fb72e Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 22 Jan 2021 16:51:42 +0200 Subject: [PATCH 5/5] Display CPU usage percentage with 2 decimal points (#2000) Signed-off-by: Alex Culliere --- src/renderer/components/+nodes/nodes.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/+nodes/nodes.tsx b/src/renderer/components/+nodes/nodes.tsx index edfa5c4026..1ca12343b5 100644 --- a/src/renderer/components/+nodes/nodes.tsx +++ b/src/renderer/components/+nodes/nodes.tsx @@ -51,6 +51,10 @@ export class Nodes extends React.Component { if (!metrics || !metrics[1]) return ; const usage = metrics[0]; const cores = metrics[1]; + const cpuUsagePercent = Math.ceil(usage * 100) / cores; + const cpuUsagePercentLabel: String = cpuUsagePercent % 1 === 0 + ? cpuUsagePercent.toString() + : cpuUsagePercent.toFixed(2); return ( { value={usage} tooltip={{ preferredPositions: TooltipPosition.BOTTOM, - children: `CPU: ${Math.ceil(usage * 100) / cores}\%, cores: ${cores}` + children: `CPU: ${cpuUsagePercentLabel}\%, cores: ${cores}` }} /> );