Improved dashboard of drive

This commit is contained in:
2025-07-27 02:14:48 +08:00
parent 7ff9605460
commit 2d728e4b07
4 changed files with 244 additions and 20 deletions

View File

@@ -0,0 +1,198 @@
<template>
<n-select
:value="modelValue"
@update:value="onUpdate"
:options="pools ?? []"
:render-label="renderPoolSelectLabel"
:render-tag="renderSingleSelectTag"
value-field="id"
label-field="name"
:placeholder="props.placeholder || 'Select a file pool to upload'"
clearable
size="large"
/>
</template>
<script setup lang="ts">
import {
NSelect,
NTag,
NDivider,
NTooltip,
type SelectOption,
type SelectRenderTag,
} from 'naive-ui'
import { h, onMounted, ref, watch } from 'vue'
import type { SnFilePool } from '@/types/pool'
import { formatBytes } from '@/views/format'
const props = defineProps<{
modelValue: string | null
placeholder: string | undefined
}>()
const emit = defineEmits(['update:modelValue', 'update:pool'])
type SnFilePoolOption = SnFilePool & any
const pools = ref<SnFilePoolOption[] | undefined>()
async function fetchPools() {
const resp = await fetch('/api/pools')
pools.value = await resp.json()
}
onMounted(() => fetchPools())
function onUpdate(value: string | null) {
emit('update:modelValue', value)
if (value === null) {
emit('update:pool', null)
return
}
if (pools.value) {
const pool = pools.value.find((p) => p.id === value) ?? null
emit('update:pool', pool)
}
}
watch(pools, (newPools) => {
if (props.modelValue && newPools) {
const pool = newPools.find((p) => p.id === props.modelValue) ?? null
emit('update:pool', pool)
}
})
const renderSingleSelectTag: SelectRenderTag = ({ option }) => {
return h(
'div',
{
style: {
display: 'flex',
alignItems: 'center',
},
},
[option.name as string],
)
}
const perkPrivilegeList = ['Stellar', 'Nova', 'Supernova']
function renderPoolSelectLabel(option: SelectOption & SnFilePool) {
const policy: any = option.policy_config
return h(
'div',
{
style: {
padding: '8px 2px',
},
},
[
h('div', null, [option.name as string]),
option.description &&
h(
'div',
{
style: {
fontSize: '0.875rem',
opacity: '0.75',
},
},
option.description,
),
h(
'div',
{
style: {
display: 'flex',
marginBottom: '4px',
fontSize: '0.75rem',
opacity: '0.75',
},
},
[
policy.max_file_size && h('span', `Max ${formatBytes(policy.max_file_size)}`),
policy.accept_types &&
h(
NTooltip,
{},
{
trigger: () => h('span', `Accept limited types`),
default: () => h('span', policy.accept_types.join(', ')),
},
),
policy.require_privilege &&
h('span', `Require ${perkPrivilegeList[policy.require_privilege - 1]} Program`),
h('span', `Cost x${option.billing_config.cost_multiplier.toFixed(1)} NSD`),
]
.filter((el) => el)
.flatMap((el, idx, arr) =>
idx < arr.length - 1 ? [el, h(NDivider, { vertical: true })] : [el],
),
),
h(
'div',
{
style: {
display: 'flex',
gap: '0.25rem',
marginTop: '2px',
marginLeft: '-2px',
marginRight: '-2px',
},
},
[
policy.public_usable &&
h(
NTag,
{
type: 'info',
size: 'small',
round: true,
},
{ default: () => 'Public Shared' },
),
policy.public_indexable &&
h(
NTag,
{
type: 'success',
size: 'small',
round: true,
},
{ default: () => 'Public Indexable' },
),
policy.allow_encryption &&
h(
NTag,
{
type: 'warning',
size: 'small',
round: true,
},
{ default: () => 'Allow Encryption' },
),
policy.allow_anonymous &&
h(
NTag,
{
type: 'info',
size: 'small',
round: true,
},
{ default: () => 'Allow Anonymous' },
),
policy.enable_recycle &&
h(
NTag,
{
type: 'info',
size: 'small',
round: true,
},
{ default: () => 'Recycle Enabled' },
),
],
),
],
)
}
</script>

View File

