mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-29 18:00:51 +01:00
updated frontend
This commit is contained in:
54
frontend/src/components/task/CreateTaskDialog.vue
Normal file
54
frontend/src/components/task/CreateTaskDialog.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<CreateEditDialog
|
||||
:action-functions="actionFunctions"
|
||||
:batch-form-data="formList"
|
||||
:batch-form-fields="batchFormFields"
|
||||
:confirm-disabled="confirmDisabled"
|
||||
:confirm-loading="confirmLoading"
|
||||
:tab-name="createEditDialogTabName"
|
||||
:type="activeDialogKey"
|
||||
:visible="createEditDialogVisible"
|
||||
:no-batch="noBatch"
|
||||
:title="title"
|
||||
>
|
||||
<template #default>
|
||||
<TaskForm/>
|
||||
</template>
|
||||
</CreateEditDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue';
|
||||
import {useStore} from 'vuex';
|
||||
import CreateEditDialog from '@/components/dialog/CreateEditDialog.vue';
|
||||
import useTask from '@/components/task/task';
|
||||
import TaskForm from '@/components/task/TaskForm.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CreateTaskDialog',
|
||||
components: {
|
||||
TaskForm,
|
||||
CreateEditDialog,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
noBatch: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
// store
|
||||
const store = useStore();
|
||||
|
||||
return {
|
||||
...useTask(store),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
314
frontend/src/components/task/TaskForm.vue
Normal file
314
frontend/src/components/task/TaskForm.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<Form v-if="form" ref="formRef" :model="form" class="task-form">
|
||||
<!-- Row -->
|
||||
<FormItem :offset="2" :span="2" label="Spider" prop="spider_id">
|
||||
<el-select
|
||||
v-model="form.spider_id"
|
||||
:disabled="isFormItemDisabled('spider_id') || readonly"
|
||||
>
|
||||
<el-option
|
||||
v-for="op in allSpiderSelectOptions"
|
||||
:key="op.value"
|
||||
:label="op.label"
|
||||
:value="op.value"
|
||||
/>
|
||||
</el-select>
|
||||
<FaIconButton
|
||||
v-if="readonly"
|
||||
:icon="['fa', 'external-link-alt']"
|
||||
class="nav-btn"
|
||||
tooltip="Go to Spider"
|
||||
@click="onGoToSpider"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- ./Row -->
|
||||
|
||||
<!-- Row -->
|
||||
<FormItem v-if="readonly" :offset="2" :span="2" label="Node" prop="node_id">
|
||||
<el-input v-if="noNodeId" disabled placeholder="Unassigned"/>
|
||||
<el-select
|
||||
v-else
|
||||
v-model="form.node_id"
|
||||
disabled
|
||||
>
|
||||
<el-option
|
||||
v-for="op in allNodeSelectOptions"
|
||||
:key="op.value"
|
||||
:label="op.label"
|
||||
:value="op.value"
|
||||
/>
|
||||
</el-select>
|
||||
<FaIconButton
|
||||
v-if="readonly"
|
||||
:icon="['fa', 'external-link-alt']"
|
||||
class="nav-btn"
|
||||
tooltip="Go to Spider"
|
||||
:disabled="noNodeId"
|
||||
@click="onGoToNode"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- ./Row -->
|
||||
|
||||
<!-- Row -->
|
||||
<FormItem v-if="readonly" :span="4" label="Status" prop="status">
|
||||
<TaskStatus :status="form.status" size="small"/>
|
||||
<Tag
|
||||
v-if="form.status === 'error'"
|
||||
:icon="['fa', 'exclamation']"
|
||||
:label="form.error"
|
||||
class="error-message"
|
||||
size="small"
|
||||
tooltip="Task error message"
|
||||
type="danger"
|
||||
/>
|
||||
<Tag
|
||||
v-else-if="cancellable"
|
||||
:icon="['fa', 'pause']"
|
||||
class="cancel-btn"
|
||||
clickable
|
||||
label="Cancel"
|
||||
size="small"
|
||||
tooltip="Cancel task"
|
||||
type="info"
|
||||
@click="onCancel"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- ./Row -->
|
||||
|
||||
<!-- Row -->
|
||||
<FormItem :span="2" label="Command" prop="cmd" required>
|
||||
<InputWithButton
|
||||
v-model="form.cmd"
|
||||
:button-icon="['fa', 'edit']"
|
||||
:disabled="isFormItemDisabled('cmd') || readonly"
|
||||
button-label="Edit"
|
||||
placeholder="Command"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem :span="2" label="Param" prop="param">
|
||||
<InputWithButton
|
||||
v-model="form.param"
|
||||
:button-icon="['fa', 'edit']"
|
||||
:disabled="isFormItemDisabled('param') || readonly"
|
||||
button-label="Edit"
|
||||
placeholder="Params"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- ./Row -->
|
||||
|
||||
<!-- Row -->
|
||||
<FormItem :span="2" label="Mode" prop="mode" required>
|
||||
<el-select
|
||||
v-model="form.mode"
|
||||
:disabled="isFormItemDisabled('mode') || readonly"
|
||||
>
|
||||
<el-option
|
||||
v-for="op in modeOptions"
|
||||
:key="op.value"
|
||||
:label="op.label"
|
||||
:value="op.value"
|
||||
/>
|
||||
</el-select>
|
||||
</FormItem>
|
||||
<FormItem :span="2" label="Priority" prop="priority" required>
|
||||
<el-select
|
||||
v-model="form.priority"
|
||||
:disabled="isFormItemDisabled('priority') || readonly"
|
||||
>
|
||||
<el-option
|
||||
v-for="op in priorityOptions"
|
||||
:key="op.value"
|
||||
:label="op.label"
|
||||
:value="op.value"
|
||||
/>
|
||||
</el-select>
|
||||
</FormItem>
|
||||
<!-- ./Row -->
|
||||
|
||||
<FormItem
|
||||
v-if="form.mode === TASK_MODE_SELECTED_NODE_TAGS"
|
||||
:span="4"
|
||||
label="Selected Tags"
|
||||
prop="node_tags"
|
||||
required
|
||||
>
|
||||
<CheckTagGroup
|
||||
v-model="form.node_tags"
|
||||
:disabled="isFormItemDisabled('node_tags') || readonly"
|
||||
:options="allNodeTags"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
v-if="[TASK_MODE_SELECTED_NODES, TASK_MODE_SELECTED_NODE_TAGS].includes(form.mode)"
|
||||
:span="4"
|
||||
label="Selected Nodes"
|
||||
required
|
||||
>
|
||||
<CheckTagGroup
|
||||
v-model="form.node_ids"
|
||||
:disabled="(form.mode === TASK_MODE_SELECTED_NODE_TAGS && isFormItemDisabled('node_ids')) || readonly"
|
||||
:options="allNodeSelectOptions"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {computed, defineComponent, watch} from 'vue';
|
||||
import {useStore} from 'vuex';
|
||||
import useSpider from '@/components/spider/spider';
|
||||
import useNode from '@/components/node/node';
|
||||
import Form from '@/components/form/Form.vue';
|
||||
import FormItem from '@/components/form/FormItem.vue';
|
||||
import InputWithButton from '@/components/input/InputWithButton.vue';
|
||||
import CheckTagGroup from '@/components/tag/CheckTagGroup.vue';
|
||||
import {TASK_MODE_SELECTED_NODE_TAGS, TASK_MODE_SELECTED_NODES} from '@/constants/task';
|
||||
import useRequest from '@/services/request';
|
||||
import useTask from '@/components/task/task';
|
||||
import TaskStatus from '@/components/task/TaskStatus.vue';
|
||||
import Tag from '@/components/tag/Tag.vue';
|
||||
import FaIconButton from '@/components/button/FaIconButton.vue';
|
||||
import {useRouter} from 'vue-router';
|
||||
import {isCancellable} from '@/utils/task';
|
||||
import {ElMessage, ElMessageBox} from 'element-plus';
|
||||
import {isZeroObjectId} from '@/utils/mongo';
|
||||
import useTaskDetail from '@/views/task/detail/taskDetail';
|
||||
|
||||
const {
|
||||
post,
|
||||
} = useRequest();
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TaskForm',
|
||||
components: {
|
||||
FaIconButton,
|
||||
Tag,
|
||||
TaskStatus,
|
||||
Form,
|
||||
FormItem,
|
||||
InputWithButton,
|
||||
CheckTagGroup,
|
||||
},
|
||||
props: {
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
// router
|
||||
const router = useRouter();
|
||||
|
||||
// store
|
||||
const ns = 'task';
|
||||
const store = useStore();
|
||||
|
||||
// use node
|
||||
const {
|
||||
allListSelectOptionsWithEmpty: allNodeSelectOptions,
|
||||
allTags: allNodeTags,
|
||||
} = useNode(store);
|
||||
|
||||
// use spider
|
||||
const {
|
||||
allListSelectOptions: allSpiderSelectOptions,
|
||||
} = useSpider(store);
|
||||
|
||||
// use task
|
||||
const {
|
||||
form,
|
||||
allSpiderDict,
|
||||
modeOptionsDict,
|
||||
} = useTask(store);
|
||||
|
||||
// use task detail
|
||||
const {
|
||||
activeId,
|
||||
} = useTaskDetail();
|
||||
|
||||
// use request
|
||||
const {
|
||||
get,
|
||||
} = useRequest();
|
||||
|
||||
// watch spider id
|
||||
watch(() => {
|
||||
const task = form.value as Task;
|
||||
return task.spider_id;
|
||||
}, async () => {
|
||||
const task = form.value as Task;
|
||||
if (!task.spider_id) return;
|
||||
const res = await get<any, Spider>(`/spiders/${task.spider_id}`);
|
||||
task.cmd = res.data.cmd;
|
||||
task.param = res.data.param;
|
||||
});
|
||||
|
||||
const getSpiderName = (id: string) => {
|
||||
const spider = allSpiderDict.value.get(id) as Spider;
|
||||
return spider?.name;
|
||||
};
|
||||
|
||||
const getModeName = (id: string) => {
|
||||
const op = modeOptionsDict.value.get(id) as SelectOption;
|
||||
return op?.label;
|
||||
};
|
||||
|
||||
const onGoToSpider = () => {
|
||||
router.push(`/spiders/${form.value.spider_id}`);
|
||||
};
|
||||
|
||||
const onGoToNode = () => {
|
||||
router.push(`/nodes/${form.value.node_id}`);
|
||||
};
|
||||
|
||||
const cancellable = computed<boolean>(() => isCancellable(form.value.status));
|
||||
|
||||
const onCancel = async () => {
|
||||
await ElMessageBox.confirm('Are you sure to cancel?', 'Cancel', {type: 'warning'});
|
||||
await ElMessage.info('Attempt to cancel');
|
||||
try {
|
||||
await post(`/tasks/${activeId.value}/cancel`);
|
||||
} finally {
|
||||
await store.dispatch(`${ns}/getById`, activeId.value);
|
||||
}
|
||||
};
|
||||
|
||||
const noNodeId = computed<boolean>(() => isZeroObjectId(form.value.node_id));
|
||||
|
||||
return {
|
||||
...useTask(store),
|
||||
|
||||
// custom
|
||||
TASK_MODE_SELECTED_NODES,
|
||||
TASK_MODE_SELECTED_NODE_TAGS,
|
||||
allNodeSelectOptions,
|
||||
allNodeTags,
|
||||
allSpiderSelectOptions,
|
||||
getSpiderName,
|
||||
getModeName,
|
||||
onGoToSpider,
|
||||
onGoToNode,
|
||||
cancellable,
|
||||
onCancel,
|
||||
noNodeId,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.task-form >>> .nav-btn {
|
||||
position: absolute;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.task-form >>> .error-message,
|
||||
.task-form >>> .cancel-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.task-form >>> .cancel-btn:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
112
frontend/src/components/task/TaskMode.vue
Normal file
112
frontend/src/components/task/TaskMode.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<el-tooltip :content="tooltip">
|
||||
<el-tag :type="type" class="task-mode" size="mini">
|
||||
<font-awesome-icon :icon="icon" class="icon"/>
|
||||
<span>{{ label }}</span>
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {computed, defineComponent} from 'vue';
|
||||
import {
|
||||
TASK_MODE_ALL,
|
||||
TASK_MODE_RANDOM,
|
||||
TASK_MODE_SELECTED_NODE_TAGS,
|
||||
TASK_MODE_SELECTED_NODES
|
||||
} from '@/constants/task';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TaskMode',
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
setup(props: TaskModeProps, {emit}) {
|
||||
const type = computed<string>(() => {
|
||||
const {mode} = props;
|
||||
switch (mode) {
|
||||
case TASK_MODE_RANDOM:
|
||||
return 'warning';
|
||||
case TASK_MODE_ALL:
|
||||
return 'success';
|
||||
case TASK_MODE_SELECTED_NODES:
|
||||
return 'primary';
|
||||
case TASK_MODE_SELECTED_NODE_TAGS:
|
||||
return 'primary';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
});
|
||||
|
||||
const label = computed<string>(() => {
|
||||
const {mode} = props;
|
||||
switch (mode) {
|
||||
case TASK_MODE_RANDOM:
|
||||
return 'Random';
|
||||
case TASK_MODE_ALL:
|
||||
return 'All Nodes';
|
||||
case TASK_MODE_SELECTED_NODES:
|
||||
return 'Nodes';
|
||||
case TASK_MODE_SELECTED_NODE_TAGS:
|
||||
return 'Tags';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
});
|
||||
|
||||
const icon = computed<string[]>(() => {
|
||||
const {mode} = props;
|
||||
switch (mode) {
|
||||
case TASK_MODE_RANDOM:
|
||||
return ['fa', 'random'];
|
||||
case TASK_MODE_ALL:
|
||||
return ['fa', 'sitemap'];
|
||||
case TASK_MODE_SELECTED_NODES:
|
||||
return ['fa', 'network-wired'];
|
||||
case TASK_MODE_SELECTED_NODE_TAGS:
|
||||
return ['fa', 'tags'];
|
||||
default:
|
||||
return ['fa', 'question'];
|
||||
}
|
||||
});
|
||||
|
||||
const tooltip = computed<string>(() => {
|
||||
const {mode} = props;
|
||||
switch (mode) {
|
||||
case TASK_MODE_RANDOM:
|
||||
return 'Run on a random node';
|
||||
case TASK_MODE_ALL:
|
||||
return 'Run on all nodes';
|
||||
case TASK_MODE_SELECTED_NODES:
|
||||
return 'Run on selected nodes';
|
||||
case TASK_MODE_SELECTED_NODE_TAGS:
|
||||
return 'Run on nodes with selected tags';
|
||||
default:
|
||||
return 'Unknown task mode';
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
type,
|
||||
label,
|
||||
icon,
|
||||
tooltip,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.task-mode {
|
||||
width: 80px;
|
||||
cursor: default;
|
||||
|
||||
.icon {
|
||||
width: 10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
80
frontend/src/components/task/TaskPriority.vue
Normal file
80
frontend/src/components/task/TaskPriority.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<Tag
|
||||
:key="data"
|
||||
:color="data.color"
|
||||
:icon="data.icon"
|
||||
:label="data.label"
|
||||
:size="size"
|
||||
:spinning="data.spinning"
|
||||
:tooltip="data.tooltip"
|
||||
:type="data.type"
|
||||
@click="$emit('click')"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import colors from '@/styles/color.scss';
|
||||
import {computed, defineComponent, PropType} from 'vue';
|
||||
import Tag from '@/components/tag/Tag.vue';
|
||||
import {getPriorityLabel} from '@/utils/task';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TaskPriority',
|
||||
components: {
|
||||
Tag,
|
||||
},
|
||||
props: {
|
||||
priority: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 5,
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<BasicSize>,
|
||||
required: false,
|
||||
default: 'mini',
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props: TaskPriorityProps, {emit}) {
|
||||
const data = computed<TagData>(() => {
|
||||
const priority = props.priority as number;
|
||||
|
||||
if (priority <= 2) {
|
||||
return {
|
||||
label: getPriorityLabel(priority),
|
||||
color: colors.red,
|
||||
};
|
||||
} else if (priority <= 4) {
|
||||
return {
|
||||
label: getPriorityLabel(priority),
|
||||
color: colors.orange,
|
||||
};
|
||||
} else if (priority <= 6) {
|
||||
return {
|
||||
label: getPriorityLabel(priority),
|
||||
color: colors.limeGreen,
|
||||
};
|
||||
} else if (priority <= 8) {
|
||||
return {
|
||||
label: getPriorityLabel(priority),
|
||||
color: colors.cyan,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
label: getPriorityLabel(priority),
|
||||
color: colors.blue,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
93
frontend/src/components/task/TaskResults.vue
Normal file
93
frontend/src/components/task/TaskResults.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<Tag
|
||||
:key="data"
|
||||
:icon="data.icon"
|
||||
:label="data.label"
|
||||
:size="size"
|
||||
:spinning="data.spinning"
|
||||
:type="data.type"
|
||||
class="task-status"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<template #tooltip>
|
||||
<div v-html="data.tooltip"/>
|
||||
</template>
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {computed, defineComponent, PropType} from 'vue';
|
||||
import Tag from '@/components/tag/Tag.vue';
|
||||
import {isCancellable} from '@/utils/task';
|
||||
import {TASK_STATUS_PENDING} from '@/constants/task';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TaskResults',
|
||||
components: {
|
||||
Tag,
|
||||
},
|
||||
props: {
|
||||
results: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
status: {
|
||||
type: String as PropType<TaskStatus>,
|
||||
required: false,
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<BasicSize>,
|
||||
required: false,
|
||||
default: 'mini',
|
||||
},
|
||||
},
|
||||
setup(props: TaskResultsProps, {emit}) {
|
||||
const data = computed<TagData>(() => {
|
||||
const {results, status} = props;
|
||||
if (isCancellable(status)) {
|
||||
if (status === TASK_STATUS_PENDING) {
|
||||
return {
|
||||
label: results?.toFixed(0),
|
||||
tooltip: `Results: ${results}`,
|
||||
type: 'primary',
|
||||
icon: ['fa', 'hourglass-start'],
|
||||
spinning: true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
label: results?.toFixed(0),
|
||||
tooltip: `Results: ${results}`,
|
||||
type: 'warning',
|
||||
icon: ['fa', 'spinner'],
|
||||
spinning: true,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (results === 0) {
|
||||
return {
|
||||
label: results?.toFixed(0),
|
||||
tooltip: `No results`,
|
||||
type: 'danger',
|
||||
icon: ['fa', 'exclamation'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
label: results?.toFixed(0),
|
||||
tooltip: `Results: ${results}`,
|
||||
type: 'success',
|
||||
icon: ['fa', 'table'],
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
120
frontend/src/components/task/TaskStatus.vue
Normal file
120
frontend/src/components/task/TaskStatus.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<Tag
|
||||
class="task-status"
|
||||
:key="data"
|
||||
:icon="data.icon"
|
||||
:label="data.label"
|
||||
:spinning="data.spinning"
|
||||
:type="data.type"
|
||||
:size="size"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<template #tooltip>
|
||||
<div v-html="data.tooltip"/>
|
||||
</template>
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {computed, defineComponent, PropType} from 'vue';
|
||||
import {
|
||||
TASK_STATUS_ABNORMAL,
|
||||
TASK_STATUS_CANCELLED,
|
||||
TASK_STATUS_ERROR,
|
||||
TASK_STATUS_FINISHED,
|
||||
TASK_STATUS_PENDING,
|
||||
TASK_STATUS_RUNNING
|
||||
} from '@/constants/task';
|
||||
import Tag from '@/components/tag/Tag.vue';
|
||||
import colors from '@/styles/color.scss';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TaskStatus',
|
||||
components: {
|
||||
Tag,
|
||||
},
|
||||
props: {
|
||||
status: {
|
||||
type: String as PropType<TaskStatus>,
|
||||
required: false,
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<BasicSize>,
|
||||
required: false,
|
||||
default: 'mini',
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props: TaskStatusProps, {emit}) {
|
||||
const data = computed<TagData>(() => {
|
||||
const {status, error} = props;
|
||||
switch (status) {
|
||||
case TASK_STATUS_PENDING:
|
||||
return {
|
||||
label: 'Pending',
|
||||
tooltip: 'Task is pending in the queue',
|
||||
type: 'primary',
|
||||
icon: ['fa', 'hourglass-start'],
|
||||
spinning: true,
|
||||
};
|
||||
case TASK_STATUS_RUNNING:
|
||||
return {
|
||||
label: 'Running',
|
||||
tooltip: 'Task is currently running',
|
||||
type: 'warning',
|
||||
icon: ['fa', 'spinner'],
|
||||
spinning: true,
|
||||
};
|
||||
case TASK_STATUS_FINISHED:
|
||||
return {
|
||||
label: 'Finished',
|
||||
tooltip: 'Task finished successfully',
|
||||
type: 'success',
|
||||
icon: ['fa', 'check'],
|
||||
};
|
||||
case TASK_STATUS_ERROR:
|
||||
return {
|
||||
label: 'Error',
|
||||
tooltip: `Task ended with an error:<br><span style="color: ${colors.red}">${error}</span>`,
|
||||
type: 'danger',
|
||||
icon: ['fa', 'times'],
|
||||
};
|
||||
case TASK_STATUS_CANCELLED:
|
||||
return {
|
||||
label: 'Cancelled',
|
||||
tooltip: 'Task has been cancelled',
|
||||
type: 'info',
|
||||
icon: ['fa', 'ban'],
|
||||
};
|
||||
case TASK_STATUS_ABNORMAL:
|
||||
return {
|
||||
label: 'Cancelled',
|
||||
tooltip: 'Task ended abnormally',
|
||||
type: 'info',
|
||||
icon: ['fa', 'exclamation'],
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: 'Unknown',
|
||||
tooltip: 'Unknown task status',
|
||||
type: 'info',
|
||||
icon: ['fa', 'question'],
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.task-status {
|
||||
}
|
||||
</style>
|
||||
108
frontend/src/components/task/task.ts
Normal file
108
frontend/src/components/task/task.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import {useRoute} from 'vue-router';
|
||||
import {computed} from 'vue';
|
||||
import {TASK_MODE_RANDOM} from '@/constants/task';
|
||||
import {Store} from 'vuex';
|
||||
import useForm from '@/components/form/form';
|
||||
import useTaskService from '@/services/task/taskService';
|
||||
import {getDefaultFormComponentData} from '@/utils/form';
|
||||
import {FORM_FIELD_TYPE_INPUT_WITH_BUTTON, FORM_FIELD_TYPE_SELECT} from '@/constants/form';
|
||||
import useSpider from '@/components/spider/spider';
|
||||
import {getModeOptions, getModeOptionsDict, getPriorityLabel} from '@/utils/task';
|
||||
|
||||
// get new task
|
||||
export const getNewTask = (): Task => {
|
||||
return {
|
||||
mode: TASK_MODE_RANDOM,
|
||||
priority: 5,
|
||||
};
|
||||
};
|
||||
|
||||
// form component data
|
||||
const formComponentData = getDefaultFormComponentData<Task>(getNewTask);
|
||||
|
||||
const useTask = (store: Store<RootStoreState>) => {
|
||||
// store state
|
||||
const {
|
||||
task: state,
|
||||
} = store.state as RootStoreState;
|
||||
|
||||
// options for default mode
|
||||
const modeOptions = getModeOptions();
|
||||
const modeOptionsDict = computed(() => getModeOptionsDict());
|
||||
|
||||
// priority options
|
||||
const priorityOptions = (() => {
|
||||
const opts = [] as SelectOption[];
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
opts.push({
|
||||
label: getPriorityLabel(i),
|
||||
value: i,
|
||||
});
|
||||
}
|
||||
return opts;
|
||||
})();
|
||||
|
||||
const {
|
||||
allListSelectOptions: allSpiderListSelectOptions,
|
||||
allDict: allSpiderDict,
|
||||
} = useSpider(store);
|
||||
|
||||
// readonly form fields
|
||||
const readonlyFormFields = computed<string[]>(() => state.readonlyFormFields);
|
||||
|
||||
// batch form fields
|
||||
const batchFormFields = computed<FormTableField[]>(() => [
|
||||
{
|
||||
prop: 'spider_id',
|
||||
label: 'Spider',
|
||||
width: '150',
|
||||
placeholder: 'Spider',
|
||||
fieldType: FORM_FIELD_TYPE_SELECT,
|
||||
options: allSpiderListSelectOptions.value,
|
||||
disabled: () => readonlyFormFields.value.includes('spider_id'),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
prop: 'cmd',
|
||||
label: 'Execute Command',
|
||||
width: '200',
|
||||
placeholder: 'Execute Command',
|
||||
fieldType: FORM_FIELD_TYPE_INPUT_WITH_BUTTON,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
prop: 'param',
|
||||
label: 'Param',
|
||||
width: '200',
|
||||
placeholder: 'Param',
|
||||
fieldType: FORM_FIELD_TYPE_INPUT_WITH_BUTTON,
|
||||
},
|
||||
{
|
||||
prop: 'mode',
|
||||
label: 'Default Run Mode',
|
||||
width: '200',
|
||||
fieldType: FORM_FIELD_TYPE_SELECT,
|
||||
options: modeOptions,
|
||||
required: true,
|
||||
},
|
||||
]);
|
||||
|
||||
// route
|
||||
const route = useRoute();
|
||||
|
||||
// task id
|
||||
const id = computed(() => route.params.id);
|
||||
|
||||
return {
|
||||
...useForm('task', store, useTaskService(store), formComponentData),
|
||||
allSpiderDict,
|
||||
batchFormFields,
|
||||
id,
|
||||
modeOptions,
|
||||
modeOptionsDict,
|
||||
priorityOptions,
|
||||
getPriorityLabel,
|
||||
};
|
||||
};
|
||||
|
||||
export default useTask;
|
||||
Reference in New Issue
Block a user