This is an automated email from the ASF dual-hosted git repository. marat pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
commit 413d383a1c82f6f1f4b1122bd425c10dfa398383 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Wed Aug 30 09:51:15 2023 -0400 Split DockerService for #817 --- .../apache/camel/karavan/api/DevModeResource.java | 6 +- .../camel/karavan/api/InfrastructureResource.java | 8 +- .../camel/karavan/docker/DockerEventListener.java | 12 +- .../camel/karavan/docker/DockerForGitea.java | 134 ++++++++++++ .../camel/karavan/docker/DockerForInfinispan.java | 89 ++++++++ .../camel/karavan/docker/DockerForKaravan.java | 112 ++++++++++ .../apache/camel/karavan/docker/DockerService.java | 241 ++------------------- .../apache/camel/karavan/service/EventService.java | 25 ++- .../apache/camel/karavan/service/GitService.java | 4 +- .../camel/karavan/service/KaravanService.java | 6 +- .../camel/karavan/service/ScheduledService.java | 6 +- .../org/apache/camel/karavan/shared/EventType.java | 1 + .../main/webui/src/designer/KaravanDesigner.tsx | 26 +-- .../webui/src/designer/route/DslConnections.tsx | 22 +- .../main/webui/src/designer/route/DslElement.tsx | 19 +- 15 files changed, 437 insertions(+), 274 deletions(-) diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java index 3db4e727..5b10fa9b 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/DevModeResource.java @@ -18,6 +18,7 @@ package org.apache.camel.karavan.api; import io.vertx.core.json.JsonObject; import io.vertx.mutiny.core.eventbus.EventBus; +import org.apache.camel.karavan.docker.DockerForKaravan; import org.apache.camel.karavan.docker.DockerService; import org.apache.camel.karavan.infinispan.InfinispanService; import org.apache.camel.karavan.infinispan.model.CamelStatus; @@ -55,6 +56,9 @@ public class DevModeResource { @Inject DockerService dockerService; + @Inject + DockerForKaravan dockerForKaravan; + @Inject EventBus eventBus; @@ -76,7 +80,7 @@ public class DevModeResource { if (ConfigService.inKubernetes()) { kubernetesService.runDevModeContainer(project, jBangOptions); } else { - dockerService.createDevmodeContainer(project.getProjectId(), jBangOptions); + dockerForKaravan.createDevmodeContainer(project.getProjectId(), jBangOptions); dockerService.runContainer(project.getProjectId()); } return Response.ok(containerName).build(); diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java index b2c1e92b..17f4c526 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/InfrastructureResource.java @@ -20,6 +20,7 @@ import io.smallrye.mutiny.Multi; import io.vertx.core.json.JsonObject; import io.vertx.mutiny.core.eventbus.EventBus; import io.vertx.mutiny.core.eventbus.Message; +import org.apache.camel.karavan.docker.DockerForKaravan; import org.apache.camel.karavan.docker.DockerService; import org.apache.camel.karavan.docker.model.DevService; import org.apache.camel.karavan.infinispan.InfinispanService; @@ -56,6 +57,9 @@ public class InfrastructureResource { @Inject KubernetesService kubernetesService; + @Inject + DockerForKaravan dockerForKaravan; + @Inject DockerService dockerService; @@ -193,11 +197,11 @@ public class InfrastructureResource { String code = projectService.getDevServiceCode(); DevService devService = dockerService.getDevService(code, name); if (devService != null) { - dockerService.createDevserviceContainer(devService); + dockerForKaravan.createDevserviceContainer(devService); dockerService.runContainer(devService.getContainer_name()); } } else if (Objects.equals(type, ContainerStatus.ContainerType.devmode.name())) { - dockerService.createDevmodeContainer(name, ""); + dockerForKaravan.createDevmodeContainer(name, ""); dockerService.runContainer(name); } return Response.ok().build(); diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java index 5c36fb4d..0bf1b6ce 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerEventListener.java @@ -21,8 +21,7 @@ import java.util.*; import java.util.stream.Collectors; import static org.apache.camel.karavan.shared.Constants.*; -import static org.apache.camel.karavan.shared.EventType.DEVMODE_CONTAINER_READY; -import static org.apache.camel.karavan.shared.EventType.INFINISPAN_STARTED; +import static org.apache.camel.karavan.shared.EventType.*; @ApplicationScoped public class DockerEventListener implements ResultCallback<Event> { @@ -61,7 +60,7 @@ public class DockerEventListener implements ResultCallback<Event> { public void onContainerEvent(Event event, Container container) { String status = event.getStatus(); if (status.startsWith("health_status:") && container.getNames()[0].equals("/gitea")) { - dockerService.installGitea(); + onGiteaHealthEvent(container, event); } if (infinispanService.isReady()) { if (status.startsWith("health_status:")) { @@ -105,6 +104,13 @@ public class DockerEventListener implements ResultCallback<Event> { } } + public void onGiteaHealthEvent(Container container, Event event) { + String status = event.getStatus(); + String health = status.replace("health_status: ", ""); + LOGGER.infof("Container %s health status: %s", container.getNames()[0], health); + eventBus.publish(GITEA_CONTAINER_STARTED, health); + } + public void onInfinispanHealthEvent(Container container, Event event) { String status = event.getStatus(); String health = status.replace("health_status: ", ""); diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForGitea.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForGitea.java new file mode 100644 index 00000000..9e5d6c6e --- /dev/null +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForGitea.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.karavan.docker; + +import com.github.dockerjava.api.command.ExecCreateCmdResponse; +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.HealthCheck; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.apache.camel.karavan.infinispan.model.ContainerStatus; +import org.apache.camel.karavan.infinispan.model.GitConfig; +import org.apache.camel.karavan.service.GitService; +import org.apache.camel.karavan.service.GiteaService; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.logging.Logger; + +import java.util.List; +import java.util.Map; + +import static org.apache.camel.karavan.shared.Constants.LABEL_TYPE; + +@ApplicationScoped +public class DockerForGitea { + + private static final Logger LOGGER = Logger.getLogger(DockerForGitea.class.getName()); + + protected static final String GITEA_CONTAINER_NAME = "gitea"; + + private static final List<String> giteaHealthCheckCMD = List.of("CMD", "curl", "-fss", "127.0.0.1:3000/api/healthz"); + + @ConfigProperty(name = "karavan.gitea.image") + String giteaImage; + + @Inject + DockerService dockerService; + + @Inject + GiteaService giteaService; + + @Inject + GitService gitService; + + public void startGitea() { + try { + LOGGER.info("Gitea container is starting..."); + + HealthCheck healthCheck = new HealthCheck().withTest(giteaHealthCheckCMD) + .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30); + + dockerService.createContainer(GITEA_CONTAINER_NAME, giteaImage, + List.of(), "3000:3000", false, List.of("3000"), healthCheck, + Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name())); + + dockerService.runContainer(GITEA_CONTAINER_NAME); + + LOGGER.info("Gitea container is started"); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } + + protected void createGiteaInstance() { + try { + LOGGER.info("Creating Gitea Instance"); + Container gitea = dockerService.getContainerByName(GITEA_CONTAINER_NAME); + ExecCreateCmdResponse instance = dockerService.execCreate(gitea.getId(), + "curl", "-X", "POST", "localhost:3000", "-d", + "db_type=sqlite3&db_host=localhost%3A3306&db_user=root&db_passwd=&db_name=gitea" + + "&ssl_mode=disable&db_schema=&db_path=%2Fvar%2Flib%2Fgitea%2Fdata%2Fgitea.db&app_name=Gitea%3A+Git+with+a+cup+of+tea" + + "&repo_root_path=%2Fvar%2Flib%2Fgitea%2Fgit%2Frepositories&lfs_root_path=%2Fvar%2Flib%2Fgitea%2Fgit%2Flfs&run_user=git" + + "&domain=localhost&ssh_port=2222&http_port=3000&app_url=http%3A%2F%2Flocalhost%3A3000%2F&log_root_path=%2Fvar%2Flib%2Fgitea%2Fdata%2Flog" + + "&smtp_addr=&smtp_port=&smtp_from=&smtp_user=&smtp_passwd=&enable_federated_avatar=on&enable_open_id_sign_in=on" + + "&enable_open_id_sign_up=on&default_allow_create_organization=on&default_enable_timetracking=on" + + "&no_reply_address=noreply.localhost&password_algorithm=pbkdf2&admin_name=&admin_email=&admin_passwd=&admin_confirm_passwd=", + "-H", "'Content-Type: application/x-www-form-urlencoded'"); + + dockerService.execStart(instance.getId()); + LOGGER.info("Created Gitea Instance"); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } + + protected void createGiteaUser() { + try { + LOGGER.info("Creating Gitea User"); + GitConfig config = gitService.getGitConfig(); + Container gitea = dockerService.getContainerByName(GITEA_CONTAINER_NAME); + ExecCreateCmdResponse user = dockerService.execCreate(gitea.getId(), + "/app/gitea/gitea", "admin", "user", "create", + "--config", "/etc/gitea/app.ini", + "--username", config.getUsername(), + "--password", config.getPassword(), + "--email", config.getUsername() + "@karavan.space", + "--admin"); + dockerService.execStart(user.getId(), new LoggerCallback()); + LOGGER.info("Created Gitea User"); + giteaService.createRepository(); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } + + protected void checkGiteaInstance() { + try { + Container gitea = dockerService.getContainerByName(GITEA_CONTAINER_NAME); + ExecCreateCmdResponse user = dockerService.execCreate(gitea.getId(), + "curl", "-Is", "localhost:3000/user/login"); + + dockerService.execStart(user.getId(), new GiteaCheckCallback(o -> createGiteaUser(), o -> checkGiteaInstance())); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } + + public void installGitea() { + createGiteaInstance(); + checkGiteaInstance(); + } +} diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForInfinispan.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForInfinispan.java new file mode 100644 index 00000000..802d8de2 --- /dev/null +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForInfinispan.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.karavan.docker; + +import com.github.dockerjava.api.command.HealthState; +import com.github.dockerjava.api.model.HealthCheck; +import io.vertx.core.eventbus.EventBus; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.apache.camel.karavan.infinispan.model.ContainerStatus; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.logging.Logger; + +import java.util.List; +import java.util.Map; + +import static org.apache.camel.karavan.shared.Constants.LABEL_TYPE; +import static org.apache.camel.karavan.shared.EventType.INFINISPAN_STARTED; + +@ApplicationScoped +public class DockerForInfinispan { + + private static final Logger LOGGER = Logger.getLogger(DockerForInfinispan.class.getName()); + + protected static final String INFINISPAN_CONTAINER_NAME = "infinispan"; + + private static final List<String> infinispanHealthCheckCMD = List.of("CMD", "curl", "-f", "http://localhost:11222/rest/v2/cache-managers/default/health/status"); + + @ConfigProperty(name = "karavan.infinispan.image") + String infinispanImage; + @ConfigProperty(name = "karavan.infinispan.port") + String infinispanPort; + @ConfigProperty(name = "karavan.infinispan.username") + String infinispanUsername; + @ConfigProperty(name = "karavan.infinispan.password") + String infinispanPassword; + + @Inject + DockerService dockerService; + + @Inject + EventBus eventBus; + + public void startInfinispan() { + try { + LOGGER.info("Infinispan is starting..."); + + HealthCheck healthCheck = new HealthCheck().withTest(infinispanHealthCheckCMD) + .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30); + + List<String> exposedPorts = List.of(infinispanPort.split(":")[0]); + + dockerService.createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage, + List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword), + infinispanPort, false, exposedPorts, healthCheck, + Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name())); + + dockerService.runContainer(INFINISPAN_CONTAINER_NAME); + LOGGER.info("Infinispan is started"); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } + + public void checkInfinispanHealth() { + dockerService.listContainers(false).stream() + .filter(c -> c.getState().equals("running")) + .forEach(c -> { + HealthState hs = dockerService.inspectContainer(c.getId()).getState().getHealth(); + if (c.getNames()[0].equals("/" + INFINISPAN_CONTAINER_NAME)) { + eventBus.publish(INFINISPAN_STARTED, hs.getStatus()); + } + }); + } +} diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForKaravan.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForKaravan.java new file mode 100644 index 00000000..ee0a6864 --- /dev/null +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerForKaravan.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.karavan.docker; + +import com.github.dockerjava.api.model.HealthCheck; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.apache.camel.karavan.docker.model.DevService; +import org.apache.camel.karavan.infinispan.model.ContainerStatus; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.logging.Logger; + +import java.util.List; +import java.util.Map; + +import static org.apache.camel.karavan.shared.Constants.*; + +@ApplicationScoped +public class DockerForKaravan { + + private static final Logger LOGGER = Logger.getLogger(DockerForKaravan.class.getName()); + + protected static final String KARAVAN_CONTAINER_NAME = "karavan-headless"; + + @ConfigProperty(name = "karavan.devmode.image") + String devmodeImage; + + @ConfigProperty(name = "karavan.headless.image") + String headlessImage; + + @ConfigProperty(name = "karavan.infinispan.username") + String infinispanUsername; + @ConfigProperty(name = "karavan.infinispan.password") + String infinispanPassword; + + @Inject + DockerService dockerService; + + public void createDevmodeContainer(String projectId, String jBangOptions) throws InterruptedException { + LOGGER.infof("DevMode starting for %s with JBANG_OPTIONS=%s", projectId, jBangOptions); + + HealthCheck healthCheck = new HealthCheck().withTest(List.of("CMD", "curl", "-f", "http://localhost:8080/q/dev/health")) + .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30); + + List<String> env = jBangOptions != null && !jBangOptions.trim().isEmpty() + ? List.of(ENV_VAR_JBANG_OPTIONS + "=" + jBangOptions) + : List.of(); + + dockerService.createContainer(projectId, devmodeImage, + env, null, false, List.of(), healthCheck, + Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devmode.name(), LABEL_PROJECT_ID, projectId)); + + LOGGER.infof("DevMode started for %s", projectId); + } + + public void createDevserviceContainer(DevService devService) throws InterruptedException { + LOGGER.infof("DevService starting for ", devService.getContainer_name()); + + HealthCheck healthCheck = dockerService.getHealthCheck(devService.getHealthcheck()); + List<String> env = devService.getEnvironment() != null ? devService.getEnvironmentList() : List.of(); + String ports = String.join(",", devService.getPorts()); + + dockerService.createContainer(devService.getContainer_name(), devService.getImage(), + env, ports, false, devService.getExpose(), healthCheck, + Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devservice.name())); + + LOGGER.infof("DevService started for %s", devService.getContainer_name()); + } + + public void startKaravanHeadlessContainer() { + try { + LOGGER.info("Karavan headless is starting..."); + + dockerService.createContainer(KARAVAN_CONTAINER_NAME, headlessImage, + List.of( + "INFINISPAN_HOSTS=infinispan:11222", + "INFINISPAN_USERNAME=" + infinispanUsername, + "INFINISPAN_PASSWORD=" + infinispanPassword + ), + null, false, List.of(), new HealthCheck(), + Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name())); + + dockerService.runContainer(KARAVAN_CONTAINER_NAME); + LOGGER.info("Karavan headless is started"); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } + + public void deleteKaravanHeadlessContainer() { + try { + dockerService.stopContainer(KARAVAN_CONTAINER_NAME); + dockerService.deleteContainer(KARAVAN_CONTAINER_NAME); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } +} diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java index 4bf53751..5f4a3b10 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/docker/DockerService.java @@ -27,69 +27,30 @@ import com.github.dockerjava.core.InvocationBuilder; import com.github.dockerjava.transport.DockerHttpClient; import com.github.dockerjava.zerodep.ZerodepDockerHttpClient; import io.vertx.core.eventbus.EventBus; -import org.apache.camel.karavan.docker.model.DevService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.apache.camel.karavan.infinispan.model.ContainerStatus; -import org.apache.camel.karavan.infinispan.model.GitConfig; -import org.apache.camel.karavan.service.GitService; -import org.apache.camel.karavan.service.GiteaService; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - import java.io.IOException; import java.time.Instant; import java.util.*; import java.util.stream.Collectors; -import static org.apache.camel.karavan.shared.Constants.*; -import static org.apache.camel.karavan.shared.EventType.INFINISPAN_STARTED; - @ApplicationScoped public class DockerService extends DockerServiceUtils { private static final Logger LOGGER = Logger.getLogger(DockerService.class.getName()); - protected static final String INFINISPAN_CONTAINER_NAME = "infinispan"; - protected static final String GITEA_CONTAINER_NAME = "gitea"; - protected static final String KARAVAN_CONTAINER_NAME = "karavan-headless"; - protected static final String NETWORK_NAME = "karavan"; - private static final List<String> infinispanHealthCheckCMD = List.of("CMD", "curl", "-f", "http://localhost:11222/rest/v2/cache-managers/default/health/status"); - private static final List<String> giteaHealthCheckCMD = List.of("CMD", "curl", "-fss", "127.0.0.1:3000/api/healthz"); - @ConfigProperty(name = "karavan.environment") String environment; - @ConfigProperty(name = "karavan.devmode.image") - String devmodeImage; - - @ConfigProperty(name = "karavan.headless.image") - String headlessImage; - - @ConfigProperty(name = "karavan.infinispan.image") - String infinispanImage; - @ConfigProperty(name = "karavan.infinispan.port") - String infinispanPort; - @ConfigProperty(name = "karavan.infinispan.username") - String infinispanUsername; - @ConfigProperty(name = "karavan.infinispan.password") - String infinispanPassword; - - @ConfigProperty(name = "karavan.gitea.image") - String giteaImage; - @Inject DockerEventListener dockerEventListener; - @Inject - GitService gitService; - - @Inject - GiteaService giteaService; - @Inject EventBus eventBus; @@ -106,87 +67,6 @@ public class DockerService extends DockerServiceUtils { } } - public void createDevmodeContainer(String projectId, String jBangOptions) throws InterruptedException { - LOGGER.infof("DevMode starting for %s with JBANG_OPTIONS=%s", projectId, jBangOptions); - - HealthCheck healthCheck = new HealthCheck().withTest(List.of("CMD", "curl", "-f", "http://localhost:8080/q/dev/health")) - .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30); - - List<String> env = jBangOptions != null && !jBangOptions.trim().isEmpty() - ? List.of(ENV_VAR_JBANG_OPTIONS + "=" + jBangOptions) - : List.of(); - - createContainer(projectId, devmodeImage, - env, null, false, List.of(), healthCheck, - Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devmode.name(), LABEL_PROJECT_ID, projectId)); - - LOGGER.infof("DevMode started for %s", projectId); - } - - public void createDevserviceContainer(DevService devService) throws InterruptedException { - LOGGER.infof("DevService starting for ", devService.getContainer_name()); - - HealthCheck healthCheck = getHealthCheck(devService.getHealthcheck()); - List<String> env = devService.getEnvironment() != null ? devService.getEnvironmentList() : List.of(); - String ports = String.join(",", devService.getPorts()); - - createContainer(devService.getContainer_name(), devService.getImage(), - env, ports, false, devService.getExpose(), healthCheck, - Map.of(LABEL_TYPE, ContainerStatus.ContainerType.devservice.name())); - - LOGGER.infof("DevService started for %s", devService.getContainer_name()); - } - - public void startInfinispan() { - try { - LOGGER.info("Infinispan is starting..."); - - HealthCheck healthCheck = new HealthCheck().withTest(infinispanHealthCheckCMD) - .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30); - - List<String> exposedPorts = List.of(infinispanPort.split(":")[0]); - - createContainer(INFINISPAN_CONTAINER_NAME, infinispanImage, - List.of("USER=" + infinispanUsername, "PASS=" + infinispanPassword), - infinispanPort, false, exposedPorts, healthCheck, - Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name())); - - runContainer(INFINISPAN_CONTAINER_NAME); - LOGGER.info("Infinispan is started"); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - - public void startKaravanHeadlessContainer() { - try { - LOGGER.info("Karavan headless is starting..."); - - createContainer(KARAVAN_CONTAINER_NAME, headlessImage, - List.of( - "INFINISPAN_HOSTS=infinispan:11222", - "INFINISPAN_USERNAME=" + infinispanUsername, - "INFINISPAN_PASSWORD=" + infinispanPassword - ), - null, false, List.of(), new HealthCheck(), - Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name())); - - runContainer(KARAVAN_CONTAINER_NAME); - LOGGER.info("Karavan headless is started"); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - - public void deleteKaravanHeadlessContainer() { - try { - stopContainer(KARAVAN_CONTAINER_NAME); - deleteContainer(KARAVAN_CONTAINER_NAME); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - public List<ContainerStatus> collectContainersStatuses() { List<ContainerStatus> result = new ArrayList<>(); getDockerClient().listContainersCmd().withShowAll(true).exec().forEach(container -> { @@ -214,7 +94,6 @@ public class DockerService extends DockerServiceUtils { public void stopListeners() throws IOException { dockerEventListener.close(); } - public void createNetwork() { if (!getDockerClient().listNetworksCmd().exec().stream() .filter(n -> n.getName().equals(NETWORK_NAME)) @@ -230,17 +109,6 @@ public class DockerService extends DockerServiceUtils { } } - public void checkInfinispanHealth() { - getDockerClient().listContainersCmd().exec().stream() - .filter(c -> c.getState().equals("running")) - .forEach(c -> { - HealthState hs = getDockerClient().inspectContainerCmd(c.getId()).exec().getState().getHealth(); - if (c.getNames()[0].equals("/" + INFINISPAN_CONTAINER_NAME)) { - eventBus.publish(INFINISPAN_STARTED, hs.getStatus()); - } - }); - } - public Container getContainer(String id) { List<Container> containers = getDockerClient().listContainersCmd().withShowAll(true).withIdFilter(List.of(id)).exec(); return containers.get(0); @@ -302,6 +170,28 @@ public class DockerService extends DockerServiceUtils { } } } + public List<Container> listContainers(Boolean showAll) { + return getDockerClient().listContainersCmd().withShowAll(showAll).exec(); + } + + public InspectContainerResponse inspectContainer(String id) { + return getDockerClient().inspectContainerCmd(id).exec(); + } + + + public ExecCreateCmdResponse execCreate(String id, String... cmd) { + return getDockerClient().execCreateCmd(id) + .withAttachStdout(true).withAttachStderr(true) + .withCmd(cmd) + .exec(); + } + + public void execStart(String id) throws InterruptedException { + getDockerClient().execStartCmd(id).start().awaitCompletion(); + } + public void execStart(String id, ResultCallback.Adapter<Frame> callBack) throws InterruptedException { + getDockerClient().execStartCmd(id).exec(callBack).awaitCompletion(); + } public void logContainer(String containerName, LogCallback callback) { try { @@ -381,87 +271,4 @@ public class DockerService extends DockerServiceUtils { } return dockerClient; } - - public void startGitea() { - try { - LOGGER.info("Gitea container is starting..."); - - HealthCheck healthCheck = new HealthCheck().withTest(giteaHealthCheckCMD) - .withInterval(10000000000L).withTimeout(10000000000L).withStartPeriod(10000000000L).withRetries(30); - - createContainer(GITEA_CONTAINER_NAME, giteaImage, - List.of(), "3000:3000", false, List.of("3000"), healthCheck, - Map.of(LABEL_TYPE, ContainerStatus.ContainerType.internal.name())); - - runContainer(GITEA_CONTAINER_NAME); - - LOGGER.info("Gitea container is started"); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - - protected void createGiteaInstance() { - try { - LOGGER.info("Creating Gitea Instance"); - Container gitea = getContainerByName(GITEA_CONTAINER_NAME); - ExecCreateCmdResponse instance = dockerClient.execCreateCmd(gitea.getId()) - .withAttachStdout(true).withAttachStderr(true) - .withCmd("curl", "-X", "POST", "localhost:3000", "-d", - "db_type=sqlite3&db_host=localhost%3A3306&db_user=root&db_passwd=&db_name=gitea" + - "&ssl_mode=disable&db_schema=&db_path=%2Fvar%2Flib%2Fgitea%2Fdata%2Fgitea.db&app_name=Gitea%3A+Git+with+a+cup+of+tea" + - "&repo_root_path=%2Fvar%2Flib%2Fgitea%2Fgit%2Frepositories&lfs_root_path=%2Fvar%2Flib%2Fgitea%2Fgit%2Flfs&run_user=git" + - "&domain=localhost&ssh_port=2222&http_port=3000&app_url=http%3A%2F%2Flocalhost%3A3000%2F&log_root_path=%2Fvar%2Flib%2Fgitea%2Fdata%2Flog" + - "&smtp_addr=&smtp_port=&smtp_from=&smtp_user=&smtp_passwd=&enable_federated_avatar=on&enable_open_id_sign_in=on" + - "&enable_open_id_sign_up=on&default_allow_create_organization=on&default_enable_timetracking=on" + - "&no_reply_address=noreply.localhost&password_algorithm=pbkdf2&admin_name=&admin_email=&admin_passwd=&admin_confirm_passwd=", - "-H", "'Content-Type: application/x-www-form-urlencoded'") - .exec(); - - dockerClient.execStartCmd(instance.getId()).start().awaitCompletion(); - LOGGER.info("Created Gitea Instance"); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - - protected void createGiteaUser() { - try { - LOGGER.info("Creating Gitea User"); - GitConfig config = gitService.getGitConfig(); - Container gitea = getContainerByName(GITEA_CONTAINER_NAME); - ExecCreateCmdResponse user = dockerClient.execCreateCmd(gitea.getId()) - .withAttachStdout(true).withAttachStderr(true) - .withCmd("/app/gitea/gitea", "admin", "user", "create", - "--config", "/etc/gitea/app.ini", - "--username", config.getUsername(), - "--password", config.getPassword(), - "--email", config.getUsername() + "@karavan.space", - "--admin") - .exec(); - dockerClient.execStartCmd(user.getId()).exec(new LoggerCallback()).awaitCompletion(); - LOGGER.info("Created Gitea User"); - giteaService.createRepository(); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - - protected void checkGiteaInstance() { - try { - Container gitea = getContainerByName(GITEA_CONTAINER_NAME); - ExecCreateCmdResponse user = dockerClient.execCreateCmd(gitea.getId()) - .withAttachStdout(true).withAttachStderr(true) - .withCmd("curl", "-Is", "localhost:3000/user/login").exec(); - - dockerClient.execStartCmd(user.getId()).exec(new GiteaCheckCallback(o -> createGiteaUser(), o -> checkGiteaInstance())); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - - public void installGitea() { - createGiteaInstance(); - checkGiteaInstance(); - } } diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java index 1eef3b1e..e4a8bc1a 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/EventService.java @@ -4,6 +4,9 @@ import io.quarkus.vertx.ConsumeEvent; import io.vertx.core.eventbus.DeliveryOptions; import io.vertx.core.eventbus.EventBus; import io.vertx.core.json.JsonObject; +import org.apache.camel.karavan.docker.DockerForGitea; +import org.apache.camel.karavan.docker.DockerForInfinispan; +import org.apache.camel.karavan.docker.DockerForKaravan; import org.apache.camel.karavan.docker.DockerService; import org.apache.camel.karavan.infinispan.InfinispanService; import org.apache.camel.karavan.infinispan.model.CamelStatus; @@ -41,6 +44,15 @@ public class EventService { @Inject DockerService dockerService; + @Inject + DockerForInfinispan dockerForInfinispan; + + @Inject + DockerForKaravan dockerForKaravan; + + @Inject + DockerForGitea dockerForGitea; + @Inject CamelService camelService; @@ -52,8 +64,8 @@ public class EventService { @ConsumeEvent(value = START_INFINISPAN_IN_DOCKER, blocking = true, ordered = true) void startInfinispan1(String data) { - dockerService.startInfinispan(); - dockerService.checkInfinispanHealth(); + dockerForInfinispan.startInfinispan(); + dockerForInfinispan.checkInfinispanHealth(); } @ConsumeEvent(value = INFINISPAN_STARTED, blocking = true, ordered = true) @@ -62,7 +74,7 @@ public class EventService { infinispanService.start(false); infinispanService.clearAllStatuses(); if (!ConfigService.inKubernetes()) { - dockerService.startKaravanHeadlessContainer(); + dockerForKaravan.startKaravanHeadlessContainer(); dockerService.collectContainersStatuses(); } eventBus.publish(EventType.IMPORT_PROJECTS, ""); @@ -71,10 +83,15 @@ public class EventService { } @ConsumeEvent(value = GITEA_STARTED, blocking = true, ordered = true) - void startServices2(String giteaHealth) { + void startInfinispanAfterGitea(String giteaHealth) { eventBus.publish(EventType.START_INFINISPAN_IN_DOCKER, null); } + @ConsumeEvent(value = GITEA_CONTAINER_STARTED, blocking = true, ordered = true) + void installGiteaInGiteaContainer(String giteaHealth) { + dockerForGitea.installGitea(); + } + void startServices(String infinispanHealth) { eventBus.publish(EventType.IMPORT_PROJECTS, ""); eventBus.publish(EventType.START_INFRASTRUCTURE_LISTENERS, ""); diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java index 16feebf9..56b3e124 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/GitService.java @@ -166,7 +166,7 @@ public class GitService { public List<GitRepo> readProjectsToImport() { Git importGit = getGitForImport(); if (importGit != null) { - return readProjectsFromRepository(importGit, null); + return readProjectsFromRepository(importGit, new String[0]); } return new ArrayList<>(0); } @@ -254,7 +254,7 @@ public class GitService { if (folderName.startsWith(".")) { // skip hidden } else if (Files.isDirectory(Paths.get(path))) { - if (filter == null || Arrays.stream(filter).filter(f -> f.equals(folderName)).findFirst().isPresent()) { + if (filter == null || filter.length == 0 || Arrays.stream(filter).filter(f -> f.equals(folderName)).findFirst().isPresent()) { LOGGER.info("Importing project from folder " + folderName); files.add(folderName); } diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java index bdd5b900..1af18359 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/KaravanService.java @@ -20,6 +20,7 @@ import io.quarkus.runtime.Quarkus; import io.quarkus.runtime.ShutdownEvent; import io.quarkus.runtime.StartupEvent; import io.vertx.core.eventbus.EventBus; +import org.apache.camel.karavan.docker.DockerForGitea; import org.apache.camel.karavan.docker.DockerService; import org.apache.camel.karavan.kubernetes.KubernetesService; import org.apache.camel.karavan.shared.ConfigService; @@ -47,6 +48,9 @@ public class KaravanService { @Inject DockerService dockerService; + @Inject + DockerForGitea dockerForGitea; + @Inject GitService gitService; @@ -66,7 +70,7 @@ public class KaravanService { dockerService.createNetwork(); dockerService.startListeners(); if (giteaInstall) { - dockerService.startGitea(); + dockerForGitea.startGitea(); } else { eventBus.publish(EventType.START_INFINISPAN_IN_DOCKER, null); } diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java index 10108b2d..392d6790 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ScheduledService.java @@ -19,6 +19,7 @@ package org.apache.camel.karavan.service; import io.quarkus.scheduler.Scheduled; import io.vertx.core.eventbus.EventBus; import io.vertx.core.json.JsonObject; +import org.apache.camel.karavan.docker.DockerForInfinispan; import org.apache.camel.karavan.docker.DockerService; import org.apache.camel.karavan.infinispan.InfinispanService; import org.apache.camel.karavan.infinispan.model.ContainerStatus; @@ -43,6 +44,9 @@ public class ScheduledService { @Inject DockerService dockerService; + @Inject + DockerForInfinispan dockerForInfinispan; + @Inject ProjectService projectService; @@ -79,7 +83,7 @@ public class ScheduledService { @Scheduled(every = "{karavan.container.infinispan.interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP) void checkInfinispanHealth() { if (!infinispanService.isReady()) { - dockerService.checkInfinispanHealth(); + dockerForInfinispan.checkInfinispanHealth(); } } diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java index edcaa284..89664a03 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/EventType.java @@ -26,6 +26,7 @@ public class EventType { public static final String START_INFINISPAN_IN_DOCKER = "START_INFINISPAN_IN_DOCKER"; public static final String INFINISPAN_STARTED = "INFINISPAN_STARTED"; + public static final String GITEA_CONTAINER_STARTED = "GITEA_CONTAINER_STARTED"; public static final String GITEA_STARTED = "GITEA_STARTED"; public static final String CONTAINER_STATUS = "CONTAINER_STATUS"; diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx index ed6ad193..badf8ba8 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/KaravanDesigner.tsx @@ -133,19 +133,19 @@ export function KaravanDesigner (props: Props) { <Tab eventKey='rest' title={getTab("REST", "REST services", "rest")}></Tab> <Tab eventKey='beans' title={getTab("Beans", "Beans Configuration", "beans")}></Tab> </Tabs> - {tab === 'routes' && <Tooltip content={"Hide Log elements"}> - <Switch - isReversed - isChecked={hideLogDSL} - onChange={(_, checked) => { - setHideLogDSL(checked) - }} - aria-label={"Hide Log"} - id="hideLogDSL" - name="hideLogDSL" - className={"hide-log"} - /> - </Tooltip>} + {/*{tab === 'routes' && <Tooltip content={"Hide Log elements"}>*/} + {/* <Switch*/} + {/* isReversed*/} + {/* isChecked={hideLogDSL}*/} + {/* onChange={(_, checked) => {*/} + {/* setHideLogDSL(checked)*/} + {/* }}*/} + {/* aria-label={"Hide Log"}*/} + {/* id="hideLogDSL"*/} + {/* name="hideLogDSL"*/} + {/* className={"hide-log"}*/} + {/* />*/} + {/*</Tooltip>}*/} </div> {tab === 'routes' && <RouteDesigner/>} {tab === 'rest' && <RestDesigner/>} diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx index 4ba95f56..c9d85ccb 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslConnections.tsx @@ -363,17 +363,12 @@ export function DslConnections() { ) } - function isElementHidden(element: CamelElement): boolean { - return element.dslName === 'LogDefinition' && hideLogDSL; - } - function getCircle(pos: DslPosition) { - const isHidden = isElementHidden(pos.step); const cx = pos.headerRect.x + pos.headerRect.width / 2 - left; const cy = pos.headerRect.y + pos.headerRect.height / 2 - top; - const r = isHidden ? 1 : pos.headerRect.height / 2; + const r = pos.headerRect.height / 2; return ( - <circle cx={cx} cy={cy} r={r} stroke="red" strokeWidth="3" fill="transparent" key={pos.step.uuid + "-circle"}/> + <circle cx={cx} cy={cy} r={r} stroke="transparent" strokeWidth="3" fill="transparent" key={pos.step.uuid + "-circle"}/> ) } @@ -390,7 +385,6 @@ export function DslConnections() { } function getArrow(pos: DslPosition) { - const isHidden = isElementHidden(pos.step); const endX = pos.headerRect.x + pos.headerRect.width / 2 - left; const endY = pos.headerRect.y - 9 - top; if (pos.parent) { @@ -401,14 +395,12 @@ export function DslConnections() { if ((!pos.inSteps || (pos.inSteps && pos.position === 0)) && parent.step.dslName !== 'MulticastDefinition') { return ( <path name={pos.step.dslName} d={`M ${startX},${startY} C ${startX},${endY} ${endX},${startY} ${endX},${endY}`} - className="path" - key={pos.step.uuid} markerEnd={isHidden ? "none" : "url(#arrowhead)"}/> + className="path" key={pos.step.uuid} markerEnd="url(#arrowhead)"/> ) } else if (parent.step.dslName === 'MulticastDefinition' && pos.inSteps) { return ( <path d={`M ${startX},${startY} C ${startX},${endY} ${endX},${startY} ${endX},${endY}`} - className="path" - key={pos.step.uuid} markerEnd={isHidden ? "none" : "url(#arrowhead)"}/> + className="path" key={pos.step.uuid} markerEnd="url(#arrowhead)"/> ) } else if (pos.inSteps && pos.position > 0 && !hasSteps(pos.step)) { const prev = getPreviousStep(pos); @@ -417,8 +409,7 @@ export function DslConnections() { const prevX = r.x + r.width / 2 - left; const prevY = r.y + r.height - top; return ( - <line x1={prevX} y1={prevY} x2={endX} y2={endY} className="path" key={pos.step.uuid} - markerEnd={isHidden ? "none" : "url(#arrowhead)"}/> + <line x1={prevX} y1={prevY} x2={endX} y2={endY} className="path" key={pos.step.uuid} markerEnd="url(#arrowhead)"/> ) } } else if (pos.inSteps && pos.position > 0 && hasSteps(pos.step)) { @@ -428,8 +419,7 @@ export function DslConnections() { const prevX = r.x + r.width / 2 - left; const prevY = r.y + r.height - top; return ( - <line x1={prevX} y1={prevY} x2={endX} y2={endY} className="path" key={pos.step.uuid} - markerEnd={isHidden ? "none" : "url(#arrowhead)"}/> + <line x1={prevX} y1={prevY} x2={endX} y2={endY} className="path" key={pos.step.uuid} markerEnd="url(#arrowhead)"/> ) } } diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx index d1c24f7b..9aaf6735 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/DslElement.tsx @@ -204,12 +204,12 @@ export function DslElement(props: Props) { const headerRect = headerIcon.getBoundingClientRect(); const rect = el.getBoundingClientRect(); if (props.step.showChildren) { - // if (isHidden) { + if (isHidden) { // EventBus.sendPosition("delete", props.step, props.parent, new DOMRect(), new DOMRect(), 0); - // EventBus.sendPosition("add", props.step, props.parent, rect, headerRect, props.position, props.inSteps, isSelected); - // } else { EventBus.sendPosition("add", props.step, props.parent, rect, headerRect, props.position, props.inSteps, isSelected); - // } + } else { + EventBus.sendPosition("add", props.step, props.parent, rect, headerRect, props.position, props.inSteps, isSelected); + } } else { EventBus.sendPosition("delete", props.step, props.parent, new DOMRect(), new DOMRect(), 0); } @@ -444,16 +444,7 @@ export function DslElement(props: Props) { <div key={"root" + element.uuid} className={className} ref={el => sendPosition(el)} - style={isElementHidden() ? { - visibility: "hidden", - height: "1px", - width: "1px", - borderStyle: hasBorder() ? "dotted" : "none", - borderColor: isElementSelected() ? "var(--step-border-color-selected)" : "var(--step-border-color)", - marginTop: isInStepWithChildren() ? "16px" : "8px", - zIndex: element.dslName === 'ToDefinition' ? 20 : 10, - boxShadow: isDraggedOver ? "0px 0px 1px 2px var(--step-border-color-selected)" : "none", - }: { + style={{ borderStyle: hasBorder() ? "dotted" : "none", borderColor: isElementSelected() ? "var(--step-border-color-selected)" : "var(--step-border-color)", marginTop: isInStepWithChildren() ? "16px" : "8px",