+
+describe("ErrorBoundary", () => {
+ let consoleSpy: any
+
+ beforeEach(() => {
+ // mock console.error to suppress the error message being printed to the console
+ consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
+ })
+
+ afterEach(() => {
+ // restore console.error to its original implementation
+ consoleSpy.mockRestore()
+ })
+
+ it("should render children when no error occurs", () => {
+ render(
+
+
+
+ )
+ expect(screen.getByText("Some Component")).toBeInTheDocument()
+ })
+
+ describe("when error occurs", () => {
+ it("should render null when displayErrorMessage is false", () => {
+ const { container } = render(
+
+
+
+ )
+ expect(container.firstChild).toBeNull()
+ expect(screen.queryByText("Some Component")).not.toBeInTheDocument()
+ })
+
+ it("should render null when displayErrorMessage is undefined", () => {
+ const { container } = render(
+
+
+
+ )
+ expect(container.firstChild).toBeNull()
+ expect(screen.queryByText("Some Component")).not.toBeInTheDocument()
+ })
+
+ it("should render default ErrorMessage when displayErrorMessage is true and no fallback provided", () => {
+ render(
+
+
+
+ )
+ expect(screen.getByText("Error: Test error message")).toBeInTheDocument()
+ expect(screen.queryByText("Some Component")).not.toBeInTheDocument()
+ })
+
+ it("should render custom fallback when displayErrorMessage is true and fallback is provided", () => {
+ render(
+
+
+
+ )
+ expect(screen.getByText("Custom fallback: Test error message")).toBeInTheDocument()
+ expect(screen.queryByText("Error: Test error message")).not.toBeInTheDocument()
+ expect(screen.queryByText("Some Component")).not.toBeInTheDocument()
+ })
+
+ it("should render null when displayErrorMessage is false even if fallback is provided", () => {
+ const { container } = render(
+
+
+
+ )
+ expect(container.firstChild).toBeNull()
+ expect(screen.queryByText("Custom fallback: Test error message")).not.toBeInTheDocument()
+ expect(screen.queryByText("Some Component")).not.toBeInTheDocument()
+ })
+ })
+})
diff --git a/apps/greenhouse/src/components/admin/common/ErrorBoundary/index.tsx b/apps/greenhouse/src/components/admin/common/ErrorBoundary/index.tsx
new file mode 100644
index 0000000000..b7e91398e9
--- /dev/null
+++ b/apps/greenhouse/src/components/admin/common/ErrorBoundary/index.tsx
@@ -0,0 +1,27 @@
+/*
+ * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { ReactNode } from "react"
+import { ErrorBoundary as ReactErrorBoundary, FallbackProps } from "react-error-boundary"
+import { ErrorMessage } from "./ErrorMessage"
+
+export const ErrorBoundary = ({
+ children,
+ displayErrorMessage,
+ fallbackRender,
+ resetKeys,
+}: {
+ children: ReactNode
+ displayErrorMessage?: boolean
+ fallbackRender?: (props: FallbackProps) => ReactNode
+ resetKeys?: any
+}) => (
+ null}
+ >
+ {children}
+
+)
diff --git a/apps/greenhouse/src/components/admin/common/FilterSelect.tsx b/apps/greenhouse/src/components/admin/common/FilterSelect.tsx
new file mode 100644
index 0000000000..9d1af2df95
--- /dev/null
+++ b/apps/greenhouse/src/components/admin/common/FilterSelect.tsx
@@ -0,0 +1,72 @@
+/*
+ * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useCallback, useState } from "react"
+import { isEmpty } from "../utils"
+import { InputGroup, ComboBox, ComboBoxOption, SelectOption, Select, Stack } from "@cloudoperators/juno-ui-components"
+import { Filter, SelectedFilter } from "../types/filter"
+
+type FilterSelectProps = {
+ filters: Filter[]
+ onChange: (filter: SelectedFilter) => void
+}
+
+export const FilterSelect = ({ filters, onChange }: FilterSelectProps) => {
+ const [selectedFilterName, setSelectedFilterName] = useState("")
+ const [selectedFilterValue, setSelectedFilterValue] = useState("")
+ // first filter gets the values, second one filters emtpy values
+ const filterValues = filters.find((filter) => filter.id === selectedFilterName)?.values?.filter((value) => value)
+
+ const handleValueChange = useCallback(
+ (value: string) => {
+ setSelectedFilterValue(value) // update the filter value state to trigger re-render on ComboBox
+ if (!isEmpty(selectedFilterName) && !isEmpty(value)) {
+ onChange({
+ id: selectedFilterName,
+ value: value,
+ })
+ }
+ // TODO: remove this after ComboBox supports resetting its value after onChange
+ // set timeout to allow ComboBox to update its value after onChange
+ setTimeout(() => {
+ setSelectedFilterValue("")
+ }, 0)
+ },
+ [selectedFilterName, setSelectedFilterValue, onChange]
+ )
+
+ return (
+
+
+
+
+ {filterValues?.map((value) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/greenhouse/src/components/admin/common/LoadingDataRow.test.tsx b/apps/greenhouse/src/components/admin/common/LoadingDataRow.test.tsx
new file mode 100644
index 0000000000..967e3a50eb
--- /dev/null
+++ b/apps/greenhouse/src/components/admin/common/LoadingDataRow.test.tsx
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from "react"
+import { render, screen } from "@testing-library/react"
+import { LoadingDataRow } from "./LoadingDataRow"
+
+describe("LoadingDataRow", () => {
+ it("should correctly render", () => {
+ render()
+ const loadingText = screen.getByText("Loading...")
+ expect(loadingText).toBeInTheDocument()
+ })
+})
diff --git a/apps/greenhouse/src/components/admin/common/LoadingDataRow.tsx b/apps/greenhouse/src/components/admin/common/LoadingDataRow.tsx
new file mode 100644
index 0000000000..de0045d201
--- /dev/null
+++ b/apps/greenhouse/src/components/admin/common/LoadingDataRow.tsx
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from "react"
+import { Stack } from "@cloudoperators/juno-ui-components/index"
+import { EmptyDataGridRow } from "./EmptyDataGridRow"
+
+export const LoadingDataRow = ({ colSpan }: { colSpan: number }) => (
+
+
+
Loading...
+
+
+)
diff --git a/apps/greenhouse/src/components/admin/common/getErrorDataRow.tsx b/apps/greenhouse/src/components/admin/common/getErrorDataRow.tsx
new file mode 100644
index 0000000000..0b46d52945
--- /dev/null
+++ b/apps/greenhouse/src/components/admin/common/getErrorDataRow.tsx
@@ -0,0 +1,21 @@
+/*
+ * SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from "react"
+import { Stack } from "@cloudoperators/juno-ui-components/index"
+import { EmptyDataGridRow } from "./EmptyDataGridRow"
+import { ErrorMessage } from "./ErrorBoundary/ErrorMessage"
+
+export const getErrorDataRowComponent = ({ colspan }: { colspan: number }) => {
+ const ErrorDataRow = ({ error }: { error: Error }) => (
+
+
+
+
+
+ )
+ ErrorDataRow.displayName = "ErrorDataRow"
+ return ErrorDataRow
+}
diff --git a/apps/greenhouse/src/components/admin/types/filter.ts b/apps/greenhouse/src/components/admin/types/filter.ts
new file mode 100644
index 0000000000..456ce86877
--- /dev/null
+++ b/apps/greenhouse/src/components/admin/types/filter.ts
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export type Filter = {
+ id: string
+ label: string
+ values: string[]
+}
+
+export type SelectedFilter = {
+ id: string
+ value: string
+ inactive?: boolean
+}
diff --git a/apps/greenhouse/src/components/admin/types/k8sTypes.ts b/apps/greenhouse/src/components/admin/types/k8sTypes.ts
new file mode 100644
index 0000000000..cc8e01bbe3
--- /dev/null
+++ b/apps/greenhouse/src/components/admin/types/k8sTypes.ts
@@ -0,0 +1,8 @@
+/*
+ * SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type { components } from "./schema"
+
+export type PluginPreset = components["schemas"]["PluginPreset"]
diff --git a/apps/greenhouse/src/components/admin/types/schema.d.ts b/apps/greenhouse/src/components/admin/types/schema.d.ts
new file mode 100644
index 0000000000..20235210b6
--- /dev/null
+++ b/apps/greenhouse/src/components/admin/types/schema.d.ts
@@ -0,0 +1,1556 @@
+/*
+ * SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Juno contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * This file was auto-generated by openapi-typescript.
+ * Do not make direct changes to the file.
+ */
+
+export type paths = Record
+export type webhooks = Record
+export interface components {
+ schemas: {
+ /**
+ * Catalog
+ * @description Catalog is the Schema for the catalogs API.
+ */
+ Catalog: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /** @description CatalogSpec defines the desired state of Catalog. */
+ spec?: {
+ /** @description Sources contains the list of Git Repository source to resolve PluginDefinitions / ClusterPluginDefinitions from */
+ sources: {
+ /** @description Interval defines how often to reconcile the Git repository source */
+ interval?: string
+ /** @description Overrides are the PluginDefinition overrides to be applied */
+ overrides?: {
+ /**
+ * @description Alias is the alias to apply to the PluginDefinition Name via Kustomize patches
+ * For SourceType Helm, this field is passed to postRender Kustomize patch
+ */
+ alias?: string
+ /** @description Name is the name of the PluginDefinition to patch with an alias */
+ name: string
+ /** @description Repository is the repository to override in the PluginDefinition .spec.helmChart.repository */
+ repository?: string
+ }[]
+ /**
+ * @description Ref is the git reference (branch, tag, or SHA) to resolve PluginDefinitions from
+ * precedence: SHA > Tag > Branch
+ * if not specified, defaults to the branch "main"
+ */
+ ref?: {
+ branch?: string
+ sha?: string
+ tag?: string
+ }
+ /** @description Repository - the Git repository URL */
+ repository: string
+ /**
+ * @description Resources contains the list of path to PluginDefinition files
+ * e.g. ["plugins/plugin-a.yaml", "plugins/plugin-b.yaml"]
+ * glob patterns are supported, e.g. ["plugins/*.yaml", "more-plugins/**\/plugindefinition.yaml"]
+ */
+ resources: string[]
+ /**
+ * @description SecretName is the name of v1.Secret containing credentials to access the Git repository
+ * the secret must be in the same namespace as the Catalog resource
+ *
+ * GitHub App Example:
+ */
+ secretName?: string
+ }[]
+ }
+ /** @description CatalogStatus defines the observed state of Catalog. */
+ status?: {
+ /** @description Inventory contains list of internal artifacts generated by Catalog */
+ inventory?: {
+ [key: string]: {
+ kind: string
+ message?: string
+ name: string
+ ready?: string
+ }[]
+ }
+ /** @description LastReconciledAt contains the value when the reconcile was last triggered via annotation. */
+ lastReconciledAt?: string
+ /** @description StatusConditions contain the different conditions that constitute the status of the Catalog */
+ statusConditions?: {
+ conditions?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }[]
+ }
+ }
+ }
+ /**
+ * ClusterKubeconfig
+ * @description ClusterKubeconfig is the Schema for the clusterkubeconfigs API
+ * ObjectMeta.OwnerReferences is used to link the ClusterKubeconfig to the Cluster
+ * ObjectMeta.Generation is used to detect changes in the ClusterKubeconfig and sync local kubeconfig files
+ * ObjectMeta.Name is designed to be the same with the Cluster name
+ */
+ ClusterKubeconfig: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /**
+ * @description ClusterKubeconfigSpec stores the kubeconfig data for the cluster
+ * The idea is to use kubeconfig data locally with minimum effort (with local tools or plain kubectl):
+ * kubectl get cluster-kubeconfig $NAME -o yaml | yq -y .spec.kubeconfig
+ */
+ spec?: {
+ /**
+ * @description ClusterKubeconfigData stores the kubeconfig data ready to use kubectl or other local tooling
+ * It is a simplified version of clientcmdapi.Config: https://pkg.go.dev/k8s.io/client-go/tools/clientcmd/api#Config
+ */
+ kubeconfig?: {
+ apiVersion?: string
+ clusters?: {
+ cluster: {
+ /** Format: byte */
+ "certificate-authority-data"?: string
+ server?: string
+ }
+ name: string
+ }[]
+ contexts?: {
+ context?: {
+ cluster: string
+ namespace?: string
+ user: string
+ }
+ name: string
+ }[]
+ "current-context"?: string
+ kind?: string
+ preferences?: Record
+ users?: {
+ name: string
+ user?: {
+ /** @description AuthProviderConfig holds the configuration for a specified auth provider. */
+ "auth-provider"?: {
+ config?: {
+ [key: string]: string
+ }
+ name: string
+ }
+ /** Format: byte */
+ "client-certificate-data"?: string
+ /** Format: byte */
+ "client-key-data"?: string
+ }
+ }[]
+ }
+ }
+ status?: {
+ /**
+ * @description A StatusConditions contains a list of conditions.
+ * Only one condition of a given type may exist in the list.
+ */
+ statusConditions?: {
+ conditions?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }[]
+ }
+ }
+ }
+ /**
+ * ClusterPluginDefinition
+ * @description ClusterPluginDefinition is the Schema for the clusterplugindefinitions API.
+ */
+ ClusterPluginDefinition: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /** @description PluginDefinitionSpec defines the desired state of PluginDefinitionSpec */
+ spec?: {
+ /** @description Description provides additional details of the pluginDefinition. */
+ description?: string
+ /** @description DisplayName provides a human-readable label for the pluginDefinition. */
+ displayName?: string
+ /**
+ * @description DocMarkDownUrl specifies the URL to the markdown documentation file for this plugin.
+ * Source needs to allow all CORS origins.
+ */
+ docMarkDownUrl?: string
+ /** @description HelmChart specifies where the Helm Chart for this pluginDefinition can be found. */
+ helmChart?: {
+ /** @description Name of the HelmChart chart. */
+ name: string
+ /** @description Repository of the HelmChart chart. */
+ repository: string
+ /** @description Version of the HelmChart chart. */
+ version: string
+ }
+ /**
+ * @description Icon specifies the icon to be used for this plugin in the Greenhouse UI.
+ * Icons can be either:
+ * - A string representing a juno icon in camel case from this list: https://github.com/sapcc/juno/blob/main/libs/juno-ui-components/src/components/Icon/Icon.component.js#L6-L52
+ * - A publicly accessible image reference to a .png file. Will be displayed 100x100px
+ */
+ icon?: string
+ /** @description RequiredValues is a list of values required to create an instance of this PluginDefinition. */
+ options?: {
+ /** @description Default provides a default value for the option */
+ default?: unknown
+ /** @description Description provides a human-readable text for the value as shown in the UI. */
+ description?: string
+ /** @description DisplayName provides a human-readable label for the configuration option */
+ displayName?: string
+ /** @description Name/Key of the config option. */
+ name: string
+ /** @description Regex specifies a match rule for validating configuration options. */
+ regex?: string
+ /** @description Required indicates that this config option is required */
+ required: boolean
+ /**
+ * @description Type of this configuration option.
+ * @enum {string}
+ */
+ type: "string" | "secret" | "bool" | "int" | "list" | "map"
+ }[]
+ /** @description UIApplication specifies a reference to a UI application */
+ uiApplication?: {
+ /** @description Name of the UI application. */
+ name: string
+ /**
+ * @description URL specifies the url to a built javascript asset.
+ * By default, assets are loaded from the Juno asset server using the provided name and version.
+ */
+ url?: string
+ /** @description Version of the frontend application. */
+ version: string
+ }
+ /** @description Version of this pluginDefinition */
+ version: string
+ /**
+ * Format: int32
+ * @description Weight configures the order in which Plugins are shown in the Greenhouse UI.
+ * Defaults to alphabetical sorting if not provided or on conflict.
+ */
+ weight?: number
+ }
+ /** @description ClusterPluginDefinitionStatus defines the observed state of ClusterPluginDefinition. */
+ status?: {
+ /** @description StatusConditions contain the different conditions that constitute the status of the Plugin. */
+ statusConditions?: {
+ conditions?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }[]
+ }
+ }
+ }
+ /**
+ * Cluster
+ * @description Cluster is the Schema for the clusters API
+ */
+ Cluster: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /** @description ClusterSpec defines the desired state of the Cluster. */
+ spec?: {
+ /**
+ * @description AccessMode configures how the cluster is accessed from the Greenhouse operator.
+ * @enum {string}
+ */
+ accessMode: "direct"
+ /** @description KubeConfig contains specific values for `KubeConfig` for the cluster. */
+ kubeConfig?: {
+ /**
+ * Format: int32
+ * @description MaxTokenValidity specifies the maximum duration for which a token remains valid in hours.
+ * @default 72
+ */
+ maxTokenValidity: number
+ }
+ }
+ /** @description ClusterStatus defines the observed state of Cluster */
+ status?: {
+ /**
+ * Format: date-time
+ * @description BearerTokenExpirationTimestamp reflects the expiration timestamp of the bearer token used to access the cluster.
+ */
+ bearerTokenExpirationTimestamp?: string
+ /** @description KubernetesVersion reflects the detected Kubernetes version of the cluster. */
+ kubernetesVersion?: string
+ /** @description Nodes contain a short summary of nodes count and not ready nodes status. */
+ nodes?: {
+ /** @description NotReady is slice of non-ready nodes status details. */
+ notReady?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime represents latest transition time of status.
+ */
+ lastTransitionTime?: string
+ /** @description Message represents the error message. */
+ message?: string
+ /** @description Name of the node. */
+ name: string
+ }[]
+ /**
+ * Format: int32
+ * @description ReadyNodes represent the number of ready nodes in the cluster.
+ */
+ ready?: number
+ /**
+ * Format: int32
+ * @description Total represent the number of all the nodes in the cluster.
+ */
+ total?: number
+ }
+ /** @description StatusConditions contain the different conditions that constitute the status of the Cluster. */
+ statusConditions?: {
+ conditions?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }[]
+ }
+ }
+ }
+ /**
+ * Organization
+ * @description Organization is the Schema for the organizations API
+ */
+ Organization: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /** @description OrganizationSpec defines the desired state of Organization */
+ spec?: {
+ /** @description Authentication configures the organizations authentication mechanism. */
+ authentication?: {
+ /** @description OIDConfig configures the OIDC provider. */
+ oidc?: {
+ /** @description ClientIDReference references the Kubernetes secret containing the client id. */
+ clientIDReference: {
+ /** @description Key in the secret to select the value from. */
+ key: string
+ /** @description Name of the secret in the same namespace. */
+ name: string
+ }
+ /** @description ClientSecretReference references the Kubernetes secret containing the client secret. */
+ clientSecretReference: {
+ /** @description Key in the secret to select the value from. */
+ key: string
+ /** @description Name of the secret in the same namespace. */
+ name: string
+ }
+ /** @description Issuer is the URL of the identity service. */
+ issuer: string
+ /**
+ * @description OAuth2ClientRedirectURIs are a registered set of redirect URIs. When redirecting from the idproxy to
+ * the client application, the URI requested to redirect to must be contained in this list.
+ */
+ oauth2ClientRedirectURIs?: string[]
+ /**
+ * @description RedirectURI is the redirect URI to be used for the OIDC flow against the upstream IdP.
+ * If none is specified, the Greenhouse ID proxy will be used.
+ */
+ redirectURI?: string
+ }
+ /** @description SCIMConfig configures the SCIM client. */
+ scim?: {
+ /**
+ * @description AuthType defined possible authentication type
+ * @default basic
+ * @enum {string}
+ */
+ authType: "basic" | "token"
+ /** @description URL to the SCIM server. */
+ baseURL: string
+ /** @description Password to be used for basic authentication. */
+ basicAuthPw?: {
+ /** @description Secret references the secret containing the value. */
+ secret?: {
+ /** @description Key in the secret to select the value from. */
+ key: string
+ /** @description Name of the secret in the same namespace. */
+ name: string
+ }
+ }
+ /** @description User to be used for basic authentication. */
+ basicAuthUser?: {
+ /** @description Secret references the secret containing the value. */
+ secret?: {
+ /** @description Key in the secret to select the value from. */
+ key: string
+ /** @description Name of the secret in the same namespace. */
+ name: string
+ }
+ }
+ /** @description BearerHeader to be used to defined bearer token header */
+ bearerHeader?: string
+ /** @description BearerPrefix to be used to defined bearer token prefix */
+ bearerPrefix?: string
+ /** @description BearerToken to be used for bearer token authorization */
+ bearerToken?: {
+ /** @description Secret references the secret containing the value. */
+ secret?: {
+ /** @description Key in the secret to select the value from. */
+ key: string
+ /** @description Name of the secret in the same namespace. */
+ name: string
+ }
+ }
+ }
+ }
+ /** @description ConfigMapRef allows to reference organizational config map. */
+ configMapRef?: string
+ /** @description Description provides additional details of the organization. */
+ description?: string
+ /**
+ * @description DisplayName is an optional name for the organization to be displayed in the Greenhouse UI.
+ * Defaults to a normalized version of metadata.name.
+ */
+ displayName?: string
+ /** @description MappedOrgAdminIDPGroup is the IDP group ID identifying org admins */
+ mappedOrgAdminIdPGroup?: string
+ }
+ /** @description OrganizationStatus defines the observed state of an Organization */
+ status?: {
+ /** @description StatusConditions contain the different conditions that constitute the status of the Organization. */
+ statusConditions?: {
+ conditions?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }[]
+ }
+ }
+ }
+ /**
+ * PluginDefinition
+ * @description PluginDefinition is the Schema for the PluginDefinitions API
+ */
+ PluginDefinition: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /** @description PluginDefinitionSpec defines the desired state of PluginDefinitionSpec */
+ spec?: {
+ /** @description Description provides additional details of the pluginDefinition. */
+ description?: string
+ /** @description DisplayName provides a human-readable label for the pluginDefinition. */
+ displayName?: string
+ /**
+ * @description DocMarkDownUrl specifies the URL to the markdown documentation file for this plugin.
+ * Source needs to allow all CORS origins.
+ */
+ docMarkDownUrl?: string
+ /** @description HelmChart specifies where the Helm Chart for this pluginDefinition can be found. */
+ helmChart?: {
+ /** @description Name of the HelmChart chart. */
+ name: string
+ /** @description Repository of the HelmChart chart. */
+ repository: string
+ /** @description Version of the HelmChart chart. */
+ version: string
+ }
+ /**
+ * @description Icon specifies the icon to be used for this plugin in the Greenhouse UI.
+ * Icons can be either:
+ * - A string representing a juno icon in camel case from this list: https://github.com/sapcc/juno/blob/main/libs/juno-ui-components/src/components/Icon/Icon.component.js#L6-L52
+ * - A publicly accessible image reference to a .png file. Will be displayed 100x100px
+ */
+ icon?: string
+ /** @description RequiredValues is a list of values required to create an instance of this PluginDefinition. */
+ options?: {
+ /** @description Default provides a default value for the option */
+ default?: unknown
+ /** @description Description provides a human-readable text for the value as shown in the UI. */
+ description?: string
+ /** @description DisplayName provides a human-readable label for the configuration option */
+ displayName?: string
+ /** @description Name/Key of the config option. */
+ name: string
+ /** @description Regex specifies a match rule for validating configuration options. */
+ regex?: string
+ /** @description Required indicates that this config option is required */
+ required: boolean
+ /**
+ * @description Type of this configuration option.
+ * @enum {string}
+ */
+ type: "string" | "secret" | "bool" | "int" | "list" | "map"
+ }[]
+ /** @description UIApplication specifies a reference to a UI application */
+ uiApplication?: {
+ /** @description Name of the UI application. */
+ name: string
+ /**
+ * @description URL specifies the url to a built javascript asset.
+ * By default, assets are loaded from the Juno asset server using the provided name and version.
+ */
+ url?: string
+ /** @description Version of the frontend application. */
+ version: string
+ }
+ /** @description Version of this pluginDefinition */
+ version: string
+ /**
+ * Format: int32
+ * @description Weight configures the order in which Plugins are shown in the Greenhouse UI.
+ * Defaults to alphabetical sorting if not provided or on conflict.
+ */
+ weight?: number
+ }
+ /** @description PluginDefinitionStatus defines the observed state of PluginDefinition */
+ status?: {
+ /** @description StatusConditions contain the different conditions that constitute the status of the Plugin. */
+ statusConditions?: {
+ conditions?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }[]
+ }
+ }
+ }
+ /**
+ * PluginPreset
+ * @description PluginPreset is the Schema for the PluginPresets API
+ */
+ PluginPreset: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /** @description PluginPresetSpec defines the desired state of PluginPreset */
+ spec?: {
+ /** @description ClusterOptionOverrides define plugin option values to override by the PluginPreset */
+ clusterOptionOverrides?: {
+ clusterName: string
+ overrides: {
+ /** @description Name of the values. */
+ name: string
+ /**
+ * @description Template is a Go string template that will be dynamically resolved for cluster-specific values.
+ * Only PluginOptionValues declared as template will be templated by the PluginController for Flux.
+ */
+ template?: string
+ /** @description Value is the actual value in plain text. */
+ value?: unknown
+ /** @description ValueFrom references a potentially confidential value in another source. */
+ valueFrom?: {
+ /** @description Secret references the secret containing the value. */
+ secret?: {
+ /** @description Key in the secret to select the value from. */
+ key: string
+ /** @description Name of the secret in the same namespace. */
+ name: string
+ }
+ }
+ }[]
+ }[]
+ /** @description ClusterSelector is a label selector to select the clusters the plugin bundle should be deployed to. */
+ clusterSelector: {
+ /** @description matchExpressions is a list of label selector requirements. The requirements are ANDed. */
+ matchExpressions?: {
+ /** @description key is the label key that the selector applies to. */
+ key: string
+ /**
+ * @description operator represents a key's relationship to a set of values.
+ * Valid operators are In, NotIn, Exists and DoesNotExist.
+ */
+ operator: string
+ /**
+ * @description values is an array of string values. If the operator is In or NotIn,
+ * the values array must be non-empty. If the operator is Exists or DoesNotExist,
+ * the values array must be empty. This array is replaced during a strategic
+ * merge patch.
+ */
+ values?: string[]
+ }[]
+ /**
+ * @description matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
+ * map is equivalent to an element of matchExpressions, whose key field is "key", the
+ * operator is "In", and the values array contains only "value". The requirements are ANDed.
+ */
+ matchLabels?: {
+ [key: string]: string
+ }
+ }
+ /**
+ * @description DeletionPolicy defines how Plugins owned by a PluginPreset are handled on deletion of the PluginPreset.
+ * Supported values are "Delete" and "Retain". If not set, defaults to "Delete".
+ * @default Delete
+ * @enum {string}
+ */
+ deletionPolicy: "Delete" | "Retain"
+ /** @description PluginSpec is the spec of the plugin to be deployed by the PluginPreset. */
+ plugin: {
+ /** @description ClusterName is the name of the cluster the plugin is deployed to. If not set, the plugin is deployed to the greenhouse cluster. */
+ clusterName?: string
+ /**
+ * @description DeletionPolicy defines how Helm Releases created by a Plugin are handled upon deletion of the Plugin.
+ * Supported values are "Delete" and "Retain". If not set, defaults to "Delete".
+ * @default Delete
+ * @enum {string}
+ */
+ deletionPolicy: "Delete" | "Retain"
+ /**
+ * @description DisplayName is an optional name for the Plugin to be displayed in the Greenhouse UI.
+ * This is especially helpful to distinguish multiple instances of a PluginDefinition in the same context.
+ * Defaults to a normalized version of metadata.name.
+ */
+ displayName?: string
+ /** @description IgnoreDifferences defines paths to ignore when detecting drift between desired and actual state. */
+ ignoreDifferences?: {
+ /** @description Group matches the APIVersion group of the resources to ignore. */
+ group?: string
+ /** @description Kind matches the Kind of the resources to ignore. */
+ kind?: string
+ /** @description Name matches the name of the resources to ignore. */
+ name?: string
+ /** @description Paths is a list of JSON paths to ignore when detecting drifts. */
+ paths: string[]
+ /** @description Version matches the APIVersion version of the resources to ignore. */
+ version?: string
+ }[]
+ /** @description Values are the values for a PluginDefinition instance. */
+ optionValues?: {
+ /** @description Name of the values. */
+ name: string
+ /**
+ * @description Template is a Go string template that will be dynamically resolved for cluster-specific values.
+ * Only PluginOptionValues declared as template will be templated by the PluginController for Flux.
+ */
+ template?: string
+ /** @description Value is the actual value in plain text. */
+ value?: unknown
+ /** @description ValueFrom references a potentially confidential value in another source. */
+ valueFrom?: {
+ /** @description Secret references the secret containing the value. */
+ secret?: {
+ /** @description Key in the secret to select the value from. */
+ key: string
+ /** @description Name of the secret in the same namespace. */
+ name: string
+ }
+ }
+ }[]
+ /**
+ * @description PluginDefinition is the name of the PluginDefinition this instance is for.
+ *
+ * Deprecated: Use PluginDefinitionRef instead. Future releases of greenhouse will remove this field.
+ */
+ pluginDefinition: string
+ /** @description PluginDefinitionRef is the reference to the (Cluster-)PluginDefinition. */
+ pluginDefinitionRef: {
+ /**
+ * @description Kind of the referent. Supported values: PluginDefinition, ClusterPluginDefinition.
+ * @enum {string}
+ */
+ kind?: "PluginDefinition" | "ClusterPluginDefinition"
+ /** @description Name of the referenced PluginDefinition or ClusterPluginDefinition resource. */
+ name?: string
+ }
+ /**
+ * @description ReleaseName is the name of the helm release in the remote cluster to which the backend is deployed.
+ * If the Plugin was already deployed, the Plugin's name is used as the release name.
+ * If this Plugin is newly created, the releaseName is defaulted to the PluginDefinitions HelmChart name.
+ */
+ releaseName?: string
+ /**
+ * @description ReleaseNamespace is the namespace in the remote cluster to which the backend is deployed.
+ * Defaults to the Greenhouse managed namespace if not set.
+ */
+ releaseNamespace?: string
+ /** @description WaitFor defines other Plugins to wait for before installing this Plugin. */
+ waitFor?: {
+ /** @description PluginRef defines a reference to the Plugin. */
+ pluginRef: {
+ /** @description Name of the Plugin. */
+ name?: string
+ /** @description PluginPreset is the name of the PluginPreset which creates the Plugin. */
+ pluginPreset?: string
+ }
+ }[]
+ }
+ /** @description WaitFor defines other Plugins to wait for before creating the Plugin. */
+ waitFor?: {
+ /** @description PluginRef defines a reference to the Plugin. */
+ pluginRef: {
+ /** @description Name of the Plugin. */
+ name?: string
+ /** @description PluginPreset is the name of the PluginPreset which creates the Plugin. */
+ pluginPreset?: string
+ }
+ }[]
+ }
+ /** @description PluginPresetStatus defines the observed state of PluginPreset */
+ status?: {
+ /** @description FailedPlugins is the number of failed Plugins managed by the PluginPreset. */
+ failedPlugins?: number
+ /** @description PluginStatuses contains statuses of Plugins managed by the PluginPreset. */
+ pluginStatuses?: {
+ pluginName?: string
+ /** @description Condition contains additional information on the state of a resource. */
+ readyCondition?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }
+ }[]
+ /** @description ReadyPlugins is the number of ready Plugins managed by the PluginPreset. */
+ readyPlugins?: number
+ /** @description StatusConditions contain the different conditions that constitute the status of the PluginPreset. */
+ statusConditions?: {
+ conditions?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }[]
+ }
+ /** @description TotalPlugins is the number of Plugins in total managed by the PluginPreset. */
+ totalPlugins?: number
+ }
+ }
+ /**
+ * Plugin
+ * @description Plugin is the Schema for the plugins API
+ */
+ Plugin: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /** @description PluginSpec defines the desired state of Plugin */
+ spec?: {
+ /** @description ClusterName is the name of the cluster the plugin is deployed to. If not set, the plugin is deployed to the greenhouse cluster. */
+ clusterName?: string
+ /**
+ * @description DeletionPolicy defines how Helm Releases created by a Plugin are handled upon deletion of the Plugin.
+ * Supported values are "Delete" and "Retain". If not set, defaults to "Delete".
+ * @default Delete
+ * @enum {string}
+ */
+ deletionPolicy: "Delete" | "Retain"
+ /**
+ * @description DisplayName is an optional name for the Plugin to be displayed in the Greenhouse UI.
+ * This is especially helpful to distinguish multiple instances of a PluginDefinition in the same context.
+ * Defaults to a normalized version of metadata.name.
+ */
+ displayName?: string
+ /** @description IgnoreDifferences defines paths to ignore when detecting drift between desired and actual state. */
+ ignoreDifferences?: {
+ /** @description Group matches the APIVersion group of the resources to ignore. */
+ group?: string
+ /** @description Kind matches the Kind of the resources to ignore. */
+ kind?: string
+ /** @description Name matches the name of the resources to ignore. */
+ name?: string
+ /** @description Paths is a list of JSON paths to ignore when detecting drifts. */
+ paths: string[]
+ /** @description Version matches the APIVersion version of the resources to ignore. */
+ version?: string
+ }[]
+ /** @description Values are the values for a PluginDefinition instance. */
+ optionValues?: {
+ /** @description Name of the values. */
+ name: string
+ /**
+ * @description Template is a Go string template that will be dynamically resolved for cluster-specific values.
+ * Only PluginOptionValues declared as template will be templated by the PluginController for Flux.
+ */
+ template?: string
+ /** @description Value is the actual value in plain text. */
+ value?: unknown
+ /** @description ValueFrom references a potentially confidential value in another source. */
+ valueFrom?: {
+ /** @description Secret references the secret containing the value. */
+ secret?: {
+ /** @description Key in the secret to select the value from. */
+ key: string
+ /** @description Name of the secret in the same namespace. */
+ name: string
+ }
+ }
+ }[]
+ /**
+ * @description PluginDefinition is the name of the PluginDefinition this instance is for.
+ *
+ * Deprecated: Use PluginDefinitionRef instead. Future releases of greenhouse will remove this field.
+ */
+ pluginDefinition: string
+ /** @description PluginDefinitionRef is the reference to the (Cluster-)PluginDefinition. */
+ pluginDefinitionRef: {
+ /**
+ * @description Kind of the referent. Supported values: PluginDefinition, ClusterPluginDefinition.
+ * @enum {string}
+ */
+ kind?: "PluginDefinition" | "ClusterPluginDefinition"
+ /** @description Name of the referenced PluginDefinition or ClusterPluginDefinition resource. */
+ name?: string
+ }
+ /**
+ * @description ReleaseName is the name of the helm release in the remote cluster to which the backend is deployed.
+ * If the Plugin was already deployed, the Plugin's name is used as the release name.
+ * If this Plugin is newly created, the releaseName is defaulted to the PluginDefinitions HelmChart name.
+ */
+ releaseName?: string
+ /**
+ * @description ReleaseNamespace is the namespace in the remote cluster to which the backend is deployed.
+ * Defaults to the Greenhouse managed namespace if not set.
+ */
+ releaseNamespace?: string
+ /** @description WaitFor defines other Plugins to wait for before installing this Plugin. */
+ waitFor?: {
+ /** @description PluginRef defines a reference to the Plugin. */
+ pluginRef: {
+ /** @description Name of the Plugin. */
+ name?: string
+ /** @description PluginPreset is the name of the PluginPreset which creates the Plugin. */
+ pluginPreset?: string
+ }
+ }[]
+ }
+ /** @description PluginStatus defines the observed state of Plugin */
+ status?: {
+ /** @description Description provides additional details of the plugin. */
+ description?: string
+ /**
+ * @description ExposedServices provides an overview of the Plugins services that are centrally exposed.
+ * It maps the exposed URL to the service found in the manifest.
+ */
+ exposedServices?: {
+ [key: string]: {
+ /** @description Name is the name of the service in the target cluster. */
+ name: string
+ /** @description Namespace is the namespace of the service in the target cluster. */
+ namespace: string
+ /**
+ * Format: int32
+ * @description Port is the port of the service. Zero for ingresses where port is not applicable.
+ */
+ port?: number
+ /** @description Protocol is the protocol of the service. */
+ protocol?: string
+ /**
+ * @description Type is the type of exposed service.
+ * @default service
+ * @enum {string}
+ */
+ type: "service" | "ingress"
+ }
+ }
+ /** @description HelmChart contains a reference the helm chart used for the deployed pluginDefinition version. */
+ helmChart?: {
+ /** @description Name of the HelmChart chart. */
+ name: string
+ /** @description Repository of the HelmChart chart. */
+ repository: string
+ /** @description Version of the HelmChart chart. */
+ version: string
+ }
+ /**
+ * @description HelmReleaseStatus reflects the status of the latest HelmChart release.
+ * This is only configured if the pluginDefinition is backed by HelmChart.
+ */
+ helmReleaseStatus?: {
+ /** @description Diff contains the difference between the deployed helm chart and the helm chart in the last reconciliation */
+ diff?: string
+ /**
+ * Format: date-time
+ * @description FirstDeployed is the timestamp of the first deployment of the release.
+ */
+ firstDeployed?: string
+ /**
+ * Format: date-time
+ * @description LastDeployed is the timestamp of the last deployment of the release.
+ */
+ lastDeployed?: string
+ /** @description PluginOptionChecksum is the checksum of plugin option values. */
+ pluginOptionChecksum?: string
+ /** @description Status is the status of a HelmChart release. */
+ status: string
+ }
+ /** @description LastReconciledAt contains the value when the reconcile was last triggered via annotation. */
+ lastReconciledAt?: string
+ /** @description StatusConditions contain the different conditions that constitute the status of the Plugin. */
+ statusConditions?: {
+ conditions?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }[]
+ }
+ /** @description UIApplication contains a reference to the frontend that is used for the deployed pluginDefinition version. */
+ uiApplication?: {
+ /** @description Name of the UI application. */
+ name: string
+ /**
+ * @description URL specifies the url to a built javascript asset.
+ * By default, assets are loaded from the Juno asset server using the provided name and version.
+ */
+ url?: string
+ /** @description Version of the frontend application. */
+ version: string
+ }
+ /** @description Version contains the latest pluginDefinition version the config was last applied with successfully. */
+ version?: string
+ /**
+ * Format: int32
+ * @description Weight configures the order in which Plugins are shown in the Greenhouse UI.
+ */
+ weight?: number
+ }
+ }
+ /**
+ * TeamRoleBinding
+ * @description TeamRoleBinding is the Schema for the rolebindings API
+ */
+ TeamRoleBinding: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /** @description TeamRoleBindingSpec defines the desired state of a TeamRoleBinding */
+ spec?: {
+ /** @description ClusterSelector is used to select a Cluster or Clusters the TeamRoleBinding should be deployed to. */
+ clusterSelector?: {
+ /** @description Name of a single Cluster to select. */
+ clusterName?: string
+ /** @description LabelSelector is a label query over a set of Clusters. */
+ labelSelector?: {
+ /** @description matchExpressions is a list of label selector requirements. The requirements are ANDed. */
+ matchExpressions?: {
+ /** @description key is the label key that the selector applies to. */
+ key: string
+ /**
+ * @description operator represents a key's relationship to a set of values.
+ * Valid operators are In, NotIn, Exists and DoesNotExist.
+ */
+ operator: string
+ /**
+ * @description values is an array of string values. If the operator is In or NotIn,
+ * the values array must be non-empty. If the operator is Exists or DoesNotExist,
+ * the values array must be empty. This array is replaced during a strategic
+ * merge patch.
+ */
+ values?: string[]
+ }[]
+ /**
+ * @description matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
+ * map is equivalent to an element of matchExpressions, whose key field is "key", the
+ * operator is "In", and the values array contains only "value". The requirements are ANDed.
+ */
+ matchLabels?: {
+ [key: string]: string
+ }
+ }
+ }
+ /**
+ * @description CreateNamespaces when enabled the controller will create namespaces for RoleBindings if they do not exist.
+ * @default false
+ */
+ createNamespaces: boolean
+ /**
+ * @description Namespaces is a list of namespaces in the Greenhouse Clusters to apply the RoleBinding to.
+ * If empty, a ClusterRoleBinding will be created on the remote cluster, otherwise a RoleBinding per namespace.
+ */
+ namespaces?: string[]
+ /** @description TeamRef references a Greenhouse Team by name */
+ teamRef?: string
+ /** @description TeamRoleRef references a Greenhouse TeamRole by name */
+ teamRoleRef?: string
+ /** @description Usernames defines list of users to add to the (Cluster-)RoleBindings */
+ usernames?: string[]
+ }
+ /** @description TeamRoleBindingStatus defines the observed state of the TeamRoleBinding */
+ status?: {
+ /** @description PropagationStatus is the list of clusters the TeamRoleBinding is applied to */
+ clusters?: {
+ /** @description ClusterName is the name of the cluster the rbacv1 resources are created on. */
+ clusterName: string
+ /** @description Condition is the overall Status of the rbacv1 resources created on the cluster */
+ condition?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }
+ }[]
+ /** @description StatusConditions contain the different conditions that constitute the status of the TeamRoleBinding. */
+ statusConditions?: {
+ conditions?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }[]
+ }
+ }
+ }
+ /**
+ * TeamRole
+ * @description TeamRole is the Schema for the TeamRoles API
+ */
+ TeamRole: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /** @description TeamRoleSpec defines the desired state of a TeamRole */
+ spec?: {
+ /** @description AggregationRule describes how to locate ClusterRoles to aggregate into the ClusterRole on the remote cluster */
+ aggregationRule?: {
+ /**
+ * @description ClusterRoleSelectors holds a list of selectors which will be used to find ClusterRoles and create the rules.
+ * If any of the selectors match, then the ClusterRole's permissions will be added
+ */
+ clusterRoleSelectors?: {
+ /** @description matchExpressions is a list of label selector requirements. The requirements are ANDed. */
+ matchExpressions?: {
+ /** @description key is the label key that the selector applies to. */
+ key: string
+ /**
+ * @description operator represents a key's relationship to a set of values.
+ * Valid operators are In, NotIn, Exists and DoesNotExist.
+ */
+ operator: string
+ /**
+ * @description values is an array of string values. If the operator is In or NotIn,
+ * the values array must be non-empty. If the operator is Exists or DoesNotExist,
+ * the values array must be empty. This array is replaced during a strategic
+ * merge patch.
+ */
+ values?: string[]
+ }[]
+ /**
+ * @description matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
+ * map is equivalent to an element of matchExpressions, whose key field is "key", the
+ * operator is "In", and the values array contains only "value". The requirements are ANDed.
+ */
+ matchLabels?: {
+ [key: string]: string
+ }
+ }[]
+ }
+ /**
+ * @description Labels are applied to the ClusterRole created on the remote cluster.
+ * This allows using TeamRoles as part of AggregationRules by other TeamRoles
+ */
+ labels?: {
+ [key: string]: string
+ }
+ /** @description Rules is a list of rbacv1.PolicyRules used on a managed RBAC (Cluster)Role */
+ rules?: {
+ /**
+ * @description APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of
+ * the enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups.
+ */
+ apiGroups?: string[]
+ /**
+ * @description NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path
+ * Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding.
+ * Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both.
+ */
+ nonResourceURLs?: string[]
+ /** @description ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. */
+ resourceNames?: string[]
+ /** @description Resources is a list of resources this rule applies to. '*' represents all resources. */
+ resources?: string[]
+ /** @description Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. */
+ verbs: string[]
+ }[]
+ }
+ /** @description TeamRoleStatus defines the observed state of a TeamRole */
+ status?: Record
+ }
+ /**
+ * Team
+ * @description Team is the Schema for the teams API
+ */
+ Team: {
+ /**
+ * @description APIVersion defines the versioned schema of this representation of an object.
+ * Servers should convert recognized schemas to the latest internal value, and
+ * may reject unrecognized values.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ */
+ apiVersion?: string
+ /**
+ * @description Kind is a string value representing the REST resource this object represents.
+ * Servers may infer this from the endpoint the client submits requests to.
+ * Cannot be updated.
+ * In CamelCase.
+ * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ */
+ kind?: string
+ metadata?: {
+ name?: string
+ namespace?: string
+ /** Format: uuid */
+ uid?: string
+ resourceVersion?: string
+ /** Format: date-time */
+ creationTimestamp?: string
+ /** Format: date-time */
+ deletionTimestamp?: string
+ labels?: {
+ [key: string]: string
+ }
+ annotations?: {
+ [key: string]: string
+ }
+ }
+ /** @description TeamSpec defines the desired state of Team */
+ spec?: {
+ /** @description Description provides additional details of the team. */
+ description?: string
+ /** @description URL to join the IdP group. */
+ joinUrl?: string
+ /** @description IdP group id matching team. */
+ mappedIdPGroup?: string
+ }
+ /** @description TeamStatus defines the observed state of Team */
+ status?: {
+ members?: {
+ /** @description Email of the user. */
+ email: string
+ /** @description FirstName of the user. */
+ firstName: string
+ /** @description ID is the unique identifier of the user. */
+ id: string
+ /** @description LastName of the user. */
+ lastName: string
+ }[]
+ /**
+ * @description A StatusConditions contains a list of conditions.
+ * Only one condition of a given type may exist in the list.
+ */
+ statusConditions: {
+ conditions?: {
+ /**
+ * Format: date-time
+ * @description LastTransitionTime is the last time the condition transitioned from one status to another.
+ */
+ lastTransitionTime: string
+ /** @description Message is an optional human-readable message indicating details about the last transition. */
+ message?: string
+ /** @description Reason is a one-word, CamelCase reason for the condition's last transition. */
+ reason?: string
+ /** @description Status of the condition. */
+ status: string
+ /** @description Type of the condition. */
+ type: string
+ }[]
+ }
+ }
+ }
+ }
+ responses: never
+ parameters: never
+ requestBodies: never
+ headers: never
+ pathItems: never
+}
+export type $defs = Record
+export type operations = Record
diff --git a/apps/greenhouse/src/components/admin/utils.ts b/apps/greenhouse/src/components/admin/utils.ts
new file mode 100644
index 0000000000..6b4eb6e59a
--- /dev/null
+++ b/apps/greenhouse/src/components/admin/utils.ts
@@ -0,0 +1,20 @@
+import { PluginPreset } from "./types/k8sTypes"
+
+// Get the "Ready" condition from a PluginPreset
+export const getReadyCondition = (preset: PluginPreset) => {
+ return preset.status?.statusConditions?.conditions?.find((condition) => condition.type === "Ready")
+}
+
+// Check if a PluginPreset is ready
+export const isReady = (preset: PluginPreset) => {
+ const readyCondition = getReadyCondition(preset)
+ return readyCondition?.type === "Ready" && readyCondition?.status === "True"
+}
+
+// Replace _.isEmpty()
+export const isEmpty = (value: any): boolean => {
+ if (value == null) return true
+ if (typeof value === "string" || Array.isArray(value)) return value.length === 0
+ if (typeof value === "object") return Object.keys(value).length === 0
+ return false
+}
diff --git a/apps/greenhouse/src/routes/__root.tsx b/apps/greenhouse/src/routes/__root.tsx
index 4ac9c63f60..b1538c5e48 100644
--- a/apps/greenhouse/src/routes/__root.tsx
+++ b/apps/greenhouse/src/routes/__root.tsx
@@ -6,6 +6,7 @@
import React, { useEffect } from "react"
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"
import { useActions } from "@cloudoperators/juno-messages-provider"
+import type { createClient } from "@cloudoperators/juno-k8s-client"
import { AppProps } from "../Shell"
import ShellLayout from "../components/layout/ShellLayout"
import useApi from "../hooks/useApi"
@@ -13,6 +14,8 @@ import { usePlugin } from "../components/StoreProvider"
export type RouteContext = {
appProps: AppProps
+ apiClient: ReturnType | null
+ organization: string
}
export const Route = createRootRouteWithContext()({
diff --git a/apps/greenhouse/src/routes/admin/index.tsx b/apps/greenhouse/src/routes/admin/index.tsx
index af27dcf659..edc72b23fb 100644
--- a/apps/greenhouse/src/routes/admin/index.tsx
+++ b/apps/greenhouse/src/routes/admin/index.tsx
@@ -9,7 +9,7 @@ export const Route = createFileRoute("/admin/")({
beforeLoad: () => {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw redirect({
- to: "/admin/clusters",
+ to: "/admin/plugin-presets",
search: (prev) => ({ ...prev }),
})
},
diff --git a/apps/greenhouse/src/routes/admin/plugin-presets.tsx b/apps/greenhouse/src/routes/admin/plugin-presets.tsx
index b63c95eabf..d08b300cff 100644
--- a/apps/greenhouse/src/routes/admin/plugin-presets.tsx
+++ b/apps/greenhouse/src/routes/admin/plugin-presets.tsx
@@ -3,22 +3,37 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React from "react"
import { createFileRoute } from "@tanstack/react-router"
+import { z } from "zod"
import { Crumb } from "../-types"
+import { PluginPresets } from "../../components/admin/PluginPresets"
+import { fetchPluginPresets } from "../../components/admin/api/plugin-presets/fetchPluginPresets"
+
+const searchParamsSchema = z.object({
+ searchTerm: z.string().optional(),
+})
export const Route = createFileRoute("/admin/plugin-presets")({
- component: RouteComponent,
- loader: async () => {
+ component: PluginPresets,
+ validateSearch: (search: Record) => {
+ return searchParamsSchema.parse(search)
+ },
+ loaderDeps: (search) => ({
+ ...search,
+ }),
+ loader: async ({ context: { apiClient, organization }, deps: { search } }) => {
const crumb: Crumb = {
label: "Plugin Presets",
}
+ const filterSettings = {
+ searchTerm: search.searchTerm,
+ }
+ const pluginPresetsPromise = fetchPluginPresets({ apiClient, namespace: organization, filterSettings })
+
return {
crumb,
+ pluginPresetsPromise: pluginPresetsPromise,
+ filterSettings,
}
},
})
-
-function RouteComponent() {
- return