Compare commits

...

19 Commits

Author SHA1 Message Date
YannC
410093cefc chore(version): update to version 'v0.15.3' 2024-03-07 16:02:22 +01:00
YannC
bd936125bd test(core): DocumentationGeneratorTest, deprecated message 2024-03-07 16:01:33 +01:00
brian.mulier
68ded87434 fix(webserver): multi-cookies in a single header decoder
part of #3228
2024-03-07 15:55:07 +01:00
Florian Hussonnois
d76807235f fix(core): pebble render function must render boolean (#3218)
Fix: #3218
2024-03-07 14:32:18 +01:00
YannC
63708a79e3 fix(core): take timezone into account for new schedule triggers (#3230)
closes #3227
2024-03-07 14:32:18 +01:00
YannC
41c0018d4b fix(ui): manage panel for SuperAdmin without tenant (#3225) 2024-03-07 14:32:18 +01:00
YannC
2cbd86c4d1 fix(): quickwins (#3215)
* fix(ui): increase flow input size in blueprints creation

closes #913

* feat(ui): keep the latest pagination size selected

closes #3030

* fix(ui): Do not display revision selector when only one revision

closes #1681

* fix(ui): can not save if flow is same as original

closes #1331

* fix(ui): now load execution before following it

closes #682

* fix(ui): display trigger.date instead of variables.date on execution page

closes #2832

* feat(ui): add new last 48 hours filter

closes #3184
2024-03-07 14:32:17 +01:00
YannC
49b64fa853 fix(ui): make dependencies expand more clear (#3222)
closes #3160
2024-03-07 14:32:17 +01:00
Ludovic DEHON
95113c5e76 fix(ui): don't save settings on page load 2024-03-07 14:32:17 +01:00
Ludovic DEHON
1e5e300974 feat(ui): change menu & icon layout
close kestra-io/kestra#2161
2024-03-07 14:32:17 +01:00
Loïc Mathieu
f69dc3c835 fix(core): DocumentationGeneratorTest.ech() test assertion 2024-03-07 14:32:17 +01:00
Ludovic DEHON
651c7bf589 feat(ui): add full-screen button on drawer
close kestra-io/kestra#2627
2024-03-07 14:32:17 +01:00
YannC
7878bcc281 fix(core): validate task default (#3224)
closes #25
2024-03-07 14:30:00 +01:00
YannC
ee059106b2 fix(controller): return 404 when flow not found in follow API (#3219)
* fix(controller): return 404 when flow not found in follow API

closes #1299

* fix(): review changes
2024-03-07 14:30:00 +01:00
YannC
4d2728a3f6 chore(version): update to version 'v0.15.2' 2024-03-04 21:52:07 +01:00
Loïc Mathieu
87f7cde742 fix(ui): missing check permission to display flow CREATE and EXECUTE button
When a user didn't have FLOW CREATE permission, the 'Create' buton will disapear from the Dashboard and the Flows pages.
When a user didn't have EXECUTION CREATE permission, the 'Execute' button will disapear from the Flow detail and Execution detail pages.
2024-03-04 18:21:03 +01:00
YannC
2e18c87907 fix(ui): change editor width storage key 2024-03-04 18:17:07 +01:00
YannC
58352411b5 fix(controller): fix 404 issue when flow of a trigger has been deleted (#3209) 2024-03-04 17:53:13 +01:00
Ludovic DEHON
438619dd8c chore(version): update to version 'v0.15.1'. 2024-03-01 23:38:36 +01:00
54 changed files with 558 additions and 191 deletions

View File

@@ -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;

View File

@@ -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

View File

@@ -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 {

View File

@@ -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()

View File

@@ -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";
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View 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>

View File

@@ -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" : "")

View File

@@ -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'"

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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: {

View File

@@ -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
}
},
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -40,6 +40,10 @@
value: "P1D",
label: "datepicker.last24hours"
},
{
value: "P2D",
label: "datepicker.last48hours"
},
{
value: "P7D",
label: "datepicker.last7days"

View File

@@ -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)
}
}]);
}

View File

@@ -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>

View File

@@ -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();
},

View File

@@ -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,

View File

@@ -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);
},

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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),

View File

@@ -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";
});
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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)
},

View File

@@ -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;
}
}
}

View File

@@ -707,6 +707,10 @@ form.ks-horizontal {
}
}
&.full-screen {
width: 99% !important;
}
.el-drawer__header {
padding: var(--spacer);
margin-bottom: 0;

View File

@@ -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"
}
}

View File

@@ -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 = {

View File

@@ -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")

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -0,0 +1 @@
io.kestra.webserver.cookies.ServerMultipleCookiesDecoder