MB-IDK's picture
Update ui.py
106e45d verified
Raw
History Blame Contribute Delete
11.7 kB
"""
ui.py — Interface navigateur pour CroxyProxy API
Contient uniquement le HTML/CSS/JS de l'UI. Importé par app.py.
"""
from html import escape
def error_page(message, url=""):
"""Page d'erreur affichée dans l'iframe quand une page ne charge pas."""
code = f'<p><code>{escape(url)}</code></p>' if url else ''
return f"""<!doctype html><html><head><meta charset="utf-8">
<style>body{{background:#0d0f17;color:#e6e8f0;font-family:system-ui,sans-serif;
display:flex;align-items:center;justify-content:center;height:100vh;margin:0}}
.box{{text-align:center;max-width:480px;padding:2rem}}
.icon{{font-size:3rem;margin-bottom:1rem}}
h2{{margin:0 0 .5rem;font-weight:600}}
p{{color:#8b90a3;line-height:1.5;margin:.4rem 0}}
code{{background:#1a1d2b;padding:.2rem .5rem;border-radius:6px;font-size:.85rem;
word-break:break-all}}</style>
</head><body><div class="box"><div class="icon">⚠️</div>
<h2>Impossible de charger la page</h2>
<p>{escape(message)}</p>
{code}
<p style="margin-top:1.5rem;font-size:.85rem">Réessaie avec un autre serveur, ou un autre site.</p>
</div></body></html>"""
# Interface principale servie sur "/"
BROWSER_UI = r"""<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Proxy Browser</title>
<style>
:root{
--bg:#0b0d14; --chrome:#12141f; --chrome-2:#171a28;
--border:#232739; --border-2:#2d3247;
--txt:#e7e9f3; --muted:#8b90a8; --faint:#5a5f78;
--accent:#6d8bff; --accent-2:#8aa2ff; --ok:#4ade80; --err:#f87171;
--radius:12px;
}
*{box-sizing:border-box}
html,body{height:100%;margin:0}
body{
background:var(--bg); color:var(--txt);
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
display:flex; flex-direction:column; overflow:hidden;
}
.chrome{
background:linear-gradient(180deg,var(--chrome-2),var(--chrome));
border-bottom:1px solid var(--border);
padding:10px 12px; display:flex; flex-direction:column; gap:9px; flex-shrink:0;
}
.row{display:flex; align-items:center; gap:8px}
.nav-btn{
width:34px; height:34px; flex-shrink:0;
background:var(--chrome-2); border:1px solid var(--border);
border-radius:9px; color:var(--muted); cursor:pointer;
font-size:15px; display:flex; align-items:center; justify-content:center; transition:.15s;
}
.nav-btn:hover{background:#1d2133; color:var(--txt); border-color:var(--border-2)}
.nav-btn:active{transform:scale(.93)}
.bar{
flex:1; display:flex; align-items:center; gap:8px;
background:var(--bg); border:1px solid var(--border);
border-radius:11px; padding:0 12px; height:38px; transition:.15s;
}
.bar:focus-within{border-color:var(--accent); box-shadow:0 0 0 3px rgba(109,139,255,.12)}
.lock{font-size:13px; color:var(--faint); flex-shrink:0}
#addr{flex:1; background:none; border:none; outline:none; color:var(--txt);
font-size:14px; height:100%; min-width:0}
#addr::placeholder{color:var(--faint)}
.go{background:var(--accent); color:#fff; border:none; border-radius:10px;
height:38px; padding:0 18px; font-size:14px; font-weight:600; cursor:pointer;
flex-shrink:0; transition:.15s}
.go:hover{background:var(--accent-2)}
.go:active{transform:scale(.96)}
.info{display:flex; align-items:center; gap:10px; font-size:12px; color:var(--muted); padding:0 2px}
.pill{display:inline-flex; align-items:center; gap:6px; background:var(--chrome-2);
border:1px solid var(--border); border-radius:20px; padding:4px 11px; font-size:12px}
.dot{width:7px; height:7px; border-radius:50%; background:var(--faint)}
.dot.ok{background:var(--ok); box-shadow:0 0 6px var(--ok)}
.dot.err{background:var(--err)}
.dot.load{background:var(--accent); animation:pulse 1s infinite}
@keyframes pulse{50%{opacity:.3}}
select{background:var(--chrome-2); border:1px solid var(--border); color:var(--txt);
border-radius:20px; padding:4px 10px; font-size:12px; cursor:pointer; outline:none; max-width:220px}
.info .spacer{flex:1}
.info a{color:var(--faint); text-decoration:none}
.info a:hover{color:var(--accent-2)}
label.chk{display:inline-flex; align-items:center; gap:6px; cursor:pointer; user-select:none}
label.chk input{accent-color:var(--accent)}
.loader{height:2px; background:transparent; flex-shrink:0; overflow:hidden}
.loader.active::after{content:""; display:block; height:100%; width:40%;
background:linear-gradient(90deg,transparent,var(--accent),transparent);
animation:slide 1s infinite linear}
@keyframes slide{from{transform:translateX(-100%)}to{transform:translateX(350%)}}
.viewport{flex:1; position:relative; background:#fff}
iframe{width:100%; height:100%; border:none; background:#fff}
.home{position:absolute; inset:0; background:var(--bg); display:flex;
flex-direction:column; align-items:center; justify-content:center;
gap:28px; padding:24px; overflow:auto}
.home h1{font-size:30px; font-weight:700; margin:0; letter-spacing:-.5px;
background:linear-gradient(120deg,var(--accent-2),#b794ff);
-webkit-background-clip:text; background-clip:text; color:transparent}
.home p{color:var(--muted); margin:0; font-size:14px}
.shortcuts{display:grid; grid-template-columns:repeat(auto-fill,minmax(96px,1fr));
gap:12px; max-width:560px; width:100%}
.sc{background:var(--chrome); border:1px solid var(--border); border-radius:var(--radius);
padding:16px 10px; text-align:center; cursor:pointer; transition:.15s; color:var(--txt)}
.sc:hover{border-color:var(--accent); transform:translateY(-2px); background:var(--chrome-2)}
.sc .em{font-size:24px; display:block; margin-bottom:7px}
.sc .nm{font-size:12px; color:var(--muted)}
.hidden{display:none !important}
</style>
</head>
<body>
<div class="chrome">
<div class="row">
<button class="nav-btn" id="back" title="Précédent">‹</button>
<button class="nav-btn" id="fwd" title="Suivant">›</button>
<button class="nav-btn" id="reload" title="Recharger">⟳</button>
<button class="nav-btn" id="home" title="Accueil">⌂</button>
<div class="bar">
<span class="lock">🔒</span>
<input id="addr" placeholder="Cherche ou entre une URL…" autocomplete="off" spellcheck="false">
</div>
<button class="go" id="go">Aller</button>
</div>
<div class="info">
<span class="pill"><span class="dot" id="dot"></span><span id="status">Prêt</span></span>
<select id="srv" title="Serveur Croxy (API uniquement — la navigation est en direct)"><option value="">🔀 Serveurs (API)</option></select>
<label class="chk"><input type="checkbox" id="rand"> Aléatoire</label>
<span class="spacer"></span>
<span id="ipinfo"></span>
<a href="/api" target="_blank">API ↗</a>
</div>
</div>
<div class="loader" id="loader"></div>
<div class="viewport">
<iframe id="frame" referrerpolicy="no-referrer"></iframe>
<div class="home" id="homescreen">
<div style="text-align:center">
<h1>Proxy Browser</h1>
<p>Navigue sur le web via les serveurs CroxyProxy</p>
</div>
<div class="shortcuts" id="shortcuts"></div>
<p style="font-size:12px;color:var(--faint);max-width:440px;text-align:center">
Récupération directe depuis le serveur. Les sites « server-side »
(Wikipedia, vieux Reddit, docs, news) marchent bien ; les apps lourdes
en JavaScript peuvent ne pas s'afficher correctement.
</p>
</div>
</div>
<script>
const frame = document.getElementById('frame');
const addr = document.getElementById('addr');
const dot = document.getElementById('dot');
const statusEl= document.getElementById('status');
const loader = document.getElementById('loader');
const srvSel = document.getElementById('srv');
const randChk = document.getElementById('rand');
const ipinfo = document.getElementById('ipinfo');
const home = document.getElementById('homescreen');
let loadTimer = null;
const SHORTCUTS = [
{nm:'Google', em:'🔍', u:'https://www.google.com'},
{nm:'DuckDuckGo', em:'🦆', u:'https://duckduckgo.com/html/'},
{nm:'Wikipedia', em:'📚', u:'https://www.wikipedia.org'},
{nm:'Reddit', em:'👽', u:'https://old.reddit.com'},
{nm:'YouTube', em:'▶️', u:'https://www.youtube.com'},
{nm:'GitHub', em:'🐙', u:'https://github.com'},
{nm:'HackerNews', em:'🟧', u:'https://news.ycombinator.com'},
{nm:'Mon IP', em:'🌐', u:'https://httpbin.org/ip'},
];
const scWrap = document.getElementById('shortcuts');
SHORTCUTS.forEach(s=>{
const d = document.createElement('div');
d.className = 'sc';
d.innerHTML = '<span class="em">'+s.em+'</span><span class="nm">'+s.nm+'</span>';
d.onclick = ()=> navigate(s.u);
scWrap.appendChild(d);
});
// Liste des serveurs
fetch('/servers').then(r=>r.json()).then(d=>{
(d.servers||[]).forEach(s=>{
const o = document.createElement('option');
o.value = s.id;
o.textContent = (s.name||'?') + (s.ip ? ' ('+s.ip+')' : '');
srvSel.appendChild(o);
});
}).catch(()=>{});
function setStatus(txt, kind){ statusEl.textContent = txt; dot.className = 'dot' + (kind ? ' '+kind : ''); }
function showLoading(on){ loader.className = on ? 'loader active' : 'loader'; }
function normalizeInput(v){
v = (v||'').trim();
if(!v) return null;
if(/^https?:\/\//i.test(v)) return v;
if(/^[\w-]+(\.[\w-]+)+(\/.*)?$/.test(v) && !v.includes(' ')) return 'https://' + v;
return 'https://duckduckgo.com/html/?q=' + encodeURIComponent(v);
}
function chosenServer(){
if(randChk.checked && srvSel.options.length > 1){
const i = 1 + Math.floor(Math.random() * (srvSel.options.length - 1));
return srvSel.options[i].value;
}
return srvSel.value;
}
function navigate(url){
const target = normalizeInput(url);
if(!target) return;
const sid = chosenServer();
home.classList.add('hidden');
showLoading(true);
setStatus('Chargement…', 'load');
addr.value = target;
let view = '/view?url=' + encodeURIComponent(target);
if(sid) view += '&server_id=' + sid;
// garde-fou : si rien ne charge en 90s on prévient
if(loadTimer) clearTimeout(loadTimer);
loadTimer = setTimeout(()=>{ setStatus('Lent ou bloqué…', 'err'); showLoading(false); }, 90000);
frame.src = view;
}
frame.addEventListener('load', ()=>{
showLoading(false);
if(loadTimer){ clearTimeout(loadTimer); loadTimer = null; }
try{
const loc = frame.contentWindow.location;
if(loc.pathname === '/view'){
const params = new URLSearchParams(loc.search);
const real = params.get('url');
if(real) addr.value = real;
const sid = params.get('server_id');
const opt = sid ? [...srvSel.options].find(o=>o.value===sid) : null;
ipinfo.textContent = opt ? (opt.textContent.match(/\(([^)]+)\)/)?.[1] || '') : '';
}
setStatus('Connecté', 'ok');
}catch(e){
setStatus('Connecté', 'ok');
}
});
document.getElementById('go').onclick = ()=> navigate(addr.value);
document.getElementById('reload').onclick = ()=> { try{ frame.contentWindow.location.reload(); }catch(e){} };
document.getElementById('back').onclick = ()=> { try{ frame.contentWindow.history.back(); }catch(e){} };
document.getElementById('fwd').onclick = ()=> { try{ frame.contentWindow.history.forward(); }catch(e){} };
document.getElementById('home').onclick = ()=> {
home.classList.remove('hidden'); addr.value = '';
setStatus('Prêt', ''); ipinfo.textContent = ''; frame.src = 'about:blank';
};
addr.addEventListener('keydown', e=>{ if(e.key === 'Enter') navigate(addr.value); });
addr.addEventListener('focus', ()=> addr.select());
</script>
</body>
</html>"""