@@ -1,6 +1,22 @@
<template> <template>
<section class="h-full px-5 py-4"> <section class="h-full px-5 py-4">
<n-data-table :columns="tableColumns" :data="files" :pagination="tablePagination" /> <div class="flex flex-col gap-3 mb-3">
<file-pool-select
v-model="filePool"
placeholder="Filter by file pool"
class="max-w-[480px]"
@update:pool="fetchFiles"
/>
</div>
<n-data-table
remote
:row-key="(row) => row.id"
:columns="tableColumns"
:data="files"
:loading="loading"
:pagination="tablePagination"
@page-change="handlePageChange"
/>
</section> </section>
</template> </template>
@@ -27,10 +43,12 @@ import {
import { h, onMounted, ref } from 'vue' import { h, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { formatBytes } from '../format' import { formatBytes } from '../format'
import FilePoolSelect from '@/components/FilePoolSelect.vue'
const router = useRouter() const router = useRouter()
const files = ref<any[]>([]) const files = ref<any[]>([])
const filePool = ref<string | null>(null)
const tableColumns: DataTableColumns<any> = [ const tableColumns: DataTableColumns<any> = [
{ {
@@ -95,7 +113,6 @@ const tableColumns: DataTableColumns<any> = [
h( h(
NButton, NButton,
{ {
quaternary: true,
circle: true, circle: true,
text: true, text: true,
onClick: () => { onClick: () => {
@@ -109,7 +126,6 @@ const tableColumns: DataTableColumns<any> = [
h( h(
NButton, NButton,
{ {
quaternary: true,
circle: true, circle: true,
text: true, text: true,
type: 'error', type: 'error',
@@ -129,11 +145,19 @@ const tableColumns: DataTableColumns<any> = [
const tablePagination = ref<PaginationProps>({ const tablePagination = ref<PaginationProps>({
page: 1, page: 1,
itemCount: 0, itemCount: 0,
pageSize: 10,
showSizePicker: true,
pageSizes: [10, 20, 30, 40, 50],
}) })
async function fetchFiles() { async function fetchFiles() {
if (loading.value) return
try { try {
const response = await fetch('/api/files/me') 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 : ''}`,
)
if (!response.ok) { if (!response.ok) {
throw new Error('Network response was not ok') throw new Error('Network response was not ok')
} }
@@ -142,10 +166,19 @@ async function fetchFiles() {
tablePagination.value.itemCount = parseInt(response.headers.get('x-total') ?? '0') tablePagination.value.itemCount = parseInt(response.headers.get('x-total') ?? '0')
} catch (error) { } catch (error) {
console.error('Failed to fetch files:', error) console.error('Failed to fetch files:', error)
} finally {
loading.value = false
} }
} }
onMounted(() => fetchFiles()) onMounted(() => fetchFiles())
function handlePageChange(page: number) {
tablePagination.value.page = page
fetchFiles()
}
const loading = ref(false)
const dialog = useDialog() const dialog = useDialog()
const messageDialog = useMessage() const messageDialog = useMessage()
const loadingBar = useLoadingBar() const loadingBar = useLoadingBar()

View File

@@ -23,17 +23,7 @@
</template> </template>
<div class="mb-3"> <div class="mb-3">
<n-select <file-pool-select v-model="filePool" @update:pool="currentFilePool = $event" />
v-model:value="filePool"
:options="pools ?? []"
:render-label="renderPoolSelectLabel"
:render-tag="renderSingleSelectTag"
value-field="id"
label-field="name"
placeholder="Select a file pool to upload"
clearable
size="large"
/>
</div> </div>
<n-collapse-transition :show="modeAdvanced"> <n-collapse-transition :show="modeAdvanced">
@@ -134,6 +124,8 @@ import { useUserStore } from '@/stores/user'
import type { SnFilePool } from '@/types/pool' import type { SnFilePool } from '@/types/pool'
import { formatBytes } from './format' import { formatBytes } from './format'
import FilePoolSelect from '@/components/FilePoolSelect.vue'
import * as tus from 'tus-js-client' import * as tus from 'tus-js-client'
const userStore = useUserStore() const userStore = useUserStore()

View File

@@ -146,16 +146,17 @@ public class FileController(
.Where(e => e.AccountId == accountId) .Where(e => e.AccountId == accountId)
.Include(e => e.Pool) .Include(e => e.Pool)
.OrderByDescending(e => e.CreatedAt) .OrderByDescending(e => e.CreatedAt)
.Skip(offset) .AsQueryable();
.Take(take);
if (pool.HasValue) query = query.Where(e => e.PoolId == pool); if (pool.HasValue) query = query.Where(e => e.PoolId == pool);
var files = await query.ToListAsync();
var total = await query.CountAsync(); var total = await query.CountAsync();
Response.Headers.Append("X-Total", total.ToString()); Response.Headers.Append("X-Total", total.ToString());
var files = await query
.Skip(offset)
.Take(take)
.ToListAsync();
return Ok(files); return Ok(files);
} }