10 lines
6.4 KiB
HTML
10 lines
6.4 KiB
HTML
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Services - ToNav</title><link rel="stylesheet" href="/static/css/admin.css"><script>document.documentElement.classList.toggle('dark',localStorage.getItem('tonav-theme')==='dark')</script></head><body><div class="admin-shell"><aside class="admin-sidebar"><div class="admin-brand">ToNav</div><div class="admin-label">OVERVIEW</div><a class="admin-nav" href="/admin/dashboard">Overview</a><div class="admin-label" style="margin-top:22px">SERVICES</div><a class="admin-nav active" href="/admin/services">Services</a><a class="admin-nav" href="/admin/categories">Categories</a><div class="admin-label" style="margin-top:22px">SYSTEM</div><a class="admin-nav" href="/admin/dashboard#settings">Settings</a><div class="admin-sidebar-foot"><a href="/">← Open navigation</a><br><a href="/admin/logout">Sign out</a></div></aside><main class="admin-content"><div class="admin-main"><header class="admin-topbar"><div><h1>Services</h1><div class="admin-sub">Manage entries in your service directory</div></div><div class="top-actions"><a class="btn" href="/">Open site</a><button class="btn btn-primary" onclick="openModal()">+ Add service</button></div></header><section class="section"><div class="table-toolbar"><input id="filter" class="search-input" placeholder="Search services..." oninput="load()"></div><div class="panel table-wrap"><table class="data-table"><thead><tr><th>Service</th><th>Category</th><th>Status</th><th>Order</th><th>Actions</th></tr></thead><tbody id="list"></tbody></table><div id="emptyMsg" class="empty-state" style="display:none">No services found.</div></div></section></div></main></div>
|
||
<div id="modal" class="modal-overlay"><div class="modal-content"><div class="modal-head"><h2 id="modalTitle">Add service</h2><button class="close" onclick="closeModal()">×</button></div><input type="hidden" id="editId"><div class="form-group"><label>Name *</label><input id="svcName" class="input"></div><div class="form-group"><label>URL *</label><input id="svcUrl" type="url" class="input" placeholder="https://example.com"></div><div class="form-row"><div class="form-group"><label>Icon</label><input id="svcIcon" class="input" maxlength="8" placeholder="◎"></div><div class="form-group"><label>Category</label><select id="svcCategory" class="input"><option value="0">No category</option>{{ range .categories }}<option value="{{ .ID }}">{{ .Name }}</option>{{ end }}</select></div></div><div class="form-group"><label>Description</label><input id="svcDesc" class="input"></div><div class="form-group"><label>Tags (comma separated)</label><input id="svcTags" class="input"></div><div class="form-row"><div class="form-group"><label>Sort order</label><input id="svcOrder" type="number" class="input" value="0"></div><div class="form-group"><label>Health check URL</label><input id="svcHealthUrl" type="url" class="input"></div></div><div class="form-group"><label><input type="checkbox" id="svcEnabled" checked> Enabled</label></div><div class="form-group"><label><input type="checkbox" id="svcHealthEnabled"> Health check enabled</label></div><div class="modal-actions"><button class="btn" onclick="closeModal()">Cancel</button><button class="btn btn-primary" onclick="save()">Save</button></div></div></div>
|
||
<script>const categoryMap={};{{ range .categories }}categoryMap[{{ .ID }}]={{ printf "%q" .Name }};{{ end }}
|
||
async function load(){const r=await fetch('/admin/api/services'),d=await r.json(),q=filter.value.toLowerCase();const rows=(d.data||[]).filter(s=>!q||[s.name,s.description,s.tags,s.url].some(x=>(x||'').toLowerCase().includes(q)));emptyMsg.style.display=rows.length?'none':'block';list.innerHTML=rows.map(s=>`<tr><td><div style="display:flex;align-items:center;gap:10px"><span class="service-icon">${escapeHTML(s.icon||'◎')}</span><span><strong>${escapeHTML(s.name)}</strong><small style="display:block;color:var(--muted)">${escapeHTML(s.url)}</small></span></div></td><td>${escapeHTML(categoryMap[s.category_id]||'—')}</td><td><span class="status-inline"><i class="dot ${s.status}"></i>${s.status==='online'?'Online':s.status==='offline'?'Offline':'Unknown'}</span></td><td>${s.sort_order}</td><td><button class="btn" onclick='editSvc(${JSON.stringify(s)})'>Edit</button> <button class="btn btn-danger" onclick="del(${s.id},'${escapeHTML(s.name)}')">Delete</button></td></tr>`).join('')}
|
||
function openModal(){editId.value='';svcName.value='';svcUrl.value='';svcIcon.value='';svcCategory.value='0';svcDesc.value='';svcTags.value='';svcOrder.value='0';svcHealthUrl.value='';svcEnabled.checked=true;svcHealthEnabled.checked=false;modalTitle.textContent='Add service';modal.style.display='flex';svcName.focus()}
|
||
function editSvc(s){editId.value=s.id;svcName.value=s.name;svcUrl.value=s.url;svcIcon.value=s.icon||'';svcCategory.value=s.category_id||'0';svcDesc.value=s.description||'';svcTags.value=s.tags||'';svcOrder.value=s.sort_order||0;svcHealthUrl.value=s.health_check_url||'';svcEnabled.checked=s.is_enabled;svcHealthEnabled.checked=s.health_check_enabled;modalTitle.textContent='Edit service';modal.style.display='flex'}function closeModal(){modal.style.display='none'}
|
||
async function save(){const id=editId.value,name=svcName.value.trim(),url=svcUrl.value.trim();if(!name||!url)return alert('Name and URL are required');const body={name,url,icon:svcIcon.value.trim(),category_id:+svcCategory.value||0,description:svcDesc.value.trim(),tags:svcTags.value.trim(),sort_order:+svcOrder.value||0,health_check_url:svcHealthUrl.value.trim(),is_enabled:svcEnabled.checked,health_check_enabled:svcHealthEnabled.checked};if(id)body.id=+id;const r=await fetch(id?'/admin/api/services/'+id:'/admin/api/services',{method:id?'PUT':'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}),d=await r.json();if(d.success){closeModal();load()}else alert(d.message||'Save failed')}
|
||
async function del(id,name){if(!confirm('Delete '+name+'?'))return;const r=await fetch('/admin/api/services/'+id,{method:'DELETE'}),d=await r.json();if(d.success)load();else alert(d.message||'Delete failed')}
|
||
function escapeHTML(s){const d=document.createElement('div');d.textContent=s||'';return d.innerHTML}function toggleTheme(){const dark=!document.documentElement.classList.contains('dark');document.documentElement.classList.toggle('dark',dark);localStorage.setItem('tonav-theme',dark?'dark':'light')}document.addEventListener('keydown',e=>{if(e.key==='Escape')closeModal()});load();</script></body></html>
|