feat(web): 存储设置与文件管理的 Alist 存储切换
- 设置 → 媒体库与目录 → 存储:登记 Alist / OpenList(令牌 / 账号密码 / 访客、路径密码、限速与监控选项), 测试连接显示版本、用户与容量;媒体库目录可选存储 - 文件管理左栏多一组「Alist 存储」,路径显示「存储名:/路径」;移动 / 复制目标可选 Alist 存储; 上传按钮;Alist 根点刷新才让 Alist 刷新目录缓存 - 运行状态的挂载根列出存储实例(令牌失效单独提示) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
c1bc389058
commit
cf364435eb
18 files changed
+618
-41
No files matched your search
@@ -5,6 +5,7 @@ import {
|
||||
ArrowUp,
|
||||
ArrowUpAZ,
|
||||
ClipboardCopy,
|
||||
Cloud,
|
||||
Copy,
|
||||
Download,
|
||||
Eye,
|
||||
@@ -47,6 +48,7 @@ import {
|
||||
isRootOffline,
|
||||
type ListQuery,
|
||||
refKey,
|
||||
refreshRemoteDir,
|
||||
useFileList,
|
||||
useFileRoots,
|
||||
useProbe,
|
||||
@@ -64,11 +66,13 @@ import {
|
||||
RenameDialog,
|
||||
ScrapeDialog,
|
||||
TransferDialog,
|
||||
UploadButton,
|
||||
} from "~/components/files/dialogs.tsx";
|
||||
import { DirTree } from "~/components/files/dir-tree.tsx";
|
||||
import { FileDetail } from "~/components/files/file-detail.tsx";
|
||||
import { DRAG_MIME, FileList } from "~/components/files/file-list.tsx";
|
||||
import {
|
||||
displayPath,
|
||||
emptySelection,
|
||||
FILTER_CHIPS,
|
||||
groupRoots,
|
||||
@@ -221,7 +225,7 @@ function TreeRail({
|
||||
<span aria-hidden className="my-1 h-px w-6 bg-border" />
|
||||
{groupRoots(roots).flatMap((g) =>
|
||||
g.items.map((r) => {
|
||||
const Icon = r.kind === "download" ? Download : HardDrive;
|
||||
const Icon = r.kind === "download" ? Download : r.kind === "storage" ? Cloud : HardDrive;
|
||||
return (
|
||||
<button
|
||||
key={r.id}
|
||||
@@ -429,7 +433,7 @@ export default function FilesPage() {
|
||||
|
||||
const copyPaths = async (list: FileEntry[]) => {
|
||||
if (!root) return;
|
||||
const text = list.map((e) => `${root.hostPath}${e.path ? `/${e.path}` : ""}`).join("\n");
|
||||
const text = list.map((e) => displayPath(root, e.path)).join("\n");
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
toast.success(`已复制 ${list.length} 个路径`);
|
||||
@@ -1053,7 +1057,9 @@ export default function FilesPage() {
|
||||
size="icon-sm"
|
||||
aria-label="刷新"
|
||||
title="刷新"
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
// Alist:手动刷新才让 Alist 刷新目录缓存(穿透到网盘接口)
|
||||
if (root?.storage) await refreshRemoteDir(root.id, path).catch(() => undefined);
|
||||
void roots.refetch();
|
||||
void list.refetch();
|
||||
}}
|
||||
@@ -1088,6 +1094,7 @@ export default function FilesPage() {
|
||||
新建目录
|
||||
</Button>
|
||||
</span>
|
||||
{root ? <UploadButton root={root} path={path} /> : null}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{searchBox}
|
||||
|
||||
@@ -473,7 +473,7 @@ function Mounts({ d }: { d: SystemHealth }) {
|
||||
return (
|
||||
<Card title="挂载根" icon={HardDrive} level={d.mounts.length ? (bad ? "critical" : "ok") : undefined}>
|
||||
{d.mounts.length === 0 ? (
|
||||
<p className="text-caption text-muted-foreground">没有启用的媒体库目录或下载目录映射。</p>
|
||||
<p className="text-caption text-muted-foreground">没有启用的媒体库目录、下载目录映射或 Alist 存储。</p>
|
||||
) : (
|
||||
<ul className="divide-y divide-border/60">
|
||||
{d.mounts.map((m) => (
|
||||
@@ -481,18 +481,28 @@ function Mounts({ d }: { d: SystemHealth }) {
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate">
|
||||
<span className="mr-1.5 text-meta text-muted-foreground">
|
||||
{m.kind === "library_root" ? "媒体库目录" : "下载目录"}
|
||||
{m.kind === "library_root"
|
||||
? "媒体库目录"
|
||||
: m.kind === "storage_instance"
|
||||
? "Alist 存储"
|
||||
: "下载目录"}
|
||||
</span>
|
||||
{m.name}
|
||||
</p>
|
||||
<p className="truncate font-mono text-meta text-muted-foreground" title={m.path}>
|
||||
{m.path}
|
||||
{m.sentinelChecked ? "" : " · 只查目录可读"}
|
||||
{m.kind === "storage_instance" ? " · 连接与根路径" : m.sentinelChecked ? "" : " · 只查目录可读"}
|
||||
</p>
|
||||
</div>
|
||||
<StatusBadge tone={m.status === "online" ? "success" : "destructive"}>
|
||||
{m.status === "online" ? "在线" : m.status === "timeout" ? "超时" : "离线"}
|
||||
{m.reason && m.status !== "timeout" ? ` · ${MOUNT_REASON[m.reason] ?? m.reason}` : ""}
|
||||
{m.reason && m.status !== "timeout"
|
||||
? ` · ${
|
||||
m.kind === "storage_instance" && m.reason === "permission_denied"
|
||||
? "令牌失效或没有权限"
|
||||
: (MOUNT_REASON[m.reason] ?? m.reason)
|
||||
}`
|
||||
: ""}
|
||||
</StatusBadge>
|
||||
</li>
|
||||
))}
|
||||
|
||||
Reference in new issue
Block a user