updated frontend

This commit is contained in:
marvzhang
2021-07-15 21:37:37 +08:00
parent db6414aa5b
commit db7920ac69
550 changed files with 27134 additions and 49444 deletions

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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;