✨ Improved dashboard of drive
This commit is contained in:
198
DysonNetwork.Drive/Client/src/components/FilePoolSelect.vue
Normal file
198
DysonNetwork.Drive/Client/src/components/FilePoolSelect.vue
Normal 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>
|
@@ -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()
|
||||||
|
@@ -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()
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user