✨ Setup drive dashboard
This commit is contained in:
6
DysonNetwork.Drive/Billing/UsageController.cs
Normal file
6
DysonNetwork.Drive/Billing/UsageController.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace DysonNetwork.Drive.Billing;
|
||||||
|
|
||||||
|
public class UsageController
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
42
DysonNetwork.Drive/Client/src/layouts/dashboard.vue
Normal file
42
DysonNetwork.Drive/Client/src/layouts/dashboard.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<n-layout has-sider class="h-full">
|
||||||
|
<n-layout-sider bordered collapse-mode="width" :collapsed-width="64" :width="240" show-trigger>
|
||||||
|
<n-menu
|
||||||
|
:collapsed-width="64"
|
||||||
|
:collapsed-icon-size="22"
|
||||||
|
:options="menuOptions"
|
||||||
|
:value="route.name as string"
|
||||||
|
@update:value="updateMenuSelect"
|
||||||
|
/>
|
||||||
|
</n-layout-sider>
|
||||||
|
<n-layout>
|
||||||
|
<router-view />
|
||||||
|
</n-layout>
|
||||||
|
</n-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { DataUsageRound } from '@vicons/material'
|
||||||
|
import { NIcon, NLayout, NLayoutSider, NMenu, type MenuOption } from 'naive-ui'
|
||||||
|
import { h, type Component } from 'vue'
|
||||||
|
import { RouterView, useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
function renderIcon(icon: Component) {
|
||||||
|
return () => h(NIcon, null, { default: () => h(icon) })
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuOptions: MenuOption[] = [
|
||||||
|
{
|
||||||
|
label: 'Usage',
|
||||||
|
key: 'dashboardUsage',
|
||||||
|
icon: renderIcon(DataUsageRound),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function updateMenuSelect(key: string) {
|
||||||
|
router.push({ name: key })
|
||||||
|
}
|
||||||
|
</script>
|
@@ -15,7 +15,7 @@
|
|||||||
</n-dropdown>
|
</n-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</n-layout-header>
|
</n-layout-header>
|
||||||
<n-layout-content embedded content-style="padding: 24px;">
|
<n-layout-content embedded>
|
||||||
<router-view />
|
<router-view />
|
||||||
</n-layout-content>
|
</n-layout-content>
|
||||||
</n-layout>
|
</n-layout>
|
||||||
@@ -28,12 +28,15 @@ import {
|
|||||||
LogInOutlined,
|
LogInOutlined,
|
||||||
PersonAddAlt1Outlined,
|
PersonAddAlt1Outlined,
|
||||||
PersonOutlineRound,
|
PersonOutlineRound,
|
||||||
|
DataUsageRound,
|
||||||
} from '@vicons/material'
|
} from '@vicons/material'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useServicesStore } from '@/stores/services'
|
import { useServicesStore } from '@/stores/services'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const hideUserMenu = computed(() => {
|
const hideUserMenu = computed(() => {
|
||||||
@@ -60,6 +63,14 @@ const guestOptions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const userOptions = computed(() => [
|
const userOptions = computed(() => [
|
||||||
|
{
|
||||||
|
label: 'Usage',
|
||||||
|
key: 'dashboardUsage',
|
||||||
|
icon: () =>
|
||||||
|
h(NIcon, null, {
|
||||||
|
default: () => h(DataUsageRound),
|
||||||
|
}),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Profile',
|
label: 'Profile',
|
||||||
key: 'profile',
|
key: 'profile',
|
||||||
@@ -67,7 +78,7 @@ const userOptions = computed(() => [
|
|||||||
h(NIcon, null, {
|
h(NIcon, null, {
|
||||||
default: () => h(PersonOutlineRound),
|
default: () => h(PersonOutlineRound),
|
||||||
}),
|
}),
|
||||||
}
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const servicesStore = useServicesStore()
|
const servicesStore = useServicesStore()
|
||||||
@@ -83,6 +94,8 @@ function handleGuestMenuSelect(key: string) {
|
|||||||
function handleUserMenuSelect(key: string) {
|
function handleUserMenuSelect(key: string) {
|
||||||
if (key === 'profile') {
|
if (key === 'profile') {
|
||||||
window.open(servicesStore.getSerivceUrl('DysonNetwork.Pass', 'accounts/me')!, '_blank')
|
window.open(servicesStore.getSerivceUrl('DysonNetwork.Pass', 'accounts/me')!, '_blank')
|
||||||
|
} else {
|
||||||
|
router.push({ name: key })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { useServicesStore } from '@/stores/services'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@@ -7,26 +8,53 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'index',
|
name: 'index',
|
||||||
component: () => import('../views/index.vue')
|
component: () => import('../views/index.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/files/:fileId',
|
path: '/files/:fileId',
|
||||||
name: 'files',
|
name: 'files',
|
||||||
component: () => import('../views/files.vue'),
|
component: () => import('../views/files.vue'),
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
path: '/dashboard',
|
||||||
|
name: 'dashboard',
|
||||||
|
component: () => import('../layouts/dashboard.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'usage',
|
||||||
|
name: 'dashboardUsage',
|
||||||
|
component: () => import('../views/dashboard/usage.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:notFound(.*)',
|
||||||
|
name: 'errorNotFound',
|
||||||
|
component: () => import('../views/not-found.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const servicesStore = useServicesStore()
|
||||||
|
|
||||||
// Initialize user state if not already initialized
|
// Initialize user state if not already initialized
|
||||||
if (!userStore.user && localStorage.getItem('authToken')) {
|
if (!userStore.user && localStorage.getItem('authToken')) {
|
||||||
await userStore.initialize()
|
await userStore.fetchUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to.matched.some((record) => record.meta.requiresAuth) && !userStore.isAuthenticated) {
|
if (to.matched.some((record) => record.meta.requiresAuth) && !userStore.isAuthenticated) {
|
||||||
next({ name: 'login', query: { redirect: to.fullPath } })
|
window.open(
|
||||||
|
servicesStore.getSerivceUrl(
|
||||||
|
'DysonNetwork.Pass',
|
||||||
|
'login?redirect=' + encodeURIComponent(window.location.href),
|
||||||
|
)!,
|
||||||
|
'_blank',
|
||||||
|
)
|
||||||
|
next('/')
|
||||||
} else {
|
} else {
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,8 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
const isAuthenticated = computed(() => !!user.value)
|
const isAuthenticated = computed(() => !!user.value)
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
async function fetchUser() {
|
async function fetchUser(reload = true) {
|
||||||
|
if (!reload && user.value) return
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
@@ -21,9 +22,6 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
// If the token is invalid, clear it and the user state
|
// If the token is invalid, clear it and the user state
|
||||||
if (response.status === 401) {
|
|
||||||
logout()
|
|
||||||
}
|
|
||||||
throw new Error('Failed to fetch user information.')
|
throw new Error('Failed to fetch user information.')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,13 +34,6 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
|
||||||
user.value = null
|
|
||||||
localStorage.removeItem('authToken')
|
|
||||||
// Optionally, redirect to login page
|
|
||||||
// router.push('/login')
|
|
||||||
}
|
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
const allowedOrigin = import.meta.env.DEV ? window.location.origin : 'https://id.solian.app'
|
const allowedOrigin = import.meta.env.DEV ? window.location.origin : 'https://id.solian.app'
|
||||||
window.addEventListener('message', (event) => {
|
window.addEventListener('message', (event) => {
|
||||||
@@ -69,7 +60,6 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
error,
|
error,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
fetchUser,
|
fetchUser,
|
||||||
logout,
|
|
||||||
initialize,
|
initialize,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -1,35 +1,36 @@
|
|||||||
export interface SnFilePool {
|
export interface SnFilePool {
|
||||||
id: string;
|
id: string
|
||||||
name: string;
|
name: string
|
||||||
storage_config: StorageConfig;
|
description: string
|
||||||
billing_config: BillingConfig;
|
storage_config: StorageConfig
|
||||||
public_indexable: boolean;
|
billing_config: BillingConfig
|
||||||
public_usable: boolean;
|
public_indexable: boolean
|
||||||
no_optimization: boolean;
|
public_usable: boolean
|
||||||
no_metadata: boolean;
|
no_optimization: boolean
|
||||||
allow_encryption: boolean;
|
no_metadata: boolean
|
||||||
allow_anonymous: boolean;
|
allow_encryption: boolean
|
||||||
require_privilege: number;
|
allow_anonymous: boolean
|
||||||
account_id: null;
|
require_privilege: number
|
||||||
resource_identifier: string;
|
account_id: null
|
||||||
created_at: Date;
|
resource_identifier: string
|
||||||
updated_at: Date;
|
created_at: Date
|
||||||
deleted_at: null;
|
updated_at: Date
|
||||||
|
deleted_at: null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BillingConfig {
|
export interface BillingConfig {
|
||||||
cost_multiplier: number;
|
cost_multiplier: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageConfig {
|
export interface StorageConfig {
|
||||||
region: string;
|
region: string
|
||||||
bucket: string;
|
bucket: string
|
||||||
endpoint: string;
|
endpoint: string
|
||||||
secret_id: string;
|
secret_id: string
|
||||||
secret_key: string;
|
secret_key: string
|
||||||
enable_signed: boolean;
|
enable_signed: boolean
|
||||||
enable_ssl: boolean;
|
enable_ssl: boolean
|
||||||
image_proxy: null;
|
image_proxy: null
|
||||||
access_proxy: null;
|
access_proxy: null
|
||||||
expiration: null;
|
expiration: null
|
||||||
}
|
}
|
||||||
|
9
DysonNetwork.Drive/Client/src/views/dashboard/usage.vue
Normal file
9
DysonNetwork.Drive/Client/src/views/dashboard/usage.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<section class="h-full"></section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useUserStore } from '@/stores/user';
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
</script>
|
@@ -143,6 +143,7 @@ import { useRoute } from 'vue-router'
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
import { downloadAndDecryptFile } from './secure'
|
import { downloadAndDecryptFile } from './secure'
|
||||||
|
import { formatBytes } from './format'
|
||||||
|
|
||||||
import hljs from 'highlight.js/lib/core'
|
import hljs from 'highlight.js/lib/core'
|
||||||
import json from 'highlight.js/lib/languages/json'
|
import json from 'highlight.js/lib/languages/json'
|
||||||
@@ -200,13 +201,4 @@ function downloadFile() {
|
|||||||
window.open(fileSource.value, '_blank')
|
window.open(fileSource.value, '_blank')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatBytes(bytes: number, decimals = 2): string {
|
|
||||||
if (bytes === 0) return '0 Bytes'
|
|
||||||
const k = 1024
|
|
||||||
const dm = decimals < 0 ? 0 : decimals
|
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
8
DysonNetwork.Drive/Client/src/views/format.ts
Normal file
8
DysonNetwork.Drive/Client/src/views/format.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export function formatBytes(bytes: number, decimals = 2): string {
|
||||||
|
if (bytes === 0) return '0 Bytes'
|
||||||
|
const k = 1024
|
||||||
|
const dm = decimals < 0 ? 0 : decimals
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
||||||
|
}
|
@@ -49,7 +49,9 @@
|
|||||||
type="password"
|
type="password"
|
||||||
class="mb-2"
|
class="mb-2"
|
||||||
/>
|
/>
|
||||||
<p class="pl-1 text-xs opacity-75 mt-[-4px]">Only available for Stellar Program and certian file pool.</p>
|
<p class="pl-1 text-xs opacity-75 mt-[-4px]">
|
||||||
|
Only available for Stellar Program and certian file pool.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-collapse-transition>
|
</n-collapse-transition>
|
||||||
@@ -110,11 +112,15 @@ import {
|
|||||||
type SelectOption,
|
type SelectOption,
|
||||||
type SelectRenderTag,
|
type SelectRenderTag,
|
||||||
type UploadFileInfo,
|
type UploadFileInfo,
|
||||||
|
useMessage,
|
||||||
|
NDivider,
|
||||||
|
NTooltip,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, h, onMounted, ref } from 'vue'
|
import { computed, h, onMounted, ref } from 'vue'
|
||||||
import { CloudUploadRound } from '@vicons/material'
|
import { CloudUploadRound } from '@vicons/material'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import type { SnFilePool } from '@/types/pool'
|
import type { SnFilePool } from '@/types/pool'
|
||||||
|
import { formatBytes } from './format'
|
||||||
|
|
||||||
import * as tus from 'tus-js-client'
|
import * as tus from 'tus-js-client'
|
||||||
|
|
||||||
@@ -160,6 +166,42 @@ function renderPoolSelectLabel(option: SelectOption & SnFilePool) {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
h('div', null, [option.name as string]),
|
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(', ')),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
].flatMap((el, idx, arr) =>
|
||||||
|
idx < arr.length - 1 ? [el, h(NDivider, { vertical: true })] : [el],
|
||||||
|
),
|
||||||
|
),
|
||||||
h(
|
h(
|
||||||
'div',
|
'div',
|
||||||
{
|
{
|
||||||
@@ -228,6 +270,8 @@ const currentFilePool = computed(() => {
|
|||||||
return pools.value?.find((pool) => pool.id === filePool.value) ?? null
|
return pools.value?.find((pool) => pool.id === filePool.value) ?? null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const messageDisplay = useMessage()
|
||||||
|
|
||||||
function customRequest({
|
function customRequest({
|
||||||
file,
|
file,
|
||||||
data,
|
data,
|
||||||
@@ -246,13 +290,21 @@ function customRequest({
|
|||||||
retryDelays: [0, 3000, 5000, 10000, 20000],
|
retryDelays: [0, 3000, 5000, 10000, 20000],
|
||||||
metadata: {
|
metadata: {
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
filetype: file.type ?? 'application/octet-stream',
|
'content-type': file.type ?? 'application/octet-stream',
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
...requestHeaders,
|
...requestHeaders,
|
||||||
...headers,
|
...headers,
|
||||||
},
|
},
|
||||||
onError: function (error) {
|
onError: function (error) {
|
||||||
|
if (error instanceof tus.DetailedError) {
|
||||||
|
const failedBody = error.originalResponse?.getBody()
|
||||||
|
if (failedBody != null)
|
||||||
|
messageDisplay.error(`Upload failed: ${failedBody}`, {
|
||||||
|
duration: 10000,
|
||||||
|
closable: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
console.error('[DRIVE] Upload failed:', error)
|
console.error('[DRIVE] Upload failed:', error)
|
||||||
onError()
|
onError()
|
||||||
},
|
},
|
||||||
@@ -290,8 +342,7 @@ function createThumbnailUrl(
|
|||||||
|
|
||||||
function customDownload(file: UploadFileInfo) {
|
function customDownload(file: UploadFileInfo) {
|
||||||
const { url, name } = file
|
const { url, name } = file
|
||||||
if (!url)
|
if (!url) return
|
||||||
return
|
|
||||||
window.open(url.replace('/api', ''), '_blank')
|
window.open(url.replace('/api', ''), '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
DysonNetwork.Drive/Client/src/views/not-found.vue
Normal file
16
DysonNetwork.Drive/Client/src/views/not-found.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<section class="h-full flex items-center justify-center">
|
||||||
|
<n-result status="404" title="404" description="Page not found">
|
||||||
|
<template #footer>
|
||||||
|
<n-button @click="router.push('/')">Go to Home</n-button>
|
||||||
|
</template>
|
||||||
|
</n-result>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { NResult, NButton } from 'naive-ui'
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
</script>
|
271
DysonNetwork.Drive/Migrations/20250726120323_AddFilePoolDescription.Designer.cs
generated
Normal file
271
DysonNetwork.Drive/Migrations/20250726120323_AddFilePoolDescription.Designer.cs
generated
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using DysonNetwork.Drive;
|
||||||
|
using DysonNetwork.Drive.Storage;
|
||||||
|
using DysonNetwork.Shared.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using NodaTime;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Drive.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDatabase))]
|
||||||
|
[Migration("20250726120323_AddFilePoolDescription")]
|
||||||
|
partial class AddFilePoolDescription
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, object>>("FileMeta")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("file_meta");
|
||||||
|
|
||||||
|
b.Property<bool>("HasCompression")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("has_compression");
|
||||||
|
|
||||||
|
b.Property<bool>("HasThumbnail")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("has_thumbnail");
|
||||||
|
|
||||||
|
b.Property<string>("Hash")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("hash");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEncrypted")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_encrypted");
|
||||||
|
|
||||||
|
b.Property<bool>("IsMarkedRecycle")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_marked_recycle");
|
||||||
|
|
||||||
|
b.Property<string>("MimeType")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("mime_type");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<Guid?>("PoolId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("pool_id");
|
||||||
|
|
||||||
|
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("sensitive_marks");
|
||||||
|
|
||||||
|
b.Property<long>("Size")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("size");
|
||||||
|
|
||||||
|
b.Property<string>("StorageId")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("storage_id");
|
||||||
|
|
||||||
|
b.Property<string>("StorageUrl")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("storage_url");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("UploadedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("uploaded_at");
|
||||||
|
|
||||||
|
b.Property<string>("UploadedTo")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("uploaded_to");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, object>>("UserMeta")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("user_meta");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_files");
|
||||||
|
|
||||||
|
b.HasIndex("PoolId")
|
||||||
|
.HasDatabaseName("ix_files_pool_id");
|
||||||
|
|
||||||
|
b.ToTable("files", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ExpiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
|
b.Property<string>("FileId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)")
|
||||||
|
.HasColumnName("file_id");
|
||||||
|
|
||||||
|
b.Property<string>("ResourceId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("resource_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<string>("Usage")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("usage");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_file_references");
|
||||||
|
|
||||||
|
b.HasIndex("FileId")
|
||||||
|
.HasDatabaseName("ix_file_references_file_id");
|
||||||
|
|
||||||
|
b.ToTable("file_references", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Drive.Storage.FilePool", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid?>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<BillingConfig>("BillingConfig")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("billing_config");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(8192)
|
||||||
|
.HasColumnType("character varying(8192)")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<PolicyConfig>("PolicyConfig")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("policy_config");
|
||||||
|
|
||||||
|
b.Property<RemoteStorageConfig>("StorageConfig")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("storage_config");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_pools");
|
||||||
|
|
||||||
|
b.ToTable("pools", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFile", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Drive.Storage.FilePool", "Pool")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PoolId")
|
||||||
|
.HasConstraintName("fk_files_pools_pool_id");
|
||||||
|
|
||||||
|
b.Navigation("Pool");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Drive.Storage.CloudFileReference", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Drive.Storage.CloudFile", "File")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_file_references_files_file_id");
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Drive.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddFilePoolDescription : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "description",
|
||||||
|
table: "pools",
|
||||||
|
type: "character varying(8192)",
|
||||||
|
maxLength: 8192,
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "description",
|
||||||
|
table: "pools");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -209,6 +209,12 @@ namespace DysonNetwork.Drive.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("deleted_at");
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(8192)
|
||||||
|
.HasColumnType("character varying(8192)")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(1024)
|
.HasMaxLength(1024)
|
||||||
|
@@ -41,6 +41,7 @@ public class FilePool : ModelBase, IIdentifiedResource
|
|||||||
{
|
{
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
[MaxLength(1024)] public string Name { get; set; } = string.Empty;
|
||||||
|
[MaxLength(8192)] public string Description { get; set; } = string.Empty;
|
||||||
[Column(TypeName = "jsonb")] public RemoteStorageConfig StorageConfig { get; set; } = new();
|
[Column(TypeName = "jsonb")] public RemoteStorageConfig StorageConfig { get; set; } = new();
|
||||||
[Column(TypeName = "jsonb")] public BillingConfig BillingConfig { get; set; } = new();
|
[Column(TypeName = "jsonb")] public BillingConfig BillingConfig { get; set; } = new();
|
||||||
[Column(TypeName = "jsonb")] public PolicyConfig PolicyConfig { get; set; } = new();
|
[Column(TypeName = "jsonb")] public PolicyConfig PolicyConfig { get; set; } = new();
|
||||||
|
@@ -173,9 +173,22 @@ public abstract class TusService
|
|||||||
|
|
||||||
// Do the policy check
|
// Do the policy check
|
||||||
var policy = pool!.PolicyConfig;
|
var policy = pool!.PolicyConfig;
|
||||||
|
if (!rejected && !pool.PolicyConfig.AllowEncryption)
|
||||||
|
{
|
||||||
|
var encryptPassword = eventContext.HttpContext.Request.Headers["X-FilePass"].FirstOrDefault();
|
||||||
|
if (!string.IsNullOrEmpty(encryptPassword))
|
||||||
|
{
|
||||||
|
eventContext.FailRequest(
|
||||||
|
HttpStatusCode.Forbidden,
|
||||||
|
"File encryption is not allowed in this pool"
|
||||||
|
);
|
||||||
|
rejected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!rejected && policy.AcceptTypes is not null)
|
if (!rejected && policy.AcceptTypes is not null)
|
||||||
{
|
{
|
||||||
if (contentType is null)
|
if (string.IsNullOrEmpty(contentType))
|
||||||
{
|
{
|
||||||
eventContext.FailRequest(
|
eventContext.FailRequest(
|
||||||
HttpStatusCode.BadRequest,
|
HttpStatusCode.BadRequest,
|
||||||
|
@@ -34,7 +34,12 @@ const router = createRouter({
|
|||||||
name: 'me',
|
name: 'me',
|
||||||
component: () => import('../views/accounts/me.vue'),
|
component: () => import('../views/accounts/me.vue'),
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true }
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
path: '/:notFound(.*)',
|
||||||
|
name: 'errorNotFound',
|
||||||
|
component: () => import('../views/not-found.vue'),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
16
DysonNetwork.Pass/Client/src/views/not-found.vue
Normal file
16
DysonNetwork.Pass/Client/src/views/not-found.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<section class="h-full flex items-center justify-center">
|
||||||
|
<n-result status="404" title="404" description="Page not found">
|
||||||
|
<template #footer>
|
||||||
|
<n-button @click="router.push('/')">Go to Home</n-button>
|
||||||
|
</template>
|
||||||
|
</n-result>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { NResult, NButton } from 'naive-ui'
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
</script>
|
Reference in New Issue
Block a user