From 1010f8337e0c8a7db3ffc8da1daae6155108deeb Mon Sep 17 00:00:00 2001 From: eric regnier Date: Wed, 22 Jan 2020 17:59:28 +0100 Subject: [PATCH] feat(ui): display and download execution inputs and outputs (#39) close #29 --- .../components/executions/ExecutionOutput.vue | 69 ++++++++++++++++ .../components/executions/ExecutionRoot.vue | 8 +- ui/src/components/executions/Overview.vue | 79 ++++++++++++++++--- ui/src/http.js | 3 + .../controllers/ExecutionController.java | 38 +++++++++ 5 files changed, 184 insertions(+), 13 deletions(-) create mode 100644 ui/src/components/executions/ExecutionOutput.vue diff --git a/ui/src/components/executions/ExecutionOutput.vue b/ui/src/components/executions/ExecutionOutput.vue new file mode 100644 index 0000000000..392af7d60e --- /dev/null +++ b/ui/src/components/executions/ExecutionOutput.vue @@ -0,0 +1,69 @@ + + \ No newline at end of file diff --git a/ui/src/components/executions/ExecutionRoot.vue b/ui/src/components/executions/ExecutionRoot.vue index a824a4a366..0990bda65b 100644 --- a/ui/src/components/executions/ExecutionRoot.vue +++ b/ui/src/components/executions/ExecutionRoot.vue @@ -20,6 +20,7 @@ import Gantt from "./Gantt"; import Overview from "./Overview"; import Logs from "./Logs"; import Topology from "./Topology"; +import ExecutionOutput from "./ExecutionOutput"; import Trigger from "vue-material-design-icons/Cogs"; import BottomLine from "../layout/BottomLine"; import FlowActions from "../flows/FlowActions"; @@ -35,7 +36,8 @@ export default { Gantt, Logs, Topology, - FlowActions + FlowActions, + ExecutionOutput }, data() { return { @@ -122,6 +124,10 @@ export default { { tab: "topology", title: title("topology") + }, + { + tab: "execution-output", + title: title("output") } ]; } diff --git a/ui/src/components/executions/Overview.vue b/ui/src/components/executions/Overview.vue index d894e71cd5..d152e9f868 100644 --- a/ui/src/components/executions/Overview.vue +++ b/ui/src/components/executions/Overview.vue @@ -5,13 +5,40 @@ +

{{$t('execution') | cap}}

+
+
+

{{$t('inputs') | cap}}

+ + + +
\ No newline at end of file diff --git a/ui/src/http.js b/ui/src/http.js index 2b82945836..7d8abf09b1 100644 --- a/ui/src/http.js +++ b/ui/src/http.js @@ -16,3 +16,6 @@ export default callback => { Vue.axios.defaults.baseURL = (process.env.VUE_APP_API_URL || "") + "/"; callback(); }; + + +export const apiRoot = `${process.env.VUE_APP_API_URL}/api/v1/` \ No newline at end of file diff --git a/webserver/src/main/java/org/kestra/webserver/controllers/ExecutionController.java b/webserver/src/main/java/org/kestra/webserver/controllers/ExecutionController.java index 74724c8162..68eccfb176 100644 --- a/webserver/src/main/java/org/kestra/webserver/controllers/ExecutionController.java +++ b/webserver/src/main/java/org/kestra/webserver/controllers/ExecutionController.java @@ -1,18 +1,22 @@ package org.kestra.webserver.controllers; import io.micronaut.data.model.Pageable; +import io.micronaut.http.HttpResponse; import io.micronaut.http.MediaType; +import io.micronaut.http.MutableHttpResponse; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Post; import io.micronaut.http.annotation.QueryValue; import io.micronaut.http.exceptions.HttpStatusException; import io.micronaut.http.multipart.StreamingFileUpload; +import io.micronaut.http.server.types.files.StreamedFile; import io.micronaut.http.sse.Event; import io.micronaut.validation.Validated; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; import io.reactivex.Maybe; +import org.apache.commons.io.FilenameUtils; import org.kestra.core.models.executions.Execution; import org.kestra.core.models.flows.Flow; import org.kestra.core.queues.QueueFactoryInterface; @@ -20,6 +24,8 @@ import org.kestra.core.repositories.ExecutionRepositoryInterface; import org.kestra.core.repositories.FlowRepositoryInterface; import org.kestra.core.runners.RunnerUtils; +import org.kestra.core.storages.StorageInterface; +import org.kestra.core.storages.StorageObject; import org.kestra.webserver.responses.PagedResults; import org.kestra.webserver.utils.PageableUtils; import org.reactivestreams.Publisher; @@ -27,6 +33,11 @@ import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.util.List; import java.util.Map; import java.util.Optional; @@ -45,6 +56,9 @@ public class ExecutionController { @Inject private RunnerUtils runnerUtils; + @Inject + private StorageInterface storageInterface; + @Inject @Named(QueueFactoryInterface.EXECUTION_NAMED) protected QueueInterface executionQueue; @@ -126,6 +140,30 @@ public Maybe trigger( return Maybe.just(current); } + /** + * Download file binary from uri parameter + * + * @param filePath The file URI to return + * @param type The file storage type + * @return data binary content + */ + @Get(uri = "executions/{executionId}/file", produces = MediaType.APPLICATION_OCTET_STREAM) + public Maybe file( + String executionId, + @QueryValue(value = "filePath") URI filePath, + @QueryValue(value = "type") String type + ) throws URISyntaxException, IOException { + Optional execution = executionRepository.findById(executionId); + if (execution.isEmpty()) { + return Maybe.empty(); + } + + InputStream fileHandler = storageInterface.get(filePath); + return Maybe.just(new StreamedFile(fileHandler, MediaType.APPLICATION_OCTET_STREAM_TYPE) + .attach(FilenameUtils.getName(filePath.toString())) + ); + } + /** * Trigger an new execution for current flow and follow execution *