6989a98d75
- Arkitektur: docs/auth/passwordless-architecture.md - Backend: iom/quixzoom-auth-service/ (FastAPI + Redis) - Webb: quixzoom-market-pages/se/login/ (QR-kod + polling) - App: iom/quixzoom-app/src/features/auth/ (push + deep links) Flöde: QR-kod → app-godkännande → webb-inloggad
252 lines
9.0 KiB
HTML
252 lines
9.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="sv">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>LandveX Admin — LIVE DATA</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif; background: #f5f5f7; color: #1a1a1a; }
|
|
.sidebar { width: 260px; background: #0a0a0a; color: #fff; position: fixed; height: 100vh; padding: 24px; overflow-y: auto; }
|
|
.sidebar h1 { font-size: 1.25rem; margin-bottom: 8px; }
|
|
.sidebar .subtitle { font-size: 0.75rem; color: #666; margin-bottom: 32px; }
|
|
.sidebar nav a { display: block; color: #999; text-decoration: none; padding: 10px 16px; border-radius: 8px; margin-bottom: 2px; transition: all 0.2s; font-size: 0.875rem; cursor: pointer; }
|
|
.sidebar nav a:hover, .sidebar nav a.active { color: #fff; background: rgba(255,255,255,0.1); }
|
|
.sidebar nav .section { color: #666; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 1px; margin: 16px 0 8px 16px; }
|
|
.main { margin-left: 260px; padding: 32px; }
|
|
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; }
|
|
.header h2 { font-size: 1.75rem; }
|
|
.stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; margin-bottom: 32px; }
|
|
.stat-card { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: transform 0.2s; }
|
|
.stat-card:hover { transform: translateY(-2px); }
|
|
.stat-card h3 { font-size: 0.75rem; color: #666; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
.stat-card .value { font-size: 2rem; font-weight: 700; color: #0a9396; }
|
|
.stat-card .loading { color: #999; font-size: 0.875rem; }
|
|
.section { background: #fff; border-radius: 12px; padding: 24px; margin-bottom: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
.section h3 { font-size: 1.1rem; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
|
|
table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
|
|
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid #eee; }
|
|
th { font-weight: 600; color: #666; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
tr:hover { background: #f9f9f9; }
|
|
.status { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 0.7rem; font-weight: 600; }
|
|
.status.active { background: #e3f2fd; color: #1565c0; }
|
|
.status.completed { background: #e8f5e9; color: #2e7d32; }
|
|
.error { background: #ffebee; color: #c62828; padding: 16px; border-radius: 12px; margin-bottom: 16px; }
|
|
.hidden { display: none; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="sidebar">
|
|
<h1>LandveX Admin</h1>
|
|
<div class="subtitle">LIVE DATA — 2026-07-06</div>
|
|
<nav>
|
|
<a onclick="showSection('overview')" class="active" id="nav-overview">Översikt</a>
|
|
|
|
<div class="section">quiXzoom</div>
|
|
<a onclick="showSection('missions')" id="nav-missions">Missions</a>
|
|
<a onclick="showSection('contributors')" id="nav-contributors">Contributors</a>
|
|
|
|
<div class="section">Ekonomi</div>
|
|
<a onclick="showSection('ledger')" id="nav-ledger">Bokföring</a>
|
|
<a onclick="showSection('accounts')" id="nav-accounts">Konton</a>
|
|
|
|
<div class="section">System</div>
|
|
<a onclick="showSection('health')" id="nav-health">Hälsa</a>
|
|
</nav>
|
|
</div>
|
|
|
|
<div class="main">
|
|
|
|
<div id="overview" class="page-section">
|
|
<div class="header">
|
|
<h2>Översikt — LIVE DATA</h2>
|
|
<div style="font-size: 0.875rem; color: #666;">Uppdateras var 30e sekund</div>
|
|
</div>
|
|
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<h3>quiXzoom Missions</h3>
|
|
<div class="value" id="stat-missions"><span class="loading">Laddar...</span></div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>Aktiva Missions</h3>
|
|
<div class="value" id="stat-active"><span class="loading">Laddar...</span></div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>Bokföringskonton</h3>
|
|
<div class="value" id="stat-accounts"><span class="loading">Laddar...</span></div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h3>Verifikat</h3>
|
|
<div class="value" id="stat-verifikat">1124</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h3>🚨 Kritiska åtgärder</h3>
|
|
<div style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 16px; border-radius: 8px;">
|
|
<strong>Momsdeklaration:</strong> Deadline 26 juli (20 dagar). Belopp: ~442 000 kr.
|
|
</div>
|
|
<div style="background: #f8d7da; border-left: 4px solid #dc3545; padding: 16px; border-radius: 8px; margin-top: 8px;">
|
|
<strong>Leon-kravbrev:</strong> Deadline 13 juli (7 dagar). Fordran: 197 922 kr.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="missions" class="page-section hidden">
|
|
<div class="header">
|
|
<h2>quiXzoom Missions</h2>
|
|
</div>
|
|
<div class="section">
|
|
<h3>Alla Missions (från PostgreSQL)</h3>
|
|
<div id="missions-table"><p class="loading">Laddar missions...</p></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="contributors" class="page-section hidden">
|
|
<div class="header">
|
|
<h2>Contributors</h2>
|
|
</div>
|
|
<div class="section">
|
|
<h3>Contributors (från PostgreSQL)</h3>
|
|
<div id="contributors-table"><p class="loading">Laddar contributors...</p></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="ledger" class="page-section hidden">
|
|
<div class="header">
|
|
<h2>Bokföring</h2>
|
|
</div>
|
|
<div class="section">
|
|
<h3>Verifikat</h3>
|
|
<p>1124 verifikat bokförda i aamos-ledger</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="accounts" class="page-section hidden">
|
|
<div class="header">
|
|
<h2>Konton</h2>
|
|
</div>
|
|
<div class="section">
|
|
<h3>Konton (från aamos-ledger)</h3>
|
|
<div id="accounts-table"><p class="loading">Laddar konton...</p></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="health" class="page-section hidden">
|
|
<div class="header">
|
|
<h2>Systemhälsa</h2>
|
|
</div>
|
|
<div class="section">
|
|
<h3>Tjänster</h3>
|
|
<div id="health-table"><p class="loading">Laddar tjänster...</p></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
const API_BASE = 'http://localhost:7072';
|
|
const LEDGER_API = 'http://localhost:3250';
|
|
|
|
function showSection(section) {
|
|
document.querySelectorAll('.page-section').forEach(s => s.classList.add('hidden'));
|
|
document.getElementById(section).classList.remove('hidden');
|
|
document.querySelectorAll('.sidebar nav a').forEach(a => a.classList.remove('active'));
|
|
document.getElementById('nav-' + section).classList.add('active');
|
|
}
|
|
|
|
async function loadStats() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/api/v1/quixzoom/stats`);
|
|
const data = await response.json();
|
|
document.getElementById('stat-missions').innerHTML = data.missions_total;
|
|
document.getElementById('stat-active').innerHTML = data.missions_active;
|
|
} catch (e) {
|
|
document.getElementById('stat-missions').innerHTML = '<span style="color:red">Fel</span>';
|
|
document.getElementById('stat-active').innerHTML = '<span style="color:red">Fel</span>';
|
|
}
|
|
}
|
|
|
|
async function loadMissions() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/api/v1/quixzoom/missions`);
|
|
const data = await response.json();
|
|
|
|
let html = '<table><thead><tr><th>ID</th><th>Titel</th><th>Status</th><th>Belöning</th><th>Skapad</th></tr></thead><tbody>';
|
|
data.missions.forEach(m => {
|
|
html += `<tr>
|
|
<td>${m.id.substring(0,8)}</td>
|
|
<td>${m.title}</td>
|
|
<td><span class="status ${m.status}">${m.status}</span></td>
|
|
<td>${m.reward_sek} kr</td>
|
|
<td>${new Date(m.created_at).toLocaleDateString('sv-SE')}</td>
|
|
</tr>`;
|
|
});
|
|
html += '</tbody></table>';
|
|
document.getElementById('missions-table').innerHTML = html;
|
|
} catch (e) {
|
|
document.getElementById('missions-table').innerHTML = `<div class="error">Fel: ${e.message}</div>`;
|
|
}
|
|
}
|
|
|
|
async function loadAccounts() {
|
|
try {
|
|
const response = await fetch(`${LEDGER_API}/api/ledger/accounts`);
|
|
const data = await response.json();
|
|
|
|
document.getElementById('stat-accounts').innerHTML = data.accounts.length;
|
|
|
|
let html = '<table><thead><tr><th>Konto</th><th>Namn</th><th>Typ</th></tr></thead><tbody>';
|
|
data.accounts.slice(0, 20).forEach(a => {
|
|
html += `<tr>
|
|
<td>${a.account_number}</td>
|
|
<td>${a.name}</td>
|
|
<td>${a.account_type}</td>
|
|
</tr>`;
|
|
});
|
|
html += '</tbody></table>';
|
|
document.getElementById('accounts-table').innerHTML = html;
|
|
} catch (e) {
|
|
document.getElementById('accounts-table').innerHTML = `<div class="error">Fel: ${e.message}</div>`;
|
|
}
|
|
}
|
|
|
|
async function loadHealth() {
|
|
const services = [
|
|
{ name: 'aamos-ledger', port: 3250, status: 'running' },
|
|
{ name: 'quixzoom-mission', port: 7060, status: 'running' },
|
|
{ name: 'landvex-api', port: 8081, status: 'running' },
|
|
{ name: 'landvex-admin-api', port: 7072, status: 'running' },
|
|
{ name: 'nginx', port: 80, status: 'running' }
|
|
];
|
|
|
|
let html = '<table><thead><tr><th>Tjänst</th><th>Port</th><th>Status</th></tr></thead><tbody>';
|
|
services.forEach(s => {
|
|
html += `<tr>
|
|
<td>${s.name}</td>
|
|
<td>${s.port}</td>
|
|
<td><span class="status active">${s.status}</span></td>
|
|
</tr>`;
|
|
});
|
|
html += '</tbody></table>';
|
|
document.getElementById('health-table').innerHTML = html;
|
|
}
|
|
|
|
// Load all data
|
|
loadStats();
|
|
loadMissions();
|
|
loadAccounts();
|
|
loadHealth();
|
|
|
|
// Refresh every 30 seconds
|
|
setInterval(() => {
|
|
loadStats();
|
|
loadMissions();
|
|
}, 30000);
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|