feat: add batch file policy actions
This commit is contained in:
@@ -5,6 +5,8 @@ import { useAlertStore } from '@/stores/alertStore'
|
|||||||
import type {
|
import type {
|
||||||
AdminBatchEditForm,
|
AdminBatchEditForm,
|
||||||
AdminBatchDeleteFilesResponse,
|
AdminBatchDeleteFilesResponse,
|
||||||
|
AdminBatchPolicyActionRequest,
|
||||||
|
AdminBatchPolicyActionResponse,
|
||||||
AdminBatchUpdateFilesRequest,
|
AdminBatchUpdateFilesRequest,
|
||||||
AdminBatchUpdateFilesResponse,
|
AdminBatchUpdateFilesResponse,
|
||||||
AdminFileDetailResponse,
|
AdminFileDetailResponse,
|
||||||
@@ -81,6 +83,8 @@ type DetailPolicyActionOption = {
|
|||||||
description: string
|
description: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BatchPolicyActionOption = DetailPolicyActionOption
|
||||||
|
|
||||||
type HealthFilterOption = {
|
type HealthFilterOption = {
|
||||||
value: AdminFileHealthFilter
|
value: AdminFileHealthFilter
|
||||||
label: string
|
label: string
|
||||||
@@ -151,8 +155,11 @@ export function useAdminFiles() {
|
|||||||
const selectedFileIds = ref<Set<number>>(new Set())
|
const selectedFileIds = ref<Set<number>>(new Set())
|
||||||
const isBatchDeleting = ref(false)
|
const isBatchDeleting = ref(false)
|
||||||
const isBatchUpdating = ref(false)
|
const isBatchUpdating = ref(false)
|
||||||
|
const isBatchPolicyActionRunning = ref(false)
|
||||||
let detailRequestSerial = 0
|
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 totalPages = computed(() => Math.max(Math.ceil(params.value.total / params.value.size), 1))
|
||||||
const storageUsedText = computed(() => formatFileSize(summary.value.storageUsed))
|
const storageUsedText = computed(() => formatFileSize(summary.value.storageUsed))
|
||||||
const currentPageSelectedCount = computed(
|
const currentPageSelectedCount = computed(
|
||||||
@@ -257,6 +264,10 @@ export function useAdminFiles() {
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const batchPolicyActionOptions = computed<BatchPolicyActionOption[]>(
|
||||||
|
() => detailPolicyActionOptions.value
|
||||||
|
)
|
||||||
|
|
||||||
const inferIsText = (file: FileListItem) => {
|
const inferIsText = (file: FileListItem) => {
|
||||||
if (typeof file.isText === 'boolean') return file.isText
|
if (typeof file.isText === 'boolean') return file.isText
|
||||||
if (typeof file.is_text === 'boolean') return file.is_text
|
if (typeof file.is_text === 'boolean') return file.is_text
|
||||||
@@ -657,7 +668,24 @@ export function useAdminFiles() {
|
|||||||
return request
|
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 now = new Date()
|
||||||
const expiredAt = file.expired_at ? new Date(file.expired_at) : null
|
const expiredAt = file.expired_at ? new Date(file.expired_at) : null
|
||||||
if (!expiredAt || Number.isNaN(expiredAt.getTime()) || expiredAt.getTime() < now.getTime()) {
|
if (!expiredAt || Number.isNaN(expiredAt.getTime()) || expiredAt.getTime() < now.getTime()) {
|
||||||
@@ -667,7 +695,7 @@ export function useAdminFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buildLegacyPolicyActionPayload = (
|
const buildLegacyPolicyActionPayload = (
|
||||||
file: AdminFileDetailViewItem,
|
file: Pick<FileListItem, 'id' | 'expired_at'>,
|
||||||
action: AdminFilePolicyAction
|
action: AdminFilePolicyAction
|
||||||
): AdminFilePatchPayload => {
|
): AdminFilePatchPayload => {
|
||||||
if (action === 'make_permanent') {
|
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 () => {
|
const deleteSelectedFiles = async () => {
|
||||||
if (!hasSelectedFiles.value || isBatchActionRunning.value) return
|
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) => {
|
const openTextPreview = async (file: AdminFileViewItem) => {
|
||||||
if (!file.isTextFile || isPreviewLoading.value) return
|
if (!file.isTextFile || isPreviewLoading.value) return
|
||||||
|
|
||||||
@@ -1135,6 +1264,7 @@ export function useAdminFiles() {
|
|||||||
isAllCurrentPageSelected,
|
isAllCurrentPageSelected,
|
||||||
isBatchActionRunning,
|
isBatchActionRunning,
|
||||||
isBatchDeleting,
|
isBatchDeleting,
|
||||||
|
isBatchPolicyActionRunning,
|
||||||
isBatchUpdating,
|
isBatchUpdating,
|
||||||
isCurrentPagePartiallySelected,
|
isCurrentPagePartiallySelected,
|
||||||
isDetailLoading,
|
isDetailLoading,
|
||||||
@@ -1142,6 +1272,7 @@ export function useAdminFiles() {
|
|||||||
isPreviewLoading,
|
isPreviewLoading,
|
||||||
isSaving,
|
isSaving,
|
||||||
batchEditForm,
|
batchEditForm,
|
||||||
|
batchPolicyActionOptions,
|
||||||
detailPolicyActionOptions,
|
detailPolicyActionOptions,
|
||||||
downloadingFileId,
|
downloadingFileId,
|
||||||
healthFilterOptions,
|
healthFilterOptions,
|
||||||
@@ -1176,6 +1307,7 @@ export function useAdminFiles() {
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
applyDetailPolicyAction,
|
applyDetailPolicyAction,
|
||||||
|
applySelectedPolicyAction,
|
||||||
handleBatchUpdate,
|
handleBatchUpdate,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
loadFiles,
|
loadFiles,
|
||||||
|
|||||||
@@ -482,6 +482,10 @@ export default {
|
|||||||
batchUpdatePartialSuccess: 'Updated {count} files, {failed} failed',
|
batchUpdatePartialSuccess: 'Updated {count} files, {failed} failed',
|
||||||
batchUpdateFailed: 'Batch update failed',
|
batchUpdateFailed: 'Batch update failed',
|
||||||
batchUpdateNoFields: 'Choose a policy to update',
|
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',
|
policyActionSuccess: 'File policy updated',
|
||||||
policyActionFailed: 'Failed to apply policy action',
|
policyActionFailed: 'Failed to apply policy action',
|
||||||
policyActions: {
|
policyActions: {
|
||||||
|
|||||||
@@ -480,6 +480,10 @@ export default {
|
|||||||
batchUpdatePartialSuccess: '已更新 {count} 个文件,{failed} 个失败',
|
batchUpdatePartialSuccess: '已更新 {count} 个文件,{failed} 个失败',
|
||||||
batchUpdateFailed: '批量更新失败',
|
batchUpdateFailed: '批量更新失败',
|
||||||
batchUpdateNoFields: '请选择要更新的策略',
|
batchUpdateNoFields: '请选择要更新的策略',
|
||||||
|
batchPolicyActionConfirm: '确认对选中的 {count} 个文件执行“{action}”?',
|
||||||
|
batchPolicyActionSuccess: '已处理 {count} 个文件',
|
||||||
|
batchPolicyActionPartialSuccess: '已处理 {count} 个文件,{failed} 个失败',
|
||||||
|
batchPolicyActionFailed: '批量策略动作执行失败',
|
||||||
policyActionSuccess: '文件策略已更新',
|
policyActionSuccess: '文件策略已更新',
|
||||||
policyActionFailed: '策略动作执行失败',
|
policyActionFailed: '策略动作执行失败',
|
||||||
policyActions: {
|
policyActions: {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import api, { rawApiClient } from './client'
|
|||||||
import { multipartUploadConfig } from './shared'
|
import { multipartUploadConfig } from './shared'
|
||||||
import type {
|
import type {
|
||||||
AdminBatchDeleteFilesResponse,
|
AdminBatchDeleteFilesResponse,
|
||||||
|
AdminBatchPolicyActionRequest,
|
||||||
|
AdminBatchPolicyActionResponse,
|
||||||
AdminBatchUpdateFilesRequest,
|
AdminBatchUpdateFilesRequest,
|
||||||
AdminBatchUpdateFilesResponse,
|
AdminBatchUpdateFilesResponse,
|
||||||
AdminFilePatchPayload,
|
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(
|
static async updateAdminFiles(
|
||||||
data: AdminBatchUpdateFilesRequest
|
data: AdminBatchUpdateFilesRequest
|
||||||
): Promise<ApiResponse<AdminBatchUpdateFilesResponse>> {
|
): Promise<ApiResponse<AdminBatchUpdateFilesResponse>> {
|
||||||
|
|||||||
@@ -323,6 +323,17 @@ export interface AdminBatchUpdateFilesResponse {
|
|||||||
failed?: AdminBatchUpdateFileFailure[]
|
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 type AdminBatchEditMode = 'expiresAt' | 'downloadLimit' | 'forever'
|
||||||
|
|
||||||
export interface AdminBatchEditForm {
|
export interface AdminBatchEditForm {
|
||||||
|
|||||||
@@ -198,10 +198,25 @@
|
|||||||
</template>
|
</template>
|
||||||
{{ t('fileManage.clearSelection') }}
|
{{ t('fileManage.clearSelection') }}
|
||||||
</BaseButton>
|
</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
|
<BaseButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
:disabled="!hasSelectedFiles || isBatchDeleting"
|
:disabled="!hasSelectedFiles || isBatchDeleting || isBatchPolicyActionRunning"
|
||||||
:loading="isBatchUpdating"
|
:loading="isBatchUpdating"
|
||||||
@click="openBatchEditModal"
|
@click="openBatchEditModal"
|
||||||
>
|
>
|
||||||
@@ -213,7 +228,7 @@
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
variant="danger"
|
variant="danger"
|
||||||
size="sm"
|
size="sm"
|
||||||
:disabled="!hasSelectedFiles || isBatchUpdating"
|
:disabled="!hasSelectedFiles || isBatchUpdating || isBatchPolicyActionRunning"
|
||||||
:loading="isBatchDeleting"
|
:loading="isBatchDeleting"
|
||||||
@click="deleteSelectedFiles"
|
@click="deleteSelectedFiles"
|
||||||
>
|
>
|
||||||
@@ -1093,6 +1108,7 @@ const {
|
|||||||
isAllCurrentPageSelected,
|
isAllCurrentPageSelected,
|
||||||
isBatchActionRunning,
|
isBatchActionRunning,
|
||||||
isBatchDeleting,
|
isBatchDeleting,
|
||||||
|
isBatchPolicyActionRunning,
|
||||||
isBatchUpdating,
|
isBatchUpdating,
|
||||||
isCurrentPagePartiallySelected,
|
isCurrentPagePartiallySelected,
|
||||||
isDetailLoading,
|
isDetailLoading,
|
||||||
@@ -1100,6 +1116,7 @@ const {
|
|||||||
isPreviewLoading,
|
isPreviewLoading,
|
||||||
isSaving,
|
isSaving,
|
||||||
batchEditForm,
|
batchEditForm,
|
||||||
|
batchPolicyActionOptions,
|
||||||
detailPolicyActionOptions,
|
detailPolicyActionOptions,
|
||||||
downloadingFileId,
|
downloadingFileId,
|
||||||
healthFilterOptions,
|
healthFilterOptions,
|
||||||
@@ -1133,6 +1150,7 @@ const {
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
applyDetailPolicyAction,
|
applyDetailPolicyAction,
|
||||||
|
applySelectedPolicyAction,
|
||||||
handleBatchUpdate,
|
handleBatchUpdate,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
loadFiles,
|
loadFiles,
|
||||||
|
|||||||
Reference in New Issue
Block a user