nim/admin/login/index.html
2025-06-17 00:22:01 +08:00

125 lines
3.9 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NIMING Login</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://unpkg.com/htmx.org@2.0.4"
integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"
crossorigin="anonymous"></script>
</head>
<body class="grid place-center w-dvw h-dvh bg-zinc-900">
<script>
const totp_length = 6;
document.addEventListener("DOMContentLoaded", function () {
let totp_cell = document.getElementById("totp_cell");
let totp_container = document.getElementById("totp");
for (let i = 0; i < totp_length; i++) {
totp_cell.content.firstElementChild.dataset.indexNumber = i
totp_cell.content.firstElementChild.name = `totp-${i}`
let node = document.importNode(totp_cell.content, true)
totp_container.appendChild(node);
}
const keyup_ev = (ev) => {
if (ev.keyCode <= 0x39 && ev.keyCode >= 0x30) {
ev.target.value = ev.key
if (ev.target.nextElementSibling) {
ev.target.nextElementSibling.focus()
}
}
if (ev.key = "a" && ev.ctrlKey) {
console.log("Select all")
for (let i = 1; i <= totp_length; i++) {
totp_container.children[i].select()
}
}
if (ev.key == "Backspace" && ev.target.dataset.indexNumber > 0) {
const previous = totp_container.querySelector(`[data-index-number='${ev.target.dataset.indexNumber - 1}']`)
previous.focus()
}
}
const paste_input_ev = (ev) => {
if (ev.inputType == "insertFromPaste") {
let data = ev.data
console.log(data)
for (let i = 1; i <= totp_length; i++) {
totp_container.children[i].value = data.slice(0, 1)
data = data.slice(1, data.length)
}
}
}
for (const cell of totp_container.children) {
if (cell instanceof HTMLInputElement) {
cell.addEventListener("keyup", keyup_ev)
cell.addEventListener("input", paste_input_ev)
}
}
const form_onsubmit = (ev) => {
let form = new FormData(ev.target)
let miss = form.entries().find(p => p[1] == "")
if (miss) {
alert(`Missing ${miss[0]}`)
ev.preventDefault()
}
console.log(form)
}
const form_formdata = (ev) => {
const data = ev.formData
let totp = new Array(totp_length)
for (const [k, v] of data.entries()) {
if (k.match(/totp-\d/g)) {
totp[parseInt(k.split("-")[1])] = v
}
}
for (let i = 0; i < totp_length; i++) {
data.delete(`totp-${i}`)
}
data.set("totp_code", totp.join(""))
}
const form = document.getElementById("login_form")
form.addEventListener("submit", form_onsubmit)
form.addEventListener("formdata", form_formdata)
});
</script>
<form id="login_form"
class="m-auto h-fit w-10/12 md:w-5/12 lg:w-1/4 bg-zinc-400 drop-shadow-lg flex flex-col px-10 box-border py-5 gap-10 rounded-xl"
action="/admin/login" method="post">
<h1 class="text-3xl text-center">中工匿名管理</h1>
<div class="flex flex-col gap-2">
<label for="username">名稱</label>
<input name="name" id="username" type="text"
class="block rounded outline-none ring-3 border-box px-3" />
</div>
<div class="flex flex-col gap-2">
<label for="password">密碼</label>
<input name="password" id="password" type="password"
class="block rounded outline-none ring-3 border-box px-3" />
</div>
<div class="flex flex-col gap-2">
<label for="totp">TOTP</label>
<div class="flex flex-row gap-3 w-full px-auto justify-center box-border" id="totp">
<template id="totp_cell">
<input type="text"
class="block rounded outline-none ring-2 border-box w-7 h-7 text-center totp"
data-index-number="0" maxlength="1" minlength="1" />
</template>
</div>
</div>
<button type="submit"
class="w-fit px-5 py-1 mx-auto bg-sky-500 rounded-lg drop-shadow-cyan-500/50 drop-shadow-lg">確認</button>
</form>
</body>
</html>