diff --git a/frontend/crawlab-ui/package.json b/frontend/crawlab-ui/package.json index 45ccc97d..cffb6cd5 100644 --- a/frontend/crawlab-ui/package.json +++ b/frontend/crawlab-ui/package.json @@ -75,7 +75,6 @@ "@popperjs/core": "^2.11.8", "@types/getos": "^3.0.4", "@types/humanize-duration": "^3.27.4", - "@types/javascript-time-ago": "^2.0.8", "@types/lodash": "^4.17.6", "@types/markdown-it": "^14.1.2", "@types/md5": "^2.3.5", diff --git a/frontend/crawlab-ui/pnpm-lock.yaml b/frontend/crawlab-ui/pnpm-lock.yaml index 3eb5de4f..df0cb410 100644 --- a/frontend/crawlab-ui/pnpm-lock.yaml +++ b/frontend/crawlab-ui/pnpm-lock.yaml @@ -50,9 +50,6 @@ importers: '@types/humanize-duration': specifier: ^3.27.4 version: 3.27.4 - '@types/javascript-time-ago': - specifier: ^2.0.8 - version: 2.5.0 '@types/lodash': specifier: ^4.17.6 version: 4.17.16 @@ -1205,10 +1202,6 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - '@types/javascript-time-ago@2.5.0': - resolution: {integrity: sha512-c0GQ02qkkZx138VphjPqU4+PkVSNkWvQcpE3Yy03S6NpWlT9XTSXPUbJ1qv4KV0/eW7wSEAoU87e2YHz7ndHrA==} - deprecated: This is a stub types definition. javascript-time-ago provides its own type definitions, so you do not need this installed. - '@types/jest@29.5.14': resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} @@ -4632,10 +4625,6 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 - '@types/javascript-time-ago@2.5.0': - dependencies: - javascript-time-ago: 2.5.11 - '@types/jest@29.5.14': dependencies: expect: 29.7.0 diff --git a/frontend/crawlab-ui/src/components/core/node/useNode.ts b/frontend/crawlab-ui/src/components/core/node/useNode.ts index 5cb1c113..0104d44f 100644 --- a/frontend/crawlab-ui/src/components/core/node/useNode.ts +++ b/frontend/crawlab-ui/src/components/core/node/useNode.ts @@ -17,19 +17,22 @@ const useNode = (store: Store) => { // form rules const formRules: FormRules = {}; + const allNodesSorted = computed(() => { + return state.allNodes.sort((a, b) => { + if (a.is_master) return -1; + if (b.is_master) return 1; + return a.name!.localeCompare(b.name!); + }); + }); + const activeNodesSorted = computed(() => { - return state.allNodes - .filter(n => n.active) - .sort((a, b) => { - if (a.is_master) return -1; - if (b.is_master) return 1; - return a.name!.localeCompare(b.name!); - }); + return allNodesSorted.value.filter(node => node.active); }); return { ...useForm(ns, store, useNodeService(store), formComponentData), formRules, + allNodesSorted, activeNodesSorted, }; }; diff --git a/frontend/crawlab-ui/src/components/core/spider/SpiderForm.vue b/frontend/crawlab-ui/src/components/core/spider/SpiderForm.vue index fc0b493f..1cf0b490 100644 --- a/frontend/crawlab-ui/src/components/core/spider/SpiderForm.vue +++ b/frontend/crawlab-ui/src/components/core/spider/SpiderForm.vue @@ -4,7 +4,7 @@ import { useStore } from 'vuex'; import { useSpider, useProject, useNode } from '@/components'; import { TASK_MODE_RANDOM, TASK_MODE_SELECTED_NODES } from '@/constants/task'; import pinyin, { STYLE_NORMAL } from 'pinyin'; -import { isZeroObjectId } from '@/utils/mongo'; +import { EMPTY_OBJECT_ID, isZeroObjectId } from '@/utils/mongo'; import { useSpiderDetail } from '@/views'; import { getToRunNodes, priorityOptions, translate } from '@/utils'; import { getSpiderTemplateGroups, getSpiderTemplates } from '@/utils/spider'; @@ -20,11 +20,11 @@ const { get } = useRequest(); const store = useStore(); // use node -const { activeNodesSorted: activeNodes } = useNode(store); +const { allNodesSorted: allNodes } = useNode(store); const toRunNodes = computed(() => { const { mode, node_ids } = form.value; - return getToRunNodes(mode, node_ids, activeNodes.value); + return getToRunNodes(mode, node_ids, allNodes.value); }); // use spider @@ -177,7 +177,15 @@ defineOptions({ name: 'ClSpiderForm' }); :label="t('components.spider.form.project')" prop="project_id" > - + @@ -246,7 +254,7 @@ defineOptions({ name: 'ClSpiderForm' }); :placeholder="t('components.spider.form.selectedNodes')" > - {{ n.name }} + + {{ + n.name + + (n.active + ? '' + : ` (${t('components.node.nodeStatus.label.offline')})`) + }} + diff --git a/frontend/crawlab-ui/src/components/ui/select/RemoteSelect.vue b/frontend/crawlab-ui/src/components/ui/select/RemoteSelect.vue index f935acf1..e2931273 100644 --- a/frontend/crawlab-ui/src/components/ui/select/RemoteSelect.vue +++ b/frontend/crawlab-ui/src/components/ui/select/RemoteSelect.vue @@ -8,11 +8,13 @@ const props = withDefaults( placeholder?: string; disabled?: boolean; filterable?: boolean; + clearable?: boolean; remoteShowSuffix?: boolean; endpoint: string; labelKey?: string; valueKey?: string; limit?: number; + emptyOption?: SelectOption; }>(), { remoteShowSuffix: true, @@ -62,12 +64,21 @@ const remoteMethod = async (query?: string) => { loading.value = false; } }; -const selectOptions = computed(() => - list.value.map(row => ({ - label: row[props.labelKey], - value: row[props.valueKey], - })) -); +const selectOptions = computed(() => { + const { emptyOption, labelKey, valueKey } = props; + const options: SelectOption[] = list.value.map(row => ({ + label: row[labelKey], + value: row[valueKey], + })); + if (emptyOption) { + const { label, value } = emptyOption; + options.unshift({ + label, + value, + }); + } + return options; +}); onBeforeMount(remoteMethod); defineOptions({ name: 'ClRemoteSelect' }); @@ -79,6 +90,7 @@ defineOptions({ name: 'ClRemoteSelect' }); :placeholder="placeholder" :filterable="filterable" :disabled="disabled" + :clearable="clearable" remote :remote-method="remoteMethod" :remote-show-suffix="remoteShowSuffix" diff --git a/frontend/crawlab-ui/src/layouts/content/detail/DetailLayout.vue b/frontend/crawlab-ui/src/layouts/content/detail/DetailLayout.vue index 31e08b49..f7d0898e 100644 --- a/frontend/crawlab-ui/src/layouts/content/detail/DetailLayout.vue +++ b/frontend/crawlab-ui/src/layouts/content/detail/DetailLayout.vue @@ -10,6 +10,7 @@ const props = withDefaults( showBackButton?: boolean; showSaveButton?: boolean; allListSelectOptions?: SelectOption[]; + navItemLabelFn?: (item: NavItem) => string; }>(), { navItemNameKey: 'name', @@ -77,7 +78,7 @@ defineOptions({ name: 'ClDetailLayout' }); diff --git a/frontend/crawlab-ui/src/layouts/content/detail/useDetail.ts b/frontend/crawlab-ui/src/layouts/content/detail/useDetail.ts index 1ad3f8a6..8f2493d4 100644 --- a/frontend/crawlab-ui/src/layouts/content/detail/useDetail.ts +++ b/frontend/crawlab-ui/src/layouts/content/detail/useDetail.ts @@ -41,6 +41,7 @@ const useDetail = (ns: ListStoreNamespace) => { items.unshift({ id: activeId.value, label: form.name || activeId.value, + data: form, }); } return items; diff --git a/frontend/crawlab-ui/src/utils/time.ts b/frontend/crawlab-ui/src/utils/time.ts index c3f55df5..97c88de0 100644 --- a/frontend/crawlab-ui/src/utils/time.ts +++ b/frontend/crawlab-ui/src/utils/time.ts @@ -1,9 +1,8 @@ import dayjs from 'dayjs'; -import TimeAgo, { LocaleData } from 'javascript-time-ago'; +import TimeAgo, { LocaleData, FormatStyleName } from 'javascript-time-ago'; import { getI18n } from '@/i18n'; import en from 'javascript-time-ago/locale/en'; import zh from 'javascript-time-ago/locale/zh'; -import { FormatStyle } from 'javascript-time-ago/style'; TimeAgo.addLocale(en as LocaleData); TimeAgo.addLocale(zh as LocaleData); @@ -18,7 +17,7 @@ export const getTimeUnitParts = (timeUnit: string) => { export const formatTimeAgo = ( value: string | Date, - formatStyle?: string | FormatStyle + formatStyle?: string | FormatStyleName ) => { const time = dayjs(value); const timeAgo = new TimeAgo( diff --git a/frontend/crawlab-ui/src/views/task/detail/TaskDetail.vue b/frontend/crawlab-ui/src/views/task/detail/TaskDetail.vue index 74307414..89ee4bab 100644 --- a/frontend/crawlab-ui/src/views/task/detail/TaskDetail.vue +++ b/frontend/crawlab-ui/src/views/task/detail/TaskDetail.vue @@ -2,13 +2,20 @@ import { useStore } from 'vuex'; import { useTaskDetail } from '@/views'; import { useTask } from '@/components'; -import { isPro } from '@/utils'; +import { formatTimeAgo, isPro } from '@/utils'; const { activeTabName } = useTaskDetail(); const store = useStore(); const { allListSelectOptions } = useTask(store); +const navItemLabelFn = (item: NavItem) => { + if (!item.data) return item.label; + const spiderName = item.data.spider?.name; + const createdAt = formatTimeAgo(item.data.created_at!, 'mini-minute-now'); + return `${spiderName} - ${createdAt}`; +}; + defineOptions({ name: 'ClTaskDetail' }); @@ -16,6 +23,7 @@ defineOptions({ name: 'ClTaskDetail' }); - -