mirror of
https://github.com/4ev-link/4ev.link.git
synced 2026-01-13 16:18:05 +00:00
119 lines
7.3 KiB
HTML
119 lines
7.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="robots" content="noindex, nofollow">
|
|
<title>Admin - 4ev.link</title>
|
|
<link rel="icon" href="/public/icon.png">
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="//unpkg.com/alpinejs" defer></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
</head>
|
|
<body class="bg-slate-900 text-slate-200 min-h-screen flex items-center justify-center font-sans text-sm">
|
|
<div x-data="adminPage()" class="max-w-xl w-full p-6 text-center">
|
|
<h1 class="text-xl font-bold mb-4">Admin Panel</h1>
|
|
|
|
<template x-if="!authenticated">
|
|
<form @submit.prevent="authenticated = !!password" class="space-y-3 max-w-xs mx-auto">
|
|
<div>
|
|
<label for="adminPass" class="block text-xs font-medium text-slate-400 mb-1">Enter Admin Password</label>
|
|
<input x-model="password" type="password" id="adminPass" required class="w-full p-2 bg-slate-800 border border-slate-700 rounded focus:outline-none focus:ring-1 focus:ring-sky-500 text-xs">
|
|
</div>
|
|
<button type="submit" class="w-full py-2 font-semibold rounded text-white bg-sky-600 hover:bg-sky-700 transition-colors text-xs">
|
|
Unlock
|
|
</button>
|
|
</form>
|
|
</template>
|
|
|
|
<template x-if="authenticated">
|
|
<div class="space-y-6">
|
|
<div class="flex flex-wrap justify-center gap-2">
|
|
<button @click="runAction('create-users-v1')" :disabled="loading" class="px-3 py-2 font-semibold rounded bg-indigo-600 hover:bg-indigo-700 transition-colors disabled:opacity-50 text-xs">
|
|
Init Users v1
|
|
</button>
|
|
<button @click="runAction('migrate-users-v2')" :disabled="loading" class="px-3 py-2 font-semibold rounded bg-violet-600 hover:bg-violet-700 transition-colors disabled:opacity-50 text-xs">
|
|
Migrate Users v2
|
|
</button>
|
|
<button @click="runAction('create-analytics-v1')" :disabled="loading" class="px-3 py-2 font-semibold rounded bg-emerald-600 hover:bg-emerald-700 transition-colors disabled:opacity-50 text-xs">
|
|
Init Analytics v1
|
|
</button>
|
|
<button @click="runAction('read-schema')" :disabled="loading" class="px-3 py-2 font-semibold rounded bg-slate-700 hover:bg-slate-600 transition-colors disabled:opacity-50 text-xs">
|
|
Read Schema
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4 border-t border-slate-700 pt-6">
|
|
<div>
|
|
<h2 class="text-xs font-bold mb-2 text-rose-400 uppercase tracking-wider">Takedown Link</h2>
|
|
<form @submit.prevent="seizeLink" class="flex flex-col gap-2">
|
|
<input x-model="takedownSlug" type="text" placeholder="Slug" required class="p-2 bg-slate-800 border border-slate-700 rounded focus:outline-none focus:ring-1 focus:ring-rose-500 text-xs">
|
|
<button type="submit" :disabled="loading" class="py-2 font-semibold rounded bg-rose-600 hover:bg-rose-700 transition-colors disabled:opacity-50 text-xs">
|
|
Seize
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-xs font-bold mb-2 text-orange-400 uppercase tracking-wider">Ban User</h2>
|
|
<form @submit.prevent="banUser" class="flex flex-col gap-2">
|
|
<input x-model="banUsername" type="text" placeholder="Username" required class="p-2 bg-slate-800 border border-slate-700 rounded focus:outline-none focus:ring-1 focus:ring-orange-500 text-xs">
|
|
<input x-model="banDays" type="number" placeholder="Days" required class="p-2 bg-slate-800 border border-slate-700 rounded focus:outline-none focus:ring-1 focus:ring-orange-500 text-xs">
|
|
<button type="submit" :disabled="loading" class="py-2 font-semibold rounded bg-orange-600 hover:bg-orange-700 transition-colors disabled:opacity-50 text-xs">
|
|
Ban
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="min-h-[8rem] bg-slate-800 rounded p-3 border border-slate-700 text-left text-xs font-mono">
|
|
<div x-show="loading" class="flex justify-center items-center h-full">
|
|
<i data-lucide="loader-2" class="animate-spin w-6 h-6 text-slate-400"></i>
|
|
</div>
|
|
<p x-show="error" class="text-rose-400" x-text="error"></p>
|
|
<pre x-show="output" class="text-slate-300 whitespace-pre-wrap break-all" x-text="output"></pre>
|
|
<p x-show="!loading && !error && !output" class="text-slate-500 text-center pt-8">Output...</p>
|
|
</div>
|
|
<a href="/" class="mt-4 inline-block text-sky-500 hover:underline text-xs">← Back to home</a>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<script>
|
|
function adminPage(){
|
|
const p = new URLSearchParams(window.location.search);
|
|
return {
|
|
password:"", authenticated:!1, loading:!1, error:"", output:"",
|
|
takedownSlug: p.get("slug")||"",
|
|
banUsername: p.get("user")||"",
|
|
banDays: "",
|
|
async runAction(t){
|
|
this.loading=!0,this.error="",this.output="";
|
|
try{
|
|
const s=await fetch(`/api/admin/${t}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({admin_pass:this.password})});
|
|
if(!s.ok)throw new Error(await s.text()||`Failed to ${t}`);
|
|
this.output=await s.text()
|
|
}catch(s){this.error=s.message}finally{this.loading=!1,this.$nextTick(()=>lucide.createIcons())}
|
|
},
|
|
async seizeLink(){
|
|
this.loading=!0,this.error="",this.output="";
|
|
try{
|
|
const s=await fetch("/api/admin/takedown",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({admin_pass:this.password,slug:this.takedownSlug})});
|
|
if(!s.ok)throw new Error(await s.text()||"Failed");
|
|
this.output=await s.text(),this.takedownSlug=""
|
|
}catch(s){this.error=s.message}finally{this.loading=!1}
|
|
},
|
|
async banUser(){
|
|
this.loading=!0,this.error="",this.output="";
|
|
try{
|
|
const s=await fetch("/api/admin/ban-user",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({admin_pass:this.password,username:this.banUsername,days:this.banDays})});
|
|
if(!s.ok)throw new Error(await s.text()||"Failed");
|
|
this.output=await s.text(),this.banUsername="",this.banDays=""
|
|
}catch(s){this.error=s.message}finally{this.loading=!1}
|
|
}
|
|
}
|
|
}
|
|
lucide.createIcons();
|
|
</script>
|
|
</body>
|
|
</html>
|