Files
weircon-front/templates/admin_claude_login.html
T
agw 6f21c07d91 feat(admin): log Claude ind igen fra telefonen
Claude Codes login på værten holder 28 dage, og et langtidstoken kan ikke
bruges til Remote Control, så der skal logges ind igen hver 4. uge. Hidtil
krævede det en terminal for at kopiere en adresse ud og en kode ind igen.

/admin/claude-login viser hvornår login udløber og har en "Start login"-knap.
Værten (claude-login-agent, som spørger portalen, ikke omvendt) starter
`claude auth login`, og siden viser login-adressen; koden fra Claudes side
indsættes på samme side og hentes af værten et par sekunder senere. Portalen
gemmer kun den korte kode, indtil den er hentet; den virker kun sammen med
login-processen på værten (PKCE).

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-03 08:32:29 +02:00

115 lines
4.7 KiB
HTML

{% extends "base.html" %}
{% block title %}Admin — Claude-login{% endblock %}
{% block body %}
<p><a href="/admin" class="wt-muted">← Admin</a></p>
<h1>Claude-login</h1>
<p class="wt-lead">Claude på serveren skal logges ind igen hver 4. uge. Det kan gøres herfra: start et login, log ind hos Claude i en ny fane, og indsæt koden du får her. Du får en påmindelse på ntfy tre dage før.</p>
<section class="wt-card" id="cl" style="margin:1rem 0;padding:1rem 1.2rem;max-width:40rem">
<p id="cl-status" style="margin:0 0 .6rem;font-weight:600">…</p>
<p id="cl-besked" class="wt-flash" hidden></p>
<div id="cl-start" hidden>
<button class="wt-btn wt-btn--primary" type="button" id="cl-start-btn">Start login</button>
</div>
<div id="cl-vent" hidden>
<p class="wt-muted" style="margin:.2rem 0">Serveren starter login-processen…</p>
</div>
<ol id="cl-trin" hidden style="padding-left:1.2rem;margin:.4rem 0">
<li style="margin:.4rem 0">
<a class="wt-btn wt-btn--primary" id="cl-url" href="#" target="_blank" rel="noopener noreferrer">Log ind hos Claude</a>
<div class="wt-muted" style="font-size:.9em;margin-top:.3rem">Åbner i en ny fane. Til sidst viser Claude en kode; kopiér den.</div>
</li>
<li style="margin:.8rem 0 .4rem">
<form id="cl-form" autocomplete="off" style="margin:0">
<label style="display:block">Indsæt koden
<input class="wt-input" name="kode" id="cl-kode" required autocomplete="one-time-code" autocapitalize="off"
autocorrect="off" spellcheck="false" style="font-family:ui-monospace,monospace" placeholder="kode#…"></label>
<button class="wt-btn wt-btn--primary" type="submit" style="margin-top:.5rem">Send til serveren</button>
</form>
</li>
</ol>
<p id="cl-tjek" class="wt-muted" hidden>Serveren logger ind…</p>
</section>
<script>
(function () {
var TEKST = {
ok: "✅ Logget ind",
snart: "⏳ Login udløber snart",
udloebet: "⚠️ Claude er logget ud",
ukendt: "Status ukendt",
starter: "Starter login…",
venter: "Venter på din kode",
tjekker: "Logger ind…"
};
var $ = function (id) { return document.getElementById(id); };
var timer = null;
function dato(t) {
var d = new Date(t * 1000);
return d.toLocaleString("da-DK", { day: "numeric", month: "numeric", hour: "2-digit", minute: "2-digit" });
}
function vis(st) {
var s = st.status || "ukendt";
var linje = TEKST[s] || s;
if (st.expires && (s === "ok" || s === "snart")) {
var dage = Math.floor((st.expires * 1000 - Date.now()) / 86400000);
linje += " · udløber " + dato(st.expires) + " (om " + dage + " " + (dage === 1 ? "dag" : "dage") + ")";
} else if (st.expires && s === "udloebet") {
linje += " siden " + dato(st.expires);
}
$("cl-status").textContent = linje;
var besked = st.error || st.message;
if (st.stale) besked = "Agenten på serveren svarer ikke (claude-login-agent). Så sker der ikke noget, når du trykker.";
$("cl-besked").hidden = !besked;
$("cl-besked").textContent = besked || "";
$("cl-start").hidden = !(s === "ok" || s === "snart" || s === "udloebet" || s === "ukendt");
$("cl-start-btn").textContent = s === "ok" ? "Forny login nu" : "Start login";
$("cl-vent").hidden = s !== "starter";
$("cl-trin").hidden = s !== "venter";
$("cl-tjek").hidden = s !== "tjekker";
if (s === "venter" && st.url && $("cl-url").getAttribute("href") !== st.url) {
$("cl-url").setAttribute("href", st.url);
}
planlaeg(s === "starter" || s === "venter" || s === "tjekker" ? 2000 : 30000);
}
function planlaeg(ms) {
clearTimeout(timer);
timer = setTimeout(hent, ms);
}
function hent() {
fetch("/admin/claude-login/status", { credentials: "same-origin", cache: "no-store" })
.then(function (r) { return r.json(); }).then(vis)
.catch(function () { planlaeg(5000); });
}
$("cl-start-btn").addEventListener("click", function () {
$("cl-start-btn").disabled = true;
fetch("/admin/claude-login/start", { method: "POST", credentials: "same-origin" })
.then(function (r) { return r.json(); }).then(vis)
.finally(function () { $("cl-start-btn").disabled = false; });
});
$("cl-form").addEventListener("submit", function (e) {
e.preventDefault();
var data = new FormData($("cl-form"));
fetch("/admin/claude-login/kode", { method: "POST", credentials: "same-origin", body: data })
.then(function (r) { return r.json(); })
.then(function (st) { if (!st.error) $("cl-kode").value = ""; vis(st); })
.catch(function () { planlaeg(2000); });
});
document.addEventListener("visibilitychange", function () { if (!document.hidden) hent(); });
vis({{ st | tojson }});
})();
</script>
{% endblock %}