mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-25 11:12:12 -05:00
Compare commits
60 Commits
run-develo
...
v0.15.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99d35b7d6d | ||
|
|
b6d6e83ed4 | ||
|
|
e32a390924 | ||
|
|
aca092050d | ||
|
|
324fc73f82 | ||
|
|
48813b1537 | ||
|
|
2c9b21cb0c | ||
|
|
581ba70743 | ||
|
|
1fd768e122 | ||
|
|
12808e7e34 | ||
|
|
ae3c5045fe | ||
|
|
196d3078d8 | ||
|
|
ed7b2f1b39 | ||
|
|
c84bf39365 | ||
|
|
152b6da8b6 | ||
|
|
37accb5bd7 | ||
|
|
a14a5de0bb | ||
|
|
99145933db | ||
|
|
7e785297f0 | ||
|
|
c7b0103226 | ||
|
|
ffc8f3cac5 | ||
|
|
b73625d666 | ||
|
|
f76e55bafb | ||
|
|
686721c7ee | ||
|
|
9f0e3bb14a | ||
|
|
1dc11fb44e | ||
|
|
b2e41223b4 | ||
|
|
579d30160c | ||
|
|
bfe3548bd7 | ||
|
|
eebcc3a010 | ||
|
|
6d65dda19a | ||
|
|
6768950515 | ||
|
|
93bbc83b5c | ||
|
|
7fbfbe1d00 | ||
|
|
f53f788100 | ||
|
|
8e3a9e4380 | ||
|
|
cb82e9f08e | ||
|
|
9deb02983c | ||
|
|
9b1939e129 | ||
|
|
4dfcbca7de | ||
|
|
103320e348 | ||
|
|
410093cefc | ||
|
|
bd936125bd | ||
|
|
68ded87434 | ||
|
|
d76807235f | ||
|
|
63708a79e3 | ||
|
|
41c0018d4b | ||
|
|
2cbd86c4d1 | ||
|
|
49b64fa853 | ||
|
|
95113c5e76 | ||
|
|
1e5e300974 | ||
|
|
f69dc3c835 | ||
|
|
651c7bf589 | ||
|
|
7878bcc281 | ||
|
|
ee059106b2 | ||
|
|
4d2728a3f6 | ||
|
|
87f7cde742 | ||
|
|
2e18c87907 | ||
|
|
58352411b5 | ||
|
|
438619dd8c |
@@ -37,11 +37,17 @@ micronaut:
|
||||
- /ui/.+
|
||||
- /health
|
||||
- /prometheus
|
||||
http-version: HTTP_1_1
|
||||
caches:
|
||||
default:
|
||||
maximum-weight: 10485760
|
||||
|
||||
http:
|
||||
client:
|
||||
read-idle-timeout: 60s
|
||||
connect-timeout: 30s
|
||||
read-timeout: 60s
|
||||
http-version: HTTP_1_1
|
||||
services:
|
||||
api:
|
||||
url: https://api.kestra.io
|
||||
|
||||
@@ -26,6 +26,10 @@ public abstract class AbstractClassDocumentation<T> {
|
||||
protected Map<String, Object> defs = new TreeMap<>();
|
||||
protected Map<String, Object> inputs = new TreeMap<>();
|
||||
protected Map<String, Object> propertiesSchema;
|
||||
private final List<String> defsExclusions = List.of(
|
||||
"io.kestra.core.models.conditions.Condition",
|
||||
"io.kestra.core.models.conditions.ScheduleCondition"
|
||||
);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected AbstractClassDocumentation(JsonSchemaGenerator jsonSchemaGenerator, Class<? extends T> cls, Class<T> baseCls) {
|
||||
@@ -36,6 +40,7 @@ public abstract class AbstractClassDocumentation<T> {
|
||||
|
||||
if (this.propertiesSchema.containsKey("$defs")) {
|
||||
this.defs.putAll((Map<String, Object>) this.propertiesSchema.get("$defs"));
|
||||
defsExclusions.forEach(this.defs::remove);
|
||||
this.propertiesSchema.remove("$defs");
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ public class EncryptionService {
|
||||
* The IV is concatenated at the beginning of the string.
|
||||
*/
|
||||
public static String encrypt(String key, String plainText) throws GeneralSecurityException {
|
||||
if (plainText == null || plainText.isEmpty()) {
|
||||
return plainText;
|
||||
}
|
||||
|
||||
byte[] keyBytes = Base64.getDecoder().decode(key);
|
||||
SecretKey secretKey = new SecretKeySpec(keyBytes, KEY_ALGORITHM);
|
||||
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
@@ -43,6 +47,10 @@ public class EncryptionService {
|
||||
* The IV is recovered from the beginning of the string.
|
||||
*/
|
||||
public static String decrypt(String key, String cipherText) throws GeneralSecurityException {
|
||||
if (cipherText == null || cipherText.isEmpty()) {
|
||||
return cipherText;
|
||||
}
|
||||
|
||||
byte[] keyBytes = Base64.getDecoder().decode(key);
|
||||
SecretKey secretKey = new SecretKeySpec(keyBytes, KEY_ALGORITHM);
|
||||
byte[] input = Base64.getDecoder().decode(cipherText);
|
||||
|
||||
@@ -189,7 +189,7 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
|
||||
public List<TaskRun> findTaskRunsByTaskId(String id) {
|
||||
if (this.taskRunList == null) {
|
||||
return new ArrayList<>();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return this.taskRunList
|
||||
@@ -199,7 +199,7 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
}
|
||||
|
||||
public TaskRun findTaskRunByTaskRunId(String id) throws InternalException {
|
||||
Optional<TaskRun> find = (this.taskRunList == null ? new ArrayList<TaskRun>() : this.taskRunList)
|
||||
Optional<TaskRun> find = (this.taskRunList == null ? Collections.<TaskRun>emptyList() : this.taskRunList)
|
||||
.stream()
|
||||
.filter(taskRun -> taskRun.getId().equals(id))
|
||||
.findFirst();
|
||||
@@ -212,9 +212,9 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
}
|
||||
|
||||
public TaskRun findTaskRunByTaskIdAndValue(String id, List<String> values) throws InternalException {
|
||||
Optional<TaskRun> find = (this.taskRunList == null ? new ArrayList<TaskRun>() : this.taskRunList)
|
||||
Optional<TaskRun> find = (this.taskRunList == null ? Collections.<TaskRun>emptyList() : this.taskRunList)
|
||||
.stream()
|
||||
.filter(taskRun -> taskRun.getTaskId().equals(id) && findChildsValues(taskRun, true).equals(values))
|
||||
.filter(taskRun -> taskRun.getTaskId().equals(id) && findParentsValues(taskRun, true).equals(values))
|
||||
.findFirst();
|
||||
|
||||
if (find.isEmpty()) {
|
||||
@@ -279,7 +279,7 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
|
||||
public List<TaskRun> findTaskRunByTasks(List<ResolvedTask> resolvedTasks, TaskRun parentTaskRun) {
|
||||
if (resolvedTasks == null || this.taskRunList == null) {
|
||||
return new ArrayList<>();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return this
|
||||
@@ -543,7 +543,6 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.orElseGet(() -> new FailedExecutionWithLog(
|
||||
this.state.getCurrent() != State.Type.FAILED ? this.withState(State.Type.FAILED) : this,
|
||||
RunContextLogger.logEntries(loggingEventFromException(e), LogEntry.of(this))
|
||||
@@ -633,24 +632,29 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
// we pre-compute the map of taskrun by id to avoid traversing the list of all taskrun for each taskrun
|
||||
Map<String, TaskRun> byIds = this.taskRunList.stream().collect(Collectors.toMap(
|
||||
taskRun -> taskRun.getId(),
|
||||
taskRun -> taskRun
|
||||
));
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
for (TaskRun current : this.taskRunList) {
|
||||
if (current.getOutputs() != null) {
|
||||
result = MapUtils.merge(result, outputs(current));
|
||||
result = MapUtils.merge(result, outputs(current, byIds));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Object> outputs(TaskRun taskRun) {
|
||||
List<TaskRun> childs = findChilds(taskRun)
|
||||
private Map<String, Object> outputs(TaskRun taskRun, Map<String, TaskRun> byIds) {
|
||||
List<TaskRun> parents = findParents(taskRun, byIds)
|
||||
.stream()
|
||||
.filter(r -> r.getValue() != null)
|
||||
.collect(Collectors.toList());
|
||||
.toList();
|
||||
|
||||
if (childs.size() == 0) {
|
||||
if (parents.isEmpty()) {
|
||||
if (taskRun.getValue() == null) {
|
||||
return Map.of(taskRun.getTaskId(), taskRun.getOutputs());
|
||||
} else {
|
||||
@@ -658,15 +662,13 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
Map<String, Object> result = MapUtils.newHashMap(1);
|
||||
Map<String, Object> current = result;
|
||||
|
||||
for (TaskRun t : childs) {
|
||||
if (t.getValue() != null) {
|
||||
HashMap<String, Object> item = new HashMap<>();
|
||||
current.put(t.getValue(), item);
|
||||
current = item;
|
||||
}
|
||||
for (TaskRun t : parents) {
|
||||
HashMap<String, Object> item = MapUtils.newHashMap(1);
|
||||
current.put(t.getValue(), item);
|
||||
current = item;
|
||||
}
|
||||
|
||||
if (taskRun.getOutputs() != null) {
|
||||
@@ -684,10 +686,10 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
public List<Map<String, Object>> parents(TaskRun taskRun) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
|
||||
List<TaskRun> childs = findChilds(taskRun);
|
||||
Collections.reverse(childs);
|
||||
List<TaskRun> parents = findParents(taskRun);
|
||||
Collections.reverse(parents);
|
||||
|
||||
for (TaskRun childTaskRun : childs) {
|
||||
for (TaskRun childTaskRun : parents) {
|
||||
HashMap<String, Object> current = new HashMap<>();
|
||||
|
||||
if (childTaskRun.getValue() != null) {
|
||||
@@ -698,7 +700,7 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
current.put("outputs", childTaskRun.getOutputs());
|
||||
}
|
||||
|
||||
if (current.size() > 0) {
|
||||
if (!current.isEmpty()) {
|
||||
result.add(current);
|
||||
}
|
||||
}
|
||||
@@ -707,22 +709,21 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all children from this {@link TaskRun}. The list is starting from deeper child and end on closest child, so
|
||||
* first element is the task that start first.
|
||||
* This method don't return the current tasks
|
||||
* Find all parents from this {@link TaskRun}.
|
||||
* The list is starting from deeper parent and end on the closest parent,
|
||||
* so the first element is the task that starts first.
|
||||
* This method doesn't return the current tasks.
|
||||
*
|
||||
* @param taskRun current child
|
||||
* @return List of parent {@link TaskRun}
|
||||
*/
|
||||
public List<TaskRun> findChilds(TaskRun taskRun) {
|
||||
public List<TaskRun> findParents(TaskRun taskRun) {
|
||||
if (taskRun.getParentTaskRunId() == null || this.taskRunList == null) {
|
||||
return new ArrayList<>();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ArrayList<TaskRun> result = new ArrayList<>();
|
||||
|
||||
boolean ended = false;
|
||||
|
||||
while (!ended) {
|
||||
final TaskRun finalTaskRun = taskRun;
|
||||
Optional<TaskRun> find = this.taskRunList
|
||||
@@ -743,10 +744,39 @@ public class Execution implements DeletedInterface, TenantInterface {
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<String> findChildsValues(TaskRun taskRun, boolean withCurrent) {
|
||||
/**
|
||||
* Find all parents from this {@link TaskRun}.
|
||||
* This method does the same as #findParents(TaskRun taskRun) but for performance reason, as it's called a lot,
|
||||
* we pre-compute the map of taskrun by ID and use it here.
|
||||
*/
|
||||
private List<TaskRun> findParents(TaskRun taskRun, Map<String, TaskRun> taskRunById) {
|
||||
if (taskRun.getParentTaskRunId() == null || taskRunById.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ArrayList<TaskRun> result = new ArrayList<>();
|
||||
boolean ended = false;
|
||||
while (!ended) {
|
||||
final TaskRun finalTaskRun = taskRun;
|
||||
TaskRun find = taskRunById.get(finalTaskRun.getParentTaskRunId());
|
||||
|
||||
if (find != null) {
|
||||
result.add(find);
|
||||
taskRun = find;
|
||||
} else {
|
||||
ended = true;
|
||||
}
|
||||
}
|
||||
|
||||
Collections.reverse(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<String> findParentsValues(TaskRun taskRun, boolean withCurrent) {
|
||||
return (withCurrent ?
|
||||
Stream.concat(findChilds(taskRun).stream(), Stream.of(taskRun)) :
|
||||
findChilds(taskRun).stream()
|
||||
Stream.concat(findParents(taskRun).stream(), Stream.of(taskRun)) :
|
||||
findParents(taskRun).stream()
|
||||
)
|
||||
.filter(t -> t.getValue() != null)
|
||||
.map(TaskRun::getValue)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.kestra.core.models.flows;
|
||||
|
||||
import io.kestra.core.validations.TaskDefaultValidation;
|
||||
import io.micronaut.core.annotation.Introspected;
|
||||
import io.micronaut.core.convert.format.MapFormat;
|
||||
import io.micronaut.core.naming.conventions.StringConvention;
|
||||
@@ -13,6 +14,7 @@ import java.util.Map;
|
||||
@Builder(toBuilder = true)
|
||||
@AllArgsConstructor
|
||||
@Introspected
|
||||
@TaskDefaultValidation
|
||||
public class TaskDefault {
|
||||
private final String type;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.kestra.core.models.hierarchies;
|
||||
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
import io.kestra.core.models.triggers.Trigger;
|
||||
import io.micronaut.core.annotation.Introspected;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
@@ -9,18 +10,20 @@ import lombok.ToString;
|
||||
@Getter
|
||||
@Introspected
|
||||
public abstract class AbstractGraphTrigger extends AbstractGraph {
|
||||
private final AbstractTrigger trigger;
|
||||
private final AbstractTrigger triggerDeclaration;
|
||||
private final Trigger trigger;
|
||||
|
||||
public AbstractGraphTrigger(AbstractTrigger trigger) {
|
||||
public AbstractGraphTrigger(AbstractTrigger triggerDeclaration, Trigger trigger) {
|
||||
super();
|
||||
|
||||
this.triggerDeclaration = triggerDeclaration;
|
||||
this.trigger = trigger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUid() {
|
||||
if (this.trigger != null) {
|
||||
return this.trigger.getId();
|
||||
if (this.uid == null && this.triggerDeclaration != null) {
|
||||
return this.triggerDeclaration.getId();
|
||||
}
|
||||
|
||||
return this.uid;
|
||||
|
||||
@@ -69,7 +69,7 @@ public class GraphCluster extends AbstractGraph {
|
||||
|
||||
public void addNode(AbstractGraph node, boolean withClusterUidPrefix) {
|
||||
if (withClusterUidPrefix) {
|
||||
node.updateUidWithChildren(prefixedUid(node.uid));
|
||||
node.updateUidWithChildren(prefixedUid(Optional.ofNullable(node.uid).orElse(node.getUid())));
|
||||
}
|
||||
this.getGraph().addNode(node);
|
||||
}
|
||||
@@ -110,7 +110,9 @@ public class GraphCluster extends AbstractGraph {
|
||||
// this is because we need other clusters' root & end to have edges over them, but they are already managed by their own cluster
|
||||
(!(node instanceof GraphClusterRoot) && !(node instanceof GraphClusterEnd))
|
||||
|| node.equals(this.root) || node.equals(this.end))
|
||||
.forEach(node -> node.updateUidWithChildren(uid + node.uid.substring(this.uid.length())));
|
||||
.forEach(node -> node.updateUidWithChildren(uid +
|
||||
Optional.ofNullable(node.uid).orElse(node.getUid()).substring(this.uid.length())
|
||||
));
|
||||
|
||||
super.updateUidWithChildren(uid);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package io.kestra.core.models.hierarchies;
|
||||
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
import io.kestra.core.models.triggers.Trigger;
|
||||
|
||||
|
||||
public class GraphTrigger extends AbstractGraphTrigger {
|
||||
public GraphTrigger(AbstractTrigger trigger) {
|
||||
super(trigger);
|
||||
public GraphTrigger(AbstractTrigger triggerDeclaration, Trigger trigger) {
|
||||
super(triggerDeclaration, trigger);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ public class Schedule extends AbstractTrigger implements PollingTriggerInterface
|
||||
)
|
||||
@PluginProperty
|
||||
@Deprecated
|
||||
private ScheduleBackfill backfill;
|
||||
private Map<String, Object> backfill;
|
||||
|
||||
@Schema(
|
||||
title = "What to do in case of missed schedules",
|
||||
@@ -254,12 +254,12 @@ public class Schedule extends AbstractTrigger implements PollingTriggerInterface
|
||||
// is after the end, then we calculate again the nextDate
|
||||
// based on now()
|
||||
if (backfill != null && nextDate != null && nextDate.isAfter(backfill.getEnd())) {
|
||||
nextDate = computeNextEvaluationDate(executionTime, ZonedDateTime.now()).orElse(null);
|
||||
nextDate = computeNextEvaluationDate(executionTime, convertDateTime(ZonedDateTime.now())).orElse(null);
|
||||
}
|
||||
}
|
||||
// no previous present & no backfill or recover missed schedules, just provide now
|
||||
else {
|
||||
nextDate = computeNextEvaluationDate(executionTime, ZonedDateTime.now()).orElse(null);
|
||||
nextDate = computeNextEvaluationDate(executionTime, convertDateTime(ZonedDateTime.now())).orElse(null);
|
||||
}
|
||||
|
||||
// if max delay reached, we calculate a new date except if we are doing a backfill
|
||||
@@ -280,7 +280,7 @@ public class Schedule extends AbstractTrigger implements PollingTriggerInterface
|
||||
public ZonedDateTime nextEvaluationDate() {
|
||||
// it didn't take into account the schedule condition, but as they are taken into account inside eval() it's OK.
|
||||
ExecutionTime executionTime = this.executionTime();
|
||||
return computeNextEvaluationDate(executionTime, ZonedDateTime.now()).orElse(ZonedDateTime.now());
|
||||
return computeNextEvaluationDate(executionTime, convertDateTime(ZonedDateTime.now())).orElse(convertDateTime(ZonedDateTime.now()));
|
||||
}
|
||||
|
||||
public ZonedDateTime previousEvaluationDate(ConditionContext conditionContext) {
|
||||
@@ -301,7 +301,7 @@ public class Schedule extends AbstractTrigger implements PollingTriggerInterface
|
||||
conditionContext.getRunContext().logger().warn("Unable to evaluate the conditions for the next evaluation date for trigger '{}', conditions will not be evaluated", this.getId());
|
||||
}
|
||||
}
|
||||
return computePreviousEvaluationDate(executionTime, ZonedDateTime.now()).orElse(ZonedDateTime.now());
|
||||
return computePreviousEvaluationDate(executionTime, convertDateTime(ZonedDateTime.now())).orElse(convertDateTime(ZonedDateTime.now()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -591,14 +591,6 @@ public class Schedule extends AbstractTrigger implements PollingTriggerInterface
|
||||
private ZonedDateTime previous;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static class ScheduleBackfill {
|
||||
@Schema(
|
||||
title = "The first start date."
|
||||
)
|
||||
ZonedDateTime start;
|
||||
}
|
||||
|
||||
public enum RecoverMissedSchedules {
|
||||
LAST,
|
||||
NONE,
|
||||
|
||||
@@ -82,6 +82,13 @@ public class RunContext {
|
||||
this.initContext(flow, null, execution, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link #RunContext(ApplicationContext, Flow, Task, Execution, TaskRun, boolean)} with decryptVariables set to true
|
||||
*/
|
||||
public RunContext(ApplicationContext applicationContext, Flow flow, Task task, Execution execution, TaskRun taskRun) {
|
||||
this(applicationContext, flow, task, execution, taskRun, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal usage
|
||||
*
|
||||
@@ -90,11 +97,12 @@ public class RunContext {
|
||||
* @param task the current {@link io.kestra.core.models.tasks.Task}
|
||||
* @param execution the current {@link Execution}
|
||||
* @param taskRun the current {@link TaskRun}
|
||||
* @param decryptVariables whether or not to decrypt secret variables
|
||||
*/
|
||||
public RunContext(ApplicationContext applicationContext, Flow flow, Task task, Execution execution, TaskRun taskRun) {
|
||||
public RunContext(ApplicationContext applicationContext, Flow flow, Task task, Execution execution, TaskRun taskRun, boolean decryptVariables) {
|
||||
this.initBean(applicationContext);
|
||||
this.initLogger(taskRun, task);
|
||||
this.initContext(flow, task, execution, taskRun);
|
||||
this.initContext(flow, task, execution, taskRun, decryptVariables);
|
||||
this.initPluginConfiguration(applicationContext, task.getType());
|
||||
}
|
||||
|
||||
@@ -156,7 +164,11 @@ public class RunContext {
|
||||
}
|
||||
|
||||
private void initContext(Flow flow, Task task, Execution execution, TaskRun taskRun) {
|
||||
this.variables = this.variables(flow, task, execution, taskRun, null);
|
||||
this.initContext(flow, task, execution, taskRun, true);
|
||||
}
|
||||
|
||||
private void initContext(Flow flow, Task task, Execution execution, TaskRun taskRun, boolean decryptVariables) {
|
||||
this.variables = this.variables(flow, task, execution, taskRun, null, decryptVariables);
|
||||
|
||||
if (taskRun != null && this.storageInterface != null) {
|
||||
this.storage = new InternalStorage(
|
||||
@@ -234,6 +246,10 @@ public class RunContext {
|
||||
}
|
||||
|
||||
protected Map<String, Object> variables(Flow flow, Task task, Execution execution, TaskRun taskRun, AbstractTrigger trigger) {
|
||||
return this.variables(flow, task, execution, taskRun, trigger, true);
|
||||
}
|
||||
|
||||
protected Map<String, Object> variables(Flow flow, Task task, Execution execution, TaskRun taskRun, AbstractTrigger trigger, boolean decryptVariables) {
|
||||
ImmutableMap.Builder<String, Object> builder = ImmutableMap.<String, Object>builder()
|
||||
.put("envs", runContextCache.getEnvVars())
|
||||
.put("globals", runContextCache.getGlobalVars());
|
||||
@@ -295,13 +311,16 @@ public class RunContext {
|
||||
|
||||
if (execution.getTaskRunList() != null) {
|
||||
Map<String, Object> outputs = new HashMap<>(execution.outputs());
|
||||
decryptOutputs(outputs);
|
||||
if (decryptVariables) {
|
||||
decryptOutputs(outputs);
|
||||
}
|
||||
builder.put("outputs", outputs);
|
||||
}
|
||||
|
||||
Map<String, Object> inputs = new HashMap<>();
|
||||
if (execution.getInputs() != null) {
|
||||
Map<String, Object> inputs = new HashMap<>(execution.getInputs());
|
||||
if (flow != null && flow.getInputs() != null) {
|
||||
inputs.putAll(execution.getInputs());
|
||||
if (decryptVariables && flow != null && flow.getInputs() != null) {
|
||||
// if some inputs are of type secret, we decode them
|
||||
for (Input<?> input : flow.getInputs()) {
|
||||
if (input instanceof SecretInput && inputs.containsKey(input.getId())) {
|
||||
@@ -314,6 +333,14 @@ public class RunContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flow != null && flow.getInputs() != null) {
|
||||
// we add default inputs value from the flow if not already set, this will be useful for triggers
|
||||
flow.getInputs().stream()
|
||||
.filter(input -> input.getDefaults() != null && !inputs.containsKey(input.getId()))
|
||||
.forEach(input -> inputs.put(input.getId(), input.getDefaults()));
|
||||
}
|
||||
if (!inputs.isEmpty()) {
|
||||
builder.put("inputs", inputs);
|
||||
}
|
||||
|
||||
@@ -324,6 +351,7 @@ public class RunContext {
|
||||
if (execution.getLabels() != null) {
|
||||
builder.put("labels", execution.getLabels()
|
||||
.stream()
|
||||
.filter(label -> label.value() != null)
|
||||
.map(label -> new AbstractMap.SimpleEntry<>(
|
||||
label.key(),
|
||||
label.value()
|
||||
@@ -565,7 +593,7 @@ public class RunContext {
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private String decrypt(String encrypted) throws GeneralSecurityException {
|
||||
public String decrypt(String encrypted) throws GeneralSecurityException {
|
||||
if (secretKey.isPresent()) {
|
||||
return EncryptionService.decrypt(secretKey.get(), encrypted);
|
||||
} else {
|
||||
|
||||
@@ -23,7 +23,11 @@ public class RunContextFactory {
|
||||
}
|
||||
|
||||
public RunContext of(Flow flow, Task task, Execution execution, TaskRun taskRun) {
|
||||
return new RunContext(applicationContext, flow, task, execution, taskRun);
|
||||
return this.of(flow, task, execution, taskRun, true);
|
||||
}
|
||||
|
||||
public RunContext of(Flow flow, Task task, Execution execution, TaskRun taskRun, boolean decryptVariables) {
|
||||
return new RunContext(applicationContext, flow, task, execution, taskRun, decryptVariables);
|
||||
}
|
||||
|
||||
public RunContext of(Flow flow, AbstractTrigger trigger) {
|
||||
|
||||
@@ -25,13 +25,12 @@ import java.util.regex.Pattern;
|
||||
|
||||
@Singleton
|
||||
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*-*%})");
|
||||
public static final int MAX_RENDERING_AMOUNT = 100;
|
||||
|
||||
private PebbleEngine pebbleEngine;
|
||||
private final PebbleEngine pebbleEngine;
|
||||
private final VariableConfiguration variableConfiguration;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Inject
|
||||
public VariableRenderer(ApplicationContext applicationContext, @Nullable VariableConfiguration variableConfiguration) {
|
||||
this.variableConfiguration = variableConfiguration != null ? variableConfiguration : new VariableConfiguration();
|
||||
@@ -81,20 +80,18 @@ public class VariableRenderer {
|
||||
return inline;
|
||||
}
|
||||
|
||||
return recursive
|
||||
String render = recursive
|
||||
? renderRecursively(inline, variables)
|
||||
: renderOnce(inline, variables);
|
||||
|
||||
return RAW_PATTERN.matcher(render).replaceAll("$2");
|
||||
}
|
||||
|
||||
public String renderOnce(String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
// pre-process raw tags
|
||||
Matcher rawMatcher = RAW_PATTERN.matcher(inline);
|
||||
Map<String, String> replacers = new HashMap<>((int) Math.ceil(rawMatcher.groupCount() / 0.75));
|
||||
String result = rawMatcher.replaceAll(matchResult -> {
|
||||
var uuid = UUID.randomUUID().toString();
|
||||
replacers.put(uuid, matchResult.group(1));
|
||||
return uuid;
|
||||
});
|
||||
String result = replaceRawTags(rawMatcher, replacers);
|
||||
|
||||
try {
|
||||
PebbleTemplate compiledTemplate = this.pebbleEngine.getLiteralTemplate(result);
|
||||
@@ -102,29 +99,31 @@ public class VariableRenderer {
|
||||
Writer writer = new JsonWriter(new StringWriter());
|
||||
compiledTemplate.evaluate(writer, variables);
|
||||
result = writer.toString();
|
||||
} catch (IOException | PebbleException e) {
|
||||
String alternativeRender = this.alternativeRender(e, inline, variables);
|
||||
if (alternativeRender == null) {
|
||||
if (e instanceof PebbleException) {
|
||||
throw properPebbleException((PebbleException) e);
|
||||
}
|
||||
|
||||
throw new IllegalVariableEvaluationException(e);
|
||||
} else {
|
||||
result = alternativeRender;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalVariableEvaluationException(e);
|
||||
} catch (PebbleException e) {
|
||||
throw properPebbleException(e);
|
||||
}
|
||||
|
||||
// post-process raw tags
|
||||
for (var entry : replacers.entrySet()) {
|
||||
result = result.replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
result = putBackRawTags(replacers, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected String alternativeRender(Exception e, String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
return null;
|
||||
private static String putBackRawTags(Map<String, String> replacers, String result) {
|
||||
for (var entry : replacers.entrySet()) {
|
||||
result = result.replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String replaceRawTags(Matcher rawMatcher, Map<String, String> replacers) {
|
||||
return rawMatcher.replaceAll(matchResult -> {
|
||||
var uuid = UUID.randomUUID().toString();
|
||||
replacers.put(uuid, matchResult.group(1));
|
||||
return uuid;
|
||||
});
|
||||
}
|
||||
|
||||
public String renderRecursively(String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
@@ -180,7 +179,8 @@ public class VariableRenderer {
|
||||
return Optional.of(this.render(string, variables, recursive));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
// Return the given object if it cannot be rendered.
|
||||
return Optional.ofNullable(object);
|
||||
}
|
||||
|
||||
public List<Object> renderList(List<Object> list, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
|
||||
@@ -39,10 +39,9 @@ public class RenderFunction implements Function {
|
||||
recursiveArg = true;
|
||||
}
|
||||
|
||||
if (!(recursiveArg instanceof Boolean)) {
|
||||
if (!(recursiveArg instanceof Boolean recursive)) {
|
||||
throw new PebbleException(null, "The 'render' function expects an optional argument 'recursive' with type boolean.", lineNumber, self.getName());
|
||||
}
|
||||
Boolean recursive = (Boolean) recursiveArg;
|
||||
|
||||
EvaluationContextImpl evaluationContext = (EvaluationContextImpl) context;
|
||||
Map<String, Object> variables = evaluationContext.getScopeChain().getGlobalScopes().stream()
|
||||
|
||||
@@ -86,21 +86,25 @@ public class CollectorService {
|
||||
}
|
||||
|
||||
public Usage metrics() {
|
||||
Usage.UsageBuilder<?, ?> builder = defaultUsage().toBuilder()
|
||||
return metrics(true);
|
||||
}
|
||||
|
||||
private Usage metrics(boolean details) {
|
||||
Usage.UsageBuilder<?, ?> builder = defaultUsage()
|
||||
.toBuilder()
|
||||
.uuid(IdUtils.create());
|
||||
|
||||
if (serverType == ServerType.EXECUTOR || serverType == ServerType.STANDALONE) {
|
||||
builder
|
||||
.flows(FlowUsage.of(flowRepository))
|
||||
.executions(ExecutionUsage.of(executionRepository));
|
||||
if (details) {
|
||||
builder = builder
|
||||
.flows(FlowUsage.of(flowRepository))
|
||||
.executions(ExecutionUsage.of(executionRepository));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void report() {
|
||||
try {
|
||||
Usage metrics = this.metrics();
|
||||
Usage metrics = this.metrics(serverType == ServerType.EXECUTOR || serverType == ServerType.STANDALONE);
|
||||
MutableHttpRequest<Usage> post = this.request(metrics);
|
||||
|
||||
if (log.isTraceEnabled()) {
|
||||
|
||||
@@ -432,7 +432,7 @@ public class ExecutionService {
|
||||
return Stream
|
||||
.concat(
|
||||
execution
|
||||
.findChilds(taskRun)
|
||||
.findParents(taskRun)
|
||||
.stream(),
|
||||
Stream.of(taskRun)
|
||||
)
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package io.kestra.core.services;
|
||||
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.hierarchies.*;
|
||||
import io.kestra.core.models.triggers.Trigger;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.repositories.TriggerRepositoryInterface;
|
||||
import io.kestra.core.utils.GraphUtils;
|
||||
import io.kestra.core.utils.Rethrow;
|
||||
import io.micronaut.data.model.Pageable;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -19,20 +23,38 @@ public class GraphService {
|
||||
@Inject
|
||||
private FlowRepositoryInterface flowRepository;
|
||||
@Inject
|
||||
private TriggerRepositoryInterface triggerRepository;
|
||||
@Inject
|
||||
private TaskDefaultService taskDefaultService;
|
||||
|
||||
public FlowGraph flowGraph(Flow flow, List<String> expandedSubflows) throws IllegalVariableEvaluationException {
|
||||
return FlowGraph.of(this.of(flow, Optional.ofNullable(expandedSubflows).orElse(Collections.emptyList()), new HashMap<>()));
|
||||
return this.flowGraph(flow, expandedSubflows, null);
|
||||
}
|
||||
|
||||
public FlowGraph flowGraph(Flow flow, List<String> expandedSubflows, Execution execution) throws IllegalVariableEvaluationException {
|
||||
return FlowGraph.of(this.of(flow, Optional.ofNullable(expandedSubflows).orElse(Collections.emptyList()), new HashMap<>(), execution));
|
||||
}
|
||||
|
||||
public GraphCluster of(Flow flow, List<String> expandedSubflows, Map<String, Flow> flowByUid) throws IllegalVariableEvaluationException {
|
||||
return this.of(null, flow, expandedSubflows, flowByUid);
|
||||
return this.of(flow, expandedSubflows, flowByUid, null);
|
||||
}
|
||||
|
||||
public GraphCluster of(Flow flow, List<String> expandedSubflows, Map<String, Flow> flowByUid, Execution execution) throws IllegalVariableEvaluationException {
|
||||
return this.of(null, flow, expandedSubflows, flowByUid, execution);
|
||||
}
|
||||
|
||||
public GraphCluster of(GraphCluster baseGraph, Flow flow, List<String> expandedSubflows, Map<String, Flow> flowByUid) throws IllegalVariableEvaluationException {
|
||||
return this.of(baseGraph, flow, expandedSubflows, flowByUid, null);
|
||||
}
|
||||
|
||||
public GraphCluster of(GraphCluster baseGraph, Flow flow, List<String> expandedSubflows, Map<String, Flow> flowByUid, Execution execution) throws IllegalVariableEvaluationException {
|
||||
String tenantId = flow.getTenantId();
|
||||
flow = taskDefaultService.injectDefaults(flow);
|
||||
GraphCluster graphCluster = GraphUtils.of(baseGraph, flow, null);
|
||||
List<Trigger> triggers = null;
|
||||
if (flow.getTriggers() != null) {
|
||||
triggers = triggerRepository.find(Pageable.UNPAGED, null, tenantId, flow.getNamespace(), flow.getId());
|
||||
}
|
||||
GraphCluster graphCluster = GraphUtils.of(baseGraph, flow, execution, triggers);
|
||||
|
||||
|
||||
Stream<Map.Entry<GraphCluster, SubflowGraphTask>> subflowToReplaceByParent = graphCluster.allNodesByParent().entrySet().stream()
|
||||
|
||||
@@ -107,7 +107,7 @@ public abstract class StorageService {
|
||||
|
||||
writers.forEach(throwConsumer(RandomAccessFile::close));
|
||||
|
||||
return files;
|
||||
return files.stream().filter(p -> p.toFile().length() > 0).toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.kestra.core.tasks.flows;
|
||||
|
||||
public interface ChildFlowInterface {
|
||||
String getNamespace();
|
||||
|
||||
String getFlowId();
|
||||
}
|
||||
@@ -173,7 +173,7 @@ import static io.kestra.core.utils.Rethrow.throwFunction;
|
||||
)
|
||||
}
|
||||
)
|
||||
public class ForEachItem extends Task implements FlowableTask<VoidOutput> {
|
||||
public class ForEachItem extends Task implements FlowableTask<VoidOutput>, ChildFlowInterface {
|
||||
@NotEmpty
|
||||
@PluginProperty(dynamic = true)
|
||||
@Schema(title = "The items to be split into batches and processed. Make sure to set it to Kestra's internal storage URI. This can be either the output from a previous task, formatted as `{{ outputs.task_id.uri }}`, or a FILE type input parameter, like `{{ inputs.myfile }}`. This task is optimized for files where each line represents a single item. Suitable file types include Amazon ION-type files (commonly produced by Query tasks), newline-separated JSON files, or CSV files formatted with one row per line and without a header. For files in other formats such as Excel, CSV, Avro, Parquet, XML, or JSON, it's recommended to first convert them to the ION format. This can be done using the conversion tasks available in the `io.kestra.plugin.serdes` module, which will transform files from their original format to ION.")
|
||||
|
||||
@@ -123,7 +123,7 @@ public class Pause extends Sequential implements FlowableTask<VoidOutput> {
|
||||
|
||||
private boolean needPause(TaskRun parentTaskRun) {
|
||||
return parentTaskRun.getState().getCurrent() == State.Type.RUNNING &&
|
||||
parentTaskRun.getState().getHistories().get(parentTaskRun.getState().getHistories().size() - 2).getState() != State.Type.PAUSED;
|
||||
parentTaskRun.getState().getHistories().stream().noneMatch(history -> history.getState() == State.Type.PAUSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -60,7 +60,7 @@ import java.util.stream.Collectors;
|
||||
)
|
||||
}
|
||||
)
|
||||
public class Subflow extends Task implements ExecutableTask<Subflow.Output> {
|
||||
public class Subflow extends Task implements ExecutableTask<Subflow.Output>, ChildFlowInterface {
|
||||
|
||||
static final String PLUGIN_FLOW_OUTPUTS_ENABLED = "outputs.enabled";
|
||||
|
||||
|
||||
@@ -13,10 +13,7 @@ import io.kestra.core.serializers.JacksonMapper;
|
||||
import io.micronaut.core.util.functional.ThrowingFunction;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -57,6 +54,7 @@ import java.util.Map;
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class DeduplicateItems extends Task implements RunnableTask<DeduplicateItems.Output> {
|
||||
|
||||
@Schema(
|
||||
|
||||
@@ -15,10 +15,7 @@ import io.kestra.core.utils.TruthUtils;
|
||||
import io.micronaut.core.util.functional.ThrowingFunction;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -56,6 +53,7 @@ import java.util.Map;
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class FilterItems extends Task implements RunnableTask<FilterItems.Output> {
|
||||
|
||||
@Schema(
|
||||
|
||||
@@ -9,27 +9,33 @@ import io.kestra.core.models.tasks.ExecutableTask;
|
||||
import io.kestra.core.models.tasks.FlowableTask;
|
||||
import io.kestra.core.models.tasks.Task;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
import io.kestra.core.models.triggers.Trigger;
|
||||
import io.kestra.core.tasks.flows.Dag;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class GraphUtils {
|
||||
public static FlowGraph flowGraph(Flow flow, Execution execution) throws IllegalVariableEvaluationException {
|
||||
return FlowGraph.of(GraphUtils.of(flow, execution));
|
||||
return GraphUtils.flowGraph(flow, execution, null);
|
||||
}
|
||||
|
||||
public static GraphCluster of(GraphCluster graph, Flow flow, Execution execution) throws IllegalVariableEvaluationException {
|
||||
public static FlowGraph flowGraph(Flow flow, Execution execution, List<Trigger> triggers) throws IllegalVariableEvaluationException {
|
||||
return FlowGraph.of(GraphUtils.of(flow, execution, triggers));
|
||||
}
|
||||
|
||||
public static GraphCluster of(GraphCluster graph, Flow flow, Execution execution, List<Trigger> triggers) throws IllegalVariableEvaluationException {
|
||||
if (graph == null) {
|
||||
graph = new GraphCluster();
|
||||
}
|
||||
|
||||
if (flow.getTriggers() != null) {
|
||||
GraphCluster triggers = GraphUtils.triggers(graph, flow.getTriggers());
|
||||
graph.addEdge(triggers.getEnd(), graph.getRoot(), new Relation());
|
||||
GraphCluster triggersClusters = GraphUtils.triggers(graph, flow.getTriggers(), triggers);
|
||||
graph.addEdge(triggersClusters.getEnd(), graph.getRoot(), new Relation());
|
||||
}
|
||||
|
||||
GraphUtils.sequential(
|
||||
@@ -44,16 +50,29 @@ public class GraphUtils {
|
||||
}
|
||||
|
||||
public static GraphCluster of(Flow flow, Execution execution) throws IllegalVariableEvaluationException {
|
||||
return GraphUtils.of(new GraphCluster(), flow, execution);
|
||||
return GraphUtils.of(flow, execution, null);
|
||||
}
|
||||
|
||||
public static GraphCluster triggers(GraphCluster graph, List<AbstractTrigger> triggers) throws IllegalVariableEvaluationException {
|
||||
public static GraphCluster of(Flow flow, Execution execution, List<Trigger> triggers) throws IllegalVariableEvaluationException {
|
||||
return GraphUtils.of(new GraphCluster(), flow, execution, triggers);
|
||||
}
|
||||
|
||||
public static GraphCluster triggers(GraphCluster graph, List<AbstractTrigger> triggersDeclarations, List<Trigger> triggers) throws IllegalVariableEvaluationException {
|
||||
GraphCluster triggerCluster = new GraphCluster("Triggers");
|
||||
|
||||
graph.addNode(triggerCluster);
|
||||
|
||||
triggers.forEach(trigger -> {
|
||||
GraphTrigger triggerNode = new GraphTrigger(trigger);
|
||||
Map<String, Trigger> triggersById = Optional.ofNullable(triggers)
|
||||
.map(Collection::stream)
|
||||
.map(s -> s.collect(Collectors.toMap(
|
||||
Trigger::getTriggerId,
|
||||
Function.identity(),
|
||||
(a, b) -> a.getNamespace().length() <= b.getNamespace().length() ? a : b
|
||||
)))
|
||||
.orElse(Collections.emptyMap());
|
||||
|
||||
triggersDeclarations.forEach(trigger -> {
|
||||
GraphTrigger triggerNode = new GraphTrigger(trigger, triggersById.get(trigger.getId()));
|
||||
triggerCluster.addNode(triggerNode);
|
||||
triggerCluster.addEdge(triggerCluster.getRoot(), triggerNode, new Relation());
|
||||
triggerCluster.addEdge(triggerNode, triggerCluster.getEnd(), new Relation());
|
||||
@@ -264,7 +283,7 @@ public class GraphUtils {
|
||||
);
|
||||
|
||||
if (execution != null && currentTaskRun != null) {
|
||||
parentValues = execution.findChildsValues(currentTaskRun, true);
|
||||
parentValues = execution.findParentsValues(currentTaskRun, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -367,7 +386,7 @@ public class GraphUtils {
|
||||
);
|
||||
|
||||
if (execution != null && currentTaskRun != null) {
|
||||
parentValues = execution.findChildsValues(currentTaskRun, true);
|
||||
parentValues = execution.findParentsValues(currentTaskRun, true);
|
||||
}
|
||||
|
||||
// detect kids
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class MapUtils {
|
||||
@@ -23,12 +22,11 @@ public class MapUtils {
|
||||
|
||||
Map copy = copyMap(a);
|
||||
|
||||
|
||||
Map<String, Object> copyMap = b
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(
|
||||
HashMap::new,
|
||||
() -> newHashMap(copy.size()),
|
||||
(m, v) -> {
|
||||
Object original = copy.get(v.getKey());
|
||||
Object value = v.getValue();
|
||||
@@ -45,19 +43,14 @@ public class MapUtils {
|
||||
} else if (value instanceof Collection
|
||||
&& original instanceof Collection) {
|
||||
try {
|
||||
Collection merge =
|
||||
copyCollection(
|
||||
found = Lists
|
||||
.newArrayList(
|
||||
(Collection) original,
|
||||
(List) Lists
|
||||
.newArrayList(
|
||||
(Collection) original,
|
||||
(Collection) value
|
||||
)
|
||||
.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
found = merge;
|
||||
(Collection) value
|
||||
)
|
||||
.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -81,15 +74,15 @@ public class MapUtils {
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(
|
||||
HashMap::new,
|
||||
() -> newHashMap(original.size()),
|
||||
(map, entry) -> {
|
||||
Object value = entry.getValue();
|
||||
Object found;
|
||||
|
||||
if (value instanceof Map) {
|
||||
found = copyMap((Map) value);
|
||||
found = cloneMap((Map) value);
|
||||
} else if (value instanceof Collection) {
|
||||
found = copyCollection((Collection) value, (Collection) value);
|
||||
found = cloneCollection((Collection) value);
|
||||
} else {
|
||||
found = value;
|
||||
}
|
||||
@@ -101,9 +94,19 @@ public class MapUtils {
|
||||
);
|
||||
}
|
||||
|
||||
private static Collection copyCollection(Collection collection, Collection elements) {
|
||||
private static Map cloneMap(Map elements) {
|
||||
try {
|
||||
Collection newInstance = collection.getClass().getDeclaredConstructor().newInstance();
|
||||
Map newInstance = elements.getClass().getDeclaredConstructor().newInstance();
|
||||
newInstance.putAll(elements);
|
||||
return newInstance;
|
||||
} catch (Exception e) {
|
||||
return new HashMap(elements);
|
||||
}
|
||||
}
|
||||
|
||||
private static Collection cloneCollection(Collection elements) {
|
||||
try {
|
||||
Collection newInstance = elements.getClass().getDeclaredConstructor().newInstance();
|
||||
newInstance.addAll(elements);
|
||||
return newInstance;
|
||||
} catch (Exception e) {
|
||||
@@ -124,4 +127,17 @@ public class MapUtils {
|
||||
// https://bugs.openjdk.org/browse/JDK-8148463
|
||||
.collect(HashMap::new, (m, v) -> m.put(v.getKey(), v.getValue()), HashMap::putAll);
|
||||
}
|
||||
/**
|
||||
* Creates a hash map that can hold <code>numMappings</code> entry.
|
||||
* This is a copy of the same methods available starting with Java 19.
|
||||
*/
|
||||
public static <K, V> HashMap<K, V> newHashMap(int numMappings) {
|
||||
if (numMappings < 0) {
|
||||
throw new IllegalArgumentException("Negative number of mappings: " + numMappings);
|
||||
}
|
||||
|
||||
int hashMapCapacity = (int) Math.ceil(numMappings / 0.75d);
|
||||
return new HashMap<>(hashMapCapacity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.kestra.core.validations;
|
||||
|
||||
import jakarta.validation.Constraint;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = {})
|
||||
public @interface TaskDefaultValidation {
|
||||
String message() default "invalid taskDefault";
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.kestra.core.validations.validator;
|
||||
|
||||
import io.kestra.core.models.flows.TaskDefault;
|
||||
import io.kestra.core.models.validations.ModelValidator;
|
||||
import io.kestra.core.serializers.YamlFlowParser;
|
||||
import io.kestra.core.services.TaskDefaultService;
|
||||
import io.micronaut.core.annotation.AnnotationValue;
|
||||
import io.micronaut.core.annotation.Introspected;
|
||||
import io.micronaut.core.annotation.NonNull;
|
||||
import io.micronaut.core.annotation.Nullable;
|
||||
import io.micronaut.validation.validator.constraints.ConstraintValidator;
|
||||
import io.micronaut.validation.validator.constraints.ConstraintValidatorContext;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import io.kestra.core.validations.TaskDefaultValidation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Singleton
|
||||
@Introspected
|
||||
public class TaskDefaultValidator implements ConstraintValidator<TaskDefaultValidation, TaskDefault> {
|
||||
@Override
|
||||
public boolean isValid(@Nullable TaskDefault value, @NonNull AnnotationValue<TaskDefaultValidation> annotationMetadata, @NonNull ConstraintValidatorContext context) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> violations = new ArrayList<>();
|
||||
|
||||
if (value.getValues() == null) {
|
||||
violations.add("Null values map found");
|
||||
context.messageTemplate("Invalid Task Default: " + String.join(", ", violations));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the "values" map is empty
|
||||
for (Map.Entry<String, Object> entry : value.getValues().entrySet()) {
|
||||
if (entry.getValue() == null) {
|
||||
violations.add("Null value found in values with key " + entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
if (!violations.isEmpty()) {
|
||||
context.messageTemplate("Invalid Task Default: " + String.join(", ", violations));
|
||||
|
||||
return false;
|
||||
} else {
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,10 +98,10 @@ class ClassPluginDocumentationTest {
|
||||
|
||||
ClassPluginDocumentation<? extends AbstractTrigger> doc = ClassPluginDocumentation.of(jsonSchemaGenerator, scan, Schedule.class, null);
|
||||
|
||||
assertThat(doc.getDefs().size(), is(4));
|
||||
assertThat(doc.getDefs().size(), is(1));
|
||||
|
||||
assertThat(((Map<String, Object>) doc.getDefs().get("io.kestra.core.models.conditions.ScheduleCondition")).get("type"), is("object"));
|
||||
assertThat(((Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.core.models.conditions.ScheduleCondition")).get("properties")).size(), is(0));
|
||||
assertThat(((Map<String, Object>) doc.getDefs().get("io.kestra.core.models.tasks.WorkerGroup")).get("type"), is("object"));
|
||||
assertThat(((Map<String, Object>) ((Map<String, Object>) doc.getDefs().get("io.kestra.core.models.tasks.WorkerGroup")).get("properties")).size(), is(1));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.kestra.core.encryption;
|
||||
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
public class EncryptionServiceTest {
|
||||
private static final String KEY = "I6EGNzRESu3X3pKZidrqCGOHQFUFC0yK";
|
||||
|
||||
@Test
|
||||
public void avoidNpeForEmptyOrNullText() throws GeneralSecurityException {
|
||||
assertThat(EncryptionService.encrypt(KEY, null), nullValue());
|
||||
assertThat(EncryptionService.decrypt(KEY, null), nullValue());
|
||||
assertThat(EncryptionService.encrypt(KEY, ""), is(""));
|
||||
assertThat(EncryptionService.decrypt(KEY, ""), is(""));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,10 @@ import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.exceptions.InternalException;
|
||||
import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.triggers.Trigger;
|
||||
import io.kestra.core.models.triggers.types.Schedule;
|
||||
import io.kestra.core.repositories.FlowRepositoryInterface;
|
||||
import io.kestra.core.repositories.TriggerRepositoryInterface;
|
||||
import io.kestra.core.runners.AbstractMemoryRunnerTest;
|
||||
import io.kestra.core.serializers.YamlFlowParser;
|
||||
import io.kestra.core.services.GraphService;
|
||||
@@ -11,6 +15,7 @@ import io.kestra.core.tasks.flows.Subflow;
|
||||
import io.kestra.core.tasks.flows.Switch;
|
||||
import io.kestra.core.utils.GraphUtils;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.micronaut.data.model.Pageable;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -31,6 +36,12 @@ class FlowGraphTest extends AbstractMemoryRunnerTest {
|
||||
@Inject
|
||||
private GraphService graphService;
|
||||
|
||||
@Inject
|
||||
private TriggerRepositoryInterface triggerRepositoryInterface;
|
||||
|
||||
@Inject
|
||||
private FlowRepositoryInterface flowRepositoryInterface;
|
||||
|
||||
@Test
|
||||
void simple() throws IllegalVariableEvaluationException {
|
||||
Flow flow = this.parse("flows/valids/return.yaml");
|
||||
@@ -114,9 +125,9 @@ class FlowGraphTest extends AbstractMemoryRunnerTest {
|
||||
Flow flow = this.parse("flows/valids/switch.yaml");
|
||||
FlowGraph flowGraph = GraphUtils.flowGraph(flow, null);
|
||||
|
||||
assertThat(flowGraph.getNodes().size(), is(14));
|
||||
assertThat(flowGraph.getEdges().size(), is(17));
|
||||
assertThat(flowGraph.getClusters().size(), is(2));
|
||||
assertThat(flowGraph.getNodes().size(), is(17));
|
||||
assertThat(flowGraph.getEdges().size(), is(20));
|
||||
assertThat(flowGraph.getClusters().size(), is(3));
|
||||
|
||||
assertThat(edge(flowGraph, ".*parent-seq", ".*parent-seq\\.[^.]*").getRelation().getRelationType(), is(RelationType.CHOICE));
|
||||
assertThat(edge(flowGraph, ".*parent-seq", ".*parent-seq\\.t3\\.[^.]*").getRelation().getValue(), is("THIRD"));
|
||||
@@ -205,11 +216,17 @@ class FlowGraphTest extends AbstractMemoryRunnerTest {
|
||||
@Test
|
||||
void trigger() throws IllegalVariableEvaluationException {
|
||||
Flow flow = this.parse("flows/valids/trigger-flow-listener.yaml");
|
||||
FlowGraph flowGraph = GraphUtils.flowGraph(flow, null);
|
||||
triggerRepositoryInterface.save(
|
||||
Trigger.of(flow, flow.getTriggers().get(0)).toBuilder().disabled(true).build()
|
||||
);
|
||||
|
||||
FlowGraph flowGraph = graphService.flowGraph(flow, null);
|
||||
|
||||
assertThat(flowGraph.getNodes().size(), is(6));
|
||||
assertThat(flowGraph.getEdges().size(), is(5));
|
||||
assertThat(flowGraph.getClusters().size(), is(1));
|
||||
AbstractGraph triggerGraph = flowGraph.getNodes().stream().filter(e -> e instanceof GraphTrigger).findFirst().orElseThrow();
|
||||
assertThat(((GraphTrigger) triggerGraph).getTrigger().getDisabled(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -227,15 +244,15 @@ class FlowGraphTest extends AbstractMemoryRunnerTest {
|
||||
Flow flow = this.parse("flows/valids/task-flow.yaml");
|
||||
FlowGraph flowGraph = GraphUtils.flowGraph(flow, null);
|
||||
|
||||
assertThat(flowGraph.getNodes().size(), is(3));
|
||||
assertThat(flowGraph.getEdges().size(), is(2));
|
||||
assertThat(flowGraph.getClusters().size(), is(0));
|
||||
assertThat(flowGraph.getNodes().size(), is(6));
|
||||
assertThat(flowGraph.getEdges().size(), is(5));
|
||||
assertThat(flowGraph.getClusters().size(), is(1));
|
||||
|
||||
flowGraph = graphService.flowGraph(flow, Collections.singletonList("root.launch"));
|
||||
|
||||
assertThat(flowGraph.getNodes().size(), is(17));
|
||||
assertThat(flowGraph.getEdges().size(), is(20));
|
||||
assertThat(flowGraph.getClusters().size(), is(3));
|
||||
assertThat(flowGraph.getNodes().size(), is(23));
|
||||
assertThat(flowGraph.getEdges().size(), is(26));
|
||||
assertThat(flowGraph.getClusters().size(), is(5));
|
||||
|
||||
assertThat(((SubflowGraphTask) ((SubflowGraphCluster) cluster(flowGraph, "root\\.launch").getCluster()).getTaskNode()).getExecutableTask().subflowId().flowId(), is("switch"));
|
||||
SubflowGraphTask subflowGraphTask = (SubflowGraphTask) nodeByUid(flowGraph, "root.launch");
|
||||
@@ -245,6 +262,11 @@ class FlowGraphTest extends AbstractMemoryRunnerTest {
|
||||
GraphTask switchNode = (GraphTask) nodeByUid(flowGraph, "root.launch.parent-seq");
|
||||
assertThat(switchNode.getTask(), instanceOf(Switch.class));
|
||||
assertThat(switchNode.getRelationType(), is(RelationType.CHOICE));
|
||||
|
||||
GraphTrigger flowTrigger = (GraphTrigger) nodeByUid(flowGraph, "root.Triggers.schedule");
|
||||
assertThat(flowTrigger.getTriggerDeclaration(), instanceOf(Schedule.class));
|
||||
GraphTrigger subflowTrigger = (GraphTrigger) nodeByUid(flowGraph, "root.launch.Triggers.schedule");
|
||||
assertThat(subflowTrigger.getTriggerDeclaration(), instanceOf(Schedule.class));
|
||||
}
|
||||
|
||||
private Flow parse(String path) {
|
||||
|
||||
@@ -72,6 +72,10 @@ public abstract class AbstractTriggerRepositoryTest {
|
||||
assertThat(find.size(), is(4));
|
||||
assertThat(find.get(0).getNamespace(), is(namespace));
|
||||
|
||||
find = triggerRepository.find(Pageable.from(1, 4, Sort.of(Sort.Order.asc("namespace"))), null, null, null, searchedTrigger.getFlowId());
|
||||
assertThat(find.size(), is(1));
|
||||
assertThat(find.get(0).getFlowId(), is(searchedTrigger.getFlowId()));
|
||||
|
||||
find = triggerRepository.find(Pageable.from(1, 100, Sort.of(Sort.Order.asc(triggerRepository.sortMapping().apply("triggerId")))), null, null, namespacePrefix);
|
||||
assertThat(find.size(), is(1));
|
||||
assertThat(find.get(0).getTriggerId(), is(trigger.getTriggerId()));
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
package io.kestra.core.runners;
|
||||
|
||||
import io.kestra.core.encryption.EncryptionService;
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.metrics.MetricRegistry;
|
||||
import io.kestra.core.models.Label;
|
||||
import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.executions.LogEntry;
|
||||
import io.kestra.core.models.executions.TaskRun;
|
||||
import io.kestra.core.models.executions.metrics.Counter;
|
||||
import io.kestra.core.models.executions.metrics.Timer;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.State;
|
||||
import io.kestra.core.models.flows.Type;
|
||||
import io.kestra.core.models.flows.input.StringInput;
|
||||
import io.kestra.core.models.tasks.common.EncryptedString;
|
||||
import io.kestra.core.queues.QueueFactoryInterface;
|
||||
import io.kestra.core.queues.QueueInterface;
|
||||
import io.kestra.core.storages.StorageInterface;
|
||||
import io.kestra.core.tasks.test.PollingTrigger;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.micronaut.context.annotation.Property;
|
||||
import io.micronaut.context.annotation.Value;
|
||||
@@ -245,4 +252,24 @@ class RunContextTest extends AbstractMemoryRunnerTest {
|
||||
// the output is automatically decrypted so the return has the decrypted value of the hello task output
|
||||
assertThat(returnTask.getOutputs().get("value"), is("Hello World"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void withDefaultInput() throws IllegalVariableEvaluationException {
|
||||
Flow flow = Flow.builder().id("triggerWithDefaultInput").namespace("io.kestra.test").revision(1).inputs(List.of(StringInput.builder().id("test").type(Type.STRING).defaults("test").build())).build();
|
||||
Execution execution = Execution.builder().id(IdUtils.create()).flowId("triggerWithDefaultInput").namespace("io.kestra.test").state(new State()).build();
|
||||
|
||||
RunContext runContext = runContextFactory.of(flow, execution);
|
||||
|
||||
assertThat(runContext.render("{{inputs.test}}"), is("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNullLabel() throws IllegalVariableEvaluationException {
|
||||
Flow flow = Flow.builder().id("triggerWithDefaultInput").namespace("io.kestra.test").revision(1).inputs(List.of(StringInput.builder().id("test").type(Type.STRING).defaults("test").build())).build();
|
||||
Execution execution = Execution.builder().id(IdUtils.create()).flowId("triggerWithDefaultInput").namespace("io.kestra.test").state(new State()).labels(List.of(new Label("key", null))).build();
|
||||
|
||||
RunContext runContext = runContextFactory.of(flow, execution);
|
||||
|
||||
assertThat(runContext.render("{{inputs.test}}"), is("test"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
@@ -63,4 +65,9 @@ class RecursivePebbleVariableRendererTest {
|
||||
);
|
||||
assertThat(illegalVariableEvaluationException.getMessage(), containsString("Function or Macro [render] does not exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void renderFunctionKeepRaw() throws IllegalVariableEvaluationException {
|
||||
assertThat(variableRenderer.render("{% raw %}{{first}}{% endraw %}", Collections.emptyMap()), is("{{first}}"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.kestra.core.runners.pebble.functions;
|
||||
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.runners.VariableRenderer;
|
||||
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@MicronautTest
|
||||
class RenderFunctionTest {
|
||||
@Inject
|
||||
VariableRenderer variableRenderer;
|
||||
|
||||
@Test
|
||||
void shouldRenderForString() throws IllegalVariableEvaluationException {
|
||||
String rendered = variableRenderer.render("{{ render(input) }}", Map.of("input", "test"));
|
||||
Assertions.assertEquals("test", rendered);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderForInteger() throws IllegalVariableEvaluationException {
|
||||
String rendered = variableRenderer.render("{{ render(input) }}", Map.of("input", 42));
|
||||
Assertions.assertEquals("42", rendered);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderForLong() throws IllegalVariableEvaluationException {
|
||||
String rendered = variableRenderer.render("{{ render(input) }}", Map.of("input", 42L));
|
||||
Assertions.assertEquals("42", rendered);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderForBoolean() throws IllegalVariableEvaluationException {
|
||||
String rendered = variableRenderer.render("{{ render(input) }}", Map.of("input", true));
|
||||
Assertions.assertEquals("true", rendered);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderForNull() throws IllegalVariableEvaluationException {
|
||||
String rendered = variableRenderer.render("{{ render(input) }}", new HashMap<>(){{put("input", null);}});
|
||||
Assertions.assertEquals("", rendered);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderForDateTime() throws IllegalVariableEvaluationException {
|
||||
Instant now = Instant.now();
|
||||
LocalDateTime datetime = LocalDateTime.ofInstant(now, ZoneOffset.UTC);
|
||||
String rendered = variableRenderer.render("{{ render(input) }}", Map.of("input", datetime));
|
||||
Assertions.assertEquals(datetime.toString(), rendered);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRenderForDuration() throws IllegalVariableEvaluationException {
|
||||
String rendered = variableRenderer.render("{{ render(input) }}", Map.of("input", Duration.ofSeconds(5)));
|
||||
Assertions.assertEquals(Duration.ofSeconds(5).toString(), rendered);
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,9 @@ class CollectorServiceTest {
|
||||
assertThat(metrics.getHost().getOs().getFamily(), notNullValue());
|
||||
assertThat(metrics.getConfigurations().getRepositoryType(), is("memory"));
|
||||
assertThat(metrics.getConfigurations().getQueueType(), is("memory"));
|
||||
assertThat(metrics.getExecutions(), notNullValue());
|
||||
assertThat(metrics.getExecutions().getDailyExecutionsCount().size(), is(0));
|
||||
assertThat(metrics.getExecutions().getDailyTaskRunsCount().size(), is(0));
|
||||
assertThat(metrics.getInstanceUuid(), is(TestSettingRepository.instanceUuid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.kestra.core.validations;
|
||||
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.models.flows.TaskDefault;
|
||||
import io.kestra.core.models.validations.ModelValidator;
|
||||
import io.kestra.core.serializers.YamlFlowParser;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
@MicronautTest
|
||||
class TaskDefaultValidationTest {
|
||||
@Inject
|
||||
private ModelValidator modelValidator;
|
||||
|
||||
@Test
|
||||
void nullValue() {
|
||||
TaskDefault taskDefault = TaskDefault.builder()
|
||||
.type("io.kestra.tests")
|
||||
.build();
|
||||
|
||||
Optional<ConstraintViolationException> validate = modelValidator.isValid(taskDefault);
|
||||
|
||||
assertThat(validate.isPresent(), is(true));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
id: restart_pause_last_failed
|
||||
namespace: io.kestra.tests
|
||||
|
||||
tasks:
|
||||
- id: a
|
||||
type: io.kestra.core.tasks.log.Log
|
||||
message: "{{ task.id }}"
|
||||
- id: b
|
||||
type: io.kestra.core.tasks.log.Log
|
||||
message: "{{ task.id }}"
|
||||
- id: pause
|
||||
type: io.kestra.core.tasks.flows.Pause
|
||||
delay: PT1S
|
||||
tasks:
|
||||
- id: c
|
||||
type: io.kestra.core.tasks.log.Log
|
||||
message: "{{taskrun.attemptsCount == 1 ? 'ok' : ko}}"
|
||||
- id: d
|
||||
type: io.kestra.core.tasks.log.Log
|
||||
message: "{{ task.id }}"
|
||||
@@ -41,3 +41,8 @@ tasks:
|
||||
- id: default
|
||||
type: io.kestra.core.tasks.debugs.Return
|
||||
format: "{{task.id}} > {{ inputs.def }} > {{taskrun.startDate}}"
|
||||
|
||||
triggers:
|
||||
- id: schedule
|
||||
type: io.kestra.core.models.triggers.types.Schedule
|
||||
cron: "0 * * * *"
|
||||
@@ -21,4 +21,9 @@ tasks:
|
||||
labels:
|
||||
launchTaskLabel: launchFoo
|
||||
outputs:
|
||||
extracted: "{{ outputs.default.value ?? outputs['error-t1'].value }}"
|
||||
extracted: "{{ outputs.default.value ?? outputs['error-t1'].value }}"
|
||||
|
||||
triggers:
|
||||
- id: schedule
|
||||
type: io.kestra.core.models.triggers.types.Schedule
|
||||
cron: "0 * * * *"
|
||||
@@ -1,4 +1,4 @@
|
||||
version=0.15.0
|
||||
version=0.15.10
|
||||
|
||||
jacksonVersion=2.16.1
|
||||
micronautVersion=4.3.4
|
||||
|
||||
@@ -65,6 +65,21 @@ public class MemoryTriggerRepository implements TriggerRepositoryInterface {
|
||||
|
||||
@Override
|
||||
public ArrayListTotal<Trigger> find(Pageable from, String query, String tenantId, String namespace, String flowId) {
|
||||
throw new UnsupportedOperationException();
|
||||
List<Trigger> filteredTriggers = triggers.stream().filter(trigger -> {
|
||||
if (tenantId != null && !tenantId.equals(trigger.getTenantId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (namespace != null && !namespace.equals(trigger.getNamespace())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flowId != null && !flowId.equals(trigger.getFlowId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).toList();
|
||||
return new ArrayListTotal<>(filteredTriggers, filteredTriggers.size());
|
||||
}
|
||||
}
|
||||
|
||||
75
ui/package-lock.json
generated
75
ui/package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "kestra",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@kestra-io/ui-libs": "^0.0.36",
|
||||
"@kestra-io/ui-libs": "^0.0.39",
|
||||
"@popperjs/core": "npm:@sxzz/popperjs-es@2.11.7",
|
||||
"@vue-flow/background": "^1.2.0",
|
||||
"@vue-flow/controls": "1.0.6",
|
||||
@@ -121,6 +121,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
@@ -299,9 +300,9 @@
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
||||
},
|
||||
"node_modules/@kestra-io/ui-libs": {
|
||||
"version": "0.0.36",
|
||||
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.36.tgz",
|
||||
"integrity": "sha512-yJJa0+tVlcWVllMVHoFQVrWzR7nIyF/+6aN8u+OPnMaHR0zSUx9MwaxF6u/YYPoBw6J4zq4ysn3pspq/DGB4ag==",
|
||||
"version": "0.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.39.tgz",
|
||||
"integrity": "sha512-uX5Iqio6Ni6woUDuuEPP2fkImP6Y041FGR88Lt0Q6gfCEhxs4yW7WqCWpyvYRBHz4FT6Pb/qYscTIlXcYZIWKA==",
|
||||
"peerDependencies": {
|
||||
"@vue-flow/background": "^1.2.0",
|
||||
"@vue-flow/controls": "1.0.6",
|
||||
@@ -494,12 +495,6 @@
|
||||
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/linkify-it": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz",
|
||||
"integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.198",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.198.tgz",
|
||||
@@ -513,22 +508,6 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/markdown-it": {
|
||||
"version": "13.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz",
|
||||
"integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/linkify-it": "*",
|
||||
"@types/mdurl": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mdurl": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz",
|
||||
"integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/minimatch": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
|
||||
@@ -2222,13 +2201,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es5-ext": {
|
||||
"version": "0.10.62",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
|
||||
"integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
|
||||
"version": "0.10.64",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
|
||||
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"es6-iterator": "^2.0.3",
|
||||
"es6-symbol": "^3.1.3",
|
||||
"esniff": "^2.0.1",
|
||||
"next-tick": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2258,6 +2238,7 @@
|
||||
"version": "0.19.8",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz",
|
||||
"integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
@@ -2460,6 +2441,25 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/esniff": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
|
||||
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
|
||||
"dependencies": {
|
||||
"d": "^1.0.1",
|
||||
"es5-ext": "^0.10.62",
|
||||
"event-emitter": "^0.3.5",
|
||||
"type": "^2.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/esniff/node_modules/type": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
|
||||
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
|
||||
@@ -2536,6 +2536,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/event-emitter": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
|
||||
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
|
||||
"dependencies": {
|
||||
"d": "1",
|
||||
"es5-ext": "~0.10.14"
|
||||
}
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
|
||||
@@ -2717,9 +2726,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.4",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
|
||||
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -5176,7 +5185,7 @@
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kestra-io/ui-libs": "^0.0.36",
|
||||
"@kestra-io/ui-libs": "^0.0.39",
|
||||
"@popperjs/core": "npm:@sxzz/popperjs-es@2.11.7",
|
||||
"@vue-flow/background": "^1.2.0",
|
||||
"@vue-flow/controls": "1.0.6",
|
||||
|
||||
@@ -80,23 +80,23 @@
|
||||
&& isSecurityAdviceEnable) {
|
||||
const checked = ref(false);
|
||||
ElMessageBox({
|
||||
title: "Your data is not secured",
|
||||
title: this.$t("security_advice.title"),
|
||||
message: () => {
|
||||
return h("div", null, [
|
||||
h("p", null, "Don't lose one bit. Enable our free security features"),
|
||||
h("p", null, this.$t("security_advice.content")),
|
||||
h(ElSwitch, {
|
||||
modelValue: checked.value,
|
||||
"onUpdate:modelValue": (val) => {
|
||||
checked.value = val
|
||||
localStorage.setItem("security.advice.show", `${!val}`)
|
||||
},
|
||||
activeText: "Don't show again"
|
||||
activeText: this.$t("security_advice.switch_text")
|
||||
}),
|
||||
])
|
||||
},
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Enabled security",
|
||||
cancelButtonText: "Dismiss",
|
||||
confirmButtonText: this.$t("security_advice.enable"),
|
||||
cancelButtonText: this.$t("cancel"),
|
||||
center: false,
|
||||
showClose: false,
|
||||
}).then(() => {
|
||||
|
||||
61
ui/src/components/Drawer.vue
Normal file
61
ui/src/components/Drawer.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
:model-value="props.modelValue"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
:class="{'full-screen': fullScreen}"
|
||||
ref="editorDomElement"
|
||||
>
|
||||
<template #header>
|
||||
<span>
|
||||
{{ title }}
|
||||
<slot name="header" />
|
||||
</span>
|
||||
<el-button link class="full-screen">
|
||||
<Fullscreen :title="$t('toggle fullscreen')" @click="toggleFullScreen" />
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<slot name="footer" />
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<slot />
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import Fullscreen from "vue-material-design-icons/Fullscreen.vue"
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue"])
|
||||
|
||||
const fullScreen = ref(false);
|
||||
|
||||
const toggleFullScreen = () => {
|
||||
fullScreen.value = !fullScreen.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
button.full-screen {
|
||||
font-size: 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -99,7 +99,7 @@
|
||||
title: this.title || "Error",
|
||||
message: h("div", children),
|
||||
position: "bottom-right",
|
||||
type: "error",
|
||||
type: this.message.variant,
|
||||
duration: 0,
|
||||
dangerouslyUseHTMLString: true,
|
||||
customClass: "error-notification" + (children.length > 1 ? " large" : "")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-tabs class="router-link" :class="{top: top}" v-model="activeName">
|
||||
<el-tab-pane
|
||||
v-for="tab in tabs"
|
||||
v-for="tab in tabs.filter(t => !t.hidden)"
|
||||
:key="tab.name"
|
||||
:label="tab.title"
|
||||
:name="tab.name || 'default'"
|
||||
@@ -16,7 +16,7 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<section :class="containerClass">
|
||||
<section v-bind="$attrs" :class="containerClass">
|
||||
<component
|
||||
v-bind="{...activeTab.props, ...attrsWithoutClass}"
|
||||
v-on="activeTab['v-on'] ?? {}"
|
||||
@@ -104,7 +104,7 @@
|
||||
return {[this.activeTab.containerClass] : true};
|
||||
}
|
||||
|
||||
return {"container" : true}
|
||||
return {"container" : true, "mt-4": true};
|
||||
},
|
||||
activeTab() {
|
||||
return this.tabs
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
<el-table-column column-key="disable" class-name="row-action">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-if="!scope.row.missingSource"
|
||||
size="small"
|
||||
:active-text="$t('enabled')"
|
||||
:model-value="!scope.row.disabled"
|
||||
@@ -127,6 +128,9 @@
|
||||
class="switch-text"
|
||||
:active-action-icon="Check"
|
||||
/>
|
||||
<el-tooltip v-else :content="'flow source not found'">
|
||||
<AlertCircle class="trigger-issue-icon" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -154,6 +158,7 @@
|
||||
import action from "../../models/action";
|
||||
import TopNavBar from "../layout/TopNavBar.vue";
|
||||
import Check from "vue-material-design-icons/Check.vue";
|
||||
import AlertCircle from "vue-material-design-icons/AlertCircle.vue";
|
||||
</script>
|
||||
<script>
|
||||
import NamespaceSelect from "../namespace/NamespaceSelect.vue";
|
||||
@@ -251,9 +256,21 @@
|
||||
},
|
||||
triggersMerged() {
|
||||
return this.triggers.map(triggers => {
|
||||
return {...triggers.abstractTrigger, ...triggers.triggerContext, codeDisabled: triggers.abstractTrigger.disabled}
|
||||
return {
|
||||
...triggers?.abstractTrigger,
|
||||
...triggers.triggerContext,
|
||||
codeDisabled: triggers?.abstractTrigger?.disabled,
|
||||
// if we have no abstract trigger, it means that flow or trigger definition hasn't been found
|
||||
missingSource: !triggers.abstractTrigger
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
<style>
|
||||
.trigger-issue-icon{
|
||||
color: var(--bs-warning);
|
||||
font-size: 1.4em;
|
||||
}
|
||||
</style>
|
||||
@@ -28,13 +28,9 @@
|
||||
</el-form-item>
|
||||
</collapse>
|
||||
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isModalOpen"
|
||||
v-model="isModalOpen"
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
:append-to-body="true"
|
||||
size=""
|
||||
:title="$t('eval.title')"
|
||||
>
|
||||
<template #footer>
|
||||
@@ -52,7 +48,7 @@
|
||||
<p><strong>{{ debugError }}</strong></p>
|
||||
<pre class="mb-0">{{ debugStackTrace }}</pre>
|
||||
</el-alert>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
|
||||
<el-table
|
||||
:data="outputsPaginated"
|
||||
@@ -99,6 +95,7 @@
|
||||
import Pagination from "../layout/Pagination.vue";
|
||||
import {apiUrl} from "override/utils/route";
|
||||
import SubFlowLink from "../flows/SubFlowLink.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -107,6 +104,7 @@
|
||||
VarValue,
|
||||
Editor,
|
||||
Collapse,
|
||||
Drawer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
<li v-if="isAllowedEdit">
|
||||
<a :href="`${finalApiUrl}/executions/${execution.id}`" target="_blank">
|
||||
<el-button :icon="Api">
|
||||
{{ $t('api') }}
|
||||
{{ $t("api") }}
|
||||
</el-button>
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="canDelete">
|
||||
<el-button :icon="Delete" @click="deleteExecution">
|
||||
{{ $t('delete') }}
|
||||
{{ $t("delete") }}
|
||||
</el-button>
|
||||
</li>
|
||||
<li v-if="isAllowedEdit">
|
||||
@@ -26,8 +26,13 @@
|
||||
</template>
|
||||
</top-nav-bar>
|
||||
<template v-if="ready">
|
||||
<tabs :route-name="$route.params && $route.params.id ? 'executions/update': ''" @follow="follow" :tabs="tabs" />
|
||||
<tabs
|
||||
:route-name="$route.params && $route.params.id ? 'executions/update': ''"
|
||||
@follow="follow"
|
||||
:tabs="tabs"
|
||||
/>
|
||||
</template>
|
||||
<div v-else class="full-space" v-loading="!ready" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -71,7 +76,7 @@
|
||||
this.follow();
|
||||
window.addEventListener("popstate", this.follow)
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.previousExecutionId = this.$route.params.id
|
||||
},
|
||||
watch: {
|
||||
@@ -105,7 +110,11 @@
|
||||
) {
|
||||
this.$store.dispatch(
|
||||
"flow/loadFlow",
|
||||
{namespace: execution.namespace, id: execution.flowId, revision: execution.flowRevision}
|
||||
{
|
||||
namespace: execution.namespace,
|
||||
id: execution.flowId,
|
||||
revision: execution.flowRevision
|
||||
}
|
||||
);
|
||||
this.$store.dispatch("flow/loadRevisions", {
|
||||
namespace: execution.namespace,
|
||||
@@ -115,6 +124,16 @@
|
||||
|
||||
this.$store.commit("execution/setExecution", execution);
|
||||
}
|
||||
// sse.onerror doesnt return the details of the error
|
||||
// but as our emitter can only throw an error on 404
|
||||
// we can safely assume that the error
|
||||
this.sse.onerror = () => {
|
||||
this.$store.dispatch("core/showMessage", {
|
||||
variant: "error",
|
||||
title: this.$t("error"),
|
||||
message: this.$t("errors.404.flow or execution"),
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
closeSSE() {
|
||||
@@ -159,12 +178,14 @@
|
||||
];
|
||||
},
|
||||
editFlow() {
|
||||
this.$router.push({name:"flows/update", params: {
|
||||
namespace: this.$route.params.namespace,
|
||||
id: this.$route.params.flowId,
|
||||
tab: "editor",
|
||||
tenant: this.$route.params.tenant
|
||||
}})
|
||||
this.$router.push({
|
||||
name: "flows/update", params: {
|
||||
namespace: this.$route.params.namespace,
|
||||
id: this.$route.params.flowId,
|
||||
tab: "editor",
|
||||
tenant: this.$route.params.tenant
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteExecution() {
|
||||
if (this.execution) {
|
||||
@@ -268,3 +289,8 @@
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.full-space {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
</style>
|
||||
@@ -123,7 +123,7 @@
|
||||
stripe
|
||||
table-layout="auto"
|
||||
fixed
|
||||
@row-dblclick="onRowDoubleClick"
|
||||
@row-dblclick="row => onRowDoubleClick(executionParams(row))"
|
||||
@sort-change="onSort"
|
||||
@selection-change="handleSelectionChange"
|
||||
:selectable="!hidden?.includes('selection') && canCheck"
|
||||
@@ -184,7 +184,7 @@
|
||||
:label="$t('id')"
|
||||
>
|
||||
<template #default="scope">
|
||||
<id :value="scope.row.id" :shrink="true" @click="onRowDoubleClick(scope.row)" />
|
||||
<id :value="scope.row.id" :shrink="true" @click="onRowDoubleClick(executionParams(scope.row))" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -568,6 +568,13 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
executionParams(row) {
|
||||
return {
|
||||
namespace: row.namespace,
|
||||
flowId: row.flowId,
|
||||
id: row.id
|
||||
}
|
||||
},
|
||||
onDisplayColumnsChange(event) {
|
||||
localStorage.setItem(this.storageKey, event);
|
||||
this.displayColumns = event;
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
<el-button size="small" type="primary" :icon="EyeOutline" @click="getFilePreview">
|
||||
Preview
|
||||
</el-button>
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="selectedPreview === value && filePreview"
|
||||
v-model="isPreviewOpen"
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<h3>{{ $t("preview") }}</h3>
|
||||
{{ $t("preview") }}
|
||||
</template>
|
||||
<template #default>
|
||||
<el-alert v-if="filePreview.truncated" show-icon type="warning" :closable="false" class="mb-2">
|
||||
@@ -58,7 +54,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -70,9 +66,10 @@
|
||||
import ListPreview from "../ListPreview.vue";
|
||||
import {mapGetters, mapState} from "vuex";
|
||||
import Markdown from "../layout/Markdown.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
|
||||
export default {
|
||||
components: {Markdown, ListPreview, Editor},
|
||||
components: {Markdown, ListPreview, Editor, Drawer},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
|
||||
@@ -6,17 +6,13 @@
|
||||
{{ $t('metrics') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
:title="$t('metrics')"
|
||||
destroy-on-close
|
||||
:append-to-body="true"
|
||||
size=""
|
||||
direction="ltr"
|
||||
>
|
||||
<metrics-table ref="table" :task-run-id="taskRun.id" :execution="execution" />
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -26,10 +22,12 @@
|
||||
|
||||
<script>
|
||||
import MetricsTable from "./MetricsTable.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetricsTable
|
||||
MetricsTable,
|
||||
Drawer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
return {
|
||||
loadInit: false,
|
||||
metrics: undefined,
|
||||
metricsTotal: undefined
|
||||
metricsTotal: 0
|
||||
};
|
||||
},
|
||||
props: {
|
||||
|
||||
@@ -7,21 +7,17 @@
|
||||
{{ $t('outputs') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
:title="$t('outputs')"
|
||||
destroy-on-close
|
||||
:append-to-body="true"
|
||||
size=""
|
||||
direction="ltr"
|
||||
>
|
||||
<vars
|
||||
:execution="execution"
|
||||
class="table-unrounded mt-1"
|
||||
:data="outputs"
|
||||
/>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -30,10 +26,12 @@
|
||||
|
||||
<script>
|
||||
import Vars from "../executions/Vars.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Vars,
|
||||
Drawer,
|
||||
},
|
||||
props: {
|
||||
outputs: {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
<div v-if="execution.trigger" class="mt-4">
|
||||
<h5>{{ $t("trigger") }}</h5>
|
||||
<vars :execution="execution" :data="execution.trigger" />
|
||||
<vars :execution="execution" :data="triggerVariables" />
|
||||
</div>
|
||||
|
||||
<div v-if="execution.inputs" class="mt-4">
|
||||
@@ -183,6 +183,14 @@
|
||||
})
|
||||
})
|
||||
return inputs;
|
||||
},
|
||||
// This is used to display correctly trigger variables
|
||||
triggerVariables() {
|
||||
let trigger = this.execution.trigger
|
||||
trigger["trigger"] = this.execution.trigger.variables
|
||||
delete trigger["variables"]
|
||||
|
||||
return trigger
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<el-tooltip
|
||||
v-if="isReplay || enabled"
|
||||
:persistent="false"
|
||||
transition=""
|
||||
:hide-after="0"
|
||||
@@ -11,13 +12,13 @@
|
||||
:is="component"
|
||||
:icon="!isReplay ? RestartIcon : PlayBoxMultiple"
|
||||
@click="isOpen = !isOpen"
|
||||
v-if="component !== 'el-dropdown-item' && (isReplay || enabled)"
|
||||
v-if="component !== 'el-dropdown-item'"
|
||||
:disabled="!enabled"
|
||||
:class="!isReplay ? 'restart me-1' : ''"
|
||||
>
|
||||
{{ $t(replayOrRestart) }}
|
||||
</component>
|
||||
<span v-else-if="component === 'el-dropdown-item' && (isReplay || enabled)">
|
||||
<span v-else-if="component === 'el-dropdown-item'">
|
||||
<component
|
||||
:is="component"
|
||||
:icon="!isReplay ? RestartIcon : PlayBoxMultiple"
|
||||
@@ -48,7 +49,7 @@
|
||||
|
||||
<p v-html="$t(replayOrRestart + ' confirm', {id: execution.id})" />
|
||||
|
||||
<el-form>
|
||||
<el-form v-if="revisionsOptions && revisionsOptions.length > 1">
|
||||
<p class="text-muted">
|
||||
{{ $t("restart change revision") }}
|
||||
</p>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<el-table stripe table-layout="auto" fixed :data="variables">
|
||||
<el-table-column prop="key" rowspan="3" :label="$t('name')">
|
||||
<el-table-column prop="key" min-width="500" :label="$t('name')">
|
||||
<template #default="scope">
|
||||
<code>{{ scope.row.key }}</code>
|
||||
<code class="key-col">{{ scope.row.key }}</code>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -50,3 +50,8 @@
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.key-col {
|
||||
min-width: 200px;
|
||||
}
|
||||
</style>
|
||||
@@ -4,10 +4,10 @@
|
||||
@change="onSelectedFilterType()"
|
||||
class="filter"
|
||||
>
|
||||
<el-radio-button :label="filterType.RELATIVE">
|
||||
<el-radio-button :value="filterType.RELATIVE">
|
||||
{{ $t("relative") }}
|
||||
</el-radio-button>
|
||||
<el-radio-button :label="filterType.ABSOLUTE">
|
||||
<el-radio-button :value="filterType.ABSOLUTE">
|
||||
{{ $t("absolute") }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
@@ -38,11 +38,6 @@
|
||||
"update:filterValue"
|
||||
],
|
||||
created() {
|
||||
this.filterType = {
|
||||
RELATIVE: "REL",
|
||||
ABSOLUTE: "ABS"
|
||||
};
|
||||
|
||||
this.selectedFilterType = (this.$route.query.startDate || this.$route.query.endDate) ? this.filterType.ABSOLUTE : this.filterType.RELATIVE;
|
||||
},
|
||||
mounted() {
|
||||
@@ -50,7 +45,11 @@
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedFilterType: undefined
|
||||
selectedFilterType: undefined,
|
||||
filterType: {
|
||||
RELATIVE: "REL",
|
||||
ABSOLUTE: "ABS"
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
value: "P1D",
|
||||
label: "datepicker.last24hours"
|
||||
},
|
||||
{
|
||||
value: "P2D",
|
||||
label: "datepicker.last48hours"
|
||||
},
|
||||
{
|
||||
value: "P7D",
|
||||
label: "datepicker.last7days"
|
||||
|
||||
@@ -20,14 +20,17 @@
|
||||
const store = useStore();
|
||||
const axios = inject("axios")
|
||||
const router = getCurrentInstance().appContext.config.globalProperties.$router;
|
||||
const t = getCurrentInstance().appContext.config.globalProperties.$t;
|
||||
|
||||
const loaded = ref([]);
|
||||
const dependencies = ref({
|
||||
nodes: [],
|
||||
edges: []
|
||||
});
|
||||
const expanded = ref([]);
|
||||
|
||||
const isLoading = ref(false);
|
||||
const initialLoad = ref(true);
|
||||
|
||||
const load = (options) => {
|
||||
isLoading.value = true;
|
||||
@@ -41,8 +44,21 @@
|
||||
dependencies.value.edges.push(...response.data.edges)
|
||||
}
|
||||
|
||||
if (!initialLoad.value) {
|
||||
let newNodes = new Set(response.data.nodes.map(n => n.uid))
|
||||
let oldNodes = new Set(getNodes.value.map(n => n.id))
|
||||
console.log(response.data.nodes)
|
||||
console.log(getNodes.value)
|
||||
store.dispatch("core/showMessage", {
|
||||
variant: "success",
|
||||
title: t("dependencies loaded"),
|
||||
message: t("loaded x dependencies", [...newNodes].filter(node => !oldNodes.has(node)).length),
|
||||
})
|
||||
}
|
||||
|
||||
removeEdges(getEdges.value)
|
||||
removeNodes(getNodes.value)
|
||||
initialLoad.value = false
|
||||
|
||||
nextTick(() => {
|
||||
generateGraph();
|
||||
@@ -59,6 +75,7 @@
|
||||
};
|
||||
|
||||
const expand = (data) => {
|
||||
expanded.value.push(data.node.uid)
|
||||
load({namespace: data.namespace, id: data.flowId})
|
||||
};
|
||||
|
||||
@@ -110,7 +127,8 @@
|
||||
flowId: node.id,
|
||||
current: node.namespace === route.params.namespace && node.id === route.params.id,
|
||||
color: "pink",
|
||||
link: true
|
||||
link: true,
|
||||
expandEnabled: !expanded.value.includes(node.uid)
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<router-link v-if="flow" :to="{name: 'flows/create', query: {copy: true}}">
|
||||
<router-link v-if="flow && canCreate" :to="{name: 'flows/create', query: {copy: true}}">
|
||||
<el-button :icon="icon.ContentCopy" size="large">
|
||||
{{ $t('copy') }}
|
||||
</el-button>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
</collapse>
|
||||
</nav>
|
||||
|
||||
<div v-loading="isLoading">
|
||||
<div v-bind="$attrs" v-loading="isLoading">
|
||||
<el-card>
|
||||
<el-tooltip
|
||||
placement="bottom"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/>
|
||||
</el-select>
|
||||
<el-row :gutter="15">
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="revisionLeft !== undefined">
|
||||
<div class="revision-select mb-3">
|
||||
<el-select v-model="revisionLeft">
|
||||
<el-option
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<crud class="mt-3" permission="FLOW" :detail="{namespace: $route.params.namespace, flowId: $route.params.id, revision: revisionNumber(revisionLeft)}" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" v-if="revisionRight !== undefined">
|
||||
<div class="revision-select mb-3">
|
||||
<el-select v-model="revisionRight">
|
||||
<el-option
|
||||
@@ -74,13 +74,13 @@
|
||||
:show-doc="false"
|
||||
/>
|
||||
|
||||
<el-drawer v-if="isModalOpen" v-model="isModalOpen" destroy-on-close :append-to-body="true" size="">
|
||||
<drawer v-if="isModalOpen" v-model="isModalOpen">
|
||||
<template #header>
|
||||
<h5>{{ $t("revision") + `: ` + revision }}</h5>
|
||||
</template>
|
||||
|
||||
<editor v-model="revisionYaml" lang="yaml" />
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-alert class="mb-0" show-icon :closable="false">
|
||||
@@ -99,10 +99,11 @@
|
||||
import YamlUtils from "../../utils/yamlUtils";
|
||||
import Editor from "../../components/inputs/Editor.vue";
|
||||
import Crud from "override/components/auth/Crud.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
import {saveFlowTemplate} from "../../utils/flowTemplate";
|
||||
|
||||
export default {
|
||||
components: {Editor, Crud},
|
||||
components: {Editor, Crud, Drawer},
|
||||
created() {
|
||||
this.load();
|
||||
},
|
||||
@@ -218,8 +219,8 @@
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
revisionLeft: 0,
|
||||
revisionRight: 0,
|
||||
revisionLeft: undefined,
|
||||
revisionRight: undefined,
|
||||
revision: undefined,
|
||||
revisionId: undefined,
|
||||
revisionYaml: undefined,
|
||||
|
||||
@@ -56,9 +56,9 @@
|
||||
v-if="input.type === 'BOOLEAN'"
|
||||
v-model="inputs[input.id]"
|
||||
>
|
||||
<el-radio-button label="true" />
|
||||
<el-radio-button label="false" />
|
||||
<el-radio-button label="undefined" />
|
||||
<el-radio-button :label="$t('true')" value="true" />
|
||||
<el-radio-button :label="$t('false')" value="false" />
|
||||
<el-radio-button :label="$t('undefined')" value="undefined" />
|
||||
</el-radio-group>
|
||||
<el-date-picker
|
||||
v-if="input.type === 'DATETIME'"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<el-table
|
||||
v-bind="$attrs"
|
||||
:data="triggersWithType"
|
||||
stripe
|
||||
table-layout="auto"
|
||||
@@ -27,7 +28,7 @@
|
||||
<refresh-button
|
||||
:can-auto-refresh="true"
|
||||
@refresh="loadData"
|
||||
:size="'small'"
|
||||
size="small"
|
||||
custom-class="mx-1"
|
||||
/>
|
||||
</template>
|
||||
@@ -158,13 +159,9 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>{{ triggerId }}</code>
|
||||
@@ -172,7 +169,7 @@
|
||||
|
||||
<markdown v-if="triggerDefinition && triggerDefinition.description" :source="triggerDefinition.description" />
|
||||
<vars :data="modalData" />
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -193,12 +190,13 @@
|
||||
import Kicon from "../Kicon.vue"
|
||||
import DateAgo from "../layout/DateAgo.vue";
|
||||
import Vars from "../executions/Vars.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
import permission from "../../models/permission";
|
||||
import action from "../../models/action";
|
||||
import moment from "moment";
|
||||
|
||||
export default {
|
||||
components: {Markdown, Kicon, DateAgo, Vars},
|
||||
components: {Markdown, Kicon, DateAgo, Vars, Drawer},
|
||||
data() {
|
||||
return {
|
||||
triggerId: undefined,
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{name: 'flows/create'}">
|
||||
<router-link :to="{name: 'flows/create'}" v-if="canCreate">
|
||||
<el-button :icon="Plus" type="primary">
|
||||
{{ $t('create') }}
|
||||
</el-button>
|
||||
@@ -290,6 +290,9 @@
|
||||
canCheck() {
|
||||
return this.canRead || this.canDelete || this.canUpdate;
|
||||
},
|
||||
canCreate() {
|
||||
return this.user && this.user.isAllowed(permission.FLOW, action.CREATE, this.$route.query.namespace);
|
||||
},
|
||||
canRead() {
|
||||
return this.user && this.user.isAllowed(permission.FLOW, action.READ, this.$route.query.namespace);
|
||||
},
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
<template>
|
||||
<div class="w-100 d-flex flex-column align-items-center">
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isEditOpen"
|
||||
v-model="isEditOpen"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>inputs</code>
|
||||
@@ -40,7 +37,7 @@
|
||||
:definitions="inputSchema.schema.definitions"
|
||||
/>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
<div class="w-100">
|
||||
<div>
|
||||
<div class="d-flex w-100" v-for="(input, index) in newInputs" :key="index">
|
||||
@@ -74,8 +71,10 @@
|
||||
</script>
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import Drawer from "../Drawer.vue";
|
||||
|
||||
export default {
|
||||
components: {Drawer},
|
||||
emits: ["update:modelValue"],
|
||||
props: {
|
||||
inputs: {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
<template>
|
||||
<div class="w-100">
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isEditOpen"
|
||||
v-model="isEditOpen"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>variables</code>
|
||||
@@ -43,7 +40,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
<div class="w-100">
|
||||
<div v-if="variables">
|
||||
<div class="d-flex w-100" v-for="(value, index) in newVariables" :key="index">
|
||||
@@ -83,9 +80,10 @@
|
||||
|
||||
<script>
|
||||
import Editor from "../inputs/Editor.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
|
||||
export default {
|
||||
components: {Editor},
|
||||
components: {Editor, Drawer},
|
||||
emits: ["update:modelValue"],
|
||||
props: {
|
||||
variables: {
|
||||
|
||||
@@ -6,13 +6,9 @@
|
||||
ref="taskEdit"
|
||||
>
|
||||
<span v-if="component !== 'el-button' && !isHidden">{{ $t("show task source") }}</span>
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isModalOpen"
|
||||
v-model="isModalOpen"
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>{{ taskId || task?.id || $t("add task") }}</code>
|
||||
@@ -81,7 +77,7 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -94,6 +90,7 @@
|
||||
import YamlUtils from "../../utils/yamlUtils";
|
||||
import Editor from "../inputs/Editor.vue";
|
||||
import TaskEditor from "./TaskEditor.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
import {canSaveFlowTemplate, saveFlowTemplate} from "../../utils/flowTemplate";
|
||||
import {mapGetters, mapState} from "vuex";
|
||||
import Utils from "../../utils/utils";
|
||||
@@ -102,7 +99,7 @@
|
||||
import {SECTIONS} from "../../utils/constants";
|
||||
|
||||
export default {
|
||||
components: {Editor, TaskEditor, Markdown, ValidationError},
|
||||
components: {Editor, TaskEditor, Drawer, Markdown, ValidationError},
|
||||
emits: ["update:task", "close"],
|
||||
props: {
|
||||
component: {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
namespace: {
|
||||
type: String,
|
||||
required: true
|
||||
default: undefined
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -1,75 +1,71 @@
|
||||
<template>
|
||||
<div v-loading="!blueprint">
|
||||
<template v-if="blueprint">
|
||||
<top-nav-bar v-if="!embed" :title="blueprint.title" :breadcrumb="breadcrumb">
|
||||
<template #additional-right>
|
||||
<ul v-if="userCanCreateFlow">
|
||||
<router-link :to="{name: 'flows/create'}" @click="asAutoRestoreDraft">
|
||||
<el-button type="primary" v-if="!embed">
|
||||
{{ $t('use') }}
|
||||
</el-button>
|
||||
</router-link>
|
||||
</ul>
|
||||
</template>
|
||||
</top-nav-bar>
|
||||
<div v-else class="header-wrapper">
|
||||
<div class="header d-flex">
|
||||
<button class="back-button align-self-center">
|
||||
<el-icon size="medium" @click="goBack">
|
||||
<ArrowLeft />
|
||||
</el-icon>
|
||||
</button>
|
||||
<h2 class="blueprint-title align-self-center">
|
||||
{{ blueprint.title }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section :class="{'container': !embed}" class="blueprint-container">
|
||||
<el-card>
|
||||
<div class="embedded-topology" v-if="flowGraph">
|
||||
<low-code-editor
|
||||
v-if="flowGraph"
|
||||
:flow-id="parsedFlow.id"
|
||||
:namespace="parsedFlow.namespace"
|
||||
:flow-graph="flowGraph"
|
||||
:source="blueprint.flow"
|
||||
:view-type="embed ? 'source-blueprints' : 'blueprints'"
|
||||
is-read-only
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-row :gutter="30">
|
||||
<el-col :md="24" :lg="embed ? 24 : 18">
|
||||
<h4>{{ $t("source") }}</h4>
|
||||
<el-card>
|
||||
<editor class="position-relative" :read-only="true" :full-height="false" :minimap="false" :model-value="blueprint.flow" lang="yaml">
|
||||
<template #nav>
|
||||
<div class="position-absolute copy-wrapper">
|
||||
<el-tooltip trigger="click" content="Copied" placement="left" :auto-close="2000">
|
||||
<el-button text round :icon="icon.ContentCopy" @click="Utils.copy(blueprint.flow)" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</editor>
|
||||
</el-card>
|
||||
<template v-if="blueprint.description">
|
||||
<h4>About this blueprint</h4>
|
||||
<markdown :source="blueprint.description" />
|
||||
</template>
|
||||
</el-col>
|
||||
<el-col :md="24" :lg="embed ? 24 : 6">
|
||||
<h4>Plugins</h4>
|
||||
<div class="plugins-container">
|
||||
<div v-for="task in [...new Set(blueprint.includedTasks)]" :key="task">
|
||||
<task-icon :cls="task" :icons="icons" />
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
<top-nav-bar v-if="!embed && blueprint" :title="blueprint.title" :breadcrumb="breadcrumb" v-loading="!blueprint">
|
||||
<template #additional-right>
|
||||
<ul v-if="userCanCreateFlow">
|
||||
<router-link :to="{name: 'flows/create'}" @click="asAutoRestoreDraft">
|
||||
<el-button type="primary" v-if="!embed">
|
||||
{{ $t('use') }}
|
||||
</el-button>
|
||||
</router-link>
|
||||
</ul>
|
||||
</template>
|
||||
</top-nav-bar>
|
||||
<div v-else-if="blueprint" class="header-wrapper">
|
||||
<div class="header d-flex">
|
||||
<button class="back-button align-self-center">
|
||||
<el-icon size="medium" @click="goBack">
|
||||
<ArrowLeft />
|
||||
</el-icon>
|
||||
</button>
|
||||
<h2 class="blueprint-title align-self-center">
|
||||
{{ blueprint.title }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section v-bind="$attrs" :class="{'container': !embed}" class="blueprint-container" v-loading="!blueprint">
|
||||
<el-card v-if="blueprint">
|
||||
<div class="embedded-topology" v-if="flowGraph">
|
||||
<low-code-editor
|
||||
v-if="flowGraph"
|
||||
:flow-id="parsedFlow.id"
|
||||
:namespace="parsedFlow.namespace"
|
||||
:flow-graph="flowGraph"
|
||||
:source="blueprint.flow"
|
||||
:view-type="embed ? 'source-blueprints' : 'blueprints'"
|
||||
is-read-only
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-row :gutter="30" v-if="blueprint">
|
||||
<el-col :md="24" :lg="embed ? 24 : 18">
|
||||
<h4>{{ $t("source") }}</h4>
|
||||
<el-card>
|
||||
<editor class="position-relative" :read-only="true" :full-height="false" :minimap="false" :model-value="blueprint.flow" lang="yaml">
|
||||
<template #nav>
|
||||
<div class="position-absolute copy-wrapper">
|
||||
<el-tooltip trigger="click" content="Copied" placement="left" :auto-close="2000">
|
||||
<el-button text round :icon="icon.ContentCopy" @click="Utils.copy(blueprint.flow)" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</editor>
|
||||
</el-card>
|
||||
<template v-if="blueprint.description">
|
||||
<h4>About this blueprint</h4>
|
||||
<markdown :source="blueprint.description" />
|
||||
</template>
|
||||
</el-col>
|
||||
<el-col :md="24" :lg="embed ? 24 : 6">
|
||||
<h4>Plugins</h4>
|
||||
<div class="plugins-container">
|
||||
<div v-for="task in [...new Set(blueprint.includedTasks)]" :key="task">
|
||||
<task-icon :cls="task" :icons="icons" />
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</template>
|
||||
<script setup>
|
||||
import ArrowLeft from "vue-material-design-icons/ArrowLeft.vue";
|
||||
|
||||
@@ -8,13 +8,9 @@
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>{{ root }}</code>
|
||||
@@ -54,7 +50,7 @@
|
||||
{{ $t("save") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -64,9 +60,11 @@
|
||||
|
||||
<script>
|
||||
import Task from "./Task"
|
||||
import Drawer from "../../Drawer.vue"
|
||||
|
||||
export default {
|
||||
mixins: [Task],
|
||||
components: {Drawer},
|
||||
data() {
|
||||
return {
|
||||
isOpen: false,
|
||||
|
||||
@@ -8,13 +8,9 @@
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>{{ root }}</code>
|
||||
@@ -33,7 +29,7 @@
|
||||
{{ $t('save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -43,8 +39,11 @@
|
||||
|
||||
<script>
|
||||
import Task from "./Task"
|
||||
import Drawer from "../../Drawer.vue"
|
||||
|
||||
export default {
|
||||
mixins: [Task],
|
||||
components: {Drawer},
|
||||
data() {
|
||||
return {
|
||||
isOpen: false,
|
||||
|
||||
@@ -8,13 +8,10 @@
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
:title="root"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>{{ root }}</code>
|
||||
@@ -33,7 +30,7 @@
|
||||
{{ $t('save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -46,10 +43,11 @@
|
||||
import Task from "./Task"
|
||||
import YamlUtils from "../../../utils/yamlUtils";
|
||||
import TaskEditor from "../TaskEditor.vue"
|
||||
import Drawer from "../../Drawer.vue"
|
||||
|
||||
export default {
|
||||
mixins: [Task],
|
||||
components: {TaskEditor},
|
||||
components: {TaskEditor, Drawer},
|
||||
emits: ["update:modelValue"],
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -8,12 +8,9 @@
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>{{ root }}</code>
|
||||
@@ -31,7 +28,7 @@
|
||||
{{ $t('save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -44,10 +41,11 @@
|
||||
import Task from "./Task"
|
||||
import YamlUtils from "../../../utils/yamlUtils";
|
||||
import TaskEditor from "../TaskEditor.vue"
|
||||
import Drawer from "../../Drawer.vue"
|
||||
|
||||
export default {
|
||||
mixins: [Task],
|
||||
components: {TaskEditor},
|
||||
components: {TaskEditor, Drawer},
|
||||
emits: ["update:modelValue"],
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<top-nav-bar v-if="!embed" :title="routeInfo.title">
|
||||
<template #additional-right>
|
||||
<template #additional-right v-if="canCreate">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{name: 'flows/create'}">
|
||||
@@ -333,6 +333,9 @@
|
||||
title: this.$t("homeDashboard.title"),
|
||||
};
|
||||
},
|
||||
canCreate() {
|
||||
return this.user.isAllowedGlobal(permission.FLOW, action.CREATE)
|
||||
},
|
||||
defaultFilters() {
|
||||
return {
|
||||
startDate: this.$moment(this.startDate).toISOString(true),
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
navbar: {type: Boolean, default: true},
|
||||
input: {type: Boolean, default: false},
|
||||
fullHeight: {type: Boolean, default: true},
|
||||
customHeight: {type: Number, default: 7},
|
||||
theme: {type: String, default: undefined},
|
||||
placeholder: {type: [String, Number], default: ""},
|
||||
diffSideBySide: {type: Boolean, default: true},
|
||||
@@ -298,47 +299,49 @@
|
||||
|
||||
if (!this.fullHeight) {
|
||||
editor.onDidContentSizeChange(e => {
|
||||
this.$refs.container.style.height = (e.contentHeight + 7) + "px";
|
||||
this.$refs.container.style.height = (e.contentHeight + this.customHeight) + "px";
|
||||
});
|
||||
}
|
||||
|
||||
this.editor.onDidContentSizeChange(_ => {
|
||||
if (this.guidedProperties.monacoRange) {
|
||||
editor.revealLine(this.guidedProperties.monacoRange.endLineNumber);
|
||||
let decorations = [
|
||||
{
|
||||
range: this.guidedProperties.monacoRange,
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
inlineClassName: "highlight-text"
|
||||
if (!this.original) {
|
||||
this.editor.onDidContentSizeChange(_ => {
|
||||
if (this.guidedProperties.monacoRange) {
|
||||
editor.revealLine(this.guidedProperties.monacoRange.endLineNumber);
|
||||
let decorations = [
|
||||
{
|
||||
range: this.guidedProperties.monacoRange,
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
inlineClassName: "highlight-text"
|
||||
},
|
||||
className: "highlight-text",
|
||||
}
|
||||
];
|
||||
decorations = this.guidedProperties.monacoDisableRange ? decorations.concat([
|
||||
{
|
||||
range: this.guidedProperties.monacoDisableRange,
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
inlineClassName: "disable-text"
|
||||
},
|
||||
className: "disable-text",
|
||||
},
|
||||
className: "highlight-text",
|
||||
}
|
||||
];
|
||||
decorations = this.guidedProperties.monacoDisableRange ? decorations.concat([
|
||||
{
|
||||
range: this.guidedProperties.monacoDisableRange,
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
inlineClassName: "disable-text"
|
||||
},
|
||||
className: "disable-text",
|
||||
},
|
||||
]) : decorations;
|
||||
this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, decorations)
|
||||
} else {
|
||||
this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, []);
|
||||
}
|
||||
});
|
||||
]) : decorations;
|
||||
this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, decorations)
|
||||
} else {
|
||||
this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, []);
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.onDidChangeCursorPosition(() => {
|
||||
let position = this.editor.getPosition();
|
||||
let model = this.editor.getModel();
|
||||
clearTimeout(this.lastTimeout);
|
||||
this.lastTimeout = setTimeout(() => {
|
||||
this.$emit("cursor", {position: position, model: model})
|
||||
}, 100);
|
||||
});
|
||||
this.editor.onDidChangeCursorPosition(() => {
|
||||
let position = this.editor.getPosition();
|
||||
let model = this.editor.getModel();
|
||||
clearTimeout(this.lastTimeout);
|
||||
this.lastTimeout = setTimeout(() => {
|
||||
this.$emit("cursor", {position: position, model: model})
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
},
|
||||
autoFold(autoFold) {
|
||||
if (autoFold) {
|
||||
|
||||
@@ -80,6 +80,14 @@
|
||||
import {defineComponent} from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
emits: [
|
||||
"delete-flow",
|
||||
"copy",
|
||||
"open-new-error",
|
||||
"open-new-trigger",
|
||||
"open-edit-metadata",
|
||||
"save"
|
||||
],
|
||||
props: {
|
||||
isCreating: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import Utils from "@kestra-io/ui-libs/src/utils/Utils";
|
||||
import {apiUrl} from "override/utils/route";
|
||||
import EditorButtons from "./EditorButtons.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
|
||||
const store = useStore();
|
||||
const router = getCurrentInstance().appContext.config.globalProperties.$router;
|
||||
@@ -167,12 +168,13 @@
|
||||
}
|
||||
|
||||
const editorDomElement = ref(null);
|
||||
const editorWidthStorageKey = "editor-width";
|
||||
const editorWidthStorageKey = "editor-size";
|
||||
const editorWidth = ref(localStorage.getItem(editorWidthStorageKey));
|
||||
const validationDomElement = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const haveChange = ref(props.isDirty)
|
||||
const flowYaml = ref("")
|
||||
const flowYamlOrigin = ref("")
|
||||
const newTrigger = ref(null)
|
||||
const isNewTriggerOpen = ref(false)
|
||||
const newError = ref(null)
|
||||
@@ -199,11 +201,7 @@
|
||||
})
|
||||
|
||||
const leftEditorWidth = computed(() => {
|
||||
return (editorWidth.value > 50 ? (editorWidth.value - 50) * 2 : 0) + "%";
|
||||
})
|
||||
|
||||
const rightEditorWidth = computed(() => {
|
||||
return (editorWidth.value < 50 ? (50 - editorWidth.value) * 2 : 0) + "%";
|
||||
return editorWidth.value + "%";
|
||||
})
|
||||
|
||||
const autoRestorelocalStorageKey = computed(() => {
|
||||
@@ -235,7 +233,7 @@
|
||||
|
||||
const initYamlSource = async () => {
|
||||
flowYaml.value = props.flow.source;
|
||||
|
||||
flowYamlOrigin.value = props.flow.source;
|
||||
if (flowHaveTasks()) {
|
||||
if ([editorViewTypes.TOPOLOGY, editorViewTypes.SOURCE_TOPOLOGY].includes(viewType.value)) {
|
||||
await fetchGraph();
|
||||
@@ -759,7 +757,7 @@
|
||||
:is-read-only="props.isReadOnly"
|
||||
:can-delete="canDelete()"
|
||||
:is-allowed-edit="isAllowedEdit()"
|
||||
:have-change="haveChange"
|
||||
:have-change="flowYaml !== flowYamlOrigin"
|
||||
:flow-have-tasks="flowHaveTasks()"
|
||||
:errors="flowErrors"
|
||||
:warnings="flowWarnings"
|
||||
@@ -771,12 +769,12 @@
|
||||
@open-edit-metadata="isEditMetadataOpen = true;"
|
||||
/>
|
||||
</div>
|
||||
<div class="main-editor" v-loading="isLoading">
|
||||
<div v-bind="$attrs" class="main-editor" v-loading="isLoading">
|
||||
<editor
|
||||
ref="editorDomElement"
|
||||
v-if="combinedEditor || viewType === editorViewTypes.SOURCE"
|
||||
:class="combinedEditor ? 'editor-combined' : ''"
|
||||
:style="combinedEditor ? {'flex-basis': leftEditorWidth} : {}"
|
||||
:style="combinedEditor ? {'flex-basis': leftEditorWidth, 'flex-grow': 0} : {}"
|
||||
@save="save"
|
||||
@execute="execute"
|
||||
v-model="flowYaml"
|
||||
@@ -790,7 +788,7 @@
|
||||
:navbar="false"
|
||||
/>
|
||||
<div class="slider" @mousedown="dragEditor" v-if="combinedEditor" />
|
||||
<div :style="combinedEditor ? {'flex-basis': rightEditorWidth} : viewType === editorViewTypes.SOURCE ? {'display': 'none'} : {}">
|
||||
<div :style="viewType === editorViewTypes.SOURCE ? {'display': 'none'} : {}">
|
||||
<Blueprints v-if="viewType === 'source-blueprints' || blueprintsLoaded" @loaded="blueprintsLoaded = true" :class="{'d-none': viewType !== editorViewTypes.SOURCE_BLUEPRINTS}" embed class="combined-right-view enhance-readability" />
|
||||
<div
|
||||
class="topology-display"
|
||||
@@ -825,13 +823,10 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isNewErrorOpen"
|
||||
v-model="isNewErrorOpen"
|
||||
title="Add a global error handler"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<el-form label-position="top">
|
||||
<task-editor
|
||||
@@ -845,14 +840,11 @@
|
||||
{{ $t("save") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
<el-drawer
|
||||
</drawer>
|
||||
<drawer
|
||||
v-if="isNewTriggerOpen"
|
||||
v-model="isNewTriggerOpen"
|
||||
title="Add a trigger"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<el-form label-position="top">
|
||||
<task-editor
|
||||
@@ -866,13 +858,10 @@
|
||||
{{ $t("save") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
<el-drawer
|
||||
</drawer>
|
||||
<drawer
|
||||
v-if="isEditMetadataOpen"
|
||||
v-model="isEditMetadataOpen"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>flow metadata</code>
|
||||
@@ -896,7 +885,7 @@
|
||||
{{ $t("save") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</div>
|
||||
<el-dialog v-if="confirmOutdatedSaveDialog" v-model="confirmOutdatedSaveDialog" destroy-on-close :append-to-body="true">
|
||||
<template #header>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import LogLevelSelector from "../logs/LogLevelSelector.vue";
|
||||
import TaskRunDetails from "../logs/TaskRunDetails.vue";
|
||||
import Collapse from "../layout/Collapse.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
|
||||
// Topology
|
||||
import {
|
||||
@@ -340,12 +341,9 @@
|
||||
|
||||
<!-- Drawer to task informations (logs, description, ..) -->
|
||||
<!-- Assuming selectedTask is always the id and the required data for the opened drawer -->
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isDrawerOpen && selectedTask"
|
||||
v-model="isDrawerOpen"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>{{ selectedTask.id }}</code>
|
||||
@@ -373,7 +371,7 @@
|
||||
<div v-if="isShowDescriptionOpen">
|
||||
<markdown class="markdown-tooltip" :source="selectedTask.description" />
|
||||
</div>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -32,12 +32,21 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#environment {
|
||||
margin-bottom: 0.5em;
|
||||
background-color: v-bind('color');
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
margin-top: -1.25rem;
|
||||
|
||||
strong {
|
||||
color: var(--bs-body-bg);
|
||||
border: 1px solid v-bind('color');
|
||||
border-radius: var(--bs-border-radius);
|
||||
color: var(--bs-body-color);
|
||||
padding: 0.125rem 0.25rem;
|
||||
font-size: var(--font-size-sm);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 90%;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
<el-tag
|
||||
v-for="(value, key) in labelMap"
|
||||
:key="key"
|
||||
:type="checked(key, value) ? 'info' : 'default'"
|
||||
:type="checked(key, value) ? 'primary' : 'info'"
|
||||
class="me-1 labels"
|
||||
size="small"
|
||||
disable-transitions
|
||||
@@ -87,7 +87,7 @@
|
||||
}
|
||||
|
||||
&.el-tag--info {
|
||||
background: var(--bs-primary);
|
||||
background: var(--bs-gray-600);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,28 +7,26 @@
|
||||
/>
|
||||
</a>
|
||||
|
||||
<el-drawer
|
||||
<drawer
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
:title="title"
|
||||
destroy-on-close
|
||||
class="sm"
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<markdown class="markdown-tooltip" :source="description" />
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HelpCircle from "vue-material-design-icons/HelpCircle.vue";
|
||||
import Markdown from "./Markdown.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HelpCircle,
|
||||
Markdown
|
||||
Markdown,
|
||||
Drawer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<CheckboxBlankCircle v-if="hasUnread" class="new" title="" />
|
||||
</el-button>
|
||||
|
||||
<el-drawer size="50%" v-if="isOpen" v-model="isOpen" destroy-on-close :append-to-body="true" class="sm" :title="$t('feeds.title')">
|
||||
<drawer v-if="isOpen" v-model="isOpen" :title="$t('feeds.title')">
|
||||
<div class="post" v-for="(feed, index) in feeds" :key="feed.id">
|
||||
<div v-if="feed.image" class="mt-2">
|
||||
<img class="float-end" :src="feed.image" alt="">
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<el-divider v-if="index !== feeds.length - 1" />
|
||||
</div>
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -32,6 +32,7 @@
|
||||
import CheckboxBlankCircle from "vue-material-design-icons/CheckboxBlankCircle.vue";
|
||||
import Markdown from "./Markdown.vue";
|
||||
import DateAgo from "./DateAgo.vue";
|
||||
import Drawer from "../Drawer.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -39,7 +40,8 @@
|
||||
OpenInNew,
|
||||
CheckboxBlankCircle,
|
||||
Markdown,
|
||||
DateAgo
|
||||
DateAgo,
|
||||
Drawer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {storageKeys} from "../../utils/constants";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
total: {type: Number, default: 0},
|
||||
@@ -61,14 +63,22 @@
|
||||
},
|
||||
methods: {
|
||||
initState() {
|
||||
let internalSize = parseInt(localStorage.getItem(storageKeys.PAGINATION_SIZE) || this.$route.query.size || this.size)
|
||||
let internalPage = parseInt(this.$route.query.page || this.page)
|
||||
this.$emit("page-changed", {
|
||||
page: internalPage,
|
||||
size: internalSize,
|
||||
});
|
||||
|
||||
return {
|
||||
internalSize: parseInt(this.$route.query.size || this.size),
|
||||
internalPage: parseInt(this.$route.query.page || this.page)
|
||||
internalSize: internalSize,
|
||||
internalPage: internalPage
|
||||
}
|
||||
},
|
||||
pageSizeChange(value) {
|
||||
pageSizeChange: function (value) {
|
||||
this.internalPage = 1;
|
||||
this.internalSize = value;
|
||||
localStorage.setItem(storageKeys.PAGINATION_SIZE, value);
|
||||
this.$emit("page-changed", {
|
||||
page: 1,
|
||||
size: this.internalSize,
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: "'default'"
|
||||
default: "default"
|
||||
},
|
||||
customClass: {
|
||||
type: String,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<top-nav-bar v-if="!embed" :title="routeInfo.title" />
|
||||
<section :class="{'container': !embed}" class="log-panel">
|
||||
<section v-bind="$attrs" :class="{'container': !embed}" class="log-panel">
|
||||
<div class="log-content">
|
||||
<data-table @page-changed="onPageChanged" ref="dataTable" :total="total" :size="pageSize" :page="pageNumber" :embed="embed">
|
||||
<template #navbar v-if="embed === false">
|
||||
|
||||
@@ -214,7 +214,7 @@
|
||||
this.autofoldTextEditor = localStorage.getItem("autofoldTextEditor") === "true";
|
||||
this.guidedTour = localStorage.getItem("tourDoneOrSkip") === "true";
|
||||
this.logDisplay = localStorage.getItem("logDisplay") || logDisplayTypes.DEFAULT;
|
||||
this.editorFontSize = localStorage.getItem("editorFontSize") || 12;
|
||||
this.editorFontSize = parseInt(localStorage.getItem("editorFontSize")) || 12;
|
||||
this.editorFontFamily = localStorage.getItem("editorFontFamily") || "'Source Code Pro', monospace";
|
||||
this.executeFlowBehaviour = localStorage.getItem("executeFlowBehaviour") || "same tab";
|
||||
this.envName = store.getters["layout/envName"] || this.configs?.environment?.name;
|
||||
|
||||
@@ -5,10 +5,12 @@ import App from "./App.vue"
|
||||
import initApp from "./utils/init"
|
||||
import configureAxios from "./utils/axios"
|
||||
import routes from "./routes/routes";
|
||||
import translations from "./translations.json";
|
||||
import fr from "./translations/fr.json";
|
||||
import en from "./translations/en.json";
|
||||
import stores from "./stores/store";
|
||||
|
||||
const app = createApp(App)
|
||||
const translations = {...fr,...en}
|
||||
const {store, router} = initApp(app, routes, stores, translations);
|
||||
|
||||
// axios
|
||||
|
||||
@@ -44,6 +44,9 @@ export default {
|
||||
canSave() {
|
||||
return canSaveFlowTemplate(true, this.user, this.item, this.dataType);
|
||||
},
|
||||
canCreate() {
|
||||
return this.dataType === "flow" && this.user.isAllowed(permission.FLOW, action.CREATE, this.item.namespace)
|
||||
},
|
||||
canExecute() {
|
||||
return this.dataType === "flow" && this.user.isAllowed(permission.EXECUTION, action.CREATE, this.item.namespace)
|
||||
},
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
<Environment />
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<span class="version">{{ configs.version }}</span>
|
||||
</template>
|
||||
<template #footer />
|
||||
|
||||
<template #toggle-icon>
|
||||
<chevron-right v-if="collapsed" />
|
||||
<chevron-left v-else />
|
||||
<el-button>
|
||||
<chevron-double-right v-if="collapsed" />
|
||||
<chevron-double-left v-else />
|
||||
</el-button>
|
||||
<span class="version">{{ configs.version }}</span>
|
||||
</template>
|
||||
</sidebar-menu>
|
||||
</template>
|
||||
@@ -29,28 +30,28 @@
|
||||
<script>
|
||||
import {SidebarMenu} from "vue-sidebar-menu";
|
||||
import Environment from "../../components/layout/Environment.vue";
|
||||
import ChevronLeft from "vue-material-design-icons/ChevronLeft.vue";
|
||||
import ChevronRight from "vue-material-design-icons/ChevronRight.vue";
|
||||
import ChevronDoubleLeft from "vue-material-design-icons/ChevronDoubleLeft.vue";
|
||||
import ChevronDoubleRight from "vue-material-design-icons/ChevronDoubleRight.vue";
|
||||
import FileTreeOutline from "vue-material-design-icons/FileTreeOutline.vue";
|
||||
import ContentCopy from "vue-material-design-icons/ContentCopy.vue";
|
||||
import TimelineClockOutline from "vue-material-design-icons/TimelineClockOutline.vue";
|
||||
import TimelineTextOutline from "vue-material-design-icons/TimelineTextOutline.vue";
|
||||
import NotebookOutline from "vue-material-design-icons/NotebookOutline.vue";
|
||||
import Ballot from "vue-material-design-icons/Ballot.vue";
|
||||
import ChartTimeline from "vue-material-design-icons/ChartTimeline.vue";
|
||||
import BallotOutline from "vue-material-design-icons/BallotOutline.vue";
|
||||
import FolderEditOutline from "vue-material-design-icons/FolderEditOutline.vue";
|
||||
import AccountSupervisorOutline from "vue-material-design-icons/AccountSupervisorOutline.vue";
|
||||
import ShieldAccountVariantOutline from "vue-material-design-icons/ShieldAccountVariantOutline.vue";
|
||||
import CogOutline from "vue-material-design-icons/CogOutline.vue";
|
||||
import ViewDashboardVariantOutline from "vue-material-design-icons/ViewDashboardVariantOutline.vue";
|
||||
import TimerCogOutline from "vue-material-design-icons/TimerCogOutline.vue";
|
||||
import {mapState} from "vuex";
|
||||
import AccountHardHatOutline from "vue-material-design-icons/AccountHardHatOutline.vue";
|
||||
import ChartBoxOutline from "vue-material-design-icons/ChartBoxOutline.vue";
|
||||
import ServerOutline from "vue-material-design-icons/ServerOutline.vue";
|
||||
import {shallowRef} from "vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronDoubleLeft,
|
||||
ChevronDoubleRight,
|
||||
SidebarMenu,
|
||||
Environment
|
||||
},
|
||||
@@ -146,7 +147,7 @@
|
||||
routes: this.routeStartWith("taskruns"),
|
||||
title: this.$t("taskruns"),
|
||||
icon: {
|
||||
element: shallowRef(TimelineTextOutline),
|
||||
element: shallowRef(ChartTimeline),
|
||||
class: "menu-icon"
|
||||
},
|
||||
hidden: !this.configs.isTaskRunEnabled
|
||||
@@ -156,7 +157,7 @@
|
||||
routes: this.routeStartWith("logs"),
|
||||
title: this.$t("logs"),
|
||||
icon: {
|
||||
element: shallowRef(NotebookOutline),
|
||||
element: shallowRef(TimelineTextOutline),
|
||||
class: "menu-icon"
|
||||
},
|
||||
},
|
||||
@@ -165,7 +166,7 @@
|
||||
routes: this.routeStartWith("blueprints"),
|
||||
title: this.$t("blueprints.title"),
|
||||
icon: {
|
||||
element: shallowRef(Ballot),
|
||||
element: shallowRef(BallotOutline),
|
||||
class: "menu-icon"
|
||||
},
|
||||
},
|
||||
@@ -173,7 +174,7 @@
|
||||
title: this.$t("administration"),
|
||||
routes: this.routeStartWith("admin"),
|
||||
icon: {
|
||||
element: shallowRef(AccountSupervisorOutline),
|
||||
element: shallowRef(ShieldAccountVariantOutline),
|
||||
class: "menu-icon"
|
||||
},
|
||||
child: [
|
||||
@@ -191,7 +192,7 @@
|
||||
routes: this.routeStartWith("admin/workers"),
|
||||
title: this.$t("workers"),
|
||||
icon: {
|
||||
element: shallowRef(AccountHardHatOutline),
|
||||
element: shallowRef(ServerOutline),
|
||||
class: "menu-icon"
|
||||
},
|
||||
},
|
||||
@@ -302,13 +303,14 @@
|
||||
span.version {
|
||||
transition: 0.2s all;
|
||||
white-space: nowrap;
|
||||
font-size: var(--el-font-size-extra-small);
|
||||
font-size: var(--font-size-xs);
|
||||
text-align: center;
|
||||
display: block;
|
||||
color: var(--bs-gray-400);
|
||||
color: var(--bs-gray-600);
|
||||
width: auto;
|
||||
|
||||
html.dark & {
|
||||
color: var(--bs-gray-600);
|
||||
color: var(--bs-gray-800);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,11 +355,21 @@
|
||||
}
|
||||
|
||||
.vsm--toggle-btn {
|
||||
padding-top: 4px;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
font-size: 20px;
|
||||
background: transparent;
|
||||
color: var(--bs-secondary);
|
||||
height: 30px;
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
|
||||
.el-button {
|
||||
padding: 8px;
|
||||
margin-right: 15px;
|
||||
transition: margin-right 0.2s ease;
|
||||
html.dark & {
|
||||
background: var(--bs-gray-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -410,8 +422,13 @@
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
span.version {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<el-radio-group v-if="ready" v-model="selectedTag" class="tags-selection">
|
||||
<el-radio-button
|
||||
:key="0"
|
||||
:label="0"
|
||||
:value="0"
|
||||
class="hoverable"
|
||||
>
|
||||
{{ $t("all tags") }}
|
||||
@@ -20,7 +20,7 @@
|
||||
<el-radio-button
|
||||
v-for="tag in Object.values(tags || {})"
|
||||
:key="tag.id"
|
||||
:label="tag.id"
|
||||
:value="tag.id"
|
||||
class="hoverable"
|
||||
>
|
||||
{{ tag.name }}
|
||||
|
||||
@@ -39,7 +39,7 @@ export default [
|
||||
|
||||
//Executions
|
||||
{name: "executions/list", path: "/:tenant?/executions", component: Executions},
|
||||
{name: "executions/update", path: "/:tenant?/executions/:namespace/:flowId/:id/:tab?", component: ExecutionRoot, props: true},
|
||||
{name: "executions/update", path: "/:tenant?/executions/:namespace/:flowId/:id/:tab?", component: ExecutionRoot},
|
||||
|
||||
//TaskRuns
|
||||
{name: "taskruns/list", path: "/:tenant?/taskruns", component: TaskRuns},
|
||||
|
||||
@@ -707,6 +707,10 @@ form.ks-horizontal {
|
||||
}
|
||||
}
|
||||
|
||||
&.full-screen {
|
||||
width: 99% !important;
|
||||
}
|
||||
|
||||
.el-drawer__header {
|
||||
padding: var(--spacer);
|
||||
margin-bottom: 0;
|
||||
|
||||
627
ui/src/translations/en.json
Normal file
627
ui/src/translations/en.json
Normal file
@@ -0,0 +1,627 @@
|
||||
{
|
||||
"en": {
|
||||
"id": "Id",
|
||||
"type": "Type",
|
||||
"ok": "OK",
|
||||
"close": "Close",
|
||||
"namespace": "Namespace",
|
||||
"description": "Description",
|
||||
"show description": "Show description",
|
||||
"revision": "Revision",
|
||||
"Language": "Language",
|
||||
"Set default page": "Set default page",
|
||||
"settings": "Settings",
|
||||
"theme": "Theme",
|
||||
"flows": "Flows",
|
||||
"flow": "Flow",
|
||||
"invalid source": "Invalid source code",
|
||||
"update": "Update",
|
||||
"update ok": "is updated",
|
||||
"delete ok": "is deleted",
|
||||
"Default page": "Default page",
|
||||
"confirmation": "Confirmation",
|
||||
"delete confirm": "Are you sure to delete <code>{name}</code>?",
|
||||
"outdated revision save confirmation": {
|
||||
"confirm": "Do you want to overwrite it?",
|
||||
"update": {
|
||||
"title": "Outdated revision",
|
||||
"description": "The revision you are editing is outdated.",
|
||||
"details": "Check the Revisions tab for more details about the latest version."
|
||||
},
|
||||
"create": {
|
||||
"title": "Flow already exists",
|
||||
"description": "A Flow with the same id / namespace already exists.",
|
||||
"details": "Check the Flows menu for more details about the existing flow."
|
||||
}
|
||||
},
|
||||
"is deprecated": "is deprecated",
|
||||
"success": "Success",
|
||||
"save": "Save",
|
||||
"saved": "Successfully saved",
|
||||
"saved done": "<em>{name}</em> is successfully saved",
|
||||
"multiple saved done": "{name} have been saved",
|
||||
"delete": "Delete",
|
||||
"deleted": "Successfully deleted",
|
||||
"deleted confirm": "<em>{name}</em> is successfully deleted!",
|
||||
"Select namespace": "Namespace",
|
||||
"Add flow": "Add flow",
|
||||
"add": "Add",
|
||||
"copy": "Copy",
|
||||
"Per page": "per page",
|
||||
"new": "New",
|
||||
"edit": "Edit",
|
||||
"edit flow": "Edit flow",
|
||||
"executions": "Executions",
|
||||
"execution": "Execution",
|
||||
"details": "Details",
|
||||
"overview": "Overview",
|
||||
"gantt": "Gantt",
|
||||
"logs": "Logs",
|
||||
"duration": "Duration",
|
||||
"running duration": "Running duration",
|
||||
"queued duration": "Queued duration",
|
||||
"choose file": "Choose a file or drop it here...",
|
||||
"launch execution": "Execute",
|
||||
"warning flow with triggers": "This flow contains triggers and a manual execution will fail if this flow depends on trigger expressions.",
|
||||
"triggered": "Execution trigger",
|
||||
"triggered done": "Execution <em>{name}</em> is successfully triggered",
|
||||
"topology": "Topology",
|
||||
"date": "Date",
|
||||
"datepicker": {
|
||||
"last5minutes": "Last 5 minutes",
|
||||
"last15minutes": "Last 15 minutes",
|
||||
"last1hour": "Last 1 hour",
|
||||
"last12hours": "Last 12 hours",
|
||||
"last24hours": "Last 24 hours",
|
||||
"last48hours": "Last 48 hours",
|
||||
"last7days": "Last 7 days",
|
||||
"last30days": "Last 30 days",
|
||||
"last365days": "Last 365 days",
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"dayBeforeYesterday": "Day before yesterday",
|
||||
"thisWeek": "This week",
|
||||
"thisWeekSoFar": "This week so far",
|
||||
"previousWeek": "Previous week",
|
||||
"thisMonth": "This month",
|
||||
"thisMonthSoFar": "This month so far",
|
||||
"previousMonth": "Previous month",
|
||||
"thisYear": "This year",
|
||||
"thisYearSoFar": "This year so far",
|
||||
"previousYear": "Previous year"
|
||||
},
|
||||
"created date": "Created date",
|
||||
"updated date": "Updated date",
|
||||
"next execution date": "Next execution date",
|
||||
"jump to...": "Jump to...",
|
||||
"source": "Source",
|
||||
"home": "Home",
|
||||
"create": "Create",
|
||||
"add flow": "Add flow",
|
||||
"from": "From",
|
||||
"to": "To",
|
||||
"search filters": {
|
||||
"saved": "Saved filters",
|
||||
"manage": "Manage search filters",
|
||||
"manage desc": "Manage saved search filters",
|
||||
"save filter": "Save filter",
|
||||
"filter name": "Filter name"
|
||||
},
|
||||
"steps": "Steps",
|
||||
"state": "State",
|
||||
"search term in message": "Search term in message",
|
||||
"search": "Search",
|
||||
"search blueprint": "Search blueprints",
|
||||
"all tags": "All tags",
|
||||
"source search": "Source search",
|
||||
"filter by log level": "Filter by log level",
|
||||
"selected": "Selected",
|
||||
"task": "Task",
|
||||
"task logs": "Task logs",
|
||||
"display flow {id} executions": "Display flow {id} executions",
|
||||
"actions": "Actions",
|
||||
"select datetime": "Select a date",
|
||||
"invalid field": "Invalid field: {name}",
|
||||
"required field": "Required field",
|
||||
"error": "Error",
|
||||
"form": "Form",
|
||||
"form error": "Form error",
|
||||
"display topology for flow": "Display topology for flow",
|
||||
"cannot create topology": "Unable to create topology for flow",
|
||||
"start date": "Start date",
|
||||
"end date": "End date",
|
||||
"creation": "Creation",
|
||||
"flow creation": "Flow creation",
|
||||
"start datetime": "Start datetime",
|
||||
"end datetime": "End datetime",
|
||||
"restart": "Restart",
|
||||
"restart latest revision": "Restart latest revision",
|
||||
"restart tooltip": "Restart the execution from the <code>{state}</code> task",
|
||||
"restarted": "Execution is restarted",
|
||||
"restart confirm": "Are you sure to restart execution <code>{id}</code>?",
|
||||
"restart change revision": "You can change the revision that will be used for the new execution.",
|
||||
"replay": "Replay",
|
||||
"replay from task tooltip": "Create a similar execution starting from task <code>{taskId}</code>",
|
||||
"replay from beginning tooltip": "Create a similar execution starting from the beginning",
|
||||
"replay latest revision": "Replay using latest revision",
|
||||
"replayed": "Execution is replayed",
|
||||
"replay confirm": "Are you sure to replay this execution <code>{id}</code> and create a new one?",
|
||||
"prefill inputs": "Prefill",
|
||||
"current": "current",
|
||||
"change status": "Change status",
|
||||
"change status done": "Task status has been updated",
|
||||
"change status confirm": "Are you sure to change the status on task <code>{task}</code> for execution <code>{id}</code>?",
|
||||
"change status hint": {
|
||||
"WARNING": [
|
||||
"The flow will be marked as WARNING.",
|
||||
"The next tasks will be executed.",
|
||||
"The error tasks will be executed."
|
||||
],
|
||||
"FAILED": [
|
||||
"The flow will be marked as FAILED.",
|
||||
"No other task will be executed.",
|
||||
"The error tasks will be executed."
|
||||
],
|
||||
"SUCCESS": [
|
||||
"The flow will restart as this task was successful.",
|
||||
"All the blocked tasks will be executed.",
|
||||
"The flow will be in a SUCCESS state if all task runs are successful."
|
||||
],
|
||||
"RUNNING": [
|
||||
"The flow will restart and will execute all next tasks.",
|
||||
"All blocked tasks will be executed."
|
||||
]
|
||||
},
|
||||
"mark as": "Mark as <code>{status}</code>",
|
||||
"kill": "Kill",
|
||||
"kill parents and subflow": "Kill parents and subflows",
|
||||
"kill only parents": "Kill parents only",
|
||||
"killed confirm": "Are you sure to kill execution <code>{id}</code>?",
|
||||
"killed done": "Execution is queued for killing",
|
||||
"resume": "Resume",
|
||||
"resumed confirm": "Are you sure to resume execution <code>{id}</code>?",
|
||||
"resumed done": "Execution is resumed",
|
||||
"toggle output": "Toggle outputs",
|
||||
"metrics": "Metrics",
|
||||
"no data current task": "No data available for current task",
|
||||
"outputs": "Outputs",
|
||||
"output": "Output",
|
||||
"eval": {
|
||||
"title": "Render expression",
|
||||
"tooltip": "You need to choose a task to render an expression from."
|
||||
},
|
||||
"attempt": "Attempt",
|
||||
"toggle output display": "Toggle output display",
|
||||
"name": "Name",
|
||||
"key": "Key",
|
||||
"value": "Value",
|
||||
"each value": "Iteration value",
|
||||
"current execution": "Current execution",
|
||||
"parent execution": "Parent execution",
|
||||
"original execution": "Original execution",
|
||||
"automatic refresh": "Automatic refresh",
|
||||
"toggle periodic refresh each 10 seconds": "Toggle periodic refresh every 10 seconds",
|
||||
"trigger refresh": "Trigger refresh",
|
||||
"refresh": "Refresh",
|
||||
"topology-graph": {
|
||||
"graph-orientation": "Graph orientation",
|
||||
"zoom-in": "Zoom in",
|
||||
"zoom-out": "Zoom out",
|
||||
"zoom-reset": "Reset zoom",
|
||||
"zoom-fit": "Fit"
|
||||
},
|
||||
"show task logs": "Show task logs",
|
||||
"show task outputs": "Show task outputs",
|
||||
"show task source": "Show task source",
|
||||
"display output for specific task": "Display output for a specific task",
|
||||
"display metric for specific task": "Display metric for a specific task",
|
||||
"display direct sub tasks count": "Display direct subtask count",
|
||||
"stream": "Stream",
|
||||
"execution statistics": "Execution statistics",
|
||||
"stats": "Stats",
|
||||
"your usage": "Your usage",
|
||||
"namespaces": "Namespaces",
|
||||
"tasks": "Tasks",
|
||||
"executions duration (in minutes)": "Total executions duration (in minutes)",
|
||||
"last 48 hours": "last 48 hours",
|
||||
"configure basic auth": "Configure Basic Authentication",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"confirm password": "Confirm password",
|
||||
"passwords do not match": "Passwords do not match",
|
||||
"avg duration": "Avg. execution duration",
|
||||
"neutral trend": "Stable",
|
||||
"up trend": "Increasing",
|
||||
"down trend": "Decreasing",
|
||||
"update aborted": "update aborted",
|
||||
"invalid flow": "Invalid flow",
|
||||
"invalid yaml": "Invalid YAML",
|
||||
"inputs": "Inputs",
|
||||
"input": "Input",
|
||||
"variables": "Variables",
|
||||
"download": "Download",
|
||||
"documentation": {
|
||||
"documentation": "Documentation",
|
||||
"github": "Open a GitHub issue"
|
||||
},
|
||||
"blueprints": {
|
||||
"title": "Blueprints",
|
||||
"header": {
|
||||
"catch phrase": {
|
||||
"1": "The first step is always the hardest.",
|
||||
"2": "Explore blueprints to kick-start your next flow."
|
||||
}
|
||||
}
|
||||
},
|
||||
"use": "Use",
|
||||
"plugins": {
|
||||
"name": "Plugin",
|
||||
"names": "Plugins",
|
||||
"please": "Please choose a task on the right to see its documentation"
|
||||
},
|
||||
"last execution date": "Last execution date",
|
||||
"last execution status": "Last status",
|
||||
"last X days count": "{count} in last {days} days",
|
||||
"date range count": "{count} between the {startDate} and the {endDate}",
|
||||
"date count": "{count} on the {date}",
|
||||
"revisions": "Revisions",
|
||||
"no revisions found": "Only one revision exists for this flow",
|
||||
"see full revision": "See full revision",
|
||||
"side-by-side": "side-by-side",
|
||||
"line-by-line": "line-by-line",
|
||||
"template": "Template",
|
||||
"template creation": "Template creation",
|
||||
"templates": "Templates",
|
||||
"templates deprecated": "Templates are deprecated. Please use subflows instead. See the <a href=\"https://kestra.io/docs/migrations/templates\" target=\"_blank\">Migrations section</a> explaining how you can migrate from templates to subflows.",
|
||||
"no result": "No results for the current selection",
|
||||
"trigger": "Trigger",
|
||||
"triggers": "Triggers",
|
||||
"trigger details": "Trigger details",
|
||||
"triggerflow disabled": "Trigger is disabled from flow definition",
|
||||
"conditions": "conditions",
|
||||
"taskruns": "Task Runs",
|
||||
"Fold auto": "Editor: automatic fold of multilines",
|
||||
"Fold content lines": "Fold multiline strings",
|
||||
"Unfold content lines": "Unfold multiline strings",
|
||||
"Max displayable": "Max displayable",
|
||||
"Total": "Total",
|
||||
"sub flow": "Subflow",
|
||||
"execute": "Execute",
|
||||
"execute flow behaviour": "Execute the flow",
|
||||
"open in same tab": "In the same tab",
|
||||
"open in new tab": "In a new tab",
|
||||
"execute the flow": "Execute the flow <code>{id}</code>",
|
||||
"execute flow now ?": "Do you want to execute this flow?",
|
||||
"Default namespace": "Default namespace",
|
||||
"Default log level": "Default log level",
|
||||
"unsaved changed ?": "You have unsaved changes, do you want to leave this page?",
|
||||
"Editor theme": "Editor theme",
|
||||
"Editor fontsize": "Editor font size",
|
||||
"Editor fontfamily": "Editor font family",
|
||||
"errors": {
|
||||
"404": {
|
||||
"title": "Page not found",
|
||||
"content": "The requested URL was not found on this server. <span class=\"text-muted\">That’s all we know.</span>",
|
||||
"flow or execution": "The flow or execution you are looking for does not exist."
|
||||
}
|
||||
},
|
||||
"copy logs": "Copy logs",
|
||||
"download logs": "Download logs",
|
||||
"delete logs": "Delete logs",
|
||||
"toggle fullscreen": "Toggle fullscreen",
|
||||
"copied": "Copied",
|
||||
"tags": "Tags",
|
||||
"disabled flow title": "This flow is disabled",
|
||||
"disabled flow desc": "This flow is disabled, please enable it in order to execute it.",
|
||||
"label": "Label",
|
||||
"labels": "Labels",
|
||||
"label filter placeholder": "Label as 'key:value'",
|
||||
"execution labels": "Execution labels",
|
||||
"wrong labels": "Empty key or value is not allowed in labels",
|
||||
"feeds": {
|
||||
"title": "What's new at Kestra"
|
||||
},
|
||||
"delete execution running": "<div class=\"alert alert-warning mt-2 mb-0\">This execution is still running, deleting it will not stop it.<br />You need to kill the execution to stop it.</div>",
|
||||
"restore": "Restore",
|
||||
"restore confirm": "Are you sure to restore the revision <code>{revision}</code>?",
|
||||
"bulk delete": "Are you sure you want to delete <code>{executionCount}</code> execution(s)?",
|
||||
"bulk replay": "Are you sure you want to replay <code>{executionCount}</code> execution(s)?",
|
||||
"bulk resume": "Are you sure you want to resume <code>{executionCount}</code> execution(s)?",
|
||||
"bulk restart": "Are you sure you want to restart <code>{executionCount}</code> execution(s)?",
|
||||
"bulk kill": "Are you sure you want to kill <code>{executionCount}</code> execution(s)?",
|
||||
"selection": {
|
||||
"selected": "<strong>{count}</strong> selected",
|
||||
"all": "Select all ({count})"
|
||||
},
|
||||
"cancel": "Cancel",
|
||||
"homeDashboard": {
|
||||
"title": "Dashboard",
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"last28Days": "Last 28 days",
|
||||
"lastXdays": "Last {days} days",
|
||||
"namespacesExecutions": "Executions per namespace",
|
||||
"namespacesErrorExecutions": "Executions errors per namespace",
|
||||
"failedExecutions": "Failed executions",
|
||||
"errorLogs": "Errors logs",
|
||||
"no executions": "Ready to see your flow in action?"
|
||||
},
|
||||
"executions replayed": "<code>{executionCount}</code> executions(s) replayed",
|
||||
"executions resumed": "<code>{executionCount}</code> executions(s) resumed",
|
||||
"executions restarted": "<code>{executionCount}</code> executions(s) restarted",
|
||||
"executions killed": "<code>{executionCount}</code> executions(s) killed",
|
||||
"executions deleted": "<code>{executionCount}</code> executions(s) deleted",
|
||||
"invalid bulk replay": "Could not replay executions",
|
||||
"invalid bulk resume": "Could not resume executions",
|
||||
"invalid bulk restart": "Could not restart executions",
|
||||
"invalid bulk kill": "Could not kill executions",
|
||||
"invalid bulk delete": "Could not delete executions",
|
||||
"execution not found": "Execution <code>{executionId}</code> not found",
|
||||
"execution not in state PAUSED": "Execution <code>{executionId}</code> not in state PAUSED",
|
||||
"execution not in state FAILED": "Execution <code>{executionId}</code> not in state FAILED",
|
||||
"execution already finished": "Execution <code>{executionId}</code> already finished",
|
||||
"seeing old revision": "You are seeing an old revision: {revision}",
|
||||
"export": "Export",
|
||||
"exports": "Exports",
|
||||
"template export": "Are you sure you want to export <code>{templateCount}</code> template(s)?",
|
||||
"templates exported": "Templates exported",
|
||||
"export all templates": "Export all templates",
|
||||
"flow export": "Are you sure you want to export <code>{flowCount}</code> flow(s)?",
|
||||
"flows exported": "Flows exported",
|
||||
"export all flows": "Export all flows",
|
||||
"import": "Import",
|
||||
"disable": "Disable",
|
||||
"enable": "Enable",
|
||||
"enabled": "Enabled",
|
||||
"template delete": "Are you sure you want to delete <code>{templateCount}</code> template(s)?",
|
||||
"flow delete": "Are you sure you want to delete <code>{flowCount}</code> flow(s)?",
|
||||
"templates deleted": "<code>{count}</code> Template(s) deleted",
|
||||
"flows deleted": "<code>{count}</code> Flow(s) deleted",
|
||||
"flow disable": "Are you sure you want to disable <code>{flowCount}</code> flow(s)?",
|
||||
"flow enable": "Are you sure you want to enable <code>{flowCount}</code> flow(s)?",
|
||||
"flows disabled": "<code>{count}</code> Flow(s) disabled",
|
||||
"flows enabled": "<code>{count}</code> Flow(s) enabled",
|
||||
"dependencies": "Dependencies",
|
||||
"see dependencies": "See dependencies",
|
||||
"dependencies missing acls": "No permissions on this flow",
|
||||
"dependencies delete flow": "This flow has dependencies. Deleting it will prevent their dependencies to be executed.<br /><br /> Here is the list of affected flows:",
|
||||
"expand dependencies": "Expand dependencies",
|
||||
"reset": "Restart",
|
||||
"Reset guided tour": "Restart Guided Tour",
|
||||
"onboarding-content": {
|
||||
"step1": {
|
||||
"title": "Welcome to Kestra!",
|
||||
"content": "Kestra uses a declarative interface to create a language-agnostic workflow definition in a YAML file format. Follow the next steps to create your first workflow."
|
||||
},
|
||||
"step2": {
|
||||
"title": "This is the built-in code editor",
|
||||
"content": "You can edit your flow directly here from the UI. To add comments to your code, use <code>#</code>. Kestra provides autocompletion for all workflow components, including tasks, triggers and inputs. Start typing and use the keyboard shortcut <kbd>CTRL or ⌘ + SPACE</kbd> to leverage the autocompletion feature."
|
||||
},
|
||||
"step3": {
|
||||
"title": "Let's write your first flow!",
|
||||
"content": "Set an ID, namespace, and (optionally) a description for your flow."
|
||||
},
|
||||
"step4": {
|
||||
"title": "Define inputs",
|
||||
"content": "You can define custom input parameters to execute your workflow dynamically using custom, runtime-specific values. Give each input a name, a type, and (optionally) a default value."
|
||||
},
|
||||
"step5": {
|
||||
"title": "Add tasks",
|
||||
"content": "Add a list of tasks to your flow. A task is an atomic unit of work that will perform a specific action. A task can execute a SQL query, run a Python script, trigger a data transformation job or send an email."
|
||||
},
|
||||
"step6": {
|
||||
"title": "Your first task",
|
||||
"content": "Let's add a task that will log a message \"Hey there, Kestra user!\" to the terminal. This message will be captured with a log level <code>INFO</code>."
|
||||
},
|
||||
"step7": {
|
||||
"title": "Fetch data from an API",
|
||||
"content": "Let's add a task that will extract data from an HTTP API. In the next steps, we will process that data in Python and SQL."
|
||||
},
|
||||
"step8": {
|
||||
"title": "Run a Python script",
|
||||
"content": "In this task, we read the output from the previous step and we extract only relevant data using <code>jq</code>. Then, we use Polars to transform that data and save the final dataframe as a CSV file. Note how the <code>beforeCommands</code> property ensures that required Python packages are installed before starting the task. You can also specify a custom Docker image."
|
||||
},
|
||||
"step9": {
|
||||
"title": "Execute a SQL query",
|
||||
"content": "In this task, we use DuckDB to execute a SQL query and output the final result as a downloadable artifact. You can preview that result artifact as a table in the Outputs tab in the UI."
|
||||
},
|
||||
"step10": {
|
||||
"title": "Schedule your flow",
|
||||
"content": "To schedule our flow, we can add one or more <code>triggers</code> — each of them can run the flow with different input parameter values. Here, we schedule the flow to run every minute."
|
||||
},
|
||||
"step11": {
|
||||
"title": "Save your flow 🚀",
|
||||
"content": "We're all set! You can save the flow by clicking on the <code>Save</code> button."
|
||||
},
|
||||
"step12": {
|
||||
"title": "Execute the flow!",
|
||||
"content": "Let's execute our flow by clicking on the <code>New Execution</code> button."
|
||||
},
|
||||
"step13": {
|
||||
"title": "Enter inputs",
|
||||
"content": "You can set custom parameter values before executing the flow."
|
||||
}
|
||||
},
|
||||
"onboarding-flow": {
|
||||
"onboardComment1": "Flow declaration with a mandatory unique ID, a namespace, and an optional description.",
|
||||
"onboardComment2": "Flow ID must be unique within a namespace.",
|
||||
"inputs": "Flow inputs: each input has an id, a type, and an optional default value.",
|
||||
"inputsDetails1": "We define one input of name 'user' with a default value 'Kestra user'",
|
||||
"tasks1": "List of tasks that will be executed one after the other.",
|
||||
"tasks2": "Each task must have an identifier unique for the flow and a type.",
|
||||
"tasks3": "Check the task documentation for a full list of attributes.",
|
||||
"taskLog1": "This task logs a message to the terminal.",
|
||||
"taskLog2": "The message is passed using the 'format' attribute.",
|
||||
"taskLog3": "We use Pebble expressions defined with curly brackets to access the input variables.",
|
||||
"taskAPI": "This task extracts data from an API.",
|
||||
"taskPython": "This task runs a Python script.",
|
||||
"taskQuery": "Run a DuckDB query.",
|
||||
"triggers": "To run the flow in an automated fashion, add one or more 'triggers'.",
|
||||
"triggerSchedule1": "Here we use the 'Schedule' trigger to run the flow every minute."
|
||||
},
|
||||
"Skip tour": "Skip tour",
|
||||
"Next step": "Next step",
|
||||
"Previous step": "Previous step",
|
||||
"Finish": "Finish",
|
||||
"Step": "Step",
|
||||
"welcome aboard": "\uD83D\uDE80 Welcome to Kestra!",
|
||||
"welcome aboard content": "Use our Guided Tour to create your first flow and check Blueprints to find more examples.",
|
||||
"welcome display require": "Run your <strong>first flow</strong> to get started",
|
||||
"welcome button create": "Create my first flow",
|
||||
"get started": "Get started",
|
||||
"get started content": "Check our documentation for a step-by-step walkthrough.",
|
||||
"watch demo": "Take a product tour",
|
||||
"watch demo content": "See the product tour video explaining key concepts.",
|
||||
"live help": "Live help",
|
||||
"need help?": "Need help?",
|
||||
"need help? content": "Need assistance with a specific feature or flow? Our developer community is here to help.",
|
||||
"show task documentation": "Show task documentation",
|
||||
"hide task documentation": "Hide task documentation",
|
||||
"show task documentation in editor": "Show task documentation in the editor",
|
||||
"show documentation": "Show documentation",
|
||||
"focus task": "Click on any element to see its documentation.",
|
||||
"validate": "Validate",
|
||||
"add global error handler": "Add global error handler",
|
||||
"add error handler": "Add an error handler",
|
||||
"add trigger": "Add trigger",
|
||||
"edit metadata": "Edit Metadata",
|
||||
"taskDefaults": "Task Defaults",
|
||||
"disabled": "Disabled",
|
||||
"before": "before",
|
||||
"after": "after",
|
||||
"add at position": "Add {position} <code>{task}</code>",
|
||||
"create first task": "Create your first task",
|
||||
"dynamic": "Dynamic",
|
||||
"choice": "Choice",
|
||||
"sequential": "Sequential",
|
||||
"can not delete": "Can not delete",
|
||||
"can not have less than 1 task": "Each flow must have at least one task.",
|
||||
"task id already exists": "Task Id already exists",
|
||||
"Task Id already exist in the flow": "Task Id {taskId} already exists in the flow.",
|
||||
"task id": "Task ID",
|
||||
"taskid column details": "Last task being executed and its number of attempts.",
|
||||
"flow already exists": "Flow already exists",
|
||||
"namespace not allowed": "Namespace not allowed",
|
||||
"switch-view": "Switch view",
|
||||
"source and topology": "Source and topology",
|
||||
"source and doc": "Source and documentation",
|
||||
"source and blueprints": "Source and blueprints",
|
||||
"editor": "Editor",
|
||||
"error in editor": "An error have been found in the editor",
|
||||
"delete task confirm": "Do you want to delete the task <code>{taskId}</code>?",
|
||||
"can not save": "Can not save",
|
||||
"flow must have id and namespace": "Flow must have an id and a namespace.",
|
||||
"readonly property": "Read-only property",
|
||||
"namespace and id readonly": "Namespace and id are read-only. They were reinitialized to their initial value.",
|
||||
"avg": "Average",
|
||||
"sum": "Sum",
|
||||
"min": "Min",
|
||||
"max": "Max",
|
||||
"metric": "Metric",
|
||||
"aggregation": "Aggregation",
|
||||
"metric choice": "Please choose a metric and an aggregation",
|
||||
"namespace choice": "Please choose a namespace to start using the editor",
|
||||
"of": "of",
|
||||
"save draft": {
|
||||
"message": "Draft saved",
|
||||
"retrieval": {
|
||||
"creation": "Flow draft was saved, do you want to continue editing the flow?",
|
||||
"existing": "A <code>{flowFullName}</code> Flow draft was saved, do you want to continue editing the flow?"
|
||||
}
|
||||
},
|
||||
"title": "Title",
|
||||
"api": "API",
|
||||
"expand error": "Expand only failed tasks",
|
||||
"expand all": "Expand all",
|
||||
"collapse all": "Collapse all",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"log expand setting": "Log default display",
|
||||
"environment name setting": "Environment name",
|
||||
"environment color setting": "Environment color",
|
||||
"slack support": "Ask any question via Slack",
|
||||
"join community": "Join the Community",
|
||||
"reach us": "Reach out to us",
|
||||
"new version": "New version {version} available!",
|
||||
"error detected": "Error(s) detected",
|
||||
"warning detected": "Warning(s) detected",
|
||||
"cannot swap tasks": "Can't swap the tasks",
|
||||
"preview": "Preview",
|
||||
"open": "Open",
|
||||
"dependency task": "Task {taskId} is a dependency of another task",
|
||||
"administration": "Administration",
|
||||
"evaluation lock date": "Evaluation lock date",
|
||||
"unlock trigger": {
|
||||
"tooltip": {
|
||||
"execution": "There is an execution running for this trigger",
|
||||
"evaluation": "The trigger is currently in evaluation"
|
||||
},
|
||||
"confirmation": "Are you sure you want to unlock the trigger?",
|
||||
"warning": "It could lead to concurrent executions for the same trigger and should be considered as a last resort option.",
|
||||
"button": "Unlock trigger",
|
||||
"success": "Trigger is unlocked"
|
||||
},
|
||||
"date format": "Date format",
|
||||
"timezone": "Timezone",
|
||||
"add task": "Add a task",
|
||||
"unable to generate graph": "An issue occurred while generating the graph which prevents the topology to be displayed.",
|
||||
"attempts": "Attempt(s)",
|
||||
"flow deleted, you can restore it": "The flow has been deleted and this is a read-only view. You can still restore it.",
|
||||
"workers": "Workers",
|
||||
"worker group": "Worker Group",
|
||||
"hostname": "Hostname",
|
||||
"port": "Port",
|
||||
"management port": "Management port",
|
||||
"file preview truncated": "Displayed content has been truncated",
|
||||
"row count": "Row count",
|
||||
"encoding": "Encoding",
|
||||
"show": "Show",
|
||||
"advanced configuration": "Advanced configuration",
|
||||
"all executions": "All executions",
|
||||
"trigger execution id": "Trigger Execution Id",
|
||||
"trigger filter": {
|
||||
"title": "Filter children executions",
|
||||
"options": {
|
||||
"ALL": "All Executions",
|
||||
"CHILD": "Child Executions",
|
||||
"MAIN": "Parent Executions"
|
||||
}
|
||||
},
|
||||
"namespace files": {
|
||||
"import": {
|
||||
"file": "Import from ZIP or add file(s)",
|
||||
"folder": "Import from folder",
|
||||
"success": "File(s) successfully imported",
|
||||
"error": "Error(s) occurred while importing the file(s)"
|
||||
},
|
||||
"export": "Export as a ZIP file"
|
||||
},
|
||||
"continue backfill":"Continue the backfill",
|
||||
"delete backfill": "Delete the backfill",
|
||||
"pause backfill": "Pause the backfill",
|
||||
"backfill executions": "Backfill executions",
|
||||
"execute backfill": "Execute backfill",
|
||||
"relative": "Relative",
|
||||
"relative start date": "Relative start date",
|
||||
"relative end date": "Relative end date",
|
||||
"now": "Now",
|
||||
"absolute": "Absolute",
|
||||
"backfill": "Backfill",
|
||||
"Set labels tooltip": "Set labels to the execution",
|
||||
"Set labels": "Set labels",
|
||||
"Set labels to execution": "Add or update the labels of the execution <code>{id}</code>",
|
||||
"Set labels done": "Successfully set the labels of the execution",
|
||||
"bulk set labels": "Are you sure you want to set labels to <code>{executionCount}</code> executions(s)?",
|
||||
"dependencies loaded": "Dependencies loaded",
|
||||
"loaded x dependencies": "{count} dependencies loaded",
|
||||
"security_advice": {
|
||||
"title": "Your data is not protected!",
|
||||
"content": "Enable basic authentication to protect your instance.",
|
||||
"switch_text": "Don't show again",
|
||||
"enable": "Enable authentication"
|
||||
},
|
||||
"true": "True",
|
||||
"false": "False",
|
||||
"undefined": "Undefined"
|
||||
}
|
||||
}
|
||||
@@ -1,604 +1,4 @@
|
||||
{
|
||||
"en": {
|
||||
"id": "Id",
|
||||
"type": "Type",
|
||||
"ok": "OK",
|
||||
"close": "Close",
|
||||
"namespace": "Namespace",
|
||||
"description": "Description",
|
||||
"show description": "Show description",
|
||||
"revision": "Revision",
|
||||
"Language": "Language",
|
||||
"Set default page": "Set default page",
|
||||
"settings": "Settings",
|
||||
"theme": "Theme",
|
||||
"flows": "Flows",
|
||||
"flow": "Flow",
|
||||
"invalid source": "Invalid source code",
|
||||
"update": "Update",
|
||||
"update ok": "is updated",
|
||||
"delete ok": "is deleted",
|
||||
"Default page": "Default page",
|
||||
"confirmation": "Confirmation",
|
||||
"delete confirm": "Are you sure to delete <code>{name}</code>?",
|
||||
"outdated revision save confirmation": {
|
||||
"confirm": "Do you want to overwrite it?",
|
||||
"update": {
|
||||
"title": "Outdated revision",
|
||||
"description": "The revision you are editing is outdated.",
|
||||
"details": "Check the Revisions tab for more details about the latest version."
|
||||
},
|
||||
"create": {
|
||||
"title": "Flow already exists",
|
||||
"description": "A Flow with the same id / namespace already exists.",
|
||||
"details": "Check the Flows menu for more details about the existing flow."
|
||||
}
|
||||
},
|
||||
"is deprecated": "is deprecated",
|
||||
"success": "Success",
|
||||
"save": "Save",
|
||||
"saved": "Successfully saved",
|
||||
"saved done": "<em>{name}</em> is successfully saved",
|
||||
"multiple saved done": "{name} have been saved",
|
||||
"delete": "Delete",
|
||||
"deleted": "Successfully deleted",
|
||||
"deleted confirm": "<em>{name}</em> is successfully deleted!",
|
||||
"Select namespace": "Namespace",
|
||||
"Add flow": "Add flow",
|
||||
"add": "Add",
|
||||
"copy": "Copy",
|
||||
"Per page": "per page",
|
||||
"new": "New",
|
||||
"edit": "Edit",
|
||||
"edit flow": "Edit flow",
|
||||
"executions": "Executions",
|
||||
"execution": "Execution",
|
||||
"details": "Details",
|
||||
"overview": "Overview",
|
||||
"gantt": "Gantt",
|
||||
"logs": "Logs",
|
||||
"duration": "Duration",
|
||||
"running duration": "Running duration",
|
||||
"queued duration": "Queued duration",
|
||||
"choose file": "Choose a file or drop it here...",
|
||||
"launch execution": "Execute",
|
||||
"warning flow with triggers": "This flow contains triggers and a manual execution will fail if this flow depends on trigger expressions.",
|
||||
"triggered": "Execution trigger",
|
||||
"triggered done": "Execution <em>{name}</em> is successfully triggered",
|
||||
"topology": "Topology",
|
||||
"date": "Date",
|
||||
"datepicker": {
|
||||
"last5minutes": "Last 5 minutes",
|
||||
"last15minutes": "Last 15 minutes",
|
||||
"last1hour": "Last 1 hour",
|
||||
"last12hours": "Last 12 hours",
|
||||
"last24hours": "Last 24 hours",
|
||||
"last7days": "Last 7 days",
|
||||
"last30days": "Last 30 days",
|
||||
"last365days": "Last 365 days",
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"dayBeforeYesterday": "Day before yesterday",
|
||||
"thisWeek": "This week",
|
||||
"thisWeekSoFar": "This week so far",
|
||||
"previousWeek": "Previous week",
|
||||
"thisMonth": "This month",
|
||||
"thisMonthSoFar": "This month so far",
|
||||
"previousMonth": "Previous month",
|
||||
"thisYear": "This year",
|
||||
"thisYearSoFar": "This year so far",
|
||||
"previousYear": "Previous year"
|
||||
},
|
||||
"created date": "Created date",
|
||||
"updated date": "Updated date",
|
||||
"next execution date": "Next execution date",
|
||||
"jump to...": "Jump to...",
|
||||
"source": "Source",
|
||||
"home": "Home",
|
||||
"create": "Create",
|
||||
"add flow": "Add flow",
|
||||
"from": "From",
|
||||
"to": "To",
|
||||
"steps": "Steps",
|
||||
"state": "State",
|
||||
"search term in message": "Search term in message",
|
||||
"search": "Search",
|
||||
"search blueprint": "Search blueprints",
|
||||
"all tags": "All tags",
|
||||
"source search": "Source search",
|
||||
"filter by log level": "Filter by log level",
|
||||
"selected": "Selected",
|
||||
"task": "Task",
|
||||
"task logs": "Task logs",
|
||||
"display flow {id} executions": "Display flow {id} executions",
|
||||
"actions": "Actions",
|
||||
"select datetime": "Select a date",
|
||||
"invalid field": "Invalid field: {name}",
|
||||
"required field": "Required field",
|
||||
"error": "Error",
|
||||
"form": "Form",
|
||||
"form error": "Form error",
|
||||
"display topology for flow": "Display topology for flow",
|
||||
"cannot create topology": "Unable to create topology for flow",
|
||||
"start date": "Start date",
|
||||
"end date": "End date",
|
||||
"creation": "Creation",
|
||||
"flow creation": "Flow creation",
|
||||
"start datetime": "Start datetime",
|
||||
"end datetime": "End datetime",
|
||||
"restart": "Restart",
|
||||
"restart latest revision": "Restart latest revision",
|
||||
"restart tooltip": "Restart the execution from the <code>{state}</code> task",
|
||||
"restarted": "Execution is restarted",
|
||||
"restart confirm": "Are you sure to restart execution <code>{id}</code>?",
|
||||
"restart change revision": "You can change the revision that will be used for the new execution.",
|
||||
"replay": "Replay",
|
||||
"replay from task tooltip": "Create a similar execution starting from task <code>{taskId}</code>",
|
||||
"replay from beginning tooltip": "Create a similar execution starting from the beginning",
|
||||
"replay latest revision": "Replay using latest revision",
|
||||
"replayed": "Execution is replayed",
|
||||
"replay confirm": "Are you sure to replay this execution <code>{id}</code> and create a new one?",
|
||||
"prefill inputs": "Prefill",
|
||||
"current": "current",
|
||||
"change status": "Change status",
|
||||
"change status done": "Task status has been updated",
|
||||
"change status confirm": "Are you sure to change the status on task <code>{task}</code> for execution <code>{id}</code>?",
|
||||
"change status hint": {
|
||||
"WARNING": [
|
||||
"The flow will be marked as WARNING.",
|
||||
"The next tasks will be executed.",
|
||||
"The error tasks will be executed."
|
||||
],
|
||||
"FAILED": [
|
||||
"The flow will be marked as FAILED.",
|
||||
"No other task will be executed.",
|
||||
"The error tasks will be executed."
|
||||
],
|
||||
"SUCCESS": [
|
||||
"The flow will restart as this task was successful.",
|
||||
"All the blocked tasks will be executed.",
|
||||
"The flow will be in a SUCCESS state if all task runs are successful."
|
||||
],
|
||||
"RUNNING": [
|
||||
"The flow will restart and will execute all next tasks.",
|
||||
"All blocked tasks will be executed."
|
||||
]
|
||||
},
|
||||
"mark as": "Mark as <code>{status}</code>",
|
||||
"kill": "Kill",
|
||||
"kill parents and subflow": "Kill parents and subflows",
|
||||
"kill only parents": "Kill parents only",
|
||||
"killed confirm": "Are you sure to kill execution <code>{id}</code>?",
|
||||
"killed done": "Execution is queued for killing",
|
||||
"resume": "Resume",
|
||||
"resumed confirm": "Are you sure to resume execution <code>{id}</code>?",
|
||||
"resumed done": "Execution is resumed",
|
||||
"toggle output": "Toggle outputs",
|
||||
"metrics": "Metrics",
|
||||
"no data current task": "No data available for current task",
|
||||
"outputs": "Outputs",
|
||||
"output": "Output",
|
||||
"eval": {
|
||||
"title": "Render expression",
|
||||
"tooltip": "You need to choose a task to render an expression from."
|
||||
},
|
||||
"attempt": "Attempt",
|
||||
"toggle output display": "Toggle output display",
|
||||
"name": "Name",
|
||||
"key": "Key",
|
||||
"value": "Value",
|
||||
"each value": "Iteration value",
|
||||
"current execution": "Current execution",
|
||||
"parent execution": "Parent execution",
|
||||
"original execution": "Original execution",
|
||||
"automatic refresh": "Automatic refresh",
|
||||
"toggle periodic refresh each 10 seconds": "Toggle periodic refresh every 10 seconds",
|
||||
"trigger refresh": "Trigger refresh",
|
||||
"refresh": "Refresh",
|
||||
"topology-graph": {
|
||||
"graph-orientation": "Graph orientation",
|
||||
"zoom-in": "Zoom in",
|
||||
"zoom-out": "Zoom out",
|
||||
"zoom-reset": "Reset zoom",
|
||||
"zoom-fit": "Fit"
|
||||
},
|
||||
"show task logs": "Show task logs",
|
||||
"show task outputs": "Show task outputs",
|
||||
"show task source": "Show task source",
|
||||
"display output for specific task": "Display output for a specific task",
|
||||
"display metric for specific task": "Display metric for a specific task",
|
||||
"display direct sub tasks count": "Display direct subtask count",
|
||||
"stream": "Stream",
|
||||
"execution statistics": "Execution statistics",
|
||||
"stats": "Stats",
|
||||
"your usage": "Your usage",
|
||||
"namespaces": "Namespaces",
|
||||
"tasks": "Tasks",
|
||||
"executions duration (in minutes)": "Total executions duration (in minutes)",
|
||||
"last 48 hours": "last 48 hours",
|
||||
"configure basic auth": "Configure Basic Authentication",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"confirm password": "Confirm password",
|
||||
"passwords do not match": "Passwords do not match",
|
||||
"avg duration": "Avg. execution duration",
|
||||
"neutral trend": "Stable",
|
||||
"up trend": "Increasing",
|
||||
"down trend": "Decreasing",
|
||||
"update aborted": "update aborted",
|
||||
"invalid flow": "Invalid flow",
|
||||
"invalid yaml": "Invalid YAML",
|
||||
"inputs": "Inputs",
|
||||
"input": "Input",
|
||||
"variables": "Variables",
|
||||
"download": "Download",
|
||||
"documentation": {
|
||||
"documentation": "Documentation",
|
||||
"github": "Open a GitHub issue"
|
||||
},
|
||||
"blueprints": {
|
||||
"title": "Blueprints",
|
||||
"header": {
|
||||
"catch phrase": {
|
||||
"1": "The first step is always the hardest.",
|
||||
"2": "Explore blueprints to kick-start your next flow."
|
||||
}
|
||||
}
|
||||
},
|
||||
"use": "Use",
|
||||
"plugins": {
|
||||
"name": "Plugin",
|
||||
"names": "Plugins",
|
||||
"please": "Please choose a task on the right to see its documentation"
|
||||
},
|
||||
"last execution date": "Last execution date",
|
||||
"last execution status": "Last status",
|
||||
"last X days count": "{count} in last {days} days",
|
||||
"date range count": "{count} between the {startDate} and the {endDate}",
|
||||
"date count": "{count} on the {date}",
|
||||
"revisions": "Revisions",
|
||||
"no revisions found": "Only one revision exists for this flow",
|
||||
"see full revision": "See full revision",
|
||||
"side-by-side": "side-by-side",
|
||||
"line-by-line": "line-by-line",
|
||||
"template": "Template",
|
||||
"template creation": "Template creation",
|
||||
"templates": "Templates",
|
||||
"templates deprecated": "Templates are deprecated. Please use subflows instead. See the <a href=\"https://kestra.io/docs/migrations/templates\" target=\"_blank\">Migrations section</a> explaining how you can migrate from templates to subflows.",
|
||||
"no result": "No results for the current selection",
|
||||
"trigger": "Trigger",
|
||||
"triggers": "Triggers",
|
||||
"trigger details": "Trigger details",
|
||||
"triggerflow disabled": "Trigger is disabled from flow definition",
|
||||
"conditions": "conditions",
|
||||
"taskruns": "Task Runs",
|
||||
"Fold auto": "Editor: automatic fold of multilines",
|
||||
"Fold content lines": "Fold multiline strings",
|
||||
"Unfold content lines": "Unfold multiline strings",
|
||||
"Max displayable": "Max displayable",
|
||||
"Total": "Total",
|
||||
"sub flow": "Subflow",
|
||||
"execute": "Execute",
|
||||
"execute flow behaviour": "Execute the flow",
|
||||
"open in same tab": "In the same tab",
|
||||
"open in new tab": "In a new tab",
|
||||
"execute the flow": "Execute the flow <code>{id}</code>",
|
||||
"execute flow now ?": "Do you want to execute this flow?",
|
||||
"Default namespace": "Default namespace",
|
||||
"Default log level": "Default log level",
|
||||
"unsaved changed ?": "You have unsaved changes, do you want to leave this page?",
|
||||
"Editor theme": "Editor theme",
|
||||
"Editor fontsize": "Editor font size",
|
||||
"Editor fontfamily": "Editor font family",
|
||||
"errors": {
|
||||
"404": {
|
||||
"title": "Page not found",
|
||||
"content": "The requested URL was not found on this server. <span class=\"text-muted\">That’s all we know.</span>"
|
||||
}
|
||||
},
|
||||
"copy logs": "Copy logs",
|
||||
"download logs": "Download logs",
|
||||
"delete logs": "Delete logs",
|
||||
"toggle fullscreen": "Toggle fullscreen",
|
||||
"copied": "Copied",
|
||||
"tags": "Tags",
|
||||
"disabled flow title": "This flow is disabled",
|
||||
"disabled flow desc": "This flow is disabled, please enable it in order to execute it.",
|
||||
"label": "Label",
|
||||
"labels": "Labels",
|
||||
"label filter placeholder": "Label as 'key:value'",
|
||||
"execution labels": "Execution labels",
|
||||
"wrong labels": "Empty key or value is not allowed in labels",
|
||||
"feeds": {
|
||||
"title": "What's new at Kestra"
|
||||
},
|
||||
"delete execution running": "<div class=\"alert alert-warning mt-2 mb-0\">This execution is still running, deleting it will not stop it.<br />You need to kill the execution to stop it.</div>",
|
||||
"restore": "Restore",
|
||||
"restore confirm": "Are you sure to restore the revision <code>{revision}</code>?",
|
||||
"bulk delete": "Are you sure you want to delete <code>{executionCount}</code> execution(s)?",
|
||||
"bulk resume": "Are you sure you want to resume <code>{executionCount}</code> execution(s)?",
|
||||
"bulk restart": "Are you sure you want to restart <code>{executionCount}</code> execution(s)?",
|
||||
"bulk kill": "Are you sure you want to kill <code>{executionCount}</code> execution(s)?",
|
||||
"selection": {
|
||||
"selected": "<strong>{count}</strong> selected",
|
||||
"all": "Select all ({count})"
|
||||
},
|
||||
"cancel": "Cancel",
|
||||
"homeDashboard": {
|
||||
"title": "Dashboard",
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"last28Days": "Last 28 days",
|
||||
"lastXdays": "Last {days} days",
|
||||
"namespacesExecutions": "Executions per namespace",
|
||||
"namespacesErrorExecutions": "Executions errors per namespace",
|
||||
"failedExecutions": "Failed executions",
|
||||
"errorLogs": "Errors logs",
|
||||
"no executions": "Ready to see your flow in action?"
|
||||
},
|
||||
"executions resumed": "<code>{executionCount}</code> executions(s) resumed",
|
||||
"executions restarted": "<code>{executionCount}</code> executions(s) restarted",
|
||||
"executions killed": "<code>{executionCount}</code> executions(s) killed",
|
||||
"executions deleted": "<code>{executionCount}</code> executions(s) deleted",
|
||||
"invalid bulk resume": "Could not resume executions",
|
||||
"invalid bulk restart": "Could not restart executions",
|
||||
"invalid bulk kill": "Could not kill executions",
|
||||
"invalid bulk delete": "Could not delete executions",
|
||||
"execution not found": "Execution <code>{executionId}</code> not found",
|
||||
"execution not in state PAUSED": "Execution <code>{executionId}</code> not in state PAUSED",
|
||||
"execution not in state FAILED": "Execution <code>{executionId}</code> not in state FAILED",
|
||||
"execution already finished": "Execution <code>{executionId}</code> already finished",
|
||||
"seeing old revision": "You are seeing an old revision: {revision}",
|
||||
"export": "Export",
|
||||
"exports": "Exports",
|
||||
"template export": "Are you sure you want to export <code>{templateCount}</code> template(s)?",
|
||||
"templates exported": "Templates exported",
|
||||
"export all templates": "Export all templates",
|
||||
"flow export": "Are you sure you want to export <code>{flowCount}</code> flow(s)?",
|
||||
"flows exported": "Flows exported",
|
||||
"export all flows": "Export all flows",
|
||||
"import": "Import",
|
||||
"disable": "Disable",
|
||||
"enable": "Enable",
|
||||
"enabled": "Enabled",
|
||||
"template delete": "Are you sure you want to delete <code>{templateCount}</code> template(s)?",
|
||||
"flow delete": "Are you sure you want to delete <code>{flowCount}</code> flow(s)?",
|
||||
"templates deleted": "<code>{count}</code> Template(s) deleted",
|
||||
"flows deleted": "<code>{count}</code> Flow(s) deleted",
|
||||
"flow disable": "Are you sure you want to disable <code>{flowCount}</code> flow(s)?",
|
||||
"flow enable": "Are you sure you want to enable <code>{flowCount}</code> flow(s)?",
|
||||
"flows disabled": "<code>{count}</code> Flow(s) disabled",
|
||||
"flows enabled": "<code>{count}</code> Flow(s) enabled",
|
||||
"dependencies": "Dependencies",
|
||||
"see dependencies": "See dependencies",
|
||||
"dependencies missing acls": "No permissions on this flow",
|
||||
"dependencies delete flow": "This flow has dependencies. Deleting it will prevent their dependencies to be executed.<br /><br /> Here is the list of affected flows:",
|
||||
"expand dependencies": "Expand dependencies",
|
||||
"reset": "Restart",
|
||||
"Reset guided tour": "Restart Guided Tour",
|
||||
"onboarding-content": {
|
||||
"step1": {
|
||||
"title": "Welcome to Kestra!",
|
||||
"content": "Kestra uses a declarative interface to create a language-agnostic workflow definition in a YAML file format. Follow the next steps to create your first workflow."
|
||||
},
|
||||
"step2": {
|
||||
"title": "This is the built-in code editor",
|
||||
"content": "You can edit your flow directly here from the UI. To add comments to your code, use <code>#</code>. Kestra provides autocompletion for all workflow components, including tasks, triggers and inputs. Start typing and use the keyboard shortcut <kbd>CTRL or ⌘ + SPACE</kbd> to leverage the autocompletion feature."
|
||||
},
|
||||
"step3": {
|
||||
"title": "Let's write your first flow!",
|
||||
"content": "Set an ID, namespace, and (optionally) a description for your flow."
|
||||
},
|
||||
"step4": {
|
||||
"title": "Define inputs",
|
||||
"content": "You can define custom input parameters to execute your workflow dynamically using custom, runtime-specific values. Give each input a name, a type, and (optionally) a default value."
|
||||
},
|
||||
"step5": {
|
||||
"title": "Add tasks",
|
||||
"content": "Add a list of tasks to your flow. A task is an atomic unit of work that will perform a specific action. A task can execute a SQL query, run a Python script, trigger a data transformation job or send an email."
|
||||
},
|
||||
"step6": {
|
||||
"title": "Your first task",
|
||||
"content": "Let's add a task that will log a message \"Hey there, Kestra user!\" to the terminal. This message will be captured with a log level <code>INFO</code>."
|
||||
},
|
||||
"step7": {
|
||||
"title": "Fetch data from an API",
|
||||
"content": "Let's add a task that will extract data from an HTTP API. In the next steps, we will process that data in Python and SQL."
|
||||
},
|
||||
"step8": {
|
||||
"title": "Run a Python script",
|
||||
"content": "In this task, we read the output from the previous step and we extract only relevant data using <code>jq</code>. Then, we use Polars to transform that data and save the final dataframe as a CSV file. Note how the <code>beforeCommands</code> property ensures that required Python packages are installed before starting the task. You can also specify a custom Docker image."
|
||||
},
|
||||
"step9": {
|
||||
"title": "Execute a SQL query",
|
||||
"content": "In this task, we use DuckDB to execute a SQL query and output the final result as a downloadable artifact. You can preview that result artifact as a table in the Outputs tab in the UI."
|
||||
},
|
||||
"step10": {
|
||||
"title": "Schedule your flow",
|
||||
"content": "To schedule our flow, we can add one or more <code>triggers</code> — each of them can run the flow with different input parameter values. Here, we schedule the flow to run every minute."
|
||||
},
|
||||
"step11": {
|
||||
"title": "Save your flow 🚀",
|
||||
"content": "We're all set! You can save the flow by clicking on the <code>Save</code> button."
|
||||
},
|
||||
"step12": {
|
||||
"title": "Execute the flow!",
|
||||
"content": "Let's execute our flow by clicking on the <code>New Execution</code> button."
|
||||
},
|
||||
"step13": {
|
||||
"title": "Enter inputs",
|
||||
"content": "You can set custom parameter values before executing the flow."
|
||||
}
|
||||
},
|
||||
"onboarding-flow": {
|
||||
"onboardComment1": "Flow declaration with a mandatory unique ID, a namespace, and an optional description.",
|
||||
"onboardComment2": "Flow ID must be unique within a namespace.",
|
||||
"inputs": "Flow inputs: each input has an id, a type, and an optional default value.",
|
||||
"inputsDetails1": "We define one input of name 'user' with a default value 'Kestra user'",
|
||||
"tasks1": "List of tasks that will be executed one after the other.",
|
||||
"tasks2": "Each task must have an identifier unique for the flow and a type.",
|
||||
"tasks3": "Check the task documentation for a full list of attributes.",
|
||||
"taskLog1": "This task logs a message to the terminal.",
|
||||
"taskLog2": "The message is passed using the 'format' attribute.",
|
||||
"taskLog3": "We use Pebble expressions defined with curly brackets to access the input variables.",
|
||||
"taskAPI": "This task extracts data from an API.",
|
||||
"taskPython": "This task runs a Python script.",
|
||||
"taskQuery": "Run a DuckDB query.",
|
||||
"triggers": "To run the flow in an automated fashion, add one or more 'triggers'.",
|
||||
"triggerSchedule1": "Here we use the 'Schedule' trigger to run the flow every minute."
|
||||
},
|
||||
"Skip tour": "Skip tour",
|
||||
"Next step": "Next step",
|
||||
"Previous step": "Previous step",
|
||||
"Finish": "Finish",
|
||||
"Step": "Step",
|
||||
"welcome aboard": "\uD83D\uDE80 Welcome to Kestra!",
|
||||
"welcome aboard content": "Use our Guided Tour to create your first flow and check Blueprints to find more examples.",
|
||||
"welcome display require": "Run your <strong>first flow</strong> to get started",
|
||||
"welcome button create": "Create my first flow",
|
||||
"get started": "Get started",
|
||||
"get started content": "Check our documentation for a step-by-step walkthrough.",
|
||||
"watch demo": "Take a product tour",
|
||||
"watch demo content": "See the product tour video explaining key concepts.",
|
||||
"live help": "Live help",
|
||||
"need help?": "Need help?",
|
||||
"need help? content": "Need assistance with a specific feature or flow? Our developer community is here to help.",
|
||||
"show task documentation": "Show task documentation",
|
||||
"hide task documentation": "Hide task documentation",
|
||||
"show task documentation in editor": "Show task documentation in the editor",
|
||||
"show documentation": "Show documentation",
|
||||
"focus task": "Click on any element to see its documentation.",
|
||||
"validate": "Validate",
|
||||
"add global error handler": "Add global error handler",
|
||||
"add error handler": "Add an error handler",
|
||||
"add trigger": "Add trigger",
|
||||
"edit metadata": "Edit Metadata",
|
||||
"taskDefaults": "Task Defaults",
|
||||
"disabled": "Disabled",
|
||||
"before": "before",
|
||||
"after": "after",
|
||||
"add at position": "Add {position} <code>{task}</code>",
|
||||
"create first task": "Create your first task",
|
||||
"dynamic": "Dynamic",
|
||||
"choice": "Choice",
|
||||
"sequential": "Sequential",
|
||||
"can not delete": "Can not delete",
|
||||
"can not have less than 1 task": "Each flow must have at least one task.",
|
||||
"task id already exists": "Task Id already exists",
|
||||
"Task Id already exist in the flow": "Task Id {taskId} already exists in the flow.",
|
||||
"task id": "Task ID",
|
||||
"taskid column details": "Last task being executed and its number of attempts.",
|
||||
"flow already exists": "Flow already exists",
|
||||
"namespace not allowed": "Namespace not allowed",
|
||||
"switch-view": "Switch view",
|
||||
"source and topology": "Source and topology",
|
||||
"source and doc": "Source and documentation",
|
||||
"source and blueprints": "Source and blueprints",
|
||||
"editor": "Editor",
|
||||
"error in editor": "An error have been found in the editor",
|
||||
"delete task confirm": "Do you want to delete the task <code>{taskId}</code>?",
|
||||
"can not save": "Can not save",
|
||||
"flow must have id and namespace": "Flow must have an id and a namespace.",
|
||||
"readonly property": "Read-only property",
|
||||
"namespace and id readonly": "Namespace and id are read-only. They were reinitialized to their initial value.",
|
||||
"avg": "Average",
|
||||
"sum": "Sum",
|
||||
"min": "Min",
|
||||
"max": "Max",
|
||||
"metric": "Metric",
|
||||
"aggregation": "Aggregation",
|
||||
"metric choice": "Please choose a metric and an aggregation",
|
||||
"namespace choice": "Please choose a namespace to start using the editor",
|
||||
"of": "of",
|
||||
"save draft": {
|
||||
"message": "Draft saved",
|
||||
"retrieval": {
|
||||
"creation": "Flow draft was saved, do you want to continue editing the flow?",
|
||||
"existing": "A <code>{flowFullName}</code> Flow draft was saved, do you want to continue editing the flow?"
|
||||
}
|
||||
},
|
||||
"title": "Title",
|
||||
"api": "API",
|
||||
"expand error": "Expand only failed tasks",
|
||||
"expand all": "Expand all",
|
||||
"collapse all": "Collapse all",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"log expand setting": "Log default display",
|
||||
"environment name setting": "Environment name",
|
||||
"environment color setting": "Environment color",
|
||||
"slack support": "Ask any question via Slack",
|
||||
"join community": "Join the Community",
|
||||
"error detected": "Error(s) detected",
|
||||
"warning detected": "Warning(s) detected",
|
||||
"cannot swap tasks": "Can't swap the tasks",
|
||||
"preview": "Preview",
|
||||
"open": "Open",
|
||||
"dependency task": "Task {taskId} is a dependency of another task",
|
||||
"administration": "Administration",
|
||||
"evaluation lock date": "Evaluation lock date",
|
||||
"unlock trigger": {
|
||||
"tooltip": {
|
||||
"execution": "There is an execution running for this trigger",
|
||||
"evaluation": "The trigger is currently in evaluation"
|
||||
},
|
||||
"confirmation": "Are you sure you want to unlock the trigger?",
|
||||
"warning": "It could lead to concurrent executions for the same trigger and should be considered as a last resort option.",
|
||||
"button": "Unlock trigger",
|
||||
"success": "Trigger is unlocked"
|
||||
},
|
||||
"date format": "Date format",
|
||||
"timezone": "Timezone",
|
||||
"add task": "Add a task",
|
||||
"unable to generate graph": "An issue occurred while generating the graph which prevents the topology to be displayed.",
|
||||
"attempts": "Attempt(s)",
|
||||
"flow deleted, you can restore it": "The flow has been deleted and this is a read-only view. You can still restore it.",
|
||||
"workers": "Workers",
|
||||
"worker group": "Worker Group",
|
||||
"hostname": "Hostname",
|
||||
"port": "Port",
|
||||
"management port": "Management port",
|
||||
"file preview truncated": "Displayed content has been truncated",
|
||||
"row count": "Row count",
|
||||
"encoding": "Encoding",
|
||||
"show": "Show",
|
||||
"advanced configuration": "Advanced configuration",
|
||||
"all executions": "All executions",
|
||||
"trigger execution id": "Trigger Execution Id",
|
||||
"trigger filter": {
|
||||
"title": "Filter children executions",
|
||||
"options": {
|
||||
"ALL": "All Executions",
|
||||
"CHILD": "Child Executions",
|
||||
"MAIN": "Parent Executions"
|
||||
}
|
||||
},
|
||||
"namespace files": {
|
||||
"import": {
|
||||
"file": "Import from ZIP or add file(s)",
|
||||
"folder": "Import from folder",
|
||||
"success": "File(s) successfully imported",
|
||||
"error": "Error(s) occurred while importing the file(s)"
|
||||
},
|
||||
"export": "Export as a ZIP file"
|
||||
},
|
||||
"continue backfill":"Continue the backfill",
|
||||
"delete backfill": "Delete the backfill",
|
||||
"pause backfill": "Pause the backfill",
|
||||
"backfill executions": "Backfill executions",
|
||||
"execute backfill": "Execute backfill",
|
||||
"relative": "Relative",
|
||||
"relative start date": "Relative start date",
|
||||
"relative end date": "Relative end date",
|
||||
"now": "Now",
|
||||
"absolute": "Absolute",
|
||||
"backfill": "Backfill",
|
||||
"Set labels tooltip": "Set labels to the execution",
|
||||
"Set labels": "Set labels",
|
||||
"Set labels to execution": "Add or update the labels of the execution <code>{id}</code>",
|
||||
"Set labels done": "Successfully set the labels of the execution",
|
||||
"bulk set labels": "Are you sure you want to set labels to <code>{executionCount}</code> executions(s)?"
|
||||
},
|
||||
"fr": {
|
||||
"id": "Identifiant",
|
||||
"type": "Type",
|
||||
@@ -622,16 +22,16 @@
|
||||
"confirmation": "Confirmation",
|
||||
"delete confirm": "Êtes-vous sur de vouloir effacer <code>{name}</code> ?",
|
||||
"outdated revision save confirmation": {
|
||||
"confirm": "Êtes-vous sûr de vouloir l'écraser ?",
|
||||
"update": {
|
||||
"title": "Révision dépassée",
|
||||
"description": "Une révision plus récente du Flow existe.",
|
||||
"details": "Regardez l'onglet Révisions pour plus de détails sur la dernière version."
|
||||
},
|
||||
"create": {
|
||||
"description": "Un Flow avec le même id / namespace existe déjà.",
|
||||
"details": "Regardez le menu Flows pour plus de détails sur le Flow existant."
|
||||
}
|
||||
"confirm": "Êtes-vous sûr de vouloir l'écraser ?",
|
||||
"update": {
|
||||
"title": "Révision dépassée",
|
||||
"description": "Une révision plus récente du Flow existe.",
|
||||
"details": "Regardez l'onglet Révisions pour plus de détails sur la dernière version."
|
||||
},
|
||||
"create": {
|
||||
"description": "Un Flow avec le même id / namespace existe déjà.",
|
||||
"details": "Regardez le menu Flows pour plus de détails sur le Flow existant."
|
||||
}
|
||||
},
|
||||
"is deprecated": "est déprécié(e)",
|
||||
"success": "Succès",
|
||||
@@ -890,7 +290,8 @@
|
||||
"errors": {
|
||||
"404": {
|
||||
"title": "Page introuvable",
|
||||
"content": "L'URL demandé est introuvable sur ce serveur. <span class=\"text-muted\">C'est tout ce que nous savons.</span>"
|
||||
"content": "L'URL demandé est introuvable sur ce serveur. <span class=\"text-muted\">C'est tout ce que nous savons.</span>",
|
||||
"flow or execution": "Le flow ou l'exécution demandé est introuvable."
|
||||
}
|
||||
},
|
||||
"copy logs": "Copier les logs",
|
||||
@@ -1185,7 +586,17 @@
|
||||
"Set labels": "Ajouter des labels",
|
||||
"Set labels to execution": "Ajouter ou mettre à jour des labels à l'exécution <code>{id}</code>",
|
||||
"Set labels done": "Labels ajoutés avec succès à l'exécution",
|
||||
"bulk set labels": "Etes-vous sûr de vouloir ajouter des labels à <code>{executionCount}</code> exécutions(s)?"
|
||||
"bulk set labels": "Etes-vous sûr de vouloir ajouter des labels à <code>{executionCount}</code> exécutions(s)?",
|
||||
"dependencies loaded": "Dépendances chargées",
|
||||
"loaded x dependencies": "{count} dépendances chargées",
|
||||
"security_advice": {
|
||||
"title": "Vos données ne sont pas protégées !",
|
||||
"content": "Activer l'authentication basique pour protéger votre instance.",
|
||||
"switch_text": "Ne plus montrer",
|
||||
"enable": "Activer l'authentification"
|
||||
},
|
||||
"true": "Vrai",
|
||||
"false": "Faux",
|
||||
"undefined": "Non défini"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import axios from "axios";
|
||||
import NProgress from "nprogress"
|
||||
import {baseUrl} from "override/utils/route";
|
||||
|
||||
// nprogress
|
||||
let requestsTotal = 0
|
||||
@@ -136,7 +135,6 @@ export default (callback, store, router) => {
|
||||
return Promise.reject(errorResponse);
|
||||
})
|
||||
|
||||
instance.defaults.baseURL = baseUrl;
|
||||
|
||||
instance.defaults.paramsSerializer = {
|
||||
indexes: null
|
||||
|
||||
@@ -74,8 +74,9 @@ export function chartClick(moment, router, route, event) {
|
||||
const query = {};
|
||||
|
||||
if (event.date) {
|
||||
query.startDate = moment(event.date).toISOString(true);
|
||||
query.endDate = moment(event.date).add(1, "d").toISOString(true);
|
||||
const formattedDate = moment(event.date, "DD/MM/YYYY");
|
||||
query.startDate = formattedDate.toISOString(true);
|
||||
query.endDate = formattedDate.add(1, "d").toISOString(true);
|
||||
}
|
||||
|
||||
if (event.startDate) {
|
||||
@@ -121,9 +122,8 @@ export function chartClick(moment, router, route, event) {
|
||||
router.push({
|
||||
name: "executions/list",
|
||||
params: {
|
||||
tab: "executions",
|
||||
tenant: route.params.tenant
|
||||
},
|
||||
tenant: route.params.tenant
|
||||
},
|
||||
query: query
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ export const storageKeys = {
|
||||
SELECTED_TENANT: "selectedTenant",
|
||||
EXECUTE_FLOW_BEHAVIOUR: "executeFlowBehaviour",
|
||||
DEFAULT_NAMESPACE: "defaultNamespace",
|
||||
LATEST_NAMESPACE: "latestNamespace"
|
||||
LATEST_NAMESPACE: "latestNamespace",
|
||||
PAGINATION_SIZE: "paginationSize",
|
||||
}
|
||||
|
||||
export const executeFlowBehaviours = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user