Update MediaStationGo branding and deployment docs

This commit is contained in:
ShukeBta
2026-06-17 16:01:16 +08:00
parent 3368ac2947
commit ab4637beed
62 changed files with 1035 additions and 599 deletions
+2 -1
View File
@@ -5,7 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#faf8f5" />
<meta name="description" content="MediaStationGo — 个人家庭媒体中心" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" href="/brand/mgo-emby-icon.png" />
<link rel="apple-touch-icon" href="/brand/mgo-emby-icon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+20
View File
@@ -0,0 +1,20 @@
<svg width="920" height="160" viewBox="0 0 460 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Logo Mark -->
<rect x="10" y="15" width="50" height="50" rx="14" fill="url(#horizonPurpleGrad)" />
<path d="M26 30 L43 40 L26 50" stroke="white" stroke-width="4" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<path d="M26 43 L26 54 L34 48" stroke="white" stroke-width="4" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<circle cx="46" cy="28" r="3.5" fill="#06B6D4" />
<!-- Typography -->
<text x="75" y="47" font-family="'Plus Jakarta Sans', system-ui, sans-serif" font-weight="800" font-size="28" fill="#111827" letter-spacing="-0.5">Mgo-Emby</text>
<text x="238" y="47" font-family="'Plus Jakarta Sans', system-ui, sans-serif" font-weight="300" font-size="28" fill="#9CA3AF" letter-spacing="-0.5"> 社区</text>
<rect x="315" y="26" width="48" height="22" rx="6" fill="#06B6D4" fill-opacity="0.1" stroke="#06B6D4" stroke-width="1" />
<text x="339" y="41" font-family="'JetBrains Mono', monospace" font-weight="700" font-size="11" fill="#06B6D4" text-anchor="middle">BBS</text>
<defs>
<linearGradient id="horizonPurpleGrad" x1="10" y1="15" x2="60" y2="65" gradientUnits="userSpaceOnUse">
<stop stop-color="#7C3AED" />
<stop offset="1" stop-color="#5B21B6" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

+12
View File
@@ -0,0 +1,12 @@
<svg width="64" height="64" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="url(#mgoPurpleGrad)" />
<path d="M11 10 L21 16 L11 22" stroke="white" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<path d="M11 18 L11 24 L16 21" stroke="white" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<circle cx="23" cy="9" r="2" fill="#06B6D4" />
<defs>
<linearGradient id="mgoPurpleGrad" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#7C3AED" />
<stop offset="1" stop-color="#5B21B6" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg width="200" height="200" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="5" y="5" width="90" height="90" rx="24" stroke="currentColor" stroke-width="6" />
<path d="M35 30 L65 47 L35 64" stroke="currentColor" stroke-width="8" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<path d="M35 51 L35 72 L48 64" stroke="currentColor" stroke-width="8" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<circle cx="69" cy="29" r="6" fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+16
View File
@@ -0,0 +1,16 @@
<svg width="400" height="480" viewBox="0 0 200 240" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="40" y="20" width="120" height="120" rx="32" fill="url(#primaryPurpleGrad)" />
<path d="M80 55 L120 80 L80 105" stroke="white" stroke-width="9" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<path d="M80 87 L80 114 L100 101" stroke="white" stroke-width="9" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<circle cx="128" cy="52" r="8" fill="#06B6D4" />
<text x="100" y="185" font-family="'Plus Jakarta Sans', system-ui, sans-serif" font-weight="800" font-size="24" fill="#111827" text-anchor="middle" letter-spacing="1">Mgo-Emby</text>
<text x="100" y="215" font-family="'Inter', system-ui, sans-serif" font-weight="500" font-size="14" fill="#06B6D4" text-anchor="middle" letter-spacing="4">社区</text>
<defs>
<linearGradient id="primaryPurpleGrad" x1="40" y1="20" x2="160" y2="140" gradientUnits="userSpaceOnUse">
<stop stop-color="#7C3AED" />
<stop offset="1" stop-color="#5B21B6" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -0,0 +1,20 @@
<svg width="460" height="80" viewBox="0 0 460 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Logo Mark -->
<rect x="10" y="15" width="50" height="50" rx="14" fill="url(#horizonPurpleGrad)" />
<path d="M26 30 L43 40 L26 50" stroke="white" stroke-width="4" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<path d="M26 43 L26 54 L34 48" stroke="white" stroke-width="4" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<circle cx="46" cy="28" r="3.5" fill="#06B6D4" />
<!-- Typography -->
<text x="75" y="47" font-family="'Plus Jakarta Sans', system-ui, sans-serif" font-weight="800" font-size="28" fill="#111827" letter-spacing="-0.5">Mgo-Emby</text>
<text x="238" y="47" font-family="'Plus Jakarta Sans', system-ui, sans-serif" font-weight="300" font-size="28" fill="#9CA3AF" letter-spacing="-0.5"> 社区</text>
<rect x="315" y="26" width="48" height="22" rx="6" fill="#06B6D4" fill-opacity="0.1" stroke="#06B6D4" stroke-width="1" />
<text x="339" y="41" font-family="'JetBrains Mono', monospace" font-weight="700" font-size="11" fill="#06B6D4" text-anchor="middle">BBS</text>
<defs>
<linearGradient id="horizonPurpleGrad" x1="10" y1="15" x2="60" y2="65" gradientUnits="userSpaceOnUse">
<stop stop-color="#7C3AED" />
<stop offset="1" stop-color="#5B21B6" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 B

