diff --git a/internal/handler/system_meta.go b/internal/handler/system_meta.go index 81e64f6..4904d83 100644 --- a/internal/handler/system_meta.go +++ b/internal/handler/system_meta.go @@ -8,6 +8,7 @@ package handler import ( "net/http" "runtime" + "strings" "time" "github.com/gin-gonic/gin" @@ -22,26 +23,30 @@ import ( // without threading state through the container. var startedAt = time.Now() -func systemInfoHandler(svc *service.Container) gin.HandlerFunc { - return func(c *gin.Context) { - directOnly := false - if svc.Repo != nil && svc.Repo.Setting != nil { - if v, err := svc.Repo.Setting.Get(c.Request.Context(), service.PlaybackDirectOnlySettingKey); err == nil { - directOnly = service.ParseBoolSetting(v, false) + func systemInfoHandler(svc *service.Container) gin.HandlerFunc { + return func(c *gin.Context) { + directOnly := false + if svc.Repo != nil && svc.Repo.Setting != nil { + if v, err := svc.Repo.Setting.Get(c.Request.Context(), service.PlaybackDirectOnlySettingKey); err == nil { + directOnly = service.ParseBoolSetting(v, false) + } } + version := svc.Version + if strings.TrimSpace(version) == "" { + version = "dev" + } + c.JSON(http.StatusOK, gin.H{ + "name": "MeBox", + "version": version, + "go": runtime.Version(), + "os": runtime.GOOS, + "arch": runtime.GOARCH, + "data_dir": svc.Cfg.App.DataDir, + "cache_dir": svc.Cfg.Cache.CacheDir, + "direct_play_only": directOnly, + }) } - c.JSON(http.StatusOK, gin.H{ - "name": "MeBox", - "version": "0.1.0", - "go": runtime.Version(), - "os": runtime.GOOS, - "arch": runtime.GOARCH, - "data_dir": svc.Cfg.App.DataDir, - "cache_dir": svc.Cfg.Cache.CacheDir, - "direct_play_only": directOnly, - }) } -} func systemStatusHandler(svc *service.Container) gin.HandlerFunc { return func(c *gin.Context) { diff --git a/internal/service/service.go b/internal/service/service.go index af8329d..6a631c5 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -15,6 +15,7 @@ import ( // Container 持有在启动时初始化的每个服务。Handler 接收指向它的指针并选择相关字段。 type Container struct { + Version string Cfg *config.Config Log *zap.Logger Repo *repository.Container diff --git a/internal/service/service_builder.go b/internal/service/service_builder.go index a86fcbb..907158a 100644 --- a/internal/service/service_builder.go +++ b/internal/service/service_builder.go @@ -27,12 +27,13 @@ func newServiceContainer(cfg *config.Config, log *zap.Logger, repos *repository. cfg: cfg, log: log, repos: repos, - version: normalizeSystemUpdateVersion(version), - c: &Container{ - Cfg: cfg, - Log: log, - Repo: repos, - }, + version: normalizeSystemUpdateVersion(version), + c: &Container{ + Version: normalizeSystemUpdateVersion(version), + Cfg: cfg, + Log: log, + Repo: repos, + }, } builder.startRealtimeServices() builder.initProviderServices() diff --git a/web/src/components/layoutNavigation.ts b/web/src/components/layoutNavigation.ts index 977535e..dc5b2f8 100644 --- a/web/src/components/layoutNavigation.ts +++ b/web/src/components/layoutNavigation.ts @@ -43,9 +43,9 @@ export const LAYOUT_NAV_ITEMS: LayoutNavItem[] = [ { to: '/queue', label: '任务队列', icon: ListChecks, adminOnly: true }, { to: '/admin', label: '用户管理', icon: Users, adminOnly: true }, { to: '/files', label: '文件管理', icon: FolderOpen, adminOnly: true }, - { to: '/settings', label: '系统设置', icon: Settings, adminOnly: true }, { to: '/emby-mount', label: 'Emby 挂载', icon: Tv, adminOnly: true }, { to: '/strm', label: 'STRM 管理', icon: FileOutput, adminOnly: true }, + { to: '/settings', label: '系统设置', icon: Settings, adminOnly: true }, ] /** Media browsing links for the mobile drawer when browsing libraries. */ diff --git a/web/src/pages/AboutSettingsPanel.tsx b/web/src/pages/AboutSettingsPanel.tsx new file mode 100644 index 0000000..a1b5d71 --- /dev/null +++ b/web/src/pages/AboutSettingsPanel.tsx @@ -0,0 +1,330 @@ +import { useEffect, useState } from 'react' +import { + BookOpen, + Check, + Copy, + Cpu, + Database, + ExternalLink, + Film, + FolderOpen, + Github, + HardDrive, + Info, + Layers, + ListChecks, + MessageSquare, + Play, + Server, + Sparkles, + Tag, + Tv, + Users, +} from 'lucide-react' +import toast from 'react-hot-toast' + +import { systemAPI } from '../api/system' + +type SystemInfo = { + name: string + version: string + go: string + os: string + arch: string + data_dir: string + cache_dir: string +} + +const GITHUB_REPO_URL = 'https://github.com/truewhile/MeBox' + +const MODULE_GUIDES = [ + { + icon: Film, + title: '媒体库管理', + badge: '核心存储', + description: + '支持创建电影、电视剧、动漫、纪录片等多种类型媒体库。支持本地目录与网络挂载路径,集成文件监控(Inotify / 定时轮询),新增与修改文件自动入库。剧集支持智能按季、集归类与多版本合并。', + }, + { + icon: Sparkles, + title: '智能刮削与元数据', + badge: '海报与信息', + description: + '内置 TMDb、Bangumi、TheTVDB、豆瓣等元数据源。入库时自动识别文件名匹配海报、背景图、演职员及中文简介。支持手动精确搜刮、重命名整理规则、识别词过滤(过滤广告词与压制组标签)及 NFO 本地文件导出。', + }, + { + icon: Play, + title: '流媒体播放与转码', + badge: '播放引擎', + description: + '现代化 Web 播放器支持原画直连(Direct Play)与 HLS 实时兼容转码。支持 Intel QSV、NVIDIA NVENC、VAAPI 等硬件加速。支持内嵌与外挂字幕(ASS/SRT/VTT)渲染、多音轨切换,并支持一键调用 PotPlayer、VLC、IINA、Infuse 等本地播放器。', + }, + { + icon: MessageSquare, + title: '实时弹幕系统', + badge: '弹幕互动', + description: + '播放时自动/手动匹配对应剧集的弹幕源。支持设置弹幕字号、滚动速度、透明度、屏幕占比限制(1/4屏、半屏、全屏)及防遮挡字幕开关,打造沉浸式的观影交流体验。', + }, + { + icon: Tv, + title: 'Emby 挂载与远程同步', + badge: '扩展聚合', + description: + '支持以客户端形式直接挂载远程 Emby 媒体服务器,无需本地下载即可无缝串流播放远程资源,同时保留本地的刮削、播放历史、收藏与弹幕能力,实现多端影视资源大聚合。', + }, + { + icon: Layers, + title: 'STRM 管理与云盘转存', + badge: '海量存储', + description: + '针对 115、阿里云盘、OpenList 等网盘挂载场景,提供批量的 STRM 索引文件生成与下载队列管理。本地仅占用微量磁盘空间,即可享受数百 TB 云端影视的高速直链秒播。', + }, + { + icon: FolderOpen, + title: '内置文件管理器', + badge: '资源维护', + description: + '内置可视化文件浏览器,支持对本地媒体目录及云盘目录进行直接浏览、批量移动、重命名及删除操作,方便快速定位未刮削或命名不规范的文件并进行整理。', + }, + { + icon: ListChecks, + title: '后台任务队列', + badge: '异步作业', + description: + '全生命周期监控媒体扫描、元数据批量刮削、STRM 生成及音视频探测等后台异步任务。实时展示队列进度百分比、执行状态及错误日志,支持手动重试与任务取消。', + }, + { + icon: Users, + title: '多用户与权限体系', + badge: '安全访问', + description: + '支持管理员与普通用户角色。管理员可精细化控制各账号的访问边界,包括特定媒体库可见性(如敏感/Adult媒体库)、是否允许调用外部播放器、是否允许下载原始文件等。', + }, + { + icon: Database, + title: '数据库与系统设置', + badge: '底层基建', + description: + '支持轻量级单文件 SQLite 与高并发 PostgreSQL 数据库,并提供一键平滑数据热迁移工具;支持自定义 HTTPS 证书绑定、转码缓存自动清理及实时系统资源监控。', + }, +] + +export function AboutSettingsPanel() { + const [info, setInfo] = useState(null) + const [loading, setLoading] = useState(true) + const [copied, setCopied] = useState(false) + + useEffect(() => { + systemAPI + .info() + .then((res) => { + setInfo({ + name: res.name || 'MeBox', + version: res.version || '0.1.0', + go: res.go || '', + os: res.os || '', + arch: res.arch || '', + data_dir: res.data_dir || '', + cache_dir: res.cache_dir || '', + }) + }) + .catch(() => { + setInfo({ + name: 'MeBox', + version: '0.1.0', + go: '', + os: '', + arch: '', + data_dir: '', + cache_dir: '', + }) + }) + .finally(() => setLoading(false)) + }, []) + + const handleCopyGitURL = async () => { + try { + await navigator.clipboard.writeText(GITHUB_REPO_URL) + setCopied(true) + toast.success('GitHub 地址已复制到剪贴板') + setTimeout(() => setCopied(false), 2000) + } catch { + toast.error('复制失败,请手动复制') + } + } + + return ( +
+ {/* 顶部概览与项目卡片 */} +
+
+
+
+

+ MeBox 影音管理系统 +

+ + + {loading ? '加载中...' : `v${info?.version || '0.1.0'}`} + +
+

+ MeBox 是一款专为个人与家庭设计的现代化开源媒体库管理与流媒体点播系统。 + 集媒体资源归纳、智能多源刮削、弹幕交互、海量云盘 STRM 转存与多端播放于一体,让观影体验更轻快自由。 +

+
+ + {/* 仓库链接操作区 */} +
+ + + 访问 GitHub 仓库 + + + +
+
+ + {/* 运行环境规格栏 */} +
+
+
+ + 系统版本 +
+

+ {loading ? '...' : info?.version || '0.1.0'} +

+
+
+
+ + 运行环境 +
+

+ {loading ? '...' : `${info?.os || 'linux'} / ${info?.arch || 'amd64'}`} +

+
+
+
+ + Go 运行时 +
+

+ {loading ? '...' : info?.go || 'go1.x'} +

+
+
+
+ + 数据目录 +
+

+ {loading ? '...' : info?.data_dir || './data'} +

+
+
+
+ + {/* 快速入门与使用流程 */} +
+
+ +

使用说明与快速上手

+
+
+
+
+ + 1 + + 创建与关联媒体库 +
+

+ 在侧边栏进入「媒体库」,点击添加媒体库。选择媒体类型(电影/电视剧),指定服务器上的实际媒体路径或挂载目录,保存后系统将自动创建索引并监控变动。 +

+
+ +
+
+ + 2 + + 智能识别与刮削 +
+

+ 扫描入库后,后台服务会自动提取文件名并通过 TMDb / 豆瓣 / Bangumi 等抓取高清海报、演职员与分集信息;如遇特殊命名,可在详情页中一键进行「手动搜刮」。 +

+
+ +
+
+ + 3 + + 多端随心畅享观影 +
+

+ 直接在浏览器中享受原画秒开与实时弹幕互动;对于高码率或特殊封装格式,支持开启 HLS 转码播放或直接一键唤醒本地 PotPlayer、Infuse 等外部播放器。 +

+
+
+
+ + {/* 各功能模块详细使用说明 */} +
+
+
+ +

各功能模块详细说明

+
+ 涵盖系统十大核心子系统 +
+ +
+ {MODULE_GUIDES.map((mod) => { + const Icon = mod.icon + return ( +
+
+
+
+
+ +
+

+ {mod.title} +

+
+ + {mod.badge} + +
+

+ {mod.description} +

+
+
+ ) + })} +
+
+
+ ) +} diff --git a/web/src/pages/SettingsPage.tsx b/web/src/pages/SettingsPage.tsx index 15c2f34..74ed738 100644 --- a/web/src/pages/SettingsPage.tsx +++ b/web/src/pages/SettingsPage.tsx @@ -8,6 +8,7 @@ import { libraryAPI } from '../api/library' import type { Library, Setting } from '../types' import { APIConfigsPanel } from '../components/APIConfigsPanel' import { FFToolsPanel } from '../components/FFToolsPanel' +import { AboutSettingsPanel } from './AboutSettingsPanel' import { AdultSettingsPanel } from './AdultSettingsPanel' import { DatabaseSettingsPanel } from './DatabaseSettingsPanel' import { RecognitionWordsPanel } from './RecognitionWordsPanel' @@ -174,6 +175,7 @@ export function SettingsPage() { {group.key === 'recognition-words' && } {group.key === 'adult' && } {group.key === 'general' && refresh().catch(() => undefined)} />} + {group.key === 'about' && } {group.key !== 'adult' && group.key !== 'library' && group.items.length > 0 && (
{group.description &&

{group.description}

} diff --git a/web/src/pages/settingsGroups.ts b/web/src/pages/settingsGroups.ts index fe24508..0247406 100644 --- a/web/src/pages/settingsGroups.ts +++ b/web/src/pages/settingsGroups.ts @@ -14,6 +14,13 @@ export const databaseSettingsGroup: SettingGroup = { items: [], } +export const aboutSettingsGroup: SettingGroup = { + key: 'about', + label: '关于', + description: '项目信息、版本信息与使用指南', + items: [], +} + export const GROUPS: SettingGroup[] = [ generalSettingsGroup, databaseSettingsGroup, @@ -21,6 +28,7 @@ export const GROUPS: SettingGroup[] = [ recognitionWordsSettingsGroup, danmakuSettingsGroup, adultSettingsGroup, + aboutSettingsGroup, ] export const ALL_KEYS = new Set(GROUPS.flatMap((group) => group.items.map((item) => item.key)))