@live-change/task-frontend 0.9.163 → 0.9.165
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/front/src/components/AdminTopMenu.vue +41 -0
- package/front/src/components/IntervalCard.vue +117 -20
- package/front/src/components/ScheduleCard.vue +123 -41
- package/front/src/components/TaskAdminDetails.vue +6 -0
- package/front/src/pages/CronAdmin.vue +13 -76
- package/front/src/pages/CronIntervalsAdmin.vue +75 -0
- package/front/src/pages/CronSchedulesAdmin.vue +75 -0
- package/front/src/router.js +14 -4
- package/package.json +40 -40
- package/server/app.config.js +3 -0
- package/server/services.list.js +2 -0
- package/server/testTasks.js +38 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
|
|
3
|
+
<ul class="bg-surface-0 dark:bg-surface-900 p-0 m-0 list-none flex overflow-x-auto select-none">
|
|
4
|
+
<li v-for="entry in entries" :key="entry.label" class="flex-1 p-0 m-0">
|
|
5
|
+
<router-link
|
|
6
|
+
:to="entry.route ? { name: entry.route, params: { } } : null"
|
|
7
|
+
:target="entry.target"
|
|
8
|
+
v-ripple
|
|
9
|
+
class="cursor-pointer px-4 py-3 flex items-center justify-center
|
|
10
|
+
border-y-2 hover:border-b-primary-400
|
|
11
|
+
transition-colors transition-duration-150 p-ripple no-underline"
|
|
12
|
+
:class="[
|
|
13
|
+
route.name === entry.route
|
|
14
|
+
? 'border-b-surface-400 text-surface-300 hover:border-b-surface-700 border-t-transparent'
|
|
15
|
+
: 'text-surface-700 dark:text-surface-100 border-transparent'
|
|
16
|
+
]">
|
|
17
|
+
<i v-if="entry.icon" class="mr-2" :class="[ entry.icon ]"></i>
|
|
18
|
+
<span class="font-medium">{{ entry.label }}</span>
|
|
19
|
+
</router-link>
|
|
20
|
+
</li>
|
|
21
|
+
</ul>
|
|
22
|
+
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup>
|
|
26
|
+
|
|
27
|
+
import { useRoute } from 'vue-router'
|
|
28
|
+
const route = useRoute()
|
|
29
|
+
|
|
30
|
+
const entries = [
|
|
31
|
+
{ label: 'Intervals', icon: 'pi pi-clock', route: 'cron:admin:intervals' },
|
|
32
|
+
{ label: 'Schedules', icon: 'pi pi-calendar', route: 'cron:admin:schedules' },
|
|
33
|
+
{ label: 'Tasks', icon: 'pi pi-tasks', route: 'task:admin' },
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
<style scoped>
|
|
40
|
+
|
|
41
|
+
</style>
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
<div class="ml-2">{{ intervalData.description || intervalData.id }}</div>
|
|
7
7
|
</div>
|
|
8
8
|
<div class="flex flex-row items-center gap-4">
|
|
9
|
+
<div class="text-sm">
|
|
10
|
+
<strong>Last</strong>
|
|
11
|
+
{{ lastRunDisplay }}
|
|
12
|
+
</div>
|
|
13
|
+
<div class="text-sm">
|
|
14
|
+
<strong>Next</strong>
|
|
15
|
+
{{ nextRunDisplay }}
|
|
16
|
+
</div>
|
|
9
17
|
<div class="text-sm">
|
|
10
18
|
Every {{ formatInterval(intervalData.interval) }}
|
|
11
19
|
</div>
|
|
@@ -15,13 +23,25 @@
|
|
|
15
23
|
text
|
|
16
24
|
rounded
|
|
17
25
|
/>
|
|
26
|
+
<Button
|
|
27
|
+
icon="pi pi-trash"
|
|
28
|
+
@click="deleteInterval"
|
|
29
|
+
text
|
|
30
|
+
rounded
|
|
31
|
+
/>
|
|
18
32
|
</div>
|
|
19
33
|
</div>
|
|
20
34
|
|
|
21
35
|
<div v-if="isExpanded" class="mt-2 p-2 bg-surface-50 dark:bg-surface-800 rounded">
|
|
22
36
|
<div class="grid grid-cols-1 gap-2 text-sm">
|
|
23
|
-
<div
|
|
24
|
-
|
|
37
|
+
<div>
|
|
38
|
+
<strong>Interval:</strong>
|
|
39
|
+
{{ formatInterval(intervalData.interval) }} ({{ intervalData.interval }}ms)
|
|
40
|
+
</div>
|
|
41
|
+
<div v-if="intervalData.wait">
|
|
42
|
+
<strong>Wait:</strong>
|
|
43
|
+
{{ formatInterval(intervalData.wait) }} ({{ intervalData.wait }}ms)
|
|
44
|
+
</div>
|
|
25
45
|
</div>
|
|
26
46
|
<div v-if="intervalData.trigger" class="mt-2">
|
|
27
47
|
<strong>Trigger:</strong>
|
|
@@ -30,7 +50,8 @@
|
|
|
30
50
|
<div><strong>Service:</strong> {{ intervalData.trigger.service || 'any' }}</div>
|
|
31
51
|
<div v-if="intervalData.trigger.properties">
|
|
32
52
|
<strong>Properties:</strong>
|
|
33
|
-
<pre class="text-xs bg-surface-100 dark:bg-surface-700 p-1 rounded mt-1"
|
|
53
|
+
<pre class="text-xs bg-surface-100 dark:bg-surface-700 p-1 rounded mt-1"
|
|
54
|
+
>{{ JSON.stringify(intervalData.trigger.properties, null, 2) }}</pre>
|
|
34
55
|
</div>
|
|
35
56
|
</div>
|
|
36
57
|
</div>
|
|
@@ -52,14 +73,37 @@
|
|
|
52
73
|
</div>
|
|
53
74
|
</div>
|
|
54
75
|
</div>
|
|
76
|
+
|
|
77
|
+
<div v-if="intervalInfoData" class="mt-2 grid grid-cols-1 gap-1 text-sm">
|
|
78
|
+
<div v-if="intervalInfoData.lastRun">
|
|
79
|
+
<strong>Last Run: </strong>
|
|
80
|
+
<span :title="lastRunAbsolute || undefined">{{ lastRunDisplay }}</span>
|
|
81
|
+
</div>
|
|
82
|
+
<div v-if="intervalInfoData.nextRun">
|
|
83
|
+
<strong>Next Run: </strong>
|
|
84
|
+
<span :title="nextRunAbsolute || undefined">{{ nextRunDisplay }}</span>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div v-if="tasksData?.length" class="mt-2">
|
|
89
|
+
<strong>Last 5 Tasks:</strong>
|
|
90
|
+
<div class="ml-2 text-sm">
|
|
91
|
+
<div v-for="task in tasksData" :key="task.id">
|
|
92
|
+
<TaskAdminCard :task="task" class="mt-1" />
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- <pre>{{ JSON.stringify(intervalData, null, 2) }}</pre> -->
|
|
55
98
|
</div>
|
|
56
99
|
</div>
|
|
57
100
|
</template>
|
|
58
101
|
|
|
59
102
|
<script setup>
|
|
60
|
-
import { ref, computed,
|
|
103
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
61
104
|
import Button from 'primevue/button'
|
|
62
105
|
import { usePath, live } from '@live-change/vue3-ssr'
|
|
106
|
+
import { currentTime } from "@live-change/frontend-base"
|
|
63
107
|
|
|
64
108
|
const props = defineProps({
|
|
65
109
|
interval: {
|
|
@@ -70,22 +114,6 @@
|
|
|
70
114
|
|
|
71
115
|
const intervalData = computed(() => props.interval)
|
|
72
116
|
const isExpanded = ref(false)
|
|
73
|
-
const runStateData = ref(null)
|
|
74
|
-
const path = usePath()
|
|
75
|
-
|
|
76
|
-
// Watch for interval changes and fetch run state
|
|
77
|
-
watch(() => props.interval?.id, async (newId) => {
|
|
78
|
-
if (newId) {
|
|
79
|
-
try {
|
|
80
|
-
// Get run state for this specific interval
|
|
81
|
-
const runStatePath = path.cron_RunState.to(['cron_Interval', newId])
|
|
82
|
-
runStateData.value = await live(runStatePath)
|
|
83
|
-
} catch (error) {
|
|
84
|
-
console.error('Error fetching run state:', error)
|
|
85
|
-
runStateData.value = null
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}, { immediate: true })
|
|
89
117
|
|
|
90
118
|
function formatInterval(ms) {
|
|
91
119
|
if (!ms) return 'N/A'
|
|
@@ -101,6 +129,8 @@
|
|
|
101
129
|
return `${seconds} second${seconds > 1 ? 's' : ''}`
|
|
102
130
|
}
|
|
103
131
|
|
|
132
|
+
const runStateData = computed(() => intervalData.value?.runState)
|
|
133
|
+
|
|
104
134
|
const runStateIcon = computed(() => {
|
|
105
135
|
switch(runStateData.value?.state) {
|
|
106
136
|
case 'running': return 'pi-play'
|
|
@@ -116,4 +146,71 @@
|
|
|
116
146
|
default: return ''
|
|
117
147
|
}
|
|
118
148
|
})
|
|
149
|
+
|
|
150
|
+
const intervalInfoData = computed(() => intervalData.value?.info)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
function formatAbsoluteMoment(timestamp) {
|
|
154
|
+
if (!timestamp) return ''
|
|
155
|
+
const date = new Date(timestamp)
|
|
156
|
+
if (Number.isNaN(date.getTime())) return ''
|
|
157
|
+
return date.toLocaleString()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function formatRelativeMoment(timestamp) {
|
|
161
|
+
if (!timestamp) return 'N/A'
|
|
162
|
+
const target = new Date(timestamp).getTime()
|
|
163
|
+
if (Number.isNaN(target)) return 'N/A'
|
|
164
|
+
|
|
165
|
+
const diff = target - currentTime.value
|
|
166
|
+
const absDiff = Math.abs(diff)
|
|
167
|
+
const dayMs = 24 * 60 * 60 * 1000
|
|
168
|
+
|
|
169
|
+
if (absDiff < dayMs) {
|
|
170
|
+
const hourMs = 60 * 60 * 1000
|
|
171
|
+
const minuteMs = 60 * 1000
|
|
172
|
+
const hours = Math.floor(absDiff / hourMs)
|
|
173
|
+
const minutes = Math.floor((absDiff % hourMs) / minuteMs)
|
|
174
|
+
const seconds = Math.floor((absDiff % minuteMs) / 1000)
|
|
175
|
+
const parts = []
|
|
176
|
+
if (hours > 0) parts.push(`${hours}h`)
|
|
177
|
+
if (minutes > 0) parts.push(`${minutes}m`)
|
|
178
|
+
parts.push(`${seconds}s`)
|
|
179
|
+
const relative = parts.join(' ')
|
|
180
|
+
if (diff > 0) return `in ${relative}`
|
|
181
|
+
if (diff < 0) return `${relative} ago`
|
|
182
|
+
return 'now'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return new Date(target).toLocaleString()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const lastRunDisplay = computed(() => formatRelativeMoment(intervalInfoData.value?.lastRun))
|
|
189
|
+
const nextRunDisplay = computed(() => formatRelativeMoment(intervalInfoData.value?.nextRun))
|
|
190
|
+
const lastRunAbsolute = computed(() => formatAbsoluteMoment(intervalInfoData.value?.lastRun))
|
|
191
|
+
const nextRunAbsolute = computed(() => formatAbsoluteMoment(intervalInfoData.value?.nextRun))
|
|
192
|
+
|
|
193
|
+
const tasksData = computed(() => intervalData.value?.tasks)
|
|
194
|
+
|
|
195
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
196
|
+
const confirm = useConfirm()
|
|
197
|
+
import { useToast } from 'primevue/usetoast'
|
|
198
|
+
const toast = useToast()
|
|
199
|
+
|
|
200
|
+
function deleteInterval() {
|
|
201
|
+
confirm.require({
|
|
202
|
+
target: event.currentTarget,
|
|
203
|
+
message: `Do you want to delete this interval?`,
|
|
204
|
+
icon: 'pi pi-info-circle',
|
|
205
|
+
acceptClass: 'p-button-danger',
|
|
206
|
+
accept: async () => {
|
|
207
|
+
console.log("deleteInterval", intervalData.value.id)
|
|
208
|
+
await api.actions.cron.deleteInterval({ interval: intervalData.value.id })
|
|
209
|
+
toast.add({ severity:'info', summary: 'Interval deleted', life: 1500 })
|
|
210
|
+
},
|
|
211
|
+
reject: () => {
|
|
212
|
+
toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
}
|
|
119
216
|
</script>
|
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
<div class="bg-surface-0 dark:bg-surface-900 px-3 py-1 shadow">
|
|
3
3
|
<div class="flex flex-row justify-between items-center" v-if="scheduleData">
|
|
4
4
|
<div class="flex flex-row items-center">
|
|
5
|
-
<i class="pi pi-
|
|
5
|
+
<i class="pi pi-clock" style="font-size: 1rem" />
|
|
6
6
|
<div class="ml-2">{{ scheduleData.description || scheduleData.id }}</div>
|
|
7
7
|
</div>
|
|
8
8
|
<div class="flex flex-row items-center gap-4">
|
|
9
|
+
<div class="text-sm">
|
|
10
|
+
<strong>Last</strong>
|
|
11
|
+
{{ lastRunDisplay }}
|
|
12
|
+
</div>
|
|
13
|
+
<div class="text-sm">
|
|
14
|
+
<strong>Next</strong>
|
|
15
|
+
{{ nextRunDisplay }}
|
|
16
|
+
</div>
|
|
9
17
|
<div class="text-sm">
|
|
10
18
|
{{ formatSchedule(scheduleData) }}
|
|
11
19
|
</div>
|
|
@@ -15,16 +23,18 @@
|
|
|
15
23
|
text
|
|
16
24
|
rounded
|
|
17
25
|
/>
|
|
26
|
+
<Button
|
|
27
|
+
icon="pi pi-trash"
|
|
28
|
+
@click="deleteSchedule"
|
|
29
|
+
text
|
|
30
|
+
rounded
|
|
31
|
+
/>
|
|
18
32
|
</div>
|
|
19
33
|
</div>
|
|
20
34
|
|
|
21
35
|
<div v-if="isExpanded" class="mt-2 p-2 bg-surface-50 dark:bg-surface-800 rounded">
|
|
22
|
-
<div class="grid grid-cols-
|
|
23
|
-
<div><strong>
|
|
24
|
-
<div><strong>Hour:</strong> {{ scheduleData.hour || '*' }}</div>
|
|
25
|
-
<div><strong>Day:</strong> {{ scheduleData.day || '*' }}</div>
|
|
26
|
-
<div><strong>Day of Week:</strong> {{ scheduleData.dayOfWeek || '*' }}</div>
|
|
27
|
-
<div><strong>Month:</strong> {{ scheduleData.month || '*' }}</div>
|
|
36
|
+
<div class="grid grid-cols-1 gap-2 text-sm">
|
|
37
|
+
<div><strong>Schedule:</strong> {{ formatSchedule(scheduleData) }}</div>
|
|
28
38
|
</div>
|
|
29
39
|
<div v-if="scheduleData.trigger" class="mt-2">
|
|
30
40
|
<strong>Trigger:</strong>
|
|
@@ -33,12 +43,14 @@
|
|
|
33
43
|
<div><strong>Service:</strong> {{ scheduleData.trigger.service || 'any' }}</div>
|
|
34
44
|
<div v-if="scheduleData.trigger.properties">
|
|
35
45
|
<strong>Properties:</strong>
|
|
36
|
-
<pre class="text-xs bg-surface-100 dark:bg-surface-700 p-1 rounded mt-1">
|
|
46
|
+
<pre class="text-xs bg-surface-100 dark:bg-surface-700 p-1 rounded mt-1">
|
|
47
|
+
{{ JSON.stringify(scheduleData.trigger.properties, null, 2) }}
|
|
48
|
+
</pre>
|
|
37
49
|
</div>
|
|
38
50
|
</div>
|
|
39
51
|
</div>
|
|
40
52
|
|
|
41
|
-
<!-- RunState for this
|
|
53
|
+
<!-- RunState for this Interval -->
|
|
42
54
|
<div v-if="runStateData" class="mt-2">
|
|
43
55
|
<strong>Current Run State:</strong>
|
|
44
56
|
<div class="ml-2 text-sm p-2 bg-surface-100 dark:bg-surface-700 rounded">
|
|
@@ -55,14 +67,37 @@
|
|
|
55
67
|
</div>
|
|
56
68
|
</div>
|
|
57
69
|
</div>
|
|
70
|
+
|
|
71
|
+
<div v-if="scheduleInfoData" class="mt-2 grid grid-cols-1 gap-1 text-sm">
|
|
72
|
+
<div v-if="scheduleInfoData.lastRun">
|
|
73
|
+
<strong>Last Run: </strong>
|
|
74
|
+
<span :title="lastRunAbsolute || undefined">{{ lastRunDisplay }}</span>
|
|
75
|
+
</div>
|
|
76
|
+
<div v-if="scheduleInfoData.nextRun">
|
|
77
|
+
<strong>Next Run: </strong>
|
|
78
|
+
<span :title="nextRunAbsolute || undefined">{{ nextRunDisplay }}</span>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div v-if="tasksData?.length" class="mt-2">
|
|
83
|
+
<strong>Last 5 Tasks:</strong>
|
|
84
|
+
<div class="ml-2 text-sm">
|
|
85
|
+
<div v-for="task in tasksData" :key="task.id">
|
|
86
|
+
<TaskAdminCard :task="task" class="mt-1" />
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<pre>{{ JSON.stringify(intervalData, null, 2) }}</pre>
|
|
58
92
|
</div>
|
|
59
93
|
</div>
|
|
60
94
|
</template>
|
|
61
95
|
|
|
62
96
|
<script setup>
|
|
63
|
-
import { ref, computed, onMounted,
|
|
97
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
64
98
|
import Button from 'primevue/button'
|
|
65
99
|
import { usePath, live } from '@live-change/vue3-ssr'
|
|
100
|
+
import { currentTime } from "@live-change/frontend-base"
|
|
66
101
|
|
|
67
102
|
const props = defineProps({
|
|
68
103
|
schedule: {
|
|
@@ -73,43 +108,23 @@
|
|
|
73
108
|
|
|
74
109
|
const scheduleData = computed(() => props.schedule)
|
|
75
110
|
const isExpanded = ref(false)
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
// Watch for schedule changes and fetch run state
|
|
80
|
-
watch(() => props.schedule?.id, async (newId) => {
|
|
81
|
-
if (newId) {
|
|
82
|
-
try {
|
|
83
|
-
// Get the run state for this specific schedule
|
|
84
|
-
const runStatePath = path.cron_RunState.to(['cron_Schedule', newId])
|
|
85
|
-
runStateData.value = await live(runStatePath)
|
|
86
|
-
} catch (error) {
|
|
87
|
-
console.error('Error fetching run state:', error)
|
|
88
|
-
runStateData.value = null
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}, { immediate: true })
|
|
111
|
+
|
|
112
|
+
const daysOfWeek = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
|
|
113
|
+
const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
|
|
92
114
|
|
|
93
115
|
function formatSchedule(schedule) {
|
|
94
116
|
const parts = []
|
|
95
|
-
if
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (schedule.day !== undefined && schedule.day !== null) parts.push(schedule.day)
|
|
102
|
-
else parts.push('*')
|
|
103
|
-
|
|
104
|
-
if (schedule.month !== undefined && schedule.month !== null) parts.push(schedule.month)
|
|
105
|
-
else parts.push('*')
|
|
106
|
-
|
|
107
|
-
if (schedule.dayOfWeek !== undefined && schedule.dayOfWeek !== null) parts.push(schedule.dayOfWeek)
|
|
108
|
-
else parts.push('*')
|
|
109
|
-
|
|
117
|
+
if(Number.isInteger(schedule.minute)) parts.push(`${schedule.minute}m`)
|
|
118
|
+
if(Number.isInteger(schedule.hour)) parts.push(`${schedule.hour}h`)
|
|
119
|
+
if(Number.isInteger(schedule.day)) parts.push(`${schedule.day}`)
|
|
120
|
+
if(Number.isInteger(schedule.dayOfWeek)) parts.push(`${daysOfWeek[schedule.dayOfWeek - 1]}`)
|
|
121
|
+
if(Number.isInteger(schedule.month)) parts.push(`${months[schedule.month - 1]}`)
|
|
122
|
+
if(parts.length === 0) parts.push('every minute')
|
|
110
123
|
return parts.join(' ')
|
|
111
124
|
}
|
|
112
125
|
|
|
126
|
+
const runStateData = computed(() => scheduleData.value?.runState)
|
|
127
|
+
|
|
113
128
|
const runStateIcon = computed(() => {
|
|
114
129
|
switch(runStateData.value?.state) {
|
|
115
130
|
case 'running': return 'pi-play'
|
|
@@ -125,4 +140,71 @@
|
|
|
125
140
|
default: return ''
|
|
126
141
|
}
|
|
127
142
|
})
|
|
143
|
+
|
|
144
|
+
const scheduleInfoData = computed(() => scheduleData.value?.info)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
function formatAbsoluteMoment(timestamp) {
|
|
148
|
+
if (!timestamp) return ''
|
|
149
|
+
const date = new Date(timestamp)
|
|
150
|
+
if (Number.isNaN(date.getTime())) return ''
|
|
151
|
+
return date.toLocaleString()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function formatRelativeMoment(timestamp) {
|
|
155
|
+
if (!timestamp) return 'N/A'
|
|
156
|
+
const target = new Date(timestamp).getTime()
|
|
157
|
+
if (Number.isNaN(target)) return 'N/A'
|
|
158
|
+
|
|
159
|
+
const diff = target - currentTime.value
|
|
160
|
+
const absDiff = Math.abs(diff)
|
|
161
|
+
const dayMs = 24 * 60 * 60 * 1000
|
|
162
|
+
|
|
163
|
+
if (absDiff < dayMs) {
|
|
164
|
+
const hourMs = 60 * 60 * 1000
|
|
165
|
+
const minuteMs = 60 * 1000
|
|
166
|
+
const hours = Math.floor(absDiff / hourMs)
|
|
167
|
+
const minutes = Math.floor((absDiff % hourMs) / minuteMs)
|
|
168
|
+
const seconds = Math.floor((absDiff % minuteMs) / 1000)
|
|
169
|
+
const parts = []
|
|
170
|
+
if (hours > 0) parts.push(`${hours}h`)
|
|
171
|
+
if (minutes > 0) parts.push(`${minutes}m`)
|
|
172
|
+
parts.push(`${seconds}s`)
|
|
173
|
+
const relative = parts.join(' ')
|
|
174
|
+
if (diff > 0) return `in ${relative}`
|
|
175
|
+
if (diff < 0) return `${relative} ago`
|
|
176
|
+
return 'now'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return new Date(target).toLocaleString()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const lastRunDisplay = computed(() => formatRelativeMoment(scheduleInfoData.value?.lastRun))
|
|
183
|
+
const nextRunDisplay = computed(() => formatRelativeMoment(scheduleInfoData.value?.nextRun))
|
|
184
|
+
const lastRunAbsolute = computed(() => formatAbsoluteMoment(scheduleInfoData.value?.lastRun))
|
|
185
|
+
const nextRunAbsolute = computed(() => formatAbsoluteMoment(scheduleInfoData.value?.nextRun))
|
|
186
|
+
|
|
187
|
+
const tasksData = computed(() => scheduleData.value?.tasks)
|
|
188
|
+
|
|
189
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
190
|
+
const confirm = useConfirm()
|
|
191
|
+
import { useToast } from 'primevue/usetoast'
|
|
192
|
+
const toast = useToast()
|
|
193
|
+
|
|
194
|
+
function deleteSchedule() {
|
|
195
|
+
confirm.require({
|
|
196
|
+
target: event.currentTarget,
|
|
197
|
+
message: `Do you want to delete this schedule?`,
|
|
198
|
+
icon: 'pi pi-info-circle',
|
|
199
|
+
acceptClass: 'p-button-danger',
|
|
200
|
+
accept: async () => {
|
|
201
|
+
console.log("deleteSchedule", scheduleData.value.id)
|
|
202
|
+
await api.actions.cron.deleteSchedule({ schedule: scheduleData.value.id })
|
|
203
|
+
toast.add({ severity:'info', summary: 'Schedule deleted', life: 1500 })
|
|
204
|
+
},
|
|
205
|
+
reject: () => {
|
|
206
|
+
toast.add({ severity:'error', summary: 'Rejected', detail: 'You have rejected', life: 3000 })
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
}
|
|
128
210
|
</script>
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
{{ (new Date(taskData.doneAt).getTime() - new Date(taskData.startedAt).getTime()) / 1000 }}s
|
|
36
36
|
</div>
|
|
37
37
|
</div>
|
|
38
|
+
|
|
38
39
|
|
|
39
40
|
<div class="grid grid-cols-2 gap-2 text-sm" v-if="taskData.doneAt">
|
|
40
41
|
<div v-if="taskData.doneAt">Done:</div>
|
|
@@ -47,6 +48,11 @@
|
|
|
47
48
|
{{ taskData.client }}
|
|
48
49
|
</div>
|
|
49
50
|
</div>
|
|
51
|
+
|
|
52
|
+
<div class="grid grid-cols-2 gap-2 text-sm" v-if="taskData.cause">
|
|
53
|
+
<div>Cause:</div>
|
|
54
|
+
<div>{{ taskData.causeType }} - {{ taskData.cause }}</div>
|
|
55
|
+
</div>
|
|
50
56
|
</div>
|
|
51
57
|
|
|
52
58
|
<div v-if="taskData.properties" class="mt-2">
|
|
@@ -1,83 +1,20 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="w-full">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
service="cron"
|
|
6
|
-
action="setInterval"
|
|
7
|
-
:parameters="intervalParameters"
|
|
8
|
-
@done="handleActionDone" />
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<div class="bg-surface-0 dark:bg-surface-900 p-3 shadow mb-1">
|
|
12
|
-
<h3>{{ t('cron.intervals') }}</h3>
|
|
13
|
-
<range-viewer :pathFunction="intervalsPathFunction" :key="JSON.stringify(intervalsPathConfig)"
|
|
14
|
-
:canLoadTop="false" :canDropBottom="false"
|
|
15
|
-
loadBottomSensorSize="3000px" dropBottomSensorSize="12000px">
|
|
16
|
-
<template #empty>
|
|
17
|
-
<div class="bg-surface-0 p-3 shadow text-center text-gray-500 text-lg">
|
|
18
|
-
No intervals found...
|
|
19
|
-
</div>
|
|
20
|
-
</template>
|
|
21
|
-
|
|
22
|
-
<template #default="{ item: interval }">
|
|
23
|
-
<IntervalCard :interval="interval" class="mt-1" />
|
|
24
|
-
</template>
|
|
25
|
-
</range-viewer>
|
|
2
|
+
<div class="w-full px-4 py-5 flex flex-col items-center justify-center">
|
|
3
|
+
<div class="bg-surface-0 dark:bg-surface-900 p-3 shadow mb-1 w-60 text-center mb-4">
|
|
4
|
+
<h1 class="my-1 text-center">Cron Admin</h1>
|
|
26
5
|
</div>
|
|
27
|
-
|
|
6
|
+
<div class="flex flex-row gap-4 items-center justify-center">
|
|
7
|
+
<router-link :to="{ name: 'cron:admin:intervals' }" class="no-underline">
|
|
8
|
+
<Button label="Intervals" icon="pi pi-clock" size="large" />
|
|
9
|
+
</router-link>
|
|
10
|
+
<router-link :to="{ name: 'cron:admin:schedules' }" class="no-underline">
|
|
11
|
+
<Button label="Schedules" icon="pi pi-calendar" size="large" />
|
|
12
|
+
</router-link>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
28
15
|
</div>
|
|
29
16
|
</template>
|
|
30
17
|
|
|
31
18
|
<script setup>
|
|
32
|
-
|
|
33
|
-
import ScheduleCard from '../components/ScheduleCard.vue'
|
|
34
|
-
import IntervalCard from '../components/IntervalCard.vue'
|
|
35
|
-
import Select from 'primevue/select'
|
|
36
|
-
import InputText from 'primevue/inputtext'
|
|
37
|
-
import InputNumber from 'primevue/inputnumber'
|
|
38
|
-
import Dropdown from 'primevue/dropdown'
|
|
39
|
-
import Button from 'primevue/button'
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
import { ActionForm } from '@live-change/frontend-auto-form'
|
|
43
|
-
|
|
44
|
-
import { useI18n } from 'vue-i18n'
|
|
45
|
-
const { t } = useI18n()
|
|
46
|
-
|
|
47
|
-
import { ref, computed } from 'vue'
|
|
48
|
-
import { RangeViewer } from "@live-change/vue3-components"
|
|
49
|
-
|
|
50
|
-
import { inject } from 'vue'
|
|
51
|
-
const workingZone = inject('workingZone')
|
|
52
|
-
|
|
53
|
-
import { usePath, live, useClient, useActions, reverseRange, useApi } from '@live-change/vue3-ssr'
|
|
54
|
-
const path = usePath()
|
|
55
|
-
const client = useClient()
|
|
56
|
-
const actions = useActions()
|
|
57
|
-
const api = useApi()
|
|
58
|
-
|
|
59
|
-
const taskType = ref('all')
|
|
60
|
-
|
|
61
|
-
// Available test tasks
|
|
62
|
-
const taskOptions = ref([
|
|
63
|
-
{ label: 'Build Shelter', value: 'buildShelter' },
|
|
64
|
-
{ label: 'Get Wood', value: 'getWood' },
|
|
65
|
-
{ label: 'Cut Wood', value: 'cutWood' },
|
|
66
|
-
{ label: 'Make Planks', value: 'makePlanks' },
|
|
67
|
-
{ label: 'Build Wall', value: 'buildWall' },
|
|
68
|
-
{ label: 'Build Roof', value: 'buildRoof' }
|
|
69
|
-
])
|
|
70
|
-
|
|
71
|
-
const schedulesPathFunction = computed(() => (range) =>
|
|
72
|
-
path.cron.schedules({ ...reverseRange(range) })
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
const intervalsPathFunction = computed(() => (range) =>
|
|
76
|
-
path.cron.intervals({ ...reverseRange(range) })
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
const services = computed(() => Object.keys(api.metadata.api.value.services))
|
|
80
|
-
|
|
81
|
-
|
|
82
19
|
|
|
83
|
-
</script>
|
|
20
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full px-4 py-5">
|
|
3
|
+
|
|
4
|
+
<AdminTopMenu />
|
|
5
|
+
|
|
6
|
+
<div class="bg-surface-0 dark:bg-surface-900 p-3 shadow mb-1">
|
|
7
|
+
<ActionForm
|
|
8
|
+
service="cron"
|
|
9
|
+
action="setInterval" />
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
<div class="bg-surface-0 dark:bg-surface-900 p-3 shadow mb-1">
|
|
15
|
+
<h3>{{ t('cron.intervals') }}</h3>
|
|
16
|
+
<range-viewer :pathFunction="intervalsPathFunction" key="intervals"
|
|
17
|
+
:canLoadTop="false" :canDropBottom="false"
|
|
18
|
+
loadBottomSensorSize="3000px" dropBottomSensorSize="12000px">
|
|
19
|
+
<template #empty>
|
|
20
|
+
<div class="bg-surface-0 p-3 shadow text-center text-gray-500 text-lg">
|
|
21
|
+
No intervals found...
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<template #default="{ item: interval }">
|
|
26
|
+
<IntervalCard :interval="interval" class="mt-1" />
|
|
27
|
+
</template>
|
|
28
|
+
</range-viewer>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup>
|
|
35
|
+
|
|
36
|
+
import ScheduleCard from '../components/ScheduleCard.vue'
|
|
37
|
+
import IntervalCard from '../components/IntervalCard.vue'
|
|
38
|
+
import Select from 'primevue/select'
|
|
39
|
+
import InputText from 'primevue/inputtext'
|
|
40
|
+
import InputNumber from 'primevue/inputnumber'
|
|
41
|
+
import Dropdown from 'primevue/dropdown'
|
|
42
|
+
import Button from 'primevue/button'
|
|
43
|
+
|
|
44
|
+
import AdminTopMenu from '../components/AdminTopMenu.vue'
|
|
45
|
+
import { ActionForm } from '@live-change/frontend-auto-form'
|
|
46
|
+
|
|
47
|
+
import { useI18n } from 'vue-i18n'
|
|
48
|
+
const { t } = useI18n()
|
|
49
|
+
|
|
50
|
+
import { ref, computed } from 'vue'
|
|
51
|
+
import { RangeViewer } from "@live-change/vue3-components"
|
|
52
|
+
|
|
53
|
+
import { inject } from 'vue'
|
|
54
|
+
const workingZone = inject('workingZone')
|
|
55
|
+
|
|
56
|
+
import { usePath, live, useClient, useActions, reverseRange, useApi } from '@live-change/vue3-ssr'
|
|
57
|
+
const path = usePath()
|
|
58
|
+
const client = useClient()
|
|
59
|
+
const actions = useActions()
|
|
60
|
+
const api = useApi()
|
|
61
|
+
|
|
62
|
+
const intervalsPathFunction = computed(() => (range) =>
|
|
63
|
+
path.cron.intervals({ ...reverseRange(range) })
|
|
64
|
+
.with(interval => path.cron.intervalInfo({ interval: interval.id }).bind('info'))
|
|
65
|
+
.with(interval => path.cron.runState({ jobType: 'cron_Interval', job: interval.id }).bind('runState'))
|
|
66
|
+
.with(interval => path.task.tasksByCauseAndCreatedAt({
|
|
67
|
+
causeType: 'cron_Interval', cause: interval.id, reverse: true, limit: 5
|
|
68
|
+
}).bind('tasks'))
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const services = computed(() => Object.keys(api.metadata.api.value.services))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="w-full px-4 py-5">
|
|
3
|
+
|
|
4
|
+
<AdminTopMenu />
|
|
5
|
+
|
|
6
|
+
<div class="bg-surface-0 dark:bg-surface-900 p-3 shadow mb-1">
|
|
7
|
+
<ActionForm
|
|
8
|
+
service="cron"
|
|
9
|
+
action="setSchedule" />
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
<div class="bg-surface-0 dark:bg-surface-900 p-3 shadow mb-1">
|
|
15
|
+
<h3>{{ t('cron.schedules') }}</h3>
|
|
16
|
+
<range-viewer :pathFunction="schedulesPathFunction" key="schedules"
|
|
17
|
+
:canLoadTop="false" :canDropBottom="false"
|
|
18
|
+
loadBottomSensorSize="3000px" dropBottomSensorSize="12000px">
|
|
19
|
+
<template #empty>
|
|
20
|
+
<div class="bg-surface-0 p-3 shadow text-center text-gray-500 text-lg">
|
|
21
|
+
No schedules found...
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<template #default="{ item: schedule }">
|
|
26
|
+
<ScheduleCard :schedule="schedule" class="mt-1" />
|
|
27
|
+
</template>
|
|
28
|
+
</range-viewer>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup>
|
|
35
|
+
|
|
36
|
+
import ScheduleCard from '../components/ScheduleCard.vue'
|
|
37
|
+
import IntervalCard from '../components/IntervalCard.vue'
|
|
38
|
+
import Select from 'primevue/select'
|
|
39
|
+
import InputText from 'primevue/inputtext'
|
|
40
|
+
import InputNumber from 'primevue/inputnumber'
|
|
41
|
+
import Dropdown from 'primevue/dropdown'
|
|
42
|
+
import Button from 'primevue/button'
|
|
43
|
+
|
|
44
|
+
import AdminTopMenu from '../components/AdminTopMenu.vue'
|
|
45
|
+
import { ActionForm } from '@live-change/frontend-auto-form'
|
|
46
|
+
|
|
47
|
+
import { useI18n } from 'vue-i18n'
|
|
48
|
+
const { t } = useI18n()
|
|
49
|
+
|
|
50
|
+
import { ref, computed } from 'vue'
|
|
51
|
+
import { RangeViewer } from "@live-change/vue3-components"
|
|
52
|
+
|
|
53
|
+
import { inject } from 'vue'
|
|
54
|
+
const workingZone = inject('workingZone')
|
|
55
|
+
|
|
56
|
+
import { usePath, live, useClient, useActions, reverseRange, useApi } from '@live-change/vue3-ssr'
|
|
57
|
+
const path = usePath()
|
|
58
|
+
const client = useClient()
|
|
59
|
+
const actions = useActions()
|
|
60
|
+
const api = useApi()
|
|
61
|
+
|
|
62
|
+
const schedulesPathFunction = computed(() => (range) =>
|
|
63
|
+
path.cron.schedules({ ...reverseRange(range) })
|
|
64
|
+
.with(schedule => path.cron.scheduleInfo({ schedule: schedule.id }).bind('info'))
|
|
65
|
+
.with(schedule => path.cron.runState({ jobType: 'cron_Schedule', job: schedule.id }).bind('runState'))
|
|
66
|
+
.with(schedule => path.task.tasksByCauseAndCreatedAt({
|
|
67
|
+
causeType: 'cron_Schedule', cause: schedule.id, reverse: true, limit: 5
|
|
68
|
+
}).bind('tasks'))
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const services = computed(() => Object.keys(api.metadata.api.value.services))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
</script>
|
package/front/src/router.js
CHANGED
|
@@ -3,7 +3,7 @@ export function taskAdminRoutes(config = {}) {
|
|
|
3
3
|
return [
|
|
4
4
|
|
|
5
5
|
route({
|
|
6
|
-
name: 'task:admin', path: prefix, meta: { },
|
|
6
|
+
name: 'task:admin', path: prefix, meta: { requireRoles: ['admin'] },
|
|
7
7
|
component: () => import("./pages/TaskAdmin.vue"),
|
|
8
8
|
props: true
|
|
9
9
|
}),
|
|
@@ -14,10 +14,20 @@ export function cronAdminRoutes(config = {}) {
|
|
|
14
14
|
const { prefix = '/', route = (r) => r } = config
|
|
15
15
|
return [
|
|
16
16
|
route({
|
|
17
|
-
name: 'cron:admin', path: prefix, meta: { },
|
|
17
|
+
name: 'cron:admin', path: prefix, meta: { requireRoles: ['admin'] },
|
|
18
18
|
component: () => import("./pages/CronAdmin.vue"),
|
|
19
19
|
props: true
|
|
20
20
|
}),
|
|
21
|
+
route({
|
|
22
|
+
name: 'cron:admin:intervals', path: prefix+'/intervals', meta: { requireRoles: ['admin'] },
|
|
23
|
+
component: () => import("./pages/CronIntervalsAdmin.vue"),
|
|
24
|
+
props: true
|
|
25
|
+
}),
|
|
26
|
+
route({
|
|
27
|
+
name: 'cron:admin:schedules', path: prefix+'/schedules', meta: { requireRoles: ['admin'] },
|
|
28
|
+
component: () => import("./pages/CronSchedulesAdmin.vue"),
|
|
29
|
+
props: true
|
|
30
|
+
}),
|
|
21
31
|
]
|
|
22
32
|
}
|
|
23
33
|
|
|
@@ -56,8 +66,8 @@ export function routes(config = {}) {
|
|
|
56
66
|
|
|
57
67
|
...contentEditRoutes({ ...config }),
|
|
58
68
|
|
|
59
|
-
...dbAdminRoutes({ prefix: '/_db', route: r => ({ ...r, meta: { ...r.meta,
|
|
60
|
-
...taskAdminRoutes({ prefix: '/_task', route: r => ({ ...r, meta: { ...r.meta,
|
|
69
|
+
...dbAdminRoutes({ prefix: '/_db', route: r => ({ ...r, meta: { ...r.meta, pageType: 'wide' }}) }),
|
|
70
|
+
...taskAdminRoutes({ prefix: '/_task', route: r => ({ ...r, meta: { ...r.meta, pageType: 'wide' }}) }),
|
|
61
71
|
...cronAdminRoutes({ prefix: '/_cron', route: r => ({ ...r, meta: { ...r.meta, pageType: 'wide' }}) }),
|
|
62
72
|
...catchAllPagesRoute({ ...config }),
|
|
63
73
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/task-frontend",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.165",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"memDev": "tsx --inspect --expose-gc server/start.js memDev --enableSessions --initScript ./init.js --dbAccess",
|
|
6
6
|
"localDevInit": "tsx server/start.js localDev --enableSessions --initScript ./init.js --dbAccess",
|
|
@@ -37,43 +37,43 @@
|
|
|
37
37
|
"@codemirror/language": "6.10.1",
|
|
38
38
|
"@dotenvx/dotenvx": "0.27.0",
|
|
39
39
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
|
40
|
-
"@live-change/access-control-frontend": "^0.9.
|
|
41
|
-
"@live-change/access-control-service": "^0.9.
|
|
42
|
-
"@live-change/backup-service": "^0.9.
|
|
43
|
-
"@live-change/blog-frontend": "^0.9.
|
|
44
|
-
"@live-change/blog-service": "^0.9.
|
|
45
|
-
"@live-change/cli": "^0.9.
|
|
46
|
-
"@live-change/content-frontend": "^0.9.
|
|
47
|
-
"@live-change/content-service": "^0.9.
|
|
48
|
-
"@live-change/cron-service": "^0.9.
|
|
49
|
-
"@live-change/dao": "^0.9.
|
|
50
|
-
"@live-change/dao-vue3": "^0.9.
|
|
51
|
-
"@live-change/dao-websocket": "^0.9.
|
|
52
|
-
"@live-change/db-client": "^0.9.
|
|
53
|
-
"@live-change/draft-service": "^0.9.
|
|
54
|
-
"@live-change/email-service": "^0.9.
|
|
55
|
-
"@live-change/framework": "^0.9.
|
|
56
|
-
"@live-change/frontend-auto-form": "^0.9.
|
|
57
|
-
"@live-change/frontend-base": "^0.9.
|
|
58
|
-
"@live-change/geoip-service": "^0.9.
|
|
59
|
-
"@live-change/image-frontend": "^0.9.
|
|
60
|
-
"@live-change/locale-settings-service": "^0.9.
|
|
61
|
-
"@live-change/password-authentication-service": "^0.9.
|
|
62
|
-
"@live-change/prosemirror-service": "^0.9.
|
|
63
|
-
"@live-change/secret-code-service": "^0.9.
|
|
64
|
-
"@live-change/secret-link-service": "^0.9.
|
|
65
|
-
"@live-change/session-service": "^0.9.
|
|
66
|
-
"@live-change/task-service": "^0.9.
|
|
67
|
-
"@live-change/upload-frontend": "^0.9.
|
|
68
|
-
"@live-change/url-frontend": "^0.9.
|
|
69
|
-
"@live-change/url-service": "^0.9.
|
|
70
|
-
"@live-change/user-frontend": "^0.9.
|
|
71
|
-
"@live-change/user-identification-service": "^0.9.
|
|
72
|
-
"@live-change/user-service": "^0.9.
|
|
73
|
-
"@live-change/vote-service": "^0.9.
|
|
74
|
-
"@live-change/vue3-components": "^0.9.
|
|
75
|
-
"@live-change/vue3-ssr": "^0.9.
|
|
76
|
-
"@live-change/wysiwyg-frontend": "^0.9.
|
|
40
|
+
"@live-change/access-control-frontend": "^0.9.165",
|
|
41
|
+
"@live-change/access-control-service": "^0.9.165",
|
|
42
|
+
"@live-change/backup-service": "^0.9.165",
|
|
43
|
+
"@live-change/blog-frontend": "^0.9.165",
|
|
44
|
+
"@live-change/blog-service": "^0.9.165",
|
|
45
|
+
"@live-change/cli": "^0.9.165",
|
|
46
|
+
"@live-change/content-frontend": "^0.9.165",
|
|
47
|
+
"@live-change/content-service": "^0.9.165",
|
|
48
|
+
"@live-change/cron-service": "^0.9.165",
|
|
49
|
+
"@live-change/dao": "^0.9.165",
|
|
50
|
+
"@live-change/dao-vue3": "^0.9.165",
|
|
51
|
+
"@live-change/dao-websocket": "^0.9.165",
|
|
52
|
+
"@live-change/db-client": "^0.9.165",
|
|
53
|
+
"@live-change/draft-service": "^0.9.165",
|
|
54
|
+
"@live-change/email-service": "^0.9.165",
|
|
55
|
+
"@live-change/framework": "^0.9.165",
|
|
56
|
+
"@live-change/frontend-auto-form": "^0.9.165",
|
|
57
|
+
"@live-change/frontend-base": "^0.9.165",
|
|
58
|
+
"@live-change/geoip-service": "^0.9.165",
|
|
59
|
+
"@live-change/image-frontend": "^0.9.165",
|
|
60
|
+
"@live-change/locale-settings-service": "^0.9.165",
|
|
61
|
+
"@live-change/password-authentication-service": "^0.9.165",
|
|
62
|
+
"@live-change/prosemirror-service": "^0.9.165",
|
|
63
|
+
"@live-change/secret-code-service": "^0.9.165",
|
|
64
|
+
"@live-change/secret-link-service": "^0.9.165",
|
|
65
|
+
"@live-change/session-service": "^0.9.165",
|
|
66
|
+
"@live-change/task-service": "^0.9.165",
|
|
67
|
+
"@live-change/upload-frontend": "^0.9.165",
|
|
68
|
+
"@live-change/url-frontend": "^0.9.165",
|
|
69
|
+
"@live-change/url-service": "^0.9.165",
|
|
70
|
+
"@live-change/user-frontend": "^0.9.165",
|
|
71
|
+
"@live-change/user-identification-service": "^0.9.165",
|
|
72
|
+
"@live-change/user-service": "^0.9.165",
|
|
73
|
+
"@live-change/vote-service": "^0.9.165",
|
|
74
|
+
"@live-change/vue3-components": "^0.9.165",
|
|
75
|
+
"@live-change/vue3-ssr": "^0.9.165",
|
|
76
|
+
"@live-change/wysiwyg-frontend": "^0.9.165",
|
|
77
77
|
"@vueuse/core": "^12.3.0",
|
|
78
78
|
"codeceptjs-assert": "^0.0.5",
|
|
79
79
|
"compression": "^1.7.5",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"vue3-scroll-border": "0.1.7"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
|
-
"@live-change/codeceptjs-helper": "^0.9.
|
|
98
|
+
"@live-change/codeceptjs-helper": "^0.9.165",
|
|
99
99
|
"codeceptjs": "^3.6.10",
|
|
100
100
|
"generate-password": "1.7.1",
|
|
101
101
|
"playwright": "1.49.1",
|
|
@@ -106,5 +106,5 @@
|
|
|
106
106
|
"author": "Michał Łaszczewski <michal@laszczewski.pl>",
|
|
107
107
|
"license": "ISC",
|
|
108
108
|
"description": "",
|
|
109
|
-
"gitHead": "
|
|
109
|
+
"gitHead": "687dab16bd0c6594544cdab70cd7eecc17bb510c"
|
|
110
110
|
}
|
package/server/app.config.js
CHANGED
package/server/services.list.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import timer from '@live-change/timer-service'
|
|
1
2
|
import session from '@live-change/session-service'
|
|
2
3
|
import user from '@live-change/user-service'
|
|
3
4
|
import email from '@live-change/email-service'
|
|
@@ -25,6 +26,7 @@ import cron from '@live-change/cron-service'
|
|
|
25
26
|
import init from './init.js'
|
|
26
27
|
|
|
27
28
|
export {
|
|
29
|
+
timer,
|
|
28
30
|
session,
|
|
29
31
|
user,
|
|
30
32
|
email,
|
package/server/testTasks.js
CHANGED
|
@@ -22,12 +22,45 @@ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
|
22
22
|
|
|
23
23
|
const workerQueue = new PQueue({ concurrency: workersCount })
|
|
24
24
|
|
|
25
|
+
const woodTypes = ['oak', 'birch', 'spruce', 'acacia']
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
const wait = task({
|
|
29
|
+
name: 'wait',
|
|
30
|
+
properties: {
|
|
31
|
+
duration: {
|
|
32
|
+
type: Number,
|
|
33
|
+
validation: ['nonEmpty', 'integer'],
|
|
34
|
+
input: 'integer',
|
|
35
|
+
inputConfig: {
|
|
36
|
+
attributes: {
|
|
37
|
+
suffix: ' ms',
|
|
38
|
+
showButtons: true,
|
|
39
|
+
step: 1000,
|
|
40
|
+
min: 0,
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
defaultValue: 10000
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
returns: {
|
|
47
|
+
type: 'void',
|
|
48
|
+
},
|
|
49
|
+
async execute({ duration }, { service, task }, emit) {
|
|
50
|
+
console.log("WAIT", duration)
|
|
51
|
+
await sleep(duration)
|
|
52
|
+
}
|
|
53
|
+
}, definition)
|
|
54
|
+
|
|
55
|
+
|
|
25
56
|
const getWood = task({
|
|
26
57
|
name: 'getWood',
|
|
27
58
|
properties: {
|
|
28
59
|
woodType: {
|
|
29
60
|
type: String,
|
|
30
|
-
validation: ['nonEmpty']
|
|
61
|
+
validation: ['nonEmpty'],
|
|
62
|
+
options: woodTypes,
|
|
63
|
+
input: 'select'
|
|
31
64
|
},
|
|
32
65
|
},
|
|
33
66
|
returns: {
|
|
@@ -39,6 +72,7 @@ const getWood = task({
|
|
|
39
72
|
}
|
|
40
73
|
},
|
|
41
74
|
async execute({ woodType }, { service, task }, emit) {
|
|
75
|
+
console.log("GET WOOD", woodType)
|
|
42
76
|
task.progress(0, 1, 'finding tree')
|
|
43
77
|
await sleep(workDuration)
|
|
44
78
|
if(Math.random() < 0.1) {
|
|
@@ -61,7 +95,9 @@ const cutWood = task({
|
|
|
61
95
|
type: 'Wood',
|
|
62
96
|
properties: {
|
|
63
97
|
woodType: {
|
|
64
|
-
type: String
|
|
98
|
+
type: String,
|
|
99
|
+
options: woodTypes,
|
|
100
|
+
input: 'select'
|
|
65
101
|
}
|
|
66
102
|
}
|
|
67
103
|
},
|