test 1.2 de la récupération de pp discord sur site web
This commit is contained in:
parent
d2bcf00ee7
commit
42db666026
4 changed files with 525 additions and 3 deletions
459
pages/test2.html
Normal file
459
pages/test2.html
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Kuby API Dashboard</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0b1220;
|
||||
--panel: #111a2e;
|
||||
--panel2: #0f172a;
|
||||
--text: #e5e7eb;
|
||||
--muted: #9ca3af;
|
||||
--accent: #60a5fa;
|
||||
--ok: #34d399;
|
||||
--bad: #f87171;
|
||||
--border: rgba(255,255,255,.08);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial;
|
||||
background: radial-gradient(1200px 700px at 15% 10%, rgba(96,165,250,.18), transparent 60%),
|
||||
radial-gradient(900px 600px at 90% 15%, rgba(52,211,153,.14), transparent 55%),
|
||||
var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
header {
|
||||
padding: 24px 20px 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: linear-gradient(180deg, rgba(255,255,255,.03), transparent);
|
||||
}
|
||||
h1 { margin: 0 0 6px; font-size: 20px; letter-spacing: .2px; }
|
||||
.sub { color: var(--muted); font-size: 13px; }
|
||||
.wrap { max-width: 1100px; margin: 0 auto; padding: 18px 20px 34px; }
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 14px;
|
||||
}
|
||||
@media (min-width: 900px) {
|
||||
.grid { grid-template-columns: 1fr 1fr; }
|
||||
.span2 { grid-column: span 2; }
|
||||
}
|
||||
.card {
|
||||
background: rgba(17,26,46,.75);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
padding: 14px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,.25);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.card h2 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 15px;
|
||||
color: #f3f4f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
|
||||
input, button, select {
|
||||
background: rgba(15,23,42,.85);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
border-radius: 10px;
|
||||
padding: 10px 10px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
input::placeholder { color: rgba(156,163,175,.7); }
|
||||
button {
|
||||
cursor: pointer;
|
||||
background: linear-gradient(180deg, rgba(96,165,250,.22), rgba(96,165,250,.06));
|
||||
border-color: rgba(96,165,250,.35);
|
||||
}
|
||||
button:hover { border-color: rgba(96,165,250,.55); }
|
||||
button:disabled { opacity: .6; cursor: not-allowed; }
|
||||
.pill {
|
||||
font-size: 12px;
|
||||
padding: 5px 9px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
background: rgba(15,23,42,.7);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pill.ok { color: #a7f3d0; border-color: rgba(52,211,153,.35); background: rgba(52,211,153,.10); }
|
||||
.pill.bad { color: #fecaca; border-color: rgba(248,113,113,.35); background: rgba(248,113,113,.10); }
|
||||
pre {
|
||||
margin: 10px 0 0;
|
||||
background: rgba(15,23,42,.75);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
max-height: 360px;
|
||||
font-size: 12px;
|
||||
color: #d1d5db;
|
||||
}
|
||||
.muted { color: var(--muted); font-size: 13px; }
|
||||
.list {
|
||||
margin-top: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
@media (min-width: 700px) {
|
||||
.list { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
.item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
background: rgba(15,23,42,.60);
|
||||
}
|
||||
.avatar, .icon {
|
||||
width: 44px; height: 44px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255,255,255,.06);
|
||||
border: 1px solid var(--border);
|
||||
object-fit: cover;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.item .meta { min-width: 0; }
|
||||
.name { font-weight: 650; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.small { font-size: 12px; color: var(--muted); }
|
||||
.right { margin-left: auto; display:flex; gap:8px; align-items:center; }
|
||||
a.link {
|
||||
color: #bfdbfe;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dashed rgba(191,219,254,.35);
|
||||
}
|
||||
.err { color: #fecaca; }
|
||||
.oktxt { color: #a7f3d0; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="wrap">
|
||||
<h1>Kuby API Dashboard</h1>
|
||||
<div class="sub">
|
||||
Affiche les réponses de l’API Flask: <code>/api/status</code>, <code>/api/user/<id></code>, <code>/api/guilds</code>, <code>/api/guild/<id></code>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<div class="grid">
|
||||
<section class="card">
|
||||
<h2>
|
||||
Statut du bot
|
||||
<span id="statusPill" class="pill">non chargé</span>
|
||||
</h2>
|
||||
<div class="row">
|
||||
<button id="btnStatus">Charger /api/status</button>
|
||||
<span class="muted">Affiche le statut global.</span>
|
||||
</div>
|
||||
<pre id="statusOut">{}</pre>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>
|
||||
Rechercher un utilisateur
|
||||
<span id="userPill" class="pill">/api/user/<id></span>
|
||||
</h2>
|
||||
<div class="row">
|
||||
<input id="userId" type="number" placeholder="User ID (ex: 123...)" />
|
||||
<button id="btnUser">Charger</button>
|
||||
</div>
|
||||
|
||||
<div id="userCard" class="list" style="grid-template-columns: 1fr; display:none;">
|
||||
<div class="item">
|
||||
<img id="userAvatar" class="avatar" alt="avatar" />
|
||||
<div class="meta">
|
||||
<div id="userName" class="name">—</div>
|
||||
<div id="userAvatarUrl" class="small">—</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a id="userAvatarLink" class="link" href="#" target="_blank" rel="noreferrer">Ouvrir avatar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pre id="userOut">{}</pre>
|
||||
</section>
|
||||
|
||||
<section class="card span2">
|
||||
<h2>
|
||||
Serveurs du bot
|
||||
<span id="guildsPill" class="pill">/api/guilds</span>
|
||||
</h2>
|
||||
<div class="row">
|
||||
<button id="btnGuilds">Charger la liste</button>
|
||||
<span class="muted">Clique un serveur pour charger ses détails.</span>
|
||||
</div>
|
||||
|
||||
<div id="guildsList" class="list"></div>
|
||||
|
||||
<pre id="guildsOut">{}</pre>
|
||||
</section>
|
||||
|
||||
<section class="card span2">
|
||||
<h2>
|
||||
Détails d’un serveur
|
||||
<span id="guildPill" class="pill">/api/guild/<id></span>
|
||||
</h2>
|
||||
<div class="row">
|
||||
<input id="guildId" type="number" placeholder="Guild ID (ex: 123...)" />
|
||||
<button id="btnGuild">Charger</button>
|
||||
<span id="guildHint" class="muted">Astuce: tu peux aussi cliquer un serveur dans la liste au-dessus.</span>
|
||||
</div>
|
||||
|
||||
<div id="guildCard" class="list" style="grid-template-columns: 1fr; display:none;"></div>
|
||||
|
||||
<pre id="guildOut">{}</pre>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Change ici si ton API est ailleurs (ex: http://localhost:5000)
|
||||
const API_BASE = "http://127.0.0.1:5000";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
function pretty(obj) {
|
||||
return JSON.stringify(obj, null, 2);
|
||||
}
|
||||
|
||||
async function apiGet(path) {
|
||||
const url = API_BASE + path;
|
||||
const res = await fetch(url, { method: "GET" });
|
||||
let data = null;
|
||||
|
||||
try {
|
||||
data = await res.json();
|
||||
} catch (e) {
|
||||
// si la réponse n'est pas JSON
|
||||
const text = await res.text();
|
||||
throw new Error(`Réponse non JSON (${res.status}) depuis ${url}: ${text}`);
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
const msg = (data && (data.error || data.message)) ? (data.error || data.message) : `HTTP ${res.status}`;
|
||||
const err = new Error(msg);
|
||||
err.status = res.status;
|
||||
err.data = data;
|
||||
throw err;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function setPill(el, kind, text) {
|
||||
el.classList.remove("ok", "bad");
|
||||
if (kind === "ok") el.classList.add("ok");
|
||||
if (kind === "bad") el.classList.add("bad");
|
||||
el.textContent = text;
|
||||
}
|
||||
|
||||
// STATUS
|
||||
$("btnStatus").addEventListener("click", async () => {
|
||||
$("btnStatus").disabled = true;
|
||||
setPill($("statusPill"), "", "chargement...");
|
||||
try {
|
||||
const data = await apiGet("/api/status");
|
||||
$("statusOut").textContent = pretty(data);
|
||||
setPill($("statusPill"), data.status === "online" ? "ok" : "", data.status || "ok");
|
||||
} catch (e) {
|
||||
$("statusOut").textContent = pretty({ error: e.message, status: e.status || null, data: e.data || null });
|
||||
setPill($("statusPill"), "bad", "erreur");
|
||||
} finally {
|
||||
$("btnStatus").disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// USER
|
||||
$("btnUser").addEventListener("click", async () => {
|
||||
const id = $("userId").value?.trim();
|
||||
$("userCard").style.display = "none";
|
||||
$("btnUser").disabled = true;
|
||||
setPill($("userPill"), "", "chargement...");
|
||||
try {
|
||||
if (!id) throw new Error("Tu dois renseigner un user_id.");
|
||||
const data = await apiGet(`/api/user/${encodeURIComponent(id)}`);
|
||||
$("userOut").textContent = pretty(data);
|
||||
|
||||
$("userName").textContent = data.username ?? "—";
|
||||
$("userAvatar").src = data.avatar_url ?? "https://cdn.discordapp.com/embed/avatars/0.png";
|
||||
$("userAvatarUrl").textContent = data.avatar_url ?? "—";
|
||||
$("userAvatarLink").href = data.avatar_url ?? "#";
|
||||
|
||||
$("userCard").style.display = "grid";
|
||||
setPill($("userPill"), "ok", "ok");
|
||||
} catch (e) {
|
||||
$("userOut").textContent = pretty({ error: e.message, status: e.status || null, data: e.data || null });
|
||||
setPill($("userPill"), "bad", "erreur");
|
||||
} finally {
|
||||
$("btnUser").disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// GUILDS LIST
|
||||
$("btnGuilds").addEventListener("click", async () => {
|
||||
$("btnGuilds").disabled = true;
|
||||
setPill($("guildsPill"), "", "chargement...");
|
||||
$("guildsList").innerHTML = "";
|
||||
try {
|
||||
const data = await apiGet("/api/guilds");
|
||||
$("guildsOut").textContent = pretty(data);
|
||||
|
||||
for (const g of data) {
|
||||
const div = document.createElement("div");
|
||||
div.className = "item";
|
||||
div.style.cursor = "pointer";
|
||||
div.title = "Cliquer pour charger /api/guild/<id>";
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.className = "icon";
|
||||
img.alt = "icon";
|
||||
img.src = g.icon_url || "https://cdn.discordapp.com/embed/avatars/0.png";
|
||||
|
||||
const meta = document.createElement("div");
|
||||
meta.className = "meta";
|
||||
|
||||
const name = document.createElement("div");
|
||||
name.className = "name";
|
||||
name.textContent = g.name || "Sans nom";
|
||||
|
||||
const small = document.createElement("div");
|
||||
small.className = "small";
|
||||
small.textContent = `ID: ${g.id} | Membres: ${g.member_count ?? "?"}`;
|
||||
|
||||
meta.appendChild(name);
|
||||
meta.appendChild(small);
|
||||
|
||||
const right = document.createElement("div");
|
||||
right.className = "right";
|
||||
const pill = document.createElement("span");
|
||||
pill.className = "pill";
|
||||
pill.textContent = "voir détails";
|
||||
right.appendChild(pill);
|
||||
|
||||
div.appendChild(img);
|
||||
div.appendChild(meta);
|
||||
div.appendChild(right);
|
||||
|
||||
div.addEventListener("click", async () => {
|
||||
$("guildId").value = g.id;
|
||||
await loadGuild(g.id);
|
||||
});
|
||||
|
||||
$("guildsList").appendChild(div);
|
||||
}
|
||||
|
||||
setPill($("guildsPill"), "ok", `${data.length} serveurs`);
|
||||
} catch (e) {
|
||||
$("guildsOut").textContent = pretty({ error: e.message, status: e.status || null, data: e.data || null });
|
||||
setPill($("guildsPill"), "bad", "erreur");
|
||||
} finally {
|
||||
$("btnGuilds").disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
async function loadGuild(id) {
|
||||
$("guildCard").style.display = "none";
|
||||
$("btnGuild").disabled = true;
|
||||
setPill($("guildPill"), "", "chargement...");
|
||||
try {
|
||||
if (!id) throw new Error("Tu dois renseigner un guild_id.");
|
||||
|
||||
const data = await apiGet(`/api/guild/${encodeURIComponent(id)}`);
|
||||
$("guildOut").textContent = pretty(data);
|
||||
|
||||
// carte “propre” comme user
|
||||
const html = `
|
||||
<div class="item">
|
||||
<img class="icon" alt="icon" src="${(data.icon_url || "https://cdn.discordapp.com/embed/avatars/0.png")}" />
|
||||
<div class="meta">
|
||||
<div class="name">${escapeHtml(data.name || "Sans nom")}</div>
|
||||
<div class="small">ID: ${data.id} | Membres: ${data.member_count ?? "?"} | Owner: ${data.owner_id ?? "?"}</div>
|
||||
<div class="small">${data.description ? escapeHtml(data.description) : ""}</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
${data.icon_url ? `<a class="link" href="${data.icon_url}" target="_blank" rel="noreferrer">Ouvrir icône</a>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" style="align-items:flex-start;">
|
||||
<div class="meta" style="width:100%;">
|
||||
<div class="name">Channels (${(data.channels || []).length})</div>
|
||||
<div class="small">${renderChannels(data.channels || [])}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" style="align-items:flex-start;">
|
||||
<div class="meta" style="width:100%;">
|
||||
<div class="name">Roles (${(data.roles || []).length})</div>
|
||||
<div class="small">${renderRoles(data.roles || [])}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$("guildCard").innerHTML = html;
|
||||
$("guildCard").style.display = "grid";
|
||||
setPill($("guildPill"), "ok", "ok");
|
||||
} catch (e) {
|
||||
$("guildOut").textContent = pretty({ error: e.message, status: e.status || null, data: e.data || null });
|
||||
setPill($("guildPill"), "bad", "erreur");
|
||||
} finally {
|
||||
$("btnGuild").disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
$("btnGuild").addEventListener("click", async () => {
|
||||
const id = $("guildId").value?.trim();
|
||||
await loadGuild(id);
|
||||
});
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s)
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
function renderChannels(channels) {
|
||||
if (!channels.length) return "Aucun channel (ou non accessible).";
|
||||
// limiter pour pas exploser l’UI
|
||||
const shown = channels.slice(0, 40);
|
||||
const more = channels.length > shown.length ? ` (+${channels.length - shown.length})` : "";
|
||||
return shown
|
||||
.map(c => {
|
||||
const cat = c.category ? ` / ${escapeHtml(c.category)}` : "";
|
||||
return `#${escapeHtml(c.name)} (${escapeHtml(c.type)})${cat}`;
|
||||
})
|
||||
.join("<br/>") + more;
|
||||
}
|
||||
|
||||
function renderRoles(roles) {
|
||||
if (!roles.length) return "Aucun rôle (ou non accessible).";
|
||||
// trier par position desc si pas déjà
|
||||
const sorted = [...roles].sort((a,b) => (b.position ?? 0) - (a.position ?? 0));
|
||||
const shown = sorted.slice(0, 40);
|
||||
const more = sorted.length > shown.length ? ` (+${sorted.length - shown.length})` : "";
|
||||
return shown
|
||||
.map(r => `${escapeHtml(r.name)} — membres: ${r.member_count ?? "?"} (pos ${r.position ?? "?"})`)
|
||||
.join("<br/>") + more;
|
||||
}
|
||||
|
||||
// Auto-load status au démarrage (optionnel)
|
||||
$("btnStatus").click();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue