同步修改
This commit is contained in:
1 parent
d647453085
commit
9939943e31
6 files changed
+60
-27
No files matched your search
@@ -60,8 +60,12 @@ const isPull = computed(() => direction.value === 'pull')
|
|||||||
const canAbandon = computed(() =>
|
const canAbandon = computed(() =>
|
||||||
!props.busy
|
!props.busy
|
||||||
&& Boolean(props.resumableJob)
|
&& Boolean(props.resumableJob)
|
||||||
&& (isPull.value || Boolean(token.value.trim()))
|
|
||||||
)
|
)
|
||||||
|
const abandonConfirm = computed(() => (
|
||||||
|
isPull.value || token.value.trim()
|
||||||
|
? t('storage.backup.siteSyncAbandonConfirm')
|
||||||
|
: t('storage.backup.siteSyncAbandonWithoutTokenConfirm')
|
||||||
|
))
|
||||||
const directionSubtitle = computed(() => (
|
const directionSubtitle = computed(() => (
|
||||||
isPull.value
|
isPull.value
|
||||||
? t('storage.backup.siteSyncPullSubtitle')
|
? t('storage.backup.siteSyncPullSubtitle')
|
||||||
@@ -110,7 +114,7 @@ function resumeSync() {
|
|||||||
|
|
||||||
function abandonSync() {
|
function abandonSync() {
|
||||||
if (!canAbandon.value || !props.resumableJob) return
|
if (!canAbandon.value || !props.resumableJob) return
|
||||||
if (!window.confirm(t('storage.backup.siteSyncAbandonConfirm'))) return
|
if (!window.confirm(abandonConfirm.value)) return
|
||||||
emit('sync', {
|
emit('sync', {
|
||||||
targetBaseUrl: targetBaseUrl.value.trim(),
|
targetBaseUrl: targetBaseUrl.value.trim(),
|
||||||
token: token.value.trim(),
|
token: token.value.trim(),
|
||||||
|
|||||||
@@ -268,11 +268,17 @@ export function useBackup() {
|
|||||||
)
|
)
|
||||||
await refreshJobsFirstPage()
|
await refreshJobsFirstPage()
|
||||||
startPolling()
|
startPolling()
|
||||||
|
const cleanupPending = Boolean(
|
||||||
|
body.abandonJobId
|
||||||
|
&& data.job.payload.cleanupPending === true
|
||||||
|
)
|
||||||
toast.add({
|
toast.add({
|
||||||
title: body.abandonJobId
|
title: cleanupPending
|
||||||
? t('storage.backup.siteSyncAbandoned')
|
? t('storage.backup.siteSyncAbandonedPendingCleanup')
|
||||||
: t('storage.backup.siteSyncStarted'),
|
: body.abandonJobId
|
||||||
color: 'success'
|
? t('storage.backup.siteSyncAbandoned')
|
||||||
|
: t('storage.backup.siteSyncStarted'),
|
||||||
|
color: cleanupPending ? 'warning' : 'success'
|
||||||
})
|
})
|
||||||
return data.job
|
return data.job
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
|||||||
@@ -695,7 +695,9 @@
|
|||||||
"siteSyncResume": "Resume last sync",
|
"siteSyncResume": "Resume last sync",
|
||||||
"siteSyncAbandon": "Abandon and clean up",
|
"siteSyncAbandon": "Abandon and clean up",
|
||||||
"siteSyncAbandonConfirm": "Abandon this sync and delete partial objects created by it that are not yet referenced by a media record?",
|
"siteSyncAbandonConfirm": "Abandon this sync and delete partial objects created by it that are not yet referenced by a media record?",
|
||||||
|
"siteSyncAbandonWithoutTokenConfirm": "No target token is provided. Abandon the task now? Partial objects will be marked for later automatic cleanup on the target.",
|
||||||
"siteSyncAbandoned": "Sync abandoned and partial objects cleaned up",
|
"siteSyncAbandoned": "Sync abandoned and partial objects cleaned up",
|
||||||
|
"siteSyncAbandonedPendingCleanup": "Sync abandoned; partial objects still need cleanup",
|
||||||
"siteSyncScanProgress": "Media scan progress",
|
"siteSyncScanProgress": "Media scan progress",
|
||||||
"siteSyncFileProgress": "Synced file count",
|
"siteSyncFileProgress": "Synced file count",
|
||||||
"siteSyncStarted": "Site-to-site sync started",
|
"siteSyncStarted": "Site-to-site sync started",
|
||||||
|
|||||||
@@ -695,7 +695,9 @@
|
|||||||
"siteSyncResume": "继续上次同步",
|
"siteSyncResume": "继续上次同步",
|
||||||
"siteSyncAbandon": "放弃并清理残留",
|
"siteSyncAbandon": "放弃并清理残留",
|
||||||
"siteSyncAbandonConfirm": "确定放弃这次同步吗?将删除本次同步新建但尚未归入媒体记录的残留对象。",
|
"siteSyncAbandonConfirm": "确定放弃这次同步吗?将删除本次同步新建但尚未归入媒体记录的残留对象。",
|
||||||
|
"siteSyncAbandonWithoutTokenConfirm": "未填写目标站 Token。确定只放弃任务吗?目标站残留将标记为待清理,之后可由目标站自动清理。",
|
||||||
"siteSyncAbandoned": "同步已放弃,残留对象已清理",
|
"siteSyncAbandoned": "同步已放弃,残留对象已清理",
|
||||||
|
"siteSyncAbandonedPendingCleanup": "同步已放弃,残留待清理",
|
||||||
"siteSyncScanProgress": "媒体扫描进度",
|
"siteSyncScanProgress": "媒体扫描进度",
|
||||||
"siteSyncFileProgress": "已同步文件数量",
|
"siteSyncFileProgress": "已同步文件数量",
|
||||||
"siteSyncStarted": "两站点同步任务已开始",
|
"siteSyncStarted": "两站点同步任务已开始",
|
||||||
|
|||||||
@@ -25,29 +25,41 @@ export async function abandonSiteSyncJob(
|
|||||||
const targetBaseUrl = typeof job.payload.targetBaseUrl === 'string'
|
const targetBaseUrl = typeof job.payload.targetBaseUrl === 'string'
|
||||||
? job.payload.targetBaseUrl.replace(/\/+$/, '')
|
? job.payload.targetBaseUrl.replace(/\/+$/, '')
|
||||||
: ''
|
: ''
|
||||||
let cleanup: CleanupResult
|
let cleanup: CleanupResult | null = null
|
||||||
|
let cleanupError: string | null = null
|
||||||
if (direction === 'push') {
|
if (direction === 'push') {
|
||||||
if (!targetBaseUrl || !token) {
|
if (targetBaseUrl && token) {
|
||||||
throw new Error('放弃推送任务并清理目标站残留时,需要目标站 Token')
|
try {
|
||||||
}
|
cleanup = await fetchSiteSyncJson<CleanupResult>(
|
||||||
cleanup = await fetchSiteSyncJson<CleanupResult>(
|
`${targetBaseUrl}/api/site-sync/cleanup`,
|
||||||
`${targetBaseUrl}/api/site-sync/cleanup`,
|
token,
|
||||||
token,
|
{
|
||||||
{
|
method: 'POST',
|
||||||
method: 'POST',
|
headers: { 'Content-Type': 'application/json' },
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: JSON.stringify({ jobId })
|
||||||
body: JSON.stringify({ jobId })
|
}
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
cleanupError = error instanceof Error ? error.message : '目标站残留清理失败'
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
} else {
|
} else {
|
||||||
cleanup = await cleanupSiteSyncJobArtifacts(jobId)
|
try {
|
||||||
|
cleanup = await cleanupSiteSyncJobArtifacts(jobId)
|
||||||
|
} catch (error) {
|
||||||
|
cleanupError = error instanceof Error ? error.message : '本地残留清理失败'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cleanup.failed > 0) {
|
const cleanupPending = Boolean(
|
||||||
throw new Error(`同步残留清理失败:${cleanup.failed} 个对象`)
|
cleanupError
|
||||||
}
|
|| !cleanup
|
||||||
|
|| cleanup.failed > 0
|
||||||
|
)
|
||||||
|
|
||||||
const message = `已放弃同步 · 清理 ${cleanup.deleted} 个残留对象`
|
const message = cleanupPending
|
||||||
|
? `已放弃同步 · 残留待清理${cleanupError ? `:${cleanupError}` : ''}`
|
||||||
|
: `已放弃同步 · 清理 ${cleanup!.deleted} 个残留对象`
|
||||||
updateBackupJob(jobId, {
|
updateBackupJob(jobId, {
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
message,
|
message,
|
||||||
@@ -58,6 +70,8 @@ export async function abandonSiteSyncJob(
|
|||||||
resumable: false,
|
resumable: false,
|
||||||
abandoned: true,
|
abandoned: true,
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
|
cleanupPending,
|
||||||
|
cleanupError,
|
||||||
failedKeys: [],
|
failedKeys: [],
|
||||||
cleanup
|
cleanup
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { basename } from 'node:path'
|
import { basename } from 'node:path'
|
||||||
|
import { File as BufferFile } from 'node:buffer'
|
||||||
import {
|
import {
|
||||||
Agent,
|
Agent,
|
||||||
|
FormData as UndiciFormData,
|
||||||
fetch as undiciFetch
|
fetch as undiciFetch
|
||||||
} from 'undici'
|
} from 'undici'
|
||||||
import type { Readable } from 'node:stream'
|
import type { Readable } from 'node:stream'
|
||||||
@@ -399,7 +401,7 @@ async function uploadObject(
|
|||||||
getJobAbortSignal(jobId)
|
getJobAbortSignal(jobId)
|
||||||
)
|
)
|
||||||
throwIfJobCancellationRequested(jobId)
|
throwIfJobCancellationRequested(jobId)
|
||||||
const form = new FormData()
|
const form = new UndiciFormData()
|
||||||
const isPrimary = key === row.key
|
const isPrimary = key === row.key
|
||||||
form.append('jobId', jobId)
|
form.append('jobId', jobId)
|
||||||
form.append('mediaKey', row.key)
|
form.append('mediaKey', row.key)
|
||||||
@@ -412,8 +414,11 @@ async function uploadObject(
|
|||||||
form.append('contentType', contentType)
|
form.append('contentType', contentType)
|
||||||
form.append(
|
form.append(
|
||||||
'file',
|
'file',
|
||||||
new Blob([new Uint8Array(bytes)], { type: contentType }),
|
new BufferFile(
|
||||||
basename(key)
|
[new Uint8Array(bytes)],
|
||||||
|
basename(key),
|
||||||
|
{ type: contentType }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const response = await undiciFetch(
|
const response = await undiciFetch(
|
||||||
@@ -424,7 +429,7 @@ async function uploadObject(
|
|||||||
headers: { 'Auth-Token': token },
|
headers: { 'Auth-Token': token },
|
||||||
signal: getJobAbortSignal(jobId),
|
signal: getJobAbortSignal(jobId),
|
||||||
dispatcher: agents.upload
|
dispatcher: agents.upload
|
||||||
} as Parameters<typeof undiciFetch>[1]
|
}
|
||||||
)
|
)
|
||||||
if (!response.ok) throw await responseError(response)
|
if (!response.ok) throw await responseError(response)
|
||||||
await response.json().catch(() => null)
|
await response.json().catch(() => null)
|
||||||
|
|||||||
Reference in new issue
Block a user