201 lines
7.7 KiB
Vue
201 lines
7.7 KiB
Vue
<template>
|
|
<div class="columns is-multiline">
|
|
<div class="column is-12 is-clearfix is-unselectable">
|
|
<span class="title is-4">Tasks</span>
|
|
<div class="is-pulled-right">
|
|
<div class="field is-grouped">
|
|
<p class="control">
|
|
<button class="button is-info" @click="loadContent()" :disabled="isLoading"
|
|
:class="{'is-loading':isLoading}">
|
|
<span class="icon"><i class="fas fa-sync"></i></span>
|
|
</button>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="is-hidden-mobile">
|
|
<span class="subtitle">
|
|
This page contains all the tasks that are currently configured.
|
|
<template v-if="queued.length > 0">
|
|
<p>The following tasks <code>{{ queued.join(', ') }}</code> are queued to be run in background soon.</p>
|
|
</template>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="column is-12" v-if="isLoading">
|
|
<Message message_class="has-background-info-90 has-text-dark" title="Loading"
|
|
icon="fas fa-spinner fa-spin" message="Loading data. Please wait..."/>
|
|
</div>
|
|
|
|
<div v-for="task in tasks" :key="task.name" class="column is-6-tablet is-12-mobile">
|
|
<div class="card" :class="{ 'is-gray' : !task.enabled, 'is-success': task.enabled }">
|
|
<header class="card-header">
|
|
<div class="is-capitalized card-header-title">
|
|
{{ task.name }}
|
|
</div>
|
|
<span class="card-header-icon" v-tooltip="'Enable/Disable Task.'">
|
|
<input :id="task.name" type="checkbox" class="switch is-success" :checked="task.enabled"
|
|
@change="toggleTask(task)">
|
|
<label :for="task.name"></label>
|
|
</span>
|
|
</header>
|
|
<div class="card-content">
|
|
<div class="columns is-multiline is-mobile has-text-centered">
|
|
<div class="column is-12 has-text-left" v-if="task.description">
|
|
{{ task.description }}
|
|
</div>
|
|
<div class="column is-12 has-text-left">
|
|
<strong class="is-hidden-mobile">Runs: </strong>
|
|
<NuxtLink class="has-tooltip" :to="`/env?edit=WS_CRON_${task.name.toUpperCase()}_AT`">
|
|
{{ cronstrue.toString(task.timer) }}
|
|
</NuxtLink>
|
|
</div>
|
|
<div class="column is-6 has-text-left">
|
|
<strong class="is-hidden-mobile">Timer: </strong>
|
|
<NuxtLink class="has-tooltip" target="_blank"
|
|
:to="`https://crontab.guru/#${task.timer.replace(/ /g, '_')}`">
|
|
{{ task.timer }}
|
|
</NuxtLink>
|
|
</div>
|
|
<div class="column is-6 has-text-right" v-if="task.args">
|
|
<strong class="is-hidden-mobile">Args:</strong> <code>{{ task.args }}</code>
|
|
</div>
|
|
<div class="column is-6 has-text-left">
|
|
<strong class="is-hidden-mobile">Prev Run: </strong>
|
|
<template v-if="task.enabled">
|
|
{{ task.prev_run ? moment(task.prev_run).fromNow() : '???' }}
|
|
</template>
|
|
<template v-else>
|
|
<span class="tag is-danger">Disabled</span>
|
|
</template>
|
|
</div>
|
|
<div class="column is-6 has-text-right">
|
|
<strong class="is-hidden-mobile">Next Run: </strong>
|
|
<template v-if="task.enabled">
|
|
{{ task.next_run ? moment(task.next_run).fromNow() : 'Never' }}
|
|
</template>
|
|
<template v-else>
|
|
<span class="tag is-danger">Disabled</span>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<footer class="card-footer">
|
|
<div class="card-footer-item">
|
|
<button class="button is-info" @click="queueTask(task)" :disabled="task.queued">
|
|
<span class="icon-text">
|
|
<span class="icon"><i class="fas fa-clock"></i></span>
|
|
<span>
|
|
<template v-if="!task.queued">Queue Task</template>
|
|
<template v-else>Queued</template>
|
|
</span>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
<div class="card-footer-item">
|
|
<button class="button is-warning" @click="confirmRun(task)">
|
|
<span class="icon-text">
|
|
<span class="icon"><i class="fas fa-terminal"></i></span>
|
|
<span class="is-hidden-mobile">Run via console</span>
|
|
<span class="is-hidden-tablet">Run now</span>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="column is-12">
|
|
<Message message_class="has-background-info-90 has-text-dark" :toggle="show_page_tips"
|
|
@toggle="show_page_tips = !show_page_tips" :use-toggle="true" title="Tips" icon="fas fa-info-circle">
|
|
<ul>
|
|
<li>For long running tasks like <code>Import</code> and <code>Export</code>, you should queue the task to run
|
|
in background. As running them via web console will take longer if you have many backends and/or has large
|
|
libraries.
|
|
</li>
|
|
<li>Use the switch next to the task to enable or disable the task from being run automatically.</li>
|
|
<li>To change when task is scheduled to run, please visit
|
|
<NuxtLink to="/env" v-text="'Environment variables'"/>
|
|
page. The <code>WS_CRON_(TASK)_*</code> variables are used to control scheduled tasks.
|
|
</li>
|
|
<li>Clicking on the Runs link will take you directly to the environment variable for the task. While on the
|
|
timer link will take you to external page that will show for you more information about the cron timer
|
|
syntax.
|
|
</li>
|
|
</ul>
|
|
</Message>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import 'assets/css/bulma-switch.css'
|
|
import moment from 'moment'
|
|
import request from '~/utils/request.js'
|
|
import {notification} from '~/utils/index.js'
|
|
import cronstrue from 'cronstrue'
|
|
import Message from '~/components/Message.vue'
|
|
import {useStorage} from '@vueuse/core'
|
|
|
|
useHead({title: 'Tasks'})
|
|
|
|
const tasks = ref([])
|
|
const queued = ref([])
|
|
const isLoading = ref(false)
|
|
const show_page_tips = useStorage('show_page_tips', true)
|
|
|
|
const loadContent = async () => {
|
|
isLoading.value = true
|
|
tasks.value = []
|
|
try {
|
|
const response = await request('/tasks')
|
|
const json = await response.json()
|
|
tasks.value = json.tasks
|
|
queued.value = json.queued
|
|
} catch (e) {
|
|
notification('error', 'Error', `Request error. ${e.message}`)
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => loadContent())
|
|
|
|
const toggleTask = async task => {
|
|
try {
|
|
const keyName = `WS_CRON_${task.name.toUpperCase()}`
|
|
await request(`/system/env/${keyName}`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({"value": !task.enabled})
|
|
});
|
|
|
|
const response = await request(`/tasks/${task.name}`)
|
|
tasks.value[tasks.value.findIndex(b => b.name === task.name)] = await response.json()
|
|
} catch (e) {
|
|
notification('error', 'Error', `Request error. ${e.message}`)
|
|
}
|
|
}
|
|
|
|
const queueTask = async task => {
|
|
if (!confirm(`Queue '${task.name}' to run in background?`)) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = await request(`/tasks/${task.name}/queue`, {method: 'POST'})
|
|
if (response.ok) {
|
|
notification('success', 'Success', `Task ${task.name} has been queued.`)
|
|
await loadContent()
|
|
}
|
|
} catch (e) {
|
|
notification('error', 'Error', `Request error. ${e.message}`)
|
|
}
|
|
}
|
|
|
|
const confirmRun = async task => {
|
|
if (!confirm(`Are you sure you want to run '${task.name}' via web console now?`)) {
|
|
return
|
|
}
|
|
await navigateTo({path: '/console', query: {task: task.name, keep: 1}})
|
|
}
|
|
</script>
|