@appscode/design-system 1.1.0-beta.41 → 1.1.0-beta.46
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/icons/close-icon.svg +3 -0
- package/package.json +1 -1
- package/vue-components/types/notification.ts +3 -3
- package/vue-components/v3/modal/Modal.vue +5 -10
- package/vue-components/v3/notification/Notification.vue +2 -2
- package/vue-components/types/longRunningTasks.ts +0 -20
- package/vue-components/v3/long-running-tasks/LongRunningTaskItem.vue +0 -94
- package/vue-components/v3/modals/LongRunningTasksModal.vue +0 -402
- package/vue-components/v3/terminal/LongRunningTaskTerminal.vue +0 -151
package/package.json
CHANGED
|
@@ -31,19 +31,14 @@ const Buttons = defineAsyncComponent(() => import("./../button/Buttons.vue"));
|
|
|
31
31
|
const AcButton = defineAsyncComponent(() => import("./../button/Button.vue"));
|
|
32
32
|
|
|
33
33
|
//TODO: need to update not a currect way to import the file
|
|
34
|
-
const modalCloseIcon = import.meta.glob(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
);
|
|
34
|
+
const modalCloseIcon = import.meta.glob("../../../icons/close-icon.svg", {
|
|
35
|
+
eager: true,
|
|
36
|
+
});
|
|
38
37
|
|
|
39
38
|
const showModal = ref(false);
|
|
40
39
|
const crossIcon = ref(
|
|
41
|
-
(
|
|
42
|
-
|
|
43
|
-
string,
|
|
44
|
-
unknown
|
|
45
|
-
>
|
|
46
|
-
).default as string
|
|
40
|
+
(modalCloseIcon["../../../icons/close-icon.svg"] as Record<string, unknown>)
|
|
41
|
+
?.default as string
|
|
47
42
|
);
|
|
48
43
|
|
|
49
44
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
watch,
|
|
10
10
|
} from "vue";
|
|
11
11
|
import type { Ref } from "vue";
|
|
12
|
-
import type { TaskLog } from "../../types/longRunningTasks";
|
|
12
|
+
// import type { TaskLog } from "../../types/longRunningTasks";
|
|
13
13
|
import type { Notification } from "../../types/notification";
|
|
14
14
|
|
|
15
15
|
const NotificationItem = defineAsyncComponent(
|
|
@@ -48,7 +48,7 @@ async function subscribeToNotifcations() {
|
|
|
48
48
|
for await (const msg of subscription) {
|
|
49
49
|
console.log("notifications ===>");
|
|
50
50
|
console.log({ data: StringCodec().decode(msg.data) });
|
|
51
|
-
const log
|
|
51
|
+
const log = JSON.parse(StringCodec().decode(msg.data));
|
|
52
52
|
console.log({ log });
|
|
53
53
|
const currentTime = new Date().getTime();
|
|
54
54
|
addNewNotification({
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export type TaskStatus =
|
|
2
|
-
| "Pending"
|
|
3
|
-
| "Running"
|
|
4
|
-
| "Success"
|
|
5
|
-
| "Failed"
|
|
6
|
-
| "Started";
|
|
7
|
-
export interface TaskLog {
|
|
8
|
-
status?: TaskStatus;
|
|
9
|
-
msg?: string;
|
|
10
|
-
step?: string;
|
|
11
|
-
error?: string;
|
|
12
|
-
id?: string;
|
|
13
|
-
}
|
|
14
|
-
export interface Task extends Omit<TaskLog, "message"> {
|
|
15
|
-
logs: Array<string>;
|
|
16
|
-
}
|
|
17
|
-
export interface LongRunningTasksCtx {
|
|
18
|
-
natsSubject: string;
|
|
19
|
-
tasks: Array<Task>;
|
|
20
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, toRefs } from "vue";
|
|
3
|
-
import type { TaskStatus } from "./../../types/longRunningTasks";
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
title?: string;
|
|
7
|
-
status?: TaskStatus;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
11
|
-
title: "no title",
|
|
12
|
-
status: "Pending",
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const { status } = toRefs(props);
|
|
16
|
-
const statusClass = computed(() => `is-${status.value.toLowerCase()}`);
|
|
17
|
-
const statusIcon = computed(() => {
|
|
18
|
-
if (status.value === "Running" || status.value === "Started")
|
|
19
|
-
return "circle-o-notch";
|
|
20
|
-
else if (status.value === "Success") return "check-circle";
|
|
21
|
-
else if (status.value === "Failed") return "times-circle";
|
|
22
|
-
else return "spinner";
|
|
23
|
-
});
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<template>
|
|
27
|
-
<span href="" class="task-item" :class="[statusClass]">
|
|
28
|
-
<i class="fa" :class="`fa-${statusIcon}`" />
|
|
29
|
-
{{ title }}
|
|
30
|
-
</span>
|
|
31
|
-
</template>
|
|
32
|
-
|
|
33
|
-
<style scoped lang="scss">
|
|
34
|
-
.task-item {
|
|
35
|
-
font-size: 14px;
|
|
36
|
-
display: block;
|
|
37
|
-
padding: 5px;
|
|
38
|
-
border-radius: 5px;
|
|
39
|
-
transition: all 0.3s ease-in-out;
|
|
40
|
-
&:hover {
|
|
41
|
-
background-color: $primary-97;
|
|
42
|
-
cursor: pointer;
|
|
43
|
-
}
|
|
44
|
-
&.is-active {
|
|
45
|
-
background-color: $primary-97;
|
|
46
|
-
}
|
|
47
|
-
&.is-pending {
|
|
48
|
-
color: $black-50;
|
|
49
|
-
i {
|
|
50
|
-
visibility: hidden;
|
|
51
|
-
}
|
|
52
|
-
&:hover {
|
|
53
|
-
background-color: transparent;
|
|
54
|
-
cursor: not-allowed;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
&.is-aborted {
|
|
58
|
-
color: $black-50;
|
|
59
|
-
&:hover {
|
|
60
|
-
background-color: transparent;
|
|
61
|
-
cursor: not-allowed;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
&.is-running,
|
|
65
|
-
&.is-started {
|
|
66
|
-
i {
|
|
67
|
-
color: $primary;
|
|
68
|
-
animation-name: spin;
|
|
69
|
-
animation-duration: 1000ms;
|
|
70
|
-
animation-iteration-count: infinite;
|
|
71
|
-
animation-timing-function: linear;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@keyframes spin {
|
|
75
|
-
from {
|
|
76
|
-
transform: rotate(0deg);
|
|
77
|
-
}
|
|
78
|
-
to {
|
|
79
|
-
transform: rotate(360deg);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
&.is-success {
|
|
84
|
-
i {
|
|
85
|
-
color: $success;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
&.is-failed {
|
|
89
|
-
i {
|
|
90
|
-
color: $danger;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
</style>
|
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
computed,
|
|
4
|
-
getCurrentInstance,
|
|
5
|
-
onBeforeUnmount,
|
|
6
|
-
toRefs,
|
|
7
|
-
watch,
|
|
8
|
-
} from "vue";
|
|
9
|
-
import type { Ref } from "vue";
|
|
10
|
-
import { defineAsyncComponent, ref } from "vue";
|
|
11
|
-
import type { Task, TaskLog } from "../../types/longRunningTasks";
|
|
12
|
-
import { StringCodec } from "nats.ws";
|
|
13
|
-
import type { Subscription } from "nats.ws";
|
|
14
|
-
|
|
15
|
-
const Modal = defineAsyncComponent(() => import("../modal/Modal.vue"));
|
|
16
|
-
const LongRunningTaskItem = defineAsyncComponent(
|
|
17
|
-
() => import("../long-running-tasks/LongRunningTaskItem.vue")
|
|
18
|
-
);
|
|
19
|
-
const LongRunningTaskTerminal = defineAsyncComponent(
|
|
20
|
-
() => import("../terminal/LongRunningTaskTerminal.vue")
|
|
21
|
-
);
|
|
22
|
-
const Preloader = defineAsyncComponent(
|
|
23
|
-
() => import("../../v2/preloader/Preloader.vue")
|
|
24
|
-
);
|
|
25
|
-
const AcButton = defineAsyncComponent(() => import("../button/Button.vue"));
|
|
26
|
-
|
|
27
|
-
defineEmits(["close"]);
|
|
28
|
-
|
|
29
|
-
interface Props {
|
|
30
|
-
open?: boolean;
|
|
31
|
-
theme?: string;
|
|
32
|
-
title?: string;
|
|
33
|
-
simple?: boolean;
|
|
34
|
-
natsSubject?: string;
|
|
35
|
-
isNatsConnectionLoading?: boolean;
|
|
36
|
-
errorCtx?: {
|
|
37
|
-
connectionError: string;
|
|
38
|
-
onError: (msg: string) => void;
|
|
39
|
-
};
|
|
40
|
-
successCtx?: {
|
|
41
|
-
btnTitle?: string;
|
|
42
|
-
isLoaderActive?: boolean;
|
|
43
|
-
onSuccess: () => void;
|
|
44
|
-
onSuccessBtnClick?: () => void;
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
49
|
-
open: true,
|
|
50
|
-
theme: "light",
|
|
51
|
-
simple: true,
|
|
52
|
-
title: "Sample title",
|
|
53
|
-
natsSubject: "",
|
|
54
|
-
isNatsConnectionLoading: false,
|
|
55
|
-
errorCtx: undefined,
|
|
56
|
-
successCtx: undefined,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const { natsSubject, open, errorCtx, successCtx } = toRefs(props);
|
|
60
|
-
const connectionError = computed(() => errorCtx.value?.connectionError);
|
|
61
|
-
const currentInstance = getCurrentInstance();
|
|
62
|
-
const $nats = currentInstance?.appContext.config.globalProperties.$nc;
|
|
63
|
-
let subscription: Subscription;
|
|
64
|
-
|
|
65
|
-
const tasks: Ref<Array<Task>> = ref([]);
|
|
66
|
-
const activeStepId: Ref<string> = ref("");
|
|
67
|
-
// to maintain stepId to stepIndex map
|
|
68
|
-
// to find active task faster
|
|
69
|
-
const idToStepIndex: Ref<Record<string, number>> = ref({});
|
|
70
|
-
const activeTask = computed(() => {
|
|
71
|
-
const task = tasks.value[idToStepIndex.value[activeStepId.value]];
|
|
72
|
-
return task;
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
function handleTaskLog(log: TaskLog) {
|
|
76
|
-
if (log.step) {
|
|
77
|
-
// log has a step
|
|
78
|
-
// so add new task
|
|
79
|
-
tasks.value.push({
|
|
80
|
-
...log,
|
|
81
|
-
logs: [(log.msg && log.msg) || ""],
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// recent pushed task index
|
|
85
|
-
const latestStepIndex = tasks.value.length - 1;
|
|
86
|
-
|
|
87
|
-
// map taskid to stepIndex
|
|
88
|
-
idToStepIndex.value[log.id as string] = latestStepIndex;
|
|
89
|
-
|
|
90
|
-
// update active step index for first task
|
|
91
|
-
// or if current active step is in latest step
|
|
92
|
-
if (
|
|
93
|
-
latestStepIndex === 0 ||
|
|
94
|
-
latestStepIndex === idToStepIndex.value[activeStepId.value] + 1
|
|
95
|
-
) {
|
|
96
|
-
activeStepId.value = log.id as string;
|
|
97
|
-
}
|
|
98
|
-
} else {
|
|
99
|
-
// get active task
|
|
100
|
-
const task = tasks.value[idToStepIndex.value[log.id as string]];
|
|
101
|
-
if (task) {
|
|
102
|
-
task.status = log.status;
|
|
103
|
-
if (log.status === "Failed") {
|
|
104
|
-
task.logs.push(log.error || "");
|
|
105
|
-
errorCtx.value?.onError(log?.error || "");
|
|
106
|
-
} else {
|
|
107
|
-
task.logs.push(log.msg || "");
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function subscribeToChannel(channelId: string) {
|
|
114
|
-
subscription = $nats?.subscribe(channelId);
|
|
115
|
-
|
|
116
|
-
console.log("Started listening", channelId);
|
|
117
|
-
|
|
118
|
-
if (subscription) {
|
|
119
|
-
// listen to channel events
|
|
120
|
-
//@ts-ignore
|
|
121
|
-
for await (const msg of subscription) {
|
|
122
|
-
console.log("Long Running Tasks Modal=>");
|
|
123
|
-
console.log({ data: StringCodec().decode(msg.data) });
|
|
124
|
-
const log: TaskLog = JSON.parse(StringCodec().decode(msg.data));
|
|
125
|
-
console.log({ log });
|
|
126
|
-
handleTaskLog(log);
|
|
127
|
-
}
|
|
128
|
-
console.log("Stopped listening", channelId);
|
|
129
|
-
console.log("Closed Channel", channelId);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
watch(
|
|
134
|
-
natsSubject,
|
|
135
|
-
(n) => {
|
|
136
|
-
if (n) {
|
|
137
|
-
subscribeToChannel(n);
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
{ immediate: true }
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
watch(open, (n) => {
|
|
144
|
-
if (!n) {
|
|
145
|
-
subscription && subscription.unsubscribe();
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
onBeforeUnmount(() => {
|
|
149
|
-
subscription && subscription.unsubscribe();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
const longRunningTaskStatus = computed(() => {
|
|
153
|
-
let successTaskCount = 0;
|
|
154
|
-
let failedTaskCount = 0;
|
|
155
|
-
|
|
156
|
-
// get count of success and failed task
|
|
157
|
-
tasks.value.forEach((task) => {
|
|
158
|
-
if (task?.status === "Success") successTaskCount++;
|
|
159
|
-
else if (task?.status === "Failed") failedTaskCount++;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
if (tasks.value.length === 0) return "NotStarted";
|
|
163
|
-
// if all the task has been successful
|
|
164
|
-
else if (successTaskCount === tasks.value.length) {
|
|
165
|
-
return "Success";
|
|
166
|
-
}
|
|
167
|
-
// if all the task has been completed and some tasks are failed
|
|
168
|
-
else if (
|
|
169
|
-
failedTaskCount &&
|
|
170
|
-
successTaskCount + failedTaskCount === tasks.value.length
|
|
171
|
-
) {
|
|
172
|
-
return "Failed";
|
|
173
|
-
} else return "Pending";
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// modal close / footer feature
|
|
177
|
-
const enableModalClose = computed(() => {
|
|
178
|
-
return (
|
|
179
|
-
connectionError.value ||
|
|
180
|
-
longRunningTaskStatus.value === "Failed" ||
|
|
181
|
-
longRunningTaskStatus.value === "Success"
|
|
182
|
-
);
|
|
183
|
-
});
|
|
184
|
-
const enableModalFooter = computed(() => {
|
|
185
|
-
return showReportButton.value || showSuccessButton.value;
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// generate report issue title with error step title
|
|
189
|
-
const getReportIssueInfo = (): { title: string; body: string } => {
|
|
190
|
-
const stepTitlesFromErrorTasks: Array<string> = [];
|
|
191
|
-
const stepLogsFromErrorTasks: Array<string> = [];
|
|
192
|
-
tasks.value.forEach((task) => {
|
|
193
|
-
// if this taskLog is error task, push it to array
|
|
194
|
-
if (task.error) {
|
|
195
|
-
stepTitlesFromErrorTasks.push(task?.step || "");
|
|
196
|
-
stepLogsFromErrorTasks.push(task?.logs[task?.logs?.length - 1] || "");
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
// return final object
|
|
200
|
-
return {
|
|
201
|
-
title: stepTitlesFromErrorTasks.join(", "),
|
|
202
|
-
body: stepLogsFromErrorTasks.join(", "),
|
|
203
|
-
};
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// report button
|
|
207
|
-
const showReportButton = computed(
|
|
208
|
-
() => longRunningTaskStatus.value === "Failed"
|
|
209
|
-
);
|
|
210
|
-
function onReportIssueClick() {
|
|
211
|
-
const url = `https://github.com/bytebuilders/community/issues/new?title=Chart Install: ${
|
|
212
|
-
getReportIssueInfo().title
|
|
213
|
-
}&labels[]=bug&body=${window.location.href} %0A%0A %60%60%60 %0A ${
|
|
214
|
-
getReportIssueInfo().body
|
|
215
|
-
} %0A %60%60%60`;
|
|
216
|
-
window.open(url, "_blank");
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// success button
|
|
220
|
-
const showSuccessButton = computed(
|
|
221
|
-
() =>
|
|
222
|
-
longRunningTaskStatus.value === "Success" && !!successCtx.value?.btnTitle
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
// execute on success and on error functions
|
|
226
|
-
watch(longRunningTaskStatus, (n) => {
|
|
227
|
-
if (n === "Success") {
|
|
228
|
-
successCtx.value?.onSuccess();
|
|
229
|
-
} else if (n === "Failed") {
|
|
230
|
-
errorCtx.value?.onError("Operation Failed");
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
</script>
|
|
234
|
-
|
|
235
|
-
<template>
|
|
236
|
-
<modal
|
|
237
|
-
:open="open"
|
|
238
|
-
:title="title"
|
|
239
|
-
:is-close-option-disabled="!enableModalClose"
|
|
240
|
-
:ignore-outside-click="true"
|
|
241
|
-
:hide-action-footer="!enableModalFooter"
|
|
242
|
-
@closemodal="$emit('close')"
|
|
243
|
-
>
|
|
244
|
-
<div v-if="connectionError" class="task-simple-wrapper">
|
|
245
|
-
<div class="task-cogs-icon">
|
|
246
|
-
<i class="fa fa-times-circle has-text-danger fa-5x fa-fw"></i>
|
|
247
|
-
</div>
|
|
248
|
-
<div class="task-log">
|
|
249
|
-
<span class="task-title">
|
|
250
|
-
<i class="fa fa-times-circle mr-5 is-failed" />
|
|
251
|
-
<span> Connection error </span>
|
|
252
|
-
</span>
|
|
253
|
-
<span>{{ connectionError }}</span>
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
256
|
-
<div
|
|
257
|
-
v-else-if="isNatsConnectionLoading || !activeStepId"
|
|
258
|
-
class="is-justify-content-center"
|
|
259
|
-
:class="simple ? 'task-simple-wrapper' : 'task-complex-wrapper'"
|
|
260
|
-
>
|
|
261
|
-
<preloader
|
|
262
|
-
:style="{ height: '100%' }"
|
|
263
|
-
class="is-fullheight"
|
|
264
|
-
message="Connecting..."
|
|
265
|
-
/>
|
|
266
|
-
</div>
|
|
267
|
-
<div v-else-if="simple" class="task-simple-wrapper">
|
|
268
|
-
<div class="task-cogs-icon">
|
|
269
|
-
<i class="fa fa-cog fa-spin fa-5x fa-fw"></i>
|
|
270
|
-
<span class="is-flex is-flex-direction-column">
|
|
271
|
-
<i class="fa fa-cog fa-spin fa-3x fa-bw"></i>
|
|
272
|
-
<i class="fa fa-cog fa-spin fa-3x fa-bw"></i>
|
|
273
|
-
</span>
|
|
274
|
-
</div>
|
|
275
|
-
<div class="task-log">
|
|
276
|
-
<span class="task-title">
|
|
277
|
-
<i
|
|
278
|
-
v-if="activeTask?.status === 'Running'"
|
|
279
|
-
class="fa fa-circle-o-notch fa-spin mr-5"
|
|
280
|
-
/>
|
|
281
|
-
<i
|
|
282
|
-
v-else-if="activeTask?.status === 'Success'"
|
|
283
|
-
class="fa fa-check-circle mr-5 is-success"
|
|
284
|
-
/>
|
|
285
|
-
<i
|
|
286
|
-
v-else-if="activeTask?.status === 'Failed'"
|
|
287
|
-
class="fa fa-times-circle mr-5 is-failed"
|
|
288
|
-
/>
|
|
289
|
-
<span>
|
|
290
|
-
{{ activeTask?.step }}
|
|
291
|
-
</span>
|
|
292
|
-
</span>
|
|
293
|
-
<span>{{ activeTask?.logs[activeTask?.logs.length - 1] }}</span>
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
<div v-else class="task-complex-wrapper">
|
|
297
|
-
<ul class="task-list">
|
|
298
|
-
<li v-for="task in tasks" :key="task.step">
|
|
299
|
-
<long-running-task-item
|
|
300
|
-
:title="task.step"
|
|
301
|
-
:status="task.status"
|
|
302
|
-
:class="{ 'is-active': activeStepId === task.id }"
|
|
303
|
-
@click="activeStepId = task.id as string"
|
|
304
|
-
/>
|
|
305
|
-
</li>
|
|
306
|
-
</ul>
|
|
307
|
-
<long-running-task-terminal
|
|
308
|
-
:key="activeTask?.id"
|
|
309
|
-
:theme="theme"
|
|
310
|
-
:logs="activeTask?.logs"
|
|
311
|
-
class="task-log"
|
|
312
|
-
/>
|
|
313
|
-
</div>
|
|
314
|
-
|
|
315
|
-
<template #modal-footer-controls>
|
|
316
|
-
<ac-button
|
|
317
|
-
title="Close"
|
|
318
|
-
modifier-classes="is-outlined"
|
|
319
|
-
@click.stop="$emit('close')"
|
|
320
|
-
/>
|
|
321
|
-
<ac-button
|
|
322
|
-
v-if="showSuccessButton"
|
|
323
|
-
:title="successCtx?.btnTitle"
|
|
324
|
-
:is-loader-active="successCtx?.isLoaderActive"
|
|
325
|
-
modifier-classes="is-primary"
|
|
326
|
-
icon-class="step-forward"
|
|
327
|
-
@click="successCtx?.onSuccessBtnClick"
|
|
328
|
-
/>
|
|
329
|
-
<ac-button
|
|
330
|
-
v-if="showReportButton"
|
|
331
|
-
title="Report Issue"
|
|
332
|
-
modifier-classes="is-danger"
|
|
333
|
-
icon-class="external-link"
|
|
334
|
-
@click="onReportIssueClick"
|
|
335
|
-
/>
|
|
336
|
-
</template>
|
|
337
|
-
</modal>
|
|
338
|
-
</template>
|
|
339
|
-
|
|
340
|
-
<style scoped lang="scss">
|
|
341
|
-
.task-simple-wrapper {
|
|
342
|
-
display: flex;
|
|
343
|
-
flex-direction: column;
|
|
344
|
-
justify-content: space-between;
|
|
345
|
-
width: 40vw;
|
|
346
|
-
height: 40vh;
|
|
347
|
-
.task-cogs-icon {
|
|
348
|
-
width: 100%;
|
|
349
|
-
height: 70%;
|
|
350
|
-
display: flex;
|
|
351
|
-
align-items: center;
|
|
352
|
-
justify-content: center;
|
|
353
|
-
font-size: 20px;
|
|
354
|
-
color: $primary;
|
|
355
|
-
}
|
|
356
|
-
.task-log {
|
|
357
|
-
width: 100%;
|
|
358
|
-
height: 30%;
|
|
359
|
-
display: flex;
|
|
360
|
-
flex-direction: column;
|
|
361
|
-
align-items: center;
|
|
362
|
-
justify-content: center;
|
|
363
|
-
.task-title {
|
|
364
|
-
span,
|
|
365
|
-
i {
|
|
366
|
-
font-size: 16px;
|
|
367
|
-
}
|
|
368
|
-
i {
|
|
369
|
-
color: $primary;
|
|
370
|
-
&.is-success {
|
|
371
|
-
color: $success;
|
|
372
|
-
}
|
|
373
|
-
&.is-failed {
|
|
374
|
-
color: $danger;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
font-weight: 500;
|
|
378
|
-
}
|
|
379
|
-
span {
|
|
380
|
-
font-size: 14px;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
.task-complex-wrapper {
|
|
385
|
-
display: flex;
|
|
386
|
-
flex-direction: row;
|
|
387
|
-
justify-content: space-between;
|
|
388
|
-
width: 60vw;
|
|
389
|
-
height: 60vh;
|
|
390
|
-
|
|
391
|
-
.task-list {
|
|
392
|
-
width: 25%;
|
|
393
|
-
height: 100%;
|
|
394
|
-
}
|
|
395
|
-
.task-log {
|
|
396
|
-
width: 70%;
|
|
397
|
-
height: 100%;
|
|
398
|
-
border-radius: 4px;
|
|
399
|
-
font-size: 13px;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
</style>
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, nextTick, ref, toRefs, watch, watchPostEffect } from "vue";
|
|
3
|
-
import { Terminal } from "xterm";
|
|
4
|
-
import { FitAddon } from "xterm-addon-fit";
|
|
5
|
-
import { WebglAddon } from "xterm-addon-webgl";
|
|
6
|
-
import { Material, MaterialDark } from "xterm-theme"; //https://github.com/ysk2014/xterm-theme/tree/master/src/iterm
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
theme?: string;
|
|
10
|
-
logs?: string[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
14
|
-
theme: "light",
|
|
15
|
-
logs: () => [],
|
|
16
|
-
});
|
|
17
|
-
// terminal print logic
|
|
18
|
-
const { logs, theme } = toRefs(props);
|
|
19
|
-
const lastPrintIdx = ref(0);
|
|
20
|
-
|
|
21
|
-
//theme
|
|
22
|
-
const bodyBgc = computed(() =>
|
|
23
|
-
theme.value === "light" ? "#eaeaea" : "#232322"
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
// xterm component logic
|
|
27
|
-
const terminalRef = ref<HTMLElement>();
|
|
28
|
-
const terminal = new Terminal({
|
|
29
|
-
windowsMode: false,
|
|
30
|
-
theme: theme.value === "light" ? Material : MaterialDark,
|
|
31
|
-
});
|
|
32
|
-
const fitAddon = new FitAddon();
|
|
33
|
-
terminal.loadAddon(fitAddon);
|
|
34
|
-
const webGlAddon = new WebglAddon();
|
|
35
|
-
webGlAddon.onContextLoss(() => {
|
|
36
|
-
webGlAddon.dispose();
|
|
37
|
-
});
|
|
38
|
-
watchPostEffect(() => {
|
|
39
|
-
if (terminalRef.value) {
|
|
40
|
-
terminal.open(terminalRef.value);
|
|
41
|
-
fitAddon.fit();
|
|
42
|
-
terminal.focus();
|
|
43
|
-
terminal.loadAddon(webGlAddon);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
function writeOnTerminal(msg: string) {
|
|
47
|
-
const lines = msg.split("\n");
|
|
48
|
-
lines.forEach((line, index) => {
|
|
49
|
-
if (lines.length === 1 || index < lines.length - 1) terminal.writeln(line);
|
|
50
|
-
else terminal.write(line);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
watch(
|
|
55
|
-
logs,
|
|
56
|
-
(n) => {
|
|
57
|
-
if (n.length > lastPrintIdx.value) {
|
|
58
|
-
nextTick(() => {
|
|
59
|
-
writeOnTerminal(n.slice(lastPrintIdx.value).join("\n"));
|
|
60
|
-
lastPrintIdx.value = n.length;
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
{ immediate: true, deep: true }
|
|
65
|
-
);
|
|
66
|
-
</script>
|
|
67
|
-
|
|
68
|
-
<template>
|
|
69
|
-
<div ref="terminalRef" class="terminal-body"></div>
|
|
70
|
-
</template>
|
|
71
|
-
|
|
72
|
-
<style lang="scss">
|
|
73
|
-
.terminal-body {
|
|
74
|
-
width: 100%;
|
|
75
|
-
height: 100%;
|
|
76
|
-
background-color: v-bind(bodyBgc);
|
|
77
|
-
padding: 5px 0px 0px 10px;
|
|
78
|
-
|
|
79
|
-
// for terminal scroll bar style
|
|
80
|
-
.xterm .xterm-viewport {
|
|
81
|
-
&::-webkit-scrollbar {
|
|
82
|
-
border-radius: 50px;
|
|
83
|
-
width: 3px;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
&::-moz-scrollbar {
|
|
87
|
-
border-radius: 50px;
|
|
88
|
-
width: 3px;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
&::-ms-scrollbar {
|
|
92
|
-
border-radius: 50px;
|
|
93
|
-
width: 3px;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
&::-webkit-scrollbar:hover {
|
|
97
|
-
width: 7px;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
&::-moz-scrollbar:hover {
|
|
101
|
-
width: 7px;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
&::-ms-scrollbar:hover {
|
|
105
|
-
width: 7px;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
&::-webkit-scrollbar-thumb {
|
|
109
|
-
background-color: #929292;
|
|
110
|
-
border-radius: 50px;
|
|
111
|
-
height: 2px !important;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
&::-moz-scrollbar-thumb {
|
|
115
|
-
background-color: #929292;
|
|
116
|
-
border-radius: 50px;
|
|
117
|
-
height: 2px !important;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
&::-ms-scrollbar-thumb {
|
|
121
|
-
background-color: #929292;
|
|
122
|
-
border-radius: 50px;
|
|
123
|
-
height: 2px !important;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
&::-webkit-scrollbar-thumb:hover {
|
|
127
|
-
background-color: #929292;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
&::-moz-scrollbar-thumb:hover {
|
|
131
|
-
background-color: #929292;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
&::-ms-scrollbar-thumb:hover {
|
|
135
|
-
background-color: #929292;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
&:hover::-webkit-scrollbar-corner {
|
|
139
|
-
height: 40px;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
&:hover::-moz-scrollbar-corner {
|
|
143
|
-
height: 40px;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
&:hover::-ms-scrollbar-corner {
|
|
147
|
-
height: 40px;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
</style>
|