feat: add Memind analytics admin and Umami SSO
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { useCallback, useEffect, useState, type FormEvent } from 'react';
|
||||
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 [opening, setOpening] = 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 () => {
|
||||
setOpening(true);
|
||||
setError(null);
|
||||
try {
|
||||
window.location.assign(await getUmamiSsoUrl());
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '无法打开 Umami');
|
||||
setOpening(false);
|
||||
}
|
||||
};
|
||||
|
||||
return <div className="admin-page">
|
||||
<div className="admin-page-head"><h2>Analytics 配置</h2><p className="muted">配置本地 Memind 生成页面的 Umami 统计。默认只连接本机 Umami,不涉及生产环境。</p></div>
|
||||
{error && <p className="banner banner-error">{error}</p>}
|
||||
{message && <p className="banner banner-info">{message}</p>}
|
||||
<section className="admin-card">
|
||||
<h2>本地 Umami</h2>
|
||||
<div className="admin-actions" style={{ marginBottom: 16 }}><button type="button" className="send-btn" onClick={() => void openUmami()} disabled={opening || loading}>{opening ? '正在进入 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>
|
||||
<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>;
|
||||
}
|
||||
Reference in New Issue
Block a user