From 5c8ca2c038c56ce1064a2da61946a88c0c500c3d Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Wed, 16 Feb 2022 16:00:21 +0200 Subject: [PATCH] Implement a lot of technical requirements for behavioural unit tests Note: the crux of this was to make routing env-agnostic, and not based on URLs as magic strings, but instead something type-enforced. Note: extension-based routes comply to same exact interface by "late-registering" their routes when installed. Routes are just injectables. Note: another chunk of global shared state is no more. Note: a lot of explicit side effects have been cornered to injectables. Note: a lot of stuff has become reactive as part if this. Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen --- package.json | 5 +- src/common/__tests__/base-store.test.ts | 10 +- src/common/__tests__/cluster-store.test.ts | 11 +- src/common/__tests__/hotbar-store.test.ts | 18 +- src/common/__tests__/user-store.test.ts | 30 +- .../general-catalog-entities.injectable.ts | 16 + .../general-catalog-entity-injection-token.ts | 10 + .../catalog-catalog-entity.injectable.ts | 41 +++ .../preferences-catalog-entity.injectable.ts | 41 +++ .../welcome-catalog-entity.injectable.ts | 41 +++ .../cluster-store/cluster-store.injectable.ts | 2 + .../navigate-to-front-page.injectable.ts | 13 + .../navigate-to-route-injection-token.ts | 52 +++ .../navigate-to-url-injection-token.ts | 16 + .../navigation-ipc-channel.ts | 9 + .../route-injection-token.ts | 20 ++ .../add-cluster-route.injectable.ts | 21 ++ .../navigate-to-add-cluster.injectable.ts | 20 ++ .../catalog/catalog-route.injectable.ts | 26 ++ .../catalog/navigate-to-catalog.injectable.ts | 25 ++ .../cluster-view-route.injectable.ts | 21 ++ .../navigate-to-cluster-view.injectable.ts | 23 ++ .../config-maps-route.injectable.ts | 25 ++ .../navigate-to-config-maps.injectable.ts | 20 ++ ...zontal-pod-autoscalers-route.injectable.ts | 25 ++ ...o-horizontal-pod-autoscalers.injectable.ts | 20 ++ .../limit-ranges-route.injectable.ts | 28 ++ .../navigate-to-limit-ranges.injectable.ts | 20 ++ ...te-to-pod-disruption-budgets.injectable.ts | 20 ++ ...pod-disruption-budgets-route.injectable.ts | 25 ++ .../navigate-to-resource-quotas.injectable.ts | 20 ++ .../resource-quotas-route.injectable.ts | 25 ++ .../secrets/navigate-to-secrets.injectable.ts | 20 ++ .../secrets/secrets-route.injectable.ts | 25 ++ .../crd-list/crd-list-route.injectable.ts | 21 ++ .../navigate-to-crd-list.injectable.ts | 20 ++ .../custom-resources-route.injectable.ts | 26 ++ ...navigate-to-custom-resources.injectable.ts | 21 ++ .../cluster/events/events-route.injectable.ts | 25 ++ .../events/navigate-to-events.injectable.ts | 20 ++ .../charts/helm-charts-route.injectable.ts | 26 ++ .../navigate-to-helm-charts.injectable.ts | 25 ++ .../helm-releases-route.injectable.ts | 26 ++ .../navigate-to-helm-releases.injectable.ts | 23 ++ .../namespaces/namespaces-route.injectable.ts | 25 ++ .../navigate-to-namespaces.injectable.ts | 20 ++ .../endpoints/endpoints-route.injectable.ts | 25 ++ .../navigate-to-endpoints.injectable.ts | 20 ++ .../ingresses/ingresses-route.injectable.ts | 25 ++ .../navigate-to-ingresses.injectable.ts | 20 ++ ...navigate-to-network-policies.injectable.ts | 20 ++ .../network-policies-route.injectable.ts | 25 ++ .../navigate-to-port-forwards.injectable.ts | 23 ++ .../port-forwards-route.injectable.ts | 25 ++ .../navigate-to-services.injectable.ts | 20 ++ .../services/services-route.injectable.ts | 25 ++ .../nodes/navigate-to-nodes.injectable.ts | 20 ++ .../cluster/nodes/nodes-route.injectable.ts | 25 ++ .../cluster-overview-route.injectable.ts | 25 ++ ...navigate-to-cluster-overview.injectable.ts | 20 ++ ...-to-persistent-volume-claims.injectable.ts | 20 ++ ...rsistent-volume-claims-route.injectable.ts | 25 ++ ...vigate-to-persistent-volumes.injectable.ts | 20 ++ .../persistent-volumes-route.injectable.ts | 25 ++ .../navigate-to-storage-classes.injectable.ts | 20 ++ .../storage-classes-route.injectable.ts | 25 ++ .../cluster-role-bindings-route.injectable.ts | 25 ++ ...ate-to-cluster-role-bindings.injectable.ts | 20 ++ .../cluster-roles-route.injectable.ts | 25 ++ .../navigate-to-cluster-roles.injectable.ts | 20 ++ ...ate-to-pod-security-policies.injectable.ts | 20 ++ .../pod-security-policies-route.injectable.ts | 25 ++ .../navigate-to-role-bindings.injectable.ts | 20 ++ .../role-bindings-route.injectable.ts | 25 ++ .../roles/navigate-to-roles.injectable.ts | 20 ++ .../roles/roles-route.injectable.ts | 25 ++ ...navigate-to-service-accounts.injectable.ts | 20 ++ .../service-accounts-route.injectable.ts | 25 ++ .../cron-jobs/cron-jobs-route.injectable.ts | 25 ++ .../navigate-to-cron-jobs.injectable.ts | 20 ++ .../daemonsets/daemonsets-route.injectable.ts | 25 ++ .../navigate-to-daemonsets.injectable.ts | 20 ++ .../deployments-route.injectable.ts | 25 ++ .../navigate-to-deployments.injectable.ts | 20 ++ .../workloads/jobs/jobs-route.injectable.ts | 25 ++ .../jobs/navigate-to-jobs.injectable.ts | 20 ++ ...vigate-to-workloads-overview.injectable.ts | 20 ++ .../workloads-overview-route.injectable.ts | 21 ++ .../pods/navigate-to-pods.injectable.ts | 20 ++ .../workloads/pods/pods-route.injectable.ts | 25 ++ .../navigate-to-replicasets.injectable.ts | 20 ++ .../replicasets-route.injectable.ts | 25 ++ .../navigate-to-statefulsets.injectable.ts | 20 ++ .../statefulsets-route.injectable.ts | 25 ++ .../entity-settings-route.injectable.ts | 25 ++ .../navigate-to-entity-settings.injectable.ts | 23 ++ .../extensions/extensions-route.injectable.ts | 21 ++ .../navigate-to-extensions.injectable.ts | 20 ++ .../app/app-preferences-route.injectable.ts | 21 ++ .../navigate-to-app-preferences.injectable.ts | 20 ++ .../editor-preferences-route.injectable.ts | 21 ++ ...vigate-to-editor-preferences.injectable.ts | 20 ++ .../extension-preferences-route.injectable.ts | 21 ++ ...ate-to-extension-preferences.injectable.ts | 20 ++ ...kubernetes-preferences-route.injectable.ts | 21 ++ ...te-to-kubernetes-preferences.injectable.ts | 20 ++ .../navigate-to-preferences.injectable.ts | 14 + ...avigate-to-proxy-preferences.injectable.ts | 20 ++ .../proxy-preferences-route.injectable.ts | 21 ++ ...ate-to-telemetry-preferences.injectable.ts | 20 ++ .../telemetry-preferences-route.injectable.ts | 21 ++ ...gate-to-terminal-preferences.injectable.ts | 20 ++ .../terminal-preferences-route.injectable.ts | 21 ++ .../welcome/navigate-to-welcome.injectable.ts | 20 ++ .../welcome/welcome-route.injectable.ts | 21 ++ ...at-all-routes-have-route-component.test.ts | 46 +++ src/common/fs/path-exists.injectable.ts | 15 + src/common/fs/read-file-buffer.injectable.ts | 15 + src/common/fs/read-file.injectable.ts | 4 +- src/common/fs/read-json-file.injectable.ts | 5 +- src/common/fs/write-json-file.injectable.ts | 4 +- src/common/hotbar-store.injectable.ts | 14 +- src/common/hotbar-store.ts | 10 +- .../create-channel/create-channel.ts | 2 +- src/common/k8s-api/endpoints/crd.api.ts | 10 +- src/common/logger.injectable.ts | 13 + src/common/logger.ts | 10 +- src/common/rbac.ts | 10 + src/common/routes/add-cluster.ts | 13 - src/common/routes/catalog.ts | 30 -- src/common/routes/cluster-view.ts | 18 - src/common/routes/cluster.ts | 13 - src/common/routes/config-maps.ts | 16 - src/common/routes/config.ts | 26 -- src/common/routes/crd.ts | 31 -- src/common/routes/endpoints.ts | 16 - src/common/routes/entity-settings.ts | 17 - src/common/routes/events.ts | 13 - src/common/routes/extensions.ts | 13 - src/common/routes/helm-charts.ts | 19 - src/common/routes/helm.ts | 13 - src/common/routes/hpa.ts | 16 - src/common/routes/index.ts | 39 --- src/common/routes/ingresses.ts | 16 - src/common/routes/limit-ranges.ts | 16 - src/common/routes/namespaces.ts | 16 - src/common/routes/network-policies.ts | 16 - src/common/routes/network.ts | 24 -- src/common/routes/nodes.ts | 16 - src/common/routes/pod-disruption-budgets.ts | 16 - src/common/routes/port-forwards.ts | 17 - src/common/routes/preferences.ts | 47 --- src/common/routes/releases.ts | 19 - src/common/routes/resource-quotas.ts | 16 - src/common/routes/secrets.ts | 16 - src/common/routes/services.ts | 16 - src/common/routes/storage-classes.ts | 16 - src/common/routes/storage.ts | 20 -- src/common/routes/user-management.ts | 63 ---- src/common/routes/volume-claims.ts | 16 - src/common/routes/volumes.ts | 16 - src/common/routes/welcome.ts | 13 - src/common/routes/workloads.ts | 93 ----- .../user-store/user-store.injectable.ts | 1 + src/common/utils/buildUrl.ts | 22 +- .../utils/is-allowed-resource.injectable.ts | 12 +- ...r-extension-api-with-modifications.test.ts | 4 +- ...ct-for-extension-api-with-modifications.ts | 5 +- .../legacy-global-di-for-extension-api.ts | 27 +- ...ile-system-provisioner-store.injectable.ts | 2 + ...tension-installation-counter.injectable.ts | 12 + .../extension-loader.injectable.ts | 8 +- .../extension-loader/extension-loader.ts | 23 +- .../extension-registrator-injection-token.ts | 12 + .../update-extensions-state.injectable.ts | 2 +- .../extensions-store.injectable.ts | 1 + src/extensions/lens-extension.ts | 11 + src/extensions/lens-renderer-extension.ts | 56 ++- .../__tests__/page-registry.test.ts | 177 ---------- .../registries/page-menu-registry.ts | 37 +- src/extensions/registries/page-registry.ts | 99 +----- src/extensions/renderer-api/k8s-api.ts | 13 +- .../__test__/kubeconfig-sync.test.ts | 4 + src/main/catalog-sources/general.ts | 76 ---- src/main/catalog-sources/index.ts | 1 - ...ync-general-catalog-entities.injectable.ts | 25 ++ .../catalog-entity-registry.injectable.ts | 14 + src/main/getDi.ts | 4 +- src/main/getDiForUnitTesting.ts | 38 +- src/main/index.ts | 29 +- .../init-ipc-main-handlers.injectable.ts | 7 +- .../init-ipc-main-handlers.ts | 13 +- src/main/is-auto-update-enabled.injectable.ts | 14 + .../menu/get-app-menu-items.injectable.ts | 331 ++++++++++++++++++ src/main/menu/menu.ts | 308 +--------------- .../navigate-to-route.injectable.ts | 31 ++ .../navigate-to-url.injectable.ts | 23 ++ .../protocol-handler/__test__/router.test.ts | 7 +- .../routes/static-file-route.injectable.ts | 11 +- src/main/tray/tray.ts | 10 +- src/main/window-manager.injectable.ts | 14 + src/main/window-manager.ts | 6 +- src/migrations/hotbar-store/5.0.0-alpha.0.ts | 8 +- src/migrations/hotbar-store/5.0.0-beta.10.ts | 14 +- ...egister-ipc-channel-listener.injectable.ts | 25 ++ src/renderer/bootstrap.tsx | 31 +- .../add-cluster-route-component.injectable.ts | 21 ++ .../components/+add-cluster/add-cluster.tsx | 15 +- .../catalog-browse-tab.ts} | 3 +- ...-previous-active-tab-storage.injectable.ts | 2 +- .../catalog-route-component.injectable.ts | 21 ++ .../catalog-route-parameters.injectable.ts | 24 ++ .../components/+catalog/catalog.test.tsx | 67 +--- src/renderer/components/+catalog/catalog.tsx | 53 ++- ...ter-overview-route-component.injectable.ts | 21 ++ ...ster-overview-sidebar-items.injectable.tsx | 42 +++ .../components/+cluster/cluster-overview.tsx | 2 +- .../components/+cluster/sidebar-item.tsx | 37 -- ...-auto-scalers-sidebar-items.injectable.tsx | 37 ++ ...-autoscalers-route-component.injectable.ts | 21 ++ .../components/+config-autoscalers/hpa.tsx | 112 +++--- ...limit-ranges-route-component.injectable.ts | 21 ++ .../limit-ranges-sidebar-items.injectable.tsx | 38 ++ .../+config-limit-ranges/limit-ranges.tsx | 66 ++-- .../config-maps-route-component.injectable.ts | 21 ++ .../config-maps-sidebar-items.injectable.tsx | 38 ++ .../components/+config-maps/config-maps.tsx | 72 ++-- ...tion-budgets-route-component.injectable.ts | 21 ++ ...ption-budgets-sidebar-items.injectable.tsx | 38 ++ .../pod-disruption-budgets.tsx | 81 ++--- ...ource-quotas-route-component.injectable.ts | 23 ++ ...source-quotas-sidebar-items.injectable.tsx | 38 ++ .../resource-quotas.tsx | 12 +- .../secrets-route-component.injectable.ts | 21 ++ .../secrets-sidebar-items.injectable.tsx | 38 ++ .../components/+config-secrets/secrets.tsx | 12 +- .../config-sidebar-items.injectable.tsx | 35 ++ .../+config/route-tabs.injectable.ts | 91 ----- src/renderer/components/+config/route.tsx | 31 -- .../components/+config/sidebar-item.tsx | 45 --- .../crd-list-route-component.injectable.ts | 21 ++ .../components/+custom-resources/crd-list.tsx | 147 ++++---- .../+custom-resources/crd-resources.tsx | 163 +++++---- ...stom-resource-sidebar-items.injectable.tsx | 68 ++++ ...om-resources-route-component.injectable.ts | 21 ++ ...m-resources-route-parameters.injectable.ts | 24 ++ .../custom-resources.injectable.ts | 14 +- .../grouped-custom-resources.injectable.ts | 35 -- .../route-tabs.injectable.ts | 61 ---- .../components/+custom-resources/route.tsx | 22 -- .../+custom-resources/sidebar-item.tsx | 76 ---- ...-items-for-definition-groups.injectable.ts | 81 +++++ ...ity-settings-route-component.injectable.ts | 21 ++ ...ty-settings-route-parameters.injectable.ts | 23 ++ .../+entity-settings/entity-settings.tsx | 86 +++-- .../events-route-component.injectable.ts | 21 ++ .../events-sidebar-items.injectable.tsx | 43 +++ src/renderer/components/+events/events.tsx | 26 +- .../components/+events/sidebar-item.tsx | 37 -- .../extensions-route-component.injectable.ts | 21 ++ .../components/+extensions/extensions.tsx | 2 +- .../helm-charts-route-component.injectable.ts | 21 ++ ...helm-charts-route-parameters.injectable.ts | 24 ++ .../helm-charts-sidebar-items.injectable.tsx | 37 ++ .../components/+helm-charts/helm-charts.tsx | 52 ++- ...elm-releases-route-component.injectable.ts | 21 ++ ...lm-releases-route-parameters.injectable.ts | 24 ++ ...helm-releases-sidebar-items.injectable.tsx | 38 ++ .../release-route-parameters.injectable.ts | 31 -- .../release-details/release.injectable.ts | 7 +- .../components/+helm-releases/releases.tsx | 62 ++-- .../+helm/helm-sidebar-items.injectable.tsx | 35 ++ .../components/+helm/route-tabs.injectable.ts | 34 -- src/renderer/components/+helm/route.tsx | 31 -- .../components/+helm/sidebar-item.tsx | 44 --- .../namespaces-route-component.injectable.ts | 21 ++ .../namespaces-sidebar-items.injectable.tsx | 42 +++ src/renderer/components/+namespaces/route.tsx | 17 +- .../components/+namespaces/sidebar-item.tsx | 37 -- .../endpoints-route-component.injectable.ts | 21 ++ .../endpoints-sidebar-items.injectable.tsx | 37 ++ .../+network-endpoints/endpoints.tsx | 78 ++--- .../ingresses-route-component.injectable.ts | 21 ++ .../ingresses-sidebar-items.injectable.tsx | 38 ++ .../+network-ingresses/ingresses.tsx | 109 +++--- ...ork-policies-route-component.injectable.ts | 21 ++ ...work-policies-sidebar-items.injectable.tsx | 38 ++ .../+network-policies/network-policies.tsx | 68 ++-- ...ort-forwards-route-component.injectable.ts | 21 ++ ...rt-forwards-route-parameters.injectable.ts | 23 ++ ...port-forwards-sidebar-items.injectable.tsx | 39 +++ .../+network-port-forwards/port-forwards.tsx | 55 +-- .../service-port-component.tsx | 17 +- .../services-route-component.injectable.ts | 21 ++ .../services-sidebar-items.injectable.tsx | 38 ++ .../components/+network-services/services.tsx | 112 +++--- .../network-sidebar-items.injectable.tsx | 35 ++ src/renderer/components/+network/network.scss | 7 - .../+network/route-tabs.injectable.ts | 80 ----- src/renderer/components/+network/route.tsx | 33 -- .../components/+network/sidebar-item.tsx | 45 --- .../nodes-route-component.injectable.ts | 21 ++ .../+nodes/nodes-sidebar-items.injectable.tsx | 43 +++ src/renderer/components/+nodes/route.tsx | 11 +- .../components/+nodes/sidebar-item.tsx | 37 -- ...ity-policies-route-component.injectable.ts | 21 ++ ...rity-policies-sidebar-items.injectable.tsx | 38 ++ .../pod-security-policies.tsx | 67 ++-- ...-preferences-route-component.injectable.ts | 21 ++ .../components/+preferences/application.tsx | 185 +++++----- .../close-preferences.injectable.ts | 28 ++ ...-preferences-route-component.injectable.ts | 21 ++ .../components/+preferences/editor.tsx | 155 ++++---- ...-preference-item-registrator.injectable.ts | 54 +++ .../extension-preference-items.injectable.ts | 50 +++ ...-preferences-route-component.injectable.ts | 21 ++ .../+preferences/extension-settings.tsx | 8 +- ...-preference-item-registrator.injectable.ts | 54 +++ .../components/+preferences/extensions.tsx | 29 +- .../+preferences/kubeconfig-syncs.tsx | 27 +- .../+preferences/kubectl-binaries.tsx | 8 +- ...-preferences-route-component.injectable.ts | 21 ++ .../components/+preferences/kubernetes.tsx | 15 +- ...-preferences-navigation-item.injectable.ts | 37 ++ ...-preferences-navigation-item.injectable.ts | 37 ++ ...-preferences-navigation-item.injectable.ts | 51 +++ ...-preferences-navigation-item.injectable.ts | 40 +++ .../navigate-to-preference-tab.injectable.ts | 21 ++ .../preference-navigation-items.injectable.ts | 37 ++ .../preferences-navigation.tsx | 61 ++++ ...-preferences-navigation-item.injectable.ts | 37 ++ ...-preferences-navigation-item.injectable.ts | 49 +++ ...-preferences-navigation-item.injectable.ts | 37 ++ .../components/+preferences/preferences.tsx | 117 ++----- ...-preferences-route-component.injectable.ts | 21 ++ .../components/+preferences/proxy.tsx | 82 +++-- .../+preferences/sentry-dns-url.injectable.ts | 13 + .../telemetry-preference-items.injectable.ts | 50 +++ ...-preferences-route-component.injectable.ts | 21 ++ .../components/+preferences/telemetry.tsx | 91 +++-- ...-preferences-route-component.injectable.ts | 21 ++ .../components/+preferences/terminal.tsx | 139 +++++--- ...rage-classes-route-component.injectable.ts | 21 ++ ...orage-classes-sidebar-items.injectable.tsx | 38 ++ .../+storage-classes/storage-classes.tsx | 76 ++-- ...olume-claims-route-component.injectable.ts | 21 ++ ...volume-claims-sidebar-items.injectable.tsx | 38 ++ .../+storage-volume-claims/volume-claims.tsx | 119 ++++--- ...tent-volumes-route-component.injectable.ts | 22 ++ ...stent-volumes-sidebar-items.injectable.tsx | 38 ++ .../components/+storage-volumes/volumes.tsx | 107 +++--- .../+storage/route-tabs.injectable.ts | 62 ---- src/renderer/components/+storage/route.tsx | 34 -- .../components/+storage/sidebar-item.tsx | 45 --- .../storage-sidebar-items.injectable.tsx | 35 ++ src/renderer/components/+storage/storage.scss | 7 - ...ole-bindings-route-component.injectable.ts | 21 ++ ...role-bindings-sidebar-items.injectable.tsx | 38 ++ .../+cluster-role-bindings/view.tsx | 12 +- ...luster-roles-route-component.injectable.ts | 21 ++ ...cluster-roles-sidebar-items.injectable.tsx | 38 ++ .../+user-management/+cluster-roles/view.tsx | 12 +- ...ole-bindings-route-component.injectable.ts | 21 ++ ...role-bindings-sidebar-items.injectable.tsx | 38 ++ .../+user-management/+role-bindings/view.tsx | 12 +- .../roles-route-component.injectable.ts | 21 ++ .../+roles/roles-sidebar-items.injectable.tsx | 38 ++ .../+user-management/+roles/view.tsx | 12 +- ...ice-accounts-route-component.injectable.ts | 21 ++ ...vice-accounts-sidebar-items.injectable.tsx | 38 ++ .../+service-accounts/view.tsx | 12 +- .../+user-management/route-tabs.injectable.ts | 92 ----- .../components/+user-management/route.tsx | 31 -- .../+user-management/sidebar-item.tsx | 45 --- ...er-management-sidebar-items.injectable.tsx | 35 ++ .../+user-management/user-management.scss | 7 - .../get-welcome-menu-items.ts | 19 +- .../welcome-menu-items.injectable.ts | 2 + .../welcome-route-component.injectable.ts | 21 ++ src/renderer/components/+welcome/welcome.tsx | 2 +- .../cron-jobs-route-component.injectable.ts | 21 ++ .../cron-jobs-sidebar-items.injectable.tsx | 38 ++ .../cron-jobs-store.injectable.ts | 14 + .../+workloads-cronjobs/cronjobs.tsx | 98 +++--- .../daemonsets-route-component.injectable.ts | 21 ++ .../daemonsets-sidebar-items.injectable.tsx | 38 ++ .../daemonsets-store.injectable.ts | 15 + .../+workloads-daemonsets/daemonsets.tsx | 82 +++-- .../deployments-route-component.injectable.ts | 21 ++ .../deployments-sidebar-items.injectable.tsx | 38 ++ .../deployments-store.injectable.ts | 14 + .../+workloads-deployments/deployments.tsx | 82 +++-- .../jobs-route-component.injectable.ts | 21 ++ .../jobs-sidebar-items.injectable.tsx | 38 ++ .../+workloads-jobs/jobs-store.injectable.ts | 14 + .../components/+workloads-jobs/jobs.tsx | 86 +++-- .../+workloads-overview/overview-statuses.tsx | 34 +- .../+workloads-overview/overview.tsx | 37 +- ...ads-overview-route-component.injectable.ts | 21 ++ ...oads-overview-sidebar-items.injectable.tsx | 38 ++ .../workloads.injectable.ts | 40 --- .../+workloads-overview/workloads.ts | 39 --- .../cron-jobs-workload.injectable.ts | 42 +++ .../daemonsets-workload.injectable.ts | 42 +++ .../deployments-workload.injectable.ts | 42 +++ .../jobs-workload.injectable.ts | 42 +++ .../pods-workload.injectable.ts | 42 +++ .../replicasets-workload.injectable.ts | 42 +++ .../statefulsets-workload.injectable.ts | 42 +++ .../workloads/workload-injection-token.ts | 20 ++ .../workloads/workloads.injectable.ts | 43 +++ .../+workloads-pods/pod-container-port.tsx | 19 +- .../pods-route-component.injectable.ts | 21 ++ .../pods-sidebar-items.injectable.tsx | 38 ++ .../+workloads-pods/pods-store.injectable.ts | 14 + .../components/+workloads-pods/pods.tsx | 136 ++++--- .../replicasets-route-component.injectable.ts | 21 ++ .../replicasets-sidebar-items.injectable.tsx | 38 ++ .../replicasets-store.injectable.ts | 14 + .../+workloads-replicasets/replicasets.tsx | 82 +++-- ...statefulsets-route-component.injectable.ts | 21 ++ .../statefulsets-sidebar-items.injectable.tsx | 38 ++ .../statefulsets-store.injectable.ts | 14 + .../+workloads-statefulsets/statefulsets.tsx | 74 ++-- .../+workloads/route-tabs.injectable.ts | 110 ------ src/renderer/components/+workloads/route.tsx | 34 -- .../components/+workloads/sidebar-item.tsx | 45 --- .../workloads-sidebar-items.injectable.tsx | 35 ++ src/renderer/components/button/button.tsx | 2 +- .../cluster-manager/cluster-manager.tsx | 75 ++-- .../cluster-manager/cluster-status.tsx | 31 +- ...cluster-view-route-component.injectable.ts | 21 ++ ...luster-view-route-parameters.injectable.ts | 23 ++ .../cluster-manager/cluster-view.tsx | 38 +- .../command-palette/command-container.tsx | 37 +- .../internal-commands.injectable.tsx | 133 +++++-- .../lens-templates.injectable.ts | 4 +- .../components/dock/install-chart/view.tsx | 17 +- .../__tests__/hotbar-remove-command.test.tsx | 6 +- .../components/hotbar/hotbar-add-command.tsx | 4 +- .../hotbar/hotbar-remove-command.tsx | 16 +- .../hotbar/hotbar-rename-command.tsx | 16 +- .../components/hotbar/hotbar-selector.tsx | 14 +- .../hotbar/hotbar-switch-command.tsx | 31 +- src/renderer/components/input/input.tsx | 2 + .../unique-hotbar-name.injectable.ts | 4 +- ...on-sidebar-item-registrator.injectable.tsx | 84 +++++ .../components/layout/setting-layout.tsx | 22 +- .../layout/siblings-in-tab-layout.tsx | 47 +++ .../components/layout/sidebar-item.scss | 54 ++- .../components/layout/sidebar-item.tsx | 115 +++--- .../layout/sidebar-items.injectable.ts | 132 +++++++ src/renderer/components/layout/sidebar.tsx | 144 ++------ .../components/layout/tab-layout-2.tsx | 60 ++++ .../layout/tab-routes-sidebar-items.tsx | 37 -- .../components/layout/top-bar/top-bar.tsx | 43 ++- .../components/select/select.test.tsx | 1 - src/renderer/components/tabs/tabs.tsx | 2 +- .../test-utils/get-application-builder.tsx | 316 +++++++++++++++++ .../test-utils/get-renderer-extension-fake.ts | 23 ++ .../components/tooltip/withTooltip.tsx | 1 + .../frames/cluster-frame/cluster-frame.tsx | 159 ++------- .../cluster-frame/start-url.injectable.ts | 34 ++ .../init-root-frame.injectable.ts | 3 + .../init-root-frame/init-root-frame.ts | 5 +- src/renderer/frames/root-frame/root-frame.tsx | 24 +- src/renderer/getDi.tsx | 4 +- src/renderer/getDiForUnitTesting.tsx | 69 ++-- .../add-sync-entries.injectable.tsx | 36 ++ .../catalog-category-registry.tsx | 23 +- .../kube-object-detail-registry.tsx | 2 +- src/renderer/initializers/registries.ts | 5 +- src/renderer/initializers/workload-events.tsx | 10 +- .../focus-window.injectable.ts | 13 + .../ipc-channel-listener-injection-token.ts | 17 + .../navigation-listener.injectable.ts | 38 ++ ...gister-ipc-channel-listeners.injectable.ts | 27 ++ ...amespaces-forbidden-handler.injectable.tsx | 81 +++++ .../ipc/register-ipc-listeners.injectable.ts | 17 + src/renderer/ipc/register-listeners.tsx | 69 +--- .../subscribe-stores.injectable.ts | 1 + src/renderer/navigation/events.ts | 29 +- src/renderer/navigation/helpers.ts | 14 - src/renderer/navigation/index.ts | 4 - .../matched-cluster-id.injectable.ts | 18 + .../observable-history.injectable.ts | 1 + src/renderer/navigation/page-param.ts | 9 +- .../about-port-forwarding.injectable.ts | 18 + ...notify-error-port-forwarding.injectable.ts | 18 + .../port-forward/port-forward-dialog.tsx | 13 +- .../port-forward/port-forward-notify.tsx | 21 +- .../port-forward-store.injectable.ts | 6 +- .../port-forward-store/port-forward-store.ts | 4 +- ...-protocol-add-route-handlers.injectable.ts | 14 + .../bind-protocol-add-route-handlers.tsx | 44 ++- ...avigate-to-preference-tab-id.injectable.ts | 30 ++ src/renderer/routes/all-routes.injectable.ts | 39 +++ .../current-path-parameters.injectable.ts | 23 ++ .../routes/current-path.injectable.ts | 19 + .../current-route-component.injectable.ts | 33 ++ .../routes/current-route.injectable.ts | 27 ++ .../currently-in-cluster-frame.injectable.ts | 12 + .../extension-page-parameters.injectable.ts | 68 ++++ ...extension-route-registrator.injectable.tsx | 116 ++++++ src/renderer/routes/get-extension-route-id.ts | 9 + .../routes/get-extension-route-path.ts | 19 + .../routes/matching-route.injectable.ts | 38 ++ .../routes/navigate-to-route.injectable.ts | 39 +++ .../routes/navigate-to-url.injectable.ts | 40 +++ src/renderer/routes/ogre-tools-fp.d.ts | 83 +++++ .../routes/query-parameters.injectable.ts | 24 ++ .../routes/route-is-active.injectable.ts | 24 ++ .../route-path-parameters.injectable.ts | 33 ++ ...oute-specific-component-injection-token.ts | 13 + src/renderer/routes/routes.injectable.ts | 36 ++ .../routes/sibling-tabs.injectable.ts | 28 ++ src/renderer/theme-store.injectable.ts | 14 + .../create-storage.injectable.ts | 9 + .../utils/create-storage/create-storage.ts | 20 +- src/test-utils/get-dis-for-unit-testing.ts | 2 +- src/test-utils/override-fs-with-fakes.ts | 65 ++++ src/test-utils/override-ipc-bridge.ts | 33 ++ yarn.lock | 30 +- 523 files changed, 11727 insertions(+), 5541 deletions(-) create mode 100644 src/common/catalog-entities/general-catalog-entities/general-catalog-entities.injectable.ts create mode 100644 src/common/catalog-entities/general-catalog-entities/general-catalog-entity-injection-token.ts create mode 100644 src/common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable.ts create mode 100644 src/common/catalog-entities/general-catalog-entities/implementations/preferences-catalog-entity.injectable.ts create mode 100644 src/common/catalog-entities/general-catalog-entities/implementations/welcome-catalog-entity.injectable.ts create mode 100644 src/common/front-end-routing/navigate-to-front-page.injectable.ts create mode 100644 src/common/front-end-routing/navigate-to-route-injection-token.ts create mode 100644 src/common/front-end-routing/navigate-to-url-injection-token.ts create mode 100644 src/common/front-end-routing/navigation-ipc-channel.ts create mode 100644 src/common/front-end-routing/route-injection-token.ts create mode 100644 src/common/front-end-routing/routes/add-cluster/add-cluster-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable.ts create mode 100644 src/common/front-end-routing/routes/catalog/catalog-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/catalog/navigate-to-catalog.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster-view/cluster-view-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/config-maps/navigate-to-config-maps.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/navigate-to-horizontal-pod-autoscalers.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/limit-ranges/navigate-to-limit-ranges.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/navigate-to-pod-disruption-budgets.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/resource-quotas/navigate-to-resource-quotas.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/secrets/navigate-to-secrets.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/custom-resources/crd-list/navigate-to-crd-list.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/events/navigate-to-events.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/helm/charts/helm-charts-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/helm/releases/helm-releases-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/namespaces/navigate-to-namespaces.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/network/endpoints/navigate-to-endpoints.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/network/ingresses/navigate-to-ingresses.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/network/network-policies/navigate-to-network-policies.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/network/port-forwards/port-forwards-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/network/services/navigate-to-services.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/nodes/navigate-to-nodes.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/overview/navigate-to-cluster-overview.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/navigate-to-persistent-volume-claims.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/storage/persistent-volumes/navigate-to-persistent-volumes.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/storage/storage-classes/navigate-to-storage-classes.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/navigate-to-cluster-role-bindings.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/cluster-roles/navigate-to-cluster-roles.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/navigate-to-pod-security-policies.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/role-bindings/navigate-to-role-bindings.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/roles/navigate-to-roles.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/service-accounts/navigate-to-service-accounts.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/cron-jobs/navigate-to-cron-jobs.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/daemonsets/navigate-to-daemonsets.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/jobs/navigate-to-jobs.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/overview/navigate-to-workloads-overview.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/overview/workloads-overview-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/pods/navigate-to-pods.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/replicasets/navigate-to-replicasets.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/statefulsets/navigate-to-statefulsets.injectable.ts create mode 100644 src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/entity-settings/entity-settings-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable.ts create mode 100644 src/common/front-end-routing/routes/extensions/extensions-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/extensions/navigate-to-extensions.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/app/app-preferences-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/app/navigate-to-app-preferences.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/editor/editor-preferences-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/editor/navigate-to-editor-preferences.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/extension/extension-preferences-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/extension/navigate-to-extension-preferences.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/kubernetes/kubernetes-preferences-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/kubernetes/navigate-to-kubernetes-preferences.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/navigate-to-preferences.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/proxy/navigate-to-proxy-preferences.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/proxy/proxy-preferences-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/telemetry/navigate-to-telemetry-preferences.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/telemetry/telemetry-preferences-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/terminal/navigate-to-terminal-preferences.injectable.ts create mode 100644 src/common/front-end-routing/routes/preferences/terminal/terminal-preferences-route.injectable.ts create mode 100644 src/common/front-end-routing/routes/welcome/navigate-to-welcome.injectable.ts create mode 100644 src/common/front-end-routing/routes/welcome/welcome-route.injectable.ts create mode 100644 src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts create mode 100644 src/common/fs/path-exists.injectable.ts create mode 100644 src/common/fs/read-file-buffer.injectable.ts create mode 100644 src/common/logger.injectable.ts delete mode 100644 src/common/routes/add-cluster.ts delete mode 100644 src/common/routes/catalog.ts delete mode 100644 src/common/routes/cluster-view.ts delete mode 100644 src/common/routes/cluster.ts delete mode 100644 src/common/routes/config-maps.ts delete mode 100644 src/common/routes/config.ts delete mode 100644 src/common/routes/crd.ts delete mode 100644 src/common/routes/endpoints.ts delete mode 100644 src/common/routes/entity-settings.ts delete mode 100644 src/common/routes/events.ts delete mode 100644 src/common/routes/extensions.ts delete mode 100644 src/common/routes/helm-charts.ts delete mode 100644 src/common/routes/helm.ts delete mode 100644 src/common/routes/hpa.ts delete mode 100644 src/common/routes/index.ts delete mode 100644 src/common/routes/ingresses.ts delete mode 100644 src/common/routes/limit-ranges.ts delete mode 100644 src/common/routes/namespaces.ts delete mode 100644 src/common/routes/network-policies.ts delete mode 100644 src/common/routes/network.ts delete mode 100644 src/common/routes/nodes.ts delete mode 100644 src/common/routes/pod-disruption-budgets.ts delete mode 100644 src/common/routes/port-forwards.ts delete mode 100644 src/common/routes/preferences.ts delete mode 100644 src/common/routes/releases.ts delete mode 100644 src/common/routes/resource-quotas.ts delete mode 100644 src/common/routes/secrets.ts delete mode 100644 src/common/routes/services.ts delete mode 100644 src/common/routes/storage-classes.ts delete mode 100644 src/common/routes/storage.ts delete mode 100644 src/common/routes/user-management.ts delete mode 100644 src/common/routes/volume-claims.ts delete mode 100644 src/common/routes/volumes.ts delete mode 100644 src/common/routes/welcome.ts delete mode 100644 src/common/routes/workloads.ts create mode 100644 src/extensions/extension-loader/extension-installation-counter.injectable.ts create mode 100644 src/extensions/extension-loader/extension-registrator-injection-token.ts delete mode 100644 src/extensions/registries/__tests__/page-registry.test.ts delete mode 100644 src/main/catalog-sources/general.ts create mode 100644 src/main/catalog-sources/sync-general-catalog-entities.injectable.ts create mode 100644 src/main/catalog/catalog-entity-registry.injectable.ts create mode 100644 src/main/is-auto-update-enabled.injectable.ts create mode 100644 src/main/menu/get-app-menu-items.injectable.ts create mode 100644 src/main/navigate-to-route/navigate-to-route.injectable.ts create mode 100644 src/main/navigate-to-url/navigate-to-url.injectable.ts create mode 100644 src/main/window-manager.injectable.ts create mode 100644 src/renderer/app-paths/get-value-from-registered-channel/register-ipc-channel-listener.injectable.ts create mode 100644 src/renderer/components/+add-cluster/add-cluster-route-component.injectable.ts rename src/renderer/components/{+workloads/workloads.scss => +catalog/catalog-browse-tab.ts} (77%) create mode 100644 src/renderer/components/+catalog/catalog-route-component.injectable.ts create mode 100644 src/renderer/components/+catalog/catalog-route-parameters.injectable.ts create mode 100644 src/renderer/components/+cluster/cluster-overview-route-component.injectable.ts create mode 100644 src/renderer/components/+cluster/cluster-overview-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+cluster/sidebar-item.tsx create mode 100644 src/renderer/components/+config-autoscalers/horizontal-pod-auto-scalers-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+config-autoscalers/horizontal-pod-autoscalers-route-component.injectable.ts create mode 100644 src/renderer/components/+config-limit-ranges/limit-ranges-route-component.injectable.ts create mode 100644 src/renderer/components/+config-limit-ranges/limit-ranges-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+config-maps/config-maps-route-component.injectable.ts create mode 100644 src/renderer/components/+config-maps/config-maps-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-route-component.injectable.ts create mode 100644 src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+config-resource-quotas/resource-quotas-route-component.injectable.ts create mode 100644 src/renderer/components/+config-resource-quotas/resource-quotas-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+config-secrets/secrets-route-component.injectable.ts create mode 100644 src/renderer/components/+config-secrets/secrets-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+config/config-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+config/route-tabs.injectable.ts delete mode 100644 src/renderer/components/+config/route.tsx delete mode 100644 src/renderer/components/+config/sidebar-item.tsx create mode 100644 src/renderer/components/+custom-resources/crd-list-route-component.injectable.ts create mode 100644 src/renderer/components/+custom-resources/custom-resource-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+custom-resources/custom-resources-route-component.injectable.ts create mode 100644 src/renderer/components/+custom-resources/custom-resources-route-parameters.injectable.ts delete mode 100644 src/renderer/components/+custom-resources/grouped-custom-resources.injectable.ts delete mode 100644 src/renderer/components/+custom-resources/route-tabs.injectable.ts delete mode 100644 src/renderer/components/+custom-resources/route.tsx delete mode 100644 src/renderer/components/+custom-resources/sidebar-item.tsx create mode 100644 src/renderer/components/+custom-resources/sidebar-items-for-definition-groups.injectable.ts create mode 100644 src/renderer/components/+entity-settings/entity-settings-route-component.injectable.ts create mode 100644 src/renderer/components/+entity-settings/entity-settings-route-parameters.injectable.ts create mode 100644 src/renderer/components/+events/events-route-component.injectable.ts create mode 100644 src/renderer/components/+events/events-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+events/sidebar-item.tsx create mode 100644 src/renderer/components/+extensions/extensions-route-component.injectable.ts create mode 100644 src/renderer/components/+helm-charts/helm-charts-route-component.injectable.ts create mode 100644 src/renderer/components/+helm-charts/helm-charts-route-parameters.injectable.ts create mode 100644 src/renderer/components/+helm-charts/helm-charts-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+helm-releases/helm-releases-route-component.injectable.ts create mode 100644 src/renderer/components/+helm-releases/helm-releases-route-parameters.injectable.ts create mode 100644 src/renderer/components/+helm-releases/helm-releases-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+helm-releases/release-details/release-route-parameters.injectable.ts create mode 100644 src/renderer/components/+helm/helm-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+helm/route-tabs.injectable.ts delete mode 100644 src/renderer/components/+helm/route.tsx delete mode 100644 src/renderer/components/+helm/sidebar-item.tsx create mode 100644 src/renderer/components/+namespaces/namespaces-route-component.injectable.ts create mode 100644 src/renderer/components/+namespaces/namespaces-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+namespaces/sidebar-item.tsx create mode 100644 src/renderer/components/+network-endpoints/endpoints-route-component.injectable.ts create mode 100644 src/renderer/components/+network-endpoints/endpoints-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+network-ingresses/ingresses-route-component.injectable.ts create mode 100644 src/renderer/components/+network-ingresses/ingresses-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+network-policies/network-policies-route-component.injectable.ts create mode 100644 src/renderer/components/+network-policies/network-policies-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+network-port-forwards/port-forwards-route-component.injectable.ts create mode 100644 src/renderer/components/+network-port-forwards/port-forwards-route-parameters.injectable.ts create mode 100644 src/renderer/components/+network-port-forwards/port-forwards-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+network-services/services-route-component.injectable.ts create mode 100644 src/renderer/components/+network-services/services-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+network/network-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+network/network.scss delete mode 100644 src/renderer/components/+network/route-tabs.injectable.ts delete mode 100644 src/renderer/components/+network/route.tsx delete mode 100644 src/renderer/components/+network/sidebar-item.tsx create mode 100644 src/renderer/components/+nodes/nodes-route-component.injectable.ts create mode 100644 src/renderer/components/+nodes/nodes-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+nodes/sidebar-item.tsx create mode 100644 src/renderer/components/+pod-security-policies/pod-security-policies-route-component.injectable.ts create mode 100644 src/renderer/components/+pod-security-policies/pod-security-policies-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+preferences/app-preferences-route-component.injectable.ts create mode 100644 src/renderer/components/+preferences/close-preferences.injectable.ts create mode 100644 src/renderer/components/+preferences/editor-preferences-route-component.injectable.ts create mode 100644 src/renderer/components/+preferences/extension-preference-item-registrator.injectable.ts create mode 100644 src/renderer/components/+preferences/extension-preference-items.injectable.ts create mode 100644 src/renderer/components/+preferences/extension-preferences-route-component.injectable.ts create mode 100644 src/renderer/components/+preferences/extension-telemetry-preference-item-registrator.injectable.ts create mode 100644 src/renderer/components/+preferences/kubernetes-preferences-route-component.injectable.ts create mode 100644 src/renderer/components/+preferences/preferences-navigation/application-preferences-navigation-item.injectable.ts create mode 100644 src/renderer/components/+preferences/preferences-navigation/editor-preferences-navigation-item.injectable.ts create mode 100644 src/renderer/components/+preferences/preferences-navigation/extensions-preferences-navigation-item.injectable.ts create mode 100644 src/renderer/components/+preferences/preferences-navigation/kubernetes-preferences-navigation-item.injectable.ts create mode 100644 src/renderer/components/+preferences/preferences-navigation/navigate-to-preference-tab.injectable.ts create mode 100644 src/renderer/components/+preferences/preferences-navigation/preference-navigation-items.injectable.ts create mode 100644 src/renderer/components/+preferences/preferences-navigation/preferences-navigation.tsx create mode 100644 src/renderer/components/+preferences/preferences-navigation/proxy-preferences-navigation-item.injectable.ts create mode 100644 src/renderer/components/+preferences/preferences-navigation/telemetry-preferences-navigation-item.injectable.ts create mode 100644 src/renderer/components/+preferences/preferences-navigation/terminal-preferences-navigation-item.injectable.ts create mode 100644 src/renderer/components/+preferences/proxy-preferences-route-component.injectable.ts create mode 100644 src/renderer/components/+preferences/sentry-dns-url.injectable.ts create mode 100644 src/renderer/components/+preferences/telemetry-preference-items.injectable.ts create mode 100644 src/renderer/components/+preferences/telemetry-preferences-route-component.injectable.ts create mode 100644 src/renderer/components/+preferences/terminal-preferences-route-component.injectable.ts create mode 100644 src/renderer/components/+storage-classes/storage-classes-route-component.injectable.ts create mode 100644 src/renderer/components/+storage-classes/storage-classes-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+storage-volume-claims/persistent-volume-claims-route-component.injectable.ts create mode 100644 src/renderer/components/+storage-volume-claims/persistent-volume-claims-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+storage-volumes/persistent-volumes-route-component.injectable.ts create mode 100644 src/renderer/components/+storage-volumes/persistent-volumes-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+storage/route-tabs.injectable.ts delete mode 100644 src/renderer/components/+storage/route.tsx delete mode 100644 src/renderer/components/+storage/sidebar-item.tsx create mode 100644 src/renderer/components/+storage/storage-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+storage/storage.scss create mode 100644 src/renderer/components/+user-management/+cluster-role-bindings/cluster-role-bindings-route-component.injectable.ts create mode 100644 src/renderer/components/+user-management/+cluster-role-bindings/cluster-role-bindings-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+user-management/+cluster-roles/cluster-roles-route-component.injectable.ts create mode 100644 src/renderer/components/+user-management/+cluster-roles/cluster-roles-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+user-management/+role-bindings/role-bindings-route-component.injectable.ts create mode 100644 src/renderer/components/+user-management/+role-bindings/role-bindings-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+user-management/+roles/roles-route-component.injectable.ts create mode 100644 src/renderer/components/+user-management/+roles/roles-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+user-management/+service-accounts/service-accounts-route-component.injectable.ts create mode 100644 src/renderer/components/+user-management/+service-accounts/service-accounts-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+user-management/route-tabs.injectable.ts delete mode 100644 src/renderer/components/+user-management/route.tsx delete mode 100644 src/renderer/components/+user-management/sidebar-item.tsx create mode 100644 src/renderer/components/+user-management/user-management-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+user-management/user-management.scss create mode 100644 src/renderer/components/+welcome/welcome-route-component.injectable.ts create mode 100644 src/renderer/components/+workloads-cronjobs/cron-jobs-route-component.injectable.ts create mode 100644 src/renderer/components/+workloads-cronjobs/cron-jobs-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+workloads-cronjobs/cron-jobs-store.injectable.ts create mode 100644 src/renderer/components/+workloads-daemonsets/daemonsets-route-component.injectable.ts create mode 100644 src/renderer/components/+workloads-daemonsets/daemonsets-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+workloads-daemonsets/daemonsets-store.injectable.ts create mode 100644 src/renderer/components/+workloads-deployments/deployments-route-component.injectable.ts create mode 100644 src/renderer/components/+workloads-deployments/deployments-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+workloads-deployments/deployments-store.injectable.ts create mode 100644 src/renderer/components/+workloads-jobs/jobs-route-component.injectable.ts create mode 100644 src/renderer/components/+workloads-jobs/jobs-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+workloads-jobs/jobs-store.injectable.ts create mode 100644 src/renderer/components/+workloads-overview/workloads-overview-route-component.injectable.ts create mode 100644 src/renderer/components/+workloads-overview/workloads-overview-sidebar-items.injectable.tsx delete mode 100644 src/renderer/components/+workloads-overview/workloads.injectable.ts delete mode 100644 src/renderer/components/+workloads-overview/workloads.ts create mode 100644 src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts create mode 100644 src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts create mode 100644 src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts create mode 100644 src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts create mode 100644 src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts create mode 100644 src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts create mode 100644 src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts create mode 100644 src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts create mode 100644 src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts create mode 100644 src/renderer/components/+workloads-pods/pods-route-component.injectable.ts create mode 100644 src/renderer/components/+workloads-pods/pods-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+workloads-pods/pods-store.injectable.ts create mode 100644 src/renderer/components/+workloads-replicasets/replicasets-route-component.injectable.ts create mode 100644 src/renderer/components/+workloads-replicasets/replicasets-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+workloads-replicasets/replicasets-store.injectable.ts create mode 100644 src/renderer/components/+workloads-statefulsets/statefulsets-route-component.injectable.ts create mode 100644 src/renderer/components/+workloads-statefulsets/statefulsets-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/+workloads-statefulsets/statefulsets-store.injectable.ts delete mode 100644 src/renderer/components/+workloads/route-tabs.injectable.ts delete mode 100644 src/renderer/components/+workloads/route.tsx delete mode 100644 src/renderer/components/+workloads/sidebar-item.tsx create mode 100644 src/renderer/components/+workloads/workloads-sidebar-items.injectable.tsx create mode 100644 src/renderer/components/cluster-manager/cluster-view-route-component.injectable.ts create mode 100644 src/renderer/components/cluster-manager/cluster-view-route-parameters.injectable.ts create mode 100644 src/renderer/components/layout/extension-sidebar-item-registrator.injectable.tsx create mode 100644 src/renderer/components/layout/siblings-in-tab-layout.tsx create mode 100644 src/renderer/components/layout/sidebar-items.injectable.ts create mode 100644 src/renderer/components/layout/tab-layout-2.tsx delete mode 100644 src/renderer/components/layout/tab-routes-sidebar-items.tsx create mode 100644 src/renderer/components/test-utils/get-application-builder.tsx create mode 100644 src/renderer/components/test-utils/get-renderer-extension-fake.ts create mode 100644 src/renderer/frames/cluster-frame/start-url.injectable.ts create mode 100644 src/renderer/initializers/add-sync-entries.injectable.tsx create mode 100644 src/renderer/ipc-channel-listeners/focus-window.injectable.ts create mode 100644 src/renderer/ipc-channel-listeners/ipc-channel-listener-injection-token.ts create mode 100644 src/renderer/ipc-channel-listeners/navigation-listener.injectable.ts create mode 100644 src/renderer/ipc-channel-listeners/register-ipc-channel-listeners.injectable.ts create mode 100644 src/renderer/ipc/list-namespaces-forbidden-handler.injectable.tsx create mode 100644 src/renderer/ipc/register-ipc-listeners.injectable.ts create mode 100644 src/renderer/navigation/matched-cluster-id.injectable.ts create mode 100644 src/renderer/port-forward/about-port-forwarding.injectable.ts create mode 100644 src/renderer/port-forward/notify-error-port-forwarding.injectable.ts create mode 100644 src/renderer/protocol-handler/bind-protocol-add-route-handlers/navigate-to-preference-tab-id.injectable.ts create mode 100644 src/renderer/routes/all-routes.injectable.ts create mode 100644 src/renderer/routes/current-path-parameters.injectable.ts create mode 100644 src/renderer/routes/current-path.injectable.ts create mode 100644 src/renderer/routes/current-route-component.injectable.ts create mode 100644 src/renderer/routes/current-route.injectable.ts create mode 100644 src/renderer/routes/currently-in-cluster-frame.injectable.ts create mode 100644 src/renderer/routes/extension-page-parameters.injectable.ts create mode 100644 src/renderer/routes/extension-route-registrator.injectable.tsx create mode 100644 src/renderer/routes/get-extension-route-id.ts create mode 100644 src/renderer/routes/get-extension-route-path.ts create mode 100644 src/renderer/routes/matching-route.injectable.ts create mode 100644 src/renderer/routes/navigate-to-route.injectable.ts create mode 100644 src/renderer/routes/navigate-to-url.injectable.ts create mode 100644 src/renderer/routes/ogre-tools-fp.d.ts create mode 100644 src/renderer/routes/query-parameters.injectable.ts create mode 100644 src/renderer/routes/route-is-active.injectable.ts create mode 100644 src/renderer/routes/route-path-parameters.injectable.ts create mode 100644 src/renderer/routes/route-specific-component-injection-token.ts create mode 100644 src/renderer/routes/routes.injectable.ts create mode 100644 src/renderer/routes/sibling-tabs.injectable.ts create mode 100644 src/renderer/theme-store.injectable.ts create mode 100644 src/test-utils/override-fs-with-fakes.ts diff --git a/package.json b/package.json index dfc8f59e3b..d017e39344 100644 --- a/package.json +++ b/package.json @@ -202,8 +202,9 @@ "@hapi/call": "^8.0.1", "@hapi/subtext": "^7.0.3", "@kubernetes/client-node": "^0.16.3", - "@ogre-tools/injectable": "5.1.2", - "@ogre-tools/injectable-react": "5.1.2", + "@ogre-tools/injectable": "5.1.3", + "@ogre-tools/injectable-react": "5.1.3", + "@ogre-tools/fp": "5.1.3", "@sentry/electron": "^2.5.4", "@sentry/integrations": "^6.19.2", "@types/circular-dependency-plugin": "5.0.5", diff --git a/src/common/__tests__/base-store.test.ts b/src/common/__tests__/base-store.test.ts index f35ed7721f..115eb817bf 100644 --- a/src/common/__tests__/base-store.test.ts +++ b/src/common/__tests__/base-store.test.ts @@ -7,10 +7,10 @@ import mockFs from "mock-fs"; import { BaseStore } from "../base-store"; import { action, comparer, makeObservable, observable, toJS } from "mobx"; import { readFileSync } from "fs"; -import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing"; import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; jest.mock("electron", () => ({ ipcMain: { @@ -78,11 +78,11 @@ describe("BaseStore", () => { let store: TestStore; beforeEach(async () => { - const dis = getDisForUnitTesting({ doGeneralOverrides: true }); + const mainDi = getDiForUnitTesting({ doGeneralOverrides: true }); - dis.mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory"); + mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory"); - await dis.runSetups(); + await mainDi.runSetups(); store = undefined; TestStore.resetInstance(); @@ -99,9 +99,9 @@ describe("BaseStore", () => { }); afterEach(() => { + mockFs.restore(); store.disableSync(); TestStore.resetInstance(); - mockFs.restore(); }); describe("persistence", () => { diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index 9bfc5346ed..284f77b0e8 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -19,11 +19,13 @@ import type { } from "@ogre-tools/injectable"; -import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing"; import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token"; import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import kubeAuthProxyCaInjectable from "../../main/kube-auth-proxy/kube-auth-proxy-ca.injectable"; +import createKubeAuthProxyCertFilesInjectable from "../../main/kube-auth-proxy/create-kube-auth-proxy-cert-files.injectable"; +import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; console = new Console(stdout, stderr); @@ -80,15 +82,14 @@ describe("cluster-store", () => { let createCluster: (model: ClusterModel) => Cluster; beforeEach(async () => { - const dis = getDisForUnitTesting({ doGeneralOverrides: true }); + mainDi = getDiForUnitTesting({ doGeneralOverrides: true }); mockFs(); - mainDi = dis.mainDi; - + mainDi.override(clusterStoreInjectable, (di) => ClusterStore.createInstance({ createCluster: di.inject(createClusterInjectionToken) })); mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); - await dis.runSetups(); + await mainDi.runSetups(); createCluster = mainDi.inject(createClusterInjectionToken); }); diff --git a/src/common/__tests__/hotbar-store.test.ts b/src/common/__tests__/hotbar-store.test.ts index e93f53abe5..df016a3f94 100644 --- a/src/common/__tests__/hotbar-store.test.ts +++ b/src/common/__tests__/hotbar-store.test.ts @@ -11,6 +11,9 @@ import { HotbarStore } from "../hotbar-store"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import writeFileInjectable from "../fs/write-file.injectable"; +import hotbarStoreInjectable from "../hotbar-store.injectable"; +import catalogCatalogEntityInjectable + from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; jest.mock("../../main/catalog/catalog-entity-registry", () => ({ catalogEntityRegistry: { @@ -119,7 +122,11 @@ describe("HotbarStore", () => { di.override(writeFileInjectable, () => () => undefined); di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); - await di.runSetups(); + di.override(hotbarStoreInjectable, (di) => + HotbarStore.createInstance({ + catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable), + }), + ); mockFs({ "some-directory-for-user-data": { @@ -127,12 +134,14 @@ describe("HotbarStore", () => { }, }); - HotbarStore.createInstance(); + await di.runSetups(); + + di.inject(hotbarStoreInjectable); }); afterEach(() => { - HotbarStore.resetInstance(); mockFs.restore(); + HotbarStore.resetInstance(); }); describe("load", () => { @@ -328,7 +337,6 @@ describe("HotbarStore", () => { describe("pre beta-5 migrations", () => { beforeEach(() => { - HotbarStore.resetInstance(); const mockOpts = { "some-directory-for-user-data": { "lens-hotbar-store.json": JSON.stringify({ @@ -392,8 +400,6 @@ describe("HotbarStore", () => { }; mockFs(mockOpts); - - HotbarStore.createInstance(); }); afterEach(() => { diff --git a/src/common/__tests__/user-store.test.ts b/src/common/__tests__/user-store.test.ts index 465acc2a45..db37b63d42 100644 --- a/src/common/__tests__/user-store.test.ts +++ b/src/common/__tests__/user-store.test.ts @@ -26,34 +26,34 @@ import { Console } from "console"; import { SemVer } from "semver"; import electron from "electron"; import { stdout, stderr } from "process"; -import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing"; import userStoreInjectable from "../user-store/user-store.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import type { ClusterStoreModel } from "../cluster-store/cluster-store"; import { defaultTheme } from "../vars"; import writeFileInjectable from "../fs/write-file.injectable"; +import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; console = new Console(stdout, stderr); describe("user store tests", () => { let userStore: UserStore; - let mainDi: DiContainer; + let di: DiContainer; beforeEach(async () => { - const dis = getDisForUnitTesting({ doGeneralOverrides: true }); + di = getDiForUnitTesting({ doGeneralOverrides: true }); mockFs(); - mainDi = dis.mainDi; + di.override(writeFileInjectable, () => () => undefined); + di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); + di.override(userStoreInjectable, () => UserStore.createInstance()); - mainDi.override(writeFileInjectable, () => () => undefined); - mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data"); - - await dis.runSetups(); + await di.runSetups(); }); afterEach(() => { + UserStore.resetInstance(); mockFs.restore(); }); @@ -61,12 +61,7 @@ describe("user store tests", () => { beforeEach(() => { mockFs({ "some-directory-for-user-data": { "config.json": "{}", "kube_config": "{}" }}); - userStore = mainDi.inject(userStoreInjectable); - }); - - afterEach(() => { - mockFs.restore(); - UserStore.resetInstance(); + userStore = di.inject(userStoreInjectable); }); it("allows setting and retrieving lastSeenAppVersion", () => { @@ -128,12 +123,7 @@ describe("user store tests", () => { }, }); - userStore = mainDi.inject(userStoreInjectable); - }); - - afterEach(() => { - UserStore.resetInstance(); - mockFs.restore(); + userStore = di.inject(userStoreInjectable); }); it("sets last seen app version to 0.0.0", () => { diff --git a/src/common/catalog-entities/general-catalog-entities/general-catalog-entities.injectable.ts b/src/common/catalog-entities/general-catalog-entities/general-catalog-entities.injectable.ts new file mode 100644 index 0000000000..acda253f6c --- /dev/null +++ b/src/common/catalog-entities/general-catalog-entities/general-catalog-entities.injectable.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; +import { generalCatalogEntityInjectionToken } from "./general-catalog-entity-injection-token"; + +const generalCatalogEntitiesInjectable = getInjectable({ + id: "general-catalog-entities", + + instantiate: (di) => + observable(di.injectMany(generalCatalogEntityInjectionToken)), +}); + +export default generalCatalogEntitiesInjectable; diff --git a/src/common/catalog-entities/general-catalog-entities/general-catalog-entity-injection-token.ts b/src/common/catalog-entities/general-catalog-entities/general-catalog-entity-injection-token.ts new file mode 100644 index 0000000000..ad743802bf --- /dev/null +++ b/src/common/catalog-entities/general-catalog-entities/general-catalog-entity-injection-token.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { GeneralEntity } from "../index"; + +export const generalCatalogEntityInjectionToken = getInjectionToken({ + id: "general-catalog-entity-injection-token", +}); diff --git a/src/common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable.ts b/src/common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable.ts new file mode 100644 index 0000000000..15195a4b74 --- /dev/null +++ b/src/common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { generalCatalogEntityInjectionToken } from "../general-catalog-entity-injection-token"; +import { GeneralEntity } from "../../index"; +import { buildURL } from "../../../utils/buildUrl"; +import catalogRouteInjectable from "../../../front-end-routing/routes/catalog/catalog-route.injectable"; + +const catalogCatalogEntityInjectable = getInjectable({ + id: "general-catalog-entity-for-catalog", + + instantiate: (di) => { + const route = di.inject(catalogRouteInjectable); + const url = buildURL(route.path); + + return new GeneralEntity({ + metadata: { + uid: "catalog-entity", + name: "Catalog", + source: "app", + labels: {}, + }, + spec: { + path: url, + icon: { + material: "view_list", + background: "#3d90ce", + }, + }, + status: { + phase: "active", + }, + }); + }, + + injectionToken: generalCatalogEntityInjectionToken, +}); + +export default catalogCatalogEntityInjectable; diff --git a/src/common/catalog-entities/general-catalog-entities/implementations/preferences-catalog-entity.injectable.ts b/src/common/catalog-entities/general-catalog-entities/implementations/preferences-catalog-entity.injectable.ts new file mode 100644 index 0000000000..80d4458b6d --- /dev/null +++ b/src/common/catalog-entities/general-catalog-entities/implementations/preferences-catalog-entity.injectable.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { generalCatalogEntityInjectionToken } from "../general-catalog-entity-injection-token"; +import { GeneralEntity } from "../../index"; +import { buildURL } from "../../../utils/buildUrl"; +import appPreferencesRouteInjectable from "../../../front-end-routing/routes/preferences/app/app-preferences-route.injectable"; + +const preferencesCatalogEntityInjectable = getInjectable({ + id: "general-catalog-entity-for-preferences", + + instantiate: (di) => { + const route = di.inject(appPreferencesRouteInjectable); + const url = buildURL(route.path); + + return new GeneralEntity({ + metadata: { + uid: "preferences-entity", + name: "Preferences", + source: "app", + labels: {}, + }, + spec: { + path: url, + icon: { + material: "settings", + background: "#3d90ce", + }, + }, + status: { + phase: "active", + }, + }); + }, + + injectionToken: generalCatalogEntityInjectionToken, +}); + +export default preferencesCatalogEntityInjectable; diff --git a/src/common/catalog-entities/general-catalog-entities/implementations/welcome-catalog-entity.injectable.ts b/src/common/catalog-entities/general-catalog-entities/implementations/welcome-catalog-entity.injectable.ts new file mode 100644 index 0000000000..363dd73c5f --- /dev/null +++ b/src/common/catalog-entities/general-catalog-entities/implementations/welcome-catalog-entity.injectable.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { generalCatalogEntityInjectionToken } from "../general-catalog-entity-injection-token"; +import { GeneralEntity } from "../../index"; +import { buildURL } from "../../../utils/buildUrl"; +import welcomeRouteInjectable from "../../../front-end-routing/routes/welcome/welcome-route.injectable"; + +const welcomeCatalogEntityInjectable = getInjectable({ + id: "general-catalog-entity-for-welcome", + + instantiate: (di) => { + const route = di.inject(welcomeRouteInjectable); + const url = buildURL(route.path); + + return new GeneralEntity({ + metadata: { + uid: "welcome-page-entity", + name: "Welcome Page", + source: "app", + labels: {}, + }, + spec: { + path: url, + icon: { + material: "meeting_room", + background: "#3d90ce", + }, + }, + status: { + phase: "active", + }, + }); + }, + + injectionToken: generalCatalogEntityInjectionToken, +}); + +export default welcomeCatalogEntityInjectable; diff --git a/src/common/cluster-store/cluster-store.injectable.ts b/src/common/cluster-store/cluster-store.injectable.ts index f6295e4bd7..a47978376a 100644 --- a/src/common/cluster-store/cluster-store.injectable.ts +++ b/src/common/cluster-store/cluster-store.injectable.ts @@ -13,6 +13,8 @@ const clusterStoreInjectable = getInjectable({ ClusterStore.createInstance({ createCluster: di.inject(createClusterInjectionToken), }), + + causesSideEffects: true, }); export default clusterStoreInjectable; diff --git a/src/common/front-end-routing/navigate-to-front-page.injectable.ts b/src/common/front-end-routing/navigate-to-front-page.injectable.ts new file mode 100644 index 0000000000..a48eee6c04 --- /dev/null +++ b/src/common/front-end-routing/navigate-to-front-page.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import navigateToCatalogInjectable from "./routes/catalog/navigate-to-catalog.injectable"; + +const navigateToFrontPageInjectable = getInjectable({ + id: "navigate-to-front-page", + instantiate: (di) => di.inject(navigateToCatalogInjectable), +}); + +export default navigateToFrontPageInjectable; diff --git a/src/common/front-end-routing/navigate-to-route-injection-token.ts b/src/common/front-end-routing/navigate-to-route-injection-token.ts new file mode 100644 index 0000000000..d6321f017e --- /dev/null +++ b/src/common/front-end-routing/navigate-to-route-injection-token.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Route } from "./route-injection-token"; + +type InferParametersFrom = TRoute extends Route + ? TParameters + : never; + +type RequiredKeys = Exclude< + { + [K in keyof T]: T extends Record ? K : never; + }[keyof T], + undefined +>; + +type ObjectContainingNoRequired = T extends void + ? never + : RequiredKeys extends [] + ? any + : never; + +type ObjectContainsNoRequired = T extends ObjectContainingNoRequired + ? true + : false; + +// TODO: Missing types for: +// - Navigating to route without parameters, with parameters +// - Navigating to route with required parameters, without parameters +type Parameters = TParameters extends void + ? {} + : ObjectContainsNoRequired extends true + ? { parameters?: TParameters } + : { parameters: TParameters }; + +export type NavigateToRouteOptions = Parameters< + InferParametersFrom +> & { + query?: Record; + fragment?: string; + withoutAffectingBackButton?: boolean; +}; + +export type NavigateToRoute = >( + route: TRoute, + options?: NavigateToRouteOptions) => void; + +export const navigateToRouteInjectionToken = getInjectionToken( + { id: "navigate-to-route-injection-token" }, +); diff --git a/src/common/front-end-routing/navigate-to-url-injection-token.ts b/src/common/front-end-routing/navigate-to-url-injection-token.ts new file mode 100644 index 0000000000..560d62ce4a --- /dev/null +++ b/src/common/front-end-routing/navigate-to-url-injection-token.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; + +export interface NavigateToUrlOptions { + withoutAffectingBackButton?: boolean; + forceRootFrame?: boolean; +} + +export type NavigateToUrl = (url: string, options?: NavigateToUrlOptions) => void; + +export const navigateToUrlInjectionToken = getInjectionToken( + { id: "navigate-to-url-injection-token" }, +); diff --git a/src/common/front-end-routing/navigation-ipc-channel.ts b/src/common/front-end-routing/navigation-ipc-channel.ts new file mode 100644 index 0000000000..6094664f81 --- /dev/null +++ b/src/common/front-end-routing/navigation-ipc-channel.ts @@ -0,0 +1,9 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { createChannel } from "../ipc-channel/create-channel/create-channel"; +import { IpcRendererNavigationEvents } from "../../renderer/navigation/events"; + +export const appNavigationIpcChannel = createChannel(IpcRendererNavigationEvents.NAVIGATE_IN_APP); +export const clusterFrameNavigationIpcChannel = createChannel(IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER); diff --git a/src/common/front-end-routing/route-injection-token.ts b/src/common/front-end-routing/route-injection-token.ts new file mode 100644 index 0000000000..7ecdbba635 --- /dev/null +++ b/src/common/front-end-routing/route-injection-token.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { IComputedValue } from "mobx"; +import type { LensRendererExtension } from "../../extensions/lens-renderer-extension"; + +export const routeInjectionToken = getInjectionToken>({ + id: "route-injection-token", +}); + +export interface Route { + path: string; + clusterFrame: boolean; + isEnabled: IComputedValue; + extension?: LensRendererExtension; + + readonly parameterSignature?: TParameter; +} diff --git a/src/common/front-end-routing/routes/add-cluster/add-cluster-route.injectable.ts b/src/common/front-end-routing/routes/add-cluster/add-cluster-route.injectable.ts new file mode 100644 index 0000000000..ce5bdcebd3 --- /dev/null +++ b/src/common/front-end-routing/routes/add-cluster/add-cluster-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../route-injection-token"; + +const addClusterRouteInjectable = getInjectable({ + id: "add-cluster-route", + + instantiate: () => ({ + path: "/add-cluster", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default addClusterRouteInjectable; diff --git a/src/common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable.ts b/src/common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable.ts new file mode 100644 index 0000000000..15595d51c2 --- /dev/null +++ b/src/common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import addClusterRouteInjectable from "./add-cluster-route.injectable"; +import { navigateToRouteInjectionToken } from "../../navigate-to-route-injection-token"; + +const navigateToAddClusterInjectable = getInjectable({ + id: "navigate-to-add-cluster", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(addClusterRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToAddClusterInjectable; diff --git a/src/common/front-end-routing/routes/catalog/catalog-route.injectable.ts b/src/common/front-end-routing/routes/catalog/catalog-route.injectable.ts new file mode 100644 index 0000000000..6dd4343049 --- /dev/null +++ b/src/common/front-end-routing/routes/catalog/catalog-route.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { Route, routeInjectionToken } from "../../route-injection-token"; + +export interface CatalogPathParameters { + group?: string; + kind?: string; +} + +const catalogRouteInjectable = getInjectable({ + id: "catalog-route", + + instantiate: (): Route => ({ + path: "/catalog/:group?/:kind?", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default catalogRouteInjectable; diff --git a/src/common/front-end-routing/routes/catalog/navigate-to-catalog.injectable.ts b/src/common/front-end-routing/routes/catalog/navigate-to-catalog.injectable.ts new file mode 100644 index 0000000000..0d52d41764 --- /dev/null +++ b/src/common/front-end-routing/routes/catalog/navigate-to-catalog.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import catalogRouteInjectable, { CatalogPathParameters } from "./catalog-route.injectable"; +import { navigateToRouteInjectionToken } from "../../navigate-to-route-injection-token"; + +export type NavigateToCatalog = (parameters?: CatalogPathParameters) => void; + +const navigateToCatalogInjectable = getInjectable({ + id: "navigate-to-catalog", + + instantiate: (di): NavigateToCatalog => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const catalogRoute = di.inject(catalogRouteInjectable); + + return (parameters) => + navigateToRoute(catalogRoute, { + parameters, + }); + }, +}); + +export default navigateToCatalogInjectable; diff --git a/src/common/front-end-routing/routes/cluster-view/cluster-view-route.injectable.ts b/src/common/front-end-routing/routes/cluster-view/cluster-view-route.injectable.ts new file mode 100644 index 0000000000..23fea9874b --- /dev/null +++ b/src/common/front-end-routing/routes/cluster-view/cluster-view-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../route-injection-token"; + +const clusterViewRouteInjectable = getInjectable({ + id: "cluster-view-route", + + instantiate: () => ({ + path: "/cluster/:clusterId", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default clusterViewRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable.ts b/src/common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable.ts new file mode 100644 index 0000000000..4edc3d3bb5 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { navigateToRouteInjectionToken } from "../../navigate-to-route-injection-token"; +import clusterViewRouteInjectable from "./cluster-view-route.injectable"; + +export type NavigateToClusterView = (clusterId: string) => void; + +const navigateToClusterViewInjectable = getInjectable({ + id: "navigate-to-cluster-view", + + instantiate: (di): NavigateToClusterView => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(clusterViewRouteInjectable); + + return (clusterId) => + navigateToRoute(route, { parameters: { clusterId }}); + }, +}); + +export default navigateToClusterViewInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts new file mode 100644 index 0000000000..b8c605fc64 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const configMapsRouteInjectable = getInjectable({ + id: "config-maps-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "configmaps"); + + return { + path: "/configmaps", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default configMapsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/config-maps/navigate-to-config-maps.injectable.ts b/src/common/front-end-routing/routes/cluster/config/config-maps/navigate-to-config-maps.injectable.ts new file mode 100644 index 0000000000..d4fa31027f --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/config-maps/navigate-to-config-maps.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import configMapsRouteInjectable from "./config-maps-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToConfigMapsInjectable = getInjectable({ + id: "navigate-to-config-maps", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(configMapsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToConfigMapsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts new file mode 100644 index 0000000000..d9eff7a026 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const horizontalPodAutoscalersRouteInjectable = getInjectable({ + id: "horizontal-pod-autoscalers-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "horizontalpodautoscalers"); + + return { + path: "/hpa", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default horizontalPodAutoscalersRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/navigate-to-horizontal-pod-autoscalers.injectable.ts b/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/navigate-to-horizontal-pod-autoscalers.injectable.ts new file mode 100644 index 0000000000..ab171f4a08 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/navigate-to-horizontal-pod-autoscalers.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import horizontalPodAutoscalersRouteInjectable from "./horizontal-pod-autoscalers-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToHorizontalPodAutoscalersInjectable = getInjectable({ + id: "navigate-to-horizontal-pod-autoscalers", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(horizontalPodAutoscalersRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToHorizontalPodAutoscalersInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts new file mode 100644 index 0000000000..70f40cb99d --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const limitRangesRouteInjectable = getInjectable({ + id: "limit-ranges-route", + + instantiate: (di) => { + const limitRangesIsAllowed = di.inject( + isAllowedResourceInjectable, + "limitranges", + ); + + return { + path: "/limitranges", + clusterFrame: true, + isEnabled: limitRangesIsAllowed, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default limitRangesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/limit-ranges/navigate-to-limit-ranges.injectable.ts b/src/common/front-end-routing/routes/cluster/config/limit-ranges/navigate-to-limit-ranges.injectable.ts new file mode 100644 index 0000000000..00e8c89d7c --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/limit-ranges/navigate-to-limit-ranges.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import limitRangesRouteInjectable from "./limit-ranges-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToLimitRangesInjectable = getInjectable({ + id: "navigate-to-limit-ranges", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(limitRangesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToLimitRangesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/navigate-to-pod-disruption-budgets.injectable.ts b/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/navigate-to-pod-disruption-budgets.injectable.ts new file mode 100644 index 0000000000..5a3f17027d --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/navigate-to-pod-disruption-budgets.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import podDisruptionBudgetsRouteInjectable from "./pod-disruption-budgets-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToPodDisruptionBudgetsInjectable = getInjectable({ + id: "navigate-to-pod-disruption-budgets", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(podDisruptionBudgetsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToPodDisruptionBudgetsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts new file mode 100644 index 0000000000..8d113e6727 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const podDisruptionBudgetsRouteInjectable = getInjectable({ + id: "pod-disruption-budgets-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "poddisruptionbudgets"); + + return { + path: "/poddisruptionbudgets", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default podDisruptionBudgetsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/resource-quotas/navigate-to-resource-quotas.injectable.ts b/src/common/front-end-routing/routes/cluster/config/resource-quotas/navigate-to-resource-quotas.injectable.ts new file mode 100644 index 0000000000..3637782115 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/resource-quotas/navigate-to-resource-quotas.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import resourceQuotasRouteInjectable from "./resource-quotas-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToResourceQuotasInjectable = getInjectable({ + id: "navigate-to-resource-quotas", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(resourceQuotasRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToResourceQuotasInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts new file mode 100644 index 0000000000..ee28deba5e --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const resourceQuotasRouteInjectable = getInjectable({ + id: "resource-quotas-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "resourcequotas"); + + return { + path: "/resourcequotas", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default resourceQuotasRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/secrets/navigate-to-secrets.injectable.ts b/src/common/front-end-routing/routes/cluster/config/secrets/navigate-to-secrets.injectable.ts new file mode 100644 index 0000000000..b8afdf74c3 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/secrets/navigate-to-secrets.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import secretsRouteInjectable from "./secrets-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToSecretsInjectable = getInjectable({ + id: "navigate-to-secrets", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(secretsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToSecretsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts new file mode 100644 index 0000000000..7806238e0c --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const secretsRouteInjectable = getInjectable({ + id: "secrets-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "secrets"); + + return { + path: "/secrets", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default secretsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable.ts b/src/common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable.ts new file mode 100644 index 0000000000..3a4865cc53 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const crdListRouteInjectable = getInjectable({ + id: "crd-list-route", + + instantiate: () => ({ + path: "/crd/definitions", + clusterFrame: true, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default crdListRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/custom-resources/crd-list/navigate-to-crd-list.injectable.ts b/src/common/front-end-routing/routes/cluster/custom-resources/crd-list/navigate-to-crd-list.injectable.ts new file mode 100644 index 0000000000..bd78dd1174 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/custom-resources/crd-list/navigate-to-crd-list.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import crdListRouteInjectable from "./crd-list-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToCrdListInjectable = getInjectable({ + id: "navigate-to-crd-list", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(crdListRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToCrdListInjectable; diff --git a/src/common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable.ts b/src/common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable.ts new file mode 100644 index 0000000000..731b4e1e96 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { Route, routeInjectionToken } from "../../../../route-injection-token"; + +export interface CustomResourcesPathParameters { + group?: string; + name?: string; +} + +const customResourcesRouteInjectable = getInjectable({ + id: "custom-resources-route", + + instantiate: (): Route => ({ + path: "/crd/:group?/:name?", + clusterFrame: true, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default customResourcesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable.ts b/src/common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable.ts new file mode 100644 index 0000000000..7a60068299 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import customResourcesRouteInjectable, { CustomResourcesPathParameters } from "./custom-resources-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToCustomResourcesInjectable = getInjectable({ + id: "navigate-to-custom-resources", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(customResourcesRouteInjectable); + + return (parameters?: CustomResourcesPathParameters) => + navigateToRoute(route, { parameters }); + }, +}); + +export default navigateToCustomResourcesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts b/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts new file mode 100644 index 0000000000..6fb9ff51f9 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const eventsRouteInjectable = getInjectable({ + id: "events-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "events"); + + return { + path: "/events", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default eventsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/events/navigate-to-events.injectable.ts b/src/common/front-end-routing/routes/cluster/events/navigate-to-events.injectable.ts new file mode 100644 index 0000000000..767f69b497 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/events/navigate-to-events.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import eventsRouteInjectable from "./events-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToEventsInjectable = getInjectable({ + id: "navigate-to-events", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(eventsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToEventsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/helm/charts/helm-charts-route.injectable.ts b/src/common/front-end-routing/routes/cluster/helm/charts/helm-charts-route.injectable.ts new file mode 100644 index 0000000000..22aa837be4 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/helm/charts/helm-charts-route.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { Route, routeInjectionToken } from "../../../../route-injection-token"; + +export interface HelmChartsPathParameters { + repo?: string; + chartName?: string; +} + +const helmChartsRouteInjectable = getInjectable({ + id: "helm-charts-route", + + instantiate: (): Route => ({ + path: "/helm/charts/:repo?/:chartName?", + clusterFrame: true, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default helmChartsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable.ts b/src/common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable.ts new file mode 100644 index 0000000000..1e5e895aae --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import helmChartsRouteInjectable, { HelmChartsPathParameters } from "./helm-charts-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +export type NavigateToHelmCharts = (parameters?: HelmChartsPathParameters) => void; + +const navigateToHelmChartsInjectable = getInjectable({ + id: "navigate-to-helm-charts", + + instantiate: (di): NavigateToHelmCharts => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(helmChartsRouteInjectable); + + return (parameters) => + navigateToRoute(route, { + parameters, + }); + }, +}); + +export default navigateToHelmChartsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/helm/releases/helm-releases-route.injectable.ts b/src/common/front-end-routing/routes/cluster/helm/releases/helm-releases-route.injectable.ts new file mode 100644 index 0000000000..5a90e1e1ff --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/helm/releases/helm-releases-route.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { Route, routeInjectionToken } from "../../../../route-injection-token"; + +export interface HelmReleasesPathParameters { + namespace?: string; + name?: string; +} + +const helmReleasesRouteInjectable = getInjectable({ + id: "helm-releases-route", + + instantiate: (): Route => ({ + path: "/helm/releases/:namespace?/:name?", + clusterFrame: true, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default helmReleasesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable.ts b/src/common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable.ts new file mode 100644 index 0000000000..9302cdc02d --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import helmReleasesRouteInjectable, { HelmReleasesPathParameters } from "./helm-releases-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +export type NavigateToHelmReleases = (parameters?: HelmReleasesPathParameters) => void; + +const navigateToHelmReleasesInjectable = getInjectable({ + id: "navigate-to-helm-releases", + + instantiate: (di): NavigateToHelmReleases => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(helmReleasesRouteInjectable); + + return (parameters) => + navigateToRoute(route, { parameters }); + }, +}); + +export default navigateToHelmReleasesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts b/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts new file mode 100644 index 0000000000..361771d575 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const namespacesRouteInjectable = getInjectable({ + id: "namespaces-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "namespaces"); + + return { + path: "/namespaces", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default namespacesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/namespaces/navigate-to-namespaces.injectable.ts b/src/common/front-end-routing/routes/cluster/namespaces/navigate-to-namespaces.injectable.ts new file mode 100644 index 0000000000..85fa63c695 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/namespaces/navigate-to-namespaces.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import namespacesRouteInjectable from "./namespaces-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToNamespacesInjectable = getInjectable({ + id: "navigate-to-namespaces", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(namespacesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToNamespacesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts new file mode 100644 index 0000000000..cc093f6cf3 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const endpointsRouteInjectable = getInjectable({ + id: "endpoints-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "endpoints"); + + return { + path: "/endpoints", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default endpointsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/network/endpoints/navigate-to-endpoints.injectable.ts b/src/common/front-end-routing/routes/cluster/network/endpoints/navigate-to-endpoints.injectable.ts new file mode 100644 index 0000000000..04439b3d3b --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/network/endpoints/navigate-to-endpoints.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import endpointsRouteInjectable from "./endpoints-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToEndpointsInjectable = getInjectable({ + id: "navigate-to-endpoints", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(endpointsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToEndpointsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts new file mode 100644 index 0000000000..8c30cefe74 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const ingressesRouteInjectable = getInjectable({ + id: "ingresses-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "ingresses"); + + return { + path: "/ingresses", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default ingressesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/network/ingresses/navigate-to-ingresses.injectable.ts b/src/common/front-end-routing/routes/cluster/network/ingresses/navigate-to-ingresses.injectable.ts new file mode 100644 index 0000000000..b47f9d4be3 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/network/ingresses/navigate-to-ingresses.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import ingressesRouteInjectable from "./ingresses-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToIngressesInjectable = getInjectable({ + id: "navigate-to-ingresses", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(ingressesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToIngressesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/network/network-policies/navigate-to-network-policies.injectable.ts b/src/common/front-end-routing/routes/cluster/network/network-policies/navigate-to-network-policies.injectable.ts new file mode 100644 index 0000000000..82bc76ee9f --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/network/network-policies/navigate-to-network-policies.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import networkPoliciesRouteInjectable from "./network-policies-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToNetworkPoliciesInjectable = getInjectable({ + id: "navigate-to-network-policies", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(networkPoliciesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToNetworkPoliciesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts new file mode 100644 index 0000000000..99f94d68fe --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const networkPoliciesRouteInjectable = getInjectable({ + id: "network-policies-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "networkpolicies"); + + return { + path: "/network-policies", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default networkPoliciesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable.ts b/src/common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable.ts new file mode 100644 index 0000000000..ba16fd2866 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import portForwardsRouteInjectable, { PortForwardsPathParameters } from "./port-forwards-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +export type NavigateToPortForwards = (parameters?: PortForwardsPathParameters) => void; + +const navigateToPortForwardsInjectable = getInjectable({ + id: "navigate-to-port-forwards", + + instantiate: (di): NavigateToPortForwards => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(portForwardsRouteInjectable); + + return (parameters) => + navigateToRoute(route, { parameters }); + }, +}); + +export default navigateToPortForwardsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/network/port-forwards/port-forwards-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/port-forwards/port-forwards-route.injectable.ts new file mode 100644 index 0000000000..a089033b76 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/network/port-forwards/port-forwards-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { Route, routeInjectionToken } from "../../../../route-injection-token"; + +export interface PortForwardsPathParameters { + forwardport?: string; +} + +const portForwardsRouteInjectable = getInjectable({ + id: "port-forwards-route", + + instantiate: (): Route => ({ + path: "/port-forwards/:forwardport?", + clusterFrame: true, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default portForwardsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/network/services/navigate-to-services.injectable.ts b/src/common/front-end-routing/routes/cluster/network/services/navigate-to-services.injectable.ts new file mode 100644 index 0000000000..02aa060b89 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/network/services/navigate-to-services.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import servicesRouteInjectable from "./services-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToServicesInjectable = getInjectable({ + id: "navigate-to-services", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(servicesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToServicesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts b/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts new file mode 100644 index 0000000000..efd83a3ffa --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const servicesRouteInjectable = getInjectable({ + id: "services-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "services"); + + return { + path: "/services", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default servicesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/nodes/navigate-to-nodes.injectable.ts b/src/common/front-end-routing/routes/cluster/nodes/navigate-to-nodes.injectable.ts new file mode 100644 index 0000000000..eccdd2377d --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/nodes/navigate-to-nodes.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import nodesRouteInjectable from "./nodes-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToNodesInjectable = getInjectable({ + id: "navigate-to-nodes", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(nodesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToNodesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts new file mode 100644 index 0000000000..2713f1cc18 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const nodesRouteInjectable = getInjectable({ + id: "nodes-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "nodes"); + + return { + path: "/nodes", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default nodesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts b/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts new file mode 100644 index 0000000000..9598666209 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const clusterOverviewRouteInjectable = getInjectable({ + id: "cluster-overview-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "nodes"); + + return { + path: "/overview", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default clusterOverviewRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/overview/navigate-to-cluster-overview.injectable.ts b/src/common/front-end-routing/routes/cluster/overview/navigate-to-cluster-overview.injectable.ts new file mode 100644 index 0000000000..f15dca518c --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/overview/navigate-to-cluster-overview.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import clusterOverviewRouteInjectable from "./cluster-overview-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToClusterOverviewInjectable = getInjectable({ + id: "navigate-to-cluster-overview", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(clusterOverviewRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToClusterOverviewInjectable; diff --git a/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/navigate-to-persistent-volume-claims.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/navigate-to-persistent-volume-claims.injectable.ts new file mode 100644 index 0000000000..015ad6e988 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/navigate-to-persistent-volume-claims.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import persistentVolumeClaimsRouteInjectable from "./persistent-volume-claims-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToPersistentVolumeClaimsInjectable = getInjectable({ + id: "navigate-to-persistent-volume-claims", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(persistentVolumeClaimsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToPersistentVolumeClaimsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts new file mode 100644 index 0000000000..a451c282e5 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const persistentVolumeClaimsRouteInjectable = getInjectable({ + id: "persistent-volume-claims-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "persistentvolumeclaims"); + + return { + path: "/persistent-volume-claims", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default persistentVolumeClaimsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/navigate-to-persistent-volumes.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/navigate-to-persistent-volumes.injectable.ts new file mode 100644 index 0000000000..6702dd6712 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/navigate-to-persistent-volumes.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import persistentVolumesRouteInjectable from "./persistent-volumes-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToPersistentVolumesInjectable = getInjectable({ + id: "navigate-to-persistent-volumes", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(persistentVolumesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToPersistentVolumesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts new file mode 100644 index 0000000000..68c8fa08f5 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const persistentVolumesRouteInjectable = getInjectable({ + id: "persistent-volumes-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "persistentvolumes"); + + return { + path: "/persistent-volumes", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default persistentVolumesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/storage/storage-classes/navigate-to-storage-classes.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/storage-classes/navigate-to-storage-classes.injectable.ts new file mode 100644 index 0000000000..adaba3225b --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/storage/storage-classes/navigate-to-storage-classes.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import storageClassesRouteInjectable from "./storage-classes-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToStorageClassesInjectable = getInjectable({ + id: "navigate-to-storage-classes", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(storageClassesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToStorageClassesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts b/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts new file mode 100644 index 0000000000..5499b5ba37 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const storageClassesRouteInjectable = getInjectable({ + id: "storage-classes-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "storageclasses"); + + return { + path: "/storage-classes", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default storageClassesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts new file mode 100644 index 0000000000..483397e6e1 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const clusterRoleBindingsRouteInjectable = getInjectable({ + id: "cluster-role-bindings-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "clusterrolebindings"); + + return { + path: "/cluster-role-bindings", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default clusterRoleBindingsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/navigate-to-cluster-role-bindings.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/navigate-to-cluster-role-bindings.injectable.ts new file mode 100644 index 0000000000..f6bd0bfd97 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/navigate-to-cluster-role-bindings.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import clusterRoleBindingsRouteInjectable from "./cluster-role-bindings-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToClusterRoleBindingsInjectable = getInjectable({ + id: "navigate-to-cluster-role-bindings", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(clusterRoleBindingsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToClusterRoleBindingsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts new file mode 100644 index 0000000000..fced712dbf --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const clusterRolesRouteInjectable = getInjectable({ + id: "cluster-roles-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "clusterroles"); + + return { + path: "/cluster-roles", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default clusterRolesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/navigate-to-cluster-roles.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/navigate-to-cluster-roles.injectable.ts new file mode 100644 index 0000000000..ed56679155 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/navigate-to-cluster-roles.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import clusterRolesRouteInjectable from "./cluster-roles-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToClusterRolesInjectable = getInjectable({ + id: "navigate-to-cluster-roles", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(clusterRolesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToClusterRolesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/navigate-to-pod-security-policies.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/navigate-to-pod-security-policies.injectable.ts new file mode 100644 index 0000000000..205354ba3b --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/navigate-to-pod-security-policies.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import podSecurityPoliciesRouteInjectable from "./pod-security-policies-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToPodSecurityPoliciesInjectable = getInjectable({ + id: "navigate-to-pod-security-policies", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(podSecurityPoliciesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToPodSecurityPoliciesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts new file mode 100644 index 0000000000..89220511c1 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const podSecurityPoliciesRouteInjectable = getInjectable({ + id: "pod-security-policies-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "podsecuritypolicies"); + + return { + path: "/pod-security-policies", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default podSecurityPoliciesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/role-bindings/navigate-to-role-bindings.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/role-bindings/navigate-to-role-bindings.injectable.ts new file mode 100644 index 0000000000..ce5bb3713b --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/role-bindings/navigate-to-role-bindings.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import roleBindingsRouteInjectable from "./role-bindings-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToRoleBindingsInjectable = getInjectable({ + id: "navigate-to-role-bindings", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(roleBindingsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToRoleBindingsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts new file mode 100644 index 0000000000..24e03d5cd9 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const roleBindingsRouteInjectable = getInjectable({ + id: "role-bindings-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "rolebindings"); + + return { + path: "/role-bindings", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default roleBindingsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/roles/navigate-to-roles.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/roles/navigate-to-roles.injectable.ts new file mode 100644 index 0000000000..adebb64ae1 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/roles/navigate-to-roles.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import rolesRouteInjectable from "./roles-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToRolesInjectable = getInjectable({ + id: "navigate-to-roles", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(rolesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToRolesInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts new file mode 100644 index 0000000000..097cbb1cf5 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const rolesRouteInjectable = getInjectable({ + id: "roles-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "roles"); + + return { + path: "/roles", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default rolesRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/service-accounts/navigate-to-service-accounts.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/service-accounts/navigate-to-service-accounts.injectable.ts new file mode 100644 index 0000000000..5eb1861a92 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/service-accounts/navigate-to-service-accounts.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import serviceAccountsRouteInjectable from "./service-accounts-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToServiceAccountsInjectable = getInjectable({ + id: "navigate-to-service-accounts", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(serviceAccountsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToServiceAccountsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts b/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts new file mode 100644 index 0000000000..f3424f1d8c --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const serviceAccountsRouteInjectable = getInjectable({ + id: "service-accounts-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "serviceaccounts"); + + return { + path: "/service-accounts", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default serviceAccountsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts new file mode 100644 index 0000000000..a99e13ea1f --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const cronJobsRouteInjectable = getInjectable({ + id: "cron-jobs-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "cronjobs"); + + return { + path: "/cronjobs", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default cronJobsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/navigate-to-cron-jobs.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/navigate-to-cron-jobs.injectable.ts new file mode 100644 index 0000000000..42b104055e --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/navigate-to-cron-jobs.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import cronJobsRouteInjectable from "./cron-jobs-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToCronJobsInjectable = getInjectable({ + id: "navigate-to-cron-jobs", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(cronJobsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToCronJobsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts new file mode 100644 index 0000000000..be54e18c91 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const daemonsetsRouteInjectable = getInjectable({ + id: "daemonsets-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "daemonsets"); + + return { + path: "/daemonsets", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default daemonsetsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/daemonsets/navigate-to-daemonsets.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/daemonsets/navigate-to-daemonsets.injectable.ts new file mode 100644 index 0000000000..1cc5ec8d8d --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/daemonsets/navigate-to-daemonsets.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import daemonsetsRouteInjectable from "./daemonsets-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToDaemonsetsInjectable = getInjectable({ + id: "navigate-to-daemonsets", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(daemonsetsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToDaemonsetsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts new file mode 100644 index 0000000000..65eed20f7a --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const deploymentsRouteInjectable = getInjectable({ + id: "deployments-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "deployments"); + + return { + path: "/deployments", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default deploymentsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable.ts new file mode 100644 index 0000000000..b6d2f07391 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import deploymentsRouteInjectable from "./deployments-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToDeploymentsInjectable = getInjectable({ + id: "navigate-to-deployments", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(deploymentsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToDeploymentsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts new file mode 100644 index 0000000000..5b2f087dc9 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const jobsRouteInjectable = getInjectable({ + id: "jobs-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "jobs"); + + return { + path: "/jobs", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default jobsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/jobs/navigate-to-jobs.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/jobs/navigate-to-jobs.injectable.ts new file mode 100644 index 0000000000..920c5c4de7 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/jobs/navigate-to-jobs.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import jobsRouteInjectable from "./jobs-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToJobsInjectable = getInjectable({ + id: "navigate-to-jobs", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(jobsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToJobsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/overview/navigate-to-workloads-overview.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/overview/navigate-to-workloads-overview.injectable.ts new file mode 100644 index 0000000000..224950206b --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/overview/navigate-to-workloads-overview.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import workloadsOverviewRouteInjectable from "./workloads-overview-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToWorkloadsOverviewInjectable = getInjectable({ + id: "navigate-to-workloads-overview", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(workloadsOverviewRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToWorkloadsOverviewInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/overview/workloads-overview-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/overview/workloads-overview-route.injectable.ts new file mode 100644 index 0000000000..00441c2ce9 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/overview/workloads-overview-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const workloadsOverviewRouteInjectable = getInjectable({ + id: "workloads-overview-route", + + instantiate: () => ({ + path: "/workloads", + clusterFrame: true, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default workloadsOverviewRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/pods/navigate-to-pods.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/pods/navigate-to-pods.injectable.ts new file mode 100644 index 0000000000..4eab211598 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/pods/navigate-to-pods.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import podsRouteInjectable from "./pods-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToPodsInjectable = getInjectable({ + id: "navigate-to-pods", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(podsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToPodsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts new file mode 100644 index 0000000000..b7c036cb65 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const podsRouteInjectable = getInjectable({ + id: "pods-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "pods"); + + return { + path: "/pods", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default podsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/replicasets/navigate-to-replicasets.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/replicasets/navigate-to-replicasets.injectable.ts new file mode 100644 index 0000000000..9549abab09 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/replicasets/navigate-to-replicasets.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import replicasetsRouteInjectable from "./replicasets-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToReplicasetsInjectable = getInjectable({ + id: "navigate-to-replicasets", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(replicasetsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToReplicasetsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts new file mode 100644 index 0000000000..c1822339f7 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const replicasetsRouteInjectable = getInjectable({ + id: "replicasets-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "replicasets"); + + return { + path: "/replicasets", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default replicasetsRouteInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/statefulsets/navigate-to-statefulsets.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/statefulsets/navigate-to-statefulsets.injectable.ts new file mode 100644 index 0000000000..403c7fa042 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/statefulsets/navigate-to-statefulsets.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import statefulsetsRouteInjectable from "./statefulsets-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToStatefulsetsInjectable = getInjectable({ + id: "navigate-to-statefulsets", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(statefulsetsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToStatefulsetsInjectable; diff --git a/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts b/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts new file mode 100644 index 0000000000..ca271a22c5 --- /dev/null +++ b/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable"; +import { routeInjectionToken } from "../../../../route-injection-token"; + +const statefulsetsRouteInjectable = getInjectable({ + id: "statefulsets-route", + + instantiate: (di) => { + const isAllowedResource = di.inject(isAllowedResourceInjectable, "statefulsets"); + + return { + path: "/statefulsets", + clusterFrame: true, + isEnabled: isAllowedResource, + }; + }, + + injectionToken: routeInjectionToken, +}); + +export default statefulsetsRouteInjectable; diff --git a/src/common/front-end-routing/routes/entity-settings/entity-settings-route.injectable.ts b/src/common/front-end-routing/routes/entity-settings/entity-settings-route.injectable.ts new file mode 100644 index 0000000000..be893c201f --- /dev/null +++ b/src/common/front-end-routing/routes/entity-settings/entity-settings-route.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { Route, routeInjectionToken } from "../../route-injection-token"; + +export interface EntitySettingsPathParameters { + entityId: string; +} + +const entitySettingsRouteInjectable = getInjectable({ + id: "entity-settings-route", + + instantiate: (): Route => ({ + path: "/entity/:entityId/settings", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default entitySettingsRouteInjectable; diff --git a/src/common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable.ts b/src/common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable.ts new file mode 100644 index 0000000000..bc49bc4041 --- /dev/null +++ b/src/common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import entitySettingsRouteInjectable from "./entity-settings-route.injectable"; +import { navigateToRouteInjectionToken } from "../../navigate-to-route-injection-token"; + +export type NavigateToEntitySettings = (entityId: string, targetTabId?: string) => void; + +const navigateToEntitySettingsInjectable = getInjectable({ + id: "navigate-to-entity-settings", + + instantiate: (di): NavigateToEntitySettings => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(entitySettingsRouteInjectable); + + return (entityId, targetTabId) => + navigateToRoute(route, { parameters: { entityId }, fragment: targetTabId }); + }, +}); + +export default navigateToEntitySettingsInjectable; diff --git a/src/common/front-end-routing/routes/extensions/extensions-route.injectable.ts b/src/common/front-end-routing/routes/extensions/extensions-route.injectable.ts new file mode 100644 index 0000000000..9459260412 --- /dev/null +++ b/src/common/front-end-routing/routes/extensions/extensions-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../route-injection-token"; + +const extensionsRouteInjectable = getInjectable({ + id: "extensions-route", + + instantiate: () => ({ + path: "/extensions", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default extensionsRouteInjectable; diff --git a/src/common/front-end-routing/routes/extensions/navigate-to-extensions.injectable.ts b/src/common/front-end-routing/routes/extensions/navigate-to-extensions.injectable.ts new file mode 100644 index 0000000000..2e4e6d7d4c --- /dev/null +++ b/src/common/front-end-routing/routes/extensions/navigate-to-extensions.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import extensionsRouteInjectable from "./extensions-route.injectable"; +import { navigateToRouteInjectionToken } from "../../navigate-to-route-injection-token"; + +const navigateToExtensionsInjectable = getInjectable({ + id: "navigate-to-extensions", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(extensionsRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToExtensionsInjectable; diff --git a/src/common/front-end-routing/routes/preferences/app/app-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/app/app-preferences-route.injectable.ts new file mode 100644 index 0000000000..f19321f800 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/app/app-preferences-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const appPreferencesRouteInjectable = getInjectable({ + id: "app-preferences-route", + + instantiate: () => ({ + path: "/preferences/app", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default appPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/app/navigate-to-app-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/app/navigate-to-app-preferences.injectable.ts new file mode 100644 index 0000000000..4f83d47dec --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/app/navigate-to-app-preferences.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import appPreferencesRouteInjectable from "./app-preferences-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToAppPreferencesInjectable = getInjectable({ + id: "navigate-to-app-preferences", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(appPreferencesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToAppPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/editor/editor-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/editor/editor-preferences-route.injectable.ts new file mode 100644 index 0000000000..485a5a1e18 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/editor/editor-preferences-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const editorPreferencesRouteInjectable = getInjectable({ + id: "editor-preferences-route", + + instantiate: () => ({ + path: "/preferences/editor", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default editorPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/editor/navigate-to-editor-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/editor/navigate-to-editor-preferences.injectable.ts new file mode 100644 index 0000000000..ab4be540cf --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/editor/navigate-to-editor-preferences.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import editorPreferencesRouteInjectable from "./editor-preferences-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToEditorPreferencesInjectable = getInjectable({ + id: "navigate-to-editor-preferences", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(editorPreferencesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToEditorPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/extension/extension-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/extension/extension-preferences-route.injectable.ts new file mode 100644 index 0000000000..92a97675b0 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/extension/extension-preferences-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const extensionPreferencesRouteInjectable = getInjectable({ + id: "extension-preferences-route", + + instantiate: () => ({ + path: "/preferences/extensions", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default extensionPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/extension/navigate-to-extension-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/extension/navigate-to-extension-preferences.injectable.ts new file mode 100644 index 0000000000..6e2f80d864 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/extension/navigate-to-extension-preferences.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import extensionPreferencesRouteInjectable from "./extension-preferences-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToExtensionPreferencesInjectable = getInjectable({ + id: "navigate-to-extension-preferences", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(extensionPreferencesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToExtensionPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/kubernetes/kubernetes-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/kubernetes/kubernetes-preferences-route.injectable.ts new file mode 100644 index 0000000000..7a84a965b6 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/kubernetes/kubernetes-preferences-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const kubernetesPreferencesRouteInjectable = getInjectable({ + id: "kubernetes-preferences-route", + + instantiate: () => ({ + path: "/preferences/kubernetes", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default kubernetesPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/kubernetes/navigate-to-kubernetes-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/kubernetes/navigate-to-kubernetes-preferences.injectable.ts new file mode 100644 index 0000000000..35f4de6621 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/kubernetes/navigate-to-kubernetes-preferences.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import kubernetesPreferencesRouteInjectable from "./kubernetes-preferences-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToKubernetesPreferencesInjectable = getInjectable({ + id: "navigate-to-kubernetes-preferences", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(kubernetesPreferencesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToKubernetesPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/navigate-to-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/navigate-to-preferences.injectable.ts new file mode 100644 index 0000000000..545751d7c3 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/navigate-to-preferences.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import navigateToAppPreferencesInjectable from "./app/navigate-to-app-preferences.injectable"; + +const navigateToPreferencesInjectable = getInjectable({ + id: "navigate-to-preferences", + + instantiate: (di) => di.inject(navigateToAppPreferencesInjectable), +}); + +export default navigateToPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/proxy/navigate-to-proxy-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/proxy/navigate-to-proxy-preferences.injectable.ts new file mode 100644 index 0000000000..258be70c60 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/proxy/navigate-to-proxy-preferences.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import proxyPreferencesRouteInjectable from "./proxy-preferences-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToProxyPreferencesInjectable = getInjectable({ + id: "navigate-to-proxy-preferences", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(proxyPreferencesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToProxyPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/proxy/proxy-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/proxy/proxy-preferences-route.injectable.ts new file mode 100644 index 0000000000..b07925bab5 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/proxy/proxy-preferences-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const proxyPreferencesRouteInjectable = getInjectable({ + id: "proxy-preferences-route", + + instantiate: () => ({ + path: "/preferences/proxy", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default proxyPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/telemetry/navigate-to-telemetry-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/telemetry/navigate-to-telemetry-preferences.injectable.ts new file mode 100644 index 0000000000..45f7559ba9 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/telemetry/navigate-to-telemetry-preferences.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import telemetryPreferencesRouteInjectable from "./telemetry-preferences-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToTelemetryPreferencesInjectable = getInjectable({ + id: "navigate-to-telemetry-preferences", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(telemetryPreferencesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToTelemetryPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/telemetry/telemetry-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/telemetry/telemetry-preferences-route.injectable.ts new file mode 100644 index 0000000000..f42cb83717 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/telemetry/telemetry-preferences-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const telemetryPreferencesRouteInjectable = getInjectable({ + id: "telemetry-preferences-route", + + instantiate: () => ({ + path: "/preferences/telemetry", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default telemetryPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/preferences/terminal/navigate-to-terminal-preferences.injectable.ts b/src/common/front-end-routing/routes/preferences/terminal/navigate-to-terminal-preferences.injectable.ts new file mode 100644 index 0000000000..8f73e556d3 --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/terminal/navigate-to-terminal-preferences.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import terminalPreferencesRouteInjectable from "./terminal-preferences-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token"; + +const navigateToTerminalPreferencesInjectable = getInjectable({ + id: "navigate-to-terminal-preferences", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(terminalPreferencesRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToTerminalPreferencesInjectable; diff --git a/src/common/front-end-routing/routes/preferences/terminal/terminal-preferences-route.injectable.ts b/src/common/front-end-routing/routes/preferences/terminal/terminal-preferences-route.injectable.ts new file mode 100644 index 0000000000..8f530b2b9d --- /dev/null +++ b/src/common/front-end-routing/routes/preferences/terminal/terminal-preferences-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../../route-injection-token"; + +const terminalPreferencesRouteInjectable = getInjectable({ + id: "terminal-preferences-route", + + instantiate: () => ({ + path: "/preferences/terminal", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default terminalPreferencesRouteInjectable; diff --git a/src/common/front-end-routing/routes/welcome/navigate-to-welcome.injectable.ts b/src/common/front-end-routing/routes/welcome/navigate-to-welcome.injectable.ts new file mode 100644 index 0000000000..46f5b42056 --- /dev/null +++ b/src/common/front-end-routing/routes/welcome/navigate-to-welcome.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import welcomeRouteInjectable from "./welcome-route.injectable"; +import { navigateToRouteInjectionToken } from "../../navigate-to-route-injection-token"; + +const navigateToWelcomeInjectable = getInjectable({ + id: "navigate-to-welcome", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(welcomeRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToWelcomeInjectable; diff --git a/src/common/front-end-routing/routes/welcome/welcome-route.injectable.ts b/src/common/front-end-routing/routes/welcome/welcome-route.injectable.ts new file mode 100644 index 0000000000..516ee6cca4 --- /dev/null +++ b/src/common/front-end-routing/routes/welcome/welcome-route.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { routeInjectionToken } from "../../route-injection-token"; + +const welcomeRouteInjectable = getInjectable({ + id: "welcome-route", + + instantiate: () => ({ + path: "/welcome", + clusterFrame: false, + isEnabled: computed(() => true), + }), + + injectionToken: routeInjectionToken, +}); + +export default welcomeRouteInjectable; diff --git a/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts b/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts new file mode 100644 index 0000000000..7cf871f9b9 --- /dev/null +++ b/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting"; +import { routeSpecificComponentInjectionToken } from "../../renderer/routes/route-specific-component-injection-token"; +import { routeInjectionToken } from "./route-injection-token"; +import { filter, map, matches } from "lodash/fp"; +import clusterStoreInjectable from "../cluster-store/cluster-store.injectable"; +import type { ClusterStore } from "../cluster-store/cluster-store"; +import { pipeline } from "@ogre-tools/fp"; + +describe("verify-that-all-routes-have-component", () => { + it("verify that routes have route component", async () => { + const rendererDi = getDiForUnitTesting({ doGeneralOverrides: true }); + + rendererDi.override( + clusterStoreInjectable, + () => ({ getById: (): null => null } as unknown as ClusterStore), + ); + + await rendererDi.runSetups(); + + const routes = rendererDi.injectMany(routeInjectionToken); + const routeComponents = rendererDi.injectMany( + routeSpecificComponentInjectionToken, + ); + + const routesMissingComponent = pipeline( + routes, + + map( + (route) => ({ + path: route.path, + routeComponent: routeComponents.find(matches({ route })), + }), + ), + + filter({ routeComponent: undefined }), + + map("path"), + ); + + expect(routesMissingComponent).toEqual([]); + }); +}); diff --git a/src/common/fs/path-exists.injectable.ts b/src/common/fs/path-exists.injectable.ts new file mode 100644 index 0000000000..aee6cac52b --- /dev/null +++ b/src/common/fs/path-exists.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import fsInjectable from "./fs.injectable"; + +export type PathExists = (path: string) => Promise; + +const pathExistsInjectable = getInjectable({ + id: "path-exists", + instantiate: (di): PathExists => di.inject(fsInjectable).pathExists, +}); + +export default pathExistsInjectable; diff --git a/src/common/fs/read-file-buffer.injectable.ts b/src/common/fs/read-file-buffer.injectable.ts new file mode 100644 index 0000000000..f7707dc594 --- /dev/null +++ b/src/common/fs/read-file-buffer.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import fsInjectable from "./fs.injectable"; + +const readFileBufferInjectable = getInjectable({ + id: "read-file-buffer", + + instantiate: (di) => (filePath: string) => + di.inject(fsInjectable).readFile(filePath), +}); + +export default readFileBufferInjectable; diff --git a/src/common/fs/read-file.injectable.ts b/src/common/fs/read-file.injectable.ts index 5c0bad27ca..b0a2b7233e 100644 --- a/src/common/fs/read-file.injectable.ts +++ b/src/common/fs/read-file.injectable.ts @@ -7,7 +7,9 @@ import fsInjectable from "./fs.injectable"; const readFileInjectable = getInjectable({ id: "read-file", - instantiate: (di) => di.inject(fsInjectable).readFile, + + instantiate: (di) => (filePath: string) => + di.inject(fsInjectable).readFile(filePath, "utf-8"), }); export default readFileInjectable; diff --git a/src/common/fs/read-json-file.injectable.ts b/src/common/fs/read-json-file.injectable.ts index 308d3b8a91..d270368cf9 100644 --- a/src/common/fs/read-json-file.injectable.ts +++ b/src/common/fs/read-json-file.injectable.ts @@ -3,11 +3,14 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import type { JsonValue } from "type-fest"; import fsInjectable from "./fs.injectable"; +export type ReadJson = (filePath: string) => Promise; + const readJsonFileInjectable = getInjectable({ id: "read-json-file", - instantiate: (di) => di.inject(fsInjectable).readJson, + instantiate: (di): ReadJson => di.inject(fsInjectable).readJson, }); export default readJsonFileInjectable; diff --git a/src/common/fs/write-json-file.injectable.ts b/src/common/fs/write-json-file.injectable.ts index 89aae40736..6d05e01a7c 100644 --- a/src/common/fs/write-json-file.injectable.ts +++ b/src/common/fs/write-json-file.injectable.ts @@ -8,12 +8,14 @@ import path from "path"; import type { JsonValue } from "type-fest"; import fsInjectable from "./fs.injectable"; +export type WriteJson = (filePath: string, contents: JsonValue) => Promise; + interface Dependencies { writeJson: (file: string, object: any, options?: WriteOptions | BufferEncoding | string) => Promise; ensureDir: (dir: string, options?: EnsureOptions | number) => Promise; } -const writeJsonFile = ({ writeJson, ensureDir }: Dependencies) => async (filePath: string, content: JsonValue) => { +const writeJsonFile = ({ writeJson, ensureDir }: Dependencies): WriteJson => async (filePath, content) => { await ensureDir(path.dirname(filePath), { mode: 0o755 }); await writeJson(filePath, content, { diff --git a/src/common/hotbar-store.injectable.ts b/src/common/hotbar-store.injectable.ts index 47510d9555..968abcc1e5 100644 --- a/src/common/hotbar-store.injectable.ts +++ b/src/common/hotbar-store.injectable.ts @@ -4,10 +4,16 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { HotbarStore } from "./hotbar-store"; +import catalogCatalogEntityInjectable from "./catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; -const hotbarManagerInjectable = getInjectable({ - id: "hotbar-manager", - instantiate: () => HotbarStore.getInstance(), +const hotbarStoreInjectable = getInjectable({ + id: "hotbar-store", + + instantiate: (di) => HotbarStore.createInstance({ + catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable), + }), + + causesSideEffects: true, }); -export default hotbarManagerInjectable; +export default hotbarStoreInjectable; diff --git a/src/common/hotbar-store.ts b/src/common/hotbar-store.ts index 82c697d0d7..34761e945a 100644 --- a/src/common/hotbar-store.ts +++ b/src/common/hotbar-store.ts @@ -8,23 +8,27 @@ import { BaseStore } from "./base-store"; import migrations from "../migrations/hotbar-store"; import { toJS } from "./utils"; import { CatalogEntity } from "./catalog"; -import { catalogEntity } from "../main/catalog-sources/general"; import logger from "../main/logger"; import { broadcastMessage } from "./ipc"; import { defaultHotbarCells, getEmptyHotbar, Hotbar, CreateHotbarData, CreateHotbarOptions } from "./hotbar-types"; import { hotbarTooManyItemsChannel } from "./ipc/hotbar"; +import type { GeneralEntity } from "./catalog-entities"; export interface HotbarStoreModel { hotbars: Hotbar[]; activeHotbarId: string; } +interface Dependencies { + catalogCatalogEntity: GeneralEntity; +} + export class HotbarStore extends BaseStore { readonly displayName = "HotbarStore"; @observable hotbars: Hotbar[] = []; @observable private _activeHotbarId: string; - constructor() { + constructor(private dependencies: Dependencies) { super({ configName: "lens-hotbar-store", accessPropertiesByDotNotation: false, // To make dots safe in cluster context names @@ -77,7 +81,7 @@ export class HotbarStore extends BaseStore { protected fromStore(data: Partial = {}) { if (!data.hotbars || !data.hotbars.length) { const hotbar = getEmptyHotbar("Default"); - const { metadata: { uid, name, source }} = catalogEntity; + const { metadata: { uid, name, source }} = this.dependencies.catalogCatalogEntity; const initialItem = { entity: { uid, name, source }}; hotbar.items[0] = initialItem; diff --git a/src/common/ipc-channel/create-channel/create-channel.ts b/src/common/ipc-channel/create-channel/create-channel.ts index c98709a062..2a18e3c145 100644 --- a/src/common/ipc-channel/create-channel/create-channel.ts +++ b/src/common/ipc-channel/create-channel/create-channel.ts @@ -4,7 +4,7 @@ */ import type { Channel } from "../channel"; -export const createChannel = (name: string): Channel => ({ +export const createChannel = (name: string): Channel => ({ name, _template: null, }); diff --git a/src/common/k8s-api/endpoints/crd.api.ts b/src/common/k8s-api/endpoints/crd.api.ts index 98f78a0716..f668c819f3 100644 --- a/src/common/k8s-api/endpoints/crd.api.ts +++ b/src/common/k8s-api/endpoints/crd.api.ts @@ -5,9 +5,11 @@ import { KubeCreationError, KubeObject } from "../kube-object"; import { KubeApi } from "../kube-api"; -import { crdResourcesURL } from "../../routes"; import { isClusterPageContext } from "../../utils/cluster-id-url-parsing"; import type { KubeJsonApiData } from "../kube-json-api"; +import { getLegacyGlobalDiForExtensionApi } from "../../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import customResourcesRouteInjectable from "../../front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable"; +import { buildURL } from "../../utils/buildUrl"; interface AdditionalPrinterColumnsCommon { name: string; @@ -99,7 +101,11 @@ export class CustomResourceDefinition extends KubeObject { } getResourceUrl() { - return crdResourcesURL({ + const di = getLegacyGlobalDiForExtensionApi(); + + const customResourcesRoute = di.inject(customResourcesRouteInjectable); + + return buildURL(customResourcesRoute.path, { params: { group: this.getGroup(), name: this.getPluralName(), diff --git a/src/common/logger.injectable.ts b/src/common/logger.injectable.ts new file mode 100644 index 0000000000..561494a20c --- /dev/null +++ b/src/common/logger.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import logger, { Logger } from "./logger"; + +const loggerInjectable = getInjectable({ + id: "logger", + instantiate: (): Logger => logger, +}); + +export default loggerInjectable; diff --git a/src/common/logger.ts b/src/common/logger.ts index f29008818b..29a22730f3 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -10,6 +10,14 @@ import { consoleFormat } from "winston-console-format"; import { isDebugging, isTestEnv } from "./vars"; import BrowserConsole from "winston-transport-browserconsole"; +export interface Logger { + info: (...args: any) => void; + error: (...args: any) => void; + debug: (...args: any) => void; + log: (...args: any) => void; + warn: (...args: any) => void; +} + const logLevel = process.env.LOG_LEVEL ? process.env.LOG_LEVEL : isDebugging @@ -55,7 +63,7 @@ if (ipcMain) { */ dirname: app.getPath("logs"), maxsize: 16 * 1024, - maxFiles: 16, + maxFiles: 1, tailable: true, }), ); diff --git a/src/common/rbac.ts b/src/common/rbac.ts index 6f5a282262..35760adf57 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -10,6 +10,16 @@ export type KubeResource = "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets" | "roles" | "clusterroles" | "rolebindings" | "clusterrolebindings" | "serviceaccounts"; +export enum KubeResourceEnum { + pods = "pods", + deployments = "deployments", + cronJobs = "cronjobs", + jobs = "jobs", + replicasets = "replicasets", + daemonsets = "daemonsets", + statefulsets = "statefulsets", +} + export interface KubeApiResource extends KubeApiResourceData { apiName: KubeResource; // valid api resource name (e.g. "namespaces") } diff --git a/src/common/routes/add-cluster.ts b/src/common/routes/add-cluster.ts deleted file mode 100644 index 6642bc3313..0000000000 --- a/src/common/routes/add-cluster.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const addClusterRoute: RouteProps = { - path: "/add-cluster", -}; - -export const addClusterURL = buildURL(addClusterRoute.path); diff --git a/src/common/routes/catalog.ts b/src/common/routes/catalog.ts deleted file mode 100644 index 7a74b5bce2..0000000000 --- a/src/common/routes/catalog.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export interface CatalogViewRouteParam { - group?: string; - kind?: string; -} -export const catalogRoute: RouteProps = { - path: "/catalog/:group?/:kind?", -}; - -export const getPreviousTabUrl = (path: string) => { - const [group, kind] = path.split("/"); - - return catalogURL({ - params: { - group: group || browseCatalogTab, - kind, - }, - }); -}; - -export const catalogURL = buildURL(catalogRoute.path); - -export const browseCatalogTab = "browse"; diff --git a/src/common/routes/cluster-view.ts b/src/common/routes/cluster-view.ts deleted file mode 100644 index 8ae6e9445c..0000000000 --- a/src/common/routes/cluster-view.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export interface ClusterViewRouteParams { - clusterId: string; -} - -export const clusterViewRoute: RouteProps = { - exact: true, - path: "/cluster/:clusterId", -}; - -export const clusterViewURL = buildURL(clusterViewRoute.path); diff --git a/src/common/routes/cluster.ts b/src/common/routes/cluster.ts deleted file mode 100644 index 201517fced..0000000000 --- a/src/common/routes/cluster.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const clusterRoute: RouteProps = { - path: "/overview", -}; - -export const clusterURL = buildURL(clusterRoute.path); diff --git a/src/common/routes/config-maps.ts b/src/common/routes/config-maps.ts deleted file mode 100644 index ba27fd96d8..0000000000 --- a/src/common/routes/config-maps.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const configMapsRoute: RouteProps = { - path: "/configmaps", -}; - -export interface ConfigMapsRouteParams { -} - -export const configMapsURL = buildURL(configMapsRoute.path); diff --git a/src/common/routes/config.ts b/src/common/routes/config.ts deleted file mode 100644 index fd8e055927..0000000000 --- a/src/common/routes/config.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import type { URLParams } from "../utils/buildUrl"; -import { configMapsRoute, configMapsURL } from "./config-maps"; -import { hpaRoute } from "./hpa"; -import { limitRangesRoute } from "./limit-ranges"; -import { pdbRoute } from "./pod-disruption-budgets"; -import { resourceQuotaRoute } from "./resource-quotas"; -import { secretsRoute } from "./secrets"; - -export const configRoute: RouteProps = { - path: [ - configMapsRoute, - secretsRoute, - resourceQuotaRoute, - limitRangesRoute, - hpaRoute, - pdbRoute, - ].map(route => route.path.toString()), -}; - -export const configURL = (params?: URLParams) => configMapsURL(params); diff --git a/src/common/routes/crd.ts b/src/common/routes/crd.ts deleted file mode 100644 index 18a3b7c81d..0000000000 --- a/src/common/routes/crd.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const crdRoute: RouteProps = { - path: "/crd", -}; - -export const crdDefinitionsRoute: RouteProps = { - path: `${crdRoute.path}/definitions`, -}; - -export const crdResourcesRoute: RouteProps = { - path: `${crdRoute.path}/:group/:name`, -}; - -export interface CRDListQuery { - groups?: string; -} - -export interface CRDRouteParams { - group: string; - name: string; -} - -export const crdURL = buildURL<{}, CRDListQuery>(crdDefinitionsRoute.path); -export const crdResourcesURL = buildURL(crdResourcesRoute.path); diff --git a/src/common/routes/endpoints.ts b/src/common/routes/endpoints.ts deleted file mode 100644 index 657f6d8b45..0000000000 --- a/src/common/routes/endpoints.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const endpointRoute: RouteProps = { - path: "/endpoints", -}; - -export interface EndpointRouteParams { -} - -export const endpointURL = buildURL(endpointRoute.path); diff --git a/src/common/routes/entity-settings.ts b/src/common/routes/entity-settings.ts deleted file mode 100644 index 202ae8e9a6..0000000000 --- a/src/common/routes/entity-settings.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export interface EntitySettingsRouteParams { - entityId: string; -} - -export const entitySettingsRoute: RouteProps = { - path: `/entity/:entityId/settings`, -}; - -export const entitySettingsURL = buildURL(entitySettingsRoute.path); diff --git a/src/common/routes/events.ts b/src/common/routes/events.ts deleted file mode 100644 index 70b7a3c387..0000000000 --- a/src/common/routes/events.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const eventRoute: RouteProps = { - path: "/events", -}; - -export const eventsURL = buildURL(eventRoute.path); diff --git a/src/common/routes/extensions.ts b/src/common/routes/extensions.ts deleted file mode 100644 index e9dba5421c..0000000000 --- a/src/common/routes/extensions.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const extensionsRoute: RouteProps = { - path: "/extensions", -}; - -export const extensionsURL = buildURL(extensionsRoute.path); diff --git a/src/common/routes/helm-charts.ts b/src/common/routes/helm-charts.ts deleted file mode 100644 index 3777ac0ebd..0000000000 --- a/src/common/routes/helm-charts.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; -import { helmRoute } from "./helm"; - -export const helmChartsRoute: RouteProps = { - path: `${helmRoute.path}/charts/:repo?/:chartName?`, -}; - -export interface HelmChartsRouteParams { - chartName?: string; - repo?: string; -} - -export const helmChartsURL = buildURL(helmChartsRoute.path); diff --git a/src/common/routes/helm.ts b/src/common/routes/helm.ts deleted file mode 100644 index bb5d5b6914..0000000000 --- a/src/common/routes/helm.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const helmRoute: RouteProps = { - path: "/helm", -}; - -export const helmURL = buildURL(helmRoute.path); diff --git a/src/common/routes/hpa.ts b/src/common/routes/hpa.ts deleted file mode 100644 index 2904dc933d..0000000000 --- a/src/common/routes/hpa.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const hpaRoute: RouteProps = { - path: "/hpa", -}; - -export interface HpaRouteParams { -} - -export const hpaURL = buildURL(hpaRoute.path); diff --git a/src/common/routes/index.ts b/src/common/routes/index.ts deleted file mode 100644 index 916806fa16..0000000000 --- a/src/common/routes/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export * from "./add-cluster"; -export * from "./catalog"; -export * from "./cluster-view"; -export * from "./cluster"; -export * from "./config-maps"; -export * from "./config"; -export * from "./crd"; -export * from "./endpoints"; -export * from "./entity-settings"; -export * from "./events"; -export * from "./extensions"; -export * from "./helm-charts"; -export * from "./helm"; -export * from "./hpa"; -export * from "./ingresses"; -export * from "./limit-ranges"; -export * from "./namespaces"; -export * from "./network-policies"; -export * from "./network"; -export * from "./nodes"; -export * from "./pod-disruption-budgets"; -export * from "./port-forwards"; -export * from "./preferences"; -export * from "./releases"; -export * from "./resource-quotas"; -export * from "./secrets"; -export * from "./services"; -export * from "./storage-classes"; -export * from "./storage"; -export * from "./user-management"; -export * from "./volume-claims"; -export * from "./volumes"; -export * from "./welcome"; -export * from "./workloads"; diff --git a/src/common/routes/ingresses.ts b/src/common/routes/ingresses.ts deleted file mode 100644 index fb6096aeba..0000000000 --- a/src/common/routes/ingresses.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const ingressRoute: RouteProps = { - path: "/ingresses", -}; - -export interface IngressRouteParams { -} - -export const ingressURL = buildURL(ingressRoute.path); diff --git a/src/common/routes/limit-ranges.ts b/src/common/routes/limit-ranges.ts deleted file mode 100644 index fbee123f09..0000000000 --- a/src/common/routes/limit-ranges.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const limitRangesRoute: RouteProps = { - path: "/limitranges", -}; - -export interface LimitRangeRouteParams { -} - -export const limitRangeURL = buildURL(limitRangesRoute.path); diff --git a/src/common/routes/namespaces.ts b/src/common/routes/namespaces.ts deleted file mode 100644 index 408abefeb4..0000000000 --- a/src/common/routes/namespaces.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const namespacesRoute: RouteProps = { - path: "/namespaces", -}; - -export interface NamespacesRouteParams { -} - -export const namespacesURL = buildURL(namespacesRoute.path); diff --git a/src/common/routes/network-policies.ts b/src/common/routes/network-policies.ts deleted file mode 100644 index 83a1fe0ed8..0000000000 --- a/src/common/routes/network-policies.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const networkPoliciesRoute: RouteProps = { - path: "/network-policies", -}; - -export interface NetworkPoliciesRouteParams { -} - -export const networkPoliciesURL = buildURL(networkPoliciesRoute.path); diff --git a/src/common/routes/network.ts b/src/common/routes/network.ts deleted file mode 100644 index 7a76e4f922..0000000000 --- a/src/common/routes/network.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import type { URLParams } from "../utils/buildUrl"; -import { endpointRoute } from "./endpoints"; -import { ingressRoute } from "./ingresses"; -import { networkPoliciesRoute } from "./network-policies"; -import { servicesRoute, servicesURL } from "./services"; -import { portForwardsRoute } from "./port-forwards"; - -export const networkRoute: RouteProps = { - path: [ - servicesRoute, - endpointRoute, - ingressRoute, - networkPoliciesRoute, - portForwardsRoute, - ].map(route => route.path.toString()), -}; - -export const networkURL = (params?: URLParams) => servicesURL(params); diff --git a/src/common/routes/nodes.ts b/src/common/routes/nodes.ts deleted file mode 100644 index 3a5fcf0f2f..0000000000 --- a/src/common/routes/nodes.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const nodesRoute: RouteProps = { - path: "/nodes", -}; - -export interface NodesRouteParams { -} - -export const nodesURL = buildURL(nodesRoute.path); diff --git a/src/common/routes/pod-disruption-budgets.ts b/src/common/routes/pod-disruption-budgets.ts deleted file mode 100644 index d97aa02236..0000000000 --- a/src/common/routes/pod-disruption-budgets.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const pdbRoute: RouteProps = { - path: "/poddisruptionbudgets", -}; - -export interface PodDisruptionBudgetsRouteParams { -} - -export const pdbURL = buildURL(pdbRoute.path); diff --git a/src/common/routes/port-forwards.ts b/src/common/routes/port-forwards.ts deleted file mode 100644 index 9fe74e03d5..0000000000 --- a/src/common/routes/port-forwards.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const portForwardsRoute: RouteProps = { - path: "/port-forwards/:forwardport?", -}; - -export interface PortForwardsRouteParams { - forwardport?: string; -} - -export const portForwardsURL = buildURL(portForwardsRoute.path); diff --git a/src/common/routes/preferences.ts b/src/common/routes/preferences.ts deleted file mode 100644 index 3afd81e614..0000000000 --- a/src/common/routes/preferences.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const preferencesRoute: RouteProps = { - path: "/preferences", -}; - -export const appRoute: RouteProps = { - path: `${preferencesRoute.path}/app`, -}; - -export const proxyRoute: RouteProps = { - path: `${preferencesRoute.path}/proxy`, -}; - -export const kubernetesRoute: RouteProps = { - path: `${preferencesRoute.path}/kubernetes`, -}; - -export const editorRoute: RouteProps = { - path: `${preferencesRoute.path}/editor`, -}; - -export const telemetryRoute: RouteProps = { - path: `${preferencesRoute.path}/telemetry`, -}; - -export const extensionRoute: RouteProps = { - path: `${preferencesRoute.path}/extensions`, -}; - -export const terminalRoute: RouteProps = { - path: `${preferencesRoute.path}/terminal`, -}; -export const preferencesURL = buildURL(preferencesRoute.path); -export const appURL = buildURL(appRoute.path); -export const proxyURL = buildURL(proxyRoute.path); -export const kubernetesURL = buildURL(kubernetesRoute.path); -export const editorURL = buildURL(editorRoute.path); -export const telemetryURL = buildURL(telemetryRoute.path); -export const extensionURL = buildURL(extensionRoute.path); -export const terminalURL = buildURL(terminalRoute.path); diff --git a/src/common/routes/releases.ts b/src/common/routes/releases.ts deleted file mode 100644 index 191d186570..0000000000 --- a/src/common/routes/releases.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; -import { helmRoute } from "./helm"; - -export const releaseRoute: RouteProps = { - path: `${helmRoute.path}/releases/:namespace?/:name?`, -}; - -export interface ReleaseRouteParams { - name?: string; - namespace?: string; -} - -export const releaseURL = buildURL(releaseRoute.path); diff --git a/src/common/routes/resource-quotas.ts b/src/common/routes/resource-quotas.ts deleted file mode 100644 index afe6f072e1..0000000000 --- a/src/common/routes/resource-quotas.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const resourceQuotaRoute: RouteProps = { - path: "/resourcequotas", -}; - -export interface ResourceQuotaRouteParams { -} - -export const resourceQuotaURL = buildURL(resourceQuotaRoute.path); diff --git a/src/common/routes/secrets.ts b/src/common/routes/secrets.ts deleted file mode 100644 index 4a467630bd..0000000000 --- a/src/common/routes/secrets.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const secretsRoute: RouteProps = { - path: "/secrets", -}; - -export interface SecretsRouteParams { -} - -export const secretsURL = buildURL(secretsRoute.path); diff --git a/src/common/routes/services.ts b/src/common/routes/services.ts deleted file mode 100644 index 595ea1b4c7..0000000000 --- a/src/common/routes/services.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const servicesRoute: RouteProps = { - path: "/services", -}; - -export interface ServicesRouteParams { -} - -export const servicesURL = buildURL(servicesRoute.path); diff --git a/src/common/routes/storage-classes.ts b/src/common/routes/storage-classes.ts deleted file mode 100644 index 235a1c6df4..0000000000 --- a/src/common/routes/storage-classes.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const storageClassesRoute: RouteProps = { - path: "/storage-classes", -}; - -export interface StorageClassesRouteParams { -} - -export const storageClassesURL = buildURL(storageClassesRoute.path); diff --git a/src/common/routes/storage.ts b/src/common/routes/storage.ts deleted file mode 100644 index baa5750113..0000000000 --- a/src/common/routes/storage.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import type { URLParams } from "../utils/buildUrl"; -import { storageClassesRoute } from "./storage-classes"; -import { volumeClaimsRoute, volumeClaimsURL } from "./volume-claims"; -import { volumesRoute } from "./volumes"; - -export const storageRoute: RouteProps = { - path: [ - volumeClaimsRoute, - volumesRoute, - storageClassesRoute, - ].map(route => route.path.toString()), -}; - -export const storageURL = (params?: URLParams) => volumeClaimsURL(params); diff --git a/src/common/routes/user-management.ts b/src/common/routes/user-management.ts deleted file mode 100644 index 47d781568d..0000000000 --- a/src/common/routes/user-management.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL, URLParams } from "../utils/buildUrl"; - -// Routes -export const serviceAccountsRoute: RouteProps = { - path: "/service-accounts", -}; -export const podSecurityPoliciesRoute: RouteProps = { - path: "/pod-security-policies", -}; -export const rolesRoute: RouteProps = { - path: "/roles", -}; -export const clusterRolesRoute: RouteProps = { - path: "/cluster-roles", -}; -export const roleBindingsRoute: RouteProps = { - path: "/role-bindings", -}; -export const clusterRoleBindingsRoute: RouteProps = { - path: "/cluster-role-bindings", -}; - -export const usersManagementRoute: RouteProps = { - path: [ - serviceAccountsRoute, - podSecurityPoliciesRoute, - roleBindingsRoute, - clusterRoleBindingsRoute, - rolesRoute, - clusterRolesRoute, - ].map(route => route.path.toString()), -}; - -// Route params -export interface ServiceAccountsRouteParams { -} - -export interface RoleBindingsRouteParams { -} - -export interface ClusterRoleBindingsRouteParams { -} - -export interface RolesRouteParams { -} - -export interface ClusterRolesRouteParams { -} - -// URL-builders -export const usersManagementURL = (params?: URLParams) => serviceAccountsURL(params); -export const serviceAccountsURL = buildURL(serviceAccountsRoute.path); -export const podSecurityPoliciesURL = buildURL(podSecurityPoliciesRoute.path); -export const rolesURL = buildURL(rolesRoute.path); -export const roleBindingsURL = buildURL(roleBindingsRoute.path); -export const clusterRolesURL = buildURL(clusterRolesRoute.path); -export const clusterRoleBindingsURL = buildURL(clusterRoleBindingsRoute.path); diff --git a/src/common/routes/volume-claims.ts b/src/common/routes/volume-claims.ts deleted file mode 100644 index d775395ef9..0000000000 --- a/src/common/routes/volume-claims.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const volumeClaimsRoute: RouteProps = { - path: "/persistent-volume-claims", -}; - -export interface VolumeClaimsRouteParams { -} - -export const volumeClaimsURL = buildURL(volumeClaimsRoute.path); diff --git a/src/common/routes/volumes.ts b/src/common/routes/volumes.ts deleted file mode 100644 index 89508904b0..0000000000 --- a/src/common/routes/volumes.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const volumesRoute: RouteProps = { - path: "/persistent-volumes", -}; - -export interface VolumesRouteParams { -} - -export const volumesURL = buildURL(volumesRoute.path); diff --git a/src/common/routes/welcome.ts b/src/common/routes/welcome.ts deleted file mode 100644 index 95465738a2..0000000000 --- a/src/common/routes/welcome.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL } from "../utils/buildUrl"; - -export const welcomeRoute: RouteProps = { - path: "/welcome", -}; - -export const welcomeURL = buildURL(welcomeRoute.path); diff --git a/src/common/routes/workloads.ts b/src/common/routes/workloads.ts deleted file mode 100644 index 6b053fc1bf..0000000000 --- a/src/common/routes/workloads.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { RouteProps } from "react-router"; -import { buildURL, URLParams } from "../utils/buildUrl"; -import type { KubeResource } from "../rbac"; - -// Routes -export const overviewRoute: RouteProps = { - path: "/workloads", -}; -export const podsRoute: RouteProps = { - path: "/pods", -}; -export const deploymentsRoute: RouteProps = { - path: "/deployments", -}; -export const daemonSetsRoute: RouteProps = { - path: "/daemonsets", -}; -export const statefulSetsRoute: RouteProps = { - path: "/statefulsets", -}; -export const replicaSetsRoute: RouteProps = { - path: "/replicasets", -}; -export const jobsRoute: RouteProps = { - path: "/jobs", -}; -export const cronJobsRoute: RouteProps = { - path: "/cronjobs", -}; - -export const workloadsRoute: RouteProps = { - path: [ - overviewRoute, - podsRoute, - deploymentsRoute, - daemonSetsRoute, - statefulSetsRoute, - replicaSetsRoute, - jobsRoute, - cronJobsRoute, - ].map(route => route.path.toString()), -}; - -// Route params -export interface WorkloadsOverviewRouteParams { -} - -export interface PodsRouteParams { -} - -export interface DeploymentsRouteParams { -} - -export interface DaemonSetsRouteParams { -} - -export interface StatefulSetsRouteParams { -} - -export interface ReplicaSetsRouteParams { -} - -export interface JobsRouteParams { -} - -export interface CronJobsRouteParams { -} - -// URL-builders -export const workloadsURL = (params?: URLParams) => overviewURL(params); -export const overviewURL = buildURL(overviewRoute.path); -export const podsURL = buildURL(podsRoute.path); -export const deploymentsURL = buildURL(deploymentsRoute.path); -export const daemonSetsURL = buildURL(daemonSetsRoute.path); -export const statefulSetsURL = buildURL(statefulSetsRoute.path); -export const replicaSetsURL = buildURL(replicaSetsRoute.path); -export const jobsURL = buildURL(jobsRoute.path); -export const cronJobsURL = buildURL(cronJobsRoute.path); - -export const workloadURL: Partial>> = { - "pods": podsURL, - "deployments": deploymentsURL, - "daemonsets": daemonSetsURL, - "statefulsets": statefulSetsURL, - "replicasets": replicaSetsURL, - "jobs": jobsURL, - "cronjobs": cronJobsURL, -}; diff --git a/src/common/user-store/user-store.injectable.ts b/src/common/user-store/user-store.injectable.ts index 36db7c0820..14be4c45fd 100644 --- a/src/common/user-store/user-store.injectable.ts +++ b/src/common/user-store/user-store.injectable.ts @@ -8,6 +8,7 @@ import { UserStore } from "./user-store"; const userStoreInjectable = getInjectable({ id: "user-store", instantiate: () => UserStore.createInstance(), + causesSideEffects: true, }); export default userStoreInjectable; diff --git a/src/common/utils/buildUrl.ts b/src/common/utils/buildUrl.ts index 8f7e994427..b239cf4c6e 100644 --- a/src/common/utils/buildUrl.ts +++ b/src/common/utils/buildUrl.ts @@ -11,25 +11,21 @@ export interface URLParams

{ fragment?: string; } -export function buildURL

(path: string | any) { +export function buildURL

(path: string | any, { params, query, fragment }: URLParams = {}) { const pathBuilder = compile(String(path)); - return function ({ params, query, fragment }: URLParams = {}): string { - const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : ""; - const parts = [ - pathBuilder(params), - queryParams && `?${queryParams}`, - fragment && `#${fragment}`, - ]; + const queryParams = query ? new URLSearchParams(Object.entries(query)).toString() : ""; + const parts = [ + pathBuilder(params), + queryParams && `?${queryParams}`, + fragment && `#${fragment}`, + ]; - return parts.filter(Boolean).join(""); - }; + return parts.filter(Boolean).join(""); } export function buildURLPositional

(path: string | any) { - const builder = buildURL(path); - return function (params?: P, query?: Q, fragment?: string): string { - return builder({ params, query, fragment }); + return buildURL(path, { params, query, fragment }); }; } diff --git a/src/common/utils/is-allowed-resource.injectable.ts b/src/common/utils/is-allowed-resource.injectable.ts index 9977f6e5f8..bb6129c519 100644 --- a/src/common/utils/is-allowed-resource.injectable.ts +++ b/src/common/utils/is-allowed-resource.injectable.ts @@ -2,21 +2,25 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getInjectable } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import { computed } from "mobx"; import allowedResourcesInjectable from "../cluster-store/allowed-resources.injectable"; import type { KubeResource } from "../rbac"; export type IsAllowedResource = (resource: KubeResource) => boolean; -// TODO: This injectable obscures MobX de-referencing. Make it more apparent in usage. const isAllowedResourceInjectable = getInjectable({ id: "is-allowed-resource", - instantiate: (di) => { + instantiate: (di, resourceName: KubeResource) => { const allowedResources = di.inject(allowedResourcesInjectable); - return (resource: KubeResource) => allowedResources.get().has(resource); + return computed(() => allowedResources.get().has(resourceName)); }, + + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (_, resource: KubeResource) => resource, + }), }); export default isAllowedResourceInjectable; diff --git a/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.test.ts b/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.test.ts index 3e3cbb6ff1..c7ebd07872 100644 --- a/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.test.ts +++ b/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.test.ts @@ -8,7 +8,7 @@ import { getInjectable, Injectable, } from "@ogre-tools/injectable"; -import { setLegacyGlobalDiForExtensionApi } from "./legacy-global-di-for-extension-api"; +import { Environments, setLegacyGlobalDiForExtensionApi } from "./legacy-global-di-for-extension-api"; import { asLegacyGlobalObjectForExtensionApiWithModifications } from "./as-legacy-global-object-for-extension-api-with-modifications"; describe("asLegacyGlobalObjectForExtensionApiWithModifications", () => { @@ -24,7 +24,7 @@ describe("asLegacyGlobalObjectForExtensionApiWithModifications", () => { jest.spyOn(di, "inject"); - setLegacyGlobalDiForExtensionApi(di); + setLegacyGlobalDiForExtensionApi(di, Environments.renderer); someInjectable = getInjectable({ id: "some-injectable", diff --git a/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts b/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts index 340a97c941..733a20908b 100644 --- a/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts +++ b/src/extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications.ts @@ -16,7 +16,4 @@ export const asLegacyGlobalObjectForExtensionApiWithModifications = < injectable: Injectable, modificationObject: ModificationObject, ) => - Object.assign( - asLegacyGlobalForExtensionApi(injectable), - modificationObject, - ); + Object.assign(asLegacyGlobalForExtensionApi(injectable), modificationObject); diff --git a/src/extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api.ts b/src/extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api.ts index ccb6403ce6..4e4dfba0a8 100644 --- a/src/extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api.ts +++ b/src/extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api.ts @@ -4,10 +4,29 @@ */ import type { DiContainer } from "@ogre-tools/injectable"; -let legacyGlobalDi: DiContainer; +const legacyGlobalDis = new Map(); -export const setLegacyGlobalDiForExtensionApi = (di: DiContainer) => { - legacyGlobalDi = di; +export enum Environments { + renderer, + main, +} + +export const setLegacyGlobalDiForExtensionApi = ( + di: DiContainer, + environment: Environments, +) => { + legacyGlobalDis.set(environment, di); }; -export const getLegacyGlobalDiForExtensionApi = () => legacyGlobalDi; +export const getLegacyGlobalDiForExtensionApi = () => { + const globalDis = [...legacyGlobalDis.values()]; + + if (globalDis.length > 1) { + throw new Error("Tried to get DI container using legacy globals where there is multiple containers available."); + } + + return globalDis[0]; +}; + +export const getEnvironmentSpecificLegacyGlobalDiForExtensionApi = (environment: Environments) => + legacyGlobalDis.get(environment); diff --git a/src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable.ts b/src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable.ts index 4851771285..5c80330c0c 100644 --- a/src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable.ts +++ b/src/extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable.ts @@ -15,6 +15,8 @@ const fileSystemProvisionerStoreInjectable = getInjectable({ directoryForExtensionDataInjectable, ), }), + + causesSideEffects: true, }); export default fileSystemProvisionerStoreInjectable; diff --git a/src/extensions/extension-loader/extension-installation-counter.injectable.ts b/src/extensions/extension-loader/extension-installation-counter.injectable.ts new file mode 100644 index 0000000000..2510a679dc --- /dev/null +++ b/src/extensions/extension-loader/extension-installation-counter.injectable.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; + +const extensionInstallationCounterInjectable = getInjectable({ + id: "extension-installation-counter", + instantiate: () => new Map(), +}); + +export default extensionInstallationCounterInjectable; diff --git a/src/extensions/extension-loader/extension-loader.injectable.ts b/src/extensions/extension-loader/extension-loader.injectable.ts index e6dd489ecc..36d8a7d9f6 100644 --- a/src/extensions/extension-loader/extension-loader.injectable.ts +++ b/src/extensions/extension-loader/extension-loader.injectable.ts @@ -5,8 +5,10 @@ import { getInjectable } from "@ogre-tools/injectable"; import { ExtensionLoader } from "./extension-loader"; import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable"; -import createExtensionInstanceInjectable - from "./create-extension-instance/create-extension-instance.injectable"; +import createExtensionInstanceInjectable from "./create-extension-instance/create-extension-instance.injectable"; +import { extensionRegistratorInjectionToken } from "./extension-registrator-injection-token"; +import extensionInstallationCounterInjectable + from "./extension-installation-counter.injectable"; const extensionLoaderInjectable = getInjectable({ id: "extension-loader", @@ -15,6 +17,8 @@ const extensionLoaderInjectable = getInjectable({ new ExtensionLoader({ updateExtensionsState: di.inject(updateExtensionsStateInjectable), createExtensionInstance: di.inject(createExtensionInstanceInjectable), + extensionRegistrators: di.injectMany(extensionRegistratorInjectionToken), + extensionInstallationCounter: di.inject(extensionInstallationCounterInjectable), }), }); diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index 5db134dd4c..33e9354284 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -25,6 +25,8 @@ const logModule = "[EXTENSIONS-LOADER]"; interface Dependencies { updateExtensionsState: (extensionsState: Record) => void; createExtensionInstance: (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => LensExtension; + extensionRegistrators: ((extension: LensExtension, extensionInstallationCount: number) => Promise)[]; + extensionInstallationCounter: Map; } export interface ExtensionLoading { @@ -247,7 +249,6 @@ export class ExtensionLoader { return this.autoInitExtensions(async (extension: LensRendererExtension) => { const removeItems = [ - registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension), registries.EntitySettingRegistry.getInstance().add(extension.entitySettings), registries.CatalogEntityDetailRegistry.getInstance().add(extension.catalogEntityDetailItems), ]; @@ -274,8 +275,6 @@ export class ExtensionLoader { } const removeItems = [ - registries.ClusterPageRegistry.getInstance().add(extension.clusterPages, extension), - registries.ClusterPageMenuRegistry.getInstance().add(extension.clusterPageMenus, extension), registries.KubeObjectDetailRegistry.getInstance().add(extension.kubeObjectDetailItems), ]; @@ -298,8 +297,8 @@ export class ExtensionLoader { // 3. Call .enable for each extension // 4. Return ExtensionLoading[] - const extensions = [...installedExtensions.entries()] - .map(([extId, extension]) => { + const extensionInstallationPromises = [...installedExtensions.entries()] + .map(async ([extId, extension]) => { const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name); if (extension.isCompatible && extension.isEnabled && !alreadyInit) { @@ -317,6 +316,12 @@ export class ExtensionLoader { extension, ); + const installationCount = (this.dependencies.extensionInstallationCounter.get(instance.sanitizedExtensionId) | 0) + 1; + + this.dependencies.extensionInstallationCounter.set(instance.sanitizedExtensionId, installationCount); + + await Promise.all(this.dependencies.extensionRegistrators.map(x => x(instance, installationCount))); + this.instances.set(extId, instance); return { @@ -332,9 +337,11 @@ export class ExtensionLoader { } return null; - }) - // Remove null values - .filter(extension => Boolean(extension)); + }); + + const extensionInstallations = (await Promise.all(extensionInstallationPromises)); + + const extensions = extensionInstallations.filter(extension => Boolean(extension)); // We first need to wait until each extension's `onActivate` is resolved or rejected, // as this might register new catalog categories. Afterwards we can safely .enable the extension. diff --git a/src/extensions/extension-loader/extension-registrator-injection-token.ts b/src/extensions/extension-loader/extension-registrator-injection-token.ts new file mode 100644 index 0000000000..6d6d11531c --- /dev/null +++ b/src/extensions/extension-loader/extension-registrator-injection-token.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { LensExtension } from "../lens-extension"; + +export const extensionRegistratorInjectionToken = getInjectionToken< + (extension: LensExtension, extensionInstallationCount: number) => Promise + >({ + id: "extension-registrator-token", + }); diff --git a/src/extensions/extension-loader/update-extensions-state/update-extensions-state.injectable.ts b/src/extensions/extension-loader/update-extensions-state/update-extensions-state.injectable.ts index e808246033..754375dd5a 100644 --- a/src/extensions/extension-loader/update-extensions-state/update-extensions-state.injectable.ts +++ b/src/extensions/extension-loader/update-extensions-state/update-extensions-state.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import extensionsStoreInjectable from "../../extensions-store/extensions-store.injectable"; const updateExtensionsStateInjectable = getInjectable({ - id: "upadte-extensions-state", + id: "update-extensions-state", instantiate: (di) => di.inject(extensionsStoreInjectable).mergeState, }); diff --git a/src/extensions/extensions-store/extensions-store.injectable.ts b/src/extensions/extensions-store/extensions-store.injectable.ts index 352922143a..b31817e2e9 100644 --- a/src/extensions/extensions-store/extensions-store.injectable.ts +++ b/src/extensions/extensions-store/extensions-store.injectable.ts @@ -8,6 +8,7 @@ import { ExtensionsStore } from "./extensions-store"; const extensionsStoreInjectable = getInjectable({ id: "extensions-store", instantiate: () => ExtensionsStore.createInstance(), + causesSideEffects: true, }); export default extensionsStoreInjectable; diff --git a/src/extensions/lens-extension.ts b/src/extensions/lens-extension.ts index bdfb4578e5..6df8e3c08a 100644 --- a/src/extensions/lens-extension.ts +++ b/src/extensions/lens-extension.ts @@ -32,6 +32,10 @@ export class LensExtension { readonly manifestPath: string; readonly isBundled: boolean; + get sanitizedExtensionId() { + return sanitizeExtensionName(this.name); + } + protocolHandlers: ProtocolHandlerRegistration[] = []; @observable private _isEnabled = false; @@ -90,6 +94,7 @@ export class LensExtension { this[Disposers].push(...await register(this)); logger.info(`[EXTENSION]: enabled ${this.name}@${this.version}`); + } catch (error) { logger.error(`[EXTENSION]: failed to activate ${this.name}@${this.version}: ${error}`); } @@ -129,6 +134,12 @@ export function sanitizeExtensionName(name: string) { return name.replace("@", "").replace("/", "--"); } +export const getSanitizedPath = (...parts: string[]) => parts + .filter(Boolean) + .join("/") + .replace(/\/+/g, "/") + .replace(/\/$/, ""); // normalize multi-slashes (e.g. coming from page.id) + export function extensionDisplayName(name: string, version: string) { return `${name}@${version}`; } diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index 0c16b142a7..3baa9fd8bc 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -5,7 +5,6 @@ import type * as registries from "./registries"; import { Disposers, LensExtension } from "./lens-extension"; -import { getExtensionPageUrl } from "./registries/page-registry"; import type { CatalogEntity } from "../common/catalog"; import type { Disposer } from "../common/utils"; import { catalogEntityRegistry, EntityFilter } from "../renderer/api/catalog-entity-registry"; @@ -22,6 +21,13 @@ import type { StatusBarRegistration } from "../renderer/components/status-bar/st import type { KubeObjectMenuRegistration } from "../renderer/components/kube-object-menu/dependencies/kube-object-menu-items/kube-object-menu-registration"; import type { WorkloadsOverviewDetailRegistration } from "../renderer/components/+workloads-overview/workloads-overview-detail-registration"; import type { KubeObjectStatusRegistration } from "../renderer/components/kube-object-status-icon/kube-object-status-registration"; +import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "./as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import routesInjectable from "../renderer/routes/routes.injectable"; +import { fromPairs, map, matches, toPairs } from "lodash/fp"; +import extensionPageParametersInjectable from "../renderer/routes/extension-page-parameters.injectable"; +import { pipeline } from "@ogre-tools/fp"; +import { getExtensionRoutePath } from "../renderer/routes/get-extension-route-path"; +import { navigateToRouteInjectionToken } from "../common/front-end-routing/navigate-to-route-injection-token"; export class LensRendererExtension extends LensExtension { globalPages: registries.PageRegistration[] = []; @@ -43,14 +49,48 @@ export class LensRendererExtension extends LensExtension { customCategoryViews: CustomCategoryViewRegistration[] = []; async navigate

(pageId?: string, params?: P) { - const { navigate } = await import("../renderer/navigation"); - const pageUrl = getExtensionPageUrl({ - extensionId: this.name, - pageId, - params: params ?? {}, // compile to url with params - }); + const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi( + Environments.renderer, + ); - navigate(pageUrl); + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const routes = di.inject(routesInjectable).get(); + + const targetRegistration = [...this.globalPages, ...this.clusterPages].find( + registration => registration.id === (pageId || undefined), + ); + + const targetRoutePath = getExtensionRoutePath(this, targetRegistration.id); + + const targetRoute = routes.find(matches({ path: targetRoutePath })); + + if (targetRoute) { + const normalizedParams = di.inject(extensionPageParametersInjectable, { + extension: this, + registration: targetRegistration, + }); + + const query = pipeline( + params, + + toPairs, + + map(([key, value]) => { + const normalizedParam = normalizedParams[key]; + + return [ + key, + normalizedParam.stringify(value), + ]; + }), + + fromPairs, + ); + + navigateToRoute(targetRoute, { + query, + }); + } } /** diff --git a/src/extensions/registries/__tests__/page-registry.test.ts b/src/extensions/registries/__tests__/page-registry.test.ts deleted file mode 100644 index 34c921cef5..0000000000 --- a/src/extensions/registries/__tests__/page-registry.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { jest } from "@jest/globals"; -import { ClusterPageRegistry, getExtensionPageUrl, GlobalPageRegistry, PageParams } from "../page-registry"; -import { LensExtension } from "../../lens-extension"; -import React from "react"; -import fse from "fs-extra"; -import { Console } from "console"; -import { stderr, stdout } from "process"; -import { ThemeStore } from "../../../renderer/theme.store"; -import { UserStore } from "../../../common/user-store"; -import { getDisForUnitTesting } from "../../../test-utils/get-dis-for-unit-testing"; -import mockFs from "mock-fs"; - -jest.mock("electron", () => ({ - app: { - getVersion: () => "99.99.99", - getName: () => "lens", - setName: jest.fn(), - setPath: jest.fn(), - getPath: () => "tmp", - getLocale: () => "en", - setLoginItemSettings: jest.fn(), - }, - ipcMain: { - on: jest.fn(), - handle: jest.fn(), - }, - ipcRenderer: { - on: jest.fn(), - invoke: jest.fn(), - }, -})); - -console = new Console(stdout, stderr); - -let ext: LensExtension = null; - -describe("page registry tests", () => { - beforeEach(async () => { - const dis = getDisForUnitTesting({ doGeneralOverrides: true }); - - mockFs(); - - await dis.runSetups(); - - ext = new LensExtension({ - manifest: { - name: "foo-bar", - version: "0.1.1", - }, - id: "/this/is/fake/package.json", - absolutePath: "/absolute/fake/", - manifestPath: "/this/is/fake/package.json", - isBundled: false, - isEnabled: true, - isCompatible: true, - }); - UserStore.createInstance(); - ThemeStore.createInstance(); - ClusterPageRegistry.createInstance(); - GlobalPageRegistry.createInstance().add({ - id: "page-with-params", - components: { - Page: () => React.createElement("Page with params"), - }, - params: { - test1: "test1-default", - test2: "", // no default value, just declaration - }, - }, ext); - GlobalPageRegistry.createInstance().add([ - { - id: "test-page", - components: { - Page: () => React.createElement("Text"), - }, - }, - { - id: "another-page", - components: { - Page: () => React.createElement("Text"), - }, - }, - { - components: { - Page: () => React.createElement("Default"), - }, - }, - ], ext); - }); - - afterEach(() => { - GlobalPageRegistry.resetInstance(); - ClusterPageRegistry.resetInstance(); - ThemeStore.resetInstance(); - UserStore.resetInstance(); - fse.remove("tmp"); - mockFs.restore(); - }); - - describe("getPageUrl", () => { - it("returns a page url for extension", () => { - expect(getExtensionPageUrl({ extensionId: ext.name })).toBe("/extension/foo-bar"); - }); - - it("allows to pass base url as parameter", () => { - expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "/test" })).toBe("/extension/foo-bar/test"); - }); - - it("removes @ and replace `/` to `--`", () => { - expect(getExtensionPageUrl({ extensionId: "@foo/bar" })).toBe("/extension/foo--bar"); - }); - - it("adds / prefix", () => { - expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "test" })).toBe("/extension/foo-bar/test"); - }); - - it("normalize possible multi-slashes in page.id", () => { - expect(getExtensionPageUrl({ extensionId: ext.name, pageId: "//test/" })).toBe("/extension/foo-bar/test"); - }); - - it("gets page url with custom params", () => { - const params: PageParams = { test1: "one", test2: "2" }; - const searchParams = new URLSearchParams(params); - const pageUrl = getExtensionPageUrl({ - extensionId: ext.name, - pageId: "page-with-params", - params, - }); - - expect(pageUrl).toBe(`/extension/foo-bar/page-with-params?${searchParams}`); - }); - - it("gets page url with default custom params", () => { - const defaultPageUrl = getExtensionPageUrl({ - extensionId: ext.name, - pageId: "page-with-params", - }); - - expect(defaultPageUrl).toBe(`/extension/foo-bar/page-with-params?test1=test1-default`); - }); - }); - - describe("globalPageRegistry", () => { - describe("getByPageTarget", () => { - it("matching to first registered page without id", () => { - const page = GlobalPageRegistry.getInstance().getByPageTarget({ extensionId: ext.name }); - - expect(page.id).toEqual(undefined); - expect(page.extensionId).toEqual(ext.name); - expect(page.url).toEqual(getExtensionPageUrl({ extensionId: ext.name })); - }); - - it("returns matching page", () => { - const page = GlobalPageRegistry.getInstance().getByPageTarget({ - pageId: "test-page", - extensionId: ext.name, - }); - - expect(page.id).toEqual("test-page"); - }); - - it("returns null if target not found", () => { - const page = GlobalPageRegistry.getInstance().getByPageTarget({ - pageId: "wrong-page", - extensionId: ext.name, - }); - - expect(page).toBeNull(); - }); - }); - }); -}); diff --git a/src/extensions/registries/page-menu-registry.ts b/src/extensions/registries/page-menu-registry.ts index 0c18bd07c0..d69bcfadfa 100644 --- a/src/extensions/registries/page-menu-registry.ts +++ b/src/extensions/registries/page-menu-registry.ts @@ -6,9 +6,7 @@ // Extensions-api -> Register page menu items import type { IconProps } from "../../renderer/components/icon"; import type React from "react"; -import type { PageTarget, RegisteredPage } from "./page-registry"; -import type { LensExtension } from "../lens-extension"; -import { BaseRegistry } from "./base-registry"; +import type { PageTarget } from "./page-registry"; export interface ClusterPageMenuRegistration { id?: string; @@ -21,36 +19,3 @@ export interface ClusterPageMenuRegistration { export interface ClusterPageMenuComponents { Icon: React.ComponentType; } - -export class ClusterPageMenuRegistry extends BaseRegistry { - add(items: ClusterPageMenuRegistration[], ext: LensExtension) { - const normalizedItems = items.map(menuItem => { - menuItem.target = { - extensionId: ext.name, - ...(menuItem.target || {}), - }; - - return menuItem; - }); - - return super.add(normalizedItems); - } - - getRootItems() { - return this.getItems().filter((item) => !item.parentId); - } - - getSubItems(parent: ClusterPageMenuRegistration) { - return this.getItems().filter((item) => ( - item.parentId === parent.id && - item.target.extensionId === parent.target.extensionId - )); - } - - getByPage({ id: pageId, extensionId }: RegisteredPage) { - return this.getItems().find((item) => ( - item.target.pageId == pageId && - item.target.extensionId === extensionId - )); - } -} diff --git a/src/extensions/registries/page-registry.ts b/src/extensions/registries/page-registry.ts index 79c1c81893..c4520b11e5 100644 --- a/src/extensions/registries/page-registry.ts +++ b/src/extensions/registries/page-registry.ts @@ -2,15 +2,10 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { PageParamInit, PageParam } from "../../renderer/navigation"; // Extensions-api -> Custom page registration -import React from "react"; -import { observer } from "mobx-react"; -import { BaseRegistry } from "./base-registry"; -import { LensExtension, LensExtensionId, sanitizeExtensionName } from "../lens-extension"; -import { createPageParam, PageParam, PageParamInit, searchParamsOptions } from "../../renderer/navigation"; - export interface PageRegistration { /** * Page ID, part of extension's page url, must be unique within same extension @@ -48,95 +43,3 @@ export interface RegisteredPage { params: PageParams; // normalized params components: PageComponents; // normalized components } - -export function getExtensionPageUrl(target: PageTarget): string { - const { extensionId, pageId = "", params: targetParams = {}} = target; - - const pagePath = ["/extension", sanitizeExtensionName(extensionId), pageId] - .filter(Boolean) - .join("/").replace(/\/+/g, "/").replace(/\/$/, ""); // normalize multi-slashes (e.g. coming from page.id) - - const pageUrl = new URL(pagePath, `http://localhost`); - - // stringify params to matched target page - const registeredPage = GlobalPageRegistry.getInstance().getByPageTarget(target) || ClusterPageRegistry.getInstance().getByPageTarget(target); - - if (registeredPage?.params) { - Object.entries(registeredPage.params).forEach(([name, param]) => { - pageUrl.searchParams.delete(name); // first off, clear existing value(s) - - param.stringify(targetParams[name]).forEach(value => { - if (searchParamsOptions.skipEmpty && !value) return; - pageUrl.searchParams.append(name, value); - }); - }); - } - - return pageUrl.href.replace(pageUrl.origin, ""); -} - -class PageRegistry extends BaseRegistry { - protected getRegisteredItem(page: PageRegistration, ext: LensExtension): RegisteredPage { - const { id: pageId } = page; - const extensionId = ext.name; - const params = this.normalizeParams(extensionId, page.params); - const components = this.normalizeComponents(page.components, params); - const url = getExtensionPageUrl({ extensionId, pageId }); - - return { - id: pageId, extensionId, params, components, url, - }; - } - - protected normalizeComponents(components: PageComponents, params?: PageParams): PageComponents { - if (params) { - const { Page } = components; - - // inject extension's page component props.params - components.Page = observer((props: object) => React.createElement(Page, { params, ...props })); - } - - return components; - } - - protected normalizeParams(extensionId: LensExtensionId, params?: PageParams>): PageParams { - if (!params) return undefined; - const normalizedParams: PageParams = {}; - - Object.entries(params).forEach(([paramName, paramValue]) => { - const paramInit: PageParamInit = { - name: paramName, - prefix: `${extensionId}:`, - defaultValue: paramValue, - }; - - // handle non-string params - if (typeof paramValue !== "string") { - const { defaultValue: value, parse, stringify } = paramValue; - - const notAStringValue = typeof value !== "string" || ( - Array.isArray(value) && !value.every(value => typeof value === "string") - ); - - if (notAStringValue && !(parse || stringify)) { - throw new Error(`PageRegistry: param's "${paramName}" initialization has failed: paramInit.parse() and paramInit.stringify() are required for non string | string[] "defaultValue"`); - } - - paramInit.defaultValue = value; - paramInit.parse = parse; - paramInit.stringify = stringify; - } - - normalizedParams[paramName] = createPageParam(paramInit); - }); - - return normalizedParams; - } - - getByPageTarget(target: PageTarget): RegisteredPage | null { - return this.getItems().find(page => page.extensionId === target.extensionId && page.id === target.pageId) || null; - } -} - -export class ClusterPageRegistry extends PageRegistry {} -export class GlobalPageRegistry extends PageRegistry {} diff --git a/src/extensions/renderer-api/k8s-api.ts b/src/extensions/renderer-api/k8s-api.ts index 1f3829f0cc..f4952b2cd6 100644 --- a/src/extensions/renderer-api/k8s-api.ts +++ b/src/extensions/renderer-api/k8s-api.ts @@ -4,15 +4,20 @@ */ import type { KubeResource } from "../../common/rbac"; import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable"; -import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; import { castArray } from "lodash/fp"; +import { getLegacyGlobalDiForExtensionApi } from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; export function isAllowedResource(resource: KubeResource | KubeResource[]) { - const _isAllowedResource = asLegacyGlobalFunctionForExtensionApi(isAllowedResourceInjectable); - const resources = castArray(resource); - return resources.every(x => _isAllowedResource(x)); + const di = getLegacyGlobalDiForExtensionApi(); + + return resources.every((resourceName: any) => { + const _isAllowedResource = di.inject(isAllowedResourceInjectable, resourceName); + + // Note: Legacy isAllowedResource does not advertise reactivity + return _isAllowedResource.get(); + }); } export { ResourceStack } from "../../common/k8s/resource-stack"; diff --git a/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts b/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts index 4d3820fd37..9c0155570a 100644 --- a/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts +++ b/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts @@ -48,6 +48,10 @@ describe("kubeconfig-sync.source tests", () => { createCluster: di.inject(createClusterInjectionToken), }); + di.override(clusterStoreInjectable, () => + ClusterStore.createInstance({ createCluster: () => null }), + ); + di.inject(clusterStoreInjectable); ClusterManager.createInstance(); diff --git a/src/main/catalog-sources/general.ts b/src/main/catalog-sources/general.ts deleted file mode 100644 index 4738f6ac78..0000000000 --- a/src/main/catalog-sources/general.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { observable } from "mobx"; -import { GeneralEntity } from "../../common/catalog-entities/general"; -import { catalogURL, preferencesURL, welcomeURL } from "../../common/routes"; -import { catalogEntityRegistry } from "../catalog"; - -export const catalogEntity = new GeneralEntity({ - metadata: { - uid: "catalog-entity", - name: "Catalog", - source: "app", - labels: {}, - }, - spec: { - path: catalogURL(), - icon: { - material: "view_list", - background: "#3d90ce", - }, - }, - status: { - phase: "active", - }, -}); - -const preferencesEntity = new GeneralEntity({ - metadata: { - uid: "preferences-entity", - name: "Preferences", - source: "app", - labels: {}, - }, - spec: { - path: preferencesURL(), - icon: { - material: "settings", - background: "#3d90ce", - }, - }, - status: { - phase: "active", - }, -}); - -const welcomePageEntity = new GeneralEntity({ - metadata: { - uid: "welcome-page-entity", - name: "Welcome Page", - source: "app", - labels: {}, - }, - spec: { - path: welcomeURL(), - icon: { - material: "meeting_room", - background: "#3d90ce", - }, - }, - status: { - phase: "active", - }, -}); - -const generalEntities = observable([ - catalogEntity, - preferencesEntity, - welcomePageEntity, -]); - -export function syncGeneralEntities() { - catalogEntityRegistry.addObservableSource("lens:general", generalEntities); -} diff --git a/src/main/catalog-sources/index.ts b/src/main/catalog-sources/index.ts index c66bbd5a8f..98c3f08536 100644 --- a/src/main/catalog-sources/index.ts +++ b/src/main/catalog-sources/index.ts @@ -4,4 +4,3 @@ */ export { syncWeblinks } from "./weblinks"; -export { syncGeneralEntities } from "./general"; diff --git a/src/main/catalog-sources/sync-general-catalog-entities.injectable.ts b/src/main/catalog-sources/sync-general-catalog-entities.injectable.ts new file mode 100644 index 0000000000..d1d7895f29 --- /dev/null +++ b/src/main/catalog-sources/sync-general-catalog-entities.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import generalCatalogEntitiesInjectable from "../../common/catalog-entities/general-catalog-entities/general-catalog-entities.injectable"; +import catalogEntityRegistryInjectable from "../catalog/catalog-entity-registry.injectable"; + +const syncGeneralCatalogEntitiesInjectable = getInjectable({ + id: "sync-general-catalog-entities", + + instantiate: (di) => { + const generalCatalogEntities = di.inject(generalCatalogEntitiesInjectable); + const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); + + return () => { + catalogEntityRegistry.addObservableSource( + "lens:general", + generalCatalogEntities, + ); + }; + }, +}); + +export default syncGeneralCatalogEntitiesInjectable; diff --git a/src/main/catalog/catalog-entity-registry.injectable.ts b/src/main/catalog/catalog-entity-registry.injectable.ts new file mode 100644 index 0000000000..37584bab78 --- /dev/null +++ b/src/main/catalog/catalog-entity-registry.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { catalogEntityRegistry } from "./catalog-entity-registry"; + +const catalogEntityRegistryInjectable = getInjectable({ + id: "catalog-entity-registry", + instantiate: () => catalogEntityRegistry, + causesSideEffects: true, +}); + +export default catalogEntityRegistryInjectable; diff --git a/src/main/getDi.ts b/src/main/getDi.ts index aa96e2ad17..d6f76122b7 100644 --- a/src/main/getDi.ts +++ b/src/main/getDi.ts @@ -4,7 +4,7 @@ */ import { createContainer } from "@ogre-tools/injectable"; -import { setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; export const getDi = () => { const di = createContainer( @@ -13,7 +13,7 @@ export const getDi = () => { getRequireContextForCommonCode, ); - setLegacyGlobalDiForExtensionApi(di); + setLegacyGlobalDiForExtensionApi(di, Environments.main); return di; }; diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index 777fc47892..262f3e6883 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -4,10 +4,10 @@ */ import glob from "glob"; -import { memoize, kebabCase } from "lodash/fp"; +import { kebabCase, memoize, noop } from "lodash/fp"; import { createContainer } from "@ogre-tools/injectable"; -import { setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import getElectronAppPathInjectable from "./app-paths/get-electron-app-path/get-electron-app-path.injectable"; import setElectronAppPathInjectable from "./app-paths/set-electron-app-path/set-electron-app-path.injectable"; import appNameInjectable from "./app-paths/app-name/app-name.injectable"; @@ -16,14 +16,24 @@ import writeJsonFileInjectable from "../common/fs/write-json-file.injectable"; import readJsonFileInjectable from "../common/fs/read-json-file.injectable"; import readFileInjectable from "../common/fs/read-file.injectable"; import directoryForBundledBinariesInjectable from "../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable"; +import loggerInjectable from "../common/logger.injectable"; import spawnInjectable from "./child-process/spawn.injectable"; +import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable"; +import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store"; +import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store.injectable"; +import type { FileSystemProvisionerStore } from "../extensions/extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store"; +import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable"; +import type { ClusterStore } from "../common/cluster-store/cluster-store"; +import type { Cluster } from "../common/cluster/cluster"; +import userStoreInjectable from "../common/user-store/user-store.injectable"; +import type { UserStore } from "../common/user-store"; export const getDiForUnitTesting = ( { doGeneralOverrides } = { doGeneralOverrides: false }, ) => { const di = createContainer(); - setLegacyGlobalDiForExtensionApi(di); + setLegacyGlobalDiForExtensionApi(di, Environments.main); for (const filePath of getInjectableFilePaths()) { // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -38,6 +48,15 @@ export const getDiForUnitTesting = ( di.preventSideEffects(); if (doGeneralOverrides) { + // eslint-disable-next-line unused-imports/no-unused-vars-ts + di.override(extensionsStoreInjectable, () => ({ isEnabled: ({ id, isBundled }) => false }) as ExtensionsStore); + + di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore); + + // eslint-disable-next-line unused-imports/no-unused-vars-ts + di.override(clusterStoreInjectable, () => ({ getById: (id): Cluster => ({}) as Cluster }) as ClusterStore); + di.override(userStoreInjectable, () => ({}) as UserStore); + di.override( getElectronAppPathInjectable, () => (name: string) => `some-electron-app-path-for-${kebabCase(name)}`, @@ -47,8 +66,9 @@ export const getDiForUnitTesting = ( di.override(appNameInjectable, () => "some-electron-app-name"); di.override(registerChannelInjectable, () => () => undefined); di.override(directoryForBundledBinariesInjectable, () => "some-bin-directory"); - di.override(spawnInjectable, () => () => { - return { + + di.override(spawnInjectable, () => () => { + return { stderr: { on: jest.fn(), removeAllListeners: jest.fn() }, stdout: { on: jest.fn(), removeAllListeners: jest.fn() }, on: jest.fn(), @@ -66,6 +86,14 @@ export const getDiForUnitTesting = ( di.override(readFileInjectable, () => () => { throw new Error("Tried to read file from file system without specifying explicit override."); }); + + di.override(loggerInjectable, () => ({ + warn: noop, + debug: noop, + log: noop, + error: (...args: any) => console.error(...args), + info: noop, + })); } return di; diff --git a/src/main/index.ts b/src/main/index.ts index d60d0ea4e6..8ec2e07c27 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -30,11 +30,10 @@ import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; import { startCatalogSyncToRenderer } from "./catalog-pusher"; import { catalogEntityRegistry } from "./catalog"; import { HelmRepoManager } from "./helm/helm-repo-manager"; -import { syncGeneralEntities, syncWeblinks } from "./catalog-sources"; +import { syncWeblinks } from "./catalog-sources"; import configurePackages from "../common/configure-packages"; import { PrometheusProviderRegistry } from "./prometheus"; import * as initializers from "./initializers"; -import { HotbarStore } from "../common/hotbar-store"; import { WeblinkStore } from "../common/weblink-store"; import { SentryInit } from "../common/sentry"; import { ensureDir } from "fs-extra"; @@ -48,7 +47,6 @@ import lensProtocolRouterMainInjectable from "./protocol-handler/lens-protocol-r import extensionDiscoveryInjectable from "../extensions/extension-discovery/extension-discovery.injectable"; import directoryForExesInjectable from "../common/app-paths/directory-for-exes/directory-for-exes.injectable"; import initIpcMainHandlersInjectable from "./initializers/init-ipc-main-handlers/init-ipc-main-handlers.injectable"; -import electronMenuItemsInjectable from "./menu/electron-menu-items.injectable"; import directoryForKubeConfigsInjectable from "../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import kubeconfigSyncManagerInjectable from "./catalog-sources/kubeconfig-sync-manager/kubeconfig-sync-manager.injectable"; import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable"; @@ -57,6 +55,11 @@ import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell import userStoreInjectable from "../common/user-store/user-store.injectable"; import trayMenuItemsInjectable from "./tray/tray-menu-items.injectable"; import { broadcastNativeThemeOnUpdate } from "./native-theme"; +import getAppMenuItemsInjectable from "./menu/get-app-menu-items.injectable"; +import windowManagerInjectable from "./window-manager.injectable"; +import navigateToPreferencesInjectable from "../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable"; +import syncGeneralCatalogEntitiesInjectable from "./catalog-sources/sync-general-catalog-entities.injectable"; +import hotbarStoreInjectable from "../common/hotbar-store.injectable"; const di = getDi(); @@ -66,7 +69,7 @@ app.on("ready", async () => { await di.runSetups(); injectSystemCAs(); - + const onCloseCleanup = disposer(); const onQuitCleanup = disposer(); @@ -184,7 +187,7 @@ app.on("ready", async () => { lensProtocolRouterMain.route(rawUrl); }); - logger.debug("[APP-MAIN] waiting for 'ready' and other messages"); + logger.debug("[APP-MAIN] waiting for 'ready' and other messages"); const directoryForExes = di.inject(directoryForExesInjectable); @@ -204,7 +207,9 @@ app.on("ready", async () => { * store has migrations that will remove items that previous migrations add * if this is not present */ - syncGeneralEntities(); + const syncGeneralCatalogEntities = di.inject(syncGeneralCatalogEntitiesInjectable); + + syncGeneralCatalogEntities(); logger.info("💾 Loading stores"); @@ -218,7 +223,7 @@ app.on("ready", async () => { clusterStore.provideInitialFromMain(); // HotbarStore depends on: ClusterStore - HotbarStore.createInstance(); + di.inject(hotbarStoreInjectable); WeblinkStore.createInstance(); @@ -291,13 +296,15 @@ app.on("ready", async () => { const startHidden = process.argv.includes("--hidden") || (isMac && app.getLoginItemSettings().wasOpenedAsHidden); logger.info("🖥️ Starting WindowManager"); - const windowManager = WindowManager.createInstance(); - const menuItems = di.inject(electronMenuItemsInjectable); + const windowManager = di.inject(windowManagerInjectable); + + const getAppMenuItems = di.inject(getAppMenuItemsInjectable); const trayMenuItems = di.inject(trayMenuItemsInjectable); + const navigateToPreferences = di.inject(navigateToPreferencesInjectable); onQuitCleanup.push( - initMenu(windowManager, menuItems), - initTray(windowManager, trayMenuItems), + initMenu(getAppMenuItems), + initTray(windowManager, trayMenuItems, navigateToPreferences), () => ShellSession.cleanup(), ); diff --git a/src/main/initializers/init-ipc-main-handlers/init-ipc-main-handlers.injectable.ts b/src/main/initializers/init-ipc-main-handlers/init-ipc-main-handlers.injectable.ts index 794cfc522a..d3ef034dd8 100644 --- a/src/main/initializers/init-ipc-main-handlers/init-ipc-main-handlers.injectable.ts +++ b/src/main/initializers/init-ipc-main-handlers/init-ipc-main-handlers.injectable.ts @@ -3,16 +3,15 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import electronMenuItemsInjectable from "../../menu/electron-menu-items.injectable"; -import directoryForLensLocalStorageInjectable - from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable"; +import directoryForLensLocalStorageInjectable from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable"; import { initIpcMainHandlers } from "./init-ipc-main-handlers"; +import getAppMenuItemsInjectable from "../../menu/get-app-menu-items.injectable"; const initIpcMainHandlersInjectable = getInjectable({ id: "init-ipc-main-handlers", instantiate: (di) => initIpcMainHandlers({ - electronMenuItems: di.inject(electronMenuItemsInjectable), + getAppMenuItems: () => di.inject(getAppMenuItemsInjectable)(), directoryForLensLocalStorage: di.inject(directoryForLensLocalStorageInjectable), }), }); diff --git a/src/main/initializers/init-ipc-main-handlers/init-ipc-main-handlers.ts b/src/main/initializers/init-ipc-main-handlers/init-ipc-main-handlers.ts index 0899052f00..4d57e39e3e 100644 --- a/src/main/initializers/init-ipc-main-handlers/init-ipc-main-handlers.ts +++ b/src/main/initializers/init-ipc-main-handlers/init-ipc-main-handlers.ts @@ -14,25 +14,22 @@ import { catalogEntityRegistry } from "../../catalog"; import { pushCatalogToRenderer } from "../../catalog-pusher"; import { ClusterManager } from "../../cluster-manager"; import { ResourceApplier } from "../../resource-applier"; -import { WindowManager } from "../../window-manager"; import path from "path"; import { remove } from "fs-extra"; -import { getAppMenu } from "../../menu/menu"; -import type { MenuRegistration } from "../../menu/menu-registration"; -import type { IComputedValue } from "mobx"; import { onLocationChange, handleWindowAction } from "../../ipc/window"; import { openFilePickingDialogChannel } from "../../../common/ipc/dialog"; import { showOpenDialog } from "../../ipc/dialog"; import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../common/ipc/window"; import { getNativeColorTheme } from "../../native-theme"; import { getNativeThemeChannel } from "../../../common/ipc/native-theme"; +import type { MenuItemsOpts } from "../../menu/get-app-menu-items.injectable"; interface Dependencies { - electronMenuItems: IComputedValue; + getAppMenuItems: () => MenuItemsOpts[]; directoryForLensLocalStorage: string; } -export const initIpcMainHandlers = ({ electronMenuItems, directoryForLensLocalStorage }: Dependencies) => () => { +export const initIpcMainHandlers = ({ getAppMenuItems, directoryForLensLocalStorage }: Dependencies) => () => { ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => { return ClusterStore.getInstance() .getById(clusterId) @@ -151,7 +148,9 @@ export const initIpcMainHandlers = ({ electronMenuItems, directoryForLensLocalSt ipcMainHandle(broadcastMainChannel, (event, channel, ...args) => broadcastMessage(channel, ...args)); ipcMainOn(windowOpenAppMenuAsContextMenuChannel, async (event) => { - const menu = Menu.buildFromTemplate(getAppMenu(WindowManager.getInstance(), electronMenuItems.get())); + const appMenu = getAppMenuItems(); + + const menu = Menu.buildFromTemplate(appMenu); menu.popup({ ...BrowserWindow.fromWebContents(event.sender), diff --git a/src/main/is-auto-update-enabled.injectable.ts b/src/main/is-auto-update-enabled.injectable.ts new file mode 100644 index 0000000000..4a61318e46 --- /dev/null +++ b/src/main/is-auto-update-enabled.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { isAutoUpdateEnabled } from "./app-updater"; + +const isAutoUpdateEnabledInjectable = getInjectable({ + id: "is-auto-update-enabled", + instantiate: () => isAutoUpdateEnabled, + causesSideEffects: true, +}); + +export default isAutoUpdateEnabledInjectable; diff --git a/src/main/menu/get-app-menu-items.injectable.ts b/src/main/menu/get-app-menu-items.injectable.ts new file mode 100644 index 0000000000..e64c12ca37 --- /dev/null +++ b/src/main/menu/get-app-menu-items.injectable.ts @@ -0,0 +1,331 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { checkForUpdates } from "../app-updater"; +import { docsUrl, isMac, productName, supportUrl } from "../../common/vars"; +import { exitApp } from "../exit-app"; +import { broadcastMessage } from "../../common/ipc"; +import { openBrowser } from "../../common/utils"; +import { showAbout } from "./menu"; +import windowManagerInjectable from "../window-manager.injectable"; +import { + BrowserWindow, + MenuItem, + MenuItemConstructorOptions, + webContents, +} from "electron"; +import loggerInjectable from "../../common/logger.injectable"; +import appNameInjectable from "../app-paths/app-name/app-name.injectable"; +import electronMenuItemsInjectable from "./electron-menu-items.injectable"; +import isAutoUpdateEnabledInjectable from "../is-auto-update-enabled.injectable"; +import navigateToPreferencesInjectable from "../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable"; +import navigateToExtensionsInjectable from "../../common/front-end-routing/routes/extensions/navigate-to-extensions.injectable"; +import navigateToCatalogInjectable from "../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; +import navigateToWelcomeInjectable from "../../common/front-end-routing/routes/welcome/navigate-to-welcome.injectable"; +import navigateToAddClusterInjectable from "../../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable"; + +function ignoreIf(check: boolean, menuItems: MenuItemConstructorOptions[]) { + return check ? [] : menuItems; +} + +export interface MenuItemsOpts extends MenuItemConstructorOptions { + submenu?: MenuItemConstructorOptions[]; +} + +const getAppMenuItemsInjectable = getInjectable({ + id: "get-app-menu-items", + + instantiate: (di) => { + const windowManager = di.inject(windowManagerInjectable); + const logger = di.inject(loggerInjectable); + const appName = di.inject(appNameInjectable); + const electronMenuItems = di.inject(electronMenuItemsInjectable); + const isAutoUpdateEnabled = di.inject(isAutoUpdateEnabledInjectable); + + const navigateToPreferences = di.inject(navigateToPreferencesInjectable); + const navigateToExtensions = di.inject(navigateToExtensionsInjectable); + const navigateToCatalog = di.inject(navigateToCatalogInjectable); + const navigateToWelcome = di.inject(navigateToWelcomeInjectable); + const navigateToAddCluster = di.inject(navigateToAddClusterInjectable); + + return (): MenuItemsOpts[] => { + const autoUpdateDisabled = !isAutoUpdateEnabled(); + + logger.info(`[MENU]: autoUpdateDisabled=${autoUpdateDisabled}`); + + const macAppMenu: MenuItemsOpts = { + label: appName, + id: "root", + submenu: [ + { + label: `About ${productName}`, + id: "about", + click(menuItem: MenuItem, browserWindow: BrowserWindow) { + showAbout(browserWindow); + }, + }, + ...ignoreIf(autoUpdateDisabled, [ + { + label: "Check for updates", + click() { + checkForUpdates().then(() => windowManager.ensureMainWindow()); + }, + }, + ]), + { type: "separator" }, + { + label: "Preferences", + accelerator: "CmdOrCtrl+,", + id: "preferences", + click() { + navigateToPreferences(); + }, + }, + { + label: "Extensions", + accelerator: "CmdOrCtrl+Shift+E", + id: "extensions", + click() { + navigateToExtensions(); + }, + }, + { type: "separator" }, + { role: "services" }, + { type: "separator" }, + { role: "hide" }, + { role: "hideOthers" }, + { role: "unhide" }, + { type: "separator" }, + { + label: "Quit", + accelerator: "Cmd+Q", + id: "quit", + click() { + exitApp(); + }, + }, + ], + }; + const fileMenu: MenuItemsOpts = { + label: "File", + id: "file", + submenu: [ + { + label: "Add Cluster", + accelerator: "CmdOrCtrl+Shift+A", + id: "add-cluster", + click() { + navigateToAddCluster(); + }, + }, + ...ignoreIf(isMac, [ + { type: "separator" }, + { + label: "Preferences", + id: "preferences", + accelerator: "Ctrl+,", + click() { + navigateToPreferences(); + }, + }, + { + label: "Extensions", + accelerator: "Ctrl+Shift+E", + click() { + navigateToExtensions(); + }, + }, + ]), + + { type: "separator" }, + + ...(isMac + ? ([ + { + role: "close", + label: "Close Window", + accelerator: "Shift+Cmd+W", + }, + ] as MenuItemConstructorOptions[]) + : []), + + ...ignoreIf(isMac, [ + { + label: "Exit", + accelerator: "Alt+F4", + id: "quit", + click() { + exitApp(); + }, + }, + ]), + ], + }; + const editMenu: MenuItemsOpts = { + label: "Edit", + id: "edit", + submenu: [ + { role: "undo" }, + { role: "redo" }, + { type: "separator" }, + { role: "cut" }, + { role: "copy" }, + { role: "paste" }, + { role: "delete" }, + { type: "separator" }, + { role: "selectAll" }, + ], + }; + const viewMenu: MenuItemsOpts = { + label: "View", + id: "view", + submenu: [ + { + label: "Catalog", + accelerator: "Shift+CmdOrCtrl+C", + id: "catalog", + click() { + navigateToCatalog(); + }, + }, + { + label: "Command Palette...", + accelerator: "Shift+CmdOrCtrl+P", + id: "command-palette", + click(_m, _b, event) { + /** + * Don't broadcast unless it was triggered by menu iteration so that + * there aren't double events in renderer + * + * NOTE: this `?` is required because of a bug in playwright. https://github.com/microsoft/playwright/issues/10554 + */ + if (!event?.triggeredByAccelerator) { + broadcastMessage("command-palette:open"); + } + }, + }, + { type: "separator" }, + { + label: "Back", + accelerator: "CmdOrCtrl+[", + id: "go-back", + click() { + webContents + .getAllWebContents() + .filter((wc) => wc.getType() === "window") + .forEach((wc) => wc.goBack()); + }, + }, + { + label: "Forward", + accelerator: "CmdOrCtrl+]", + id: "go-forward", + click() { + webContents + .getAllWebContents() + .filter((wc) => wc.getType() === "window") + .forEach((wc) => wc.goForward()); + }, + }, + { + label: "Reload", + accelerator: "CmdOrCtrl+R", + id: "reload", + click() { + windowManager.reload(); + }, + }, + { role: "toggleDevTools" }, + { type: "separator" }, + { role: "resetZoom" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { type: "separator" }, + { role: "togglefullscreen" }, + ], + }; + const helpMenu: MenuItemsOpts = { + role: "help", + id: "help", + submenu: [ + { + label: "Welcome", + id: "welcome", + click() { + navigateToWelcome(); + }, + }, + { + label: "Documentation", + id: "documentation", + click: async () => { + openBrowser(docsUrl).catch((error) => { + logger.error("[MENU]: failed to open browser", { error }); + }); + }, + }, + { + label: "Support", + id: "support", + click: async () => { + openBrowser(supportUrl).catch((error) => { + logger.error("[MENU]: failed to open browser", { error }); + }); + }, + }, + ...ignoreIf(isMac, [ + { + label: `About ${productName}`, + id: "about", + click(menuItem: MenuItem, browserWindow: BrowserWindow) { + showAbout(browserWindow); + }, + }, + ...ignoreIf(autoUpdateDisabled, [ + { + label: "Check for updates", + click() { + checkForUpdates().then(() => + windowManager.ensureMainWindow(), + ); + }, + }, + ]), + ]), + ], + }; + // Prepare menu items order + const appMenu = new Map([ + ["mac", macAppMenu], + ["file", fileMenu], + ["edit", editMenu], + ["view", viewMenu], + ["help", helpMenu], + ]); + + // Modify menu from extensions-api + for (const menuItem of electronMenuItems.get()) { + if (!appMenu.has(menuItem.parentId)) { + logger.error( + `[MENU]: cannot register menu item for parentId=${menuItem.parentId}, parent item doesn't exist`, + { menuItem }, + ); + + continue; + } + + appMenu.get(menuItem.parentId).submenu.push(menuItem); + } + + if (!isMac) { + appMenu.delete("mac"); + } + + return [...appMenu.values()]; + }; + }, +}); + +export default getAppMenuItemsInjectable; diff --git a/src/main/menu/menu.ts b/src/main/menu/menu.ts index 18309cb71b..540e19d1d9 100644 --- a/src/main/menu/menu.ts +++ b/src/main/menu/menu.ts @@ -2,30 +2,18 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { app, BrowserWindow, dialog, Menu, MenuItem, MenuItemConstructorOptions, webContents } from "electron"; -import { autorun, IComputedValue } from "mobx"; -import type { WindowManager } from "../window-manager"; -import { appName, isMac, isWindows, docsUrl, supportUrl, productName } from "../../common/vars"; -import logger from "../logger"; -import { exitApp } from "../exit-app"; -import { broadcastMessage } from "../../common/ipc"; -import { openBrowser } from "../../common/utils"; +import { app, BrowserWindow, dialog, Menu } from "electron"; +import { autorun } from "mobx"; +import { appName, isWindows, productName } from "../../common/vars"; import packageJson from "../../../package.json"; -import { preferencesURL, extensionsURL, addClusterURL, catalogURL, welcomeURL } from "../../common/routes"; -import { checkForUpdates, isAutoUpdateEnabled } from "../app-updater"; -import type { MenuRegistration } from "./menu-registration"; +import type { MenuItemsOpts } from "./get-app-menu-items.injectable"; export type MenuTopId = "mac" | "file" | "edit" | "view" | "help"; -interface MenuItemsOpts extends MenuItemConstructorOptions { - submenu?: MenuItemConstructorOptions[]; -} - export function initMenu( - windowManager: WindowManager, - electronMenuItems: IComputedValue, + getAppMenuItems: () => MenuItemsOpts[], ) { - return autorun(() => buildMenu(windowManager, electronMenuItems.get()), { + return autorun(() => buildMenu(getAppMenuItems), { delay: 100, }); } @@ -48,288 +36,12 @@ export function showAbout(browserWindow: BrowserWindow) { }); } -export function getAppMenu( - windowManager: WindowManager, - electronMenuItems: MenuRegistration[], -) { - function ignoreIf(check: boolean, menuItems: MenuItemConstructorOptions[]) { - return check ? [] : menuItems; - } - - async function navigate(url: string) { - logger.info(`[MENU]: navigating to ${url}`); - await windowManager.navigate(url); - } - - const autoUpdateDisabled = !isAutoUpdateEnabled(); - - logger.info(`[MENU]: autoUpdateDisabled=${autoUpdateDisabled}`); - - const macAppMenu: MenuItemsOpts = { - label: app.getName(), - id: "root", - submenu: [ - { - label: `About ${productName}`, - id: "about", - click(menuItem: MenuItem, browserWindow: BrowserWindow) { - showAbout(browserWindow); - }, - }, - ...ignoreIf(autoUpdateDisabled, [{ - label: "Check for updates", - click() { - checkForUpdates() - .then(() => windowManager.ensureMainWindow()); - }, - }]), - { type: "separator" }, - { - label: "Preferences", - accelerator: "CmdOrCtrl+,", - id: "preferences", - click() { - navigate(preferencesURL()); - }, - }, - { - label: "Extensions", - accelerator: "CmdOrCtrl+Shift+E", - id: "extensions", - click() { - navigate(extensionsURL()); - }, - }, - { type: "separator" }, - { role: "services" }, - { type: "separator" }, - { role: "hide" }, - { role: "hideOthers" }, - { role: "unhide" }, - { type: "separator" }, - { - label: "Quit", - accelerator: "Cmd+Q", - id: "quit", - click() { - exitApp(); - }, - }, - ], - }; - const fileMenu: MenuItemsOpts = { - label: "File", - id: "file", - submenu: [ - { - label: "Add Cluster", - accelerator: "CmdOrCtrl+Shift+A", - id: "add-cluster", - click() { - navigate(addClusterURL()); - }, - }, - ...ignoreIf(isMac, [ - { type: "separator" }, - { - label: "Preferences", - id: "preferences", - accelerator: "Ctrl+,", - click() { - navigate(preferencesURL()); - }, - }, - { - label: "Extensions", - accelerator: "Ctrl+Shift+E", - click() { - navigate(extensionsURL()); - }, - }, - ]), - - { type: "separator" }, - - ...(isMac ? [ - { - role: "close", - label: "Close Window", - accelerator: "Shift+Cmd+W", - }, - ] as MenuItemConstructorOptions[] : []), - - ...ignoreIf(isMac, [ - { - label: "Exit", - accelerator: "Alt+F4", - id: "quit", - click() { - exitApp(); - }, - }, - ]), - ], - }; - const editMenu: MenuItemsOpts = { - label: "Edit", - id: "edit", - submenu: [ - { role: "undo" }, - { role: "redo" }, - { type: "separator" }, - { role: "cut" }, - { role: "copy" }, - { role: "paste" }, - { role: "delete" }, - { type: "separator" }, - { role: "selectAll" }, - ], - }; - const viewMenu: MenuItemsOpts = { - label: "View", - id: "view", - submenu: [ - { - label: "Catalog", - accelerator: "Shift+CmdOrCtrl+C", - id: "catalog", - click() { - navigate(catalogURL()); - }, - }, - { - label: "Command Palette...", - accelerator: "Shift+CmdOrCtrl+P", - id: "command-palette", - click(_m, _b, event) { - /** - * Don't broadcast unless it was triggered by menu iteration so that - * there aren't double events in renderer - * - * NOTE: this `?` is required because of a bug in playwright. https://github.com/microsoft/playwright/issues/10554 - */ - if (!event?.triggeredByAccelerator) { - broadcastMessage("command-palette:open"); - } - }, - }, - { type: "separator" }, - { - label: "Back", - accelerator: "CmdOrCtrl+[", - id: "go-back", - click() { - webContents.getAllWebContents().filter(wc => wc.getType() === "window").forEach(wc => wc.goBack()); - }, - }, - { - label: "Forward", - accelerator: "CmdOrCtrl+]", - id: "go-forward", - click() { - webContents.getAllWebContents().filter(wc => wc.getType() === "window").forEach(wc => wc.goForward()); - }, - }, - { - label: "Reload", - accelerator: "CmdOrCtrl+R", - id: "reload", - click() { - windowManager.reload(); - }, - }, - { role: "toggleDevTools" }, - { type: "separator" }, - { role: "resetZoom" }, - { role: "zoomIn" }, - { role: "zoomOut" }, - { type: "separator" }, - { role: "togglefullscreen" }, - ], - }; - const helpMenu: MenuItemsOpts = { - role: "help", - id: "help", - submenu: [ - { - label: "Welcome", - id: "welcome", - click() { - navigate(welcomeURL()); - }, - }, - { - label: "Documentation", - id: "documentation", - click: async () => { - openBrowser(docsUrl).catch(error => { - logger.error("[MENU]: failed to open browser", { error }); - }); - }, - }, - { - label: "Support", - id: "support", - click: async () => { - openBrowser(supportUrl).catch(error => { - logger.error("[MENU]: failed to open browser", { error }); - }); - }, - }, - ...ignoreIf(isMac, [ - { - label: `About ${productName}`, - id: "about", - click(menuItem: MenuItem, browserWindow: BrowserWindow) { - showAbout(browserWindow); - }, - }, - ...ignoreIf(autoUpdateDisabled, [{ - label: "Check for updates", - click() { - checkForUpdates() - .then(() => windowManager.ensureMainWindow()); - }, - }]), - ]), - ], - }; - // Prepare menu items order - const appMenu = new Map([ - ["mac", macAppMenu], - ["file", fileMenu], - ["edit", editMenu], - ["view", viewMenu], - ["help", helpMenu], - ]); - - // Modify menu from extensions-api - for (const menuItem of electronMenuItems) { - if (!appMenu.has(menuItem.parentId)) { - logger.error( - `[MENU]: cannot register menu item for parentId=${menuItem.parentId}, parent item doesn't exist`, - { menuItem }, - ); - - continue; - } - - appMenu.get(menuItem.parentId).submenu.push(menuItem); - } - - if (!isMac) { - appMenu.delete("mac"); - } - - return [...appMenu.values()]; - -} - export function buildMenu( - windowManager: WindowManager, - electronMenuItems: MenuRegistration[], + getAppMenuItems: () => MenuItemsOpts[], ) { + const menuItems = getAppMenuItems(); + Menu.setApplicationMenu( - Menu.buildFromTemplate(getAppMenu(windowManager, electronMenuItems)), + Menu.buildFromTemplate(menuItems), ); } diff --git a/src/main/navigate-to-route/navigate-to-route.injectable.ts b/src/main/navigate-to-route/navigate-to-route.injectable.ts new file mode 100644 index 0000000000..12bf077ced --- /dev/null +++ b/src/main/navigate-to-route/navigate-to-route.injectable.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { navigateToUrlInjectionToken } from "../../common/front-end-routing/navigate-to-url-injection-token"; +import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token"; +import { buildURL } from "../../common/utils/buildUrl"; + +const navigateToRouteInjectable = getInjectable({ + id: "navigate-to-route", + + instantiate: (di) => { + const navigateToUrl = di.inject(navigateToUrlInjectionToken); + + return (route, options) => { + const url = buildURL(route.path, { + // TODO: enhance typing + params: options?.parameters as any, + query: options?.query, + fragment: options?.fragment, + }); + + navigateToUrl(url, options); + }; + }, + + injectionToken: navigateToRouteInjectionToken, +}); + +export default navigateToRouteInjectable; diff --git a/src/main/navigate-to-url/navigate-to-url.injectable.ts b/src/main/navigate-to-url/navigate-to-url.injectable.ts new file mode 100644 index 0000000000..5a3cd2bf49 --- /dev/null +++ b/src/main/navigate-to-url/navigate-to-url.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import windowManagerInjectable from "../window-manager.injectable"; +import { navigateToUrlInjectionToken } from "../../common/front-end-routing/navigate-to-url-injection-token"; + +const navigateToUrlInjectable = getInjectable({ + id: "navigate-to-url", + + instantiate: (di) => { + const windowManager = di.inject(windowManagerInjectable); + + return (url) => { + windowManager.navigateSync(url); + }; + }, + + injectionToken: navigateToUrlInjectionToken, +}); + +export default navigateToUrlInjectable; diff --git a/src/main/protocol-handler/__test__/router.test.ts b/src/main/protocol-handler/__test__/router.test.ts index d96cd342dc..20be8ae314 100644 --- a/src/main/protocol-handler/__test__/router.test.ts +++ b/src/main/protocol-handler/__test__/router.test.ts @@ -42,11 +42,13 @@ describe("protocol router tests", () => { "tmp": {}, }); + di.override(extensionsStoreInjectable, () => ExtensionsStore.createInstance()); + await di.runSetups(); extensionLoader = di.inject(extensionLoaderInjectable); - extensionsStore = di.inject(extensionsStoreInjectable); + extensionsStore = di.inject(extensionsStoreInjectable); lpr = di.inject(lensProtocolRouterMainInjectable); @@ -56,9 +58,6 @@ describe("protocol router tests", () => { afterEach(() => { jest.clearAllMocks(); - // TODO: Remove Singleton from BaseStore to achieve independent unit testing - ExtensionsStore.resetInstance(); - mockFs.restore(); }); diff --git a/src/main/routes/static-file-route.injectable.ts b/src/main/routes/static-file-route.injectable.ts index f35976f320..616cccb001 100644 --- a/src/main/routes/static-file-route.injectable.ts +++ b/src/main/routes/static-file-route.injectable.ts @@ -9,16 +9,16 @@ import logger from "../logger"; import { routeInjectionToken } from "../router/router.injectable"; import { appName, publicPath } from "../../common/vars"; import path from "path"; -import readFileInjectable from "../../common/fs/read-file.injectable"; import isDevelopmentInjectable from "../../common/vars/is-development.injectable"; import httpProxy from "http-proxy"; +import readFileBufferInjectable from "../../common/fs/read-file-buffer.injectable"; interface ProductionDependencies { - readFile: (path: string) => Promise; + readFileBuffer: (path: string) => Promise; } const handleStaticFileInProduction = - ({ readFile }: ProductionDependencies) => + ({ readFileBuffer }: ProductionDependencies) => async ({ params }: LensApiRequest) => { const staticPath = path.resolve(__static); let filePath = params.path; @@ -38,7 +38,7 @@ const handleStaticFileInProduction = const contentType = contentTypes[fileExtension] || contentTypes.txt; - return { response: await readFile(asset), contentType }; + return { response: await readFileBuffer(asset), contentType }; } catch (err) { if (retryCount > 5) { logger.error("handleStaticFile:", err.toString()); @@ -78,13 +78,14 @@ const staticFileRouteInjectable = getInjectable({ instantiate: (di): Route => { const isDevelopment = di.inject(isDevelopmentInjectable); + const readFileBuffer = di.inject(readFileBufferInjectable); return { method: "get", path: `/{path*}`, handler: isDevelopment ? handleStaticFileInDevelopment({ proxy: httpProxy.createProxy() }) - : handleStaticFileInProduction({ readFile: di.inject(readFileInjectable) }), + : handleStaticFileInProduction({ readFileBuffer }), }; }, diff --git a/src/main/tray/tray.ts b/src/main/tray/tray.ts index b4e8a24a8c..9bdfa5c082 100644 --- a/src/main/tray/tray.ts +++ b/src/main/tray/tray.ts @@ -13,10 +13,10 @@ import type { WindowManager } from "../window-manager"; import logger from "../logger"; import { isDevelopment, isWindows, productName } from "../../common/vars"; import { exitApp } from "../exit-app"; -import { preferencesURL } from "../../common/routes"; import { toJS } from "../../common/utils"; import type { TrayMenuRegistration } from "./tray-menu-registration"; + const TRAY_LOG_PREFIX = "[TRAY]"; // note: instance of Tray should be saved somewhere, otherwise it disappears @@ -33,6 +33,7 @@ export function getTrayIcon(): string { export function initTray( windowManager: WindowManager, trayMenuItems: IComputedValue, + navigateToPreferences: () => void, ) { const icon = getTrayIcon(); @@ -51,7 +52,7 @@ export function initTray( const disposers = [ autorun(() => { try { - const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get())); + const menu = createTrayMenu(windowManager, toJS(trayMenuItems.get()), navigateToPreferences); tray.setContextMenu(menu); } catch (error) { @@ -80,6 +81,7 @@ function getMenuItemConstructorOptions(trayItem: TrayMenuRegistration): Electron function createTrayMenu( windowManager: WindowManager, extensionTrayItems: TrayMenuRegistration[], + navigateToPreferences: () => void, ): Menu { let template: Electron.MenuItemConstructorOptions[] = [ { @@ -93,9 +95,7 @@ function createTrayMenu( { label: "Preferences", click() { - windowManager - .navigate(preferencesURL()) - .catch(error => logger.error(`${TRAY_LOG_PREFIX}: Failed to navigate to Preferences`, { error })); + navigateToPreferences(); }, }, ]; diff --git a/src/main/window-manager.injectable.ts b/src/main/window-manager.injectable.ts new file mode 100644 index 0000000000..ce4f0a6f77 --- /dev/null +++ b/src/main/window-manager.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { WindowManager } from "./window-manager"; + +const windowManagerInjectable = getInjectable({ + id: "window-manager", + instantiate: () => WindowManager.createInstance(), + causesSideEffects: true, +}); + +export default windowManagerInjectable; diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index d95e1b69ce..74b80ca563 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -187,7 +187,7 @@ export class WindowManager extends Singleton { return this.mainWindow; } - private sendToView({ channel, frameInfo, data = [] }: SendToViewArgs) { + sendToView({ channel, frameInfo, data = [] }: SendToViewArgs) { if (frameInfo) { this.mainWindow.webContents.sendToFrame([frameInfo.processId, frameInfo.frameId], channel, ...data); } else { @@ -210,6 +210,10 @@ export class WindowManager extends Singleton { async navigate(url: string, frameId?: number) { await this.ensureMainWindow(); + this.navigateSync(url, frameId); + } + + navigateSync(url: string, frameId?: number) { const frameInfo = iter.find(clusterFrameMap.values(), frameInfo => frameInfo.frameId === frameId); const channel = frameInfo ? IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER diff --git a/src/migrations/hotbar-store/5.0.0-alpha.0.ts b/src/migrations/hotbar-store/5.0.0-alpha.0.ts index eb6c479f8e..3515a927bd 100644 --- a/src/migrations/hotbar-store/5.0.0-alpha.0.ts +++ b/src/migrations/hotbar-store/5.0.0-alpha.0.ts @@ -5,14 +5,18 @@ // Cleans up a store that had the state related data stored import type { MigrationDeclaration } from "../helpers"; -import { catalogEntity } from "../../main/catalog-sources/general"; import { getEmptyHotbar } from "../../common/hotbar-types"; +import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import catalogCatalogEntityInjectable from "../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; export default { version: "5.0.0-alpha.0", run(store) { const hotbar = getEmptyHotbar("default"); - const { metadata: { uid, name, source }} = catalogEntity; + const di = getLegacyGlobalDiForExtensionApi(); + const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); + + const { metadata: { uid, name, source }} = catalogCatalogEntity; hotbar.items[0] = { entity: { uid, name, source }}; diff --git a/src/migrations/hotbar-store/5.0.0-beta.10.ts b/src/migrations/hotbar-store/5.0.0-beta.10.ts index 5008232e5d..e880310adf 100644 --- a/src/migrations/hotbar-store/5.0.0-beta.10.ts +++ b/src/migrations/hotbar-store/5.0.0-beta.10.ts @@ -9,12 +9,13 @@ import path from "path"; import * as uuid from "uuid"; import type { ClusterStoreModel } from "../../common/cluster-store/cluster-store"; import { defaultHotbarCells, getEmptyHotbar, Hotbar, HotbarItem } from "../../common/hotbar-types"; -import { catalogEntity } from "../../main/catalog-sources/general"; import { MigrationDeclaration, migrationLog } from "../helpers"; import { generateNewIdFor } from "../utils"; import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import catalogCatalogEntityInjectable + from "../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; interface Pre500WorkspaceStoreModel { workspaces: { @@ -42,7 +43,11 @@ export default { // Hotbars might be empty, if some of the previous migrations weren't run if (hotbars.length === 0) { const hotbar = getEmptyHotbar("default"); - const { metadata: { uid, name, source }} = catalogEntity; + + const di = getLegacyGlobalDiForExtensionApi(); + const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); + + const { metadata: { uid, name, source }} = catalogCatalogEntity; hotbar.items[0] = { entity: { uid, name, source }}; @@ -120,8 +125,11 @@ export default { */ if (hotbars.every(hotbar => hotbar.items.every(item => item?.entity?.uid !== "catalog-entity"))) { // note, we will add a new whole hotbar here called "default" if that was previously removed + const di = getLegacyGlobalDiForExtensionApi(); + const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); + const defaultHotbar = hotbars.find(hotbar => hotbar.name === "default"); - const { metadata: { uid, name, source }} = catalogEntity; + const { metadata: { uid, name, source }} = catalogCatalogEntity; if (defaultHotbar) { const freeIndex = defaultHotbar.items.findIndex(isNull); diff --git a/src/renderer/app-paths/get-value-from-registered-channel/register-ipc-channel-listener.injectable.ts b/src/renderer/app-paths/get-value-from-registered-channel/register-ipc-channel-listener.injectable.ts new file mode 100644 index 0000000000..151d77f097 --- /dev/null +++ b/src/renderer/app-paths/get-value-from-registered-channel/register-ipc-channel-listener.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import ipcRendererInjectable from "./ipc-renderer/ipc-renderer.injectable"; +import type { + IpcChannelListener, +} from "../../ipc-channel-listeners/ipc-channel-listener-injection-token"; + +const registerIpcChannelListenerInjectable = getInjectable({ + id: "register-ipc-channel-listener", + + instantiate: (di) => { + const ipc = di.inject(ipcRendererInjectable); + + return ({ channel, handle }: IpcChannelListener) => { + ipc.on(channel.name, (_, data) => { + handle(data); + }); + }; + }, +}); + +export default registerIpcChannelListenerInjectable; diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 91e6892e33..5ea4726e98 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -20,9 +20,7 @@ import { DefaultProps } from "./mui-base-theme"; import configurePackages from "../common/configure-packages"; import * as initializers from "./initializers"; import logger from "../common/logger"; -import { HotbarStore } from "../common/hotbar-store"; import { WeblinkStore } from "../common/weblink-store"; -import { ThemeStore } from "./theme.store"; import { SentryInit } from "../common/sentry"; import { registerCustomThemes } from "./components/monaco-editor"; import { getDi } from "./getDi"; @@ -36,6 +34,13 @@ import userStoreInjectable from "../common/user-store/user-store.injectable"; import initRootFrameInjectable from "./frames/root-frame/init-root-frame/init-root-frame.injectable"; import initClusterFrameInjectable from "./frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable"; import commandOverlayInjectable from "./components/command-palette/command-overlay.injectable"; +import { Router } from "react-router"; +import historyInjectable from "./navigation/history.injectable"; +import themeStoreInjectable from "./theme-store.injectable"; +import navigateToAddClusterInjectable from "../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable"; +import addSyncEntriesInjectable from "./initializers/add-sync-entries.injectable"; +import hotbarStoreInjectable from "../common/hotbar-store.injectable"; +import { bindEvents } from "./navigation/events"; if (process.isMainFrame) { SentryInit(); @@ -58,6 +63,9 @@ async function attachChromeDebugger() { export async function bootstrap(di: DiContainer) { await di.runSetups(); + // TODO: Consolidate import time side-effect to setup time + bindEvents(); + const rootElem = document.getElementById("app"); const logPrefix = `[BOOTSTRAP-${process.isMainFrame ? "ROOT" : "CLUSTER"}-FRAME]:`; @@ -79,8 +87,14 @@ export async function bootstrap(di: DiContainer) { logger.info(`${logPrefix} initializing CatalogEntityDetailRegistry`); initializers.initCatalogEntityDetailRegistry(); + const navigateToAddCluster = di.inject(navigateToAddClusterInjectable); + const addSyncEntries = di.inject(addSyncEntriesInjectable); + logger.info(`${logPrefix} initializing CatalogCategoryRegistryEntries`); - initializers.initCatalogCategoryRegistryEntries(); + initializers.initCatalogCategoryRegistryEntries({ + navigateToAddCluster, + addSyncEntries, + }); logger.info(`${logPrefix} initializing Catalog`); initializers.initCatalog({ @@ -104,10 +118,11 @@ export async function bootstrap(di: DiContainer) { await clusterStore.loadInitialOnRenderer(); // HotbarStore depends on: ClusterStore - HotbarStore.createInstance(); + di.inject(hotbarStoreInjectable); // ThemeStore depends on: UserStore - ThemeStore.createInstance(); + // TODO: Remove temporal dependencies + di.inject(themeStoreInjectable); WeblinkStore.createInstance(); @@ -142,9 +157,13 @@ export async function bootstrap(di: DiContainer) { }); } + const history = di.inject(historyInjectable); + render( - {DefaultProps(App)} + + {DefaultProps(App)} + , rootElem, diff --git a/src/renderer/components/+add-cluster/add-cluster-route-component.injectable.ts b/src/renderer/components/+add-cluster/add-cluster-route-component.injectable.ts new file mode 100644 index 0000000000..5d41fd7b5b --- /dev/null +++ b/src/renderer/components/+add-cluster/add-cluster-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { AddCluster } from "./add-cluster"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import addClusterRouteInjectable from "../../../common/front-end-routing/routes/add-cluster/add-cluster-route.injectable"; + +const addClusterRouteComponentInjectable = getInjectable({ + id: "add-cluster-route-component", + + instantiate: (di) => ({ + route: di.inject(addClusterRouteInjectable), + Component: AddCluster, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default addClusterRouteComponentInjectable; diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index 76fa6436f3..2381c781eb 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -14,19 +14,19 @@ import path from "path"; import React from "react"; import * as uuid from "uuid"; -import { catalogURL } from "../../../common/routes"; import { appEventBus } from "../../../common/app-event-bus/event-bus"; import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers"; import { docsUrl } from "../../../common/vars"; -import { navigate } from "../../navigation"; import { iter } from "../../utils"; import { Button } from "../button"; import { Notifications } from "../notifications"; import { SettingLayout } from "../layout/setting-layout"; import { MonacoEditor } from "../monaco-editor"; import { withInjectables } from "@ogre-tools/injectable-react"; -import getCustomKubeConfigDirectoryInjectable - from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable"; +import getCustomKubeConfigDirectoryInjectable from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable"; +import navigateToCatalogInjectable, { + NavigateToCatalog, +} from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; interface Option { config: KubeConfig; @@ -35,6 +35,7 @@ interface Option { interface Dependencies { getCustomKubeConfigDirectory: (directoryName: string) => string; + navigateToCatalog: NavigateToCatalog; } function getContexts(config: KubeConfig): Map { @@ -96,7 +97,7 @@ class NonInjectedAddCluster extends React.Component { Notifications.ok(`Successfully added ${this.kubeContexts.size} new cluster(s)`); - return navigate(catalogURL()); + return this.props.navigateToCatalog(); } catch (error) { Notifications.error(`Failed to add clusters: ${error}`); } @@ -104,7 +105,7 @@ class NonInjectedAddCluster extends React.Component { render() { return ( - +

Add Clusters from Kubeconfig

Clusters added here are not merged into the ~/.kube/config file.{" "} @@ -149,5 +150,7 @@ export const AddCluster = withInjectables(NonInjectedAddCluster, { getCustomKubeConfigDirectory: di.inject( getCustomKubeConfigDirectoryInjectable, ), + + navigateToCatalog: di.inject(navigateToCatalogInjectable), }), }); diff --git a/src/renderer/components/+workloads/workloads.scss b/src/renderer/components/+catalog/catalog-browse-tab.ts similarity index 77% rename from src/renderer/components/+workloads/workloads.scss rename to src/renderer/components/+catalog/catalog-browse-tab.ts index d05328095c..107e12894e 100644 --- a/src/renderer/components/+workloads/workloads.scss +++ b/src/renderer/components/+catalog/catalog-browse-tab.ts @@ -3,5 +3,4 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -.Workloads { -} +export const browseCatalogTab = "browse"; diff --git a/src/renderer/components/+catalog/catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable.ts b/src/renderer/components/+catalog/catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable.ts index c978f8667e..e0ab887393 100644 --- a/src/renderer/components/+catalog/catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable.ts +++ b/src/renderer/components/+catalog/catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { browseCatalogTab } from "../../../../common/routes"; import createStorageInjectable from "../../../utils/create-storage/create-storage.injectable"; +import { browseCatalogTab } from "../catalog-browse-tab"; const catalogPreviousActiveTabStorageInjectable = getInjectable({ id: "catalog-previous-active-tab-storage", diff --git a/src/renderer/components/+catalog/catalog-route-component.injectable.ts b/src/renderer/components/+catalog/catalog-route-component.injectable.ts new file mode 100644 index 0000000000..feb450328f --- /dev/null +++ b/src/renderer/components/+catalog/catalog-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Catalog } from "./catalog"; +import catalogRouteInjectable from "../../../common/front-end-routing/routes/catalog/catalog-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const catalogRouteComponentInjectable = getInjectable({ + id: "catalog-route-component", + + instantiate: (di) => ({ + route: di.inject(catalogRouteInjectable), + Component: Catalog, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default catalogRouteComponentInjectable; diff --git a/src/renderer/components/+catalog/catalog-route-parameters.injectable.ts b/src/renderer/components/+catalog/catalog-route-parameters.injectable.ts new file mode 100644 index 0000000000..0d9bd1fc4e --- /dev/null +++ b/src/renderer/components/+catalog/catalog-route-parameters.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import routePathParametersInjectable from "../../routes/route-path-parameters.injectable"; +import catalogRouteInjectable from "../../../common/front-end-routing/routes/catalog/catalog-route.injectable"; + +const catalogRouteParametersInjectable = getInjectable({ + id: "catalog-route-parameters", + + instantiate: (di) => { + const route = di.inject(catalogRouteInjectable); + const pathParameters = di.inject(routePathParametersInjectable, route); + + return { + group: computed(() => pathParameters.get().group), + kind: computed(() => pathParameters.get().kind), + }; + }, +}); + +export default catalogRouteParametersInjectable; diff --git a/src/renderer/components/+catalog/catalog.test.tsx b/src/renderer/components/+catalog/catalog.test.tsx index 5c221bb457..19a74cf0d7 100644 --- a/src/renderer/components/+catalog/catalog.test.tsx +++ b/src/renderer/components/+catalog/catalog.test.tsx @@ -7,7 +7,6 @@ import React from "react"; import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { Catalog } from "./catalog"; -import { createMemoryHistory } from "history"; import { mockWindow } from "../../../../__mocks__/windowMock"; import { CatalogCategoryRegistry, CatalogEntity, CatalogEntityActionContext, CatalogEntityData } from "../../../common/catalog"; import { CatalogEntityRegistry } from "../../api/catalog-entity-registry"; @@ -66,24 +65,8 @@ class MockCatalogEntity extends CatalogEntity { } describe("", () => { - const history = createMemoryHistory(); - const mockLocation = { - pathname: "", - search: "", - state: "", - hash: "", - }; - const mockMatch = { - params: { - // will be used to match activeCategory - // need to be the same as property values in kubernetesClusterCategory - group: "entity.k8slens.dev", - kind: "KubernetesCluster", - }, - isExact: true, - path: "", - url: "", - }; + // group: "entity.k8slens.dev", + // kind: "KubernetesCluster", function createMockCatalogEntity(onRun: (context: CatalogEntityActionContext) => void | Promise) { return new MockCatalogEntity({ @@ -169,11 +152,7 @@ describe("", () => { ); render( - , + , ); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); @@ -198,13 +177,7 @@ describe("", () => { }, ); - render( - , - ); + render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); }); @@ -229,13 +202,7 @@ describe("", () => { }, ); - render( - , - ); + render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); }); @@ -255,13 +222,7 @@ describe("", () => { }, ); - render( - , - ); + render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); }); @@ -288,13 +249,7 @@ describe("", () => { }, ); - render( - , - ); + render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); }); @@ -319,13 +274,7 @@ describe("", () => { }, ); - render( - , - ); + render(); userEvent.click(screen.getByTestId("detail-panel-hot-bar-icon")); }); diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index a3af640fc5..46379e2f14 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -8,7 +8,16 @@ import styles from "./catalog.module.scss"; import React from "react"; import { disposeOnUnmount, observer } from "mobx-react"; import { ItemListLayout } from "../item-object-list"; -import { action, IComputedValue, makeObservable, observable, reaction, runInAction, when } from "mobx"; +import { + action, + computed, + IComputedValue, + makeObservable, + observable, + reaction, + runInAction, + when, +} from "mobx"; import type { CatalogEntityStore } from "./catalog-entity-store/catalog-entity.store"; import { navigate } from "../../navigation"; import { MenuItem, MenuActions } from "../menu"; @@ -17,12 +26,10 @@ import { HotbarStore } from "../../../common/hotbar-store"; import { ConfirmDialog } from "../confirm-dialog"; import { catalogCategoryRegistry, CatalogEntity } from "../../../common/catalog"; import { CatalogAddButton } from "./catalog-add-button"; -import type { RouteComponentProps } from "react-router"; import { Notifications } from "../notifications"; import { MainLayout } from "../layout/main-layout"; import { prevDefault } from "../../utils"; import { CatalogEntityDetails } from "./catalog-entity-details"; -import { browseCatalogTab, catalogURL, CatalogViewRouteParam } from "../../../common/routes"; import { CatalogMenu } from "./catalog-menu"; import { RenderDelay } from "../render-delay/render-delay"; import { Icon } from "../icon"; @@ -36,34 +43,48 @@ import getCategoryColumnsInjectable from "./get-category-columns.injectable"; import type { RegisteredCustomCategoryViewDecl } from "./custom-views.injectable"; import customCategoryViewsInjectable from "./custom-views.injectable"; import type { CustomCategoryViewComponents } from "./custom-views"; +import navigateToCatalogInjectable, { + NavigateToCatalog, +} from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; +import catalogRouteParametersInjectable from "./catalog-route-parameters.injectable"; +import { browseCatalogTab } from "./catalog-browse-tab"; import type { AppEvent } from "../../../common/app-event-bus/event-bus"; import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable"; -export interface CatalogProps extends RouteComponentProps {} - interface Dependencies { catalogPreviousActiveTabStorage: { set: (value: string ) => void }; catalogEntityStore: CatalogEntityStore; getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns; customCategoryViews: IComputedValue>>; emitEvent: (event: AppEvent) => void; + + routeParameters: { + group: IComputedValue; + kind: IComputedValue; + }; + + navigateToCatalog: NavigateToCatalog; } @observer -class NonInjectedCatalog extends React.Component { +class NonInjectedCatalog extends React.Component { @observable private contextMenu: CatalogEntityContextMenuContext; @observable activeTab?: string; - constructor(props: CatalogProps & Dependencies) { + constructor(props: Dependencies) { super(props); makeObservable(this); } + @computed get routeActiveTab(): string { - const { group, kind } = this.props.match.params ?? {}; + const { group, kind } = this.props.routeParameters; - if (group && kind) { - return `${group}/${kind}`; + const dereferencedGroup = group.get(); + const dereferencedKind = kind.get(); + + if (dereferencedGroup && dereferencedKind) { + return `${dereferencedGroup}/${dereferencedKind}`; } return browseCatalogTab; @@ -163,9 +184,10 @@ class NonInjectedCatalog extends React.Component { }); if (activeCategory) { - navigate(catalogURL({ params: { group: activeCategory.spec.group, kind: activeCategory.spec.names.kind }})); + + this.props.navigateToCatalog({ group: activeCategory.spec.group, kind: activeCategory.spec.names.kind }); } else { - navigate(catalogURL({ params: { group: browseCatalogTab }})); + this.props.navigateToCatalog({ group: browseCatalogTab }); } }); @@ -321,13 +343,14 @@ class NonInjectedCatalog extends React.Component { } } -export const Catalog = withInjectables( NonInjectedCatalog, { - getProps: (di, props) => ({ +export const Catalog = withInjectables( NonInjectedCatalog, { + getProps: (di) => ({ catalogEntityStore: di.inject(catalogEntityStoreInjectable), catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable), getCategoryColumns: di.inject(getCategoryColumnsInjectable), customCategoryViews: di.inject(customCategoryViewsInjectable), + routeParameters: di.inject(catalogRouteParametersInjectable), + navigateToCatalog: di.inject(navigateToCatalogInjectable), emitEvent: di.inject(appEventBusInjectable).emit, - ...props, }), }); diff --git a/src/renderer/components/+cluster/cluster-overview-route-component.injectable.ts b/src/renderer/components/+cluster/cluster-overview-route-component.injectable.ts new file mode 100644 index 0000000000..5f4e5516cd --- /dev/null +++ b/src/renderer/components/+cluster/cluster-overview-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import { ClusterOverview } from "./cluster-overview"; +import clusterOverviewRouteInjectable from "../../../common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable"; + +const clusterOverviewRouteComponentInjectable = getInjectable({ + id: "cluster-overview-route-component", + + instantiate: (di) => ({ + route: di.inject(clusterOverviewRouteInjectable), + Component: ClusterOverview, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default clusterOverviewRouteComponentInjectable; diff --git a/src/renderer/components/+cluster/cluster-overview-sidebar-items.injectable.tsx b/src/renderer/components/+cluster/cluster-overview-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..88f5900fed --- /dev/null +++ b/src/renderer/components/+cluster/cluster-overview-sidebar-items.injectable.tsx @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Icon } from "../icon"; +import React from "react"; +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { computed } from "mobx"; +import clusterOverviewRouteInjectable from "../../../common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToClusterOverviewInjectable from "../../../common/front-end-routing/routes/cluster/overview/navigate-to-cluster-overview.injectable"; + +const clusterOverviewSidebarItemsInjectable = getInjectable({ + id: "cluster-overview-sidebar-items", + + instantiate: (di) => { + const route = di.inject(clusterOverviewRouteInjectable); + const navigateToClusterOverview = di.inject(navigateToClusterOverviewInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed((): SidebarItemRegistration[] => [ + { + id: "cluster-overview", + parentId: null, + title: "Cluster", + getIcon: () => , + onClick: navigateToClusterOverview, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 10, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default clusterOverviewSidebarItemsInjectable; diff --git a/src/renderer/components/+cluster/cluster-overview.tsx b/src/renderer/components/+cluster/cluster-overview.tsx index fcbd73875b..609138c965 100644 --- a/src/renderer/components/+cluster/cluster-overview.tsx +++ b/src/renderer/components/+cluster/cluster-overview.tsx @@ -11,7 +11,7 @@ import { disposeOnUnmount, observer } from "mobx-react"; import { nodesStore } from "../+nodes/nodes.store"; import { podsStore } from "../+workloads-pods/pods.store"; import { Disposer, getHostedClusterId, interval } from "../../utils"; -import { TabLayout } from "../layout/tab-layout"; +import { TabLayout } from "../layout/tab-layout-2"; import { Spinner } from "../spinner"; import { ClusterIssues } from "./cluster-issues"; import { ClusterMetrics } from "./cluster-metrics"; diff --git a/src/renderer/components/+cluster/sidebar-item.tsx b/src/renderer/components/+cluster/sidebar-item.tsx deleted file mode 100644 index 597118eb02..0000000000 --- a/src/renderer/components/+cluster/sidebar-item.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { withInjectables } from "@ogre-tools/injectable-react"; -import { observer } from "mobx-react"; -import React from "react"; -import { clusterRoute, clusterURL } from "../../../common/routes"; -import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; -import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; - -export interface ClusterSidebarItemProps {} - -interface Dependencies { - isAllowedResource: IsAllowedResource; -} - -const NonInjectedClusterSidebarItem = observer(({ isAllowedResource }: Dependencies & ClusterSidebarItemProps) => ( - } - /> -)); - -export const ClusterSidebarItem = withInjectables(NonInjectedClusterSidebarItem, { - getProps: (di, props) => ({ - isAllowedResource: di.inject(isAllowedResourceInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+config-autoscalers/horizontal-pod-auto-scalers-sidebar-items.injectable.tsx b/src/renderer/components/+config-autoscalers/horizontal-pod-auto-scalers-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..99bff705b8 --- /dev/null +++ b/src/renderer/components/+config-autoscalers/horizontal-pod-auto-scalers-sidebar-items.injectable.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import horizontalPodAutoscalersRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable"; +import { configSidebarItemId } from "../+config/config-sidebar-items.injectable"; +import { SidebarItemRegistration, sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToHorizontalPodAutoscalersInjectable from "../../../common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/navigate-to-horizontal-pod-autoscalers.injectable"; + +const horizontalPodAutoScalersSidebarItemsInjectable = getInjectable({ + id: "horizontal-pod-auto-scalers-sidebar-items", + + instantiate: (di) => { + const route = di.inject(horizontalPodAutoscalersRouteInjectable); + const navigateToHorizontalPodAutoscalers = di.inject(navigateToHorizontalPodAutoscalersInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed((): SidebarItemRegistration[] => [ + { + id: "horizontal-pod-auto-scalers", + parentId: configSidebarItemId, + title: "HPA", + onClick: navigateToHorizontalPodAutoscalers, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 50, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default horizontalPodAutoScalersSidebarItemsInjectable; diff --git a/src/renderer/components/+config-autoscalers/horizontal-pod-autoscalers-route-component.injectable.ts b/src/renderer/components/+config-autoscalers/horizontal-pod-autoscalers-route-component.injectable.ts new file mode 100644 index 0000000000..991f6cab1b --- /dev/null +++ b/src/renderer/components/+config-autoscalers/horizontal-pod-autoscalers-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import horizontalPodAutoscalersRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable"; +import { HorizontalPodAutoscalers } from "./hpa"; + +const horizontalPodAutoscalersRouteComponentInjectable = getInjectable({ + id: "horizontal-pod-autoscalers-route-component", + + instantiate: (di) => ({ + route: di.inject(horizontalPodAutoscalersRouteInjectable), + Component: HorizontalPodAutoscalers, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default horizontalPodAutoscalersRouteComponentInjectable; diff --git a/src/renderer/components/+config-autoscalers/hpa.tsx b/src/renderer/components/+config-autoscalers/hpa.tsx index 0f99dd1d78..51866e491e 100644 --- a/src/renderer/components/+config-autoscalers/hpa.tsx +++ b/src/renderer/components/+config-autoscalers/hpa.tsx @@ -7,14 +7,13 @@ import "./hpa.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import type { HorizontalPodAutoscaler } from "../../../common/k8s-api/endpoints/hpa.api"; import { hpaStore } from "./hpa.store"; import { Badge } from "../badge"; import { cssNames } from "../../utils"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { HpaRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -28,11 +27,8 @@ enum columnId { status = "status", } -export interface HorizontalPodAutoscalersProps extends RouteComponentProps { -} - @observer -export class HorizontalPodAutoscalers extends React.Component { +export class HorizontalPodAutoscalers extends React.Component { getTargets(hpa: HorizontalPodAutoscaler) { const metrics = hpa.getMetrics(); @@ -47,57 +43,59 @@ export class HorizontalPodAutoscalers extends React.Component hpa.getName(), - [columnId.namespace]: hpa => hpa.getNs(), - [columnId.minPods]: hpa => hpa.getMinPods(), - [columnId.maxPods]: hpa => hpa.getMaxPods(), - [columnId.replicas]: hpa => hpa.getReplicas(), - [columnId.age]: hpa => -hpa.getCreationTimestamp(), - }} - searchFilters={[ - hpa => hpa.getSearchFields(), - ]} - renderHeaderTitle="Horizontal Pod Autoscalers" - renderTableHeader={[ - { title: "Name", className: "name", sortBy: columnId.name }, - { className: "warning", showWithColumn: columnId.name }, - { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, - { title: "Metrics", className: "metrics", id: columnId.metrics }, - { title: "Min Pods", className: "min-pods", sortBy: columnId.minPods, id: columnId.minPods }, - { title: "Max Pods", className: "max-pods", sortBy: columnId.maxPods, id: columnId.maxPods }, - { title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - { title: "Status", className: "status scrollable", id: columnId.status }, - ]} - renderTableContents={hpa => [ - hpa.getName(), - , - hpa.getNs(), - this.getTargets(hpa), - hpa.getMinPods(), - hpa.getMaxPods(), - hpa.getReplicas(), - , - hpa.getConditions() - .filter(({ isReady }) => isReady) - .map(({ type, tooltip }) => ( - - )), - ]} - /> + + hpa.getName(), + [columnId.namespace]: hpa => hpa.getNs(), + [columnId.minPods]: hpa => hpa.getMinPods(), + [columnId.maxPods]: hpa => hpa.getMaxPods(), + [columnId.replicas]: hpa => hpa.getReplicas(), + [columnId.age]: hpa => -hpa.getCreationTimestamp(), + }} + searchFilters={[ + hpa => hpa.getSearchFields(), + ]} + renderHeaderTitle="Horizontal Pod Autoscalers" + renderTableHeader={[ + { title: "Name", className: "name", sortBy: columnId.name }, + { className: "warning", showWithColumn: columnId.name }, + { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, + { title: "Metrics", className: "metrics", id: columnId.metrics }, + { title: "Min Pods", className: "min-pods", sortBy: columnId.minPods, id: columnId.minPods }, + { title: "Max Pods", className: "max-pods", sortBy: columnId.maxPods, id: columnId.maxPods }, + { title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + { title: "Status", className: "status scrollable", id: columnId.status }, + ]} + renderTableContents={hpa => [ + hpa.getName(), + , + hpa.getNs(), + this.getTargets(hpa), + hpa.getMinPods(), + hpa.getMaxPods(), + hpa.getReplicas(), + , + hpa.getConditions() + .filter(({ isReady }) => isReady) + .map(({ type, tooltip }) => ( + + )), + ]} + /> + ); } } diff --git a/src/renderer/components/+config-limit-ranges/limit-ranges-route-component.injectable.ts b/src/renderer/components/+config-limit-ranges/limit-ranges-route-component.injectable.ts new file mode 100644 index 0000000000..b79ffaa8f6 --- /dev/null +++ b/src/renderer/components/+config-limit-ranges/limit-ranges-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { LimitRanges } from "./limit-ranges"; +import limitRangesRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const limitRangesRouteComponentInjectable = getInjectable({ + id: "limit-ranges-route-component", + + instantiate: (di) => ({ + route: di.inject(limitRangesRouteInjectable), + Component: LimitRanges, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default limitRangesRouteComponentInjectable; diff --git a/src/renderer/components/+config-limit-ranges/limit-ranges-sidebar-items.injectable.tsx b/src/renderer/components/+config-limit-ranges/limit-ranges-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..f172cd2e8d --- /dev/null +++ b/src/renderer/components/+config-limit-ranges/limit-ranges-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import limitRangesRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable"; +import { configSidebarItemId } from "../+config/config-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToLimitRangesInjectable from "../../../common/front-end-routing/routes/cluster/config/limit-ranges/navigate-to-limit-ranges.injectable"; + +const limitRangesSidebarItemsInjectable = getInjectable({ + id: "limit-ranges-sidebar-items", + + instantiate: (di) => { + const route = di.inject(limitRangesRouteInjectable); + const navigateToLimitRanges = di.inject(navigateToLimitRangesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "limit-ranges", + parentId: configSidebarItemId, + title: "Limit Ranges", + onClick: navigateToLimitRanges, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 40, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default limitRangesSidebarItemsInjectable; diff --git a/src/renderer/components/+config-limit-ranges/limit-ranges.tsx b/src/renderer/components/+config-limit-ranges/limit-ranges.tsx index 285e9533c7..8bf77cadc1 100644 --- a/src/renderer/components/+config-limit-ranges/limit-ranges.tsx +++ b/src/renderer/components/+config-limit-ranges/limit-ranges.tsx @@ -5,13 +5,12 @@ import "./limit-ranges.scss"; -import type { RouteComponentProps } from "react-router"; import { observer } from "mobx-react"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { limitRangeStore } from "./limit-ranges.store"; import React from "react"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { LimitRangeRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -20,41 +19,40 @@ enum columnId { age = "age", } -export interface LimitRangesProps extends RouteComponentProps { -} - @observer -export class LimitRanges extends React.Component { +export class LimitRanges extends React.Component { render() { return ( - limitRange.getName(), - [columnId.namespace]: limitRange => limitRange.getNs(), - [columnId.age]: limitRange => -limitRange.getCreationTimestamp(), - }} - searchFilters={[ - item => item.getName(), - item => item.getNs(), - ]} - renderHeaderTitle="Limit Ranges" - renderTableHeader={[ - { 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: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={limitRange => [ - limitRange.getName(), - , - limitRange.getNs(), - , - ]} - /> + + limitRange.getName(), + [columnId.namespace]: limitRange => limitRange.getNs(), + [columnId.age]: limitRange => -limitRange.getCreationTimestamp(), + }} + searchFilters={[ + item => item.getName(), + item => item.getNs(), + ]} + renderHeaderTitle="Limit Ranges" + renderTableHeader={[ + { 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: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={limitRange => [ + limitRange.getName(), + , + limitRange.getNs(), + , + ]} + /> + ); } } diff --git a/src/renderer/components/+config-maps/config-maps-route-component.injectable.ts b/src/renderer/components/+config-maps/config-maps-route-component.injectable.ts new file mode 100644 index 0000000000..fd9c7df320 --- /dev/null +++ b/src/renderer/components/+config-maps/config-maps-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { ConfigMaps } from "./config-maps"; +import configMapsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const configMapsRouteComponentInjectable = getInjectable({ + id: "config-maps-route-component", + + instantiate: (di) => ({ + route: di.inject(configMapsRouteInjectable), + Component: ConfigMaps, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default configMapsRouteComponentInjectable; diff --git a/src/renderer/components/+config-maps/config-maps-sidebar-items.injectable.tsx b/src/renderer/components/+config-maps/config-maps-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..638273b128 --- /dev/null +++ b/src/renderer/components/+config-maps/config-maps-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import configMapsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable"; +import { configSidebarItemId } from "../+config/config-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToConfigMapsInjectable from "../../../common/front-end-routing/routes/cluster/config/config-maps/navigate-to-config-maps.injectable"; + +const configMapsSidebarItemsInjectable = getInjectable({ + id: "config-maps-sidebar-items", + + instantiate: (di) => { + const route = di.inject(configMapsRouteInjectable); + const navigateToConfigMaps = di.inject(navigateToConfigMapsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "config-maps", + parentId: configSidebarItemId, + title: "ConfigMaps", + onClick: navigateToConfigMaps, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 10, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default configMapsSidebarItemsInjectable; diff --git a/src/renderer/components/+config-maps/config-maps.tsx b/src/renderer/components/+config-maps/config-maps.tsx index 3dc189560e..a7dc001eea 100644 --- a/src/renderer/components/+config-maps/config-maps.tsx +++ b/src/renderer/components/+config-maps/config-maps.tsx @@ -7,11 +7,10 @@ import "./config-maps.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { configMapsStore } from "./config-maps.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { ConfigMapsRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -21,44 +20,43 @@ enum columnId { age = "age", } -export interface ConfigMapsProps extends RouteComponentProps { -} - @observer -export class ConfigMaps extends React.Component { +export class ConfigMaps extends React.Component { render() { return ( - configMap.getName(), - [columnId.namespace]: configMap => configMap.getNs(), - [columnId.keys]: configMap => configMap.getKeys(), - [columnId.age]: configMap => -configMap.getCreationTimestamp(), - }} - searchFilters={[ - configMap => configMap.getSearchFields(), - configMap => configMap.getKeys(), - ]} - renderHeaderTitle="Config Maps" - renderTableHeader={[ - { 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: "Keys", className: "keys", sortBy: columnId.keys, id: columnId.keys }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={configMap => [ - configMap.getName(), - , - configMap.getNs(), - configMap.getKeys().join(", "), - , - ]} - /> + + configMap.getName(), + [columnId.namespace]: configMap => configMap.getNs(), + [columnId.keys]: configMap => configMap.getKeys(), + [columnId.age]: configMap => -configMap.getCreationTimestamp(), + }} + searchFilters={[ + configMap => configMap.getSearchFields(), + configMap => configMap.getKeys(), + ]} + renderHeaderTitle="Config Maps" + renderTableHeader={[ + { 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: "Keys", className: "keys", sortBy: columnId.keys, id: columnId.keys }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={configMap => [ + configMap.getName(), + , + configMap.getNs(), + configMap.getKeys().join(", "), + , + ]} + /> + ); } } diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-route-component.injectable.ts b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-route-component.injectable.ts new file mode 100644 index 0000000000..e963b9408e --- /dev/null +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { PodDisruptionBudgets } from "./pod-disruption-budgets"; +import podDisruptionBudgetsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const podDisruptionBudgetsRouteComponentInjectable = getInjectable({ + id: "pod-disruption-budgets-route-component", + + instantiate: (di) => ({ + route: di.inject(podDisruptionBudgetsRouteInjectable), + Component: PodDisruptionBudgets, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default podDisruptionBudgetsRouteComponentInjectable; diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-sidebar-items.injectable.tsx b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..a4e6092b33 --- /dev/null +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import podDisruptionBudgetsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable"; +import { configSidebarItemId } from "../+config/config-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToPodDisruptionBudgetsInjectable from "../../../common/front-end-routing/routes/cluster/config/pod-disruption-budgets/navigate-to-pod-disruption-budgets.injectable"; + +const podDisruptionBudgetsSidebarItemsInjectable = getInjectable({ + id: "pod-disruption-budgets-sidebar-items", + + instantiate: (di) => { + const route = di.inject(podDisruptionBudgetsRouteInjectable); + const navigateToPodDisruptionBudgets = di.inject(navigateToPodDisruptionBudgetsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "pod-disruption-budgets", + parentId: configSidebarItemId, + title: "Pod Disruption Budgets", + onClick: navigateToPodDisruptionBudgets, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 60, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default podDisruptionBudgetsSidebarItemsInjectable; diff --git a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx index 74f685df59..f9871f646a 100644 --- a/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx +++ b/src/renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.tsx @@ -12,6 +12,7 @@ import type { PodDisruptionBudget } from "../../../common/k8s-api/endpoints/podd import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { KubeObjectListLayout } from "../kube-object-list-layout"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -31,45 +32,47 @@ export interface PodDisruptionBudgetsProps extends KubeObjectDetailsProps { render() { return ( - pdb.getName(), - [columnId.namespace]: pdb => pdb.getNs(), - [columnId.minAvailable]: pdb => pdb.getMinAvailable(), - [columnId.maxUnavailable]: pdb => pdb.getMaxUnavailable(), - [columnId.currentHealthy]: pdb => pdb.getCurrentHealthy(), - [columnId.desiredHealthy]: pdb => pdb.getDesiredHealthy(), - [columnId.age]: pdb => -pdb.getCreationTimestamp(), - }} - searchFilters={[ - pdb => pdb.getSearchFields(), - ]} - renderHeaderTitle="Pod Disruption Budgets" - renderTableHeader={[ - { 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: "Min Available", className: "min-available", sortBy: columnId.minAvailable, id: columnId.minAvailable }, - { title: "Max Unavailable", className: "max-unavailable", sortBy: columnId.maxUnavailable, id: columnId.maxUnavailable }, - { title: "Current Healthy", className: "current-healthy", sortBy: columnId.currentHealthy, id: columnId.currentHealthy }, - { title: "Desired Healthy", className: "desired-healthy", sortBy: columnId.desiredHealthy, id: columnId.desiredHealthy }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={pdb => [ - pdb.getName(), - , - pdb.getNs(), - pdb.getMinAvailable(), - pdb.getMaxUnavailable(), - pdb.getCurrentHealthy(), - pdb.getDesiredHealthy(), - , - ]} - /> + + pdb.getName(), + [columnId.namespace]: pdb => pdb.getNs(), + [columnId.minAvailable]: pdb => pdb.getMinAvailable(), + [columnId.maxUnavailable]: pdb => pdb.getMaxUnavailable(), + [columnId.currentHealthy]: pdb => pdb.getCurrentHealthy(), + [columnId.desiredHealthy]: pdb => pdb.getDesiredHealthy(), + [columnId.age]: pdb => -pdb.getCreationTimestamp(), + }} + searchFilters={[ + pdb => pdb.getSearchFields(), + ]} + renderHeaderTitle="Pod Disruption Budgets" + renderTableHeader={[ + { 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: "Min Available", className: "min-available", sortBy: columnId.minAvailable, id: columnId.minAvailable }, + { title: "Max Unavailable", className: "max-unavailable", sortBy: columnId.maxUnavailable, id: columnId.maxUnavailable }, + { title: "Current Healthy", className: "current-healthy", sortBy: columnId.currentHealthy, id: columnId.currentHealthy }, + { title: "Desired Healthy", className: "desired-healthy", sortBy: columnId.desiredHealthy, id: columnId.desiredHealthy }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={pdb => [ + pdb.getName(), + , + pdb.getNs(), + pdb.getMinAvailable(), + pdb.getMaxUnavailable(), + pdb.getCurrentHealthy(), + pdb.getDesiredHealthy(), + , + ]} + /> + ); } } diff --git a/src/renderer/components/+config-resource-quotas/resource-quotas-route-component.injectable.ts b/src/renderer/components/+config-resource-quotas/resource-quotas-route-component.injectable.ts new file mode 100644 index 0000000000..8e138a7064 --- /dev/null +++ b/src/renderer/components/+config-resource-quotas/resource-quotas-route-component.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { ResourceQuotas } from "./resource-quotas"; +import resourceQuotasRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable"; +import { + routeSpecificComponentInjectionToken, +} from "../../routes/route-specific-component-injection-token"; + +const resourceQuotasRouteComponentInjectable = getInjectable({ + id: "resource-quotas-route-component", + + instantiate: (di) => ({ + route: di.inject(resourceQuotasRouteInjectable), + Component: ResourceQuotas, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default resourceQuotasRouteComponentInjectable; diff --git a/src/renderer/components/+config-resource-quotas/resource-quotas-sidebar-items.injectable.tsx b/src/renderer/components/+config-resource-quotas/resource-quotas-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..7df66c8210 --- /dev/null +++ b/src/renderer/components/+config-resource-quotas/resource-quotas-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import resourceQuotasRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable"; +import { configSidebarItemId } from "../+config/config-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToResourceQuotasInjectable from "../../../common/front-end-routing/routes/cluster/config/resource-quotas/navigate-to-resource-quotas.injectable"; + +const resourceQuotasSidebarItemsInjectable = getInjectable({ + id: "resource-quotas-sidebar-items", + + instantiate: (di) => { + const route = di.inject(resourceQuotasRouteInjectable); + const navigateToResourceQuotas = di.inject(navigateToResourceQuotasInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "resource-quotas", + parentId: configSidebarItemId, + title: "Resource Quotas", + onClick: navigateToResourceQuotas, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 30, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default resourceQuotasSidebarItemsInjectable; diff --git a/src/renderer/components/+config-resource-quotas/resource-quotas.tsx b/src/renderer/components/+config-resource-quotas/resource-quotas.tsx index 29a79d6bcc..5e602b6f1f 100644 --- a/src/renderer/components/+config-resource-quotas/resource-quotas.tsx +++ b/src/renderer/components/+config-resource-quotas/resource-quotas.tsx @@ -7,12 +7,11 @@ import "./resource-quotas.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { AddQuotaDialog } from "./add-quota-dialog"; import { resourceQuotaStore } from "./resource-quotas.store"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { ResourceQuotaRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -21,14 +20,11 @@ enum columnId { age = "age", } -export interface ResourceQuotasProps extends RouteComponentProps { -} - @observer -export class ResourceQuotas extends React.Component { +export class ResourceQuotas extends React.Component { render() { return ( - <> + { }} /> - + ); } } diff --git a/src/renderer/components/+config-secrets/secrets-route-component.injectable.ts b/src/renderer/components/+config-secrets/secrets-route-component.injectable.ts new file mode 100644 index 0000000000..35dc02d5fe --- /dev/null +++ b/src/renderer/components/+config-secrets/secrets-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Secrets } from "./secrets"; +import secretsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const secretsRouteComponentInjectable = getInjectable({ + id: "secrets-route-component", + + instantiate: (di) => ({ + route: di.inject(secretsRouteInjectable), + Component: Secrets, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default secretsRouteComponentInjectable; diff --git a/src/renderer/components/+config-secrets/secrets-sidebar-items.injectable.tsx b/src/renderer/components/+config-secrets/secrets-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..cf8a07dc66 --- /dev/null +++ b/src/renderer/components/+config-secrets/secrets-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import secretsRouteInjectable from "../../../common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable"; +import { configSidebarItemId } from "../+config/config-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToSecretsInjectable from "../../../common/front-end-routing/routes/cluster/config/secrets/navigate-to-secrets.injectable"; + +const secretsSidebarItemsInjectable = getInjectable({ + id: "secrets-sidebar-items", + + instantiate: (di) => { + const route = di.inject(secretsRouteInjectable); + const navigateToSecrets = di.inject(navigateToSecretsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "secrets", + parentId: configSidebarItemId, + title: "Secrets", + onClick: navigateToSecrets, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 20, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default secretsSidebarItemsInjectable; diff --git a/src/renderer/components/+config-secrets/secrets.tsx b/src/renderer/components/+config-secrets/secrets.tsx index 9df906187e..e519150007 100644 --- a/src/renderer/components/+config-secrets/secrets.tsx +++ b/src/renderer/components/+config-secrets/secrets.tsx @@ -7,13 +7,12 @@ import "./secrets.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { AddSecretDialog } from "./add-secret-dialog"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { Badge } from "../badge"; import { secretsStore } from "./secrets.store"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { SecretsRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -25,14 +24,11 @@ enum columnId { age = "age", } -export interface SecretsProps extends RouteComponentProps { -} - @observer -export class Secrets extends React.Component { +export class Secrets extends React.Component { render() { return ( - <> + { }} /> - + ); } } diff --git a/src/renderer/components/+config/config-sidebar-items.injectable.tsx b/src/renderer/components/+config/config-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..60d1820635 --- /dev/null +++ b/src/renderer/components/+config/config-sidebar-items.injectable.tsx @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Icon } from "../icon"; +import React from "react"; +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { computed } from "mobx"; +import { noop } from "lodash/fp"; + +export const configSidebarItemId = "config"; + +const configSidebarItemsInjectable = getInjectable({ + id: "config-sidebar-items", + + instantiate: () => + computed((): SidebarItemRegistration[] => [ + { + id: configSidebarItemId, + parentId: null, + title: "Config", + getIcon: () => , + onClick: noop, + orderNumber: 40, + }, + ]), + + injectionToken: sidebarItemsInjectionToken, +}); + +export default configSidebarItemsInjectable; diff --git a/src/renderer/components/+config/route-tabs.injectable.ts b/src/renderer/components/+config/route-tabs.injectable.ts deleted file mode 100644 index 91bf531eff..0000000000 --- a/src/renderer/components/+config/route-tabs.injectable.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import { HorizontalPodAutoscalers } from "../+config-autoscalers"; -import { LimitRanges } from "../+config-limit-ranges"; -import { ConfigMaps } from "../+config-maps"; -import { PodDisruptionBudgets } from "../+config-pod-disruption-budgets"; -import { ResourceQuotas } from "../+config-resource-quotas"; -import { Secrets } from "../+config-secrets"; -import isAllowedResourceInjectable, { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import * as routes from "../../../common/routes"; - -interface Dependencies { - isAllowedResource: IsAllowedResource; -} - -function getRouteTabs({ isAllowedResource }: Dependencies) { - return computed(() => { - const tabs: TabLayoutRoute[] = []; - - if (isAllowedResource("configmaps")) { - tabs.push({ - title: "ConfigMaps", - component: ConfigMaps, - url: routes.configMapsURL(), - routePath: routes.configMapsRoute.path.toString(), - }); - } - - if (isAllowedResource("secrets")) { - tabs.push({ - title: "Secrets", - component: Secrets, - url: routes.secretsURL(), - routePath: routes.secretsRoute.path.toString(), - }); - } - - if (isAllowedResource("resourcequotas")) { - tabs.push({ - title: "Resource Quotas", - component: ResourceQuotas, - url: routes.resourceQuotaURL(), - routePath: routes.resourceQuotaRoute.path.toString(), - }); - } - - if (isAllowedResource("limitranges")) { - tabs.push({ - title: "Limit Ranges", - component: LimitRanges, - url: routes.limitRangeURL(), - routePath: routes.limitRangesRoute.path.toString(), - }); - } - - if (isAllowedResource("horizontalpodautoscalers")) { - tabs.push({ - title: "HPA", - component: HorizontalPodAutoscalers, - url: routes.hpaURL(), - routePath: routes.hpaRoute.path.toString(), - }); - } - - if (isAllowedResource("poddisruptionbudgets")) { - tabs.push({ - title: "Pod Disruption Budgets", - component: PodDisruptionBudgets, - url: routes.pdbURL(), - routePath: routes.pdbRoute.path.toString(), - }); - } - - return tabs; - }); -} - -const configRoutesInjectable = getInjectable({ - id: "config-routes", - - instantiate: (di) => getRouteTabs({ - isAllowedResource: di.inject(isAllowedResourceInjectable), - }), -}); - -export default configRoutesInjectable; diff --git a/src/renderer/components/+config/route.tsx b/src/renderer/components/+config/route.tsx deleted file mode 100644 index 44b0934bc6..0000000000 --- a/src/renderer/components/+config/route.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import React from "react"; -import { observer } from "mobx-react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import type { IComputedValue } from "mobx"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import configRoutesInjectable from "./route-tabs.injectable"; - -export interface ConfigRouteProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedConfigRoute = observer(({ routes }: Dependencies & ConfigRouteProps) => ( - -)); - -export const ConfigRoute = withInjectables(NonInjectedConfigRoute, { - getProps: (di, props) => ({ - routes: di.inject(configRoutesInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+config/sidebar-item.tsx b/src/renderer/components/+config/sidebar-item.tsx deleted file mode 100644 index 4bd8d7b719..0000000000 --- a/src/renderer/components/+config/sidebar-item.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { withInjectables } from "@ogre-tools/injectable-react"; -import type { IComputedValue } from "mobx"; -import { observer } from "mobx-react"; -import React from "react"; -import { configRoute, configURL } from "../../../common/routes"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import { renderTabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; -import configRoutesInjectable from "./route-tabs.injectable"; - -export interface ConfigSidebarItemProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedConfigSidebarItem = observer(({ routes }: Dependencies & ConfigSidebarItemProps) => { - const tabRoutes = routes.get(); - - return ( - } - > - {renderTabRoutesSidebarItems(tabRoutes)} - - ); -}); - -export const ConfigSidebarItem = withInjectables(NonInjectedConfigSidebarItem, { - getProps: (di, props) => ({ - routes: di.inject(configRoutesInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+custom-resources/crd-list-route-component.injectable.ts b/src/renderer/components/+custom-resources/crd-list-route-component.injectable.ts new file mode 100644 index 0000000000..55e4a21006 --- /dev/null +++ b/src/renderer/components/+custom-resources/crd-list-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import crdListRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable"; +import { CustomResourceDefinitions } from "./crd-list"; + +const crdListRouteComponentInjectable = getInjectable({ + id: "crd-list-route-component", + + instantiate: (di) => ({ + route: di.inject(crdListRouteInjectable), + Component: CustomResourceDefinitions, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default crdListRouteComponentInjectable; diff --git a/src/renderer/components/+custom-resources/crd-list.tsx b/src/renderer/components/+custom-resources/crd-list.tsx index 8a3277ce21..9dc35743c5 100644 --- a/src/renderer/components/+custom-resources/crd-list.tsx +++ b/src/renderer/components/+custom-resources/crd-list.tsx @@ -16,6 +16,7 @@ import { Select, SelectOption } from "../select"; import { createPageParam } from "../../navigation"; import { Icon } from "../icon"; import { KubeObjectAge } from "../kube-object/age"; +import { TabLayout } from "../layout/tab-layout-2"; export const crdGroupsUrlParam = createPageParam({ name: "groups", @@ -64,81 +65,83 @@ export class CustomResourceDefinitions extends React.Component { const { items, selectedGroups } = this; return ( - already has and is always mounted - subscribeStores={false} - items={items} - sortingCallbacks={{ - [columnId.kind]: crd => crd.getResourceKind(), - [columnId.group]: crd => crd.getGroup(), - [columnId.version]: crd => crd.getVersion(), - [columnId.scope]: crd => crd.getScope(), - [columnId.age]: crd => -crd.getCreationTimestamp(), - }} - searchFilters={[ - crd => crd.getResourceKind(), - crd => crd.getGroup(), - crd => crd.getVersion(), - crd => crd.getScope(), - crd => -crd.getCreationTimestamp(), - ]} - renderHeaderTitle="Custom Resources" - customizeHeader={({ filters, ...headerPlaceholders }) => { - let placeholder = <>All groups; + + already has and is always mounted + subscribeStores={false} + items={items} + sortingCallbacks={{ + [columnId.kind]: crd => crd.getResourceKind(), + [columnId.group]: crd => crd.getGroup(), + [columnId.version]: crd => crd.getVersion(), + [columnId.scope]: crd => crd.getScope(), + [columnId.age]: crd => -crd.getCreationTimestamp(), + }} + searchFilters={[ + crd => crd.getResourceKind(), + crd => crd.getGroup(), + crd => crd.getVersion(), + crd => crd.getScope(), + crd => -crd.getCreationTimestamp(), + ]} + renderHeaderTitle="Custom Resources" + customizeHeader={({ filters, ...headerPlaceholders }) => { + let placeholder = <>All groups; - if (selectedGroups.length == 1) placeholder = <>Group: {selectedGroups[0]}; - if (selectedGroups.length >= 2) placeholder = <>Groups: {selectedGroups.join(", ")}; + if (selectedGroups.length == 1) placeholder = <>Group: {selectedGroups[0]}; + if (selectedGroups.length >= 2) placeholder = <>Groups: {selectedGroups.join(", ")}; - return { - // todo: move to global filters - filters: ( - <> - {filters} - this.toggleSelection(group)} + closeMenuOnSelect={false} + controlShouldRenderValue={false} + formatOptionLabel={({ value: group }: SelectOption) => { + const isSelected = selectedGroups.includes(group); - return ( -

- - {group} - {isSelected && } -
- ); - }} - /> - - ), - ...headerPlaceholders, - }; - }} - renderTableHeader={[ - { title: "Resource", className: "kind", sortBy: columnId.kind, id: columnId.kind }, - { title: "Group", className: "group", sortBy: columnId.group, id: columnId.group }, - { title: "Version", className: "version", sortBy: columnId.version, id: columnId.version }, - { title: "Scope", className: "scope", sortBy: columnId.scope, id: columnId.scope }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={crd => [ - - {crd.getResourceKind()} - , - crd.getGroup(), - crd.getVersion(), - crd.getScope(), - , - ]} - /> + return ( +
+ + {group} + {isSelected && } +
+ ); + }} + /> + + ), + ...headerPlaceholders, + }; + }} + renderTableHeader={[ + { title: "Resource", className: "kind", sortBy: columnId.kind, id: columnId.kind }, + { title: "Group", className: "group", sortBy: columnId.group, id: columnId.group }, + { title: "Version", className: "version", sortBy: columnId.version, id: columnId.version }, + { title: "Scope", className: "scope", sortBy: columnId.scope, id: columnId.scope }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={crd => [ + + {crd.getResourceKind()} + , + crd.getGroup(), + crd.getVersion(), + crd.getScope(), + , + ]} + /> + ); } } diff --git a/src/renderer/components/+custom-resources/crd-resources.tsx b/src/renderer/components/+custom-resources/crd-resources.tsx index 0669f5ff86..25601b1112 100644 --- a/src/renderer/components/+custom-resources/crd-resources.tsx +++ b/src/renderer/components/+custom-resources/crd-resources.tsx @@ -8,35 +8,36 @@ import "./crd-resources.scss"; import React from "react"; import { value } from "jsonpath"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../kube-object-list-layout"; -import { computed, makeObservable } from "mobx"; +import { computed, IComputedValue, makeObservable } from "mobx"; import { crdStore } from "./crd.store"; import { apiManager } from "../../../common/k8s-api/api-manager"; import { parseJsonPath } from "../../utils/jsonPath"; -import type { CRDRouteParams } from "../../../common/routes"; +import { TabLayout } from "../layout/tab-layout-2"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import customResourcesRouteParametersInjectable from "./custom-resources-route-parameters.injectable"; import { KubeObjectAge } from "../kube-object/age"; -export interface CustomResourceDefinitionResourcesProps extends RouteComponentProps { -} - enum columnId { name = "name", namespace = "namespace", age = "age", } +interface Dependencies { + group: IComputedValue; + name: IComputedValue; +} + @observer -export class CustomResourceDefinitionResources extends React.Component { - constructor(props: CustomResourceDefinitionResourcesProps) { +class NonInjectedCrdResources extends React.Component { + constructor(props: Dependencies) { super(props); makeObservable(this); } @computed get crd() { - const { group, name } = this.props.match.params; - - return crdStore.getByGroup(group, name); + return crdStore.getByGroup(this.props.group.get(), this.props.name.get()); } @computed get store() { @@ -55,69 +56,87 @@ export class CustomResourceDefinitionResources extends React.Component customResource.getName(), - [columnId.namespace]: customResource => customResource.getNs(), - [columnId.age]: customResource => -customResource.getCreationTimestamp(), - ...Object.fromEntries(extraColumns.map(({ name, jsonPath }) => [ - name, - customResource => value(customResource, parseJsonPath(jsonPath.slice(1))), - ])), - }} - searchFilters={[ - customResource => customResource.getSearchFields(), - ]} - renderHeaderTitle={crd.getResourceKind()} - customizeHeader={({ searchProps, ...headerPlaceholders }) => ({ - searchProps: { - ...searchProps, - placeholder: `${crd.getResourceKind()} search ...`, - }, - ...headerPlaceholders, - })} - renderTableHeader={[ - { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, - isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, - ...extraColumns.map(({ name }) => ({ - title: name, - className: name.toLowerCase(), - sortBy: name, - id: name, - })), - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={crdInstance => [ - crdInstance.getName(), - isNamespaced && crdInstance.getNs(), - ...extraColumns.map((column) => { - let rawValue = value(crdInstance, parseJsonPath(column.jsonPath.slice(1))); + + customResource.getName(), + [columnId.namespace]: customResource => customResource.getNs(), + [columnId.age]: customResource => -customResource.getCreationTimestamp(), + ...Object.fromEntries(extraColumns.map(({ name, jsonPath }) => [ + name, + customResource => value(customResource, parseJsonPath(jsonPath.slice(1))), + ])), + }} + searchFilters={[ + customResource => customResource.getSearchFields(), + ]} + renderHeaderTitle={crd.getResourceKind()} + customizeHeader={({ searchProps, ...headerPlaceholders }) => ({ + searchProps: { + ...searchProps, + placeholder: `${crd.getResourceKind()} search ...`, + }, + ...headerPlaceholders, + })} + renderTableHeader={[ + { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, + isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, + ...extraColumns.map(({ name }) => ({ + title: name, + className: name.toLowerCase(), + sortBy: name, + id: name, + })), + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={crdInstance => [ + crdInstance.getName(), + isNamespaced && crdInstance.getNs(), + ...extraColumns.map((column) => { + let rawValue = value(crdInstance, parseJsonPath(column.jsonPath.slice(1))); - if (Array.isArray(rawValue) || typeof rawValue === "object") { - rawValue = JSON.stringify(rawValue); - } + if (Array.isArray(rawValue) || typeof rawValue === "object") { + rawValue = JSON.stringify(rawValue); + } - return { - renderBoolean: true, - children: rawValue, - }; - }), - , - ]} - failedToLoadMessage={( - <> -

Failed to load {crd.getPluralName()}

- {!version.served && ( -

Prefered version ({crd.getGroup()}/{version.name}) is not served

- )} - - )} - /> + return { + renderBoolean: true, + children: rawValue, + }; + }), + , + ]} + failedToLoadMessage={( + <> +

Failed to load {crd.getPluralName()}

+ {!version.served && ( +

Prefered version ({crd.getGroup()}/{version.name}) is not served

+ )} + + )} + /> +
); } } + +export const CrdResources = withInjectables( + NonInjectedCrdResources, + + { + getProps: (di) => { + const routeParameters = di.inject(customResourcesRouteParametersInjectable); + + return { + group: routeParameters.group, + name: routeParameters.name, + }; + }, + }, +); + diff --git a/src/renderer/components/+custom-resources/custom-resource-sidebar-items.injectable.tsx b/src/renderer/components/+custom-resources/custom-resource-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..d9908d6260 --- /dev/null +++ b/src/renderer/components/+custom-resources/custom-resource-sidebar-items.injectable.tsx @@ -0,0 +1,68 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { noop, some } from "lodash/fp"; +import { computed } from "mobx"; + +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { Icon } from "../icon"; +import React from "react"; + +import crdListRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable"; +import sidebarItemsForDefinitionGroupsInjectable from "./sidebar-items-for-definition-groups.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToCrdListInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/navigate-to-crd-list.injectable"; + +const customResourceSidebarItemsInjectable = getInjectable({ + id: "custom-resource-sidebar-items", + + instantiate: (di) => { + const navigateToCrdList = di.inject(navigateToCrdListInjectable); + const crdListRoute = di.inject(crdListRouteInjectable); + const crdListRouteIsActive = di.inject(routeIsActiveInjectable, crdListRoute); + + const definitionGroupSidebarItems = di.inject( + sidebarItemsForDefinitionGroupsInjectable, + ); + + return computed((): SidebarItemRegistration[] => { + const definitionsItem = { + id: "definitions", + parentId: "custom-resources", + title: "Definitions", + onClick: navigateToCrdList, + isActive: crdListRouteIsActive, + isVisible: crdListRoute.isEnabled, + orderNumber: 10, + }; + + const definitionGroupItems = definitionGroupSidebarItems.get(); + + const childrenAndGrandChildren = [ + definitionsItem, + ...definitionGroupItems, + ]; + + const parentItem: SidebarItemRegistration = { + id: "custom-resources", + parentId: null, + title: "Custom Resources", + getIcon: () => , + onClick: noop, + isVisible: computed(() => some(item => item.isVisible.get(), childrenAndGrandChildren)), + orderNumber: 110, + }; + + return [parentItem, definitionsItem, ...definitionGroupItems]; + }); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default customResourceSidebarItemsInjectable; diff --git a/src/renderer/components/+custom-resources/custom-resources-route-component.injectable.ts b/src/renderer/components/+custom-resources/custom-resources-route-component.injectable.ts new file mode 100644 index 0000000000..1df50fe5df --- /dev/null +++ b/src/renderer/components/+custom-resources/custom-resources-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { CrdResources } from "./crd-resources"; +import customResourcesRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const customResourcesRouteComponentInjectable = getInjectable({ + id: "custom-resources-route-component", + + instantiate: (di) => ({ + route: di.inject(customResourcesRouteInjectable), + Component: CrdResources, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default customResourcesRouteComponentInjectable; diff --git a/src/renderer/components/+custom-resources/custom-resources-route-parameters.injectable.ts b/src/renderer/components/+custom-resources/custom-resources-route-parameters.injectable.ts new file mode 100644 index 0000000000..2debbe85bd --- /dev/null +++ b/src/renderer/components/+custom-resources/custom-resources-route-parameters.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import routePathParametersInjectable from "../../routes/route-path-parameters.injectable"; +import customResourcesRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable"; + +const customResourcesRouteParametersInjectable = getInjectable({ + id: "custom-resources-route-parameters", + + instantiate: (di) => { + const route = di.inject(customResourcesRouteInjectable); + const pathParameters = di.inject(routePathParametersInjectable, route); + + return { + group: computed(() => pathParameters.get().group), + name: computed(() => pathParameters.get().name), + }; + }, +}); + +export default customResourcesRouteParametersInjectable; diff --git a/src/renderer/components/+custom-resources/custom-resources.injectable.ts b/src/renderer/components/+custom-resources/custom-resources.injectable.ts index c35a278afa..e1346dcf2b 100644 --- a/src/renderer/components/+custom-resources/custom-resources.injectable.ts +++ b/src/renderer/components/+custom-resources/custom-resources.injectable.ts @@ -6,11 +6,23 @@ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; import { crdStore } from "./crd.store"; +import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; +import currentlyInClusterFrameInjectable from "../../routes/currently-in-cluster-frame.injectable"; const customResourceDefinitionsInjectable = getInjectable({ id: "custom-resource-definitions", - instantiate: () => computed(() => [...crdStore.items]), + instantiate: (di) => { + const currentlyInClusterFrame = di.inject(currentlyInClusterFrameInjectable); + + if (currentlyInClusterFrame) { + const subscribeStores = di.inject(subscribeStoresInjectable); + + subscribeStores([crdStore]); + } + + return computed(() => [...crdStore.items]); + }, }); export default customResourceDefinitionsInjectable; diff --git a/src/renderer/components/+custom-resources/grouped-custom-resources.injectable.ts b/src/renderer/components/+custom-resources/grouped-custom-resources.injectable.ts deleted file mode 100644 index 0c06b6061b..0000000000 --- a/src/renderer/components/+custom-resources/grouped-custom-resources.injectable.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed, IComputedValue } from "mobx"; -import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints"; -import { getOrInsert } from "../../utils"; -import customResourceDefinitionsInjectable from "./custom-resources.injectable"; - -interface Dependencies { - definitions: IComputedValue; -} - -function getGroupedCustomResourceDefinitions({ definitions }: Dependencies) { - return computed(() => { - const groups = new Map(); - - for (const crd of definitions.get()) { - getOrInsert(groups, crd.getGroup(), []).push(crd); - } - - return groups; - }); -} - -const groupedCustomResourceDefinitionsInjectable = getInjectable({ - id: "grouped-custom-resource-definitions", - - instantiate: (di) => getGroupedCustomResourceDefinitions({ - definitions: di.inject(customResourceDefinitionsInjectable), - }), -}); - -export default groupedCustomResourceDefinitionsInjectable; diff --git a/src/renderer/components/+custom-resources/route-tabs.injectable.ts b/src/renderer/components/+custom-resources/route-tabs.injectable.ts deleted file mode 100644 index 87fd4b25d3..0000000000 --- a/src/renderer/components/+custom-resources/route-tabs.injectable.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed, IComputedValue } from "mobx"; -import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints"; -import { crdURL, crdDefinitionsRoute } from "../../../common/routes"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import groupedCustomResourceDefinitionsInjectable from "./grouped-custom-resources.injectable"; - -export interface CustomResourceTabLayoutRoute extends Omit { - id: string; -} - -export interface CustomResourceGroupTabLayoutRoute extends CustomResourceTabLayoutRoute { - subRoutes?: CustomResourceTabLayoutRoute[]; -} - -interface Dependencies { - customResourcesDefinitions: IComputedValue>; -} - -function getRouteTabs({ customResourcesDefinitions }: Dependencies) { - return computed(() => { - const tabs: CustomResourceGroupTabLayoutRoute[] = [ - { - id: "definitions", - title: "Definitions", - url: crdURL(), - routePath: String(crdDefinitionsRoute.path), - exact: true, - }, - ]; - - for (const [group, definitions] of customResourcesDefinitions.get()) { - tabs.push({ - id: `crd-group:${group}`, - title: group, - routePath: crdURL({ query: { groups: group }}), - subRoutes: definitions.map(crd => ({ - id: `crd-resource:${crd.getResourceApiBase()}`, - title: crd.getResourceKind(), - routePath: crd.getResourceUrl(), - })), - }); - } - - return tabs; - }); -} - -const customResourcesRouteTabsInjectable = getInjectable({ - id: "custom-resources-route-tabs", - - instantiate: (di) => getRouteTabs({ - customResourcesDefinitions: di.inject(groupedCustomResourceDefinitionsInjectable), - }), -}); - -export default customResourcesRouteTabsInjectable; diff --git a/src/renderer/components/+custom-resources/route.tsx b/src/renderer/components/+custom-resources/route.tsx deleted file mode 100644 index 805d6216d6..0000000000 --- a/src/renderer/components/+custom-resources/route.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import React from "react"; -import { Redirect, Route, Switch } from "react-router"; -import { TabLayout } from "../layout/tab-layout"; -import { crdDefinitionsRoute, crdResourcesRoute, crdURL } from "../../../common/routes"; -import { CustomResourceDefinitions } from "./crd-list"; -import { CustomResourceDefinitionResources } from "./crd-resources"; - -export const CustomResourcesRoute = () => ( - - - - - - - -); - diff --git a/src/renderer/components/+custom-resources/sidebar-item.tsx b/src/renderer/components/+custom-resources/sidebar-item.tsx deleted file mode 100644 index 40fb76b34a..0000000000 --- a/src/renderer/components/+custom-resources/sidebar-item.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import React, { useEffect } from "react"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import type { IComputedValue } from "mobx"; -import { observer } from "mobx-react"; -import customResourcesRouteTabsInjectable, { type CustomResourceGroupTabLayoutRoute } from "./route-tabs.injectable"; -import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; -import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; -import { crdURL, crdRoute } from "../../../common/routes"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; -import subscribeStoresInjectable from "../../kube-watch-api/subscribe-stores.injectable"; -import type { SubscribeStores } from "../../kube-watch-api/kube-watch-api"; -import { crdStore } from "./crd.store"; -import { Spinner } from "../spinner"; - -export interface CustomResourcesSidebarItemProps {} - -interface Dependencies { - routes: IComputedValue; - isAllowedResource: IsAllowedResource; - subscribeStores: SubscribeStores; -} - -const NonInjectedCustomResourcesSidebarItem = observer(({ routes, isAllowedResource, subscribeStores }: Dependencies & CustomResourcesSidebarItemProps) => { - useEffect(() => subscribeStores([ - crdStore, - ]), []); - - return ( - } - > - {routes.get().map((route) => ( - - {route.subRoutes?.map((subRoute) => ( - - ))} - - ))} - {crdStore.isLoading && ( -
- -
- )} -
- ); -}); - -export const CustomResourcesSidebarItem = withInjectables(NonInjectedCustomResourcesSidebarItem, { - getProps: (di, props) => ({ - routes: di.inject(customResourcesRouteTabsInjectable), - isAllowedResource: di.inject(isAllowedResourceInjectable), - subscribeStores: di.inject(subscribeStoresInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+custom-resources/sidebar-items-for-definition-groups.injectable.ts b/src/renderer/components/+custom-resources/sidebar-items-for-definition-groups.injectable.ts new file mode 100644 index 0000000000..f05e5df43a --- /dev/null +++ b/src/renderer/components/+custom-resources/sidebar-items-for-definition-groups.injectable.ts @@ -0,0 +1,81 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import crdListRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/crd-list/crd-list-route.injectable"; +import customResourceDefinitionsInjectable from "./custom-resources.injectable"; +import { groupBy, matches, noop, some, toPairs } from "lodash/fp"; +import customResourcesRouteInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable"; +import currentPathParametersInjectable from "../../routes/current-path-parameters.injectable"; +import type { SidebarItemRegistration } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToCustomResourcesInjectable from "../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable"; + +const sidebarItemsForDefinitionGroupsInjectable = getInjectable({ + id: "sidebar-items-for-definition-groups", + + instantiate: (di) => { + const customResourceDefinitions = di.inject( + customResourceDefinitionsInjectable, + ); + + const crdRoute = di.inject(customResourcesRouteInjectable); + const crdRouteIsActive = di.inject(routeIsActiveInjectable, crdRoute); + const crdListRoute = di.inject(crdListRouteInjectable); + const pathParameters = di.inject(currentPathParametersInjectable); + const navigateToCustomResources = di.inject(navigateToCustomResourcesInjectable); + + return computed((): SidebarItemRegistration[] => { + const definitions = customResourceDefinitions.get(); + + const groupedCrds = toPairs( + groupBy((crd) => crd.getGroup(), definitions), + ); + + return groupedCrds.flatMap(([group, definitions]) => { + const childItems = definitions.map((crd) => { + const title = crd.getResourceKind(); + + const crdPathParameters = { + group: crd.getGroup(), + name: crd.getPluralName(), + }; + + return { + id: `custom-resource-definition-group-${group}-crd-${crd.getId()}`, + parentId: `custom-resource-definition-group-${group}`, + title, + + onClick: () => navigateToCustomResources(crdPathParameters), + + isActive: computed( + () => + crdRouteIsActive.get() && + matches(crdPathParameters, pathParameters.get()), + ), + + isVisible: crdListRoute.isEnabled, + orderNumber: 10, + }; + }); + + return [ + { + id: `custom-resource-definition-group-${group}`, + parentId: "custom-resources", + title: group, + onClick: noop, + isVisible: computed(() => some(item => item.isVisible.get(), childItems)), + orderNumber: 10, + }, + + ...childItems, + ]; + }); + }); + }, +}); + +export default sidebarItemsForDefinitionGroupsInjectable; diff --git a/src/renderer/components/+entity-settings/entity-settings-route-component.injectable.ts b/src/renderer/components/+entity-settings/entity-settings-route-component.injectable.ts new file mode 100644 index 0000000000..5e3a2da43f --- /dev/null +++ b/src/renderer/components/+entity-settings/entity-settings-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { EntitySettings } from "./entity-settings"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import entitySettingsRouteInjectable from "../../../common/front-end-routing/routes/entity-settings/entity-settings-route.injectable"; + +const entitySettingsRouteComponentInjectable = getInjectable({ + id: "entity-settings-route-component", + + instantiate: (di) => ({ + route: di.inject(entitySettingsRouteInjectable), + Component: EntitySettings, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default entitySettingsRouteComponentInjectable; diff --git a/src/renderer/components/+entity-settings/entity-settings-route-parameters.injectable.ts b/src/renderer/components/+entity-settings/entity-settings-route-parameters.injectable.ts new file mode 100644 index 0000000000..8a1d5600b8 --- /dev/null +++ b/src/renderer/components/+entity-settings/entity-settings-route-parameters.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import routePathParametersInjectable from "../../routes/route-path-parameters.injectable"; +import entitySettingsRouteInjectable from "../../../common/front-end-routing/routes/entity-settings/entity-settings-route.injectable"; + +const entitySettingsRouteParametersInjectable = getInjectable({ + id: "entity-settings-route-parameters", + + instantiate: (di) => { + const route = di.inject(entitySettingsRouteInjectable); + const pathParameters = di.inject(routePathParametersInjectable, route); + + return { + entityId: computed(() => pathParameters.get().entityId), + }; + }, +}); + +export default entitySettingsRouteParametersInjectable; diff --git a/src/renderer/components/+entity-settings/entity-settings.tsx b/src/renderer/components/+entity-settings/entity-settings.tsx index 9b9df16acf..755aa03b80 100644 --- a/src/renderer/components/+entity-settings/entity-settings.tsx +++ b/src/renderer/components/+entity-settings/entity-settings.tsx @@ -6,28 +6,29 @@ import styles from "./entity-settings.module.scss"; import React from "react"; -import { observable, makeObservable } from "mobx"; -import type { RouteComponentProps } from "react-router"; +import { observable, makeObservable, IComputedValue, computed } from "mobx"; import { observer } from "mobx-react"; import { navigation } from "../../navigation"; import { Tabs, Tab } from "../tabs"; import type { CatalogEntity } from "../../api/catalog-entity"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { EntitySettingRegistry } from "../../../extensions/registries"; -import type { EntitySettingsRouteParams } from "../../../common/routes"; import { groupBy } from "lodash"; import { SettingLayout } from "../layout/setting-layout"; import logger from "../../../common/logger"; import { Avatar } from "../avatar"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import entitySettingsRouteParametersInjectable from "./entity-settings-route-parameters.injectable"; -export interface EntitySettingsProps extends RouteComponentProps { +interface Dependencies { + entityId: IComputedValue; } @observer -export class EntitySettings extends React.Component { +class NonInjectedEntitySettings extends React.Component { @observable activeTab: string; - constructor(props: EntitySettingsProps) { + constructor(props: Dependencies) { super(props); makeObservable(this); @@ -43,8 +44,9 @@ export class EntitySettings extends React.Component { } } + @computed get entityId() { - return this.props.match.params.entityId; + return this.props.entityId.get(); } get entity(): CatalogEntity { @@ -54,7 +56,11 @@ export class EntitySettings extends React.Component { get menuItems() { if (!this.entity) return []; - return EntitySettingRegistry.getInstance().getItemsForKind(this.entity.kind, this.entity.apiVersion, this.entity.metadata.source); + return EntitySettingRegistry.getInstance().getItemsForKind( + this.entity.kind, + this.entity.apiVersion, + this.entity.metadata.source, + ); } get activeSetting() { @@ -68,7 +74,9 @@ export class EntitySettings extends React.Component { }; renderNavigation() { - const groups = Object.entries(groupBy(this.menuItems, (item) => item.group || "Extensions")); + const groups = Object.entries( + groupBy(this.menuItems, (item) => item.group || "Extensions"), + ); groups.sort((a, b) => { if (a[0] === "Settings") return -1; @@ -82,21 +90,26 @@ export class EntitySettings extends React.Component {
-
- {this.entity.getName()} -
+
{this.entity.getName()}
- - { groups.map((group, groupIndex) => ( + + {groups.map((group, groupIndex) => ( -
+
{group[0]}
- { group[1].map((setting, index) => ( + {group[1].map((setting, index) => ( { const { activeSetting } = this; - return ( - - { - activeSetting && ( + + {activeSetting && ( +
+

+ {activeSetting.title} +

-

{activeSetting.title}

-
- -
+
- ) - } +
+ )}
); } } + +export const EntitySettings = withInjectables( + NonInjectedEntitySettings, + + { + getProps: (di) => { + const routeParameters = di.inject(entitySettingsRouteParametersInjectable); + + return { + entityId: routeParameters.entityId, + }; + }, + }, +); diff --git a/src/renderer/components/+events/events-route-component.injectable.ts b/src/renderer/components/+events/events-route-component.injectable.ts new file mode 100644 index 0000000000..b9954b22c0 --- /dev/null +++ b/src/renderer/components/+events/events-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Events } from "./events"; +import eventsRouteInjectable from "../../../common/front-end-routing/routes/cluster/events/events-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const eventsRouteComponentInjectable = getInjectable({ + id: "events-route-component", + + instantiate: (di) => ({ + route: di.inject(eventsRouteInjectable), + Component: Events, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default eventsRouteComponentInjectable; diff --git a/src/renderer/components/+events/events-sidebar-items.injectable.tsx b/src/renderer/components/+events/events-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..90fa787a84 --- /dev/null +++ b/src/renderer/components/+events/events-sidebar-items.injectable.tsx @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import React from "react"; +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { Icon } from "../icon"; + +import eventsRouteInjectable from "../../../common/front-end-routing/routes/cluster/events/events-route.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToEventsInjectable from "../../../common/front-end-routing/routes/cluster/events/navigate-to-events.injectable"; + +const eventsSidebarItemsInjectable = getInjectable({ + id: "events-sidebar-items", + + instantiate: (di) => { + const route = di.inject(eventsRouteInjectable); + const navigateToEvents = di.inject(navigateToEventsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed((): SidebarItemRegistration[] => [ + { + id: "events", + parentId: null, + getIcon: () => , + title: "Events", + onClick: navigateToEvents, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 80, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default eventsSidebarItemsInjectable; diff --git a/src/renderer/components/+events/events.tsx b/src/renderer/components/+events/events.tsx index bdf83b750d..fd922c647f 100644 --- a/src/renderer/components/+events/events.tsx +++ b/src/renderer/components/+events/events.tsx @@ -9,7 +9,7 @@ import React, { Fragment } from "react"; import { computed, observable, makeObservable } from "mobx"; import { observer } from "mobx-react"; import { orderBy } from "lodash"; -import { TabLayout } from "../layout/tab-layout"; +import { TabLayout } from "../layout/tab-layout-2"; import { EventStore, eventStore } from "./event.store"; import { KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object-list-layout"; import type { KubeEvent } from "../../../common/k8s-api/endpoints/events.api"; @@ -19,9 +19,10 @@ import { Tooltip } from "../tooltip"; import { Link } from "react-router-dom"; import { cssNames, IClassName, stopPropagation } from "../../utils"; import { Icon } from "../icon"; -import { eventsURL } from "../../../common/routes"; import { getDetailsUrl } from "../kube-detail-params"; import { apiManager } from "../../../common/k8s-api/api-manager"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import navigateToEventsInjectable from "../../../common/front-end-routing/routes/cluster/events/navigate-to-events.injectable"; import { KubeObjectAge } from "../kube-object/age"; import { ReactiveDuration } from "../duration/reactive-duration"; @@ -46,8 +47,12 @@ const defaultProps: Partial = { compactLimit: 10, }; +interface Dependencies { + navigateToEvents: () => void; +} + @observer -export class Events extends React.Component { +class NonInjectedEvents extends React.Component { static defaultProps = defaultProps as object; @observable sorting: TableSortParams = { @@ -64,7 +69,7 @@ export class Events extends React.Component { [columnId.lastSeen]: event => -new Date(event.lastTimestamp).getTime(), }; - constructor(props: EventsProps) { + constructor(props: Dependencies & EventsProps) { super(props); makeObservable(this); } @@ -105,7 +110,7 @@ export class Events extends React.Component { return { title, - info: ({visibleItems.length} of {items.length}), + info: ({visibleItems.length} of {items.length}), }; } @@ -204,3 +209,14 @@ export class Events extends React.Component { ); } } + +export const Events = withInjectables( + NonInjectedEvents, + + { + getProps: (di, props) => ({ + navigateToEvents: di.inject(navigateToEventsInjectable), + ...props, + }), + }, +); diff --git a/src/renderer/components/+events/sidebar-item.tsx b/src/renderer/components/+events/sidebar-item.tsx deleted file mode 100644 index 50ffddcefc..0000000000 --- a/src/renderer/components/+events/sidebar-item.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { withInjectables } from "@ogre-tools/injectable-react"; -import { observer } from "mobx-react"; -import React from "react"; -import { eventRoute, eventsURL } from "../../../common/routes"; -import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; -import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; - -export interface EventsSidebarItemProps {} - -interface Dependencies { - isAllowedResource: IsAllowedResource; -} - -const NonInjectedEventsSidebarItem = observer(({ isAllowedResource }: Dependencies & EventsSidebarItemProps) => ( - } - /> -)); - -export const EventsSidebarItem = withInjectables(NonInjectedEventsSidebarItem, { - getProps: (di, props) => ({ - isAllowedResource: di.inject(isAllowedResourceInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+extensions/extensions-route-component.injectable.ts b/src/renderer/components/+extensions/extensions-route-component.injectable.ts new file mode 100644 index 0000000000..aede3b340a --- /dev/null +++ b/src/renderer/components/+extensions/extensions-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Extensions } from "./extensions"; +import extensionsRouteInjectable from "../../../common/front-end-routing/routes/extensions/extensions-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const extensionsRouteComponentInjectable = getInjectable({ + id: "extensions-route-component", + + instantiate: (di) => ({ + route: di.inject(extensionsRouteInjectable), + Component: Extensions, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default extensionsRouteComponentInjectable; diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index eb61cd2265..a078528d57 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -71,7 +71,7 @@ class NonInjectedExtensions extends React.Component { return ( - +

Extensions

diff --git a/src/renderer/components/+helm-charts/helm-charts-route-component.injectable.ts b/src/renderer/components/+helm-charts/helm-charts-route-component.injectable.ts new file mode 100644 index 0000000000..6cee1b1d42 --- /dev/null +++ b/src/renderer/components/+helm-charts/helm-charts-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { HelmCharts } from "./helm-charts"; +import helmChartsRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/helm-charts-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const helmChartsRouteComponentInjectable = getInjectable({ + id: "helm-charts-route-component", + + instantiate: (di) => ({ + route: di.inject(helmChartsRouteInjectable), + Component: HelmCharts, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default helmChartsRouteComponentInjectable; diff --git a/src/renderer/components/+helm-charts/helm-charts-route-parameters.injectable.ts b/src/renderer/components/+helm-charts/helm-charts-route-parameters.injectable.ts new file mode 100644 index 0000000000..cc800ff7f3 --- /dev/null +++ b/src/renderer/components/+helm-charts/helm-charts-route-parameters.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import routePathParametersInjectable from "../../routes/route-path-parameters.injectable"; +import helmChartsRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/helm-charts-route.injectable"; + +const helmChartsRouteParametersInjectable = getInjectable({ + id: "helm-charts-route-parameters", + + instantiate: (di) => { + const route = di.inject(helmChartsRouteInjectable); + const pathParameters = di.inject(routePathParametersInjectable, route); + + return { + chartName: computed(() => pathParameters.get().chartName), + repo: computed(() => pathParameters.get().repo), + }; + }, +}); + +export default helmChartsRouteParametersInjectable; diff --git a/src/renderer/components/+helm-charts/helm-charts-sidebar-items.injectable.tsx b/src/renderer/components/+helm-charts/helm-charts-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..a03b255797 --- /dev/null +++ b/src/renderer/components/+helm-charts/helm-charts-sidebar-items.injectable.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import helmChartsRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/helm-charts-route.injectable"; +import { helmSidebarItemId } from "../+helm/helm-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable"; + +const helmChartsSidebarItemsInjectable = getInjectable({ + id: "helm-charts-sidebar-items", + + instantiate: (di) => { + const route = di.inject(helmChartsRouteInjectable); + const navigateToHelmCharts = di.inject(navigateToHelmChartsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "charts", + parentId: helmSidebarItemId, + title: "Charts", + onClick: navigateToHelmCharts, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 10, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default helmChartsSidebarItemsInjectable; diff --git a/src/renderer/components/+helm-charts/helm-charts.tsx b/src/renderer/components/+helm-charts/helm-charts.tsx index 9cf8c81482..50ca0ccb88 100644 --- a/src/renderer/components/+helm-charts/helm-charts.tsx +++ b/src/renderer/components/+helm-charts/helm-charts.tsx @@ -6,15 +6,17 @@ import "./helm-charts.scss"; import React, { Component } from "react"; -import type { RouteComponentProps } from "react-router"; import { observer } from "mobx-react"; import { helmChartStore } from "./helm-chart.store"; import type { HelmChart } from "../../../common/k8s-api/endpoints/helm-charts.api"; import { HelmChartDetails } from "./helm-chart-details"; -import { navigation } from "../../navigation"; import { ItemListLayout } from "../item-object-list/list-layout"; -import { helmChartsURL } from "../../../common/routes"; -import type { HelmChartsRouteParams } from "../../../common/routes"; +import type { IComputedValue } from "mobx"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; +import helmChartsRouteParametersInjectable from "./helm-charts-route-parameters.injectable"; +import type { NavigateToHelmCharts } from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable"; +import navigateToHelmChartsInjectable from "../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable"; enum columnId { name = "name", @@ -24,19 +26,25 @@ enum columnId { repo = "repo", } -export interface HelmChartsProps extends RouteComponentProps { +interface Dependencies { + routeParameters: { + chartName: IComputedValue; + repo: IComputedValue; + }; + + navigateToHelmCharts: NavigateToHelmCharts; } @observer -export class HelmCharts extends Component { +class NonInjectedHelmCharts extends Component { componentDidMount() { helmChartStore.loadAll(); } get selectedChart() { - const { match: { params: { chartName, repo }}} = this.props; + const { chartName, repo } = this.props.routeParameters; - return helmChartStore.getByName(chartName, repo); + return helmChartStore.getByName(chartName.get(), repo.get()); } onDetails = (chart: HelmChart) => { @@ -49,15 +57,13 @@ export class HelmCharts extends Component { showDetails = (chart: HelmChart) => { if (!chart) { - navigation.push(helmChartsURL()); + this.props.navigateToHelmCharts(); } else { - navigation.push(helmChartsURL({ - params: { - chartName: chart.getName(), - repo: chart.getRepository(), - }, - })); + this.props.navigateToHelmCharts({ + chartName: chart.getName(), + repo: chart.getRepository(), + }); } }; @@ -67,7 +73,7 @@ export class HelmCharts extends Component { render() { return ( - <> + { hideDetails={this.hideDetails} /> )} - + ); } } + +export const HelmCharts = withInjectables( + NonInjectedHelmCharts, + + { + getProps: (di) => ({ + routeParameters: di.inject(helmChartsRouteParametersInjectable), + navigateToHelmCharts: di.inject(navigateToHelmChartsInjectable), + }), + }, +); + diff --git a/src/renderer/components/+helm-releases/helm-releases-route-component.injectable.ts b/src/renderer/components/+helm-releases/helm-releases-route-component.injectable.ts new file mode 100644 index 0000000000..8e08c2c093 --- /dev/null +++ b/src/renderer/components/+helm-releases/helm-releases-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { HelmReleases } from "./releases"; +import helmReleasesRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/releases/helm-releases-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const helmReleasesRouteComponentInjectable = getInjectable({ + id: "helm-releases-route-component", + + instantiate: (di) => ({ + route: di.inject(helmReleasesRouteInjectable), + Component: HelmReleases, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default helmReleasesRouteComponentInjectable; diff --git a/src/renderer/components/+helm-releases/helm-releases-route-parameters.injectable.ts b/src/renderer/components/+helm-releases/helm-releases-route-parameters.injectable.ts new file mode 100644 index 0000000000..e8f637621f --- /dev/null +++ b/src/renderer/components/+helm-releases/helm-releases-route-parameters.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import helmReleasesRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/releases/helm-releases-route.injectable"; +import routePathParametersInjectable from "../../routes/route-path-parameters.injectable"; + +const helmReleasesRouteParametersInjectable = getInjectable({ + id: "helm-releases-route-parameters", + + instantiate: (di) => { + const route = di.inject(helmReleasesRouteInjectable); + const pathParameters = di.inject(routePathParametersInjectable, route); + + return { + namespace: computed(() => pathParameters.get().namespace), + name: computed(() => pathParameters.get().name), + }; + }, +}); + +export default helmReleasesRouteParametersInjectable; diff --git a/src/renderer/components/+helm-releases/helm-releases-sidebar-items.injectable.tsx b/src/renderer/components/+helm-releases/helm-releases-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..99cbbba0e2 --- /dev/null +++ b/src/renderer/components/+helm-releases/helm-releases-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import helmReleasesRouteInjectable from "../../../common/front-end-routing/routes/cluster/helm/releases/helm-releases-route.injectable"; +import { helmSidebarItemId } from "../+helm/helm-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToHelmReleasesInjectable from "../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable"; + +const helmReleasesSidebarItemsInjectable = getInjectable({ + id: "helm-releases-sidebar-items", + + instantiate: (di) => { + const route = di.inject(helmReleasesRouteInjectable); + const navigateToHelmReleases = di.inject(navigateToHelmReleasesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "releases", + parentId: helmSidebarItemId, + title: "Releases", + onClick: navigateToHelmReleases, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 20, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default helmReleasesSidebarItemsInjectable; diff --git a/src/renderer/components/+helm-releases/release-details/release-route-parameters.injectable.ts b/src/renderer/components/+helm-releases/release-details/release-route-parameters.injectable.ts deleted file mode 100644 index 8ed009932c..0000000000 --- a/src/renderer/components/+helm-releases/release-details/release-route-parameters.injectable.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import { matchPath } from "react-router"; -import observableHistoryInjectable from "../../../navigation/observable-history.injectable"; -import { releaseRoute, ReleaseRouteParams } from "../../../../common/routes"; - -const releaseRouteParametersInjectable = getInjectable({ - id: "release-route-parameters", - - instantiate: (di) => { - const observableHistory = di.inject(observableHistoryInjectable); - - return computed(() => { - const releasePathParameters = matchPath(observableHistory.location.pathname, { - path: releaseRoute.path, - }); - - if (!releasePathParameters) { - return {}; - } - - return releasePathParameters.params; - }); - }, -}); - -export default releaseRouteParametersInjectable; diff --git a/src/renderer/components/+helm-releases/release-details/release.injectable.ts b/src/renderer/components/+helm-releases/release-details/release.injectable.ts index 5506388af2..981ffce517 100644 --- a/src/renderer/components/+helm-releases/release-details/release.injectable.ts +++ b/src/renderer/components/+helm-releases/release-details/release.injectable.ts @@ -5,18 +5,19 @@ import { getInjectable } from "@ogre-tools/injectable"; import { matches } from "lodash/fp"; import releasesInjectable from "../releases.injectable"; -import releaseRouteParametersInjectable from "./release-route-parameters.injectable"; import { computed } from "mobx"; +import helmReleasesRouteParametersInjectable from "../helm-releases-route-parameters.injectable"; const releaseInjectable = getInjectable({ id: "release", instantiate: (di) => { const releases = di.inject(releasesInjectable); - const releaseRouteParameters = di.inject(releaseRouteParametersInjectable); + const routeParameters = di.inject(helmReleasesRouteParametersInjectable); return computed(() => { - const { name, namespace } = releaseRouteParameters.get(); + const name = routeParameters.name.get(); + const namespace = routeParameters.namespace.get(); if (!name || !namespace) { return null; diff --git a/src/renderer/components/+helm-releases/releases.tsx b/src/renderer/components/+helm-releases/releases.tsx index 2e5d9b862a..0026cda062 100644 --- a/src/renderer/components/+helm-releases/releases.tsx +++ b/src/renderer/components/+helm-releases/releases.tsx @@ -7,11 +7,7 @@ import "../item-object-list/item-list-layout.scss"; import "./releases.scss"; import React, { Component } from "react"; -import type { RouteComponentProps } from "react-router"; import type { HelmRelease } from "../../../common/k8s-api/endpoints/helm-releases.api"; -import { navigation } from "../../navigation"; -import type { ReleaseRouteParams } from "../../../common/routes"; -import { releaseURL } from "../../../common/routes"; import { withInjectables } from "@ogre-tools/injectable-react"; import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable"; import { ItemListLayout } from "../item-object-list"; @@ -25,6 +21,10 @@ import removableReleasesInjectable from "./removable-releases.injectable"; import type { RemovableHelmRelease } from "./removable-releases"; import type { IComputedValue } from "mobx"; import releasesInjectable from "./releases.injectable"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; +import helmReleasesRouteParametersInjectable from "./helm-releases-route-parameters.injectable"; +import type { NavigateToHelmReleases } from "../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable"; +import navigateToHelmReleasesInjectable from "../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable"; enum columnId { name = "name", @@ -37,18 +37,18 @@ enum columnId { updated = "update", } -export interface HelmReleasesProps extends RouteComponentProps { -} - interface Dependencies { releases: IComputedValue; releasesArePending: IComputedValue; selectNamespace: (namespace: string) => void; + namespace: IComputedValue; + navigateToHelmReleases: NavigateToHelmReleases; } -class NonInjectedHelmReleases extends Component { +class NonInjectedHelmReleases extends Component { + // TODO: This side-effect in mount must go. componentDidMount() { - const { match: { params: { namespace }}} = this.props; + const namespace = this.props.namespace.get(); if (namespace) { this.props.selectNamespace(namespace); @@ -60,16 +60,23 @@ class NonInjectedHelmReleases extends Component { - navigation.push(releaseURL({ - params: { - name: item.getName(), - namespace: item.getNs(), - }, - })); + this.props.navigateToHelmReleases({ + name: item.getName(), + namespace: item.getNs(), + }); + + // navigation.push(releaseURL({ + // params: { + // name: item.getName(), + // namespace: item.getNs(), + // }, + // })); }; hideDetails = () => { - navigation.push(releaseURL()); + this.props.navigateToHelmReleases(); + + // navigation.push(releaseURL()); }; renderRemoveDialogMessage(selectedItems: HelmRelease[]) { @@ -144,7 +151,7 @@ class NonInjectedHelmReleases extends Component; return ( - <> + legacyReleaseStore.items} @@ -218,20 +225,25 @@ class NonInjectedHelmReleases extends Component - + ); } } -export const HelmReleases = withInjectables( +export const HelmReleases = withInjectables( NonInjectedHelmReleases, { - getProps: (di, props) => ({ - releases: di.inject(removableReleasesInjectable), - releasesArePending: di.inject(releasesInjectable).pending, - selectNamespace: di.inject(namespaceStoreInjectable).selectNamespaces, - ...props, - }), + getProps: (di) => { + const routeParameters = di.inject(helmReleasesRouteParametersInjectable); + + return { + releases: di.inject(removableReleasesInjectable), + releasesArePending: di.inject(releasesInjectable).pending, + selectNamespace: di.inject(namespaceStoreInjectable).selectNamespaces, + navigateToHelmReleases: di.inject(navigateToHelmReleasesInjectable), + namespace: routeParameters.namespace, + }; + }, }, ); diff --git a/src/renderer/components/+helm/helm-sidebar-items.injectable.tsx b/src/renderer/components/+helm/helm-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..76796bc440 --- /dev/null +++ b/src/renderer/components/+helm/helm-sidebar-items.injectable.tsx @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import React from "react"; +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { Icon } from "../icon"; +import { noop } from "lodash/fp"; + +export const helmSidebarItemId = "helm"; + +const helmSidebarItemsInjectable = getInjectable({ + id: "helm-sidebar-items", + + instantiate: () => + computed((): SidebarItemRegistration[] => [ + { + id: helmSidebarItemId, + parentId: null, + getIcon: () => , + title: "Helm", + onClick: noop, + orderNumber: 90, + }, + ]), + + injectionToken: sidebarItemsInjectionToken, +}); + +export default helmSidebarItemsInjectable; diff --git a/src/renderer/components/+helm/route-tabs.injectable.ts b/src/renderer/components/+helm/route-tabs.injectable.ts deleted file mode 100644 index 6b599a368a..0000000000 --- a/src/renderer/components/+helm/route-tabs.injectable.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import { HelmCharts } from "../+helm-charts"; -import { HelmReleases } from "../+helm-releases"; -import { helmChartsURL, helmChartsRoute, releaseURL, releaseRoute } from "../../../common/routes"; - -function getRouteTabs() { - return computed((): TabLayoutRoute[] => [ - { - title: "Charts", - component: HelmCharts, - url: helmChartsURL(), - routePath: helmChartsRoute.path.toString(), - }, - { - title: "Releases", - component: HelmReleases, - url: releaseURL(), - routePath: releaseRoute.path.toString(), - }, - ]); -} - -const helmRoutesInjectable = getInjectable({ - id: "helm-routes", - instantiate: () => getRouteTabs(), -}); - -export default helmRoutesInjectable; diff --git a/src/renderer/components/+helm/route.tsx b/src/renderer/components/+helm/route.tsx deleted file mode 100644 index da2983a8ba..0000000000 --- a/src/renderer/components/+helm/route.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import React from "react"; -import { observer } from "mobx-react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import type { IComputedValue } from "mobx"; -import helmRoutesInjectable from "./route-tabs.injectable"; - -export interface HelmRouteProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedHelmRoute = observer(({ routes }: Dependencies & HelmRouteProps) => ( - -)); - -export const HelmRoute = withInjectables(NonInjectedHelmRoute, { - getProps: (di, props) => ({ - routes: di.inject(helmRoutesInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+helm/sidebar-item.tsx b/src/renderer/components/+helm/sidebar-item.tsx deleted file mode 100644 index b01e19d40f..0000000000 --- a/src/renderer/components/+helm/sidebar-item.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import React from "react"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import type { IComputedValue } from "mobx"; -import { observer } from "mobx-react"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import { renderTabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; -import { helmRoute, helmURL } from "../../../common/routes"; -import networkRouteTabsInjectable from "./route-tabs.injectable"; - -export interface HelmSidebarItemProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedHelmSidebarItem = observer(({ routes }: Dependencies & HelmSidebarItemProps) => { - const tabRoutes = routes.get(); - - return ( - } - > - {renderTabRoutesSidebarItems(tabRoutes)} - - ); -}); - -export const HelmSidebarItem = withInjectables(NonInjectedHelmSidebarItem, { - getProps: (di, props) => ({ - routes: di.inject(networkRouteTabsInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+namespaces/namespaces-route-component.injectable.ts b/src/renderer/components/+namespaces/namespaces-route-component.injectable.ts new file mode 100644 index 0000000000..3965510f1d --- /dev/null +++ b/src/renderer/components/+namespaces/namespaces-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { NamespacesRoute } from "./route"; +import namespacesRouteInjectable from "../../../common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const namespacesRouteComponentInjectable = getInjectable({ + id: "namespaces-route-component", + + instantiate: (di) => ({ + route: di.inject(namespacesRouteInjectable), + Component: NamespacesRoute, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default namespacesRouteComponentInjectable; diff --git a/src/renderer/components/+namespaces/namespaces-sidebar-items.injectable.tsx b/src/renderer/components/+namespaces/namespaces-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..296bed7336 --- /dev/null +++ b/src/renderer/components/+namespaces/namespaces-sidebar-items.injectable.tsx @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { Icon } from "../icon"; +import React from "react"; +import namespacesRouteInjectable from "../../../common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToNamespacesInjectable from "../../../common/front-end-routing/routes/cluster/namespaces/navigate-to-namespaces.injectable"; + +const namespacesSidebarItemsInjectable = getInjectable({ + id: "namespaces", + + instantiate: (di) => { + const route = di.inject(namespacesRouteInjectable); + const navigateToNamespaces = di.inject(navigateToNamespacesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed((): SidebarItemRegistration[] => [ + { + id: "namespaces", + parentId: null, + getIcon: () => , + title: "Namespaces", + onClick: navigateToNamespaces, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 70, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default namespacesSidebarItemsInjectable; diff --git a/src/renderer/components/+namespaces/route.tsx b/src/renderer/components/+namespaces/route.tsx index b1bf0601d0..00956306ef 100644 --- a/src/renderer/components/+namespaces/route.tsx +++ b/src/renderer/components/+namespaces/route.tsx @@ -8,17 +8,14 @@ import "./namespaces.scss"; import React from "react"; import { NamespaceStatus } from "../../../common/k8s-api/endpoints"; import { AddNamespaceDialog } from "./add-namespace-dialog"; -import { TabLayout } from "../layout/tab-layout"; +import { TabLayout } from "../layout/tab-layout-2"; import { Badge } from "../badge"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import type { NamespaceStore } from "./namespace-store/namespace.store"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { NamespacesRouteParams } from "../../../common/routes"; import { withInjectables } from "@ogre-tools/injectable-react"; import namespaceStoreInjectable from "./namespace-store/namespace-store.injectable"; -import addNamespaceDialogModelInjectable - from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable"; +import addNamespaceDialogModelInjectable from "./add-namespace-dialog-model/add-namespace-dialog-model.injectable"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -28,15 +25,12 @@ enum columnId { status = "status", } -export interface NamespacesRouteProps extends RouteComponentProps { -} - interface Dependencies { namespaceStore: NamespaceStore; openAddNamespaceDialog: () => void; } -export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }: Dependencies & NamespacesRouteProps) => ( +export const NonInjectedNamespacesRoute = ({ namespaceStore, openAddNamespaceDialog }: Dependencies) => ( (NonInjectedNamespacesRoute, { - getProps: (di, props) => ({ +export const NamespacesRoute = withInjectables(NonInjectedNamespacesRoute, { + getProps: (di) => ({ namespaceStore: di.inject(namespaceStoreInjectable), openAddNamespaceDialog: di.inject(addNamespaceDialogModelInjectable).open, - ...props, }), }); diff --git a/src/renderer/components/+namespaces/sidebar-item.tsx b/src/renderer/components/+namespaces/sidebar-item.tsx deleted file mode 100644 index 78bbc8a3ae..0000000000 --- a/src/renderer/components/+namespaces/sidebar-item.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { withInjectables } from "@ogre-tools/injectable-react"; -import { observer } from "mobx-react"; -import React from "react"; -import { namespacesRoute, namespacesURL } from "../../../common/routes"; -import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; -import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; - -export interface NamespacesSidebarItemProps {} - -interface Dependencies { - isAllowedResource: IsAllowedResource; -} - -const NonInjectedNamespacesSidebarItem = observer(({ isAllowedResource }: Dependencies & NamespacesSidebarItemProps) => ( - } - /> -)); - -export const NamespacesSidebarItem = withInjectables(NonInjectedNamespacesSidebarItem, { - getProps: (di, props) => ({ - isAllowedResource: di.inject(isAllowedResourceInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+network-endpoints/endpoints-route-component.injectable.ts b/src/renderer/components/+network-endpoints/endpoints-route-component.injectable.ts new file mode 100644 index 0000000000..0236e2be80 --- /dev/null +++ b/src/renderer/components/+network-endpoints/endpoints-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Endpoints } from "./endpoints"; +import endpointsRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const endpointsRouteComponentInjectable = getInjectable({ + id: "endpoints-route-component", + + instantiate: (di) => ({ + route: di.inject(endpointsRouteInjectable), + Component: Endpoints, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default endpointsRouteComponentInjectable; diff --git a/src/renderer/components/+network-endpoints/endpoints-sidebar-items.injectable.tsx b/src/renderer/components/+network-endpoints/endpoints-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..2f8c3adefb --- /dev/null +++ b/src/renderer/components/+network-endpoints/endpoints-sidebar-items.injectable.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import endpointsRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable"; +import { networkSidebarItemId } from "../+network/network-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToEndpointsInjectable from "../../../common/front-end-routing/routes/cluster/network/endpoints/navigate-to-endpoints.injectable"; + +const endpointsSidebarItemsInjectable = getInjectable({ + id: "endpoints-sidebar-items", + + instantiate: (di) => { + const route = di.inject(endpointsRouteInjectable); + const navigateToEndpoints = di.inject(navigateToEndpointsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "endpoints", + parentId: networkSidebarItemId, + title: "Endpoints", + onClick: navigateToEndpoints, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 20, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default endpointsSidebarItemsInjectable; diff --git a/src/renderer/components/+network-endpoints/endpoints.tsx b/src/renderer/components/+network-endpoints/endpoints.tsx index 0c5595be54..b1411f9681 100644 --- a/src/renderer/components/+network-endpoints/endpoints.tsx +++ b/src/renderer/components/+network-endpoints/endpoints.tsx @@ -7,11 +7,10 @@ import "./endpoints.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router-dom"; import { endpointStore } from "./endpoints.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { EndpointRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -21,48 +20,47 @@ enum columnId { age = "age", } -export interface EndpointsProps extends RouteComponentProps { -} - @observer -export class Endpoints extends React.Component { +export class Endpoints extends React.Component { render() { return ( - endpoint.getName(), - [columnId.namespace]: endpoint => endpoint.getNs(), - [columnId.age]: endpoint => -endpoint.getCreationTimestamp(), - }} - searchFilters={[ - endpoint => endpoint.getSearchFields(), - ]} - renderHeaderTitle="Endpoints" - renderTableHeader={[ - { 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: "Endpoints", className: "endpoints", id: columnId.endpoints }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={endpoint => [ - endpoint.getName(), - , - endpoint.getNs(), - endpoint.toString(), - , - ]} - tableProps={{ - customRowHeights: (item, lineHeight, paddings) => { - const lines = item.getEndpointSubsets().length || 1; + + endpoint.getName(), + [columnId.namespace]: endpoint => endpoint.getNs(), + [columnId.age]: endpoint => -endpoint.getCreationTimestamp(), + }} + searchFilters={[ + endpoint => endpoint.getSearchFields(), + ]} + renderHeaderTitle="Endpoints" + renderTableHeader={[ + { 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: "Endpoints", className: "endpoints", id: columnId.endpoints }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={endpoint => [ + endpoint.getName(), + , + endpoint.getNs(), + endpoint.toString(), + , + ]} + tableProps={{ + customRowHeights: (item, lineHeight, paddings) => { + const lines = item.getEndpointSubsets().length || 1; - return lines * lineHeight + paddings; - }, - }} - /> + return lines * lineHeight + paddings; + }, + }} + /> + ); } } diff --git a/src/renderer/components/+network-ingresses/ingresses-route-component.injectable.ts b/src/renderer/components/+network-ingresses/ingresses-route-component.injectable.ts new file mode 100644 index 0000000000..17a015ad42 --- /dev/null +++ b/src/renderer/components/+network-ingresses/ingresses-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Ingresses } from "./ingresses"; +import ingressesRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const ingressesRouteComponentInjectable = getInjectable({ + id: "ingresses-route-component", + + instantiate: (di) => ({ + route: di.inject(ingressesRouteInjectable), + Component: Ingresses, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default ingressesRouteComponentInjectable; diff --git a/src/renderer/components/+network-ingresses/ingresses-sidebar-items.injectable.tsx b/src/renderer/components/+network-ingresses/ingresses-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..80e9a7939c --- /dev/null +++ b/src/renderer/components/+network-ingresses/ingresses-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import ingressesRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import { networkSidebarItemId } from "../+network/network-sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToIngressesInjectable from "../../../common/front-end-routing/routes/cluster/network/ingresses/navigate-to-ingresses.injectable"; + +const ingressesSidebarItemsInjectable = getInjectable({ + id: "ingresses-sidebar-items", + + instantiate: (di) => { + const route = di.inject(ingressesRouteInjectable); + const navigateToIngresses = di.inject(navigateToIngressesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "ingresses", + parentId: networkSidebarItemId, + title: "Ingresses", + onClick: navigateToIngresses, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 30, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default ingressesSidebarItemsInjectable; diff --git a/src/renderer/components/+network-ingresses/ingresses.tsx b/src/renderer/components/+network-ingresses/ingresses.tsx index 4f4305c64c..a585025324 100644 --- a/src/renderer/components/+network-ingresses/ingresses.tsx +++ b/src/renderer/components/+network-ingresses/ingresses.tsx @@ -7,11 +7,10 @@ import "./ingresses.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router-dom"; import { ingressStore } from "./ingress.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { IngressRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; import { computeRouteDeclarations } from "../../../common/k8s-api/endpoints"; @@ -23,59 +22,65 @@ enum columnId { age = "age", } -export interface IngressesProps extends RouteComponentProps { -} - @observer -export class Ingresses extends React.Component { +export class Ingresses extends React.Component { render() { return ( - ingress.getName(), - [columnId.namespace]: ingress => ingress.getNs(), - [columnId.age]: ingress => -ingress.getCreationTimestamp(), - }} - searchFilters={[ - ingress => ingress.getSearchFields(), - ingress => ingress.getPorts(), - ]} - renderHeaderTitle="Ingresses" - renderTableHeader={[ - { 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: "LoadBalancers", className: "loadbalancers", id: columnId.loadBalancers }, - { title: "Rules", className: "rules", id: columnId.rules }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={ingress => [ - ingress.getName(), - , - ingress.getNs(), - ingress.getLoadBalancers().map(lb =>

{lb}

), - computeRouteDeclarations(ingress).map(decl => ( - decl.displayAsLink - ? ( - - e.stopPropagation()} - > - {decl.url} - ⇢ {decl.service} - - ) - : {decl.url} ⇢ {decl.service} - )), - , - ]} - /> + + ingress.getName(), + [columnId.namespace]: ingress => ingress.getNs(), + [columnId.age]: ingress => -ingress.getCreationTimestamp(), + }} + searchFilters={[ + ingress => ingress.getSearchFields(), + ingress => ingress.getPorts(), + ]} + renderHeaderTitle="Ingresses" + renderTableHeader={[ + { 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: "LoadBalancers", className: "loadbalancers", id: columnId.loadBalancers }, + { title: "Rules", className: "rules", id: columnId.rules }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={ingress => [ + ingress.getName(), + , + ingress.getNs(), + ingress.getLoadBalancers().map(lb =>

{lb}

), + computeRouteDeclarations(ingress).map(decl => ( + decl.displayAsLink + ? ( + + e.stopPropagation()} + > + {decl.url} + ⇢ {decl.service} + + ) + : {decl.url} ⇢ {decl.service} + )), + , + ]} + tableProps={{ + customRowHeights: (item, lineHeight, paddings) => { + const lines = item.getRoutes().length || 1; + + return lines * lineHeight + paddings; + }, + }} + /> +
); } } diff --git a/src/renderer/components/+network-policies/network-policies-route-component.injectable.ts b/src/renderer/components/+network-policies/network-policies-route-component.injectable.ts new file mode 100644 index 0000000000..94d124d94a --- /dev/null +++ b/src/renderer/components/+network-policies/network-policies-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { NetworkPolicies } from "./network-policies"; +import networkPoliciesRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const networkPoliciesRouteComponentInjectable = getInjectable({ + id: "network-policies-route-component", + + instantiate: (di) => ({ + route: di.inject(networkPoliciesRouteInjectable), + Component: NetworkPolicies, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default networkPoliciesRouteComponentInjectable; diff --git a/src/renderer/components/+network-policies/network-policies-sidebar-items.injectable.tsx b/src/renderer/components/+network-policies/network-policies-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..6c6df9506d --- /dev/null +++ b/src/renderer/components/+network-policies/network-policies-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import networkPoliciesRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable"; +import { networkSidebarItemId } from "../+network/network-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToNetworkPoliciesInjectable from "../../../common/front-end-routing/routes/cluster/network/network-policies/navigate-to-network-policies.injectable"; + +const networkPoliciesSidebarItemsInjectable = getInjectable({ + id: "network-policies-sidebar-items", + + instantiate: (di) => { + const route = di.inject(networkPoliciesRouteInjectable); + const navigateToNetworkPolicies = di.inject(navigateToNetworkPoliciesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "network-policies", + parentId: networkSidebarItemId, + title: "Network Policies", + onClick: navigateToNetworkPolicies, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 40, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default networkPoliciesSidebarItemsInjectable; diff --git a/src/renderer/components/+network-policies/network-policies.tsx b/src/renderer/components/+network-policies/network-policies.tsx index c226f090d6..9a442677d7 100644 --- a/src/renderer/components/+network-policies/network-policies.tsx +++ b/src/renderer/components/+network-policies/network-policies.tsx @@ -7,11 +7,10 @@ import "./network-policies.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router-dom"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { networkPolicyStore } from "./network-policy.store"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { NetworkPoliciesRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -21,42 +20,41 @@ enum columnId { age = "age", } -export interface NetworkPoliciesProps extends RouteComponentProps { -} - @observer -export class NetworkPolicies extends React.Component { +export class NetworkPolicies extends React.Component { render() { return ( - networkPolicy.getName(), - [columnId.namespace]: networkPolicy => networkPolicy.getNs(), - [columnId.age]: networkPolicy => -networkPolicy.getCreationTimestamp(), - }} - searchFilters={[ - networkPolicy => networkPolicy.getSearchFields(), - ]} - renderHeaderTitle="Network Policies" - renderTableHeader={[ - { 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: "Policy Types", className: "type", id: columnId.types }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={networkPolicy => [ - networkPolicy.getName(), - , - networkPolicy.getNs(), - networkPolicy.getTypes().join(", "), - , - ]} - /> + + networkPolicy.getName(), + [columnId.namespace]: networkPolicy => networkPolicy.getNs(), + [columnId.age]: networkPolicy => -networkPolicy.getCreationTimestamp(), + }} + searchFilters={[ + networkPolicy => networkPolicy.getSearchFields(), + ]} + renderHeaderTitle="Network Policies" + renderTableHeader={[ + { 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: "Policy Types", className: "type", id: columnId.types }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={networkPolicy => [ + networkPolicy.getName(), + , + networkPolicy.getNs(), + networkPolicy.getTypes().join(", "), + , + ]} + /> + ); } } diff --git a/src/renderer/components/+network-port-forwards/port-forwards-route-component.injectable.ts b/src/renderer/components/+network-port-forwards/port-forwards-route-component.injectable.ts new file mode 100644 index 0000000000..01c5b3254f --- /dev/null +++ b/src/renderer/components/+network-port-forwards/port-forwards-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { PortForwards } from "./port-forwards"; +import portForwardsRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/port-forwards/port-forwards-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const portForwardsRouteComponentInjectable = getInjectable({ + id: "port-forwards-route-component", + + instantiate: (di) => ({ + route: di.inject(portForwardsRouteInjectable), + Component: PortForwards, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default portForwardsRouteComponentInjectable; diff --git a/src/renderer/components/+network-port-forwards/port-forwards-route-parameters.injectable.ts b/src/renderer/components/+network-port-forwards/port-forwards-route-parameters.injectable.ts new file mode 100644 index 0000000000..7b56a14ab3 --- /dev/null +++ b/src/renderer/components/+network-port-forwards/port-forwards-route-parameters.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import routePathParametersInjectable from "../../routes/route-path-parameters.injectable"; +import portForwardsRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/port-forwards/port-forwards-route.injectable"; + +const portForwardsRouteParametersInjectable = getInjectable({ + id: "port-forwards-route-parameters", + + instantiate: (di) => { + const route = di.inject(portForwardsRouteInjectable); + const pathParameters = di.inject(routePathParametersInjectable, route); + + return { + forwardport: computed(() => pathParameters.get().forwardport), + }; + }, +}); + +export default portForwardsRouteParametersInjectable; diff --git a/src/renderer/components/+network-port-forwards/port-forwards-sidebar-items.injectable.tsx b/src/renderer/components/+network-port-forwards/port-forwards-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..271a697faf --- /dev/null +++ b/src/renderer/components/+network-port-forwards/port-forwards-sidebar-items.injectable.tsx @@ -0,0 +1,39 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import portForwardsRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/port-forwards/port-forwards-route.injectable"; +import { networkSidebarItemId } from "../+network/network-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToPortForwardsInjectable from "../../../common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable"; + +const portForwardsSidebarItemsInjectable = getInjectable({ + id: "port-forwards-sidebar-items", + + instantiate: (di) => { + const route = di.inject(portForwardsRouteInjectable); + const navigateToPortForwards = di.inject(navigateToPortForwardsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "port-forwards", + parentId: networkSidebarItemId, + + title: "Port Forwarding", + onClick: navigateToPortForwards, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 50, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default portForwardsSidebarItemsInjectable; diff --git a/src/renderer/components/+network-port-forwards/port-forwards.tsx b/src/renderer/components/+network-port-forwards/port-forwards.tsx index 1a079cd598..fa05924516 100644 --- a/src/renderer/components/+network-port-forwards/port-forwards.tsx +++ b/src/renderer/components/+network-port-forwards/port-forwards.tsx @@ -7,15 +7,17 @@ import "./port-forwards.scss"; import React from "react"; import { disposeOnUnmount, observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router-dom"; import { ItemListLayout } from "../item-object-list/list-layout"; import type { PortForwardItem, PortForwardStore } from "../../port-forward"; import { PortForwardMenu } from "./port-forward-menu"; -import { PortForwardsRouteParams, portForwardsURL } from "../../../common/routes"; import { PortForwardDetails } from "./port-forward-details"; -import { navigation } from "../../navigation"; import { withInjectables } from "@ogre-tools/injectable-react"; import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; + +import { computed, IComputedValue, makeObservable } from "mobx"; +import portForwardsRouteParametersInjectable from "./port-forwards-route-parameters.injectable"; +import navigateToPortForwardsInjectable, { NavigateToPortForwards } from "../../../common/front-end-routing/routes/cluster/network/port-forwards/navigate-to-port-forwards.injectable"; enum columnId { name = "name", @@ -27,26 +29,30 @@ enum columnId { status = "status", } -export interface PortForwardsProps extends RouteComponentProps { -} - interface Dependencies { portForwardStore: PortForwardStore; + forwardport: IComputedValue; + navigateToPortForwards: NavigateToPortForwards; } @observer -class NonInjectedPortForwards extends React.Component { +class NonInjectedPortForwards extends React.Component { + constructor(props: Dependencies) { + super(props); + + makeObservable(this); + } componentDidMount() { + disposeOnUnmount(this, [ this.props.portForwardStore.watch(), ]); } + @computed get selectedPortForward() { - const { match: { params: { forwardport }}} = this.props; - - return this.props.portForwardStore.getById(forwardport); + return this.props.portForwardStore.getById(this.props.forwardport.get()); } onDetails = (item: PortForwardItem) => { @@ -58,15 +64,13 @@ class NonInjectedPortForwards extends React.Component { - navigation.push(portForwardsURL({ - params: { - forwardport: item.getId(), - }, - })); + this.props.navigateToPortForwards({ + forwardport: item.getId(), + }); }; hideDetails = () => { - navigation.push(portForwardsURL()); + this.props.navigateToPortForwards(); }; renderRemoveDialogMessage(selectedItems: PortForwardItem[]) { @@ -82,7 +86,7 @@ class NonInjectedPortForwards extends React.Component + )} - + ); } } -export const PortForwards = withInjectables( +export const PortForwards = withInjectables( NonInjectedPortForwards, { - getProps: (di, props) => ({ - portForwardStore: di.inject(portForwardStoreInjectable), - ...props, - }), + getProps: (di) => { + const routeParameters = di.inject(portForwardsRouteParametersInjectable); + + return { + portForwardStore: di.inject(portForwardStoreInjectable), + forwardport: routeParameters.forwardport, + navigateToPortForwards: di.inject(navigateToPortForwardsInjectable), + }; + }, }, ); diff --git a/src/renderer/components/+network-services/service-port-component.tsx b/src/renderer/components/+network-services/service-port-component.tsx index 39baf0da09..6945a5dc6a 100644 --- a/src/renderer/components/+network-services/service-port-component.tsx +++ b/src/renderer/components/+network-services/service-port-component.tsx @@ -13,17 +13,14 @@ import { cssNames } from "../../utils"; import { Notifications } from "../notifications"; import { Button } from "../button"; import type { ForwardedPort } from "../../port-forward"; -import { - aboutPortForwarding, - notifyErrorPortForwarding, openPortForward, - PortForwardStore, - predictProtocol, -} from "../../port-forward"; +import { openPortForward, PortForwardStore, predictProtocol } from "../../port-forward"; import { Spinner } from "../spinner"; import { withInjectables } from "@ogre-tools/injectable-react"; import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable"; import portForwardDialogModelInjectable from "../../port-forward/port-forward-dialog-model/port-forward-dialog-model.injectable"; import logger from "../../../common/logger"; +import aboutPortForwardingInjectable from "../../port-forward/about-port-forwarding.injectable"; +import notifyErrorPortForwardingInjectable from "../../port-forward/notify-error-port-forwarding.injectable"; export interface ServicePortComponentProps { service: Service; @@ -33,6 +30,8 @@ export interface ServicePortComponentProps { interface Dependencies { portForwardStore: PortForwardStore; openPortForwardDialog: (item: ForwardedPort, options: { openInBrowser: boolean; onClose: () => void }) => void; + aboutPortForwarding: () => void; + notifyErrorPortForwarding: (message: string) => void; } @observer @@ -115,10 +114,10 @@ class NonInjectedServicePortComponent extends React.Component ({ portForwardStore: di.inject(portForwardStoreInjectable), openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open, + aboutPortForwarding: di.inject(aboutPortForwardingInjectable), + notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable), ...props, }), }, diff --git a/src/renderer/components/+network-services/services-route-component.injectable.ts b/src/renderer/components/+network-services/services-route-component.injectable.ts new file mode 100644 index 0000000000..c16b724e0a --- /dev/null +++ b/src/renderer/components/+network-services/services-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Services } from "./services"; +import servicesRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/services/services-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const servicesRouteComponentInjectable = getInjectable({ + id: "services-route-component", + + instantiate: (di) => ({ + route: di.inject(servicesRouteInjectable), + Component: Services, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default servicesRouteComponentInjectable; diff --git a/src/renderer/components/+network-services/services-sidebar-items.injectable.tsx b/src/renderer/components/+network-services/services-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..c4e8d93a99 --- /dev/null +++ b/src/renderer/components/+network-services/services-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import servicesRouteInjectable from "../../../common/front-end-routing/routes/cluster/network/services/services-route.injectable"; +import { networkSidebarItemId } from "../+network/network-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToServicesInjectable from "../../../common/front-end-routing/routes/cluster/network/services/navigate-to-services.injectable"; + +const servicesSidebarItemsInjectable = getInjectable({ + id: "services-sidebar-items", + + instantiate: (di) => { + const route = di.inject(servicesRouteInjectable); + const navigateToServices = di.inject(navigateToServicesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "services", + parentId: networkSidebarItemId, + title: "Services", + onClick: navigateToServices, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 10, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default servicesSidebarItemsInjectable; diff --git a/src/renderer/components/+network-services/services.tsx b/src/renderer/components/+network-services/services.tsx index 57107f1fc2..a05f3c1ab5 100644 --- a/src/renderer/components/+network-services/services.tsx +++ b/src/renderer/components/+network-services/services.tsx @@ -7,12 +7,11 @@ import "./services.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { Badge } from "../badge"; import { serviceStore } from "./services.store"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { ServicesRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -27,66 +26,65 @@ enum columnId { status = "status", } -export interface ServicesProps extends RouteComponentProps { -} - @observer -export class Services extends React.Component { +export class Services extends React.Component { render() { return ( - service.getName(), - [columnId.namespace]: service => service.getNs(), - [columnId.selector]: service => service.getSelector(), - [columnId.ports]: service => (service.spec.ports || []).map(({ port }) => port)[0], - [columnId.clusterIp]: service => service.getClusterIp(), - [columnId.type]: service => service.getType(), - [columnId.age]: service => -service.getCreationTimestamp(), - [columnId.status]: service => service.getStatus(), - }} - searchFilters={[ - service => service.getSearchFields(), - service => service.getSelector().join(" "), - service => service.getPorts().join(" "), - ]} - renderHeaderTitle="Services" - renderTableHeader={[ - { 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: "Type", className: "type", sortBy: columnId.type, id: columnId.type }, - { title: "Cluster IP", className: "clusterIp", sortBy: columnId.clusterIp, id: columnId.clusterIp }, - { title: "Ports", className: "ports", sortBy: columnId.ports, id: columnId.ports }, - { title: "External IP", className: "externalIp", id: columnId.externalIp }, - { title: "Selector", className: "selector", sortBy: columnId.selector, id: columnId.selector }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, - ]} - renderTableContents={service => { - const externalIps = service.getExternalIps(); + + service.getName(), + [columnId.namespace]: service => service.getNs(), + [columnId.selector]: service => service.getSelector(), + [columnId.ports]: service => (service.spec.ports || []).map(({ port }) => port)[0], + [columnId.clusterIp]: service => service.getClusterIp(), + [columnId.type]: service => service.getType(), + [columnId.age]: service => -service.getCreationTimestamp(), + [columnId.status]: service => service.getStatus(), + }} + searchFilters={[ + service => service.getSearchFields(), + service => service.getSelector().join(" "), + service => service.getPorts().join(" "), + ]} + renderHeaderTitle="Services" + renderTableHeader={[ + { 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: "Type", className: "type", sortBy: columnId.type, id: columnId.type }, + { title: "Cluster IP", className: "clusterIp", sortBy: columnId.clusterIp, id: columnId.clusterIp }, + { title: "Ports", className: "ports", sortBy: columnId.ports, id: columnId.ports }, + { title: "External IP", className: "externalIp", id: columnId.externalIp }, + { title: "Selector", className: "selector", sortBy: columnId.selector, id: columnId.selector }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, + ]} + renderTableContents={service => { + const externalIps = service.getExternalIps(); - if (externalIps.length === 0 && service.spec?.externalName) { - externalIps.push(service.spec.externalName); - } + if (externalIps.length === 0 && service.spec?.externalName) { + externalIps.push(service.spec.externalName); + } - return [ - service.getName(), - , - service.getNs(), - service.getType(), - service.getClusterIp(), - service.getPorts().join(", "), - externalIps.join(", ") || "-", - service.getSelector().map(label => ), - , - { title: service.getStatus(), className: service.getStatus().toLowerCase() }, - ]; - }} - /> + return [ + service.getName(), + , + service.getNs(), + service.getType(), + service.getClusterIp(), + service.getPorts().join(", "), + externalIps.join(", ") || "-", + service.getSelector().map(label => ), + , + { title: service.getStatus(), className: service.getStatus().toLowerCase() }, + ]; + }} + /> + ); } } diff --git a/src/renderer/components/+network/network-sidebar-items.injectable.tsx b/src/renderer/components/+network/network-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..e0e78a77cf --- /dev/null +++ b/src/renderer/components/+network/network-sidebar-items.injectable.tsx @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { Icon } from "../icon"; +import React from "react"; +import { noop } from "lodash/fp"; + +export const networkSidebarItemId = "network"; + +const networkSidebarItemsInjectable = getInjectable({ + id: "network-sidebar-items", + + instantiate: () => + computed((): SidebarItemRegistration[] => [ + { + id: networkSidebarItemId, + parentId: null, + getIcon: () => , + title: "Network", + onClick: noop, + orderNumber: 50, + }, + ]), + + injectionToken: sidebarItemsInjectionToken, +}); + +export default networkSidebarItemsInjectable; diff --git a/src/renderer/components/+network/network.scss b/src/renderer/components/+network/network.scss deleted file mode 100644 index e862468c8a..0000000000 --- a/src/renderer/components/+network/network.scss +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -.Network { -} diff --git a/src/renderer/components/+network/route-tabs.injectable.ts b/src/renderer/components/+network/route-tabs.injectable.ts deleted file mode 100644 index bef8a69d67..0000000000 --- a/src/renderer/components/+network/route-tabs.injectable.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import { Services } from "../+network-services"; -import { Endpoints } from "../+network-endpoints"; -import { Ingresses } from "../+network-ingresses"; -import { NetworkPolicies } from "../+network-policies"; -import { PortForwards } from "../+network-port-forwards"; -import * as routes from "../../../common/routes"; -import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; -import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; - -interface Dependencies { - isAllowedResource: IsAllowedResource; -} - -function getRouteTabs({ isAllowedResource }: Dependencies) { - return computed(() => { - const tabs: TabLayoutRoute[] = []; - - if (isAllowedResource("services")) { - tabs.push({ - title: "Services", - component: Services, - url: routes.servicesURL(), - routePath: routes.servicesRoute.path.toString(), - }); - } - - if (isAllowedResource("endpoints")) { - tabs.push({ - title: "Endpoints", - component: Endpoints, - url: routes.endpointURL(), - routePath: routes.endpointRoute.path.toString(), - }); - } - - if (isAllowedResource("ingresses")) { - tabs.push({ - title: "Ingresses", - component: Ingresses, - url: routes.ingressURL(), - routePath: routes.ingressRoute.path.toString(), - }); - } - - if (isAllowedResource("networkpolicies")) { - tabs.push({ - title: "Network Policies", - component: NetworkPolicies, - url: routes.networkPoliciesURL(), - routePath: routes.networkPoliciesRoute.path.toString(), - }); - } - - tabs.push({ - title: "Port Forwarding", - component: PortForwards, - url: routes.portForwardsURL(), - routePath: routes.portForwardsRoute.path.toString(), - }); - - return tabs; - }); -} - -const networkRouteTabsInjectable = getInjectable({ - id: "network-route-tabs", - - instantiate: (di) => getRouteTabs({ - isAllowedResource: di.inject(isAllowedResourceInjectable), - }), -}); - -export default networkRouteTabsInjectable; diff --git a/src/renderer/components/+network/route.tsx b/src/renderer/components/+network/route.tsx deleted file mode 100644 index 1abf1ca445..0000000000 --- a/src/renderer/components/+network/route.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import "./network.scss"; - -import React from "react"; -import { observer } from "mobx-react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import type { IComputedValue } from "mobx"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import networkRouteTabsInjectable from "./route-tabs.injectable"; - -export interface NetworksRouteProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedNetworksRoute = observer(({ routes }: Dependencies & NetworksRouteProps) => ( - -)); - -export const NetworkRoute = withInjectables(NonInjectedNetworksRoute, { - getProps: (di, props) => ({ - routes: di.inject(networkRouteTabsInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+network/sidebar-item.tsx b/src/renderer/components/+network/sidebar-item.tsx deleted file mode 100644 index 6189ef3b13..0000000000 --- a/src/renderer/components/+network/sidebar-item.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import React from "react"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import type { IComputedValue } from "mobx"; -import { observer } from "mobx-react"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import { renderTabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; -import { networkRoute, networkURL } from "../../../common/routes"; -import networkRouteTabsInjectable from "./route-tabs.injectable"; - -export interface NetworkSidebarItemProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedNetworkSidebarItem = observer(({ routes }: Dependencies & NetworkSidebarItemProps) => { - const tabRoutes = routes.get(); - - return ( - } - > - {renderTabRoutesSidebarItems(tabRoutes)} - - ); -}); - -export const NetworkSidebarItem = withInjectables(NonInjectedNetworkSidebarItem, { - getProps: (di, props) => ({ - routes: di.inject(networkRouteTabsInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+nodes/nodes-route-component.injectable.ts b/src/renderer/components/+nodes/nodes-route-component.injectable.ts new file mode 100644 index 0000000000..4db966406d --- /dev/null +++ b/src/renderer/components/+nodes/nodes-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { NodesRoute } from "./route"; +import nodesRouteInjectable from "../../../common/front-end-routing/routes/cluster/nodes/nodes-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const nodesRouteComponentInjectable = getInjectable({ + id: "nodes-route-component", + + instantiate: (di) => ({ + route: di.inject(nodesRouteInjectable), + Component: NodesRoute, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default nodesRouteComponentInjectable; diff --git a/src/renderer/components/+nodes/nodes-sidebar-items.injectable.tsx b/src/renderer/components/+nodes/nodes-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..605754a248 --- /dev/null +++ b/src/renderer/components/+nodes/nodes-sidebar-items.injectable.tsx @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { Icon } from "../icon"; +import React from "react"; + +import nodesRouteInjectable from "../../../common/front-end-routing/routes/cluster/nodes/nodes-route.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToNodesInjectable from "../../../common/front-end-routing/routes/cluster/nodes/navigate-to-nodes.injectable"; + +const nodesSidebarItemsInjectable = getInjectable({ + id: "nodes-sidebar-items", + + instantiate: (di) => { + const route = di.inject(nodesRouteInjectable); + const navigateToNodes = di.inject(navigateToNodesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed((): SidebarItemRegistration[] => [ + { + id: "nodes", + parentId: null, + getIcon: () => , + title: "Nodes", + onClick: navigateToNodes, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 20, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default nodesSidebarItemsInjectable; diff --git a/src/renderer/components/+nodes/route.tsx b/src/renderer/components/+nodes/route.tsx index 9ca6874743..2fd494a3ae 100644 --- a/src/renderer/components/+nodes/route.tsx +++ b/src/renderer/components/+nodes/route.tsx @@ -6,9 +6,8 @@ import "./nodes.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { cssNames, interval } from "../../utils"; -import { TabLayout } from "../layout/tab-layout"; +import { TabLayout } from "../layout/tab-layout-2"; import { nodesStore } from "./nodes.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { formatNodeTaint, getMetricsForAllNodes, INodeMetrics, Node } from "../../../common/k8s-api/endpoints/nodes.api"; @@ -20,7 +19,6 @@ import upperFirst from "lodash/upperFirst"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { Badge } from "../badge/badge"; import { eventStore } from "../+events/event.store"; -import type { NodesRouteParams } from "../../../common/routes"; import { makeObservable, observable } from "mobx"; import isEmpty from "lodash/isEmpty"; import { KubeObjectAge } from "../kube-object/age"; @@ -38,9 +36,6 @@ enum columnId { status = "status", } -export interface NodesRouteProps extends RouteComponentProps { -} - type MetricsTooltipFormatter = (metrics: [number, number]) => string; interface UsageArgs { @@ -51,11 +46,11 @@ interface UsageArgs { } @observer -export class NodesRoute extends React.Component { +export class NodesRoute extends React.Component { @observable.ref metrics: Partial = {}; private metricsWatcher = interval(30, async () => this.metrics = await getMetricsForAllNodes()); - constructor(props: NodesRouteProps) { + constructor(props: any) { super(props); makeObservable(this); } diff --git a/src/renderer/components/+nodes/sidebar-item.tsx b/src/renderer/components/+nodes/sidebar-item.tsx deleted file mode 100644 index 1a384cc0fc..0000000000 --- a/src/renderer/components/+nodes/sidebar-item.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { withInjectables } from "@ogre-tools/injectable-react"; -import { observer } from "mobx-react"; -import React from "react"; -import { nodesRoute, nodesURL } from "../../../common/routes"; -import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; -import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; - -export interface NodeSidebarItemProps {} - -interface Dependencies { - isAllowedResource: IsAllowedResource; -} - -const NonInjectedNodeSidebarItem = observer(({ isAllowedResource }: Dependencies & NodeSidebarItemProps) => ( - } - /> -)); - -export const NodesSidebarItem = withInjectables(NonInjectedNodeSidebarItem, { - getProps: (di, props) => ({ - isAllowedResource: di.inject(isAllowedResourceInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+pod-security-policies/pod-security-policies-route-component.injectable.ts b/src/renderer/components/+pod-security-policies/pod-security-policies-route-component.injectable.ts new file mode 100644 index 0000000000..58b607e6d8 --- /dev/null +++ b/src/renderer/components/+pod-security-policies/pod-security-policies-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { PodSecurityPolicies } from "./pod-security-policies"; +import podSecurityPoliciesRouteInjectable from "../../../common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const podSecurityPoliciesRouteComponentInjectable = getInjectable({ + id: "pod-security-policies-route-component", + + instantiate: (di) => ({ + route: di.inject(podSecurityPoliciesRouteInjectable), + Component: PodSecurityPolicies, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default podSecurityPoliciesRouteComponentInjectable; diff --git a/src/renderer/components/+pod-security-policies/pod-security-policies-sidebar-items.injectable.tsx b/src/renderer/components/+pod-security-policies/pod-security-policies-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..711ee8cef4 --- /dev/null +++ b/src/renderer/components/+pod-security-policies/pod-security-policies-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import podSecurityPoliciesRouteInjectable from "../../../common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable"; +import { userManagementSidebarItemId } from "../+user-management/user-management-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToPodSecurityPoliciesInjectable from "../../../common/front-end-routing/routes/cluster/user-management/pod-security-policies/navigate-to-pod-security-policies.injectable"; + +const podSecurityPoliciesSidebarItemsInjectable = getInjectable({ + id: "pod-security-policies-sidebar-items", + + instantiate: (di) => { + const route = di.inject(podSecurityPoliciesRouteInjectable); + const navigateToPodSecurityPolicies = di.inject(navigateToPodSecurityPoliciesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "pod-security-policies", + parentId: userManagementSidebarItemId, + title: "Pod Security Policies", + onClick: navigateToPodSecurityPolicies, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 60, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default podSecurityPoliciesSidebarItemsInjectable; diff --git a/src/renderer/components/+pod-security-policies/pod-security-policies.tsx b/src/renderer/components/+pod-security-policies/pod-security-policies.tsx index 341db9cc35..3ccdc9ad7d 100644 --- a/src/renderer/components/+pod-security-policies/pod-security-policies.tsx +++ b/src/renderer/components/+pod-security-policies/pod-security-policies.tsx @@ -10,6 +10,7 @@ import { observer } from "mobx-react"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { podSecurityPoliciesStore } from "./pod-security-policies.store"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -23,38 +24,40 @@ enum columnId { export class PodSecurityPolicies extends React.Component { render() { return ( - podSecurityPolicy.getName(), - [columnId.volumes]: podSecurityPolicy => podSecurityPolicy.getVolumes(), - [columnId.privileged]: podSecurityPolicy => +podSecurityPolicy.isPrivileged(), - [columnId.age]: podSecurityPolicy => -podSecurityPolicy.getCreationTimestamp(), - }} - searchFilters={[ - podSecurityPolicy => podSecurityPolicy.getSearchFields(), - podSecurityPolicy => podSecurityPolicy.getVolumes(), - podSecurityPolicy => Object.values(podSecurityPolicy.getRules()), - ]} - renderHeaderTitle="Pod Security Policies" - renderTableHeader={[ - { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, - { className: "warning", showWithColumn: columnId.name }, - { title: "Privileged", className: "privileged", sortBy: columnId.privileged, id: columnId.privileged }, - { title: "Volumes", className: "volumes", sortBy: columnId.volumes, id: columnId.volumes }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={podSecurityPolicy => [ - podSecurityPolicy.getName(), - , - podSecurityPolicy.isPrivileged() ? "Yes" : "No", - podSecurityPolicy.getVolumes().join(", "), - , - ]} - /> + + podSecurityPolicy.getName(), + [columnId.volumes]: podSecurityPolicy => podSecurityPolicy.getVolumes(), + [columnId.privileged]: podSecurityPolicy => +podSecurityPolicy.isPrivileged(), + [columnId.age]: podSecurityPolicy => -podSecurityPolicy.getCreationTimestamp(), + }} + searchFilters={[ + podSecurityPolicy => podSecurityPolicy.getSearchFields(), + podSecurityPolicy => podSecurityPolicy.getVolumes(), + podSecurityPolicy => Object.values(podSecurityPolicy.getRules()), + ]} + renderHeaderTitle="Pod Security Policies" + renderTableHeader={[ + { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, + { className: "warning", showWithColumn: columnId.name }, + { title: "Privileged", className: "privileged", sortBy: columnId.privileged, id: columnId.privileged }, + { title: "Volumes", className: "volumes", sortBy: columnId.volumes, id: columnId.volumes }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={podSecurityPolicy => [ + podSecurityPolicy.getName(), + , + podSecurityPolicy.isPrivileged() ? "Yes" : "No", + podSecurityPolicy.getVolumes().join(", "), + , + ]} + /> + ); } } diff --git a/src/renderer/components/+preferences/app-preferences-route-component.injectable.ts b/src/renderer/components/+preferences/app-preferences-route-component.injectable.ts new file mode 100644 index 0000000000..aa887c2df9 --- /dev/null +++ b/src/renderer/components/+preferences/app-preferences-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import appPreferencesRouteInjectable from "../../../common/front-end-routing/routes/preferences/app/app-preferences-route.injectable"; +import { Application } from "./application"; + +const appPreferencesRouteComponentInjectable = getInjectable({ + id: "app-preferences-route-component", + + instantiate: (di) => ({ + route: di.inject(appPreferencesRouteInjectable), + Component: Application, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default appPreferencesRouteComponentInjectable; diff --git a/src/renderer/components/+preferences/application.tsx b/src/renderer/components/+preferences/application.tsx index b85d3e9665..f7ea4619ba 100644 --- a/src/renderer/components/+preferences/application.tsx +++ b/src/renderer/components/+preferences/application.tsx @@ -7,8 +7,8 @@ import React from "react"; import { observer } from "mobx-react"; import { SubTitle } from "../layout/sub-title"; import { Select, SelectOption } from "../select"; -import { ThemeStore } from "../../theme.store"; -import { UserStore } from "../../../common/user-store"; +import type { ThemeStore } from "../../theme.store"; +import type { UserStore } from "../../../common/user-store"; import { Input } from "../input"; import { Switch } from "../switch"; import moment from "moment-timezone"; @@ -19,6 +19,9 @@ import { ExtensionSettings } from "./extension-settings"; import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration"; import { withInjectables } from "@ogre-tools/injectable-react"; import appPreferencesInjectable from "./app-preferences/app-preferences.injectable"; +import { Preferences } from "./preferences"; +import userStoreInjectable from "../../../common/user-store/user-store.injectable"; +import themeStoreInjectable from "../../theme-store.injectable"; const timezoneOptions: SelectOption[] = moment.tz.names().map(zone => ({ label: zone, @@ -31,100 +34,102 @@ const updateChannelOptions: SelectOption[] = Array.from( interface Dependencies { appPreferenceItems: IComputedValue; + userStore: UserStore; + themeStore: ThemeStore; } -const NonInjectedApplication: React.FC = ({ appPreferenceItems }) => { - const userStore = UserStore.getInstance(); +const NonInjectedApplication: React.FC = ({ appPreferenceItems, userStore, themeStore }) => { const [customUrl, setCustomUrl] = React.useState(userStore.extensionRegistryUrl.customUrl || ""); const extensionSettings = appPreferenceItems.get().filter((preference) => preference.showInPreferencesTab === "application"); - const themeStore = ThemeStore.getInstance(); return ( -
-

Application

-
- - userStore.colorTheme = value} + themeName="lens" + /> +
+ +
+ +
+ + userStore.extensionRegistryUrl.customUrl = customUrl} + placeholder="Custom Extension Registry URL..." + disabled={userStore.extensionRegistryUrl.location !== ExtensionRegistryLocation.CUSTOM} + /> +
+ +
+ +
+ + userStore.openAtLogin = !userStore.openAtLogin}> + Automatically start Lens on login + +
+ +
+ + {extensionSettings.map(setting => ( + + ))} + +
+ + userStore.setLocaleTimezone(value)} + themeName="lens" + /> +
- -
- -
- - userStore.extensionRegistryUrl.customUrl = customUrl} - placeholder="Custom Extension Registry URL..." - disabled={userStore.extensionRegistryUrl.location !== ExtensionRegistryLocation.CUSTOM} - /> -
- -
- -
- - userStore.openAtLogin = !userStore.openAtLogin}> - Automatically start Lens on login - -
- -
- - {extensionSettings.map(setting => ( - - ))} - -
- - userStore.setLocaleTimezone(value)} - themeName="lens" - /> -
-
+ ); }; @@ -134,6 +139,8 @@ export const Application = withInjectables( { getProps: (di) => ({ appPreferenceItems: di.inject(appPreferencesInjectable), + userStore: di.inject(userStoreInjectable), + themeStore: di.inject(themeStoreInjectable), }), }, ); diff --git a/src/renderer/components/+preferences/close-preferences.injectable.ts b/src/renderer/components/+preferences/close-preferences.injectable.ts new file mode 100644 index 0000000000..61eb3f9042 --- /dev/null +++ b/src/renderer/components/+preferences/close-preferences.injectable.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import observableHistoryInjectable from "../../navigation/observable-history.injectable"; +import navigateToFrontPageInjectable from "../../../common/front-end-routing/navigate-to-front-page.injectable"; + +const closePreferencesInjectable = getInjectable({ + id: "close-preferences", + + instantiate: (di) => { + const observableHistory = di.inject(observableHistoryInjectable); + const navigateToFrontPage = di.inject(navigateToFrontPageInjectable); + + return () => { + if (observableHistory.length === 1) { + navigateToFrontPage(); + + return; + } + + observableHistory.goBack(); + }; + }, +}); + +export default closePreferencesInjectable; diff --git a/src/renderer/components/+preferences/editor-preferences-route-component.injectable.ts b/src/renderer/components/+preferences/editor-preferences-route-component.injectable.ts new file mode 100644 index 0000000000..a51d95438b --- /dev/null +++ b/src/renderer/components/+preferences/editor-preferences-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import { Editor } from "./editor"; +import editorPreferencesRouteInjectable from "../../../common/front-end-routing/routes/preferences/editor/editor-preferences-route.injectable"; + +const editorPreferencesRouteComponentInjectable = getInjectable({ + id: "editor-preferences-route-component", + + instantiate: (di) => ({ + route: di.inject(editorPreferencesRouteInjectable), + Component: Editor, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default editorPreferencesRouteComponentInjectable; diff --git a/src/renderer/components/+preferences/editor.tsx b/src/renderer/components/+preferences/editor.tsx index fe1f4ca602..7150e118dd 100644 --- a/src/renderer/components/+preferences/editor.tsx +++ b/src/renderer/components/+preferences/editor.tsx @@ -4,12 +4,15 @@ */ import { observer } from "mobx-react"; import React from "react"; -import { UserStore } from "../../../common/user-store"; +import type { UserStore } from "../../../common/user-store"; import { Switch } from "../switch"; import { Select } from "../select"; import { SubTitle } from "../layout/sub-title"; import { SubHeader } from "../layout/sub-header"; import { Input, InputValidators } from "../input"; +import { Preferences } from "./preferences"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import userStoreInjectable from "../../../common/user-store/user-store.injectable"; enum EditorLineNumbersStyles { on = "On", @@ -18,78 +21,98 @@ enum EditorLineNumbersStyles { interval = "Interval", } -export const Editor = observer(() => { - const editorConfiguration = UserStore.getInstance().editorConfiguration; +interface Dependencies { + userStore: UserStore; +} + +const NonInjectedEditor = observer(({ userStore }: Dependencies) => { + const editorConfiguration = userStore.editorConfiguration; return ( -
-

Editor configuration

+ +
+

Editor configuration

- -
-
-
- editorConfiguration.minimap.enabled = !editorConfiguration.minimap.enabled} - > - Show minimap - + +
+
+
+ editorConfiguration.minimap.enabled = !editorConfiguration.minimap.enabled} + > + Show minimap + +
+
+ Position + editorConfiguration.minimap.side = value} - /> -
-
-
+
-
- - ({ + label, + value, + }))} + value={editorConfiguration.lineNumbers} + onChange={({ value }) => editorConfiguration.lineNumbers = value} + themeName="lens" + /> +
-
- - editorConfiguration.tabSize = Number(value)} - /> +
+ + editorConfiguration.tabSize = Number(value)} + /> +
+
+ + editorConfiguration.fontSize = Number(value)} + /> +
+
+ + editorConfiguration.fontFamily = value} + /> +
-
- - editorConfiguration.fontSize = Number(value)} - /> -
-
- - editorConfiguration.fontFamily = value} - /> -
-
+
); }); +export const Editor = withInjectables( + NonInjectedEditor, + + { + getProps: (di) => ({ + userStore: di.inject(userStoreInjectable), + }), + }, +); + + diff --git a/src/renderer/components/+preferences/extension-preference-item-registrator.injectable.ts b/src/renderer/components/+preferences/extension-preference-item-registrator.injectable.ts new file mode 100644 index 0000000000..189f11e45f --- /dev/null +++ b/src/renderer/components/+preferences/extension-preference-item-registrator.injectable.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { filter, map } from "lodash/fp"; +import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token"; +import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; +import { pipeline } from "@ogre-tools/fp"; +import { extensionPreferenceItemInjectionToken } from "./extension-preference-items.injectable"; + +const extensionPreferenceItemRegistratorInjectable = getInjectable({ + id: "extension-preference-item-registrator", + + instantiate: + (di) => + async (extension: LensRendererExtension, extensionInstallationCount) => { + const injectables = pipeline( + extension.appPreferences, + + filter( + (registration) => !registration.showInPreferencesTab, + ), + + map((registration) => { + const id = `extension-preferences-item-${registration.id}-for-extension-${extension.sanitizedExtensionId}`; + + return getInjectable({ + id: `${id}-for-instance-${extensionInstallationCount}`, + injectionToken: extensionPreferenceItemInjectionToken, + + instantiate: () => ({ + id: registration.id || id, + title: registration.title, + extension, + + components: { + Hint: registration.components.Hint, + Input: registration.components.Input, + }, + }), + }); + }), + ); + + injectables.forEach(di.register); + + return; + }, + + injectionToken: extensionRegistratorInjectionToken, +}); + +export default extensionPreferenceItemRegistratorInjectable; diff --git a/src/renderer/components/+preferences/extension-preference-items.injectable.ts b/src/renderer/components/+preferences/extension-preference-items.injectable.ts new file mode 100644 index 0000000000..f4d08304d2 --- /dev/null +++ b/src/renderer/components/+preferences/extension-preference-items.injectable.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { pipeline } from "@ogre-tools/fp"; +import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; +import { filter, overSome } from "lodash/fp"; +import { computed } from "mobx"; +import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable"; +import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; +import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration"; + +interface ExtensionPreferenceItem extends RegisteredAppPreference { + extension: LensRendererExtension; +} + +export const extensionPreferenceItemInjectionToken = getInjectionToken({ + id: "extension-preference-item-injection-token", +}); + +const extensionsPreferenceItemsInjectable = getInjectable({ + id: "extension-preference-items", + + instantiate: (di) => { + const extensions = di.inject(rendererExtensionsInjectable); + + return computed(() => { + const enabledExtensions = extensions.get(); + + return pipeline( + di.injectMany(extensionPreferenceItemInjectionToken), + + filter((item) => + overSome([ + isNonExtensionItem, + isEnabledExtensionItemFor(enabledExtensions), + ])(item), + ), + ); + }); + }, +}); + +const isNonExtensionItem = (item: ExtensionPreferenceItem) => !item.extension; + +const isEnabledExtensionItemFor = + (enabledExtensions: LensRendererExtension[]) => (item: ExtensionPreferenceItem) => + !!enabledExtensions.find((extension) => extension === item.extension); + +export default extensionsPreferenceItemsInjectable; diff --git a/src/renderer/components/+preferences/extension-preferences-route-component.injectable.ts b/src/renderer/components/+preferences/extension-preferences-route-component.injectable.ts new file mode 100644 index 0000000000..f67e94fd98 --- /dev/null +++ b/src/renderer/components/+preferences/extension-preferences-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import extensionPreferencesRouteInjectable from "../../../common/front-end-routing/routes/preferences/extension/extension-preferences-route.injectable"; +import { Extensions } from "./extensions"; + +const extensionPreferencesRouteComponentInjectable = getInjectable({ + id: "extension-preferences-route-component", + + instantiate: (di) => ({ + route: di.inject(extensionPreferencesRouteInjectable), + Component: Extensions, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default extensionPreferencesRouteComponentInjectable; diff --git a/src/renderer/components/+preferences/extension-settings.tsx b/src/renderer/components/+preferences/extension-settings.tsx index eb4970976b..d796bc35f8 100644 --- a/src/renderer/components/+preferences/extension-settings.tsx +++ b/src/renderer/components/+preferences/extension-settings.tsx @@ -4,15 +4,15 @@ */ import { SubTitle } from "../layout/sub-title"; import type { AppPreferenceRegistration } from "./app-preferences/app-preference-registration"; -import React from "react"; +import React, { DOMAttributes } from "react"; import { cssNames } from "../../../renderer/utils"; -export interface ExtensionSettingsProps { +export interface ExtensionSettingsProps extends DOMAttributes { setting: AppPreferenceRegistration; size: "small" | "normal"; } -export function ExtensionSettings({ setting, size }: ExtensionSettingsProps) { +export function ExtensionSettings({ setting, size, ...props }: ExtensionSettingsProps) { const { title, id, @@ -21,7 +21,7 @@ export function ExtensionSettings({ setting, size }: ExtensionSettingsProps) { return ( -
+
diff --git a/src/renderer/components/+preferences/extension-telemetry-preference-item-registrator.injectable.ts b/src/renderer/components/+preferences/extension-telemetry-preference-item-registrator.injectable.ts new file mode 100644 index 0000000000..f6a57cde9a --- /dev/null +++ b/src/renderer/components/+preferences/extension-telemetry-preference-item-registrator.injectable.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { filter, map } from "lodash/fp"; +import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token"; +import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; +import { pipeline } from "@ogre-tools/fp"; +import { telemetryPreferenceItemInjectionToken } from "./telemetry-preference-items.injectable"; + +const extensionTelemetryPreferenceItemRegistratorInjectable = getInjectable({ + id: "extension-telemetry-preference-item-registrator", + + instantiate: + (di) => + async (extension: LensRendererExtension, extensionInstallationCount) => { + const injectables = pipeline( + extension.appPreferences, + + filter( + (registration) => registration.showInPreferencesTab === "telemetry", + ), + + map((registration) => { + const id = `telemetry-preferences-item-${registration.id}-for-extension-${extension.sanitizedExtensionId}`; + + return getInjectable({ + id: `${id}-for-instance-${extensionInstallationCount}`, + injectionToken: telemetryPreferenceItemInjectionToken, + + instantiate: () => ({ + id: registration.id || id, + title: registration.title, + extension, + + components: { + Hint: registration.components.Hint, + Input: registration.components.Input, + }, + }), + }); + }), + ); + + injectables.forEach(di.register); + + return; + }, + + injectionToken: extensionRegistratorInjectionToken, +}); + +export default extensionTelemetryPreferenceItemRegistratorInjectable; diff --git a/src/renderer/components/+preferences/extensions.tsx b/src/renderer/components/+preferences/extensions.tsx index c3a2632905..930b74c8ee 100644 --- a/src/renderer/components/+preferences/extensions.tsx +++ b/src/renderer/components/+preferences/extensions.tsx @@ -8,33 +8,36 @@ import type { IComputedValue } from "mobx"; import { observer } from "mobx-react"; import React from "react"; import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration"; -import appPreferencesInjectable from "./app-preferences/app-preferences.injectable"; import { ExtensionSettings } from "./extension-settings"; +import { Preferences } from "./preferences"; +import extensionsPreferenceItemsInjectable from "./extension-preference-items.injectable"; interface Dependencies { - appPreferenceItems: IComputedValue; + preferenceItems: IComputedValue; } -const NonInjectedExtensions: React.FC = ({ appPreferenceItems }) => { - - const settings = appPreferenceItems.get(); - - return ( +const NonInjectedExtensions = ({ preferenceItems }: Dependencies) => ( +

Extensions

- {settings.filter(e => !e.showInPreferencesTab).map((setting) => - , - )} + {preferenceItems.get().map((preferenceItem) => ( + + ))}
- ); -}; +
+); export const Extensions = withInjectables( observer(NonInjectedExtensions), { getProps: (di) => ({ - appPreferenceItems: di.inject(appPreferencesInjectable), + preferenceItems: di.inject(extensionsPreferenceItemsInjectable), }), }, ); diff --git a/src/renderer/components/+preferences/kubeconfig-syncs.tsx b/src/renderer/components/+preferences/kubeconfig-syncs.tsx index de6b926824..3cbf628c40 100644 --- a/src/renderer/components/+preferences/kubeconfig-syncs.tsx +++ b/src/renderer/components/+preferences/kubeconfig-syncs.tsx @@ -2,13 +2,13 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ +import { withInjectables } from "@ogre-tools/injectable-react"; import fse from "fs-extra"; import { computed, makeObservable, observable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import React from "react"; import { Notice } from "../+extensions/notice"; - -import { KubeconfigSyncEntry, KubeconfigSyncValue, UserStore } from "../../../common/user-store"; +import type { KubeconfigSyncEntry, KubeconfigSyncValue, UserStore } from "../../../common/user-store"; import { isWindows } from "../../../common/vars"; import logger from "../../../main/logger"; import { iter, tuple } from "../../utils"; @@ -16,6 +16,7 @@ import { SubTitle } from "../layout/sub-title"; import { PathPicker } from "../path-picker/path-picker"; import { Spinner } from "../spinner"; import { RemovableItem } from "./removable-item"; +import userStoreInjectable from "../../../common/user-store/user-store.injectable"; interface SyncInfo { type: "file" | "folder" | "unknown"; @@ -57,12 +58,16 @@ export async function getAllEntries(filePaths: string[]): Promise<[string, Value return Promise.all(filePaths.map(filePath => getMapEntry({ filePath }))); } +interface Dependencies { + userStore: UserStore; +} + @observer -export class KubeconfigSyncs extends React.Component { +class NonInjectedKubeconfigSyncs extends React.Component { syncs = observable.map(); @observable loaded = false; - constructor(props: {}) { + constructor(props: Dependencies) { super(props); makeObservable(this); } @@ -70,7 +75,7 @@ export class KubeconfigSyncs extends React.Component { async componentDidMount() { const mapEntries = await Promise.all( iter.map( - UserStore.getInstance().syncKubeconfigEntries, + this.props.userStore.syncKubeconfigEntries, ([filePath, ...value]) => getMapEntry({ filePath, ...value }), ), ); @@ -82,7 +87,7 @@ export class KubeconfigSyncs extends React.Component { reaction( () => Array.from(this.syncs.entries(), ([filePath, { data }]) => tuple.from(filePath, data)), syncs => { - UserStore.getInstance().syncKubeconfigEntries.replace(syncs); + this.props.userStore.syncKubeconfigEntries.replace(syncs); }, ), ]); @@ -195,3 +200,13 @@ export class KubeconfigSyncs extends React.Component { ); } } + +export const KubeconfigSyncs = withInjectables( + NonInjectedKubeconfigSyncs, + + { + getProps: (di) => ({ + userStore: di.inject(userStoreInjectable), + }), + }, +); diff --git a/src/renderer/components/+preferences/kubectl-binaries.tsx b/src/renderer/components/+preferences/kubectl-binaries.tsx index efcf1f807b..95cc69d103 100644 --- a/src/renderer/components/+preferences/kubectl-binaries.tsx +++ b/src/renderer/components/+preferences/kubectl-binaries.tsx @@ -7,20 +7,21 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; import { Input, InputValidators } from "../input"; import { SubTitle } from "../layout/sub-title"; -import { UserStore } from "../../../common/user-store"; +import type { UserStore } from "../../../common/user-store"; import { SelectOption, Select } from "../select"; import { Switch } from "../switch"; import { packageMirrors } from "../../../common/user-store/preferences-helpers"; import directoryForBinariesInjectable from "../../../common/app-paths/directory-for-binaries/directory-for-binaries.injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; +import userStoreInjectable from "../../../common/user-store/user-store.injectable"; import { kubectlBinaryPath } from "../../../common/vars"; interface Dependencies { defaultPathForKubectlBinaries: string; + userStore: UserStore; } -const NonInjectedKubectlBinaries: React.FC = observer(({ defaultPathForKubectlBinaries }) => { - const userStore = UserStore.getInstance(); +const NonInjectedKubectlBinaries: React.FC = observer(({ defaultPathForKubectlBinaries, userStore }) => { const [downloadPath, setDownloadPath] = useState(userStore.downloadBinariesPath || ""); const [binariesPath, setBinariesPath] = useState(userStore.kubectlBinariesPath || ""); const pathValidator = downloadPath ? InputValidators.isPath : undefined; @@ -94,5 +95,6 @@ const NonInjectedKubectlBinaries: React.FC = observer(({ defaultPa export const KubectlBinaries = withInjectables(NonInjectedKubectlBinaries, { getProps: (di) => ({ defaultPathForKubectlBinaries: di.inject(directoryForBinariesInjectable), + userStore: di.inject(userStoreInjectable), }), }); diff --git a/src/renderer/components/+preferences/kubernetes-preferences-route-component.injectable.ts b/src/renderer/components/+preferences/kubernetes-preferences-route-component.injectable.ts new file mode 100644 index 0000000000..51c33f73a6 --- /dev/null +++ b/src/renderer/components/+preferences/kubernetes-preferences-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import kubernetesPreferencesRouteInjectable from "../../../common/front-end-routing/routes/preferences/kubernetes/kubernetes-preferences-route.injectable"; +import { Kubernetes } from "./kubernetes"; + +const kubernetesPreferencesRouteComponentInjectable = getInjectable({ + id: "kubernetes-preferences-route-component", + + instantiate: (di) => ({ + route: di.inject(kubernetesPreferencesRouteInjectable), + Component: Kubernetes, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default kubernetesPreferencesRouteComponentInjectable; diff --git a/src/renderer/components/+preferences/kubernetes.tsx b/src/renderer/components/+preferences/kubernetes.tsx index a7002cacc7..aaaffab0aa 100644 --- a/src/renderer/components/+preferences/kubernetes.tsx +++ b/src/renderer/components/+preferences/kubernetes.tsx @@ -8,24 +8,25 @@ import React from "react"; import { HelmCharts } from "./helm-charts"; import { KubeconfigSyncs } from "./kubeconfig-syncs"; import { KubectlBinaries } from "./kubectl-binaries"; +import { Preferences } from "./preferences"; -export const Kubernetes = observer(() => { - return ( +export const Kubernetes = observer(() => ( +

Kubernetes

-
+

Kubeconfig Syncs

-
+

Helm Charts

- +
- ); -}); +
+)); diff --git a/src/renderer/components/+preferences/preferences-navigation/application-preferences-navigation-item.injectable.ts b/src/renderer/components/+preferences/preferences-navigation/application-preferences-navigation-item.injectable.ts new file mode 100644 index 0000000000..0b333f4fed --- /dev/null +++ b/src/renderer/components/+preferences/preferences-navigation/application-preferences-navigation-item.injectable.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { preferenceNavigationItemInjectionToken } from "./preference-navigation-items.injectable"; +import appPreferencesRouteInjectable from "../../../../common/front-end-routing/routes/preferences/app/app-preferences-route.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import { computed } from "mobx"; +import navigateToPreferenceTabInjectable from "./navigate-to-preference-tab.injectable"; + +const applicationPreferencesNavigationItemInjectable = getInjectable({ + id: "application-preferences-navigation-item", + + instantiate: (di) => { + const route = di.inject(appPreferencesRouteInjectable); + const navigateToPreferenceTab = di.inject(navigateToPreferenceTabInjectable); + + const routeIsActive = di.inject( + routeIsActiveInjectable, + route, + ); + + return { + id: "application", + label: "App", + navigate: navigateToPreferenceTab(route), + isActive: routeIsActive, + isVisible: computed(() => true), + orderNumber: 10, + }; + }, + + injectionToken: preferenceNavigationItemInjectionToken, +}); + +export default applicationPreferencesNavigationItemInjectable; diff --git a/src/renderer/components/+preferences/preferences-navigation/editor-preferences-navigation-item.injectable.ts b/src/renderer/components/+preferences/preferences-navigation/editor-preferences-navigation-item.injectable.ts new file mode 100644 index 0000000000..f5738cf985 --- /dev/null +++ b/src/renderer/components/+preferences/preferences-navigation/editor-preferences-navigation-item.injectable.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { preferenceNavigationItemInjectionToken } from "./preference-navigation-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import { computed } from "mobx"; +import editorPreferencesRouteInjectable from "../../../../common/front-end-routing/routes/preferences/editor/editor-preferences-route.injectable"; +import navigateToPreferenceTabInjectable from "./navigate-to-preference-tab.injectable"; + +const editorPreferencesNavigationItemInjectable = getInjectable({ + id: "editor-preferences-navigation-item", + + instantiate: (di) => { + const route = di.inject(editorPreferencesRouteInjectable); + const navigateToPreferenceTab = di.inject(navigateToPreferenceTabInjectable); + + const routeIsActive = di.inject( + routeIsActiveInjectable, + route, + ); + + return { + id: "editor", + label: "Editor", + navigate: navigateToPreferenceTab(route), + isActive: routeIsActive, + isVisible: computed(() => true), + orderNumber: 40, + }; + }, + + injectionToken: preferenceNavigationItemInjectionToken, +}); + +export default editorPreferencesNavigationItemInjectable; diff --git a/src/renderer/components/+preferences/preferences-navigation/extensions-preferences-navigation-item.injectable.ts b/src/renderer/components/+preferences/preferences-navigation/extensions-preferences-navigation-item.injectable.ts new file mode 100644 index 0000000000..029e9ba896 --- /dev/null +++ b/src/renderer/components/+preferences/preferences-navigation/extensions-preferences-navigation-item.injectable.ts @@ -0,0 +1,51 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { preferenceNavigationItemInjectionToken } from "./preference-navigation-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import { computed } from "mobx"; +import extensionsPreferenceItemsInjectable from "../extension-preference-items.injectable"; +import extensionPreferencesRouteInjectable from "../../../../common/front-end-routing/routes/preferences/extension/extension-preferences-route.injectable"; +import navigateToPreferenceTabInjectable from "./navigate-to-preference-tab.injectable"; + +const extensionsPreferencesNavigationItemInjectable = getInjectable({ + id: "extension-preferences-navigation-item", + + instantiate: (di) => { + const preferenceItems = di.inject( + extensionsPreferenceItemsInjectable, + ); + + const navigateToPreferenceTab = di.inject( + navigateToPreferenceTabInjectable, + ); + + const route = di.inject( + extensionPreferencesRouteInjectable, + ); + + const routeIsActive = di.inject( + routeIsActiveInjectable, + route, + ); + + return { + id: "extensions", + label: "Extensions", + navigate: navigateToPreferenceTab(route), + isActive: routeIsActive, + + isVisible: computed( + () => preferenceItems.get().length > 0, + ), + + orderNumber: 70, + }; + }, + + injectionToken: preferenceNavigationItemInjectionToken, +}); + +export default extensionsPreferencesNavigationItemInjectable; diff --git a/src/renderer/components/+preferences/preferences-navigation/kubernetes-preferences-navigation-item.injectable.ts b/src/renderer/components/+preferences/preferences-navigation/kubernetes-preferences-navigation-item.injectable.ts new file mode 100644 index 0000000000..ec3429a212 --- /dev/null +++ b/src/renderer/components/+preferences/preferences-navigation/kubernetes-preferences-navigation-item.injectable.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { preferenceNavigationItemInjectionToken } from "./preference-navigation-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import { computed } from "mobx"; +import kubernetesPreferencesRouteInjectable from "../../../../common/front-end-routing/routes/preferences/kubernetes/kubernetes-preferences-route.injectable"; +import navigateToPreferenceTabInjectable from "./navigate-to-preference-tab.injectable"; + +const kubernetesPreferencesNavigationItemInjectable = getInjectable({ + id: "kubernetes-preferences-navigation-item", + + instantiate: (di) => { + const navigateToPreferenceTab = di.inject(navigateToPreferenceTabInjectable); + + const route = di.inject( + kubernetesPreferencesRouteInjectable, + ); + + const routeIsActive = di.inject( + routeIsActiveInjectable, + route, + ); + + return { + id: "kubernetes", + label: "Kubernetes", + navigate: navigateToPreferenceTab(route), + isActive: routeIsActive, + isVisible: computed(() => true), + orderNumber: 30, + }; + }, + + injectionToken: preferenceNavigationItemInjectionToken, +}); + +export default kubernetesPreferencesNavigationItemInjectable; diff --git a/src/renderer/components/+preferences/preferences-navigation/navigate-to-preference-tab.injectable.ts b/src/renderer/components/+preferences/preferences-navigation/navigate-to-preference-tab.injectable.ts new file mode 100644 index 0000000000..05a20dab29 --- /dev/null +++ b/src/renderer/components/+preferences/preferences-navigation/navigate-to-preference-tab.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { Route } from "../../../../common/front-end-routing/route-injection-token"; +import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token"; + +const navigateToPreferenceTabInjectable = getInjectable({ + id: "navigate-to-preference-tab", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + + return (route: Route) => () => { + navigateToRoute(route, { withoutAffectingBackButton: true }); + }; + }, +}); + +export default navigateToPreferenceTabInjectable; diff --git a/src/renderer/components/+preferences/preferences-navigation/preference-navigation-items.injectable.ts b/src/renderer/components/+preferences/preferences-navigation/preference-navigation-items.injectable.ts new file mode 100644 index 0000000000..28eaa06efd --- /dev/null +++ b/src/renderer/components/+preferences/preferences-navigation/preference-navigation-items.injectable.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { pipeline } from "@ogre-tools/fp"; +import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; +import { filter, orderBy } from "lodash/fp"; +import { computed, IComputedValue } from "mobx"; + +export const preferenceNavigationItemInjectionToken = + getInjectionToken({ + id: "preference-navigation-item-injection-token", + }); + +export interface PreferenceNavigationItem { + id: string; + label: string; + isActive: IComputedValue; + isVisible: IComputedValue; + navigate: () => void; + orderNumber: number; +} + +const preferenceNavigationItemsInjectable = getInjectable({ + id: "preference-navigation-items", + + instantiate: (di) => + computed((): PreferenceNavigationItem[] => + pipeline( + di.injectMany(preferenceNavigationItemInjectionToken), + filter((item) => !!item.isVisible.get()), + (items) => orderBy([(item) => item.orderNumber], ["asc"], items), + ), + ), +}); + +export default preferenceNavigationItemsInjectable; diff --git a/src/renderer/components/+preferences/preferences-navigation/preferences-navigation.tsx b/src/renderer/components/+preferences/preferences-navigation/preferences-navigation.tsx new file mode 100644 index 0000000000..ef0f85a263 --- /dev/null +++ b/src/renderer/components/+preferences/preferences-navigation/preferences-navigation.tsx @@ -0,0 +1,61 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { Tab, Tabs } from "../../tabs"; + +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import React from "react"; +import preferenceNavigationItemsInjectable, { + PreferenceNavigationItem, +} from "./preference-navigation-items.injectable"; + +import { observer } from "mobx-react"; + +interface Dependencies { + navigationItems: IComputedValue; +} + +const NonInjectedPreferencesNavigation = ({ + navigationItems, +}: Dependencies) => ( + item.navigate()} + > +
Preferences
+ + {navigationItems.get().map((item) => ( + + ))} +
+); + +interface PreferenceNavigationTabProps extends React.DOMAttributes { + item: PreferenceNavigationItem; +} + +const PreferencesNavigationTab = observer(({ item }: PreferenceNavigationTabProps) => ( + +)); + +export const PreferencesNavigation = withInjectables( + NonInjectedPreferencesNavigation, + + { + getProps: (di) => ({ + navigationItems: di.inject(preferenceNavigationItemsInjectable), + }), + }, +); diff --git a/src/renderer/components/+preferences/preferences-navigation/proxy-preferences-navigation-item.injectable.ts b/src/renderer/components/+preferences/preferences-navigation/proxy-preferences-navigation-item.injectable.ts new file mode 100644 index 0000000000..d4a588db87 --- /dev/null +++ b/src/renderer/components/+preferences/preferences-navigation/proxy-preferences-navigation-item.injectable.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { preferenceNavigationItemInjectionToken } from "./preference-navigation-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import { computed } from "mobx"; +import proxyPreferencesRouteInjectable from "../../../../common/front-end-routing/routes/preferences/proxy/proxy-preferences-route.injectable"; +import navigateToPreferenceTabInjectable from "./navigate-to-preference-tab.injectable"; + +const proxyPreferencesNavigationItemInjectable = getInjectable({ + id: "proxy-preferences-navigation-item", + + instantiate: (di) => { + const route = di.inject(proxyPreferencesRouteInjectable); + + const navigateToPreferenceTab = di.inject( + navigateToPreferenceTabInjectable, + ); + + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return { + id: "proxy", + label: "Proxy", + navigate: navigateToPreferenceTab(route), + isActive: routeIsActive, + isVisible: computed(() => true), + orderNumber: 20, + }; + }, + + injectionToken: preferenceNavigationItemInjectionToken, +}); + +export default proxyPreferencesNavigationItemInjectable; diff --git a/src/renderer/components/+preferences/preferences-navigation/telemetry-preferences-navigation-item.injectable.ts b/src/renderer/components/+preferences/preferences-navigation/telemetry-preferences-navigation-item.injectable.ts new file mode 100644 index 0000000000..f6576696c6 --- /dev/null +++ b/src/renderer/components/+preferences/preferences-navigation/telemetry-preferences-navigation-item.injectable.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { preferenceNavigationItemInjectionToken } from "./preference-navigation-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import { computed } from "mobx"; +import telemetryPreferenceItemsInjectable from "../telemetry-preference-items.injectable"; +import telemetryPreferencesRouteInjectable from "../../../../common/front-end-routing/routes/preferences/telemetry/telemetry-preferences-route.injectable"; +import sentryDnsUrlInjectable from "../sentry-dns-url.injectable"; +import navigateToPreferenceTabInjectable from "./navigate-to-preference-tab.injectable"; + +const terminalPreferencesNavigationItemInjectable = getInjectable({ + id: "telemetry-preferences-navigation-item", + + instantiate: (di) => { + const sentryDnsUrl = di.inject(sentryDnsUrlInjectable); + + const telemetryPreferenceItems = di.inject( + telemetryPreferenceItemsInjectable, + ); + + const navigateToPreferenceTab = di.inject( + navigateToPreferenceTabInjectable, + ); + + const route = di.inject(telemetryPreferencesRouteInjectable); + + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return { + id: "telemetry", + label: "Telemetry", + navigate: navigateToPreferenceTab(route), + isActive: routeIsActive, + + isVisible: computed( + () => !!sentryDnsUrl || telemetryPreferenceItems.get().length > 0, + ), + + orderNumber: 60, + }; + }, + + injectionToken: preferenceNavigationItemInjectionToken, +}); + +export default terminalPreferencesNavigationItemInjectable; diff --git a/src/renderer/components/+preferences/preferences-navigation/terminal-preferences-navigation-item.injectable.ts b/src/renderer/components/+preferences/preferences-navigation/terminal-preferences-navigation-item.injectable.ts new file mode 100644 index 0000000000..b623148c10 --- /dev/null +++ b/src/renderer/components/+preferences/preferences-navigation/terminal-preferences-navigation-item.injectable.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { preferenceNavigationItemInjectionToken } from "./preference-navigation-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import { computed } from "mobx"; +import terminalPreferencesRouteInjectable from "../../../../common/front-end-routing/routes/preferences/terminal/terminal-preferences-route.injectable"; +import navigateToPreferenceTabInjectable from "./navigate-to-preference-tab.injectable"; + +const terminalPreferencesNavigationItemInjectable = getInjectable({ + id: "terminal-preferences-navigation-item", + + instantiate: (di) => { + const navigateToPreferenceTab = di.inject( + navigateToPreferenceTabInjectable, + ); + + const route = di.inject(terminalPreferencesRouteInjectable); + + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return { + id: "terminal", + label: "Terminal", + navigate: navigateToPreferenceTab(route), + isActive: routeIsActive, + isVisible: computed(() => true), + orderNumber: 50, + }; + }, + + injectionToken: preferenceNavigationItemInjectionToken, +}); + +export default terminalPreferencesNavigationItemInjectable; diff --git a/src/renderer/components/+preferences/preferences.tsx b/src/renderer/components/+preferences/preferences.tsx index cb469b062c..ab40a5ba13 100644 --- a/src/renderer/components/+preferences/preferences.tsx +++ b/src/renderer/components/+preferences/preferences.tsx @@ -3,99 +3,46 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import "./preferences.scss"; - -import type { IComputedValue } from "mobx"; -import { observer } from "mobx-react"; import React from "react"; -import { matchPath, Redirect, Route, RouteProps, Switch } from "react-router"; -import { - appRoute, - appURL, - editorURL, - extensionRoute, - extensionURL, - kubernetesRoute, - kubernetesURL, - preferencesURL, - proxyRoute, - proxyURL, - editorRoute, - telemetryRoute, - telemetryURL, - terminalRoute, - terminalURL, -} from "../../../common/routes"; -import { navigateWithoutHistoryChange, navigation } from "../../navigation"; -import { SettingLayout } from "../layout/setting-layout"; -import { Tab, Tabs } from "../tabs"; -import { Application } from "./application"; -import { Kubernetes } from "./kubernetes"; -import { Editor } from "./editor"; -import { Terminal } from "./terminal"; -import { LensProxy } from "./proxy"; -import { Telemetry } from "./telemetry"; -import { Extensions } from "./extensions"; -import { sentryDsn } from "../../../common/vars"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration"; -import appPreferencesInjectable from "./app-preferences/app-preferences.injectable"; -interface Dependencies { - appPreferenceItems: IComputedValue; +import { SettingLayout } from "../layout/setting-layout"; +import { PreferencesNavigation } from "./preferences-navigation/preferences-navigation"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import closePreferencesInjectable from "./close-preferences.injectable"; + +interface PreferencesProps extends React.DOMAttributes { + children: React.ReactNode; } -const NonInjectedPreferences: React.FC = ({ appPreferenceItems }) => { +interface Dependencies { + closePreferences: () => void; +} - function renderNavigation() { - const extensions = appPreferenceItems.get(); - const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == "telemetry"); - const currentLocation = navigation.location.pathname; - const isActive = (route: RouteProps) => !!matchPath(currentLocation, { path: route.path, exact: route.exact }); +const NonInjectedPreferences = ({ + children, + closePreferences, + ...props +}: PreferencesProps & Dependencies) => ( + } + className="Preferences" + contentGaps={false} + closeButtonProps={{ "data-testid": "close-preferences" }} + back={closePreferences} + {...props} + > + {children} + +); - return ( - navigateWithoutHistoryChange({ pathname: url })}> -
Preferences
- - - - - - {(telemetryExtensions.length > 0 || !!sentryDsn) && - - } - {extensions.filter(e => !e.showInPreferencesTab).length > 0 && - - } -
- ); - } - - return ( - - - - - - - - - - - - - ); -}; - -export const Preferences = withInjectables( - observer(NonInjectedPreferences), +export const Preferences = withInjectables( + NonInjectedPreferences, { - getProps: (di) => ({ - appPreferenceItems: di.inject(appPreferencesInjectable), + getProps: (di, props) => ({ + closePreferences: di.inject(closePreferencesInjectable), + ...props, }), }, ); + diff --git a/src/renderer/components/+preferences/proxy-preferences-route-component.injectable.ts b/src/renderer/components/+preferences/proxy-preferences-route-component.injectable.ts new file mode 100644 index 0000000000..62540dfc42 --- /dev/null +++ b/src/renderer/components/+preferences/proxy-preferences-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import proxyPreferencesRouteInjectable from "../../../common/front-end-routing/routes/preferences/proxy/proxy-preferences-route.injectable"; +import { LensProxy } from "./proxy"; + +const proxyPreferencesRouteComponentInjectable = getInjectable({ + id: "proxy-preferences-route-component", + + instantiate: (di) => ({ + route: di.inject(proxyPreferencesRouteInjectable), + Component: LensProxy, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default proxyPreferencesRouteComponentInjectable; diff --git a/src/renderer/components/+preferences/proxy.tsx b/src/renderer/components/+preferences/proxy.tsx index 69db754d24..1ce4cbc4cf 100644 --- a/src/renderer/components/+preferences/proxy.tsx +++ b/src/renderer/components/+preferences/proxy.tsx @@ -2,48 +2,66 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ +import { withInjectables } from "@ogre-tools/injectable-react"; import { observer } from "mobx-react"; import React from "react"; - -import { UserStore } from "../../../common/user-store"; +import type { UserStore } from "../../../common/user-store"; import { Input } from "../input"; import { SubTitle } from "../layout/sub-title"; import { Switch } from "../switch"; +import { Preferences } from "./preferences"; +import userStoreInjectable from "../../../common/user-store/user-store.injectable"; -export const LensProxy = observer(() => { - const [proxy, setProxy] = React.useState(UserStore.getInstance().httpsProxy || ""); - const store = UserStore.getInstance(); +interface Dependencies { + userStore: UserStore; +} + +const NonInjectedLensProxy = observer(({ userStore }: Dependencies) => { + const [proxy, setProxy] = React.useState(userStore.httpsProxy || ""); return ( -
-
-

Proxy

- - setProxy(v)} - onBlur={() => UserStore.getInstance().httpsProxy = proxy} - /> - - Proxy is used only for non-cluster communication. - -
+ +
+
+

Proxy

+ + setProxy(v)} + onBlur={() => userStore.httpsProxy = proxy} + /> + + Proxy is used only for non-cluster communication. + +
-
+
-
- - store.allowUntrustedCAs = !store.allowUntrustedCAs}> - Allow untrusted Certificate Authorities - - - This will make Lens to trust ANY certificate authority without any validations.{" "} - Needed with some corporate proxies that do certificate re-writing.{" "} - Does not affect cluster communications! - +
+ + userStore.allowUntrustedCAs = !userStore.allowUntrustedCAs}> + Allow untrusted Certificate Authorities + + + This will make Lens to trust ANY certificate authority without any validations.{" "} + Needed with some corporate proxies that do certificate re-writing.{" "} + Does not affect cluster communications! + +
-
+
); }); + +export const LensProxy = withInjectables( + NonInjectedLensProxy, + + { + getProps: (di) => ({ + userStore: di.inject(userStoreInjectable), + }), + }, +); diff --git a/src/renderer/components/+preferences/sentry-dns-url.injectable.ts b/src/renderer/components/+preferences/sentry-dns-url.injectable.ts new file mode 100644 index 0000000000..5f337436fc --- /dev/null +++ b/src/renderer/components/+preferences/sentry-dns-url.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { sentryDsn } from "../../../common/vars"; + +const sentryDnsUrlInjectable = getInjectable({ + id: "sentry-dns-url", + instantiate: () => sentryDsn, +}); + +export default sentryDnsUrlInjectable; diff --git a/src/renderer/components/+preferences/telemetry-preference-items.injectable.ts b/src/renderer/components/+preferences/telemetry-preference-items.injectable.ts new file mode 100644 index 0000000000..421a45b83f --- /dev/null +++ b/src/renderer/components/+preferences/telemetry-preference-items.injectable.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; +import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable"; +import { pipeline } from "@ogre-tools/fp"; +import { filter, overSome } from "lodash/fp"; +import { computed } from "mobx"; +import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; +import type { AppPreferenceRegistration } from "./app-preferences/app-preference-registration"; + +export interface ExtensionTelemetryPreferenceRegistration extends AppPreferenceRegistration { + extension?: LensRendererExtension; +} + +export const telemetryPreferenceItemInjectionToken = getInjectionToken({ + id: "telemetry-preference-item-injection-token", +}); + +const telemetryPreferenceItemsInjectable = getInjectable({ + id: "telemetry-preference-items", + instantiate: (di) => { + const extensions = di.inject(rendererExtensionsInjectable); + + return computed(() => { + const enabledExtensions = extensions.get(); + + return pipeline( + di.injectMany(telemetryPreferenceItemInjectionToken), + filter((item) => + overSome([ + isNonExtensionItem, + isEnabledExtensionItemFor(enabledExtensions), + ])(item), + ), + ); + }); + }, +}); + + +const isNonExtensionItem = (item: ExtensionTelemetryPreferenceRegistration) => !item.extension; + +const isEnabledExtensionItemFor = + (enabledExtensions: LensRendererExtension[]) => (item: ExtensionTelemetryPreferenceRegistration) => + !!enabledExtensions.find((extension) => extension === item.extension); + + +export default telemetryPreferenceItemsInjectable; diff --git a/src/renderer/components/+preferences/telemetry-preferences-route-component.injectable.ts b/src/renderer/components/+preferences/telemetry-preferences-route-component.injectable.ts new file mode 100644 index 0000000000..6d78ecfccc --- /dev/null +++ b/src/renderer/components/+preferences/telemetry-preferences-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import telemetryPreferencesRouteInjectable from "../../../common/front-end-routing/routes/preferences/telemetry/telemetry-preferences-route.injectable"; +import { Telemetry } from "./telemetry"; + +const telemetryPreferencesRouteComponentInjectable = getInjectable({ + id: "telemetry-preferences-route-component", + + instantiate: (di) => ({ + route: di.inject(telemetryPreferencesRouteInjectable), + Component: Telemetry, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default telemetryPreferencesRouteComponentInjectable; diff --git a/src/renderer/components/+preferences/telemetry.tsx b/src/renderer/components/+preferences/telemetry.tsx index 984b8e60d5..c568f410c6 100644 --- a/src/renderer/components/+preferences/telemetry.tsx +++ b/src/renderer/components/+preferences/telemetry.tsx @@ -4,52 +4,67 @@ */ import { observer } from "mobx-react"; import React from "react"; -import { UserStore } from "../../../common/user-store"; -import { sentryDsn } from "../../../common/vars"; +import type { UserStore } from "../../../common/user-store"; import { Checkbox } from "../checkbox"; import { SubTitle } from "../layout/sub-title"; import { ExtensionSettings } from "./extension-settings"; -import type { RegisteredAppPreference } from "./app-preferences/app-preference-registration"; -import appPreferencesInjectable from "./app-preferences/app-preferences.injectable"; import type { IComputedValue } from "mobx"; import { withInjectables } from "@ogre-tools/injectable-react"; +import { Preferences } from "./preferences"; +import type { ExtensionTelemetryPreferenceRegistration } from "./telemetry-preference-items.injectable"; +import telemetryPreferenceItemsInjectable from "./telemetry-preference-items.injectable"; +import sentryDnsUrlInjectable from "./sentry-dns-url.injectable"; +import userStoreInjectable from "../../../common/user-store/user-store.injectable"; interface Dependencies { - appPreferenceItems: IComputedValue; + telemetryPreferenceItems: IComputedValue; + sentryDnsUrl: string; + userStore: UserStore; } -const NonInjectedTelemetry: React.FC = ({ appPreferenceItems }) => { - const extensions = appPreferenceItems.get(); - const telemetryExtensions = extensions.filter(e => e.showInPreferencesTab == "telemetry"); - +const NonInjectedTelemetry: React.FC = ({ + telemetryPreferenceItems, + sentryDnsUrl, + userStore, +}) => { return ( -
-

Telemetry

- {telemetryExtensions.map((extension) => )} - {sentryDsn ? ( - -
- - { - UserStore.getInstance().allowErrorReporting = value; - }} - /> -
- - Automatic error reports provide vital information about issues and application crashes. - It is highly recommended to keep this feature enabled to ensure fast turnaround for issues you might encounter. - -
-
-
-
) : - // we don't need to shows the checkbox at all if Sentry dsn is not a valid url - null - } -
+ +
+

Telemetry

+ {telemetryPreferenceItems.get().map((item) => ( + + ))} + {sentryDnsUrl ? ( + +
+ + { + userStore.allowErrorReporting = value; + }} + /> +
+ + Automatic error reports provide vital information about issues + and application crashes. It is highly recommended to keep this + feature enabled to ensure fast turnaround for issues you might + encounter. + +
+
+
+
+ ) : // we don't need to shows the checkbox at all if Sentry dsn is not a valid url + null} +
+
); }; @@ -58,7 +73,9 @@ export const Telemetry = withInjectables( { getProps: (di) => ({ - appPreferenceItems: di.inject(appPreferencesInjectable), + telemetryPreferenceItems: di.inject(telemetryPreferenceItemsInjectable), + sentryDnsUrl: di.inject(sentryDnsUrlInjectable), + userStore: di.inject(userStoreInjectable), }), }, ); diff --git a/src/renderer/components/+preferences/terminal-preferences-route-component.injectable.ts b/src/renderer/components/+preferences/terminal-preferences-route-component.injectable.ts new file mode 100644 index 0000000000..e23a4520a8 --- /dev/null +++ b/src/renderer/components/+preferences/terminal-preferences-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; +import terminalPreferencesRouteInjectable from "../../../common/front-end-routing/routes/preferences/terminal/terminal-preferences-route.injectable"; +import { Terminal } from "./terminal"; + +const terminalPreferencesRouteComponentInjectable = getInjectable({ + id: "terminal-preferences-route-component", + + instantiate: (di) => ({ + route: di.inject(terminalPreferencesRouteInjectable), + Component: Terminal, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default terminalPreferencesRouteComponentInjectable; diff --git a/src/renderer/components/+preferences/terminal.tsx b/src/renderer/components/+preferences/terminal.tsx index f9f1226609..414e1663e4 100644 --- a/src/renderer/components/+preferences/terminal.tsx +++ b/src/renderer/components/+preferences/terminal.tsx @@ -5,17 +5,24 @@ import React from "react"; import { observer } from "mobx-react"; -import { UserStore } from "../../../common/user-store"; +import type { UserStore } from "../../../common/user-store"; import { SubTitle } from "../layout/sub-title"; import { Input, InputValidators } from "../input"; import { isWindows } from "../../../common/vars"; import { Switch } from "../switch"; import { Select } from "../select"; -import { ThemeStore } from "../../theme.store"; +import type { ThemeStore } from "../../theme.store"; +import { Preferences } from "./preferences"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import userStoreInjectable from "../../../common/user-store/user-store.injectable"; +import themeStoreInjectable from "../../theme-store.injectable"; -export const Terminal = observer(() => { - const userStore = UserStore.getInstance(); - const themeStore = ThemeStore.getInstance(); +interface Dependencies { + userStore: UserStore; + themeStore: ThemeStore; +} + +const NonInjectedTerminal = observer(({ userStore, themeStore }: Dependencies) => { const defaultShell = process.env.SHELL || process.env.PTYSHELL || ( @@ -24,61 +31,77 @@ export const Terminal = observer(() => { : "System default shell" ); - return (
-

Terminal

+ return ( + +
+

Terminal

-
- - userStore.shell = value} - /> -
+
+ + userStore.shell = value} + /> +
-
- - userStore.terminalCopyOnSelect = !userStore.terminalCopyOnSelect} - > - Copy on select and paste on right-click - -
+
+ + userStore.terminalCopyOnSelect = !userStore.terminalCopyOnSelect} + > + Copy on select and paste on right-click + +
-
- - userStore.terminalTheme = value} + /> +
-
- - userStore.terminalConfig.fontSize=Number(value)} - /> -
-
- - userStore.terminalConfig.fontFamily=value} - /> -
-
); +
+ + userStore.terminalConfig.fontSize = Number(value)} + /> +
+
+ + userStore.terminalConfig.fontFamily = value} + /> +
+
+ + ); }); + +export const Terminal = withInjectables( + NonInjectedTerminal, + + { + getProps: (di) => ({ + userStore: di.inject(userStoreInjectable), + themeStore: di.inject(themeStoreInjectable), + }), + }, +); + diff --git a/src/renderer/components/+storage-classes/storage-classes-route-component.injectable.ts b/src/renderer/components/+storage-classes/storage-classes-route-component.injectable.ts new file mode 100644 index 0000000000..c935983d78 --- /dev/null +++ b/src/renderer/components/+storage-classes/storage-classes-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { StorageClasses } from "./storage-classes"; +import storageClassesRouteInjectable from "../../../common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const storageClassesRouteComponentInjectable = getInjectable({ + id: "storage-classes-route-component", + + instantiate: (di) => ({ + route: di.inject(storageClassesRouteInjectable), + Component: StorageClasses, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default storageClassesRouteComponentInjectable; diff --git a/src/renderer/components/+storage-classes/storage-classes-sidebar-items.injectable.tsx b/src/renderer/components/+storage-classes/storage-classes-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..e09d20897e --- /dev/null +++ b/src/renderer/components/+storage-classes/storage-classes-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import storageClassesRouteInjectable from "../../../common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable"; +import { storageSidebarItemId } from "../+storage/storage-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToStorageClassesInjectable from "../../../common/front-end-routing/routes/cluster/storage/storage-classes/navigate-to-storage-classes.injectable"; + +const storageClassesSidebarItemsInjectable = getInjectable({ + id: "storage-classes-sidebar-items", + + instantiate: (di) => { + const route = di.inject(storageClassesRouteInjectable); + const navigateToStorageClasses = di.inject(navigateToStorageClassesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "storage-classes", + parentId: storageSidebarItemId, + title: "Storage Classes", + onClick: navigateToStorageClasses, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 30, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default storageClassesSidebarItemsInjectable; diff --git a/src/renderer/components/+storage-classes/storage-classes.tsx b/src/renderer/components/+storage-classes/storage-classes.tsx index 5da1239b43..3eff2fecd3 100644 --- a/src/renderer/components/+storage-classes/storage-classes.tsx +++ b/src/renderer/components/+storage-classes/storage-classes.tsx @@ -6,12 +6,11 @@ import "./storage-classes.scss"; import React from "react"; -import type { RouteComponentProps } from "react-router-dom"; import { observer } from "mobx-react"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { storageClassStore } from "./storage-class.store"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { StorageClassesRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -22,46 +21,45 @@ enum columnId { reclaimPolicy = "reclaim", } -export interface StorageClassesProps extends RouteComponentProps { -} - @observer -export class StorageClasses extends React.Component { +export class StorageClasses extends React.Component { render() { return ( - storageClass.getName(), - [columnId.age]: storageClass => -storageClass.getCreationTimestamp(), - [columnId.provisioner]: storageClass => storageClass.provisioner, - [columnId.reclaimPolicy]: storageClass => storageClass.reclaimPolicy, - }} - searchFilters={[ - storageClass => storageClass.getSearchFields(), - storageClass => storageClass.provisioner, - ]} - renderHeaderTitle="Storage Classes" - renderTableHeader={[ - { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, - { className: "warning", showWithColumn: columnId.name }, - { title: "Provisioner", className: "provisioner", sortBy: columnId.provisioner, id: columnId.provisioner }, - { title: "Reclaim Policy", className: "reclaim-policy", sortBy: columnId.reclaimPolicy, id: columnId.reclaimPolicy }, - { title: "Default", className: "is-default", id: columnId.default }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={storageClass => [ - storageClass.getName(), - , - storageClass.provisioner, - storageClass.getReclaimPolicy(), - storageClass.isDefault() ? "Yes" : null, - , - ]} - /> + + storageClass.getName(), + [columnId.age]: storageClass => -storageClass.getCreationTimestamp(), + [columnId.provisioner]: storageClass => storageClass.provisioner, + [columnId.reclaimPolicy]: storageClass => storageClass.reclaimPolicy, + }} + searchFilters={[ + storageClass => storageClass.getSearchFields(), + storageClass => storageClass.provisioner, + ]} + renderHeaderTitle="Storage Classes" + renderTableHeader={[ + { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, + { className: "warning", showWithColumn: columnId.name }, + { title: "Provisioner", className: "provisioner", sortBy: columnId.provisioner, id: columnId.provisioner }, + { title: "Reclaim Policy", className: "reclaim-policy", sortBy: columnId.reclaimPolicy, id: columnId.reclaimPolicy }, + { title: "Default", className: "is-default", id: columnId.default }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={storageClass => [ + storageClass.getName(), + , + storageClass.provisioner, + storageClass.getReclaimPolicy(), + storageClass.isDefault() ? "Yes" : null, + , + ]} + /> + ); } } diff --git a/src/renderer/components/+storage-volume-claims/persistent-volume-claims-route-component.injectable.ts b/src/renderer/components/+storage-volume-claims/persistent-volume-claims-route-component.injectable.ts new file mode 100644 index 0000000000..2c5cef3fcb --- /dev/null +++ b/src/renderer/components/+storage-volume-claims/persistent-volume-claims-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { PersistentVolumeClaims } from "./volume-claims"; +import persistentVolumeClaimsRouteInjectable from "../../../common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const persistentVolumeClaimsRouteComponentInjectable = getInjectable({ + id: "persistent-volume-claims-route-component", + + instantiate: (di) => ({ + route: di.inject(persistentVolumeClaimsRouteInjectable), + Component: PersistentVolumeClaims, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default persistentVolumeClaimsRouteComponentInjectable; diff --git a/src/renderer/components/+storage-volume-claims/persistent-volume-claims-sidebar-items.injectable.tsx b/src/renderer/components/+storage-volume-claims/persistent-volume-claims-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..a3b6742347 --- /dev/null +++ b/src/renderer/components/+storage-volume-claims/persistent-volume-claims-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import persistentVolumeClaimsRouteInjectable from "../../../common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable"; +import { storageSidebarItemId } from "../+storage/storage-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToPersistentVolumeClaimsInjectable from "../../../common/front-end-routing/routes/cluster/storage/persistent-volume-claims/navigate-to-persistent-volume-claims.injectable"; + +const persistentVolumeClaimsSidebarItemsInjectable = getInjectable({ + id: "persistent-volume-claims-sidebar-items", + + instantiate: (di) => { + const route = di.inject(persistentVolumeClaimsRouteInjectable); + const navigateToPersistentVolumeClaims = di.inject(navigateToPersistentVolumeClaimsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "persistent-volume-claims", + parentId: storageSidebarItemId, + title: "Persistent Volume Claims", + onClick: navigateToPersistentVolumeClaims, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 10, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default persistentVolumeClaimsSidebarItemsInjectable; diff --git a/src/renderer/components/+storage-volume-claims/volume-claims.tsx b/src/renderer/components/+storage-volume-claims/volume-claims.tsx index 4e3da7fa24..2365d107fc 100644 --- a/src/renderer/components/+storage-volume-claims/volume-claims.tsx +++ b/src/renderer/components/+storage-volume-claims/volume-claims.tsx @@ -7,7 +7,7 @@ import "./volume-claims.scss"; import React from "react"; import { observer } from "mobx-react"; -import { Link, RouteComponentProps } from "react-router-dom"; +import { Link } from "react-router-dom"; import { volumeClaimStore } from "./volume-claim.store"; import { podsStore } from "../+workloads-pods/pods.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; @@ -15,8 +15,8 @@ import { unitsToBytes } from "../../../common/utils/convertMemory"; import { stopPropagation } from "../../utils"; import { storageClassApi } from "../../../common/k8s-api/endpoints"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { VolumeClaimsRouteParams } from "../../../common/routes"; import { getDetailsUrl } from "../kube-detail-params"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -29,68 +29,67 @@ enum columnId { age = "age", } -export interface PersistentVolumeClaimsProps extends RouteComponentProps { -} - @observer -export class PersistentVolumeClaims extends React.Component { +export class PersistentVolumeClaims extends React.Component { render() { return ( - pvc.getName(), - [columnId.namespace]: pvc => pvc.getNs(), - [columnId.pods]: pvc => pvc.getPods(podsStore.items).map(pod => pod.getName()), - [columnId.status]: pvc => pvc.getStatus(), - [columnId.size]: pvc => unitsToBytes(pvc.getStorage()), - [columnId.storageClass]: pvc => pvc.spec.storageClassName, - [columnId.age]: pvc => -pvc.getCreationTimestamp(), - }} - searchFilters={[ - pvc => pvc.getSearchFields(), - pvc => pvc.getPods(podsStore.items).map(pod => pod.getName()), - ]} - renderHeaderTitle="Persistent Volume Claims" - renderTableHeader={[ - { 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: "Storage class", className: "storageClass", sortBy: columnId.storageClass, id: columnId.storageClass }, - { title: "Size", className: "size", sortBy: columnId.size, id: columnId.size }, - { title: "Pods", className: "pods", sortBy: columnId.pods, id: columnId.pods }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, - ]} - renderTableContents={pvc => { - const pods = pvc.getPods(podsStore.items); - const { storageClassName } = pvc.spec; - const storageClassDetailsUrl = getDetailsUrl(storageClassApi.getUrl({ - name: storageClassName, - })); + + pvc.getName(), + [columnId.namespace]: pvc => pvc.getNs(), + [columnId.pods]: pvc => pvc.getPods(podsStore.items).map(pod => pod.getName()), + [columnId.status]: pvc => pvc.getStatus(), + [columnId.size]: pvc => unitsToBytes(pvc.getStorage()), + [columnId.storageClass]: pvc => pvc.spec.storageClassName, + [columnId.age]: pvc => -pvc.getCreationTimestamp(), + }} + searchFilters={[ + pvc => pvc.getSearchFields(), + pvc => pvc.getPods(podsStore.items).map(pod => pod.getName()), + ]} + renderHeaderTitle="Persistent Volume Claims" + renderTableHeader={[ + { 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: "Storage class", className: "storageClass", sortBy: columnId.storageClass, id: columnId.storageClass }, + { title: "Size", className: "size", sortBy: columnId.size, id: columnId.size }, + { title: "Pods", className: "pods", sortBy: columnId.pods, id: columnId.pods }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, + ]} + renderTableContents={pvc => { + const pods = pvc.getPods(podsStore.items); + const { storageClassName } = pvc.spec; + const storageClassDetailsUrl = getDetailsUrl(storageClassApi.getUrl({ + name: storageClassName, + })); - return [ - pvc.getName(), - , - pvc.getNs(), - - {storageClassName} - , - pvc.getStorage(), - pods.map(pod => ( - - {pod.getName()} - - )), - , - { title: pvc.getStatus(), className: pvc.getStatus().toLowerCase() }, - ]; - }} - /> + return [ + pvc.getName(), + , + pvc.getNs(), + + {storageClassName} + , + pvc.getStorage(), + pods.map(pod => ( + + {pod.getName()} + + )), + , + { title: pvc.getStatus(), className: pvc.getStatus().toLowerCase() }, + ]; + }} + /> + ); } } diff --git a/src/renderer/components/+storage-volumes/persistent-volumes-route-component.injectable.ts b/src/renderer/components/+storage-volumes/persistent-volumes-route-component.injectable.ts new file mode 100644 index 0000000000..4ef6443384 --- /dev/null +++ b/src/renderer/components/+storage-volumes/persistent-volumes-route-component.injectable.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { PersistentVolumes } from "./volumes"; + +import persistentVolumesRouteInjectable from "../../../common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const persistentVolumesRouteComponentInjectable = getInjectable({ + id: "persistent-volumes-route-component", + + instantiate: (di) => ({ + route: di.inject(persistentVolumesRouteInjectable), + Component: PersistentVolumes, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default persistentVolumesRouteComponentInjectable; diff --git a/src/renderer/components/+storage-volumes/persistent-volumes-sidebar-items.injectable.tsx b/src/renderer/components/+storage-volumes/persistent-volumes-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..950882383d --- /dev/null +++ b/src/renderer/components/+storage-volumes/persistent-volumes-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import persistentVolumesRouteInjectable from "../../../common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable"; +import { storageSidebarItemId } from "../+storage/storage-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToPersistentVolumesInjectable from "../../../common/front-end-routing/routes/cluster/storage/persistent-volumes/navigate-to-persistent-volumes.injectable"; + +const persistentVolumesSidebarItemsInjectable = getInjectable({ + id: "persistent-volumes-sidebar-items", + + instantiate: (di) => { + const route = di.inject(persistentVolumesRouteInjectable); + const navigateToPersistentVolumes = di.inject(navigateToPersistentVolumesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "persistent-volumes", + parentId: storageSidebarItemId, + title: "Persistent Volumes", + onClick: navigateToPersistentVolumes, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 20, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default persistentVolumesSidebarItemsInjectable; diff --git a/src/renderer/components/+storage-volumes/volumes.tsx b/src/renderer/components/+storage-volumes/volumes.tsx index ab44b4cf3f..1f5a744f0d 100644 --- a/src/renderer/components/+storage-volumes/volumes.tsx +++ b/src/renderer/components/+storage-volumes/volumes.tsx @@ -7,14 +7,14 @@ import "./volumes.scss"; import React from "react"; import { observer } from "mobx-react"; -import { Link, RouteComponentProps } from "react-router-dom"; +import { Link } from "react-router-dom"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { getDetailsUrl } from "../kube-detail-params"; import { stopPropagation } from "../../utils"; import { volumesStore } from "./volumes.store"; import { pvcApi, storageClassApi } from "../../../common/k8s-api/endpoints"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { VolumesRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -26,62 +26,61 @@ enum columnId { age = "age", } -export interface PersistentVolumesProps extends RouteComponentProps { -} - @observer -export class PersistentVolumes extends React.Component { +export class PersistentVolumes extends React.Component { render() { return ( - volume.getName(), - [columnId.storageClass]: volume => volume.getStorageClass(), - [columnId.capacity]: volume => volume.getCapacity(true), - [columnId.status]: volume => volume.getStatus(), - [columnId.age]: volume => -volume.getCreationTimestamp(), - }} - searchFilters={[ - volume => volume.getSearchFields(), - volume => volume.getClaimRefName(), - ]} - renderHeaderTitle="Persistent Volumes" - renderTableHeader={[ - { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, - { className: "warning", showWithColumn: columnId.name }, - { title: "Storage Class", className: "storageClass", sortBy: columnId.storageClass, id: columnId.storageClass }, - { title: "Capacity", className: "capacity", sortBy: columnId.capacity, id: columnId.capacity }, - { title: "Claim", className: "claim", id: columnId.claim }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, - ]} - renderTableContents={volume => { - const { claimRef, storageClassName } = volume.spec; - const storageClassDetailsUrl = getDetailsUrl(storageClassApi.getUrl({ - name: storageClassName, - })); + + volume.getName(), + [columnId.storageClass]: volume => volume.getStorageClass(), + [columnId.capacity]: volume => volume.getCapacity(true), + [columnId.status]: volume => volume.getStatus(), + [columnId.age]: volume => -volume.getCreationTimestamp(), + }} + searchFilters={[ + volume => volume.getSearchFields(), + volume => volume.getClaimRefName(), + ]} + renderHeaderTitle="Persistent Volumes" + renderTableHeader={[ + { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, + { className: "warning", showWithColumn: columnId.name }, + { title: "Storage Class", className: "storageClass", sortBy: columnId.storageClass, id: columnId.storageClass }, + { title: "Capacity", className: "capacity", sortBy: columnId.capacity, id: columnId.capacity }, + { title: "Claim", className: "claim", id: columnId.claim }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status }, + ]} + renderTableContents={volume => { + const { claimRef, storageClassName } = volume.spec; + const storageClassDetailsUrl = getDetailsUrl(storageClassApi.getUrl({ + name: storageClassName, + })); - return [ - volume.getName(), - , - - {storageClassName} - , - volume.getCapacity(), - claimRef && ( - - {claimRef.name} - - ), - , - { title: volume.getStatus(), className: volume.getStatus().toLowerCase() }, - ]; - }} - /> + return [ + volume.getName(), + , + + {storageClassName} + , + volume.getCapacity(), + claimRef && ( + + {claimRef.name} + + ), + , + { title: volume.getStatus(), className: volume.getStatus().toLowerCase() }, + ]; + }} + /> + ); } } diff --git a/src/renderer/components/+storage/route-tabs.injectable.ts b/src/renderer/components/+storage/route-tabs.injectable.ts deleted file mode 100644 index 9c7acf50a9..0000000000 --- a/src/renderer/components/+storage/route-tabs.injectable.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import { PersistentVolumes } from "../+storage-volumes"; -import { StorageClasses } from "../+storage-classes"; -import { PersistentVolumeClaims } from "../+storage-volume-claims"; -import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; -import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; -import * as routes from "../../../common/routes"; - -interface Dependencies { - isAllowedResource: IsAllowedResource; -} - -function getRouteTabs({ isAllowedResource }: Dependencies) { - return computed(() => { - const tabs: TabLayoutRoute[] = []; - - if (isAllowedResource("persistentvolumeclaims")) { - tabs.push({ - title: "Persistent Volume Claims", - component: PersistentVolumeClaims, - url: routes.volumeClaimsURL(), - routePath: routes.volumeClaimsRoute.path.toString(), - }); - } - - if (isAllowedResource("persistentvolumes")) { - tabs.push({ - title: "Persistent Volumes", - component: PersistentVolumes, - url: routes.volumesURL(), - routePath: routes.volumesRoute.path.toString(), - }); - } - - if (isAllowedResource("storageclasses")) { - tabs.push({ - title: "Storage Classes", - component: StorageClasses, - url: routes.storageClassesURL(), - routePath: routes.storageClassesRoute.path.toString(), - }); - } - - return tabs; - }); -} - -const storageRouteTabsInjectable = getInjectable({ - id: "storage-route-tabs", - - instantiate: (di) => getRouteTabs({ - isAllowedResource: di.inject(isAllowedResourceInjectable), - }), -}); - -export default storageRouteTabsInjectable; diff --git a/src/renderer/components/+storage/route.tsx b/src/renderer/components/+storage/route.tsx deleted file mode 100644 index b11d643186..0000000000 --- a/src/renderer/components/+storage/route.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import "./storage.scss"; - -import React from "react"; -import { observer } from "mobx-react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import type { IComputedValue } from "mobx"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import storageRouteTabsInjectable from "./route-tabs.injectable"; - -export interface StorageRouteProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedStorageRoute = observer(({ routes }: Dependencies & StorageRouteProps) => ( - -)); - -export const StorageRoute = withInjectables(NonInjectedStorageRoute, { - getProps: (di, props) => ({ - routes: di.inject(storageRouteTabsInjectable), - ...props, - }), -}); - diff --git a/src/renderer/components/+storage/sidebar-item.tsx b/src/renderer/components/+storage/sidebar-item.tsx deleted file mode 100644 index 96447ab5de..0000000000 --- a/src/renderer/components/+storage/sidebar-item.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { withInjectables } from "@ogre-tools/injectable-react"; -import type { IComputedValue } from "mobx"; -import { observer } from "mobx-react"; -import React from "react"; -import { storageRoute, storageURL } from "../../../common/routes"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import { renderTabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; -import storageRouteTabsInjectable from "./route-tabs.injectable"; - -export interface StorageSidebarItemProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedStorageSidebarItem = observer(({ routes }: Dependencies & StorageSidebarItemProps) => { - const tabRoutes = routes.get(); - - return ( - } - > - {renderTabRoutesSidebarItems(tabRoutes)} - - ); -}); - -export const StorageSidebarItem = withInjectables(NonInjectedStorageSidebarItem, { - getProps: (di, props) => ({ - routes: di.inject(storageRouteTabsInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+storage/storage-sidebar-items.injectable.tsx b/src/renderer/components/+storage/storage-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..3faebf6835 --- /dev/null +++ b/src/renderer/components/+storage/storage-sidebar-items.injectable.tsx @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { Icon } from "../icon"; +import React from "react"; +import { noop } from "lodash/fp"; + +export const storageSidebarItemId = "storage"; + +const storageSidebarItemsInjectable = getInjectable({ + id: "storage-sidebar-items", + + instantiate: () => + computed((): SidebarItemRegistration[] => [ + { + id: storageSidebarItemId, + parentId: null, + getIcon: () => , + title: "Storage", + onClick: noop, + orderNumber: 60, + }, + ]), + + injectionToken: sidebarItemsInjectionToken, +}); + +export default storageSidebarItemsInjectable; diff --git a/src/renderer/components/+storage/storage.scss b/src/renderer/components/+storage/storage.scss deleted file mode 100644 index 62aa8d4294..0000000000 --- a/src/renderer/components/+storage/storage.scss +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -.Storage { -} diff --git a/src/renderer/components/+user-management/+cluster-role-bindings/cluster-role-bindings-route-component.injectable.ts b/src/renderer/components/+user-management/+cluster-role-bindings/cluster-role-bindings-route-component.injectable.ts new file mode 100644 index 0000000000..47de616795 --- /dev/null +++ b/src/renderer/components/+user-management/+cluster-role-bindings/cluster-role-bindings-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { ClusterRoleBindings } from "./view"; +import clusterRoleBindingsRouteInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../../routes/route-specific-component-injection-token"; + +const clusterRoleBindingsRouteComponentInjectable = getInjectable({ + id: "cluster-role-bindings-route-component", + + instantiate: (di) => ({ + route: di.inject(clusterRoleBindingsRouteInjectable), + Component: ClusterRoleBindings, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default clusterRoleBindingsRouteComponentInjectable; diff --git a/src/renderer/components/+user-management/+cluster-role-bindings/cluster-role-bindings-sidebar-items.injectable.tsx b/src/renderer/components/+user-management/+cluster-role-bindings/cluster-role-bindings-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..bb4ed2a928 --- /dev/null +++ b/src/renderer/components/+user-management/+cluster-role-bindings/cluster-role-bindings-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import clusterRoleBindingsRouteInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable"; +import { userManagementSidebarItemId } from "../user-management-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import navigateToClusterRoleBindingsInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/navigate-to-cluster-role-bindings.injectable"; + +const clusterRoleBindingsSidebarItemsInjectable = getInjectable({ + id: "cluster-role-bindings-sidebar-items", + + instantiate: (di) => { + const route = di.inject(clusterRoleBindingsRouteInjectable); + const navigateToClusterRoleBindings = di.inject(navigateToClusterRoleBindingsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "cluster-role-bindings", + parentId: userManagementSidebarItemId, + title: "Cluster Role Bindings", + onClick: navigateToClusterRoleBindings, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 40, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default clusterRoleBindingsSidebarItemsInjectable; diff --git a/src/renderer/components/+user-management/+cluster-role-bindings/view.tsx b/src/renderer/components/+user-management/+cluster-role-bindings/view.tsx index 48fc0840a0..2dc0436f50 100644 --- a/src/renderer/components/+user-management/+cluster-role-bindings/view.tsx +++ b/src/renderer/components/+user-management/+cluster-role-bindings/view.tsx @@ -7,14 +7,13 @@ import "./view.scss"; import { observer } from "mobx-react"; import React from "react"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../../kube-object-status-icon"; import { ClusterRoleBindingDialog } from "./dialog"; import { clusterRoleBindingsStore } from "./store"; import { clusterRolesStore } from "../+cluster-roles/store"; import { serviceAccountsStore } from "../+service-accounts/store"; -import type { ClusterRoleBindingsRouteParams } from "../../../../common/routes"; +import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../../kube-object/age"; enum columnId { @@ -24,14 +23,11 @@ enum columnId { age = "age", } -export interface ClusterRoleBindingsProps extends RouteComponentProps { -} - @observer -export class ClusterRoleBindings extends React.Component { +export class ClusterRoleBindings extends React.Component { render() { return ( - <> + - + ); } } diff --git a/src/renderer/components/+user-management/+cluster-roles/cluster-roles-route-component.injectable.ts b/src/renderer/components/+user-management/+cluster-roles/cluster-roles-route-component.injectable.ts new file mode 100644 index 0000000000..829ae16baa --- /dev/null +++ b/src/renderer/components/+user-management/+cluster-roles/cluster-roles-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { ClusterRoles } from "./view"; +import clusterRolesRouteInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../../routes/route-specific-component-injection-token"; + +const clusterRolesRouteComponentInjectable = getInjectable({ + id: "cluster-roles-route-component", + + instantiate: (di) => ({ + route: di.inject(clusterRolesRouteInjectable), + Component: ClusterRoles, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default clusterRolesRouteComponentInjectable; diff --git a/src/renderer/components/+user-management/+cluster-roles/cluster-roles-sidebar-items.injectable.tsx b/src/renderer/components/+user-management/+cluster-roles/cluster-roles-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..54ae68ef44 --- /dev/null +++ b/src/renderer/components/+user-management/+cluster-roles/cluster-roles-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import clusterRolesRouteInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable"; +import { userManagementSidebarItemId } from "../user-management-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import navigateToClusterRolesInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/cluster-roles/navigate-to-cluster-roles.injectable"; + +const clusterRolesSidebarItemsInjectable = getInjectable({ + id: "cluster-roles-sidebar-items", + + instantiate: (di) => { + const route = di.inject(clusterRolesRouteInjectable); + const navigateToClusterRoles = di.inject(navigateToClusterRolesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "cluster-roles", + parentId: userManagementSidebarItemId, + title: "Cluster Roles", + onClick: navigateToClusterRoles, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 20, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default clusterRolesSidebarItemsInjectable; diff --git a/src/renderer/components/+user-management/+cluster-roles/view.tsx b/src/renderer/components/+user-management/+cluster-roles/view.tsx index 0b979145d6..735d523227 100644 --- a/src/renderer/components/+user-management/+cluster-roles/view.tsx +++ b/src/renderer/components/+user-management/+cluster-roles/view.tsx @@ -7,12 +7,11 @@ import "./view.scss"; import { observer } from "mobx-react"; import React from "react"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../../kube-object-status-icon"; import { AddClusterRoleDialog } from "./add-dialog"; import { clusterRolesStore } from "./store"; -import type { ClusterRolesRouteParams } from "../../../../common/routes"; +import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../../kube-object/age"; enum columnId { @@ -21,14 +20,11 @@ enum columnId { age = "age", } -export interface ClusterRolesProps extends RouteComponentProps { -} - @observer -export class ClusterRoles extends React.Component { +export class ClusterRoles extends React.Component { render() { return ( - <> + { }} /> - + ); } } diff --git a/src/renderer/components/+user-management/+role-bindings/role-bindings-route-component.injectable.ts b/src/renderer/components/+user-management/+role-bindings/role-bindings-route-component.injectable.ts new file mode 100644 index 0000000000..4d0ccb4bca --- /dev/null +++ b/src/renderer/components/+user-management/+role-bindings/role-bindings-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { RoleBindings } from "./view"; +import roleBindingsRouteInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../../routes/route-specific-component-injection-token"; + +const roleBindingsRouteComponentInjectable = getInjectable({ + id: "role-bindings-route-component", + + instantiate: (di) => ({ + route: di.inject(roleBindingsRouteInjectable), + Component: RoleBindings, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default roleBindingsRouteComponentInjectable; diff --git a/src/renderer/components/+user-management/+role-bindings/role-bindings-sidebar-items.injectable.tsx b/src/renderer/components/+user-management/+role-bindings/role-bindings-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..f2eb240839 --- /dev/null +++ b/src/renderer/components/+user-management/+role-bindings/role-bindings-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import roleBindingsRouteInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable"; +import { userManagementSidebarItemId } from "../user-management-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import navigateToRoleBindingsInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/role-bindings/navigate-to-role-bindings.injectable"; + +const roleBindingsSidebarItemsInjectable = getInjectable({ + id: "role-bindings-sidebar-items", + + instantiate: (di) => { + const route = di.inject(roleBindingsRouteInjectable); + const navigateToRoleBindings = di.inject(navigateToRoleBindingsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "role-bindings", + parentId: userManagementSidebarItemId, + title: "Role Bindings", + onClick: navigateToRoleBindings, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 50, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default roleBindingsSidebarItemsInjectable; diff --git a/src/renderer/components/+user-management/+role-bindings/view.tsx b/src/renderer/components/+user-management/+role-bindings/view.tsx index 5ef4099afc..fbf5d11081 100644 --- a/src/renderer/components/+user-management/+role-bindings/view.tsx +++ b/src/renderer/components/+user-management/+role-bindings/view.tsx @@ -6,7 +6,6 @@ import "./view.scss"; import { observer } from "mobx-react"; import React from "react"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../../kube-object-status-icon"; import { RoleBindingDialog } from "./dialog"; @@ -14,7 +13,7 @@ import { roleBindingsStore } from "./store"; import { rolesStore } from "../+roles/store"; import { clusterRolesStore } from "../+cluster-roles/store"; import { serviceAccountsStore } from "../+service-accounts/store"; -import type { RoleBindingsRouteParams } from "../../../../common/routes"; +import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../../kube-object/age"; enum columnId { @@ -24,14 +23,11 @@ enum columnId { age = "age", } -export interface RoleBindingsProps extends RouteComponentProps { -} - @observer -export class RoleBindings extends React.Component { +export class RoleBindings extends React.Component { render() { return ( - <> + { }} /> - + ); } } diff --git a/src/renderer/components/+user-management/+roles/roles-route-component.injectable.ts b/src/renderer/components/+user-management/+roles/roles-route-component.injectable.ts new file mode 100644 index 0000000000..980b2499de --- /dev/null +++ b/src/renderer/components/+user-management/+roles/roles-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Roles } from "./view"; +import rolesRouteInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../../routes/route-specific-component-injection-token"; + +const rolesRouteComponentInjectable = getInjectable({ + id: "roles-route-component", + + instantiate: (di) => ({ + route: di.inject(rolesRouteInjectable), + Component: Roles, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default rolesRouteComponentInjectable; diff --git a/src/renderer/components/+user-management/+roles/roles-sidebar-items.injectable.tsx b/src/renderer/components/+user-management/+roles/roles-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..97ee9715a2 --- /dev/null +++ b/src/renderer/components/+user-management/+roles/roles-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import rolesRouteInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable"; +import { userManagementSidebarItemId } from "../user-management-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import navigateToRolesInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/roles/navigate-to-roles.injectable"; + +const rolesSidebarItemsInjectable = getInjectable({ + id: "roles-sidebar-items", + + instantiate: (di) => { + const route = di.inject(rolesRouteInjectable); + const navigateToRoles = di.inject(navigateToRolesInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "roles", + parentId: userManagementSidebarItemId, + title: "Roles", + onClick: navigateToRoles, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 30, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default rolesSidebarItemsInjectable; diff --git a/src/renderer/components/+user-management/+roles/view.tsx b/src/renderer/components/+user-management/+roles/view.tsx index 63e77c3e04..2f8316c089 100644 --- a/src/renderer/components/+user-management/+roles/view.tsx +++ b/src/renderer/components/+user-management/+roles/view.tsx @@ -7,12 +7,11 @@ import "./view.scss"; import { observer } from "mobx-react"; import React from "react"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../../kube-object-status-icon"; import { AddRoleDialog } from "./add-dialog"; import { rolesStore } from "./store"; -import type { RolesRouteParams } from "../../../../common/routes"; +import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../../kube-object/age"; enum columnId { @@ -21,14 +20,11 @@ enum columnId { age = "age", } -export interface RolesProps extends RouteComponentProps { -} - @observer -export class Roles extends React.Component { +export class Roles extends React.Component { render() { return ( - <> + { }} /> - + ); } } diff --git a/src/renderer/components/+user-management/+service-accounts/service-accounts-route-component.injectable.ts b/src/renderer/components/+user-management/+service-accounts/service-accounts-route-component.injectable.ts new file mode 100644 index 0000000000..5903af5194 --- /dev/null +++ b/src/renderer/components/+user-management/+service-accounts/service-accounts-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { ServiceAccounts } from "./view"; +import serviceAccountsRouteInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../../routes/route-specific-component-injection-token"; + +const serviceAccountsRouteComponentInjectable = getInjectable({ + id: "service-accounts-route-component", + + instantiate: (di) => ({ + route: di.inject(serviceAccountsRouteInjectable), + Component: ServiceAccounts, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default serviceAccountsRouteComponentInjectable; diff --git a/src/renderer/components/+user-management/+service-accounts/service-accounts-sidebar-items.injectable.tsx b/src/renderer/components/+user-management/+service-accounts/service-accounts-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..d01b8d6480 --- /dev/null +++ b/src/renderer/components/+user-management/+service-accounts/service-accounts-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import serviceAccountsRouteInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable"; +import { userManagementSidebarItemId } from "../user-management-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; +import navigateToServiceAccountsInjectable from "../../../../common/front-end-routing/routes/cluster/user-management/service-accounts/navigate-to-service-accounts.injectable"; + +const serviceAccountsSidebarItemsInjectable = getInjectable({ + id: "service-accounts-sidebar-items", + + instantiate: (di) => { + const route = di.inject(serviceAccountsRouteInjectable); + const navigateToServiceAccounts = di.inject(navigateToServiceAccountsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "service-accounts", + parentId: userManagementSidebarItemId, + title: "Service Accounts", + onClick: navigateToServiceAccounts, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 10, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default serviceAccountsSidebarItemsInjectable; diff --git a/src/renderer/components/+user-management/+service-accounts/view.tsx b/src/renderer/components/+user-management/+service-accounts/view.tsx index 5c43716ed5..bce7955469 100644 --- a/src/renderer/components/+user-management/+service-accounts/view.tsx +++ b/src/renderer/components/+user-management/+service-accounts/view.tsx @@ -7,12 +7,11 @@ import "./view.scss"; import { observer } from "mobx-react"; import React from "react"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../../kube-object-status-icon"; import { CreateServiceAccountDialog } from "./create-dialog"; import { serviceAccountsStore } from "./store"; -import type { ServiceAccountsRouteParams } from "../../../../common/routes"; +import { SiblingsInTabLayout } from "../../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../../kube-object/age"; enum columnId { @@ -21,14 +20,11 @@ enum columnId { age = "age", } -export interface ServiceAccountsProps extends RouteComponentProps { -} - @observer -export class ServiceAccounts extends React.Component { +export class ServiceAccounts extends React.Component { render() { return ( - <> + { }} /> - + ); } } diff --git a/src/renderer/components/+user-management/route-tabs.injectable.ts b/src/renderer/components/+user-management/route-tabs.injectable.ts deleted file mode 100644 index c4c9f5c134..0000000000 --- a/src/renderer/components/+user-management/route-tabs.injectable.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; -import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; -import * as routes from "../../../common/routes"; -import { PodSecurityPolicies } from "../+pod-security-policies"; -import { ClusterRoleBindings } from "./+cluster-role-bindings"; -import { ClusterRoles } from "./+cluster-roles"; -import { RoleBindings } from "./+role-bindings"; -import { Roles } from "./+roles"; -import { ServiceAccounts } from "./+service-accounts"; - -interface Dependencies { - isAllowedResource: IsAllowedResource; -} - -function getRouteTabs({ isAllowedResource }: Dependencies) { - return computed(() => { - const tabs: TabLayoutRoute[] = []; - - if (isAllowedResource("serviceaccounts")) { - tabs.push({ - title: "Service Accounts", - component: ServiceAccounts, - url: routes.serviceAccountsURL(), - routePath: routes.serviceAccountsRoute.path.toString(), - }); - } - - if (isAllowedResource("clusterroles")) { - tabs.push({ - title: "Cluster Roles", - component: ClusterRoles, - url: routes.clusterRolesURL(), - routePath: routes.clusterRolesRoute.path.toString(), - }); - } - - if (isAllowedResource("roles")) { - tabs.push({ - title: "Roles", - component: Roles, - url: routes.rolesURL(), - routePath: routes.rolesRoute.path.toString(), - }); - } - - if (isAllowedResource("clusterrolebindings")) { - tabs.push({ - title: "Cluster Role Bindings", - component: ClusterRoleBindings, - url: routes.clusterRoleBindingsURL(), - routePath: routes.clusterRoleBindingsRoute.path.toString(), - }); - } - - if (isAllowedResource("rolebindings")) { - tabs.push({ - title: "Role Bindings", - component: RoleBindings, - url: routes.roleBindingsURL(), - routePath: routes.roleBindingsRoute.path.toString(), - }); - } - - if (isAllowedResource("podsecuritypolicies")) { - tabs.push({ - title: "Pod Security Policies", - component: PodSecurityPolicies, - url: routes.podSecurityPoliciesURL(), - routePath: routes.podSecurityPoliciesRoute.path.toString(), - }); - } - - return tabs; - }); -} - -const userManagementRouteTabsInjectable = getInjectable({ - id: "user-management-route-tabs", - - instantiate: (di) => getRouteTabs({ - isAllowedResource: di.inject(isAllowedResourceInjectable), - }), -}); - -export default userManagementRouteTabsInjectable; diff --git a/src/renderer/components/+user-management/route.tsx b/src/renderer/components/+user-management/route.tsx deleted file mode 100644 index 360b0f57d0..0000000000 --- a/src/renderer/components/+user-management/route.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import "./user-management.scss"; - -import React from "react"; -import { observer } from "mobx-react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import type { IComputedValue } from "mobx"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import userManagementRouteTabsInjectable from "./route-tabs.injectable"; - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedUserManagementRoute = observer(({ routes }: Dependencies) => ( - -)); - -export const UserManagementRoute = withInjectables(NonInjectedUserManagementRoute, { - getProps: (di, props) => ({ - routes: di.inject(userManagementRouteTabsInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+user-management/sidebar-item.tsx b/src/renderer/components/+user-management/sidebar-item.tsx deleted file mode 100644 index 20d9500e2b..0000000000 --- a/src/renderer/components/+user-management/sidebar-item.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { withInjectables } from "@ogre-tools/injectable-react"; -import type { IComputedValue } from "mobx"; -import { observer } from "mobx-react"; -import React from "react"; -import { usersManagementRoute, usersManagementURL } from "../../../common/routes"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import { renderTabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; -import userManagementRouteTabsInjectable from "./route-tabs.injectable"; - -export interface UserManagementSidebarItemProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedUserManagementSidebarItem = observer(({ routes }: Dependencies & UserManagementSidebarItemProps) => { - const tabRoutes = routes.get(); - - return ( - } - > - {renderTabRoutesSidebarItems(tabRoutes)} - - ); -}); - -export const UserManagementSidebarItem = withInjectables(NonInjectedUserManagementSidebarItem, { - getProps: (di, props) => ({ - routes: di.inject(userManagementRouteTabsInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+user-management/user-management-sidebar-items.injectable.tsx b/src/renderer/components/+user-management/user-management-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..f07fcaf6c8 --- /dev/null +++ b/src/renderer/components/+user-management/user-management-sidebar-items.injectable.tsx @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { Icon } from "../icon"; +import React from "react"; +import { noop } from "lodash/fp"; + +export const userManagementSidebarItemId = "user-management"; + +const userManagementSidebarItemsInjectable = getInjectable({ + id: "user-management-sidebar-items", + + instantiate: () => + computed((): SidebarItemRegistration[] => [ + { + id: userManagementSidebarItemId, + parentId: null, + getIcon: () => , + title: "Access Control", + onClick: noop, + orderNumber: 100, + }, + ]), + + injectionToken: sidebarItemsInjectionToken, +}); + +export default userManagementSidebarItemsInjectable; diff --git a/src/renderer/components/+user-management/user-management.scss b/src/renderer/components/+user-management/user-management.scss deleted file mode 100644 index d6788626da..0000000000 --- a/src/renderer/components/+user-management/user-management.scss +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -.UserManagement { -} diff --git a/src/renderer/components/+welcome/welcome-menu-items/get-welcome-menu-items.ts b/src/renderer/components/+welcome/welcome-menu-items/get-welcome-menu-items.ts index a4073afe1e..4d01e7e9cb 100644 --- a/src/renderer/components/+welcome/welcome-menu-items/get-welcome-menu-items.ts +++ b/src/renderer/components/+welcome/welcome-menu-items/get-welcome-menu-items.ts @@ -4,23 +4,26 @@ */ import { computed, IComputedValue } from "mobx"; import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension"; -import { navigate } from "../../../navigation"; -import { catalogURL } from "../../../../common/routes"; +import type { NavigateToCatalog } from "../../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; interface Dependencies { extensions: IComputedValue; + navigateToCatalog: NavigateToCatalog; } -export const getWelcomeMenuItems = ({ extensions }: Dependencies) => { +export const getWelcomeMenuItems = ({ + extensions, + navigateToCatalog, +}: Dependencies) => { const browseClusters = { title: "Browse Clusters in Catalog", icon: "view_list", + click: () => - navigate( - catalogURL({ - params: { group: "entity.k8slens.dev", kind: "KubernetesCluster" }, - }), - ), + navigateToCatalog({ + group: "entity.k8slens.dev", + kind: "KubernetesCluster", + }), }; return computed(() => [ diff --git a/src/renderer/components/+welcome/welcome-menu-items/welcome-menu-items.injectable.ts b/src/renderer/components/+welcome/welcome-menu-items/welcome-menu-items.injectable.ts index 1fbfcffed6..c5234ac6f8 100644 --- a/src/renderer/components/+welcome/welcome-menu-items/welcome-menu-items.injectable.ts +++ b/src/renderer/components/+welcome/welcome-menu-items/welcome-menu-items.injectable.ts @@ -5,6 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable"; import { getWelcomeMenuItems } from "./get-welcome-menu-items"; +import navigateToCatalogInjectable from "../../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; const welcomeMenuItemsInjectable = getInjectable({ id: "welcome-menu-items", @@ -12,6 +13,7 @@ const welcomeMenuItemsInjectable = getInjectable({ instantiate: (di) => getWelcomeMenuItems({ extensions: di.inject(rendererExtensionsInjectable), + navigateToCatalog: di.inject(navigateToCatalogInjectable), }), }); diff --git a/src/renderer/components/+welcome/welcome-route-component.injectable.ts b/src/renderer/components/+welcome/welcome-route-component.injectable.ts new file mode 100644 index 0000000000..65fa0dfb00 --- /dev/null +++ b/src/renderer/components/+welcome/welcome-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Welcome } from "./welcome"; +import welcomeRouteInjectable from "../../../common/front-end-routing/routes/welcome/welcome-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const welcomeRouteComponentInjectable = getInjectable({ + id: "welcome-route-component", + + instantiate: (di) => ({ + route: di.inject(welcomeRouteInjectable), + Component: Welcome, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default welcomeRouteComponentInjectable; diff --git a/src/renderer/components/+welcome/welcome.tsx b/src/renderer/components/+welcome/welcome.tsx index 20f6ed1a27..b7390f6776 100644 --- a/src/renderer/components/+welcome/welcome.tsx +++ b/src/renderer/components/+welcome/welcome.tsx @@ -38,7 +38,7 @@ const NonInjectedWelcome: React.FC = ({ welcomeMenuItems, welcomeB }, defaultWidth); return ( -
+
({ + route: di.inject(cronJobsRouteInjectable), + Component: CronJobs, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default cronJobsRouteComponentInjectable; diff --git a/src/renderer/components/+workloads-cronjobs/cron-jobs-sidebar-items.injectable.tsx b/src/renderer/components/+workloads-cronjobs/cron-jobs-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..cc7c4a7140 --- /dev/null +++ b/src/renderer/components/+workloads-cronjobs/cron-jobs-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import cronJobsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable"; +import { workloadsSidebarItemId } from "../+workloads/workloads-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToCronJobsInjectable from "../../../common/front-end-routing/routes/cluster/workloads/cron-jobs/navigate-to-cron-jobs.injectable"; + +const cronJobsSidebarItemsInjectable = getInjectable({ + id: "cron-jobs-sidebar-items", + + instantiate: (di) => { + const route = di.inject(cronJobsRouteInjectable); + const navigateToCronJobs = di.inject(navigateToCronJobsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "cron-jobs", + parentId: workloadsSidebarItemId, + title: "CronJobs", + onClick: navigateToCronJobs, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 80, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default cronJobsSidebarItemsInjectable; diff --git a/src/renderer/components/+workloads-cronjobs/cron-jobs-store.injectable.ts b/src/renderer/components/+workloads-cronjobs/cron-jobs-store.injectable.ts new file mode 100644 index 0000000000..a05ca0fc2a --- /dev/null +++ b/src/renderer/components/+workloads-cronjobs/cron-jobs-store.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { cronJobStore } from "./cronjob.store"; + +const cronJobsStoreInjectable = getInjectable({ + id: "cron-jobs-store", + instantiate: () => cronJobStore, + causesSideEffects: true, +}); + +export default cronJobsStoreInjectable; diff --git a/src/renderer/components/+workloads-cronjobs/cronjobs.tsx b/src/renderer/components/+workloads-cronjobs/cronjobs.tsx index 7a818543af..41812f3a9b 100644 --- a/src/renderer/components/+workloads-cronjobs/cronjobs.tsx +++ b/src/renderer/components/+workloads-cronjobs/cronjobs.tsx @@ -7,14 +7,13 @@ import "./cronjobs.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { cronJobStore } from "./cronjob.store"; import { jobStore } from "../+workloads-jobs/job.store"; import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { CronJobsRouteParams } from "../../../common/routes"; import moment from "moment"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -27,57 +26,56 @@ enum columnId { age = "age", } -export interface CronJobsProps extends RouteComponentProps { -} - @observer -export class CronJobs extends React.Component { +export class CronJobs extends React.Component { render() { return ( - cronJob.getName(), - [columnId.namespace]: cronJob => cronJob.getNs(), - [columnId.suspend]: cronJob => cronJob.getSuspendFlag(), - [columnId.active]: cronJob => cronJobStore.getActiveJobsNum(cronJob), - [columnId.lastSchedule]: cronJob => ( - cronJob.status?.lastScheduleTime - ? moment().diff(cronJob.status.lastScheduleTime) - : 0 - ), - [columnId.age]: cronJob => -cronJob.getCreationTimestamp(), - }} - searchFilters={[ - cronJob => cronJob.getSearchFields(), - cronJob => cronJob.getSchedule(), - ]} - renderHeaderTitle="Cron Jobs" - renderTableHeader={[ - { 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: "Schedule", className: "schedule", id: columnId.schedule }, - { title: "Suspend", className: "suspend", sortBy: columnId.suspend, id: columnId.suspend }, - { title: "Active", className: "active", sortBy: columnId.active, id: columnId.active }, - { title: "Last schedule", className: "last-schedule", sortBy: columnId.lastSchedule, id: columnId.lastSchedule }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={cronJob => [ - cronJob.getName(), - , - cronJob.getNs(), - cronJob.isNeverRun() ? "never" : cronJob.getSchedule(), - cronJob.getSuspendFlag(), - cronJobStore.getActiveJobsNum(cronJob), - cronJob.getLastScheduleTime(), - , - ]} - /> + + cronJob.getName(), + [columnId.namespace]: cronJob => cronJob.getNs(), + [columnId.suspend]: cronJob => cronJob.getSuspendFlag(), + [columnId.active]: cronJob => cronJobStore.getActiveJobsNum(cronJob), + [columnId.lastSchedule]: cronJob => ( + cronJob.status?.lastScheduleTime + ? moment().diff(cronJob.status.lastScheduleTime) + : 0 + ), + [columnId.age]: cronJob => -cronJob.getCreationTimestamp(), + }} + searchFilters={[ + cronJob => cronJob.getSearchFields(), + cronJob => cronJob.getSchedule(), + ]} + renderHeaderTitle="Cron Jobs" + renderTableHeader={[ + { 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: "Schedule", className: "schedule", id: columnId.schedule }, + { title: "Suspend", className: "suspend", sortBy: columnId.suspend, id: columnId.suspend }, + { title: "Active", className: "active", sortBy: columnId.active, id: columnId.active }, + { title: "Last schedule", className: "last-schedule", sortBy: columnId.lastSchedule, id: columnId.lastSchedule }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={cronJob => [ + cronJob.getName(), + , + cronJob.getNs(), + cronJob.isNeverRun() ? "never" : cronJob.getSchedule(), + cronJob.getSuspendFlag(), + cronJobStore.getActiveJobsNum(cronJob), + cronJob.getLastScheduleTime(), + , + ]} + /> + ); } } diff --git a/src/renderer/components/+workloads-daemonsets/daemonsets-route-component.injectable.ts b/src/renderer/components/+workloads-daemonsets/daemonsets-route-component.injectable.ts new file mode 100644 index 0000000000..329c36943c --- /dev/null +++ b/src/renderer/components/+workloads-daemonsets/daemonsets-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { DaemonSets } from "./daemonsets"; +import daemonsetsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const daemonsetsRouteComponentInjectable = getInjectable({ + id: "daemonsets-route-component", + + instantiate: (di) => ({ + route: di.inject(daemonsetsRouteInjectable), + Component: DaemonSets, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default daemonsetsRouteComponentInjectable; diff --git a/src/renderer/components/+workloads-daemonsets/daemonsets-sidebar-items.injectable.tsx b/src/renderer/components/+workloads-daemonsets/daemonsets-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..3ec7c3a143 --- /dev/null +++ b/src/renderer/components/+workloads-daemonsets/daemonsets-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import daemonsetsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable"; +import { workloadsSidebarItemId } from "../+workloads/workloads-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToDaemonsetsInjectable from "../../../common/front-end-routing/routes/cluster/workloads/daemonsets/navigate-to-daemonsets.injectable"; + +const daemonsetsSidebarItemsInjectable = getInjectable({ + id: "daemonsets-sidebar-items", + + instantiate: (di) => { + const route = di.inject(daemonsetsRouteInjectable); + const navigateToDaemonsets = di.inject(navigateToDaemonsetsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "daemon-sets", + parentId: workloadsSidebarItemId, + title: "DaemonSets", + onClick: () => navigateToDaemonsets(), + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 40, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default daemonsetsSidebarItemsInjectable; diff --git a/src/renderer/components/+workloads-daemonsets/daemonsets-store.injectable.ts b/src/renderer/components/+workloads-daemonsets/daemonsets-store.injectable.ts new file mode 100644 index 0000000000..044750d493 --- /dev/null +++ b/src/renderer/components/+workloads-daemonsets/daemonsets-store.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { daemonSetStore } from "./daemonsets.store"; + +const daemonsetsStoreInjectable = getInjectable({ + id: "daemonsets-store", + instantiate: () => daemonSetStore, + causesSideEffects: true, +}); + +export default daemonsetsStoreInjectable; + diff --git a/src/renderer/components/+workloads-daemonsets/daemonsets.tsx b/src/renderer/components/+workloads-daemonsets/daemonsets.tsx index 37293daa05..286cd947c9 100644 --- a/src/renderer/components/+workloads-daemonsets/daemonsets.tsx +++ b/src/renderer/components/+workloads-daemonsets/daemonsets.tsx @@ -7,7 +7,6 @@ import "./daemonsets.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import type { DaemonSet } from "../../../common/k8s-api/endpoints"; import { eventStore } from "../+events/event.store"; import { daemonSetStore } from "./daemonsets.store"; @@ -15,7 +14,7 @@ import { podsStore } from "../+workloads-pods/pods.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { Badge } from "../badge"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { DaemonSetsRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -26,53 +25,52 @@ enum columnId { age = "age", } -export interface DaemonSetsProps extends RouteComponentProps { -} - @observer -export class DaemonSets extends React.Component { +export class DaemonSets extends React.Component { getPodsLength(daemonSet: DaemonSet) { return daemonSetStore.getChildPods(daemonSet).length; } render() { return ( - daemonSet.getName(), - [columnId.namespace]: daemonSet => daemonSet.getNs(), - [columnId.pods]: daemonSet => this.getPodsLength(daemonSet), - [columnId.age]: daemonSet => -daemonSet.getCreationTimestamp(), - }} - searchFilters={[ - daemonSet => daemonSet.getSearchFields(), - daemonSet => daemonSet.getLabels(), - ]} - renderHeaderTitle="Daemon Sets" - renderTableHeader={[ - { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, - { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, - { title: "Pods", className: "pods", sortBy: columnId.pods, id: columnId.pods }, - { className: "warning", showWithColumn: columnId.pods }, - { title: "Node Selector", className: "labels scrollable", id: columnId.labels }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={daemonSet => [ - daemonSet.getName(), - daemonSet.getNs(), - this.getPodsLength(daemonSet), - , - daemonSet.getNodeSelectors().map(selector => ( - - )), - , - ]} - /> + + daemonSet.getName(), + [columnId.namespace]: daemonSet => daemonSet.getNs(), + [columnId.pods]: daemonSet => this.getPodsLength(daemonSet), + [columnId.age]: daemonSet => -daemonSet.getCreationTimestamp(), + }} + searchFilters={[ + daemonSet => daemonSet.getSearchFields(), + daemonSet => daemonSet.getLabels(), + ]} + renderHeaderTitle="Daemon Sets" + renderTableHeader={[ + { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, + { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, + { title: "Pods", className: "pods", sortBy: columnId.pods, id: columnId.pods }, + { className: "warning", showWithColumn: columnId.pods }, + { title: "Node Selector", className: "labels scrollable", id: columnId.labels }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={daemonSet => [ + daemonSet.getName(), + daemonSet.getNs(), + this.getPodsLength(daemonSet), + , + daemonSet.getNodeSelectors().map(selector => ( + + )), + , + ]} + /> + ); } } diff --git a/src/renderer/components/+workloads-deployments/deployments-route-component.injectable.ts b/src/renderer/components/+workloads-deployments/deployments-route-component.injectable.ts new file mode 100644 index 0000000000..65ec34acb2 --- /dev/null +++ b/src/renderer/components/+workloads-deployments/deployments-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Deployments } from "./deployments"; +import deploymentsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const deploymentsRouteComponentInjectable = getInjectable({ + id: "deployments-route-component", + + instantiate: (di) => ({ + route: di.inject(deploymentsRouteInjectable), + Component: Deployments, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default deploymentsRouteComponentInjectable; diff --git a/src/renderer/components/+workloads-deployments/deployments-sidebar-items.injectable.tsx b/src/renderer/components/+workloads-deployments/deployments-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..9f06301b8c --- /dev/null +++ b/src/renderer/components/+workloads-deployments/deployments-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import deploymentsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable"; +import { workloadsSidebarItemId } from "../+workloads/workloads-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToDeploymentsInjectable from "../../../common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable"; + +const deploymentsSidebarItemsInjectable = getInjectable({ + id: "deployments-sidebar-items", + + instantiate: (di) => { + const route = di.inject(deploymentsRouteInjectable); + const navigateToDeployments = di.inject(navigateToDeploymentsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "deployments", + parentId: workloadsSidebarItemId, + title: "Deployments", + onClick: navigateToDeployments, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 30, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default deploymentsSidebarItemsInjectable; diff --git a/src/renderer/components/+workloads-deployments/deployments-store.injectable.ts b/src/renderer/components/+workloads-deployments/deployments-store.injectable.ts new file mode 100644 index 0000000000..38068fdf00 --- /dev/null +++ b/src/renderer/components/+workloads-deployments/deployments-store.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { deploymentStore } from "./deployments.store"; + +const deploymentsStoreInjectable = getInjectable({ + id: "deployments-store", + instantiate: () => deploymentStore, + causesSideEffects: true, +}); + +export default deploymentsStoreInjectable; diff --git a/src/renderer/components/+workloads-deployments/deployments.tsx b/src/renderer/components/+workloads-deployments/deployments.tsx index 44f1d55b8e..9a2b7f239e 100644 --- a/src/renderer/components/+workloads-deployments/deployments.tsx +++ b/src/renderer/components/+workloads-deployments/deployments.tsx @@ -7,7 +7,6 @@ import "./deployments.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import type { Deployment } from "../../../common/k8s-api/endpoints"; import { deploymentStore } from "./deployments.store"; import { eventStore } from "../+events/event.store"; @@ -16,7 +15,7 @@ import { cssNames } from "../../utils"; import kebabCase from "lodash/kebabCase"; import orderBy from "lodash/orderBy"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { DeploymentsRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -28,11 +27,8 @@ enum columnId { condition = "condition", } -export interface DeploymentsProps extends RouteComponentProps { -} - @observer -export class Deployments extends React.Component { +export class Deployments extends React.Component { renderPods(deployment: Deployment) { const { replicas, availableReplicas } = deployment.status; @@ -51,42 +47,44 @@ export class Deployments extends React.Component { render() { return ( - deployment.getName(), - [columnId.namespace]: deployment => deployment.getNs(), - [columnId.replicas]: deployment => deployment.getReplicas(), - [columnId.age]: deployment => -deployment.getCreationTimestamp(), - [columnId.condition]: deployment => deployment.getConditionsText(), - }} - searchFilters={[ - deployment => deployment.getSearchFields(), - deployment => deployment.getConditionsText(), - ]} - renderHeaderTitle="Deployments" - renderTableHeader={[ - { 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: "Pods", className: "pods", id: columnId.pods }, - { title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - { title: "Conditions", className: "conditions", sortBy: columnId.condition, id: columnId.condition }, - ]} - renderTableContents={deployment => [ - deployment.getName(), - , - deployment.getNs(), - this.renderPods(deployment), - deployment.getReplicas(), - , - this.renderConditions(deployment), - ]} - /> + + deployment.getName(), + [columnId.namespace]: deployment => deployment.getNs(), + [columnId.replicas]: deployment => deployment.getReplicas(), + [columnId.age]: deployment => -deployment.getCreationTimestamp(), + [columnId.condition]: deployment => deployment.getConditionsText(), + }} + searchFilters={[ + deployment => deployment.getSearchFields(), + deployment => deployment.getConditionsText(), + ]} + renderHeaderTitle="Deployments" + renderTableHeader={[ + { 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: "Pods", className: "pods", id: columnId.pods }, + { title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + { title: "Conditions", className: "conditions", sortBy: columnId.condition, id: columnId.condition }, + ]} + renderTableContents={deployment => [ + deployment.getName(), + , + deployment.getNs(), + this.renderPods(deployment), + deployment.getReplicas(), + , + this.renderConditions(deployment), + ]} + /> + ); } } diff --git a/src/renderer/components/+workloads-jobs/jobs-route-component.injectable.ts b/src/renderer/components/+workloads-jobs/jobs-route-component.injectable.ts new file mode 100644 index 0000000000..e060cf95ee --- /dev/null +++ b/src/renderer/components/+workloads-jobs/jobs-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Jobs } from "./jobs"; +import jobsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const jobsRouteComponentInjectable = getInjectable({ + id: "jobs-route-component", + + instantiate: (di) => ({ + route: di.inject(jobsRouteInjectable), + Component: Jobs, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default jobsRouteComponentInjectable; diff --git a/src/renderer/components/+workloads-jobs/jobs-sidebar-items.injectable.tsx b/src/renderer/components/+workloads-jobs/jobs-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..d89d6a2874 --- /dev/null +++ b/src/renderer/components/+workloads-jobs/jobs-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import jobsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable"; +import { workloadsSidebarItemId } from "../+workloads/workloads-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToJobsInjectable from "../../../common/front-end-routing/routes/cluster/workloads/jobs/navigate-to-jobs.injectable"; + +const jobsSidebarItemsInjectable = getInjectable({ + id: "jobs-sidebar-items", + + instantiate: (di) => { + const route = di.inject(jobsRouteInjectable); + const navigateToJobs = di.inject(navigateToJobsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "jobs", + parentId: workloadsSidebarItemId, + title: "Jobs", + onClick: navigateToJobs, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 70, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default jobsSidebarItemsInjectable; diff --git a/src/renderer/components/+workloads-jobs/jobs-store.injectable.ts b/src/renderer/components/+workloads-jobs/jobs-store.injectable.ts new file mode 100644 index 0000000000..773706b8d6 --- /dev/null +++ b/src/renderer/components/+workloads-jobs/jobs-store.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { jobStore } from "./job.store"; + +const jobsStoreInjectable = getInjectable({ + id: "jobs-store", + instantiate: () => jobStore, + causesSideEffects: true, +}); + +export default jobsStoreInjectable; diff --git a/src/renderer/components/+workloads-jobs/jobs.tsx b/src/renderer/components/+workloads-jobs/jobs.tsx index d6d1e47c5b..0c94808014 100644 --- a/src/renderer/components/+workloads-jobs/jobs.tsx +++ b/src/renderer/components/+workloads-jobs/jobs.tsx @@ -7,13 +7,12 @@ import "./jobs.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { jobStore } from "./job.store"; import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import kebabCase from "lodash/kebabCase"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { JobsRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -24,52 +23,51 @@ enum columnId { age = "age", } -export interface JobsProps extends RouteComponentProps { -} - @observer -export class Jobs extends React.Component { +export class Jobs extends React.Component { render() { return ( - job.getName(), - [columnId.namespace]: job => job.getNs(), - [columnId.conditions]: job => job.getCondition() != null ? job.getCondition().type : "", - [columnId.age]: job => -job.getCreationTimestamp(), - }} - searchFilters={[ - job => job.getSearchFields(), - ]} - renderHeaderTitle="Jobs" - renderTableHeader={[ - { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, - { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, - { title: "Completions", className: "completions", id: columnId.completions }, - { className: "warning", showWithColumn: columnId.completions }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - { title: "Conditions", className: "conditions", sortBy: columnId.conditions, id: columnId.conditions }, - ]} - renderTableContents={job => { - const condition = job.getCondition(); + + job.getName(), + [columnId.namespace]: job => job.getNs(), + [columnId.conditions]: job => job.getCondition() != null ? job.getCondition().type : "", + [columnId.age]: job => -job.getCreationTimestamp(), + }} + searchFilters={[ + job => job.getSearchFields(), + ]} + renderHeaderTitle="Jobs" + renderTableHeader={[ + { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, + { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, + { title: "Completions", className: "completions", id: columnId.completions }, + { className: "warning", showWithColumn: columnId.completions }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + { title: "Conditions", className: "conditions", sortBy: columnId.conditions, id: columnId.conditions }, + ]} + renderTableContents={job => { + const condition = job.getCondition(); - return [ - job.getName(), - job.getNs(), - `${job.getCompletions()} / ${job.getDesiredCompletions()}`, - , - , - condition && { - title: condition.type, - className: kebabCase(condition.type), - }, - ]; - }} - /> + return [ + job.getName(), + job.getNs(), + `${job.getCompletions()} / ${job.getDesiredCompletions()}`, + , + , + condition && { + title: condition.type, + className: kebabCase(condition.type), + }, + ]; + }} + /> + ); } } diff --git a/src/renderer/components/+workloads-overview/overview-statuses.tsx b/src/renderer/components/+workloads-overview/overview-statuses.tsx index 1eb684b553..c69944d0a5 100644 --- a/src/renderer/components/+workloads-overview/overview-statuses.tsx +++ b/src/renderer/components/+workloads-overview/overview-statuses.tsx @@ -8,22 +8,13 @@ import "./overview-statuses.scss"; import React from "react"; import { observer } from "mobx-react"; import { OverviewWorkloadStatus } from "./overview-workload-status"; -import { Link } from "react-router-dom"; -import type { KubeResource } from "../../../common/rbac"; import { withInjectables } from "@ogre-tools/injectable-react"; import type { IComputedValue } from "mobx"; -import workloadsInjectable from "./workloads.injectable"; +import workloadsInjectable from "./workloads/workloads.injectable"; +import type { Workload } from "./workloads/workload-injection-token"; export interface OverviewStatusesProps {} -interface Workload { - resource: KubeResource; - amountOfItems: number; - href: string; - status: Record; - title: string; -} - interface Dependencies { workloads: IComputedValue; } @@ -32,18 +23,17 @@ const NonInjectedOverviewStatuses = observer( ({ workloads }: Dependencies & OverviewStatusesProps) => (
- {workloads.get() - .map(({ resource, title, href, status, amountOfItems }) => ( -
-
- - {title} ({amountOfItems}) - -
- - + {workloads.get().map((workload) => ( + + ))}
), diff --git a/src/renderer/components/+workloads-overview/overview.tsx b/src/renderer/components/+workloads-overview/overview.tsx index 527fc547b7..d4914972b9 100644 --- a/src/renderer/components/+workloads-overview/overview.tsx +++ b/src/renderer/components/+workloads-overview/overview.tsx @@ -7,7 +7,6 @@ import "./overview.scss"; import React from "react"; import { disposeOnUnmount, observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { eventStore } from "../+events/event.store"; import { podsStore } from "../+workloads-pods/pods.store"; import { deploymentStore } from "../+workloads-deployments/deployments.store"; @@ -16,7 +15,6 @@ import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store"; import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; import { jobStore } from "../+workloads-jobs/job.store"; import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; -import type { WorkloadsOverviewRouteParams } from "../../../common/routes"; import { IComputedValue, makeObservable, observable, reaction } from "mobx"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter"; import { Icon } from "../icon"; @@ -30,9 +28,7 @@ import type { Disposer } from "../../../common/utils"; import kubeWatchApiInjectable from "../../kube-watch-api/kube-watch-api.injectable"; import type { KubeWatchSubscribeStoreOptions } from "../../kube-watch-api/kube-watch-api"; import detailComponentsInjectable from "./detail-components.injectable"; - -export interface WorkloadsOverviewProps extends RouteComponentProps { -} +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; interface Dependencies { detailComponents: IComputedValue[]>; @@ -41,10 +37,10 @@ interface Dependencies { } @observer -class NonInjectedWorkloadsOverview extends React.Component { +class NonInjectedWorkloadsOverview extends React.Component { @observable loadErrors: string[] = []; - constructor(props: WorkloadsOverviewProps & Dependencies) { + constructor(props: Dependencies) { super(props); makeObservable(this); } @@ -93,30 +89,31 @@ class NonInjectedWorkloadsOverview extends React.Component -
-
Overview
- {this.renderLoadErrors()} - -
+ +
+
+
Overview
+ {this.renderLoadErrors()} + +
- {this.props.detailComponents.get().map((Details, index) => ( -
- ))} -
+ {this.props.detailComponents.get().map((Details, index) => ( +
+ ))} +
+ ); } } -export const WorkloadsOverview = withInjectables( +export const WorkloadsOverview = withInjectables( NonInjectedWorkloadsOverview, { - getProps: (di, props) => ({ + getProps: (di) => ({ detailComponents: di.inject(detailComponentsInjectable), clusterFrameContext: di.inject(clusterFrameContextInjectable), subscribeStores: di.inject(kubeWatchApiInjectable).subscribeStores, - ...props, }), }, ); diff --git a/src/renderer/components/+workloads-overview/workloads-overview-route-component.injectable.ts b/src/renderer/components/+workloads-overview/workloads-overview-route-component.injectable.ts new file mode 100644 index 0000000000..59a2a6d7ae --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads-overview-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { WorkloadsOverview } from "./overview"; +import workloadsOverviewRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/overview/workloads-overview-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const workloadsOverviewRouteComponentInjectable = getInjectable({ + id: "workloads-overview-route-component", + + instantiate: (di) => ({ + route: di.inject(workloadsOverviewRouteInjectable), + Component: WorkloadsOverview, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default workloadsOverviewRouteComponentInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads-overview-sidebar-items.injectable.tsx b/src/renderer/components/+workloads-overview/workloads-overview-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..c82c1e6e75 --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads-overview-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import workloadsOverviewRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/overview/workloads-overview-route.injectable"; +import { workloadsSidebarItemId } from "../+workloads/workloads-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToWorkloadsOverviewInjectable from "../../../common/front-end-routing/routes/cluster/workloads/overview/navigate-to-workloads-overview.injectable"; + +const workloadsOverviewSidebarItemsInjectable = getInjectable({ + id: "workloads-overview-sidebar-items", + + instantiate: (di) => { + const route = di.inject(workloadsOverviewRouteInjectable); + const navigateToWorkloadsOverview = di.inject(navigateToWorkloadsOverviewInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "overview", + parentId: workloadsSidebarItemId, + title: "Overview", + onClick: navigateToWorkloadsOverview, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 10, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default workloadsOverviewSidebarItemsInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads.injectable.ts b/src/renderer/components/+workloads-overview/workloads.injectable.ts deleted file mode 100644 index 646dcb6325..0000000000 --- a/src/renderer/components/+workloads-overview/workloads.injectable.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import type { KubeResource } from "../../../common/rbac"; -import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; -import { podsStore } from "../+workloads-pods/pods.store"; -import { deploymentStore } from "../+workloads-deployments/deployments.store"; -import { daemonSetStore } from "../+workloads-daemonsets/daemonsets.store"; -import { statefulSetStore } from "../+workloads-statefulsets/statefulset.store"; -import { replicaSetStore } from "../+workloads-replicasets/replicasets.store"; -import { jobStore } from "../+workloads-jobs/job.store"; -import { cronJobStore } from "../+workloads-cronjobs/cronjob.store"; -import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; -import namespaceStoreInjectable from "../+namespaces/namespace-store/namespace-store.injectable"; -import { workloads } from "./workloads"; - -const workloadsInjectable = getInjectable({ - id: "workloads", - - instantiate: (di) => - workloads({ - isAllowedResource: di.inject(isAllowedResourceInjectable), - namespaceStore: di.inject(namespaceStoreInjectable), - - workloadStores: new Map>([ - ["pods", podsStore], - ["deployments", deploymentStore], - ["daemonsets", daemonSetStore], - ["statefulsets", statefulSetStore], - ["replicasets", replicaSetStore], - ["jobs", jobStore], - ["cronjobs", cronJobStore], - ]), - }), -}); - -export default workloadsInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads.ts b/src/renderer/components/+workloads-overview/workloads.ts deleted file mode 100644 index f3db713e63..0000000000 --- a/src/renderer/components/+workloads-overview/workloads.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { computed } from "mobx"; -import type { KubeResource } from "../../../common/rbac"; -import type { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; -import { workloadURL } from "../../../common/routes"; -import { ResourceNames } from "../../utils/rbac"; -import type { NamespaceStore } from "../+namespaces/namespace-store/namespace.store"; -import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; - -interface Dependencies { - workloadStores: Map>; - isAllowedResource: IsAllowedResource; - namespaceStore: NamespaceStore; -} - -export const workloads = ({ - workloadStores, - isAllowedResource, - namespaceStore, -}: Dependencies) => - computed(() => - [...workloadStores.entries()] - .filter(([resource]) => isAllowedResource(resource)) - .map(([resource, store]) => { - const items = store.getAllByNs(namespaceStore.contextNamespaces); - - return { - resource, - href: workloadURL[resource](), - amountOfItems: items.length, - status: store.getStatuses(items), - title: ResourceNames[resource], - }; - }), - ); diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts new file mode 100644 index 0000000000..a9327ddca2 --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads/implementations/cron-jobs-workload.injectable.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { workloadInjectionToken } from "../workload-injection-token"; +import { ResourceNames } from "../../../../utils/rbac"; +import navigateToPodsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable"; +import { KubeResourceEnum } from "../../../../../common/rbac"; +import namespaceStoreInjectable from "../../../+namespaces/namespace-store/namespace-store.injectable"; +import cronJobsStoreInjectable from "../../../+workloads-cronjobs/cron-jobs-store.injectable"; +import { computed } from "mobx"; + +const cronJobsWorkloadInjectable = getInjectable({ + id: "cron-jobs-workload", + + instantiate: (di) => { + const navigate = di.inject(navigateToPodsInjectable); + const namespaceStore = di.inject(namespaceStoreInjectable); + const store = di.inject(cronJobsStoreInjectable); + + return { + resourceName: KubeResourceEnum.cronJobs, + open: navigate, + + amountOfItems: computed( + () => store.getAllByNs(namespaceStore.contextNamespaces).length, + ), + + status: computed(() => + store.getStatuses(store.getAllByNs(namespaceStore.contextNamespaces)), + ), + + title: ResourceNames.cronjobs, + orderNumber: 70, + }; + }, + + injectionToken: workloadInjectionToken, +}); + +export default cronJobsWorkloadInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts new file mode 100644 index 0000000000..afdc061b0e --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads/implementations/daemonsets-workload.injectable.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { workloadInjectionToken } from "../workload-injection-token"; +import { ResourceNames } from "../../../../utils/rbac"; +import { KubeResourceEnum } from "../../../../../common/rbac"; +import namespaceStoreInjectable from "../../../+namespaces/namespace-store/namespace-store.injectable"; +import daemonsetsStoreInjectable from "../../../+workloads-daemonsets/daemonsets-store.injectable"; +import navigateToDaemonsetsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/daemonsets/navigate-to-daemonsets.injectable"; +import { computed } from "mobx"; + +const daemonsetsWorkloadInjectable = getInjectable({ + id: "daemonsets-workload", + + instantiate: (di) => { + const navigate = di.inject(navigateToDaemonsetsInjectable); + const namespaceStore = di.inject(namespaceStoreInjectable); + const store = di.inject(daemonsetsStoreInjectable); + + return { + resourceName: KubeResourceEnum.daemonsets, + open: navigate, + + amountOfItems: computed( + () => store.getAllByNs(namespaceStore.contextNamespaces).length, + ), + + status: computed(() => + store.getStatuses(store.getAllByNs(namespaceStore.contextNamespaces)), + ), + + title: ResourceNames.daemonsets, + orderNumber: 30, + }; + }, + + injectionToken: workloadInjectionToken, +}); + +export default daemonsetsWorkloadInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts new file mode 100644 index 0000000000..d4ded7c5ef --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads/implementations/deployments-workload.injectable.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { workloadInjectionToken } from "../workload-injection-token"; +import { ResourceNames } from "../../../../utils/rbac"; +import { KubeResourceEnum } from "../../../../../common/rbac"; +import namespaceStoreInjectable from "../../../+namespaces/namespace-store/namespace-store.injectable"; +import deploymentsStoreInjectable from "../../../+workloads-deployments/deployments-store.injectable"; +import navigateToDeploymentsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable"; +import { computed } from "mobx"; + +const deploymentsWorkloadInjectable = getInjectable({ + id: "deployments-workload", + + instantiate: (di) => { + const navigate = di.inject(navigateToDeploymentsInjectable); + const namespaceStore = di.inject(namespaceStoreInjectable); + const store = di.inject(deploymentsStoreInjectable); + + return { + resourceName: KubeResourceEnum.deployments, + open: navigate, + + amountOfItems: computed( + () => store.getAllByNs(namespaceStore.contextNamespaces).length, + ), + + status: computed(() => + store.getStatuses(store.getAllByNs(namespaceStore.contextNamespaces)), + ), + + title: ResourceNames.deployments, + orderNumber: 20, + }; + }, + + injectionToken: workloadInjectionToken, +}); + +export default deploymentsWorkloadInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts new file mode 100644 index 0000000000..8a14c9c4f1 --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads/implementations/jobs-workload.injectable.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { workloadInjectionToken } from "../workload-injection-token"; +import { ResourceNames } from "../../../../utils/rbac"; +import { KubeResourceEnum } from "../../../../../common/rbac"; +import namespaceStoreInjectable from "../../../+namespaces/namespace-store/namespace-store.injectable"; +import jobsStoreInjectable from "../../../+workloads-jobs/jobs-store.injectable"; +import navigateToJobsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/jobs/navigate-to-jobs.injectable"; +import { computed } from "mobx"; + +const jobsWorkloadInjectable = getInjectable({ + id: "jobs-workload", + + instantiate: (di) => { + const navigate = di.inject(navigateToJobsInjectable); + const namespaceStore = di.inject(namespaceStoreInjectable); + const store = di.inject(jobsStoreInjectable); + + return { + resourceName: KubeResourceEnum.jobs, + open: navigate, + + amountOfItems: computed( + () => store.getAllByNs(namespaceStore.contextNamespaces).length, + ), + + status: computed(() => + store.getStatuses(store.getAllByNs(namespaceStore.contextNamespaces)), + ), + + title: ResourceNames.jobs, + orderNumber: 60, + }; + }, + + injectionToken: workloadInjectionToken, +}); + +export default jobsWorkloadInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts new file mode 100644 index 0000000000..1a800f70d2 --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads/implementations/pods-workload.injectable.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { workloadInjectionToken } from "../workload-injection-token"; +import { ResourceNames } from "../../../../utils/rbac"; +import navigateToPodsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/pods/navigate-to-pods.injectable"; +import { KubeResourceEnum } from "../../../../../common/rbac"; +import podsStoreInjectable from "../../../+workloads-pods/pods-store.injectable"; +import namespaceStoreInjectable from "../../../+namespaces/namespace-store/namespace-store.injectable"; +import { computed } from "mobx"; + +const podsWorkloadInjectable = getInjectable({ + id: "pods-workload", + + instantiate: (di) => { + const navigate = di.inject(navigateToPodsInjectable); + const namespaceStore = di.inject(namespaceStoreInjectable); + const store = di.inject(podsStoreInjectable); + + return { + resourceName: KubeResourceEnum.pods, + open: navigate, + + amountOfItems: computed( + () => store.getAllByNs(namespaceStore.contextNamespaces).length, + ), + + status: computed(() => + store.getStatuses(store.getAllByNs(namespaceStore.contextNamespaces)), + ), + + title: ResourceNames.pods, + orderNumber: 10, + }; + }, + + injectionToken: workloadInjectionToken, +}); + +export default podsWorkloadInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts new file mode 100644 index 0000000000..ce94017fef --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads/implementations/replicasets-workload.injectable.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { workloadInjectionToken } from "../workload-injection-token"; +import { ResourceNames } from "../../../../utils/rbac"; +import navigateToPodsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable"; +import { KubeResourceEnum } from "../../../../../common/rbac"; +import namespaceStoreInjectable from "../../../+namespaces/namespace-store/namespace-store.injectable"; +import replicasetsStoreInjectable from "../../../+workloads-replicasets/replicasets-store.injectable"; +import { computed } from "mobx"; + +const replicasetsWorkloadInjectable = getInjectable({ + id: "replicasets-workload", + + instantiate: (di) => { + const navigate = di.inject(navigateToPodsInjectable); + const namespaceStore = di.inject(namespaceStoreInjectable); + const store = di.inject(replicasetsStoreInjectable); + + return { + resourceName: KubeResourceEnum.replicasets, + open: navigate, + + amountOfItems: computed( + () => store.getAllByNs(namespaceStore.contextNamespaces).length, + ), + + status: computed(() => + store.getStatuses(store.getAllByNs(namespaceStore.contextNamespaces)), + ), + + title: ResourceNames.replicasets, + orderNumber: 50, + }; + }, + + injectionToken: workloadInjectionToken, +}); + +export default replicasetsWorkloadInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts b/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts new file mode 100644 index 0000000000..04a9482fc4 --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads/implementations/statefulsets-workload.injectable.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { workloadInjectionToken } from "../workload-injection-token"; +import { ResourceNames } from "../../../../utils/rbac"; +import navigateToPodsInjectable from "../../../../../common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable"; +import { KubeResourceEnum } from "../../../../../common/rbac"; +import namespaceStoreInjectable from "../../../+namespaces/namespace-store/namespace-store.injectable"; +import statefulsetsStoreInjectable from "../../../+workloads-statefulsets/statefulsets-store.injectable"; +import { computed } from "mobx"; + +const statefulsetsWorkloadInjectable = getInjectable({ + id: "statefulsets-workload", + + instantiate: (di) => { + const navigate = di.inject(navigateToPodsInjectable); + const namespaceStore = di.inject(namespaceStoreInjectable); + const store = di.inject(statefulsetsStoreInjectable); + + return { + resourceName: KubeResourceEnum.statefulsets, + open: navigate, + + amountOfItems: computed( + () => store.getAllByNs(namespaceStore.contextNamespaces).length, + ), + + status: computed(() => + store.getStatuses(store.getAllByNs(namespaceStore.contextNamespaces)), + ), + + title: ResourceNames.statefulsets, + orderNumber: 40, + }; + }, + + injectionToken: workloadInjectionToken, +}); + +export default statefulsetsWorkloadInjectable; diff --git a/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts b/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts new file mode 100644 index 0000000000..9055f11bf0 --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads/workload-injection-token.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { IComputedValue } from "mobx"; +import type { KubeResourceEnum } from "../../../../common/rbac"; + +export interface Workload { + resourceName: KubeResourceEnum; + open: () => void; + amountOfItems: IComputedValue; + status: IComputedValue>; + title: string; + orderNumber: number; +} + +export const workloadInjectionToken = getInjectionToken({ + id: "workload-injection-token", +}); diff --git a/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts b/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts new file mode 100644 index 0000000000..3354fe85bc --- /dev/null +++ b/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { pipeline } from "@ogre-tools/fp"; +import { getInjectable } from "@ogre-tools/injectable"; +import { filter, sortBy as sortByWithBadTyping } from "lodash/fp"; +import { computed } from "mobx"; +import { Workload, workloadInjectionToken } from "./workload-injection-token"; +import isAllowedResourceInjectable from "../../../../common/utils/is-allowed-resource.injectable"; +import type { KubeResourceEnum } from "../../../../common/rbac"; + +const sortBy = + (propertyPath: string) => + (collection: Collection[]) => + sortByWithBadTyping(propertyPath, collection); + +const workloadsInjectable = getInjectable({ + id: "workloads", + + instantiate: (di) => { + const workloads = di.injectMany(workloadInjectionToken); + + const isAllowedResource = (resourceName: KubeResourceEnum) => + di.inject(isAllowedResourceInjectable, resourceName); + + return computed(() => + pipeline( + workloads, + + filter((workload: Workload) => { + const isAllowed = isAllowedResource(workload.resourceName); + + return isAllowed.get(); + }), + + sortBy("orderNumber"), + ), + ); + }, +}); + +export default workloadsInjectable; diff --git a/src/renderer/components/+workloads-pods/pod-container-port.tsx b/src/renderer/components/+workloads-pods/pod-container-port.tsx index 386554bec3..1e38f377a4 100644 --- a/src/renderer/components/+workloads-pods/pod-container-port.tsx +++ b/src/renderer/components/+workloads-pods/pod-container-port.tsx @@ -13,19 +13,14 @@ import { cssNames } from "../../utils"; import { Notifications } from "../notifications"; import { Button } from "../button"; import type { ForwardedPort } from "../../port-forward"; -import { - aboutPortForwarding, - notifyErrorPortForwarding, - openPortForward, - PortForwardStore, - predictProtocol, -} from "../../port-forward"; - +import { openPortForward, PortForwardStore, predictProtocol } from "../../port-forward"; import { Spinner } from "../spinner"; import { withInjectables } from "@ogre-tools/injectable-react"; import portForwardStoreInjectable from "../../port-forward/port-forward-store/port-forward-store.injectable"; import portForwardDialogModelInjectable from "../../port-forward/port-forward-dialog-model/port-forward-dialog-model.injectable"; import logger from "../../../common/logger"; +import aboutPortForwardingInjectable from "../../port-forward/about-port-forwarding.injectable"; +import notifyErrorPortForwardingInjectable from "../../port-forward/notify-error-port-forwarding.injectable"; export interface PodContainerPortProps { pod: Pod; @@ -39,6 +34,8 @@ export interface PodContainerPortProps { interface Dependencies { portForwardStore: PortForwardStore; openPortForwardDialog: (item: ForwardedPort, options: { openInBrowser: boolean; onClose: () => void }) => void; + aboutPortForwarding: () => void; + notifyErrorPortForwarding: (message: string) => void; } @observer @@ -119,10 +116,10 @@ class NonInjectedPodContainerPort extends React.Component ({ portForwardStore: di.inject(portForwardStoreInjectable), openPortForwardDialog: di.inject(portForwardDialogModelInjectable).open, + aboutPortForwarding: di.inject(aboutPortForwardingInjectable), + notifyErrorPortForwarding: di.inject(notifyErrorPortForwardingInjectable), ...props, }), }, diff --git a/src/renderer/components/+workloads-pods/pods-route-component.injectable.ts b/src/renderer/components/+workloads-pods/pods-route-component.injectable.ts new file mode 100644 index 0000000000..6ab45945cb --- /dev/null +++ b/src/renderer/components/+workloads-pods/pods-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { Pods } from "./pods"; +import podsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const podsRouteComponentInjectable = getInjectable({ + id: "pods-route-component", + + instantiate: (di) => ({ + route: di.inject(podsRouteInjectable), + Component: Pods, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default podsRouteComponentInjectable; diff --git a/src/renderer/components/+workloads-pods/pods-sidebar-items.injectable.tsx b/src/renderer/components/+workloads-pods/pods-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..849db460e7 --- /dev/null +++ b/src/renderer/components/+workloads-pods/pods-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import podsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable"; +import { workloadsSidebarItemId } from "../+workloads/workloads-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToPodsInjectable from "../../../common/front-end-routing/routes/cluster/workloads/pods/navigate-to-pods.injectable"; + +const podsSidebarItemsInjectable = getInjectable({ + id: "pods-sidebar-items", + + instantiate: (di) => { + const route = di.inject(podsRouteInjectable); + const navigateToPods = di.inject(navigateToPodsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "pods", + parentId: workloadsSidebarItemId, + title: "Pods", + onClick: navigateToPods, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 20, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default podsSidebarItemsInjectable; diff --git a/src/renderer/components/+workloads-pods/pods-store.injectable.ts b/src/renderer/components/+workloads-pods/pods-store.injectable.ts new file mode 100644 index 0000000000..7515931f80 --- /dev/null +++ b/src/renderer/components/+workloads-pods/pods-store.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { podsStore } from "./pods.store"; + +const podsStoreInjectable = getInjectable({ + id: "pods-store", + instantiate: () => podsStore, + causesSideEffects: true, +}); + +export default podsStoreInjectable; diff --git a/src/renderer/components/+workloads-pods/pods.tsx b/src/renderer/components/+workloads-pods/pods.tsx index b3f57db369..fb3e571d84 100644 --- a/src/renderer/components/+workloads-pods/pods.tsx +++ b/src/renderer/components/+workloads-pods/pods.tsx @@ -9,7 +9,6 @@ import React, { Fragment } from "react"; import { observer } from "mobx-react"; import { Link } from "react-router-dom"; import { podsStore } from "./pods.store"; -import type { RouteComponentProps } from "react-router"; import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { nodesApi, Pod } from "../../../common/k8s-api/endpoints"; @@ -21,8 +20,8 @@ import kebabCase from "lodash/kebabCase"; import { apiManager } from "../../../common/k8s-api/api-manager"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { Badge } from "../badge"; -import type { PodsRouteParams } from "../../../common/routes"; import { getDetailsUrl } from "../kube-detail-params"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -37,11 +36,8 @@ enum columnId { status = "status", } -export interface PodsProps extends RouteComponentProps { -} - @observer -export class Pods extends React.Component { +export class Pods extends React.Component { renderContainersStatus(pod: Pod) { return pod.getContainerStatuses().map(containerStatus => { const { name, state, ready } = containerStatus; @@ -76,72 +72,74 @@ export class Pods extends React.Component { render() { return ( - getConvertedParts(pod.getName()), - [columnId.namespace]: pod => pod.getNs(), - [columnId.containers]: pod => pod.getContainers().length, - [columnId.restarts]: pod => pod.getRestartsCount(), - [columnId.owners]: pod => pod.getOwnerRefs().map(ref => ref.kind), - [columnId.qos]: pod => pod.getQosClass(), - [columnId.node]: pod => pod.getNodeName(), - [columnId.age]: pod => -pod.getCreationTimestamp(), - [columnId.status]: pod => pod.getStatusMessage(), - }} - searchFilters={[ - pod => pod.getSearchFields(), - pod => pod.getStatusMessage(), - pod => pod.status.podIP, - pod => pod.getNodeName(), - ]} - renderHeaderTitle="Pods" - renderTableHeader={[ - { 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.getNs(), - this.renderContainersStatus(pod), - pod.getRestartsCount(), - pod.getOwnerRefs().map(ref => { - const { kind, name } = ref; - const detailsLink = getDetailsUrl(apiManager.lookupApiLink(ref, pod)); + + getConvertedParts(pod.getName()), + [columnId.namespace]: pod => pod.getNs(), + [columnId.containers]: pod => pod.getContainers().length, + [columnId.restarts]: pod => pod.getRestartsCount(), + [columnId.owners]: pod => pod.getOwnerRefs().map(ref => ref.kind), + [columnId.qos]: pod => pod.getQosClass(), + [columnId.node]: pod => pod.getNodeName(), + [columnId.age]: pod => -pod.getCreationTimestamp(), + [columnId.status]: pod => pod.getStatusMessage(), + }} + searchFilters={[ + pod => pod.getSearchFields(), + pod => pod.getStatusMessage(), + pod => pod.status.podIP, + pod => pod.getNodeName(), + ]} + renderHeaderTitle="Pods" + renderTableHeader={[ + { 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.getNs(), + this.renderContainersStatus(pod), + pod.getRestartsCount(), + pod.getOwnerRefs().map(ref => { + const { kind, name } = ref; + const detailsLink = getDetailsUrl(apiManager.lookupApiLink(ref, pod)); - return ( - - - {kind} + return ( + + + {kind} + + + ); + }), + pod.getNodeName() ? + + + {pod.getNodeName()} - ); - }), - pod.getNodeName() ? - - - {pod.getNodeName()} - - - : "", - pod.getQosClass(), - , - { title: pod.getStatusMessage(), className: kebabCase(pod.getStatusMessage()) }, - ]} - /> + : "", + pod.getQosClass(), + , + { title: pod.getStatusMessage(), className: kebabCase(pod.getStatusMessage()) }, + ]} + /> + ); } } diff --git a/src/renderer/components/+workloads-replicasets/replicasets-route-component.injectable.ts b/src/renderer/components/+workloads-replicasets/replicasets-route-component.injectable.ts new file mode 100644 index 0000000000..092d81f6d8 --- /dev/null +++ b/src/renderer/components/+workloads-replicasets/replicasets-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { ReplicaSets } from "./replicasets"; +import replicasetsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const replicasetsRouteComponentInjectable = getInjectable({ + id: "replicasets-route-component", + + instantiate: (di) => ({ + route: di.inject(replicasetsRouteInjectable), + Component: ReplicaSets, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default replicasetsRouteComponentInjectable; diff --git a/src/renderer/components/+workloads-replicasets/replicasets-sidebar-items.injectable.tsx b/src/renderer/components/+workloads-replicasets/replicasets-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..3fe3c0dca6 --- /dev/null +++ b/src/renderer/components/+workloads-replicasets/replicasets-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import replicasetsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable"; +import { workloadsSidebarItemId } from "../+workloads/workloads-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToReplicasetsInjectable from "../../../common/front-end-routing/routes/cluster/workloads/replicasets/navigate-to-replicasets.injectable"; + +const replicasetsSidebarItemsInjectable = getInjectable({ + id: "replicasets-sidebar-items", + + instantiate: (di) => { + const route = di.inject(replicasetsRouteInjectable); + const navigateToReplicasets = di.inject(navigateToReplicasetsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "replica-sets", + parentId: workloadsSidebarItemId, + title: "ReplicaSets", + onClick: navigateToReplicasets, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 60, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default replicasetsSidebarItemsInjectable; diff --git a/src/renderer/components/+workloads-replicasets/replicasets-store.injectable.ts b/src/renderer/components/+workloads-replicasets/replicasets-store.injectable.ts new file mode 100644 index 0000000000..5b66d75ca8 --- /dev/null +++ b/src/renderer/components/+workloads-replicasets/replicasets-store.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { replicaSetStore } from "./replicasets.store"; + +const replicasetsStoreInjectable = getInjectable({ + id: "replicasets-store", + instantiate: () => replicaSetStore, + causesSideEffects: true, +}); + +export default replicasetsStoreInjectable; diff --git a/src/renderer/components/+workloads-replicasets/replicasets.tsx b/src/renderer/components/+workloads-replicasets/replicasets.tsx index 08d597efb6..834b33aecb 100644 --- a/src/renderer/components/+workloads-replicasets/replicasets.tsx +++ b/src/renderer/components/+workloads-replicasets/replicasets.tsx @@ -9,10 +9,9 @@ import React from "react"; import { observer } from "mobx-react"; import { replicaSetStore } from "./replicasets.store"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { RouteComponentProps } from "react-router"; import { KubeObjectListLayout } from "../kube-object-list-layout"; -import type { ReplicaSetsRouteParams } from "../../../common/routes"; import { eventStore } from "../+events/event.store"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -24,49 +23,48 @@ enum columnId { age = "age", } -export interface ReplicaSetsProps extends RouteComponentProps { -} - @observer -export class ReplicaSets extends React.Component { +export class ReplicaSets extends React.Component { render() { return ( - replicaSet.getName(), - [columnId.namespace]: replicaSet => replicaSet.getNs(), - [columnId.desired]: replicaSet => replicaSet.getDesired(), - [columnId.current]: replicaSet => replicaSet.getCurrent(), - [columnId.ready]: replicaSet => replicaSet.getReady(), - [columnId.age]: replicaSet => -replicaSet.getCreationTimestamp(), - }} - searchFilters={[ - replicaSet => replicaSet.getSearchFields(), - ]} - renderHeaderTitle="Replica Sets" - renderTableHeader={[ - { 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: "Desired", className: "desired", sortBy: columnId.desired, id: columnId.desired }, - { title: "Current", className: "current", sortBy: columnId.current, id: columnId.current }, - { title: "Ready", className: "ready", sortBy: columnId.ready, id: columnId.ready }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={replicaSet => [ - replicaSet.getName(), - , - replicaSet.getNs(), - replicaSet.getDesired(), - replicaSet.getCurrent(), - replicaSet.getReady(), - , - ]} - /> + + replicaSet.getName(), + [columnId.namespace]: replicaSet => replicaSet.getNs(), + [columnId.desired]: replicaSet => replicaSet.getDesired(), + [columnId.current]: replicaSet => replicaSet.getCurrent(), + [columnId.ready]: replicaSet => replicaSet.getReady(), + [columnId.age]: replicaSet => -replicaSet.getCreationTimestamp(), + }} + searchFilters={[ + replicaSet => replicaSet.getSearchFields(), + ]} + renderHeaderTitle="Replica Sets" + renderTableHeader={[ + { 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: "Desired", className: "desired", sortBy: columnId.desired, id: columnId.desired }, + { title: "Current", className: "current", sortBy: columnId.current, id: columnId.current }, + { title: "Ready", className: "ready", sortBy: columnId.ready, id: columnId.ready }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={replicaSet => [ + replicaSet.getName(), + , + replicaSet.getNs(), + replicaSet.getDesired(), + replicaSet.getCurrent(), + replicaSet.getReady(), + , + ]} + /> + ); } } diff --git a/src/renderer/components/+workloads-statefulsets/statefulsets-route-component.injectable.ts b/src/renderer/components/+workloads-statefulsets/statefulsets-route-component.injectable.ts new file mode 100644 index 0000000000..0f461e4735 --- /dev/null +++ b/src/renderer/components/+workloads-statefulsets/statefulsets-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { StatefulSets } from "./statefulsets"; +import statefulsetsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const statefulsetsRouteComponentInjectable = getInjectable({ + id: "statefulsets-route-component", + + instantiate: (di) => ({ + route: di.inject(statefulsetsRouteInjectable), + Component: StatefulSets, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default statefulsetsRouteComponentInjectable; diff --git a/src/renderer/components/+workloads-statefulsets/statefulsets-sidebar-items.injectable.tsx b/src/renderer/components/+workloads-statefulsets/statefulsets-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..db78a8eac8 --- /dev/null +++ b/src/renderer/components/+workloads-statefulsets/statefulsets-sidebar-items.injectable.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; + +import statefulsetsRouteInjectable from "../../../common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable"; +import { workloadsSidebarItemId } from "../+workloads/workloads-sidebar-items.injectable"; +import { sidebarItemsInjectionToken } from "../layout/sidebar-items.injectable"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import navigateToStatefulsetsInjectable from "../../../common/front-end-routing/routes/cluster/workloads/statefulsets/navigate-to-statefulsets.injectable"; + +const statefulsetsSidebarItemsInjectable = getInjectable({ + id: "statefulsets-sidebar-items", + + instantiate: (di) => { + const route = di.inject(statefulsetsRouteInjectable); + const navigateToStatefulsets = di.inject(navigateToStatefulsetsInjectable); + const routeIsActive = di.inject(routeIsActiveInjectable, route); + + return computed(() => [ + { + id: "stateful-sets", + parentId: workloadsSidebarItemId, + title: "StatefulSets", + onClick: navigateToStatefulsets, + isActive: routeIsActive, + isVisible: route.isEnabled, + orderNumber: 50, + }, + ]); + }, + + injectionToken: sidebarItemsInjectionToken, +}); + +export default statefulsetsSidebarItemsInjectable; diff --git a/src/renderer/components/+workloads-statefulsets/statefulsets-store.injectable.ts b/src/renderer/components/+workloads-statefulsets/statefulsets-store.injectable.ts new file mode 100644 index 0000000000..832da43271 --- /dev/null +++ b/src/renderer/components/+workloads-statefulsets/statefulsets-store.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { statefulSetStore } from "./statefulset.store"; + +const statefulsetsStoreInjectable = getInjectable({ + id: "statefulset-store", + instantiate: () => statefulSetStore, + causesSideEffects: true, +}); + +export default statefulsetsStoreInjectable; diff --git a/src/renderer/components/+workloads-statefulsets/statefulsets.tsx b/src/renderer/components/+workloads-statefulsets/statefulsets.tsx index 4aed8d3a6f..43963a798c 100644 --- a/src/renderer/components/+workloads-statefulsets/statefulsets.tsx +++ b/src/renderer/components/+workloads-statefulsets/statefulsets.tsx @@ -7,14 +7,13 @@ import "./statefulsets.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import type { StatefulSet } from "../../../common/k8s-api/endpoints"; import { podsStore } from "../+workloads-pods/pods.store"; import { statefulSetStore } from "./statefulset.store"; import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; -import type { StatefulSetsRouteParams } from "../../../common/routes"; +import { SiblingsInTabLayout } from "../layout/siblings-in-tab-layout"; import { KubeObjectAge } from "../kube-object/age"; enum columnId { @@ -25,11 +24,8 @@ enum columnId { replicas = "replicas", } -export interface StatefulSetsProps extends RouteComponentProps { -} - @observer -export class StatefulSets extends React.Component { +export class StatefulSets extends React.Component { renderPods(statefulSet: StatefulSet) { const { readyReplicas, currentReplicas } = statefulSet.status; @@ -38,38 +34,40 @@ export class StatefulSets extends React.Component { render() { return ( - statefulSet.getName(), - [columnId.namespace]: statefulSet => statefulSet.getNs(), - [columnId.age]: statefulSet => -statefulSet.getCreationTimestamp(), - [columnId.replicas]: statefulSet => statefulSet.getReplicas(), - }} - searchFilters={[ - statefulSet => statefulSet.getSearchFields(), - ]} - renderHeaderTitle="Stateful Sets" - renderTableHeader={[ - { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, - { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, - { title: "Pods", className: "pods", id: columnId.pods }, - { title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas }, - { className: "warning", showWithColumn: columnId.replicas }, - { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, - ]} - renderTableContents={statefulSet => [ - statefulSet.getName(), - statefulSet.getNs(), - this.renderPods(statefulSet), - statefulSet.getReplicas(), - , - , - ]} - /> + + statefulSet.getName(), + [columnId.namespace]: statefulSet => statefulSet.getNs(), + [columnId.age]: statefulSet => -statefulSet.getCreationTimestamp(), + [columnId.replicas]: statefulSet => statefulSet.getReplicas(), + }} + searchFilters={[ + statefulSet => statefulSet.getSearchFields(), + ]} + renderHeaderTitle="Stateful Sets" + renderTableHeader={[ + { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name }, + { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace }, + { title: "Pods", className: "pods", id: columnId.pods }, + { title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas }, + { className: "warning", showWithColumn: columnId.replicas }, + { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age }, + ]} + renderTableContents={statefulSet => [ + statefulSet.getName(), + statefulSet.getNs(), + this.renderPods(statefulSet), + statefulSet.getReplicas(), + , + , + ]} + /> + ); } } diff --git a/src/renderer/components/+workloads/route-tabs.injectable.ts b/src/renderer/components/+workloads/route-tabs.injectable.ts deleted file mode 100644 index c263734af1..0000000000 --- a/src/renderer/components/+workloads/route-tabs.injectable.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import { WorkloadsOverview } from "../+workloads-overview/overview"; -import { Pods } from "../+workloads-pods"; -import { Deployments } from "../+workloads-deployments"; -import { DaemonSets } from "../+workloads-daemonsets"; -import { StatefulSets } from "../+workloads-statefulsets"; -import { Jobs } from "../+workloads-jobs"; -import { CronJobs } from "../+workloads-cronjobs"; -import { ReplicaSets } from "../+workloads-replicasets"; -import * as routes from "../../../common/routes"; -import type { IsAllowedResource } from "../../../common/utils/is-allowed-resource.injectable"; -import isAllowedResourceInjectable from "../../../common/utils/is-allowed-resource.injectable"; - -interface Dependencies { - isAllowedResource: IsAllowedResource; -} - -function getRouteTabs({ isAllowedResource }: Dependencies) { - return computed(() => { - const tabs: TabLayoutRoute[] = [ - { - title: "Overview", - component: WorkloadsOverview, - url: routes.overviewURL(), - routePath: routes.overviewRoute.path.toString(), - }, - ]; - - if (isAllowedResource("pods")) { - tabs.push({ - title: "Pods", - component: Pods, - url: routes.podsURL(), - routePath: routes.podsRoute.path.toString(), - }); - } - - if (isAllowedResource("deployments")) { - tabs.push({ - title: "Deployments", - component: Deployments, - url: routes.deploymentsURL(), - routePath: routes.deploymentsRoute.path.toString(), - }); - } - - if (isAllowedResource("daemonsets")) { - tabs.push({ - title: "DaemonSets", - component: DaemonSets, - url: routes.daemonSetsURL(), - routePath: routes.daemonSetsRoute.path.toString(), - }); - } - - if (isAllowedResource("statefulsets")) { - tabs.push({ - title: "StatefulSets", - component: StatefulSets, - url: routes.statefulSetsURL(), - routePath: routes.statefulSetsRoute.path.toString(), - }); - } - - if (isAllowedResource("replicasets")) { - tabs.push({ - title: "ReplicaSets", - component: ReplicaSets, - url: routes.replicaSetsURL(), - routePath: routes.replicaSetsRoute.path.toString(), - }); - } - - if (isAllowedResource("jobs")) { - tabs.push({ - title: "Jobs", - component: Jobs, - url: routes.jobsURL(), - routePath: routes.jobsRoute.path.toString(), - }); - } - - if (isAllowedResource("cronjobs")) { - tabs.push({ - title: "CronJobs", - component: CronJobs, - url: routes.cronJobsURL(), - routePath: routes.cronJobsRoute.path.toString(), - }); - } - - return tabs; - }); -} - -const workloadsRouteTabsInjectable = getInjectable({ - id: "workloads-route-tabs", - - instantiate: (di) => getRouteTabs({ - isAllowedResource: di.inject(isAllowedResourceInjectable), - }), -}); - -export default workloadsRouteTabsInjectable; diff --git a/src/renderer/components/+workloads/route.tsx b/src/renderer/components/+workloads/route.tsx deleted file mode 100644 index 9778ef5ee9..0000000000 --- a/src/renderer/components/+workloads/route.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import "./workloads.scss"; - -import React from "react"; -import { TabLayout, TabLayoutRoute } from "../layout/tab-layout"; -import type { IComputedValue } from "mobx"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import { observer } from "mobx-react"; -import workloadsRouteTabsInjectable from "./route-tabs.injectable"; - -export interface WorkloadsRouteProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedWorkloadsRoute = observer(({ routes }: Dependencies & WorkloadsRouteProps) => ( - -)); - -export const WorkloadsRoute = withInjectables(NonInjectedWorkloadsRoute, { - getProps: (di, props) => ({ - routes: di.inject(workloadsRouteTabsInjectable), - ...props, - }), -}); - diff --git a/src/renderer/components/+workloads/sidebar-item.tsx b/src/renderer/components/+workloads/sidebar-item.tsx deleted file mode 100644 index 4301994990..0000000000 --- a/src/renderer/components/+workloads/sidebar-item.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import React from "react"; -import { withInjectables } from "@ogre-tools/injectable-react"; -import type { IComputedValue } from "mobx"; -import { observer } from "mobx-react"; -import { workloadsRoute, workloadsURL } from "../../../common/routes"; -import { isActiveRoute } from "../../navigation"; -import { Icon } from "../icon"; -import { SidebarItem } from "../layout/sidebar-item"; -import type { TabLayoutRoute } from "../layout/tab-layout"; -import { renderTabRoutesSidebarItems } from "../layout/tab-routes-sidebar-items"; -import workloadsRouteTabsInjectable from "./route-tabs.injectable"; - -export interface WorkloadSidebarItemProps {} - -interface Dependencies { - routes: IComputedValue; -} - -const NonInjectedWorkloadsSidebarItem = observer(({ routes }: Dependencies & WorkloadSidebarItemProps) => { - const tabRoutes = routes.get(); - - return ( - } - > - {renderTabRoutesSidebarItems(tabRoutes)} - - ); -}); - -export const WorkloadsSidebarItem = withInjectables(NonInjectedWorkloadsSidebarItem, { - getProps: (di, props) => ({ - routes: di.inject(workloadsRouteTabsInjectable), - ...props, - }), -}); diff --git a/src/renderer/components/+workloads/workloads-sidebar-items.injectable.tsx b/src/renderer/components/+workloads/workloads-sidebar-items.injectable.tsx new file mode 100644 index 0000000000..9a0dfc86af --- /dev/null +++ b/src/renderer/components/+workloads/workloads-sidebar-items.injectable.tsx @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import { + SidebarItemRegistration, + sidebarItemsInjectionToken, +} from "../layout/sidebar-items.injectable"; +import { Icon } from "../icon"; +import React from "react"; +import { noop } from "lodash/fp"; + +export const workloadsSidebarItemId = "workloads"; + +const workloadsSidebarItemsInjectable = getInjectable({ + id: "workloads-sidebar-items", + + instantiate: () => + computed((): SidebarItemRegistration[] => [ + { + id: workloadsSidebarItemId, + parentId: null, + title: "Workloads", + getIcon: () => , + onClick: noop, + orderNumber: 20, + }, + ]), + + injectionToken: sidebarItemsInjectionToken, +}); + +export default workloadsSidebarItemsInjectable; diff --git a/src/renderer/components/button/button.tsx b/src/renderer/components/button/button.tsx index bd578b2e77..6e94403fba 100644 --- a/src/renderer/components/button/button.tsx +++ b/src/renderer/components/button/button.tsx @@ -29,7 +29,7 @@ export class Button extends React.PureComponent { render() { const { waiting, label, primary, accent, plain, hidden, active, big, - round, outlined, tooltip, light, children, ...btnProps + round, outlined, tooltip, light, children, tooltipOverrideDisabled, ...btnProps } = this.props; if (hidden) return null; diff --git a/src/renderer/components/cluster-manager/cluster-manager.tsx b/src/renderer/components/cluster-manager/cluster-manager.tsx index ce853d1c50..97854d8d11 100644 --- a/src/renderer/components/cluster-manager/cluster-manager.tsx +++ b/src/renderer/components/cluster-manager/cluster-manager.tsx @@ -6,30 +6,26 @@ import "./cluster-manager.scss"; import React from "react"; -import { Redirect, Route, Switch } from "react-router"; +import { Redirect } from "react-router"; import { disposeOnUnmount, observer } from "mobx-react"; import { StatusBar } from "../status-bar/status-bar"; -import { Catalog } from "../+catalog"; -import { Preferences } from "../+preferences"; -import { AddCluster } from "../+add-cluster"; -import { ClusterView } from "./cluster-view"; -import { GlobalPageRegistry } from "../../../extensions/registries/page-registry"; -import { Extensions } from "../+extensions"; import { HotbarMenu } from "../hotbar/hotbar-menu"; -import { EntitySettings } from "../+entity-settings"; -import { Welcome } from "../+welcome"; -import * as routes from "../../../common/routes"; import { DeleteClusterDialog } from "../delete-cluster-dialog"; -import { reaction } from "mobx"; -import { navigation } from "../../navigation"; -import { setEntityOnRouteMatch } from "../../api/helpers/general-active-sync"; -import { catalogURL, getPreviousTabUrl } from "../../../common/routes"; import { withInjectables } from "@ogre-tools/injectable-react"; import { TopBar } from "../layout/top-bar/top-bar"; import catalogPreviousActiveTabStorageInjectable from "../+catalog/catalog-previous-active-tab-storage/catalog-previous-active-tab-storage.injectable"; +import { IComputedValue, reaction } from "mobx"; +import currentRouteComponentInjectable from "../../routes/current-route-component.injectable"; +import { setEntityOnRouteMatch } from "../../api/helpers/general-active-sync"; +import { navigation } from "../../navigation"; +import welcomeRouteInjectable + from "../../../common/front-end-routing/routes/welcome/welcome-route.injectable"; +import { buildURL } from "../../../common/utils/buildUrl"; interface Dependencies { catalogPreviousActiveTabStorage: { get: () => string }; + currentRouteComponent: IComputedValue; + welcomeUrl: string; } @observer @@ -41,34 +37,19 @@ class NonInjectedClusterManager extends React.Component { } render() { + const Component = this.props.currentRouteComponent.get(); + + if (!Component) { + return ; + } + return (
- - - - - - - - - - {GlobalPageRegistry.getInstance() - .getItems() - .map(({ url, components: { Page }}) => ( - - ))} - - +
@@ -78,8 +59,20 @@ class NonInjectedClusterManager extends React.Component { } } -export const ClusterManager = withInjectables(NonInjectedClusterManager, { - getProps: di => ({ - catalogPreviousActiveTabStorage: di.inject(catalogPreviousActiveTabStorageInjectable), - }), -}); +export const ClusterManager = withInjectables( + NonInjectedClusterManager, + { + getProps: (di) => { + const welcomeRoute = di.inject(welcomeRouteInjectable); + + return { + catalogPreviousActiveTabStorage: di.inject( + catalogPreviousActiveTabStorageInjectable, + ), + + currentRouteComponent: di.inject(currentRouteComponentInjectable), + welcomeUrl: buildURL(welcomeRoute.path), + }; + }, + }, +); diff --git a/src/renderer/components/cluster-manager/cluster-status.tsx b/src/renderer/components/cluster-manager/cluster-status.tsx index e2e002c8b7..0199052f06 100644 --- a/src/renderer/components/cluster-manager/cluster-status.tsx +++ b/src/renderer/components/cluster-manager/cluster-status.tsx @@ -14,23 +14,28 @@ import { cssNames, IClassName } from "../../utils"; import { Button } from "../button"; import { Icon } from "../icon"; import { Spinner } from "../spinner"; -import { navigate } from "../../navigation"; -import { entitySettingsURL } from "../../../common/routes"; import type { KubeAuthUpdate } from "../../../common/cluster-types"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { requestClusterActivation } from "../../ipc"; +import type { NavigateToEntitySettings } from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import navigateToEntitySettingsInjectable from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; export interface ClusterStatusProps { className?: IClassName; cluster: Cluster; } +interface Dependencies { + navigateToEntitySettings: NavigateToEntitySettings; +} + @observer -export class ClusterStatus extends React.Component { +class NonInjectedClusterStatus extends React.Component { @observable authOutput: KubeAuthUpdate[] = []; @observable isReconnecting = false; - constructor(props: ClusterStatusProps) { + constructor(props: ClusterStatusProps & Dependencies) { super(props); makeObservable(this); } @@ -72,12 +77,7 @@ export class ClusterStatus extends React.Component { }; manageProxySettings = () => { - navigate(entitySettingsURL({ - params: { - entityId: this.cluster.id, - }, - fragment: "proxy", - })); + this.props.navigateToEntitySettings(this.cluster.id, "proxy"); }; renderAuthenticationOutput() { @@ -146,3 +146,14 @@ export class ClusterStatus extends React.Component { ); } } + +export const ClusterStatus = withInjectables( + NonInjectedClusterStatus, + + { + getProps: (di, props) => ({ + navigateToEntitySettings: di.inject(navigateToEntitySettingsInjectable), + ...props, + }), + }, +); diff --git a/src/renderer/components/cluster-manager/cluster-view-route-component.injectable.ts b/src/renderer/components/cluster-manager/cluster-view-route-component.injectable.ts new file mode 100644 index 0000000000..856f3647f0 --- /dev/null +++ b/src/renderer/components/cluster-manager/cluster-view-route-component.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { ClusterView } from "./cluster-view"; +import clusterViewRouteInjectable from "../../../common/front-end-routing/routes/cluster-view/cluster-view-route.injectable"; +import { routeSpecificComponentInjectionToken } from "../../routes/route-specific-component-injection-token"; + +const clusterViewRouteComponentInjectable = getInjectable({ + id: "cluster-view-route-component", + + instantiate: (di) => ({ + route: di.inject(clusterViewRouteInjectable), + Component: ClusterView, + }), + + injectionToken: routeSpecificComponentInjectionToken, +}); + +export default clusterViewRouteComponentInjectable; diff --git a/src/renderer/components/cluster-manager/cluster-view-route-parameters.injectable.ts b/src/renderer/components/cluster-manager/cluster-view-route-parameters.injectable.ts new file mode 100644 index 0000000000..6343286dff --- /dev/null +++ b/src/renderer/components/cluster-manager/cluster-view-route-parameters.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import routePathParametersInjectable from "../../routes/route-path-parameters.injectable"; +import clusterViewRouteInjectable from "../../../common/front-end-routing/routes/cluster-view/cluster-view-route.injectable"; + +const clusterViewRouteParametersInjectable = getInjectable({ + id: "cluster-view-route-parameters", + + instantiate: (di) => { + const route = di.inject(clusterViewRouteInjectable); + const pathParameters = di.inject(routePathParametersInjectable, route); + + return { + clusterId: computed(() => pathParameters.get().clusterId), + }; + }, +}); + +export default clusterViewRouteParametersInjectable; diff --git a/src/renderer/components/cluster-manager/cluster-view.tsx b/src/renderer/components/cluster-manager/cluster-view.tsx index 7f4c50ddff..bd5a010449 100644 --- a/src/renderer/components/cluster-manager/cluster-view.tsx +++ b/src/renderer/components/cluster-manager/cluster-view.tsx @@ -5,32 +5,36 @@ import "./cluster-view.scss"; import React from "react"; -import { computed, makeObservable, reaction } from "mobx"; +import { computed, IComputedValue, makeObservable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; -import type { RouteComponentProps } from "react-router"; import { ClusterStatus } from "./cluster-status"; import { ClusterFrameHandler } from "./lens-views"; import type { Cluster } from "../../../common/cluster/cluster"; import { ClusterStore } from "../../../common/cluster-store/cluster-store"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; -import { navigate } from "../../navigation"; -import { catalogURL, ClusterViewRouteParams } from "../../../common/routes"; import { requestClusterActivation } from "../../ipc"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import navigateToCatalogInjectable, { + NavigateToCatalog, +} from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; +import clusterViewRouteParametersInjectable from "./cluster-view-route-parameters.injectable"; -export interface ClusterViewProps extends RouteComponentProps { +interface Dependencies { + clusterId: IComputedValue; + navigateToCatalog: NavigateToCatalog; } @observer -export class ClusterView extends React.Component { +class NonInjectedClusterView extends React.Component { private store = ClusterStore.getInstance(); - constructor(props: ClusterViewProps) { + constructor(props: Dependencies) { super(props); makeObservable(this); } @computed get clusterId() { - return this.props.match.params.clusterId; + return this.props.clusterId.get(); } @computed get cluster(): Cluster | undefined { @@ -65,7 +69,7 @@ export class ClusterView extends React.Component { reaction(() => [this.cluster?.ready, this.cluster?.disconnected], ([, disconnected]) => { if (ClusterFrameHandler.getInstance().hasLoadedView(this.clusterId) && disconnected) { - navigate(catalogURL()); // redirect to catalog when active cluster get disconnected/not available + this.props.navigateToCatalog(); // redirect to catalog when active cluster get disconnected/not available } }), ]); @@ -89,3 +93,19 @@ export class ClusterView extends React.Component { ); } } + +export const ClusterView = withInjectables( + NonInjectedClusterView, + + { + getProps: (di) => { + const routeParameters = di.inject(clusterViewRouteParametersInjectable); + + return { + clusterId: routeParameters.clusterId, + navigateToCatalog: di.inject(navigateToCatalogInjectable), + }; + }, + }, +); + diff --git a/src/renderer/components/command-palette/command-container.tsx b/src/renderer/components/command-palette/command-container.tsx index 3744f3969e..dee3dd27c8 100644 --- a/src/renderer/components/command-palette/command-container.tsx +++ b/src/renderer/components/command-palette/command-container.tsx @@ -14,22 +14,22 @@ import commandOverlayInjectable, { CommandOverlay } from "./command-overlay.inje import { isMac } from "../../../common/vars"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { broadcastMessage, ipcRendererOn } from "../../../common/ipc"; -import { getMatchedClusterId } from "../../navigation"; import type { Disposer } from "../../utils"; import { withInjectables } from "@ogre-tools/injectable-react"; import windowAddEventListenerInjectable from "../../window/event-listener.injectable"; - -export interface CommandContainerProps { - clusterId?: ClusterId; -} +import hostedClusterInjectable from "../../../common/cluster-store/hosted-cluster.injectable"; +import type { IComputedValue } from "mobx"; +import matchedClusterIdInjectable from "../../navigation/matched-cluster-id.injectable"; interface Dependencies { addWindowEventListener: (type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions) => Disposer; commandOverlay: CommandOverlay; + clusterId?: ClusterId; + matchedClusterId: IComputedValue; } @observer -class NonInjectedCommandContainer extends React.Component { +class NonInjectedCommandContainer extends React.Component { private escHandler(event: KeyboardEvent) { const { commandOverlay } = this.props; @@ -41,7 +41,7 @@ class NonInjectedCommandContainer extends React.Component { const { commandOverlay } = this.props; - const clusterIsActive = getMatchedClusterId() !== undefined; + const clusterIsActive = this.props.matchedClusterId.get() !== undefined; if (clusterIsActive) { broadcastMessage(`command-palette:${catalogEntityRegistry.activeEntity.getId()}:open`); @@ -95,10 +95,19 @@ class NonInjectedCommandContainer extends React.Component(NonInjectedCommandContainer, { - getProps: (di, props) => ({ - addWindowEventListener: di.inject(windowAddEventListenerInjectable), - commandOverlay: di.inject(commandOverlayInjectable), - ...props, - }), -}); +export const CommandContainer = withInjectables( + NonInjectedCommandContainer, + { + getProps: (di, props) => { + const hostedCluster = di.inject(hostedClusterInjectable); + + return { + clusterId: hostedCluster?.id, + addWindowEventListener: di.inject(windowAddEventListenerInjectable), + commandOverlay: di.inject(commandOverlayInjectable), + matchedClusterId: di.inject(matchedClusterIdInjectable), + ...props, + }; + }, + }, +); diff --git a/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx b/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx index e951c7a713..cf544f7569 100644 --- a/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx +++ b/src/renderer/components/command-palette/registered-commands/internal-commands.injectable.tsx @@ -4,7 +4,6 @@ */ import React from "react"; -import * as routes from "../../../../common/routes"; import { EntitySettingRegistry, RegisteredEntitySetting } from "../../../../extensions/registries"; import { HotbarAddCommand } from "../../hotbar/hotbar-add-command"; import { HotbarRemoveCommand } from "../../hotbar/hotbar-remove-command"; @@ -16,6 +15,28 @@ import { getInjectable } from "@ogre-tools/injectable"; import commandOverlayInjectable from "../command-overlay.injectable"; import createTerminalTabInjectable from "../../dock/terminal/create-terminal-tab.injectable"; import type { DockTabCreate } from "../../dock/dock/store"; +import navigateToPreferencesInjectable from "../../../../common/front-end-routing/routes/preferences/navigate-to-preferences.injectable"; +import navigateToHelmChartsInjectable from "../../../../common/front-end-routing/routes/cluster/helm/charts/navigate-to-helm-charts.injectable"; +import navigateToHelmReleasesInjectable from "../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable"; +import navigateToConfigMapsInjectable from "../../../../common/front-end-routing/routes/cluster/config/config-maps/navigate-to-config-maps.injectable"; +import navigateToSecretsInjectable from "../../../../common/front-end-routing/routes/cluster/config/secrets/navigate-to-secrets.injectable"; +import navigateToResourceQuotasInjectable from "../../../../common/front-end-routing/routes/cluster/config/resource-quotas/navigate-to-resource-quotas.injectable"; +import navigateToLimitRangesInjectable from "../../../../common/front-end-routing/routes/cluster/config/limit-ranges/navigate-to-limit-ranges.injectable"; +import navigateToHorizontalPodAutoscalersInjectable from "../../../../common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/navigate-to-horizontal-pod-autoscalers.injectable"; +import navigateToPodDisruptionBudgetsInjectable from "../../../../common/front-end-routing/routes/cluster/config/pod-disruption-budgets/navigate-to-pod-disruption-budgets.injectable"; +import navigateToServicesInjectable from "../../../../common/front-end-routing/routes/cluster/network/services/navigate-to-services.injectable"; +import navigateToEndpointsInjectable from "../../../../common/front-end-routing/routes/cluster/network/endpoints/navigate-to-endpoints.injectable"; +import navigateToIngressesInjectable from "../../../../common/front-end-routing/routes/cluster/network/ingresses/navigate-to-ingresses.injectable"; +import navigateToNetworkPoliciesInjectable from "../../../../common/front-end-routing/routes/cluster/network/network-policies/navigate-to-network-policies.injectable"; +import navigateToNodesInjectable from "../../../../common/front-end-routing/routes/cluster/nodes/navigate-to-nodes.injectable"; +import navigateToPodsInjectable from "../../../../common/front-end-routing/routes/cluster/workloads/pods/navigate-to-pods.injectable"; +import navigateToDeploymentsInjectable from "../../../../common/front-end-routing/routes/cluster/workloads/deployments/navigate-to-deployments.injectable"; +import navigateToDaemonsetsInjectable from "../../../../common/front-end-routing/routes/cluster/workloads/daemonsets/navigate-to-daemonsets.injectable"; +import navigateToStatefulsetsInjectable from "../../../../common/front-end-routing/routes/cluster/workloads/statefulsets/navigate-to-statefulsets.injectable"; +import navigateToJobsInjectable from "../../../../common/front-end-routing/routes/cluster/workloads/jobs/navigate-to-jobs.injectable"; +import navigateToCronJobsInjectable from "../../../../common/front-end-routing/routes/cluster/workloads/cron-jobs/navigate-to-cron-jobs.injectable"; +import navigateToCustomResourcesInjectable from "../../../../common/front-end-routing/routes/cluster/custom-resources/custom-resources/navigate-to-custom-resources.injectable"; +import navigateToEntitySettingsInjectable from "../../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; export function isKubernetesClusterActive(context: CommandContext): boolean { return context.entity?.kind === "KubernetesCluster"; @@ -25,181 +46,200 @@ interface Dependencies { openCommandDialog: (component: React.ReactElement) => void; getEntitySettingItems: (kind: string, apiVersion: string, source?: string) => RegisteredEntitySetting[]; createTerminalTab: () => DockTabCreate; + + navigateToPreferences: () => void; + navigateToHelmCharts: () => void; + navigateToHelmReleases: () => void; + navigateToConfigMaps: () => void; + navigateToSecrets: () => void; + navigateToResourceQuotas: () => void; + navigateToLimitRanges: () => void; + navigateToHorizontalPodAutoscalers: () => void; + navigateToPodDisruptionBudgets: () => void; + navigateToServices: () => void; + navigateToEndpoints: () => void; + navigateToIngresses: () => void; + navigateToNetworkPolicies: () => void; + navigateToNodes: () => void; + navigateToPods: () => void; + navigateToDeployments: () => void; + navigateToDaemonsets: () => void; + navigateToStatefulsets: () => void; + navigateToJobs: () => void; + navigateToCronJobs: () => void; + navigateToCustomResources: () => void; + navigateToEntitySettings: (entityId: string) => void; } -function getInternalCommands({ openCommandDialog, getEntitySettingItems, createTerminalTab }: Dependencies): CommandRegistration[] { +function getInternalCommands(dependencies: Dependencies): CommandRegistration[] { return [ { id: "app.showPreferences", title: "Preferences: Open", - action: ({ navigate }) => navigate(routes.preferencesURL(), { - forceRootFrame: true, - }), + action: () => dependencies.navigateToPreferences(), }, { id: "cluster.viewHelmCharts", title: "Cluster: View Helm Charts", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.helmChartsURL()), + action: () => dependencies.navigateToHelmCharts(), }, { id: "cluster.viewHelmReleases", title: "Cluster: View Helm Releases", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.releaseURL()), + action: () => dependencies.navigateToHelmReleases(), }, { id: "cluster.viewConfigMaps", title: "Cluster: View ConfigMaps", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.configMapsURL()), + action: () => dependencies.navigateToConfigMaps(), }, { id: "cluster.viewSecrets", title: "Cluster: View Secrets", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.secretsURL()), + action: () => dependencies.navigateToSecrets(), }, { id: "cluster.viewResourceQuotas", title: "Cluster: View ResourceQuotas", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.resourceQuotaURL()), + action: () => dependencies.navigateToResourceQuotas(), }, { id: "cluster.viewLimitRanges", title: "Cluster: View LimitRanges", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.limitRangeURL()), + action: () => dependencies.navigateToLimitRanges(), }, { id: "cluster.viewHorizontalPodAutoscalers", title: "Cluster: View HorizontalPodAutoscalers (HPA)", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.hpaURL()), + action: () => dependencies.navigateToHorizontalPodAutoscalers(), }, { id: "cluster.viewPodDisruptionBudget", title: "Cluster: View PodDisruptionBudgets", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.pdbURL()), + action: () => dependencies.navigateToPodDisruptionBudgets(), }, { id: "cluster.viewServices", title: "Cluster: View Services", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.servicesURL()), + action: () => dependencies.navigateToServices(), }, { id: "cluster.viewEndpoints", title: "Cluster: View Endpoints", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.endpointURL()), + action: () => dependencies.navigateToEndpoints(), }, { id: "cluster.viewIngresses", title: "Cluster: View Ingresses", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.ingressURL()), + action: () => dependencies.navigateToIngresses(), }, { id: "cluster.viewNetworkPolicies", title: "Cluster: View NetworkPolicies", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.networkPoliciesURL()), + action: () => dependencies.navigateToNetworkPolicies, }, { id: "cluster.viewNodes", title: "Cluster: View Nodes", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.nodesURL()), + action: () => dependencies.navigateToNodes(), }, { id: "cluster.viewPods", title: "Cluster: View Pods", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.podsURL()), + action: () => dependencies.navigateToPods(), }, { id: "cluster.viewDeployments", title: "Cluster: View Deployments", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.deploymentsURL()), + action: () => dependencies.navigateToDeployments(), }, { id: "cluster.viewDaemonSets", title: "Cluster: View DaemonSets", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.daemonSetsURL()), + action: () => dependencies.navigateToDaemonsets(), }, { id: "cluster.viewStatefulSets", title: "Cluster: View StatefulSets", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.statefulSetsURL()), + action: () => dependencies.navigateToStatefulsets(), }, { id: "cluster.viewJobs", title: "Cluster: View Jobs", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.jobsURL()), + action: () => dependencies.navigateToJobs(), }, { id: "cluster.viewCronJobs", title: "Cluster: View CronJobs", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.cronJobsURL()), + action: () => dependencies.navigateToCronJobs(), }, { id: "cluster.viewCustomResourceDefinitions", title: "Cluster: View Custom Resource Definitions", isActive: isKubernetesClusterActive, - action: ({ navigate }) => navigate(routes.crdURL()), + action: () => dependencies.navigateToCustomResources(), }, { id: "entity.viewSettings", title: ({ entity }) => `${entity.kind}/${entity.getName()}: View Settings`, - action: ({ entity, navigate }) => navigate(`/entity/${entity.getId()}/settings`, { - forceRootFrame: true, - }), + action: ({ entity }) => dependencies.navigateToEntitySettings(entity.getId()), isActive: ({ entity }) => { if (!entity) { return false; } - return getEntitySettingItems(entity.kind, entity.apiVersion, entity.metadata.source).length > 0; + return dependencies.getEntitySettingItems(entity.kind, entity.apiVersion, entity.metadata.source).length > 0; }, }, { id: "cluster.openTerminal", title: "Cluster: Open terminal", - action: () => createTerminalTab(), + action: () => dependencies.createTerminalTab(), isActive: isKubernetesClusterActive, }, { id: "hotbar.switchHotbar", title: "Hotbar: Switch ...", - action: () => openCommandDialog(), + action: () => dependencies.openCommandDialog(), }, { id: "hotbar.addHotbar", title: "Hotbar: Add Hotbar ...", - action: () => openCommandDialog(), + action: () => dependencies.openCommandDialog(), }, { id: "hotbar.removeHotbar", title: "Hotbar: Remove Hotbar ...", - action: () => openCommandDialog(), + action: () => dependencies.openCommandDialog(), }, { id: "hotbar.renameHotbar", title: "Hotbar: Rename Hotbar ...", - action: () => openCommandDialog(), + action: () => dependencies.openCommandDialog(), }, { id: "catalog.searchEntities", title: "Catalog: Activate Entity ...", - action: () => openCommandDialog(), + action: () => dependencies.openCommandDialog(), }, ]; } @@ -213,6 +253,29 @@ const internalCommandsInjectable = getInjectable({ .getInstance() .getItemsForKind, createTerminalTab: di.inject(createTerminalTabInjectable), + + navigateToPreferences: di.inject(navigateToPreferencesInjectable), + navigateToHelmCharts: di.inject(navigateToHelmChartsInjectable), + navigateToHelmReleases: di.inject(navigateToHelmReleasesInjectable), + navigateToConfigMaps: di.inject(navigateToConfigMapsInjectable), + navigateToSecrets: di.inject(navigateToSecretsInjectable), + navigateToResourceQuotas: di.inject(navigateToResourceQuotasInjectable), + navigateToLimitRanges: di.inject(navigateToLimitRangesInjectable), + navigateToHorizontalPodAutoscalers: di.inject(navigateToHorizontalPodAutoscalersInjectable), + navigateToPodDisruptionBudgets: di.inject(navigateToPodDisruptionBudgetsInjectable), + navigateToServices: di.inject(navigateToServicesInjectable), + navigateToEndpoints: di.inject(navigateToEndpointsInjectable), + navigateToIngresses: di.inject(navigateToIngressesInjectable), + navigateToNetworkPolicies: di.inject(navigateToNetworkPoliciesInjectable), + navigateToNodes: di.inject(navigateToNodesInjectable), + navigateToPods: di.inject(navigateToPodsInjectable), + navigateToDeployments: di.inject(navigateToDeploymentsInjectable), + navigateToDaemonsets: di.inject(navigateToDaemonsetsInjectable), + navigateToStatefulsets: di.inject(navigateToStatefulsetsInjectable), + navigateToJobs: di.inject(navigateToJobsInjectable), + navigateToCronJobs: di.inject(navigateToCronJobsInjectable), + navigateToCustomResources: di.inject(navigateToCustomResourcesInjectable), + navigateToEntitySettings: di.inject(navigateToEntitySettingsInjectable), }), }); diff --git a/src/renderer/components/dock/create-resource/lens-templates.injectable.ts b/src/renderer/components/dock/create-resource/lens-templates.injectable.ts index a83b33011d..8bd757079b 100644 --- a/src/renderer/components/dock/create-resource/lens-templates.injectable.ts +++ b/src/renderer/components/dock/create-resource/lens-templates.injectable.ts @@ -12,7 +12,7 @@ import type { RawTemplates } from "./create-resource-templates.injectable"; interface Dependencies { readDir: (dirPath: string) => Promise; - readFile: (filePath: string, encoding: "utf-8") => Promise; + readFile: (filePath: string) => Promise; } async function getTemplates({ readDir, readFile }: Dependencies) { @@ -25,7 +25,7 @@ async function getTemplates({ readDir, readFile }: Dependencies) { for (const dirEntry of await readDir(templatesFolder)) { if (hasCorrectExtension(dirEntry)) { - templates.push([path.parse(dirEntry).name, await readFile(path.join(templatesFolder, dirEntry), "utf-8")]); + templates.push([path.parse(dirEntry).name, await readFile(path.join(templatesFolder, dirEntry))]); } } diff --git a/src/renderer/components/dock/install-chart/view.tsx b/src/renderer/components/dock/install-chart/view.tsx index 897f15d450..71767f0ef6 100644 --- a/src/renderer/components/dock/install-chart/view.tsx +++ b/src/renderer/components/dock/install-chart/view.tsx @@ -21,14 +21,14 @@ import { LogsDialog } from "../../dialog/logs-dialog"; import { Select, SelectOption } from "../../select"; import { Input } from "../../input"; import { EditorPanel } from "../editor-panel"; -import { navigate } from "../../../navigation"; -import { releaseURL } from "../../../../common/routes"; import type { IReleaseCreatePayload, IReleaseUpdateDetails } from "../../../../common/k8s-api/endpoints/helm-releases.api"; import { withInjectables } from "@ogre-tools/injectable-react"; import installChartTabStoreInjectable from "./store.injectable"; import dockStoreInjectable from "../dock/store.injectable"; import createReleaseInjectable from "../../+helm-releases/create-release/create-release.injectable"; import { Notifications } from "../../notifications"; +import type { NavigateToHelmReleases } from "../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable"; +import navigateToHelmReleasesInjectable from "../../../../common/front-end-routing/routes/cluster/helm/releases/navigate-to-helm-releases.injectable"; export interface InstallCharProps { tab: DockTab; @@ -38,6 +38,7 @@ interface Dependencies { createRelease: (payload: IReleaseCreatePayload) => Promise; installChartStore: InstallChartTabStore; dockStore: DockStore; + navigateToHelmReleases: NavigateToHelmReleases; } @observer @@ -74,12 +75,11 @@ class NonInjectedInstallChart extends Component viewRelease = () => { const { release } = this.releaseDetails; - navigate(releaseURL({ - params: { - name: release.name, - namespace: release.namespace, - }, - })); + this.props.navigateToHelmReleases({ + name: release.name, + namespace: release.namespace, + }); + this.props.dockStore.closeTab(this.tabId); }; @@ -226,6 +226,7 @@ export const InstallChart = withInjectables( createRelease: di.inject(createReleaseInjectable), installChartStore: di.inject(installChartTabStoreInjectable), dockStore: di.inject(dockStoreInjectable), + navigateToHelmReleases: di.inject(navigateToHelmReleasesInjectable), ...props, }), }, diff --git a/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx b/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx index 9de6a310e3..465f12de0a 100644 --- a/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx +++ b/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx @@ -10,7 +10,7 @@ import React from "react"; import type { DiContainer } from "@ogre-tools/injectable"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import { DiRender, renderFor } from "../../test-utils/renderFor"; -import hotbarManagerInjectable from "../../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable"; import { ThemeStore } from "../../../theme.store"; import { ConfirmDialog } from "../../confirm-dialog"; import type { HotbarStore } from "../../../../common/hotbar-store"; @@ -57,7 +57,7 @@ describe("", () => { }); it("renders w/o errors", async () => { - di.override(hotbarManagerInjectable, () => ({ + di.override(hotbarStoreInjectable, () => ({ hotbars: [mockHotbars["1"]], getById: (id: string) => mockHotbars[id], remove: () => { @@ -76,7 +76,7 @@ describe("", () => { it("calls remove if you click on the entry", async () => { const removeMock = jest.fn(); - di.override(hotbarManagerInjectable, () => ({ + di.override(hotbarStoreInjectable, () => ({ hotbars: [mockHotbars["1"]], getById: (id: string) => mockHotbars[id], remove: removeMock, diff --git a/src/renderer/components/hotbar/hotbar-add-command.tsx b/src/renderer/components/hotbar/hotbar-add-command.tsx index c283a6dc05..ee270dd866 100644 --- a/src/renderer/components/hotbar/hotbar-add-command.tsx +++ b/src/renderer/components/hotbar/hotbar-add-command.tsx @@ -9,7 +9,7 @@ import { Input, InputValidator } from "../input"; import type { CreateHotbarData, CreateHotbarOptions } from "../../../common/hotbar-types"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; -import hotbarManagerInjectable from "../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable"; interface Dependencies { @@ -50,7 +50,7 @@ const NonInjectedHotbarAddCommand = observer(({ closeCommandOverlay, addHotbar, export const HotbarAddCommand = withInjectables(NonInjectedHotbarAddCommand, { getProps: (di, props) => ({ closeCommandOverlay: di.inject(commandOverlayInjectable).close, - addHotbar: di.inject(hotbarManagerInjectable).add, + addHotbar: di.inject(hotbarStoreInjectable).add, uniqueHotbarName: di.inject(uniqueHotbarNameInjectable), ...props, }), diff --git a/src/renderer/components/hotbar/hotbar-remove-command.tsx b/src/renderer/components/hotbar/hotbar-remove-command.tsx index ed3cd275ca..238b6375c1 100644 --- a/src/renderer/components/hotbar/hotbar-remove-command.tsx +++ b/src/renderer/components/hotbar/hotbar-remove-command.tsx @@ -6,7 +6,7 @@ import React from "react"; import { observer } from "mobx-react"; import { Select } from "../select"; -import hotbarManagerInjectable from "../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; import { ConfirmDialog } from "../confirm-dialog"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; @@ -14,7 +14,7 @@ import type { Hotbar } from "../../../common/hotbar-types"; interface Dependencies { closeCommandOverlay: () => void; - hotbarManager: { + hotbarStore: { hotbars: Hotbar[]; getById: (id: string) => Hotbar | undefined; remove: (hotbar: Hotbar) => void; @@ -22,14 +22,14 @@ interface Dependencies { }; } -const NonInjectedHotbarRemoveCommand = observer(({ closeCommandOverlay, hotbarManager }: Dependencies) => { - const options = hotbarManager.hotbars.map(hotbar => ({ +const NonInjectedHotbarRemoveCommand = observer(({ closeCommandOverlay, hotbarStore }: Dependencies) => { + const options = hotbarStore.hotbars.map(hotbar => ({ value: hotbar.id, - label: hotbarManager.getDisplayLabel(hotbar), + label: hotbarStore.getDisplayLabel(hotbar), })); const onChange = (id: string): void => { - const hotbar = hotbarManager.getById(id); + const hotbar = hotbarStore.getById(id); if (!hotbar) { return; @@ -43,7 +43,7 @@ const NonInjectedHotbarRemoveCommand = observer(({ closeCommandOverlay, hotbarMa primary: false, accent: true, }, - ok: () => hotbarManager.remove(hotbar), + ok: () => hotbarStore.remove(hotbar), message: (

@@ -71,7 +71,7 @@ const NonInjectedHotbarRemoveCommand = observer(({ closeCommandOverlay, hotbarMa export const HotbarRemoveCommand = withInjectables(NonInjectedHotbarRemoveCommand, { getProps: (di, props) => ({ closeCommandOverlay: di.inject(commandOverlayInjectable).close, - hotbarManager: di.inject(hotbarManagerInjectable), + hotbarStore: di.inject(hotbarStoreInjectable), ...props, }), }); diff --git a/src/renderer/components/hotbar/hotbar-rename-command.tsx b/src/renderer/components/hotbar/hotbar-rename-command.tsx index e82bd44d3c..5e59447404 100644 --- a/src/renderer/components/hotbar/hotbar-rename-command.tsx +++ b/src/renderer/components/hotbar/hotbar-rename-command.tsx @@ -6,7 +6,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; import { Select } from "../select"; -import hotbarManagerInjectable from "../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; import { Input, InputValidator } from "../input"; import type { Hotbar } from "../../../common/hotbar-types"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -15,7 +15,7 @@ import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.i interface Dependencies { closeCommandOverlay: () => void; - hotbarManager: { + hotbarStore: { hotbars: Hotbar[]; getById: (id: string) => Hotbar | undefined; setHotbarName: (id: string, name: string) => void; @@ -24,25 +24,25 @@ interface Dependencies { uniqueHotbarName: InputValidator; } -const NonInjectedHotbarRenameCommand = observer(({ closeCommandOverlay, hotbarManager, uniqueHotbarName }: Dependencies) => { +const NonInjectedHotbarRenameCommand = observer(({ closeCommandOverlay, hotbarStore, uniqueHotbarName }: Dependencies) => { const [hotbarId, setHotbarId] = useState(""); const [hotbarName, setHotbarName] = useState(""); - const options = hotbarManager.hotbars.map(hotbar => ({ + const options = hotbarStore.hotbars.map(hotbar => ({ value: hotbar.id, - label: hotbarManager.getDisplayLabel(hotbar), + label: hotbarStore.getDisplayLabel(hotbar), })); const onSelect = (id: string) => { setHotbarId(id); - setHotbarName(hotbarManager.getById(id).name); + setHotbarName(hotbarStore.getById(id).name); }; const onSubmit = (name: string) => { if (!name.trim()) { return; } - hotbarManager.setHotbarName(hotbarId, name); + hotbarStore.setHotbarName(hotbarId, name); closeCommandOverlay(); }; @@ -84,7 +84,7 @@ const NonInjectedHotbarRenameCommand = observer(({ closeCommandOverlay, hotbarMa export const HotbarRenameCommand = withInjectables(NonInjectedHotbarRenameCommand, { getProps: (di, props) => ({ closeCommandOverlay: di.inject(commandOverlayInjectable).close, - hotbarManager: di.inject(hotbarManagerInjectable), + hotbarStore: di.inject(hotbarStoreInjectable), uniqueHotbarName: di.inject(uniqueHotbarNameInjectable), ...props, }), diff --git a/src/renderer/components/hotbar/hotbar-selector.tsx b/src/renderer/components/hotbar/hotbar-selector.tsx index 68a8f0e81b..59e39605e2 100644 --- a/src/renderer/components/hotbar/hotbar-selector.tsx +++ b/src/renderer/components/hotbar/hotbar-selector.tsx @@ -7,7 +7,7 @@ import styles from "./hotbar-selector.module.scss"; import React, { useRef, useState } from "react"; import { Icon } from "../icon"; import { Badge } from "../badge"; -import hotbarManagerInjectable from "../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../common/hotbar-store.injectable"; import { HotbarSwitchCommand } from "./hotbar-switch-command"; import { Tooltip, TooltipPosition } from "../tooltip"; import { observer } from "mobx-react"; @@ -17,7 +17,7 @@ import commandOverlayInjectable from "../command-palette/command-overlay.injecta import { cssNames } from "../../utils"; interface Dependencies { - hotbarManager: { + hotbarStore: { switchToPrevious: () => void; switchToNext: () => void; getActive: () => Hotbar; @@ -30,7 +30,7 @@ export interface HotbarSelectorProps extends Partial { hotbar: Hotbar; } -const NonInjectedHotbarSelector = observer(({ hotbar, hotbarManager, openCommandOverlay }: HotbarSelectorProps & Dependencies) => { +const NonInjectedHotbarSelector = observer(({ hotbar, hotbarStore, openCommandOverlay }: HotbarSelectorProps & Dependencies) => { const [tooltipVisible, setTooltipVisible] = useState(false); const tooltipTimeout = useRef(); @@ -59,13 +59,13 @@ const NonInjectedHotbarSelector = observer(({ hotbar, hotbarManager, openCommand onArrowClick(hotbarManager.switchToPrevious)} + onClick={() => onArrowClick(hotbarStore.switchToPrevious)} />

openCommandOverlay()} className={styles.Badge} onMouseEnter={onMouseEvent} @@ -79,14 +79,14 @@ const NonInjectedHotbarSelector = observer(({ hotbar, hotbarManager, openCommand {hotbar.name}
- onArrowClick(hotbarManager.switchToNext)} /> + onArrowClick(hotbarStore.switchToNext)} />
); }); export const HotbarSelector = withInjectables(NonInjectedHotbarSelector, { getProps: (di, props) => ({ - hotbarManager: di.inject(hotbarManagerInjectable), + hotbarStore: di.inject(hotbarStoreInjectable), openCommandOverlay: di.inject(commandOverlayInjectable).open, ...props, }), diff --git a/src/renderer/components/hotbar/hotbar-switch-command.tsx b/src/renderer/components/hotbar/hotbar-switch-command.tsx index d9e9d76c00..b4e8d83772 100644 --- a/src/renderer/components/hotbar/hotbar-switch-command.tsx +++ b/src/renderer/components/hotbar/hotbar-switch-command.tsx @@ -6,12 +6,15 @@ import React from "react"; import { observer } from "mobx-react"; import { Select } from "../select"; -import hotbarManagerInjectable from "../../../common/hotbar-store.injectable"; + +import hotbarStoreInjectable, { + HotbarStore, +} from "../../../common/hotbar-store.injectable"; + import type { CommandOverlay } from "../command-palette"; import { HotbarAddCommand } from "./hotbar-add-command"; import { HotbarRemoveCommand } from "./hotbar-remove-command"; import { HotbarRenameCommand } from "./hotbar-rename-command"; -import type { Hotbar } from "../../../common/hotbar-types"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; @@ -19,26 +22,20 @@ const addActionId = "__add__"; const removeActionId = "__remove__"; const renameActionId = "__rename__"; -interface HotbarManager { - hotbars: Hotbar[]; - setActiveHotbar: (id: string) => void; - getDisplayLabel: (hotbar: Hotbar) => string; -} - interface Dependencies { - hotbarManager: HotbarManager; + hotbarStore: HotbarStore; commandOverlay: CommandOverlay; } -function getHotbarSwitchOptions(hotbarManager: HotbarManager) { - const options = hotbarManager.hotbars.map(hotbar => ({ +function getHotbarSwitchOptions(hotbarStore: HotbarStore) { + const options = hotbarStore.hotbars.map(hotbar => ({ value: hotbar.id, - label: hotbarManager.getDisplayLabel(hotbar), + label: hotbarStore.getDisplayLabel(hotbar), })); options.push({ value: addActionId, label: "Add hotbar ..." }); - if (hotbarManager.hotbars.length > 1) { + if (hotbarStore.hotbars.length > 1) { options.push({ value: removeActionId, label: "Remove hotbar ..." }); } @@ -47,8 +44,8 @@ function getHotbarSwitchOptions(hotbarManager: HotbarManager) { return options; } -const NonInjectedHotbarSwitchCommand = observer(({ hotbarManager, commandOverlay }: Dependencies) => { - const options = getHotbarSwitchOptions(hotbarManager); +const NonInjectedHotbarSwitchCommand = observer(({ hotbarStore, commandOverlay }: Dependencies) => { + const options = getHotbarSwitchOptions(hotbarStore); const onChange = (idOrAction: string): void => { switch (idOrAction) { @@ -59,7 +56,7 @@ const NonInjectedHotbarSwitchCommand = observer(({ hotbarManager, commandOverlay case renameActionId: return commandOverlay.open(); default: - hotbarManager.setActiveHotbar(idOrAction); + hotbarStore.setActiveHotbar(idOrAction); commandOverlay.close(); } }; @@ -80,7 +77,7 @@ const NonInjectedHotbarSwitchCommand = observer(({ hotbarManager, commandOverlay export const HotbarSwitchCommand = withInjectables(NonInjectedHotbarSwitchCommand, { getProps: (di, props) => ({ - hotbarManager: di.inject(hotbarManagerInjectable), + hotbarStore: di.inject(hotbarStoreInjectable), commandOverlay: di.inject(commandOverlayInjectable), ...props, }), diff --git a/src/renderer/components/input/input.tsx b/src/renderer/components/input/input.tsx index 788bc142f0..903c1ba162 100644 --- a/src/renderer/components/input/input.tsx +++ b/src/renderer/components/input/input.tsx @@ -386,6 +386,8 @@ export class Input extends React.Component { {errors.map((error, i) =>

{error}

)}
); + + // TODO: Remove side-effect to allow deterministic unit testing const componentId = id || showErrorsAsTooltip ? getRandId({ prefix: "input_tooltip_id" }) : undefined; let tooltipError: React.ReactNode; diff --git a/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts b/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts index 815d73493b..fb9bdb6e43 100644 --- a/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts +++ b/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; -import hotbarManagerInjectable from "../../../../common/hotbar-store.injectable"; +import hotbarStoreInjectable from "../../../../common/hotbar-store.injectable"; import type { InputValidator } from "../input_validators"; const uniqueHotbarNameInjectable = getInjectable({ @@ -13,7 +13,7 @@ const uniqueHotbarNameInjectable = getInjectable({ instantiate: di => ({ condition: ({ required }) => required, message: () => "Hotbar with this name already exists", - validate: value => !di.inject(hotbarManagerInjectable).getByName(value), + validate: value => !di.inject(hotbarStoreInjectable).getByName(value), } as InputValidator), }); diff --git a/src/renderer/components/layout/extension-sidebar-item-registrator.injectable.tsx b/src/renderer/components/layout/extension-sidebar-item-registrator.injectable.tsx new file mode 100644 index 0000000000..d96081dec1 --- /dev/null +++ b/src/renderer/components/layout/extension-sidebar-item-registrator.injectable.tsx @@ -0,0 +1,84 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { DiContainer, getInjectable } from "@ogre-tools/injectable"; +import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; +import React from "react"; +import { extensionRegistratorInjectionToken } from "../../../extensions/extension-loader/extension-registrator-injection-token"; +import { SidebarItemRegistration, sidebarItemsInjectionToken } from "./sidebar-items.injectable"; +import { computed } from "mobx"; +import routesInjectable from "../../routes/routes.injectable"; +import { matches, noop } from "lodash/fp"; +import routeIsActiveInjectable from "../../routes/route-is-active.injectable"; +import { getExtensionRoutePath } from "../../routes/get-extension-route-path"; +import { navigateToRouteInjectionToken } from "../../../common/front-end-routing/navigate-to-route-injection-token"; + +const extensionSidebarItemRegistratorInjectable = getInjectable({ + id: "extension-sidebar-item-registrator", + + instantiate: + (di: DiContainer) => + async ( + extension: LensRendererExtension, + extensionInstallationCount: number, + ) => { + const sidebarItemsForExtensionInjectable = getInjectable({ + id: `sidebar-items-for-extension-${extension.sanitizedExtensionId}-instance-${extensionInstallationCount}`, + injectionToken: sidebarItemsInjectionToken, + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const routes = di.inject(routesInjectable); + + return computed((): SidebarItemRegistration[] => { + const extensionRoutes = routes.get().filter(matches({ extension })); + + return extension.clusterPageMenus.map((registration) => { + const targetRoutePath = getExtensionRoutePath( + extension, + registration.target?.pageId, + ); + + const targetRoute = extensionRoutes.find( + matches({ path: targetRoutePath }), + ); + + return { + id: `${extension.sanitizedExtensionId}-${registration.id}`, + extension, + orderNumber: 9999, + + parentId: registration.parentId + ? `${extension.sanitizedExtensionId}-${registration.parentId}` + : null, + + title: registration.title, + getIcon: registration.components.Icon + ? () => + : null, + + ...(targetRoute + ? { + onClick: () => navigateToRoute(targetRoute), + + isActive: di.inject( + routeIsActiveInjectable, + targetRoute, + ), + } + : { onClick: noop }), + }; + }); + }); + }, + }); + + // TODO: Transactional register + di.register(sidebarItemsForExtensionInjectable); + }, + + injectionToken: extensionRegistratorInjectionToken, +}); + +export default extensionSidebarItemRegistratorInjectable; diff --git a/src/renderer/components/layout/setting-layout.tsx b/src/renderer/components/layout/setting-layout.tsx index ade8625762..db92aff4af 100644 --- a/src/renderer/components/layout/setting-layout.tsx +++ b/src/renderer/components/layout/setting-layout.tsx @@ -8,9 +8,10 @@ import "./setting-layout.scss"; import React from "react"; import { observer } from "mobx-react"; import { cssNames, IClassName } from "../../utils"; -import { navigation } from "../../navigation"; -import { catalogURL } from "../../../common/routes"; import { CloseButton } from "./close-button"; +import { getLegacyGlobalDiForExtensionApi } from "../../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; +import observableHistoryInjectable from "../../navigation/observable-history.injectable"; export interface SettingLayoutProps extends React.DOMAttributes { className?: IClassName; @@ -19,16 +20,22 @@ export interface SettingLayoutProps extends React.DOMAttributes { contentGaps?: boolean; navigation?: React.ReactNode; back?: (evt: React.MouseEvent | KeyboardEvent) => void; + closeButtonProps?: { "data-testid"?: string }; } const defaultProps: Partial = { + closeButtonProps: {}, provideBackButtonNavigation: true, contentGaps: true, back: () => { - if (navigation.length <= 1) { - navigation.push(catalogURL()); + const di = getLegacyGlobalDiForExtensionApi(); + const navigateToCatalog = di.inject(navigateToCatalogInjectable); + const observableHistory = di.inject(observableHistoryInjectable); + + if (observableHistory.length <= 1) { + navigateToCatalog(); } else { - navigation.goBack(); + observableHistory.goBack(); } }, }; @@ -68,7 +75,7 @@ export class SettingLayout extends React.Component { render() { const { contentClass, provideBackButtonNavigation, - contentGaps, navigation, children, back, ...elemProps + contentGaps, navigation, children, back, closeButtonProps, ...elemProps } = this.props; const className = cssNames("SettingLayout", { showNavigation: navigation }, this.props.className); @@ -89,7 +96,8 @@ export class SettingLayout extends React.Component { { this.props.provideBackButtonNavigation && (
- + +
) } diff --git a/src/renderer/components/layout/siblings-in-tab-layout.tsx b/src/renderer/components/layout/siblings-in-tab-layout.tsx new file mode 100644 index 0000000000..5122ead09d --- /dev/null +++ b/src/renderer/components/layout/siblings-in-tab-layout.tsx @@ -0,0 +1,47 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import { observer } from "mobx-react"; +import React from "react"; +import siblingTabsInjectable from "../../routes/sibling-tabs.injectable"; +import { TabLayout } from "./tab-layout-2"; +import type { HierarchicalSidebarItem } from "./sidebar-items.injectable"; + +interface SiblingTabLayoutProps { + children: React.ReactNode; +} + +interface Dependencies { + tabs: IComputedValue; +} + +const NonInjectedSiblingsInTabLayout = observer( + ({ tabs, children }: Dependencies & SiblingTabLayoutProps) => { + const dereferencedTabs = tabs.get(); + + if (dereferencedTabs.length) { + return ( + + {children} + + ); + } + + return <>{children}; + }, +); + +export const SiblingsInTabLayout = withInjectables( + NonInjectedSiblingsInTabLayout, + + { + getProps: (di, props) => ({ + tabs: di.inject(siblingTabsInjectable), + ...props, + }), + }, +); diff --git a/src/renderer/components/layout/sidebar-item.scss b/src/renderer/components/layout/sidebar-item.scss index bba6717a9b..90ad92ee21 100644 --- a/src/renderer/components/layout/sidebar-item.scss +++ b/src/renderer/components/layout/sidebar-item.scss @@ -25,11 +25,6 @@ white-space: nowrap; } - &.active, &:hover { - background: var(--blue); - color: var(--sidebarActiveColor); - } - .expand-icon { --size: 20px; } @@ -39,10 +34,6 @@ $borderSize: 4px; border-left: $borderSize solid transparent; - &.active { - border-left-color: var(--blue); - } - > .SidebarItem { color: var(--textColorPrimary); padding-left: 30px + $borderSize; @@ -56,10 +47,49 @@ &.expandable { font-weight: 500; } + } + } + } +} - &.active, &:hover { - color: var(--sidebarSubmenuActiveColor); - background: none; +// TODO: Make less flaky. +.sidebar-active-status { + > .SidebarItem { + .nav-item { + &.active, &:hover { + background: var(--blue); + color: var(--sidebarActiveColor); + } + } + + .sub-menu { + &.active { + border-left-color: var(--blue); + } + + > .SidebarItem { + .nav-item { + &.active, &:hover { + color: var(--sidebarSubmenuActiveColor); + background: none; + } + + &.expandable { + color: var(--textColorPrimary) + } + } + + .sub-menu { + &.active { + border-left-color: transparent; + } + + .nav-item { + &.active, &:hover { + color: var(--sidebarSubmenuActiveColor); + background: none; + } + } } } } diff --git a/src/renderer/components/layout/sidebar-item.tsx b/src/renderer/components/layout/sidebar-item.tsx index 0152725e37..c4b785d718 100644 --- a/src/renderer/components/layout/sidebar-item.tsx +++ b/src/renderer/components/layout/sidebar-item.tsx @@ -7,39 +7,28 @@ import "./sidebar-item.scss"; import React from "react"; import { computed, makeObservable } from "mobx"; -import { cssNames, prevDefault, StorageHelper } from "../../utils"; +import { cssNames, StorageHelper } from "../../utils"; import { observer } from "mobx-react"; import { NavLink } from "react-router-dom"; import { Icon } from "../icon"; -import { isActiveRoute } from "../../navigation"; import { withInjectables } from "@ogre-tools/injectable-react"; -import sidebarStorageInjectable, { SidebarStorageState } from "./sidebar-storage/sidebar-storage.injectable"; - -export interface SidebarItemProps { - /** - * Unique id, used in storage and integration tests - */ - id: string; - url: string; - className?: string; - text: React.ReactNode; - icon?: React.ReactNode; - isHidden?: boolean; - /** - * Forces this item to be also show as active or not. - * - * Default: dynamically checks the location against the `url` props to determine if - * this item should be shown as active - */ - isActive?: boolean; -} +import sidebarStorageInjectable, { + SidebarStorageState, +} from "./sidebar-storage/sidebar-storage.injectable"; +import type { HierarchicalSidebarItem } from "./sidebar-items.injectable"; interface Dependencies { sidebarStorage: StorageHelper; } +export interface SidebarItemProps { + item: HierarchicalSidebarItem; +} + @observer -class NonInjectedSidebarItem extends React.Component { +class NonInjectedSidebarItem extends React.Component< + SidebarItemProps & Dependencies +> { static displayName = "SidebarItem"; constructor(props: SidebarItemProps & Dependencies) { @@ -48,65 +37,85 @@ class NonInjectedSidebarItem extends React.Component 0; } - @computed get isExpandable(): boolean { - return React.Children.count(this.props.children) > 0; + @computed get isActive(): boolean { + return this.props.item.isActive.get(); + } + + get registration() { + return this.props.item.registration; } toggleExpand = () => { - this.props.sidebarStorage.merge(draft => { + this.props.sidebarStorage.merge((draft) => { draft.expanded[this.id] = !draft.expanded[this.id]; }); }; renderSubMenu() { - const { isExpandable, expanded, isActive } = this; + const { isExpandable, expanded } = this; if (!isExpandable || !expanded) { return null; } return ( -
    - {this.props.children} +
      + {this.props.item.children.map(item => )}
    ); } render() { - const { isHidden, icon, text, url, className } = this.props; - - if (isHidden) return null; - - const { isActive, id, expanded, isExpandable, toggleExpand } = this; - const classNames = cssNames("SidebarItem", className); - return ( -
    +
    isActive} - className={cssNames("nav-item flex gaps align-center", { expandable: isExpandable })} - onClick={isExpandable ? prevDefault(toggleExpand) : undefined}> - {icon} - {text} - {isExpandable && } + to={""} + isActive={() => this.isActive} + + className={cssNames("nav-item flex gaps align-center", { + expandable: this.isExpandable, + })} + + onClick={(event) => { + event.preventDefault(); + event.stopPropagation(); + + if (this.isExpandable) { + this.toggleExpand(); + } else { + this.registration.onClick(); + } + }} + data-testid={`sidebar-item-link-for-${this.id}`} + > + {this.registration.getIcon?.()} + {this.registration.title} + {this.isExpandable && ( + + )} {this.renderSubMenu()}
    diff --git a/src/renderer/components/layout/sidebar-items.injectable.ts b/src/renderer/components/layout/sidebar-items.injectable.ts new file mode 100644 index 0000000000..f24a1db132 --- /dev/null +++ b/src/renderer/components/layout/sidebar-items.injectable.ts @@ -0,0 +1,132 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; +import { computed, IComputedValue } from "mobx"; +// @ts-ignore +import { pipeline } from "@ogre-tools/fp"; + +import { + filter, + flatMap, + identity, + invokeMap, + isEmpty, + map, + orderBy, + overSome, + some, +} from "lodash/fp"; +import rendererExtensionsInjectable from "../../../extensions/renderer-extensions.injectable"; +import type { LensRendererExtension } from "../../../extensions/lens-renderer-extension"; + +export interface SidebarItemRegistration { + id: string; + parentId: string | null; + title: React.ReactNode; + onClick: () => void; + getIcon?: () => React.ReactNode; + isActive?: IComputedValue; + isVisible?: IComputedValue; + orderNumber: number; + extension?: LensRendererExtension; +} + +export const sidebarItemsInjectionToken = getInjectionToken< + IComputedValue +>({ id: "sidebar-items-injection-token" }); + +export interface HierarchicalSidebarItem { + registration: SidebarItemRegistration; + children: HierarchicalSidebarItem[]; + isActive: IComputedValue; +} + +const sidebarItemsInjectable = getInjectable({ + id: "sidebar-items", + + instantiate: (di) => { + const extensions = di.inject(rendererExtensionsInjectable); + + return computed((): HierarchicalSidebarItem[] => { + const enabledExtensions = extensions.get(); + + const sidebarItemRegistrations = di.injectMany( + sidebarItemsInjectionToken, + ); + + const registrations = pipeline( + sidebarItemRegistrations, + flatMap(dereference), + + filter( + overSome([ + isNonExtensionSidebarItem, + isEnabledExtensionSidebarItemFor(enabledExtensions), + ]), + ), + ); + + const getSidebarItemsHierarchy = ( + registrations: SidebarItemRegistration[], + ) => { + const _getSidebarItemsHierarchy = ( + parentId: string, + ): HierarchicalSidebarItem[] => + pipeline( + registrations, + + filter((item) => item.parentId === parentId), + + map((registration) => { + const children = _getSidebarItemsHierarchy(registration.id); + + return { + registration, + children, + + isActive: computed(() => { + if (isEmpty(children)) { + return registration.isActive ? registration.isActive.get() : false; + } + + return pipeline( + children, + invokeMap("isActive.get"), + some(identity), + ); + }), + }; + }), + + filter(item => item.registration.isVisible?.get() ?? true), + + (items) => + orderBy( + ["registration.orderNumber"], + ["asc"], + items, + ), + ); + + return _getSidebarItemsHierarchy(null); + }; + + return getSidebarItemsHierarchy(registrations); + }); + }, +}); + +const isNonExtensionSidebarItem = (sidebarItem: SidebarItemRegistration) => + !sidebarItem.extension; + +const isEnabledExtensionSidebarItemFor = + (enabledExtensions: LensRendererExtension[]) => + (sidebarItem: SidebarItemRegistration) => + !!enabledExtensions.find((x) => x === sidebarItem.extension); + +const dereference = (items: IComputedValue) => + items.get(); + +export default sidebarItemsInjectable; diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index 5af023c622..fc6330a3d4 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -4,137 +4,61 @@ */ import styles from "./sidebar.module.scss"; -import type { TabLayoutRoute } from "./tab-layout"; import React from "react"; import { observer } from "mobx-react"; import { cssNames } from "../../utils"; -import { isActiveRoute } from "../../navigation"; -import { ClusterPageMenuRegistration, ClusterPageMenuRegistry, ClusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries"; import { SidebarItem } from "./sidebar-item"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import { SidebarCluster } from "./sidebar-cluster"; -import { renderTabRoutesSidebarItems } from "./tab-routes-sidebar-items"; -import { ConfigSidebarItem } from "../+config/sidebar-item"; -import { ClusterSidebarItem } from "../+cluster/sidebar-item"; -import { NodesSidebarItem } from "../+nodes/sidebar-item"; -import { WorkloadsSidebarItem } from "../+workloads/sidebar-item"; -import { NetworkSidebarItem } from "../+network/sidebar-item"; -import { StorageSidebarItem } from "../+storage/sidebar-item"; -import { NamespacesSidebarItem } from "../+namespaces/sidebar-item"; -import { EventsSidebarItem } from "../+events/sidebar-item"; -import { HelmSidebarItem } from "../+helm/sidebar-item"; -import { UserManagementSidebarItem } from "../+user-management/sidebar-item"; -import { CustomResourcesSidebarItem } from "../+custom-resources/sidebar-item"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import sidebarItemsInjectable, { + HierarchicalSidebarItem, +} from "./sidebar-items.injectable"; +import type { IComputedValue } from "mobx"; -export interface SidebarProps { - className?: string; +interface Dependencies { + sidebarItems: IComputedValue; } @observer -export class Sidebar extends React.Component { +class NonInjectedSidebar extends React.Component { static displayName = "Sidebar"; - getTabLayoutRoutes(menu: ClusterPageMenuRegistration): TabLayoutRoute[] { - if (!menu.id) { - return []; - } - - const routes: TabLayoutRoute[] = []; - const subMenus = ClusterPageMenuRegistry.getInstance().getSubItems(menu); - const clusterPageRegistry = ClusterPageRegistry.getInstance(); - - for (const subMenu of subMenus) { - const page = clusterPageRegistry.getByPageTarget(subMenu.target); - - if (!page) { - continue; - } - - const { extensionId, id: pageId, url, components } = page; - - if (subMenu.components.Icon) { - console.warn( - "ClusterPageMenuRegistration has components.Icon defined and a valid parentId. Icon will not be displayed", - { - id: subMenu.id, - parentId: subMenu.parentId, - target: subMenu.target, - }, - ); - } - - routes.push({ - routePath: url, - url: getExtensionPageUrl({ extensionId, pageId, params: subMenu.target.params }), - title: subMenu.title, - component: components.Page, - }); - } - - return routes; - } - - renderRegisteredMenus() { - return ClusterPageMenuRegistry.getInstance().getRootItems().map((menuItem, index) => { - const registeredPage = ClusterPageRegistry.getInstance().getByPageTarget(menuItem.target); - const tabRoutes = this.getTabLayoutRoutes(menuItem); - const id = `registered-item-${index}`; - let pageUrl: string; - let isActive = false; - - if (registeredPage) { - const { extensionId, id: pageId } = registeredPage; - - pageUrl = getExtensionPageUrl({ extensionId, pageId, params: menuItem.target.params }); - isActive = isActiveRoute(registeredPage.url); - } else if (tabRoutes.length > 0) { - pageUrl = tabRoutes[0].url; - isActive = isActiveRoute(tabRoutes.map((tab) => tab.routePath)); - } else { - return null; - } - - return ( - } - > - {renderTabRoutesSidebarItems(tabRoutes)} - - ); - }); - } - get clusterEntity() { return catalogEntityRegistry.activeEntity; } render() { - const { className } = this.props; - return ( -
    - -
    - - - - - - - - - - - - {this.renderRegisteredMenus()} +
    + + +
    + {this.props.sidebarItems.get().map(( + hierarchicalSidebarItem: HierarchicalSidebarItem, + ) => ( + + ))}
    ); } } + +export const Sidebar = withInjectables( + NonInjectedSidebar, + + { + getProps: (di, props) => ({ + sidebarItems: di.inject(sidebarItemsInjectable), + ...props, + }), + }, +); + + + diff --git a/src/renderer/components/layout/tab-layout-2.tsx b/src/renderer/components/layout/tab-layout-2.tsx new file mode 100644 index 0000000000..a0348383c1 --- /dev/null +++ b/src/renderer/components/layout/tab-layout-2.tsx @@ -0,0 +1,60 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import "./tab-layout.scss"; + +import React from "react"; +import { observer } from "mobx-react"; +import { cssNames } from "../../utils"; +import { Tab, Tabs } from "../tabs"; +import { ErrorBoundary } from "../error-boundary"; +import type { HierarchicalSidebarItem } from "./sidebar-items.injectable"; + +export interface TabLayoutProps { + tabs?: HierarchicalSidebarItem[]; + children?: React.ReactNode; +} + +export const TabLayout = observer( + ({ + tabs = [], + children, + }: TabLayoutProps) => { + const hasTabs = tabs.length > 0; + + return ( +
    + + {hasTabs && ( + + {tabs.map(({ registration, isActive }) => { + const active = isActive.get(); + + return ( + + ); + })} + + )} + +
    + + {children} + +
    +
    + ); + }, +); diff --git a/src/renderer/components/layout/tab-routes-sidebar-items.tsx b/src/renderer/components/layout/tab-routes-sidebar-items.tsx deleted file mode 100644 index fdc0e37165..0000000000 --- a/src/renderer/components/layout/tab-routes-sidebar-items.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import React from "react"; -import { isActiveRoute } from "../../navigation"; -import { SidebarItem } from "./sidebar-item"; -import type { TabLayoutRoute } from "./tab-layout"; - -function withId(src: TabLayoutRoute) { - return { - ...src, - id: `tab-route-item-${src.url ?? src.routePath}`, - }; -} - -/** - * Renders a sidebar item for each route - * - * NOTE: this cannot be a component because then the `.isExandable` - * check will always return true because a component that renders to `null` is - * still a present child to the parent `` - */ -export const renderTabRoutesSidebarItems = (routes: TabLayoutRoute[]) => ( - routes - .map(withId) - .map(({ title, routePath, url = routePath, exact = true, id }) => ( - - )) -); diff --git a/src/renderer/components/layout/top-bar/top-bar.tsx b/src/renderer/components/layout/top-bar/top-bar.tsx index 976c79a4d5..ac51c66448 100644 --- a/src/renderer/components/layout/top-bar/top-bar.tsx +++ b/src/renderer/components/layout/top-bar/top-bar.tsx @@ -11,8 +11,6 @@ import { Icon } from "../../icon"; import { observable } from "mobx"; import { ipcRendererOn } from "../../../../common/ipc"; import { watchHistoryState } from "../../../remote-helpers/history-updater"; -import { isActiveRoute, navigate } from "../../../navigation"; -import { catalogRoute, catalogURL } from "../../../../common/routes"; import { cssNames } from "../../../utils"; import topBarItemsInjectable from "./top-bar-items/top-bar-items.injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -21,10 +19,13 @@ import { emitOpenAppMenuAsContextMenu, requestWindowAction } from "../../../ipc" import { WindowAction } from "../../../../common/ipc/window"; import isLinuxInjectable from "../../../../common/vars/is-linux.injectable"; import isWindowsInjectable from "../../../../common/vars/is-windows.injectable"; - -export interface TopBarProps {} +import navigateToCatalogInjectable, { NavigateToCatalog } from "../../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; +import catalogRouteInjectable from "../../../../common/front-end-routing/routes/catalog/catalog-route.injectable"; +import routeIsActiveInjectable from "../../../routes/route-is-active.injectable"; interface Dependencies { + navigateToCatalog: NavigateToCatalog; + catalogRouteIsActive: IComputedValue; items: IComputedValue; isWindows: boolean; isLinux: boolean; @@ -41,7 +42,7 @@ ipcRendererOn("history:can-go-forward", (event, state: boolean) => { nextEnabled.set(state); }); -const NonInjectedTopBar = observer(({ items, isWindows, isLinux }: TopBarProps & Dependencies) => { +const NonInjectedTopBar = observer(({ items, navigateToCatalog, catalogRouteIsActive, isWindows, isLinux }: Dependencies) => { const elem = useRef(); const openAppContextMenu = () => { @@ -49,7 +50,7 @@ const NonInjectedTopBar = observer(({ items, isWindows, isLinux }: TopBarProps & }; const goHome = () => { - navigate(catalogURL()); + navigateToCatalog(); }; const goBack = () => { @@ -98,7 +99,7 @@ const NonInjectedTopBar = observer(({ items, isWindows, isLinux }: TopBarProps & data-testid="home-button" material="home" onClick={goHome} - disabled={isActiveRoute(catalogRoute)} + disabled={catalogRouteIsActive.get()} /> ( }) ); -export const TopBar = withInjectables(NonInjectedTopBar, { - getProps: (di, props) => ({ - items: di.inject(topBarItemsInjectable), - isLinux: di.inject(isLinuxInjectable), - isWindows: di.inject(isWindowsInjectable), - ...props, - }), -}); +export const TopBar = withInjectables( + NonInjectedTopBar, + { + getProps: (di) => { + const catalogRoute = di.inject(catalogRouteInjectable); + + return { + navigateToCatalog: di.inject(navigateToCatalogInjectable), + items: di.inject(topBarItemsInjectable), + isLinux: di.inject(isLinuxInjectable), + isWindows: di.inject(isWindowsInjectable), + + catalogRouteIsActive: di.inject( + routeIsActiveInjectable, + catalogRoute, + ), + }; + }, + }, +); diff --git a/src/renderer/components/select/select.test.tsx b/src/renderer/components/select/select.test.tsx index 6374199ea0..0df53bd13e 100644 --- a/src/renderer/components/select/select.test.tsx +++ b/src/renderer/components/select/select.test.tsx @@ -29,7 +29,6 @@ describe("