/* =================================================== JAESWIFT — Admin Panel JS =================================================== */ const AdminApp = (() => { const API = window.location.hostname === 'localhost' ? 'http://localhost:5000' : '/api'; let token = localStorage.getItem('jaeswift_token') || ''; // ─── Auth ─── function headers() { return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }; } async function login(e) { e.preventDefault(); const user = document.getElementById('loginUser').value; const pass = document.getElementById('loginPass').value; const errEl = document.getElementById('loginError'); errEl.textContent = ''; try { const r = await fetch(API + '/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: user, password: pass }) }); const d = await r.json(); if (d.token) { token = d.token; localStorage.setItem('jaeswift_token', token); showApp(); } else { errEl.textContent = 'ACCESS DENIED: ' + (d.error || 'Invalid credentials'); } } catch { errEl.textContent = 'CONNECTION FAILED'; } } function logout() { token = ''; localStorage.removeItem('jaeswift_token'); document.getElementById('adminApp').style.display = 'none'; document.getElementById('loginScreen').style.display = ''; } async function checkAuth() { if (!token) return false; try { const r = await fetch(API + '/auth/check', { headers: headers() }); const d = await r.json(); return d.valid === true; } catch { return false; } } async function showApp() { document.getElementById('loginScreen').style.display = 'none'; document.getElementById('adminApp').style.display = ''; loadDashboard(); } // ─── Section Switching ─── function showSection(name) { document.querySelectorAll('.admin-section').forEach(s => s.style.display = 'none'); const target = document.getElementById('section-' + name); if (target) target.style.display = ''; document.querySelectorAll('.sidebar-link').forEach(l => l.classList.remove('active')); const link = document.querySelector('.sidebar-link[data-section="' + name + '"]'); if (link) link.classList.add('active'); const titles = { dashboard: 'DASHBOARD', posts: 'POSTS', editor: 'EDITOR', tracks: 'TRACKS', settings: 'SETTINGS' }; document.getElementById('topbarSection').textContent = titles[name] || name.toUpperCase(); if (name === 'dashboard') loadDashboard(); if (name === 'posts') loadPosts(); if (name === 'tracks') loadTracks(); if (name === 'settings') loadSettings(); if (name === 'editor') { if (!document.getElementById('editSlugOriginal').value) { document.getElementById('editorHeading').textContent = 'NEW TRANSMISSION'; } } } // ─── Dashboard ─── async function loadDashboard() { try { const [posts, tracks, stats, services, threats] = await Promise.all([ fetch(API + '/posts').then(r => r.json()), fetch(API + '/nowplaying').then(r => r.json()).catch(() => null), fetch(API + '/stats').then(r => r.json()), fetch(API + '/services').then(r => r.json()).catch(() => []), fetch(API + '/threats').then(r => r.json()).catch(() => []), ]); // Fetch tracks count let trackCount = '--'; try { const tr = await fetch(API + '/tracks'); if (tr.ok) { trackCount = (await tr.json()).length; } } catch { // tracks endpoint might not exist, try loading from data trackCount = '~35'; } document.getElementById('dashPosts').textContent = posts.length; document.getElementById('dashWords').textContent = posts.reduce((sum, p) => sum + (p.word_count || 0), 0).toLocaleString(); document.getElementById('dashTracks').textContent = trackCount; document.getElementById('dashCPU').textContent = Math.round(stats.cpu_percent) + '%'; document.getElementById('dashMem').textContent = Math.round(stats.memory_percent) + '%'; document.getElementById('dashDisk').textContent = stats.disk_percent + '%'; // Services const svcEl = document.getElementById('dashServices'); svcEl.innerHTML = services.map(s => `
${s.status === 'online' ? '●' : '○'} ${s.name} ${s.response_time_ms}ms
`).join(''); // Threats const threatEl = document.getElementById('dashThreats'); threatEl.innerHTML = threats.length ? threats.map(t => `
${t.id} ${t.summary} ${t.cvss || '?'}
`).join('') : 'No threats detected'; } catch (e) { console.error('Dashboard load error:', e); } } // ─── Posts ─── async function loadPosts() { const posts = await fetch(API + '/posts').then(r => r.json()); posts.sort((a, b) => new Date(b.date) - new Date(a.date)); const tbody = document.getElementById('postsTableBody'); tbody.innerHTML = posts.map(p => ` ${p.date || '--'} ${p.title} ${(p.tags || []).map(t => '' + t + '').join(' ')} ${p.word_count || 0} ${(p.threat_level || 'low').toUpperCase()} `).join(''); } async function editPost(slug) { const post = await fetch(API + '/posts/' + slug).then(r => r.json()); document.getElementById('editSlugOriginal').value = slug; document.getElementById('edTitle').value = post.title || ''; document.getElementById('edSlug').value = post.slug || ''; document.getElementById('edDate').value = post.date || ''; document.getElementById('edTime').value = post.time || '00:00'; document.getElementById('edTags').value = (post.tags || []).join(', '); document.getElementById('edExcerpt').value = post.excerpt || ''; document.getElementById('edContent').value = post.content || ''; document.getElementById('edMood').value = post.mood || 'focused'; document.getElementById('edEnergy').value = post.energy || 3; document.getElementById('edEnergyVal').textContent = post.energy || 3; document.getElementById('edMotivation').value = post.motivation || 3; document.getElementById('edMotivationVal').textContent = post.motivation || 3; document.getElementById('edFocus').value = post.focus || 3; document.getElementById('edFocusVal').textContent = post.focus || 3; document.getElementById('edDifficulty').value = post.difficulty || 3; document.getElementById('edDifficultyVal').textContent = post.difficulty || 3; document.getElementById('edCoffee').value = post.coffee || 2; document.getElementById('edBPM').value = post.bpm || post.heart_rate || 72; document.getElementById('edThreat').value = post.threat_level || 'low'; document.getElementById('editorHeading').textContent = 'EDIT TRANSMISSION'; updateWordCount(); updatePreview(); showSection('editor'); } async function savePost(e) { e.preventDefault(); const slugOrig = document.getElementById('editSlugOriginal').value; const title = document.getElementById('edTitle').value.trim(); const slug = document.getElementById('edSlug').value.trim() || title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); const data = { title, slug, date: document.getElementById('edDate').value || new Date().toISOString().split('T')[0], time: document.getElementById('edTime').value || '00:00', time_written: document.getElementById('edTime').value || '00:00', tags: document.getElementById('edTags').value.split(',').map(t => t.trim()).filter(Boolean), excerpt: document.getElementById('edExcerpt').value.trim(), content: document.getElementById('edContent').value, mood: document.getElementById('edMood').value, energy: parseInt(document.getElementById('edEnergy').value), motivation: parseInt(document.getElementById('edMotivation').value), focus: parseInt(document.getElementById('edFocus').value), difficulty: parseInt(document.getElementById('edDifficulty').value), coffee: parseInt(document.getElementById('edCoffee').value), bpm: parseInt(document.getElementById('edBPM').value), heart_rate: parseInt(document.getElementById('edBPM').value), threat_level: document.getElementById('edThreat').value, }; const method = slugOrig ? 'PUT' : 'POST'; const url = slugOrig ? API + '/posts/' + slugOrig : API + '/posts'; try { const r = await fetch(url, { method, headers: headers(), body: JSON.stringify(data) }); if (r.ok) { clearEditor(); showSection('posts'); showNotification('TRANSMISSION ' + (slugOrig ? 'UPDATED' : 'SENT')); } else { const err = await r.json(); showNotification('ERROR: ' + (err.message || r.status), 'error'); } } catch (e) { showNotification('TRANSMISSION FAILED: ' + e.message, 'error'); } } async function deletePost(slug) { if (!confirm('Delete post "' + slug + '"? This cannot be undone.')) return; const r = await fetch(API + '/posts/' + slug, { method: 'DELETE', headers: headers() }); if (r.ok) { loadPosts(); showNotification('POST DELETED'); } } function clearEditor() { document.getElementById('editSlugOriginal').value = ''; document.getElementById('editorForm').reset(); document.getElementById('edEnergyVal').textContent = '3'; document.getElementById('edMotivationVal').textContent = '3'; document.getElementById('edFocusVal').textContent = '3'; document.getElementById('edDifficultyVal').textContent = '3'; document.getElementById('edWordCount').textContent = '0 words'; document.getElementById('edPreview').innerHTML = ''; document.getElementById('editorHeading').textContent = 'NEW TRANSMISSION'; } // ─── Markdown Helpers ─── function insertMD(before, after) { const ta = document.getElementById('edContent'); const start = ta.selectionStart; const end = ta.selectionEnd; const sel = ta.value.substring(start, end); ta.value = ta.value.substring(0, start) + before + sel + after + ta.value.substring(end); ta.focus(); ta.selectionStart = start + before.length; ta.selectionEnd = start + before.length + sel.length; updateWordCount(); updatePreview(); } function updateWordCount() { const text = document.getElementById('edContent').value.trim(); const count = text ? text.split(/\s+/).length : 0; document.getElementById('edWordCount').textContent = count + ' words'; } function updatePreview() { const md = document.getElementById('edContent').value; document.getElementById('edPreview').innerHTML = renderMarkdown(md); } function renderMarkdown(md) { let html = md .replace(/```([\s\S]*?)```/g, '
$1
') .replace(/`([^`]+)`/g, '$1') .replace(/^#### (.+)$/gm, '

$1

') .replace(/^### (.+)$/gm, '

$1

') .replace(/^## (.+)$/gm, '

$1

') .replace(/^# (.+)$/gm, '

$1

') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/^- (.+)$/gm, '
  • $1
  • ') .replace(/(
  • .*<\/li>)/s, '') .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') .replace(/^---$/gm, '
    ') .replace(/\n\n/g, '

    ') .replace(/\n/g, '
    '); return '

    ' + html + '

    '; } // ─── Tracks ─── async function loadTracks() { try { // Try fetching from tracks API endpoint let tracks; try { const r = await fetch(API + '/tracks'); if (r.ok) { tracks = await r.json(); } else throw new Error(); } catch { // Fallback: load from data file directly tracks = []; } const list = document.getElementById('tracksList'); if (!tracks.length) { list.innerHTML = 'No tracks loaded — API endpoint /api/tracks may need adding.'; return; } list.innerHTML = tracks.map((t, i) => `
    ${String(i + 1).padStart(2, '0')} ${t.track} ${t.artist} ${t.album || ''}
    `).join(''); } catch (e) { console.error('Track load error:', e); } } function showAddTrack() { const f = document.getElementById('trackAddForm'); f.style.display = f.style.display === 'none' ? 'flex' : 'none'; } async function addTrack() { const artist = document.getElementById('trackArtist').value.trim(); const track = document.getElementById('trackTitle').value.trim(); const album = document.getElementById('trackAlbum').value.trim(); if (!artist || !track) return; try { const r = await fetch(API + '/tracks', { method: 'POST', headers: headers(), body: JSON.stringify({ artist, track, album }) }); if (r.ok) { document.getElementById('trackArtist').value = ''; document.getElementById('trackTitle').value = ''; document.getElementById('trackAlbum').value = ''; loadTracks(); showNotification('TRACK ADDED'); } } catch (e) { showNotification('Failed to add track', 'error'); } } async function deleteTrack(index) { try { const r = await fetch(API + '/tracks/' + index, { method: 'DELETE', headers: headers() }); if (r.ok) loadTracks(); } catch {} } // ─── Settings ─── async function loadSettings() { try { const settings = await fetch(API + '/settings').then(r => r.json()); if (settings.weather !== undefined) document.getElementById('setWeather').checked = settings.weather; if (settings.nowplaying !== undefined) document.getElementById('setNowPlaying').checked = settings.nowplaying; if (settings.threats !== undefined) document.getElementById('setThreats').checked = settings.threats; if (settings.terminal !== undefined) document.getElementById('setTerminal').checked = settings.terminal; } catch {} } async function saveSettings(e) { e.preventDefault(); const data = { weather: document.getElementById('setWeather').checked, nowplaying: document.getElementById('setNowPlaying').checked, threats: document.getElementById('setThreats').checked, terminal: document.getElementById('setTerminal').checked, }; await fetch(API + '/settings', { method: 'PUT', headers: headers(), body: JSON.stringify(data) }); showNotification('SETTINGS SAVED'); } // ─── Notifications ─── function showNotification(msg, type = 'success') { let notif = document.querySelector('.admin-notification'); if (!notif) { notif = document.createElement('div'); notif.className = 'admin-notification'; document.body.appendChild(notif); } notif.textContent = msg; notif.className = 'admin-notification ' + type + ' show'; setTimeout(() => notif.classList.remove('show'), 3000); } // ─── Init ─── async function init() { // Check if already authed if (await checkAuth()) { showApp(); } // Login form document.getElementById('loginForm').addEventListener('submit', login); // Editor form document.getElementById('editorForm').addEventListener('submit', savePost); // Settings form document.getElementById('settingsForm').addEventListener('submit', saveSettings); // Auto slug from title document.getElementById('edTitle').addEventListener('input', (e) => { const slugField = document.getElementById('edSlug'); if (!document.getElementById('editSlugOriginal').value) { slugField.value = e.target.value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); } }); // Content updates document.getElementById('edContent').addEventListener('input', () => { updateWordCount(); updatePreview(); }); // Energy range display document.getElementById('edEnergy').addEventListener('input', (e) => { document.getElementById('edEnergyVal').textContent = e.target.value; }); document.getElementById('edMotivation').addEventListener('input', (e) => { document.getElementById('edMotivationVal').textContent = e.target.value; }); document.getElementById('edFocus').addEventListener('input', (e) => { document.getElementById('edFocusVal').textContent = e.target.value; }); document.getElementById('edDifficulty').addEventListener('input', (e) => { document.getElementById('edDifficultyVal').textContent = e.target.value; }); // Set today's date default document.getElementById('edDate').value = new Date().toISOString().split('T')[0]; } document.addEventListener('DOMContentLoaded', init); // Public API return { showSection, logout, editPost, deletePost, clearEditor, insertMD, showAddTrack, addTrack, deleteTrack, }; })();