Possibilité d'avoir une clé API Globale par famille
This commit is contained in:
@@ -247,7 +247,7 @@ export default function Aide() {
|
||||
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
|
||||
<code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4 }}>crowdlending_get_investisseur</code>
|
||||
</td>
|
||||
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>Profil de l'investisseur lié à la clé API (nom, type famille/entreprise, régime fiscal).</td>
|
||||
<td style={{ padding: '8px 0', verticalAlign: 'top' }}>Profil de l'investisseur lié à la clé API (nom, type famille/entreprise, régime fiscal). Avec une clé « Famille et entreprises », renvoie la liste des profils du foyer.</td>
|
||||
</tr>
|
||||
<tr style={{ borderBottom: '1px solid var(--border)' }}>
|
||||
<td style={{ padding: '8px 10px 8px 0', verticalAlign: 'top', whiteSpace: 'nowrap' }}>
|
||||
|
||||
@@ -805,6 +805,12 @@ function NewApiKeyModal({ open, onClose, onCreated, investisseurs }) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [err, setErr] = useState(null);
|
||||
|
||||
// La clé "Famille et entreprises" (scope_all) n'est autorisée que depuis le
|
||||
// profil principal — cohérent avec l'enforcement côté serveur (routes/apiKeys.js).
|
||||
// Si aucun principal n'est trouvé (ne devrait pas arriver), on masque
|
||||
// simplement l'option plutôt que de proposer un choix qui échouera au submit.
|
||||
const principal = investisseurs.find(i => i.is_principal);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setNom('');
|
||||
@@ -813,6 +819,8 @@ function NewApiKeyModal({ open, onClose, onCreated, investisseurs }) {
|
||||
}
|
||||
}, [open, investisseurs]);
|
||||
|
||||
const isScopeAll = investisseurId === 'all';
|
||||
|
||||
const submit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!nom.trim()) return setErr('Le nom de la clé est requis');
|
||||
@@ -820,7 +828,10 @@ function NewApiKeyModal({ open, onClose, onCreated, investisseurs }) {
|
||||
setBusy(true);
|
||||
setErr(null);
|
||||
try {
|
||||
const created = await api.post('/api-keys', { nom: nom.trim(), investisseur_id: Number(investisseurId) });
|
||||
const payload = isScopeAll
|
||||
? { nom: nom.trim(), investisseur_id: principal.id, scope_all: true }
|
||||
: { nom: nom.trim(), investisseur_id: Number(investisseurId) };
|
||||
const created = await api.post('/api-keys', payload);
|
||||
onCreated(created);
|
||||
} catch (e) { setErr(e.message); }
|
||||
finally { setBusy(false); }
|
||||
@@ -841,10 +852,13 @@ function NewApiKeyModal({ open, onClose, onCreated, investisseurs }) {
|
||||
{investisseurs.map(i => (
|
||||
<option key={i.id} value={i.id}>{memberLabel(i)}</option>
|
||||
))}
|
||||
{principal && <option value="all">Famille et entreprises</option>}
|
||||
</select>
|
||||
</div>
|
||||
<p className="text-muted" style={{ margin: 0, fontSize: 'var(--fs-sm)' }}>
|
||||
La clé donne un accès en lecture seule aux données de cet investisseur. Elle ne sera affichée en clair qu'une seule fois.
|
||||
{isScopeAll
|
||||
? "La clé donnera un accès en lecture seule agrégé à tous les membres du foyer (comme la vue « Famille et entreprises » de l'app). Elle ne sera affichée en clair qu'une seule fois."
|
||||
: "La clé donne un accès en lecture seule aux données de cet investisseur. Elle ne sera affichée en clair qu'une seule fois."}
|
||||
</p>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 4 }}>
|
||||
<button type="button" className="ghost" onClick={onClose}>Annuler</button>
|
||||
@@ -1043,7 +1057,7 @@ function ApiKeysSection() {
|
||||
{keys.map(k => (
|
||||
<tr key={k.id}>
|
||||
<td>{k.nom}</td>
|
||||
<td>{k.investisseur_nom}</td>
|
||||
<td>{k.scope_all ? 'Famille et entreprises' : k.investisseur_nom}</td>
|
||||
<td style={{ fontFamily: 'monospace', fontSize: 12 }}>{k.key_prefix}…</td>
|
||||
<td>{fmtKeyDate(k.created_at)}</td>
|
||||
<td>{fmtKeyDate(k.last_used_at)}</td>
|
||||
@@ -1149,10 +1163,12 @@ function CopyBlock({ text }) {
|
||||
}
|
||||
|
||||
function McpServerSection({ goToApiKeys }) {
|
||||
const navigate = useNavigate();
|
||||
const [mcpUrl, setMcpUrl] = useState(guessMcpUrl());
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [manualLabel, setManualLabel] = useState(null); // null = auto-détecté depuis mcpUrl, sinon override manuel
|
||||
const [debugFlag, setDebugFlag] = useState(false); // ajoute --debug : génère un fichier mcp-server-<nom>.log dédié (voir Dépannage)
|
||||
const [systemCaFlag, setSystemCaFlag] = useState(true); // ajoute NODE_OPTIONS=--use-system-ca : contourne un antivirus/proxy qui intercepte le HTTPS (voir Dépannage) — coché par défaut, ne s'applique qu'en prod (voir envBlock)
|
||||
|
||||
const detectedLabel = detectLabelFromUrl(mcpUrl);
|
||||
const label = manualLabel ?? detectedLabel;
|
||||
@@ -1177,11 +1193,20 @@ function McpServerSection({ goToApiKeys }) {
|
||||
// macOS/Linux, qui n'ont pas ce problème (npx s'exécute nativement).
|
||||
const isWindows = typeof navigator !== 'undefined' && /win/i.test(navigator.platform || navigator.userAgent || '');
|
||||
|
||||
const envBlock = {
|
||||
[envVarName]: apiKey || '<VOTRE_CLE_API>',
|
||||
// Uniquement pertinent pour une URL distante (prod) : force Node à utiliser
|
||||
// le magasin de certificats du système plutôt que le sien, pour accepter
|
||||
// les connexions re-signées par un antivirus/proxy à inspection HTTPS
|
||||
// (Avast, Kaspersky, proxy d'entreprise…) — voir Dépannage.
|
||||
...(!isLocal && systemCaFlag ? { NODE_OPTIONS: '--use-system-ca' } : {}),
|
||||
};
|
||||
|
||||
const configJson = JSON.stringify({
|
||||
mcpServers: {
|
||||
[serverKey]: isWindows
|
||||
? { command: 'cmd', args: ['/c', 'npx', ...mcpRemoteArgs], env: { [envVarName]: apiKey || '<VOTRE_CLE_API>' } }
|
||||
: { command: 'npx', args: mcpRemoteArgs, env: { [envVarName]: apiKey || '<VOTRE_CLE_API>' } },
|
||||
? { command: 'cmd', args: ['/c', 'npx', ...mcpRemoteArgs], env: envBlock }
|
||||
: { command: 'npx', args: mcpRemoteArgs, env: envBlock },
|
||||
},
|
||||
}, null, 2);
|
||||
|
||||
@@ -1261,12 +1286,15 @@ function McpServerSection({ goToApiKeys }) {
|
||||
action nécessaire au-delà de renseigner l'URL et votre clé API.
|
||||
</p>
|
||||
)}
|
||||
<p style={{ margin: '0 0 20px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
Claude Desktop peut se connecter à plusieurs serveurs MCP en même temps : pour avoir dev et
|
||||
prod accessibles simultanément, répétez ces étapes une deuxième fois avec l'autre URL (et une clé
|
||||
API distincte) — la clé de config (<code>{serverKey}</code> ci-dessous) s'ajuste automatiquement,
|
||||
Claude Desktop ne confondra jamais les deux portefeuilles.
|
||||
</p>
|
||||
{isLocal && (
|
||||
<p style={{ margin: '0 0 20px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
Claude Desktop peut se connecter à plusieurs serveurs MCP en même temps : pour avoir aussi votre
|
||||
portefeuille en production accessible depuis Claude Desktop, répétez ces étapes une deuxième fois
|
||||
avec l'URL de production (et une clé API distincte) — la clé de config
|
||||
(<code>{serverKey}</code> ci-dessous) s'ajuste automatiquement, Claude Desktop ne confondra jamais
|
||||
les deux connexions.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>3. Copier la configuration</h4>
|
||||
<p style={{ margin: '0 0 10px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
@@ -1295,10 +1323,27 @@ function McpServerSection({ goToApiKeys }) {
|
||||
style={{ width: '100%', maxWidth: 520, borderRadius: 8, border: '1px solid var(--border)', display: 'block', margin: '0 auto 16px' }}
|
||||
onError={(e) => { e.currentTarget.style.display = 'none'; }}
|
||||
/>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 6, margin: '0 0 10px', fontWeight: 400, fontSize: 'var(--fs-sm)', cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={debugFlag} onChange={e => setDebugFlag(e.target.checked)} style={{ width: 'auto' }} />
|
||||
Ajouter <code>--debug</code> (recommandé en cas de problème — voir Dépannage ci-dessous)
|
||||
</label>
|
||||
<div style={{ margin: '0 0 10px' }}>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontWeight: 400, fontSize: 'var(--fs-sm)', cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={debugFlag} onChange={e => setDebugFlag(e.target.checked)} style={{ width: 'auto' }} />
|
||||
Ajouter <code>--debug</code>
|
||||
</label>
|
||||
<p style={{ margin: '2px 0 0 22px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
Recommandé en cas de problème — voir Dépannage ci-dessous.
|
||||
</p>
|
||||
</div>
|
||||
{!isLocal && (
|
||||
<div style={{ margin: '0 0 10px' }}>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontWeight: 400, fontSize: 'var(--fs-sm)', cursor: 'pointer' }}>
|
||||
<input type="checkbox" checked={systemCaFlag} onChange={e => setSystemCaFlag(e.target.checked)} style={{ width: 'auto' }} />
|
||||
Ajouter <code>NODE_OPTIONS=--use-system-ca</code>
|
||||
</label>
|
||||
<p style={{ margin: '2px 0 0 22px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
Coché par défaut : nécessaire si un antivirus ou un proxy intercepte le HTTPS — voir Dépannage
|
||||
ci-dessous. Décochez uniquement si vous savez que ce n'est pas votre cas.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<CopyBlock text={configJson} />
|
||||
|
||||
<h4 style={{ margin: '20px 0 6px', fontSize: 'var(--fs-sm)' }}>4. Redémarrer Claude Desktop</h4>
|
||||
@@ -1307,12 +1352,22 @@ function McpServerSection({ goToApiKeys }) {
|
||||
</p>
|
||||
|
||||
<h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>5. Vérifier</h4>
|
||||
<p style={{ margin: '0 0 20px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
Dans la liste des outils MCP de Claude Desktop, les outils <code>crowdlending_*</code> doivent
|
||||
apparaître (6 en production, 7 en développement local si <code>crowdlending_fetch_url</code> est
|
||||
activé — voir <code>mcp-server/README.md</code>). Testez avec une question du type « Quel est mon
|
||||
encours de crowdlending actuellement ? ».
|
||||
<p style={{ margin: '0 0 10px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
Demandez à Claude : « <strong style={{ color: 'var(--text)' }}>Peux-tu me lister les outils auxquels
|
||||
tu as accès ?</strong> » — c'est la façon la plus fiable de vérifier que la connexion fonctionne
|
||||
vraiment (plutôt qu'une question sur vos données, qui peut échouer pour d'autres raisons même si la
|
||||
connexion est bonne). Les outils <code>crowdlending_*</code> doivent apparaître dans sa réponse
|
||||
{isLocal
|
||||
? <> (6, ou 7 si <code>crowdlending_fetch_url</code> est activé — voir <code>mcp-server/README.md</code>)</>
|
||||
: ' (6 au total)'}.
|
||||
</p>
|
||||
<p style={{ margin: '0 0 20px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
Une fois la connexion confirmée, consultez la FAQ « Comment utiliser le serveur MCP » pour des
|
||||
exemples de questions et le détail de chaque fonction.
|
||||
</p>
|
||||
<button type="button" className="ghost" onClick={() => navigate('/aide?section=faq')} style={{ marginTop: -12, marginBottom: 20 }}>
|
||||
Voir la FAQ →
|
||||
</button>
|
||||
|
||||
<h4 style={{ margin: '0 0 6px', fontSize: 'var(--fs-sm)' }}>6. Dépannage</h4>
|
||||
|
||||
@@ -1341,13 +1396,40 @@ function McpServerSection({ goToApiKeys }) {
|
||||
l'étape 3 (essayez l'autre si l'un des deux est vide ou ne se met pas à jour).
|
||||
</p>
|
||||
|
||||
<p style={{ margin: '0 0 4px', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>Vérifier côté serveur</p>
|
||||
<p style={{ margin: 0, fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
La console du serveur (le terminal où tourne <code>npm run dev</code>) affiche désormais une ligne
|
||||
par requête reçue — session, outil appelé, statut, durée. Si rien n'y apparaît alors qu'un appel a
|
||||
été fait depuis Claude Desktop, la requête n'arrive jamais jusqu'ici : le problème est côté
|
||||
<code>npx</code>/<code>mcp-remote</code> (voir ci-dessus), pas dans <code>server.js</code>.
|
||||
</p>
|
||||
{!isLocal && (
|
||||
<>
|
||||
<p style={{ margin: '0 0 4px', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>
|
||||
Connexion refusée avec une erreur de certificat, alors que le serveur répond bien
|
||||
</p>
|
||||
<p style={{ margin: '0 0 14px', fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
Symptôme différent du précédent : la config est correcte, mais <code>mcp-remote</code> échoue à
|
||||
joindre l'URL de production elle-même (pas le registre npm cette fois) avec une erreur
|
||||
<code> UNABLE_TO_VERIFY_LEAF_SIGNATURE</code> dans les logs. Même cause de fond — un antivirus ou
|
||||
un proxy avec inspection HTTPS (Avast, Kaspersky, ESET…) re-signe le trafic avec son propre
|
||||
certificat, que Node.js ne reconnaît pas (contrairement à votre navigateur, qui lui fait
|
||||
confiance via le magasin Windows). Solution : cochez la case <code>NODE_OPTIONS=--use-system-ca</code>
|
||||
ci-dessus (étape 3) — Node utilisera alors le magasin de certificats Windows plutôt que le sien.
|
||||
Nécessite Node.js 22.16 ou plus récent (vérifiable avec <code>node -v</code>) ; sur une version
|
||||
plus ancienne, il faut exporter le certificat racine de l'antivirus (<code>certmgr.msc</code> →
|
||||
Autorités de certification racines de confiance → Exporter en Base-64 X.509) et le référencer via
|
||||
<code> NODE_EXTRA_CA_CERTS</code> à la place. Évitez de désactiver complètement la vérification
|
||||
TLS (<code>NODE_TLS_REJECT_UNAUTHORIZED=0</code>) : cette connexion sort sur internet avec votre
|
||||
clé API et vos données, contrairement au développement local en <code>localhost</code>.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isLocal && (
|
||||
<>
|
||||
<p style={{ margin: '0 0 4px', fontSize: 'var(--fs-sm)', fontWeight: 600 }}>Vérifier côté serveur</p>
|
||||
<p style={{ margin: 0, fontSize: 'var(--fs-sm)', color: 'var(--text-muted)' }}>
|
||||
La console du serveur (le terminal où tourne <code>npm run dev</code>) affiche désormais une ligne
|
||||
par requête reçue — session, outil appelé, statut, durée. Si rien n'y apparaît alors qu'un appel a
|
||||
été fait depuis Claude Desktop, la requête n'arrive jamais jusqu'ici : le problème est côté
|
||||
<code>npx</code>/<code>mcp-remote</code> (voir ci-dessus), pas dans <code>server.js</code>.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user