mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-21 22:18:13 +02:00
v0.5.1 : accès distant ajean.link depuis l'UI + app Windows sans console
- Panneau « Accès distant » dans l'UI web : abonnement + liaison ajean.link en un clic (popup connect.html → clé remise à l'agent local), sans terminal. Endpoints locaux /api/link/status|connect|disconnect|paircode (web_link_api.go). - Accès OpenAI public : refuse d'être activé sans accès distant, avec message. - Windows : binaire compilé en sous-système GUI (-H=windowsgui) → plus aucune fenêtre de console au double-clic ; rattachement à la console parente en CLI (sys_console_windows.go). Suppression du hack relaunchDetachedApp. - Windows : masquage (CREATE_NO_WINDOW) de TOUS les sous-process — git/vswhere du panneau MOTEUR (flash à chaque refresh), llama-server, MCP stdio, winget… - Vouvoiement du nouveau parcours.
This commit is contained in:
1 parent
8e57685b19
commit
d8b08dbb89
22 files changed
+444
-103
No files matched your search
@@ -124,6 +124,30 @@
|
||||
<button onclick="apiKeyAction('clear')">retirer</button>
|
||||
</div>
|
||||
</details>
|
||||
<details id="remote-details"><summary>Accès distant <span id="remote-badge" style="margin-left:auto;margin-right:12px;font-size:10px"></span></summary>
|
||||
<div class="subhead">ajean.link<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">Accédez à ce serveur Jean depuis <b>n'importe où</b>, sans ouvrir de port, à travers le relais <b>ajean.link</b>. Le trafic est chiffré de <b>bout en bout</b> : le relais ne voit rien. Abonnement 4,80 €/mois.</span></span></div>
|
||||
<!-- Non connecté : un seul bouton, tout se passe dans la popup (compte + paiement). -->
|
||||
<div id="remote-off">
|
||||
<p class="muted" style="font-size:12px;line-height:1.5;margin:6px 0 10px">Ouvrez l'accès distant à ce serveur en quelques clics — compte, abonnement et connexion, sans terminal.</p>
|
||||
<button onclick="remoteConnect()" style="border-color:var(--accent);color:var(--accent)">🔗 Connecter à ajean.link</button>
|
||||
</div>
|
||||
<!-- Connecté : URL publique + appairage + déconnexion. -->
|
||||
<div id="remote-on" style="display:none">
|
||||
<div class="muted" style="font-size:11px;margin-bottom:2px">🌍 votre adresse d'accès distant</div>
|
||||
<div class="row">
|
||||
<input id="remote-url" readonly style="flex:1;min-width:0;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px;font:inherit;font-size:12px">
|
||||
<button onclick="copyText(document.getElementById('remote-url').value,'adresse copiée')" title="copier">⧉</button>
|
||||
<button onclick="window.open(document.getElementById('remote-url').value,'_blank')" title="ouvrir">↗</button>
|
||||
</div>
|
||||
<div id="remote-status" class="muted" style="font-size:11px;margin-top:6px"></div>
|
||||
<div class="subhead" style="margin-top:12px">Première connexion<span class="help" tabindex="0" onclick="event.preventDefault();event.stopPropagation();this.classList.toggle('open')">?<span class="tip">À votre première ouverture du portail distant, confirmez l'<b>empreinte</b> et saisissez le <b>code d'appairage</b> : cela verrouille la boîte noire sur CE serveur (anti-interception). Une seule fois par appareil.</span></span></div>
|
||||
<button onclick="remotePairCode()" style="margin-top:4px">🔑 Afficher l'empreinte + le code d'appairage</button>
|
||||
<div id="remote-pair" class="muted" style="font-size:12px;margin-top:8px;display:none;line-height:1.7"></div>
|
||||
<div class="row" style="margin-top:12px">
|
||||
<button onclick="remoteDisconnect()" style="border-color:var(--err);color:var(--err)">déconnecter l'accès distant</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<details id="lc-details"><summary>MOTEUR</summary>
|
||||
<div id="lc-status" class="lc-head muted">…</div>
|
||||
<div class="lc-modes">
|
||||
|
||||
@@ -195,7 +195,19 @@ async function apiKeyAction(action){
|
||||
renderApiKey(await jpost('/api/apikey', {action}));
|
||||
}
|
||||
async function toggleOAIPublic(){
|
||||
const on = document.getElementById('oai-public-toggle').checked;
|
||||
const cb = document.getElementById('oai-public-toggle');
|
||||
const on = cb.checked;
|
||||
// L'accès OpenAI public passe par ajean.link (<machine>.oai.ajean.link) : il exige
|
||||
// que l'accès distant soit activé sur ce serveur. Sinon, on annule et on explique.
|
||||
if(on){
|
||||
let linked = false;
|
||||
try{ const s = await jget('/api/link/status'); linked = !!(s && s.linked); }catch(e){}
|
||||
if(!linked){
|
||||
cb.checked = false;
|
||||
await askAlert('Vous devez activer l\'accès distant (ajean.link) pour bénéficier de cette fonctionnalité. Ouvrez le panneau « Accès distant » pour connecter ce serveur.', {title:'Accès distant requis'});
|
||||
return;
|
||||
}
|
||||
}
|
||||
await jpost('/api/oai/public', {enabled:on});
|
||||
toast(on ? 'accès public activé' : 'accès public coupé');
|
||||
loadApiKey();
|
||||
@@ -216,5 +228,5 @@ async function saveCrawlUrl(){
|
||||
const url=document.getElementById('crawl-url').value.trim();
|
||||
renderInternet(await jpost('/api/internet',{url}));
|
||||
}
|
||||
async function loadAll(){ await Promise.all([loadStatus(),loadVram(),loadRam(),loadCfg(),loadPresets(),loadAgent(),loadInternet(),loadMCP(),loadApiKey(),loadPrefs(),loadLlamacpp()]); }
|
||||
async function loadAll(){ await Promise.all([loadStatus(),loadVram(),loadRam(),loadCfg(),loadPresets(),loadAgent(),loadInternet(),loadMCP(),loadApiKey(),loadPrefs(),loadLlamacpp(),loadRemote()]); }
|
||||
async function act(a){ toast(a+'…'); await jpost('/api/'+a); setTimeout(loadAll,1500); }
|
||||
@@ -0,0 +1,77 @@
|
||||
// Accès distant (ajean.link) — piloté depuis l'UI, sans terminal.
|
||||
// « Connecter » ouvre une popup app.ajean.link/connect.html qui gère compte +
|
||||
// abonnement, puis renvoie une clé de liaison par postMessage. On la POSTe à
|
||||
// l'agent local (/api/link/connect) qui fait le `jean link` (token + service).
|
||||
|
||||
const AJEAN_APP_ORIGIN = 'https://app.ajean.link';
|
||||
|
||||
function renderRemote(d){
|
||||
const off = document.getElementById('remote-off'), on = document.getElementById('remote-on');
|
||||
const badge = document.getElementById('remote-badge');
|
||||
if(!d || !d.linked){
|
||||
off.style.display=''; on.style.display='none';
|
||||
if(badge){ badge.textContent=''; }
|
||||
return;
|
||||
}
|
||||
off.style.display='none'; on.style.display='';
|
||||
document.getElementById('remote-url').value = d.machineURL || '';
|
||||
const st = document.getElementById('remote-status');
|
||||
if(d.active){ st.textContent='● en ligne — accessible à distance'; st.style.color='var(--accent)'; }
|
||||
else { st.textContent='○ service arrêté (l\'accès distant ne répondra pas)'; st.style.color='var(--warn)'; }
|
||||
if(badge){ badge.textContent = d.active ? '● connecté' : '○ arrêté'; badge.style.color = d.active?'var(--accent)':'var(--warn)'; }
|
||||
}
|
||||
|
||||
async function loadRemote(){
|
||||
// Le panneau ne vaut qu'en LOCAL : il pilote /api/link/* que le tunnel bloque
|
||||
// (boîte noire). Sur le portail distant (app.ajean.link/server.html), on le cache.
|
||||
const det = document.getElementById('remote-details');
|
||||
if(location.hostname === 'app.ajean.link'){ if(det) det.style.display='none'; return; }
|
||||
try{ renderRemote(await jget('/api/link/status')); }catch(e){}
|
||||
}
|
||||
|
||||
// Ouvre la popup de connexion et attend la clé renvoyée par postMessage.
|
||||
function remoteConnect(){
|
||||
const params = new URLSearchParams({ origin: window.location.origin, host: window.location.hostname || 'ce serveur' });
|
||||
const url = AJEAN_APP_ORIGIN + '/connect.html?' + params.toString();
|
||||
const pop = window.open(url, 'ajean-connect', 'width=440,height=640');
|
||||
if(!pop){ toast('autorisez les popups pour connecter ajean.link'); return; }
|
||||
|
||||
async function onMsg(ev){
|
||||
// Anti-usurpation : n'accepte QUE des messages du portail ajean.link.
|
||||
if(ev.origin !== AJEAN_APP_ORIGIN) return;
|
||||
const d = ev.data || {};
|
||||
if(d.type !== 'ajean-link' || !d.token) return;
|
||||
window.removeEventListener('message', onMsg);
|
||||
try{
|
||||
const r = await jpost('/api/link/connect', { token: d.token });
|
||||
if(r && r.linked){
|
||||
toast('✓ accès distant connecté');
|
||||
if(r.serviceErr){ toast('token enregistré (service : '+r.serviceErr+')'); }
|
||||
loadRemote();
|
||||
} else {
|
||||
toast((r && r.error) || 'échec de la connexion');
|
||||
}
|
||||
}catch(e){ toast('erreur : '+e); }
|
||||
}
|
||||
window.addEventListener('message', onMsg);
|
||||
}
|
||||
|
||||
async function remoteDisconnect(){
|
||||
const ok = await askConfirm('Couper l\'accès distant et oublier la clé de liaison de ce serveur ?', {title:'Accès distant', okLabel:'Déconnecter'});
|
||||
if(!ok) return;
|
||||
try{ await jpost('/api/link/disconnect', {}); toast('accès distant coupé'); loadRemote(); }
|
||||
catch(e){ toast('erreur : '+e); }
|
||||
}
|
||||
|
||||
async function remotePairCode(){
|
||||
const box = document.getElementById('remote-pair');
|
||||
box.style.display=''; box.textContent='génération du code…';
|
||||
try{
|
||||
const r = await jpost('/api/link/paircode', {});
|
||||
if(r && r.code){
|
||||
box.innerHTML = 'Empreinte : <b>'+(r.fingerprint||'—')+'</b><br>Code d\'appairage (valable 10 min, usage unique) : <b>'+r.code+'</b>';
|
||||
} else {
|
||||
box.textContent = (r && r.error) || 'code indisponible';
|
||||
}
|
||||
}catch(e){ box.textContent='erreur : '+e; }
|
||||
}
|
||||
Reference in new issue
Block a user