mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-08-28 03:29:35 +08:00
feat: Add application domain and file domain configuration
This commit is contained in:
@@ -41,7 +41,9 @@ async def get_system_status():
|
|||||||
"version": VERSION,
|
"version": VERSION,
|
||||||
"title": await ConfigCenter.get("APP_NAME", "Foxel"),
|
"title": await ConfigCenter.get("APP_NAME", "Foxel"),
|
||||||
"logo": await ConfigCenter.get("APP_LOGO", "/logo.svg"),
|
"logo": await ConfigCenter.get("APP_LOGO", "/logo.svg"),
|
||||||
"is_initialized": await has_users()
|
"is_initialized": await has_users(),
|
||||||
|
"app_domain": await ConfigCenter.get("APP_DOMAIN"),
|
||||||
|
"file_domain": await ConfigCenter.get("FILE_DOMAIN"),
|
||||||
}
|
}
|
||||||
return success(system_info)
|
return success(system_info)
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from services.virtual_fs import (
|
|||||||
from services.thumbnail import is_image_filename, get_or_create_thumb, is_raw_filename
|
from services.thumbnail import is_image_filename, get_or_create_thumb, is_raw_filename
|
||||||
from schemas import MkdirRequest, MoveRequest
|
from schemas import MkdirRequest, MoveRequest
|
||||||
from api.response import success
|
from api.response import success
|
||||||
|
from services.config import ConfigCenter
|
||||||
|
|
||||||
router = APIRouter(prefix='/api/fs', tags=["virtual-fs"])
|
router = APIRouter(prefix='/api/fs', tags=["virtual-fs"])
|
||||||
|
|
||||||
@@ -151,7 +152,13 @@ async def get_temp_link(
|
|||||||
"""获取文件的临时公开访问令牌"""
|
"""获取文件的临时公开访问令牌"""
|
||||||
full_path = '/' + full_path if not full_path.startswith('/') else full_path
|
full_path = '/' + full_path if not full_path.startswith('/') else full_path
|
||||||
token = await generate_temp_link_token(full_path, expires_in=expires_in)
|
token = await generate_temp_link_token(full_path, expires_in=expires_in)
|
||||||
return success({"token": token, "path": full_path})
|
file_domain = await ConfigCenter.get("FILE_DOMAIN")
|
||||||
|
if file_domain:
|
||||||
|
file_domain = file_domain.rstrip('/')
|
||||||
|
url = f"{file_domain}/api/fs/public/{token}"
|
||||||
|
else:
|
||||||
|
url = f"/api/fs/public/{token}"
|
||||||
|
return success({"token": token, "path": full_path, "url": url})
|
||||||
|
|
||||||
|
|
||||||
@router.get("/public/{token}")
|
@router.get("/public/{token}")
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export interface SystemStatus {
|
|||||||
title: string;
|
title: string;
|
||||||
logo: string;
|
logo: string;
|
||||||
is_initialized: boolean;
|
is_initialized: boolean;
|
||||||
|
app_domain?: string;
|
||||||
|
file_domain?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function status() {
|
export async function status() {
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ export const vfsApi = {
|
|||||||
streamUrl: (path: string) => `${API_BASE_URL}/fs/stream/${encodeURI(path.replace(/^\/+/, ''))}`,
|
streamUrl: (path: string) => `${API_BASE_URL}/fs/stream/${encodeURI(path.replace(/^\/+/, ''))}`,
|
||||||
stat: (path: string) => request(`/fs/stat/${encodeURI(path.replace(/^\/+/, ''))}`),
|
stat: (path: string) => request(`/fs/stat/${encodeURI(path.replace(/^\/+/, ''))}`),
|
||||||
getTempLinkToken: (path: string, expiresIn: number = 3600) =>
|
getTempLinkToken: (path: string, expiresIn: number = 3600) =>
|
||||||
request<{token: string}>(`/fs/temp-link/${encodeURI(path.replace(/^\/+/, ''))}?expires_in=${expiresIn}`),
|
request<{token: string, path: string, url: string}>(`/fs/temp-link/${encodeURI(path.replace(/^\/+/, ''))}?expires_in=${expiresIn}`),
|
||||||
getTempPublicUrl: (token: string) => `${API_BASE_URL}/fs/public/${token}`,
|
getTempPublicUrl: (token: string) => `${API_BASE_URL}/fs/public/${token}`,
|
||||||
uploadStream: (fullPath: string, file: File, overwrite: boolean = true, onProgress?: (loaded: number, total: number) => void) => {
|
uploadStream: (fullPath: string, file: File, overwrite: boolean = true, onProgress?: (loaded: number, total: number) => void) => {
|
||||||
const enc = encodeURI(fullPath.replace(/^\/+/, ''));
|
const enc = encodeURI(fullPath.replace(/^\/+/, ''));
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { vfsApi } from '../../api/client';
|
import { vfsApi } from '../../api/client';
|
||||||
import type { AppComponentProps } from '../types';
|
import type { AppComponentProps } from '../types';
|
||||||
import { Spin, Result, Button } from 'antd';
|
import { Spin, Result, Button } from 'antd';
|
||||||
|
import { useSystemStatus } from '../../contexts/SystemContext';
|
||||||
|
|
||||||
export const OfficeViewerApp: React.FC<AppComponentProps> = ({ filePath, onRequestClose }) => {
|
export const OfficeViewerApp: React.FC<AppComponentProps> = ({ filePath, onRequestClose }) => {
|
||||||
|
const systemStatus = useSystemStatus();
|
||||||
const [url, setUrl] = useState<string>();
|
const [url, setUrl] = useState<string>();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [err, setErr] = useState<string>();
|
const [err, setErr] = useState<string>();
|
||||||
@@ -17,8 +19,8 @@ export const OfficeViewerApp: React.FC<AppComponentProps> = ({ filePath, onReque
|
|||||||
vfsApi.getTempLinkToken(filePath.replace(/^\/+/, ''))
|
vfsApi.getTempLinkToken(filePath.replace(/^\/+/, ''))
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
// 注意:vfsApi.getTempPublicUrl 返回的是相对路径,我们需要构建完整的 URL
|
const baseUrl = systemStatus?.file_domain || window.location.origin;
|
||||||
const fullUrl = new URL(vfsApi.getTempPublicUrl(res.token), window.location.origin).href;
|
const fullUrl = new URL(res.url, baseUrl).href;
|
||||||
const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(fullUrl)}`;
|
const officeUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(fullUrl)}`;
|
||||||
setUrl(officeUrl);
|
setUrl(officeUrl);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ export const DirectLinkModal = memo(function DirectLinkModal({ entry, path, open
|
|||||||
try {
|
try {
|
||||||
const fullPath = (path === '/' ? '' : path) + '/' + entry.name;
|
const fullPath = (path === '/' ? '' : path) + '/' + entry.name;
|
||||||
const res = await vfsApi.getTempLinkToken(fullPath, expiresIn);
|
const res = await vfsApi.getTempLinkToken(fullPath, expiresIn);
|
||||||
const tempLink = `${window.location.origin}/api/fs/public/${res.token}`;
|
setLink(res.url);
|
||||||
setLink(tempLink);
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
message.error(e.message || '生成链接失败');
|
message.error(e.message || '生成链接失败');
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Modal, Form, Input, Radio, InputNumber, message, Button, Typography } f
|
|||||||
import { CopyOutlined } from '@ant-design/icons';
|
import { CopyOutlined } from '@ant-design/icons';
|
||||||
import type { VfsEntry, ShareInfoWithPassword } from '../../../../api/client';
|
import type { VfsEntry, ShareInfoWithPassword } from '../../../../api/client';
|
||||||
import { shareApi } from '../../../../api/share';
|
import { shareApi } from '../../../../api/share';
|
||||||
|
import { useSystemStatus } from '../../../../contexts/SystemContext';
|
||||||
|
|
||||||
interface ShareModalProps {
|
interface ShareModalProps {
|
||||||
entries: VfsEntry[];
|
entries: VfsEntry[];
|
||||||
@@ -13,6 +14,7 @@ interface ShareModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ShareModal = memo(function ShareModal({ entries, path, open, onOk, onCancel }: ShareModalProps) {
|
export const ShareModal = memo(function ShareModal({ entries, path, open, onOk, onCancel }: ShareModalProps) {
|
||||||
|
const systemStatus = useSystemStatus();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [accessType, setAccessType] = useState('public');
|
const [accessType, setAccessType] = useState('public');
|
||||||
@@ -66,7 +68,8 @@ export const ShareModal = memo(function ShareModal({ entries, path, open, onOk,
|
|||||||
message.success('已复制到剪贴板');
|
message.success('已复制到剪贴板');
|
||||||
};
|
};
|
||||||
|
|
||||||
const shareUrl = createdShare ? `${window.location.origin}/share/${createdShare.token}` : '';
|
const baseUrl = systemStatus?.app_domain || window.location.origin;
|
||||||
|
const shareUrl = createdShare ? new URL(`/share/${createdShare.token}`, baseUrl).href : '';
|
||||||
|
|
||||||
const renderForm = () => (
|
const renderForm = () => (
|
||||||
<Form form={form} layout="vertical" initialValues={{ name: defaultName, accessType: 'public', expiresInDays: 7 }}>
|
<Form form={form} layout="vertical" initialValues={{ name: defaultName, accessType: 'public', expiresInDays: 7 }}>
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import PageCard from '../components/PageCard';
|
|||||||
import { shareApi, type ShareInfo } from '../api/share';
|
import { shareApi, type ShareInfo } from '../api/share';
|
||||||
import { format, parseISO } from 'date-fns';
|
import { format, parseISO } from 'date-fns';
|
||||||
import { LinkOutlined, CopyOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { LinkOutlined, CopyOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
|
import { useSystemStatus } from '../contexts/SystemContext';
|
||||||
|
|
||||||
const SharePage = memo(function SharePage() {
|
const SharePage = memo(function SharePage() {
|
||||||
|
const systemStatus = useSystemStatus();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState<ShareInfo[]>([]);
|
const [data, setData] = useState<ShareInfo[]>([]);
|
||||||
|
|
||||||
@@ -24,7 +26,8 @@ const SharePage = memo(function SharePage() {
|
|||||||
useEffect(() => { fetchList(); }, [fetchList]);
|
useEffect(() => { fetchList(); }, [fetchList]);
|
||||||
|
|
||||||
const doCopy = (rec: ShareInfo) => {
|
const doCopy = (rec: ShareInfo) => {
|
||||||
const shareUrl = `${window.location.origin}/share/${rec.token}`;
|
const baseUrl = systemStatus?.app_domain || window.location.origin;
|
||||||
|
const shareUrl = new URL(`/share/${rec.token}`, baseUrl).href;
|
||||||
navigator.clipboard.writeText(shareUrl);
|
navigator.clipboard.writeText(shareUrl);
|
||||||
message.success('链接已复制');
|
message.success('链接已复制');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { Form, Input, Button, message, Tabs, Space, Card } from 'antd';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import PageCard from '../../components/PageCard';
|
import PageCard from '../../components/PageCard';
|
||||||
import { getAllConfig, setConfig } from '../../api/config';
|
import { getAllConfig, setConfig } from '../../api/config';
|
||||||
import { API_BASE_URL } from '../../api/client';
|
|
||||||
import { AppstoreOutlined, RobotOutlined } from '@ant-design/icons';
|
import { AppstoreOutlined, RobotOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const APP_CONFIG_KEYS = [
|
const APP_CONFIG_KEYS: {key: string, label: string, default?: string}[] = [
|
||||||
{ key: 'APP_NAME', label: '应用名称' },
|
{ key: 'APP_NAME', label: '应用名称' },
|
||||||
{ key: 'APP_LOGO', label: 'LOGO地址' },
|
{ key: 'APP_LOGO', label: 'LOGO地址' },
|
||||||
{ key: 'SERVER_URL', label: '服务端URL', default: API_BASE_URL },
|
{ key: 'APP_DOMAIN', label: '应用域名' },
|
||||||
|
{ key: 'FILE_DOMAIN', label: '文件域名' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const VISION_CONFIG_KEYS = [
|
const VISION_CONFIG_KEYS = [
|
||||||
|
|||||||
Reference in New Issue
Block a user