fixed path case issue

This commit is contained in:
marvzhang
2021-07-16 14:09:25 +08:00
parent ee09515e71
commit 5bf1dc9b14
28 changed files with 0 additions and 2 deletions

View File

@@ -0,0 +1,45 @@
<template>
<CreateEditDialog
:type="activeDialogKey"
:tab-name="createEditDialogTabName"
:visible="createEditDialogVisible"
:action-functions="actionFunctions"
:batch-form-data="formList"
:confirm-disabled="confirmDisabled"
:confirm-loading="confirmLoading"
:batch-form-fields="batchFormFields"
:form-rules="formRules"
>
<template #default>
<SpiderForm/>
</template>
</CreateEditDialog>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import {useStore} from 'vuex';
import CreateEditDialog from '@/components/dialog/CreateEditDialog.vue';
import SpiderForm from '@/components/spider/SpiderForm.vue';
import useSpider from '@/components/spider/spider';
export default defineComponent({
name: 'CreateSpiderDialog',
components: {
CreateEditDialog,
SpiderForm,
},
setup() {
// store
const store = useStore();
return {
...useSpider(store),
};
},
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,186 @@
<template>
<Dialog
:title="title"
:visible="visible"
@close="onClose"
@confirm="onConfirm"
>
<Form
ref="formRef"
:model="options"
>
<!-- Row -->
<FormItem :span="2" label="Command" prop="cmd" required>
<InputWithButton
v-model="options.cmd"
:button-icon="['fa', 'edit']"
button-label="Edit"
placeholder="Command"
/>
</FormItem>
<FormItem :span="2" label="Param" prop="param">
<InputWithButton
v-model="options.param"
:button-icon="['fa', 'edit']"
button-label="Edit"
placeholder="Params"
/>
</FormItem>
<!-- ./Row -->
<!-- Row -->
<FormItem :span="2" label="Mode" prop="mode" required>
<el-select
v-model="options.mode"
>
<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="options.priority"
>
<el-option
v-for="op in priorityOptions"
:key="op.value"
:label="op.label"
:value="op.value"
/>
</el-select>
</FormItem>
<!-- ./Row -->
<FormItem
v-if="options.mode === TASK_MODE_SELECTED_NODE_TAGS"
:span="4"
label="Selected Tags"
prop="node_tags"
required
>
<CheckTagGroup
v-model="options.node_tags"
:options="allNodeTags"
/>
</FormItem>
<FormItem
v-if="[TASK_MODE_SELECTED_NODES, TASK_MODE_SELECTED_NODE_TAGS].includes(options.mode)"
:span="4"
label="Selected Nodes"
required
>
<CheckTagGroup
v-model="options.node_ids"
:options="allNodeSelectOptions"
/>
</FormItem>
</Form>
</Dialog>
</template>
<script lang="ts">
import {computed, defineComponent, ref} from 'vue';
import {useStore} from 'vuex';
import Dialog from '@/components/dialog/Dialog.vue';
import Form from '@/components/form/Form.vue';
import useSpider from '@/components/spider/spider';
import useNode from '@/components/node/node';
import {TASK_MODE_RANDOM, TASK_MODE_SELECTED_NODE_TAGS, TASK_MODE_SELECTED_NODES} from '@/constants/task';
import useTask from '@/components/task/task';
import FormItem from '@/components/form/FormItem.vue';
import InputWithButton from '@/components/input/InputWithButton.vue';
import CheckTagGroup from '@/components/tag/CheckTagGroup.vue';
import {ElMessage} from 'element-plus';
export default defineComponent({
name: 'RunSpiderDialog',
components: {
Dialog,
Form,
FormItem,
InputWithButton,
CheckTagGroup,
},
setup() {
// store
const ns = 'spider';
const store = useStore();
const {
spider: state,
} = store.state as RootStoreState;
const {
allListSelectOptions: allNodeSelectOptions,
allTags: allNodeTags,
} = useNode(store);
const {
modeOptions,
form,
} = useSpider(store);
const {
priorityOptions,
} = useTask(store);
// spider
const spider = computed<Spider>(() => form.value);
// form ref
const formRef = ref<typeof Form>();
// run options
const options = ref<SpiderRunOptions>({
mode: TASK_MODE_RANDOM,
cmd: spider.value.cmd,
param: spider.value.param,
priority: 5,
});
// dialog visible
const visible = computed<boolean>(() => state.activeDialogKey === 'run');
// title
const title = computed<string>(() => {
if (!spider.value) return 'Run Spider';
return `Run Spider - ${spider.value.name}`;
});
const onClose = () => {
store.commit(`${ns}/hideDialog`);
};
const onConfirm = async () => {
await formRef.value?.validate();
await store.dispatch(`${ns}/runById`, {id: spider.value?._id, options: options.value});
store.commit(`${ns}/hideDialog`);
await ElMessage.success('Scheduled task successfully');
await store.dispatch(`${ns}/getList`);
};
return {
TASK_MODE_SELECTED_NODES,
TASK_MODE_SELECTED_NODE_TAGS,
visible,
title,
formRef,
options,
modeOptions,
allNodeSelectOptions,
allNodeTags,
priorityOptions,
onClose,
onConfirm,
};
},
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,198 @@
<template>
<Form v-if="form" ref="formRef" :model="form">
<!-- Row -->
<FormItem :span="2" label="Name" prop="name" required>
<el-input v-model="form.name" :disabled="isFormItemDisabled('name')" placeholder="Name"/>
</FormItem>
<FormItem :span="2" label="Project" prop="project_id">
<el-select
v-model="form.project_id"
:disabled="isFormItemDisabled('project_id')"
filterable
>
<el-option
v-for="op in allProjectSelectOptions"
:key="op.value"
:label="op.label"
:value="op.value"
/>
</el-select>
</FormItem>
<!-- ./Row -->
<!-- Row -->
<FormItem :span="2" label="Command" prop="cmd" required>
<InputWithButton
v-model="form.cmd"
:button-icon="['fa', 'edit']"
button-label="Edit"
placeholder="Command"
:disabled="isFormItemDisabled('cmd')"
/>
</FormItem>
<FormItem :span="2" label="Param" prop="param">
<InputWithButton
v-model="form.param"
:button-icon="['fa', 'edit']"
button-label="Edit"
placeholder="Params"
:disabled="isFormItemDisabled('param')"
/>
</FormItem>
<!-- ./Row -->
<!-- Row -->
<FormItem :span="2" label="Default Mode" prop="mode" required>
<el-select
v-model="form.mode"
:disabled="isFormItemDisabled('mode')"
>
<el-option
v-for="op in modeOptions"
:key="op.value"
:label="op.label"
:value="op.value"
/>
</el-select>
</FormItem>
<FormItem :span="2" label="Results Collection" prop="col_name" required>
<el-autocomplete
v-model="form.col_name"
:disabled="isFormItemDisabled('col_name')"
placeholder="Results Collection"
:fetch-suggestions="fetchDataCollectionSuggestions"
@input="onDataCollectionInput"
@select="onDataCollectionSuggestionSelect"
/>
</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"
:options="allNodeTags"
:disabled="isFormItemDisabled('node_tags')"
/>
</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"
:options="allNodeSelectOptions"
:disabled="form.mode === TASK_MODE_SELECTED_NODE_TAGS && isFormItemDisabled('node_ids')"
/>
</FormItem>
<FormItem :span="4" label="Description" prop="description">
<el-input
v-model="form.description"
:disabled="isFormItemDisabled('description')"
placeholder="Description"
type="textarea"
/>
</FormItem>
</Form>
</template>
<script lang="ts">
import {defineComponent, ref, watch} from 'vue';
import {useStore} from 'vuex';
import useSpider from '@/components/spider/spider';
import useNode from '@/components/node/node';
import useProject from '@/components/project/project';
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 pinyin, {STYLE_NORMAL} from 'pinyin';
import {isZeroObjectId} from '@/utils/mongo';
export default defineComponent({
name: 'SpiderForm',
components: {
Form,
FormItem,
InputWithButton,
CheckTagGroup,
},
setup() {
// store
const store = useStore();
// use node
const {
allListSelectOptions: allNodeSelectOptions,
allTags: allNodeTags,
} = useNode(store);
// use project
const {
allListSelectOptionsWithEmpty: allProjectSelectOptions,
} = useProject(store);
// use spider
const {
form,
} = useSpider(store);
// whether col field of form has been changed
const isFormColChanged = ref<boolean>(false);
const onColInput = () => {
isFormColChanged.value = true;
};
watch(() => form.value?.name, () => {
if (isFormColChanged.value) return;
if (form.value?._id && isZeroObjectId(form.value?._id)) return;
if (!form.value.name) {
form.value.col_name = '';
} else {
const name = pinyin(form.value.name, {style: STYLE_NORMAL})
.map(d => d.join('_'))
.join('_');
form.value.col_name = `results_${name}`;
}
});
const onDataCollectionSuggestionSelect = ({_id}: { _id: string; value: string }) => {
form.value.col_id = _id;
};
const onDataCollectionInput = (value: string) => {
form.value.col_name = value;
form.value.col_id = undefined;
};
return {
...useSpider(store),
// custom
TASK_MODE_SELECTED_NODES,
TASK_MODE_SELECTED_NODE_TAGS,
allNodeSelectOptions,
allNodeTags,
allProjectSelectOptions,
onColInput,
onDataCollectionSuggestionSelect,
onDataCollectionInput,
};
},
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,100 @@
<template>
<div class="spider-stat">
<Tag
:icon="['fa', 'tasks']"
:label="labels.tasks"
:tooltip="tooltips.tasks"
type="primary"
/>
<Tag
:icon="['fa', 'database']"
:label="labels.results"
:tooltip="tooltips.results"
type="success"
/>
<Tag
:icon="['fa', 'stopwatch']"
:label="labels.duration"
type="warning"
>
<template #tooltip>
<div v-html="tooltips.duration"/>
</template>
</Tag>
</div>
</template>
<script lang="ts">
import {computed, defineComponent, PropType} from 'vue';
import Tag from '@/components/tag/Tag.vue';
import colors from '@/styles/color.scss';
import humanizeDuration from 'humanize-duration';
export default defineComponent({
name: 'SpiderStats',
components: {
Tag,
},
props: {
stat: {
type: Object as PropType<SpiderStat>,
required: false,
},
},
setup(props: SpiderStatProps, {emit}) {
const labels = computed<SpiderStatLabels>(() => {
const {stat} = props;
const {
tasks,
results,
// average_wait_duration,
// average_runtime_duration,
average_total_duration,
} = stat as SpiderStat;
return {
tasks: `${tasks}`,
results: `${results}`,
duration: `${average_total_duration}`
};
});
const tooltips = computed<SpiderStatTooltips>(() => {
const {stat} = props;
const {
tasks,
results,
average_wait_duration,
average_runtime_duration,
average_total_duration,
} = stat as SpiderStat;
return {
tasks: `Total Tasks: ${tasks}`,
results: `Total Results: ${results}`,
duration: `
<span class="label">Average Wait Duration:</span>
<span class="value" style="color: ${colors.blue}">${humanizeDuration(average_wait_duration * 1000, {spacer: ' '})}</span><br>
<span class="label">Average Runtime Duration:</span>
<span class="value" style="color: ${colors.orange}">${humanizeDuration(average_runtime_duration * 1000, {spacer: ' '})}</span><br>
<span class="label">Average Total Duration:</span>
<span class="value" style="color: ${colors.white}">${humanizeDuration(average_total_duration * 1000, {spacer: ' '})}</span><br>
`,
};
});
return {
tooltips,
labels,
};
},
});
</script>
<style lang="scss" scoped>
.spider-stat {
}
</style>
<style scoped>
</style>

View File

@@ -0,0 +1,38 @@
<template>
<LinkTag
:label="spider.name"
:path="path"
type="primary"
/>
</template>
<script lang="ts">
import {computed, defineComponent, PropType} from 'vue';
import LinkTag from '@/components/tag/LinkTag.vue';
export default defineComponent({
name: 'SpiderTag',
components: {LinkTag},
props: {
spider: {
type: Object as PropType<Spider>,
required: true,
},
},
setup(props: SpiderTagProps, {emit}) {
const path = computed<string>(() => {
const {spider} = props;
const {_id} = spider;
return `/spiders/${_id}`;
});
return {
path,
};
},
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,66 @@
<template>
<Tag
:icon="data.icon"
:label="data.label"
:tooltip="data.tooltip"
:type="data.type"
width="100px"
@click="$emit('click')"
/>
</template>
<script lang="ts">
import {computed, defineComponent, PropType} from 'vue';
import {SPIDER_TYPE_CONFIGURABLE, SPIDER_TYPE_CUSTOMIZED} from '@/constants/spider';
import Tag from '@/components/tag/Tag.vue';
export default defineComponent({
name: 'SpiderType',
components: {
Tag,
},
props: {
type: {
type: String as PropType<SpiderType>,
default: SPIDER_TYPE_CUSTOMIZED,
}
},
emits: ['click'],
setup(props: SpiderTypeProps, {emit}) {
const data = computed<TagData>(() => {
const {type} = props;
switch (type) {
case SPIDER_TYPE_CUSTOMIZED:
return {
tooltip: 'Customized Spider',
label: 'Customized',
type: 'success',
icon: ['fa', 'code'],
};
case SPIDER_TYPE_CONFIGURABLE:
return {
label: 'Configurable',
tooltip: 'Configurable Spider',
type: 'primary',
icon: ['fa', 'cog'],
};
default:
return {
label: 'Unknown',
tooltip: 'Unknown Type',
type: 'info',
icon: ['fa', 'question'],
};
}
});
return {
data,
};
},
});
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,130 @@
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 useSpiderService from '@/services/spider/spiderService';
import {getDefaultFormComponentData} from '@/utils/form';
import {
FORM_FIELD_TYPE_INPUT,
FORM_FIELD_TYPE_INPUT_TEXTAREA,
FORM_FIELD_TYPE_INPUT_WITH_BUTTON,
FORM_FIELD_TYPE_SELECT
} from '@/constants/form';
import useProject from '@/components/project/project';
import useRequest from '@/services/request';
import {FILTER_OP_CONTAINS} from '@/constants/filter';
import {getModeOptions} from '@/utils/task';
const {
getList,
} = useRequest();
// get new spider
export const getNewSpider = (): Spider => {
return {
mode: TASK_MODE_RANDOM,
};
};
// form component data
const formComponentData = getDefaultFormComponentData<Spider>(getNewSpider);
const useSpider = (store: Store<RootStoreState>) => {
// options for default mode
const modeOptions = getModeOptions();
// use project
const {
allProjectSelectOptions,
} = useProject(store);
// batch form fields
const batchFormFields = computed<FormTableField[]>(() => [
{
prop: 'name',
label: 'Name',
width: '150',
placeholder: 'Spider Name',
fieldType: FORM_FIELD_TYPE_INPUT,
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,
},
{
prop: 'project_id',
label: 'Project',
width: '200',
fieldType: FORM_FIELD_TYPE_SELECT,
options: allProjectSelectOptions.value,
},
{
prop: 'description',
label: 'Description',
width: '200',
fieldType: FORM_FIELD_TYPE_INPUT_TEXTAREA,
},
]);
// route
const route = useRoute();
// spider id
const id = computed(() => route.params.id);
// fetch data collections
const fetchDataCollection = async (query: string) => {
const conditions = [{
key: 'name',
op: FILTER_OP_CONTAINS,
value: query,
}] as FilterConditionData[];
const res = await getList(`/data/collections`, {conditions});
return res.data;
};
// fetch data collection suggestions
const fetchDataCollectionSuggestions = (query: string, cb: Function) => {
fetchDataCollection(query)
.then(data => {
cb(data?.map((d: DataCollection) => {
return {
_id: d._id,
value: d.name,
};
}));
});
};
return {
...useForm('spider', store, useSpiderService(store), formComponentData),
batchFormFields,
id,
modeOptions,
fetchDataCollection,
fetchDataCollectionSuggestions,
};
};
export default useSpider;