@@ -0,0 +1,12 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="8" fill="url(#mgoPurpleGrad)" />
<path d="M11 10 L21 16 L11 22" stroke="white" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<path d="M11 18 L11 24 L16 21" stroke="white" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<circle cx="23" cy="9" r="2" fill="#06B6D4" />
<defs>
<linearGradient id="mgoPurpleGrad" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#7C3AED" />
<stop offset="1" stop-color="#5B21B6" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,6 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="5" y="5" width="90" height="90" rx="24" stroke="currentColor" stroke-width="6" />
<path d="M35 30 L65 47 L35 64" stroke="currentColor" stroke-width="8" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<path d="M35 51 L35 72 L48 64" stroke="currentColor" stroke-width="8" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<circle cx="69" cy="29" r="6" fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

@@ -0,0 +1,16 @@
<svg width="200" height="240" viewBox="0 0 200 240" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="40" y="20" width="120" height="120" rx="32" fill="url(#primaryPurpleGrad)" />
<path d="M80 55 L120 80 L80 105" stroke="white" stroke-width="9" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<path d="M80 87 L80 114 L100 101" stroke="white" stroke-width="9" stroke-linecap="square" stroke-linejoin="miter" fill="none" />
<circle cx="128" cy="52" r="8" fill="#06B6D4" />
<text x="100" y="185" font-family="'Plus Jakarta Sans', system-ui, sans-serif" font-weight="800" font-size="24" fill="#111827" text-anchor="middle" letter-spacing="1">Mgo-Emby</text>
<text x="100" y="215" font-family="'Inter', system-ui, sans-serif" font-weight="500" font-size="14" fill="#06B6D4" text-anchor="middle" letter-spacing="4">社区</text>
<defs>
<linearGradient id="primaryPurpleGrad" x1="40" y1="20" x2="160" y2="140" gradientUnits="userSpaceOnUse">
<stop stop-color="#7C3AED" />
<stop offset="1" stop-color="#5B21B6" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+1 -15
View File
@@ -1,17 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="MediaStationGo">
<defs>
<linearGradient id="bg" x1="10" y1="6" x2="54" y2="58" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#7c3aed"/>
<stop offset="0.48" stop-color="#2563eb"/>
<stop offset="1" stop-color="#06b6d4"/>
</linearGradient>
<linearGradient id="play" x1="23" y1="18" x2="45" y2="46" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#fff7ed"/>
<stop offset="1" stop-color="#ffffff"/>
</linearGradient>
</defs>
<rect width="64" height="64" rx="18" fill="url(#bg)"/>
<path d="M18 21.5A7.5 7.5 0 0 1 25.5 14h13A7.5 7.5 0 0 1 46 21.5v21A7.5 7.5 0 0 1 38.5 50h-13A7.5 7.5 0 0 1 18 42.5v-21Z" fill="#0f172a" opacity=".22"/>
<path d="M22 22.9c0-3.05 3.32-4.95 5.96-3.42l17.08 9.86c2.64 1.52 2.64 5.34 0 6.86L27.96 46.06C25.32 47.58 22 45.68 22 42.63V22.9Z" fill="url(#play)"/>
<path d="M13 18h6M13 28h6M13 38h6M45 18h6M45 28h6M45 38h6" stroke="#ffffff" stroke-width="3" stroke-linecap="round" opacity=".72"/>
<image href="/brand/mgo-emby-icon.png" width="64" height="64" preserveAspectRatio="xMidYMid meet"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 209 B

