6de2455917
- Added GLOBAL_MARKETS_TITLE to all translation files - Updated footer with 12 markets (4 active + 8 upcoming) - Translated market section to: zh-cn, zh-tw, ja, ko, th, vi, id, ms, hi - Built and deployed to production - CloudFront invalidation: I3RTMXVFDJXWLG3SYX208OP1CC
272 lines
9.7 KiB
HTML
272 lines
9.7 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 API Demo</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: #f5f5f7;
|
|
color: #1d1d1f;
|
|
line-height: 1.5;
|
|
padding: 20px;
|
|
}
|
|
.container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
h1 {
|
|
font-size: 32px;
|
|
margin-bottom: 8px;
|
|
color: #0071e3;
|
|
}
|
|
.subtitle {
|
|
color: #86868b;
|
|
margin-bottom: 32px;
|
|
}
|
|
.card {
|
|
background: white;
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
margin-bottom: 16px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
}
|
|
.card h2 {
|
|
font-size: 20px;
|
|
margin-bottom: 16px;
|
|
color: #1d1d1f;
|
|
}
|
|
input, button {
|
|
font-family: inherit;
|
|
font-size: 16px;
|
|
padding: 12px 16px;
|
|
border-radius: 10px;
|
|
border: 1px solid #d2d2d7;
|
|
width: 100%;
|
|
margin-bottom: 12px;
|
|
}
|
|
button {
|
|
background: #0071e3;
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
font-weight: 600;
|
|
}
|
|
button:hover {
|
|
background: #0077ed;
|
|
}
|
|
.result {
|
|
background: #f5f5f7;
|
|
border-radius: 10px;
|
|
padding: 16px;
|
|
margin-top: 16px;
|
|
font-family: 'SF Mono', Monaco, monospace;
|
|
font-size: 13px;
|
|
overflow-x: auto;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
.metrics {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 12px;
|
|
margin-top: 16px;
|
|
}
|
|
.metric {
|
|
background: #f5f5f7;
|
|
border-radius: 10px;
|
|
padding: 16px;
|
|
text-align: center;
|
|
}
|
|
.metric-value {
|
|
font-size: 32px;
|
|
font-weight: 700;
|
|
color: #0071e3;
|
|
}
|
|
.metric-label {
|
|
font-size: 13px;
|
|
color: #86868b;
|
|
margin-top: 4px;
|
|
}
|
|
.status {
|
|
display: inline-block;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
margin-right: 6px;
|
|
}
|
|
.status.ok { background: #34c759; }
|
|
.status.warn { background: #ff9500; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🔍 LandveX</h1>
|
|
<p class="subtitle">Infrastructure Object Library — API Demo</p>
|
|
|
|
<div class="card">
|
|
<h2>API Status</h2>
|
|
<div id="api-status">
|
|
<span class="status ok"></span> Object API (port 8083)<br>
|
|
<span class="status ok"></span> Bounty API (port 8084)<br>
|
|
<span class="status ok"></span> Claims API (port 8085)
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>📊 Mätetal</h2>
|
|
<div class="metrics" id="metrics">
|
|
<div class="metric">
|
|
<div class="metric-value" id="total-objects">—</div>
|
|
<div class="metric-label">Totalt objekt</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value" id="klasser">—</div>
|
|
<div class="metric-label">Klasser</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value" id="modeller">—</div>
|
|
<div class="metric-label">Modeller</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value" id="trp">—</div>
|
|
<div class="metric-label">Trafik</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value" id="vat">—</div>
|
|
<div class="metric-label">Vatten/Avlopp</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value" id="eln">—</div>
|
|
<div class="metric-label">El/Nät</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>🎯 Aktiva Bounties</h2>
|
|
<div id="bounties-list">
|
|
<p>Laddar bounties...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>🔎 Sök objekt</h2>
|
|
<input type="text" id="search-query" placeholder="T.ex. brunn, elstolpe, GE Evolve...">
|
|
<button onclick="search()">Sök</button>
|
|
<div class="result" id="search-result">Resultat visas här...</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>🎯 Identify (OCR)</h2>
|
|
<input type="text" id="ocr-input" placeholder="T.ex. GE ERS2 4000K">
|
|
<button onclick="identify()">Identifiera</button>
|
|
<div class="result" id="identify-result">Kandidater visas här...</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>📋 Hämta objekt</h2>
|
|
<input type="text" id="object-id" placeholder="LVX-ID, t.ex. LVX-ELN-0101">
|
|
<button onclick="getObject()">Hämta</button>
|
|
<div class="result" id="object-result">Objektdata visas här...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const API_BASE = '';
|
|
|
|
async function loadMetrics() {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/v0/metrics`, {
|
|
headers: { 'X-API-Key': 'demo-key-2026' }
|
|
});
|
|
const data = await res.json();
|
|
document.getElementById('total-objects').textContent = data.objects.total;
|
|
document.getElementById('klasser').textContent = data.objects.klasser;
|
|
document.getElementById('modeller').textContent = data.objects.modeller;
|
|
} catch (e) {
|
|
console.error('Kunde inte ladda mätetal:', e);
|
|
}
|
|
}
|
|
|
|
async function search() {
|
|
const query = document.getElementById('search-query').value;
|
|
if (!query) return;
|
|
try {
|
|
const res = await fetch(`${API_BASE}/v0/search?q=${encodeURIComponent(query)}`, {
|
|
headers: { 'X-API-Key': 'demo-key-2026' }
|
|
});
|
|
const data = await res.json();
|
|
document.getElementById('search-result').textContent = JSON.stringify(data, null, 2);
|
|
} catch (e) {
|
|
document.getElementById('search-result').textContent = 'Fel: ' + e.message;
|
|
}
|
|
}
|
|
|
|
async function identify() {
|
|
const ocr = document.getElementById('ocr-input').value.split(' ').filter(x => x);
|
|
try {
|
|
const res = await fetch(`${API_BASE}/v0/identify`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-API-Key': 'demo-key-2026' },
|
|
body: JSON.stringify({ ocr_extraherat: ocr })
|
|
});
|
|
const data = await res.json();
|
|
document.getElementById('identify-result').textContent = JSON.stringify(data, null, 2);
|
|
} catch (e) {
|
|
document.getElementById('identify-result').textContent = 'Fel: ' + e.message;
|
|
}
|
|
}
|
|
|
|
async function getObject() {
|
|
const id = document.getElementById('object-id').value;
|
|
if (!id) return;
|
|
try {
|
|
const res = await fetch(`${API_BASE}/v0/objects/${id}`, {
|
|
headers: { 'X-API-Key': 'demo-key-2026' }
|
|
});
|
|
const data = await res.json();
|
|
document.getElementById('object-result').textContent = JSON.stringify(data, null, 2);
|
|
} catch (e) {
|
|
document.getElementById('object-result').textContent = 'Fel: ' + e.message;
|
|
}
|
|
}
|
|
|
|
async function loadBounties() {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/v0/bounties`, {
|
|
headers: { 'X-API-Key': '***' }
|
|
});
|
|
const data = await res.json();
|
|
const list = document.getElementById('bounties-list');
|
|
|
|
if (data.bounties && data.bounties.length > 0) {
|
|
const active = data.bounties.filter(b => b.status === 'open').slice(0, 5);
|
|
if (active.length > 0) {
|
|
list.innerHTML = active.map(b => `
|
|
<div style="padding: 12px; background: #f5f5f7; border-radius: 8px; margin-bottom: 8px;">
|
|
<strong>${b.title}</strong>
|
|
<span style="color: #0071e3; float: right;">${b.reward} kr</span>
|
|
<div style="font-size: 13px; color: #86868b; margin-top: 4px;">${b.description || 'Ingen beskrivning'}</div>
|
|
</div>
|
|
`).join('');
|
|
} else {
|
|
list.innerHTML = '<p>Inga aktiva bounties just nu.</p>';
|
|
}
|
|
} else {
|
|
list.innerHTML = '<p>Inga bounties hittades.</p>';
|
|
}
|
|
} catch (e) {
|
|
document.getElementById('bounties-list').innerHTML = '<p>Kunde inte ladda bounties.</p>';
|
|
}
|
|
}
|
|
|
|
// Ladda mätetal och bounties vid start
|
|
loadMetrics();
|
|
loadBounties();
|
|
</script>
|
|
</body>
|
|
</html>
|