feat: 添加 Admin Dashboard — React 19 SPA,包含 7 个页面

- Dashboard: 健康状态轮询、状态卡片、内存统计、快捷操作
- Login: 二维码展示 + 3 秒自动轮询 + 倒计时 + 登出
- Browser: 探索/搜索/用户三标签页,Feed 网格、详情面板、评论树
- Publish: 图文/视频发布表单,支持标签、可见性、定时发布
- Interactions: 点赞/取消点赞、收藏、评论、回复 + 操作日志
- API Tester: 端点选择器、请求体编辑器、cURL 生成、响应查看、历史记录
- Settings: Token 配置、服务器 URL 设置

后端改动:
- app.ts: 生产环境提供 dist/web/ 静态文件服务 + SPA fallback
- Dockerfile: 添加 web 构建阶段
- package.json: 添加 build:web、build:all、dev:web 脚本

技术栈: React 19 + TypeScript + Vite 6 + Tailwind CSS(暗色主题)
产物: 85.5 KB gzip JS + 4 KB gzip CSS,零重型依赖
This commit is contained in:
2026-03-01 13:58:55 +08:00
parent 6d35387e2b
commit c6a8177718
51 changed files with 5665 additions and 1 deletions
+83
View File
@@ -0,0 +1,83 @@
import { Card } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { useAuth } from '@/context/AuthContext';
import { useToast } from '@/context/ToastContext';
export function SettingsPage() {
const { token, serverUrl, setToken, setServerUrl } = useAuth();
const { toast } = useToast();
return (
<div className="max-w-2xl space-y-6">
<h1 className="text-2xl font-bold">Settings</h1>
{/* Server Connection */}
<Card>
<h2 className="text-sm font-semibold text-dark-muted uppercase tracking-wider mb-4">Server Connection</h2>
<div className="space-y-4">
<Input
label="Server URL"
value={serverUrl}
onChange={(e) => setServerUrl(e.target.value)}
placeholder="Leave empty for same-origin (default)"
/>
<p className="text-xs text-dark-muted">
Leave empty when the dashboard is served by the same Express server.
Set to e.g. <code className="text-dark-accent">http://192.168.1.100:3000</code> for remote servers.
</p>
</div>
</Card>
{/* Authentication */}
<Card>
<h2 className="text-sm font-semibold text-dark-muted uppercase tracking-wider mb-4">Authentication</h2>
<div className="space-y-4">
<Input
label="Bearer Token"
type="password"
value={token}
onChange={(e) => setToken(e.target.value)}
placeholder="Enter your API token"
/>
<p className="text-xs text-dark-muted">
Token from <code className="text-dark-accent">BEARER_TOKEN</code> environment variable or{' '}
<code className="text-dark-accent">.social-mcp/bearer-token</code> file.
</p>
<div className="flex gap-2">
<Button
variant="secondary"
size="sm"
onClick={() => {
toast('success', 'Settings saved');
}}
>
Save
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => {
setToken('');
setServerUrl('');
toast('info', 'Settings cleared');
}}
>
Clear All
</Button>
</div>
</div>
</Card>
{/* About */}
<Card>
<h2 className="text-sm font-semibold text-dark-muted uppercase tracking-wider mb-4">About</h2>
<div className="space-y-2 text-sm text-dark-muted">
<p><span className="text-dark-text">Social MCP</span> Multi-platform social media automation</p>
<p>Version: 0.1.0</p>
<p>Stack: React 19 + TypeScript + Tailwind CSS</p>
</div>
</Card>
</div>
);
}