+1 -1
View File
@@ -113,7 +113,7 @@ class AppErrorBoundary extends Component<{ children: ReactNode }, { hasError: bo
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error('MediaStation UI crashed', error, info)
console.error('MediaStationGo UI crashed', error, info)
}
render() {
+1 -1
View File
@@ -1,6 +1,6 @@
import { api } from './client'
// ── License Server types (generic, ready for MediaStationLicenseServer integration) ──
// ── License Server types (generic, ready for MediaStationGo license integration) ──
/** Response after binding/activating a license. */
export interface LicenseActivation {
+7 -5
View File
@@ -3,7 +3,7 @@ import { Link, NavLink, Outlet, useLocation, useNavigate } from 'react-router-do
import { AnimatePresence, motion } from 'framer-motion'
import toast from 'react-hot-toast'
import {
Activity, Bell, Clock, CloudDownload, Compass, Film,
Activity, Bell, Clock, CloudDownload, Compass,
Cast, Globe, HardDrive, Heart, Home, Image, KeySquare,
ListMusic, LogOut, Rss, Search, Trash2,
Settings, Sliders, Sparkles, UserCog,
@@ -139,16 +139,18 @@ export function Layout() {
{/* Brand Logo & Brand Title */}
<div className="flex h-20 items-center justify-between px-6 border-b border-gray-100">
<Link to="/" className="flex items-center gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-[#111827] to-[#1f2937] shadow-sm">
<Film className="h-5 w-5 text-[#c9954a]" />
</div>
<img
src="/brand/mgo-emby-icon.png"
alt="MediaStationGo"
className="h-10 w-10 shrink-0 rounded-xl object-contain shadow-sm"
/>
{(isSidebarOpen || isMobileDrawerOpen) && (
<motion.span
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
className="font-display text-lg font-extrabold tracking-tight text-[#111827]"
>
MediaStation
MediaStationGo
</motion.span>
)}
</Link>
+8 -8
View File
@@ -2,7 +2,7 @@
import { useNavigate } from 'react-router-dom'
import { motion } from 'framer-motion'
import toast from 'react-hot-toast'
import { Eye, EyeOff, Film, ArrowRight, ShieldCheck, Lock, User } from 'lucide-react'
import { Eye, EyeOff, ArrowRight, ShieldCheck, Lock, User } from 'lucide-react'
import { AppFooter } from '../components/AppFooter'
import { authAPI } from '../api/auth'
import { useAuthStore } from '../stores/auth'
@@ -50,14 +50,14 @@ export function LoginPage() {
{/* Logo & Headline */}
<div className="flex flex-col items-center text-center pb-8">
<motion.div
<motion.img
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.15, type: 'spring', stiffness: 200 }}
className="mb-4 flex h-14 w-14 items-center justify-center rounded-2xl bg-[#111827] shadow-sm"
>
<Film className="h-7 w-7 text-[#c9954a]" />
</motion.div>
src="/brand/mgo-emby-icon.png"
alt="MediaStationGo"
className="mb-4 h-14 w-14 rounded-2xl object-contain shadow-sm"
/>
<motion.h1
initial={{ opacity: 0, y: 10 }}
@@ -65,7 +65,7 @@ export function LoginPage() {
transition={{ delay: 0.25 }}
className="font-display text-2xl font-extrabold tracking-tight text-gray-900"
>
MediaStation
MediaStationGo
</motion.h1>
<motion.p
@@ -195,4 +195,4 @@ export function LoginPage() {
</motion.div>
</div>
)
}
}
+50
View File
@@ -129,6 +129,14 @@ const TYPE_LABELS: Record<NotifyChannel['type'], string> = {
email: 'Email',
}
const EVENT_OPTIONS = [
{ value: 'subscription_hit', label: '订阅命中新资源' },
{ value: 'download_complete', label: '下载任务完成' },
{ value: 'library_ingest', label: '入库完成' },
{ value: 'scrape_failed', label: '刮削失败告警' },
{ value: 'system_alert', label: '系统异常通知' },
]
function ChannelCard({
channel,
onTest,
@@ -154,6 +162,7 @@ function ChannelCard({
)}
</div>
<div className="mt-1 truncate text-xs text-ink-50">{summary}</div>
<div className="mt-1 truncate text-xs text-sand-500">{eventSummary(channel.events)}</div>
</div>
<div className="flex shrink-0 gap-2">
<button
@@ -179,6 +188,12 @@ function ChannelCard({
)
}
function eventSummary(events: string[] | undefined): string {
if (!events || events.length === 0) return '事件:全部'
const labels = events.map((event) => EVENT_OPTIONS.find((item) => item.value === event)?.label ?? event)
return `事件:${labels.join('、')}`
}
function channelSummary(ch: NotifyChannel): string {
const cfg = ch.config ?? {}
switch (ch.type) {
@@ -245,6 +260,7 @@ function ChannelFormModal({
const [config, setConfig] = useState<Record<string, string>>(
normalizeInitialConfig(editing?.type ?? 'telegram', editing?.config ?? {}),
)
const [events, setEvents] = useState<string[]>(editing?.events ?? [])
const [enabled, setEnabled] = useState(editing?.enabled ?? true)
const [saving, setSaving] = useState(false)
@@ -275,6 +291,7 @@ function ChannelFormModal({
name: name.trim(),
type: type,
config: cleanedConfig,
events,
enabled,
}
if (editing) {
@@ -296,6 +313,12 @@ function ChannelFormModal({
const updateConfig = (k: string, v: string) =>
setConfig((c) => ({ ...c, [k]: v }))
const toggleEvent = (event: string) => {
setEvents((current) =>
current.includes(event) ? current.filter((item) => item !== event) : [...current, event],
)
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm">
<div className="glass-panel w-full max-w-lg max-h-[90vh] overflow-y-auto">
@@ -532,6 +555,33 @@ function ChannelFormModal({
</>
)}
<Field label="推送事件">
<div className="space-y-2 rounded-lg border border-gray-200 bg-gray-50/60 p-3">
<label className="flex cursor-pointer items-center gap-2 text-sm text-ink-100">
<input
type="checkbox"
className="h-4 w-4 accent-primary-400"
checked={events.length === 0}
onChange={() => setEvents([])}
/>
全部事件
</label>
<div className="grid gap-2 sm:grid-cols-2">
{EVENT_OPTIONS.map((event) => (
<label key={event.value} className="flex cursor-pointer items-center gap-2 text-sm text-ink-100">
<input
type="checkbox"
className="h-4 w-4 accent-primary-400"
checked={events.includes(event.value)}
onChange={() => toggleEvent(event.value)}
/>
{event.label}
</label>
))}
</div>
</div>
</Field>
<label className="flex cursor-pointer items-center gap-2 text-sm text-ink-100">
<input
type="checkbox"
+1 -1
View File
@@ -140,7 +140,7 @@ const GROUPS: SettingGroup[] = [
{
key: 'license',
label: '授权服务',
description: '连接私有 MediaStationLicenseServer;开源版默认最多 20 个用户,激活后按授权策略提升额度。',
description: '连接私有 MediaStationGo 授权服务;开源版默认最多 20 个用户,激活后按授权策略提升额度。',
items: [
{
key: 'license.server_url',
+1 -1
View File
@@ -312,7 +312,7 @@ export interface NotifyChannel {
name: string
type: 'telegram' | 'wechat' | 'bark' | 'webhook' | 'email'
enabled: boolean
events: string
events: string[]
config: Record<string, string>
created_at: string
updated_at: string