0e26db1857
Expose Umami memind-pages discoveryChannel data in adm with a public-page style table, proxy it via admin-api, and remove Rybbit configuration paths. Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
5.0 KiB
TypeScript
92 lines
5.0 KiB
TypeScript
import { useCallback, useEffect, useState, type FormEvent } from 'react';
|
||
import { Link } from 'react-router-dom';
|
||
import { getMindSpaceAdminConfig, getUmamiSsoUrl, updateMindSpaceAdminConfig } from '../../api/client';
|
||
|
||
const initial = {
|
||
enabled: false,
|
||
websiteId: '',
|
||
analyticsUrl: 'http://127.0.0.1:3100',
|
||
domains: '127.0.0.1,localhost',
|
||
idSecretConfigured: false,
|
||
};
|
||
|
||
export function AnalyticsConfigPage() {
|
||
const [config, setConfig] = useState(initial);
|
||
const [secret, setSecret] = useState('');
|
||
const [busy, setBusy] = useState(false);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [message, setMessage] = useState<string | null>(null);
|
||
const [openingUmami, setOpeningUmami] = useState(false);
|
||
|
||
const load = useCallback(async () => {
|
||
setLoading(true);
|
||
try {
|
||
const result = await getMindSpaceAdminConfig();
|
||
setConfig({ ...initial, ...result.analytics });
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : '加载 Analytics 配置失败');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
useEffect(() => { void load(); }, [load]);
|
||
|
||
const save = async (event: FormEvent) => {
|
||
event.preventDefault();
|
||
setBusy(true);
|
||
setError(null);
|
||
setMessage(null);
|
||
try {
|
||
const result = await updateMindSpaceAdminConfig({
|
||
analytics: { enabled: config.enabled, websiteId: config.websiteId, analyticsUrl: config.analyticsUrl, domains: config.domains, ...(secret.trim() ? { idSecret: secret.trim() } : {}) },
|
||
});
|
||
setConfig({ ...initial, ...result.analytics });
|
||
setSecret('');
|
||
setMessage('Analytics 配置已保存;密钥不会在后台回显。');
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : '保存 Analytics 配置失败');
|
||
} finally {
|
||
setBusy(false);
|
||
}
|
||
};
|
||
|
||
const openUmami = async () => {
|
||
setOpeningUmami(true);
|
||
setError(null);
|
||
try {
|
||
window.location.assign(await getUmamiSsoUrl());
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : '无法打开 Umami');
|
||
setOpeningUmami(false);
|
||
}
|
||
};
|
||
|
||
return <div className="admin-page">
|
||
<div className="admin-page-head"><h2>Analytics 配置</h2><p className="muted">配置 Umami 并进入统计后台。Rybbit 已退役,见 Memind 文档 analytics-platform.md。</p></div>
|
||
{error && <p className="banner banner-error">{error}</p>}
|
||
{message && <p className="banner banner-info">{message}</p>}
|
||
<section className="admin-card">
|
||
<h2>SEO / GEO 页面流量</h2>
|
||
<p className="muted">按页面查看搜索引擎与生成式引擎来源访问,表格口径与 Umami「所有生成页面明细」一致。</p>
|
||
<div className="admin-actions">
|
||
<Link to="/analytics/seo-geo" className="send-btn">打开 SEO / GEO 流量看板</Link>
|
||
</div>
|
||
</section>
|
||
<section className="admin-card">
|
||
<h2>Umami</h2>
|
||
<div className="admin-actions" style={{ marginBottom: 16 }}><button type="button" className="send-btn" onClick={() => void openUmami()} disabled={openingUmami || loading}>{openingUmami ? '正在进入 Umami...' : '打开 Umami 分析后台'}</button></div>
|
||
<form className="admin-form" onSubmit={save}>
|
||
<label className="admin-form-row"><span>启用页面统计</span><input type="checkbox" checked={config.enabled} disabled={loading || busy} onChange={(e) => setConfig({ ...config, enabled: e.target.checked })} /></label>
|
||
<label className="admin-form-row"><span>Website ID</span><input value={config.websiteId} disabled={loading || busy} onChange={(e) => setConfig({ ...config, websiteId: e.target.value })} placeholder="Umami Website ID" required={config.enabled} /></label>
|
||
<label className="admin-form-row"><span>Umami 地址</span><input value={config.analyticsUrl} disabled={loading || busy} onChange={(e) => setConfig({ ...config, analyticsUrl: e.target.value })} placeholder="http://127.0.0.1:3100" /></label>
|
||
<label className="admin-form-row"><span>统计域名</span><input value={config.domains} disabled={loading || busy} onChange={(e) => setConfig({ ...config, domains: e.target.value })} placeholder="127.0.0.1,localhost" /></label>
|
||
<label className="admin-form-row"><span>匿名化密钥 {config.idSecretConfigured ? '(已配置,留空保持不变)' : ''}</span><input type="password" value={secret} disabled={loading || busy} onChange={(e) => setSecret(e.target.value)} placeholder={config.idSecretConfigured ? '留空保持当前密钥' : '输入本地随机密钥'} autoComplete="new-password" /></label>
|
||
<p className="muted">SEO/GEO 看板还需在 memind_adm 环境变量配置 <code>UMAMI_ADMIN_PASSWORD</code>(与 Umami admin 账号匹配)。</p>
|
||
<div className="admin-actions"><button type="submit" className="send-btn" disabled={busy || loading}>{busy ? '保存中...' : '保存配置'}</button><button type="button" className="ghost-btn" onClick={() => void load()} disabled={busy}>重新加载</button></div>
|
||
</form>
|
||
</section>
|
||
</div>;
|
||
}
|