mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-25 02:14:38 -05:00
feat(ui): introduced new topology as libs (#1877)
close #1721 close #1670
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -34,6 +34,7 @@ ui/.env.*.local
|
||||
webserver/src/main/resources/ui
|
||||
yarn.lock
|
||||
ui/coverage
|
||||
ui/stats.html
|
||||
|
||||
### Docker
|
||||
/.env
|
||||
|
||||
4524
ui/package-lock.json
generated
4524
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,9 @@
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path ../.gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kestra-io/ui-libs": "^0.0.9",
|
||||
"@popperjs/core": "npm:@sxzz/popperjs-es@2.11.7",
|
||||
"@vue-flow/background": "^1.2.0",
|
||||
"@vue-flow/controls": "1.0.6",
|
||||
"@vue-flow/core": "1.14.3",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
@@ -24,7 +27,7 @@
|
||||
"element-plus": "^2.3.9",
|
||||
"humanize-duration": "^3.29.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-anchor": "^8.6.7",
|
||||
"markdown-it-container": "^3.0.0",
|
||||
@@ -35,6 +38,7 @@
|
||||
"moment-range": "4.0.2",
|
||||
"moment-timezone": "^0.5.43",
|
||||
"node-modules-polyfill": "^0.1.4",
|
||||
"npm": "^9.8.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"throttle-debounce": "^5.0.0",
|
||||
@@ -47,11 +51,11 @@
|
||||
"vue-router": "^4.2.4",
|
||||
"vue-sidebar-menu": "^5.2.10",
|
||||
"vue-virtual-scroller": "^2.0.0-beta.8",
|
||||
"vue3-popper": "^1.5.0",
|
||||
"vue3-tour": "git@github.com:kestra-io/vue3-tour.git",
|
||||
"vuex": "^4.1.0",
|
||||
"xss": "^1.0.14",
|
||||
"yaml": "^2.3.1",
|
||||
"@popperjs/core": "npm:@sxzz/popperjs-es@2.11.7"
|
||||
"yaml": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
@@ -64,6 +68,7 @@
|
||||
"monaco-editor": "^0.39.0",
|
||||
"monaco-yaml": "4.0.0-alpha.0",
|
||||
"prettier": "^3.0.1",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"sass": "^1.64.2",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-rewrite-all": "^1.0.1",
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup>
|
||||
import {ref, onMounted, inject, nextTick} from "vue";
|
||||
import {ref, onMounted, inject, nextTick, getCurrentInstance} from "vue";
|
||||
import {useRoute} from "vue-router";
|
||||
import {VueFlow, useVueFlow, Position, MarkerType} from "@vue-flow/core"
|
||||
import {Controls, ControlButton} from "@vue-flow/controls"
|
||||
import {Background} from "@vue-flow/background";
|
||||
import dagre from "dagre"
|
||||
import ArrowExpandAll from "vue-material-design-icons/ArrowExpandAll.vue";
|
||||
|
||||
import {cssVariable} from "../../utils/global"
|
||||
import FlowDependenciesBlock from "./FlowDependenciesBlock.vue";
|
||||
import {DependenciesNode} from "@kestra-io/ui-libs"
|
||||
|
||||
import {linkedElements} from "../../utils/vueFlow"
|
||||
|
||||
@@ -15,6 +16,7 @@
|
||||
|
||||
const route = useRoute();
|
||||
const axios = inject("axios")
|
||||
const router = getCurrentInstance().appContext.config.globalProperties.$router;
|
||||
|
||||
const loaded = ref([]);
|
||||
const dependencies = ref({
|
||||
@@ -54,7 +56,7 @@
|
||||
};
|
||||
|
||||
const expand = (data) => {
|
||||
load({namespace: data.namespace, id: data.id})
|
||||
load({namespace: data.namespace, id: data.flowId})
|
||||
};
|
||||
|
||||
const generateDagreGraph = () => {
|
||||
@@ -64,8 +66,8 @@
|
||||
|
||||
for (const node of dependencies.value.nodes) {
|
||||
dagreGraph.setNode(node.uid, {
|
||||
width: 250 ,
|
||||
height: 62
|
||||
width: 184 ,
|
||||
height: 44
|
||||
})
|
||||
}
|
||||
|
||||
@@ -93,8 +95,8 @@
|
||||
type: "flow",
|
||||
position: getNodePosition(dagreNode),
|
||||
style: {
|
||||
width: "250px",
|
||||
height: "62px",
|
||||
width: "184px",
|
||||
height: "44px",
|
||||
},
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left,
|
||||
@@ -104,6 +106,8 @@
|
||||
namespace: node.namespace,
|
||||
flowId: node.id,
|
||||
current: node.namespace === route.params.namespace && node.id === route.params.id,
|
||||
color: "pink",
|
||||
link: true
|
||||
}
|
||||
}]);
|
||||
}
|
||||
@@ -113,7 +117,10 @@
|
||||
id: edge.source + "|" + edge.target,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
markerEnd: MarkerType.ArrowClosed,
|
||||
markerEnd: {
|
||||
id: "marker-custom",
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
type: "smoothstep"
|
||||
}]);
|
||||
}
|
||||
@@ -134,6 +141,13 @@
|
||||
removeSelectedNodes(getNodes.value);
|
||||
removeSelectedEdges(getEdges.value);
|
||||
}
|
||||
|
||||
const openFlow = (data) => {
|
||||
router.push({
|
||||
name: "flows/update",
|
||||
params: {"namespace": data.namespace, "id": data.flowId, tab: "dependencies"},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -145,13 +159,14 @@
|
||||
:nodes-draggable="false"
|
||||
:elevate-nodes-on-select="false"
|
||||
>
|
||||
<Background />
|
||||
<template #node-flow="props">
|
||||
<FlowDependenciesBlock
|
||||
:node="props.data.node"
|
||||
:loaded="props.data.loaded"
|
||||
@expand="expand"
|
||||
<DependenciesNode
|
||||
v-bind="props"
|
||||
@expand-dependencies="expand"
|
||||
@mouseover="onMouseOver"
|
||||
@mouseleave="onMouseLeave"
|
||||
@open-link="openFlow($event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
:is="component"
|
||||
:icon="CodeTags"
|
||||
@click="onShow"
|
||||
ref="taskEdit"
|
||||
>
|
||||
<span v-if="component !== 'el-button'">{{ $t('show task source') }}</span>
|
||||
<span v-if="component !== 'el-button' && !isHidden">{{ $t("show task source") }}</span>
|
||||
<el-drawer
|
||||
v-if="isModalOpen"
|
||||
v-model="isModalOpen"
|
||||
@@ -14,25 +15,37 @@
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #header>
|
||||
<code>{{ taskId || task.id }}</code>
|
||||
<code>{{ taskId || task?.id || $t("add task") }}</code>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div v-loading="isLoading">
|
||||
<ValidationError link :error="taskError" />
|
||||
<ValidationError link :error="taskError" />
|
||||
|
||||
<el-button :icon="ContentSave" @click="saveTask" v-if="canSave && !isReadOnly" :disabled="taskError !== undefined" type="primary">
|
||||
{{ $t('save') }}
|
||||
<el-button
|
||||
:icon="ContentSave"
|
||||
@click="saveTask"
|
||||
v-if="canSave && !isReadOnly"
|
||||
:disabled="taskError !== undefined"
|
||||
type="primary"
|
||||
>
|
||||
{{ $t("save") }}
|
||||
</el-button>
|
||||
<el-alert show-icon :closable="false" class="mb-0 mt-3" v-if="revision && isReadOnly" type="warning">
|
||||
<strong>{{ $t('seeing old revision', {revision: revision}) }}</strong>
|
||||
<el-alert
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="mb-0 mt-3"
|
||||
v-if="revision && isReadOnly"
|
||||
type="warning"
|
||||
>
|
||||
<strong>{{ $t("seeing old revision", {revision: revision}) }}</strong>
|
||||
</el-alert>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-tabs v-if="taskYaml" v-model="activeTabs">
|
||||
<el-tabs v-model="activeTabs">
|
||||
<el-tab-pane name="form">
|
||||
<template #label>
|
||||
<span>{{ $t('form') }}</span>
|
||||
<span>{{ $t("form") }}</span>
|
||||
</template>
|
||||
<task-editor
|
||||
ref="editor"
|
||||
@@ -43,10 +56,9 @@
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="source">
|
||||
<template #label>
|
||||
<span>{{ $t('source') }}</span>
|
||||
<span>{{ $t("source") }}</span>
|
||||
</template>
|
||||
<editor
|
||||
v-if="taskYaml"
|
||||
:read-only="isReadOnly"
|
||||
ref="editor"
|
||||
@save="saveTask"
|
||||
@@ -61,7 +73,7 @@
|
||||
<el-tab-pane v-if="pluginMardown" name="documentation">
|
||||
<template #label>
|
||||
<span>
|
||||
{{ $t('documentation.documentation') }}
|
||||
{{ $t("documentation.documentation") }}
|
||||
</span>
|
||||
</template>
|
||||
<div class="documentation">
|
||||
@@ -91,7 +103,7 @@
|
||||
|
||||
export default {
|
||||
components: {Editor, TaskEditor, Markdown, ValidationError},
|
||||
emits: ["update:task"],
|
||||
emits: ["update:task", "close"],
|
||||
props: {
|
||||
component: {
|
||||
type: String,
|
||||
@@ -127,6 +139,48 @@
|
||||
emitOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
emitTaskOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isHidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
task: {
|
||||
async handler() {
|
||||
if (this.task) {
|
||||
this.taskYaml = YamlUtils.stringify(this.task);
|
||||
if (this.task.type) {
|
||||
this.$store
|
||||
.dispatch("plugin/load", {cls: this.task.type})
|
||||
}
|
||||
} else {
|
||||
this.taskYaml = "";
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
taskYaml: {
|
||||
handler() {
|
||||
const task = YamlUtils.parse(this.taskYaml);
|
||||
if (task?.type && task.type !== this.type) {
|
||||
this.$store
|
||||
.dispatch("plugin/load", {cls: task.type})
|
||||
this.type = task.type
|
||||
}
|
||||
},
|
||||
},
|
||||
isModalOpen: {
|
||||
handler() {
|
||||
if (!this.isModalOpen) {
|
||||
this.$emit("close");
|
||||
this.activeTabs = "form";
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -147,6 +201,13 @@
|
||||
},
|
||||
|
||||
saveTask() {
|
||||
if (this.emitTaskOnly) {
|
||||
this.$emit("update:task", this.taskYaml);
|
||||
this.taskYaml = "";
|
||||
this.isModalOpen = false;
|
||||
|
||||
return
|
||||
}
|
||||
let updatedSource;
|
||||
try {
|
||||
updatedSource = YamlUtils.replaceTaskInDocument(
|
||||
@@ -175,12 +236,12 @@
|
||||
},
|
||||
async onShow() {
|
||||
this.isModalOpen = !this.isModalOpen;
|
||||
if (this.taskId || this.task.id) {
|
||||
if (this.taskId) {
|
||||
this.taskYaml = await this.load(this.taskId ? this.taskId : this.task.id);
|
||||
} else {
|
||||
} else if (this.task) {
|
||||
this.taskYaml = YamlUtils.stringify(this.task);
|
||||
}
|
||||
if(this.task.type) {
|
||||
if (this.task?.type) {
|
||||
this.$store
|
||||
.dispatch("plugin/load", {cls: this.task.type})
|
||||
}
|
||||
@@ -195,13 +256,11 @@
|
||||
data() {
|
||||
return {
|
||||
uuid: Utils.uid(),
|
||||
taskYaml: undefined,
|
||||
taskYaml: "",
|
||||
isModalOpen: false,
|
||||
activeTabs: "form",
|
||||
type: null,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
computed: {
|
||||
...mapState("flow", ["flow"]),
|
||||
@@ -211,7 +270,7 @@
|
||||
...mapState("flow", ["revisions"]),
|
||||
...mapState("plugin", ["plugin"]),
|
||||
pluginMardown() {
|
||||
if(this.plugin && this.plugin.markdown) {
|
||||
if (this.plugin && this.plugin.markdown && YamlUtils.parse(this.taskYaml)?.type) {
|
||||
return this.plugin.markdown
|
||||
}
|
||||
return null
|
||||
@@ -224,11 +283,6 @@
|
||||
},
|
||||
isReadOnly() {
|
||||
return this.flow && this.revision && this.flow.revision !== this.revision
|
||||
},
|
||||
taskErrorContent() {
|
||||
return this.taskError
|
||||
? "<pre style='max-width: 40vw; white-space: pre-wrap'>" + this.taskError + "</pre>"
|
||||
: ""
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,11 +42,17 @@
|
||||
emits: ["update:modelValue"],
|
||||
created() {
|
||||
if (this.modelValue) {
|
||||
this.taskObject = YamlUtils.parse(this.modelValue);
|
||||
this.selectedTaskType = this.taskObject.type;
|
||||
this.$store.dispatch("flow/validateTask", {task: this.modelValue, section: this.section})
|
||||
|
||||
this.load();
|
||||
this.setup()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler() {
|
||||
if (!this.modelValue) {
|
||||
this.taskObject = {};
|
||||
this.selectedTaskType = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
@@ -73,6 +79,13 @@
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
setup() {
|
||||
this.taskObject = YamlUtils.parse(this.modelValue);
|
||||
this.selectedTaskType = this.taskObject.type;
|
||||
this.$store.dispatch("flow/validateTask", {task: this.modelValue, section: this.section})
|
||||
|
||||
this.load();
|
||||
},
|
||||
load() {
|
||||
this.isLoading = true;
|
||||
this.$store
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../styles/variable";
|
||||
@import "@kestra-io/ui-libs/src/scss/variables.scss";
|
||||
|
||||
.el-button.el-button--default {
|
||||
transition: none;
|
||||
|
||||
@@ -75,8 +75,6 @@
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -175,7 +173,7 @@
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import "../../../styles/variable";
|
||||
@import "@kestra-io/ui-libs/src/scss/variables.scss";
|
||||
|
||||
.header-wrapper {
|
||||
margin-bottom: calc($spacer * 2);
|
||||
|
||||
@@ -74,104 +74,104 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.node-wrapper {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
width: 200px;
|
||||
background: var(--bs-gray-100);
|
||||
|
||||
.el-button, .card-header {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
&.node-disabled {
|
||||
.card-header .task-title {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
> .icon {
|
||||
width: 35px;
|
||||
height: 53px;
|
||||
background: var(--bs-white);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.status-color {
|
||||
width: 10px;
|
||||
height: 53px;
|
||||
border-right: 1px solid var(--bs-border-color);
|
||||
}
|
||||
|
||||
|
||||
.is-success {
|
||||
background-color: var(--green);
|
||||
}
|
||||
|
||||
.is-running {
|
||||
background-color: var(--blue);
|
||||
}
|
||||
|
||||
.is-failed {
|
||||
background-color: var(--red);
|
||||
}
|
||||
|
||||
.bg-undefined {
|
||||
background-color: var(--bs-gray-400);
|
||||
}
|
||||
|
||||
.task-content {
|
||||
flex-grow: 1;
|
||||
width: 38px;
|
||||
|
||||
.card-header {
|
||||
height: 25px;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
flex: 1;
|
||||
flex-wrap: nowrap;
|
||||
background-color: var(--bs-gray-200);
|
||||
color: var(--bs-body-color);
|
||||
|
||||
html.dark & {
|
||||
background-color: var(--bs-gray-300);
|
||||
}
|
||||
|
||||
.task-title {
|
||||
margin-left: 2px;
|
||||
display: inline-block;
|
||||
font-size: var(--font-size-sm);
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:deep(.node-action) {
|
||||
flex-shrink: 2;
|
||||
padding-top: 18px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
top: 50px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.info-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.node-action {
|
||||
height: 28px;
|
||||
padding-top: 1px;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
//.node-wrapper {
|
||||
// cursor: pointer;
|
||||
// display: flex;
|
||||
// width: 200px;
|
||||
// background: var(--bs-gray-100);
|
||||
//
|
||||
// .el-button, .card-header {
|
||||
// border-radius: 0 !important;
|
||||
// }
|
||||
//
|
||||
// &.node-disabled {
|
||||
// .card-header .task-title {
|
||||
// text-decoration: line-through;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// > .icon {
|
||||
// width: 35px;
|
||||
// height: 53px;
|
||||
// background: var(--bs-white);
|
||||
// position: relative;
|
||||
// }
|
||||
//
|
||||
// .status-color {
|
||||
// width: 10px;
|
||||
// height: 53px;
|
||||
// border-right: 1px solid var(--bs-border-color);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// .is-success {
|
||||
// background-color: var(--green);
|
||||
// }
|
||||
//
|
||||
// .is-running {
|
||||
// background-color: var(--blue);
|
||||
// }
|
||||
//
|
||||
// .is-failed {
|
||||
// background-color: var(--red);
|
||||
// }
|
||||
//
|
||||
// .bg-undefined {
|
||||
// background-color: var(--bs-gray-400);
|
||||
// }
|
||||
//
|
||||
// .task-content {
|
||||
// flex-grow: 1;
|
||||
// width: 38px;
|
||||
//
|
||||
// .card-header {
|
||||
// height: 25px;
|
||||
// padding: 2px;
|
||||
// margin: 0;
|
||||
// border-bottom: 1px solid var(--bs-border-color);
|
||||
// flex: 1;
|
||||
// flex-wrap: nowrap;
|
||||
// background-color: var(--bs-gray-200);
|
||||
// color: var(--bs-body-color);
|
||||
//
|
||||
// html.dark & {
|
||||
// background-color: var(--bs-gray-300);
|
||||
// }
|
||||
//
|
||||
// .task-title {
|
||||
// margin-left: 2px;
|
||||
// display: inline-block;
|
||||
// font-size: var(--font-size-sm);
|
||||
// flex-grow: 1;
|
||||
// overflow: hidden;
|
||||
// text-overflow: ellipsis;
|
||||
// max-width: 100%;
|
||||
// white-space: nowrap;
|
||||
// }
|
||||
//
|
||||
// :deep(.node-action) {
|
||||
// flex-shrink: 2;
|
||||
// padding-top: 18px;
|
||||
// padding-right: 18px;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// .card-wrapper {
|
||||
// top: 50px;
|
||||
// position: absolute;
|
||||
// }
|
||||
//
|
||||
// .info-wrapper {
|
||||
// display: flex;
|
||||
// }
|
||||
//
|
||||
// .node-action {
|
||||
// height: 28px;
|
||||
// padding-top: 1px;
|
||||
// padding-right: 5px;
|
||||
// padding-left: 5px;
|
||||
// }
|
||||
//}
|
||||
</style>-
|
||||
@@ -1,29 +0,0 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="label" class="label" v-html="label" />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../../../styles/variable" as global-var;
|
||||
|
||||
.vue-flow__node-cluster {
|
||||
background-color: rgba(global-var.$cyan, 0.05);
|
||||
border: 1px solid var(--bs-cyan);
|
||||
pointer-events: none !important;
|
||||
|
||||
.label {
|
||||
color: var(--bs-cyan);
|
||||
text-align: center;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
<script setup>
|
||||
import {Handle} from "@vue-flow/core"
|
||||
|
||||
const props = defineProps({
|
||||
sourcePosition: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
targetPosition: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Handle type="source" :position="sourcePosition" />
|
||||
<div class="dot" />
|
||||
<Handle type="target" :position="targetPosition" />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.vue-flow__node-dot {
|
||||
border: 0 !important;
|
||||
.vue-flow__handle {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
div.dot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
border-radius: 50%;
|
||||
height: 5px;
|
||||
width: 5px;
|
||||
background-color: var(--bs-cyan);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,314 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type {EdgeProps, Position} from '@vue-flow/core'
|
||||
import {EdgeLabelRenderer, getSmoothStepPath, useEdge} from '@vue-flow/core'
|
||||
import type {CSSProperties} from 'vue'
|
||||
import {computed, getCurrentInstance, ref, watch} from 'vue'
|
||||
import TaskEditor from "../../flows/TaskEditor.vue"
|
||||
import Help from "vue-material-design-icons/Help.vue";
|
||||
import HelpCircle from "vue-material-design-icons/HelpCircle.vue";
|
||||
import Exclamation from "vue-material-design-icons/Exclamation.vue";
|
||||
import Reload from "vue-material-design-icons/Reload.vue";
|
||||
import ViewParallelOutline from "vue-material-design-icons/ViewParallelOutline.vue";
|
||||
import ViewSequentialOutline from "vue-material-design-icons/ViewSequentialOutline.vue";
|
||||
import Plus from "vue-material-design-icons/Plus.vue";
|
||||
import ContentSave from "vue-material-design-icons/ContentSave.vue";
|
||||
import yamlUtils from "../../../utils/yamlUtils.js";
|
||||
import YamlUtils from "../../../utils/yamlUtils.js";
|
||||
import {useStore} from "vuex";
|
||||
import ValidationError from "../../flows/ValidationError.vue";
|
||||
import {Ref} from "@vue/reactivity";
|
||||
import {SECTIONS} from "../../../utils/constants.js";
|
||||
|
||||
const store = useStore();
|
||||
const t = getCurrentInstance().appContext.config.globalProperties.$t;
|
||||
|
||||
|
||||
interface CustomEdgeProps<T = any> extends /* @vue-ignore */ EdgeProps<T> {
|
||||
id: string
|
||||
sourceX: number
|
||||
sourceY: number
|
||||
targetX: number
|
||||
targetY: number
|
||||
sourcePosition: Position
|
||||
targetPosition: Position
|
||||
data: T
|
||||
markerEnd: string
|
||||
style: CSSProperties,
|
||||
yamlSource: String,
|
||||
flowablesIds: Array<String>,
|
||||
isReadOnly: Boolean,
|
||||
isAllowedEdit: Boolean
|
||||
}
|
||||
|
||||
const props = defineProps<CustomEdgeProps>()
|
||||
const isHover = ref(false);
|
||||
const isOpen = ref(false);
|
||||
const {edge} = useEdge()
|
||||
const emit = defineEmits(["edit"])
|
||||
const taskYaml = ref("");
|
||||
const execution = store.getters["execution/execution"];
|
||||
const timer = ref(undefined);
|
||||
const taskError: Ref<string> = ref(store.getters["flow/taskError"])
|
||||
|
||||
watch(() => store.getters["flow/taskError"], async () => {
|
||||
taskError.value = store.getters["flow/taskError"];
|
||||
});
|
||||
|
||||
const isBorderEdge = () => {
|
||||
if (!props.data.haveAdd && props.data.isFlowable) {
|
||||
return false
|
||||
}
|
||||
const task1 = props.id.split("|")[0]
|
||||
const task2 = props.id.split("|")[1]
|
||||
// Check if relation is root > task or task > end or if it contains a haveAdd
|
||||
return (task1.includes("_root") && yamlUtils.extractTask(props.yamlSource, task2)) || (task2.includes("_end") && yamlUtils.extractTask(props.yamlSource, task1)) || props.data.haveAdd
|
||||
}
|
||||
|
||||
const getEdgeLabel = (relation) => {
|
||||
let label = "";
|
||||
if (relation.relationType) {
|
||||
label = relation.relationType;
|
||||
if (relation.relationType === "CHOICE" && relation.value) {
|
||||
label += ` : ${relation.value}`;
|
||||
}
|
||||
} else if (isBorderEdge()) {
|
||||
label += "SEQUENTIAL"
|
||||
}
|
||||
return label;
|
||||
};
|
||||
|
||||
const getEdgeIcon = (relation) => {
|
||||
if (relation.relationType) {
|
||||
if (relation.relationType === "ERROR") {
|
||||
return Exclamation;
|
||||
} else if (relation.relationType === "DYNAMIC") {
|
||||
return Reload;
|
||||
} else if (relation.relationType === "CHOICE") {
|
||||
return Help;
|
||||
} else if (relation.relationType === "PARALLEL") {
|
||||
return ViewParallelOutline;
|
||||
} else {
|
||||
return ViewSequentialOutline;
|
||||
}
|
||||
} else if (isBorderEdge()) {
|
||||
return ViewSequentialOutline;
|
||||
}
|
||||
|
||||
return HelpCircle;
|
||||
};
|
||||
|
||||
const getClassName = computed(() => {
|
||||
return {
|
||||
[props.data.edge.relation.relationType]: true,
|
||||
hover: isHover
|
||||
}
|
||||
})
|
||||
|
||||
const path = computed(() => getSmoothStepPath(props))
|
||||
|
||||
const onMouseOver = () => {
|
||||
isHover.value = true;
|
||||
}
|
||||
|
||||
const onMouseLeave = () => {
|
||||
isHover.value = false;
|
||||
}
|
||||
|
||||
const updateTask = (task) => {
|
||||
taskYaml.value = task;
|
||||
clearTimeout(timer.value);
|
||||
timer.value = setTimeout(() => {
|
||||
store.dispatch("flow/validateTask", {task: task, section: SECTIONS.TASKS})
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const getAddTaskInformation = () => {
|
||||
// end to end edge case
|
||||
if (props.data.haveAdd) {
|
||||
return {taskId: props.data.haveAdd[0], taskYaml: taskYaml.value, insertPosition: props.data.haveAdd[1]};
|
||||
}
|
||||
|
||||
let leftNodeIsFlowable = false;
|
||||
|
||||
const leftNodeIsTask = YamlUtils.extractTask(props.yamlSource, props.id.split("|")[0]) !== undefined;
|
||||
if (leftNodeIsTask) {
|
||||
leftNodeIsFlowable = props.flowablesIds.includes(props.id.split("|")[0])
|
||||
}
|
||||
// If left node is a flowable task or is not a task, then we insert
|
||||
// the new task before the right task node
|
||||
const [taskId, insertPosition] = leftNodeIsTask && !leftNodeIsFlowable ? [props.id.split("|")[0], "after"] : [props.data.nextTaskId, "before"];
|
||||
|
||||
return {taskId: taskId, taskYaml: taskYaml.value, insertPosition: insertPosition}
|
||||
}
|
||||
|
||||
const taskHaveId = () => {
|
||||
return taskYaml.value.length > 0 ? !!YamlUtils.parse(taskYaml.value).id : false;
|
||||
}
|
||||
|
||||
const checkTaskExist = () => {
|
||||
return yamlUtils.checkTaskAlreadyExist(props.yamlSource, YamlUtils.parse(taskYaml.value).id)
|
||||
}
|
||||
|
||||
const forwardTask = () => {
|
||||
if (!checkTaskExist()) {
|
||||
emit("edit", getAddTaskInformation());
|
||||
isOpen.value = false;
|
||||
} else {
|
||||
store.dispatch("core/showMessage", {
|
||||
variant: "error",
|
||||
title: t("task id already exist"),
|
||||
message: t(`Task Id already exist in the flow`, {taskId: YamlUtils.parse(taskYaml.value).id})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const addTooltip = () => {
|
||||
const addInformation = getAddTaskInformation();
|
||||
const taskId = addInformation.insertPosition === 'before' ? props.data.nextTaskId : addInformation.taskId;
|
||||
|
||||
if (execution || !taskId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!props.data.initTask) {
|
||||
return t("add at position", {
|
||||
position: t(addInformation.insertPosition),
|
||||
task: taskId
|
||||
})
|
||||
} else {
|
||||
return t("create first task");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<path
|
||||
:id="id"
|
||||
:style="style"
|
||||
class="vue-flow__edge-path"
|
||||
:class="getClassName"
|
||||
:d="path[0]"
|
||||
:marker-end="markerEnd"
|
||||
/>
|
||||
|
||||
<!-- hidden path to have largest hover region -->
|
||||
<path
|
||||
:d="path[0]"
|
||||
fill="none"
|
||||
stroke-opacity="0"
|
||||
stroke-width="20"
|
||||
@mouseover="onMouseOver"
|
||||
@mouseleave="onMouseLeave"
|
||||
/>
|
||||
|
||||
<EdgeLabelRenderer style="z-index: 10">
|
||||
<div
|
||||
v-if="getEdgeLabel(props.data.edge.relation) !== '' && !props.data.disabled"
|
||||
@mouseover="onMouseOver"
|
||||
@mouseleave="onMouseLeave"
|
||||
:style="{
|
||||
pointerEvents: 'all',
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${path[1]}px,${path[2]}px)`,
|
||||
}"
|
||||
class="nodrag nopan"
|
||||
:class="props.data.edge.relation.relationType"
|
||||
>
|
||||
<el-tooltip placement="bottom" :persistent="false" transition="" :hide-after="0">
|
||||
<template #content>
|
||||
<template v-if="isHover && !isReadOnly && isAllowedEdit">
|
||||
{{ getEdgeLabel(props.data.edge.relation) }}<br/>
|
||||
<span v-html="addTooltip()"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ getEdgeLabel(props.data.edge.relation) }}
|
||||
</template>
|
||||
</template>
|
||||
<span>
|
||||
<el-button v-if="isHover && !isReadOnly && isAllowedEdit" :icon="Plus" link @click="isOpen = true"/>
|
||||
<el-button v-else :icon="getEdgeIcon(props.data.edge.relation)" link/>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
|
||||
|
||||
<el-drawer
|
||||
v-if="isOpen"
|
||||
v-model="isOpen"
|
||||
title="Add a task"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<el-form label-position="top">
|
||||
<task-editor
|
||||
:section="SECTIONS.TASKS"
|
||||
@update:model-value="updateTask($event)"
|
||||
/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<ValidationError link :error="taskError"/>
|
||||
<el-button
|
||||
:disabled="!taskHaveId() || taskError !== undefined"
|
||||
:icon="ContentSave"
|
||||
@click="forwardTask"
|
||||
type="primary"
|
||||
>
|
||||
{{ $t("save") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.vue-flow__edge-path {
|
||||
&.ERROR {
|
||||
stroke: var(--bs-danger);
|
||||
}
|
||||
|
||||
&.DYNAMIC {
|
||||
stroke: var(--bs-teal);
|
||||
}
|
||||
|
||||
&.CHOICE {
|
||||
stroke: var(--bs-orange);
|
||||
}
|
||||
}
|
||||
|
||||
.vue-flow__edge-labels > div {
|
||||
border-radius: 50%;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
background: var(--bs-purple);
|
||||
|
||||
.el-button {
|
||||
margin-top: -9px;
|
||||
margin-left: -1px;
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
&:hover, &:active {
|
||||
color: var(--el-color-white) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.ERROR {
|
||||
background: var(--bs-danger);
|
||||
}
|
||||
|
||||
&.DYNAMIC {
|
||||
background: var(--bs-teal);
|
||||
}
|
||||
|
||||
&.CHOICE {
|
||||
background: var(--bs-orange);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,73 +0,0 @@
|
||||
<script setup>
|
||||
import {Handle} from "@vue-flow/core"
|
||||
import TreeTaskNode from "../TreeTaskNode.vue";
|
||||
|
||||
const emit = defineEmits(["follow", "mouseover", "mouseleave", "edit", "delete", "addFlowableError"])
|
||||
|
||||
const props = defineProps({
|
||||
sourcePosition: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
targetPosition: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
isAllowedEdit: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
})
|
||||
|
||||
const mouseover = () => {
|
||||
emit("mouseover", props.data.node);
|
||||
};
|
||||
|
||||
const mouseleave = () => {
|
||||
emit("mouseleave", props.data.node);
|
||||
};
|
||||
|
||||
const forwardEvent = (type, event) => {
|
||||
emit(type, event);
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Handle type="source" :position="sourcePosition" />
|
||||
<TreeTaskNode
|
||||
:n="data.node"
|
||||
:namespace="data.namespace"
|
||||
:flow-id="data.flowId"
|
||||
:revision="data.revision"
|
||||
:is-flowable="data.isFlowable"
|
||||
:is-read-only="props.isReadOnly"
|
||||
:is-allowed-edit="props.isAllowedEdit"
|
||||
@follow="forwardEvent('follow', $event)"
|
||||
@edit="forwardEvent('edit', $event)"
|
||||
@delete="forwardEvent('delete', $event)"
|
||||
@addFlowableError="forwardEvent('addFlowableError', $event)"
|
||||
@mouseover="mouseover"
|
||||
@mouseleave="mouseleave"
|
||||
/>
|
||||
<Handle type="target" :position="targetPosition" />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.vue-flow__node-task {
|
||||
border: 1px solid var(--bs-border-color);
|
||||
}
|
||||
</style>
|
||||
@@ -1,71 +0,0 @@
|
||||
<script setup>
|
||||
import {Handle} from "@vue-flow/core"
|
||||
import TreeTriggerNode from "../TreeTriggerNode.vue";
|
||||
import TreeTaskNode from "../TreeTaskNode.vue";
|
||||
|
||||
const emit = defineEmits(["mouseover", "mouseleave", "edit", "delete"])
|
||||
|
||||
const props = defineProps({
|
||||
sourcePosition: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
targetPosition: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
isAllowedEdit: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
})
|
||||
|
||||
const mouseover = () => {
|
||||
emit("mouseover", props.data.node);
|
||||
};
|
||||
|
||||
const mouseleave = () => {
|
||||
emit("mouseleave", props.data.node);
|
||||
};
|
||||
|
||||
const forwardEvent = (type, event) => {
|
||||
emit(type, event);
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Handle type="source" :position="sourcePosition" />
|
||||
<TreeTriggerNode
|
||||
:n="data.node"
|
||||
:namespace="data.namespace"
|
||||
:flow-id="data.flowId"
|
||||
:revision="data.revision"
|
||||
:is-read-only="props.isReadOnly"
|
||||
:is-allowed-edit="props.isAllowedEdit"
|
||||
@edit="forwardEvent('edit', $event)"
|
||||
@delete="forwardEvent('delete', $event)"
|
||||
@mouseover="mouseover"
|
||||
@mouseleave="mouseleave"
|
||||
/>
|
||||
<Handle type="target" :position="targetPosition" />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.vue-flow__node-task {
|
||||
border: 1px solid var(--bs-border-color);
|
||||
}
|
||||
</style>
|
||||
@@ -82,7 +82,7 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/_variable.scss";
|
||||
@import "@kestra-io/ui-libs/src/scss/variables.scss";
|
||||
|
||||
.status-pie {
|
||||
div {
|
||||
|
||||
@@ -1,29 +1,35 @@
|
||||
<script setup>
|
||||
// Core
|
||||
import {getCurrentInstance, nextTick, onMounted, ref, watch} from "vue";
|
||||
import {getCurrentInstance, nextTick, onMounted, onBeforeMount, ref, watch} from "vue";
|
||||
import {useStore} from "vuex";
|
||||
import {MarkerType, Position, useVueFlow, VueFlow} from "@vue-flow/core";
|
||||
import {MarkerType, Position, useVueFlow} from "@vue-flow/core";
|
||||
|
||||
// Nodes
|
||||
import Cluster from "../graph/nodes/Cluster.vue";
|
||||
import Dot from "../graph/nodes/Dot.vue"
|
||||
import Task from "../graph/nodes/Task.vue";
|
||||
import Trigger from "../graph/nodes/Trigger.vue";
|
||||
import Edge from "../graph/nodes/Edge.vue";
|
||||
import TaskEdit from "../flows/TaskEdit.vue";
|
||||
import SearchField from "../layout/SearchField.vue";
|
||||
import LogLevelSelector from "../logs/LogLevelSelector.vue";
|
||||
import LogList from "../logs/LogList.vue";
|
||||
import Collapse from "../layout/Collapse.vue";
|
||||
|
||||
// Topology Control
|
||||
import {Controls, ControlButton} from "@vue-flow/controls"
|
||||
import SplitCellsHorizontal from "../../assets/icons/SplitCellsHorizontal.vue"
|
||||
import SplitCellsVertical from "../../assets/icons/SplitCellsVertical.vue"
|
||||
// Topology
|
||||
import {
|
||||
Topology
|
||||
} from "@kestra-io/ui-libs"
|
||||
|
||||
// Utils
|
||||
import YamlUtils from "../../utils/yamlUtils";
|
||||
import {YamlUtils, VueFlowUtils} from "@kestra-io/ui-libs";
|
||||
import {SECTIONS} from "../../utils/constants";
|
||||
import {linkedElements} from "../../utils/vueFlow";
|
||||
import {cssVariable} from "../../utils/global";
|
||||
import dagre from "dagre";
|
||||
import Utils from "../../utils/utils";
|
||||
import ContentSave from "vue-material-design-icons/ContentSave.vue";
|
||||
import TaskEditor from "../flows/TaskEditor.vue";
|
||||
import ValidationError from "../flows/ValidationError.vue";
|
||||
import Markdown from "../layout/Markdown.vue";
|
||||
import yamlUtils from "../../utils/yamlUtils";
|
||||
|
||||
const router = getCurrentInstance().appContext.config.globalProperties.$router;
|
||||
|
||||
const vueflowId = ref(Math.random().toString());
|
||||
// Vue flow methods to interact with Graph
|
||||
const {
|
||||
id,
|
||||
@@ -36,8 +42,9 @@
|
||||
removeSelectedElements,
|
||||
onNodeDragStart,
|
||||
onNodeDragStop,
|
||||
onNodeDrag
|
||||
} = useVueFlow({id: Math.random().toString()});
|
||||
onNodeDrag,
|
||||
setElements,
|
||||
} = useVueFlow({id: vueflowId.value});
|
||||
|
||||
// props
|
||||
const props = defineProps({
|
||||
@@ -90,25 +97,41 @@
|
||||
(props.viewType?.indexOf("blueprint") !== -1 ? true : localStorage.getItem("topology-orientation") === "1")
|
||||
}
|
||||
|
||||
|
||||
// Components variables
|
||||
const dragging = ref(false);
|
||||
const isHorizontal = ref(isHorizontalDefault());
|
||||
const elements = ref([])
|
||||
const lastPosition = ref(null)
|
||||
const vueFlow = ref(null);
|
||||
const timer = ref(null);
|
||||
const icons = ref(store.getters["plugin/getIcons"]);
|
||||
const taskObject = ref(null);
|
||||
const taskEditData = ref(null);
|
||||
const taskEdit = ref(null);
|
||||
const isShowLogsOpen = ref(false);
|
||||
const logFilter = ref("");
|
||||
const logLevel = ref(localStorage.getItem("defaultLogLevel") || "INFO");
|
||||
const isDrawerOpen = ref(false);
|
||||
const isShowDescriptionOpen = ref(false);
|
||||
const selectedTask = ref(null);
|
||||
|
||||
// Init components
|
||||
onMounted( async() => {
|
||||
onMounted(() => {
|
||||
// Regenerate graph on window resize
|
||||
observeWidth();
|
||||
})
|
||||
|
||||
|
||||
watch(() => props.flowGraph, () => {
|
||||
generateGraph();
|
||||
})
|
||||
|
||||
watch(() => isDrawerOpen.value, () => {
|
||||
if (!isDrawerOpen.value) {
|
||||
isShowDescriptionOpen.value = false;
|
||||
isShowLogsOpen.value = false;
|
||||
selectedTask.value = null;
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.viewType, () => {
|
||||
isHorizontal.value = props.viewType === "source-topology" ? false :
|
||||
(props.viewType?.indexOf("blueprint") !== -1 ? true : localStorage.getItem("topology-orientation") === "1")
|
||||
@@ -129,15 +152,15 @@
|
||||
resizeObserver.observe(vueFlow.value);
|
||||
}
|
||||
|
||||
|
||||
const forwardEvent = (type, event) => {
|
||||
emit(type, event);
|
||||
};
|
||||
// Source edit functions
|
||||
|
||||
const onDelete = (event) => {
|
||||
const flowParsed = YamlUtils.parse(props.source);
|
||||
toast.confirm(
|
||||
t("delete task confirm", {taskId: flowParsed.id}),
|
||||
t("delete task confirm", {taskId: event.id}),
|
||||
() => {
|
||||
|
||||
const section = event.section ? event.section : SECTIONS.TASKS;
|
||||
@@ -149,123 +172,74 @@
|
||||
});
|
||||
return;
|
||||
}
|
||||
emit("on-edit",YamlUtils.deleteTask(props.source, event.id, section))
|
||||
emit("on-edit", YamlUtils.deleteTask(props.source, event.id, section))
|
||||
},
|
||||
() => {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Source edit functions
|
||||
const onCreateNewTask = (event) => {
|
||||
const source = props.source;
|
||||
emit("on-edit",YamlUtils.insertTask(source, event.taskId, event.taskYaml, event.insertPosition))
|
||||
taskEditData.value = {
|
||||
insertionDetails: event,
|
||||
action: "create_task",
|
||||
section: SECTIONS.TASKS
|
||||
};
|
||||
taskEdit.value.$refs.taskEdit.click()
|
||||
}
|
||||
|
||||
const onEditTask = (event) => {
|
||||
taskEditData.value = {
|
||||
action: "edit_task",
|
||||
section: event.section ? event.section : SECTIONS.TASKS,
|
||||
oldTaskId: event.task.id,
|
||||
};
|
||||
taskObject.value = event.task
|
||||
taskEdit.value.$refs.taskEdit.click()
|
||||
}
|
||||
|
||||
const onAddFlowableError = (event) => {
|
||||
taskEditData.value = {
|
||||
action: "add_flowable_error",
|
||||
taskId: event.task.id
|
||||
};
|
||||
taskEdit.value.$refs.taskEdit.click()
|
||||
|
||||
}
|
||||
|
||||
const confirmEdit = (event) => {
|
||||
const source = props.source;
|
||||
emit("on-edit",YamlUtils.insertErrorInFlowable(source, event.error, event.taskId))
|
||||
}
|
||||
|
||||
// Flow check functions
|
||||
const flowHaveTasks = (source) => {
|
||||
const flow = source ? source : props.source
|
||||
return flow ? YamlUtils.flowHaveTasks(flow) : false;
|
||||
}
|
||||
|
||||
const flowables = () => {
|
||||
return props.flowGraph && props.flowGraph.flowables ? props.flowGraph.flowables : [];
|
||||
}
|
||||
|
||||
// Graph interactions functions
|
||||
const onMouseOver = (node) => {
|
||||
if (!dragging.value) {
|
||||
linkedElements(id, node.uid).forEach((n) => {
|
||||
if (n.type === "task") {
|
||||
n.style = {...n.style, outline: "0.5px solid " + cssVariable("--bs-yellow")}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const onMouseLeave = () => {
|
||||
resetNodesStyle();
|
||||
}
|
||||
|
||||
const resetNodesStyle = () => {
|
||||
getNodes.value.filter(n => n.type === "task" || n.type === " trigger")
|
||||
.forEach(n => {
|
||||
n.style = {...n.style, opacity: "1", outline: "none"}
|
||||
})
|
||||
}
|
||||
|
||||
onNodeDragStart((e) => {
|
||||
dragging.value = true;
|
||||
resetNodesStyle();
|
||||
e.node.style = {...e.node.style, zIndex: 1976}
|
||||
lastPosition.value = e.node.position;
|
||||
})
|
||||
|
||||
onNodeDragStop((e) => {
|
||||
dragging.value = false;
|
||||
if (checkIntersections(e.intersections, e.node) === null) {
|
||||
const taskNode1 = e.node;
|
||||
// check multiple intersection with task
|
||||
const taskNode2 = e.intersections.find(n => n.type === "task");
|
||||
if (taskNode2) {
|
||||
try {
|
||||
emit("on-edit", YamlUtils.swapTasks(props.source, taskNode1.id, taskNode2.id))
|
||||
} catch (e) {
|
||||
store.dispatch("core/showMessage", {
|
||||
variant: "error",
|
||||
title: t("cannot swap tasks"),
|
||||
message: t(e.message, e.messageOptions)
|
||||
});
|
||||
taskNode1.position = lastPosition.value;
|
||||
}
|
||||
} else {
|
||||
taskNode1.position = lastPosition.value;
|
||||
const task = YamlUtils.extractTask(props.source, YamlUtils.parse(event).id);
|
||||
if (task === undefined || (task && YamlUtils.parse(event).id === taskEditData.value.oldTaskId)) {
|
||||
switch (taskEditData.value.action) {
|
||||
case("create_task"):
|
||||
emit("on-edit", YamlUtils.insertTask(source, taskEditData.value.insertionDetails[0], event, taskEditData.value.insertionDetails[1]))
|
||||
return;
|
||||
case("edit_task"):
|
||||
emit("on-edit", YamlUtils.replaceTaskInDocument(
|
||||
source,
|
||||
taskEditData.value.oldTaskId,
|
||||
event
|
||||
))
|
||||
return;
|
||||
case("add_flowable_error"):
|
||||
emit("on-edit", YamlUtils.insertErrorInFlowable(props.source, event, taskEditData.value.taskId))
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
e.node.position = lastPosition.value;
|
||||
store.dispatch("core/showMessage", {
|
||||
variant: "error",
|
||||
title: t("error detected"),
|
||||
message: t("Task Id already exist in the flow", {taskId: YamlUtils.parse(event).id})
|
||||
});
|
||||
}
|
||||
resetNodesStyle();
|
||||
e.node.style = {...e.node.style, zIndex: 1}
|
||||
lastPosition.value = null;
|
||||
})
|
||||
taskEditData.value = null;
|
||||
taskObject.value = null;
|
||||
}
|
||||
|
||||
onNodeDrag((e) => {
|
||||
resetNodesStyle();
|
||||
getNodes.value.filter(n => n.id !== e.node.id).forEach(n => {
|
||||
if (n.type === "trigger" || (n.type === "task" && YamlUtils.isParentChildrenRelation(props.source, n.id, e.node.id))) {
|
||||
n.style = {...n.style, opacity: "0.5"}
|
||||
} else {
|
||||
n.style = {...n.style, opacity: "1"}
|
||||
}
|
||||
})
|
||||
if (!checkIntersections(e.intersections, e.node) && e.intersections.filter(n => n.type === "task").length === 1) {
|
||||
e.intersections.forEach(n => {
|
||||
if (n.type === "task") {
|
||||
n.style = {...n.style, outline: "0.5px solid " + cssVariable("--bs-primary")}
|
||||
}
|
||||
})
|
||||
e.node.style = {...e.node.style, outline: "0.5px solid " + cssVariable("--bs-primary")}
|
||||
}
|
||||
})
|
||||
|
||||
const checkIntersections = (intersections, node) => {
|
||||
const tasksMeet = intersections.filter(n => n.type === "task").map(n => n.id);
|
||||
if (tasksMeet.length > 1) {
|
||||
return "toomuchtaskerror";
|
||||
}
|
||||
if (tasksMeet.length === 1 && YamlUtils.isParentChildrenRelation(props.source, tasksMeet[0], node.id)) {
|
||||
return "parentchildrenerror";
|
||||
}
|
||||
if (intersections.filter(n => n.type === "trigger").length > 0) {
|
||||
return "triggererror";
|
||||
}
|
||||
return null;
|
||||
const closeEdit = () => {
|
||||
taskEditData.value = null;
|
||||
taskObject.value = null;
|
||||
}
|
||||
|
||||
const toggleOrientation = () => {
|
||||
@@ -280,271 +254,75 @@
|
||||
|
||||
// Graph generation functions
|
||||
const generateDagreGraph = () => {
|
||||
const dagreGraph = new dagre.graphlib.Graph({compound: true})
|
||||
dagreGraph.setDefaultEdgeLabel(() => ({}))
|
||||
dagreGraph.setGraph({rankdir: isHorizontal.value ? "LR" : "TB"})
|
||||
|
||||
for (const node of props.flowGraph.nodes) {
|
||||
dagreGraph.setNode(node.uid, {
|
||||
width: getNodeWidth(node),
|
||||
height: getNodeHeight(node)
|
||||
})
|
||||
}
|
||||
|
||||
for (const edge of props.flowGraph.edges) {
|
||||
dagreGraph.setEdge(edge.source, edge.target)
|
||||
}
|
||||
|
||||
for (let cluster of (props.flowGraph.clusters || [])) {
|
||||
dagreGraph.setNode(cluster.cluster.uid, {clusterLabelPos: "top"});
|
||||
|
||||
if (cluster.parents) {
|
||||
dagreGraph.setParent(cluster.cluster.uid, cluster.parents[cluster.parents.length - 1]);
|
||||
}
|
||||
|
||||
for (let node of (cluster.nodes || [])) {
|
||||
dagreGraph.setParent(node, cluster.cluster.uid)
|
||||
}
|
||||
}
|
||||
dagre.layout(dagreGraph)
|
||||
return dagreGraph;
|
||||
return VueFlowUtils.generateDagreGraph(props.flowGraph, hiddenNodes.value, isHorizontal.value, clusterCollapseToNode.value, edgeReplacer.value, collapsed.value, clusterToNode.value);
|
||||
}
|
||||
|
||||
const getNodePosition = (n, parent) => {
|
||||
const position = {x: n.x - n.width / 2, y: n.y - n.height / 2};
|
||||
|
||||
// bug with parent node,
|
||||
if (parent) {
|
||||
const parentPosition = getNodePosition(parent);
|
||||
position.x = position.x - parentPosition.x;
|
||||
position.y = position.y - parentPosition.y;
|
||||
const openFlow = (data) => {
|
||||
if (data.link.executionId) {
|
||||
store
|
||||
.dispatch("execution/loadExecution", {id: data.link.executionId})
|
||||
.then(value => {
|
||||
store.commit("execution/setExecution", value);
|
||||
window.open(router.resolve({
|
||||
name: "executions/update",
|
||||
params: {
|
||||
namespace: data.link.namespace,
|
||||
flowId: data.link.id,
|
||||
tab: "topology",
|
||||
id: data.link.executionId,
|
||||
},
|
||||
}).href,'_blank');;
|
||||
})
|
||||
} else {
|
||||
window.open(router.resolve({
|
||||
name: "flows/update",
|
||||
params: {"namespace": data.link.namespace, "id": data.link.id, tab: "overview"},
|
||||
}).href,'_blank');
|
||||
}
|
||||
return position;
|
||||
};
|
||||
|
||||
const isTaskNode = (node) => {
|
||||
return node.task !== undefined && (node.type === "io.kestra.core.models.hierarchies.GraphTask" || node.type === "io.kestra.core.models.hierarchies.GraphClusterRoot")
|
||||
};
|
||||
|
||||
const isTriggerNode = (node) => {
|
||||
return node.trigger !== undefined && (node.type === "io.kestra.core.models.hierarchies.GraphTrigger");
|
||||
}
|
||||
|
||||
const getNodeWidth = (node) => {
|
||||
return isTaskNode(node) || isTriggerNode(node) ? 202 : 5;
|
||||
};
|
||||
|
||||
const getNodeHeight = (node) => {
|
||||
return isTaskNode(node) || isTriggerNode(node) ? 55 : (isHorizontal.value ? 55 : 5);
|
||||
};
|
||||
|
||||
const complexEdgeHaveAdd = (edge) => {
|
||||
// Check if edge is an ending flowable
|
||||
// If true, enable add button to add a task
|
||||
// under the flowable task
|
||||
const isEndtoEndEdge = edge.source.includes("_end") && edge.target.includes("_end")
|
||||
if (isEndtoEndEdge) {
|
||||
// Cluster uid contains the flowable task id
|
||||
// So we look for the cluster having this end edge
|
||||
// to return his flowable id
|
||||
return [getClusterTaskIdWithEndNodeUid(edge.source), "after"];
|
||||
}
|
||||
if (isLinkToFirstFlowableTask(edge)) {
|
||||
return [getFirstTaskId(), "before"];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const getClusterTaskIdWithEndNodeUid = (nodeUid) => {
|
||||
const cluster = props.flowGraph.clusters.find(cluster => cluster.end === nodeUid);
|
||||
if (cluster) {
|
||||
return Utils.splitFirst(cluster.cluster.uid, "cluster_");
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isLinkToFirstFlowableTask = (edge) => {
|
||||
const firstTaskId = getFirstTaskId();
|
||||
|
||||
return flowables().includes(firstTaskId) && edge.target === firstTaskId;
|
||||
}
|
||||
|
||||
const getFirstTaskId = () => {
|
||||
return YamlUtils.getFirstTask(props.source);
|
||||
}
|
||||
|
||||
const getNextTaskId = (target) => {
|
||||
while (YamlUtils.extractTask(props.source, target) === undefined) {
|
||||
const edge = props.flowGraph.edges.find(e => e.source === target)
|
||||
if (!edge) {
|
||||
return null
|
||||
}
|
||||
target = edge.target
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
const cleanGraph = () => {
|
||||
removeEdges(getEdges.value)
|
||||
removeNodes(getNodes.value)
|
||||
removeSelectedElements(getElements.value)
|
||||
elements.value = []
|
||||
}
|
||||
|
||||
const generateGraph = () => {
|
||||
cleanGraph();
|
||||
// VueFlowUtils.cleanGraph(vueflowId.value);
|
||||
//
|
||||
// nextTick(() => {
|
||||
// emit("loading", true);
|
||||
// fitView();
|
||||
// setElements(elements.value);
|
||||
// emit("loading", false);
|
||||
// })
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
emit("loading", true);
|
||||
try {
|
||||
if (!props.flowGraph || !flowHaveTasks()) {
|
||||
elements.value.push({
|
||||
id: "start",
|
||||
label: "",
|
||||
type: "dot",
|
||||
position: {x: 0, y: 0},
|
||||
style: {
|
||||
width: "5px",
|
||||
height: "5px"
|
||||
},
|
||||
sourcePosition: isHorizontal.value ? Position.Right : Position.Bottom,
|
||||
targetPosition: isHorizontal.value ? Position.Left : Position.Top,
|
||||
parentNode: undefined,
|
||||
draggable: false,
|
||||
})
|
||||
elements.value.push({
|
||||
id: "end",
|
||||
label: "",
|
||||
type: "dot",
|
||||
position: isHorizontal.value ? {x: 50, y: 0} : {x: 0, y: 50},
|
||||
style: {
|
||||
width: "5px",
|
||||
height: "5px"
|
||||
},
|
||||
sourcePosition: isHorizontal.value ? Position.Right : Position.Bottom,
|
||||
targetPosition: isHorizontal.value ? Position.Left : Position.Top,
|
||||
parentNode: undefined,
|
||||
draggable: false,
|
||||
})
|
||||
elements.value.push({
|
||||
id: "start|end",
|
||||
source: "start",
|
||||
target: "end",
|
||||
type: "edge",
|
||||
markerEnd: MarkerType.ArrowClosed,
|
||||
data: {
|
||||
edge: {
|
||||
relation: {
|
||||
relationType: "SEQUENTIAL"
|
||||
}
|
||||
},
|
||||
isFlowable: false,
|
||||
initTask: true,
|
||||
}
|
||||
})
|
||||
const showLogs = (event) => {
|
||||
selectedTask.value = event
|
||||
isShowLogsOpen.value = true;
|
||||
isDrawerOpen.value = true;
|
||||
}
|
||||
|
||||
emit("loading", false);
|
||||
return;
|
||||
}
|
||||
if (props.flowGraph === undefined) {
|
||||
emit("loading", false);
|
||||
return;
|
||||
}
|
||||
const dagreGraph = generateDagreGraph();
|
||||
const clusters = {};
|
||||
for (let cluster of (props.flowGraph.clusters || [])) {
|
||||
for (let nodeUid of cluster.nodes) {
|
||||
clusters[nodeUid] = cluster.cluster;
|
||||
}
|
||||
const onSearch = (search) => {
|
||||
logFilter.value = search;
|
||||
}
|
||||
|
||||
const dagreNode = dagreGraph.node(cluster.cluster.uid)
|
||||
const parentNode = cluster.parents ? cluster.parents[cluster.parents.length - 1] : undefined;
|
||||
const onLevelChange = (level) => {
|
||||
logLevel.value = level;
|
||||
}
|
||||
|
||||
const clusterUid = cluster.cluster.uid;
|
||||
elements.value.push({
|
||||
id: clusterUid,
|
||||
label: clusterUid,
|
||||
type: "cluster",
|
||||
parentNode: parentNode,
|
||||
position: getNodePosition(dagreNode, parentNode ? dagreGraph.node(parentNode) : undefined),
|
||||
style: {
|
||||
width: clusterUid === "Triggers" && isHorizontal.value ? "400px" : dagreNode.width + "px",
|
||||
height: clusterUid === "Triggers" && !isHorizontal.value ? "250px" : dagreNode.height + "px",
|
||||
},
|
||||
})
|
||||
}
|
||||
const showDescription = (event) => {
|
||||
selectedTask.value = event
|
||||
isShowDescriptionOpen.value = true;
|
||||
isDrawerOpen.value = true;
|
||||
}
|
||||
|
||||
let disabledLowCode = [];
|
||||
const emitEdit = (event) => {
|
||||
emit("on-edit", event)
|
||||
}
|
||||
|
||||
for (const node of props.flowGraph.nodes) {
|
||||
const dagreNode = dagreGraph.node(node.uid);
|
||||
let nodeType = "task";
|
||||
if (node.type.includes("GraphClusterEnd")) {
|
||||
nodeType = "dot";
|
||||
} else if (clusters[node.uid] === undefined && node.type.includes("GraphClusterRoot")) {
|
||||
nodeType = "dot";
|
||||
} else if (node.type.includes("GraphClusterRoot")) {
|
||||
nodeType = "dot";
|
||||
} else if (node.type.includes("GraphTrigger")) {
|
||||
nodeType = "trigger";
|
||||
}
|
||||
// Disable interaction for Dag task
|
||||
// because our low code editor can not handle it for now
|
||||
if (isTaskNode(node) && node.task.type === "io.kestra.core.tasks.flows.Dag") {
|
||||
disabledLowCode.push(node.task.id);
|
||||
YamlUtils.getChildrenTasks(props.source, node.task.id).forEach(child => {
|
||||
disabledLowCode.push(child);
|
||||
})
|
||||
}
|
||||
|
||||
elements.value.push({
|
||||
id: node.uid,
|
||||
label: isTaskNode(node) ? node.task.id : "",
|
||||
type: nodeType,
|
||||
position: getNodePosition(dagreNode, clusters[node.uid] ? dagreGraph.node(clusters[node.uid].uid) : undefined),
|
||||
style: {
|
||||
width: getNodeWidth(node) + "px",
|
||||
height: getNodeHeight(node) + "px"
|
||||
},
|
||||
sourcePosition: isHorizontal.value ? Position.Right : Position.Bottom,
|
||||
targetPosition: isHorizontal.value ? Position.Left : Position.Top,
|
||||
parentNode: clusters[node.uid] ? clusters[node.uid].uid : undefined,
|
||||
draggable: nodeType === "task" && !props.isReadOnly && isTaskNode(node) ? !disabledLowCode.includes(node.task.id) : false,
|
||||
data: {
|
||||
node: node,
|
||||
namespace: props.namespace,
|
||||
flowId: props.flowId,
|
||||
revision: props.execution ? props.execution.flowRevision : undefined,
|
||||
isFlowable: isTaskNode(node) ? flowables().includes(node.task.id) : false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for (const edge of props.flowGraph.edges) {
|
||||
elements.value.push({
|
||||
id: edge.source + "|" + edge.target,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
type: "edge",
|
||||
markerEnd: MarkerType.ArrowClosed,
|
||||
data: {
|
||||
edge: edge,
|
||||
haveAdd: complexEdgeHaveAdd(edge),
|
||||
isFlowable: flowables().includes(edge.source) || flowables().includes(edge.target),
|
||||
nextTaskId: getNextTaskId(edge.target),
|
||||
disabled: disabledLowCode.includes(edge.source)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error while creating topology graph: " + e);
|
||||
}
|
||||
finally {
|
||||
emit("loading", false);
|
||||
}
|
||||
})
|
||||
const message = (event) => {
|
||||
store.dispatch("core/showMessage", {
|
||||
variant: event.variant,
|
||||
title: t(event.title),
|
||||
message: t(event.message)
|
||||
});
|
||||
}
|
||||
|
||||
// Expose method to be triggered by parents
|
||||
@@ -555,71 +333,88 @@
|
||||
|
||||
<template>
|
||||
<div ref="vueFlow" class="vueflow">
|
||||
<slot name="top-bar" />
|
||||
<VueFlow
|
||||
v-model="elements"
|
||||
:default-marker-color="cssVariable('--bs-cyan')"
|
||||
:fit-view-on-init="true"
|
||||
:nodes-draggable="false"
|
||||
:nodes-connectable="false"
|
||||
:elevate-nodes-on-select="false"
|
||||
:elevate-edges-on-select="false"
|
||||
<slot name="top-bar"/>
|
||||
<Topology
|
||||
:id="vueflowId"
|
||||
:is-horizontal="isHorizontal"
|
||||
:is-read-only="isReadOnly"
|
||||
:is-allowed-edit="isAllowedEdit"
|
||||
:source="source"
|
||||
:toggle-orientation-button="['topology'].includes(viewType)"
|
||||
:flowGraph="props.flowGraph"
|
||||
:flow-id="flowId"
|
||||
:namespace="namespace"
|
||||
@toggle-orientation="toggleOrientation"
|
||||
@edit="onEditTask($event)"
|
||||
@delete="onDelete"
|
||||
@open-link="openFlow($event)"
|
||||
@show-logs="showLogs($event)"
|
||||
@show-description="showDescription($event)"
|
||||
@on-add-flowable-error="onAddFlowableError($event)"
|
||||
@add-task="onCreateNewTask($event)"
|
||||
@swapped-task="emitEdit($event)"
|
||||
@message="message($event)"
|
||||
/>
|
||||
|
||||
<!-- Drawer to create/add task -->
|
||||
<task-edit
|
||||
component="div"
|
||||
is-hidden
|
||||
:emit-task-only="true"
|
||||
class="node-action"
|
||||
:section="SECTIONS.TASKS"
|
||||
:task="taskObject"
|
||||
:flow-id="flowId"
|
||||
size="small"
|
||||
:namespace="namespace"
|
||||
:revision="execution ? execution.flowRevision : undefined"
|
||||
:emit-only="true"
|
||||
@update:task="confirmEdit($event)"
|
||||
@close="closeEdit()"
|
||||
ref="taskEdit"
|
||||
/>
|
||||
|
||||
<!-- Drawer to task informations (logs, description, ..) -->
|
||||
<!-- Assuming selectedTask is always the id and the required data for the opened drawer -->
|
||||
<el-drawer
|
||||
v-if="isDrawerOpen && selectedTask"
|
||||
v-model="isDrawerOpen"
|
||||
destroy-on-close
|
||||
size=""
|
||||
:append-to-body="true"
|
||||
>
|
||||
<template #node-cluster="props">
|
||||
<Cluster v-bind="props" />
|
||||
<template #header>
|
||||
<code>{{ selectedTask.id }}</code>
|
||||
</template>
|
||||
|
||||
<template #node-dot="props">
|
||||
<Dot v-bind="props" />
|
||||
</template>
|
||||
|
||||
<template #node-task="props">
|
||||
<Task
|
||||
v-bind="props"
|
||||
<div v-if="isShowLogsOpen">
|
||||
<collapse>
|
||||
<el-form-item>
|
||||
<search-field :router="false" @search="onSearch" class="me-2"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<log-level-selector :value="logLevel" @update:model-value="onLevelChange"/>
|
||||
</el-form-item>
|
||||
</collapse>
|
||||
<log-list
|
||||
v-for="taskRun in selectedTask.taskRuns"
|
||||
:key="taskRun.id"
|
||||
:execution="execution"
|
||||
:task-run-id="taskRun.id"
|
||||
:filter="logFilter"
|
||||
:exclude-metas="['namespace', 'flowId', 'taskId', 'executionId']"
|
||||
:level="logLevel"
|
||||
@follow="forwardEvent('follow', $event)"
|
||||
@edit="forwardEvent('on-edit', $event)"
|
||||
@delete="onDelete"
|
||||
@addFlowableError="onAddFlowableError"
|
||||
@mouseover="onMouseOver"
|
||||
@mouseleave="onMouseLeave"
|
||||
:is-read-only="isReadOnly"
|
||||
:is-allowed-edit="isAllowedEdit"
|
||||
:hide-others-on-select="true"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #node-trigger="props">
|
||||
<Trigger
|
||||
v-bind="props"
|
||||
@edit="forwardEvent('on-edit', $event)"
|
||||
@delete="onDelete"
|
||||
:is-read-only="isReadOnly"
|
||||
:is-allowed-edit="isAllowedEdit"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #edge-edge="props">
|
||||
<Edge
|
||||
v-bind="props"
|
||||
:yaml-source="source"
|
||||
:flowables-ids="flowables()"
|
||||
@edit="onCreateNewTask"
|
||||
:is-read-only="isReadOnly"
|
||||
:is-allowed-edit="isAllowedEdit"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<Controls :show-interactive="false">
|
||||
<ControlButton @click="toggleOrientation" v-if="['topology'].includes(viewType)">
|
||||
<SplitCellsVertical :size="48" v-if="!isHorizontal" />
|
||||
<SplitCellsHorizontal v-if="isHorizontal" />
|
||||
</ControlButton>
|
||||
</Controls>
|
||||
</VueFlow>
|
||||
</div>
|
||||
<div v-if="isShowDescriptionOpen">
|
||||
<markdown class="markdown-tooltip" :source="selectedTask.description"/>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.vueflow {
|
||||
height: 100%;
|
||||
|
||||
@@ -40,9 +40,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
},
|
||||
computed: {
|
||||
...mapState("plugin", ["icons"]),
|
||||
name() {
|
||||
|
||||
@@ -28,13 +28,20 @@
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<template #table>
|
||||
<el-alert type="info" v-if="!blueprints || blueprints.length === 0" :closable="false">
|
||||
<el-alert type="info" v-if="!blueprints || blueprints.length === 0" :closable="false">
|
||||
{{ $t('no result') }}
|
||||
</el-alert>
|
||||
<el-card class="blueprint-card" :class="{'embed': embed}" v-for="blueprint in blueprints"
|
||||
@click="goToDetail(blueprint.id)">
|
||||
<component class="blueprint-link" :is="embed ? 'div' : 'router-link'"
|
||||
:to="embed ? undefined : {name: 'blueprints/view', params: {blueprintId: blueprint.id}}">
|
||||
<el-card
|
||||
class="blueprint-card"
|
||||
:class="{'embed': embed}"
|
||||
v-for="blueprint in blueprints"
|
||||
@click="goToDetail(blueprint.id)"
|
||||
>
|
||||
<component
|
||||
class="blueprint-link"
|
||||
:is="embed ? 'div' : 'router-link'"
|
||||
:to="embed ? undefined : {name: 'blueprints/view', params: {blueprintId: blueprint.id}}"
|
||||
>
|
||||
<div class="left">
|
||||
<div>
|
||||
<div class="title">
|
||||
@@ -45,15 +52,23 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="tasks-container">
|
||||
<task-icon :cls="task" only-icon
|
||||
v-for="task in [...new Set(blueprint.includedTasks)]" />
|
||||
<task-icon
|
||||
:cls="task"
|
||||
only-icon
|
||||
v-for="task in [...new Set(blueprint.includedTasks)]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="side buttons ms-auto">
|
||||
<slot name="buttons" :blueprint="blueprint" />
|
||||
<el-tooltip v-if="embed" trigger="click" content="Copied" placement="left" :auto-close="2000">
|
||||
<el-button @click.prevent.stop="copy(blueprint.id)" :icon="icon.ContentCopy"
|
||||
size="large" text bg>
|
||||
<el-button
|
||||
@click.prevent.stop="copy(blueprint.id)"
|
||||
:icon="icon.ContentCopy"
|
||||
size="large"
|
||||
text
|
||||
bg
|
||||
>
|
||||
{{ $t('copy') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
@@ -127,7 +142,7 @@
|
||||
async blueprintToEditor(blueprintId) {
|
||||
localStorage.setItem(editorViewTypes.STORAGE_KEY, editorViewTypes.SOURCE_TOPOLOGY);
|
||||
localStorage.setItem("autoRestore-creation_draft", (await this.$http.get(`${this.blueprintBaseUri}/${blueprintId}/flow`)).data);
|
||||
this.$router.push({name: 'flows/create'});
|
||||
this.$router.push({name: "flows/create"});
|
||||
},
|
||||
tagsToString(blueprintTags) {
|
||||
return blueprintTags?.map(id => this.tags?.[id]?.name).join(" ")
|
||||
@@ -265,7 +280,7 @@
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@use 'element-plus/theme-chalk/src/mixins/mixins' as *;
|
||||
@import "../../../../styles/variable";
|
||||
@import "@kestra-io/ui-libs/src/scss/variables.scss";
|
||||
|
||||
.sub-nav {
|
||||
margin: 0 0 $spacer;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../../../styles/variable";
|
||||
@import "@kestra-io/ui-libs/src/scss/variables.scss";
|
||||
|
||||
.header {
|
||||
$neg-offset-from-menu: calc(-1 * var(--offset-from-menu));
|
||||
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
});
|
||||
},
|
||||
loadInputsType({commit}) {
|
||||
return this.$http.get(`/api/v1/plugins/inputs`, {}).then(response => {
|
||||
return this.$http.get("/api/v1/plugins/inputs", {}).then(response => {
|
||||
commit("setInputsType", response.data)
|
||||
|
||||
return response.data;
|
||||
@@ -101,6 +101,7 @@ export default {
|
||||
getters: {
|
||||
getPluginSingleList: state => state.pluginSingleList,
|
||||
getPluginsDocumentation: state => state.pluginsDocumentation,
|
||||
getIcons: state => state.icons
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// gray
|
||||
$gray-900: #CAC5DA;
|
||||
$gray-800: #A69FC1;
|
||||
$gray-700: #918BA9;
|
||||
$gray-600: #404559;
|
||||
$gray-500: #2F3342;
|
||||
$gray-400: #2C303F;
|
||||
$gray-300: #202435;
|
||||
$gray-200: #21242E;
|
||||
$gray-100: #1C1E27;
|
||||
|
||||
$light: $gray-200;
|
||||
$dark: $gray-100;
|
||||
|
||||
// body
|
||||
$body-color: $gray-900;
|
||||
$border-color: $gray-600;
|
||||
$body-bg: $gray-200;
|
||||
$card-bg: $gray-500;
|
||||
$input-bg: $gray-100;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
$gray-900: #d6e2f3;
|
||||
$gray-800: #d0d7e0;
|
||||
$gray-700: #c1c1d8;
|
||||
$gray-600: #7c89ad;
|
||||
$gray-500: #7989b4;
|
||||
$gray-400: #5f72b1;
|
||||
$gray-300: #283456;
|
||||
$gray-200: #292e40;
|
||||
$gray-100: #202331;
|
||||
|
||||
$border-color: $gray-200;
|
||||
$body-bg: #1b1e2a;
|
||||
$card-bg: #222635;
|
||||
*/
|
||||
@@ -1,100 +0,0 @@
|
||||
// color system
|
||||
$blue: #1761FD !default;
|
||||
$indigo: #8405FF !default;
|
||||
$purple: #9F9DFF !default;
|
||||
$pink: #FD3C97 !default;
|
||||
$red: #E36065 !default;
|
||||
$red-light: #FF9D9D !default;
|
||||
$orange: #FCB37C !default;
|
||||
$yellow: #FCE07C !default;
|
||||
$green: #03DABA !default;
|
||||
$teal: #03D87F !default;
|
||||
$cyan: #60C5FE !default;
|
||||
|
||||
// primary color
|
||||
$primary: #8405FF !default;
|
||||
$secondary: #C182FF !default;
|
||||
$tertiary: #2F3342 !default;
|
||||
|
||||
// gray
|
||||
$white: #FFF !default;
|
||||
$gray-100: #F5F5FF !default;
|
||||
$gray-200: #f1f5fa !default;
|
||||
$gray-300: #E5E4F7 !default;
|
||||
$gray-400: #b6c2e4 !default;
|
||||
$gray-500: #8997bd !default;
|
||||
$gray-600: #7081b9 !default;
|
||||
$gray-700: #303e67 !default;
|
||||
$gray-800: #2c3652 !default;
|
||||
$gray-900: #1d2c48 !default;
|
||||
$black: #26282D !default;
|
||||
|
||||
$light: $gray-200 !default;
|
||||
$dark: $gray-900 !default;
|
||||
|
||||
// fonts
|
||||
$font-size-base: 1rem !default;
|
||||
$font-family-sans-serif: "Public Sans", sans-serif;
|
||||
$font-family-monospace: "Source Code Pro", monospace;
|
||||
$font-size-xs: $font-size-base * 0.75 !default;
|
||||
|
||||
// border radius
|
||||
$border-radius: 0.25rem !default;
|
||||
$border-radius-lg: 0.5rem !default;
|
||||
$border-radius-sm: 0.15rem !default;
|
||||
|
||||
// layout
|
||||
$menu-width: 268px !default;
|
||||
$spacer: 1rem !default;
|
||||
|
||||
// body
|
||||
$body-color: $gray-800 !default;
|
||||
$border-color: $gray-300 !default;
|
||||
$body-bg: $gray-100 !default;
|
||||
$card-bg: $white !default;
|
||||
$input-bg: $white !default;
|
||||
|
||||
$link-color: $primary !default;
|
||||
|
||||
// border radius
|
||||
$border-radius: .25rem !default;
|
||||
$border-radius-sm: .15rem !default;
|
||||
|
||||
// shadow
|
||||
$box-shadow-sm: 0 .125rem .25rem rgba($black, .075);
|
||||
$box-shadow: 0 .5rem 1rem rgba($black, .15);
|
||||
$box-shadow-lg: 0 1rem 3rem rgba($black, .175);
|
||||
|
||||
// boostrap flags
|
||||
$enable-reduced-motion: false;
|
||||
|
||||
// element-plus
|
||||
$types: primary, success, warning, danger, error, info !default;
|
||||
$element-colors: (
|
||||
'white': $white,
|
||||
'black': $black,
|
||||
'primary': (
|
||||
'base': $primary,
|
||||
),
|
||||
'success': (
|
||||
'base': $green,
|
||||
),
|
||||
'warning': (
|
||||
'base': $orange,
|
||||
),
|
||||
'danger': (
|
||||
'base': $red,
|
||||
),
|
||||
'error': (
|
||||
'base': $red,
|
||||
),
|
||||
'info': (
|
||||
'base': $cyan,
|
||||
),
|
||||
);
|
||||
|
||||
// bootstrap
|
||||
@import "bootstrap/scss/functions";
|
||||
@import "bootstrap/scss/mixins";
|
||||
@import "bootstrap/scss/vendor/rfs";
|
||||
@import 'bootstrap/scss/variables';
|
||||
@@ -1,6 +1,8 @@
|
||||
@use "variable" as global-var;
|
||||
@use "@kestra-io/ui-libs/src/scss/variables.scss" as global-var;
|
||||
@use 'element-plus/theme-chalk/src/mixins/mixins' as mixin;
|
||||
|
||||
@use "@kestra-io/ui-libs/src/scss/app.scss";
|
||||
|
||||
// element-plus
|
||||
@use "layout/element-plus-overload";
|
||||
|
||||
|
||||
@@ -7,40 +7,15 @@ $indigo: "" !default;
|
||||
|
||||
}
|
||||
|
||||
.vue-flow__edge-path, .vue-flow__connection-path {
|
||||
stroke: $indigo;
|
||||
|
||||
.vue-flow__edge.selected & {
|
||||
stroke: var(--bs-yellow);
|
||||
}
|
||||
}
|
||||
|
||||
.vue-flow__handle {
|
||||
border: 0;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
|
||||
.vue-flow__edge-textbg {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.vue-flow__edge-text {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.vue-flow__node {
|
||||
border: 1px solid var(--bs-border-color);
|
||||
|
||||
&.selected {
|
||||
border: 1px solid var(--bs-yellow);
|
||||
}
|
||||
.vue-flow__edge-labels {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.vue-flow__controls-button {
|
||||
border: 1px solid var(--bs-border-color);
|
||||
background-color: var(--bs-body-bg);
|
||||
color: var(--bs-body-color);
|
||||
box-sizing: content-box !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bs-body-bg);
|
||||
|
||||
36
ui/src/styles/layout/bootstrap.scss
vendored
36
ui/src/styles/layout/bootstrap.scss
vendored
@@ -1,36 +0,0 @@
|
||||
@import "../variable";
|
||||
|
||||
$include-column-box-sizing: true !default;
|
||||
|
||||
@import "bootstrap/scss/functions";
|
||||
@import "bootstrap/scss/variables";
|
||||
@import "bootstrap/scss/variables-dark";
|
||||
@import "bootstrap/scss/maps";
|
||||
@import "bootstrap/scss/mixins";
|
||||
|
||||
@import "bootstrap/scss/mixins/lists";
|
||||
@import "bootstrap/scss/mixins/breakpoints";
|
||||
@import "bootstrap/scss/mixins/container";
|
||||
@import "bootstrap/scss/mixins/grid";
|
||||
@import "bootstrap/scss/mixins/utilities";
|
||||
|
||||
@import "bootstrap/scss/vendor/rfs";
|
||||
|
||||
@import "bootstrap/scss/root";
|
||||
@import "bootstrap/scss/reboot";
|
||||
|
||||
@import "bootstrap/scss/containers";
|
||||
@import "bootstrap/scss/grid";
|
||||
|
||||
@import "bootstrap/scss/utilities";
|
||||
@import "bootstrap/scss/utilities/api";
|
||||
|
||||
@import "bootstrap/scss/progress";
|
||||
@import "bootstrap/scss/type";
|
||||
|
||||
@import "bootstrap/scss/helpers/text-truncation";
|
||||
|
||||
// overload
|
||||
.font-monospace {
|
||||
font-size: $font-size-base * 0.7;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
@use 'sass:math';
|
||||
@use "sass:map";
|
||||
@use 'element-plus/theme-chalk/src/mixins/mixins' as *;
|
||||
@import "../variable";
|
||||
@import "@kestra-io/ui-libs/src/scss/variables.scss";
|
||||
|
||||
// button
|
||||
.el-button {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@use 'sass:map';
|
||||
@import "../theme-dark";
|
||||
@import "../variable";
|
||||
|
||||
@import "@kestra-io/ui-libs/src/scss/theme-dark.scss";
|
||||
@import "@kestra-io/ui-libs/src/scss/variables.scss";
|
||||
|
||||
// Bootstrap
|
||||
@import "bootstrap/scss/functions";
|
||||
@@ -8,6 +9,8 @@
|
||||
@import "bootstrap/scss/vendor/rfs";
|
||||
@import "bootstrap/scss/variables";
|
||||
|
||||
|
||||
|
||||
html.dark {
|
||||
#{--bs-gray}: #{map.get($grays, "600")};
|
||||
@each $key, $value in $grays {
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
@use 'sass:math';
|
||||
@use 'element-plus/theme-chalk/src/mixins/var' as *;
|
||||
|
||||
|
||||
@import "../variable";
|
||||
@import "@kestra-io/ui-libs/src/scss/variables.scss";
|
||||
|
||||
// use bootstrap for main style
|
||||
:root {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "../variable";
|
||||
@import "@kestra-io/ui-libs/src/scss/variables.scss";
|
||||
|
||||
#app {
|
||||
.v-step {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
@use "variable" as global-var;
|
||||
|
||||
// bootstrap
|
||||
@use "layout/bootstrap";
|
||||
|
||||
@use "@kestra-io/ui-libs/src/scss/variables.scss" as global-var;
|
||||
@use "@kestra-io/ui-libs/src/scss/vendor.scss";
|
||||
|
||||
// element-plus
|
||||
@forward 'element-plus/theme-chalk/src/common/var.scss' as light-* with (
|
||||
@@ -45,10 +42,6 @@
|
||||
|
||||
// third
|
||||
@use "nprogress/nprogress.css";
|
||||
@use "vue-material-design-icons/styles.css";
|
||||
@use "@vue-flow/core/dist/style.css" as core;
|
||||
@use "@vue-flow/core/dist/theme-default.css" as theme;
|
||||
@use "@vue-flow/controls/dist/style.css" as controls;
|
||||
|
||||
//noinspection CssUnknownTarget
|
||||
@import url("https://fonts.googleapis.com/css2?family=Public+Sans:wght@300;400;700;800&family=Source+Code+Pro:wght@400;700;800&display=swap");
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"close": "Close",
|
||||
"namespace": "Namespace",
|
||||
"description": "Description",
|
||||
"show description": "Show description",
|
||||
"revision": "Revision",
|
||||
"Language": "Language",
|
||||
"Set default page": "Set default page",
|
||||
@@ -412,6 +413,7 @@
|
||||
"focus task": "Focus on any element to see its documentation.",
|
||||
"validate": "Validate",
|
||||
"add global error handler": "Add global error handler",
|
||||
"add error handler": "Add an error handler",
|
||||
"add trigger": "Add trigger",
|
||||
"edit metadata": "Edit Metadata",
|
||||
"taskDefaults": "Task Defaults",
|
||||
@@ -425,7 +427,7 @@
|
||||
"sequential": "Sequential",
|
||||
"can not delete": "Can not delete",
|
||||
"can not have less than 1 task": "Flows can not have less than 1 task.",
|
||||
"task id already exist": "Task Id already exists",
|
||||
"task id already exists": "Task Id already exists",
|
||||
"Task Id already exist in the flow": "Task Id {taskId} already exists in the flow.",
|
||||
"flow already exists": "Flow already exists",
|
||||
"namespace not allowed": "Namespace not allowed",
|
||||
@@ -459,6 +461,8 @@
|
||||
"expand error": "Expand only failed task",
|
||||
"expand all": "Expand all",
|
||||
"collapse all": "Collapse all",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"log expand setting": "Log default display",
|
||||
"environment name setting": "Environment name",
|
||||
"environment color setting": "Environment color",
|
||||
@@ -481,7 +485,8 @@
|
||||
"success": "Trigger is unlocked"
|
||||
},
|
||||
"date format": "Date format",
|
||||
"timezone": "Timezone"
|
||||
"timezone": "Timezone",
|
||||
"add task": "Add a task"
|
||||
},
|
||||
"fr": {
|
||||
"id": "Identifiant",
|
||||
@@ -489,6 +494,7 @@
|
||||
"ok": "OK",
|
||||
"close": "Fermer",
|
||||
"description": "Description",
|
||||
"show description": "Afficher la description",
|
||||
"namespace": "Espace de nom",
|
||||
"revision": "Révision",
|
||||
"Language": "Langue",
|
||||
@@ -898,6 +904,7 @@
|
||||
"focus task": "Cliquer sur une tâche pour voir sa documentation",
|
||||
"validate": "Valider",
|
||||
"add global error handler": "Gérer les erreurs globales",
|
||||
"add error handler": "Gérer les erreurs",
|
||||
"add trigger": "Ajouter un déclencheur",
|
||||
"edit metadata": "Éditer les métadonnées",
|
||||
"taskDefaults": "Valeur de tâches par défaut",
|
||||
@@ -911,7 +918,7 @@
|
||||
"sequential": "Séquentiel",
|
||||
"can not delete": "Suppression impossible",
|
||||
"can not have less than 1 task": "Un flow ne peut avoir moins d'1 tâche.",
|
||||
"task id already exist": "Identifiant de tâche déjà utilisé",
|
||||
"task id already exists": "Identifiant de tâche déjà utilisé",
|
||||
"Task Id already exist in the flow": "L'identifiant {taskId} est déjà utilisé dans le flow.",
|
||||
"flow already exists": "Flow déjà existant",
|
||||
"namespace not allowed": "Espace de nom non autorisé",
|
||||
@@ -945,6 +952,8 @@
|
||||
"expand error": "Afficher uniquement les erreurs",
|
||||
"expand all": "Afficher tout",
|
||||
"collapse all": "Masquer tout",
|
||||
"expand": "Afficher",
|
||||
"collapse": "Masquer",
|
||||
"log expand setting": "Affichage par défaut des journaux",
|
||||
"slack support": "Demandez de l'aide sur notre Slack",
|
||||
"error detected": "Erreur détectée",
|
||||
@@ -965,6 +974,8 @@
|
||||
"success": "Le déclencheur est débloqué"
|
||||
},
|
||||
"date format": "Format de date",
|
||||
"timezone": "Fuseau horaire"
|
||||
"timezone": "Fuseau horaire",
|
||||
"add task": "Ajouter une tâche"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -207,10 +207,10 @@ export default class YamlUtils {
|
||||
|
||||
yaml.visit(yamlDoc, {
|
||||
Pair(_, pair) {
|
||||
if (pair.key.value === 'dependsOn' && pair.value.items.map(e => e.value).includes(taskId2)) {
|
||||
if (pair.key.value === "dependsOn" && pair.value.items.map(e => e.value).includes(taskId2)) {
|
||||
throw {
|
||||
message: 'dependency task',
|
||||
messageOptions: { taskId: taskId2 }
|
||||
message: "dependency task",
|
||||
messageOptions: {taskId: taskId2}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -428,6 +428,66 @@ export default class YamlUtils {
|
||||
return children;
|
||||
}
|
||||
|
||||
static getParentTask(source, taskId) {
|
||||
const yamlDoc = yaml.parseDocument(source);
|
||||
let parentTask = null;
|
||||
yaml.visit(yamlDoc, {
|
||||
Map(_, map) {
|
||||
if (map.get("id") !== taskId) {
|
||||
yaml.visit(map, {
|
||||
Map(_, childMap) {
|
||||
if (childMap.get("id") === taskId) {
|
||||
parentTask = map.get("id");
|
||||
return yaml.visit.BREAK;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
return parentTask;
|
||||
}
|
||||
|
||||
static isTaskError(source, taskId) {
|
||||
const yamlDoc = yaml.parseDocument(source);
|
||||
let isTaskError = false;
|
||||
yaml.visit(yamlDoc, {
|
||||
Pair(_, pair) {
|
||||
if (pair.key.value === "errors") {
|
||||
yaml.visit(pair, {
|
||||
Map(_, map) {
|
||||
if (map.get("id") === taskId) {
|
||||
isTaskError = true;
|
||||
return yaml.visit.BREAK;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
return isTaskError;
|
||||
}
|
||||
|
||||
static isTrigger(source, taskId) {
|
||||
const yamlDoc = yaml.parseDocument(source);
|
||||
let isTrigger = false;
|
||||
yaml.visit(yamlDoc, {
|
||||
Pair(_, pair) {
|
||||
if (pair.key.value === "triggers") {
|
||||
yaml.visit(pair, {
|
||||
Map(_, map) {
|
||||
if (map.get("id") === taskId) {
|
||||
isTrigger = true;
|
||||
return yaml.visit.BREAK;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
return isTrigger;
|
||||
}
|
||||
|
||||
static replaceIdAndNamespace(source, id, namespace) {
|
||||
return source.replace(/^(id\s*:\s*(["']?))\S*/m, "$1"+id+"$2").replace(/^(namespace\s*:\s*(["']?))\S*/m, "$1"+namespace+"$2")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import path from "path";
|
||||
import {defineConfig} from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import pluginRewriteAll from 'vite-plugin-rewrite-all';
|
||||
import {visualizer} from "rollup-plugin-visualizer";
|
||||
|
||||
export default defineConfig({
|
||||
base: "",
|
||||
@@ -15,9 +16,15 @@ export default defineConfig({
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
pluginRewriteAll()
|
||||
pluginRewriteAll(),
|
||||
visualizer()
|
||||
],
|
||||
css: {
|
||||
devSourcemap: true
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
'* > @kestra-io/ui-libs'
|
||||
]
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user