Extract memind_adm admin server, add local dev tooling, and remove image-generation.

Split platform admin and ops APIs into standalone admin-server.mjs with network guards; simplify billing to RMB token pricing, refactor user auth, and add rsync deploy plus local-test scripts and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Your Name
2026-06-17 16:39:39 -07:00
parent ab0718938e
commit b0f5d6a51c
98 changed files with 5394 additions and 3010 deletions
+38
View File
@@ -0,0 +1,38 @@
import { NavLink, Outlet } from 'react-router-dom';
const links = [
{ to: '/admin', label: '概览', end: true },
{ to: '/admin/users', label: '用户管理' },
{ to: '/admin/llm', label: 'LLM 配置' },
{ to: '/admin/billing', label: '账单记录' },
];
export function AdminLayout() {
return (
<div className="layout">
<header>
<h1></h1>
<p style={{ color: '#68716c' }}> LLM </p>
</header>
<nav className="nav">
<NavLink
to="/"
style={{ opacity: 0.6 }}
>
</NavLink>
{links.map((link) => (
<NavLink
key={link.to}
to={link.to}
end={link.end}
className={({ isActive }) => (isActive ? 'active' : undefined)}
>
{link.label}
</NavLink>
))}
</nav>
<Outlet />
</div>
);
}
+16 -4
View File
@@ -1,7 +1,8 @@
import { NavLink, Outlet } from 'react-router-dom';
import { useAuth } from '../lib/auth';
const links = [
{ to: '/', label: '审核队列' },
const opsLinks = [
{ to: '/', label: '审核队列', end: true },
{ to: '/reports', label: '举报处理' },
{ to: '/featured', label: '精选管理' },
{ to: '/creators', label: '创作者' },
@@ -9,6 +10,8 @@ const links = [
];
export function OpsLayout() {
const { user } = useAuth();
return (
<div className="layout">
<header>
@@ -16,16 +19,25 @@ export function OpsLayout() {
<p style={{ color: '#68716c' }}></p>
</header>
<nav className="nav">
{links.map((link) => (
{opsLinks.map((link) => (
<NavLink
key={link.to}
to={link.to}
end={link.to === '/'}
end={link.end}
className={({ isActive }) => (isActive ? 'active' : undefined)}
>
{link.label}
</NavLink>
))}
{user?.role === 'admin' ? (
<NavLink
to="/admin"
className={({ isActive }) => (isActive ? 'active' : undefined)}
style={{ marginLeft: 'auto', opacity: 0.75 }}
>
</NavLink>
) : null}
</nav>
<Outlet />
</div>
+31
View File
@@ -0,0 +1,31 @@
import { useAuth } from '../lib/auth';
import { mindSpaceLoginUrl } from '../lib/site';
export function RequireAdmin({ children }: { children: React.ReactNode }) {
const { loading, user } = useAuth();
if (loading) return <p></p>;
if (!user) {
return (
<div className="card">
<h2></h2>
<p> MindSpace 访</p>
<a className="btn" href={mindSpaceLoginUrl()}>
</a>
</div>
);
}
if (user.role !== 'admin') {
return (
<div className="card">
<h2></h2>
<p> role = admin ({user.username}) </p>
</div>
);
}
return children;
}
+23 -19
View File
@@ -1,45 +1,48 @@
import { useEffect, useState } from 'react';
import { fetchAuthStatus, fetchReviewQueue } from '../api/client';
import { useAuth } from '../lib/auth';
import { mindSpaceLoginUrl } from '../lib/site';
import { fetchReviewQueue } from '../api/client';
import { useEffect, useState } from 'react';
export function RequireOps({ children }: { children: React.ReactNode }) {
const [state, setState] = useState<'loading' | 'ok' | 'denied' | 'forbidden'>('loading');
const { loading: authLoading, user } = useAuth();
const [state, setState] = useState<'loading' | 'ok' | 'forbidden'>('loading');
const [message, setMessage] = useState<string | null>(null);
useEffect(() => {
if (authLoading) return;
if (!user) { setState('forbidden'); setMessage(null); return; }
// Admins bypass ops-role check.
if (user.role === 'admin') { setState('ok'); return; }
void (async () => {
try {
const auth = await fetchAuthStatus();
if (!auth.authenticated) {
setState('denied');
return;
}
await fetchReviewQueue('status=pending_review&limit=1');
setState('ok');
} catch (err) {
const text = err instanceof Error ? err.message : '无运营权限';
if (text.includes('未授权') || text.includes('登录')) {
setState('denied');
} else {
setMessage(text);
setState('forbidden');
}
setMessage(err instanceof Error ? err.message : '无运营权限');
setState('forbidden');
}
})();
}, []);
}, [authLoading, user]);
if (state === 'loading') return <p></p>;
if (state === 'denied') {
if (authLoading || state === 'loading') return <p></p>;
if (!user) {
return (
<div className="card">
<h2></h2>
<p> MindSpace ops_rolereviewer / editor / ops_admin</p>
<p> MindSpace Ops 使 127.0.0.1 *.localhost</p>
<p style={{ color: '#68716c' }}>
Ops <code>http://127.0.0.1:3002/ops/</code>
</p>
<a className="btn" href={mindSpaceLoginUrl()}>
</a>
</div>
);
}
if (state === 'forbidden') {
return (
<div className="card">
@@ -51,5 +54,6 @@ export function RequireOps({ children }: { children: React.ReactNode }) {
</div>
);
}
return children;
}