release 2.0
This commit is contained in:
@@ -324,7 +324,7 @@ def api_birthdate():
|
|||||||
match request.method:
|
match request.method:
|
||||||
case "POST":
|
case "POST":
|
||||||
reqData = request.json
|
reqData = request.json
|
||||||
userUpdate = reqData["userUpdate"]
|
userUpdate = reqData.get("userUpdate", None)
|
||||||
if userUpdate:
|
if userUpdate:
|
||||||
logger.info("Обновлен пользователь")
|
logger.info("Обновлен пользователь")
|
||||||
userId = userUpdate["userId"]
|
userId = userUpdate["userId"]
|
||||||
@@ -338,7 +338,7 @@ def api_birthdate():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при обновлении пользователя: {e}")
|
logger.error(f"Ошибка при обновлении пользователя: {e}")
|
||||||
return jsonify({"status": "error"}), 500
|
return jsonify({"status": "error"}), 500
|
||||||
scheduleSettings = reqData["scheduleSettings"]
|
scheduleSettings = reqData.get("scheduleSettings")
|
||||||
if scheduleSettings:
|
if scheduleSettings:
|
||||||
logger.info("Обновлены настройки расписания")
|
logger.info("Обновлены настройки расписания")
|
||||||
try:
|
try:
|
||||||
@@ -554,13 +554,13 @@ def login():
|
|||||||
protection = Protection.query.first()
|
protection = Protection.query.first()
|
||||||
if not protection:
|
if not protection:
|
||||||
protection = Protection()
|
protection = Protection()
|
||||||
protection.set_password(password)
|
protection.set_password(new_password)
|
||||||
protection.generate_token()
|
protection.generate_token()
|
||||||
|
|
||||||
db.session.add(protection)
|
db.session.add(protection)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
else:
|
else:
|
||||||
protection.set_password(password)
|
protection.set_password(new_password)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
@@ -595,5 +595,4 @@ def check_auth_cookie():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
init_app()
|
init_app()
|
||||||
app.run(debug=True, host="0.0.0.0", port=80)
|
app.run(host="0.0.0.0", port=80)
|
||||||
# app.run(host="0.0.0.0", port=80)
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
/* Таблица сотрудников */
|
/* Таблица сотрудников */
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
max-height: 800px;
|
max-height: 950px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,22 +77,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status-enabled {
|
.status-enabled {
|
||||||
background-color: rgba(25, 135, 84, 0.1);
|
background-color: rgba(25, 135, 84, 0.5);
|
||||||
color: #198754;
|
color: #198754;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-disabled {
|
.status-disabled {
|
||||||
background-color: rgba(108, 117, 125, 0.1);
|
background-color: rgba(108, 117, 125, 0.2);
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-data {
|
.status-data {
|
||||||
background-color: rgba(13, 110, 253, 0.1);
|
background-color: rgba(13, 110, 253, 0.5);
|
||||||
color: #0d6efd;
|
color: #0d6efd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-nodata {
|
.status-nodata {
|
||||||
background-color: rgba(220, 53, 69, 0.1);
|
background-color: rgba(220, 53, 69, 0.5);
|
||||||
color: #dc3545;
|
color: #dc3545;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+56
-7
@@ -7,6 +7,8 @@ let schedulerFormChanged = false;
|
|||||||
let originalUserData = null;
|
let originalUserData = null;
|
||||||
let originalSchedulerData = null;
|
let originalSchedulerData = null;
|
||||||
let pendingUserSwitch = null;
|
let pendingUserSwitch = null;
|
||||||
|
const monthNames = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
|
||||||
|
|
||||||
|
|
||||||
// Инициализация при загрузке страницы
|
// Инициализация при загрузке страницы
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
@@ -18,6 +20,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
|
|
||||||
// Устанавливаем обработчики событий
|
// Устанавливаем обработчики событий
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Загрузка списка сотрудников
|
// Загрузка списка сотрудников
|
||||||
@@ -28,6 +31,8 @@ async function loadUsersList() {
|
|||||||
|
|
||||||
if (data.status === 'ok') {
|
if (data.status === 'ok') {
|
||||||
usersData = data.users;
|
usersData = data.users;
|
||||||
|
// Определение ближайшего дня рождения
|
||||||
|
findNearestBirthday();
|
||||||
renderUsersTable();
|
renderUsersTable();
|
||||||
} else {
|
} else {
|
||||||
showAlert('danger', 'Ошибка загрузки списка сотрудников');
|
showAlert('danger', 'Ошибка загрузки списка сотрудников');
|
||||||
@@ -39,6 +44,53 @@ async function loadUsersList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findNearestBirthday() {
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
let nextBirthday = null;
|
||||||
|
let nextBirthdayUserData = null;
|
||||||
|
|
||||||
|
for (const user of usersData) {
|
||||||
|
if (!user.enabled || !user.birthdate) continue;
|
||||||
|
|
||||||
|
const birthdate = new Date(user.birthdate);
|
||||||
|
|
||||||
|
// День рождения в текущем году
|
||||||
|
let candidate = new Date(
|
||||||
|
today.getFullYear(),
|
||||||
|
birthdate.getMonth(),
|
||||||
|
birthdate.getDate()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Если уже прошёл — переносим на следующий год
|
||||||
|
if (candidate < today) {
|
||||||
|
candidate.setFullYear(today.getFullYear() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextBirthday || candidate < nextBirthday) {
|
||||||
|
nextBirthday = candidate;
|
||||||
|
|
||||||
|
const age = candidate.getFullYear() - birthdate.getFullYear();
|
||||||
|
|
||||||
|
nextBirthdayUserData = {
|
||||||
|
date: new Intl.DateTimeFormat('ru-RU', { month: 'long', day: 'numeric' })
|
||||||
|
.format(new Date(user.birthdate)),
|
||||||
|
name: user.name,
|
||||||
|
age
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextBirthdayElement = document.getElementById('nextBirthday');
|
||||||
|
if (nextBirthdayUserData) {
|
||||||
|
nextBirthdayElement.textContent = `${nextBirthdayUserData.date} ${nextBirthdayUserData.name} исполнится ${nextBirthdayUserData.age} лет 🎉`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextBirthdayUserData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Отображение таблицы сотрудников
|
// Отображение таблицы сотрудников
|
||||||
function renderUsersTable() {
|
function renderUsersTable() {
|
||||||
const tbody = document.getElementById('usersTableBody');
|
const tbody = document.getElementById('usersTableBody');
|
||||||
@@ -81,14 +133,11 @@ function renderUsersTable() {
|
|||||||
const birthdate = new Date(user.birthdate);
|
const birthdate = new Date(user.birthdate);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const age = now.getFullYear() - birthdate.getFullYear();
|
const age = now.getFullYear() - birthdate.getFullYear();
|
||||||
const month = birthdate.getMonth() + 1;
|
|
||||||
const day = birthdate.getDate();
|
|
||||||
const fullDate = birthdate.toLocaleDateString('ru-RU');
|
const fullDate = birthdate.toLocaleDateString('ru-RU');
|
||||||
|
|
||||||
// Форматируем месяц и день (двузначные)
|
// Форматируем месяц и день (двузначные)
|
||||||
const monthNames = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
|
const dateText = new Intl.DateTimeFormat('ru-RU', { month: 'long', day: 'numeric' })
|
||||||
const monthStr = monthNames[month - 1];
|
.format(new Date(user.birthdate));
|
||||||
const dayStr = day.toString().padStart(2, '0');
|
|
||||||
|
|
||||||
// Определяем пол
|
// Определяем пол
|
||||||
const sexBadge = user.sex === 'male' ?
|
const sexBadge = user.sex === 'male' ?
|
||||||
@@ -121,7 +170,7 @@ function renderUsersTable() {
|
|||||||
<tr class="${isSelected}" onclick="selectUser(${user.id})" data-user-id="${user.id}">
|
<tr class="${isSelected}" onclick="selectUser(${user.id})" data-user-id="${user.id}">
|
||||||
<td>
|
<td>
|
||||||
<div class="birthdate-cell">
|
<div class="birthdate-cell">
|
||||||
<span class="month-day">${dayStr} ${monthStr}</span>
|
<span class="month-day">${dateText}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -134,7 +183,7 @@ function renderUsersTable() {
|
|||||||
</td>
|
</td>
|
||||||
<td>${sexBadge}</td>
|
<td>${sexBadge}</td>
|
||||||
<td>${specialtiesHtml}</td>
|
<td>${specialtiesHtml}</td>
|
||||||
<td class="text-center">${enabledStatus} ${dataStatus}</td>
|
<td class="text-center">${enabledStatus} ${user.enabled ? dataStatus : ''}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {% if request.path.startswith('/posts') %}active{% endif %}" href="/posts">
|
<a class="nav-link {% if request.path.startswith('/posts') %}active{% endif %}" href="/posts">
|
||||||
<i class="bi bi-file-earmark-text me-2"></i>Посты
|
<i class="bi bi-file-earmark-text me-2"></i>Публикации
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|||||||
@@ -204,12 +204,17 @@
|
|||||||
<!-- Информация о следующем запуске -->
|
<!-- Информация о следующем запуске -->
|
||||||
{% if data.schedulerStatus.next_run_time %}
|
{% if data.schedulerStatus.next_run_time %}
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
<div class="d-flex align-items-center">
|
<div class="row g-3">
|
||||||
<i class="bi bi-calendar-event fs-5 me-3"></i>
|
<div class="col-md-5">
|
||||||
<div>
|
<h6 class="alert-heading mb-1"><i class="bi bi-calendar-event fs-5 me-2"></i>Следующий
|
||||||
<h6 class="alert-heading mb-1">Следующий запуск</h6>
|
запуск</h6>
|
||||||
<p class="mb-0">{{ data.schedulerStatus.next_run_time }}</p>
|
<p class="mb-0">{{ data.schedulerStatus.next_run_time }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-7">
|
||||||
|
<h6 class="alert-heading mb-1"><i class="bi bi-calendar-week fs-5 me-2"></i>Ближайший
|
||||||
|
день рождения</h6>
|
||||||
|
<p class="mb-0"><span id="nextBirthday"></span></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
+20
-11
@@ -172,7 +172,7 @@
|
|||||||
<div class="col-xl-4 col-lg-6">
|
<div class="col-xl-4 col-lg-6">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0"><i class="bi bi-calendar-week text-warning me-2"></i>Планировщик</h5>
|
<h5 class="mb-0"><i class="bi bi-calendar-week text-warning me-2"></i>Планировщик Публикаций</h5>
|
||||||
{% if exitData.vkPost and exitData.vkPost.scheduler %}
|
{% if exitData.vkPost and exitData.vkPost.scheduler %}
|
||||||
{% if exitData.vkPost.scheduler.scheduler %}
|
{% if exitData.vkPost.scheduler.scheduler %}
|
||||||
<span class="badge bg-success"><i class="bi bi-play-circle me-1"></i>Активен</span>
|
<span class="badge bg-success"><i class="bi bi-play-circle me-1"></i>Активен</span>
|
||||||
@@ -278,7 +278,7 @@
|
|||||||
<!-- Статистика и ссылки -->
|
<!-- Статистика и ссылки -->
|
||||||
<div class="row g-4 mb-4">
|
<div class="row g-4 mb-4">
|
||||||
<!-- Быстрые действия -->
|
<!-- Быстрые действия -->
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-7">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="mb-0">
|
<h5 class="mb-0">
|
||||||
@@ -287,7 +287,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-4">
|
<div class="col-md-3">
|
||||||
<a href="/medods"
|
<a href="/medods"
|
||||||
class="btn btn-outline-info w-100 py-4 d-flex flex-column align-items-center justify-content-center">
|
class="btn btn-outline-info w-100 py-4 d-flex flex-column align-items-center justify-content-center">
|
||||||
<i class="bi bi-plug fs-1 mb-2"></i>
|
<i class="bi bi-plug fs-1 mb-2"></i>
|
||||||
@@ -296,33 +296,43 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-3">
|
||||||
<a href="/vk"
|
<a href="/vk"
|
||||||
class="btn btn-outline-success w-100 py-4 d-flex flex-column align-items-center justify-content-center">
|
class="btn btn-outline-success w-100 py-4 d-flex flex-column align-items-center justify-content-center">
|
||||||
<i class="bi bi-megaphone fs-1 mb-2"></i>
|
<i class="bi bi-chat-dots fs-1 mb-2"></i>
|
||||||
<span class="fw-semibold">VK API</span>
|
<span class="fw-semibold">VK API</span>
|
||||||
<small class="text-muted text-center">Настройки VK</small>
|
<small class="text-muted text-center">Настройки VK</small>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-3">
|
||||||
<a href="/posts"
|
<a href="/posts"
|
||||||
class="btn btn-outline-warning w-100 py-4 d-flex flex-column align-items-center justify-content-center">
|
class="btn btn-outline-warning w-100 py-4 d-flex flex-column align-items-center justify-content-center">
|
||||||
<i class="bi bi-calendar-week fs-1 mb-2"></i>
|
<i class="bi bi-file-earmark-text fs-1 mb-2"></i>
|
||||||
<span class="fw-semibold">Публикации</span>
|
<span class="fw-semibold">Публикации</span>
|
||||||
<small class="text-muted text-center">Управление</small>
|
<small class="text-muted text-center">Управление</small>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<a href="/birthdate"
|
||||||
|
class="btn btn-outline-primary w-100 py-4 d-flex flex-column align-items-center justify-content-center">
|
||||||
|
<i class="bi bi-balloon-heart-fill mb-2 fs-1"></i>
|
||||||
|
<span class="fw-semibold">Дни рождения</span>
|
||||||
|
<small class="text-muted text-center">Управление</small>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Последний пост VK -->
|
<!-- Последний пост VK -->
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-5">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0"><i class="bi bi-send text-success me-2"></i>Последняя публикация</h5>
|
<h5 class="mb-0"><i class="bi bi-send text-success me-2"></i>Последняя публикация слотов</h5>
|
||||||
{% if exitData.vkPost and exitData.vkPost.post_link %}
|
{% if exitData.vkPost and exitData.vkPost.post_link %}
|
||||||
<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Опубликовано</span>
|
<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Опубликовано</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -337,8 +347,7 @@
|
|||||||
<i class="bi bi-check-circle-fill fs-4"></i>
|
<i class="bi bi-check-circle-fill fs-4"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h6 class="alert-heading mb-1">Пост успешно опубликован</h6>
|
<h6 class="alert-heading mb-1">Пост со списком свободных слотов успешно опубликован</h6>
|
||||||
<p class="mb-2">Ссылка на публикацию в VK</p>
|
|
||||||
<a href="{{ exitData.vkPost.post_link }}" target="_blank"
|
<a href="{{ exitData.vkPost.post_link }}" target="_blank"
|
||||||
class="btn btn-sm btn-outline-success">
|
class="btn btn-sm btn-outline-success">
|
||||||
<i class="bi bi-box-arrow-up-right me-1"></i>Открыть в VK
|
<i class="bi bi-box-arrow-up-right me-1"></i>Открыть в VK
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<!-- Заголовок -->
|
<!-- Заголовок -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="mb-1"><i class="bi bi-file-earmark-text text-primary me-2"></i>Управление постами</h2>
|
<h2 class="mb-1"><i class="bi bi-file-earmark-text text-primary me-2"></i>Управление публикациями</h2>
|
||||||
<p class="text-muted mb-0">Создание и планирование публикаций в VK</p>
|
<p class="text-muted mb-0">Создание и планирование публикаций в VK</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-success btn-lg px-5" onclick="saveSettings()">
|
<button type="button" class="btn btn-success btn-lg px-5" onclick="saveSettings()">
|
||||||
|
|||||||
+18
-12
@@ -87,18 +87,24 @@ def handle_vk_birthdate():
|
|||||||
vk = vk_session.get_api()
|
vk = vk_session.get_api()
|
||||||
|
|
||||||
for user in birthdayUsers:
|
for user in birthdayUsers:
|
||||||
new_post = vk.wall.post(
|
if not user.congratulations or not user.photo_link:
|
||||||
owner_id=-vkApi.group_id,
|
logger.error(f"Пользователь {user.short_name} не настроен для публикации")
|
||||||
from_group=1,
|
continue
|
||||||
message=user.congratulations.strip(),
|
try:
|
||||||
attachments=user.photo_link,
|
new_post = vk.wall.post(
|
||||||
)
|
owner_id=-vkApi.group_id,
|
||||||
|
from_group=1,
|
||||||
|
message=user.congratulations.strip(),
|
||||||
|
attachments=user.photo_link,
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"Пост #{new_post.get('post_id')} создан")
|
logger.info(f"Пост #{new_post.get('post_id')} создан")
|
||||||
|
|
||||||
user.post_link = (
|
user.post_link = (
|
||||||
f"https://vk.com/wall-{vkApi.group_id}_{new_post.get('post_id')}"
|
f"https://vk.com/wall-{vkApi.group_id}_{new_post.get('post_id')}"
|
||||||
)
|
)
|
||||||
user.publish_at = datetime.now()
|
user.publish_at = datetime.now()
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при публикации поста: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user