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
The following commit(s) were added to refs/heads/main by this push: new 812f39bd Fixes for demo 812f39bd is described below commit 812f39bd40acada7660749a2d5d6ffb6bb5b1bd4 Author: Marat Gubaidullin <ma...@talismancloud.io> AuthorDate: Wed Dec 13 20:32:29 2023 -0500 Fixes for demo --- .../apache/camel/karavan/api/BuildResource.java | 56 +++++++++ .../camel/karavan/api/ContainerResource.java | 2 +- .../apache/camel/karavan/api/DevModeResource.java | 2 +- .../org/apache/camel/karavan/code/CodeService.java | 9 +- .../org/apache/camel/karavan/git/GitService.java | 2 + .../karavan/infinispan/InfinispanService.java | 6 +- .../karavan/kubernetes/KubernetesService.java | 100 +++++++++++----- .../camel/karavan/kubernetes/PodEventHandler.java | 13 ++- .../apache/camel/karavan/service/CamelService.java | 46 +++++--- .../karavan/service/ContainerStatusService.java | 1 + .../camel/karavan/service/ProjectService.java | 17 +-- .../org/apache/camel/karavan/shared/Constants.java | 5 + .../src/main/webui/src/api/KaravanApi.tsx | 11 ++ .../designer/route/element/DslElementHeader.tsx | 5 +- .../route/property/ComponentParameterField.tsx | 5 +- .../designer/route/property/DslPropertyField.tsx | 3 +- .../src/designer/route/useRouteDesignerHook.tsx | 20 +++- .../webui/src/project/{file => }/FileEditor.tsx | 21 ++-- .../src/main/webui/src/project/ProjectPage.tsx | 2 +- .../webui/src/project/file/PropertiesPanel.tsx | 61 ---------- .../webui/src/project/file/PropertiesTable.tsx | 126 --------------------- .../webui/src/project/file/PropertiesToolbar.tsx | 63 ----------- .../main/webui/src/project/file/PropertyField.tsx | 73 ------------ .../main/webui/src/project/files/FilesToolbar.tsx | 26 ++++- 24 files changed, 268 insertions(+), 407 deletions(-) diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/BuildResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/BuildResource.java new file mode 100644 index 00000000..ace9fa8c --- /dev/null +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/BuildResource.java @@ -0,0 +1,56 @@ +/* + * 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.api; + +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.camel.karavan.code.CodeService; +import org.apache.camel.karavan.infinispan.InfinispanService; +import org.apache.camel.karavan.kubernetes.KubernetesService; + +@Path("/api/build") +public class BuildResource { + + @Inject + InfinispanService infinispanService; + + @Inject + KubernetesService kubernetesService; + + @Inject + CodeService codeService; + + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @Path("/update-config-map") + public Response updateConfigMaps() { + if (infinispanService.isReady()) { + String script = codeService.getBuilderScript(); + kubernetesService.createBuildScriptConfigmap(script, true); + return Response.ok().build(); + } else { + return Response.noContent().build(); + } + } + +} \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java index 8effafe0..bfcae002 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/api/ContainerResource.java @@ -156,7 +156,7 @@ public class ContainerResource { status = ContainerStatus.createByType(projectId, environment, ContainerStatus.ContainerType.valueOf(type)); } status.setInTransit(true); - eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(status)); + eventBus.publish(CONTAINER_STATUS, JsonObject.mapFrom(status)); } @GET 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 1a8fe8ad..14bbc5a6 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 @@ -114,7 +114,7 @@ public class DevModeResource { status = ContainerStatus.createByType(name, environment, ContainerStatus.ContainerType.valueOf(type)); } status.setInTransit(true); - eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(status)); + eventBus.publish(CONTAINER_STATUS, JsonObject.mapFrom(status)); } @GET diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java index cab3a77e..6bd42fec 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/code/CodeService.java @@ -40,6 +40,7 @@ import org.apache.camel.karavan.infinispan.model.Project; import org.apache.camel.karavan.infinispan.model.ProjectFile; import org.apache.camel.karavan.kubernetes.KubernetesService; import org.apache.camel.karavan.service.ConfigService; +import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; @@ -58,12 +59,16 @@ public class CodeService { public static final String BUILD_SCRIPT_FILENAME = "build.sh"; public static final String DEV_SERVICES_FILENAME = "devservices.yaml"; public static final String PROJECT_COMPOSE_FILENAME = "docker-compose.yaml"; + public static final String MARKDOWN_EXTENSION = ".md"; public static final String PROJECT_JKUBE_EXTENSION = ".jkube.yaml"; public static final String PROJECT_DEPLOYMENT_JKUBE_FILENAME = "deployment" + PROJECT_JKUBE_EXTENSION; private static final String SNIPPETS_PATH = "/snippets/"; private static final String DATA_FOLDER = System.getProperty("user.dir") + File.separator + "data"; private static final int INTERNAL_PORT = 8080; + @ConfigProperty(name = "karavan.environment") + String environment; + @Inject KubernetesService kubernetesService; @@ -91,6 +96,7 @@ public class CodeService { public Map<String, String> getProjectFilesForDevMode(String projectId, Boolean withKamelets) { Map<String, String> files = infinispanService.getProjectFiles(projectId).stream() + .filter(f -> !f.getName().endsWith(MARKDOWN_EXTENSION)) .filter(f -> !Objects.equals(f.getName(), PROJECT_COMPOSE_FILENAME)) .filter(f -> !f.getName().endsWith(PROJECT_JKUBE_EXTENSION)) .collect(Collectors.toMap(ProjectFile::getName, ProjectFile::getCode)); @@ -142,7 +148,8 @@ public class CodeService { ? (kubernetesService.isOpenshift() ? "openshift" : "kubernetes") : "docker"; String templateName = target + "-" + BUILD_SCRIPT_FILENAME; - return getTemplateText(templateName); + String envTemplate = getTemplateText(environment + "." + templateName); + return envTemplate != null ? envTemplate : getTemplateText(templateName); } public String getTemplateText(String fileName) { diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java index 779e0edb..c07496b0 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/git/GitService.java @@ -457,6 +457,8 @@ public class GitService { String folder = vertx.fileSystem().createTempDirectoryBlocking(uuid); try (Git git = clone(folder, gitConfig.getUri(), gitConfig.getBranch(), cred)) { LOGGER.info("Git is ready"); + } catch (Exception e) { + LOGGER.info("Error connecting git: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage())); } return true; } diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java index 109781e3..a2f4c6fe 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/infinispan/InfinispanService.java @@ -255,7 +255,11 @@ public class InfinispanService implements HealthCheck { } public ContainerStatus getContainerStatus(String projectId, String env, String containerName) { - return containerStatuses.get(GroupedKey.create(projectId, env, containerName)); + return getContainerStatus(GroupedKey.create(projectId, env, containerName)); + } + + public ContainerStatus getContainerStatus(GroupedKey key) { + return containerStatuses.get(key); } public ContainerStatus getDevModeContainerStatus(String projectId, String env) { diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java index 5b2459ab..d3123d13 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/KubernetesService.java @@ -18,8 +18,8 @@ package org.apache.camel.karavan.kubernetes; import io.fabric8.kubernetes.api.model.*; import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.client.dsl.LogWatch; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; import io.fabric8.openshift.api.model.ImageStream; @@ -43,6 +43,7 @@ import org.eclipse.microprofile.health.Readiness; import org.jboss.logging.Logger; import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; @@ -62,11 +63,14 @@ public class KubernetesService implements HealthCheck { @Inject InfinispanService infinispanService; + @Inject + CodeService codeService; + private String namespace; @Produces public KubernetesClient kubernetesClient() { - return new DefaultKubernetesClient(); + return new KubernetesClientBuilder().build(); } @Produces @@ -77,6 +81,12 @@ public class KubernetesService implements HealthCheck { @ConfigProperty(name = "karavan.environment") public String environment; + @ConfigProperty(name = "karavan.devmode.image") + public String devmodeImage; + + @ConfigProperty(name = "karavan.devmode.create-pvc") + public Boolean devmodePVC; + @ConfigProperty(name = "karavan.version") String version; @@ -131,19 +141,27 @@ public class KubernetesService implements HealthCheck { informers.clear(); } + public void createBuildScriptConfigmap(String script, boolean overwrite) { + try (KubernetesClient client = kubernetesClient()) { + ConfigMap configMap = client.configMaps().inNamespace(getNamespace()).withName(BUILD_CONFIG_MAP).get(); + if (configMap == null) { + configMap = getConfigMapForBuilder(BUILD_CONFIG_MAP, getPartOfLabels()); + configMap.setData(Map.of("build.sh", script)); + client.resource(configMap).create(); + } else if (overwrite) { + configMap.setData(Map.of("build.sh", script)); + client.resource(configMap).patch(); + } + } catch (Exception e) { + LOGGER.error("Error starting informers: " + e.getMessage()); + } + } public void runBuildProject(Project project, String script, List<String> env, String tag) { try (KubernetesClient client = kubernetesClient()) { String containerName = project.getProjectId() + BUILDER_SUFFIX; Map<String, String> labels = getLabels(containerName, project, ContainerStatus.ContainerType.build); // createPVC(containerName, labels); - - // create script configMap - ConfigMap configMap = client.configMaps().inNamespace(getNamespace()).withName(containerName).get(); - if (configMap == null) { - configMap = getConfigMapForBuilder(containerName, labels); - } - configMap.setData(Map.of("build.sh", script)); - client.resource(configMap).serverSideApply(); + createBuildScriptConfigmap(script, false); // Delete old build pod Pod old = client.pods().inNamespace(getNamespace()).withName(containerName).get(); @@ -151,9 +169,11 @@ public class KubernetesService implements HealthCheck { client.resource(old).delete(); } Pod pod = getBuilderPod(containerName, env, labels); - Pod result = client.resource(pod).serverSideApply(); + Pod result = client.resource(pod).create(); LOGGER.info("Created pod " + result.getMetadata().getName()); + } catch (Exception e) { + LOGGER.error("Error creating build container: " + e.getMessage()); } } @@ -166,6 +186,9 @@ public class KubernetesService implements HealthCheck { if (type != null) { labels.put(LABEL_TYPE, type.name()); } + if (Objects.equals(type, ContainerStatus.ContainerType.devmode)) { + labels.put(LABEL_CAMEL_RUNTIME, CamelRuntime.CAMEL_MAIN.getValue()); + } return labels; } @@ -238,13 +261,13 @@ public class KubernetesService implements HealthCheck { Container container = new ContainerBuilder() .withName(name) - .withImage("ghcr.io/apache/camel-karavan-devmode:" + version) + .withImage(devmodeImage) .withPorts(port) .withImagePullPolicy("Always") .withEnv(envVars) .withCommand("/bin/sh", "-c", "/karavan/builder/build.sh") .withVolumeMounts( - new VolumeMountBuilder().withName("builder").withMountPath("/karavan/builder").withReadOnly(true).build() + new VolumeMountBuilder().withName(BUILD_CONFIG_MAP).withMountPath("/karavan/builder").withReadOnly(true).build() ) .build(); @@ -254,14 +277,15 @@ public class KubernetesService implements HealthCheck { .withRestartPolicy("Never") .withServiceAccount(KARAVAN_SERVICE_ACCOUNT) .withVolumes( - new VolumeBuilder().withName("builder") - .withConfigMap(new ConfigMapVolumeSourceBuilder().withName(name).withItems( + new VolumeBuilder().withName(BUILD_CONFIG_MAP) + .withConfigMap(new ConfigMapVolumeSourceBuilder().withName(BUILD_CONFIG_MAP).withItems( new KeyToPathBuilder().withKey("build.sh").withPath("build.sh").build() ).withDefaultMode(511).build()).build() // new VolumeBuilder().withName("maven-settings") // .withConfigMap(new ConfigMapVolumeSourceBuilder() // .withName("karavan").build()).build() - ).build(); + ) + .build(); return new PodBuilder() .withMetadata(meta) @@ -363,22 +387,38 @@ public class KubernetesService implements HealthCheck { return result; } - public void runDevModeContainer(Project project, String jBangOptions) { + public void runDevModeContainer(Project project, String jBangOptions, Map<String, String> files) { String name = project.getProjectId(); Map<String, String> labels = getLabels(name, project, ContainerStatus.ContainerType.devmode); + try (KubernetesClient client = kubernetesClient()) { - createPVC(name, labels); + if (devmodePVC) { + createPVC(name, labels); + } Pod old = client.pods().inNamespace(getNamespace()).withName(name).get(); if (old == null) { Map<String, String> containerResources = CodeService.DEFAULT_CONTAINER_RESOURCES; Pod pod = getDevModePod(name, jBangOptions, containerResources, labels); Pod result = client.resource(pod).createOrReplace(); + copyFilesToContainer(result, files, "/karavan/code"); LOGGER.info("Created pod " + result.getMetadata().getName()); } } createService(name, labels); } + private void copyFilesToContainer(Pod pod, Map<String, String> files, String dirName) { + try (KubernetesClient client = kubernetesClient()) { + String temp = codeService.saveProjectFilesInTemp(files); + client.pods().inNamespace(getNamespace()) + .withName(pod.getMetadata().getName()) + .dir(dirName) + .upload(Paths.get(temp)); + } catch (Exception e) { + LOGGER.info("Error copying filed to devmode pod: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage())); + } + } + public void deleteDevModePod(String name, boolean deletePVC) { try (KubernetesClient client = kubernetesClient()) { LOGGER.info("Delete devmode pod: " + name + " in the namespace: " + getNamespace()); @@ -418,27 +458,29 @@ public class KubernetesService implements HealthCheck { Container container = new ContainerBuilder() .withName(name) - .withImage("ghcr.io/apache/camel-karavan-devmode:" + version) + .withImage(devmodeImage) .withPorts(port) .withResources(resources) .withImagePullPolicy("Always") .withEnv(new EnvVarBuilder().withName(ENV_VAR_JBANG_OPTIONS).withValue(jbangOptions).build()) -// .withVolumeMounts( -// new VolumeMountBuilder().withName("maven-settings").withSubPath("maven-settings.xml") -// .withMountPath("/karavan-config-map/maven-settings.xml").build() -// ) .build(); + + List<Volume> volumes = new ArrayList<>(); + volumes.add(new VolumeBuilder().withName("maven-settings") + .withConfigMap(new ConfigMapVolumeSourceBuilder() + .withName("karavan").build()).build()); + + if (devmodePVC) { + volumes.add(new VolumeBuilder().withName(name) + .withNewPersistentVolumeClaim(name, false).build()); + } + PodSpec spec = new PodSpecBuilder() .withTerminationGracePeriodSeconds(0L) .withContainers(container) .withRestartPolicy("Never") - .withVolumes( - new VolumeBuilder().withName(name) - .withNewPersistentVolumeClaim(name, false).build(), - new VolumeBuilder().withName("maven-settings") - .withConfigMap(new ConfigMapVolumeSourceBuilder() - .withName("karavan").build()).build()) + .withVolumes(volumes) .build(); return new PodBuilder() diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java index 0af91999..937cb2f2 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/kubernetes/PodEventHandler.java @@ -33,8 +33,7 @@ import java.util.Objects; import static org.apache.camel.karavan.code.CodeService.DEFAULT_CONTAINER_RESOURCES; import static org.apache.camel.karavan.service.ContainerStatusService.CONTAINER_STATUS; -import static org.apache.camel.karavan.shared.Constants.LABEL_PROJECT_ID; -import static org.apache.camel.karavan.shared.Constants.LABEL_TYPE; +import static org.apache.camel.karavan.shared.Constants.*; public class PodEventHandler implements ResourceEventHandler<Pod> { @@ -55,7 +54,7 @@ public class PodEventHandler implements ResourceEventHandler<Pod> { LOGGER.info("onAdd " + pod.getMetadata().getName()); ContainerStatus ps = getPodStatus(pod); if (ps != null) { - eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(ps)); + eventBus.publish(CONTAINER_STATUS, JsonObject.mapFrom(ps)); } } catch (Exception e) { LOGGER.error(e.getMessage(), e.getCause()); @@ -69,7 +68,7 @@ public class PodEventHandler implements ResourceEventHandler<Pod> { if (!newPod.isMarkedForDeletion() && newPod.getMetadata().getDeletionTimestamp() == null) { ContainerStatus ps = getPodStatus(newPod); if (ps != null) { - eventBus.send(CONTAINER_STATUS, JsonObject.mapFrom(ps)); + eventBus.publish(CONTAINER_STATUS, JsonObject.mapFrom(ps)); } } } catch (Exception e) { @@ -94,6 +93,8 @@ public class PodEventHandler implements ResourceEventHandler<Pod> { public ContainerStatus getPodStatus(Pod pod) { String deployment = pod.getMetadata().getLabels().get("app"); String projectId = deployment != null ? deployment : pod.getMetadata().getLabels().get(LABEL_PROJECT_ID); + String camel = deployment != null ? deployment : pod.getMetadata().getLabels().get(LABEL_KUBERNETES_RUNTIME); + String runtime = deployment != null ? deployment : pod.getMetadata().getLabels().get(LABEL_CAMEL_RUNTIME); String type = pod.getMetadata().getLabels().get(LABEL_TYPE); ContainerStatus.ContainerType containerType = deployment != null ? ContainerStatus.ContainerType.project @@ -125,7 +126,8 @@ public class PodEventHandler implements ResourceEventHandler<Pod> { status.setContainerId(pod.getMetadata().getName()); status.setPhase(pod.getStatus().getPhase()); status.setPodIP(pod.getStatus().getPodIP()); - if (ready) { + status.setCamelRuntime(runtime != null ? runtime : (camel != null ? CamelRuntime.CAMEL_MAIN.getValue() : "")); + if (running) { status.setState(ContainerStatus.State.running.name()); } else if (failed) { status.setState(ContainerStatus.State.dead.name()); @@ -136,6 +138,7 @@ public class PodEventHandler implements ResourceEventHandler<Pod> { } return status; } catch (Exception ex) { + ex.printStackTrace(); LOGGER.error(ex.getMessage(), ex.getCause()); return null; } diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CamelService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CamelService.java index 134b41fe..0923251a 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CamelService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/CamelService.java @@ -79,6 +79,7 @@ public class CamelService { @Scheduled(every = "{karavan.camel.status.interval}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP) public void collectCamelStatuses() { + LOGGER.info("Collect Camel Statuses"); if (infinispanService.isReady()) { infinispanService.getContainerStatuses(environment).stream() .filter(cs -> @@ -98,12 +99,13 @@ public class CamelService { public void reloadProjectCode(String projectId) { LOGGER.info("Reload project code " + projectId); try { + deleteRequest(projectId); Map<String, String> files = codeService.getProjectFilesForDevMode(projectId, true); files.forEach((name, code) -> putRequest(projectId, name, code, 1000)); reloadRequest(projectId); ContainerStatus containerStatus = infinispanService.getDevModeContainerStatus(projectId, environment); containerStatus.setCodeLoaded(true); - eventBus.send(ContainerStatusService.CONTAINER_STATUS, JsonObject.mapFrom(containerStatus)); + eventBus.publish(ContainerStatusService.CONTAINER_STATUS, JsonObject.mapFrom(containerStatus)); } catch (Exception ex) { LOGGER.error(ex.getMessage()); } @@ -122,10 +124,20 @@ public class CamelService { return false; } + public String deleteRequest(String containerName) { + String url = getContainerAddressForReload(containerName) + "/q/upload/*"; + try { + return deleteResult(url, 1000); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error(e.getMessage()); + } + return null; + } + public String reloadRequest(String containerName) { String url = getContainerAddressForReload(containerName) + "/q/dev/reload?reload=true"; try { - return result(url, 1000); + return getResult(url, 1000); } catch (InterruptedException | ExecutionException e) { LOGGER.error(e.getMessage()); } @@ -158,7 +170,7 @@ public class CamelService { public String getCamelStatus(ContainerStatus containerStatus, CamelStatusValue.Name statusName) { String url = getContainerAddressForStatus(containerStatus) + "/q/dev/" + statusName.name(); try { - return result(url, 500); + return getResult(url, 500); } catch (InterruptedException | ExecutionException e) { LOGGER.error(e.getMessage()); } @@ -169,6 +181,7 @@ public class CamelService { public void collectCamelStatuses(JsonObject data) { CamelStatusRequest dms = data.getJsonObject("camelStatusRequest").mapTo(CamelStatusRequest.class); ContainerStatus containerStatus = data.getJsonObject("containerStatus").mapTo(ContainerStatus.class); + LOGGER.info("Collect Camel Status for " + containerStatus.getContainerName()); String projectId = dms.getProjectId(); String containerName = dms.getContainerName(); List<CamelStatusValue> statuses = new ArrayList<>(); @@ -176,27 +189,14 @@ public class CamelService { String status = getCamelStatus(containerStatus, statusName); if (status != null) { statuses.add(new CamelStatusValue(statusName, status)); - if (ConfigService.inKubernetes() && Objects.equals(statusName, CamelStatusValue.Name.context)) { - checkReloadRequired(containerStatus); - } } }); CamelStatus cs = new CamelStatus(projectId, containerName, statuses, environment); infinispanService.saveCamelStatus(cs); } - private void checkReloadRequired(ContainerStatus cs) { - if (ConfigService.inKubernetes()) { - if (!Objects.equals(cs.getCodeLoaded(), true) - && Objects.equals(cs.getState(), ContainerStatus.State.running.name()) - && Objects.equals(cs.getType(), ContainerStatus.ContainerType.devmode)) { - eventBus.publish(RELOAD_PROJECT_CODE, cs.getProjectId()); - } - } - } - @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 1000) - public String result(String url, int timeout) throws InterruptedException, ExecutionException { + public String getResult(String url, int timeout) throws InterruptedException, ExecutionException { try { HttpResponse<Buffer> result = getWebClient().getAbs(url).putHeader("Accept", "application/json") .timeout(timeout).send().subscribeAsCompletionStage().toCompletableFuture().get(); @@ -210,6 +210,18 @@ public class CamelService { return null; } + public String deleteResult(String url, int timeout) throws InterruptedException, ExecutionException { + try { + HttpResponse<Buffer> result = getWebClient().deleteAbs(url) + .timeout(timeout).send().subscribeAsCompletionStage().toCompletableFuture().get(); + JsonObject res = result.bodyAsJsonObject(); + return res.encodePrettily(); + } catch (Exception e) { + LOGGER.info(e.getMessage()); + } + return null; + } + public static class CamelStatusRequest { private String projectId; private String containerName; diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ContainerStatusService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ContainerStatusService.java index 9909b20b..712eb638 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ContainerStatusService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ContainerStatusService.java @@ -103,6 +103,7 @@ public class ContainerStatusService { if (infinispanService.isReady()) { ContainerStatus newStatus = data.mapTo(ContainerStatus.class); ContainerStatus oldStatus = infinispanService.getContainerStatus(newStatus.getProjectId(), newStatus.getEnv(), newStatus.getContainerName()); + if (oldStatus == null) { infinispanService.saveContainerStatus(newStatus); } else if (Objects.equals(oldStatus.getInTransit(), Boolean.FALSE)) { diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java index 3634fee3..75d0f004 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/service/ProjectService.java @@ -34,6 +34,7 @@ import org.apache.camel.karavan.infinispan.model.Project; import org.apache.camel.karavan.infinispan.model.ProjectFile; import org.apache.camel.karavan.kubernetes.KubernetesService; import org.apache.camel.karavan.registry.RegistryService; +import org.apache.camel.karavan.shared.Constants; import org.apache.camel.karavan.shared.Property; import org.apache.camel.karavan.shared.exception.ProjectExistsException; import org.apache.commons.lang3.StringUtils; @@ -99,16 +100,14 @@ public class ProjectService implements HealthCheck { if (status == null) { status = ContainerStatus.createDevMode(project.getProjectId(), environment); } - if (!Objects.equals(status.getState(), ContainerStatus.State.running.name())) { status.setInTransit(true); - eventBus.send(ContainerStatusService.CONTAINER_STATUS, JsonObject.mapFrom(status)); + eventBus.publish(ContainerStatusService.CONTAINER_STATUS, JsonObject.mapFrom(status)); + Map<String, String> files = codeService.getProjectFilesForDevMode(project.getProjectId(), true); if (ConfigService.inKubernetes()) { - kubernetesService.runDevModeContainer(project, jBangOptions); + kubernetesService.runDevModeContainer(project, jBangOptions, files); } else { - Map<String, String> files = codeService.getProjectFilesForDevMode(project.getProjectId(), true); - DockerComposeService dcs = codeService.getDockerComposeService(project.getProjectId()); dockerForKaravan.runProjectInDevMode(project.getProjectId(), jBangOptions, dcs.getPortsMap(), files); } @@ -260,9 +259,11 @@ public class ProjectService implements HealthCheck { if (infinispanService.getProjects().isEmpty()) { importAllProjects(); } - addKameletsProject(); - addTemplatesProject(); - addServicesProject(); + if (Objects.equals(environment, Constants.DEV_ENV)) { + addKameletsProject(); + addTemplatesProject(); + addServicesProject(); + } ready.set(true); } else { LOGGER.info("Projects are not ready"); diff --git a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java index 8b633ed0..72ea919d 100644 --- a/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java +++ b/karavan-web/karavan-app/src/main/java/org/apache/camel/karavan/shared/Constants.java @@ -18,12 +18,14 @@ package org.apache.camel.karavan.shared; public class Constants { + public static final String DEV_ENV = "dev"; public static final String ENV_VAR_JBANG_OPTIONS = "JBANG_OPTIONS"; public static final String LABEL_PART_OF = "app.kubernetes.io/part-of"; public static final String LABEL_TYPE = "org.apache.camel.karavan/type"; public static final String LABEL_PROJECT_ID = "org.apache.camel.karavan/projectId"; public static final String LABEL_CAMEL_RUNTIME = "org.apache.camel.karavan/runtime"; + public static final String LABEL_KUBERNETES_RUNTIME = "app.kubernetes.io/runtime"; public static final String LABEL_TAG = "org.apache.camel.karavan/tag"; public static final String BUILDER_SUFFIX = "-builder"; @@ -36,6 +38,9 @@ public class Constants { public static final String M2_CACHE_SUFFIX = "m2-cache"; public static final String PVC_MAVEN_SETTINGS = "maven-settings"; + public static final String BUILD_CONFIG_MAP = "build-config-map"; + public static final String BUILD_SCRIPT_FILENAME_SUFFIX = "-build.sh"; + public enum CamelRuntime { CAMEL_MAIN("camel-main"), QUARKUS("quarkus"), diff --git a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx index 5c01bd6f..0d07f0da 100644 --- a/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/api/KaravanApi.tsx @@ -273,6 +273,17 @@ export class KaravanApi { }); } + static async updateBuildConfigMap(after: (res: AxiosResponse<any>) => void) { + instance.post('/api/build/update-config-map', "{}") + .then(res => { + if (res.status === 200) { + after(res.data); + } + }).catch(err => { + console.log(err); + }); + } + static async getFiles(projectId: string, after: (files: ProjectFile[]) => void) { instance.get('/api/file/' + projectId) .then(res => { diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx index d201d105..c850e514 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/element/DslElementHeader.tsx @@ -147,8 +147,9 @@ export function DslElementHeader(props: Props) { classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected') } else if (step.dslName === 'RouteConfigurationDefinition') { classes.push('header-route') - if (hasElements(step)) classes.push('header-bottom-line') - classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected') + if (hasElements(step)) { + classes.push(isElementSelected() ? 'header-bottom-selected' : 'header-bottom-not-selected') + } } else { classes.push('header') } diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx index f80c3e75..49eb2f6a 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/ComponentParameterField.tsx @@ -297,13 +297,14 @@ export function ComponentParameterField(props: Props) { } function getSwitch(property: ComponentProperty, value: any) { + const isChecked = value !== undefined ? Boolean(value) : (property.defaultValue !== undefined && ['true', true].includes(property.defaultValue)) return ( <Switch id={id} name={id} value={value?.toString()} aria-label={id} - isChecked={value !== undefined ? Boolean(value) : property.defaultValue !== undefined && property.defaultValue === 'true'} - onChange={e => parametersChanged(property.name, !Boolean(value))}/> + isChecked={isChecked} + onChange={(e, checked) => parametersChanged(property.name, checked)}/> ) } diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx index 77974d13..a7feb03c 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/property/DslPropertyField.tsx @@ -171,7 +171,8 @@ export function DslPropertyField(props: Props) { function isUriReadOnly(property: PropertyMeta): boolean { const dslName: string = props.element?.dslName || ''; - return property.name === 'uri' && !['ToDynamicDefinition', 'WireTapDefinition', 'InterceptFromDefinition'].includes(dslName) + return property.name === 'uri' + && !['ToDynamicDefinition', 'WireTapDefinition', 'InterceptFromDefinition', 'InterceptSendToEndpointDefinition'].includes(dslName) } function selectInfrastructure(value: string) { diff --git a/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx b/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx index 1457adba..51b97e20 100644 --- a/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/designer/route/useRouteDesignerHook.tsx @@ -18,7 +18,14 @@ import React from 'react'; import '../karavan.css'; import {DslMetaModel} from "../utils/DslMetaModel"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; -import {ChoiceDefinition, FromDefinition, LogDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition"; +import { + ChoiceDefinition, + FromDefinition, JsonDataFormat, + LogDefinition, + MarshalDefinition, + RouteConfigurationDefinition, + RouteDefinition, UnmarshalDefinition +} from "karavan-core/lib/model/CamelDefinition"; import {CamelElement, MetadataLabels} from "karavan-core/lib/model/IntegrationDefinition"; import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt"; import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi"; @@ -247,6 +254,7 @@ export function useRouteDesignerHook () { default: const step = CamelDefinitionApi.createStep(dsl.dsl, undefined); const augmentedStep = setDslDefaults(step); + console.log(step, augmentedStep) addStep(augmentedStep, parentId, position) break; } @@ -261,6 +269,16 @@ export function useRouteDesignerHook () { (step as ChoiceDefinition).when?.push(CamelDefinitionApi.createStep('WhenDefinition', undefined)); (step as ChoiceDefinition).otherwise = CamelDefinitionApi.createStep('OtherwiseDefinition', undefined); } + if (step.dslName === 'MarshalDefinition') { + if (CamelDefinitionApiExt.getDataFormat(step) === undefined) { + (step as MarshalDefinition).json = new JsonDataFormat() + } + } + if (step.dslName === 'UnmarshalDefinition') { + if (CamelDefinitionApiExt.getDataFormat(step) === undefined) { + (step as UnmarshalDefinition).json = new JsonDataFormat() + } + } return step; } diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx b/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx similarity index 82% rename from karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx rename to karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx index b5bf2fdc..b98285eb 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/file/FileEditor.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/FileEditor.tsx @@ -15,18 +15,14 @@ * limitations under the License. */ import React from 'react'; -import '../../designer/karavan.css'; +import '../designer/karavan.css'; import Editor from "@monaco-editor/react"; import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml"; -import {ProjectFile} from "../../api/ProjectModels"; -import {useFilesStore, useFileStore} from "../../api/ProjectStore"; -import {KaravanDesigner} from "../../designer/KaravanDesigner"; -import {ProjectService} from "../../api/ProjectService"; -import {PropertiesTable} from "./PropertiesTable"; +import {ProjectFile} from "../api/ProjectModels"; +import {useFilesStore, useFileStore} from "../api/ProjectStore"; +import {KaravanDesigner} from "../designer/KaravanDesigner"; +import {ProjectService} from "../api/ProjectService"; import {shallow} from "zustand/shallow"; -import {PropertiesToolbar} from "./PropertiesToolbar"; -import {Card, Panel} from "@patternfly/react-core"; -import {PropertiesPanel} from "./PropertiesPanel"; interface Props { projectId: string @@ -34,7 +30,8 @@ interface Props { const languages = new Map<string, string>([ ['sh', 'shell'], - ['md', 'markdown'] + ['md', 'markdown'], + ['properties', 'ini'] ]) export function FileEditor (props: Props) { @@ -93,14 +90,12 @@ export function FileEditor (props: Props) { const isCamelYaml = file !== undefined && file.name.endsWith(".camel.yaml"); const isKameletYaml = file !== undefined && file.name.endsWith(".kamelet.yaml"); const isIntegration = isCamelYaml && file?.code && CamelDefinitionYaml.yamlIsIntegration(file.code); - const isProperties = file !== undefined && file.name.endsWith("properties"); const showDesigner = (isCamelYaml && isIntegration) || isKameletYaml; - const showEditor = !showDesigner && !isProperties; + const showEditor = !showDesigner; return ( <> {showDesigner && getDesigner()} {showEditor && getEditor()} - {isProperties && file !== undefined && <PropertiesPanel/>} </> ) } diff --git a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx index 39c4ff1e..59950bb6 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/ProjectPage.tsx @@ -29,7 +29,7 @@ import {useAppConfigStore, useFilesStore, useFileStore, useProjectsStore, usePro import {MainToolbar} from "../designer/MainToolbar"; import {ProjectTitle} from "./ProjectTitle"; import {ProjectPanel} from "./ProjectPanel"; -import {FileEditor} from "./file/FileEditor"; +import {FileEditor} from "./FileEditor"; import {shallow} from "zustand/shallow"; import {useParams} from "react-router-dom"; import {KaravanApi} from "../api/KaravanApi"; diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesPanel.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesPanel.tsx deleted file mode 100644 index db4e290c..00000000 --- a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesPanel.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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. - */ - -import React from 'react'; -import { - PageSection, PanelHeader, Panel -} from '@patternfly/react-core'; -import '../../designer/karavan.css'; -import { useProjectStore} from "../../api/ProjectStore"; -import { ProjectFileTypes} from "../../api/ProjectModels"; -import {shallow} from "zustand/shallow"; -import {PropertiesToolbar} from "./PropertiesToolbar"; -import {PropertiesTable} from "./PropertiesTable"; - -export function PropertiesPanel () { - - const [project] = useProjectStore((s) => [s.project], shallow); - - function isBuildIn(): boolean { - return ['kamelets', 'templates', 'services'].includes(project.projectId); - } - - function canDeleteFiles(): boolean { - return !['templates', 'services'].includes(project.projectId); - } - - function isKameletsProject(): boolean { - return project.projectId === 'kamelets'; - } - - const types = isBuildIn() - ? (isKameletsProject() ? ['KAMELET'] : ['CODE', 'PROPERTIES']) - : ProjectFileTypes.filter(p => !['PROPERTIES', 'LOG', 'KAMELET'].includes(p.name)).map(p => p.name); - - return ( - <PageSection padding={{default: 'noPadding'}} className="scrollable-out"> - <PageSection isFilled padding={{default: 'padding'}} className="scrollable-in"> - <Panel> - <PanelHeader> - <PropertiesToolbar/> - </PanelHeader> - </Panel> - <PropertiesTable/> - </PageSection> - </PageSection> - ) -} diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx deleted file mode 100644 index 71357cbb..00000000 --- a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesTable.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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. - */ -import React, {useEffect, useState} from 'react'; -import { - Button, - Modal, -} from '@patternfly/react-core'; -import '../../designer/karavan.css'; -import { - Tbody, - Th, - Thead, - Tr -} from '@patternfly/react-table'; -import { - Table -} from '@patternfly/react-table/deprecated'; - -import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel"; -import {useFileStore} from "../../api/ProjectStore"; -import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi"; -import {shallow} from "zustand/shallow" -import {PropertyField} from "./PropertyField"; -import {ProjectService} from "../../api/ProjectService"; - -export function PropertiesTable() { - - const [showDeleteConfirmation, setShowDeleteConfirmation] = useState<boolean>(false); - const [deleteId, setDeleteId] = useState<string | undefined>(undefined); - const [key, setKey] = useState<string | undefined>(undefined); - const [properties, setProperties] = useState<ProjectProperty[]>([]); - const [file, editAdvancedProperties, addProperty, setAddProperty] = useFileStore((state) => - [state.file, state.editAdvancedProperties, state.addProperty, state.setAddProperty], shallow) - - useEffect(() => { - setProperties(getProjectModel().properties) - }, [addProperty,setDeleteId]); - - function save(props: ProjectProperty[]) { - if (file) { - file.code = ProjectModelApi.propertiesToString(props); - ProjectService.saveFile(file, true); - } - } - - function getProjectModel(): ProjectModel { - return file ? ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew() - } - - function changeProperty(property: ProjectProperty) { - const props = properties.map(prop => prop.id === property.id ? property : prop); - save(props); - } - - function startDelete(id: string) { - setShowDeleteConfirmation(true); - setDeleteId(id); - } - - function confirmDelete() { - const props = properties.filter(p => p.id !== deleteId); - save(props); - setShowDeleteConfirmation(false); - setDeleteId(undefined); - setAddProperty(Math.random().toString()); - } - - function getDeleteConfirmation() { - return (<Modal - className="modal-delete" - title="Confirmation" - isOpen={showDeleteConfirmation} - onClose={() => setShowDeleteConfirmation(false)} - actions={[ - <Button key="confirm" variant="primary" onClick={e => confirmDelete()}>Delete</Button>, - <Button key="cancel" variant="link" - onClick={e => setShowDeleteConfirmation(false)}>Cancel</Button> - ]} - onEscapePress={e => setShowDeleteConfirmation(false)}> - <div>Delete property?</div> - </Modal>) - } - - return ( - <> - {properties.length > 0 && - <Table aria-label="Property table" variant='compact' borders={false} - className="project-properties"> - <Thead> - <Tr> - <Th key='name'>Name</Th> - <Th key='value'>Value</Th> - <Th></Th> - </Tr> - </Thead> - <Tbody> - {properties.map((property, idx: number) => { - const readOnly = (property.key.startsWith("camel.jbang") || property.key.startsWith("camel.karavan")) && !editAdvancedProperties; - return ( - <PropertyField key={idx + property.key} - property={property} - readOnly={readOnly} - changeProperty={changeProperty} - onDelete={startDelete}/> - ) - })} - </Tbody> - </Table>} - {showDeleteConfirmation && getDeleteConfirmation()} - </> - ) -} \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesToolbar.tsx deleted file mode 100644 index 598dc95e..00000000 --- a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertiesToolbar.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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. - */ -import React from 'react'; -import { - Button, Checkbox, - Flex, - FlexItem -} from '@patternfly/react-core'; -import '../../designer/karavan.css'; -import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; -import {useFileStore} from "../../api/ProjectStore"; -import {shallow} from "zustand/shallow"; -import {ProjectService} from "../../api/ProjectService"; -import {ProjectModelApi} from "karavan-core/lib/api/ProjectModelApi"; -import {ProjectModel, ProjectProperty} from "karavan-core/lib/model/ProjectModel"; - -export function PropertiesToolbar () { - - const [file, editAdvancedProperties, setEditAdvancedProperties, setAddProperty] = useFileStore((state) => - [state.file, state.editAdvancedProperties, state.setEditAdvancedProperties, state.setAddProperty], shallow ) - - - function addProperty() { - if (file) { - const project = file ? ProjectModelApi.propertiesToProject(file?.code) : ProjectModel.createNew(); - const props = project.properties; - props.push(ProjectProperty.createNew("", "")); - file.code = ProjectModelApi.propertiesToString(props); - ProjectService.saveFile(file, true); - setAddProperty(Math.random().toString()); - } - } - - return ( - <Flex className="toolbar" direction={{default: "row"}} justifyContent={{default: "justifyContentFlexEnd"}}> - <FlexItem> - <Checkbox - id="advanced" - label="Edit advanced" - isChecked={editAdvancedProperties} - onChange={(_, checked) => setEditAdvancedProperties(checked)} - /> - </FlexItem> - <FlexItem> - <Button size="sm" variant="primary" icon={<PlusIcon/>} onClick={e => addProperty()}>Add property</Button> - </FlexItem> - </Flex> - ) -} diff --git a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertyField.tsx b/karavan-web/karavan-app/src/main/webui/src/project/file/PropertyField.tsx deleted file mode 100644 index 2393164d..00000000 --- a/karavan-web/karavan-app/src/main/webui/src/project/file/PropertyField.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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. - */ -import React, {useEffect, useState} from 'react'; -import { - Button, - TextInput -} from '@patternfly/react-core'; -import '../../designer/karavan.css'; -import {ProjectProperty} from "karavan-core/lib/model/ProjectModel"; -import {Td, Tr} from "@patternfly/react-table"; -import DeleteIcon from "@patternfly/react-icons/dist/js/icons/times-icon"; - -interface Props { - property: ProjectProperty, - readOnly: boolean, - changeProperty: (p: ProjectProperty) => void - onDelete: (id: string) => void -} - -export function PropertyField (props: Props) { - - const [key, setKey] = useState<string | undefined>(props.property.key); - const [value, setValue] = useState<string | undefined>(props.property.value); - - useEffect(() => { - }, []); - - return ( - <Tr key={props.property.id}> - <Td noPadding width={10} dataLabel="key"> - <TextInput isDisabled={props.readOnly} isRequired={true} className="text-field" type={"text"} - id={"key-" + props.property.id} - value={key} - onChange={(e, val) => { - e.preventDefault(); - setKey(val) - props.changeProperty(new ProjectProperty({id: props.property.id, key: val, value: value})); - }}/> - </Td> - <Td noPadding width={20} dataLabel="value"> - <TextInput isDisabled={props.readOnly} isRequired={true} className="text-field" type={"text"} - id={"value-" + props.property.id} - value={value } - onChange={(e, val) => { - e.preventDefault(); - setValue(val); - props.changeProperty(new ProjectProperty({id: props.property.id, key: key, value: val})); - }}/> - </Td> - <Td noPadding isActionCell dataLabel="delete" className="delete-cell"> - {!props.readOnly && <Button variant={"plain"} icon={<DeleteIcon/>} className={"delete-button"} - onClick={event => { - props.onDelete(props.property.id) - }}/>} - </Td> - </Tr> - - ) -} \ No newline at end of file diff --git a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx index 2b22b917..033ad6e0 100644 --- a/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx +++ b/karavan-web/karavan-app/src/main/webui/src/project/files/FilesToolbar.tsx @@ -33,14 +33,19 @@ import { import '../../designer/karavan.css'; import UploadIcon from "@patternfly/react-icons/dist/esm/icons/upload-icon"; import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon"; -import {useFilesStore, useFileStore, useProjectStore} from "../../api/ProjectStore"; +import {useAppConfigStore, useFilesStore, useFileStore, useProjectStore} from "../../api/ProjectStore"; import {shallow} from "zustand/shallow"; import {ProjectService} from "../../api/ProjectService"; import PushIcon from "@patternfly/react-icons/dist/esm/icons/code-branch-icon"; +import UpdateIcon from "@patternfly/react-icons/dist/esm/icons/cog-icon"; import RefreshIcon from "@patternfly/react-icons/dist/esm/icons/sync-alt-icon"; +import {ProjectType} from "../../api/ProjectModels"; +import {KaravanApi} from "../../api/KaravanApi"; +import {EventBus} from "../../designer/utils/EventBus"; export function FileToolbar () { + const {config} = useAppConfigStore(); const [commitMessageIsOpen, setCommitMessageIsOpen] = useState(false); const [pullIsOpen, setPullIsOpen] = useState(false); const [commitMessage, setCommitMessage] = useState(''); @@ -58,6 +63,12 @@ export function FileToolbar () { ProjectService.pushProject(project, commitMessage); } + function updateScripts () { + KaravanApi.updateBuildConfigMap(res => { + EventBus.sendAlert("Success", "Script updated!", "info") + }) + } + function pull () { setPullIsOpen(false); ProjectService.pullProject(project.projectId); @@ -67,6 +78,10 @@ export function FileToolbar () { return !['templates', 'services'].includes(project.projectId); } + function isTemplates(): boolean { + return project.projectId === 'templates' && project.type === ProjectType.templates; + } + function getCommitModal() { return ( <Modal @@ -198,6 +213,15 @@ export function FileToolbar () { </Button> </Tooltip> </FlexItem> + {isTemplates() && config.infrastructure === 'kubernetes' && <FlexItem> + <Tooltip content="Update Build Script in Config Maps" position={"bottom-end"}> + <Button size="sm" variant={"primary"} icon={<UpdateIcon/>} + onClick={e => updateScripts()} + > + Update Script + </Button> + </Tooltip> + </FlexItem>} {canAddFiles() && <FlexItem> <Button size="sm" variant={"primary"} icon={<PlusIcon/>} onClick={e => setFile("create")}>Create</Button>