ddns-core 0.1.0

Runtime core for ddns-rs: scheduler, HTTP API and shared state.
Documentation
<!doctype html>
<meta charset="utf-8">
<title>DDNS Login</title>
<script src="https://cdn.tailwindcss.com"></script>

<body class="min-h-screen flex items-center justify-center bg-gradient-to-br

             from-sky-100 to-indigo-100 dark:from-gray-800 dark:to-gray-900

             text-gray-800 dark:text-gray-100 antialiased">

<form id="f"

      class="w-full max-w-sm space-y-5 bg-white/80 dark:bg-gray-800/80

               backdrop-blur-xl p-8 rounded-2xl shadow-xl border

               border-gray-200 dark:border-gray-700">
    <h1 class="text-2xl font-bold text-center flex items-center justify-center gap-2">
        <svg class="w-6 h-6 text-sky-600" fill="none" stroke="currentColor" stroke-width="2"

             viewBox="0 0 24 24"><path d="M12 2v20M2 12h20"/></svg>
        DDNS Login
    </h1>

    <div class="space-y-1">
        <label class="text-sm font-medium">Username</label>
        <input name="username" required

               class="input w-full" placeholder="admin">
    </div>

    <div class="space-y-1">
        <label class="text-sm font-medium">Password</label>
        <input name="password" type="password" required

               class="input w-full" placeholder="•••••••">
    </div>

    <button

            class="w-full py-2 rounded-lg bg-sky-600 text-white font-medium hover:bg-sky-700

               transition">Sign in</button>

    <p id="err" class="text-center text-rose-500 text-sm hidden">Login failed</p>
</form>

<style>
    .input{ @apply border border-gray-300 dark:border-gray-600/70 rounded-lg
    px-3 py-2 bg-white/70 dark:bg-gray-700/40 outline-none
    focus:ring-2 focus:ring-sky-500/50 transition;}
</style>

<script>
    document.getElementById('f').addEventListener('submit', async e => {
        e.preventDefault();
        const fd = Object.fromEntries(new FormData(e.target));
        const r  = await fetch('/api/login', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(fd)
        });
        if (r.ok) {
            location.replace('/');
        } else {
            document.getElementById('err').classList.remove('hidden');
            setTimeout(()=>document.getElementById('err').classList.add('hidden'), 3000);
        }
    });
</script>
</body>