Admin UX: light/dark theme, gallery pagination, content modal + delete, openai type
- Light/dark theme toggle persisted in localStorage - Gallery pagination: page size 50/100/150/200 with prev/next - Fullscreen modal viewer (image/video/audio) with metadata and delete - Delete content: DELETE /api/generations/:id removes file (media-dir guarded) and journal record; added Journal.deleteById - Provider types now include openai - HTML output escaping in UI
This commit is contained in:
@@ -1,22 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<html lang="ru" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>NexusAI Admin</title>
|
||||
<style>
|
||||
:root {
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #0f1115; --panel: #171a21; --panel2: #1f242e; --line: #2a2f3a;
|
||||
--text: #e6e9ef; --muted: #9aa4b2; --accent: #4f8cff; --ok: #35c46a;
|
||||
--warn: #f0b429; --err: #f26d6d;
|
||||
--warn: #f0b429; --err: #f26d6d; --shadow: rgba(0,0,0,.5);
|
||||
}
|
||||
:root[data-theme="light"] {
|
||||
--bg: #f4f6fa; --panel: #ffffff; --panel2: #f0f2f7; --line: #dce1ea;
|
||||
--text: #1a1f2b; --muted: #5c6675; --accent: #2f6fed; --ok: #1a9e51;
|
||||
--warn: #b3810f; --err: #d63d3d; --shadow: rgba(0,0,0,.18);
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: var(--bg); color: var(--text);
|
||||
font: 14px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
|
||||
font: 14px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
transition: background .15s, color .15s; }
|
||||
header { padding: 14px 20px; border-bottom: 1px solid var(--line);
|
||||
display: flex; align-items: center; gap: 16px; }
|
||||
header h1 { font-size: 16px; margin: 0; font-weight: 600; }
|
||||
.badge { font-size: 12px; color: var(--muted); }
|
||||
header .spacer { flex: 1; }
|
||||
#themeToggle { background: var(--panel2); border: 1px solid var(--line); color: var(--text);
|
||||
border-radius: 8px; padding: 6px 12px; cursor: pointer; font: inherit; }
|
||||
nav { display: flex; gap: 6px; padding: 12px 20px 0; }
|
||||
nav button { background: var(--panel); color: var(--muted); border: 1px solid var(--line);
|
||||
padding: 8px 14px; border-radius: 8px 8px 0 0; cursor: pointer; }
|
||||
@@ -39,6 +48,7 @@
|
||||
button.ghost { background: transparent; color: var(--text); border: 1px solid var(--line);
|
||||
border-radius: 8px; padding: 6px 10px; cursor: pointer; }
|
||||
button.danger { color: var(--err); border-color: var(--err); }
|
||||
button:disabled { opacity: .45; cursor: not-allowed; }
|
||||
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: end; }
|
||||
.row > div { flex: 1; min-width: 140px; }
|
||||
.pill { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; }
|
||||
@@ -46,22 +56,43 @@
|
||||
.pill.off { background: rgba(242,109,109,.15); color: var(--err); }
|
||||
.pill.def { background: rgba(79,140,255,.15); color: var(--accent); }
|
||||
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 14px; }
|
||||
.tile { background: var(--panel2); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
|
||||
.tile { background: var(--panel2); border: 1px solid var(--line); border-radius: 10px;
|
||||
overflow: hidden; position: relative; }
|
||||
.tile .thumb { cursor: pointer; }
|
||||
.tile img { width: 100%; height: 150px; object-fit: cover; display: block; background: #000; }
|
||||
.tile .meta { padding: 8px 10px; font-size: 12px; }
|
||||
.tile .meta .m { color: var(--muted); }
|
||||
.tile .del { position: absolute; top: 6px; right: 6px; background: var(--panel);
|
||||
border: 1px solid var(--line); color: var(--err); border-radius: 6px; padding: 2px 8px;
|
||||
cursor: pointer; font-size: 12px; opacity: .85; }
|
||||
.tile .del:hover { opacity: 1; }
|
||||
.pager { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.muted { color: var(--muted); }
|
||||
.hidden { display: none; }
|
||||
.toast { position: fixed; right: 16px; bottom: 16px; background: var(--panel2);
|
||||
border: 1px solid var(--line); padding: 10px 14px; border-radius: 8px; }
|
||||
border: 1px solid var(--line); padding: 10px 14px; border-radius: 8px; box-shadow: 0 6px 24px var(--shadow); }
|
||||
audio { width: 100%; }
|
||||
code { background: var(--panel2); padding: 1px 5px; border-radius: 4px; }
|
||||
/* Modal */
|
||||
.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.7);
|
||||
display: flex; align-items: center; justify-content: center; padding: 24px; z-index: 50; }
|
||||
.modal { background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
|
||||
max-width: 92vw; max-height: 92vh; overflow: auto; box-shadow: 0 20px 60px var(--shadow); }
|
||||
.modal-head { display: flex; align-items: center; gap: 12px; padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--panel); }
|
||||
.modal-head .spacer { flex: 1; }
|
||||
.modal-body { padding: 16px; }
|
||||
.modal-body img, .modal-body video { max-width: 88vw; max-height: 72vh; display: block; margin: 0 auto; }
|
||||
.modal-body audio { width: 60vw; max-width: 600px; }
|
||||
.kv { color: var(--muted); font-size: 12px; margin-top: 10px; word-break: break-all; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>NexusAI Admin</h1>
|
||||
<span class="badge" id="cryptoBadge"></span>
|
||||
<span class="spacer"></span>
|
||||
<button id="themeToggle" title="Переключить тему">🌙 Тёмная</button>
|
||||
</header>
|
||||
<nav>
|
||||
<button data-tab="usage" class="active">Потребление</button>
|
||||
@@ -113,8 +144,22 @@
|
||||
<option value="search">search</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="max-width:180px">
|
||||
<label>Элементов на странице</label>
|
||||
<select id="pageSize">
|
||||
<option value="50" selected>50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="150">150</option>
|
||||
<option value="200">200</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="max-width:120px"><label> </label><button class="ghost" id="galleryReload">Обновить</button></div>
|
||||
</div>
|
||||
<div class="pager" style="margin-top:12px">
|
||||
<button class="ghost" id="prevPage">← Назад</button>
|
||||
<span class="muted" id="pageInfo">Страница 1</span>
|
||||
<button class="ghost" id="nextPage">Вперёд →</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gallery" id="gallery"></div>
|
||||
</section>
|
||||
@@ -143,6 +188,8 @@
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="modalRoot"></div>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user