✨ Recycled files action
This commit is contained in:
@@ -8,8 +8,8 @@
|
||||
value-field="id"
|
||||
label-field="name"
|
||||
:placeholder="props.placeholder || 'Select a file pool to upload'"
|
||||
:size="props.size || 'large'"
|
||||
clearable
|
||||
size="large"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -29,6 +29,7 @@ import { formatBytes } from '@/views/format'
|
||||
const props = defineProps<{
|
||||
modelValue: string | null
|
||||
placeholder?: string | undefined
|
||||
size?: 'tiny' | 'small' | 'medium' | 'large' | undefined
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'update:pool'])
|
||||
|
@@ -1,12 +1,30 @@
|
||||
<template>
|
||||
<section class="h-full px-5 py-4">
|
||||
<div class="flex flex-col gap-3 mb-3">
|
||||
<div class="flex items-center gap-4 mb-3">
|
||||
<file-pool-select
|
||||
v-model="filePool"
|
||||
placeholder="Filter by file pool"
|
||||
size="medium"
|
||||
class="max-w-[480px]"
|
||||
@update:pool="fetchFiles"
|
||||
/>
|
||||
<div class="flex items-center gap-2.5">
|
||||
<n-switch size="large" v-model:value="showRecycled">
|
||||
<template #checked>Recycled</template>
|
||||
<template #unchecked>Unrecycled</template>
|
||||
</n-switch>
|
||||
<n-button
|
||||
@click="askDeleteRecycledFiles"
|
||||
v-if="showRecycled"
|
||||
type="error"
|
||||
circle
|
||||
size="small"
|
||||
>
|
||||
<n-icon>
|
||||
<delete-sweep-round />
|
||||
</n-icon>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<n-data-table
|
||||
remote
|
||||
@@ -32,6 +50,8 @@ import {
|
||||
useDialog,
|
||||
useMessage,
|
||||
useLoadingBar,
|
||||
NSwitch,
|
||||
NTooltip,
|
||||
} from 'naive-ui'
|
||||
import {
|
||||
AudioFileRound,
|
||||
@@ -39,8 +59,9 @@ import {
|
||||
VideoFileRound,
|
||||
FileDownloadOutlined,
|
||||
DeleteRound,
|
||||
DeleteSweepRound,
|
||||
} from '@vicons/material'
|
||||
import { h, onMounted, ref } from 'vue'
|
||||
import { h, onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { formatBytes } from '../format'
|
||||
import FilePoolSelect from '@/components/FilePoolSelect.vue'
|
||||
@@ -48,7 +69,9 @@ import FilePoolSelect from '@/components/FilePoolSelect.vue'
|
||||
const router = useRouter()
|
||||
|
||||
const files = ref<any[]>([])
|
||||
|
||||
const filePool = ref<string | null>(null)
|
||||
const showRecycled = ref(false)
|
||||
|
||||
const tableColumns: DataTableColumns<any> = [
|
||||
{
|
||||
@@ -98,6 +121,28 @@ const tableColumns: DataTableColumns<any> = [
|
||||
return formatBytes(row.size)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Pool',
|
||||
key: 'pool',
|
||||
render(row: any) {
|
||||
return h(
|
||||
NTooltip,
|
||||
{},
|
||||
{
|
||||
default: () => h('span', row.pool.id),
|
||||
trigger: () => h('span', row.pool.name),
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Expired At',
|
||||
key: 'expired_at',
|
||||
render(row: any) {
|
||||
if (!row.expired_at) return 'Keep-alive'
|
||||
return new Date(row.expired_at).toLocaleString()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Uploaded At',
|
||||
key: 'created_at',
|
||||
@@ -156,7 +201,7 @@ async function fetchFiles() {
|
||||
loading.value = true
|
||||
const pag = tablePagination.value
|
||||
const response = await fetch(
|
||||
`/api/files/me?take=${pag.pageSize}&offset=${(pag.page! - 1) * pag.pageSize!}${filePool.value ? '&pool=' + filePool.value : ''}`,
|
||||
`/api/files/me?take=${pag.pageSize}&offset=${(pag.page! - 1) * pag.pageSize!}&recycled=${showRecycled.value}${filePool.value ? '&pool=' + filePool.value : ''}`,
|
||||
)
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok')
|
||||
@@ -172,6 +217,12 @@ async function fetchFiles() {
|
||||
}
|
||||
onMounted(() => fetchFiles())
|
||||
|
||||
watch(showRecycled, () => {
|
||||
tablePagination.value.itemCount = 0
|
||||
tablePagination.value.page = 1
|
||||
fetchFiles()
|
||||
})
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
tablePagination.value.page = page
|
||||
fetchFiles()
|
||||
@@ -183,10 +234,10 @@ const dialog = useDialog()
|
||||
const messageDialog = useMessage()
|
||||
const loadingBar = useLoadingBar()
|
||||
|
||||
async function askDeleteFile(file: any) {
|
||||
function askDeleteFile(file: any) {
|
||||
dialog.warning({
|
||||
title: 'Confirm',
|
||||
content: `Are you sure you want delete ${file.name}?`,
|
||||
content: `Are you sure you want delete ${file.name}? This will delete the stored file data immediately, there is no return.`,
|
||||
positiveText: 'Sure',
|
||||
negativeText: 'Not Sure',
|
||||
draggable: true,
|
||||
@@ -214,4 +265,37 @@ async function deleteFile(file: any) {
|
||||
messageDialog.error('Failed to delete file: ' + (error as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
function askDeleteRecycledFiles() {
|
||||
dialog.warning({
|
||||
title: 'Confirm',
|
||||
content: `Are you sure you want to delete all ${tablePagination.value.itemCount} marked recycled file(s) by system?`,
|
||||
positiveText: 'Sure',
|
||||
negativeText: 'Not Sure',
|
||||
draggable: true,
|
||||
onPositiveClick: () => {
|
||||
deleteRecycledFiles()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteRecycledFiles() {
|
||||
try {
|
||||
loadingBar.start()
|
||||
const response = await fetch('/api/files/me/recycle', {
|
||||
method: 'DELETE',
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok')
|
||||
}
|
||||
const resp = await response.json()
|
||||
tablePagination.value.page = 1
|
||||
await fetchFiles()
|
||||
loadingBar.finish()
|
||||
messageDialog.success(`Recycled files deleted successfully, deleted count: ${resp.count}`)
|
||||
} catch (error) {
|
||||
loadingBar.error()
|
||||
messageDialog.error('Failed to delete recycled files: ' + (error as Error).message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
Reference in New Issue
Block a user