/
mcmare
/
RustAPI
Обзор
Документация
Войти
/
mcmare
/
RustAPI
Код
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
engine/webui-assets/index.html
339 строк
20 KB
mcmare
Fix Tailwind CDN script blocked by CORS on SRI crossorigin fetch
19 июл 2026, 09:32
19 июл 2026, 09:32
c555564
Код
Авторство
О чём код?
<!doctype html> <html lang="ru"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>rustapi — admin</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <!-- No `integrity` here: fonts.googleapis.com deliberately serves a different CSS body per User-Agent (different @font-face formats/subsets), so a fixed SRI hash would break the stylesheet for most browsers — Google's own guidance is to not pin this endpoint. --> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet"> <!-- No `integrity`/`crossorigin` here: cdn.tailwindcss.com doesn't send Access-Control-Allow-Origin, so a CORS-mode (crossorigin) fetch — required for SRI — gets blocked by the browser and the script never loads. Pinned to an exact version instead of the floating latest tag. --> <script src="https://cdn.tailwindcss.com/3.4.16"></script> <script> tailwind.config = { darkMode: ['selector', '[data-theme="dark"]'], theme: { extend: { colors: { bg: 'var(--bg)', surface: 'var(--surface)', 'surface-alt': 'var(--surface-alt)', border: 'var(--border)', text: 'var(--text)', muted: 'var(--text-muted)', accent: 'var(--accent)', 'accent-text': 'var(--accent-text)', danger: 'var(--danger)', success: 'var(--success)', code: 'var(--code-bg)', }, fontFamily: { sans: ['"IBM Plex Sans"', 'system-ui', 'sans-serif'], mono: ['"IBM Plex Mono"', 'ui-monospace', 'monospace'], }, }, }, }; </script> <link rel="stylesheet" href="/ui/assets/style.css"> </head> <body class="min-h-screen bg-bg font-sans text-text antialiased"> <header class="sticky top-0 z-20 flex items-center justify-between gap-4 border-b border-border bg-surface px-4 py-3 sm:px-6"> <h1 class="font-mono text-sm font-semibold tracking-tight sm:text-base"> <span class="text-accent">$</span> rustapi <span class="font-normal text-muted">admin</span> </h1> <div class="flex items-center gap-1"> <button id="theme-toggle" type="button" class="rounded-md p-2 text-muted transition-colors hover:bg-surface-alt hover:text-text" title="Переключить тему">🌓</button> <button id="logout-btn" class="hidden rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted transition-colors hover:bg-surface-alt hover:text-text">Выйти</button> </div> </header> <!-- Первый запуск: создание администратора --> <div id="view-setup" class="hidden"> <div class="mx-auto mt-[10vh] max-w-sm rounded-lg border border-border bg-surface p-7 shadow-sm"> <h2 class="mb-1 text-lg font-semibold">Создать администратора</h2> <p class="notice">Это первый запуск — учётной записи ещё нет. Логин и пароль ниже будут нужны только для входа в эту панель, они не имеют отношения к токенам API из вкладки «Токены».</p> <form id="setup-form" class="mt-4 space-y-3"> <div> <label class="mb-1 block text-xs text-muted">Логин</label> <input type="text" id="setup-username" required autocomplete="username"> </div> <div> <label class="mb-1 block text-xs text-muted">Пароль (минимум 8 символов)</label> <input type="password" id="setup-password" required autocomplete="new-password"> </div> <div class="pt-2"> <button type="submit" class="w-full rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-text transition-opacity hover:opacity-90">Создать и войти</button> </div> </form> <div id="setup-error"></div> </div> </div> <!-- Вход --> <div id="view-login" class="hidden"> <div class="mx-auto mt-[10vh] max-w-sm rounded-lg border border-border bg-surface p-7 shadow-sm"> <h2 class="mb-1 text-lg font-semibold">Вход</h2> <form id="login-form" class="mt-4 space-y-3"> <div> <label class="mb-1 block text-xs text-muted">Логин</label> <input type="text" id="login-username" required autocomplete="username"> </div> <div> <label class="mb-1 block text-xs text-muted">Пароль</label> <input type="password" id="login-password" required autocomplete="current-password"> </div> <div class="pt-2"> <button type="submit" class="w-full rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-text transition-opacity hover:opacity-90">Войти</button> </div> </form> <div id="login-error"></div> </div> </div> <!-- Основная панель --> <div id="view-dashboard" class="hidden"> <main class="mx-auto max-w-5xl px-4 py-6 sm:px-6 md:grid md:grid-cols-[180px_1fr] md:items-start md:gap-8 md:py-8"> <nav class="-mx-4 mb-6 flex gap-1 overflow-x-auto border-b border-border px-4 pb-px sm:-mx-6 sm:px-6 md:sticky md:top-[73px] md:mx-0 md:mb-0 md:flex-col md:overflow-visible md:border-b-0 md:border-r md:px-0 md:pb-0 md:pr-4"> <button type="button" class="tab-btn shrink-0 whitespace-nowrap border-b-2 border-transparent px-3 py-2.5 text-sm font-medium text-muted transition-colors hover:text-text [&.active]:border-accent [&.active]:font-semibold [&.active]:text-text md:rounded-md md:border-b-0 md:border-l-2 md:px-3 md:py-2 md:text-left md:hover:bg-surface-alt md:[&.active]:bg-surface-alt" data-tab="connections">Подключения</button> <button type="button" class="tab-btn shrink-0 whitespace-nowrap border-b-2 border-transparent px-3 py-2.5 text-sm font-medium text-muted transition-colors hover:text-text [&.active]:border-accent [&.active]:font-semibold [&.active]:text-text md:rounded-md md:border-b-0 md:border-l-2 md:px-3 md:py-2 md:text-left md:hover:bg-surface-alt md:[&.active]:bg-surface-alt" data-tab="tokens">Токены</button> <button type="button" class="tab-btn shrink-0 whitespace-nowrap border-b-2 border-transparent px-3 py-2.5 text-sm font-medium text-muted transition-colors hover:text-text [&.active]:border-accent [&.active]:font-semibold [&.active]:text-text md:rounded-md md:border-b-0 md:border-l-2 md:px-3 md:py-2 md:text-left md:hover:bg-surface-alt md:[&.active]:bg-surface-alt" data-tab="endpoints">Эндпоинты</button> <button type="button" class="tab-btn shrink-0 whitespace-nowrap border-b-2 border-transparent px-3 py-2.5 text-sm font-medium text-muted transition-colors hover:text-text [&.active]:border-accent [&.active]:font-semibold [&.active]:text-text md:rounded-md md:border-b-0 md:border-l-2 md:px-3 md:py-2 md:text-left md:hover:bg-surface-alt md:[&.active]:bg-surface-alt" data-tab="scripts">Скрипты</button> </nav> <div class="min-w-0"> <!-- Подключения --> <section id="panel-connections" class="panel hidden space-y-4 [&.active]:block"> <div class="rounded-lg border border-border bg-surface p-4 shadow-sm sm:p-5"> <div class="mb-3 flex items-center justify-between"><h3 class="text-sm font-semibold">Текущие подключения</h3></div> <div class="overflow-x-auto"> <table class="w-full text-left text-[13px]"> <thead> <tr class="border-b border-border text-[11px] uppercase tracking-wide text-muted"> <th class="px-3 py-2 font-semibold">Имя</th><th class="px-3 py-2 font-semibold">Драйвер</th><th class="px-3 py-2 font-semibold">URL</th><th class="px-3 py-2"></th> </tr> </thead> <tbody id="connections-table"></tbody> </table> </div> <p class="notice">Изменения здесь применяются только после перезапуска сервера.</p> </div> <div class="rounded-lg border border-border bg-surface p-4 shadow-sm sm:p-5"> <div class="mb-3"><h3 class="text-sm font-semibold">Добавить подключение</h3></div> <form id="connection-form" class="space-y-3"> <div class="grid gap-3 sm:grid-cols-2"> <div> <label class="mb-1 block text-xs text-muted">Имя</label> <input type="text" id="conn-name" placeholder="main_db" required> </div> <div> <label class="mb-1 block text-xs text-muted">Драйвер</label> <select id="conn-driver"> <option value="sqlite">sqlite</option> <option value="postgres">postgres</option> <option value="mysql">mysql</option> </select> </div> </div> <div> <label class="mb-1 block text-xs text-muted">URL</label> <input type="text" id="conn-url" placeholder="sqlite://data.db" required> </div> <div class="pt-1"> <button type="submit" class="rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-text transition-opacity hover:opacity-90">Сохранить</button> </div> </form> <div id="connection-error"></div> </div> </section> <!-- Токены --> <section id="panel-tokens" class="panel hidden space-y-4 [&.active]:block"> <div class="rounded-lg border border-border bg-surface p-4 shadow-sm sm:p-5"> <div class="mb-3"><h3 class="text-sm font-semibold">Токены доступа к API</h3></div> <div class="overflow-x-auto"> <table class="w-full text-left text-[13px]"> <thead> <tr class="border-b border-border text-[11px] uppercase tracking-wide text-muted"> <th class="px-3 py-2 font-semibold">Токен</th><th class="px-3 py-2"></th> </tr> </thead> <tbody id="tokens-table"></tbody> </table> </div> <p class="notice">Этими токенами защищены все объявленные эндпоинты (не эта админка). Изменения — после перезапуска сервера.</p> </div> <div class="rounded-lg border border-border bg-surface p-4 shadow-sm sm:p-5"> <div class="mb-3"><h3 class="text-sm font-semibold">Добавить токен</h3></div> <form id="token-form" class="space-y-3"> <div> <label class="mb-1 block text-xs text-muted">Значение (или <code class="mono">${ENV_VAR}</code>)</label> <input type="text" id="token-value" required> </div> <div class="pt-1"> <button type="submit" class="rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-text transition-opacity hover:opacity-90">Добавить</button> </div> </form> <div id="token-error"></div> </div> </section> <!-- Эндпоинты --> <section id="panel-endpoints" class="panel hidden space-y-4 [&.active]:block"> <div class="rounded-lg border border-border bg-surface p-4 shadow-sm sm:p-5"> <div class="mb-3 flex flex-wrap items-center justify-between gap-2"> <h3 class="text-sm font-semibold">Эндпоинты</h3> <div class="flex gap-2"> <button type="button" id="new-crud-btn" class="rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted transition-colors hover:bg-surface-alt hover:text-text">+ CRUD</button> <button type="button" id="new-script-btn" class="rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted transition-colors hover:bg-surface-alt hover:text-text">+ Скрипт</button> </div> </div> <div class="overflow-x-auto"> <table class="w-full text-left text-[13px]"> <thead> <tr class="border-b border-border text-[11px] uppercase tracking-wide text-muted"> <th class="px-3 py-2 font-semibold">Метод</th><th class="px-3 py-2 font-semibold">Путь</th><th class="px-3 py-2 font-semibold">Тип</th><th class="px-3 py-2"></th><th class="px-3 py-2"></th> </tr> </thead> <tbody id="endpoints-table"></tbody> </table> </div> </div> <div class="hidden rounded-lg border border-border bg-surface p-4 shadow-sm sm:p-5" id="crud-form-card"> <div class="mb-3"><h3 id="crud-form-title" class="text-sm font-semibold">Новый CRUD-эндпоинт</h3></div> <form id="crud-form" class="space-y-3"> <div class="grid gap-3 sm:grid-cols-2"> <div> <label class="mb-1 block text-xs text-muted">Метод</label> <select id="crud-method"> <option>GET</option><option>POST</option><option>PUT</option><option>PATCH</option><option>DELETE</option> </select> </div> <div> <label class="mb-1 block text-xs text-muted">Путь</label> <input type="text" id="crud-path" placeholder="/products/{id}" required> </div> </div> <div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4"> <div> <label class="mb-1 block text-xs text-muted">Подключение</label> <select id="crud-connection"></select> </div> <div> <label class="mb-1 block text-xs text-muted">Таблица</label> <input type="text" id="crud-table" placeholder="products" required> </div> <div> <label class="mb-1 block text-xs text-muted">Operation</label> <select id="crud-operation"> <option value="create">create</option> <option value="read">read</option> <option value="list">list</option> <option value="update">update</option> <option value="delete">delete</option> </select> </div> <div> <label class="mb-1 block text-xs text-muted">Primary key</label> <input type="text" id="crud-primary-key" placeholder="id" value="id"> </div> </div> <div class="flex items-center justify-between pt-2"><h3 class="text-sm font-semibold">Input</h3><button type="button" id="crud-input-add" class="rounded-md border border-border px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-alt hover:text-text">+ поле</button></div> <div id="crud-input-fields" class="space-y-1.5"></div> <div class="flex items-center justify-between pt-2"><h3 class="text-sm font-semibold">Output</h3><button type="button" id="crud-output-add" class="rounded-md border border-border px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-alt hover:text-text">+ поле</button></div> <div id="crud-output-fields" class="space-y-1.5"></div> <div class="flex gap-2 pt-2"> <button type="submit" class="rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-text transition-opacity hover:opacity-90">Сохранить</button> <button type="button" id="crud-cancel" class="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted hover:bg-surface-alt hover:text-text">Отмена</button> </div> </form> <div id="crud-error"></div> </div> <div class="hidden rounded-lg border border-border bg-surface p-4 shadow-sm sm:p-5" id="script-form-card"> <div class="mb-3"><h3 id="script-form-title" class="text-sm font-semibold">Новый скриптовый эндпоинт</h3></div> <form id="script-endpoint-form" class="space-y-3"> <div class="grid gap-3 sm:grid-cols-3"> <div> <label class="mb-1 block text-xs text-muted">Метод</label> <select id="script-method"> <option>GET</option><option>POST</option><option>PUT</option><option>PATCH</option><option>DELETE</option> </select> </div> <div> <label class="mb-1 block text-xs text-muted">Путь</label> <input type="text" id="script-path" placeholder="/auth/login" required> </div> <div> <label class="mb-1 block text-xs text-muted">Файл скрипта</label> <input type="text" id="script-filename" placeholder="login.rhai" required> </div> </div> <div> <label class="mb-1 block text-xs text-muted">Код скрипта</label> <textarea class="code" id="script-source" spellcheck="false">let user = db.query_one("main_db", "SELECT id FROM users WHERE 1=0", []); response.json(#{ ok: true })</textarea> </div> <div class="flex items-center justify-between pt-2"><h3 class="text-sm font-semibold">Input</h3><button type="button" id="script-input-add" class="rounded-md border border-border px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-alt hover:text-text">+ поле</button></div> <div id="script-input-fields" class="space-y-1.5"></div> <div class="flex items-center justify-between pt-2"><h3 class="text-sm font-semibold">Output</h3><button type="button" id="script-output-add" class="rounded-md border border-border px-2.5 py-1 text-xs font-medium text-muted hover:bg-surface-alt hover:text-text">+ поле</button></div> <div id="script-output-fields" class="space-y-1.5"></div> <p class="notice">Поля используются только для схемы в /docs (Swagger) — сам скрипт их не проверяет.</p> <div class="flex gap-2 pt-2"> <button type="submit" class="rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-text transition-opacity hover:opacity-90">Сохранить</button> <button type="button" id="script-endpoint-cancel" class="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted hover:bg-surface-alt hover:text-text">Отмена</button> </div> </form> <div id="script-endpoint-error"></div> </div> </section> <!-- Скрипты --> <section id="panel-scripts" class="panel hidden space-y-4 [&.active]:block"> <div class="rounded-lg border border-border bg-surface p-4 shadow-sm sm:p-5"> <div class="mb-3"><h3 class="text-sm font-semibold">Файлы скриптов</h3></div> <div class="overflow-x-auto"> <table class="w-full text-left text-[13px]"> <thead> <tr class="border-b border-border text-[11px] uppercase tracking-wide text-muted"> <th class="px-3 py-2 font-semibold">Файл</th><th class="px-3 py-2"></th> </tr> </thead> <tbody id="scripts-table"></tbody> </table> </div> </div> <div class="hidden rounded-lg border border-border bg-surface p-4 shadow-sm sm:p-5" id="script-edit-card"> <div class="mb-3"><h3 id="script-edit-title" class="text-sm font-semibold">Редактирование скрипта</h3></div> <textarea class="code" id="script-edit-source" spellcheck="false"></textarea> <div class="flex gap-2 pt-3"> <button id="script-save-btn" class="rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-text transition-opacity hover:opacity-90">Сохранить</button> <button id="script-edit-cancel" class="rounded-md border border-border px-4 py-2 text-sm font-medium text-muted hover:bg-surface-alt hover:text-text">Отмена</button> </div> <div id="script-edit-error"></div> </div> </section> </div> </main> </div> <script src="/ui/assets/app.js"></script> </body> </html>