intoai / static /admin.html
hello-aditya's picture
Update static/admin.html
b960f80 verified
Raw
History Blame Contribute Delete
15.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Admin_Control | Duochrome</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
:root { --bg-color: #1A1A1D; --text-color: #f3f2ec; --shadow-color: #f3f2ec; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: "Roboto Mono", monospace; background: var(--bg-color); color: var(--text-color); padding: 40px 20px; }
.container { max-width: 800px; margin: 0 auto; }
.box { border: 1px solid var(--text-color); padding: 40px; box-shadow: 8px 8px 0 var(--shadow-color); margin-top: 10vh; }
label { display: block; margin-top: 20px; font-weight: 700; font-size: 0.8rem; opacity: 0.7; }
input, textarea, select { width: 100%; background: transparent; border: 1px solid var(--text-color); color: var(--text-color); padding: 12px; margin-top: 8px; font-family: inherit; outline: none; }
select option { color: #000; }
.btn { padding: 12px 24px; font-weight: 700; cursor: pointer; border: 1px solid var(--text-color); text-transform: uppercase; font-family: inherit; margin-top: 20px; }
.btn-main { background: var(--text-color); color: var(--bg-color); }
.btn-alt { background: transparent; color: var(--text-color); border-color: var(--text-color); }
.tabs { display: flex; border: 1px solid var(--text-color); margin: 30px 0; overflow-x: auto;}
.tab { flex: 1; padding: 15px; background: none; color: var(--text-color); border: none; border-right: 1px solid var(--text-color); cursor: pointer; text-transform: uppercase; font-weight: 700; white-space: nowrap;}
.tab:last-child { border-right: none; }
.tab.active { background: var(--text-color); color: var(--bg-color); }
</style>
</head>
<body>
<div class="container">
<div id="loginSection" class="box">
<h2 style="margin-bottom: 20px;">SYSTEM_AUTH</h2>
<input type="password" id="adminPass" placeholder="ENTER_SECRET_KEY">
<button class="btn btn-main" onclick="attemptLogin()" style="width: 100%;">AUTHORIZE</button>
</div>
<div id="adminPanel" style="display: none;">
<header style="display: flex; justify-content: space-between; border-bottom: 1px solid; padding-bottom: 20px;">
<h1>ADMIN_PANEL</h1>
<button class="btn" onclick="sessionStorage.clear(); location.reload()">EXIT</button>
</header>
<div class="tabs">
<button class="tab active" id="btn-post" onclick="switchTab('post')">EDITOR</button>
<button class="tab" id="btn-manage" onclick="switchTab('manage')">MANAGE_DATA</button>
<button class="tab" id="btn-link" onclick="switchTab('link')">ADD_LINK</button>
<button class="tab" id="btn-manage-links" onclick="switchTab('manage-links')">MANAGE_LINKS</button>
</div>
<div id="tab-post">
<label>TITLE_HEADER</label>
<input type="text" id="postTitle" placeholder="Post Title" oninput="autoSlug(this.value)">
<label>URL_SLUG (Auto-Generated)</label>
<input type="text" id="postSlug" placeholder="post-title">
<label>EXCERPT (Preview text)</label>
<input type="text" id="postExcerpt" placeholder="A brief summary of this log...">
<label>TAGS (Comma-separated)</label>
<input type="text" id="postTags" placeholder="e.g., asr, whisper, python, fastapi">
<label>MARKDOWN_CONTENT</label>
<textarea id="postContent" style="height: 300px;" placeholder="# Hello World..."></textarea>
<div style="display: flex; gap: 10px;">
<button class="btn btn-main" id="submitBtn" onclick="submitArticle()">EXECUTE_UPLOAD</button>
<button class="btn btn-alt" id="cancelBtn" onclick="cancelEdit()" style="display: none;">CANCEL_EDIT</button>
</div>
</div>
<div id="tab-manage" style="display: none;">
<div id="articleList"></div>
</div>
<div id="tab-link" style="display: none;">
<label>RESOURCE_TITLE</label>
<input type="text" id="linkTitle" placeholder="e.g., Evaluating Whisper Medium on FLEURS">
<label>TARGET_URL</label>
<input type="url" id="linkUrl" placeholder="https://...">
<label>CATEGORY</label>
<select id="linkCategory">
<option value="Research Paper">Research Paper</option>
<option value="GitHub Repo">GitHub Repo</option>
<option value="Dataset">Dataset</option>
<option value="Article/Tutorial">Article/Tutorial</option>
</select>
<label>DESCRIPTION (Optional)</label>
<input type="text" id="linkDesc" placeholder="Brief note on why this is saved...">
<div style="display: flex; gap: 10px;">
<button class="btn btn-main" id="submitLinkBtn" onclick="submitLink()">SAVE_LINK</button>
<button class="btn btn-alt" id="cancelLinkBtn" onclick="cancelLinkEdit()" style="display: none;">CANCEL_EDIT</button>
</div>
</div>
<div id="tab-manage-links" style="display: none;">
<div id="linkList"></div>
</div>
</div>
</div>
<script>
const API = '/api';
let globalArticles = [];
let globalLinks = [];
let currentEditId = null;
let currentEditLinkId = null;
function autoSlug(val) {
if (currentEditId) return;
document.getElementById('postSlug').value = val.toLowerCase().trim()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
async function attemptLogin() {
const pass = document.getElementById('adminPass').value;
sessionStorage.setItem('key', pass);
const res = await fetch(`${API}/auth/verify`, {
headers: { 'X-Admin-Password': pass }
});
if (res.ok) {
document.getElementById('loginSection').style.display = 'none';
document.getElementById('adminPanel').style.display = 'block';
loadArticles();
loadLinks();
} else {
alert('ACCESS_DENIED: Invalid Key');
sessionStorage.clear();
}
}
// --- ARTICLE LOGIC ---
async function submitArticle() {
const data = {
title: document.getElementById('postTitle').value,
slug: document.getElementById('postSlug').value,
excerpt: document.getElementById('postExcerpt').value,
content: document.getElementById('postContent').value,
tags: document.getElementById('postTags').value.split(',').map(t => t.trim()).filter(Boolean)
};
const url = currentEditId ? `${API}/articles/${currentEditId}` : `${API}/articles`;
const method = currentEditId ? 'PUT' : 'POST';
const res = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'X-Admin-Password': sessionStorage.getItem('key')
},
body: JSON.stringify(data)
});
if (res.ok) {
alert(currentEditId ? '✓ UPDATE_SUCCESS' : '✓ UPLOAD_SUCCESS');
cancelEdit();
loadArticles();
switchTab('manage');
} else { alert('✗ OPERATION_FAILED'); }
}
async function loadArticles() {
const res = await fetch(`${API}/articles?published_only=false`);
globalArticles = await res.json();
document.getElementById('articleList').innerHTML = globalArticles.map(a => `
<div style="border: 1px solid; padding: 20px; margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center;">
<span style="text-transform: uppercase; font-weight: 700;">${a.title}</span>
<div>
<button class="btn btn-main" style="margin: 0; padding: 5px 15px; font-size: 0.7rem; margin-right: 10px;" onclick="editArt(${a.id})">EDIT</button>
<button class="btn" style="margin: 0; padding: 5px 15px; font-size: 0.7rem;" onclick="deleteArt(${a.id})">PURGE</button>
</div>
</div>
`).join('');
}
function editArt(id) {
const article = globalArticles.find(a => a.id === id);
if (!article) return;
currentEditId = id;
document.getElementById('postTitle').value = article.title;
document.getElementById('postSlug').value = article.slug;
document.getElementById('postExcerpt').value = article.excerpt || '';
document.getElementById('postTags').value = (article.tags || []).join(', ');
document.getElementById('postContent').value = article.content;
document.getElementById('submitBtn').innerText = "UPDATE_DATA";
document.getElementById('cancelBtn').style.display = "block";
switchTab('post');
}
function cancelEdit() {
currentEditId = null;
document.getElementById('postTitle').value = '';
document.getElementById('postSlug').value = '';
document.getElementById('postExcerpt').value = '';
document.getElementById('postTags').value = '';
document.getElementById('postContent').value = '';
document.getElementById('submitBtn').innerText = "EXECUTE_UPLOAD";
document.getElementById('cancelBtn').style.display = "none";
}
async function deleteArt(id) {
if (!confirm('CONFIRM_DATA_PURGE?')) return;
await fetch(`${API}/articles/${id}`, {
method: 'DELETE',
headers: { 'X-Admin-Password': sessionStorage.getItem('key') }
});
loadArticles();
}
// --- LINK LOGIC ---
async function submitLink() {
const data = {
title: document.getElementById('linkTitle').value,
url: document.getElementById('linkUrl').value,
category: document.getElementById('linkCategory').value,
description: document.getElementById('linkDesc').value,
tags: []
};
const url = currentEditLinkId ? `${API}/links/${currentEditLinkId}` : `${API}/links`;
const method = currentEditLinkId ? 'PUT' : 'POST';
const res = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'X-Admin-Password': sessionStorage.getItem('key')
},
body: JSON.stringify(data)
});
if (res.ok) {
alert(currentEditLinkId ? '✓ LINK_UPDATED' : '✓ LINK_SAVED');
cancelLinkEdit();
loadLinks();
switchTab('manage-links');
} else { alert('✗ OPERATION_FAILED'); }
}
async function loadLinks() {
const res = await fetch(`${API}/links`);
globalLinks = await res.json();
document.getElementById('linkList').innerHTML = globalLinks.map(l => `
<div style="border: 1px solid; padding: 20px; margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center;">
<div>
<span style="text-transform: uppercase; font-weight: 700; display: block;">${l.title}</span>
<span style="font-size: 0.8rem; opacity: 0.7;">[${l.category}] ${l.url}</span>
</div>
<div>
<button class="btn btn-main" style="margin: 0; padding: 5px 15px; font-size: 0.7rem; margin-right: 10px;" onclick="editLink(${l.id})">EDIT</button>
<button class="btn" style="margin: 0; padding: 5px 15px; font-size: 0.7rem;" onclick="deleteLink(${l.id})">PURGE</button>
</div>
</div>
`).join('');
}
function editLink(id) {
const link = globalLinks.find(l => l.id === id);
if (!link) return;
currentEditLinkId = id;
document.getElementById('linkTitle').value = link.title;
document.getElementById('linkUrl').value = link.url;
document.getElementById('linkCategory').value = link.category;
document.getElementById('linkDesc').value = link.description || '';
document.getElementById('submitLinkBtn').innerText = "UPDATE_LINK";
document.getElementById('cancelLinkBtn').style.display = "block";
switchTab('link');
}
function cancelLinkEdit() {
currentEditLinkId = null;
document.getElementById('linkTitle').value = '';
document.getElementById('linkUrl').value = '';
document.getElementById('linkCategory').value = 'Research Paper';
document.getElementById('linkDesc').value = '';
document.getElementById('submitLinkBtn').innerText = "SAVE_LINK";
document.getElementById('cancelLinkBtn').style.display = "none";
}
async function deleteLink(id) {
if (!confirm('CONFIRM_LINK_PURGE?')) return;
await fetch(`${API}/links/${id}`, {
method: 'DELETE',
headers: { 'X-Admin-Password': sessionStorage.getItem('key') }
});
loadLinks();
}
// --- TAB NAVIGATION ---
function switchTab(t) {
document.getElementById('tab-post').style.display = t === 'post' ? 'block' : 'none';
document.getElementById('tab-manage').style.display = t === 'manage' ? 'block' : 'none';
document.getElementById('tab-link').style.display = t === 'link' ? 'block' : 'none';
document.getElementById('tab-manage-links').style.display = t === 'manage-links' ? 'block' : 'none';
document.getElementById('btn-post').classList.toggle('active', t === 'post');
document.getElementById('btn-manage').classList.toggle('active', t === 'manage');
document.getElementById('btn-link').classList.toggle('active', t === 'link');
document.getElementById('btn-manage-links').classList.toggle('active', t === 'manage-links');
}
if(sessionStorage.getItem('key')) {
document.getElementById('adminPass').value = sessionStorage.getItem('key');
attemptLogin();
}
</script>
</body>
</html>