mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-26 05:00:31 -05:00
Compare commits
24 Commits
dependabot
...
v0.13.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83b7399171 | ||
|
|
d948e14319 | ||
|
|
7143de9ec6 | ||
|
|
33ece57996 | ||
|
|
5804ff7b26 | ||
|
|
2888d929aa | ||
|
|
8a75fd3fe5 | ||
|
|
3b1bed8bf4 | ||
|
|
01128f0927 | ||
|
|
bface3755a | ||
|
|
8fe518ceae | ||
|
|
28e39ab756 | ||
|
|
24843cfc17 | ||
|
|
2e1c3733a4 | ||
|
|
61db3ccedb | ||
|
|
6a03dc9e41 | ||
|
|
b2a107bc55 | ||
|
|
97a6afbe2e | ||
|
|
cc800b4528 | ||
|
|
da50cf4287 | ||
|
|
1ecfed1559 | ||
|
|
4a3e250019 | ||
|
|
2917c178a5 | ||
|
|
0ceaeb4a97 |
@@ -22,6 +22,7 @@ dependencies {
|
|||||||
implementation group: 'net.jodah', name: 'failsafe', version: '2.4.4'
|
implementation group: 'net.jodah', name: 'failsafe', version: '2.4.4'
|
||||||
implementation 'com.github.oshi:oshi-core:6.4.6'
|
implementation 'com.github.oshi:oshi-core:6.4.6'
|
||||||
implementation 'io.pebbletemplates:pebble:3.2.1'
|
implementation 'io.pebbletemplates:pebble:3.2.1'
|
||||||
|
implementation group: 'co.elastic.logging', name: 'logback-ecs-encoder', version: '1.5.0'
|
||||||
|
|
||||||
// scheduler
|
// scheduler
|
||||||
implementation group: 'com.cronutils', name: 'cron-utils', version: '9.2.1'
|
implementation group: 'com.cronutils', name: 'cron-utils', version: '9.2.1'
|
||||||
|
|||||||
@@ -696,7 +696,7 @@ public class Execution implements DeletedInterface, TenantInterface {
|
|||||||
* @return List of parent {@link TaskRun}
|
* @return List of parent {@link TaskRun}
|
||||||
*/
|
*/
|
||||||
public List<TaskRun> findChilds(TaskRun taskRun) {
|
public List<TaskRun> findChilds(TaskRun taskRun) {
|
||||||
if (taskRun.getParentTaskRunId() == null) {
|
if (taskRun.getParentTaskRunId() == null || this.taskRunList == null) {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package io.kestra.core.models.flows;
|
package io.kestra.core.models.flows;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import io.kestra.core.models.executions.Execution;
|
||||||
import io.micronaut.core.annotation.Introspected;
|
import io.micronaut.core.annotation.Introspected;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -7,6 +9,9 @@ import lombok.NoArgsConstructor;
|
|||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@Getter
|
@Getter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@@ -15,4 +20,22 @@ import lombok.experimental.SuperBuilder;
|
|||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class FlowWithException extends FlowWithSource {
|
public class FlowWithException extends FlowWithSource {
|
||||||
String exception;
|
String exception;
|
||||||
|
|
||||||
|
public static Optional<FlowWithException> from(JsonNode jsonNode, Exception exception) {
|
||||||
|
if (jsonNode.hasNonNull("id") && jsonNode.hasNonNull("namespace")) {
|
||||||
|
var flow = FlowWithException.builder()
|
||||||
|
.id(jsonNode.get("id").asText())
|
||||||
|
.tenantId(jsonNode.hasNonNull("tenant_id") ? jsonNode.get("tenant_id").asText() : null)
|
||||||
|
.namespace(jsonNode.get("namespace").asText())
|
||||||
|
.revision(jsonNode.hasNonNull("revision") ? jsonNode.get("revision").asInt() : 1)
|
||||||
|
.deleted(jsonNode.hasNonNull("deleted") ? jsonNode.get("deleted").asBoolean() : false)
|
||||||
|
.exception(exception.getMessage())
|
||||||
|
.tasks(List.of())
|
||||||
|
.build();
|
||||||
|
return Optional.of(flow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is no id and namespace, we return null as we cannot create a meaningful FlowWithException
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ public final class ExecutableUtils {
|
|||||||
|
|
||||||
return WorkerTaskExecution.builder()
|
return WorkerTaskExecution.builder()
|
||||||
.task(currentTask)
|
.task(currentTask)
|
||||||
.taskRun(currentTaskRun)
|
.taskRun(currentTaskRun.withState(State.Type.RUNNING))
|
||||||
.execution(execution)
|
.execution(execution)
|
||||||
.iteration(iteration)
|
.iteration(iteration)
|
||||||
.build();
|
.build();
|
||||||
@@ -117,11 +117,11 @@ public final class ExecutableUtils {
|
|||||||
State.Type currentState = taskRun.getState().getCurrent();
|
State.Type currentState = taskRun.getState().getCurrent();
|
||||||
Optional<State.Type> previousState = taskRun.getState().getHistories().size() > 1 ? Optional.of(taskRun.getState().getHistories().get(taskRun.getState().getHistories().size() - 2).getState()) : Optional.empty();
|
Optional<State.Type> previousState = taskRun.getState().getHistories().size() > 1 ? Optional.of(taskRun.getState().getHistories().get(taskRun.getState().getHistories().size() - 2).getState()) : Optional.empty();
|
||||||
|
|
||||||
int currentStateIteration = getIterationCounter(iterations, currentState, maxIterations) + 1;
|
int currentStateIteration = iterations.getOrDefault(currentState.toString(), 0);
|
||||||
iterations.put(currentState.toString(), currentStateIteration);
|
iterations.put(currentState.toString(), currentStateIteration + 1);
|
||||||
if (previousState.isPresent() && previousState.get() != currentState) {
|
if (previousState.isPresent() && previousState.get() != currentState) {
|
||||||
int previousStateIterations = getIterationCounter(iterations, previousState.get(), maxIterations) - 1;
|
int previousStateIterations = iterations.getOrDefault(previousState.get() .toString(), maxIterations);
|
||||||
iterations.put(previousState.get().toString(), previousStateIterations);
|
iterations.put(previousState.get().toString(), previousStateIterations - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the state to success if current == max
|
// update the state to success if current == max
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public class Executor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Boolean canBeProcessed() {
|
public Boolean canBeProcessed() {
|
||||||
return !(this.getException() != null || this.getFlow() == null || this.getFlow() instanceof FlowWithException || this.getExecution().isDeleted());
|
return !(this.getException() != null || this.getFlow() == null || this.getFlow() instanceof FlowWithException || this.getFlow().getTasks() == null || this.getExecution().isDeleted());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Executor withFlow(Flow flow) {
|
public Executor withFlow(Flow flow) {
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ public class NamespaceFilesService {
|
|||||||
private void copy(String tenantId, String namespace, Path basePath, List<URI> files) throws IOException {
|
private void copy(String tenantId, String namespace, Path basePath, List<URI> files) throws IOException {
|
||||||
files
|
files
|
||||||
.forEach(throwConsumer(f -> {
|
.forEach(throwConsumer(f -> {
|
||||||
InputStream inputStream = storageInterface.get(tenantId, uri(namespace, f));
|
|
||||||
Path destination = Paths.get(basePath.toString(), f.getPath());
|
Path destination = Paths.get(basePath.toString(), f.getPath());
|
||||||
|
|
||||||
if (!destination.getParent().toFile().exists()) {
|
if (!destination.getParent().toFile().exists()) {
|
||||||
@@ -115,7 +114,9 @@ public class NamespaceFilesService {
|
|||||||
destination.getParent().toFile().mkdirs();
|
destination.getParent().toFile().mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
Files.copy(inputStream, destination);
|
try (InputStream inputStream = storageInterface.get(tenantId, uri(namespace, f))) {
|
||||||
|
Files.copy(inputStream, destination);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -598,7 +598,7 @@ public class RunContext {
|
|||||||
URI uri = URI.create(this.taskStateFilePathPrefix(state, isNamespace, useTaskRun));
|
URI uri = URI.create(this.taskStateFilePathPrefix(state, isNamespace, useTaskRun));
|
||||||
URI resolve = uri.resolve(uri.getPath() + "/" + name);
|
URI resolve = uri.resolve(uri.getPath() + "/" + name);
|
||||||
|
|
||||||
return this.storageInterface.get(getTenantId(), resolve);
|
return this.storageInterface.get(getTenantId(), resolve);
|
||||||
}
|
}
|
||||||
|
|
||||||
public URI putTaskStateFile(byte[] content, String state, String name) throws IOException {
|
public URI putTaskStateFile(byte[] content, String state, String name) throws IOException {
|
||||||
|
|||||||
@@ -5,11 +5,6 @@ import com.github.jknack.handlebars.Handlebars;
|
|||||||
import com.github.jknack.handlebars.HandlebarsException;
|
import com.github.jknack.handlebars.HandlebarsException;
|
||||||
import com.github.jknack.handlebars.Template;
|
import com.github.jknack.handlebars.Template;
|
||||||
import com.github.jknack.handlebars.helper.*;
|
import com.github.jknack.handlebars.helper.*;
|
||||||
import io.pebbletemplates.pebble.PebbleEngine;
|
|
||||||
import io.pebbletemplates.pebble.error.AttributeNotFoundException;
|
|
||||||
import io.pebbletemplates.pebble.error.PebbleException;
|
|
||||||
import io.pebbletemplates.pebble.extension.AbstractExtension;
|
|
||||||
import io.pebbletemplates.pebble.template.PebbleTemplate;
|
|
||||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||||
import io.kestra.core.runners.handlebars.VariableRendererPlugins;
|
import io.kestra.core.runners.handlebars.VariableRendererPlugins;
|
||||||
import io.kestra.core.runners.handlebars.helpers.*;
|
import io.kestra.core.runners.handlebars.helpers.*;
|
||||||
@@ -18,6 +13,15 @@ import io.kestra.core.runners.pebble.JsonWriter;
|
|||||||
import io.kestra.core.runners.pebble.PebbleLruCache;
|
import io.kestra.core.runners.pebble.PebbleLruCache;
|
||||||
import io.micronaut.context.ApplicationContext;
|
import io.micronaut.context.ApplicationContext;
|
||||||
import io.micronaut.context.annotation.ConfigurationProperties;
|
import io.micronaut.context.annotation.ConfigurationProperties;
|
||||||
|
import io.micronaut.core.annotation.Nullable;
|
||||||
|
import io.pebbletemplates.pebble.PebbleEngine;
|
||||||
|
import io.pebbletemplates.pebble.error.AttributeNotFoundException;
|
||||||
|
import io.pebbletemplates.pebble.error.PebbleException;
|
||||||
|
import io.pebbletemplates.pebble.extension.AbstractExtension;
|
||||||
|
import io.pebbletemplates.pebble.template.PebbleTemplate;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.inject.Singleton;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
@@ -26,11 +30,6 @@ import java.util.*;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import io.micronaut.core.annotation.Nullable;
|
|
||||||
import jakarta.inject.Inject;
|
|
||||||
import jakarta.inject.Singleton;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class VariableRenderer {
|
public class VariableRenderer {
|
||||||
private static final Pattern RAW_PATTERN = Pattern.compile("\\{%[-]*\\s*raw\\s*[-]*%\\}(.*?)\\{%[-]*\\s*endraw\\s*[-]*%\\}");
|
private static final Pattern RAW_PATTERN = Pattern.compile("\\{%[-]*\\s*raw\\s*[-]*%\\}(.*?)\\{%[-]*\\s*endraw\\s*[-]*%\\}");
|
||||||
@@ -93,6 +92,10 @@ public class VariableRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String recursiveRender(String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
public String recursiveRender(String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||||
|
return recursiveRender(inline, variables, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String recursiveRender(String inline, Map<String, Object> variables, Boolean preprocessVariables) throws IllegalVariableEvaluationException {
|
||||||
if (inline == null) {
|
if (inline == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -111,6 +114,11 @@ public class VariableRenderer {
|
|||||||
return uuid;
|
return uuid;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// pre-process variables
|
||||||
|
if (preprocessVariables) {
|
||||||
|
variables = this.preprocessVariables(variables);
|
||||||
|
}
|
||||||
|
|
||||||
boolean isSame = false;
|
boolean isSame = false;
|
||||||
String current = "";
|
String current = "";
|
||||||
PebbleTemplate compiledTemplate;
|
PebbleTemplate compiledTemplate;
|
||||||
@@ -131,28 +139,43 @@ public class VariableRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Template template = handlebars.compileInline(currentTemplate);
|
Template template = handlebars.compileInline(currentTemplate);
|
||||||
current = template.apply(variables);
|
current = template.apply(variables);
|
||||||
} catch (HandlebarsException | IOException hbE) {
|
} catch (HandlebarsException | IOException hbE) {
|
||||||
throw new IllegalVariableEvaluationException(
|
throw new IllegalVariableEvaluationException(
|
||||||
"Pebble evaluation failed with '" + e.getMessage() + "' " +
|
"Pebble evaluation failed with '" + e.getMessage() + "' " +
|
||||||
"and Handlebars fallback failed also with '" + hbE.getMessage() + "'" ,
|
"and Handlebars fallback failed also with '" + hbE.getMessage() + "'",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
isSame = currentTemplate.equals(current);
|
isSame = currentTemplate.equals(current);
|
||||||
currentTemplate = current;
|
currentTemplate = current;
|
||||||
}
|
}
|
||||||
|
|
||||||
// post-process raw tags
|
// post-process raw tags
|
||||||
for(var entry: replacers.entrySet()) {
|
for (var entry : replacers.entrySet()) {
|
||||||
current = current.replace(entry.getKey(), entry.getValue());
|
current = current.replace(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> preprocessVariables(Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||||
|
Map<String, Object> currentVariables = variables;
|
||||||
|
Map<String, Object> previousVariables;
|
||||||
|
boolean isSame = false;
|
||||||
|
|
||||||
|
while (!isSame) {
|
||||||
|
previousVariables = currentVariables;
|
||||||
|
currentVariables = this.render(currentVariables, variables, false);
|
||||||
|
isSame = previousVariables.equals(currentVariables);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentVariables;
|
||||||
|
}
|
||||||
|
|
||||||
public IllegalVariableEvaluationException properPebbleException(PebbleException e) {
|
public IllegalVariableEvaluationException properPebbleException(PebbleException e) {
|
||||||
if (e instanceof AttributeNotFoundException current) {
|
if (e instanceof AttributeNotFoundException current) {
|
||||||
return new IllegalVariableEvaluationException(
|
return new IllegalVariableEvaluationException(
|
||||||
@@ -166,17 +189,27 @@ public class VariableRenderer {
|
|||||||
return new IllegalVariableEvaluationException(e);
|
return new IllegalVariableEvaluationException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// By default, render() will render variables
|
||||||
|
// so we keep the previous behavior
|
||||||
public String render(String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
public String render(String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||||
return this.recursiveRender(inline, variables);
|
return this.recursiveRender(inline, variables, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String render(String inline, Map<String, Object> variables, boolean preprocessVariables) throws IllegalVariableEvaluationException {
|
||||||
|
return this.recursiveRender(inline, variables, preprocessVariables);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default behavior
|
||||||
public Map<String, Object> render(Map<String, Object> in, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
public Map<String, Object> render(Map<String, Object> in, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||||
|
return render(in, variables, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> render(Map<String, Object> in, Map<String, Object> variables, boolean preprocessVariables) throws IllegalVariableEvaluationException {
|
||||||
Map<String, Object> map = new HashMap<>();
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
|
||||||
for (Map.Entry<String, Object> r : in.entrySet()) {
|
for (Map.Entry<String, Object> r : in.entrySet()) {
|
||||||
String key = this.render(r.getKey(), variables);
|
String key = this.render(r.getKey(), variables, preprocessVariables);
|
||||||
Object value = renderObject(r.getValue(), variables).orElse(r.getValue());
|
Object value = renderObject(r.getValue(), variables, preprocessVariables).orElse(r.getValue());
|
||||||
|
|
||||||
map.putIfAbsent(
|
map.putIfAbsent(
|
||||||
key,
|
key,
|
||||||
@@ -188,23 +221,25 @@ public class VariableRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
private Optional<Object> renderObject(Object object, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
private Optional<Object> renderObject(Object object, Map<String, Object> variables, boolean preprocessVariables) throws IllegalVariableEvaluationException {
|
||||||
if (object instanceof Map) {
|
if (object instanceof Map) {
|
||||||
return Optional.of(this.render((Map) object, variables));
|
return Optional.of(this.render((Map) object, variables, preprocessVariables));
|
||||||
} else if (object instanceof Collection) {
|
} else if (object instanceof Collection) {
|
||||||
return Optional.of(this.renderList((List) object, variables));
|
return Optional.of(this.renderList((List) object, variables, preprocessVariables));
|
||||||
} else if (object instanceof String) {
|
} else if (object instanceof String) {
|
||||||
return Optional.of(this.render((String) object, variables));
|
return Optional.of(this.render((String) object, variables, preprocessVariables));
|
||||||
|
} else if (object == null) {
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Optional.empty();
|
return Optional.of(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Object> renderList(List<Object> list, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
private List<Object> renderList(List<Object> list, Map<String, Object> variables, boolean preprocessVariables) throws IllegalVariableEvaluationException {
|
||||||
List<Object> result = new ArrayList<>();
|
List<Object> result = new ArrayList<>();
|
||||||
|
|
||||||
for (Object inline : list) {
|
for (Object inline : list) {
|
||||||
this.renderObject(inline, variables)
|
this.renderObject(inline, variables, preprocessVariables)
|
||||||
.ifPresent(result::add);
|
.ifPresent(result::add);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package io.kestra.core.runners.pebble.functions;
|
package io.kestra.core.runners.pebble.functions;
|
||||||
|
|
||||||
import io.kestra.core.storages.StorageInterface;
|
import io.kestra.core.storages.StorageInterface;
|
||||||
import io.kestra.core.tenant.TenantService;
|
|
||||||
import io.kestra.core.utils.Slugify;
|
import io.kestra.core.utils.Slugify;
|
||||||
import io.pebbletemplates.pebble.error.PebbleException;
|
import io.pebbletemplates.pebble.error.PebbleException;
|
||||||
import io.pebbletemplates.pebble.extension.Function;
|
import io.pebbletemplates.pebble.extension.Function;
|
||||||
@@ -11,6 +10,7 @@ import jakarta.inject.Inject;
|
|||||||
import jakarta.inject.Singleton;
|
import jakarta.inject.Singleton;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -47,7 +47,9 @@ public class ReadFileFunction implements Function {
|
|||||||
private String readFromNamespaceFile(EvaluationContext context, String path) throws IOException {
|
private String readFromNamespaceFile(EvaluationContext context, String path) throws IOException {
|
||||||
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
|
Map<String, String> flow = (Map<String, String>) context.getVariable("flow");
|
||||||
URI namespaceFile = URI.create(storageInterface.namespaceFilePrefix(flow.get("namespace")) + "/" + path);
|
URI namespaceFile = URI.create(storageInterface.namespaceFilePrefix(flow.get("namespace")) + "/" + path);
|
||||||
return new String(storageInterface.get(flow.get("tenantId"), namespaceFile).readAllBytes(), StandardCharsets.UTF_8);
|
try (InputStream inputStream = storageInterface.get(flow.get("tenantId"), namespaceFile)) {
|
||||||
|
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String readFromInternalStorageUri(EvaluationContext context, String path) throws IOException {
|
private String readFromInternalStorageUri(EvaluationContext context, String path) throws IOException {
|
||||||
@@ -69,7 +71,9 @@ public class ReadFileFunction implements Function {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
URI internalStorageFile = URI.create(path);
|
URI internalStorageFile = URI.create(path);
|
||||||
return new String(storageInterface.get(flow.get("tenantId"), internalStorageFile).readAllBytes(), StandardCharsets.UTF_8);
|
try (InputStream inputStream = storageInterface.get(flow.get("tenantId"), internalStorageFile)) {
|
||||||
|
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean validateFileUri(String namespace, String flowId, String executionId, String path) {
|
private boolean validateFileUri(String namespace, String flowId, String executionId, String path) {
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import io.kestra.core.runners.RunContext;
|
|||||||
import io.kestra.core.storages.StorageSplitInterface;
|
import io.kestra.core.storages.StorageSplitInterface;
|
||||||
import io.micronaut.core.convert.format.ReadableBytesTypeConverter;
|
import io.micronaut.core.convert.format.ReadableBytesTypeConverter;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ public interface StorageInterface {
|
|||||||
* @return true if the uri points to a file/object that exist in the internal storage.
|
* @return true if the uri points to a file/object that exist in the internal storage.
|
||||||
*/
|
*/
|
||||||
default boolean exists(String tenantId, URI uri) {
|
default boolean exists(String tenantId, URI uri) {
|
||||||
try {
|
try (InputStream ignored = get(tenantId, uri)){
|
||||||
get(tenantId, uri);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException ieo) {
|
} catch (IOException ieo) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -175,7 +175,10 @@ public class Flow extends Task implements ExecutableTask<Flow.Output> {
|
|||||||
|
|
||||||
taskRun = taskRun.withOutputs(builder.build().toMap());
|
taskRun = taskRun.withOutputs(builder.build().toMap());
|
||||||
|
|
||||||
taskRun = taskRun.withState(ExecutableUtils.guessState(execution, this.transmitFailed, State.Type.SUCCESS));
|
State.Type finalState = ExecutableUtils.guessState(execution, this.transmitFailed, State.Type.SUCCESS);
|
||||||
|
if (taskRun.getState().getCurrent() != finalState) {
|
||||||
|
taskRun = taskRun.withState(finalState);
|
||||||
|
}
|
||||||
|
|
||||||
return Optional.of(ExecutableUtils.workerTaskResult(taskRun));
|
return Optional.of(ExecutableUtils.workerTaskResult(taskRun));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ public abstract class AbstractState extends Task {
|
|||||||
|
|
||||||
|
|
||||||
protected Map<String, Object> get(RunContext runContext) throws IllegalVariableEvaluationException, IOException {
|
protected Map<String, Object> get(RunContext runContext) throws IllegalVariableEvaluationException, IOException {
|
||||||
InputStream taskStateFile = runContext.getTaskStateFile("tasks-states", runContext.render(this.name), this.namespace, this.taskrunValue);
|
try (InputStream taskStateFile = runContext.getTaskStateFile("tasks-states", runContext.render(this.name), this.namespace, this.taskrunValue)) {
|
||||||
|
return JacksonMapper.ofJson(false).readValue(taskStateFile, TYPE_REFERENCE);
|
||||||
return JacksonMapper.ofJson(false).readValue(taskStateFile, TYPE_REFERENCE);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Pair<URI, Map<String, Object>> merge(RunContext runContext, Map<String, Object> map) throws IllegalVariableEvaluationException, IOException {
|
protected Pair<URI, Map<String, Object>> merge(RunContext runContext, Map<String, Object> map) throws IllegalVariableEvaluationException, IOException {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import io.kestra.core.runners.RunContext;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -128,7 +129,9 @@ public class Concat extends Task implements RunnableTask<Concat.Output> {
|
|||||||
|
|
||||||
finalFiles.forEach(throwConsumer(s -> {
|
finalFiles.forEach(throwConsumer(s -> {
|
||||||
URI from = new URI(runContext.render(s));
|
URI from = new URI(runContext.render(s));
|
||||||
IOUtils.copyLarge(runContext.uriToInputStream(from), fileOutputStream);
|
try (InputStream inputStream = runContext.uriToInputStream(from)) {
|
||||||
|
IOUtils.copyLarge(inputStream, fileOutputStream);
|
||||||
|
}
|
||||||
|
|
||||||
if (separator != null) {
|
if (separator != null) {
|
||||||
IOUtils.copy(new ByteArrayInputStream(this.separator.getBytes()), fileOutputStream);
|
IOUtils.copy(new ByteArrayInputStream(this.separator.getBytes()), fileOutputStream);
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ public class FlowTopologyService {
|
|||||||
.filter(t -> t instanceof ExecutableTask)
|
.filter(t -> t instanceof ExecutableTask)
|
||||||
.map(t -> (ExecutableTask<?>) t)
|
.map(t -> (ExecutableTask<?>) t)
|
||||||
.anyMatch(t ->
|
.anyMatch(t ->
|
||||||
t.subflowId().namespace().equals(child.getNamespace()) && t.subflowId().flowId().equals(child.getId())
|
t.subflowId() != null && t.subflowId().namespace().equals(child.getNamespace()) && t.subflowId().flowId().equals(child.getId())
|
||||||
);
|
);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to detect flow task on namespace:'" + parent.getNamespace() + "', flowId:'" + parent.getId() + "'", e);
|
log.warn("Failed to detect flow task on namespace:'" + parent.getNamespace() + "', flowId:'" + parent.getId() + "'", e);
|
||||||
|
|||||||
25
core/src/main/resources/logback/ecs.xml
Normal file
25
core/src/main/resources/logback/ecs.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<included>
|
||||||
|
<appender name="CONSOLE_ECS_OUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<target>System.out</target>
|
||||||
|
<immediateFlush>true</immediateFlush>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>ERROR</level>
|
||||||
|
<onMatch>DENY</onMatch>
|
||||||
|
</filter>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>WARN</level>
|
||||||
|
<onMatch>DENY</onMatch>
|
||||||
|
</filter>
|
||||||
|
<encoder class="co.elastic.logging.logback.EcsEncoder" />
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="CONSOLE_ECS_ERR" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<target>System.err</target>
|
||||||
|
<immediateFlush>true</immediateFlush>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<level>WARN</level>
|
||||||
|
</filter>
|
||||||
|
<encoder class="co.elastic.logging.logback.EcsEncoder" />
|
||||||
|
</appender>
|
||||||
|
</included>
|
||||||
@@ -35,8 +35,8 @@ class FlowGraphTest extends AbstractMemoryRunnerTest {
|
|||||||
Flow flow = this.parse("flows/valids/return.yaml");
|
Flow flow = this.parse("flows/valids/return.yaml");
|
||||||
FlowGraph flowGraph = GraphUtils.flowGraph(flow, null);
|
FlowGraph flowGraph = GraphUtils.flowGraph(flow, null);
|
||||||
|
|
||||||
assertThat(flowGraph.getNodes().size(), is(5));
|
assertThat(flowGraph.getNodes().size(), is(6));
|
||||||
assertThat(flowGraph.getEdges().size(), is(4));
|
assertThat(flowGraph.getEdges().size(), is(5));
|
||||||
assertThat(flowGraph.getClusters().size(), is(0));
|
assertThat(flowGraph.getClusters().size(), is(0));
|
||||||
|
|
||||||
assertThat(((AbstractGraphTask) flowGraph.getNodes().get(2)).getTask().getId(), is("date"));
|
assertThat(((AbstractGraphTask) flowGraph.getNodes().get(2)).getTask().getId(), is("date"));
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
@Property(name = "kestra.tasks.tmp-dir.path", value = "/tmp/sub/dir/tmp/")
|
@Property(name = "kestra.tasks.tmp-dir.path", value = "/tmp/sub/dir/tmp/")
|
||||||
@@ -112,7 +113,7 @@ class RunContextTest extends AbstractMemoryRunnerTest {
|
|||||||
void variables() throws TimeoutException {
|
void variables() throws TimeoutException {
|
||||||
Execution execution = runnerUtils.runOne(null, "io.kestra.tests", "return");
|
Execution execution = runnerUtils.runOne(null, "io.kestra.tests", "return");
|
||||||
|
|
||||||
assertThat(execution.getTaskRunList(), hasSize(3));
|
assertThat(execution.getTaskRunList(), hasSize(4));
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
ZonedDateTime.from(ZonedDateTime.parse((String) execution.getTaskRunList().get(0).getOutputs().get("value"))),
|
ZonedDateTime.from(ZonedDateTime.parse((String) execution.getTaskRunList().get(0).getOutputs().get("value"))),
|
||||||
@@ -120,6 +121,7 @@ class RunContextTest extends AbstractMemoryRunnerTest {
|
|||||||
);
|
);
|
||||||
assertThat(execution.getTaskRunList().get(1).getOutputs().get("value"), is("task-id"));
|
assertThat(execution.getTaskRunList().get(1).getOutputs().get("value"), is("task-id"));
|
||||||
assertThat(execution.getTaskRunList().get(2).getOutputs().get("value"), is("return"));
|
assertThat(execution.getTaskRunList().get(2).getOutputs().get("value"), is("return"));
|
||||||
|
assertThat((String) execution.getTaskRunList().get(3).getOutputs().get("value"), containsString("toto"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
|||||||
import io.kestra.core.runners.VariableRenderer;
|
import io.kestra.core.runners.VariableRenderer;
|
||||||
import io.kestra.core.utils.Rethrow;
|
import io.kestra.core.utils.Rethrow;
|
||||||
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import jakarta.inject.Inject;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|||||||
@@ -105,8 +105,8 @@ public class ForEachItemCaseTest {
|
|||||||
assertThat(outputs.get("iterations"), notNullValue());
|
assertThat(outputs.get("iterations"), notNullValue());
|
||||||
Map<String, Integer> iterations = (Map<String, Integer>) outputs.get("iterations");
|
Map<String, Integer> iterations = (Map<String, Integer>) outputs.get("iterations");
|
||||||
assertThat(iterations.get("max"), is(3));
|
assertThat(iterations.get("max"), is(3));
|
||||||
assertThat(iterations.get("CREATED"), is(0));
|
assertThat(iterations.get("CREATED"), nullValue());// if we didn't wait we will only observe RUNNING and SUCCESS
|
||||||
assertThat(iterations.get("RUNNING"), nullValue()); // if we didn't wait we will only observe CREATED and SUCCESS
|
assertThat(iterations.get("RUNNING"), is(0));
|
||||||
assertThat(iterations.get("SUCCESS"), is(3));
|
assertThat(iterations.get("SUCCESS"), is(3));
|
||||||
|
|
||||||
// assert that not all subflows ran (depending on the speed of execution there can be some)
|
// assert that not all subflows ran (depending on the speed of execution there can be some)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ inputs:
|
|||||||
|
|
||||||
variables:
|
variables:
|
||||||
var-1: var-1
|
var-1: var-1
|
||||||
var-2: '{{ var-1 }}'
|
var-2: "{{ 'var-1' }}"
|
||||||
|
|
||||||
taskDefaults:
|
taskDefaults:
|
||||||
- type: io.kestra.core.tasks.log.Log
|
- type: io.kestra.core.tasks.log.Log
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
id: return
|
id: return
|
||||||
namespace: io.kestra.tests
|
namespace: io.kestra.tests
|
||||||
|
|
||||||
|
variables:
|
||||||
|
period: "{{ schedule.date ?? execution.startDate }}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- id: date
|
- id: date
|
||||||
type: io.kestra.core.tasks.debugs.Return
|
type: io.kestra.core.tasks.debugs.Return
|
||||||
@@ -10,4 +13,7 @@ tasks:
|
|||||||
format: "{{task.id}}"
|
format: "{{task.id}}"
|
||||||
- id: flow-id
|
- id: flow-id
|
||||||
type: io.kestra.core.tasks.debugs.Return
|
type: io.kestra.core.tasks.debugs.Return
|
||||||
format: "{{flow.id}}"
|
format: "{{flow.id}}"
|
||||||
|
- id: variables
|
||||||
|
type: io.kestra.core.tasks.debugs.Return
|
||||||
|
format: "{{ vars.period | replace({':': 'toto'}) }}"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
version=0.13.0
|
version=0.13.8
|
||||||
|
|
||||||
jacksonVersion=2.15.2
|
jacksonVersion=2.15.2
|
||||||
micronautVersion=3.10.1
|
micronautVersion=3.10.1
|
||||||
|
|||||||
@@ -61,14 +61,7 @@ public abstract class AbstractJdbcFlowRepository extends AbstractJdbcRepository
|
|||||||
} catch (DeserializationException e) {
|
} catch (DeserializationException e) {
|
||||||
try {
|
try {
|
||||||
JsonNode jsonNode = JdbcMapper.of().readTree(source);
|
JsonNode jsonNode = JdbcMapper.of().readTree(source);
|
||||||
return FlowWithException.builder()
|
return FlowWithException.from(jsonNode, e).orElseThrow(() -> e);
|
||||||
.id(jsonNode.get("id").asText())
|
|
||||||
.tenantId(jsonNode.get("tenant_id") != null ? jsonNode.get("tenant_id").asText() : null)
|
|
||||||
.namespace(jsonNode.get("namespace").asText())
|
|
||||||
.revision(jsonNode.get("revision").asInt())
|
|
||||||
.exception(e.getMessage())
|
|
||||||
.tasks(List.of())
|
|
||||||
.build();
|
|
||||||
} catch (JsonProcessingException ex) {
|
} catch (JsonProcessingException ex) {
|
||||||
throw new DeserializationException(ex, source);
|
throw new DeserializationException(ex, source);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ public class JdbcExecutor implements ExecutorInterface {
|
|||||||
|
|
||||||
// send a running worker task result to track running vs created status
|
// send a running worker task result to track running vs created status
|
||||||
if (workerTaskExecution.getTask().waitForExecution()) {
|
if (workerTaskExecution.getTask().waitForExecution()) {
|
||||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(State.Type.RUNNING));
|
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -426,7 +426,7 @@ public class JdbcExecutor implements ExecutorInterface {
|
|||||||
.ifPresent(workerTaskExecution -> {
|
.ifPresent(workerTaskExecution -> {
|
||||||
// If we didn't wait for the flow execution, the worker task execution has already been created by the Executor service.
|
// If we didn't wait for the flow execution, the worker task execution has already been created by the Executor service.
|
||||||
if (workerTaskExecution.getTask().waitForExecution()) {
|
if (workerTaskExecution.getTask().waitForExecution()) {
|
||||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(State.Type.RUNNING).withState(execution.getState().getCurrent()));
|
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(execution.getState().getCurrent()));
|
||||||
}
|
}
|
||||||
|
|
||||||
workerTaskExecutionStorage.delete(workerTaskExecution);
|
workerTaskExecutionStorage.delete(workerTaskExecution);
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ public abstract class JdbcRunnerTest {
|
|||||||
skipExecutionCaseTest.skipExecution();
|
skipExecutionCaseTest.skipExecution();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@RetryingTest(5)
|
||||||
void forEachItem() throws URISyntaxException, IOException, InterruptedException, TimeoutException {
|
void forEachItem() throws URISyntaxException, IOException, InterruptedException, TimeoutException {
|
||||||
forEachItemCaseTest.forEachItem();
|
forEachItemCaseTest.forEachItem();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ public class MemoryExecutor implements ExecutorInterface {
|
|||||||
|
|
||||||
// send a running worker task result to track running vs created status
|
// send a running worker task result to track running vs created status
|
||||||
if (workerTaskExecution.getTask().waitForExecution()) {
|
if (workerTaskExecution.getTask().waitForExecution()) {
|
||||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(State.Type.RUNNING));
|
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@ public class MemoryExecutor implements ExecutorInterface {
|
|||||||
|
|
||||||
// If we didn't wait for the flow execution, the worker task execution has already been created by the Executor service.
|
// If we didn't wait for the flow execution, the worker task execution has already been created by the Executor service.
|
||||||
if (workerTaskExecution.getTask().waitForExecution()) {
|
if (workerTaskExecution.getTask().waitForExecution()) {
|
||||||
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(State.Type.RUNNING).withState(execution.getState().getCurrent()));
|
sendWorkerTaskResultForWorkerTaskExecution(execution, workerTaskExecution, workerTaskExecution.getTaskRun().withState(execution.getState().getCurrent()));
|
||||||
}
|
}
|
||||||
|
|
||||||
WORKERTASKEXECUTIONS_WATCHER.remove(execution.getId());
|
WORKERTASKEXECUTIONS_WATCHER.remove(execution.getId());
|
||||||
|
|||||||
@@ -30,6 +30,31 @@ const extensionsToFetch = Object.entries(versionByExtensionIdToFetch).map(([exte
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// used to configure VSCode startup
|
// used to configure VSCode startup
|
||||||
|
const sidebarTabs = [
|
||||||
|
{"id": "workbench.view.explorer", "pinned": true, "visible": true, "order": 0},
|
||||||
|
{"id": "workbench.view.search", "pinned": true, "visible": true, "order": 1},
|
||||||
|
{"id": "workbench.view.scm", "pinned": false, "visible": false, "order": 2},
|
||||||
|
{"id": "workbench.view.debug", "pinned": false,"visible": false,"order": 3},
|
||||||
|
{"id": "workbench.view.extensions", "pinned": true,"visible": true,"order": 4},
|
||||||
|
{"id": "workbench.view.remote", "pinned": false,"visible": false,"order": 4},
|
||||||
|
{"id": "workbench.view.extension.test", "pinned": false,"visible": false,"order": 6},
|
||||||
|
{"id": "workbench.view.extension.references-view", "pinned": false,"visible": false,"order": 7},
|
||||||
|
{"id": "workbench.panel.chatSidebar", "pinned": false,"visible": false,"order": 100},
|
||||||
|
{"id": "userDataProfiles", "pinned": false, "visible": false},
|
||||||
|
{"id": "workbench.view.sync", "pinned": false,"visible": false},
|
||||||
|
{"id": "workbench.view.editSessions", "pinned": false, "visible": false}
|
||||||
|
];
|
||||||
|
|
||||||
|
const bottomBarTabs = [
|
||||||
|
{"id":"workbench.panel.markers", "pinned": false,"visible": false,"order": 0},
|
||||||
|
{"id":"workbench.panel.output", "pinned": false,"visible": false,"order": 1},
|
||||||
|
{"id":"workbench.panel.repl", "pinned": false,"visible": false,"order": 2},
|
||||||
|
{"id":"terminal", "pinned": false,"visible": false,"order": 3},
|
||||||
|
{"id":"workbench.panel.testResults", "pinned": false,"visible": false,"order": 3},
|
||||||
|
{"id":"~remote.forwardedPortsContainer", "pinned": false,"visible": false,"order": 5},
|
||||||
|
{"id":"refactorPreview", "pinned": false,"visible": false}
|
||||||
|
];
|
||||||
|
|
||||||
window.product = {
|
window.product = {
|
||||||
productConfiguration: {
|
productConfiguration: {
|
||||||
nameShort: "Kestra VSCode",
|
nameShort: "Kestra VSCode",
|
||||||
@@ -77,5 +102,17 @@ window.product = {
|
|||||||
"workbench.colorTheme": THEME === "dark" ? "Sweet Dracula" : "Default Light Modern",
|
"workbench.colorTheme": THEME === "dark" ? "Sweet Dracula" : "Default Light Modern",
|
||||||
// provide the Kestra root URL to extension
|
// provide the Kestra root URL to extension
|
||||||
"kestra.api.url": KESTRA_API_URL
|
"kestra.api.url": KESTRA_API_URL
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
name: "Kestra VSCode",
|
||||||
|
contents: JSON.stringify({
|
||||||
|
globalState: JSON.stringify({
|
||||||
|
"storage": {
|
||||||
|
"workbench.activity.pinnedViewlets2": sidebarTabs,
|
||||||
|
"workbench.activity.showAccounts": "false",
|
||||||
|
"workbench.panel.pinnedPanels": bottomBarTabs
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -447,7 +447,7 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onDisplayColumnsChange(event) {
|
onDisplayColumnsChange(event) {
|
||||||
localStorage.setItem("displayExecutionsColumns", event);
|
localStorage.setItem(this.storageKey, event);
|
||||||
this.displayColumns = event;
|
this.displayColumns = event;
|
||||||
},
|
},
|
||||||
displayColumn(column) {
|
displayColumn(column) {
|
||||||
@@ -608,4 +608,4 @@
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
executionId: this.executionId,
|
executionId: this.executionId,
|
||||||
path: this.value,
|
path: this.value,
|
||||||
maxRows: this.maxPreview,
|
maxRows: this.maxPreview,
|
||||||
encoding: this.encoding
|
encoding: this.encoding.value
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isPreviewOpen = true;
|
this.isPreviewOpen = true;
|
||||||
|
|||||||
@@ -456,15 +456,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bottom-right {
|
.bottom-right {
|
||||||
bottom: var(--spacer);
|
bottom: 0px;
|
||||||
right: var(--spacer);
|
right: 0px;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
gap: calc(var(--spacer) / 2);
|
//gap: calc(var(--spacer) / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
121
ui/src/components/inputs/EditorButtons.vue
Normal file
121
ui/src/components/inputs/EditorButtons.vue
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<div class="to-action-button">
|
||||||
|
<div v-if="isAllowedEdit || canDelete" class="mx-2">
|
||||||
|
<el-dropdown>
|
||||||
|
<el-button type="default" :disabled="isReadOnly">
|
||||||
|
<DotsVertical title=""/>
|
||||||
|
{{ $t("actions") }}
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu class="m-dropdown-menu">
|
||||||
|
<el-dropdown-item
|
||||||
|
v-if="!isCreating && canDelete"
|
||||||
|
:icon="Delete"
|
||||||
|
size="large"
|
||||||
|
@click="forwardEvent('delete-flow', $event)"
|
||||||
|
>
|
||||||
|
{{ $t("delete") }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
|
||||||
|
<el-dropdown-item
|
||||||
|
v-if="!isCreating"
|
||||||
|
:icon="ContentCopy"
|
||||||
|
size="large"
|
||||||
|
@click="forwardEvent('copy', $event)"
|
||||||
|
>
|
||||||
|
{{ $t("copy") }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
v-if="isAllowedEdit"
|
||||||
|
:icon="Exclamation"
|
||||||
|
size="large"
|
||||||
|
@click="forwardEvent('open-new-error', null)"
|
||||||
|
:disabled="!flowHaveTasks"
|
||||||
|
>
|
||||||
|
{{ $t("add global error handler") }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
v-if="isAllowedEdit"
|
||||||
|
:icon="LightningBolt"
|
||||||
|
size="large"
|
||||||
|
@click="forwardEvent('open-new-trigger', null)"
|
||||||
|
:disabled="!flowHaveTasks"
|
||||||
|
>
|
||||||
|
{{ $t("add trigger") }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
v-if="isAllowedEdit"
|
||||||
|
:icon="FileEdit"
|
||||||
|
size="large"
|
||||||
|
@click="forwardEvent('open-edit-metadata', null)"
|
||||||
|
>
|
||||||
|
{{ $t("edit metadata") }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-button
|
||||||
|
:icon="ContentSave"
|
||||||
|
@click="forwardEvent('save', $event)"
|
||||||
|
v-if="isAllowedEdit"
|
||||||
|
:type="flowError ? 'danger' : 'primary'"
|
||||||
|
:disabled="!haveChange && !isCreating"
|
||||||
|
class="edit-flow-save-button"
|
||||||
|
>
|
||||||
|
{{ $t("save") }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import DotsVertical from "vue-material-design-icons/DotsVertical.vue";
|
||||||
|
import Delete from "vue-material-design-icons/Delete.vue";
|
||||||
|
import ContentCopy from "vue-material-design-icons/ContentCopy.vue";
|
||||||
|
import Exclamation from "vue-material-design-icons/Exclamation.vue";
|
||||||
|
import LightningBolt from "vue-material-design-icons/LightningBolt.vue";
|
||||||
|
import FileEdit from "vue-material-design-icons/FileEdit.vue";
|
||||||
|
import ContentSave from "vue-material-design-icons/ContentSave.vue";
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
isCreating: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
canDelete: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isAllowedEdit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
haveChange: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
flowHaveTasks: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
flowError: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
forwardEvent(type, event) {
|
||||||
|
this.$emit(type, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
import {editorViewTypes} from "../../utils/constants";
|
import {editorViewTypes} from "../../utils/constants";
|
||||||
import Utils from "@kestra-io/ui-libs/src/utils/Utils";
|
import Utils from "@kestra-io/ui-libs/src/utils/Utils";
|
||||||
import {apiUrl} from "override/utils/route";
|
import {apiUrl} from "override/utils/route";
|
||||||
|
import EditorButtons from "./EditorButtons.vue";
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const router = getCurrentInstance().appContext.config.globalProperties.$router;
|
const router = getCurrentInstance().appContext.config.globalProperties.$router;
|
||||||
@@ -675,75 +676,22 @@
|
|||||||
<ValidationError ref="validationDomElement" tooltip-placement="bottom-start" size="small" class="ms-2" :error="flowError" :warnings="flowWarnings" />
|
<ValidationError ref="validationDomElement" tooltip-placement="bottom-start" size="small" class="ms-2" :error="flowError" :warnings="flowWarnings" />
|
||||||
</template>
|
</template>
|
||||||
<template #buttons>
|
<template #buttons>
|
||||||
<ul>
|
<EditorButtons
|
||||||
<li v-if="isAllowedEdit || canDelete">
|
v-if="![editorViewTypes.TOPOLOGY, editorViewTypes.SOURCE_TOPOLOGY].includes(viewType)"
|
||||||
<el-dropdown>
|
:is-creating="props.isCreating"
|
||||||
<el-button type="default" :disabled="isReadOnly">
|
:is-read-only="props.isReadOnly"
|
||||||
<DotsVertical title="" />
|
:can-delete="canDelete"
|
||||||
{{ $t("actions") }}
|
:is-allowed-edit="isAllowedEdit"
|
||||||
</el-button>
|
:have-change="haveChange"
|
||||||
<template #dropdown>
|
:flow-have-tasks="flowHaveTasks()"
|
||||||
<el-dropdown-menu class="m-dropdown-menu">
|
:flow-error="flowError"
|
||||||
<el-dropdown-item
|
@delete-flow="deleteFlow"
|
||||||
v-if="!props.isCreating && canDelete"
|
@save="save"
|
||||||
:icon="Delete"
|
@copy="() => router.push({name: 'flows/create', query: {copy: true}})"
|
||||||
size="large"
|
@open-new-error="isNewErrorOpen = true;"
|
||||||
@click="deleteFlow"
|
@open-new-trigger="isNewTriggerOpen = true;"
|
||||||
>
|
@open-edit-metadata="isEditMetadataOpen = true;"
|
||||||
{{ $t("delete") }}
|
/>
|
||||||
</el-dropdown-item>
|
|
||||||
|
|
||||||
<el-dropdown-item
|
|
||||||
v-if="!props.isCreating"
|
|
||||||
:icon="ContentCopy"
|
|
||||||
size="large"
|
|
||||||
@click="() => router.push({name: 'flows/create', query: {copy: true}})"
|
|
||||||
>
|
|
||||||
{{ $t("copy") }}
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item
|
|
||||||
v-if="isAllowedEdit"
|
|
||||||
:icon="Exclamation"
|
|
||||||
size="large"
|
|
||||||
@click="isNewErrorOpen = true;"
|
|
||||||
:disabled="!flowHaveTasks()"
|
|
||||||
>
|
|
||||||
{{ $t("add global error handler") }}
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item
|
|
||||||
v-if="isAllowedEdit"
|
|
||||||
:icon="LightningBolt"
|
|
||||||
size="large"
|
|
||||||
@click="isNewTriggerOpen = true;"
|
|
||||||
:disabled="!flowHaveTasks()"
|
|
||||||
>
|
|
||||||
{{ $t("add trigger") }}
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item
|
|
||||||
v-if="isAllowedEdit"
|
|
||||||
:icon="FileEdit"
|
|
||||||
size="large"
|
|
||||||
@click="isEditMetadataOpen = true;"
|
|
||||||
>
|
|
||||||
{{ $t("edit metadata") }}
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<el-button
|
|
||||||
:icon="ContentSave"
|
|
||||||
@click="save"
|
|
||||||
v-if="isAllowedEdit"
|
|
||||||
:type="flowError ? 'danger' : 'primary'"
|
|
||||||
:disabled="!haveChange && !isCreating"
|
|
||||||
class="edit-flow-save-button"
|
|
||||||
>
|
|
||||||
{{ $t("save") }}
|
|
||||||
</el-button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
</template>
|
||||||
</editor>
|
</editor>
|
||||||
<div class="slider" @mousedown="dragEditor" v-if="combinedEditor" />
|
<div class="slider" @mousedown="dragEditor" v-if="combinedEditor" />
|
||||||
@@ -859,6 +807,22 @@
|
|||||||
class="to-topology-button"
|
class="to-topology-button"
|
||||||
@switch-view="switchViewType"
|
@switch-view="switchViewType"
|
||||||
/>
|
/>
|
||||||
|
<EditorButtons
|
||||||
|
v-if="[editorViewTypes.TOPOLOGY, editorViewTypes.SOURCE_TOPOLOGY].includes(viewType)"
|
||||||
|
:is-creating="props.isCreating"
|
||||||
|
:is-read-only="props.isReadOnly"
|
||||||
|
:can-delete="canDelete"
|
||||||
|
:is-allowed-edit="isAllowedEdit"
|
||||||
|
:have-change="haveChange"
|
||||||
|
:flow-have-tasks="flowHaveTasks()"
|
||||||
|
:flow-error="flowError"
|
||||||
|
@delete-flow="deleteFlow"
|
||||||
|
@save="save"
|
||||||
|
@copy="() => router.push({name: 'flows/create', query: {copy: true}})"
|
||||||
|
@open-new-error="isNewErrorOpen = true;"
|
||||||
|
@open-new-trigger="isNewTriggerOpen = true;"
|
||||||
|
@open-edit-metadata="isEditMetadataOpen = true;"
|
||||||
|
/>
|
||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -879,6 +843,13 @@
|
|||||||
right: 45px;
|
right: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.to-action-button {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
right: 45px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.editor-combined {
|
.editor-combined {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
:allow-auto-expand-subflows="false"
|
:allow-auto-expand-subflows="false"
|
||||||
:target-execution-id="currentTaskRun.outputs.executionId"
|
:target-execution-id="currentTaskRun.outputs.executionId"
|
||||||
:class="$el.classList.contains('even') ? '' : 'even'"
|
:class="$el.classList.contains('even') ? '' : 'even'"
|
||||||
|
:show-progress-bar="showProgressBar"
|
||||||
|
:show-logs="showLogs"
|
||||||
/>
|
/>
|
||||||
</DynamicScrollerItem>
|
</DynamicScrollerItem>
|
||||||
</template>
|
</template>
|
||||||
@@ -315,6 +317,16 @@
|
|||||||
this.logsWithIndexByAttemptUid[this.attemptUid(taskRun.id, this.selectedAttemptNumberByTaskRunId[taskRun.id])])) &&
|
this.logsWithIndexByAttemptUid[this.attemptUid(taskRun.id, this.selectedAttemptNumberByTaskRunId[taskRun.id])])) &&
|
||||||
this.showLogs
|
this.showLogs
|
||||||
},
|
},
|
||||||
|
followExecution(executionId) {
|
||||||
|
this.$store
|
||||||
|
.dispatch("execution/followExecution", {id: executionId})
|
||||||
|
.then(sse => {
|
||||||
|
this.executionSSE = sse;
|
||||||
|
this.executionSSE.onmessage = async (event) => {
|
||||||
|
this.followedExecution = JSON.parse(event.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
followLogs(executionId) {
|
followLogs(executionId) {
|
||||||
this.$store
|
this.$store
|
||||||
.dispatch("execution/followLogs", {id: executionId})
|
.dispatch("execution/followLogs", {id: executionId})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<sidebar-menu
|
<sidebar-menu
|
||||||
id="side-menu"
|
id="side-menu"
|
||||||
:menu="menu"
|
:menu="localMenu"
|
||||||
@update:collapsed="onToggleCollapse"
|
@update:collapsed="onToggleCollapse"
|
||||||
width="268px"
|
width="268px"
|
||||||
:collapsed="collapsed"
|
:collapsed="collapsed"
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
import TimerCogOutline from "vue-material-design-icons/TimerCogOutline.vue";
|
import TimerCogOutline from "vue-material-design-icons/TimerCogOutline.vue";
|
||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
import AccountHardHatOutline from "vue-material-design-icons/AccountHardHatOutline.vue";
|
import AccountHardHatOutline from "vue-material-design-icons/AccountHardHatOutline.vue";
|
||||||
|
import {shallowRef} from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -54,6 +55,16 @@
|
|||||||
},
|
},
|
||||||
emits: ["menu-collapse"],
|
emits: ["menu-collapse"],
|
||||||
methods: {
|
methods: {
|
||||||
|
flattenMenu(menu) {
|
||||||
|
return menu.reduce((acc, item) => {
|
||||||
|
if (item.child) {
|
||||||
|
acc.push(...this.flattenMenu(item.child));
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.push(item);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
},
|
||||||
onToggleCollapse(folded) {
|
onToggleCollapse(folded) {
|
||||||
this.collapsed = folded;
|
this.collapsed = folded;
|
||||||
localStorage.setItem("menuCollapsed", folded ? "true" : "false");
|
localStorage.setItem("menuCollapsed", folded ? "true" : "false");
|
||||||
@@ -71,7 +82,7 @@
|
|||||||
r.class = "vsm--link_active";
|
r.class = "vsm--link_active";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.child && r.child.some(c => this.$route.path.startsWith(c.href))) {
|
if (r.child && r.child.some(c => this.$route.path.startsWith(c.href) || c.routes?.includes(this.$route.name))) {
|
||||||
r.class = "vsm--link_active";
|
r.class = "vsm--link_active";
|
||||||
r.child = this.disabledCurrentRoute(r.child);
|
r.child = this.disabledCurrentRoute(r.child);
|
||||||
}
|
}
|
||||||
@@ -88,7 +99,7 @@
|
|||||||
href: {name: "home"},
|
href: {name: "home"},
|
||||||
title: this.$t("homeDashboard.title"),
|
title: this.$t("homeDashboard.title"),
|
||||||
icon: {
|
icon: {
|
||||||
element: ViewDashboardVariantOutline,
|
element: shallowRef(ViewDashboardVariantOutline),
|
||||||
class: "menu-icon",
|
class: "menu-icon",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -96,7 +107,7 @@
|
|||||||
href: {name: "editor"},
|
href: {name: "editor"},
|
||||||
title: this.$t("editor"),
|
title: this.$t("editor"),
|
||||||
icon: {
|
icon: {
|
||||||
element: FolderEditOutline,
|
element: shallowRef(FolderEditOutline),
|
||||||
class: "menu-icon",
|
class: "menu-icon",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -105,7 +116,7 @@
|
|||||||
routes: this.routeStartWith("flows"),
|
routes: this.routeStartWith("flows"),
|
||||||
title: this.$t("flows"),
|
title: this.$t("flows"),
|
||||||
icon: {
|
icon: {
|
||||||
element: FileTreeOutline,
|
element: shallowRef(FileTreeOutline),
|
||||||
class: "menu-icon",
|
class: "menu-icon",
|
||||||
},
|
},
|
||||||
exact: false,
|
exact: false,
|
||||||
@@ -115,7 +126,7 @@
|
|||||||
routes: this.routeStartWith("templates"),
|
routes: this.routeStartWith("templates"),
|
||||||
title: this.$t("templates"),
|
title: this.$t("templates"),
|
||||||
icon: {
|
icon: {
|
||||||
element: ContentCopy,
|
element: shallowRef(ContentCopy),
|
||||||
class: "menu-icon",
|
class: "menu-icon",
|
||||||
},
|
},
|
||||||
hidden: !this.configs.isTemplateEnabled
|
hidden: !this.configs.isTemplateEnabled
|
||||||
@@ -125,7 +136,7 @@
|
|||||||
routes: this.routeStartWith("executions"),
|
routes: this.routeStartWith("executions"),
|
||||||
title: this.$t("executions"),
|
title: this.$t("executions"),
|
||||||
icon: {
|
icon: {
|
||||||
element: TimelineClockOutline,
|
element: shallowRef(TimelineClockOutline),
|
||||||
class: "menu-icon"
|
class: "menu-icon"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -134,7 +145,7 @@
|
|||||||
routes: this.routeStartWith("taskruns"),
|
routes: this.routeStartWith("taskruns"),
|
||||||
title: this.$t("taskruns"),
|
title: this.$t("taskruns"),
|
||||||
icon: {
|
icon: {
|
||||||
element: TimelineTextOutline,
|
element: shallowRef(TimelineTextOutline),
|
||||||
class: "menu-icon"
|
class: "menu-icon"
|
||||||
},
|
},
|
||||||
hidden: !this.configs.isTaskRunEnabled
|
hidden: !this.configs.isTaskRunEnabled
|
||||||
@@ -144,7 +155,7 @@
|
|||||||
routes: this.routeStartWith("logs"),
|
routes: this.routeStartWith("logs"),
|
||||||
title: this.$t("logs"),
|
title: this.$t("logs"),
|
||||||
icon: {
|
icon: {
|
||||||
element: NotebookOutline,
|
element: shallowRef(NotebookOutline),
|
||||||
class: "menu-icon"
|
class: "menu-icon"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -153,7 +164,7 @@
|
|||||||
routes: this.routeStartWith("blueprints"),
|
routes: this.routeStartWith("blueprints"),
|
||||||
title: this.$t("blueprints.title"),
|
title: this.$t("blueprints.title"),
|
||||||
icon: {
|
icon: {
|
||||||
element: Ballot,
|
element: shallowRef(Ballot),
|
||||||
class: "menu-icon"
|
class: "menu-icon"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -161,7 +172,7 @@
|
|||||||
title: this.$t("administration"),
|
title: this.$t("administration"),
|
||||||
routes: this.routeStartWith("admin"),
|
routes: this.routeStartWith("admin"),
|
||||||
icon: {
|
icon: {
|
||||||
element: AccountSupervisorOutline,
|
element: shallowRef(AccountSupervisorOutline),
|
||||||
class: "menu-icon"
|
class: "menu-icon"
|
||||||
},
|
},
|
||||||
child: [
|
child: [
|
||||||
@@ -170,7 +181,7 @@
|
|||||||
routes: this.routeStartWith("admin/triggers"),
|
routes: this.routeStartWith("admin/triggers"),
|
||||||
title: this.$t("triggers"),
|
title: this.$t("triggers"),
|
||||||
icon: {
|
icon: {
|
||||||
element: TimerCogOutline,
|
element: shallowRef(TimerCogOutline),
|
||||||
class: "menu-icon"
|
class: "menu-icon"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -179,7 +190,7 @@
|
|||||||
routes: this.routeStartWith("admin/workers"),
|
routes: this.routeStartWith("admin/workers"),
|
||||||
title: this.$t("workers"),
|
title: this.$t("workers"),
|
||||||
icon: {
|
icon: {
|
||||||
element: AccountHardHatOutline,
|
element: shallowRef(AccountHardHatOutline),
|
||||||
class: "menu-icon"
|
class: "menu-icon"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -190,41 +201,54 @@
|
|||||||
routes: this.routeStartWith("admin/settings"),
|
routes: this.routeStartWith("admin/settings"),
|
||||||
title: this.$t("settings"),
|
title: this.$t("settings"),
|
||||||
icon: {
|
icon: {
|
||||||
element: CogOutline,
|
element: shallowRef(CogOutline),
|
||||||
class: "menu-icon"
|
class: "menu-icon"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
expandParentIfNeeded() {
|
expandParentIfNeeded() {
|
||||||
document.querySelectorAll(".vsm--link_level-1.vsm--link_active:not(.vsm--link_open)[aria-haspopup]").forEach(e => e.click());
|
document.querySelectorAll(".vsm--link.vsm--link_level-1.vsm--link_active:not(.vsm--link_open)[aria-haspopup]").forEach(e => {
|
||||||
|
e.click()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updated() {
|
||||||
|
// Required here because in mounted() the menu is not yet rendered
|
||||||
|
this.expandParentIfNeeded();
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
menu: {
|
menu: {
|
||||||
handler() {
|
handler(newVal, oldVal) {
|
||||||
this.$el.querySelectorAll(".vsm--item span").forEach(e => {
|
// Check if the active menu item has changed, if yes then update the menu
|
||||||
//empty icon name on mouseover
|
if (JSON.stringify(this.flattenMenu(newVal).map(e => e.class?.includes("vsm--link_active") ?? false)) !==
|
||||||
e.setAttribute("title", "")
|
JSON.stringify(this.flattenMenu(oldVal).map(e => e.class?.includes("vsm--link_active") ?? false))) {
|
||||||
});
|
this.localMenu = newVal;
|
||||||
this.expandParentIfNeeded();
|
this.$el.querySelectorAll(".vsm--item span").forEach(e => {
|
||||||
|
//empty icon name on mouseover
|
||||||
|
e.setAttribute("title", "")
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
flush: 'post'
|
flush: "post",
|
||||||
}
|
deep: true
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
collapsed: localStorage.getItem("menuCollapsed") === "true",
|
collapsed: localStorage.getItem("menuCollapsed") === "true",
|
||||||
|
localMenu: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState("misc", ["configs"]),
|
...
|
||||||
|
mapState("misc", ["configs"]),
|
||||||
menu() {
|
menu() {
|
||||||
return this.disabledCurrentRoute(this.generateMenu());
|
return this.disabledCurrentRoute(this.generateMenu());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.expandParentIfNeeded();
|
this.localMenu = this.menu;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {apiUrl} from "override/utils/route";
|
import {apiUrlWithoutTenants} from "override/utils/route";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
findAll(_, __) {
|
findAll(_, __) {
|
||||||
return this.$http.get(`${apiUrl(this)}/workers`).then(response => {
|
return this.$http.get(`${apiUrlWithoutTenants(this)}/workers`).then(response => {
|
||||||
return response.data;
|
return response.data;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,11 +413,6 @@ form.ks-horizontal {
|
|||||||
|
|
||||||
&.is-dark {
|
&.is-dark {
|
||||||
color: var(--bs-gray-100);
|
color: var(--bs-gray-100);
|
||||||
html:not(.dark) & {
|
|
||||||
* {
|
|
||||||
color: var(--bs-gray-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: var(--bs-gray-900);
|
background: var(--bs-gray-900);
|
||||||
border: 1px solid var(--bs-border-color);
|
border: 1px solid var(--bs-border-color);
|
||||||
|
|||||||
@@ -1009,12 +1009,11 @@ public class ExecutionController {
|
|||||||
@Parameter(description = "The execution id") @PathVariable String executionId,
|
@Parameter(description = "The execution id") @PathVariable String executionId,
|
||||||
@Parameter(description = "The internal storage uri") @QueryValue URI path,
|
@Parameter(description = "The internal storage uri") @QueryValue URI path,
|
||||||
@Parameter(description = "The max row returns") @QueryValue @Nullable Integer maxRows,
|
@Parameter(description = "The max row returns") @QueryValue @Nullable Integer maxRows,
|
||||||
@Parameter(description = "The file encoding as Java charset name. Defaults to UTF-8", example = "ISO-8859-1") @QueryValue @Nullable String encoding
|
@Parameter(description = "The file encoding as Java charset name. Defaults to UTF-8", example = "ISO-8859-1") @QueryValue(defaultValue = "UTF-8") String encoding
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
this.validateFile(executionId, path, "/api/v1/executions/{executionId}/file?path=" + path);
|
this.validateFile(executionId, path, "/api/v1/executions/{executionId}/file?path=" + path);
|
||||||
|
|
||||||
String extension = FilenameUtils.getExtension(path.toString());
|
String extension = FilenameUtils.getExtension(path.toString());
|
||||||
InputStream fileStream = storageInterface.get(tenantService.resolveTenant(), path);
|
|
||||||
Optional<Charset> charset;
|
Optional<Charset> charset;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1023,13 +1022,15 @@ public class ExecutionController {
|
|||||||
throw new IllegalArgumentException("Unable to preview using encoding '" + encoding + "'");
|
throw new IllegalArgumentException("Unable to preview using encoding '" + encoding + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
FileRender fileRender = FileRenderBuilder.of(
|
try (InputStream fileStream = storageInterface.get(tenantService.resolveTenant(), path)){
|
||||||
extension,
|
FileRender fileRender = FileRenderBuilder.of(
|
||||||
fileStream,
|
extension,
|
||||||
charset,
|
fileStream,
|
||||||
maxRows == null ? this.initialPreviewRows : (maxRows > this.maxPreviewRows ? this.maxPreviewRows : maxRows)
|
charset,
|
||||||
);
|
maxRows == null ? this.initialPreviewRows : (maxRows > this.maxPreviewRows ? this.maxPreviewRows : maxRows)
|
||||||
|
);
|
||||||
|
|
||||||
return HttpResponse.ok(fileRender);
|
return HttpResponse.ok(fileRender);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,9 @@ public class NamespaceFileController {
|
|||||||
) throws IOException, URISyntaxException {
|
) throws IOException, URISyntaxException {
|
||||||
ensureWritableFile(path);
|
ensureWritableFile(path);
|
||||||
|
|
||||||
storageInterface.put(tenantService.resolveTenant(), toNamespacedStorageUri(namespace, path), new BufferedInputStream(fileContent.getInputStream()));
|
try(BufferedInputStream inputStream = new BufferedInputStream(fileContent.getInputStream())) {
|
||||||
|
storageInterface.put(tenantService.resolveTenant(), toNamespacedStorageUri(namespace, path), inputStream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExecuteOn(TaskExecutors.IO)
|
@ExecuteOn(TaskExecutors.IO)
|
||||||
|
|||||||
Reference in New Issue
Block a user