@kestra-io/ui-libs 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  Kestra UI library implemented in both the website and the application UI.
12
12
 
13
- Install with npm: `npm install @kestra-io/ui-library`
13
+ Install with npm: `npm install @kestra/ui-library`
14
14
 
15
15
  <p align="center">
16
16
  <a href="https://kestra.io/"><b>Website</b></a> •
package/package.json CHANGED
@@ -1,53 +1,39 @@
1
1
  {
2
2
  "name": "@kestra-io/ui-libs",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "files": [
6
- "dist",
6
+ "src",
7
7
  "package.json"
8
8
  ],
9
- "main": "./dist/kestra-ui.umd.cjs",
10
- "module": "./dist/kestra-ui.js",
11
- "exports": {
12
- ".": {
13
- "import": "./dist/kestra-ui.js",
14
- "require": "./dist/kestra-ui.umd.cjs"
15
- },
16
- "./dist/*.css": {
17
- "import": "./dist/*.css",
18
- "require": "./dist/*.css"
19
- },
20
- "./dist/*.scss": {
21
- "import": "./dist/*.css",
22
- "require": "./dist/*.css"
23
- }
24
- },
9
+ "main": "./src/index.js",
10
+ "module": "./src/index.js",
25
11
  "scripts": {
26
- "prepublishOnly": "vite build",
27
12
  "dev": "vite",
28
- "build": "vite build",
29
13
  "debug": "vite --debug",
30
14
  "preview": "vite preview",
15
+ "build": "vite build",
31
16
  "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path ./.gitignore"
32
17
  },
33
18
  "peerDependencies": {
34
- "@popperjs/core": "^2.11.8",
35
- "@vitejs/plugin-vue": "^4.2.3",
36
- "@vue-flow/core": "^1.22.2",
19
+ "@vue-flow/background": "^1.2.0",
20
+ "@vue-flow/controls": "1.0.6",
21
+ "@vue-flow/core": "1.14.3",
37
22
  "bootstrap": "^5.3.0",
38
23
  "buffer": "^6.0.3",
39
24
  "humanize-duration": "^3.29.0",
40
25
  "lodash": "^4.17.21",
41
26
  "moment": "^2.29.4",
42
- "sass": "^1.64.0",
43
- "vite": "^4.4.9",
44
27
  "vue": "^3.3.4",
45
28
  "vue-material-design-icons": "^5.2.0"
46
29
  },
47
30
  "devDependencies": {
31
+ "@vitejs/plugin-vue": "^4.2.3",
48
32
  "eslint": "^8.46.0",
49
33
  "eslint-plugin-vue": "^9.17.0",
50
34
  "npm": "^9.8.1",
35
+ "sass": "^1.64.0",
36
+ "vite": "^4.4.9",
51
37
  "vite-plugin-static-copy": "^0.17.0"
52
38
  }
53
39
  }
