feat: add batch file policy actions
This commit is contained in:
@@ -5,6 +5,8 @@ import { useAlertStore } from '@/stores/alertStore'
|
||||
import type {
|
||||
AdminBatchEditForm,
|
||||
AdminBatchDeleteFilesResponse,
|
||||
AdminBatchPolicyActionRequest,
|
||||
AdminBatchPolicyActionResponse,
|
||||
AdminBatchUpdateFilesRequest,
|
||||
AdminBatchUpdateFilesResponse,
|
||||
AdminFileDetailResponse,
|
||||
@@ -81,6 +83,8 @@ type DetailPolicyActionOption = {
|
||||
description: string
|
||||
}
|
||||
|
||||
type BatchPolicyActionOption = DetailPolicyActionOption
|
||||
|
||||
type HealthFilterOption = {
|
||||
value: AdminFileHealthFilter
|
||||
label: string
|
||||
@@ -151,8 +155,11 @@ export function useAdminFiles() {
|
||||
const selectedFileIds = ref<Set<number>>(new Set())
|
||||
const isBatchDeleting = ref(false)
|
||||
const isBatchUpdating = ref(false)
|
||||
const isBatchPolicyActionRunning = ref(false)
|
||||
let detailRequestSerial = 0
|
||||
const isBatchActionRunning = computed(() => isBatchDeleting.value || isBatchUpdating.value)
|
||||
const isBatchActionRunning = computed(
|
||||
() => isBatchDeleting.value || isBatchUpdating.value || isBatchPolicyActionRunning.value
|
||||
)
|
||||
const totalPages = computed(() => Math.max(Math.ceil(params.value.total / params.value.size), 1))
|
||||
const storageUsedText = computed(() => formatFileSize(summary.value.storageUsed))
|
||||
const currentPageSelectedCount = computed(
|
||||
@@ -257,6 +264,10 @@ export function useAdminFiles() {
|
||||
}
|
||||
])
|
||||
|
||||
const batchPolicyActionOptions = computed<BatchPolicyActionOption[]>(
|
||||
() => detailPolicyActionOptions.value
|
||||
)
|
||||
|
||||
const inferIsText = (file: FileListItem) => {
|
||||
if (typeof file.isText === 'boolean') return file.isText
|
||||
if (typeof file.is_text === 'boolean') return file.is_text
|
||||
@@ -657,7 +668,24 @@ export function useAdminFiles() {
|
||||
return request
|
||||
}
|
||||
|
||||
const getPolicyActionBaseDate = (file: AdminFileDetailViewItem) => {
|
||||
const buildBatchPolicyActionRequest = (
|
||||
ids: number[],
|
||||
action: AdminFilePolicyAction
|
||||
): AdminBatchPolicyActionRequest => {
|
||||
const request: AdminBatchPolicyActionRequest = {
|
||||
ids,
|
||||
action
|
||||
}
|
||||
|
||||
if (action === 'reset_download_limit') {
|
||||
request.downloadLimit = detailPolicyDownloadLimit
|
||||
request.download_limit = detailPolicyDownloadLimit
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
const getPolicyActionBaseDate = (file: Pick<FileListItem, 'expired_at'>) => {
|
||||
const now = new Date()
|
||||
const expiredAt = file.expired_at ? new Date(file.expired_at) : null
|
||||
if (!expiredAt || Number.isNaN(expiredAt.getTime()) || expiredAt.getTime() < now.getTime()) {
|
||||
@@ -667,7 +695,7 @@ export function useAdminFiles() {
|
||||
}
|
||||
|
||||
const buildLegacyPolicyActionPayload = (
|
||||
file: AdminFileDetailViewItem,
|
||||
file: Pick<FileListItem, 'id' | 'expired_at'>,
|
||||
action: AdminFilePolicyAction
|
||||
): AdminFilePatchPayload => {
|
||||
if (action === 'make_permanent') {
|
||||
@@ -889,6 +917,60 @@ export function useAdminFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
const applyAdminFilesPolicyActionWithFallback = async (
|
||||
ids: number[],
|
||||
action: AdminFilePolicyAction
|
||||
): Promise<ApiResponse<AdminBatchPolicyActionResponse>> => {
|
||||
const request = buildBatchPolicyActionRequest(ids, action)
|
||||
try {
|
||||
return await FileService.applyAdminFilesPolicyAction(request)
|
||||
} catch (error: unknown) {
|
||||
if (!isLegacyEndpointUnavailable(error)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const filesById = new Map(tableData.value.map((file) => [file.id, file]))
|
||||
const updated: number[] = []
|
||||
const missing: number[] = []
|
||||
const failed: { id: number; reason: string }[] = []
|
||||
|
||||
for (const id of ids) {
|
||||
const file = filesById.get(id)
|
||||
if (!file) {
|
||||
missing.push(id)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
await FileService.updateFile(buildLegacyPolicyActionPayload(file, action))
|
||||
updated.push(id)
|
||||
} catch (legacyError: unknown) {
|
||||
failed.push({ id, reason: getErrorMessage(legacyError, t('common.unknownError')) })
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
detail: {
|
||||
requestedCount: ids.length,
|
||||
requested_count: ids.length,
|
||||
uniqueCount: ids.length,
|
||||
unique_count: ids.length,
|
||||
updatedCount: updated.length,
|
||||
updated_count: updated.length,
|
||||
missingCount: missing.length,
|
||||
missing_count: missing.length,
|
||||
failedCount: failed.length,
|
||||
failed_count: failed.length,
|
||||
action,
|
||||
updated,
|
||||
missing,
|
||||
failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deleteSelectedFiles = async () => {
|
||||
if (!hasSelectedFiles.value || isBatchActionRunning.value) return
|
||||
|
||||
@@ -972,6 +1054,53 @@ export function useAdminFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
const applySelectedPolicyAction = async (action: AdminFilePolicyAction) => {
|
||||
if (!hasSelectedFiles.value || isBatchActionRunning.value) return
|
||||
|
||||
const ids = Array.from(selectedFileIds.value)
|
||||
const actionLabel =
|
||||
batchPolicyActionOptions.value.find((option) => option.action === action)?.label || action
|
||||
if (
|
||||
!window.confirm(
|
||||
t('fileManage.batchPolicyActionConfirm', {
|
||||
count: ids.length,
|
||||
action: actionLabel
|
||||
})
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
isBatchPolicyActionRunning.value = true
|
||||
try {
|
||||
const response = await applyAdminFilesPolicyActionWithFallback(ids, action)
|
||||
const detail = response.detail
|
||||
const updatedCount = detail?.updatedCount ?? detail?.updated_count ?? ids.length
|
||||
const failedCount =
|
||||
(detail?.failedCount ?? detail?.failed_count ?? 0) +
|
||||
(detail?.missingCount ?? detail?.missing_count ?? 0)
|
||||
|
||||
clearSelection()
|
||||
await loadFiles()
|
||||
alertStore.showAlert(
|
||||
t(
|
||||
failedCount > 0
|
||||
? 'fileManage.batchPolicyActionPartialSuccess'
|
||||
: 'fileManage.batchPolicyActionSuccess',
|
||||
{
|
||||
count: updatedCount,
|
||||
failed: failedCount
|
||||
}
|
||||
),
|
||||
failedCount > 0 ? 'warning' : 'success'
|
||||
)
|
||||
} catch (error: unknown) {
|
||||
alertStore.showAlert(getErrorMessage(error, t('fileManage.batchPolicyActionFailed')), 'error')
|
||||
} finally {
|
||||
isBatchPolicyActionRunning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openTextPreview = async (file: AdminFileViewItem) => {
|
||||
if (!file.isTextFile || isPreviewLoading.value) return
|
||||
|
||||
@@ -1135,6 +1264,7 @@ export function useAdminFiles() {
|
||||
isAllCurrentPageSelected,
|
||||
isBatchActionRunning,
|
||||
isBatchDeleting,
|
||||
isBatchPolicyActionRunning,
|
||||
isBatchUpdating,
|
||||
isCurrentPagePartiallySelected,
|
||||
isDetailLoading,
|
||||
@@ -1142,6 +1272,7 @@ export function useAdminFiles() {
|
||||
isPreviewLoading,
|
||||
isSaving,
|
||||
batchEditForm,
|
||||
batchPolicyActionOptions,
|
||||
detailPolicyActionOptions,
|
||||
downloadingFileId,
|
||||
healthFilterOptions,
|
||||
@@ -1176,6 +1307,7 @@ export function useAdminFiles() {
|
||||
handlePageChange,
|
||||
handleSearch,
|
||||
applyDetailPolicyAction,
|
||||
applySelectedPolicyAction,
|
||||
handleBatchUpdate,
|
||||
handleUpdate,
|
||||
loadFiles,
|
||||
|
||||
@@ -482,6 +482,10 @@ export default {
|
||||
batchUpdatePartialSuccess: 'Updated {count} files, {failed} failed',
|
||||
batchUpdateFailed: 'Batch update failed',
|
||||
batchUpdateNoFields: 'Choose a policy to update',
|
||||
batchPolicyActionConfirm: 'Apply "{action}" to the selected {count} files?',
|
||||
batchPolicyActionSuccess: 'Processed {count} files',
|
||||
batchPolicyActionPartialSuccess: 'Processed {count} files, {failed} failed',
|
||||
batchPolicyActionFailed: 'Batch policy action failed',
|
||||
policyActionSuccess: 'File policy updated',
|
||||
policyActionFailed: 'Failed to apply policy action',
|
||||
policyActions: {
|
||||
|
||||
@@ -480,6 +480,10 @@ export default {
|
||||
batchUpdatePartialSuccess: '已更新 {count} 个文件,{failed} 个失败',
|
||||
batchUpdateFailed: '批量更新失败',
|
||||
batchUpdateNoFields: '请选择要更新的策略',
|
||||
batchPolicyActionConfirm: '确认对选中的 {count} 个文件执行“{action}”?',
|
||||
batchPolicyActionSuccess: '已处理 {count} 个文件',
|
||||
batchPolicyActionPartialSuccess: '已处理 {count} 个文件,{failed} 个失败',
|
||||
batchPolicyActionFailed: '批量策略动作执行失败',
|
||||
policyActionSuccess: '文件策略已更新',
|
||||
policyActionFailed: '策略动作执行失败',
|
||||
policyActions: {
|
||||
|
||||
@@ -2,6 +2,8 @@ import api, { rawApiClient } from './client'
|
||||
import { multipartUploadConfig } from './shared'
|
||||
import type {
|
||||
AdminBatchDeleteFilesResponse,
|
||||
AdminBatchPolicyActionRequest,
|
||||
AdminBatchPolicyActionResponse,
|
||||
AdminBatchUpdateFilesRequest,
|
||||
AdminBatchUpdateFilesResponse,
|
||||
AdminFilePatchPayload,
|
||||
@@ -159,6 +161,20 @@ export class FileService {
|
||||
}
|
||||
}
|
||||
|
||||
static async applyAdminFilesPolicyAction(
|
||||
data: AdminBatchPolicyActionRequest
|
||||
): Promise<ApiResponse<AdminBatchPolicyActionResponse>> {
|
||||
try {
|
||||
return await api.patch('/admin/file/batch-policy-action', data)
|
||||
} catch (error: unknown) {
|
||||
if (!isMethodFallbackError(error)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return api.post('/admin/file/batch-policy-action', data)
|
||||
}
|
||||
}
|
||||
|
||||
static async updateAdminFiles(
|
||||
data: AdminBatchUpdateFilesRequest
|
||||
): Promise<ApiResponse<AdminBatchUpdateFilesResponse>> {
|
||||
|
||||
@@ -323,6 +323,17 @@ export interface AdminBatchUpdateFilesResponse {
|
||||
failed?: AdminBatchUpdateFileFailure[]
|
||||
}
|
||||
|
||||
export interface AdminBatchPolicyActionRequest {
|
||||
ids: number[]
|
||||
action: AdminFilePolicyAction
|
||||
downloadLimit?: number
|
||||
download_limit?: number
|
||||
}
|
||||
|
||||
export interface AdminBatchPolicyActionResponse extends AdminBatchUpdateFilesResponse {
|
||||
action?: AdminFilePolicyAction | string
|
||||
}
|
||||
|
||||
export type AdminBatchEditMode = 'expiresAt' | 'downloadLimit' | 'forever'
|
||||
|
||||
export interface AdminBatchEditForm {
|
||||
|
||||
@@ -198,10 +198,25 @@
|
||||
</template>
|
||||
{{ t('fileManage.clearSelection') }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-for="option in batchPolicyActionOptions"
|
||||
:key="option.action"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:title="option.description"
|
||||
:disabled="!hasSelectedFiles || isBatchDeleting || isBatchUpdating"
|
||||
:loading="isBatchPolicyActionRunning"
|
||||
@click="applySelectedPolicyAction(option.action)"
|
||||
>
|
||||
<template #icon>
|
||||
<component :is="getDetailPolicyActionIcon(option.action)" class="mr-2 h-4 w-4" />
|
||||
</template>
|
||||
{{ option.label }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
:disabled="!hasSelectedFiles || isBatchDeleting"
|
||||
:disabled="!hasSelectedFiles || isBatchDeleting || isBatchPolicyActionRunning"
|
||||
:loading="isBatchUpdating"
|
||||
@click="openBatchEditModal"
|
||||
>
|
||||
@@ -213,7 +228,7 @@
|
||||
<BaseButton
|
||||
variant="danger"
|
||||
size="sm"
|
||||
:disabled="!hasSelectedFiles || isBatchUpdating"
|
||||
:disabled="!hasSelectedFiles || isBatchUpdating || isBatchPolicyActionRunning"
|
||||
:loading="isBatchDeleting"
|
||||
@click="deleteSelectedFiles"
|
||||
>
|
||||
@@ -1093,6 +1108,7 @@ const {
|
||||
isAllCurrentPageSelected,
|
||||
isBatchActionRunning,
|
||||
isBatchDeleting,
|
||||
isBatchPolicyActionRunning,
|
||||
isBatchUpdating,
|
||||
isCurrentPagePartiallySelected,
|
||||
isDetailLoading,
|
||||
@@ -1100,6 +1116,7 @@ const {
|
||||
isPreviewLoading,
|
||||
isSaving,
|
||||
batchEditForm,
|
||||
batchPolicyActionOptions,
|
||||
detailPolicyActionOptions,
|
||||
downloadingFileId,
|
||||
healthFilterOptions,
|
||||
@@ -1133,6 +1150,7 @@ const {
|
||||
handlePageChange,
|
||||
handleSearch,
|
||||
applyDetailPolicyAction,
|
||||
applySelectedPolicyAction,
|
||||
handleBatchUpdate,
|
||||
handleUpdate,
|
||||
loadFiles,
|
||||
|
||||
Reference in New Issue
Block a user