Merge remote-tracking branch 'origin/1.3.0-beta1' into 1.3.0-beta1
This commit is contained in:
commit
a4c8916000
2
.env
2
.env
@ -4,7 +4,7 @@ VITE_APP_TITLE=Snail Job
|
|||||||
|
|
||||||
VITE_APP_DESC=A flexible, reliable, and fast platform for distributed task retry and distributed task scheduling.
|
VITE_APP_DESC=A flexible, reliable, and fast platform for distributed task retry and distributed task scheduling.
|
||||||
|
|
||||||
VITE_APP_VERSION=1.2.0-beta2
|
VITE_APP_VERSION=1.2.0
|
||||||
|
|
||||||
VITE_APP_DEFAULT_TOKEN=SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj
|
VITE_APP_DEFAULT_TOKEN=SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { NButton, NCode, NTag } from 'naive-ui';
|
|||||||
import hljs from 'highlight.js/lib/core';
|
import hljs from 'highlight.js/lib/core';
|
||||||
import json from 'highlight.js/lib/languages/json';
|
import json from 'highlight.js/lib/languages/json';
|
||||||
import { ref, render } from 'vue';
|
import { ref, render } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { taskStatusRecord, taskStatusRecordOptions } from '@/constants/business';
|
import { taskStatusRecord, taskStatusRecordOptions } from '@/constants/business';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { isNotNull, parseArgsJson, translateOptions } from '@/utils/common';
|
import { isNotNull, parseArgsJson, translateOptions } from '@/utils/common';
|
||||||
@ -251,6 +252,18 @@ const { columns, searchParams, columnChecks, data, getData, loading, mobilePagin
|
|||||||
title: $t('page.jobBatch.jobTask.createDt'),
|
title: $t('page.jobBatch.jobTask.createDt'),
|
||||||
align: 'left',
|
align: 'left',
|
||||||
minWidth: 130
|
minWidth: 130
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'duration',
|
||||||
|
title: $t('page.jobBatch.duration'),
|
||||||
|
align: 'center',
|
||||||
|
width: 120,
|
||||||
|
render: row => {
|
||||||
|
if (row.taskStatus === 3) {
|
||||||
|
return Math.round(dayjs(row.updateDt).diff(dayjs(row.createDt)) / 1000);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -245,6 +245,15 @@ export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>,
|
|||||||
openDrawer();
|
openDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCopy(id: T['id']) {
|
||||||
|
operateType.value = 'add';
|
||||||
|
const findItem = data.value.find(item => item.id === id) || null;
|
||||||
|
editingData.value = jsonClone(findItem);
|
||||||
|
delete editingData.value?.id;
|
||||||
|
|
||||||
|
openDrawer();
|
||||||
|
}
|
||||||
|
|
||||||
/** the checked row keys of table */
|
/** the checked row keys of table */
|
||||||
const checkedRowKeys = ref<string[]>([]);
|
const checkedRowKeys = ref<string[]>([]);
|
||||||
|
|
||||||
@ -272,6 +281,7 @@ export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>,
|
|||||||
handleAdd,
|
handleAdd,
|
||||||
editingData,
|
editingData,
|
||||||
handleEdit,
|
handleEdit,
|
||||||
|
handleCopy,
|
||||||
checkedRowKeys,
|
checkedRowKeys,
|
||||||
onBatchDeleted,
|
onBatchDeleted,
|
||||||
onDeleted
|
onDeleted
|
||||||
|
2
src/typings/api.d.ts
vendored
2
src/typings/api.d.ts
vendored
@ -1073,6 +1073,8 @@ declare namespace Api {
|
|||||||
children: JobTaskTree[];
|
children: JobTaskTree[];
|
||||||
/** 是否存在下级 */
|
/** 是否存在下级 */
|
||||||
isLeaf: boolean;
|
isLeaf: boolean;
|
||||||
|
/** 执行时长(virtual) */
|
||||||
|
duration?: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type JobTaskTree = {
|
type JobTaskTree = {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive, ref, watch } from 'vue';
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { translateOptions, translateOptions2 } from '@/utils/common';
|
import { translateOptions, translateOptions2 } from '@/utils/common';
|
||||||
@ -29,6 +30,7 @@ const visible = defineModel<boolean>('visible', {
|
|||||||
default: false
|
default: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { copy, isSupported } = useClipboard();
|
||||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||||
const { defaultRequiredRule } = useFormRules();
|
const { defaultRequiredRule } = useFormRules();
|
||||||
|
|
||||||
@ -160,6 +162,20 @@ watch(visible, () => {
|
|||||||
restoreValidation();
|
restoreValidation();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function handleCopy(source: string) {
|
||||||
|
if (!isSupported) {
|
||||||
|
window.$message?.error('您的浏览器不支持 Clipboard API');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await copy(source);
|
||||||
|
window.$message?.success('复制成功');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -196,9 +212,17 @@ watch(visible, () => {
|
|||||||
:placeholder="$t('page.groupConfig.form.token')"
|
:placeholder="$t('page.groupConfig.form.token')"
|
||||||
:disabled="props.operateType === 'edit'"
|
:disabled="props.operateType === 'edit'"
|
||||||
/>
|
/>
|
||||||
<NTooltip trigger="hover">
|
<NTooltip v-if="props.operateType === 'edit'" trigger="hover">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton type="default" ghost :disabled="props.operateType === 'edit'" @click="setToken">
|
<NButton type="default" ghost @click="handleCopy(model.token)">
|
||||||
|
<icon-ic:round-content-copy class="text-icon" />
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
复制
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip v-else trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton type="default" ghost @click="setToken">
|
||||||
<icon-ic-round-refresh class="text-icon" />
|
<icon-ic-round-refresh class="text-icon" />
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { NButton, NPopconfirm, NTag } from 'naive-ui';
|
import { NButton, NDropdown, NPopconfirm, NTag } from 'naive-ui';
|
||||||
import { useBoolean } from '@sa/hooks';
|
import { useBoolean } from '@sa/hooks';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { fetchBatchDeleteJob, fetchGetJobPage, fetchTriggerJob, fetchUpdateJobStatus } from '@/service/api';
|
import { fetchBatchDeleteJob, fetchGetJobPage, fetchTriggerJob, fetchUpdateJobStatus } from '@/service/api';
|
||||||
@ -177,41 +177,86 @@ const { columnChecks, columns, data, getData, loading, mobilePagination, searchP
|
|||||||
key: 'operate',
|
key: 'operate',
|
||||||
title: $t('common.operate'),
|
title: $t('common.operate'),
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 180,
|
width: 120,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: row => (
|
render: row => {
|
||||||
<div class="flex-center gap-8px">
|
const options = [
|
||||||
<NPopconfirm onPositiveClick={() => handleTriggerJob(row.id!)}>
|
{
|
||||||
{{
|
label: $t('common.copy'),
|
||||||
default: () => $t('common.confirmExecute'),
|
key: 'copy',
|
||||||
trigger: () => (
|
click: () => copy(row.id!)
|
||||||
<NButton type="error" text ghost size="small">
|
},
|
||||||
{$t('common.execute')}
|
{
|
||||||
</NButton>
|
type: 'divider',
|
||||||
)
|
key: 'd2'
|
||||||
}}
|
},
|
||||||
</NPopconfirm>
|
{
|
||||||
<n-divider vertical />
|
label: $t('common.batchList'),
|
||||||
<NButton type="primary" ghost text size="small" onClick={() => goToBatch(row.id!)}>
|
key: 'batchList',
|
||||||
{$t('common.batchList')}
|
click: () => goToBatch(row.id!)
|
||||||
</NButton>
|
},
|
||||||
<n-divider vertical />
|
{
|
||||||
<NButton type="warning" ghost text size="small" onClick={() => edit(row.id!)}>
|
type: 'divider',
|
||||||
{$t('common.edit')}
|
key: 'd2'
|
||||||
</NButton>
|
},
|
||||||
<n-divider vertical />
|
{
|
||||||
<NPopconfirm onPositiveClick={() => handleDelete(row.id!)}>
|
type: 'render',
|
||||||
{{
|
key: 'delete',
|
||||||
default: () => $t('common.confirmDelete'),
|
render: () => (
|
||||||
trigger: () => (
|
<div class="flex-center">
|
||||||
<NButton type="error" text ghost size="small">
|
<NPopconfirm onPositiveClick={() => handleDelete(row.id!)}>
|
||||||
{$t('common.delete')}
|
{{
|
||||||
</NButton>
|
default: () => $t('common.confirmDelete'),
|
||||||
)
|
trigger: () => (
|
||||||
}}
|
<NButton quaternary size="small">
|
||||||
</NPopconfirm>
|
{$t('common.delete')}
|
||||||
</div>
|
</NButton>
|
||||||
)
|
)
|
||||||
|
}}
|
||||||
|
</NPopconfirm>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const onSelect = (key: string) => {
|
||||||
|
const fun = options.filter(o => o.key === key)[0].click;
|
||||||
|
if (fun) fun();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex-center gap-8px">
|
||||||
|
<NButton text type="warning" ghost size="small" onClick={() => edit(row.id!)}>
|
||||||
|
{$t('common.edit')}
|
||||||
|
</NButton>
|
||||||
|
|
||||||
|
<n-divider vertical />
|
||||||
|
|
||||||
|
<NPopconfirm onPositiveClick={() => handleTriggerJob(row.id!)}>
|
||||||
|
{{
|
||||||
|
default: () => $t('common.confirmExecute'),
|
||||||
|
trigger: () => (
|
||||||
|
<NButton type="error" text ghost size="small">
|
||||||
|
{$t('common.execute')}
|
||||||
|
</NButton>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NPopconfirm>
|
||||||
|
|
||||||
|
<n-divider vertical />
|
||||||
|
|
||||||
|
<NDropdown trigger="click" show-arrow={false} options={options} size="small" on-select={onSelect}>
|
||||||
|
{{
|
||||||
|
default: () => (
|
||||||
|
<NButton text type="primary" ghost size="small">
|
||||||
|
更多
|
||||||
|
</NButton>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NDropdown>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -222,6 +267,7 @@ const {
|
|||||||
editingData,
|
editingData,
|
||||||
handleAdd,
|
handleAdd,
|
||||||
handleEdit,
|
handleEdit,
|
||||||
|
handleCopy,
|
||||||
checkedRowKeys,
|
checkedRowKeys,
|
||||||
onDeleted,
|
onDeleted,
|
||||||
onBatchDeleted
|
onBatchDeleted
|
||||||
@ -244,6 +290,10 @@ function edit(id: string) {
|
|||||||
handleEdit(id);
|
handleEdit(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copy(id: string) {
|
||||||
|
handleCopy(id);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleTriggerJob(id: string) {
|
async function handleTriggerJob(id: string) {
|
||||||
const { error } = await fetchTriggerJob(id);
|
const { error } = await fetchTriggerJob(id);
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -218,7 +218,7 @@ function createDefaultScriptParams() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleUpdateModelWhenEdit() {
|
function handleUpdateModelWhenEdit() {
|
||||||
if (props.operateType === 'add') {
|
if (props.operateType === 'add' && !props.rowData) {
|
||||||
Object.assign(model, createDefaultModel());
|
Object.assign(model, createDefaultModel());
|
||||||
executorCustomType.value = 0;
|
executorCustomType.value = 0;
|
||||||
httpHeaders.value = [];
|
httpHeaders.value = [];
|
||||||
@ -227,7 +227,7 @@ function handleUpdateModelWhenEdit() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.operateType === 'edit' && props.rowData) {
|
if (props.rowData) {
|
||||||
Object.assign(model, props.rowData);
|
Object.assign(model, props.rowData);
|
||||||
if (model.taskType === 3 && model.argsStr) {
|
if (model.taskType === 3 && model.argsStr) {
|
||||||
Object.assign(dynamicForm, {
|
Object.assign(dynamicForm, {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { NButton, NPopconfirm } from 'naive-ui';
|
import { NButton, NPopconfirm, NPopover } from 'naive-ui';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
import { fetchDeleteNamespace, fetchGetNamespaceList } from '@/service/api';
|
import { fetchDeleteNamespace, fetchGetNamespaceList } from '@/service/api';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
@ -14,12 +15,27 @@ import NamespaceSearch from './modules/namespace-search.vue';
|
|||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const namespaceId = ref<string>(localStg.get('namespaceId')!);
|
const namespaceId = ref<string>(localStg.get('namespaceId')!);
|
||||||
|
const { copy, isSupported } = useClipboard();
|
||||||
|
|
||||||
const handleChange = (uniqueId: string) => {
|
const handleChange = (uniqueId: string) => {
|
||||||
namespaceId.value = uniqueId;
|
namespaceId.value = uniqueId;
|
||||||
authStore.setNamespaceId(uniqueId);
|
authStore.setNamespaceId(uniqueId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function handleCopy(source: string) {
|
||||||
|
if (!isSupported) {
|
||||||
|
window.$message?.error('您的浏览器不支持 Clipboard API');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await copy(source);
|
||||||
|
window.$message?.success('复制成功');
|
||||||
|
}
|
||||||
|
|
||||||
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
|
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
|
||||||
apiFn: fetchGetNamespaceList,
|
apiFn: fetchGetNamespaceList,
|
||||||
apiParams: {
|
apiParams: {
|
||||||
@ -59,7 +75,19 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
|
|||||||
key: 'uniqueId',
|
key: 'uniqueId',
|
||||||
title: $t('page.namespace.uniqueId'),
|
title: $t('page.namespace.uniqueId'),
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 180
|
width: 180,
|
||||||
|
render: row => (
|
||||||
|
<NPopover>
|
||||||
|
{{
|
||||||
|
trigger: () => (
|
||||||
|
<NButton text type="primary" onClick={() => handleCopy(row.uniqueId)}>
|
||||||
|
{row.uniqueId}
|
||||||
|
</NButton>
|
||||||
|
),
|
||||||
|
default: () => <span>点击复制</span>
|
||||||
|
}}
|
||||||
|
</NPopover>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'createDt',
|
key: 'createDt',
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive, watch } from 'vue';
|
import { computed, reactive, watch } from 'vue';
|
||||||
import { nanoid } from '@sa/utils';
|
import { nanoid } from '@sa/utils';
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||||
import OperateDrawer from '@/components/common/operate-drawer.vue';
|
import OperateDrawer from '@/components/common/operate-drawer.vue';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
@ -26,6 +27,7 @@ interface Emits {
|
|||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
const { copy, isSupported } = useClipboard();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const visible = defineModel<boolean>('visible', {
|
const visible = defineModel<boolean>('visible', {
|
||||||
default: false
|
default: false
|
||||||
@ -111,6 +113,20 @@ watch(visible, () => {
|
|||||||
restoreValidation();
|
restoreValidation();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function handleCopy(source: string) {
|
||||||
|
if (!isSupported) {
|
||||||
|
window.$message?.error('您的浏览器不支持 Clipboard API');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await copy(source);
|
||||||
|
window.$message?.success('复制成功');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -123,9 +139,17 @@ watch(visible, () => {
|
|||||||
:disabled="props.operateType === 'edit'"
|
:disabled="props.operateType === 'edit'"
|
||||||
:placeholder="$t('page.namespace.form.uniqueId')"
|
:placeholder="$t('page.namespace.form.uniqueId')"
|
||||||
/>
|
/>
|
||||||
<NTooltip trigger="hover">
|
<NTooltip v-if="props.operateType === 'edit'" trigger="hover">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton type="default" ghost :disabled="props.operateType === 'edit'" @click="setUniqueId">
|
<NButton type="default" ghost @click="handleCopy(model.uniqueId)">
|
||||||
|
<icon-ic:round-content-copy class="text-icon" />
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
复制
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip v-else trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton type="default" ghost @click="setUniqueId">
|
||||||
<icon-ic-round-refresh class="text-icon" />
|
<icon-ic-round-refresh class="text-icon" />
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user