831 lines
60 KiB
HTML
831 lines
60 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>PostgreSQL SensoLab Panel</title>
|
||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script src="https://unpkg.com/lucide@latest"></script>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="assets/app.css">
|
||
</head>
|
||
<body class="bg-slate-50 text-slate-800 overflow-hidden" data-theme="light">
|
||
|
||
<!-- Login Screen -->
|
||
<div id="loginScreen" class="fixed inset-0 z-50 flex items-center justify-center bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900">
|
||
<div class="w-full max-w-md p-8 glass-panel rounded-2xl shadow-2xl">
|
||
<div class="text-center mb-8">
|
||
<div class="inline-flex items-center justify-center w-16 h-16 bg-blue-600 rounded-2xl mb-4 shadow-lg shadow-blue-600/30">
|
||
<i data-lucide="database" class="w-8 h-8 text-white"></i>
|
||
</div>
|
||
<h1 class="text-2xl font-bold text-slate-800">PostgreSQL SensoLab</h1>
|
||
<p class="text-slate-500 mt-2">Войдите для управления базой данных</p>
|
||
</div>
|
||
|
||
<form id="loginForm" class="space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Логин администратора</label>
|
||
<input type="text" id="adminUser" value="admin" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all" placeholder="admin">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Пароль</label>
|
||
<input type="password" id="adminPass" value="admin" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all" placeholder="••••••••">
|
||
</div>
|
||
<button type="submit" class="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-all transform hover:scale-[1.02] shadow-lg shadow-blue-600/30 flex items-center justify-center gap-2">
|
||
<span id="loginText">Войти</span>
|
||
<div id="loginLoader" class="loader hidden" style="width: 20px; height: 20px; border-width: 2px;"></div>
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Application -->
|
||
<div id="mainApp" class="hidden h-screen flex flex-col">
|
||
<!-- Header -->
|
||
<div id="mobileBackdrop" class="hidden fixed inset-0 bg-slate-950/50 z-20" onclick="app.closeSidebar()"></div>
|
||
<header id="mainHeader" class="bg-white border-b border-slate-200 h-16 flex items-center justify-between px-6 shadow-sm z-10">
|
||
<div class="flex items-center gap-4">
|
||
<button onclick="app.toggleSidebar()" class="lg:hidden p-2 rounded-lg bg-slate-100 text-slate-700">
|
||
<i data-lucide="menu" class="w-4 h-4"></i>
|
||
</button>
|
||
<div class="flex items-center gap-2 text-blue-600">
|
||
<i data-lucide="database" class="w-6 h-6"></i>
|
||
<span class="font-bold text-lg">PostgreSQL SensoLab</span>
|
||
</div>
|
||
<div class="h-6 w-px bg-slate-200 mx-2"></div>
|
||
<div class="flex items-center gap-2 text-sm text-slate-600 bg-slate-100 px-3 py-1 rounded-full">
|
||
<div class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||
<span id="connectionStatus">localhost:5432/postgres</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex items-center gap-3">
|
||
<select id="themeSelect" onchange="app.setTheme(this.value)" class="border border-slate-200 rounded-lg px-3 py-2 text-sm bg-white text-slate-700">
|
||
<option value="light">Light</option>
|
||
<option value="dark">Dark</option>
|
||
<option value="system">System</option>
|
||
</select>
|
||
<button id="managementButton" onclick="app.showManagementPanel('settings')" class="hidden flex items-center gap-2 px-4 py-2 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors text-sm font-medium">
|
||
<i data-lucide="panel-left-open" class="w-4 h-4"></i>
|
||
Menu
|
||
</button>
|
||
<button id="logsButton" onclick="app.showLogsPanel()" class="hidden flex items-center gap-2 px-4 py-2 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors text-sm font-medium">
|
||
<i data-lucide="scroll-text" class="w-4 h-4"></i>
|
||
Logs
|
||
</button>
|
||
<button onclick="app.showSQLPanel()" class="flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 transition-colors text-sm font-medium">
|
||
<i data-lucide="terminal" class="w-4 h-4"></i>
|
||
SQL Query
|
||
</button>
|
||
<div class="h-6 w-px bg-slate-200"></div>
|
||
<div id="roleBadge" class="px-3 py-1 rounded-full bg-slate-100 text-slate-600 text-xs font-semibold uppercase tracking-wide"></div>
|
||
<button onclick="app.logout()" class="flex items-center gap-2 px-4 py-2 text-slate-600 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors text-sm font-medium">
|
||
<i data-lucide="log-out" class="w-4 h-4"></i>
|
||
Выйти
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="flex flex-1 overflow-hidden">
|
||
<!-- Sidebar -->
|
||
<aside id="sidebar" class="w-64 bg-slate-900 text-slate-300 flex flex-col">
|
||
<div class="p-4 border-b border-slate-800">
|
||
<div class="flex items-center justify-between mb-2">
|
||
<span class="text-xs font-semibold text-slate-500 uppercase tracking-wider">Таблицы</span>
|
||
<button onclick="app.showCreateTableModal()" class="p-1 hover:bg-slate-800 rounded transition-colors" title="Создать таблицу">
|
||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||
</button>
|
||
</div>
|
||
<input type="text" id="tableSearch" placeholder="Поиск..." class="w-full bg-slate-800 border border-slate-700 rounded px-3 py-1.5 text-sm focus:outline-none focus:border-blue-500 text-slate-300" oninput="app.filterTables(this.value)">
|
||
</div>
|
||
|
||
<div id="tableList" class="flex-1 overflow-y-auto py-2">
|
||
<!-- Tables will be rendered here -->
|
||
</div>
|
||
|
||
<div class="p-4 border-t border-slate-800">
|
||
<div class="flex items-center gap-2 text-xs text-slate-500">
|
||
<i data-lucide="hard-drive" class="w-4 h-4"></i>
|
||
<span id="dbSize">24.5 MB</span>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- Main Content -->
|
||
<main class="flex-1 flex flex-col bg-slate-50 overflow-hidden">
|
||
<!-- Toolbar -->
|
||
<div id="toolbar" class="bg-white border-b border-slate-200 p-4 flex items-center justify-between">
|
||
<div class="flex items-center gap-4">
|
||
<h2 id="currentTableTitle" class="text-xl font-semibold text-slate-800">Выберите таблицу</h2>
|
||
<div id="tableActions" class="hidden flex items-center gap-2">
|
||
<button onclick="app.showAddRecordModal()" class="flex items-center gap-2 px-3 py-1.5 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors text-sm">
|
||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||
Добавить запись
|
||
</button>
|
||
<button onclick="app.showTableStructure()" class="flex items-center gap-2 px-3 py-1.5 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors text-sm">
|
||
<i data-lucide="settings" class="w-4 h-4"></i>
|
||
Структура
|
||
</button>
|
||
<button onclick="app.showIndexesModal()" class="flex items-center gap-2 px-3 py-1.5 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors text-sm">
|
||
<i data-lucide="list-tree" class="w-4 h-4"></i>
|
||
Индексы
|
||
</button>
|
||
<button onclick="app.showMoveTableModal()" class="flex items-center gap-2 px-3 py-1.5 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors text-sm">
|
||
<i data-lucide="folder-input" class="w-4 h-4"></i>
|
||
Move
|
||
</button>
|
||
<button onclick="app.deleteTable()" class="flex items-center gap-2 px-3 py-1.5 bg-red-50 text-red-600 rounded-lg hover:bg-red-100 transition-colors text-sm">
|
||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||
Удалить
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center gap-3" id="recordControls">
|
||
<input id="recordSearch" type="text" placeholder="Поиск по записям..." class="w-56 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||
<button onclick="app.toggleFilters()" class="flex items-center gap-2 px-4 py-2 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 transition-colors text-sm">
|
||
<i data-lucide="filter" class="w-4 h-4"></i>
|
||
Фильтры
|
||
</button>
|
||
<div class="text-sm text-slate-600" id="recordCount">
|
||
<!-- Record count will be shown here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Content Area -->
|
||
<div id="contentArea" class="flex-1 overflow-auto p-6">
|
||
<!-- Dynamic content: Table data, SQL editor, Structure, etc. -->
|
||
<div id="emptyState" class="flex flex-col items-center justify-center h-full text-slate-400">
|
||
<i data-lucide="database" class="w-16 h-16 mb-4 opacity-20"></i>
|
||
<p class="text-lg">Выберите таблицу из списка слева или выполните SQL-запрос</p>
|
||
</div>
|
||
|
||
<div id="dataGrid" class="hidden bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||
<div class="overflow-x-auto">
|
||
<table class="w-full text-sm text-left">
|
||
<thead class="bg-slate-50 text-slate-600 font-medium border-b border-slate-200">
|
||
<tr id="tableHeaders"></tr>
|
||
</thead>
|
||
<tbody id="filterRow" class="hidden bg-blue-50 border-b border-slate-200">
|
||
<tr id="filterInputs"></tr>
|
||
</tbody>
|
||
<tbody id="tableBody" class="divide-y divide-slate-200"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="p-4 border-t border-slate-200 flex items-center justify-between bg-slate-50">
|
||
<div class="flex items-center gap-2">
|
||
<button onclick="app.changePage(-1)" class="p-2 hover:bg-slate-200 rounded-lg disabled:opacity-50" id="prevPage">
|
||
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||
</button>
|
||
<span class="text-sm text-slate-600">Страница <span id="currentPage">1</span> из <span id="totalPages">1</span></span>
|
||
<button onclick="app.changePage(1)" class="p-2 hover:bg-slate-200 rounded-lg disabled:opacity-50" id="nextPage">
|
||
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||
</button>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<select onchange="app.changeLimit(this.value)" class="bg-white border border-slate-300 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:border-blue-500">
|
||
<option value="10">10 строк</option>
|
||
<option value="25">25 строк</option>
|
||
<option value="50">50 строк</option>
|
||
<option value="100">100 строк</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SQL Editor Panel -->
|
||
<div id="sqlPanel" class="hidden h-full flex flex-col gap-4">
|
||
<div class="bg-slate-900 rounded-xl overflow-hidden flex flex-col flex-1 shadow-lg">
|
||
<div class="bg-slate-800 px-4 py-2 flex items-center justify-between border-b border-slate-700">
|
||
<div class="flex items-center gap-2 text-slate-300 text-sm">
|
||
<i data-lucide="terminal" class="w-4 h-4"></i>
|
||
<span>SQL Query Editor</span>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<button onclick="app.formatSQL()" class="px-3 py-1 text-xs bg-slate-700 hover:bg-slate-600 text-slate-300 rounded transition-colors">
|
||
Форматировать
|
||
</button>
|
||
<button onclick="app.clearSQL()" class="px-3 py-1 text-xs bg-slate-700 hover:bg-slate-600 text-slate-300 rounded transition-colors">
|
||
Очистить
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<textarea id="sqlEditor" class="flex-1 bg-slate-900 text-slate-300 p-4 font-mono text-sm resize-none outline-none" placeholder="-- Введите SQL запрос здесь
|
||
SELECT * FROM users LIMIT 10;"></textarea>
|
||
</div>
|
||
<div class="flex flex-wrap gap-2">
|
||
<button onclick="app.applySQLTemplate('select')" class="px-3 py-2 bg-slate-800 text-slate-200 rounded-lg text-xs">SELECT *</button>
|
||
<button onclick="app.applySQLTemplate('count')" class="px-3 py-2 bg-slate-800 text-slate-200 rounded-lg text-xs">COUNT rows</button>
|
||
<button onclick="app.applySQLTemplate('insert')" class="px-3 py-2 bg-slate-800 text-slate-200 rounded-lg text-xs">INSERT row</button>
|
||
<button onclick="app.applySQLTemplate('update')" class="px-3 py-2 bg-slate-800 text-slate-200 rounded-lg text-xs">UPDATE row</button>
|
||
<button onclick="app.applySQLTemplate('delete')" class="px-3 py-2 bg-slate-800 text-slate-200 rounded-lg text-xs">DELETE row</button>
|
||
<button onclick="app.applySQLTemplate('schema')" class="px-3 py-2 bg-slate-800 text-slate-200 rounded-lg text-xs">Describe table</button>
|
||
</div>
|
||
<div class="flex justify-end">
|
||
<button onclick="app.executeSQL()" class="flex items-center gap-2 px-6 py-2.5 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-all shadow-lg shadow-blue-600/20">
|
||
<i data-lucide="play" class="w-4 h-4"></i>
|
||
Выполнить (Ctrl+Enter)
|
||
</button>
|
||
</div>
|
||
<div id="sqlResults" class="hidden bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden flex-1">
|
||
<div class="p-4 border-b border-slate-200 bg-slate-50 flex items-center justify-between">
|
||
<span class="font-medium text-slate-700">Результаты</span>
|
||
<span id="sqlStats" class="text-sm text-slate-500"></span>
|
||
</div>
|
||
<div class="overflow-x-auto max-h-96" id="sqlResultsContent"></div>
|
||
</div>
|
||
</div>
|
||
<div id="logsPanel" class="hidden h-full flex flex-col gap-4">
|
||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 p-4 flex flex-wrap items-center gap-3">
|
||
<select id="containerSelect" onchange="app.changeContainer(this.value)" class="border border-slate-300 rounded-lg px-3 py-2 text-sm min-w-56">
|
||
<option value="">Select container</option>
|
||
</select>
|
||
<button onclick="app.refreshLogs()" class="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg text-sm">Refresh</button>
|
||
<button onclick="app.toggleLogStream()" id="logStreamButton" class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm">Start live</button>
|
||
<button onclick="app.clearLogs()" class="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg text-sm">Clear</button>
|
||
<div id="logStatus" class="text-sm text-slate-500">Logs are available for admin roles.</div>
|
||
</div>
|
||
<div class="bg-slate-900 text-slate-200 rounded-xl border border-slate-800 p-4 font-mono text-sm log-terminal overflow-auto" id="logOutput">Select a container to load recent logs.</div>
|
||
</div>
|
||
<div id="managementPanel" class="hidden h-full">
|
||
<div class="grid lg:grid-cols-[240px,1fr] gap-6 h-full">
|
||
<aside class="bg-white rounded-2xl border border-slate-200 p-4 space-y-2">
|
||
<button id="managementTab-settings" onclick="app.showManagementPanel('settings')" class="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left bg-slate-100 text-slate-700">
|
||
<i data-lucide="sliders-horizontal" class="w-4 h-4"></i>
|
||
<span>Settings</span>
|
||
</button>
|
||
<button id="managementTab-backups" onclick="app.showManagementPanel('backups')" class="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left text-slate-700 hover:bg-slate-100">
|
||
<i data-lucide="archive" class="w-4 h-4"></i>
|
||
<span>Backups</span>
|
||
</button>
|
||
<button id="managementTab-users" onclick="app.showManagementPanel('users')" class="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left text-slate-700 hover:bg-slate-100">
|
||
<i data-lucide="users" class="w-4 h-4"></i>
|
||
<span>Users</span>
|
||
</button>
|
||
<button id="managementTab-audit" onclick="app.showManagementPanel('audit')" class="w-full flex items-center gap-3 px-4 py-3 rounded-xl text-left text-slate-700 hover:bg-slate-100">
|
||
<i data-lucide="history" class="w-4 h-4"></i>
|
||
<span>Audit</span>
|
||
</button>
|
||
</aside>
|
||
<section class="min-h-0">
|
||
<div id="managementSection-settings" class="hidden bg-white rounded-2xl border border-slate-200 h-full overflow-auto p-6 space-y-6">
|
||
<div>
|
||
<h3 class="text-2xl font-bold text-slate-800">System settings</h3>
|
||
<p class="text-sm text-slate-500 mt-1">Telegram, automatic backups and retention are configured here.</p>
|
||
</div>
|
||
<section class="space-y-4">
|
||
<div>
|
||
<h4 class="text-lg font-semibold text-slate-800">Automatic backups</h4>
|
||
<p class="text-sm text-slate-500">The panel creates a database archive and, optionally, a site snapshot every day.</p>
|
||
</div>
|
||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||
<input type="checkbox" id="managementSettingsBackupsEnabled" class="w-4 h-4">
|
||
Enable automatic backups
|
||
</label>
|
||
<div class="grid sm:grid-cols-3 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Time</label>
|
||
<input type="time" id="managementSettingsBackupTime" class="w-full px-4 py-2 border border-slate-300 rounded-lg">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Keep last archives</label>
|
||
<input type="number" id="managementSettingsKeepLast" min="1" max="90" class="w-full px-4 py-2 border border-slate-300 rounded-lg">
|
||
</div>
|
||
<div class="flex items-end">
|
||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||
<input type="checkbox" id="managementSettingsIncludeAppSnapshot" class="w-4 h-4">
|
||
Include site settings and audit snapshot
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="space-y-4">
|
||
<div>
|
||
<h4 class="text-lg font-semibold text-slate-800">Telegram notifications</h4>
|
||
<p class="text-sm text-slate-500">Used for backup errors, restore events and scheduled notifications.</p>
|
||
</div>
|
||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||
<input type="checkbox" id="managementSettingsTelegramEnabled" class="w-4 h-4">
|
||
Enable Telegram notifications
|
||
</label>
|
||
<div class="grid sm:grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Bot token</label>
|
||
<input type="text" id="managementSettingsTelegramToken" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="123456:ABC...">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Chat ID</label>
|
||
<input type="text" id="managementSettingsTelegramChatId" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="-1001234567890">
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<div class="flex justify-end">
|
||
<button onclick="app.saveSettings()" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">Save settings</button>
|
||
</div>
|
||
</div>
|
||
<div id="managementSection-backups" class="hidden bg-white rounded-2xl border border-slate-200 h-full overflow-auto p-6 space-y-4">
|
||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||
<div>
|
||
<h3 class="text-2xl font-bold text-slate-800">Backups</h3>
|
||
<p class="text-sm text-slate-500 mt-1">Archives contain the SQL dump and, if enabled, the application snapshot.</p>
|
||
</div>
|
||
<button onclick="app.createBackup()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">Create archive</button>
|
||
</div>
|
||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||
<input type="checkbox" id="managementRestoreAppSnapshot" class="w-4 h-4" checked>
|
||
Restore users, settings and audit snapshot together with the database
|
||
</label>
|
||
<div id="managementBackupsList" class="space-y-3"></div>
|
||
</div>
|
||
<div id="managementSection-users" class="hidden bg-white rounded-2xl border border-slate-200 h-full overflow-auto">
|
||
<div class="grid xl:grid-cols-[1.15fr,0.85fr] min-h-full">
|
||
<div class="border-r border-slate-200 overflow-auto">
|
||
<div class="p-6 border-b border-slate-200">
|
||
<h3 class="text-2xl font-bold text-slate-800">Users</h3>
|
||
<p class="text-sm text-slate-500 mt-1">Create accounts and tune access to folders, tables and destructive actions.</p>
|
||
</div>
|
||
<table class="w-full text-sm">
|
||
<thead class="bg-slate-50 border-b border-slate-200">
|
||
<tr>
|
||
<th class="text-left p-3">Username</th>
|
||
<th class="text-left p-3">Role</th>
|
||
<th class="text-left p-3">Status</th>
|
||
<th class="text-left p-3">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="managementUsersTableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="p-6 overflow-auto space-y-3">
|
||
<input type="hidden" id="managementUserEditMode" value="">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Username</label>
|
||
<input type="text" id="managementUserUsername" class="w-full px-4 py-2 border border-slate-300 rounded-lg">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Password</label>
|
||
<input type="text" id="managementUserPassword" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="Leave empty to keep current">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Role</label>
|
||
<select id="managementUserRole" class="w-full px-4 py-2 border border-slate-300 rounded-lg">
|
||
<option value="viewer">viewer</option>
|
||
<option value="moderator">moderator</option>
|
||
<option value="admin">admin</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Readable folders</label>
|
||
<details class="border border-slate-300 rounded-lg p-3">
|
||
<summary class="cursor-pointer text-sm text-slate-700">Choose folders</summary>
|
||
<div id="managementUserViewFoldersList" class="mt-3 grid gap-2"></div>
|
||
</details>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Editable tables</label>
|
||
<details class="border border-slate-300 rounded-lg p-3">
|
||
<summary class="cursor-pointer text-sm text-slate-700">Choose tables</summary>
|
||
<div id="managementUserEditTablesList" class="mt-3 grid gap-2 max-h-48 overflow-auto"></div>
|
||
</details>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Deletable folders</label>
|
||
<details class="border border-slate-300 rounded-lg p-3">
|
||
<summary class="cursor-pointer text-sm text-slate-700">Choose folders</summary>
|
||
<div id="managementUserDeleteFoldersList" class="mt-3 grid gap-2"></div>
|
||
</details>
|
||
</div>
|
||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||
<input type="checkbox" id="managementUserDisabled" class="w-4 h-4">
|
||
Disable login
|
||
</label>
|
||
<div class="flex justify-end gap-3 pt-2">
|
||
<button onclick="app.resetUserForm()" class="px-4 py-2 text-slate-600 hover:bg-slate-100 rounded-lg">Reset</button>
|
||
<button onclick="app.saveUser()" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">Save user</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="managementSection-audit" class="hidden bg-white rounded-2xl border border-slate-200 h-full overflow-auto p-6">
|
||
<div class="flex flex-wrap items-start justify-between gap-4 mb-4">
|
||
<div>
|
||
<h3 class="text-2xl font-bold text-slate-800">Audit log</h3>
|
||
<p class="text-sm text-slate-500 mt-1">Human-readable actions with time, source and actor.</p>
|
||
</div>
|
||
<button onclick="app.loadAuditLog()" class="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg">Refresh</button>
|
||
</div>
|
||
<div id="managementAuditList" class="space-y-3"></div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modals -->
|
||
|
||
<!-- Create Table Modal -->
|
||
<div id="createTableModal" class="hidden fixed inset-0 z-40 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col fade-in">
|
||
<div class="p-6 border-b border-slate-200 flex items-center justify-between">
|
||
<h3 class="text-xl font-bold text-slate-800">Создать новую таблицу</h3>
|
||
<button onclick="app.closeModal('createTableModal')" class="p-2 hover:bg-slate-100 rounded-lg transition-colors">
|
||
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
|
||
</button>
|
||
</div>
|
||
<div class="p-6 overflow-y-auto flex-1">
|
||
<div class="mb-4">
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Папка</label>
|
||
<input type="text" id="newTableFolder" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" placeholder="frontend">
|
||
<p class="text-xs text-slate-500 mt-1">Если указано, имя таблицы будет создано как <code>папка__имя</code>. Оставьте пустым для создания в корне.</p>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Название таблицы</label>
|
||
<input type="text" id="newTableName" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" placeholder="users">
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<div class="flex items-center justify-between mb-2">
|
||
<label class="block text-sm font-medium text-slate-700">Колонки</label>
|
||
<button onclick="app.addColumnField()" class="text-sm text-blue-600 hover:text-blue-700 font-medium">+ Добавить колонку</button>
|
||
</div>
|
||
<div id="columnsContainer" class="space-y-2">
|
||
<!-- Column fields will be added here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="p-6 border-t border-slate-200 bg-slate-50 flex justify-end gap-3">
|
||
<button onclick="app.closeModal('createTableModal')" class="px-4 py-2 text-slate-600 hover:bg-slate-200 rounded-lg transition-colors">Отмена</button>
|
||
<button onclick="app.createTable()" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors shadow-lg shadow-blue-600/20">Создать таблицу</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add/Edit Record Modal -->
|
||
<div id="recordModal" class="hidden fixed inset-0 z-40 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-hidden flex flex-col fade-in">
|
||
<div class="p-6 border-b border-slate-200 flex items-center justify-between">
|
||
<h3 id="recordModalTitle" class="text-xl font-bold text-slate-800">Добавить запись</h3>
|
||
<button onclick="app.closeModal('recordModal')" class="p-2 hover:bg-slate-100 rounded-lg transition-colors">
|
||
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
|
||
</button>
|
||
</div>
|
||
<div id="recordForm" class="p-6 overflow-y-auto flex-1 space-y-4">
|
||
<!-- Form fields will be generated here -->
|
||
</div>
|
||
<div class="p-6 border-t border-slate-200 bg-slate-50 flex justify-end gap-3">
|
||
<button onclick="app.closeModal('recordModal')" class="px-4 py-2 text-slate-600 hover:bg-slate-200 rounded-lg transition-colors">Отмена</button>
|
||
<button onclick="app.saveRecord()" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors shadow-lg shadow-blue-600/20">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Table Structure Modal -->
|
||
<div id="structureModal" class="hidden fixed inset-0 z-40 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col fade-in">
|
||
<div class="p-6 border-b border-slate-200 flex items-center justify-between">
|
||
<h3 class="text-xl font-bold text-slate-800">Структура таблицы: <span id="structureTableName"></span></h3>
|
||
<button onclick="app.closeModal('structureModal')" class="p-2 hover:bg-slate-100 rounded-lg transition-colors">
|
||
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
|
||
</button>
|
||
</div>
|
||
<div class="p-6 overflow-y-auto flex-1">
|
||
<div class="mb-4 flex justify-end">
|
||
<button onclick="app.showCreateColumnModal()" class="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm">
|
||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||
Добавить колонку
|
||
</button>
|
||
</div>
|
||
<table class="w-full text-sm">
|
||
<thead class="bg-slate-50 text-slate-600 font-medium border-b border-slate-200">
|
||
<tr>
|
||
<th class="text-left p-3">Колонка</th>
|
||
<th class="text-left p-3">Тип</th>
|
||
<th class="text-left p-3">NULL</th>
|
||
<th class="text-left p-3">По умолчанию</th>
|
||
<th class="text-left p-3">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="structureBody" class="divide-y divide-slate-200"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Indexes Modal -->
|
||
<div id="indexesModal" class="hidden fixed inset-0 z-40 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-hidden flex flex-col fade-in">
|
||
<div class="p-6 border-b border-slate-200 flex items-center justify-between">
|
||
<h3 class="text-xl font-bold text-slate-800">Индексы таблицы: <span id="indexesTableName"></span></h3>
|
||
<button onclick="app.closeModal('indexesModal')" class="p-2 hover:bg-slate-100 rounded-lg transition-colors">
|
||
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
|
||
</button>
|
||
</div>
|
||
<div class="p-6 overflow-y-auto flex-1">
|
||
<div class="mb-4 flex justify-end">
|
||
<button onclick="app.showCreateIndexModal()" class="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm">
|
||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||
Создать индекс
|
||
</button>
|
||
</div>
|
||
<table class="w-full text-sm">
|
||
<thead class="bg-slate-50 text-slate-600 font-medium border-b border-slate-200">
|
||
<tr>
|
||
<th class="text-left p-3">Название</th>
|
||
<th class="text-left p-3">Колонки</th>
|
||
<th class="text-left p-3">Тип</th>
|
||
<th class="text-left p-3">Уникальный</th>
|
||
<th class="text-left p-3">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="indexesBody" class="divide-y divide-slate-200"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Column Modal -->
|
||
<div id="columnModal" class="hidden fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-md fade-in">
|
||
<div class="p-6 border-b border-slate-200 flex items-center justify-between">
|
||
<h3 id="columnModalTitle" class="text-xl font-bold text-slate-800">Добавить колонку</h3>
|
||
<button onclick="app.closeModal('columnModal')" class="p-2 hover:bg-slate-100 rounded-lg transition-colors">
|
||
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
|
||
</button>
|
||
</div>
|
||
<div class="p-6 space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Название колонки</label>
|
||
<input type="text" id="columnName" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Тип</label>
|
||
<select id="columnType" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
|
||
<option value="VARCHAR(255)">VARCHAR(255)</option>
|
||
<option value="TEXT">TEXT</option>
|
||
<option value="INTEGER">INTEGER</option>
|
||
<option value="BIGINT">BIGINT</option>
|
||
<option value="DECIMAL">DECIMAL</option>
|
||
<option value="BOOLEAN">BOOLEAN</option>
|
||
<option value="DATE">DATE</option>
|
||
<option value="TIMESTAMP">TIMESTAMP</option>
|
||
<option value="UUID">UUID</option>
|
||
<option value="JSON">JSON</option>
|
||
<option value="JSONB">JSONB</option>
|
||
</select>
|
||
</div>
|
||
<div class="flex items-center gap-4">
|
||
<label class="flex items-center gap-2">
|
||
<input type="checkbox" id="columnNullable" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-500">
|
||
<span class="text-sm text-slate-700">NULL</span>
|
||
</label>
|
||
<label class="flex items-center gap-2">
|
||
<input type="checkbox" id="columnPrimary" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-500">
|
||
<span class="text-sm text-slate-700">Первичный ключ</span>
|
||
</label>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Значение по умолчанию</label>
|
||
<input type="text" id="columnDefault" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
|
||
</div>
|
||
</div>
|
||
<div class="p-6 border-t border-slate-200 bg-slate-50 flex justify-end gap-3">
|
||
<button onclick="app.closeModal('columnModal')" class="px-4 py-2 text-slate-600 hover:bg-slate-200 rounded-lg">Отмена</button>
|
||
<button onclick="app.saveColumn()" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Create Index Modal -->
|
||
<div id="createIndexModal" class="hidden fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-md fade-in">
|
||
<div class="p-6 border-b border-slate-200">
|
||
<h3 class="text-xl font-bold text-slate-800">Создать индекс</h3>
|
||
</div>
|
||
<div class="p-6 space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Название индекса</label>
|
||
<input type="text" id="indexName" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" placeholder="idx_users_email">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Колонки (через запятую)</label>
|
||
<input type="text" id="indexColumns" class="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" placeholder="email, created_at">
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<input type="checkbox" id="indexUnique" class="w-4 h-4 text-blue-600 rounded focus:ring-blue-500">
|
||
<label for="indexUnique" class="text-sm text-slate-700">Уникальный индекс</label>
|
||
</div>
|
||
</div>
|
||
<div class="p-6 border-t border-slate-200 bg-slate-50 flex justify-end gap-3">
|
||
<button onclick="app.closeModal('createIndexModal')" class="px-4 py-2 text-slate-600 hover:bg-slate-200 rounded-lg">Отмена</button>
|
||
<button onclick="app.createIndex()" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">Создать</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="moveTableModal" class="hidden fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-md fade-in">
|
||
<div class="p-6 border-b border-slate-200">
|
||
<h3 class="text-xl font-bold text-slate-800">Move table</h3>
|
||
</div>
|
||
<div class="p-6 space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Folder</label>
|
||
<select id="moveTableFolder" class="w-full px-4 py-2 border border-slate-300 rounded-lg"></select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Table name</label>
|
||
<input type="text" id="moveTableName" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="users">
|
||
</div>
|
||
</div>
|
||
<div class="p-6 border-t border-slate-200 bg-slate-50 flex justify-end gap-3">
|
||
<button onclick="app.closeModal('moveTableModal')" class="px-4 py-2 text-slate-600 hover:bg-slate-200 rounded-lg">Cancel</button>
|
||
<button onclick="app.moveTable()" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="usersModal" class="hidden fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col fade-in">
|
||
<div class="p-6 border-b border-slate-200 flex items-center justify-between">
|
||
<h3 class="text-xl font-bold text-slate-800">Users & access</h3>
|
||
<button onclick="app.closeModal('usersModal')" class="p-2 hover:bg-slate-100 rounded-lg transition-colors">
|
||
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
|
||
</button>
|
||
</div>
|
||
<div class="grid lg:grid-cols-[1.2fr,0.8fr] gap-0 flex-1 min-h-0">
|
||
<div class="border-r border-slate-200 overflow-auto">
|
||
<table class="w-full text-sm">
|
||
<thead class="bg-slate-50 border-b border-slate-200">
|
||
<tr>
|
||
<th class="text-left p-3">Username</th>
|
||
<th class="text-left p-3">Role</th>
|
||
<th class="text-left p-3">Status</th>
|
||
<th class="text-left p-3">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="usersTableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="p-6 overflow-auto space-y-3">
|
||
<input type="hidden" id="userEditMode" value="">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Username</label>
|
||
<input type="text" id="userUsername" class="w-full px-4 py-2 border border-slate-300 rounded-lg">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Password</label>
|
||
<input type="text" id="userPassword" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="Leave empty to keep current">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Role</label>
|
||
<select id="userRole" class="w-full px-4 py-2 border border-slate-300 rounded-lg">
|
||
<option value="viewer">viewer</option>
|
||
<option value="moderator">moderator</option>
|
||
<option value="admin">admin</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Readable folders</label>
|
||
<details class="border border-slate-300 rounded-lg p-3">
|
||
<summary class="cursor-pointer text-sm text-slate-700">Choose folders</summary>
|
||
<div id="userViewFoldersList" class="mt-3 grid gap-2"></div>
|
||
</details>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Editable tables</label>
|
||
<details class="border border-slate-300 rounded-lg p-3">
|
||
<summary class="cursor-pointer text-sm text-slate-700">Choose tables</summary>
|
||
<div id="userEditTablesList" class="mt-3 grid gap-2 max-h-48 overflow-auto"></div>
|
||
</details>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Deletable folders</label>
|
||
<details class="border border-slate-300 rounded-lg p-3">
|
||
<summary class="cursor-pointer text-sm text-slate-700">Choose folders</summary>
|
||
<div id="userDeleteFoldersList" class="mt-3 grid gap-2"></div>
|
||
</details>
|
||
</div>
|
||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||
<input type="checkbox" id="userDisabled" class="w-4 h-4">
|
||
Disable login
|
||
</label>
|
||
<div class="flex justify-end gap-3 pt-2">
|
||
<button onclick="app.resetUserForm()" class="px-4 py-2 text-slate-600 hover:bg-slate-100 rounded-lg">Reset</button>
|
||
<button onclick="app.saveUser()" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">Save user</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="auditModal" class="hidden fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-5xl max-h-[90vh] overflow-hidden flex flex-col fade-in">
|
||
<div class="p-6 border-b border-slate-200 flex items-center justify-between">
|
||
<h3 class="text-xl font-bold text-slate-800">Audit log</h3>
|
||
<button onclick="app.closeModal('auditModal')" class="p-2 hover:bg-slate-100 rounded-lg transition-colors">
|
||
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
|
||
</button>
|
||
</div>
|
||
<div class="p-6 overflow-auto">
|
||
<div class="flex justify-end mb-4">
|
||
<button onclick="app.loadAuditLog()" class="px-4 py-2 bg-slate-100 text-slate-700 rounded-lg">Refresh</button>
|
||
</div>
|
||
<div id="auditList" class="space-y-3"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="backupsModal" class="hidden fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col fade-in">
|
||
<div class="p-6 border-b border-slate-200 flex items-center justify-between">
|
||
<h3 class="text-xl font-bold text-slate-800">Backups</h3>
|
||
<button onclick="app.closeModal('backupsModal')" class="p-2 hover:bg-slate-100 rounded-lg transition-colors">
|
||
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
|
||
</button>
|
||
</div>
|
||
<div class="p-6 overflow-auto">
|
||
<div class="flex justify-between items-center mb-4">
|
||
<p class="text-sm text-slate-500">Create and download recovery snapshots.</p>
|
||
<button onclick="app.createBackup()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg">Create backup</button>
|
||
</div>
|
||
<div id="backupsList" class="space-y-3"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="settingsModal" class="hidden fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4">
|
||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-hidden flex flex-col fade-in">
|
||
<div class="p-6 border-b border-slate-200 flex items-center justify-between">
|
||
<h3 class="text-xl font-bold text-slate-800">System settings</h3>
|
||
<button onclick="app.closeModal('settingsModal')" class="p-2 hover:bg-slate-100 rounded-lg transition-colors">
|
||
<i data-lucide="x" class="w-5 h-5 text-slate-500"></i>
|
||
</button>
|
||
</div>
|
||
<div class="p-6 overflow-auto space-y-6">
|
||
<section class="space-y-4">
|
||
<div>
|
||
<h4 class="text-lg font-semibold text-slate-800">Automatic backups</h4>
|
||
<p class="text-sm text-slate-500">Daily SQL dump of PostgreSQL plus optional application snapshot.</p>
|
||
</div>
|
||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||
<input type="checkbox" id="settingsBackupsEnabled" class="w-4 h-4">
|
||
Enable automatic backups
|
||
</label>
|
||
<div class="grid sm:grid-cols-3 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Time</label>
|
||
<input type="time" id="settingsBackupTime" class="w-full px-4 py-2 border border-slate-300 rounded-lg">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Keep last days</label>
|
||
<input type="number" id="settingsKeepLast" min="1" max="90" class="w-full px-4 py-2 border border-slate-300 rounded-lg">
|
||
</div>
|
||
<div class="flex items-end">
|
||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||
<input type="checkbox" id="settingsIncludeAppSnapshot" class="w-4 h-4">
|
||
Include site settings and audit snapshot
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="space-y-4">
|
||
<div>
|
||
<h4 class="text-lg font-semibold text-slate-800">Telegram notifications</h4>
|
||
<p class="text-sm text-slate-500">Used for backup errors and important service events.</p>
|
||
</div>
|
||
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||
<input type="checkbox" id="settingsTelegramEnabled" class="w-4 h-4">
|
||
Enable Telegram notifications
|
||
</label>
|
||
<div class="grid sm:grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Bot token</label>
|
||
<input type="text" id="settingsTelegramToken" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="123456:ABC...">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Chat ID</label>
|
||
<input type="text" id="settingsTelegramChatId" class="w-full px-4 py-2 border border-slate-300 rounded-lg" placeholder="-1001234567890">
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
<div class="p-6 border-t border-slate-200 bg-slate-50 flex justify-end gap-3">
|
||
<button onclick="app.closeModal('settingsModal')" class="px-4 py-2 text-slate-600 hover:bg-slate-200 rounded-lg transition-colors">Cancel</button>
|
||
<button onclick="app.saveSettings()" class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">Save settings</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast Notifications -->
|
||
<div id="toastContainer" class="fixed bottom-6 right-6 z-50 flex flex-col gap-2"></div>
|
||
|
||
<script src="assets/app.js"></script>
|
||
</body>
|
||
</html>
|
||
|
||
|