mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-25 11:12:12 -05:00
Compare commits
19 Commits
docs/retur
...
v0.15.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
410093cefc | ||
|
|
bd936125bd | ||
|
|
68ded87434 | ||
|
|
d76807235f | ||
|
|
63708a79e3 | ||
|
|
41c0018d4b | ||
|
|
2cbd86c4d1 | ||
|
|
49b64fa853 | ||
|
|
95113c5e76 | ||
|
|
1e5e300974 | ||
|
|
f69dc3c835 | ||
|
|
651c7bf589 | ||
|
|
7878bcc281 | ||
|
|
ee059106b2 | ||
|
|
4d2728a3f6 | ||
|
|
87f7cde742 | ||
|
|
2e18c87907 | ||
|
|
58352411b5 | ||
|
|
438619dd8c |
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,10 +28,9 @@ public class VariableRenderer {
|
||||
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();
|
||||
@@ -102,17 +101,10 @@ 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
|
||||
@@ -123,10 +115,6 @@ public class VariableRenderer {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected String alternativeRender(Exception e, String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String renderRecursively(String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
return this.renderRecursively(0, inline, variables);
|
||||
}
|
||||
@@ -180,7 +168,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()
|
||||
|
||||
@@ -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,65 @@
|
||||
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> {
|
||||
@Inject
|
||||
private ModelValidator modelValidator;
|
||||
|
||||
@Inject
|
||||
private TaskDefaultService taskDefaultService;
|
||||
|
||||
@Inject
|
||||
private YamlFlowParser yamlFlowParser;
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
version=0.15.0
|
||||
version=0.15.3
|
||||
|
||||
jacksonVersion=2.16.1
|
||||
micronautVersion=4.3.4
|
||||
|
||||
8
ui/package-lock.json
generated
8
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.37",
|
||||
"@popperjs/core": "npm:@sxzz/popperjs-es@2.11.7",
|
||||
"@vue-flow/background": "^1.2.0",
|
||||
"@vue-flow/controls": "1.0.6",
|
||||
@@ -299,9 +299,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.37",
|
||||
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.37.tgz",
|
||||
"integrity": "sha512-86l7zSjXkjyjc1pKTH3sYj4NemWdgq718yUiif8zye8XaI1np/gAeHtovyS+VF57Sx5rd3exKzMnN7HDwFgb0A==",
|
||||
"peerDependencies": {
|
||||
"@vue-flow/background": "^1.2.0",
|
||||
"@vue-flow/controls": "1.0.6",
|
||||
|
||||
@@ -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.37",
|
||||
"@popperjs/core": "npm:@sxzz/popperjs-es@2.11.7",
|
||||
"@vue-flow/background": "^1.2.0",
|
||||
"@vue-flow/controls": "1.0.6",
|
||||
|
||||
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'"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
},
|
||||
methods: {
|
||||
follow() {
|
||||
this.$store.dispatch("execution/loadExecution", this.$route.params)
|
||||
const self = this;
|
||||
this.closeSSE();
|
||||
this.previousExecutionId = this.$route.params.id;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -48,7 +48,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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
|
||||
@@ -158,13 +158,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 +168,7 @@
|
||||
|
||||
<markdown v-if="triggerDefinition && triggerDefinition.description" :source="triggerDefinition.description" />
|
||||
<vars :data="modalData" />
|
||||
</el-drawer>
|
||||
</drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -193,12 +189,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: {
|
||||
|
||||
@@ -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,7 +299,7 @@
|
||||
|
||||
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";
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -235,7 +237,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 +761,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"
|
||||
@@ -825,13 +827,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 +844,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 +862,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 +889,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>
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,6 +707,10 @@ form.ks-horizontal {
|
||||
}
|
||||
}
|
||||
|
||||
&.full-screen {
|
||||
width: 99% !important;
|
||||
}
|
||||
|
||||
.el-drawer__header {
|
||||
padding: var(--spacer);
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"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",
|
||||
@@ -597,7 +598,9 @@
|
||||
"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)?"
|
||||
"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"
|
||||
},
|
||||
"fr": {
|
||||
"id": "Identifiant",
|
||||
@@ -1185,7 +1188,9 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -58,6 +58,7 @@ import io.micronaut.http.annotation.PathVariable;
|
||||
import io.micronaut.http.annotation.Post;
|
||||
import io.micronaut.http.annotation.Put;
|
||||
import io.micronaut.http.annotation.QueryValue;
|
||||
import io.micronaut.http.exceptions.HttpStatusException;
|
||||
import io.micronaut.http.multipart.StreamingFileUpload;
|
||||
import io.micronaut.http.server.types.files.StreamedFile;
|
||||
import io.micronaut.http.sse.Event;
|
||||
@@ -1003,7 +1004,7 @@ public class ExecutionController {
|
||||
);
|
||||
}
|
||||
|
||||
for(Execution execution : executions) {
|
||||
for (Execution execution : executions) {
|
||||
Execution resumeExecution = this.executionService.resume(execution, State.Type.RUNNING);
|
||||
this.executionQueue.emit(resumeExecution);
|
||||
}
|
||||
@@ -1107,7 +1108,14 @@ public class ExecutionController {
|
||||
() -> executionRepository.findById(tenantService.resolveTenant(), executionId).orElse(null),
|
||||
Duration.ofMillis(500)
|
||||
);
|
||||
Flow flow = flowRepository.findByExecution(execution);
|
||||
|
||||
Flow flow;
|
||||
try {
|
||||
flow = flowRepository.findByExecution(execution);
|
||||
} catch (IllegalStateException e) {
|
||||
emitter.error(new HttpStatusException(HttpStatus.NOT_FOUND, "Unable to find the flow for the execution " + executionId));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isStopFollow(flow, execution)) {
|
||||
emitter.next(Event.of(execution).id("end"));
|
||||
@@ -1273,7 +1281,8 @@ public class ExecutionController {
|
||||
return HttpResponse.ok(BulkResponse.builder().count(executions.size()).build());
|
||||
}
|
||||
|
||||
public record SetLabelsByIdsRequest(List<String> executionsId, List<Label> executionLabels) {}
|
||||
public record SetLabelsByIdsRequest(List<String> executionsId, List<Label> executionLabels) {
|
||||
}
|
||||
|
||||
@ExecuteOn(TaskExecutors.IO)
|
||||
@Post(uri = "/labels/by-query")
|
||||
|
||||
@@ -81,6 +81,13 @@ public class TriggerController {
|
||||
if (flow.isEmpty()) {
|
||||
// Warn instead of throwing to avoid blocking the trigger UI
|
||||
log.warn(String.format("Flow %s not found for trigger %s", tc.getFlowId(), tc.getTriggerId()));
|
||||
triggers.add(Triggers.builder()
|
||||
.abstractTrigger(null)
|
||||
.triggerContext(tc)
|
||||
.build()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractTrigger abstractTrigger = flow.get().getTriggers().stream().filter(t -> t.getId().equals(tc.getTriggerId())).findFirst().orElse(null);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2017-2024 original authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.kestra.webserver.cookies;
|
||||
|
||||
import io.micronaut.core.annotation.Internal;
|
||||
import io.micronaut.core.annotation.NonNull;
|
||||
import io.micronaut.http.cookie.Cookie;
|
||||
import io.micronaut.http.cookie.DefaultServerCookieDecoder;
|
||||
import io.micronaut.http.cookie.ServerCookieDecoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Workaround for <a href="https://github.com/micronaut-projects/micronaut-core/issues/10435">multi-cookies header parsing issue</a>
|
||||
*/
|
||||
@Internal
|
||||
public final class ServerMultipleCookiesDecoder implements ServerCookieDecoder {
|
||||
|
||||
public static final DefaultServerCookieDecoder DEFAULT_SERVER_COOKIE_DECODER = new DefaultServerCookieDecoder();
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<Cookie> decode(@NonNull String header) {
|
||||
String cookie2Header = header;
|
||||
if (!header.toLowerCase().startsWith("set-cookie2:")) {
|
||||
cookie2Header = "Set-Cookie2:" + header.replace(";", ",");
|
||||
}
|
||||
|
||||
return DEFAULT_SERVER_COOKIE_DECODER.decode(cookie2Header);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.kestra.webserver.cookies.ServerMultipleCookiesDecoder
|
||||
Reference in New Issue
Block a user