├── .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 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 | 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 | 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 |
} 16 | footerContent={ 17 | 21 | {t("AppLayout.external_link")} 22 | 23 | } 24 | > 25 | {children} 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /ui/components/ThemeInitializer.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useColorTheme } from "@/app/[locale]/useColorTheme"; 4 | 5 | export function ThemeInitializer() { 6 | // This hook applies the correct theme on page load 7 | useColorTheme(); 8 | return null; // It doesn’t render anything visible 9 | } 10 | -------------------------------------------------------------------------------- /ui/components/TopicsTable/components/EmptyStateNoTopics.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/nextjs"; 2 | 3 | import { EmptyStateNoTopics as Comp } from "./EmptyStateNoTopics"; 4 | 5 | export default { 6 | component: Comp, 7 | } as Meta; 8 | type Story = StoryObj; 9 | 10 | export const Default: Story = {}; 11 | 12 | export const CanCreate: Story = { 13 | args: { 14 | canCreate: true, 15 | createHref: "#/sample", 16 | showLearningLinks: true, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /ui/environment.d.ts: -------------------------------------------------------------------------------- 1 | namespace NodeJS { 2 | interface ProcessEnv { 3 | NEXTAUTH_URL: string; 4 | NEXTAUTH_SECRET: string; 5 | BACKEND_URL: string; 6 | LOG_LEVEL?: "fatal" | "error" | "warn" | "info" | "debug" | "trace"; 7 | CONSOLE_MODE?: "read-only" | "read-write"; 8 | CONSOLE_CONFIG_PATH: string; 9 | CONSOLE_SHOW_LEARNING?: "true" | "false"; 10 | CONSOLE_TECH_PREVIEW?: "true" | "false"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ui/i18n.tsx: -------------------------------------------------------------------------------- 1 | import { getRequestConfig } from "next-intl/server"; 2 | import { routing } from "./i18n/routing"; 3 | 4 | export default getRequestConfig(async ({ requestLocale }) => { 5 | let locale = await requestLocale; 6 | 7 | // Ensure that the incoming locale is valid 8 | if (!locale || !routing.locales.includes(locale as any)) { 9 | locale = routing.defaultLocale; 10 | } 11 | 12 | return { 13 | messages: (await import(`./messages/${locale}.json`)).default, 14 | locale, 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /ui/i18n/request.ts: -------------------------------------------------------------------------------- 1 | import { getRequestConfig } from "next-intl/server"; 2 | import { routing } from "@/i18n/routing"; 3 | 4 | export default getRequestConfig(async ({ requestLocale }) => { 5 | let locale = await requestLocale; 6 | 7 | // Ensure that the incoming locale is valid 8 | if (!locale || !routing.locales.includes(locale as any)) { 9 | locale = routing.defaultLocale; 10 | } 11 | 12 | return { 13 | messages: (await import(`../../messages/${locale}.json`)).default, 14 | locale, 15 | }; 16 | }); 17 | -------------------------------------------------------------------------------- /ui/i18n/routing.ts: -------------------------------------------------------------------------------- 1 | import { defineRouting } from "next-intl/routing"; 2 | import { createNavigation } from "next-intl/navigation"; 3 | 4 | export const locales = ["en"] as const; 5 | 6 | export const routing = defineRouting({ 7 | // A list of all locales that are supported 8 | locales, 9 | 10 | // Used when no locale matches 11 | defaultLocale: "en", 12 | localePrefix: "never", 13 | }); 14 | 15 | // Lightweight wrappers around Next.js' navigation APIs 16 | // that will consider the routing configuration 17 | let nav = createNavigation(routing); 18 | 19 | export const { Link, redirect, usePathname, useRouter } = { 20 | Link: nav.Link, 21 | redirect: (path: string) => { 22 | nav.redirect({ href: path, locale: "en" }); 23 | }, 24 | usePathname: nav.usePathname, 25 | useRouter: nav.useRouter, 26 | }; 27 | -------------------------------------------------------------------------------- /ui/libs/patternfly/quickstarts.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | export * from "@patternfly/quickstarts"; 3 | -------------------------------------------------------------------------------- /ui/libs/patternfly/react-charts.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | export * from "@patternfly/react-charts/victory"; 3 | -------------------------------------------------------------------------------- /ui/libs/patternfly/react-core.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | export * from "@patternfly/react-core"; 3 | -------------------------------------------------------------------------------- /ui/libs/patternfly/react-icons.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | export * from "@patternfly/react-icons"; 3 | -------------------------------------------------------------------------------- /ui/libs/patternfly/react-table.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | export * from "@patternfly/react-table"; 3 | -------------------------------------------------------------------------------- /ui/libs/patternfly/react-tokens.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | export * from "@patternfly/react-tokens"; 3 | -------------------------------------------------------------------------------- /ui/public/home-hero-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamshub/console/e26188458c3e845b6daf0f984541644f17a09aaf/ui/public/home-hero-bg.png -------------------------------------------------------------------------------- /ui/public/pictogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamshub/console/e26188458c3e845b6daf0f984541644f17a09aaf/ui/public/pictogram.png -------------------------------------------------------------------------------- /ui/tests/playwright/.auth/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamshub/console/e26188458c3e845b6daf0f984541644f17a09aaf/ui/tests/playwright/.auth/.gitkeep -------------------------------------------------------------------------------- /ui/tests/playwright/auth.setup.ts: -------------------------------------------------------------------------------- 1 | import { authFile } from "./playwright.config"; 2 | import { expect, test as setup } from "@playwright/test"; 3 | 4 | setup("authenticate", async ({ page }) => { 5 | await page.goto("./"); 6 | await page.waitForURL("**/login", { waitUntil: "commit" }); 7 | await page.getByRole("button", { name: 'Click to login anonymously' }).click(); 8 | await page.waitForURL("**/overview", { waitUntil: "commit" }); 9 | await expect(page.getByRole("heading", { name: "Cluster overview" }),).toBeVisible(); 10 | const newPage = page.mainFrame(); 11 | expect(await newPage.innerText("body")).toContain("Cluster overview"); 12 | 13 | process.env.TEST_BASE_URL = page.url(); 14 | 15 | console.log(process.env.TEST_BASE_URL); 16 | 17 | await page.context().storageState({ path: authFile }); 18 | }); 19 | -------------------------------------------------------------------------------- /ui/tests/playwright/authenticated-test.ts: -------------------------------------------------------------------------------- 1 | import { test as base } from "@playwright/test"; 2 | import { AuthenticatedPage } from "./authenticated-page"; 3 | 4 | // Declare the types of your fixtures. 5 | type MyFixtures = { 6 | authenticatedPage: AuthenticatedPage; 7 | }; 8 | 9 | // Extend base test by providing "todoPage" and "settingsPage". 10 | // This new "test" can be used in multiple test files, and each of them will get the fixtures. 11 | export const test = base.extend({ 12 | authenticatedPage: async ({ page }, use) => { 13 | // Set up the fixture. 14 | const todoPage = new AuthenticatedPage(page); 15 | await use(todoPage); 16 | }, 17 | }); 18 | export { expect } from "@playwright/test"; 19 | -------------------------------------------------------------------------------- /ui/tests/playwright/utils.ts: -------------------------------------------------------------------------------- 1 | export const URL = process.env.APP_URL; -------------------------------------------------------------------------------- /ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules", "**/*.stories.tsx", "**/*.test.tsx", "**/storybookHelpers.tsx", "**/tests/**/*"] 28 | } 29 | -------------------------------------------------------------------------------- /ui/types/next-auth.d.ts: -------------------------------------------------------------------------------- 1 | import NextAuth, { DefaultSession } from "next-auth"; 2 | 3 | declare module "next-auth" { 4 | /** 5 | * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context 6 | */ 7 | interface Session { 8 | error?: "RefreshAccessTokenError"; 9 | authorization?: string; 10 | 11 | /** Added for OIDC logout support */ 12 | idToken?: string; 13 | 14 | user?: { 15 | name: string; 16 | email?: string | null; 17 | picture?: string | null; 18 | } & DefaultSession["user"]; 19 | } 20 | 21 | interface User { 22 | authorization?: string; 23 | } 24 | } 25 | 26 | declare module "next-auth/jwt" { 27 | /** Returned by the `jwt` callback and `getToken`, when using JWT sessions */ 28 | interface JWT { 29 | authorization?: string; 30 | 31 | /** Added to persist id_token from provider */ 32 | id_token?: string; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ui/utils/dateTime.ts: -------------------------------------------------------------------------------- 1 | import { formatInTimeZone } from "date-fns-tz"; 2 | 3 | export function formatDateTime({ 4 | value, 5 | timeZone, 6 | format = "yyyy-MM-dd HH:mm:ssXXX", 7 | } : { 8 | value: number | string | Date | undefined, 9 | timeZone?: string, 10 | format?: string, 11 | }) { 12 | if (value === undefined) { 13 | return "-"; 14 | } 15 | 16 | timeZone ??= Intl.DateTimeFormat().resolvedOptions().timeZone; 17 | 18 | return formatInTimeZone(value, timeZone, format); 19 | } 20 | -------------------------------------------------------------------------------- /ui/utils/filterUndefinedFromObj.ts: -------------------------------------------------------------------------------- 1 | export function filterUndefinedFromObj(obj: Record) { 2 | return Object.fromEntries( 3 | Object.entries(obj).filter( 4 | ([_, value]) => value !== undefined && value !== null, 5 | ), 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /ui/utils/loggerClient.ts: -------------------------------------------------------------------------------- 1 | import pino from "pino"; 2 | 3 | export const logger = pino({ 4 | level: "trace", 5 | }); 6 | -------------------------------------------------------------------------------- /ui/utils/privileges.ts: -------------------------------------------------------------------------------- 1 | type HasMeta = { 2 | meta?: { 3 | privileges?: string[], 4 | } 5 | } 6 | 7 | export function hasPrivilege(privilege: string, resource?: HasMeta | null): boolean { 8 | return resource?.meta?.privileges?.includes(privilege) ?? false; 9 | } 10 | -------------------------------------------------------------------------------- /ui/utils/stringToBoolean.ts: -------------------------------------------------------------------------------- 1 | export function stringToBoolean(value: string | undefined) { 2 | if (value === undefined) { 3 | return undefined; 4 | } 5 | try { 6 | const maybeBoolean = JSON.parse(value); 7 | return typeof maybeBoolean === "boolean" ? maybeBoolean : undefined; 8 | } catch {} 9 | return undefined; 10 | } 11 | -------------------------------------------------------------------------------- /ui/utils/stringToInt.ts: -------------------------------------------------------------------------------- 1 | export function stringToInt(value: string | undefined) { 2 | if (value === undefined) { 3 | return undefined; 4 | } 5 | const maybeNumber = parseInt(value, 10); 6 | return Number.isInteger(maybeNumber) ? maybeNumber : undefined; 7 | } 8 | --------------------------------------------------------------------------------