Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>TUM Search - Admin Console</title> | |
| <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <style> .scroll-box { height: 70vh; overflow-y: auto; border: 1px solid #ddd; padding: 10px; } </style> | |
| </head> | |
| <body class="bg-dark text-light"> | |
| <div id="app" class="container-fluid py-4"> | |
| <h2 class="mb-4">⚡️ Admin Control Center</h2> | |
| <div class="row"> | |
| <div class="col-md-6"> | |
| <div class="card bg-secondary text-white"> | |
| <div class="card-header d-flex justify-content-between"> | |
| <h5 class="mb-0">Space R (Reference / Anchors)</h5> | |
| <button class="btn btn-sm btn-light" @click="loadData('R', true)">Refresh</button> | |
| </div> | |
| <div class="card-body scroll-box"> | |
| <div v-for="item in rItems" :key="item.id" class="card bg-dark mb-2 border-secondary"> | |
| <div class="card-body py-2"> | |
| <div class="small text-muted mb-1">ID: {{item.id}}</div> | |
| <div>{{ (item.payload.content || item.payload.content_preview || 'No Content').substring(0, 80) }}...</div> | |
| <div class="mt-2 text-end"> | |
| <button class="btn btn-sm btn-danger py-0" @click="deleteItem('R', item.id)">Delete</button> | |
| </div> | |
| </div> | |
| </div> | |
| <button v-if="rNext" class="btn btn-outline-light w-100 mt-2" @click="loadData('R')">Load More</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-6"> | |
| <div class="card bg-light text-dark"> | |
| <div class="card-header d-flex justify-content-between"> | |
| <h5 class="mb-0">Space X (Search Pool)</h5> | |
| <button class="btn btn-sm btn-dark" @click="loadData('X', true)">Refresh</button> | |
| </div> | |
| <div class="card-body scroll-box"> | |
| <div v-for="item in xItems" :key="item.id" class="card mb-2 border-0 shadow-sm"> | |
| <div class="card-body py-2"> | |
| <div class="small text-muted mb-1">ID: {{item.id}} | Score: {{item.score.toFixed(4)}}</div> | |
| <div class="fw-bold">{{ item.payload.url }}</div> | |
| <div>{{ (item.payload.content_preview || '').substring(0, 80) }}...</div> | |
| <div class="mt-2 d-flex justify-content-end gap-2"> | |
| <button class="btn btn-sm btn-success py-0" @click="promote(item.id)">Promote to R</button> | |
| <button class="btn btn-sm btn-danger py-0" @click="deleteItem('X', item.id)">Delete</button> | |
| </div> | |
| </div> | |
| </div> | |
| <button v-if="xNext" class="btn btn-outline-dark w-100 mt-2" @click="loadData('X')">Load More</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="config.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.js"></script> | |
| <script> | |
| const { createApp } = Vue; | |
| createApp({ | |
| data() { return { rItems: [], xItems: [], rNext: null, xNext: null } }, | |
| mounted() { this.loadData('R'); this.loadData('X'); }, | |
| methods: { | |
| async loadData(space, refresh=false) { | |
| if(refresh) { | |
| if(space==='R') { this.rItems=[]; this.rNext=null; } | |
| else { this.xItems=[]; this.xNext=null; } | |
| } | |
| const offset = space==='R' ? this.rNext : this.xNext; | |
| const res = await fetch(API_CONFIG.getURL(`${API_CONFIG.endpoints.admin.browse}?space=${space}&limit=50&offset=${offset||''}`)); | |
| const data = await res.json(); | |
| if(space === 'R') { | |
| this.rItems.push(...data.items); | |
| this.rNext = data.next_offset; | |
| } else { | |
| this.xItems.push(...data.items); | |
| this.xNext = data.next_offset; | |
| } | |
| }, | |
| async deleteItem(space, id) { | |
| if(!confirm('Are you sure?')) return; | |
| await fetch(API_CONFIG.getURL(`${API_CONFIG.endpoints.admin.delete}?space=${space}&id=${id}`), { method: 'DELETE' }); | |
| alert('Deleted.'); | |
| this.loadData(space, true); // Refresh | |
| }, | |
| async promote(id) { | |
| const formData = new FormData(); formData.append('id', id); | |
| await fetch(API_CONFIG.getURL(API_CONFIG.endpoints.admin.promote), { method: 'POST', body: formData }); | |
| alert('Promoted to Space R. Global recalculation triggered.'); | |
| this.loadData('R', true); // Refresh R | |
| } | |
| } | |
| }).mount('#app'); | |
| </script> | |
| </body> | |
| </html> |