Upload files to "templates"
This commit is contained in:
@@ -0,0 +1,95 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Смена пароля</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
.change-password-container {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 30px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('zilant.index') }}">Управление постами VK</a>
|
||||||
|
<div class="collapse navbar-collapse">
|
||||||
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<span class="navbar-text me-3">
|
||||||
|
Вы вошли как: <strong>{{ current_user.username }}</strong>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="btn btn-outline-light" href="{{ url_for('zilant.index') }}">
|
||||||
|
<i class="fas fa-home"></i> На главную
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="change-password-container">
|
||||||
|
<h2 class="text-center mb-4">Смена пароля</h2>
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url_for('zilant.change_password') }}">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="current_password" class="form-label">
|
||||||
|
<i class="fas fa-lock me-1"></i> Текущий пароль
|
||||||
|
</label>
|
||||||
|
<input type="password" class="form-control" id="current_password" name="current_password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="new_password" class="form-label">
|
||||||
|
<i class="fas fa-key me-1"></i> Новый пароль
|
||||||
|
</label>
|
||||||
|
<input type="password" class="form-control" id="new_password" name="new_password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="confirm_password" class="form-label">
|
||||||
|
<i class="fas fa-check-circle me-1"></i> Подтвердите новый пароль
|
||||||
|
</label>
|
||||||
|
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg">
|
||||||
|
<i class="fas fa-save me-1"></i> Сохранить изменения
|
||||||
|
</button>
|
||||||
|
<a href="{{ url_for('zilant.index') }}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-times me-1"></i> Отмена
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Редактирование события</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
.form-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.preview-image {
|
||||||
|
max-height: 400px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
.required:after {
|
||||||
|
content: " *";
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.form-label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.toast-container {
|
||||||
|
z-index: 1055; /* Выше чем у других элементов */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">Редактирование события</a>
|
||||||
|
<div class="navbar-nav ms-auto">
|
||||||
|
<a class="nav-link" href="#" id="back-link">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> Назад к списку
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 id="form-title">{{ 'Редактирование события' if event_id else 'Добавление нового события' }}</h2>
|
||||||
|
<a href="#" id="back-button" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> Назад
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card form-container">
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="event-form">
|
||||||
|
<input type="hidden" id="event-id" value="{{ event_id if event_id }}">
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="number" class="form-label required">Номер события</label>
|
||||||
|
<input type="number" class="form-control" id="number" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Local ID</label>
|
||||||
|
<div class="form-control-plaintext" id="event-local-id">
|
||||||
|
{{ event_id if event_id else 'Новый' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="name" class="form-label required">Название события</label>
|
||||||
|
<input type="text" class="form-control" id="name" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="unit_name" class="form-label">Площадка</label>
|
||||||
|
<input type="text" class="form-control" id="unit_name">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="id_event" class="form-label">Серверный ID</label>
|
||||||
|
<input type="number" class="form-control" id="id_event">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="about" class="form-label">Описание</label>
|
||||||
|
<textarea class="form-control" id="about" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="about_social_picture" class="form-label">URL изображения</label>
|
||||||
|
<input type="url" class="form-control" id="about_social_picture">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="tags" class="form-label">Тэги</label>
|
||||||
|
<input type="text" class="form-control" id="tags">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Preview изображения -->
|
||||||
|
<div class="mb-3 text-center" id="image-preview-container" style="display: none;">
|
||||||
|
<img id="image-preview" class="preview-image img-thumbnail">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="added" class="form-label">Дата добавления</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="added">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="modified" class="form-label">Дата изменения</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="modified">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="announcement_link" class="form-label">Ссылка на анонс</label>
|
||||||
|
<input type="text" class="form-control" id="announcement_link">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="is_canceled">
|
||||||
|
<label class="form-check-label" for="is_canceled">Отменено</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="accepted">
|
||||||
|
<label class="form-check-label" for="accepted">Принято</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="denied">
|
||||||
|
<label class="form-check-label" for="denied">Отказано</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="is_visible">
|
||||||
|
<label class="form-check-label" for="is_visible">Видно</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="marked_to_publication">
|
||||||
|
<label class="form-check-label" for="marked_to_publication">Отмечено для публикации</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="is_posted_tg">
|
||||||
|
<label class="form-check-label" for="is_posted_tg">Опубликовано в TG</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="tg_message_id" class="form-label">ID сообщения в TG</label>
|
||||||
|
<input type="number" class="form-control" id="tg_message_id">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="tg_posted_date" class="form-label">Дата публикации в TG</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="tg_posted_date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<button type="button" class="btn btn-secondary" id="cancel-btn">
|
||||||
|
<i class="fas fa-times me-1"></i> Отмена
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i> Сохранить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Контейнер для уведомлений -->
|
||||||
|
<div class="toast-container position-fixed top-0 end-0 p-3">
|
||||||
|
<div id="success-toast" class="toast align-items-center text-white bg-success border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="toast-body">
|
||||||
|
<i class="fas fa-check-circle me-2"></i> Событие успешно сохранено!
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Сообщение о процессе выполнения -->
|
||||||
|
<div id="processing-message" class="alert alert-info position-fixed top-50 start-50 translate-middle" style="display: none; z-index: 1050;">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||||
|
<span class="visually-hidden">Загрузка...</span>
|
||||||
|
</div>
|
||||||
|
<span id="processing-text">Обработка...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const PREFIX = "{{ g.prefix }}";
|
||||||
|
const EVENT_ID = {{ event_id if event_id else 'null' }};
|
||||||
|
|
||||||
|
// Инициализация toast
|
||||||
|
const successToast = new bootstrap.Toast(document.getElementById('success-toast'));
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Загрузка данных события, если редактируем существующее
|
||||||
|
if (EVENT_ID) {
|
||||||
|
loadEventData(EVENT_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик отправки формы
|
||||||
|
document.getElementById('event-form').addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
saveEvent();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчики кнопок отмены и назад
|
||||||
|
const cancelHandler = () => {
|
||||||
|
if (EVENT_ID) {
|
||||||
|
window.location.href = `${PREFIX}/?selected_event=${EVENT_ID}&tab=events`;
|
||||||
|
} else {
|
||||||
|
window.location.href = `${PREFIX}/?tab=events`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('cancel-btn').addEventListener('click', cancelHandler);
|
||||||
|
document.getElementById('back-button').addEventListener('click', cancelHandler);
|
||||||
|
document.getElementById('back-link').addEventListener('click', cancelHandler);
|
||||||
|
|
||||||
|
// Предпросмотр изображения
|
||||||
|
document.getElementById('about_social_picture').addEventListener('change', function() {
|
||||||
|
const url = this.value.trim();
|
||||||
|
const previewContainer = document.getElementById('image-preview-container');
|
||||||
|
const previewImage = document.getElementById('image-preview');
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
previewImage.src = `${PREFIX}/image_proxy?url=${encodeURIComponent(url)}`;
|
||||||
|
previewContainer.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
previewContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Загрузка данных события
|
||||||
|
async function loadEventData(eventId) {
|
||||||
|
showProcessingMessage("Загрузка данных события...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${PREFIX}/api/events/${eventId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Событие не найдено');
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = await response.json();
|
||||||
|
|
||||||
|
// Заполняем форму данными
|
||||||
|
document.getElementById('number').value = event.number || '';
|
||||||
|
document.getElementById('name').value = event.name || '';
|
||||||
|
document.getElementById('unit_name').value = event.unit_name || '';
|
||||||
|
document.getElementById('id_event').value = event.id_event || '';
|
||||||
|
document.getElementById('about').value = event.about || '';
|
||||||
|
document.getElementById('about_social_picture').value = event.about_social_picture || '';
|
||||||
|
document.getElementById('tags').value = event.tags || '';
|
||||||
|
document.getElementById('announcement_link').value = event.announcement_link || '';
|
||||||
|
document.getElementById('tg_message_id').value = event.tg_message_id || '';
|
||||||
|
document.getElementById('event-local-id').textContent = event.id;
|
||||||
|
// Заполняем чекбоксы
|
||||||
|
document.getElementById('is_canceled').checked = event.is_canceled || false;
|
||||||
|
document.getElementById('accepted').checked = event.accepted || false;
|
||||||
|
document.getElementById('denied').checked = event.denied || false;
|
||||||
|
document.getElementById('is_visible').checked = event.is_visible || false;
|
||||||
|
document.getElementById('marked_to_publication').checked = event.marked_to_publication || false;
|
||||||
|
document.getElementById('is_posted_tg').checked = event.is_posted_tg || false;
|
||||||
|
|
||||||
|
// Заполняем даты
|
||||||
|
if (event.added) {
|
||||||
|
const addedDate = new Date(event.added);
|
||||||
|
// document.getElementById('added').value = addedDate.toISOString().slice(0, 16);
|
||||||
|
document.getElementById('added').value = convertUTCToLocal(event.added);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.modified) {
|
||||||
|
const modifiedDate = new Date(event.modified);
|
||||||
|
// document.getElementById('modified').value = modifiedDate.toISOString().slice(0, 16);
|
||||||
|
document.getElementById('modified').value = convertUTCToLocal(event.modified);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.tg_posted_date) {
|
||||||
|
const tgPostedDate = new Date(event.tg_posted_date);
|
||||||
|
// document.getElementById('tg_posted_date').value = tgPostedDate.toISOString().slice(0, 16);
|
||||||
|
document.getElementById('tg_posted_date').value = convertUTCToLocal(event.tg_posted_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показываем изображение, если есть
|
||||||
|
if (event.about_social_picture) {
|
||||||
|
document.getElementById('image-preview').src =
|
||||||
|
`${PREFIX}/image_proxy?url=${encodeURIComponent(event.about_social_picture)}`;
|
||||||
|
document.getElementById('image-preview-container').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
alert(`Ошибка загрузки данных: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
hideProcessingMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохранение события
|
||||||
|
async function saveEvent() {
|
||||||
|
showProcessingMessage("Сохранение события...");
|
||||||
|
|
||||||
|
const formData = {
|
||||||
|
number: document.getElementById('number').value,
|
||||||
|
name: document.getElementById('name').value,
|
||||||
|
unit_name: document.getElementById('unit_name').value,
|
||||||
|
id_event: document.getElementById('id_event').value,
|
||||||
|
about: document.getElementById('about').value,
|
||||||
|
about_social_picture: document.getElementById('about_social_picture').value,
|
||||||
|
tags: document.getElementById('tags').value,
|
||||||
|
announcement_link: document.getElementById('announcement_link').value,
|
||||||
|
tg_message_id: document.getElementById('tg_message_id').value,
|
||||||
|
is_canceled: document.getElementById('is_canceled').checked,
|
||||||
|
accepted: document.getElementById('accepted').checked,
|
||||||
|
denied: document.getElementById('denied').checked,
|
||||||
|
is_visible: document.getElementById('is_visible').checked,
|
||||||
|
marked_to_publication: document.getElementById('marked_to_publication').checked,
|
||||||
|
is_posted_tg: document.getElementById('is_posted_tg').checked,
|
||||||
|
added: convertLocalToUTC(document.getElementById('added').value),
|
||||||
|
modified: convertLocalToUTC(document.getElementById('modified').value),
|
||||||
|
tg_posted_date: convertLocalToUTC(document.getElementById('tg_posted_date').value)
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
if (EVENT_ID) {
|
||||||
|
// Обновление существующего события
|
||||||
|
response = await fetch(`${PREFIX}/api/events/${EVENT_ID}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Создание нового события
|
||||||
|
response = await fetch(`${PREFIX}/api/events`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
// Показываем уведомление об успехе
|
||||||
|
successToast.show();
|
||||||
|
|
||||||
|
// Ждем 1.5 секунды перед перенаправлением
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = `${PREFIX}/?selected_event=${result.event_id || EVENT_ID}&tab=events`;
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
throw new Error(result.error || 'Неизвестная ошибка');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert(`Ошибка сохранения: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
hideProcessingMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// добавим функцию преобразования UTC в локальное время
|
||||||
|
function convertUTCToLocal(utcString) {
|
||||||
|
if (!utcString) return '';
|
||||||
|
const date = new Date(utcString);
|
||||||
|
const localDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
|
||||||
|
return localDate.toISOString().slice(0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// добавим функцию преобразования локального времени в UTC
|
||||||
|
function convertLocalToUTC(localString) {
|
||||||
|
if (!localString) return null;
|
||||||
|
const date = new Date(localString);
|
||||||
|
const utcDate = new Date(date.getTime() + date.getTimezoneOffset() * 60000);
|
||||||
|
return utcDate.toISOString().slice(0, 19).replace('T', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функции для показа/скрытия сообщения о обработке
|
||||||
|
function showProcessingMessage(message) {
|
||||||
|
const processingElement = document.getElementById('processing-message');
|
||||||
|
const processingText = document.getElementById('processing-text');
|
||||||
|
|
||||||
|
processingText.textContent = message;
|
||||||
|
processingElement.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideProcessingMessage() {
|
||||||
|
const processingElement = document.getElementById('processing-message');
|
||||||
|
processingElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% if post_id %}Редактирование поста{% else %}Добавление поста{% endif %}</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
.form-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.preview-image {
|
||||||
|
max-height: 400px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
.required:after {
|
||||||
|
content: " *";
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.toast-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1050;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2>{% if post_id %}Редактирование поста{% else %}Добавление поста{% endif %}</h2>
|
||||||
|
<a href="#" id="back-button" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> Назад
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card form-container">
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="post-form">
|
||||||
|
<input type="hidden" id="post-id" value="{{ post_id if post_id else '' }}">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="vk_post_id" class="form-label required">VK ID поста</label>
|
||||||
|
<input type="text" class="form-control" id="vk_post_id" required>
|
||||||
|
<div class="form-text">Числовой идентификатор поста во ВКонтакте</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="shortname" class="form-label">Название</label>
|
||||||
|
<input type="text" class="form-control" id="shortname" maxlength="100">
|
||||||
|
<div class="form-text">Короткое имя поста (максимум 100 символов)</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="action_number" class="form-label">Номер события в системе расписания</label>
|
||||||
|
<input type="number" class="form-control" id="action_number">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="text" class="form-label required">Текст поста</label>
|
||||||
|
<textarea class="form-control" id="text" rows="4" required></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="image_url" class="form-label">URL изображения</label>
|
||||||
|
<input type="url" class="form-control" id="image_url">
|
||||||
|
<div class="form-text">Ссылка на изображение для поста</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 text-center" id="image-preview-container" style="display: none;">
|
||||||
|
<img id="image-preview" class="preview-image img-thumbnail">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="vk_post_url" class="form-label">Ссылка на пост VK</label>
|
||||||
|
<input type="url" class="form-control" id="vk_post_url">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="published_at" class="form-label">Дата публикации</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="published_at">
|
||||||
|
<div class="form-text">Формат: ГГГГ-ММ-ДД ЧЧ:ММ</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Новое поле: is_event -->
|
||||||
|
<div class="mb-3 form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="is_event">
|
||||||
|
<label class="form-check-label" for="is_event">Это событие</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="is_poll">
|
||||||
|
<label class="form-check-label" for="is_poll">Это опрос</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="poll-fields" style="display: none;">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="poll_question" class="form-label">Вопрос опроса</label>
|
||||||
|
<input type="text" class="form-control" id="poll_question">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="poll_options" class="form-label">Варианты ответов (JSON)</label>
|
||||||
|
<textarea class="form-control" id="poll_options" rows="2"></textarea>
|
||||||
|
<div class="form-text">Формат: ["Вариант 1", "Вариант 2", ...]</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="poll_multiple">
|
||||||
|
<label class="form-check-label" for="poll_multiple">Множественный выбор</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="poll_end_date" class="form-label">Дата окончания опроса</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="poll_end_date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-top mt-4 pt-3">
|
||||||
|
<div class="mb-3 form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="marked_for_publication">
|
||||||
|
<label class="form-check-label" for="marked_for_publication">Отметить для публикации</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="published_in_tg">
|
||||||
|
<label class="form-check-label" for="published_in_tg">Опубликовано в Telegram</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="tg_publication_date" class="form-label">Дата публикации в Telegram</label>
|
||||||
|
<input type="datetime-local" class="form-control" id="tg_publication_date">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="tg_message_id" class="form-label">ID сообщения в Telegram</label>
|
||||||
|
<input type="number" class="form-control" id="tg_message_id">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="tg_poll_id" class="form-label">ID опроса в Telegram</label>
|
||||||
|
<input type="number" class="form-control" id="tg_poll_id">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="tg_markpost_id" class="form-label">ID поста для отметок в Telegram</label>
|
||||||
|
<input type="number" class="form-control" id="tg_markpost_id">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="tg_poll_results" class="form-label">Результаты опроса</label>
|
||||||
|
<textarea class="form-control" id="tg_poll_results" rows="4"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-end mt-4">
|
||||||
|
<button type="button" class="btn btn-secondary me-2" id="cancel-btn">Отмена</button>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
{% if post_id %}Обновить{% else %}Создать{% endif %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Контейнер для уведомлений -->
|
||||||
|
<div class="toast-container">
|
||||||
|
<div id="success-toast" class="toast align-items-center text-white bg-success border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="toast-body">
|
||||||
|
<i class="fas fa-check-circle me-2"></i> Пост успешно сохранен!
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const PREFIX = "{{ g.prefix }}";
|
||||||
|
console.log("Editor Prefix is:", PREFIX); // Должно выводить "/zilant"
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const postId = document.getElementById('post-id').value;
|
||||||
|
const form = document.getElementById('post-form');
|
||||||
|
const isPollCheckbox = document.getElementById('is_poll');
|
||||||
|
const pollFields = document.getElementById('poll-fields');
|
||||||
|
const imageUrlInput = document.getElementById('image_url');
|
||||||
|
const imagePreviewContainer = document.getElementById('image-preview-container');
|
||||||
|
const imagePreview = document.getElementById('image-preview');
|
||||||
|
const successToast = new bootstrap.Toast(document.getElementById('success-toast'));
|
||||||
|
|
||||||
|
// Обработчики кнопок отмены и назад
|
||||||
|
const cancelHandler = () => {
|
||||||
|
if (postId) {
|
||||||
|
window.location.href = `${PREFIX}/?selected_post=${postId}&tab=posts`;
|
||||||
|
} else {
|
||||||
|
window.location.href = `${PREFIX}/?tab=posts`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('cancel-btn').addEventListener('click', cancelHandler);
|
||||||
|
document.getElementById('back-button').addEventListener('click', cancelHandler);
|
||||||
|
|
||||||
|
// Показать/скрыть поля опроса
|
||||||
|
isPollCheckbox.addEventListener('change', () => {
|
||||||
|
pollFields.style.display = isPollCheckbox.checked ? 'block' : 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Предпросмотр изображения
|
||||||
|
imageUrlInput.addEventListener('change', () => {
|
||||||
|
const url = imageUrlInput.value.trim();
|
||||||
|
if (url) {
|
||||||
|
imagePreview.src = `${PREFIX}/image_proxy?url=${encodeURIComponent(url)}`;
|
||||||
|
imagePreviewContainer.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
imagePreviewContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Загрузка данных поста (если редактирование)
|
||||||
|
if (postId) {
|
||||||
|
loadPostData(postId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка отправки формы
|
||||||
|
form.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
savePost();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Загрузка данных поста
|
||||||
|
async function loadPostData(postId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${PREFIX}/api/posts/${postId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Пост не найден');
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = await response.json();
|
||||||
|
|
||||||
|
// Заполняем форму
|
||||||
|
document.getElementById('vk_post_id').value = post.vk_post_id || '';
|
||||||
|
document.getElementById('shortname').value = post.shortname || '';
|
||||||
|
document.getElementById('action_number').value = post.action_number || '';
|
||||||
|
document.getElementById('text').value = post.text || '';
|
||||||
|
document.getElementById('image_url').value = post.image_url || '';
|
||||||
|
document.getElementById('vk_post_url').value = post.vk_post_url || '';
|
||||||
|
|
||||||
|
// Форматирование дат для input[type=datetime-local]
|
||||||
|
const formatForInput = (dateStr) => {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return date.toISOString().slice(0, 16);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('published_at').value = formatForInput(post.published_at);
|
||||||
|
document.getElementById('is_event').checked = post.is_event; // Новое поле
|
||||||
|
document.getElementById('is_poll').checked = post.is_poll;
|
||||||
|
document.getElementById('poll_question').value = post.poll_question || '';
|
||||||
|
document.getElementById('poll_options').value = post.poll_options || '';
|
||||||
|
document.getElementById('poll_multiple').checked = post.poll_multiple;
|
||||||
|
document.getElementById('poll_end_date').value = formatForInput(post.poll_end_date);
|
||||||
|
|
||||||
|
document.getElementById('marked_for_publication').checked = post.marked_for_publication;
|
||||||
|
document.getElementById('published_in_tg').checked = post.published_in_tg;
|
||||||
|
document.getElementById('tg_publication_date').value = formatForInput(post.tg_publication_date);
|
||||||
|
document.getElementById('tg_message_id').value = post.tg_message_id || '';
|
||||||
|
document.getElementById('tg_poll_id').value = post.tg_poll_id || '';
|
||||||
|
document.getElementById('tg_markpost_id').value = post.tg_markpost_id || '';
|
||||||
|
document.getElementById('tg_poll_results').value = post.tg_poll_results || '';
|
||||||
|
|
||||||
|
// Показываем поля опроса, если нужно
|
||||||
|
if (post.is_poll) {
|
||||||
|
document.getElementById('poll-fields').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показываем изображение, если есть
|
||||||
|
if (post.image_url) {
|
||||||
|
document.getElementById('image-preview').src =
|
||||||
|
`${PREFIX}/image_proxy?url=${encodeURIComponent(post.image_url)}`;
|
||||||
|
document.getElementById('image-preview-container').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
alert(`Ошибка загрузки данных: ${error.message}`);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохранение поста
|
||||||
|
async function savePost() {
|
||||||
|
const postId = document.getElementById('post-id').value;
|
||||||
|
const form = document.getElementById('post-form');
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
const originalBtnText = submitBtn.innerHTML;
|
||||||
|
const isNewPost = !postId;
|
||||||
|
|
||||||
|
// Показываем индикатор загрузки
|
||||||
|
submitBtn.innerHTML = `
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status"></span>
|
||||||
|
Сохранение...
|
||||||
|
`;
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Собираем данные формы
|
||||||
|
const formData = {
|
||||||
|
vk_post_id: document.getElementById('vk_post_id').value,
|
||||||
|
shortname: document.getElementById('shortname').value,
|
||||||
|
action_number: document.getElementById('action_number').value,
|
||||||
|
text: document.getElementById('text').value,
|
||||||
|
image_url: document.getElementById('image_url').value,
|
||||||
|
vk_post_url: document.getElementById('vk_post_url').value,
|
||||||
|
published_at: document.getElementById('published_at').value,
|
||||||
|
is_event: document.getElementById('is_event').checked, // Новое поле
|
||||||
|
is_poll: document.getElementById('is_poll').checked,
|
||||||
|
poll_question: document.getElementById('poll_question').value,
|
||||||
|
poll_options: document.getElementById('poll_options').value,
|
||||||
|
poll_multiple: document.getElementById('poll_multiple').checked,
|
||||||
|
poll_end_date: document.getElementById('poll_end_date').value,
|
||||||
|
marked_for_publication: document.getElementById('marked_for_publication').checked,
|
||||||
|
published_in_tg: document.getElementById('published_in_tg').checked,
|
||||||
|
tg_publication_date: document.getElementById('tg_publication_date').value,
|
||||||
|
tg_message_id: document.getElementById('tg_message_id').value,
|
||||||
|
tg_poll_id: document.getElementById('tg_poll_id').value,
|
||||||
|
tg_markpost_id: document.getElementById('tg_markpost_id').value,
|
||||||
|
tg_poll_results: document.getElementById('tg_poll_results').value
|
||||||
|
};
|
||||||
|
|
||||||
|
// Валидация
|
||||||
|
if (!formData.vk_post_id) {
|
||||||
|
throw new Error('VK ID поста обязателен для заполнения');
|
||||||
|
}
|
||||||
|
|
||||||
|
let response;
|
||||||
|
let result;
|
||||||
|
if (postId) {
|
||||||
|
// Обновление существующего поста
|
||||||
|
response = await fetch(`${PREFIX}/api/posts/${postId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData)
|
||||||
|
});
|
||||||
|
result = await response.json();
|
||||||
|
} else {
|
||||||
|
// Создание нового поста
|
||||||
|
response = await fetch(`${PREFIX}/api/posts`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData)
|
||||||
|
});
|
||||||
|
result = await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.error || 'Ошибка сохранения');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показываем уведомление об успехе
|
||||||
|
const successToast = new bootstrap.Toast(document.getElementById('success-toast'));
|
||||||
|
successToast.show();
|
||||||
|
|
||||||
|
// Ждем 1.5 секунды перед перенаправлением
|
||||||
|
setTimeout(() => {
|
||||||
|
// Перенаправляем на главную страницу с выбранным постом
|
||||||
|
window.location.href = `${PREFIX}/?selected_post=${postId || result.post_id}&tab=posts`;
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
alert(`Ошибка: ${error.message}`);
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
// Восстанавливаем кнопку
|
||||||
|
submitBtn.innerHTML = originalBtnText;
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Авторизация</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.login-container {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 100px auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="login-container">
|
||||||
|
<h2 class="text-center mb-4">Авторизация</h2>
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<form action="{{ url_for('zilant.login') }}" method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Имя пользователя</label>
|
||||||
|
<input type="text" class="form-control" id="username" name="username" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Пароль</label>
|
||||||
|
<input type="password" class="form-control" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="remember" name="remember">
|
||||||
|
<label class="form-check-label" for="remember">Запомнить меня</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Войти</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user