Initial commit
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TNProxy - Telegram Notification Proxy</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; padding: 20px; }
|
||||
.container { max-width: 900px; margin: 0 auto; }
|
||||
h1 { color: #333; margin-bottom: 20px; }
|
||||
.btn { display: inline-block; padding: 10px 20px; background: #0088cc; color: white; text-decoration: none; border-radius: 5px; border: none; cursor: pointer; font-size: 14px; }
|
||||
.btn:hover { background: #006699; }
|
||||
.btn-secondary { background: #666; }
|
||||
.btn-danger { background: #dc3545; }
|
||||
.btn-danger:hover { background: #c82333; }
|
||||
.btn-sm { padding: 5px 10px; font-size: 12px; }
|
||||
table { width: 100%; background: white; border-collapse: collapse; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
||||
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
|
||||
th { background: #f8f9fa; font-weight: 600; }
|
||||
.status { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; }
|
||||
.status.active { background: #d4edda; color: #155724; }
|
||||
.status.inactive { background: #f8d7da; color: #721c24; }
|
||||
.token { font-family: monospace; background: #f8f9fa; padding: 4px 8px; border-radius: 4px; font-size: 12px; }
|
||||
.card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
label { display: block; margin-bottom: 5px; font-weight: 500; }
|
||||
input[type="text"] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; }
|
||||
.modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); align-items: center; justify-content: center; }
|
||||
.modal.show { display: flex; }
|
||||
.modal-content { background: white; padding: 20px; border-radius: 8px; width: 400px; }
|
||||
.modal-actions { margin-top: 15px; text-align: right; }
|
||||
.nav { margin-bottom: 20px; }
|
||||
.nav a { margin-right: 15px; color: #0088cc; text-decoration: none; }
|
||||
.nav a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>TNProxy - Dashboard</h1>
|
||||
|
||||
<div class="nav">
|
||||
<a href="/adtn">Приложения</a>
|
||||
<a href="/adtn/logs">Логи</a>
|
||||
<a href="/adtn/api">API Docs</a>
|
||||
<a href="/adtn/password">Пароль</a>
|
||||
<a href="/adtn/export">Экспорт</a>
|
||||
<a href="#" id="importBtn">Импорт</a>
|
||||
<a href="#" id="logout">Выход</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<h2>Приложения</h2>
|
||||
<button class="btn" onclick="showModal()">+ Добавить приложение</button>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Название</th>
|
||||
<th>Token</th>
|
||||
<th>Chat ID</th>
|
||||
<th>Статус</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for app in apps %}
|
||||
<tr>
|
||||
<td>{{ app.id }}</td>
|
||||
<td>{{ app.name }}</td>
|
||||
<td><span class="token">{{ app.token }}</span></td>
|
||||
<td>{{ app.telegram_chat_id }}</td>
|
||||
<td>
|
||||
<span class="status {{ 'active' if app.is_active else 'inactive' }}">
|
||||
{{ 'Активно' if app.is_active else 'Отключено' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary btn-sm" onclick="toggleApp({{ app.id }})">
|
||||
{{ 'Отключить' if app.is_active else 'Включить' }}
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteApp({{ app.id }})">Удалить</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" style="text-align: center; color: #666;">Нет приложений</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="addModal">
|
||||
<div class="modal-content">
|
||||
<h3>Добавить приложение</h3>
|
||||
<form id="addForm">
|
||||
<div class="form-group">
|
||||
<label>Название</label>
|
||||
<input type="text" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Telegram Bot Token</label>
|
||||
<input type="text" name="telegram_bot_token" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Telegram Chat ID</label>
|
||||
<input type="text" name="telegram_chat_id" required>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn btn-secondary" onclick="hideModal()">Отмена</button>
|
||||
<button type="submit" class="btn">Создать</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showModal() { document.getElementById('addModal').classList.add('show'); }
|
||||
function hideModal() { document.getElementById('addModal').classList.remove('show'); }
|
||||
|
||||
document.getElementById('addForm').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const data = Object.fromEntries(formData);
|
||||
|
||||
const res = await fetch('/adtn/applications', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const result = await res.json();
|
||||
|
||||
if (result.token) {
|
||||
alert('Приложение создано!\n\nToken: ' + result.token + '\n\nСкопируйте токен - он больше не будет показан!');
|
||||
hideModal();
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteApp(id) {
|
||||
if (!confirm('Удалить приложение?')) return;
|
||||
await fetch('/adtn/applications/' + id, { method: 'DELETE' });
|
||||
location.reload();
|
||||
}
|
||||
|
||||
async function toggleApp(id) {
|
||||
await fetch('/adtn/applications/' + id + '/toggle', { method: 'POST' });
|
||||
location.reload();
|
||||
}
|
||||
|
||||
document.getElementById('logout').onclick = async (e) => {
|
||||
e.preventDefault();
|
||||
await fetch('/logout', { method: 'POST' });
|
||||
window.location.href = '/logtn';
|
||||
};
|
||||
|
||||
document.getElementById('importBtn').onclick = async (e) => {
|
||||
e.preventDefault();
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = async (ev) => {
|
||||
const file = ev.target.files[0];
|
||||
if (!file) return;
|
||||
const text = await file.text();
|
||||
const res = await fetch('/adtn/import', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: text
|
||||
});
|
||||
const result = await res.json();
|
||||
if (result.success) {
|
||||
alert('Настройки импортированы!');
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Ошибка: ' + result.error);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user