├── .checkstyle
├── checkstyle.xml
└── suppressions.xml
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── inform-the-direction-of-products.md
│ ├── open-support-case.md
│ └── share-feeedback.md
├── actions
│ ├── setup-minikube
│ │ └── action.yml
│ └── systemtests
│ │ ├── common.sh
│ │ ├── generate-test-matrix.sh
│ │ ├── parse-pr-comment-params.sh
│ │ ├── parse-results-update-pr.sh
│ │ ├── pr-help.sh
│ │ ├── prepare-systemtests-env.sh
│ │ ├── update-pr-systemtests-started.sh
│ │ ├── verify-team-member.sh
│ │ └── verify-workflow-run-succeeded.sh
├── dependabot.yml
├── gitops-config.json
├── project.yml
└── workflows
│ ├── dependabot-auto.yml
│ ├── integration.yml
│ ├── playwright-tests.yml
│ ├── pre-release.yml
│ ├── pre-systemtest.yml
│ ├── release.yml
│ ├── snapshot.yml
│ ├── sonar-analysis.yml
│ ├── storybook.yml
│ └── systemtests.yml
├── .gitignore
├── .snyk
├── .spotbugs
└── spotbugs-exclude.xml
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── api
├── .gitignore
├── CONTRIBUTING.md
├── README.md
├── TESTING.md
├── pom.xml
└── src
│ ├── main
│ ├── docker
│ │ └── Dockerfile
│ ├── java
│ │ └── com
│ │ │ └── github
│ │ │ └── streamshub
│ │ │ └── console
│ │ │ ├── LoggingConfigWatcher.java
│ │ │ └── api
│ │ │ ├── Annotations.java
│ │ │ ├── BlockingSupplier.java
│ │ │ ├── ClientFactory.java
│ │ │ ├── ConsumerGroupsResource.java
│ │ │ ├── KafkaClustersResource.java
│ │ │ ├── KafkaConnectorsResource.java
│ │ │ ├── KafkaConnectsResource.java
│ │ │ ├── KafkaRebalancesResource.java
│ │ │ ├── MetadataResource.java
│ │ │ ├── NodesResource.java
│ │ │ ├── RecordsResource.java
│ │ │ ├── SchemasResource.java
│ │ │ ├── TopicsResource.java
│ │ │ ├── errors
│ │ │ ├── AbstractExceptionHandler.java
│ │ │ ├── CompletionExceptionHandler.java
│ │ │ ├── SelectableExceptionMapper.java
│ │ │ ├── UnwrappingExceptionHandler.java
│ │ │ ├── WebApplicationExceptionHandler.java
│ │ │ ├── client
│ │ │ │ ├── AbstractClientExceptionHandler.java
│ │ │ │ ├── AbstractNotFoundExceptionHandler.java
│ │ │ │ ├── ConstraintViolationHandler.java
│ │ │ │ ├── ForbiddenExceptionHandler.java
│ │ │ │ ├── InvalidPageCursorException.java
│ │ │ │ ├── InvalidPageCursorExceptionHandler.java
│ │ │ │ ├── JsonProcessingExceptionMapper.java
│ │ │ │ ├── KafkaClientExceptionHandlers.java
│ │ │ │ ├── MismatchedInputExceptionMapper.java
│ │ │ │ ├── NotAllowedExceptionHandler.java
│ │ │ │ ├── NotAuthorizedExceptionHandler.java
│ │ │ │ ├── NotFoundExceptionHandler.java
│ │ │ │ ├── NotSupportedExceptionHandler.java
│ │ │ │ └── package-info.java
│ │ │ └── server
│ │ │ │ ├── AbstractServerExceptionHandler.java
│ │ │ │ ├── KafkaServerExceptionHandlers.java
│ │ │ │ ├── TimeoutExceptionHandler.java
│ │ │ │ ├── UnhandledThrowableHandler.java
│ │ │ │ └── package-info.java
│ │ │ ├── model
│ │ │ ├── Condition.java
│ │ │ ├── ConfigEntry.java
│ │ │ ├── ConsumerGroup.java
│ │ │ ├── ConsumerGroupFilterParams.java
│ │ │ ├── Either.java
│ │ │ ├── FetchFilter.java
│ │ │ ├── FetchParams.java
│ │ │ ├── FilterParams.java
│ │ │ ├── KafkaCluster.java
│ │ │ ├── KafkaClusterFilterParams.java
│ │ │ ├── KafkaListener.java
│ │ │ ├── KafkaRebalance.java
│ │ │ ├── KafkaRebalanceFilterParams.java
│ │ │ ├── KafkaRecord.java
│ │ │ ├── ListFetchParams.java
│ │ │ ├── MemberDescription.java
│ │ │ ├── Metrics.java
│ │ │ ├── NewTopic.java
│ │ │ ├── Node.java
│ │ │ ├── NodeFilterParams.java
│ │ │ ├── NodeSummary.java
│ │ │ ├── OffsetAndMetadata.java
│ │ │ ├── OffsetInfo.java
│ │ │ ├── PartitionId.java
│ │ │ ├── PartitionInfo.java
│ │ │ ├── PartitionReplica.java
│ │ │ ├── RecordFilterParams.java
│ │ │ ├── ReplicaAssignment.java
│ │ │ ├── ReplicaLocalStorage.java
│ │ │ ├── Topic.java
│ │ │ ├── TopicFilterParams.java
│ │ │ ├── TopicPatch.java
│ │ │ ├── connect
│ │ │ │ ├── ConnectCluster.java
│ │ │ │ ├── ConnectClusterFilterParams.java
│ │ │ │ ├── Connector.java
│ │ │ │ ├── ConnectorFilterParams.java
│ │ │ │ ├── ConnectorPlugin.java
│ │ │ │ └── ConnectorTask.java
│ │ │ ├── jsonapi
│ │ │ │ ├── ErrorSource.java
│ │ │ │ ├── Identifier.java
│ │ │ │ ├── JsonApiBase.java
│ │ │ │ ├── JsonApiData.java
│ │ │ │ ├── JsonApiError.java
│ │ │ │ ├── JsonApiErrors.java
│ │ │ │ ├── JsonApiMeta.java
│ │ │ │ ├── JsonApiRelationshipToMany.java
│ │ │ │ ├── JsonApiRelationshipToOne.java
│ │ │ │ ├── JsonApiResource.java
│ │ │ │ ├── JsonApiRoot.java
│ │ │ │ ├── JsonApiRootData.java
│ │ │ │ ├── JsonApiRootDataList.java
│ │ │ │ └── None.java
│ │ │ └── kubernetes
│ │ │ │ ├── KubeApiResource.java
│ │ │ │ ├── KubeAttributes.java
│ │ │ │ └── PaginatedKubeResource.java
│ │ │ ├── package-info.java
│ │ │ ├── security
│ │ │ ├── AuthorizationInterceptor.java
│ │ │ ├── Authorized.java
│ │ │ ├── ConsoleAuthenticationMechanism.java
│ │ │ ├── ConsolePermission.java
│ │ │ ├── ConsolePermissionPossessed.java
│ │ │ ├── ConsolePermissionRequired.java
│ │ │ ├── OidcTenantConfigResolver.java
│ │ │ ├── PermissionCache.java
│ │ │ ├── PermissionService.java
│ │ │ ├── ResourcePrivilege.java
│ │ │ └── SaslJaasConfigCredential.java
│ │ │ ├── service
│ │ │ ├── ConfigService.java
│ │ │ ├── ConsumerGroupService.java
│ │ │ ├── KafkaClusterService.java
│ │ │ ├── KafkaConnectService.java
│ │ │ ├── KafkaRebalanceService.java
│ │ │ ├── MetricsService.java
│ │ │ ├── NodeService.java
│ │ │ ├── RecordService.java
│ │ │ ├── StrimziResourceService.java
│ │ │ ├── TopicDescribeService.java
│ │ │ └── TopicService.java
│ │ │ └── support
│ │ │ ├── AbstractOperationFilter.java
│ │ │ ├── AuthenticationSupport.java
│ │ │ ├── ComparatorBuilder.java
│ │ │ ├── ConsumerGroupValidation.java
│ │ │ ├── EitherValueExtractors.java
│ │ │ ├── EnabledOperationFilter.java
│ │ │ ├── ErrorCategory.java
│ │ │ ├── FetchFilterPredicate.java
│ │ │ ├── FieldFilter.java
│ │ │ ├── Holder.java
│ │ │ ├── InformerFactory.java
│ │ │ ├── JacksonObjectMapperCustomizer.java
│ │ │ ├── KafkaConnectAPI.java
│ │ │ ├── KafkaContext.java
│ │ │ ├── KafkaOffsetSpec.java
│ │ │ ├── KafkaUuid.java
│ │ │ ├── ListRequestContext.java
│ │ │ ├── MetadataQuorumSupport.java
│ │ │ ├── OASModelFilter.java
│ │ │ ├── PrometheusAPI.java
│ │ │ ├── Promises.java
│ │ │ ├── SizeLimitedSortedSet.java
│ │ │ ├── StringEnumeration.java
│ │ │ ├── StringListParamConverterProvider.java
│ │ │ ├── TopicValidation.java
│ │ │ ├── TrustAllCertificateManager.java
│ │ │ ├── TrustStoreSupport.java
│ │ │ ├── TrustedTlsConfiguration.java
│ │ │ ├── UncheckedIO.java
│ │ │ ├── UnknownTopicIdPatch.java
│ │ │ ├── ValidationProxy.java
│ │ │ ├── factories
│ │ │ └── ConsoleConfigFactory.java
│ │ │ └── serdes
│ │ │ ├── ApicurioClient.java
│ │ │ ├── ArtifactReferences.java
│ │ │ ├── AvroDatumProvider.java
│ │ │ ├── AvroDeserializer.java
│ │ │ ├── ForceCloseable.java
│ │ │ ├── MultiformatDeserializer.java
│ │ │ ├── MultiformatSchemaParser.java
│ │ │ ├── MultiformatSerializer.java
│ │ │ ├── ProtobufDeserializer.java
│ │ │ ├── ProtobufSerializer.java
│ │ │ └── RecordData.java
│ └── resources
│ │ ├── application.properties
│ │ ├── metrics
│ │ └── queries
│ │ │ ├── kafkaCluster_ranges.promql
│ │ │ └── kafkaCluster_values.promql
│ │ └── openapi
│ │ └── examples
│ │ ├── createTopic-configs.json
│ │ ├── createTopic-simple.json
│ │ ├── createTopic-validateOnly.json
│ │ ├── patchConsumerGroup-allPartitions.json
│ │ ├── patchConsumerGroup-byPartition.json
│ │ ├── patchTopic-simple.json
│ │ └── patchTopic-validateOnly.json
│ └── test
│ ├── java
│ └── com
│ │ └── github
│ │ └── streamshub
│ │ └── console
│ │ ├── LoggingConfigWatcherTest.java
│ │ ├── api
│ │ ├── ConsumerGroupsResourceIT.java
│ │ ├── KafkaClustersResourceIT.java
│ │ ├── KafkaClustersResourceMetricsIT.java
│ │ ├── KafkaClustersResourceNoK8sIT.java
│ │ ├── KafkaClustersResourceOidcIT.java
│ │ ├── KafkaConnectorsResourceIT.java
│ │ ├── KafkaConnectsResourceIT.java
│ │ ├── KafkaRebalancesResourceIT.java
│ │ ├── KafkaRebalancesResourceOidcIT.java
│ │ ├── MetadataResourceIT.java
│ │ ├── MetadataResourceNoK8sIT.java
│ │ ├── NodesResourceIT.java
│ │ ├── RecordsResourceIT.java
│ │ ├── TopicsResourceIT.java
│ │ ├── TopicsResourceOidcIT.java
│ │ ├── service
│ │ │ └── ClusterServiceTest.java
│ │ └── support
│ │ │ ├── EnabledOperationFilterTest.java
│ │ │ ├── OASModelFilterTest.java
│ │ │ ├── OperationFilterTestProfile.java
│ │ │ ├── SizeLimitedSortedSetTest.java
│ │ │ ├── SourceTest.java
│ │ │ └── UuidValidatorTest.java
│ │ ├── kafka
│ │ └── systemtest
│ │ │ ├── TestPlainNoK8sProfile.java
│ │ │ ├── TestPlainProfile.java
│ │ │ ├── deployment
│ │ │ ├── ApicurioResourceManager.java
│ │ │ ├── KafkaResourceManager.java
│ │ │ ├── KeycloakResourceManager.java
│ │ │ ├── ResourceManagerBase.java
│ │ │ └── StrimziCrdResourceManager.java
│ │ │ └── utils
│ │ │ ├── ClientsConfig.java
│ │ │ ├── ConsumerUtils.java
│ │ │ └── TokenUtils.java
│ │ └── test
│ │ ├── AdminClientSpy.java
│ │ ├── LogCapture.java
│ │ ├── MockHelper.java
│ │ ├── RecordHelper.java
│ │ ├── TestHelper.java
│ │ ├── TlsHelper.java
│ │ ├── TopicHelper.java
│ │ └── VarargsAggregator.java
│ └── resources
│ ├── Dockerfile.apicurio
│ ├── Dockerfile.kafka
│ ├── Dockerfile.keycloak
│ ├── com
│ └── github
│ │ └── streamshub
│ │ └── console
│ │ └── api
│ │ └── KafkaConnect-fixtures.yaml
│ ├── keycloak
│ └── console-realm.json
│ ├── patchConsumerGroup-invalid-requests.txt
│ ├── patchTopic-invalid-requests.txt
│ └── systemtests-config.properties
├── common
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── github
│ │ └── streamshub
│ │ └── console
│ │ ├── config
│ │ ├── ConsoleConfig.java
│ │ ├── KafkaClusterConfig.java
│ │ ├── KafkaConfig.java
│ │ ├── KafkaConnectConfig.java
│ │ ├── KubernetesConfig.java
│ │ ├── Named.java
│ │ ├── PrometheusConfig.java
│ │ ├── SchemaRegistryConfig.java
│ │ ├── TrustStoreConfig.java
│ │ ├── Trustable.java
│ │ ├── Value.java
│ │ ├── authentication
│ │ │ ├── Authenticated.java
│ │ │ ├── AuthenticationConfig.java
│ │ │ ├── Basic.java
│ │ │ ├── Bearer.java
│ │ │ └── OIDC.java
│ │ └── security
│ │ │ ├── AuditConfig.java
│ │ │ ├── Decision.java
│ │ │ ├── GlobalSecurityConfig.java
│ │ │ ├── KafkaSecurityConfig.java
│ │ │ ├── OidcConfig.java
│ │ │ ├── Privilege.java
│ │ │ ├── ResourceTypes.java
│ │ │ ├── RoleConfig.java
│ │ │ ├── RuleConfig.java
│ │ │ ├── SecurityConfig.java
│ │ │ └── SubjectConfig.java
│ │ └── support
│ │ ├── Identifiers.java
│ │ └── RootCause.java
│ └── test
│ └── java
│ └── com
│ └── github
│ └── streamshub
│ └── console
│ └── config
│ ├── ConsoleConfigTest.java
│ └── security
│ └── RuleConfigTest.java
├── compose.yaml
├── coverage
└── pom.xml
├── dco.txt
├── docs
├── .gitignore
├── resources
│ ├── console-consumer-groups.png
│ ├── console-nodes.png
│ ├── console-overview.png
│ └── console-topics.png
└── sources
│ ├── README.md
│ ├── assemblies
│ ├── assembly-deploying-operator.adoc
│ └── assembly-deploying.adoc
│ ├── attributes.adoc
│ ├── index.adoc
│ └── modules
│ ├── con-brokers-page.adoc
│ ├── con-checking-connect-clusters.adoc
│ ├── con-checking-connectors.adoc
│ ├── con-cluster-overview-page.adoc
│ ├── con-connect-page.adoc
│ ├── con-consumer-groups-page.adoc
│ ├── con-homepage-checking-connected-users.adoc
│ ├── con-navigating-the-console.adoc
│ ├── con-topics-page.adoc
│ ├── con-using-the-console.adoc
│ ├── deploying
│ ├── con-deploying-prereqs.adoc
│ ├── proc-connecting-console.adoc
│ ├── proc-deploying-kafka.adoc
│ ├── proc-deploying-operator-crd.adoc
│ ├── proc-deploying-operator-olm-cli.adoc
│ ├── proc-deploying-operator-olm-ui.adoc
│ ├── ref-authentication-options.adoc
│ ├── ref-kafka-connect-clusters.adoc
│ ├── ref-metrics-options.adoc
│ └── ref-schema-registries.adoc
│ ├── proc-accessing-connection-details.adoc
│ ├── proc-changing-topic-configuration.adoc
│ ├── proc-checking-broker-configuration.adoc
│ ├── proc-checking-consumer-groups-members.adoc
│ ├── proc-checking-topic-configuration.adoc
│ ├── proc-checking-topic-consumer-groups.adoc
│ ├── proc-checking-topic-messages.adoc
│ ├── proc-checking-topic-partitions.adoc
│ ├── proc-creating-topics.adoc
│ ├── proc-deleting-topics.adoc
│ ├── proc-logging-in.adoc
│ ├── proc-managing-rebalances.adoc
│ ├── proc-pausing-reconciliation.adoc
│ └── proc-resetting-consumer-offsets.adoc
├── examples
├── console-config.yaml
├── console
│ ├── 010-Console-example.yaml
│ ├── console-openshift-metrics-self-signed-cluster-ca.yaml
│ ├── console-openshift-metrics.yaml
│ ├── console-remote-kafka.yaml
│ ├── console-security-oidc.yaml
│ └── console-standalone-prometheus.yaml
├── dex-openshift
│ ├── 020-ClusterRole-console-dex.yaml
│ ├── 030-ClusterRoleBinding-console-dex.yaml
│ ├── 040-Secret-console-dex.yaml
│ ├── 050-Deployment-console-dex.yaml
│ ├── 060-Service-console-dex.yaml
│ ├── 070-Ingress-console-dex.yaml
│ └── README.md
├── kafka
│ ├── 010-ConfigMap-console-kafka-metrics.yaml
│ ├── 020-KafkaNodePool-broker-console-nodepool.yaml
│ ├── 021-KafkaNodePool-controller-console-nodepool.yaml
│ ├── 030-Kafka-console-kafka.yaml
│ ├── 040-KafkaUser-console-kafka-user1.yaml
│ └── 050-KafkaTopic-console-topic.yaml
└── prometheus
│ ├── 010-ServiceAccount-console-prometheus-server.yaml
│ ├── 020-ClusterRole-console-prometheus-server.yaml
│ ├── 030-ClusterRoleBinding-console-prometheus-server.yaml
│ ├── 040-PodMonitor-kafka-resources.yaml
│ ├── 050-Secret-kubernetes-scrape-configs.yaml
│ ├── 060-Prometheus-console-prometheus.yaml
│ ├── 070-Service-console-prometheus.yaml
│ └── optional
│ └── 080-Ingress-console-prometheus.yaml
├── install
├── operator
│ └── olm
│ │ ├── 000-OperatorGroup-console-operator.yaml
│ │ ├── 010-CatalogSource-console-operator-catalog.yaml
│ │ └── 020-Subscription-console-operator.yaml
└── operatorless
│ ├── 010-ServiceAccount-console-server.yaml
│ ├── 020-ClusterRole-console-server.yaml
│ ├── 030-ClusterRoleBinding-console-server.yaml
│ ├── 040-Deployment-console.yaml
│ ├── 050-Service-console-ui.yaml
│ └── 060-Ingress-console-ui.yaml
├── operator
├── README.md
├── bin
│ ├── common.sh
│ ├── fetch-operator-utilities.sh
│ ├── generate-catalog.sh
│ ├── generate-community-catalog.sh
│ ├── modify-bundle-metadata.sh
│ ├── release-prepare.sh
│ └── version-check.sh
├── pom.xml
└── src
│ ├── main
│ ├── docker
│ │ ├── Dockerfile
│ │ └── catalog.Dockerfile
│ ├── java
│ │ └── com
│ │ │ └── github
│ │ │ └── streamshub
│ │ │ └── console
│ │ │ ├── ConsoleReconciler.java
│ │ │ ├── LeaderConfiguration.java
│ │ │ ├── ReconciliationException.java
│ │ │ ├── api
│ │ │ └── v1alpha1
│ │ │ │ ├── Console.java
│ │ │ │ ├── spec
│ │ │ │ ├── ConfigVar.java
│ │ │ │ ├── ConfigVarSource.java
│ │ │ │ ├── ConfigVars.java
│ │ │ │ ├── ConsoleSpec.java
│ │ │ │ ├── Credentials.java
│ │ │ │ ├── CredentialsKafkaUser.java
│ │ │ │ ├── Images.java
│ │ │ │ ├── KafkaCluster.java
│ │ │ │ ├── KafkaConnect.java
│ │ │ │ ├── SchemaRegistry.java
│ │ │ │ ├── TrustStore.java
│ │ │ │ ├── Value.java
│ │ │ │ ├── ValueReference.java
│ │ │ │ ├── authentication
│ │ │ │ │ ├── Authentication.java
│ │ │ │ │ ├── Basic.java
│ │ │ │ │ ├── Bearer.java
│ │ │ │ │ └── OIDC.java
│ │ │ │ ├── containers
│ │ │ │ │ ├── ContainerSpec.java
│ │ │ │ │ ├── ContainerTemplateSpec.java
│ │ │ │ │ └── Containers.java
│ │ │ │ ├── metrics
│ │ │ │ │ ├── MetricsSource.java
│ │ │ │ │ └── MetricsSourceAuthentication.java
│ │ │ │ └── security
│ │ │ │ │ ├── AuditRule.java
│ │ │ │ │ ├── GlobalSecurity.java
│ │ │ │ │ ├── KafkaSecurity.java
│ │ │ │ │ ├── Oidc.java
│ │ │ │ │ ├── Role.java
│ │ │ │ │ ├── Rule.java
│ │ │ │ │ ├── Security.java
│ │ │ │ │ └── Subject.java
│ │ │ │ └── status
│ │ │ │ ├── Condition.java
│ │ │ │ └── ConsoleStatus.java
│ │ │ └── dependents
│ │ │ ├── BaseClusterRole.java
│ │ │ ├── BaseClusterRoleBinding.java
│ │ │ ├── BaseDeployment.java
│ │ │ ├── BaseService.java
│ │ │ ├── BaseServiceAccount.java
│ │ │ ├── ConfigurationProcessor.java
│ │ │ ├── ConsoleClusterRole.java
│ │ │ ├── ConsoleClusterRoleBinding.java
│ │ │ ├── ConsoleDeployment.java
│ │ │ ├── ConsoleIngress.java
│ │ │ ├── ConsoleMonitoringClusterRoleBinding.java
│ │ │ ├── ConsoleResource.java
│ │ │ ├── ConsoleSecret.java
│ │ │ ├── ConsoleService.java
│ │ │ ├── ConsoleServiceAccount.java
│ │ │ ├── PrometheusClusterRole.java
│ │ │ ├── PrometheusClusterRoleBinding.java
│ │ │ ├── PrometheusConfigMap.java
│ │ │ ├── PrometheusDeployment.java
│ │ │ ├── PrometheusService.java
│ │ │ ├── PrometheusServiceAccount.java
│ │ │ ├── conditions
│ │ │ ├── DeploymentReadyCondition.java
│ │ │ ├── IngressReadyCondition.java
│ │ │ └── PrometheusPrecondition.java
│ │ │ └── support
│ │ │ └── ConfigSupport.java
│ ├── kubernetes
│ │ └── kubernetes.yml
│ ├── olm
│ │ ├── bundles
│ │ │ ├── README.md
│ │ │ ├── streamshub-console-operator.v0.10.0.yaml
│ │ │ ├── streamshub-console-operator.v0.10.1.yaml
│ │ │ ├── streamshub-console-operator.v0.10.2.yaml
│ │ │ ├── streamshub-console-operator.v0.10.3.yaml
│ │ │ ├── streamshub-console-operator.v0.5.0.yaml
│ │ │ ├── streamshub-console-operator.v0.6.0.yaml
│ │ │ ├── streamshub-console-operator.v0.6.1.yaml
│ │ │ ├── streamshub-console-operator.v0.6.2.yaml
│ │ │ ├── streamshub-console-operator.v0.6.3.yaml
│ │ │ ├── streamshub-console-operator.v0.6.4.yaml
│ │ │ ├── streamshub-console-operator.v0.6.5.yaml
│ │ │ ├── streamshub-console-operator.v0.6.6.yaml
│ │ │ ├── streamshub-console-operator.v0.6.7.yaml
│ │ │ ├── streamshub-console-operator.v0.7.0.yaml
│ │ │ ├── streamshub-console-operator.v0.8.0.yaml
│ │ │ ├── streamshub-console-operator.v0.8.1.yaml
│ │ │ ├── streamshub-console-operator.v0.8.2.yaml
│ │ │ ├── streamshub-console-operator.v0.8.3.yaml
│ │ │ ├── streamshub-console-operator.v0.8.4.yaml
│ │ │ ├── streamshub-console-operator.v0.8.5.yaml
│ │ │ ├── streamshub-console-operator.v0.8.6.yaml
│ │ │ └── streamshub-console-operator.v0.9.0.yaml
│ │ ├── channel.0.10.x.yaml
│ │ ├── channel.0.11.x.yaml
│ │ ├── channel.0.5.x.yaml
│ │ ├── channel.0.6.x.yaml
│ │ ├── channel.0.7.x.yaml
│ │ ├── channel.0.8.x.yaml
│ │ ├── channel.0.9.x.yaml
│ │ ├── channel.alpha.yaml
│ │ ├── icon.png
│ │ └── package.yaml
│ └── resources
│ │ ├── application.properties
│ │ └── com
│ │ └── github
│ │ └── streamshub
│ │ └── console
│ │ └── dependents
│ │ ├── console-monitoring.clusterrolebinding.yaml
│ │ ├── console.clusterrole.yaml
│ │ ├── console.clusterrolebinding.yaml
│ │ ├── console.deployment.yaml
│ │ ├── console.ingress.yaml
│ │ ├── console.kafkauser.yaml
│ │ ├── console.service.yaml
│ │ ├── console.serviceaccount.yaml
│ │ ├── prometheus.clusterrole.yaml
│ │ ├── prometheus.clusterrolebinding.yaml
│ │ ├── prometheus.configmap.yaml
│ │ ├── prometheus.deployment.yaml
│ │ ├── prometheus.service.yaml
│ │ └── prometheus.serviceaccount.yaml
│ └── test
│ ├── example-console.yaml
│ ├── java
│ └── com
│ │ └── github
│ │ └── streamshub
│ │ └── console
│ │ ├── ConsoleReconcilerSecurityTest.java
│ │ ├── ConsoleReconcilerTest.java
│ │ └── ConsoleReconcilerTestBase.java
│ └── resources
│ └── com
│ └── github
│ └── streamshub
│ └── console
│ ├── kube-apiserver-lb-signer.pem
│ ├── kube-certs.jks
│ └── kube-certs.pem
├── pom.xml
├── systemtests
├── config.yaml
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── github
│ │ │ └── streamshub
│ │ │ └── systemtests
│ │ │ ├── Environment.java
│ │ │ ├── MessageStore.java
│ │ │ ├── SystemTestExecutionListener.java
│ │ │ ├── TestCaseConfig.java
│ │ │ ├── TestExecutionWatcher.java
│ │ │ ├── annotations
│ │ │ ├── SetupTestBucket.java
│ │ │ └── TestBucket.java
│ │ │ ├── clients
│ │ │ └── KafkaClients.java
│ │ │ ├── constants
│ │ │ ├── AuthTestConstants.java
│ │ │ ├── Constants.java
│ │ │ ├── ExampleFiles.java
│ │ │ ├── Labels.java
│ │ │ ├── ResourceConditions.java
│ │ │ ├── TestTags.java
│ │ │ └── TimeConstants.java
│ │ │ ├── enums
│ │ │ ├── BrowserTypes.java
│ │ │ ├── ConditionStatus.java
│ │ │ ├── FilterType.java
│ │ │ ├── ResetOffsetDateTimeType.java
│ │ │ ├── ResetOffsetType.java
│ │ │ ├── ResourceStatus.java
│ │ │ └── TopicStatus.java
│ │ │ ├── exceptions
│ │ │ ├── ClusterUnreachableException.java
│ │ │ ├── OperatorSdkNotInstalledException.java
│ │ │ ├── PlaywrightActionExecutionException.java
│ │ │ ├── SetupException.java
│ │ │ └── UnsupportedKafkaRoleException.java
│ │ │ ├── interfaces
│ │ │ ├── BucketMethodsOrderRandomizer.java
│ │ │ ├── ExtensionContextParameterResolver.java
│ │ │ ├── TestBucketExtension.java
│ │ │ └── TestBucketExtensionContext.java
│ │ │ ├── locators
│ │ │ ├── ClusterOverviewPageSelectors.java
│ │ │ ├── ConsumerGroupsPageSelectors.java
│ │ │ ├── CssBuilder.java
│ │ │ ├── CssSelectors.java
│ │ │ ├── KafkaDashboardPageSelectors.java
│ │ │ ├── MessagesPageSelectors.java
│ │ │ ├── NodesPageSelectors.java
│ │ │ ├── SingleConsumerGroupPageSelectors.java
│ │ │ └── TopicsPageSelectors.java
│ │ │ ├── logs
│ │ │ ├── LogWrapper.java
│ │ │ └── TestLogCollector.java
│ │ │ ├── resourcetypes
│ │ │ ├── ConsoleType.java
│ │ │ ├── KafkaTopicType.java
│ │ │ ├── KafkaType.java
│ │ │ └── KafkaUserType.java
│ │ │ ├── setup
│ │ │ ├── console
│ │ │ │ ├── ConsoleInstanceSetup.java
│ │ │ │ ├── ConsoleOperatorSetup.java
│ │ │ │ ├── InstallConfig.java
│ │ │ │ ├── OlmConfig.java
│ │ │ │ └── YamlConfig.java
│ │ │ ├── keycloak
│ │ │ │ ├── KeycloakConfig.java
│ │ │ │ ├── KeycloakSetup.java
│ │ │ │ ├── console-realm.json
│ │ │ │ ├── keycloak-instance.yaml
│ │ │ │ ├── postgres.yaml
│ │ │ │ ├── prepare_keycloak_operator.sh
│ │ │ │ ├── prepare_keycloak_truststore.sh
│ │ │ │ └── teardown_keycloak_operator.sh
│ │ │ └── strimzi
│ │ │ │ ├── KafkaSetup.java
│ │ │ │ └── StrimziOperatorSetup.java
│ │ │ ├── upgrade
│ │ │ ├── OlmVersionModificationData.java
│ │ │ ├── VersionModificationDataLoader.java
│ │ │ └── YamlVersionModificationData.java
│ │ │ └── utils
│ │ │ ├── FileUtils.java
│ │ │ ├── SetupUtils.java
│ │ │ ├── Utils.java
│ │ │ ├── WaitUtils.java
│ │ │ ├── playwright
│ │ │ ├── PwPageUrls.java
│ │ │ └── PwUtils.java
│ │ │ ├── resourceutils
│ │ │ ├── ClusterUtils.java
│ │ │ ├── ConsoleUtils.java
│ │ │ ├── JobUtils.java
│ │ │ ├── KafkaClientsUtils.java
│ │ │ ├── KafkaCmdUtils.java
│ │ │ ├── KafkaNamingUtils.java
│ │ │ ├── KafkaTopicUtils.java
│ │ │ ├── KafkaUtils.java
│ │ │ ├── KeycloakUtils.java
│ │ │ ├── NamespaceUtils.java
│ │ │ ├── PodUtils.java
│ │ │ └── ResourceUtils.java
│ │ │ ├── testchecks
│ │ │ └── TopicChecks.java
│ │ │ └── testutils
│ │ │ ├── AuthTestSetupUtils.java
│ │ │ ├── ConsumerTestUtils.java
│ │ │ └── TopicsTestUtils.java
│ └── resources
│ │ └── log4j2.properties
│ └── test
│ ├── java
│ └── com
│ │ └── github
│ │ └── streamshub
│ │ └── systemtests
│ │ ├── AbstractST.java
│ │ ├── auth
│ │ └── AuthST.java
│ │ ├── consumers
│ │ └── ConsumerST.java
│ │ ├── kafka
│ │ └── KafkaST.java
│ │ ├── messages
│ │ └── MessagesST.java
│ │ ├── topics
│ │ └── TopicST.java
│ │ └── upgrade
│ │ ├── AbstractUpgradeST.java
│ │ ├── OlmUpgradeST.java
│ │ └── YamlUpgradeST.java
│ └── resources
│ ├── META-INF
│ └── services
│ │ └── org.junit.platform.launcher.TestExecutionListener
│ ├── junit-platform.properties
│ └── upgrade
│ ├── OlmUpgrade.yaml
│ └── YamlUpgrade.yaml
└── ui
├── .dockerignore
├── .eslintrc.json
├── .gitignore
├── .storybook
├── main.ts
├── preview-body.html
├── preview-head.html
└── preview.tsx
├── CONTRIBUTING.md
├── Dockerfile
├── api
├── api.ts
├── consumerGroups
│ ├── actions.ts
│ └── schema.ts
├── kafka
│ ├── actions.ts
│ └── schema.ts
├── kafkaConnect
│ ├── action.ts
│ └── schema.ts
├── messages
│ ├── actions.ts
│ └── schema.ts
├── meta
│ ├── actions.ts
│ └── schema.ts
├── nodes
│ ├── actions.ts
│ └── schema.ts
├── rebalance
│ ├── actions.ts
│ └── schema.ts
├── schema
│ └── action.ts
└── topics
│ ├── actions.ts
│ └── schema.ts
├── app
├── [locale]
│ ├── (authorized)
│ │ ├── AppSessionProvider.tsx
│ │ ├── SessionRefresher.tsx
│ │ ├── kafka
│ │ │ ├── [kafkaId]
│ │ │ │ ├── @activeBreadcrumb
│ │ │ │ │ ├── brokers
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── consumer-groups
│ │ │ │ │ │ ├── [groupId]
│ │ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ │ └── reset-offset
│ │ │ │ │ │ │ │ ├── dryrun
│ │ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── default.tsx
│ │ │ │ │ ├── kafka-connect
│ │ │ │ │ │ ├── [connectorId]
│ │ │ │ │ │ │ ├── ConnectorBreadcrumb.tsx
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── connect-clusters
│ │ │ │ │ │ │ ├── [clusterId]
│ │ │ │ │ │ │ │ ├── ConnectClusterBreadcrumb.tsx
│ │ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── nodes
│ │ │ │ │ │ ├── [nodeId]
│ │ │ │ │ │ │ ├── NodeBreadcrumb.tsx
│ │ │ │ │ │ │ └── configuration
│ │ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ └── rebalances
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── overview
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── schema-registry
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── topics
│ │ │ │ │ │ ├── [topicId]
│ │ │ │ │ │ ├── TopicBreadcrumb.tsx
│ │ │ │ │ │ ├── configuration
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── consumer-groups
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── delete
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── messages
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── partitions
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── schema-registry
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── create
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ ├── @header
│ │ │ │ │ ├── KafkaHeader.tsx
│ │ │ │ │ ├── consumer-groups
│ │ │ │ │ │ ├── [groupId]
│ │ │ │ │ │ │ ├── ConsumerGroupActionButton.tsx
│ │ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ │ └── reset-offset
│ │ │ │ │ │ │ │ ├── dryrun
│ │ │ │ │ │ │ │ ├── DryrunDownloadButton.tsx
│ │ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── default.tsx
│ │ │ │ │ ├── kafka-connect
│ │ │ │ │ │ ├── KafkaConnectTabs.tsx
│ │ │ │ │ │ ├── [connectorId]
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── connect-clusters
│ │ │ │ │ │ │ ├── [clusterId]
│ │ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── nodes
│ │ │ │ │ │ ├── NodesTabs.tsx
│ │ │ │ │ │ ├── [nodeId]
│ │ │ │ │ │ │ ├── NodeHeader.tsx
│ │ │ │ │ │ │ └── configuration
│ │ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ └── rebalances
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── overview
│ │ │ │ │ │ ├── ConnectButton.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── schema-registry
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── topics
│ │ │ │ │ │ ├── [topicId]
│ │ │ │ │ │ ├── TopicHeader.tsx
│ │ │ │ │ │ ├── TopicTabs.tsx
│ │ │ │ │ │ ├── configuration
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── consumer-groups
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── delete
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── messages
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── partitions
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── schema-registry
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── create
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ ├── @modal
│ │ │ │ │ ├── [...catchAll]
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── default.tsx
│ │ │ │ │ └── topics
│ │ │ │ │ │ └── [topicId]
│ │ │ │ │ │ └── delete
│ │ │ │ │ │ ├── DeleteTopicModal.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ ├── ClusterLinks.tsx
│ │ │ │ ├── KafkaBreadcrumbItem.tsx
│ │ │ │ ├── KafkaSwitcher.tsx
│ │ │ │ ├── consumer-groups
│ │ │ │ │ ├── ConnectedConsumerGroupTable.tsx
│ │ │ │ │ ├── ConsumerGroupsTable.stories.tsx
│ │ │ │ │ ├── ConsumerGroupsTable.tsx
│ │ │ │ │ ├── [groupId]
│ │ │ │ │ │ ├── KafkaConsumerGroupMembers.params.ts
│ │ │ │ │ │ ├── LagTable.stories.tsx
│ │ │ │ │ │ ├── LagTable.tsx
│ │ │ │ │ │ ├── MembersTable.stories.tsx
│ │ │ │ │ │ ├── MembersTable.tsx
│ │ │ │ │ │ ├── ResetOffsetModal.stories.tsx
│ │ │ │ │ │ ├── ResetOffsetModal.tsx
│ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ ├── reset-offset
│ │ │ │ │ │ │ ├── DryrunSelect.stories.tsx
│ │ │ │ │ │ │ ├── DryrunSelect.tsx
│ │ │ │ │ │ │ ├── LoadingPage.stories.tsx
│ │ │ │ │ │ │ ├── LoadingPage.tsx
│ │ │ │ │ │ │ ├── ResetConsumerOffset.tsx
│ │ │ │ │ │ │ ├── ResetOffset.stories.tsx
│ │ │ │ │ │ │ ├── ResetOffset.tsx
│ │ │ │ │ │ │ ├── SelectComponent.tsx
│ │ │ │ │ │ │ ├── TypeaheadSelect.stories.tsx
│ │ │ │ │ │ │ ├── TypeaheadSelect.tsx
│ │ │ │ │ │ │ ├── dryrun
│ │ │ │ │ │ │ │ ├── ConnectedDryrunPage.tsx
│ │ │ │ │ │ │ │ ├── Dryrun.stories.tsx
│ │ │ │ │ │ │ │ ├── Dryrun.tsx
│ │ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── types.ts
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── kafka-connect
│ │ │ │ │ ├── ConnectedConnectorsTable.tsx
│ │ │ │ │ ├── ConnectorsTable.stories.tsx
│ │ │ │ │ ├── ConnectorsTable.tsx
│ │ │ │ │ ├── ManagedConnectorLabel.tsx
│ │ │ │ │ ├── [...connectorId]
│ │ │ │ │ │ ├── ConnectorDetails.stories.tsx
│ │ │ │ │ │ ├── ConnectorDetails.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── connect-clusters
│ │ │ │ │ │ ├── ConnectClusterTable.stories.tsx
│ │ │ │ │ │ ├── ConnectClustersTable.tsx
│ │ │ │ │ │ ├── ConnectedConnectClustersTable.tsx
│ │ │ │ │ │ ├── [clusterId]
│ │ │ │ │ │ │ ├── ConnectClusterDeatils.stories.tsx
│ │ │ │ │ │ │ ├── ConnectClusterDetails.tsx
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── kafkaConnect.params.ts
│ │ │ │ │ ├── kafkaConnectors.params.ts
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── kafka.params.ts
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── nodes
│ │ │ │ │ ├── ConnectedNodesTable.tsx
│ │ │ │ │ ├── DistributionChart.stories.tsx
│ │ │ │ │ ├── DistributionChart.tsx
│ │ │ │ │ ├── NodesLabel.tsx
│ │ │ │ │ ├── NodesTable.tsx
│ │ │ │ │ ├── [nodeId]
│ │ │ │ │ │ ├── configuration
│ │ │ │ │ │ │ ├── ConfigTable.tsx
│ │ │ │ │ │ │ ├── NoResultsEmptyState.tsx
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── kafkaNode.params.ts
│ │ │ │ │ ├── nodesTable.stories.tsx
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ └── rebalances
│ │ │ │ │ │ ├── ConnectedRebalancesTable.tsx
│ │ │ │ │ │ ├── EmptyStateNoKafkaRebalance.stories.tsx
│ │ │ │ │ │ ├── EmptyStateNoKafkaRebalance.tsx
│ │ │ │ │ │ ├── RebalanceTable.stories.tsx
│ │ │ │ │ │ ├── RebalanceTable.tsx
│ │ │ │ │ │ ├── RebalancesCountCard.stories.tsx
│ │ │ │ │ │ ├── RebalancesCountCard.tsx
│ │ │ │ │ │ ├── ValidationModal.stories.tsx
│ │ │ │ │ │ ├── ValidationModal.tsx
│ │ │ │ │ │ ├── [rebalanceId]
│ │ │ │ │ │ ├── KafkaRebalance.params.ts
│ │ │ │ │ │ ├── OptimizationProposal.stories.tsx
│ │ │ │ │ │ ├── OptimizationProposal.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ ├── overview
│ │ │ │ │ ├── ConnectedClusterCard.tsx
│ │ │ │ │ ├── ConnectedClusterChartsCard.tsx
│ │ │ │ │ ├── ConnectedRecentTopics.tsx
│ │ │ │ │ ├── ConnectedTopicChartsCard.tsx
│ │ │ │ │ ├── ConnectedTopicsPartitionsCard.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ ├── schema-registry
│ │ │ │ │ └── page.tsx
│ │ │ │ └── topics
│ │ │ │ │ ├── (page)
│ │ │ │ │ ├── ConnectedTopicsTable.tsx
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ └── post-delete
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── [topicId]
│ │ │ │ │ ├── configuration
│ │ │ │ │ │ ├── ConfigTable.tsx
│ │ │ │ │ │ ├── NoResultsEmptyState.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── consumer-groups
│ │ │ │ │ │ ├── ConsumerGroupsTable.stories.tsx
│ │ │ │ │ │ ├── ConsumerGroupsTable.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── messages
│ │ │ │ │ │ ├── ConnectedMessagesTable.tsx
│ │ │ │ │ │ ├── page.tsx
│ │ │ │ │ │ ├── parseSearchParams.ts
│ │ │ │ │ │ └── useParseSearchParams.ts
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ ├── partitions
│ │ │ │ │ │ ├── NoResultsEmptyState.stories.tsx
│ │ │ │ │ │ ├── NoResultsEmptyState.tsx
│ │ │ │ │ │ ├── PartitionsTable.stories.tsx
│ │ │ │ │ │ ├── PartitionsTable.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── schema-registry
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── template.tsx
│ │ │ │ │ ├── create
│ │ │ │ │ ├── ConfigTable.tsx
│ │ │ │ │ ├── CreateTopic.stories.tsx
│ │ │ │ │ ├── CreateTopic.tsx
│ │ │ │ │ ├── Errors.tsx
│ │ │ │ │ ├── FieldName.stories.tsx
│ │ │ │ │ ├── FieldName.tsx
│ │ │ │ │ ├── FieldPartition.stories.tsx
│ │ │ │ │ ├── FieldPartitions.tsx
│ │ │ │ │ ├── FieldReplicas.stories.tsx
│ │ │ │ │ ├── FieldReplicas.tsx
│ │ │ │ │ ├── ReviewTable.tsx
│ │ │ │ │ ├── StepDetails.tsx
│ │ │ │ │ ├── StepOptions.tsx
│ │ │ │ │ ├── StepReview.tsx
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ └── topicMutateErrorToFieldError.ts
│ │ │ │ │ └── kafkaTopic.params.ts
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── (public)
│ │ ├── (home)
│ │ │ ├── ConnectedClustersTable.tsx
│ │ │ ├── home.module.css
│ │ │ └── page.tsx
│ │ ├── kafka
│ │ │ ├── [kafkaId]
│ │ │ │ └── login
│ │ │ │ │ ├── SignInPage.tsx
│ │ │ │ │ └── page.tsx
│ │ │ └── not-found.tsx
│ │ └── schema
│ │ │ ├── ConnectedSchema.tsx
│ │ │ └── page.tsx
│ ├── NextIntlProvider.tsx
│ ├── ThemeSelector.tsx
│ ├── layout.tsx
│ ├── not-found.tsx
│ └── useColorTheme.ts
├── api
│ └── auth
│ │ ├── [...nextauth]
│ │ ├── anonymous.ts
│ │ ├── auth-options.ts
│ │ ├── oauth-token.ts
│ │ ├── oidc.ts
│ │ ├── route.ts
│ │ └── scram.ts
│ │ └── oidc
│ │ ├── layout.tsx
│ │ └── signin
│ │ └── page.tsx
├── config
│ └── route.ts
├── error.tsx
├── global-error.tsx
├── globals.css
├── healthz
│ └── route.ts
├── icon.svg
├── layout.tsx
└── loading.tsx
├── components
├── AlertContext.tsx
├── AlertProvider.tsx
├── AlertToastGroup.tsx
├── AppDropdown.stories.tsx
├── AppDropdown.tsx
├── AppHeader.stories.tsx
├── AppHeader.tsx
├── AppLayout.tsx
├── AppLayoutProvider.tsx
├── AppMasthead.tsx
├── AppSidebar.tsx
├── ApplicationError.stories.tsx
├── ApplicationError.tsx
├── ClusterConnectionDetails.tsx
├── ClusterDrawer.tsx
├── ClusterDrawerContext.tsx
├── ClusterDrawerProvider.tsx
├── ClusterOverview
│ ├── ClusterCard.stories.tsx
│ ├── ClusterCard.tsx
│ ├── ClusterChartsCard.stories.tsx
│ ├── ClusterChartsCard.tsx
│ ├── PageLayout.stories.tsx
│ ├── PageLayout.tsx
│ ├── RecentTopicsCard.tsx
│ ├── ReconciliationModal.stories.tsx
│ ├── ReconciliationModal.tsx
│ ├── ReconciliationPauseButton.tsx
│ ├── TopicChartsCard.tsx
│ ├── TopicsPartitionsCard.stories.tsx
│ ├── TopicsPartitionsCard.tsx
│ └── components
│ │ ├── ChartCpuUsage.tsx
│ │ ├── ChartDiskUsage.stories.tsx
│ │ ├── ChartDiskUsage.tsx
│ │ ├── ChartIncomingOutgoing.tsx
│ │ ├── ChartMemoryUsage.tsx
│ │ ├── ChartSkeletonLoader.stories.tsx
│ │ ├── ChartSkeletonLoader.tsx
│ │ ├── ErrorsAndWarnings.stories.tsx
│ │ ├── ErrorsAndWarnings.tsx
│ │ ├── TopicsTable.stories.tsx
│ │ ├── TopicsTable.tsx
│ │ ├── chartConsts.ts
│ │ └── useChartWidth.tsx
├── ClustersTable.stories.tsx
├── ClustersTable.tsx
├── DeleteModal.stories.tsx
├── DeleteModal.tsx
├── EmptyStateLoading.stories.tsx
├── EmptyStateLoading.tsx
├── ExpandableCard.tsx
├── ExpandableSection.stories.tsx
├── ExpandableSection.tsx
├── Format
│ ├── Bytes.stories.tsx
│ ├── Bytes.tsx
│ ├── DateTime.stories.tsx
│ ├── DateTime.tsx
│ ├── Number.stories.tsx
│ └── Number.tsx
├── ManagedTopicLabel.stories.tsx
├── ManagedTopicLabel.tsx
├── MessagesTable
│ ├── AlertContinuousMode.stories.tsx
│ ├── AlertContinuousMode.tsx
│ ├── AlertTopicGone.stories.tsx
│ ├── AlertTopicGone.tsx
│ ├── MessagesTable.stories.tsx
│ ├── MessagesTable.tsx
│ ├── MessagesTableSkeleton.tsx
│ ├── NoDataEmptyState.stories.tsx
│ ├── NoDataEmptyState.tsx
│ ├── components
│ │ ├── AdvancedSearch.stories.tsx
│ │ ├── AdvancedSearch.tsx
│ │ ├── ColumnsModal.stories.tsx
│ │ ├── ColumnsModal.tsx
│ │ ├── DateTimePicker.stories.tsx
│ │ ├── DateTimePicker.tsx
│ │ ├── FromGroup.stories.tsx
│ │ ├── FromGroup.tsx
│ │ ├── LimitSelector.stories.tsx
│ │ ├── LimitSelector.tsx
│ │ ├── MessageDetails.stories.tsx
│ │ ├── MessageDetails.tsx
│ │ ├── MessagesTableToolbar.stories.tsx
│ │ ├── MessagesTableToolbar.tsx
│ │ ├── NoData.stories.tsx
│ │ ├── NoData.tsx
│ │ ├── NoResultsEmptyState.stories.tsx
│ │ ├── NoResultsEmptyState.tsx
│ │ ├── PartitionSelector.stories.tsx
│ │ ├── PartitionSelector.tsx
│ │ ├── SchemaValue.stories.tsx
│ │ ├── SchemaValue.tsx
│ │ ├── UnknownValuePreview.stories.tsx
│ │ ├── UnknownValuePreview.tsx
│ │ ├── UntilGroup.stories.tsx
│ │ ├── UntilGroup.tsx
│ │ ├── WhereSelector.tsx
│ │ ├── parseSearchInput.ts
│ │ └── utils.ts
│ └── types.ts
├── NavExpandable.tsx
├── Navigation
│ ├── BreadcrumbLink.stories.tsx
│ ├── BreadcrumbLink.tsx
│ ├── ButtonLink.stories.tsx
│ ├── ButtonLink.tsx
│ ├── ExternalLink.stories.tsx
│ ├── ExternalLink.tsx
│ ├── LabelLink.stories.tsx
│ ├── LabelLink.tsx
│ ├── NavItemLink.tsx
│ └── RedirectOnLoad.tsx
├── NoDataErrorState.tsx
├── Pagination.stories.tsx
├── Pagination.tsx
├── Quickstarts
│ ├── HelpContainer.tsx
│ └── help-topics.ts
├── ReconciliationContext.tsx
├── ReconciliationPausedBanner.tsx
├── ReconciliationProvider.tsx
├── RefreshButton.stories.tsx
├── RefreshButton.tsx
├── RichText.tsx
├── Table
│ ├── EmptyStateNoMatchFound.tsx
│ ├── ResponsiveTable.css
│ ├── ResponsiveTable.stories.tsx
│ ├── ResponsiveTable.tsx
│ ├── TableSkeleton.tsx
│ ├── TableView.stories.tsx
│ ├── TableView.tsx
│ ├── Toolbar
│ │ ├── ChipFilter
│ │ │ ├── ChipFilter.stories.tsx
│ │ │ ├── ChipFilter.tsx
│ │ │ ├── components
│ │ │ │ ├── FilterCheckbox.tsx
│ │ │ │ ├── FilterGroupedCheckbox.tsx
│ │ │ │ ├── FilterSearch.tsx
│ │ │ │ ├── FilterSelect.tsx
│ │ │ │ ├── FilterSwitcher.tsx
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── storybookHelpers.tsx
│ │ │ └── types.ts
│ │ ├── SearchInput.stories.tsx
│ │ ├── SearchInput.tsx
│ │ └── index.ts
│ ├── index.ts
│ └── storybookHelpers.tsx
├── TechPreviewPopover.stories.tsx
├── TechPreviewPopover.tsx
├── ThemeInitializer.tsx
├── TopicsTable
│ ├── TopicsTable.stories.tsx
│ ├── TopicsTable.tsx
│ └── components
│ │ ├── EmptyStateNoTopics.stories.tsx
│ │ └── EmptyStateNoTopics.tsx
└── UserDropdown.tsx
├── environment.d.ts
├── global.d.ts
├── i18n.tsx
├── i18n
├── request.ts
└── routing.ts
├── libs
└── patternfly
│ ├── quickstarts.ts
│ ├── react-charts.ts
│ ├── react-core.ts
│ ├── react-icons.ts
│ ├── react-table.ts
│ └── react-tokens.ts
├── messages
└── en.json
├── middleware.ts
├── next.config.js
├── package-lock.json
├── package.json
├── pom.xml
├── public
├── avatar_img.svg
├── full_logo_hori_default.svg
├── full_logo_hori_reverse.svg
├── home-hero-bg.png
├── pf_feedback.svg
├── pictogram.png
└── stop-icon.svg
├── tests
└── playwright
│ ├── .auth
│ └── .gitkeep
│ ├── ClusterOverview.test.tsx
│ ├── ConsumerGroupsPage.test.tsx
│ ├── ConsumerPage.test.tsx
│ ├── CreateTopicPage.test.tsx
│ ├── HomePage.test.tsx
│ ├── MessagesPage.test.tsx
│ ├── NodePropertyPage.test.tsx
│ ├── NodesPage.test.tsx
│ ├── PartitionsPage.test.tsx
│ ├── TopicConfigurationPage.test.tsx
│ ├── TopicConsumers.test.tsx
│ ├── TopicsPage.test.tsx
│ ├── auth.setup.ts
│ ├── authenticated-page.ts
│ ├── authenticated-test.ts
│ ├── playwright.config.ts
│ └── utils.ts
├── tsconfig.json
├── types
└── next-auth.d.ts
└── utils
├── config.ts
├── dateTime.ts
├── filterUndefinedFromObj.ts
├── logger.ts
├── loggerClient.ts
├── logout.ts
├── privileges.ts
├── session.ts
├── stringToBoolean.ts
├── stringToInt.ts
├── useFilterParams.ts
├── useFormatBytes.ts
└── usePagination.ts
/.checkstyle/suppressions.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
10 |
12 |
14 |
16 |
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/inform-the-direction-of-products.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Inform the direction of products
3 | about: Learn about opportunities to share your feedback with our User Research Team.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/open-support-case.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Open support case
3 | about: Get help and support.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/share-feeedback.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Share feeedback
3 | about: What has you console experience been like so far?
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/actions/systemtests/prepare-systemtests-env.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -xeuo pipefail
3 |
4 | echo "Prepare systemtests environment variables"
5 |
6 | # Export each key=value pair in ENVS
7 | IFS=';' read -ra KV_PAIRS <<< "$ENVS"
8 | for KV in "${KV_PAIRS[@]}"; do
9 | echo "$KV" >> "$GITHUB_ENV"
10 | done
11 |
12 | # Build Maven parameters
13 | PARAMETERS="-Dfailsafe.rerunFailingTestsCount=${RETRY_COUNT}"
14 |
15 | if [[ -n "$TEST_CASE" ]]; then
16 | PARAMETERS="$PARAMETERS -Dit.test=${TEST_CASE} -DskipSTs=false"
17 | fi
18 |
19 | if [[ -n "$PROFILE" ]]; then
20 | PARAMETERS="$PARAMETERS -P $PROFILE"
21 | fi
22 |
23 | # Export parameters
24 | echo "PARAMETERS=$PARAMETERS" >> "$GITHUB_ENV"
25 |
--------------------------------------------------------------------------------
/.github/actions/systemtests/verify-team-member.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -xeuo pipefail
3 |
4 | ORG=$1
5 | TEAM=$2
6 | COMMENTER=$3
7 |
8 | echo "Verify that user is a team member"
9 |
10 | echo "Fetching team members for $ORG/$TEAM..."
11 | members=$(gh api "orgs/$ORG/teams/$TEAM/members" --jq '.[].login' --paginate)
12 |
13 | if [[ -z "$members" ]]; then
14 | echo "❌ Unable to get team members"
15 | exit 1
16 | fi
17 |
18 | echo "Checking if $COMMENTER is in the team..."
19 |
20 | is_member=false
21 | while IFS= read -r member; do
22 | if [[ "$member" == "$COMMENTER" ]]; then
23 | is_member=true
24 | break
25 | fi
26 | done <<< "$members"
27 |
28 | if $is_member; then
29 | echo "✅ $COMMENTER IS a member of $ORG/$TEAM"
30 | else
31 | echo "❌ $COMMENTER is not allowed to trigger this workflow"
32 | exit 1
33 | fi
34 |
--------------------------------------------------------------------------------
/.github/gitops-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "platform": "github",
4 | "repositories": [ "eyefloaters/deployment-environments" ]
5 | }
6 |
--------------------------------------------------------------------------------
/.github/project.yml:
--------------------------------------------------------------------------------
1 | name: Streaming Platform Console
2 | release:
3 | current-version: 0.10.3
4 | next-version: 0.11.0-SNAPSHOT
5 |
--------------------------------------------------------------------------------
/.github/workflows/pre-release.yml:
--------------------------------------------------------------------------------
1 | name: Pre-Release
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - '.github/project.yml'
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | name: pre release
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v6
16 |
17 | - name: Retrieve Project Metadata
18 | uses: radcortez/project-metadata-action@874c89bea2ee8282008328c3418eec4d219013f3
19 | id: metadata
20 | with:
21 | metadata-file-path: '.github/project.yml'
22 |
23 | - name: Review Milestone
24 | uses: radcortez/milestone-review-action@e0f8f2498c85fa915c1181ff93e5c16a810506ef
25 | with:
26 | github-token: ${{secrets.GITHUB_TOKEN}}
27 | milestone-title: ${{steps.metadata.outputs.current-version}}
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_store
2 |
3 | # Maven build targets
4 | target/
5 |
6 | # Maven release files
7 | *.releaseBackup
8 | release.properties
9 |
10 | # Eclipse specific
11 | .project
12 | .settings/
13 | .prefs
14 | .classpath
15 |
16 | # IntelliJ IDEA specific
17 | .idea/
18 | *.iml
19 |
20 | # VS Code
21 | .factorypath
22 | .vscode
23 |
24 | # env files
25 | .env*
26 |
27 | # User-provided and generated compose configurations
28 | /compose-runtime.env
29 | /console-config.yaml
30 | /compose.env
31 |
32 | # Systemtests
33 | systemtests/screenshots/
34 | systemtests/config.yaml
35 | systemtests/tracing/
--------------------------------------------------------------------------------
/.snyk:
--------------------------------------------------------------------------------
1 | # See https://docs.snyk.io/snyk-cli/scan-and-maintain-projects-using-the-cli/snyk-cli-for-snyk-code/exclude-directories-and-files-from-snyk-code-cli-tests
2 | ignore: {}
3 | version: v1.25.0
4 | patch: {}
5 | exclude:
6 | global:
7 | # CWE-295 TrustAllCertificateManager is non-production code, therefore exclusion is reasonable
8 | - ./api/src/main/java/com/github/streamshub/console/api/support/TrustAllCertificateManager.java
9 |
--------------------------------------------------------------------------------
/.spotbugs/spotbugs-exclude.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/api/.gitignore:
--------------------------------------------------------------------------------
1 | /.env
2 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/BlockingSupplier.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api;
2 |
3 | import java.util.function.Supplier;
4 |
5 | import org.apache.kafka.common.KafkaFuture;
6 |
7 | public class BlockingSupplier {
8 |
9 | private BlockingSupplier() {
10 | // No instances
11 | }
12 |
13 | public static T get(Supplier> source) {
14 | return source.get()
15 | .toCompletionStage()
16 | .toCompletableFuture()
17 | .join();
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/errors/CompletionExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.errors;
2 |
3 | import java.util.concurrent.CompletionException;
4 |
5 | import jakarta.enterprise.context.ApplicationScoped;
6 | import jakarta.ws.rs.ext.Provider;
7 |
8 | @Provider
9 | @ApplicationScoped
10 | public class CompletionExceptionHandler extends UnwrappingExceptionHandler {
11 | }
12 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/errors/WebApplicationExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.errors;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 | import jakarta.ws.rs.WebApplicationException;
5 | import jakarta.ws.rs.ext.Provider;
6 |
7 | @Provider
8 | @ApplicationScoped
9 | public class WebApplicationExceptionHandler extends UnwrappingExceptionHandler {
10 | }
11 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/errors/client/AbstractNotFoundExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.errors.client;
2 |
3 | import com.github.streamshub.console.api.support.ErrorCategory;
4 |
5 | abstract class AbstractNotFoundExceptionHandler extends AbstractClientExceptionHandler {
6 |
7 | AbstractNotFoundExceptionHandler() {
8 | super(ErrorCategory.ResourceNotFound.class, null, (String) null);
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/errors/client/InvalidPageCursorException.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.errors.client;
2 |
3 | import java.util.List;
4 |
5 | public class InvalidPageCursorException extends RuntimeException {
6 |
7 | private static final long serialVersionUID = 1L;
8 |
9 | private final List sources;
10 |
11 | public InvalidPageCursorException(String message, List sources) {
12 | super(message);
13 | this.sources = sources;
14 | }
15 |
16 | @Override
17 | public synchronized Throwable fillInStackTrace() {
18 | return this;
19 | }
20 |
21 | public List getSources() {
22 | return sources;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/errors/client/NotAllowedExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.errors.client;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 | import jakarta.ws.rs.NotAllowedException;
5 | import jakarta.ws.rs.ext.Provider;
6 |
7 | import com.github.streamshub.console.api.support.ErrorCategory;
8 |
9 | @Provider
10 | @ApplicationScoped
11 | public class NotAllowedExceptionHandler extends AbstractClientExceptionHandler {
12 |
13 | public NotAllowedExceptionHandler() {
14 | super(ErrorCategory.MethodNotAllowed.class, "HTTP method not allowed for this resource", (String) null);
15 | }
16 |
17 | @Override
18 | public boolean handlesException(Throwable thrown) {
19 | return thrown instanceof NotAllowedException;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/errors/client/NotFoundExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.errors.client;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 | import jakarta.ws.rs.NotFoundException;
5 | import jakarta.ws.rs.ext.Provider;
6 |
7 | @Provider
8 | @ApplicationScoped
9 | public class NotFoundExceptionHandler extends AbstractNotFoundExceptionHandler {
10 | @Override
11 | public boolean handlesException(Throwable thrown) {
12 | return thrown instanceof NotFoundException;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/errors/client/NotSupportedExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.errors.client;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 | import jakarta.ws.rs.NotSupportedException;
5 | import jakarta.ws.rs.core.HttpHeaders;
6 | import jakarta.ws.rs.ext.Provider;
7 |
8 | import com.github.streamshub.console.api.support.ErrorCategory;
9 |
10 | @Provider
11 | @ApplicationScoped
12 | public class NotSupportedExceptionHandler extends AbstractClientExceptionHandler {
13 |
14 | public NotSupportedExceptionHandler() {
15 | super(ErrorCategory.UnsupportedMediaType.class, "Content-type not supported", HttpHeaders.CONTENT_TYPE);
16 | }
17 |
18 | @Override
19 | public boolean handlesException(Throwable thrown) {
20 | return thrown instanceof NotSupportedException;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/errors/client/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains custom exceptions and
3 | * {@linkplain jakarta.ws.rs.ext.ExceptionMapper ExceptionMapper}
4 | * implementations that deal with client errors. These are errors that are
5 | * caused by some kind of invalid request made by a client that result in one of
6 | * the 4XX series HTTP status codes.
7 | */
8 | package com.github.streamshub.console.api.errors.client;
9 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/errors/server/TimeoutExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.errors.server;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 | import jakarta.ws.rs.ext.ExceptionMapper;
5 | import jakarta.ws.rs.ext.Provider;
6 |
7 | import org.apache.kafka.common.errors.TimeoutException;
8 |
9 | import com.github.streamshub.console.api.support.ErrorCategory;
10 |
11 | @Provider
12 | @ApplicationScoped
13 | public class TimeoutExceptionHandler extends AbstractServerExceptionHandler implements ExceptionMapper {
14 |
15 | public TimeoutExceptionHandler() {
16 | super(ErrorCategory.BackendTimeout.class);
17 | }
18 |
19 | @Override
20 | public boolean handlesException(Throwable thrown) {
21 | return thrown instanceof TimeoutException;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/errors/server/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains custom exceptions and
3 | * {@linkplain jakarta.ws.rs.ext.ExceptionMapper ExceptionMapper}
4 | * implementations that deal with server errors. These are errors that are
5 | * caused by an unexpected or unhandled condition that result in one of the 5XX
6 | * series HTTP status codes.
7 | */
8 | package com.github.streamshub.console.api.errors.server;
9 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/Condition.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.annotation.JsonInclude.Include;
5 |
6 | @JsonInclude(value = Include.NON_NULL)
7 | public record Condition(
8 | String status,
9 | String reason,
10 | String message,
11 | String type,
12 | String lastTransitionTime) {
13 |
14 | public Condition(io.strimzi.api.kafka.model.common.Condition condition) {
15 | this(condition.getStatus(),
16 | condition.getReason(),
17 | condition.getMessage(),
18 | condition.getType(),
19 | condition.getLastTransitionTime());
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/KafkaListener.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model;
2 |
3 | import org.eclipse.microprofile.openapi.annotations.media.Schema;
4 |
5 | import io.strimzi.api.kafka.model.kafka.listener.KafkaListenerType;
6 |
7 | public record KafkaListener(
8 | @Schema(implementation = KafkaListenerType.class)
9 | String type,
10 | String bootstrapServers,
11 | String authType) {
12 | }
13 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/OffsetInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model;
2 |
3 | import java.time.Instant;
4 |
5 | import com.fasterxml.jackson.annotation.JsonInclude;
6 | import com.fasterxml.jackson.annotation.JsonInclude.Include;
7 |
8 | @JsonInclude(value = Include.NON_NULL)
9 | public record OffsetInfo(long offset, Instant timestamp, Integer leaderEpoch) {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/PartitionId.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model;
2 |
3 | import java.util.Collection;
4 | import java.util.Map;
5 | import java.util.Objects;
6 | import java.util.function.Function;
7 | import java.util.stream.Collectors;
8 |
9 | import org.apache.kafka.common.TopicPartition;
10 |
11 | public record PartitionId(String topicId, String topicName, int partition) {
12 |
13 | public static Map keyMap(Collection keys) {
14 | return keys.stream().collect(Collectors.toMap(PartitionId::toKafkaModel, Function.identity()));
15 | }
16 |
17 | public PartitionId {
18 | Objects.requireNonNull(topicId);
19 | Objects.requireNonNull(topicName);
20 | }
21 |
22 | public TopicPartition toKafkaModel() {
23 | return new TopicPartition(topicName, partition);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/jsonapi/Identifier.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model.jsonapi;
2 |
3 | import java.util.Objects;
4 |
5 | public record Identifier(String type, String id) {
6 |
7 | public boolean equals(String type, String id) {
8 | return Objects.equals(this.type, type) && Objects.equals(this.id, id);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/jsonapi/JsonApiData.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model.jsonapi;
2 |
3 | import jakarta.validation.Valid;
4 | import jakarta.validation.constraints.NotNull;
5 |
6 | import com.fasterxml.jackson.annotation.JsonProperty;
7 | import com.github.streamshub.console.api.support.ErrorCategory;
8 |
9 | public class JsonApiData extends JsonApiBase {
10 |
11 | @Valid
12 | @NotNull(payload = ErrorCategory.InvalidResource.class)
13 | private final T data;
14 |
15 | protected JsonApiData(T data) {
16 | this.data = data;
17 | }
18 |
19 | @JsonProperty
20 | public T getData() {
21 | return data;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/jsonapi/JsonApiErrors.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model.jsonapi;
2 |
3 | import java.util.List;
4 |
5 | import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
6 | import org.eclipse.microprofile.openapi.annotations.media.Schema;
7 |
8 | import com.github.streamshub.console.api.support.OASModelFilter;
9 |
10 | @Schema(extensions = @Extension(name = OASModelFilter.REMOVE, parseValue = true, value = "[ \"included\" ]"))
11 | public class JsonApiErrors extends JsonApiRoot {
12 |
13 | private final List errors;
14 |
15 | public JsonApiErrors(List errors) {
16 | this.errors = errors;
17 | }
18 |
19 | public List getErrors() {
20 | return errors;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/jsonapi/JsonApiRelationshipToMany.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model.jsonapi;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import com.fasterxml.jackson.annotation.JsonInclude;
7 | import com.fasterxml.jackson.annotation.JsonInclude.Include;
8 |
9 | /**
10 | * Representation of a JSON API resource relationship linking a resource to another or
11 | * providing meta information about the relationship.
12 | *
13 | * @see JSON API Document Structure, 7.2.2.2 Relationships
14 | */
15 | @JsonInclude(value = Include.NON_NULL)
16 | public class JsonApiRelationshipToMany extends JsonApiData> {
17 |
18 | public JsonApiRelationshipToMany() {
19 | this(new ArrayList<>());
20 | }
21 |
22 | public JsonApiRelationshipToMany(List data) {
23 | super(data);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/jsonapi/JsonApiRelationshipToOne.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model.jsonapi;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.annotation.JsonInclude.Include;
5 |
6 | /**
7 | * Representation of a JSON API resource relationship linking a resource to another or
8 | * providing meta information about the relationship.
9 | *
10 | * @see JSON API Document Structure, 7.2.2.2 Relationships
11 | */
12 | @JsonInclude(value = Include.NON_NULL)
13 | public class JsonApiRelationshipToOne extends JsonApiData {
14 |
15 | public JsonApiRelationshipToOne(Identifier data) {
16 | super(data);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/jsonapi/JsonApiRootData.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model.jsonapi;
2 |
3 | import jakarta.validation.Valid;
4 | import jakarta.validation.constraints.NotNull;
5 |
6 | import com.fasterxml.jackson.annotation.JsonProperty;
7 | import com.github.streamshub.console.api.support.ErrorCategory;
8 |
9 | public class JsonApiRootData extends JsonApiRoot {
10 |
11 | @Valid
12 | @NotNull(payload = ErrorCategory.InvalidResource.class)
13 | private final T data;
14 |
15 | protected JsonApiRootData(T data) {
16 | this.data = data;
17 | }
18 |
19 | @JsonProperty
20 | public T getData() {
21 | return data;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/jsonapi/JsonApiRootDataList.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model.jsonapi;
2 |
3 | import java.util.List;
4 |
5 | public class JsonApiRootDataList extends JsonApiRootData> {
6 |
7 | protected JsonApiRootDataList(List data) {
8 | super(data);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/model/jsonapi/None.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.model.jsonapi;
2 |
3 | import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
4 | import org.eclipse.microprofile.openapi.annotations.media.Schema;
5 |
6 | import com.github.streamshub.console.api.support.OASModelFilter;
7 |
8 | /**
9 | * Marker class used to allow nodes in the OpenAPI model to be pruned. For
10 | * example, classes that extend JsonApiResource and do not use the
11 | * `relationships` property may use this as the JsonApiResource generic type
12 | * parameter that represents the `relationships`.
13 | */
14 | @Schema(extensions = @Extension(name = OASModelFilter.REMOVE, parseValue = true, value = "true"))
15 | public final class None {
16 | }
17 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/security/Authorized.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.security;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | import jakarta.interceptor.InterceptorBinding;
9 |
10 | /**
11 | * Binding annotation to mark methods that should be intercepted by the
12 | * {@link AuthorizationInterceptor}.
13 | */
14 | @InterceptorBinding
15 | @Retention(RetentionPolicy.RUNTIME)
16 | @Target({ ElementType.TYPE, ElementType.METHOD })
17 | public @interface Authorized {
18 | }
19 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/security/ResourcePrivilege.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.security;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | import com.github.streamshub.console.config.security.Privilege;
9 |
10 | /**
11 | * Method annotation used by the {@link AuthorizationInterceptor} to declare
12 | * the privilege a principal must be granted to execute the annotated method.
13 | */
14 | @Retention(RetentionPolicy.RUNTIME)
15 | @Target(ElementType.METHOD)
16 | public @interface ResourcePrivilege {
17 |
18 | Privilege value() default Privilege.ALL;
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/support/UncheckedIO.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.support;
2 |
3 | import java.io.IOException;
4 | import java.io.UncheckedIOException;
5 | import java.util.function.Supplier;
6 |
7 | public interface UncheckedIO {
8 |
9 | R call() throws IOException;
10 |
11 | static R call(UncheckedIO io, Supplier exceptionMessage) {
12 | try {
13 | return io.call();
14 | } catch (IOException e) {
15 | throw new UncheckedIOException(exceptionMessage.get(), e);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/api/src/main/java/com/github/streamshub/console/api/support/serdes/ForceCloseable.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.support.serdes;
2 |
3 | import java.io.IOException;
4 |
5 | /**
6 | * The de-/serializers in this package are re-used between requests and have made
7 | * their {@code close} methods no-ops. This interface is implemented by each to perform
8 | * the actual close operations when a {@link com.github.streamshub.console.api.support.KafkaContext}
9 | * is disposed.
10 | */
11 | public interface ForceCloseable {
12 |
13 | void forceClose() throws IOException;
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/api/src/main/resources/openapi/examples/createTopic-configs.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "type": "topics",
4 | "attributes": {
5 | "name": "my-topic",
6 | "numPartitions": 3,
7 | "replicationFactor": 3,
8 | "configs": {
9 | "retention.ms": {
10 | "value": "86400000"
11 | }
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/api/src/main/resources/openapi/examples/createTopic-simple.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "type": "topics",
4 | "attributes": {
5 | "name": "my-topic",
6 | "numPartitions": 3,
7 | "replicationFactor": 3
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/api/src/main/resources/openapi/examples/createTopic-validateOnly.json:
--------------------------------------------------------------------------------
1 | {
2 | "meta": {
3 | "validateOnly": true
4 | },
5 | "data": {
6 | "type": "topics",
7 | "attributes": {
8 | "name": "my-topic",
9 | "numPartitions": 3,
10 | "replicationFactor": 3
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/api/src/main/resources/openapi/examples/patchConsumerGroup-allPartitions.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "type": "consumerGroups",
4 | "id": "my-group",
5 | "attributes": {
6 | "offsets": [
7 | {
8 | "topicId": "PjgNTE1MSYu0IUIhcBijaA",
9 | "offset": "earliest",
10 | "metadata": "Last reset @ 2023-11-02T18:37:28-04:00"
11 | }
12 | ]
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/api/src/main/resources/openapi/examples/patchTopic-simple.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "type": "topics",
4 | "id": "TjaapKOXR1-CNlZ1bDmYow",
5 | "attributes": {
6 | "name": "my-topic",
7 | "numPartitions": 6
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/api/src/main/resources/openapi/examples/patchTopic-validateOnly.json:
--------------------------------------------------------------------------------
1 | {
2 | "meta": {
3 | "validateOnly": true
4 | },
5 | "data": {
6 | "type": "topics",
7 | "id": "TjaapKOXR1-CNlZ1bDmYow",
8 | "attributes": {
9 | "name": "my-topic",
10 | "numPartitions": 6
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/api/src/test/java/com/github/streamshub/console/api/support/UuidValidatorTest.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.support;
2 |
3 | import org.junit.jupiter.api.BeforeEach;
4 | import org.junit.jupiter.params.ParameterizedTest;
5 | import org.junit.jupiter.params.provider.CsvSource;
6 |
7 | import static org.junit.jupiter.api.Assertions.assertEquals;
8 |
9 | class UuidValidatorTest {
10 |
11 | KafkaUuid.Validator target;
12 |
13 | @BeforeEach
14 | void setUp() {
15 | target = new KafkaUuid.Validator();
16 | }
17 |
18 | @ParameterizedTest
19 | @CsvSource({
20 | " , true",
21 | "'' , false",
22 | "'dfGTCUxRSQWxpl--2Ditng', true",
23 | "'invalid#$%^' , false"
24 | })
25 | void testIsValid(String value, boolean expectedResult) {
26 | boolean actualResult = target.isValid(value, null);
27 | assertEquals(expectedResult, actualResult);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/api/src/test/java/com/github/streamshub/console/test/MockHelper.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.test;
2 |
3 | import java.util.Map;
4 | import java.util.function.Function;
5 |
6 | import org.mockito.Mockito;
7 |
8 | public class MockHelper {
9 |
10 | public static T mockAll(Class mockType, Map, Object> properties) {
11 | T mock = Mockito.mock(mockType);
12 | properties.forEach((getter, value) -> Mockito.when(getter.apply(mock)).thenReturn(value));
13 | return mock;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/api/src/test/resources/Dockerfile.apicurio:
--------------------------------------------------------------------------------
1 | FROM quay.io/apicurio/apicurio-registry-mem:2.6.13.Final
2 | # No operations, this is only a placeholder used to manage the image
3 | # version via dependabot. The FROM statement is always expected to
4 | # present on the first line of this file.
5 |
--------------------------------------------------------------------------------
/api/src/test/resources/Dockerfile.kafka:
--------------------------------------------------------------------------------
1 | FROM quay.io/strimzi-test-container/test-container:0.113.0-kafka-4.1.0
2 | # No operations, this is only a placeholder used to manage the image
3 | # version via dependabot. The FROM statement is always expected to
4 | # present on the first line of this file.
5 |
--------------------------------------------------------------------------------
/api/src/test/resources/Dockerfile.keycloak:
--------------------------------------------------------------------------------
1 | FROM quay.io/keycloak/keycloak:26.4
2 | # No operations, this is only a placeholder used to manage the image
3 | # version via dependabot. The FROM statement is always expected to
4 | # present on the first line of this file.
5 |
--------------------------------------------------------------------------------
/api/src/test/resources/systemtests-config.properties:
--------------------------------------------------------------------------------
1 | systemtests.kafka.admin.acl.resource-operations={ "cluster": [ "describe", "alter" ], "group": [ "all", "delete", "describe", "read" ], "topic": [ "all", "alter", "alter_configs", "create", "delete", "describe", "describe_configs", "read", "write" ], "transactional_id": [ "all", "describe", "write" ] }
2 |
--------------------------------------------------------------------------------
/common/src/main/java/com/github/streamshub/console/config/KubernetesConfig.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.config;
2 |
3 | import io.sundr.builder.annotations.Buildable;
4 |
5 | @Buildable(editableEnabled = false)
6 | public class KubernetesConfig {
7 |
8 | boolean enabled = true;
9 |
10 | public boolean isEnabled() {
11 | return enabled;
12 | }
13 |
14 | public void setEnabled(boolean enabled) {
15 | this.enabled = enabled;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/common/src/main/java/com/github/streamshub/console/config/Named.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.config;
2 |
3 | import java.util.Collection;
4 |
5 | public interface Named {
6 |
7 | static boolean uniqueNames(Collection extends Named> items) {
8 | if (items == null) {
9 | return true;
10 | }
11 | return items.stream().map(Named::getName).distinct().count() == items.size();
12 | }
13 |
14 | String getName();
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/common/src/main/java/com/github/streamshub/console/config/Trustable.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.config;
2 |
3 | public interface Trustable extends Named {
4 |
5 | TrustStoreConfig getTrustStore();
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/common/src/main/java/com/github/streamshub/console/config/authentication/Authenticated.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.config.authentication;
2 |
3 | import java.util.Optional;
4 |
5 | import com.fasterxml.jackson.annotation.JsonIgnore;
6 | import com.github.streamshub.console.config.Trustable;
7 |
8 | public interface Authenticated extends Trustable {
9 |
10 | AuthenticationConfig getAuthentication();
11 |
12 | void setAuthentication(AuthenticationConfig authentication);
13 |
14 | @JsonIgnore
15 | default Optional getTrustableAuthentication() {
16 | return Optional.ofNullable(getAuthentication())
17 | .map(AuthenticationConfig::getOidc);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/common/src/main/java/com/github/streamshub/console/config/authentication/Bearer.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.config.authentication;
2 |
3 | import jakarta.validation.Valid;
4 |
5 | import com.fasterxml.jackson.annotation.JsonInclude;
6 | import com.fasterxml.jackson.annotation.JsonInclude.Include;
7 | import com.github.streamshub.console.config.Value;
8 |
9 | import io.sundr.builder.annotations.Buildable;
10 |
11 | @Buildable(editableEnabled = false)
12 | @JsonInclude(Include.NON_NULL)
13 | public class Bearer {
14 |
15 | @Valid
16 | private Value token;
17 |
18 | public Value getToken() {
19 | return token;
20 | }
21 |
22 | public void setToken(Value token) {
23 | this.token = token;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/common/src/main/java/com/github/streamshub/console/config/security/AuditConfig.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.config.security;
2 |
3 | import io.sundr.builder.annotations.Buildable;
4 |
5 | @Buildable(editableEnabled = false)
6 | public class AuditConfig extends RuleConfig {
7 |
8 | Decision decision;
9 |
10 | public Decision getDecision() {
11 | return decision;
12 | }
13 |
14 | public void setDecision(Decision decision) {
15 | this.decision = decision;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/common/src/main/java/com/github/streamshub/console/config/security/GlobalSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.config.security;
2 |
3 | import jakarta.validation.Valid;
4 |
5 | import io.sundr.builder.annotations.Buildable;
6 |
7 | @Buildable(editableEnabled = false)
8 | public class GlobalSecurityConfig extends SecurityConfig {
9 |
10 | @Valid
11 | private OidcConfig oidc;
12 |
13 | public OidcConfig getOidc() {
14 | return oidc;
15 | }
16 |
17 | public void setOidc(OidcConfig oidc) {
18 | this.oidc = oidc;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/common/src/main/java/com/github/streamshub/console/config/security/KafkaSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.config.security;
2 |
3 | import io.sundr.builder.annotations.Buildable;
4 |
5 | @Buildable(editableEnabled = false)
6 | public class KafkaSecurityConfig extends SecurityConfig {
7 | }
8 |
--------------------------------------------------------------------------------
/common/src/main/java/com/github/streamshub/console/config/security/RoleConfig.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.config.security;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import jakarta.validation.Valid;
7 | import jakarta.validation.constraints.NotBlank;
8 | import jakarta.validation.constraints.NotEmpty;
9 |
10 | import io.sundr.builder.annotations.Buildable;
11 |
12 | @Buildable(editableEnabled = false)
13 | public class RoleConfig {
14 |
15 | @NotBlank
16 | private String name;
17 |
18 | @Valid
19 | @NotEmpty
20 | private List rules = new ArrayList<>();
21 |
22 | public String getName() {
23 | return name;
24 | }
25 |
26 | public void setName(String name) {
27 | this.name = name;
28 | }
29 |
30 | public List getRules() {
31 | return rules;
32 | }
33 |
34 | public void setRules(List rules) {
35 | this.rules = rules;
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/common/src/main/java/com/github/streamshub/console/support/RootCause.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.support;
2 |
3 | import java.util.Optional;
4 |
5 | /**
6 | * Utility to find the root cause of a throwable
7 | */
8 | public final class RootCause {
9 |
10 | private RootCause() {
11 | }
12 |
13 | /**
14 | * Utility to find the root cause of a throwable.
15 | *
16 | * @param thrown the Throwable, possibly null
17 | * @return an optional containing the root cause of {@code thrown}, or empty
18 | * when it was {@code null}.
19 | */
20 | public static Optional of(Throwable thrown) {
21 | if (thrown == null) {
22 | return Optional.empty();
23 | }
24 |
25 | Throwable rootCause = thrown;
26 |
27 | while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
28 | rootCause = rootCause.getCause();
29 | }
30 |
31 | return Optional.of(rootCause);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/compose.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | version: '3.9'
3 |
4 | services:
5 | console-api:
6 | image: ${CONSOLE_API_IMAGE}
7 | container_name: console-api
8 | network_mode: host
9 | volumes:
10 | - ${PWD}/console-config.yaml:/deployments/console-config.yaml:z
11 | environment:
12 | CONSOLE_CONFIG_PATH: /deployments/console-config.yaml
13 | QUARKUS_KUBERNETES_CLIENT_API_SERVER_URL: ${CONSOLE_API_KUBERNETES_API_SERVER_URL}
14 | QUARKUS_KUBERNETES_CLIENT_TRUST_CERTS: "true"
15 | QUARKUS_KUBERNETES_CLIENT_TOKEN: ${CONSOLE_API_SERVICE_ACCOUNT_TOKEN}
16 |
17 | console-ui:
18 | image: ${CONSOLE_UI_IMAGE}
19 | container_name: console-ui
20 | network_mode: host
21 | environment:
22 | HOSTNAME: localhost
23 | PORT: 3005
24 | NEXTAUTH_SECRET: ${CONSOLE_UI_NEXTAUTH_SECRET}
25 | NEXTAUTH_URL: http://localhost:3005
26 | BACKEND_URL: http://localhost:8080/
27 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 |
--------------------------------------------------------------------------------
/docs/resources/console-consumer-groups.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamshub/console/e26188458c3e845b6daf0f984541644f17a09aaf/docs/resources/console-consumer-groups.png
--------------------------------------------------------------------------------
/docs/resources/console-nodes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamshub/console/e26188458c3e845b6daf0f984541644f17a09aaf/docs/resources/console-nodes.png
--------------------------------------------------------------------------------
/docs/resources/console-overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamshub/console/e26188458c3e845b6daf0f984541644f17a09aaf/docs/resources/console-overview.png
--------------------------------------------------------------------------------
/docs/resources/console-topics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamshub/console/e26188458c3e845b6daf0f984541644f17a09aaf/docs/resources/console-topics.png
--------------------------------------------------------------------------------
/docs/sources/attributes.adoc:
--------------------------------------------------------------------------------
1 | :ProductName: StreamsHub Console
2 |
3 | // AsciiDoc settings
4 | :data-uri!:
5 | :doctype: book
6 | :experimental:
7 | :idprefix:
8 | :imagesdir: images
9 | :numbered:
10 | :sectanchors!:
11 | :sectnums:
12 | :source-highlighter: highlightjs
13 | :toc: left
14 | :linkattrs:
15 | :toclevels: 4
16 |
17 | // Variables
18 | :minKubernetesVersion: 1.25
19 | :ReleaseDownload: https://github.com/streamshub/console/releases
20 | :BookURLDeploying: https://strimzi.io/docs/operators/latest/deploying
21 | :BookURLConfiguring: https://strimzi.io/docs/operators/latest/configuring
22 | //External links
23 | :ApicurioRegistrySite: https://www.apicur.io/[Apicurio Registry^]
24 | :kafkaDoc: https://kafka.apache.org/documentation/[Apache Kafka documentation^]
25 |
26 | // Section enabler for conditioning text in/out
27 | :Section:
--------------------------------------------------------------------------------
/docs/sources/modules/con-cluster-overview-page.adoc:
--------------------------------------------------------------------------------
1 | :_mod-docs-content-type: CONCEPT
2 |
3 | [id='con-cluster-overview-page-{context}']
4 | = Cluster overview page
5 |
6 | [role="_abstract"]
7 | The *Cluster overview* page shows the status of a Kafka cluster.
8 | Use this page to assess broker readiness, identify cluster errors or warnings, and monitor overall health.
9 |
10 | At a glance, the page displays:
11 |
12 | * Number of topics and partitions
13 | * Replication status
14 | * Cluster metrics:
15 | ** Used disk space
16 | ** CPU utilization
17 | ** Memory usage
18 | * Topic metrics:
19 | ** Total incoming byte rate
20 | ** Total outgoing byte rate
21 |
22 | These metrics are presented in charts for quick scanning and analysis of cluster performance.
23 |
24 | To view more information, click on the broker and topic-related links.
--------------------------------------------------------------------------------
/docs/sources/modules/con-connect-page.adoc:
--------------------------------------------------------------------------------
1 | :_mod-docs-content-type: CONCEPT
2 |
3 | [id='con-connect-page-{context}']
4 | = Kafka Connect page
5 |
6 | [role="_abstract"]
7 | The *Kafka Connect* page lists Kafka Connect connectors and clusters associated with a Kafka cluster.
8 |
9 | You can filter each list by name.
10 |
11 | Connectors list:: Shows the state of each connector and the number of tasks it's running, as well as the associated Kafka Connect cluster
12 | Clusters list:: Shows the Kafka Connect version, with the number of workers (or replicas) in the cluster.
13 |
14 | If a connector is shown as *Managed*, it means that is managed using Strimzi.
15 |
16 | Use the information provided on the tabs to check the configuration of your Kafka Connect environment.
17 | Click on a name to view additional information on specific connectors and clusters.
--------------------------------------------------------------------------------
/docs/sources/modules/con-homepage-checking-connected-users.adoc:
--------------------------------------------------------------------------------
1 | :_mod-docs-content-type: CONCEPT
2 |
3 | [id='con-homepage-checking-connected-users-{context}']
4 | = HOME: Checking connected clusters
5 |
6 | [role="_abstract"]
7 | The homepage offers a snapshot of connected Kafka clusters, providing information on the Kafka version and associated project for each cluster.
8 | To find more information, log in to a cluster.
--------------------------------------------------------------------------------
/docs/sources/modules/con-using-the-console.adoc:
--------------------------------------------------------------------------------
1 | :_mod-docs-content-type: CONCEPT
2 |
3 | [id='con-using-the-console-{context}']
4 | = StreamsHub Console overview
5 |
6 | [role="_abstract"]
7 | The StreamsHub Console provides a user interface to facilitate the administration of Kafka clusters, delivering real-time insights for monitoring, managing, and optimizing each cluster from its user interface.
8 |
9 | Connect a Kafka cluster managed by Strimzi to gain real-time insights and optimize cluster performance from its user interface.
10 | The console's homepage displays connected Kafka clusters, allowing you to access detailed information on components such as brokers, topics, partitions, and consumer groups.
11 |
12 | From the console, view the status of a Kafka cluster before navigating to view information on the cluster’s brokers and topics, or the consumer groups connected to the Kafka cluster.
--------------------------------------------------------------------------------
/docs/sources/modules/proc-accessing-connection-details.adoc:
--------------------------------------------------------------------------------
1 | :_mod-docs-content-type: PROCEDURE
2 |
3 | [id='proc-accessing-connection-details-{context}']
4 | = Accessing cluster connection details for client access
5 |
6 | [role="_abstract"]
7 | Retrieve the necessary connection details from the *Cluster overview* page to connect a client to a Kafka cluster.
8 |
9 | .Procedure
10 |
11 | . Log in to the Kafka cluster in the StreamsHub Console.
12 | +
13 | On the *Cluster overview* page, click *Cluster connection details*.
14 | . Copy the bootstrap address (external or internal, depending on your client environment).
15 | . Add any required connection properties to your Kafka client configuration to establish a secure connection.
16 |
17 | NOTE: Ensure that the authentication type configured for the Kafka cluster matches the authentication type used by the client.
--------------------------------------------------------------------------------
/docs/sources/modules/proc-checking-broker-configuration.adoc:
--------------------------------------------------------------------------------
1 | :_mod-docs-content-type: PROCEDURE
2 |
3 | [id='proc-checking-broker-configuration-{context}']
4 | = Checking broker configuration
5 |
6 | [role="_abstract"]
7 | Check the configuration of a specific broker by clicking on a broker node ID.
8 | The broker page lists the configuration values for the broker.
9 |
10 | .Procedure
11 |
12 | . Log in to the Kafka cluster in the StreamsHub Console, then click *Kafka nodes*.
13 | . On the *Nodes* page, click the name of the broker you want to inspect.
14 | . Check the information on the broker page.
15 | +
16 | Filter the configuration properties to narrow results by data source:
17 | +
18 | * *DEFAULT_CONFIG*: The fallback value used when no other configuration is specified.
19 | * *STATIC_BROKER_CONFIG*: Predefined, broker-wide values that apply to all topics by default.
--------------------------------------------------------------------------------
/docs/sources/modules/proc-deleting-topics.adoc:
--------------------------------------------------------------------------------
1 | :_mod-docs-content-type: PROCEDURE
2 |
3 | [id='proc-deleting-topics-{context}']
4 | = Deleting topics
5 |
6 | [role="_abstract"]
7 | Delete topics from the *Topics* page.
8 |
9 | .Procedure
10 |
11 | . Log in to the Kafka cluster in the StreamsHub Console, then click *Topics*.
12 | . Select the options icon (three vertical dots) for the relevant topic and click *Delete*.
13 | . Enter the topic name to confirm the deletion.
--------------------------------------------------------------------------------
/docs/sources/modules/proc-pausing-reconciliation.adoc:
--------------------------------------------------------------------------------
1 | :_mod-docs-content-type: PROCEDURE
2 |
3 | [id='proc-pausing-reconciliation-{context}']
4 | = Pausing reconciliation of clusters
5 |
6 | [role="_abstract"]
7 | Pause cluster reconciliation from the *Cluster overview* page.
8 | While reconciliation is paused, changes to the cluster configuration using the `Kafka` custom resource are ignored until reconciliation is resumed.
9 |
10 | .Procedure
11 |
12 | . Log in to the Kafka cluster in the StreamsHub Console.
13 | +
14 | On the *Cluster overview* page, click *Pause reconciliation*.
15 | . Confirm the pause, after which the *Cluster overview* page shows a change of status warning that reconciliation is paused.
16 | . Click *Resume reconciliation* to restart reconciliation.
17 |
18 | NOTE: If the status change is not displayed after pausing reconciliation, try refreshing the page.
--------------------------------------------------------------------------------
/examples/dex-openshift/020-ClusterRole-console-dex.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: console-dex
6 | rules:
7 | - apiGroups: ["dex.coreos.com"] # API group created by dex
8 | resources: ["*"]
9 | verbs: ["*"]
10 | - apiGroups: ["apiextensions.k8s.io"]
11 | resources: ["customresourcedefinitions"]
12 | verbs: ["create"] # To manage its own resources, dex must be able to create customresourcedefinitions
13 |
--------------------------------------------------------------------------------
/examples/dex-openshift/030-ClusterRoleBinding-console-dex.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRoleBinding
4 | metadata:
5 | name: console-dex
6 | roleRef:
7 | apiGroup: rbac.authorization.k8s.io
8 | kind: ClusterRole
9 | name: console-dex
10 | subjects:
11 | - kind: ServiceAccount
12 | name: console-dex # Service account assigned to the dex pod
13 | namespace: ${NAMESPACE} # The namespace dex is running in
14 |
--------------------------------------------------------------------------------
/examples/dex-openshift/060-Service-console-dex.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | name: console-dex
6 | spec:
7 | type: ClusterIP
8 | ports:
9 | - name: dex
10 | port: 5556
11 | protocol: TCP
12 | targetPort: http
13 | selector:
14 | app: console-dex
15 |
--------------------------------------------------------------------------------
/examples/dex-openshift/070-Ingress-console-dex.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: Ingress
3 | apiVersion: networking.k8s.io/v1
4 | metadata:
5 | name: console-dex
6 | annotations:
7 | nginx.ingress.kubernetes.io/backend-protocol: HTTP
8 | route.openshift.io/termination: edge
9 | spec:
10 | defaultBackend:
11 | service:
12 | name: console-dex
13 | port:
14 | number: 5556
15 | rules:
16 | - host: console-dex.${CLUSTER_DOMAIN}
17 | http:
18 | paths:
19 | - pathType: ImplementationSpecific
20 | backend:
21 | service:
22 | name: console-dex
23 | port:
24 | number: 5556
25 |
--------------------------------------------------------------------------------
/examples/kafka/020-KafkaNodePool-broker-console-nodepool.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kafka.strimzi.io/v1beta2
3 | kind: KafkaNodePool
4 | metadata:
5 | name: console-brokers
6 | labels:
7 | strimzi.io/cluster: console-kafka
8 | spec:
9 | replicas: 3
10 | roles:
11 | - broker
12 | storage:
13 | type: jbod
14 | volumes:
15 | - deleteClaim: false
16 | id: 0
17 | size: 10Gi
18 | type: persistent-claim
19 |
--------------------------------------------------------------------------------
/examples/kafka/021-KafkaNodePool-controller-console-nodepool.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kafka.strimzi.io/v1beta2
3 | kind: KafkaNodePool
4 | metadata:
5 | name: console-controllers
6 | labels:
7 | strimzi.io/cluster: console-kafka
8 | spec:
9 | replicas: 3
10 | roles:
11 | - controller
12 | storage:
13 | type: jbod
14 | volumes:
15 | - deleteClaim: false
16 | id: 0
17 | size: 10Gi
18 | type: persistent-claim
19 |
--------------------------------------------------------------------------------
/examples/kafka/040-KafkaUser-console-kafka-user1.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kafka.strimzi.io/v1beta2
3 | kind: KafkaUser
4 | metadata:
5 | name: console-kafka-user1
6 | labels:
7 | strimzi.io/cluster: console-kafka
8 | spec:
9 | authentication:
10 | type: scram-sha-512
11 |
--------------------------------------------------------------------------------
/examples/kafka/050-KafkaTopic-console-topic.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: kafka.strimzi.io/v1beta2
3 | kind: KafkaTopic
4 | metadata:
5 | name: console-topic
6 | labels:
7 | strimzi.io/cluster: console-kafka
8 | spec:
9 | partitions: 1
10 | replicas: 1
11 | config:
12 | retention.ms: 7200000
13 | segment.bytes: 1073741824
--------------------------------------------------------------------------------
/examples/prometheus/010-ServiceAccount-console-prometheus-server.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: console-prometheus-server
5 |
--------------------------------------------------------------------------------
/examples/prometheus/020-ClusterRole-console-prometheus-server.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: console-prometheus-server
5 | labels:
6 | app: strimzi
7 | rules:
8 | - apiGroups: [ '' ]
9 | resources:
10 | - nodes
11 | - nodes/proxy
12 | - services
13 | - endpoints
14 | - pods
15 | verbs: [ get, list, watch ]
16 | - apiGroups:
17 | - extensions
18 | resources:
19 | - ingresses
20 | verbs: [ get, list, watch ]
21 | - nonResourceURLs: [ /metrics ]
22 | verbs: [ get ]
23 |
--------------------------------------------------------------------------------
/examples/prometheus/030-ClusterRoleBinding-console-prometheus-server.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRoleBinding
3 | metadata:
4 | name: console-prometheus-server
5 | roleRef:
6 | apiGroup: rbac.authorization.k8s.io
7 | kind: ClusterRole
8 | name: console-prometheus-server
9 | subjects:
10 | - kind: ServiceAccount
11 | name: console-prometheus-server
12 | namespace: ${NAMESPACE}
13 |
--------------------------------------------------------------------------------
/examples/prometheus/060-Prometheus-console-prometheus.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: monitoring.coreos.com/v1
2 | kind: Prometheus
3 | metadata:
4 | name: console-prometheus
5 | spec:
6 | replicas: 1
7 | serviceAccountName: console-prometheus-server
8 | podMonitorSelector:
9 | matchLabels:
10 | app: console-kafka-monitor
11 | serviceMonitorSelector: {}
12 | resources:
13 | requests:
14 | memory: 400Mi
15 | enableAdminAPI: false
16 | additionalScrapeConfigs:
17 | name: kubernetes-scrape-configs
18 | key: prometheus-additional.yaml
19 |
--------------------------------------------------------------------------------
/examples/prometheus/070-Service-console-prometheus.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: console-prometheus
5 | spec:
6 | type: ClusterIP
7 | ports:
8 | - port: 9090
9 | targetPort: 9090
10 | selector:
11 | prometheus: console-prometheus
12 |
--------------------------------------------------------------------------------
/examples/prometheus/optional/080-Ingress-console-prometheus.yaml:
--------------------------------------------------------------------------------
1 | kind: Ingress
2 | apiVersion: networking.k8s.io/v1
3 | metadata:
4 | name: console-prometheus-ingress
5 | annotations:
6 | nginx.ingress.kubernetes.io/backend-protocol: HTTP
7 | route.openshift.io/termination: none
8 | spec:
9 | defaultBackend:
10 | service:
11 | name: console-prometheus
12 | port:
13 | number: 9090
14 | rules:
15 | - host: console-prometheus.${CLUSTER_DOMAIN}
16 | http:
17 | paths:
18 | - pathType: ImplementationSpecific
19 | backend:
20 | service:
21 | name: console-prometheus
22 | port:
23 | number: 9090
24 |
--------------------------------------------------------------------------------
/install/operator/olm/000-OperatorGroup-console-operator.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: operators.coreos.com/v1
3 | kind: OperatorGroup
4 | metadata:
5 | name: streamshub-operators
6 | spec:
7 | targetNamespaces: []
8 | upgradeStrategy: Default
9 |
--------------------------------------------------------------------------------
/install/operator/olm/010-CatalogSource-console-operator-catalog.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: operators.coreos.com/v1alpha1
3 | kind: CatalogSource
4 | metadata:
5 | name: streamshub-console-catalog
6 | spec:
7 | displayName: StreamsHub
8 | image: quay.io/streamshub/console-operator-catalog:latest
9 | publisher: StreamsHub
10 | sourceType: grpc
11 |
--------------------------------------------------------------------------------
/install/operator/olm/020-Subscription-console-operator.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: operators.coreos.com/v1alpha1
3 | kind: Subscription
4 | metadata:
5 | name: streamshub-console-sub
6 | spec:
7 | name: streamshub-console-operator
8 | channel: alpha
9 | source: streamshub-console-catalog
10 | sourceNamespace: ${NAMESPACE}
11 |
--------------------------------------------------------------------------------
/install/operatorless/010-ServiceAccount-console-server.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: console-server
5 |
--------------------------------------------------------------------------------
/install/operatorless/030-ClusterRoleBinding-console-server.yaml:
--------------------------------------------------------------------------------
1 | kind: ClusterRoleBinding
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | metadata:
4 | name: console-server
5 | roleRef:
6 | apiGroup: rbac.authorization.k8s.io
7 | kind: ClusterRole
8 | name: console-server
9 | subjects:
10 | - kind: ServiceAccount
11 | name: console-server
12 | namespace: ${NAMESPACE}
13 |
--------------------------------------------------------------------------------
/install/operatorless/050-Service-console-ui.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: console-ui
5 | spec:
6 | ports:
7 | - port: 80
8 | targetPort: 3000
9 | selector:
10 | app: console
11 |
--------------------------------------------------------------------------------
/install/operatorless/060-Ingress-console-ui.yaml:
--------------------------------------------------------------------------------
1 | kind: Ingress
2 | apiVersion: networking.k8s.io/v1
3 | metadata:
4 | name: console-ui-ingress
5 | annotations:
6 | nginx.ingress.kubernetes.io/backend-protocol: HTTP
7 | route.openshift.io/termination: edge
8 | spec:
9 | defaultBackend:
10 | service:
11 | name: console-ui
12 | port:
13 | number: 80
14 | rules:
15 | - host: ${CONSOLE_HOSTNAME}
16 | http:
17 | paths:
18 | - pathType: ImplementationSpecific
19 | backend:
20 | service:
21 | name: console-ui
22 | port:
23 | number: 80
24 |
--------------------------------------------------------------------------------
/operator/bin/common.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | YQ="$(which yq 2>/dev/null)" || :
4 |
5 | if [ "${YQ}" == "" ] ; then
6 | echo -e "'yq' is not installed, please visit https://github.com/mikefarah/yq for more info"
7 | exit 1
8 | fi
9 |
10 | SKOPEO="$(which skopeo 2>/dev/null)" || :
11 |
12 | if [ "${SKOPEO}" == "" ] ; then
13 | echo "'skopeo' is not installed, please visit https://github.com/containers/skopeo/blob/main/install.md for more info"
14 | exit 1
15 | fi
16 |
17 | # Operator naming
18 | OPERATOR_NAME="streamshub-console-operator"
19 |
--------------------------------------------------------------------------------
/operator/bin/fetch-operator-utilities.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -Eeuo pipefail
4 |
5 | SCRIPT_PATH="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)"
6 |
7 | OPERATOR_SDK_VERSION="${OPERATOR_SDK_VERSION:-v1.39.2}"
8 | OPM_VERSION="${OPM_VERSION:-v1.51.0}"
9 |
10 | curl -L -s -o operator-sdk https://github.com/operator-framework/operator-sdk/releases/download/${OPERATOR_SDK_VERSION}/operator-sdk_linux_amd64
11 | chmod +x operator-sdk
12 | sudo cp -v operator-sdk /usr/bin/
13 | rm -vf operator-sdk
14 |
15 | curl -L -s -o opm https://github.com/operator-framework/operator-registry/releases/download/${OPM_VERSION}/linux-amd64-opm
16 | chmod +x opm
17 | sudo cp -v opm /usr/bin/
18 | rm -vf opm
19 |
--------------------------------------------------------------------------------
/operator/bin/version-check.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -xEeuo pipefail
4 |
5 | SCRIPT_PATH="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)"
6 | OPERATOR_PATH="$(cd -- "${SCRIPT_PATH}/.." >/dev/null 2>&1 ; pwd -P)"
7 |
8 | VERSION="${1?version is required}"
9 |
10 | source ${SCRIPT_PATH}/common.sh
11 | OPERATOR_CSV_NAME="${OPERATOR_NAME}.v${VERSION}"
12 | ALPHA_CHANNEL=${OPERATOR_PATH}/src/main/olm/channel.alpha.yaml
13 |
14 | if [ "$(${YQ} '(.entries[].name | select(. == "'${OPERATOR_CSV_NAME}'"))' ${ALPHA_CHANNEL})" != "" ] ; then
15 | echo "[INFO] Bundle ${OPERATOR_CSV_NAME} has an entry in ${ALPHA_CHANNEL}"
16 | exit 0;
17 | fi
18 |
19 | echo "[ERROR] Bundle ${OPERATOR_CSV_NAME} has no entry in ${ALPHA_CHANNEL}"
20 | exit 1;
21 |
--------------------------------------------------------------------------------
/operator/src/main/docker/catalog.Dockerfile:
--------------------------------------------------------------------------------
1 | # The base image is expected to contain
2 | # /bin/opm (with a serve subcommand) and /bin/grpc_health_probe
3 | FROM quay.io/operator-framework/opm:v1.61.0
4 |
5 | # Configure the entrypoint and command
6 | ENTRYPOINT ["/bin/opm"]
7 | CMD ["serve", "/configs", "--cache-dir=/tmp/cache"]
8 |
9 | # Copy declarative config root into image at /configs and pre-populate serve cache
10 | ADD target/catalog /configs
11 | RUN ["/bin/opm", "serve", "/configs", "--cache-dir=/tmp/cache", "--cache-only"]
12 |
13 | # Set DC-specific label for the location of the DC root directory
14 | # in the image
15 | LABEL operators.operatorframework.io.index.configs.v1=/configs
16 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/LeaderConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 |
5 | import io.javaoperatorsdk.operator.api.config.LeaderElectionConfiguration;
6 |
7 | @ApplicationScoped
8 | public class LeaderConfiguration extends LeaderElectionConfiguration {
9 | public LeaderConfiguration() {
10 | super("streamshub-console-operator-lease");
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/ReconciliationException.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console;
2 |
3 | public class ReconciliationException extends RuntimeException {
4 |
5 | private static final long serialVersionUID = 1L;
6 |
7 | public ReconciliationException(String message) {
8 | super(message);
9 | }
10 |
11 | public ReconciliationException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/ConfigVar.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | import io.sundr.builder.annotations.Buildable;
7 |
8 | @Buildable(editableEnabled = false)
9 | @JsonInclude(JsonInclude.Include.NON_NULL)
10 | public class ConfigVar {
11 |
12 | @JsonProperty("name")
13 | private String name;
14 |
15 | @JsonProperty("value")
16 | private String value;
17 |
18 | public String getName() {
19 | return name;
20 | }
21 |
22 | public void setName(String name) {
23 | this.name = name;
24 | }
25 |
26 | public String getValue() {
27 | return value;
28 | }
29 |
30 | public void setValue(String value) {
31 | this.value = value;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/ConfigVars.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import com.fasterxml.jackson.annotation.JsonInclude;
7 |
8 | import io.sundr.builder.annotations.Buildable;
9 |
10 | @Buildable(editableEnabled = false)
11 | @JsonInclude(JsonInclude.Include.NON_NULL)
12 | public class ConfigVars {
13 |
14 | List values = new ArrayList<>();
15 |
16 | List valuesFrom = new ArrayList<>();
17 |
18 | public List getValues() {
19 | return values;
20 | }
21 |
22 | public void setValues(List values) {
23 | this.values = values;
24 | }
25 |
26 | public List getValuesFrom() {
27 | return valuesFrom;
28 | }
29 |
30 | public void setValuesFrom(List valuesFrom) {
31 | this.valuesFrom = valuesFrom;
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/Credentials.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.annotation.JsonPropertyDescription;
5 |
6 | import io.fabric8.generator.annotation.ValidationRule;
7 | import io.sundr.builder.annotations.Buildable;
8 |
9 | @ValidationRule(value = "has(self.kafkaUser)", message = "kafkaUser is required")
10 | @Buildable(editableEnabled = false)
11 | @JsonInclude(JsonInclude.Include.NON_NULL)
12 | public class Credentials {
13 |
14 | @JsonPropertyDescription("Reference to a Strimzi KafkaUser resource")
15 | CredentialsKafkaUser kafkaUser;
16 |
17 | public CredentialsKafkaUser getKafkaUser() {
18 | return kafkaUser;
19 | }
20 |
21 | public void setKafkaUser(CredentialsKafkaUser user) {
22 | this.kafkaUser = user;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/CredentialsKafkaUser.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 |
5 | import io.fabric8.generator.annotation.Required;
6 | import io.sundr.builder.annotations.Buildable;
7 |
8 | @Buildable(editableEnabled = false)
9 | @JsonInclude(JsonInclude.Include.NON_NULL)
10 | public class CredentialsKafkaUser {
11 |
12 | @Required
13 | private String name;
14 |
15 | private String namespace;
16 |
17 | public String getName() {
18 | return name;
19 | }
20 |
21 | public void setName(String name) {
22 | this.name = name;
23 | }
24 |
25 | public String getNamespace() {
26 | return namespace;
27 | }
28 |
29 | public void setNamespace(String namespace) {
30 | this.namespace = namespace;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/Images.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 |
5 | import io.sundr.builder.annotations.Buildable;
6 |
7 | @Buildable(editableEnabled = false)
8 | @JsonInclude(JsonInclude.Include.NON_NULL)
9 | public class Images {
10 |
11 | String api;
12 | String ui;
13 |
14 | public String getApi() {
15 | return api;
16 | }
17 |
18 | public void setApi(String api) {
19 | this.api = api;
20 | }
21 |
22 | public String getUi() {
23 | return ui;
24 | }
25 |
26 | public void setUi(String ui) {
27 | this.ui = ui;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/authentication/Basic.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec.authentication;
2 |
3 | import com.github.streamshub.console.api.v1alpha1.spec.Value;
4 |
5 | import io.fabric8.generator.annotation.Required;
6 | import io.sundr.builder.annotations.Buildable;
7 |
8 | @Buildable(editableEnabled = false)
9 | public class Basic {
10 |
11 | @Required
12 | private String username;
13 | @Required
14 | private Value password;
15 |
16 | public String getUsername() {
17 | return username;
18 | }
19 |
20 | public void setUsername(String username) {
21 | this.username = username;
22 | }
23 |
24 | public Value getPassword() {
25 | return password;
26 | }
27 |
28 | public void setPassword(Value password) {
29 | this.password = password;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/authentication/Bearer.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec.authentication;
2 |
3 | import com.github.streamshub.console.api.v1alpha1.spec.Value;
4 |
5 | import io.fabric8.generator.annotation.Required;
6 | import io.sundr.builder.annotations.Buildable;
7 |
8 | @Buildable(editableEnabled = false)
9 | public class Bearer {
10 |
11 | @Required
12 | private Value token;
13 |
14 | public Value getToken() {
15 | return token;
16 | }
17 |
18 | public void setToken(Value token) {
19 | this.token = token;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/containers/ContainerTemplateSpec.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec.containers;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.annotation.JsonPropertyDescription;
5 |
6 | import io.sundr.builder.annotations.Buildable;
7 |
8 | @Buildable(editableEnabled = false)
9 | @JsonInclude(JsonInclude.Include.NON_NULL)
10 | public class ContainerTemplateSpec {
11 |
12 | @JsonPropertyDescription("Specification to be applied to the resulting container.")
13 | ContainerSpec spec;
14 |
15 | public ContainerSpec getSpec() {
16 | return spec;
17 | }
18 |
19 | public void setSpec(ContainerSpec spec) {
20 | this.spec = spec;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/GlobalSecurity.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec.security;
2 |
3 | import io.sundr.builder.annotations.Buildable;
4 |
5 | @Buildable(editableEnabled = false)
6 | public class GlobalSecurity extends Security {
7 |
8 | private Oidc oidc;
9 |
10 | public Oidc getOidc() {
11 | return oidc;
12 | }
13 |
14 | public void setOidc(Oidc oidc) {
15 | this.oidc = oidc;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/KafkaSecurity.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec.security;
2 |
3 | import io.sundr.builder.annotations.Buildable;
4 |
5 | @Buildable(editableEnabled = false)
6 | public class KafkaSecurity extends Security {
7 | }
8 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/Role.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.api.v1alpha1.spec.security;
2 |
3 | import java.util.List;
4 |
5 | import io.fabric8.generator.annotation.Required;
6 | import io.sundr.builder.annotations.Buildable;
7 |
8 | @Buildable(editableEnabled = false)
9 | public class Role {
10 |
11 | @Required
12 | private String name;
13 |
14 | private List rules;
15 |
16 | public String getName() {
17 | return name;
18 | }
19 |
20 | public void setName(String name) {
21 | this.name = name;
22 | }
23 |
24 | public List getRules() {
25 | return rules;
26 | }
27 |
28 | public void setRules(List rules) {
29 | this.rules = rules;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/dependents/ConsoleClusterRole.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.dependents;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 |
5 | import io.javaoperatorsdk.operator.api.config.informer.Informer;
6 | import io.javaoperatorsdk.operator.api.reconciler.Constants;
7 | import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
8 |
9 | @ApplicationScoped
10 | @KubernetesDependent(
11 | informer = @Informer(
12 | namespaces = Constants.WATCH_ALL_NAMESPACES,
13 | labelSelector = ConsoleResource.MANAGEMENT_SELECTOR))
14 | public class ConsoleClusterRole extends BaseClusterRole {
15 |
16 | public static final String NAME = "console-clusterrole";
17 |
18 | public ConsoleClusterRole() {
19 | super("console", "console.clusterrole.yaml", NAME);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/dependents/ConsoleService.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.dependents;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 | import jakarta.inject.Inject;
5 |
6 | import com.github.streamshub.console.api.v1alpha1.Console;
7 |
8 | import io.javaoperatorsdk.operator.api.config.informer.Informer;
9 | import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
10 |
11 | @ApplicationScoped
12 | @KubernetesDependent(informer = @Informer(labelSelector = ConsoleResource.MANAGEMENT_SELECTOR))
13 | public class ConsoleService extends BaseService {
14 |
15 | public static final String NAME = "console-service";
16 |
17 | @Inject
18 | ConsoleDeployment deployment;
19 |
20 | public ConsoleService() {
21 | super("console", "console.service.yaml", NAME);
22 | }
23 |
24 | @Override
25 | protected String appName(Console primary) {
26 | return deployment.instanceName(primary);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/dependents/ConsoleServiceAccount.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.dependents;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 |
5 | import io.javaoperatorsdk.operator.api.config.informer.Informer;
6 | import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
7 |
8 | @ApplicationScoped
9 | @KubernetesDependent(informer = @Informer(labelSelector = ConsoleResource.MANAGEMENT_SELECTOR))
10 | public class ConsoleServiceAccount extends BaseServiceAccount {
11 |
12 | public static final String NAME = "console-serviceaccount";
13 |
14 | public ConsoleServiceAccount() {
15 | super("console", "console.serviceaccount.yaml", NAME);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/dependents/PrometheusClusterRole.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.dependents;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 |
5 | import io.javaoperatorsdk.operator.api.config.informer.Informer;
6 | import io.javaoperatorsdk.operator.api.reconciler.Constants;
7 | import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
8 |
9 | @ApplicationScoped
10 | @KubernetesDependent(
11 | informer = @Informer(
12 | namespaces = Constants.WATCH_ALL_NAMESPACES,
13 | labelSelector = ConsoleResource.MANAGEMENT_SELECTOR))
14 | public class PrometheusClusterRole extends BaseClusterRole {
15 |
16 | public static final String NAME = "prometheus-clusterrole";
17 |
18 | public PrometheusClusterRole() {
19 | super("prometheus", "prometheus.clusterrole.yaml", NAME);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/operator/src/main/java/com/github/streamshub/console/dependents/PrometheusServiceAccount.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.console.dependents;
2 |
3 | import jakarta.enterprise.context.ApplicationScoped;
4 |
5 | import io.javaoperatorsdk.operator.api.config.informer.Informer;
6 | import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
7 |
8 | @ApplicationScoped
9 | @KubernetesDependent(informer = @Informer(labelSelector = ConsoleResource.MANAGEMENT_SELECTOR))
10 | public class PrometheusServiceAccount extends BaseServiceAccount {
11 |
12 | public static final String NAME = "prometheus-serviceaccount";
13 |
14 | public PrometheusServiceAccount() {
15 | super("prometheus", "prometheus.serviceaccount.yaml", NAME);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/README.md:
--------------------------------------------------------------------------------
1 | This directory contains bundle stub files with references to the images to be used
2 | to render the actual bundle metadata placed in the operator's OLM catalog.
3 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.10.0.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.10.0
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.10.1.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.10.1
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.10.2.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.10.2
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.10.3.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.10.3
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.5.0.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.5.0
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.6.0.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.6.0
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.6.1.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.6.1
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.6.2.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.6.2
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.6.3.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.6.3
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.6.4.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.6.4
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.6.5.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.6.5
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.6.6.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.6.6
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.6.7.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.6.7
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.7.0.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.7.0
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.8.0.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.8.0
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.8.1.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.8.1
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.8.2.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.8.2
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.8.3.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.8.3
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.8.4.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.8.4
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.8.5.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.8.5
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.8.6.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.8.6
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/bundles/streamshub-console-operator.v0.9.0.yaml:
--------------------------------------------------------------------------------
1 | image: quay.io/streamshub/console-operator-bundle:0.9.0
2 |
--------------------------------------------------------------------------------
/operator/src/main/olm/channel.0.10.x.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | schema: olm.channel
3 | name: 0.10.x
4 | package: streamshub-console-operator
5 | properties: []
6 | entries:
7 | - name: streamshub-console-operator.v0.10.0
8 | replaces: streamshub-console-operator.v0.9.0
9 | - name: streamshub-console-operator.v0.10.1
10 | replaces: streamshub-console-operator.v0.10.0
11 | - name: streamshub-console-operator.v0.10.2
12 | replaces: streamshub-console-operator.v0.10.1
13 | - name: streamshub-console-operator.v0.10.3
14 | replaces: streamshub-console-operator.v0.10.2
15 |
--------------------------------------------------------------------------------
/operator/src/main/olm/channel.0.11.x.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | schema: olm.channel
3 | name: 0.11.x
4 | package: streamshub-console-operator
5 | properties: []
6 | entries:
7 | - name: streamshub-console-operator.v0.11.0-snapshot
8 | replaces: streamshub-console-operator.v0.10.3
9 |
--------------------------------------------------------------------------------
/operator/src/main/olm/channel.0.5.x.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | schema: olm.channel
3 | name: 0.5.x
4 | package: streamshub-console-operator
5 | properties: []
6 | entries:
7 | - name: streamshub-console-operator.v0.5.0
8 |
--------------------------------------------------------------------------------
/operator/src/main/olm/channel.0.6.x.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | schema: olm.channel
3 | name: 0.6.x
4 | package: streamshub-console-operator
5 | properties: []
6 | entries:
7 | - name: streamshub-console-operator.v0.6.0
8 | replaces: streamshub-console-operator.v0.5.0
9 | - name: streamshub-console-operator.v0.6.1
10 | replaces: streamshub-console-operator.v0.6.0
11 | - name: streamshub-console-operator.v0.6.2
12 | replaces: streamshub-console-operator.v0.6.1
13 | - name: streamshub-console-operator.v0.6.3
14 | replaces: streamshub-console-operator.v0.6.2
15 | - name: streamshub-console-operator.v0.6.4
16 | replaces: streamshub-console-operator.v0.6.3
17 | - name: streamshub-console-operator.v0.6.5
18 | replaces: streamshub-console-operator.v0.6.4
19 | - name: streamshub-console-operator.v0.6.6
20 | replaces: streamshub-console-operator.v0.6.5
21 | - name: streamshub-console-operator.v0.6.7
22 | replaces: streamshub-console-operator.v0.6.6
23 |
--------------------------------------------------------------------------------
/operator/src/main/olm/channel.0.7.x.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | schema: olm.channel
3 | name: 0.7.x
4 | package: streamshub-console-operator
5 | properties: []
6 | entries:
7 | - name: streamshub-console-operator.v0.7.0
8 | replaces: streamshub-console-operator.v0.6.3
9 |
--------------------------------------------------------------------------------
/operator/src/main/olm/channel.0.8.x.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | schema: olm.channel
3 | name: 0.8.x
4 | package: streamshub-console-operator
5 | properties: []
6 | entries:
7 | - name: streamshub-console-operator.v0.8.0
8 | replaces: streamshub-console-operator.v0.7.0
9 | - name: streamshub-console-operator.v0.8.1
10 | replaces: streamshub-console-operator.v0.8.0
11 | - name: streamshub-console-operator.v0.8.2
12 | replaces: streamshub-console-operator.v0.8.1
13 | - name: streamshub-console-operator.v0.8.3
14 | replaces: streamshub-console-operator.v0.8.2
15 | - name: streamshub-console-operator.v0.8.4
16 | replaces: streamshub-console-operator.v0.8.3
17 | - name: streamshub-console-operator.v0.8.5
18 | replaces: streamshub-console-operator.v0.8.4
19 | - name: streamshub-console-operator.v0.8.6
20 | replaces: streamshub-console-operator.v0.8.5
21 |
--------------------------------------------------------------------------------
/operator/src/main/olm/channel.0.9.x.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | schema: olm.channel
3 | name: 0.9.x
4 | package: streamshub-console-operator
5 | properties: []
6 | entries:
7 | - name: streamshub-console-operator.v0.9.0
8 | replaces: streamshub-console-operator.v0.8.6
9 |
--------------------------------------------------------------------------------
/operator/src/main/olm/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamshub/console/e26188458c3e845b6daf0f984541644f17a09aaf/operator/src/main/olm/icon.png
--------------------------------------------------------------------------------
/operator/src/main/olm/package.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | schema: olm.package
3 | name: streamshub-console-operator
4 | defaultChannel: alpha
5 | properties: []
6 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/console-monitoring.clusterrolebinding.yaml:
--------------------------------------------------------------------------------
1 | kind: ClusterRoleBinding
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | metadata:
4 | name: console-server-monitoring
5 | roleRef:
6 | # This role is pre-existing in an OpenShift cluster
7 | apiGroup: rbac.authorization.k8s.io
8 | kind: ClusterRole
9 | name: cluster-monitoring-view
10 | subjects:
11 | - kind: ServiceAccount
12 | name: console-server
13 | namespace: ${NAMESPACE}
14 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/console.clusterrole.yaml:
--------------------------------------------------------------------------------
1 | kind: ClusterRole
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | metadata:
4 | name: console-server
5 | rules:
6 | - verbs:
7 | - get
8 | - watch
9 | - list
10 | apiGroups:
11 | - kafka.strimzi.io
12 | resources:
13 | - kafkas
14 | - kafkanodepools
15 | - kafkarebalances
16 | - kafkatopics
17 | - kafkaconnects
18 | - kafkamirrormaker2s
19 | - verbs:
20 | - patch
21 | apiGroups:
22 | - kafka.strimzi.io
23 | resources:
24 | - kafkas
25 | - kafkarebalances
26 | - verbs:
27 | - get
28 | - list
29 | apiGroups:
30 | - ""
31 | resources:
32 | - pods
33 | - verbs:
34 | - get
35 | apiGroups:
36 | - config.openshift.io
37 | resources:
38 | - clusterversions
39 | resourceNames:
40 | - version
41 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/console.clusterrolebinding.yaml:
--------------------------------------------------------------------------------
1 | kind: ClusterRoleBinding
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | metadata:
4 | name: console-server
5 | roleRef:
6 | apiGroup: rbac.authorization.k8s.io
7 | kind: ClusterRole
8 | name: console-server
9 | subjects:
10 | - kind: ServiceAccount
11 | name: console-server
12 | namespace: ${NAMESPACE}
13 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/console.ingress.yaml:
--------------------------------------------------------------------------------
1 | kind: Ingress
2 | apiVersion: networking.k8s.io/v1
3 | metadata:
4 | name: console-ui-ingress
5 | annotations:
6 | nginx.ingress.kubernetes.io/backend-protocol: HTTP
7 | route.openshift.io/termination: edge
8 | spec:
9 | defaultBackend:
10 | service:
11 | name: console-ui
12 | port:
13 | number: 80
14 | rules:
15 | - host: placeholder
16 | http:
17 | paths:
18 | - pathType: ImplementationSpecific
19 | backend:
20 | service:
21 | name: console-ui
22 | port:
23 | number: 80
24 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/console.kafkauser.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: kafka.strimzi.io/v1beta2
2 | kind: KafkaUser
3 | metadata:
4 | name: console-kafka-user1
5 | labels:
6 | strimzi.io/cluster: console-kafka
7 | spec:
8 | authentication:
9 | type: scram-sha-512
10 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/console.service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: console-ui
5 | spec:
6 | ports:
7 | - port: 80
8 | targetPort: 3000
9 | selector: {}
10 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/console.serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: console-server
5 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/prometheus.clusterrole.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: console-prometheus-server
5 | rules:
6 | - apiGroups: [ '' ]
7 | resources:
8 | - nodes
9 | - nodes/proxy
10 | - services
11 | - endpoints
12 | - pods
13 | verbs: [ get, list, watch ]
14 | - apiGroups:
15 | - extensions
16 | resources:
17 | - ingresses
18 | verbs: [ get, list, watch ]
19 | - nonResourceURLs: [ /metrics ]
20 | verbs: [ get ]
21 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/prometheus.clusterrolebinding.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRoleBinding
3 | metadata:
4 | name: console-prometheus-server
5 | roleRef:
6 | apiGroup: rbac.authorization.k8s.io
7 | kind: ClusterRole
8 | name: console-prometheus-server
9 | subjects:
10 | - kind: ServiceAccount
11 | name: console-prometheus-server
12 | namespace: ${NAMESPACE}
13 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/prometheus.service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: prometheus-service
5 | namespace: prometheus
6 | spec:
7 | selector: {}
8 | ports:
9 | - protocol: TCP
10 | port: 9090
11 | targetPort: 9090
12 | type: ClusterIP
13 |
--------------------------------------------------------------------------------
/operator/src/main/resources/com/github/streamshub/console/dependents/prometheus.serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: console-prometheus-server
5 |
--------------------------------------------------------------------------------
/operator/src/test/example-console.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: console.streamshub.github.com/v1alpha1
2 | kind: Console
3 | metadata:
4 | name: example
5 | spec:
6 | hostname: example-console.apps-crc.testing
7 | kafkaClusters:
8 | - id: my-console
9 | credentials:
10 | kafkaUser:
11 | name: console-kafka-user1
12 | #namespace: same as kafkaCluster
13 | listener: secure
14 | name: console-kafka
15 | namespace: streams-console
16 | properties:
17 | values:
18 | - name: x-some-test-property
19 | value: the-value
20 |
--------------------------------------------------------------------------------
/operator/src/test/resources/com/github/streamshub/console/kube-certs.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/streamshub/console/e26188458c3e845b6daf0f984541644f17a09aaf/operator/src/test/resources/com/github/streamshub/console/kube-certs.jks
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/annotations/SetupTestBucket.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.METHOD)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface SetupTestBucket {
11 | String value();
12 | }
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/annotations/TestBucket.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.annotations;
2 |
3 | import com.github.streamshub.systemtests.interfaces.TestBucketExtension;
4 | import org.junit.jupiter.api.extension.ExtendWith;
5 |
6 | import java.lang.annotation.ElementType;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.RetentionPolicy;
9 | import java.lang.annotation.Target;
10 |
11 | @Target({ ElementType.METHOD })
12 | @Retention(RetentionPolicy.RUNTIME)
13 | @ExtendWith(TestBucketExtension.class)
14 | public @interface TestBucket {
15 | String value();
16 | }
17 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/constants/ExampleFiles.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.constants;
2 |
3 | import java.io.File;
4 |
5 | import static io.skodjob.testframe.TestFrameEnv.USER_PATH;
6 |
7 | public class ExampleFiles {
8 | private ExampleFiles() {}
9 |
10 | // ---------------
11 | // Install resources
12 | // --------------
13 | public static final String EXAMPLES_PATH = USER_PATH + "/../examples";
14 | // -----
15 | // Kafka
16 | // -----
17 | public static final String EXAMPLE_KAFKA_PATH = EXAMPLES_PATH + "/kafka/";
18 | public static final File EXAMPLES_KAFKA_METRICS_CONFIG_MAP = new File(EXAMPLE_KAFKA_PATH + "010-ConfigMap-console-kafka-metrics.yaml");
19 | }
20 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/constants/TestTags.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.constants;
2 |
3 | public class TestTags {
4 | public static final String REGRESSION = "regression";
5 | public static final String OLM_UPGRADE = "olm-upgrade";
6 | public static final String YAML_UPGRADE = "yaml-upgrade";
7 | }
8 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/enums/BrowserTypes.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.enums;
2 |
3 | public enum BrowserTypes {
4 | CHROMIUM,
5 | MSEDGE,
6 | FIREFOX,
7 | WEBKIT
8 | }
9 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/enums/ConditionStatus.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.enums;
2 |
3 | public enum ConditionStatus {
4 | TRUE("True"),
5 | FALSE("False");
6 |
7 | private final String status;
8 |
9 | ConditionStatus(String status) {
10 | this.status = status;
11 | }
12 |
13 | @Override
14 | public String toString() {
15 | return status;
16 | }
17 | }
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/enums/FilterType.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.enums;
2 |
3 | public enum FilterType {
4 | NAME("Name", 1),
5 | TOPIC_ID("Topic ID", 2),
6 | STATUS("Status", 3);
7 |
8 | private final String name;
9 | private final int position;
10 |
11 | FilterType(String name, int position) {
12 | this.name = name;
13 | this.position = position;
14 | }
15 |
16 | public String getName() {
17 | return name;
18 | }
19 |
20 | public int getPosition() {
21 | return position;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/enums/ResetOffsetDateTimeType.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.enums;
2 |
3 | public enum ResetOffsetDateTimeType {
4 | UNIX_EPOCH("Unix_Epoch_Milliseconds"),
5 | ISO_8601("ISO_8601_DateTime");
6 |
7 | private final String description;
8 |
9 | ResetOffsetDateTimeType(String description) {
10 | this.description = description;
11 | }
12 |
13 | @Override
14 | public String toString() {
15 | return description;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/enums/ResetOffsetType.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.enums;
2 |
3 | public enum ResetOffsetType {
4 |
5 | EARLIEST("Earliest", "earliest"),
6 | LATEST("Latest", "latest"),
7 | DATE_TIME("DateTime", "datetime"),
8 | // Is displayed only if resetting with specific partition
9 | CUSTOM_OFFSET("CustomOffset", "offset");
10 |
11 | private final String description;
12 | private final String command;
13 |
14 | ResetOffsetType(String description, String command) {
15 | this.description = description;
16 | this.command = command;
17 | }
18 |
19 | @Override
20 | public String toString() {
21 | return description;
22 | }
23 |
24 | public String getCommand() {
25 | return command;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/enums/ResourceStatus.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.enums;
2 |
3 | public enum ResourceStatus {
4 | READY("Ready"),
5 | NOT_READY("NotReady"),
6 | WARNING("Warning"),
7 | RECONCILIATION_PAUSED("ReconciliationPaused");
8 |
9 | private final String status;
10 |
11 | ResourceStatus(String status) {
12 | this.status = status;
13 | }
14 |
15 | @Override
16 | public String toString() {
17 | return status;
18 | }
19 | }
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/enums/TopicStatus.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.enums;
2 |
3 | public enum TopicStatus {
4 | FULLY_REPLICATED("Fully replicated", 1),
5 | UNDER_REPLICATED("Under replicated", 2),
6 | PARTIALLY_OFFLINE("Partially offline", 3),
7 | UNKNOWN("Unknown", 4),
8 | OFFLINE("Offline", 5);
9 |
10 | private final String name;
11 | private final int position;
12 |
13 | TopicStatus(String name, int position) {
14 | this.name = name;
15 | this.position = position;
16 | }
17 |
18 | public String getName() {
19 | return name;
20 | }
21 |
22 | public int getPosition() {
23 | return position;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/exceptions/ClusterUnreachableException.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.exceptions;
2 |
3 | import io.skodjob.testframe.clients.KubeClusterException;
4 | import io.skodjob.testframe.executor.ExecResult;
5 |
6 | public class ClusterUnreachableException extends KubeClusterException {
7 | public ClusterUnreachableException(ExecResult result) {
8 | super(result,
9 | "Cluster is currently unreachable. This may be due to the cluster being unstable or down. Please check the connection.: " + result.out());
10 | }
11 |
12 | public ClusterUnreachableException(ExecResult result, String message) {
13 | super(result, message);
14 | }
15 |
16 | public ClusterUnreachableException(String message) {
17 | super(new Throwable(message));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/exceptions/OperatorSdkNotInstalledException.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.exceptions;
2 |
3 | public class OperatorSdkNotInstalledException extends SetupException {
4 | public OperatorSdkNotInstalledException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/exceptions/PlaywrightActionExecutionException.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.exceptions;
2 |
3 | public class PlaywrightActionExecutionException extends RuntimeException {
4 |
5 | public PlaywrightActionExecutionException(String message, Throwable cause) {
6 | super(message, cause);
7 | }
8 |
9 | public PlaywrightActionExecutionException(String message) {
10 | super(message);
11 | }
12 | }
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/exceptions/SetupException.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.exceptions;
2 |
3 | public class SetupException extends RuntimeException {
4 | public SetupException(String message) {
5 | super(message);
6 | }
7 |
8 | public SetupException(String message, Throwable cause) {
9 | super(message, cause);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/exceptions/UnsupportedKafkaRoleException.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.exceptions;
2 |
3 | public class UnsupportedKafkaRoleException extends RuntimeException {
4 | public UnsupportedKafkaRoleException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/interfaces/ExtensionContextParameterResolver.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.interfaces;
2 |
3 | import org.junit.jupiter.api.extension.ExtensionContext;
4 | import org.junit.jupiter.api.extension.ParameterContext;
5 | import org.junit.jupiter.api.extension.ParameterResolutionException;
6 | import org.junit.jupiter.api.extension.ParameterResolver;
7 |
8 | public class ExtensionContextParameterResolver implements ParameterResolver {
9 | @Override
10 | public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
11 | return parameterContext.getParameter().getType() == ExtensionContext.class;
12 | }
13 |
14 | @Override
15 | public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
16 | return extensionContext;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/setup/keycloak/keycloak-instance.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: k8s.keycloak.org/v2alpha1
2 | kind: Keycloak
3 | metadata:
4 | name: keycloak
5 | labels:
6 | app: sso
7 | spec:
8 | instances: 1
9 | db:
10 | vendor: postgres
11 | host: postgres
12 | usernameSecret:
13 | name: keycloak-db-secret
14 | key: username
15 | passwordSecret:
16 | name: keycloak-db-secret
17 | key: password
18 | http:
19 | tlsSecret: example-tls-secret
20 | httpEnabled: true
21 | httpPort: 9595
22 | httpsPort: 8443
23 | hostname:
24 | hostname: ${HOSTNAME}
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/setup/keycloak/teardown_keycloak_operator.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | if [[ -z "${1-}" ]]; then echo "ERROR: KEYCLOAK_OPERATOR_NAMESPACE (arg 1) is missing" >&2; exit 1; fi
6 | if [[ -z "${2-}" ]]; then echo "ERROR: KEYCLOAK_VERSION (arg 2) is missing" >&2; exit 1; fi
7 |
8 | KEYCLOAK_OPERATOR_NAMESPACE=$1
9 | KEYCLOAK_VERSION=$2
10 |
11 | echo "Delete Keycloak Operator"
12 | kubectl delete -n ${KEYCLOAK_OPERATOR_NAMESPACE} -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_VERSION}/kubernetes/kubernetes.yml
13 | kubectl delete -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_VERSION}/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
14 | kubectl delete -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_VERSION}/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
--------------------------------------------------------------------------------
/systemtests/src/main/java/com/github/streamshub/systemtests/upgrade/YamlVersionModificationData.java:
--------------------------------------------------------------------------------
1 | package com.github.streamshub.systemtests.upgrade;
2 |
3 | public class YamlVersionModificationData {
4 | private String oldOperatorVersion;
5 | private String newOperatorVersion;
6 | private String oldOperatorCrdsUrl;
7 | private String newOperatorCrdsUrl;
8 |
9 | public String getOldOperatorVersion() {
10 | return oldOperatorVersion;
11 | }
12 |
13 | public String getNewOperatorVersion() {
14 | return newOperatorVersion;
15 | }
16 |
17 | public String getOldOperatorCrdsUrl() {
18 | return oldOperatorCrdsUrl;
19 | }
20 |
21 | public String getNewOperatorCrdsUrl() {
22 | return newOperatorCrdsUrl;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/systemtests/src/main/resources/log4j2.properties:
--------------------------------------------------------------------------------
1 | name = STConfig
2 |
3 | rootLogger.additivity = false
4 |
5 | logger.clients.name = org.apache.kafka.clients
6 | logger.clients.level = INFO
7 |
8 | logger.fabric8.name = io.fabric8.kubernetes.client
9 | logger.fabric8.level = OFF
10 | logger.jayway.name = com.jayway.jsonpath.internal.path.CompiledPath
11 | logger.jayway.level = OFF
12 |
--------------------------------------------------------------------------------
/systemtests/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener:
--------------------------------------------------------------------------------
1 | com.github.streamshub.systemtests.SystemTestExecutionListener
--------------------------------------------------------------------------------
/systemtests/src/test/resources/junit-platform.properties:
--------------------------------------------------------------------------------
1 | # These are default values if @ParallelTest or @ParallelSuite is not specified
2 | # junit.jupiter.execution.parallel.mode.default <==> ParallelTest = concurrent
3 | # junit.jupiter.execution.parallel.mode.classes.default <==> ParallelSuite = concurrent
4 | junit.jupiter.execution.parallel.enabled=false
5 |
6 | # Configure parallelism at the class level
7 | junit.jupiter.execution.parallel.mode.default=same_thread
8 | junit.jupiter.execution.parallel.mode.classes.default=concurrent
9 |
10 | # Configure sequential execution at the method level
11 | junit.jupiter.execution.parallel.config.strategy=fixed
12 | junit.jupiter.execution.parallel.config.fixed.parallelism=1
13 | junit.platform.output.capture.maxBuffer=true
14 |
--------------------------------------------------------------------------------
/systemtests/src/test/resources/upgrade/OlmUpgrade.yaml:
--------------------------------------------------------------------------------
1 | # OLM channel
2 | oldOlmChannel: 0.9.x
3 | newOlmChannel: alpha
4 | # Operator version
5 | oldOperatorVersion: 0.9.0
6 | newOperatorVersion: 0.10.2-snapshot
7 |
8 |
--------------------------------------------------------------------------------
/systemtests/src/test/resources/upgrade/YamlUpgrade.yaml:
--------------------------------------------------------------------------------
1 | # Operator version
2 | oldOperatorVersion: 0.9.0
3 | newOperatorVersion: 0.10.0
4 | # Url to generated CRDs Yaml containing operator.
5 | oldOperatorCrdsUrl: https://github.com/streamshub/console/releases/download/0.9.0/streamshub-console-operator.yaml
6 | newOperatorCrdsUrl: https://github.com/streamshub/console/releases/download/0.10.0/streamshub-console-operator.yaml
7 |
8 |
--------------------------------------------------------------------------------
/ui/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
2 | .dockerignore
3 | node_modules
4 | npm-debug.log
5 | CONTRIBUTING.md
6 | .git
7 | .env.*
8 |
--------------------------------------------------------------------------------
/ui/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "next/core-web-vitals",
4 | "prettier",
5 | "plugin:storybook/recommended"
6 | ],
7 | "ignorePatterns": [
8 | "storybookHelpers.tsx",
9 | "**/*.stories.tsx"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/ui/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
37 | # storybook
38 | build-storybook.log
39 | storybook-static
40 |
41 | # other package management lock files
42 | pnpm-lock.yaml
43 | yarn.lock
44 |
45 | tests/playwright/.auth/*.json
46 | /test-results/
47 | /.pino-prettyrc
48 |
--------------------------------------------------------------------------------
/ui/.storybook/preview-body.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ui/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM registry.access.redhat.com/ubi9/nodejs-22-minimal
2 |
3 | WORKDIR /app
4 | EXPOSE 3000
5 |
6 | ENV NODE_ENV=production
7 | ENV HOSTNAME=0.0.0.0
8 | ENV PORT=3000
9 | ENV LOG_LEVEL=info
10 |
11 | CMD ["node", "server.js"]
12 |
13 | USER 0
14 |
15 | COPY public ./public
16 | COPY --chown=1001:1001 .next/standalone ./
17 | COPY --chown=1001:1001 .next/static ./.next/static
18 |
19 | USER 1001
20 |
--------------------------------------------------------------------------------
/ui/api/meta/actions.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import { fetchData, ApiResponse } from "@/api/api";
4 | import { MetadataResponse, MetadataSchema } from "./schema";
5 |
6 | export async function getMetadata(): Promise> {
7 | return fetchData(
8 | `/api/metadata`,
9 | "",
10 | (rawData) => MetadataSchema.parse(rawData.data),
11 | true,
12 | {
13 | next: {
14 | // 60s cache
15 | revalidate: 60,
16 | }
17 | },
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/ui/api/meta/schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const MetadataSchema = z.object({
4 | id: z.string(),
5 | type: z.literal("metadata"),
6 | attributes: z.object({
7 | version: z.string(),
8 | platform: z.string(),
9 | }),
10 | });
11 |
12 | export type MetadataResponse = z.infer;
13 |
--------------------------------------------------------------------------------
/ui/api/schema/action.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import { getHeaders } from "../api";
4 |
5 | export async function getSchema(contentLink: string) {
6 | const url = `${process.env.BACKEND_URL}/${contentLink}`;
7 | const res = await fetch(url, {
8 | headers: await getHeaders(),
9 | });
10 | const rawData = await res.text();
11 | return rawData;
12 | }
13 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/AppSessionProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Session } from "next-auth";
4 | import { SessionProvider } from "next-auth/react";
5 | import { PropsWithChildren } from "react";
6 |
7 | export function AppSessionProvider({
8 | session,
9 | children,
10 | }: PropsWithChildren<{ session: Session | null }>) {
11 | return {children};
12 | }
13 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/brokers/page.tsx:
--------------------------------------------------------------------------------
1 | export default function NodesActiveBreadcrumb() {
2 | return "Nodes";
3 | }
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/default.tsx:
--------------------------------------------------------------------------------
1 | export default function Default() {
2 | return null;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/nodes/[nodeId]/configuration/page.tsx:
--------------------------------------------------------------------------------
1 | import { NodeBreadcrumb } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/nodes/[nodeId]/NodeBreadcrumb";
2 |
3 | export default NodeBreadcrumb;
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/overview/page.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Breadcrumb,
3 | BreadcrumbItem,
4 | Tooltip,
5 | } from "@/libs/patternfly/react-core";
6 | import { HomeIcon } from "@/libs/patternfly/react-icons";
7 | import { useTranslations } from "next-intl";
8 |
9 | export default function OverviewBreadcrumb() {
10 | const t = useTranslations("breadcrumbs");
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {t("overview")}
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/schema-registry/page.tsx:
--------------------------------------------------------------------------------
1 | export default function SchemaRegistryActiveBreadcrumb() {
2 | return "Schema registry";
3 | }
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/configuration/page.tsx:
--------------------------------------------------------------------------------
1 | import { TopicBreadcrumb } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/TopicBreadcrumb";
2 |
3 | export default TopicBreadcrumb;
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/consumer-groups/page.tsx:
--------------------------------------------------------------------------------
1 | import { TopicBreadcrumb } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/TopicBreadcrumb";
2 |
3 | export default TopicBreadcrumb;
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/delete/page.tsx:
--------------------------------------------------------------------------------
1 | export default function Null() {
2 | return null;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/messages/page.tsx:
--------------------------------------------------------------------------------
1 | import { TopicBreadcrumb } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/TopicBreadcrumb";
2 |
3 | export default TopicBreadcrumb;
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/partitions/page.tsx:
--------------------------------------------------------------------------------
1 | import { TopicBreadcrumb } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/TopicBreadcrumb";
2 |
3 | export default TopicBreadcrumb;
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/schema-registry/page.tsx:
--------------------------------------------------------------------------------
1 | import { TopicBreadcrumb } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@activeBreadcrumb/topics/[topicId]/TopicBreadcrumb";
2 |
3 | export default TopicBreadcrumb;
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/KafkaHeader.tsx:
--------------------------------------------------------------------------------
1 | import { getKafkaCluster } from "@/api/kafka/actions";
2 | import { KafkaParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/kafka.params";
3 | import { AppHeader } from "@/components/AppHeader";
4 | import { Skeleton } from "@/libs/patternfly/react-core";
5 | import { Suspense } from "react";
6 |
7 | export function KafkaHeader({ params: { kafkaId } }: { params: KafkaParams }) {
8 | return (
9 | } />}>
10 |
11 |
12 | );
13 | }
14 |
15 | async function ConnectedKafkaHeader({
16 | params: { kafkaId },
17 | }: {
18 | params: KafkaParams;
19 | }) {
20 | const cluster = (await getKafkaCluster(kafkaId))?.payload;
21 |
22 | if (cluster) {
23 | return ;
24 | }
25 |
26 | return ;
27 | }
28 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/consumer-groups/page.tsx:
--------------------------------------------------------------------------------
1 | import { KafkaParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/kafka.params";
2 | import { AppHeader } from "@/components/AppHeader";
3 |
4 | export default function ConsumerGroupsHeader({
5 | params,
6 | }: {
7 | params: KafkaParams;
8 | }) {
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/default.tsx:
--------------------------------------------------------------------------------
1 | export default function DefaultNav() {
2 | return null;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/kafka-connect/connect-clusters/page.tsx:
--------------------------------------------------------------------------------
1 | import { KafkaParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/kafka.params";
2 | import { AppHeader } from "@/components/AppHeader";
3 | import { PageSection } from "@/libs/patternfly/react-core";
4 | import { useTranslations } from "next-intl";
5 | import { KafkaConnectTabs } from "../KafkaConnectTabs";
6 |
7 | export default function ConnectClustersHeader({
8 | params,
9 | }: {
10 | params: KafkaParams;
11 | }) {
12 | return (
13 |
17 |
18 |
19 | }
20 | />
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/kafka-connect/page.tsx:
--------------------------------------------------------------------------------
1 | import { KafkaParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/kafka.params";
2 | import { AppHeader } from "@/components/AppHeader";
3 | import { PageSection } from "@/libs/patternfly/react-core";
4 | import { useTranslations } from "next-intl";
5 | import { KafkaConnectTabs } from "./KafkaConnectTabs";
6 |
7 | export default function ConnectorsHeader({ params }: { params: KafkaParams }) {
8 | return (
9 |
13 |
14 |
15 | }
16 | />
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/nodes/[nodeId]/configuration/page.tsx:
--------------------------------------------------------------------------------
1 | import { NodeHeader } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@header/nodes/[nodeId]/NodeHeader";
2 |
3 | export default NodeHeader;
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/overview/ConnectButton.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useOpenClusterConnectionPanel } from "@/components/ClusterDrawerContext";
4 | import { Button } from "@/libs/patternfly/react-core";
5 | import { useTranslations } from "next-intl";
6 |
7 | export function ConnectButton({ clusterId }: { clusterId: string }) {
8 | const t = useTranslations();
9 | const open = useOpenClusterConnectionPanel();
10 | return (
11 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/schema-registry/page.tsx:
--------------------------------------------------------------------------------
1 | import { KafkaHeader } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@header/KafkaHeader";
2 |
3 | export default KafkaHeader;
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/configuration/page.tsx:
--------------------------------------------------------------------------------
1 | import { TopicHeader } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/TopicHeader";
2 |
3 | export default TopicHeader;
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/consumer-groups/page.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | TopicHeader,
3 | TopicHeaderProps,
4 | } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/TopicHeader";
5 |
6 | export default function TopicHeaderNoRefresh(
7 | props: Omit,
8 | ) {
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/delete/page.tsx:
--------------------------------------------------------------------------------
1 | export default function Null() {
2 | return null;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/messages/page.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | TopicHeader,
3 | TopicHeaderProps,
4 | } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/TopicHeader";
5 |
6 | export default function TopicHeaderNoRefresh(
7 | props: Omit,
8 | ) {
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/partitions/page.tsx:
--------------------------------------------------------------------------------
1 | import { TopicHeader } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/TopicHeader";
2 |
3 | export default TopicHeader;
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/schema-registry/page.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | TopicHeader,
3 | TopicHeaderProps,
4 | } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/[topicId]/TopicHeader";
5 |
6 | export default function TopicHeaderNoRefresh(
7 | props: Omit,
8 | ) {
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@header/topics/create/page.tsx:
--------------------------------------------------------------------------------
1 | import { AppHeader } from "@/components/AppHeader";
2 |
3 | export default function CreateTopicHeader() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@modal/[...catchAll]/page.tsx:
--------------------------------------------------------------------------------
1 | export default function Default() {
2 | return null;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/@modal/default.tsx:
--------------------------------------------------------------------------------
1 | export default function Page() {
2 | return null;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/KafkaBreadcrumbItem.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ClusterDetail, ClusterList } from "@/api/kafka/schema";
4 | import { KafkaSwitcher } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/KafkaSwitcher";
5 | import { useSelectedLayoutSegment } from "next/navigation";
6 | import { Suspense } from "react";
7 |
8 | export function KafkaBreadcrumbItem({
9 | selected,
10 | clusters,
11 | isActive,
12 | }: {
13 | selected: ClusterDetail;
14 | clusters: ClusterList[];
15 | isActive: boolean;
16 | }) {
17 | const segment = useSelectedLayoutSegment();
18 |
19 | return (
20 | Loading clusters...}>
21 | `/kafka/${kafkaId}/${segment || ""}`}
26 | />
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/consumer-groups/[groupId]/KafkaConsumerGroupMembers.params.ts:
--------------------------------------------------------------------------------
1 | import { KafkaParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/kafka.params";
2 |
3 | export type KafkaConsumerGroupMembersParams = KafkaParams & { groupId: string };
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/consumer-groups/[groupId]/ResetOffsetModal.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "@storybook/nextjs";
2 | import { ResetOffsetModal } from "./ResetOffsetModal";
3 |
4 | export default {
5 | component: ResetOffsetModal,
6 | } as Meta;
7 |
8 | type Story = StoryObj;
9 |
10 | export const Default: Story = {
11 | args: {
12 | isResetOffsetModalOpen: true,
13 | members: ["console-datagen-consumer-0", "console-datagen-consumer-1"],
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/consumer-groups/[groupId]/reset-offset/DryrunSelect.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "@storybook/nextjs";
2 | import { DryrunSelect } from "./DryrunSelect";
3 |
4 | export default {
5 | component: DryrunSelect,
6 | args: {},
7 | } as Meta;
8 |
9 | type Story = StoryObj;
10 |
11 | export const Default: Story = {
12 | args: {
13 | cliCommand:
14 | "$kafka-consumer-groups --bootstrap-server localhost:9092 --group 'my-consumer-group' --reset-offsets --topic mytopic --to-earliest --dry-run",
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/consumer-groups/[groupId]/reset-offset/LoadingPage.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "@storybook/nextjs";
2 | import { LoadingPage } from "./LoadingPage";
3 |
4 | export default {
5 | component: LoadingPage,
6 | args: {},
7 | } as Meta;
8 |
9 | type Story = StoryObj;
10 |
11 | export const Default: Story = {
12 | args: {},
13 | };
14 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/consumer-groups/[groupId]/reset-offset/LoadingPage.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Bullseye,
3 | Grid,
4 | GridItem,
5 | Spinner,
6 | } from "@/libs/patternfly/react-core";
7 | import { useTranslations } from "next-intl";
8 |
9 | export function LoadingPage() {
10 | const t = useTranslations("ConsumerGroupsTable");
11 | return (
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 | {t("reseting_consumer_group_offsets_text")}
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/consumer-groups/[groupId]/reset-offset/ResetOffset.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "@storybook/nextjs";
2 | import { ResetOffset } from "./ResetOffset";
3 |
4 | export default {
5 | component: ResetOffset,
6 | } as Meta;
7 |
8 | type Story = StoryObj;
9 |
10 | export const Default: Story = {
11 | args: {
12 | topics: [
13 | { topicId: "123", topicName: "console_datagen_002-a" },
14 | { topicId: "456", topicName: "console_datagen_002-b" },
15 | { topicId: "234", topicName: "console_datagen_002-c" },
16 | { topicId: "431", topicName: "console_datagen_002-d" },
17 | ],
18 | selectTopic: "allTopics",
19 | partitions: [1, 2, 3],
20 | selectOffset: "latest",
21 | isLoading: false,
22 | cliCommand:
23 | "$ kafka-consumer-groups --bootstrap-server localhost:9092 --group 'my-consumer-group' --reset-offsets --topic mytopic --to-earliest --dry-run",
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/consumer-groups/[groupId]/reset-offset/TypeaheadSelect.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "@storybook/nextjs";
2 | import { TypeaheadSelect } from "./TypeaheadSelect";
3 |
4 | export default {
5 | component: TypeaheadSelect,
6 | } as Meta;
7 |
8 | type Story = StoryObj;
9 |
10 | export const Default: Story = {
11 | args: {
12 | selectItems: [
13 | "console_datagen_002-a",
14 | "console_datagen_002-b",
15 | "console_datagen_002-c",
16 | "console_datagen_002-d",
17 | "console_datagen_002-a",
18 | "console_datagen_002-a",
19 | "console_datagen_002-b",
20 | "console_datagen_002-c",
21 | "console_datagen_002-d",
22 | "console_datagen_002-c",
23 | ],
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/consumer-groups/[groupId]/reset-offset/dryrun/ConnectedDryrunPage.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Dryrun, NewOffset } from "./Dryrun";
4 | import { useRouter } from "@/i18n/routing";
5 |
6 | export function ConnectedDryrunPage({
7 | groupId,
8 | offsetvalue,
9 | baseurl,
10 | cliCommand,
11 | }: {
12 | groupId: string;
13 | offsetvalue: NewOffset[];
14 | baseurl: string;
15 | cliCommand: string;
16 | }) {
17 | const router = useRouter();
18 |
19 | const onClickCloseDryrun = () => {
20 | router.push(baseurl);
21 | };
22 |
23 | return (
24 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/consumer-groups/[groupId]/types.ts:
--------------------------------------------------------------------------------
1 | export type TopicSelection = "allTopics" | "selectedTopic";
2 |
3 | export type partitionSelection = "allPartitions" | "selectedPartition";
4 |
5 | export type OffsetValue = "custom" | "latest" | "earliest" | "specificDateTime";
6 |
7 | export type DateTimeFormatSelection = "ISO" | "Epoch";
8 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/kafka-connect/ManagedConnectorLabel.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Label, Tooltip } from "@/libs/patternfly/react-core";
3 | import { ServicesIcon } from "@/libs/patternfly/react-icons";
4 | import { useTranslations } from "next-intl";
5 |
6 | export function ManagedConnectorLabel() {
7 | const t = useTranslations("KafkaConnect");
8 | return (
9 |
10 | }
14 | className={"pf-v6-u-ml-sm"}
15 | >
16 | {t("ManagedConnectorLabel.label")}
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/kafka-connect/connect-clusters/[clusterId]/ConnectClusterDeatils.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { ConnectClusterDetails } from "./ConnectClusterDetails";
3 |
4 | const meta: Meta = {
5 | component: ConnectClusterDetails,
6 | args: {
7 | connectVersion: "4.0.0",
8 | workers: 3,
9 | data: [
10 | {
11 | id: "11234",
12 | name: "my-connector-cluster",
13 | type: "sink",
14 | state: "RUNNING",
15 | replicas: 2,
16 | },
17 | {
18 | id: "324dxsgs",
19 | name: "another-connector",
20 | type: "source",
21 | state: "PAUSED",
22 | replicas: 1,
23 | },
24 | ],
25 | },
26 | };
27 |
28 | export default meta;
29 | type Story = StoryObj;
30 |
31 | export const Default: Story = {};
32 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/kafka-connect/kafkaConnect.params.ts:
--------------------------------------------------------------------------------
1 | export type KafkaConnectParams = { kafkaId: string; clusterId: string };
2 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/kafka-connect/kafkaConnectors.params.ts:
--------------------------------------------------------------------------------
1 | export type KafkaConnectorParams = { kafkaId: string; connectorId: string };
2 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/kafka.params.ts:
--------------------------------------------------------------------------------
1 | export type KafkaParams = {
2 | kafkaId: string;
3 | };
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/DistributionChart.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { DistributionChart } from "./DistributionChart";
3 |
4 | const sampleData = {
5 | 1: { leaders: 20, followers: 80 },
6 | 2: { leaders: 30, followers: 60 },
7 | 3: { leaders: 45, followers: 65 },
8 | };
9 |
10 | const sampleNodesCount = {
11 | totalNodes: 10,
12 | brokers: {
13 | total: 7,
14 | warning: false
15 | },
16 | controllers: {
17 | total: 3,
18 | warning: false
19 | },
20 | leadControllerId: "1",
21 | };
22 |
23 | const meta: Meta = {
24 | component: DistributionChart,
25 | };
26 |
27 | export default meta;
28 | type Story = StoryObj;
29 |
30 | export const Default: Story = {
31 | args: {
32 | data: sampleData,
33 | nodesCount: sampleNodesCount,
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/[nodeId]/configuration/NoResultsEmptyState.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | EmptyState,
4 | EmptyStateBody,
5 | Title,
6 | } from "@/libs/patternfly/react-core";
7 | import { SearchIcon } from "@/libs/patternfly/react-icons";
8 | import { useTranslations } from "next-intl";
9 |
10 | export function NoResultsEmptyState({ onReset }: { onReset: () => void }) {
11 | const t = useTranslations("node-config-table");
12 | return (
13 |
14 |
15 | {t("no_results_title")}
16 |
17 | {t("no_results_body")}
18 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/[nodeId]/page.tsx:
--------------------------------------------------------------------------------
1 | import { KafkaNodeParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/kafkaNode.params";
2 | import { redirect } from "@/i18n/routing";
3 |
4 | export default function NodePage({ params }: { params: KafkaNodeParams }) {
5 | redirect(`/kafka/${params.kafkaId}/nodes/${params.nodeId}/configuration`);
6 | }
7 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/kafkaNode.params.ts:
--------------------------------------------------------------------------------
1 | export type KafkaNodeParams = { kafkaId: string; nodeId: string };
2 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/rebalances/EmptyStateNoKafkaRebalance.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { EmptyStateNoKafkaRebalance } from "./EmptyStateNoKafkaRebalance";
3 |
4 | const meta: Meta = {
5 | component: EmptyStateNoKafkaRebalance,
6 | };
7 |
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 | export const NoKafkaClusterRebalances: Story = {};
12 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/rebalances/EmptyStateNoKafkaRebalance.tsx:
--------------------------------------------------------------------------------
1 | import { EmptyState, EmptyStateBody } from "@/libs/patternfly/react-core";
2 | import { CubesIcon } from "@/libs/patternfly/react-icons";
3 | import { useTranslations } from "next-intl";
4 |
5 | export function EmptyStateNoKafkaRebalance({}: {}) {
6 | const t = useTranslations("Rebalancing");
7 | return (
8 |
13 |
14 | {t("no_kafka_cluster_rebalances_found_description")}
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/rebalances/RebalancesCountCard.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { RebalancesCountCard } from "./RebalancesCountCard";
3 |
4 | const meta: Meta = {
5 | component: RebalancesCountCard,
6 | };
7 |
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 | export const stopRebalance: Story = {
12 | args: {
13 | TotalRebalancing: 10,
14 | proposalReady: 2,
15 | rebalancing: 5,
16 | ready: 0,
17 | stopped: 1,
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/rebalances/ValidationModal.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { ValidationModal } from "./ValidationModal";
3 |
4 | const meta: Meta = {
5 | component: ValidationModal,
6 | };
7 |
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 | export const stopRebalance: Story = {
12 | args: {
13 | isModalOpen: true,
14 | },
15 | };
16 |
17 | export const ApproveRebalance: Story = {
18 | args: {
19 | isModalOpen: true,
20 | status: "approve",
21 | },
22 | };
23 |
24 | export const RefreshRebalance: Story = {
25 | args: {
26 | isModalOpen: true,
27 | status: "approve",
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/rebalances/[rebalanceId]/KafkaRebalance.params.ts:
--------------------------------------------------------------------------------
1 | import { KafkaParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/kafka.params";
2 |
3 | export type KafkaRebalanceParams = KafkaParams & { rebalanceId: string };
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/nodes/rebalances/[rebalanceId]/OptimizationProposal.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { OptimizationProposal } from "./OptimizationProposal";
3 |
4 | const meta: Meta = {
5 | component: OptimizationProposal,
6 | };
7 |
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 | export const OptimizationProposalModal: Story = {
12 | args: {
13 | isModalOpen: true,
14 | dataToMoveMB: 0,
15 | monitoredPartitionsPercentage: 100,
16 | numIntraBrokerReplicaMovements: 0,
17 | numLeaderMovements: 24,
18 | numReplicaMovements: 25,
19 | onDemandBalancednessScoreAfter: 82.65544,
20 | onDemandBalancednessScoreBefore: 82.6543,
21 | recentWindows: 5,
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/overview/ConnectedRecentTopics.tsx:
--------------------------------------------------------------------------------
1 | import { ViewedTopic } from "@/api/topics/actions";
2 | import { RecentTopicsCard } from "@/components/ClusterOverview/RecentTopicsCard";
3 |
4 | export async function ConnectedRecentTopics({
5 | data,
6 | }: {
7 | data: Promise;
8 | }) {
9 | const viewedTopics = await data;
10 | return ;
11 | }
12 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/page.tsx:
--------------------------------------------------------------------------------
1 | import { KafkaParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/kafka.params";
2 | import { redirect } from "@/i18n/routing";
3 |
4 | //export const dynamic = "force-dynamic";
5 |
6 | export default function KafkaRoot({ params }: { params: KafkaParams }) {
7 | redirect(`/kafka/${params.kafkaId}/overview`);
8 | }
9 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/schema-registry/page.tsx:
--------------------------------------------------------------------------------
1 | export default function SchemaRegistryPage() {
2 | return TBD
;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/(page)/post-delete/page.tsx:
--------------------------------------------------------------------------------
1 | import { KafkaParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/kafka.params";
2 | import { RedirectOnLoad } from "@/components/Navigation/RedirectOnLoad";
3 |
4 | export default function PostDeletePage({
5 | params: { kafkaId },
6 | }: {
7 | params: KafkaParams;
8 | }) {
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/[topicId]/configuration/NoResultsEmptyState.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | EmptyState,
4 | EmptyStateBody,
5 | Title,
6 | } from "@/libs/patternfly/react-core";
7 | import { SearchIcon } from "@/libs/patternfly/react-icons";
8 | import { useTranslations } from "next-intl";
9 |
10 | export function NoResultsEmptyState({ onReset }: { onReset: () => void }) {
11 | const t = useTranslations("node-config-table");
12 | return (
13 |
14 |
15 | {t("no_results_title")}
16 |
17 | {t("no_results_body")}
18 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/[topicId]/messages/useParseSearchParams.ts:
--------------------------------------------------------------------------------
1 | import { parseSearchParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/topics/[topicId]/messages/parseSearchParams";
2 | import { useSearchParams } from "next/navigation";
3 | import { useMemo } from "react";
4 |
5 | export function useParseSearchParams(): [
6 | ReturnType,
7 | Record,
8 | ] {
9 | const searchParamsEntities = useSearchParams();
10 | return useMemo(() => {
11 | const searchParams = Object.fromEntries(searchParamsEntities);
12 | return [parseSearchParams(searchParams), searchParams];
13 | }, [searchParamsEntities]);
14 | }
15 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/[topicId]/page.tsx:
--------------------------------------------------------------------------------
1 | import { KafkaTopicParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/topics/kafkaTopic.params";
2 | import { redirect } from "@/i18n/routing";
3 |
4 | export default function TopicPage({ params }: { params: KafkaTopicParams }) {
5 | redirect(`/kafka/${params.kafkaId}/topics/${params.topicId}/messages`);
6 | }
7 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/[topicId]/partitions/NoResultsEmptyState.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { NoResultsEmptyState as Comp } from "./NoResultsEmptyState";
3 |
4 | export default {
5 | component: Comp,
6 | } as Meta;
7 |
8 | type Story = StoryObj;
9 |
10 | export const Default: Story = {};
11 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/[topicId]/partitions/NoResultsEmptyState.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import {
3 | Button,
4 | EmptyState,
5 | EmptyStateBody,
6 | Title,
7 | } from "@/libs/patternfly/react-core";
8 | import { SearchIcon } from "@/libs/patternfly/react-icons";
9 | import { useTranslations } from "next-intl";
10 |
11 | export function NoResultsEmptyState({ onReset }: { onReset: () => void }) {
12 | const t = useTranslations("topics");
13 | return (
14 |
15 |
16 | {t("partition_table.partition_empty_title")}
17 |
18 |
19 | {t("partition_table.partition_empty_state_body")}
20 |
21 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/[topicId]/schema-registry/page.tsx:
--------------------------------------------------------------------------------
1 | export default function SchemaRegistryPage() {
2 | return TODO
;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/[topicId]/template.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { setTopicAsViewed } from "@/api/topics/actions";
3 | import { KafkaTopicParams } from "@/app/[locale]/(authorized)/kafka/[kafkaId]/topics/kafkaTopic.params";
4 | import { useParams } from "next/navigation";
5 | import { PropsWithChildren, useEffect } from "react";
6 |
7 | export default function TopicTemplate({ children }: PropsWithChildren) {
8 | const params = useParams();
9 |
10 | useEffect(() => {
11 | void setTopicAsViewed(params.kafkaId, params.topicId);
12 | }, [params.kafkaId, params.topicId]);
13 | return children;
14 | }
15 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/create/FieldName.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { FieldName } from "./FieldName";
3 |
4 | const meta: Meta = {
5 | component: FieldName,
6 | };
7 |
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 | export const ValidName: Story = {
12 | args: {
13 | name: "Hello world"
14 | },
15 | };
16 |
17 | export const InvalidName: Story = {
18 | args: {
19 | name: "..",
20 | nameInvalid: true
21 | },
22 | };
23 |
24 | export const InvalidLength: Story = {
25 | args: {
26 | name: "..",
27 | nameInvalid: true,
28 | lengthInvalid: true
29 | },
30 | };
31 |
32 | export const NameFormatInvalid: Story = {
33 | args: {
34 | name: "123",
35 | formatInvalid: true
36 | },
37 | };
38 |
39 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/create/FieldPartition.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "@storybook/nextjs";
2 | import { FieldPartitions } from "./FieldPartitions";;
3 |
4 | const meta: Meta = {
5 | component: FieldPartitions,
6 | };
7 |
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 |
12 | export const Default: Story = {
13 | args: {
14 | partitions: 3,
15 | invalid: false,
16 | backendError: false,
17 | }
18 | }
19 |
20 | export const InvalidState: Story = {
21 | args: {
22 | partitions: 1,
23 | invalid: true,
24 | backendError: "Backend error message",
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/create/FieldReplicas.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "@storybook/nextjs";
2 | import { FieldReplicas } from "./FieldReplicas";;
3 |
4 | const meta: Meta = {
5 | component: FieldReplicas,
6 | };
7 |
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 |
12 | export const Default: Story = {
13 | args: {
14 | replicas: 3,
15 | maxReplicas: 5,
16 | showErrors: false,
17 | backendError: false,
18 | }
19 | }
20 |
21 | export const InvalidState: Story = {
22 | args: {
23 | replicas: 6,
24 | maxReplicas: 5,
25 | showErrors: true,
26 | backendError: "Exceeded maximum replicas",
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/create/topicMutateErrorToFieldError.ts:
--------------------------------------------------------------------------------
1 | import { ApiError } from "@/api/api";
2 |
3 | export function topicMutateErrorToFieldError(
4 | errors: ApiError[] | undefined,
5 | isConfig: boolean,
6 | fields: string[],
7 | ) {
8 | if (errors) {
9 | const fieldErrors = errors.map(error => {
10 | const field = error.source?.pointer?.split("/")[isConfig ? 4 : 3];
11 | if (field && fields.includes(field)) {
12 | return {
13 | field,
14 | error: error.detail,
15 | };
16 | }
17 | });
18 |
19 | return fieldErrors.length > 0 ? fieldErrors[0] : undefined
20 | }
21 | return undefined;
22 | }
23 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/[kafkaId]/topics/kafkaTopic.params.ts:
--------------------------------------------------------------------------------
1 | export type KafkaTopicParams = { kafkaId: string; topicId: string };
2 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/kafka/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "@/i18n/routing";
2 |
3 | export default function Page({}) {
4 | return redirect("/");
5 | }
6 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(authorized)/layout.tsx:
--------------------------------------------------------------------------------
1 | import { getAuthOptions } from "@/app/api/auth/[...nextauth]/auth-options";
2 |
3 | import { getServerSession } from "next-auth";
4 | import { ReactNode } from "react";
5 | import { AppSessionProvider } from "./AppSessionProvider";
6 | import { SessionRefresher } from "./SessionRefresher";
7 |
8 | type Props = {
9 | children: ReactNode;
10 | params: { locale: string };
11 | };
12 |
13 | export default async function Layout({ children, params: { locale } }: Props) {
14 | const authOptions = await getAuthOptions();
15 | const session = await getServerSession(authOptions);
16 | return (
17 |
18 | {children}
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(public)/(home)/home.module.css:
--------------------------------------------------------------------------------
1 | .hero {
2 | display: grid;
3 | padding: var(--pf-v6-global--spacer--lg);
4 | }
5 |
6 | @media screen and (min-width: 768px) {
7 | .hero {
8 | grid-template-columns: 250px auto;
9 | grid-template-areas: "graphic content";
10 | grid-grap: var(--pf-v6-global--spacer--2xl);
11 | padding: var(--pf-v6-global--spacer--2xl);
12 | }
13 |
14 | .hero::before {
15 | display: block;
16 | content: "";
17 | grid-area: graphic;
18 | background-image: url("/home-hero-bg.png");
19 | background-repeat: no-repeat;
20 | background-position-y: 59px;
21 | background-size: 380px;
22 | margin: -98px 0 -48px -128px;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(public)/schema/ConnectedSchema.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { SchemaValue } from "@/components/MessagesTable/components/SchemaValue";
4 | import {
5 | Flex,
6 | FlexItem,
7 | PageSection,
8 | Title,
9 | } from "@/libs/patternfly/react-core";
10 |
11 | export function ConnectedSchema({
12 | content,
13 | name,
14 | }: {
15 | content: string;
16 | name: string;
17 | }) {
18 | return (
19 |
20 |
21 |
22 | {name}
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/ui/app/[locale]/(public)/schema/page.tsx:
--------------------------------------------------------------------------------
1 | import { getSchema } from "@/api/schema/action";
2 | import { ConnectedSchema } from "./ConnectedSchema";
3 |
4 | export default async function ConnectedSchemaPage({
5 | searchParams,
6 | }: {
7 | searchParams: { content?: string; schemaname?: string };
8 | }) {
9 | const urlSearchParams = new URLSearchParams(searchParams);
10 |
11 | const content = urlSearchParams.get("content");
12 | const schemaname = urlSearchParams.get("schemaname");
13 |
14 | if (!content) {
15 | throw new Error("Content parameter is missing.");
16 | }
17 |
18 | const schemaContent = await getSchema(content);
19 |
20 | return ;
21 | }
22 |
--------------------------------------------------------------------------------
/ui/app/[locale]/NextIntlProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { AbstractIntlMessages, NextIntlClientProvider } from "next-intl";
3 | import { ReactNode } from "react";
4 |
5 | type Props = {
6 | messages: AbstractIntlMessages;
7 | locale: string;
8 | children: ReactNode;
9 | };
10 | export default function NextIntlProvider({
11 | messages,
12 | locale,
13 | children,
14 | }: Props) {
15 | const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
16 | return (
17 |
22 | {children}
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/ui/app/[locale]/layout.tsx:
--------------------------------------------------------------------------------
1 | import { getMessages, getTranslations } from "next-intl/server";
2 | import { ReactNode } from "react";
3 | import NextIntlProvider from "./NextIntlProvider";
4 | import "../globals.css";
5 |
6 | type Props = {
7 | children: ReactNode;
8 | params: { locale: string };
9 | };
10 |
11 | export default async function Layout({ children, params: { locale } }: Props) {
12 | const messages = await getMessages();
13 | return (
14 |
15 | {children}
16 |
17 | );
18 | }
19 |
20 | export async function generateMetadata({
21 | params: { locale },
22 | }: Omit) {
23 | const t = await getTranslations({ locale, namespace: "common" });
24 |
25 | return {
26 | title: t("title"),
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/ui/app/api/auth/[...nextauth]/anonymous.ts:
--------------------------------------------------------------------------------
1 | import CredentialsProvider from "next-auth/providers/credentials";
2 | import { Provider } from "next-auth/providers/index";
3 |
4 | export function makeAnonymous(): Provider {
5 | const provider = CredentialsProvider({
6 | id: "anonymous",
7 | credentials: {},
8 | async authorize() {
9 | return { id: "1", name: "Anonymous", email: "anonymous@example.com" };
10 | },
11 | });
12 |
13 | return provider;
14 | }
15 |
--------------------------------------------------------------------------------
/ui/app/api/auth/oidc/layout.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { SessionProvider } from "next-auth/react";
4 |
5 | interface Props {
6 | children: React.ReactNode
7 | }
8 |
9 | export default function AuthLayout(props: Props) {
10 | return (
11 |
12 | { props.children }
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/ui/app/api/auth/oidc/signin/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { signIn, useSession } from "next-auth/react";
4 | import { useRouter } from 'next/navigation'
5 | import { useEffect } from "react";
6 |
7 | export default function SignIn() {
8 | const router = useRouter()
9 | const { status } = useSession()
10 |
11 | useEffect(() => {
12 | if (status === 'unauthenticated') {
13 | signIn('oidc')
14 | }
15 | else if (status === 'authenticated') {
16 | router.push('/')
17 | }
18 | }, [ router, status ])
19 |
20 | return (
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/ui/app/config/route.ts:
--------------------------------------------------------------------------------
1 | import config from '@/utils/config';
2 |
3 | export const dynamic = "force-dynamic";
4 |
5 | /*
6 | * This route serves as an endpoint for middleware.js to fetch whether
7 | * OIDC security is enabled or not.
8 | */
9 | export async function GET() {
10 | const oidcEnabled = await config().then(cfg => cfg.security?.oidc != null);
11 |
12 | return Response.json({
13 | "oidc": oidcEnabled,
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/ui/app/error.tsx:
--------------------------------------------------------------------------------
1 | "use client"; // Error components must be Client Components
2 |
3 | import { ApplicationError } from "@/components/ApplicationError";
4 | import { useEffect } from "react";
5 |
6 | export default function Error({
7 | error,
8 | reset,
9 | }: {
10 | error: Error & { digest?: string };
11 | reset: () => void;
12 | }) {
13 | useEffect(() => {
14 | // Log the error to an error reporting service
15 | console.error(error);
16 | }, [error]);
17 |
18 | return ;
19 | }
20 |
--------------------------------------------------------------------------------
/ui/app/global-error.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { logger } from "@/utils/logger";
3 | import { useEffect } from "react";
4 | const log = logger.child({ module: "ui" });
5 | export default function GlobalError({
6 | error,
7 | reset,
8 | }: {
9 | error: Error & { digest?: string };
10 | reset: () => void;
11 | }) {
12 | useEffect(() => {
13 | log.error({error}, "unmanaged error");
14 | }, [error]);
15 | return (
16 |
17 |
18 | Something went wrong!
19 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/ui/app/globals.css:
--------------------------------------------------------------------------------
1 | @import "@patternfly/react-core/dist/styles/base.css";
2 | @import "@patternfly/patternfly/patternfly-addons.css";
3 | @import "@patternfly/quickstarts/dist/quickstarts.min.css";
4 | @import "react-json-view-lite/dist/index.css";
5 | @import "@patternfly/patternfly/patternfly-charts.css";
6 |
--------------------------------------------------------------------------------
/ui/app/healthz/route.ts:
--------------------------------------------------------------------------------
1 | export async function GET() {
2 | return Response.json({
3 | "status": "UP",
4 | "checks": [],
5 | });
6 | }
7 |
--------------------------------------------------------------------------------
/ui/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 |
3 | type Props = {
4 | children: ReactNode;
5 | };
6 |
7 | //export const fetchCache = "force-no-store";
8 | //export const dynamic = "force-dynamic";
9 |
10 | // Since we have a `not-found.tsx` page on the root, a layout file
11 | // is required, even if it's just passing children through.
12 | export default function RootLayout({ children }: Props) {
13 | return (
14 |
15 |
16 | {children}
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/ui/app/loading.tsx:
--------------------------------------------------------------------------------
1 | import { EmptyStateLoading } from "@/components/EmptyStateLoading";
2 | import { PageSection } from "@/libs/patternfly/react-core";
3 |
4 | export default function AppLoading() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/ui/components/AlertContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from "react";
2 | import { GroupToastAlertProps } from "./AlertToastGroup";
3 |
4 | type AlertContextType = {
5 | addAlert: (alert: Omit) => void;
6 | removeAlert: (id: string) => void;
7 | };
8 |
9 | export const AlertContext = createContext(
10 | undefined,
11 | );
12 |
13 | export function useAlert() {
14 | const context = useContext(AlertContext);
15 | if (!context) {
16 | throw new Error("useAlert must be used within an AlertProvider");
17 | }
18 | return context;
19 | }
20 |
--------------------------------------------------------------------------------
/ui/components/AppLayoutProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { createContext, PropsWithChildren, useContext, useState } from "react";
3 |
4 | const AppLayoutContext = createContext({
5 | sidebarExpanded: true,
6 | toggleSidebar: () => {},
7 | closeSidebar: () => {},
8 | openSidebar: () => {},
9 | });
10 |
11 | export function AppLayoutProvider({ children }: PropsWithChildren) {
12 | const [sidebarExpanded, setSidebarExpanded] = useState(true);
13 | const closeSidebar = () => {
14 | setSidebarExpanded(false);
15 | };
16 | const openSidebar = () => {
17 | setSidebarExpanded(true);
18 | };
19 | const toggleSidebar = () => {
20 | setSidebarExpanded((o) => !o);
21 | };
22 | return (
23 |
26 | {children}
27 |
28 | );
29 | }
30 |
31 | export function useAppLayout() {
32 | return useContext(AppLayoutContext);
33 | }
34 |
--------------------------------------------------------------------------------
/ui/components/AppSidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { PageSidebar, PageSidebarBody } from "@/libs/patternfly/react-core";
3 | import { PropsWithChildren } from "react";
4 | import { useAppLayout } from "./AppLayoutProvider";
5 |
6 | export function AppSidebar({ children }: PropsWithChildren) {
7 | const { sidebarExpanded } = useAppLayout();
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/ui/components/ApplicationError.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { expect, fn, userEvent, within } from "storybook/test";
3 | import { ApplicationError as Comp } from "./ApplicationError";
4 |
5 | const meta: Meta = {
6 | component: Comp,
7 | args: {
8 | error: {
9 | digest: "Some description of the error",
10 | message: "Some message",
11 | name: "Some name",
12 | },
13 | onReset: fn(),
14 | },
15 | };
16 |
17 | export default meta;
18 | type Story = StoryObj;
19 |
20 | export const ApplicationError: Story = {
21 | play: async ({ canvasElement, args }) => {
22 | const canvas = within(canvasElement);
23 | await userEvent.click(canvas.getByText("Retry"));
24 | await expect(args.onReset).toHaveBeenCalled();
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/ui/components/ClusterDrawerContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from "react";
2 |
3 | export const ClusterDrawerContext = createContext<{
4 | open: (clusterId: string) => void;
5 | close: () => void;
6 | expanded: boolean;
7 | clusterId: string | undefined;
8 | }>(null!);
9 |
10 | export function useOpenClusterConnectionPanel() {
11 | const { open } = useContext(ClusterDrawerContext);
12 | return open;
13 | }
14 |
15 | export function useClusterDrawerContext() {
16 | return useContext(ClusterDrawerContext);
17 | }
18 |
--------------------------------------------------------------------------------
/ui/components/ClusterDrawerProvider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { PropsWithChildren, useState } from "react";
3 | import { ClusterDrawerContext } from "./ClusterDrawerContext";
4 |
5 | export function ClusterDrawerProvider({ children }: PropsWithChildren) {
6 | const [expanded, setExpanded] = useState(false);
7 | const [clusterId, setClusterId] = useState();
8 | const open = (clusterId: string) => {
9 | setClusterId(clusterId);
10 | setExpanded(true);
11 | };
12 | const close = () => setExpanded(false);
13 |
14 | return (
15 |
16 | {children}
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/ui/components/ClusterOverview/ReconciliationModal.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "@storybook/nextjs";
2 | import { ReconciliationModal } from "./ReconciliationModal";
3 |
4 | export default {
5 | component: ReconciliationModal,
6 | } as Meta;
7 |
8 | type Story = StoryObj;
9 |
10 | export const ReconcilationPaused: Story = {
11 | args: {
12 | isReconciliationPaused: false,
13 | isModalOpen: true,
14 | },
15 | };
16 |
17 | export const ReconcilationResumed: Story = {
18 | args: {
19 | isReconciliationPaused: true,
20 | isModalOpen: true,
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/ui/components/ClusterOverview/TopicsPartitionsCard.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 |
3 | import { TopicsPartitionsCard } from "./TopicsPartitionsCard";
4 |
5 | const meta: Meta = {
6 | component: TopicsPartitionsCard,
7 | };
8 |
9 | export default meta;
10 | type Story = StoryObj;
11 |
12 | export const WithData: Story = {
13 | args: {
14 | isLoading: false,
15 | topicsTotal: 999999,
16 | topicsReplicated: 400000,
17 | topicsUnderReplicated: 400000,
18 | partitions: 999999,
19 | },
20 | };
21 | export const Loading: Story = {
22 | args: {
23 | isLoading: true,
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/ui/components/ClusterOverview/components/ChartSkeletonLoader.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 |
3 | import { ChartSkeletonLoader as Comp } from "./ChartSkeletonLoader";
4 |
5 | export default {
6 | component: Comp,
7 | args: {},
8 | parameters: {
9 | backgrounds: {
10 | default: "Background color 100",
11 | },
12 | },
13 | } as Meta;
14 |
15 | type Story = StoryObj;
16 |
17 | export const ChartSkeletonLoader: Story = {};
18 |
--------------------------------------------------------------------------------
/ui/components/ClusterOverview/components/ChartSkeletonLoader.tsx:
--------------------------------------------------------------------------------
1 | import { Flex, FlexItem, Skeleton } from "@/libs/patternfly/react-core";
2 | import { getHeight, getPadding } from "./chartConsts";
3 |
4 | export function ChartSkeletonLoader() {
5 | const height = getHeight(0);
6 | const padding = getPadding(0);
7 | return (
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/ui/components/ClusterOverview/components/ErrorsAndWarnings.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 |
3 | import { ErrorsAndWarnings as Comp } from "./ErrorsAndWarnings";
4 |
5 | export default {
6 | component: Comp,
7 | args: {
8 | dangers: 999,
9 | warnings: 999,
10 | },
11 | } as Meta;
12 |
13 | type Story = StoryObj;
14 |
15 | export const ErrorsAndWarnings: Story = {};
16 |
--------------------------------------------------------------------------------
/ui/components/ClusterOverview/components/chartConsts.ts:
--------------------------------------------------------------------------------
1 | export const getHeight = (legendEntriesCount: number) => {
2 | const { bottom } = getPadding(legendEntriesCount);
3 | return 150 + bottom;
4 | };
5 | export const getPadding = (legendEntriesCount: number) => ({
6 | bottom: 50 + 32 * legendEntriesCount,
7 | top: 5,
8 | left: 70,
9 | right: 30,
10 | });
11 |
--------------------------------------------------------------------------------
/ui/components/EmptyStateLoading.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { EmptyStateLoading as Comp } from "./EmptyStateLoading";
3 |
4 | const meta: Meta = {
5 | component: Comp,
6 | };
7 |
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 | export const EmptyStateLoading: Story = {};
12 |
--------------------------------------------------------------------------------
/ui/components/EmptyStateLoading.tsx:
--------------------------------------------------------------------------------
1 | import { EmptyState, Spinner } from "@/libs/patternfly/react-core";
2 |
3 | export function EmptyStateLoading() {
4 | return (
5 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/ui/components/ExpandableCard.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import {
3 | Card,
4 | CardExpandableContent,
5 | CardHeader,
6 | CardTitle,
7 | } from "@/libs/patternfly/react-core";
8 | import { PropsWithChildren, ReactNode, useState } from "react";
9 |
10 | export function ExpandableCard({
11 | title,
12 | collapsedTitle,
13 | isCompact,
14 | children,
15 | }: PropsWithChildren<{
16 | title: ReactNode;
17 | collapsedTitle?: ReactNode;
18 | isCompact?: boolean;
19 | }>) {
20 | const [expanded, setExpanded] = useState(true);
21 | const titleNode =
22 | typeof title === "string" ? {title} : title;
23 | return (
24 |
25 | setExpanded((e) => !e)}>
26 | {expanded && titleNode}
27 | {!expanded && (collapsedTitle || titleNode)}
28 |
29 | {children}
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/ui/components/ExpandableSection.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ExpandableSection as PFExpandableSection,
3 | ExpandableSectionProps,
4 | } from "@/libs/patternfly/react-core";
5 | import { useState } from "react";
6 |
7 | export function ExpandableSection({
8 | initialExpanded = true,
9 | ...props
10 | }: Omit & {
11 | initialExpanded?: boolean;
12 | }) {
13 | const [expanded, setExpanded] = useState(initialExpanded);
14 |
15 | return (
16 | setExpanded(e)}
20 | />
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/ui/components/Format/Bytes.tsx:
--------------------------------------------------------------------------------
1 | import { useFormatBytes } from "@/utils/useFormatBytes";
2 |
3 | export function Bytes({
4 | value,
5 | }: {
6 | value: string | number | null | undefined;
7 | }) {
8 | const formatter = useFormatBytes();
9 | if (value === undefined || value === null) {
10 | return "-";
11 | }
12 | value = typeof value === "string" ? parseInt(value, 10) : value;
13 | return isNaN(value) ? "-" : formatter(value);
14 | }
15 |
--------------------------------------------------------------------------------
/ui/components/Format/Number.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useFormatter } from "next-intl";
4 |
5 | export function Number({ value }: { value: string | number | null | undefined }) {
6 | const formatter = useFormatter();
7 | value = typeof value === "string" ? parseInt(value, 10) : value;
8 | return value !== undefined && value !== null && !isNaN(value) ? formatter.number(value) : "-";
9 | }
10 |
--------------------------------------------------------------------------------
/ui/components/ManagedTopicLabel.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { ManagedTopicLabel } from "./ManagedTopicLabel";
3 |
4 | const meta: Meta = {
5 | component: ManagedTopicLabel,
6 | };
7 |
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 | export const Default: Story = {};
12 |
--------------------------------------------------------------------------------
/ui/components/ManagedTopicLabel.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Label, Tooltip } from "@/libs/patternfly/react-core";
3 | import { ServicesIcon } from "@/libs/patternfly/react-icons";
4 | import { useTranslations } from "next-intl";
5 |
6 | export function ManagedTopicLabel() {
7 | const t = useTranslations();
8 | return (
9 |
10 | }
14 | className={"pf-v6-u-ml-sm"}
15 | >
16 | {t("ManagedTopicLabel.label")}
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/AlertTopicGone.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { AlertTopicGone as Comp } from "./AlertTopicGone";
3 |
4 | const meta: Meta = {
5 | component: Comp,
6 | args: {},
7 | };
8 |
9 | export default meta;
10 | type Story = StoryObj;
11 |
12 | export const AlertTopicGone: Story = {};
13 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/AlertTopicGone.tsx:
--------------------------------------------------------------------------------
1 | import { Alert } from "@/libs/patternfly/react-core";
2 | import { AlertActionLink } from "@/libs/patternfly/react-core";
3 | import { useTranslations } from "next-intl";
4 |
5 | export function AlertTopicGone({ onClick }: { onClick: () => void }) {
6 | const t = useTranslations();
7 | return (
8 |
14 | {t("AlertTopicGone.go_back_to_the_list_of_topics")}
15 |
16 | }
17 | >
18 | {t("AlertTopicGone.this_topic_was_deleted_or_you_donapost_have_the_co")}
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/NoDataEmptyState.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { NoDataEmptyState } from "./NoDataEmptyState";
3 |
4 | const meta: Meta = {
5 | component: NoDataEmptyState,
6 | };
7 |
8 | export default meta;
9 | type Story = StoryObj;
10 |
11 | export const Default: Story = {};
12 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/components/DateTimePicker.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { DateTimePicker } from "./DateTimePicker";
3 |
4 | export default {
5 | component: DateTimePicker,
6 | } as Meta;
7 |
8 | type Story = StoryObj;
9 |
10 | export const Example: Story = {};
11 |
12 | export const WithInitialValue: Story = {
13 | args: {
14 | value: "2023-01-01T00:00:00Z",
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/components/NoData.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { expect, within } from "storybook/test";
3 | import { NoData as Comp } from "./NoData";
4 |
5 | const meta: Meta = {
6 | component: Comp,
7 | };
8 |
9 | export default meta;
10 | type Story = StoryObj;
11 |
12 | export const NoData: Story = {
13 | play: async ({ canvasElement }) => {
14 | const canvas = within(canvasElement);
15 | expect(canvas.getByText("No data"));
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/components/NoData.tsx:
--------------------------------------------------------------------------------
1 | import { useTranslations } from "next-intl";
2 |
3 | export function NoData() {
4 | const t = useTranslations("message-browser");
5 | return {t("no_data")};
6 | }
7 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/components/NoResultsEmptyState.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { expect, fn, userEvent, within } from "storybook/test";
3 | import { NoResultsEmptyState as Comp } from "./NoResultsEmptyState";
4 |
5 | const meta: Meta = {
6 | component: Comp,
7 | args: {
8 | onReset: fn(),
9 | },
10 | };
11 |
12 | export default meta;
13 | type Story = StoryObj;
14 |
15 | export const NoResultsEmptyState: Story = {
16 | play: async ({ canvasElement, args }) => {
17 | const canvas = within(canvasElement);
18 | expect(canvas.getByText("No messages data"));
19 | await userEvent.click(canvas.getByText("Show latest messages"));
20 | await expect(args.onReset).toHaveBeenCalled();
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/components/NoResultsEmptyState.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | EmptyState,
4 | EmptyStateBody,
5 | Title,
6 | } from "@/libs/patternfly/react-core";
7 | import { SearchIcon } from "@/libs/patternfly/react-icons";
8 | import { useTranslations } from "next-intl";
9 |
10 | export function NoResultsEmptyState({ onReset }: { onReset: () => void }) {
11 | const t = useTranslations("message-browser");
12 |
13 | return (
14 |
15 |
16 | {t("no_results_title")}
17 |
18 | {t("no_results_body")}
19 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/components/PartitionSelector.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { expect, fn, within } from "storybook/test";
3 | import { PartitionSelector as Comp } from "./PartitionSelector";
4 |
5 | const meta: Meta = {
6 | component: Comp,
7 | args: {
8 | onChange: fn(),
9 | partitions: 2,
10 | },
11 | };
12 |
13 | export default meta;
14 | type Story = StoryObj;
15 |
16 | export const DefaultValue: Story = {
17 | play: async ({ canvasElement }) => {
18 | const canvas = within(canvasElement);
19 | expect(canvas.getByText("All partitions"));
20 | },
21 | };
22 |
23 | export const WithInitialValue: Story = {
24 | args: {
25 | value: 1,
26 | },
27 | play: async ({ canvasElement }) => {
28 | const canvas = within(canvasElement);
29 | expect(canvas.getByText("Partition 1"));
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/components/SchemaValue.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryObj } from "@storybook/nextjs";
2 | import { SchemaValue } from "./SchemaValue";
3 |
4 | export default {
5 | component: SchemaValue,
6 | args: {},
7 | } as Meta;
8 |
9 | type Story = StoryObj;
10 |
11 | export const Default: Story = {
12 | args: {
13 | name: "SchemaValue",
14 | schema: JSON.stringify({
15 | type: "record",
16 | name: "price",
17 | namespace: "com.example",
18 | fields: [
19 | { name: "symbol", type: "string" },
20 | { name: "price", type: "string" },
21 | ],
22 | }),
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/components/UntilGroup.stories.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta, StoryObj } from "@storybook/nextjs";
2 | import { expect, fn, within } from "storybook/test";
3 | import { UntilGroup as Comp } from "./UntilGroup";
4 |
5 | const meta: Meta = {
6 | component: Comp,
7 | args: {
8 | onLimitChange: fn(),
9 | onLive: fn(),
10 | },
11 | };
12 |
13 | export default meta;
14 | type Story = StoryObj;
15 |
16 | export const Limit: Story = {
17 | args: {
18 | limit: 50,
19 | },
20 | play: async ({ canvasElement, args }) => {
21 | const canvas = within(canvasElement);
22 | expect(canvas.getByText(args.limit, { exact: false }));
23 | },
24 | };
25 |
26 | export const Continuosly: Story = {
27 | args: {
28 | limit: "continuously",
29 | },
30 | play: async ({ canvasElement, args }) => {
31 | const canvas = within(canvasElement);
32 | expect(canvas.findByText("Continuously"));
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/components/utils.ts:
--------------------------------------------------------------------------------
1 | import { Message } from "@/api/messages/schema";
2 |
3 | export function isSameMessage(m1: Message, m2: Message) {
4 | return JSON.stringify(m1) === JSON.stringify(m2);
5 | }
6 |
7 | export function beautifyUnknownValue(value: string): string {
8 | try {
9 | return JSON.stringify(JSON.parse(value), null, 2);
10 | } catch (e) {
11 | // noop
12 | }
13 | return value;
14 | }
15 |
16 | export function maybeJson(value: string): [string | object, boolean] {
17 | try {
18 | const parsed = JSON.parse(value);
19 | return [parsed, typeof parsed !== "string"];
20 | } catch (e) {
21 | // noop
22 | }
23 | return [value, false];
24 | }
25 |
--------------------------------------------------------------------------------
/ui/components/MessagesTable/types.ts:
--------------------------------------------------------------------------------
1 | export type SearchParams = {
2 | partition?: number;
3 | limit: number | "continuously";
4 | query?: {
5 | value: string;
6 | where: "headers" | "key" | "value" | "everywhere";
7 | };
8 | from:
9 | | { type: "timestamp"; value: string }
10 | | { type: "epoch"; value: number }
11 | | { type: "offset"; value: number }
12 | | { type: "latest" };
13 | };
14 |
--------------------------------------------------------------------------------
/ui/components/NavExpandable.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { usePathname } from "@/i18n/routing";
3 | import {
4 | NavExpandable as PFNavExpendable,
5 | NavExpandableProps,
6 | } from "@/libs/patternfly/react-core";
7 | import { PropsWithChildren } from "react";
8 |
9 | export function NavExpandable({
10 | title,
11 | groupId,
12 | children,
13 | url,
14 | startExpanded,
15 | }: PropsWithChildren<
16 | Pick & {
17 | url: string;
18 | startExpanded: boolean;
19 | }
20 | >) {
21 | const pathname = usePathname();
22 | const isActive = pathname.startsWith(url.toString());
23 | const expanded = startExpanded || isActive;
24 | return (
25 |
31 | {children}
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/ui/components/Navigation/BreadcrumbLink.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import {
3 | BreadcrumbItem,
4 | BreadcrumbItemProps,
5 | } from "@/libs/patternfly/react-core";
6 | import { Link } from "@/i18n/routing";
7 | import { Route } from "next";
8 |
9 | export function BreadcrumbLink({
10 | href,
11 | isActive = false,
12 | children,
13 | ...props
14 | }: Omit & {
15 | href: Route | URL;
16 | isActive?: boolean;
17 | }) {
18 | return (
19 | (
23 |
24 | {children}
25 |
26 | )}
27 | />
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/ui/components/Navigation/ButtonLink.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { ButtonProps } from "@/libs/patternfly/react-core";
3 | import { Link } from "@/i18n/routing";
4 | import { Route } from "next";
5 |
6 | export function ButtonLink({
7 | href,
8 | variant,
9 | children,
10 | }: Pick & { href: Route | URL }) {
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/ui/components/Navigation/LabelLink.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Label, LabelProps } from "@/libs/patternfly/react-core";
3 | import { Link } from "@/i18n/routing";
4 | import { Route } from "next";
5 |
6 | export function LabelLink({
7 | href,
8 | ...props
9 | }: LabelProps & { href: Route | URL }) {
10 | return (
11 |