@@ -0,0 +1,33 @@
1
+ <script>
2
+ import Plus from "vue-material-design-icons/Plus.vue";
3
+
4
+ export default {
5
+ name: "AddTaskButton",
6
+ components: {Plus},
7
+ props: {
8
+ addTask: {
9
+ type: Boolean,
10
+ default: false
11
+ },
12
+ }
13
+ }
14
+ </script>
15
+
16
+ <template>
17
+ <div class="add-task-div rounded d-flex justify-content-center align-items-center">
18
+ <Plus v-if="addTask" alt="add task icon" />
19
+ </div>
20
+ </template>
21
+
22
+ <style scoped lang="scss">
23
+ .add-task-div {
24
+ margin: 0.2rem;
25
+ width: 25px;
26
+ height: 25px;
27
+ border: 0.4px solid var(--bs-border-color);
28
+ background-color: var(--bs-white);
29
+ html.dark & {
30
+ background-color: var(--card-bg);
31
+ }
32
+ }
33
+ </style>
@@ -0,0 +1,21 @@
1
+ // nodes
2
+ import ClusterNode from "./nodes/ClusterNode.vue";
3
+ import DotNode from "./nodes/DotNode.vue";
4
+ import EdgeNode from "./nodes/EdgeNode.vue";
5
+ import TaskNode from "./nodes/TaskNode.vue";
6
+ import TriggerNode from "./nodes/TriggerNode.vue"
7
+ import BasicNode from "./nodes/BasicNode.vue";
8
+ import CollapsedClusterNode from "./nodes/CollapsedClusterNode.vue";
9
+ import DependenciesNode from "./nodes/DependenciesNode.vue"
10
+
11
+ // misc
12
+ import ExecutionInformations from "./misc/ExecutionInformations.vue";
13
+ import State from "./misc/State.vue";
14
+ import TaskIcon from "./misc/TaskIcon.vue";
15
+
16
+ // buttons
17
+ import AddTaskButton from "./buttons/AddTaskButton.vue";
18
+
19
+ export {ClusterNode, DotNode, EdgeNode, TaskNode, TriggerNode, BasicNode, CollapsedClusterNode, DependenciesNode};
20
+ export {ExecutionInformations, State, TaskIcon};
21
+ export {AddTaskButton};
@@ -0,0 +1,110 @@
1
+ <template>
2
+ <span>
3
+ <el-tooltip v-if="histories" popper-class="duration-tt" :persistent="false" transition="" :hide-after="0">
4
+ <template #content>
5
+ <span v-for="(history, index) in histories" :key="'tt-' + index">
6
+ <span class="square" :class="squareClass(history.state)" />
7
+ <strong>{{ history.state }}:</strong> {{ $filters.date(history.date, 'iso') }} <br>
8
+ </span>
9
+ </template>
10
+
11
+ <span>{{ duration }}</span>
12
+ </el-tooltip>
13
+ </span>
14
+ </template>
15
+
16
+ <script>
17
+ import State from "../../utils/state";
18
+ import Utils from "../../utils/Utils";
19
+
20
+ const ts = date => new Date(date).getTime();
21
+
22
+ export default {
23
+ props: {
24
+ histories: {
25
+ type: Array,
26
+ default: undefined
27
+ }
28
+ },
29
+ watch: {
30
+ histories(newValue, oldValue) {
31
+ if (oldValue[0].date !== newValue[0].date) {
32
+ this.paint()
33
+ }
34
+ },
35
+ },
36
+ data () {
37
+ return {
38
+ duration: "",
39
+ refreshHandler: undefined
40
+ }
41
+ },
42
+ mounted() {
43
+ this.paint()
44
+ },
45
+ computed: {
46
+ start() {
47
+ return this.histories && this.histories.length && ts(this.histories[0].date);
48
+ },
49
+
50
+ lastStep() {
51
+ return this.histories[this.histories.length - 1]
52
+ }
53
+ },
54
+ methods: {
55
+ paint() {
56
+ if (!this.refreshHandler) {
57
+ this.refreshHandler = setInterval(() => {
58
+ this.computeDuration()
59
+ if (this.histories && !State.isRunning(this.lastStep.state)) {
60
+ this.cancel();
61
+ }
62
+ }, 100);
63
+ }
64
+ },
65
+ cancel() {
66
+ if (this.refreshHandler) {
67
+ clearInterval(this.refreshHandler);
68
+ this.refreshHandler = undefined
69
+ }
70
+ },
71
+ delta() {
72
+ return this.stop() - this.start;
73
+ },
74
+ stop() {
75
+ if (!this.histories || State.isRunning(this.lastStep.state)) {
76
+ return +new Date();
77
+ }
78
+ return ts(this.lastStep.date)
79
+ },
80
+ computeDuration() {
81
+ this.duration = Utils.humanDuration(this.delta() / 1000)
82
+ },
83
+ squareClass(state) {
84
+ return [
85
+ "bg-" + State.colorClass()[state]
86
+ ]
87
+ }
88
+ },
89
+ beforeUnmount() {
90
+ this.cancel();
91
+ }
92
+ }
93
+ </script>
94
+
95
+ <style lang="scss">
96
+ .duration-tt {
97
+ .tooltip-inner {
98
+ text-align: left;
99
+ white-space: nowrap;
100
+ max-width: none;
101
+ }
102
+
103
+ .square {
104
+ display: inline-block;
105
+ width: 10px;
106
+ height: 10px;
107
+ margin-right: 5px;
108
+ }
109
+ }
110
+ </style>
@@ -0,0 +1,119 @@
1
+ <template>
2
+ <div class="btn-group content rounded-1 content-children">
3
+ <span v-if="taskRuns.length > 0">{{ taskRuns.length }} task runs</span>
4
+ <span><duration :histories="histories" /></span>
5
+ </div>
6
+ </template>
7
+ <script>
8
+ import Delete from "vue-material-design-icons/Delete.vue";
9
+ import Pencil from "vue-material-design-icons/Pencil.vue";
10
+ import {EVENTS} from "../../utils/constants"
11
+ import moment from "moment";
12
+ import Duration from "./Duration.vue";
13
+ import State from "../../utils/state.js";
14
+
15
+ export default {
16
+ name: "ExecutionInformations",
17
+ computed: {
18
+ EVENTS() {
19
+ return EVENTS
20
+ },
21
+ taskRunList() {
22
+ return this.execution && this.execution.taskRunList ? this.execution.taskRunList : []
23
+ },
24
+ taskRuns() {
25
+ return this.taskRunList.filter(t => t.taskId === this.task.id)
26
+ },
27
+ state() {
28
+ if (!this.taskRuns) {
29
+ return null;
30
+ }
31
+
32
+ if (this.taskRuns.length === 1) {
33
+ return this.taskRuns[0].state.current
34
+ }
35
+
36
+ const allStates = this.taskRuns.map(t => t.state.current);
37
+
38
+ const SORT_STATUS = [
39
+ State.FAILED,
40
+ State.KILLED,
41
+ State.WARNING,
42
+ State.KILLING,
43
+ State.RUNNING,
44
+ State.SUCCESS,
45
+ State.RESTARTED,
46
+ State.CREATED,
47
+ ];
48
+
49
+ // sorting based on SORT_STATUS array
50
+ const result = allStates
51
+ .map((item) => {
52
+ const n = SORT_STATUS.indexOf(item[1]);
53
+ SORT_STATUS[n] = undefined;
54
+ return [n, item]
55
+ })
56
+ .sort()
57
+ .map((j) => j[1])
58
+
59
+ return result[0];
60
+ },
61
+ histories() {
62
+ if (!this.taskRuns) {
63
+ return undefined;
64
+ }
65
+
66
+ const max = Math.max(...this.taskRuns
67
+ .filter(value => value.state.histories && value.state.histories.length > 0)
68
+ .map(value => new Date(value.state.histories[value.state.histories.length - 1].date).getTime()));
69
+
70
+ const duration = Math.max(...this.taskRuns
71
+ .map((taskRun) => moment.duration(taskRun.state.duration).asMilliseconds() / 1000, 0));
72
+
73
+ return [
74
+ {date: moment(max).subtract(duration, "second"), state: "CREATED"},
75
+ {date: moment(max), state: this.state}
76
+ ]
77
+ },
78
+ },
79
+ components: {Duration},
80
+ props: {
81
+ color: {
82
+ type: String,
83
+ default: "primary"
84
+ },
85
+ execution: {
86
+ type: Object,
87
+ default: null
88
+ }
89
+ }
90
+
91
+ }
92
+ </script>
93
+ <style scoped>
94
+ .content {
95
+ display: flex;
96
+
97
+ *:not(:first-child)::before {
98
+ content: "";
99
+ position: absolute;
100
+ left: 0;
101
+ top: 50%;
102
+ height: 50%;
103
+ border-left: 1px solid var(--bs-gray-100);
104
+ transform: translateY(-50%);
105
+ z-index: 500;
106
+ }
107
+ }
108
+
109
+ .content-children {
110
+ flex-grow: 1;
111
+ display: flex;
112
+ height: 1.25rem;
113
+ gap: 0.5rem;
114
+ align-self: stretch;
115
+ pointer-events: none;
116
+ cursor: default;
117
+ font-size: 0.75rem;
118
+ }
119
+ </style>
@@ -0,0 +1,57 @@
1
+ <script>
2
+ import AlertOctagonOutline from "vue-material-design-icons/AlertOctagonOutline.vue";
3
+ import Check from "vue-material-design-icons/Check.vue";
4
+
5
+ export default {
6
+ name: "State",
7
+ props: {
8
+ color: {
9
+ type: String,
10
+ default: "primary"
11
+ }
12
+ },
13
+ computed: {
14
+ stateIcon() {
15
+ switch (this.color) {
16
+ case "primary":
17
+ return AlertOctagonOutline
18
+ case "danger":
19
+ return AlertOctagonOutline
20
+ case "success":
21
+ return Check
22
+ case "warning":
23
+ return AlertOctagonOutline
24
+ default:
25
+ return AlertOctagonOutline
26
+ }
27
+ },
28
+ stateText() {
29
+ switch (this.color) {
30
+ case "primary":
31
+ return "Running"
32
+ case "danger":
33
+ return "Failed"
34
+ case "success":
35
+ return "Success"
36
+ case "warning":
37
+ return "Warning"
38
+ default:
39
+ return "Error"
40
+ }
41
+ },
42
+ },
43
+ }
44
+ </script>
45
+
46
+ <template>
47
+ <div :class="[`text-${color}`]">
48
+ <component class="icon" :is="stateIcon" fill-color="#ff0000" />
49
+ <span>{{ stateText }}</span>
50
+ </div>
51
+ </template>
52
+
53
+ <style scoped>
54
+ .icon {
55
+ margin-right: 0.3rem;
56
+ }
57
+ </style>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <div
3
+ :class="[color ? `bg-${color}`: '']"
4
+ class="div-icon rounded d-flex justify-content-center"
5
+ >
6
+ <img :src="backgroundImage" alt="task icon">
7
+ </div>
8
+ </template>
9
+ <script>
10
+ import {Buffer} from "buffer";
11
+
12
+ export default {
13
+ name: "TaskIcon",
14
+ props: {
15
+ icon: {
16
+ type: Object,
17
+ default: undefined
18
+ },
19
+ color: {
20
+ type: String,
21
+ default: undefined
22
+ }
23
+ },
24
+ computed: {
25
+ backgroundImage() {
26
+ return `data:image/svg+xml;base64,${this.imageBase64}`
27
+ },
28
+ imageBase64() {
29
+ const icon = this.icon ? this.icon.icon : undefined;
30
+ return icon ? icon : Buffer.from("<svg xmlns=\"http://www.w3.org/2000/svg\" " +
31
+ "xmlns:xlink=\"http://www.w3.org/1999/xlink\" aria-hidden=\"true\" " +
32
+ "focusable=\"false\" width=\"0.75em\" height=\"1em\" style=\"-ms-transform: " +
33
+ "rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);\" " +
34
+ "preserveAspectRatio=\"xMidYMid meet\" viewBox=\"0 0 384 512\">" +
35
+ "<path d=\"M288 32H0v448h384V128l-96-96zm64 416H32V64h224l96 96v288z\" fill=\"#0D1523FF\"/>" +
36
+ "</svg>", "utf8").toString("base64");
37
+ },
38
+ }
39
+ }
40
+ </script>
41
+ <style scoped>
42
+ .div-icon {
43
+ padding: 0.3rem;
44
+ margin: 0.2rem;
45
+ width: 25px;
46
+ height: 25px;
47
+ background-color: var(--bs-white);
48
+ border: 0.4px solid var(--bs-border-color);
49
+ }
50
+ </style>
@@ -0,0 +1,224 @@
1
+ <template>
2
+ <div
3
+ :class="[`border-${!expandable ? data.color : 'blue'}`]"
4
+ class="node-wrapper rounded-3 border"
5
+ @mouseover="mouseover"
6
+ @mouseleave="mouseleave"
7
+ >
8
+ <div v-if="state" class="status-div" :class="[`bg-${stateColor}`]" />
9
+ <div>
10
+ <TaskIcon :icon="data.icon" :class="taskIconBg" />
11
+ </div>
12
+ <div class="node-content">
13
+ <div class="d-flex justify-content-around">
14
+ <div class="text-truncate task-title">
15
+ <span> {{ id }} </span>
16
+ </div>
17
+ <InformationOutline
18
+ v-if="description"
19
+ @click="$emit(EVENTS.SHOW_DESCRIPTION, {id: id, description:description})"
20
+ class="description-button mx-2"
21
+ />
22
+ </div>
23
+ <slot name="content" />
24
+ </div>
25
+ <div class="position-absolute top-0 text-white d-flex top-button-div">
26
+ <slot name="badge-button-before" />
27
+ <span
28
+ v-if="link"
29
+ class="rounded-button"
30
+ :class="[`bg-${data.color}`]"
31
+ @click="$emit(EVENTS.OPEN_LINK, data)"
32
+ >
33
+ <OpenInNew class="button-icon" alt="Open in new tab" />
34
+ </span>
35
+ <span
36
+ v-if="expandable"
37
+ class="rounded-button"
38
+ :class="[`bg-${data.color}`]"
39
+ @click="$emit(EVENTS.EXPAND)"
40
+ >
41
+ <ArrowExpand class="button-icon" alt="Expand task" />
42
+ </span>
43
+ <slot name="badge-button-after" />
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <script>
49
+ import TaskIcon from "../misc/TaskIcon.vue";
50
+ import InformationOutline from "vue-material-design-icons/InformationOutline.vue";
51
+ import {EVENTS} from "../../utils/constants.js";
52
+ import ArrowExpand from "vue-material-design-icons/ArrowExpand.vue";
53
+ import OpenInNew from "vue-material-design-icons/OpenInNew.vue";
54
+
55
+ export default {
56
+ components: {
57
+ ArrowExpand,
58
+ TaskIcon,
59
+ InformationOutline,
60
+ OpenInNew
61
+ },
62
+ emits: [
63
+ EVENTS.EXPAND,
64
+ EVENTS.OPEN_LINK,
65
+ EVENTS.SHOW_LOGS,
66
+ EVENTS.MOUSE_OVER,
67
+ EVENTS.MOUSE_LEAVE,
68
+ EVENTS.ADD_ERROR,
69
+ EVENTS.EDIT,
70
+ EVENTS.DELETE,
71
+ EVENTS.ADD_TASK
72
+ ],
73
+ inheritAttrs: false,
74
+ props: {
75
+ id: {
76
+ type: String,
77
+ default: undefined
78
+ },
79
+ type: {
80
+ type: String,
81
+ default: undefined
82
+ },
83
+ disabled: {
84
+ type: Boolean,
85
+ default: undefined
86
+ },
87
+ state: {
88
+ type: String,
89
+ default: undefined
90
+ },
91
+ data: {
92
+ type: Object,
93
+ required: true
94
+ },
95
+ style: {
96
+ type: Object,
97
+ required: false,
98
+ default: undefined
99
+ }
100
+ },
101
+ methods: {
102
+ mouseover() {
103
+ this.$emit(EVENTS.MOUSE_OVER, this.data.node);
104
+ },
105
+ mouseleave() {
106
+ this.$emit(EVENTS.MOUSE_LEAVE);
107
+ },
108
+ },
109
+ data() {
110
+ return {
111
+ filter: undefined,
112
+ isOpen: false,
113
+ };
114
+ },
115
+ computed: {
116
+ EVENTS() {
117
+ return EVENTS
118
+ },
119
+ expandable() {
120
+ return this.data?.expandable || false
121
+ },
122
+ link() {
123
+ return this.data?.link || false
124
+ },
125
+ description() {
126
+ const node = this.data.node.task ?? this.data.node.trigger ?? null
127
+ if (node) {
128
+ return node.description ?? null
129
+ }
130
+ return null
131
+ },
132
+ taskIconBg() {
133
+ return !["default", "danger"].includes(this.data.color) ? this.data.color : "";
134
+ },
135
+ stateColor() {
136
+ switch (this.state) {
137
+ case "RUNNING":
138
+ return "primary"
139
+ case "SUCCESS":
140
+ return "success"
141
+ case "WARNING":
142
+ return "warning"
143
+ case "FAILED":
144
+ return "danger"
145
+ default:
146
+ return null;
147
+ }
148
+ }
149
+ },
150
+ }
151
+ </script>
152
+
153
+ <style lang="scss" scoped>
154
+ .node-wrapper {
155
+ background-color: var(--bs-white);
156
+
157
+ html.dark & {
158
+ background-color: var(--card-bg);
159
+ }
160
+
161
+ width: 184px;
162
+ height: 44px;
163
+ margin: 0;
164
+ padding: 8px;
165
+ display: flex;
166
+ z-index: 150000;
167
+ align-items: center;
168
+ box-shadow: 0 12px 12px 0 rgba(130, 103, 158, 0.10);
169
+ }
170
+
171
+ .node-content {
172
+ display: flex;
173
+ flex-direction: column;
174
+ justify-content: center;
175
+ margin-left: 0.7rem;
176
+ }
177
+
178
+ .description-button {
179
+ color: var(--bs-gray-600);
180
+ cursor: pointer;
181
+ width: 25px;
182
+ }
183
+
184
+ .material-design-icon.icon-rounded {
185
+ border-radius: 1rem;
186
+ padding: 1px;
187
+ }
188
+
189
+ .rounded-button {
190
+ border-radius: 1rem;
191
+ width: 1rem;
192
+ height: 1rem;
193
+ display: flex;
194
+ justify-content: center;
195
+ align-items: center;
196
+ margin-left: 0.25rem;
197
+ }
198
+
199
+ .button-icon {
200
+ font-size: 0.75rem;
201
+ }
202
+
203
+ .task-title {
204
+ font-size: 0.75rem;
205
+ font-weight: 700;
206
+ line-height: 1.5rem;
207
+ color: var(--bs-black);
208
+
209
+ html.dark & {
210
+ color: var(--bs-white);
211
+ }
212
+
213
+ width: 6rem;
214
+ }
215
+
216
+
217
+ .status-div {
218
+ width: 8px;
219
+ height: 100%;
220
+ position: absolute;
221
+ left: -0.04438rem;
222
+ border-radius: 0.5rem 0 0 0.5rem;
223
+ }
224
+ </style>