Files
memind_adm/src/admin/pages/AnalyticsConfigPage.tsx
T
john 6a7d6cec70 fix(analytics): use Umami SSO for SEO/GEO API on SSO-only deploy
Production Umami rejects password login when MEMIND_UMAMI_SSO_ONLY=1;
server-side memind-pages fetch now authenticates via MEMIND_UMAMI_SSO_SECRET.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 17:21:28 +08:00

92 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 <code>MEMIND_UMAMI_SSO_SECRET</code> 105 Umami <code>UMAMI_ADMIN_PASSWORD</code> 访 Umami API Umami SSO-only </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>;
}