mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-19 14:36:05 +00:00
v2.5.0: add QR import and backup scheduling
This commit is contained in:
@@ -64,6 +64,7 @@ const i18n = {
|
||||
addKey: "Add key",
|
||||
copyLink: "Copy link",
|
||||
copySecret: "Copy secret",
|
||||
showQr: "QR",
|
||||
delete: "Delete",
|
||||
enabled: "Enabled",
|
||||
disabled: "Disabled",
|
||||
@@ -73,6 +74,20 @@ const i18n = {
|
||||
enableKey: "Enable key",
|
||||
main: "main",
|
||||
createBackup: "Create backup",
|
||||
restoreBackup: "Restore",
|
||||
backupScheduleTitle: "Automatic backups",
|
||||
backupScheduleLoading: "Loading schedule...",
|
||||
backupIncludesTitle: "Backup contents",
|
||||
backupIncludesText: "telemt config, goTelegram settings, keys, disabled keys, site, templates, SSL certificates, bot, admin panel and traffic history.",
|
||||
scheduleOff: "Off",
|
||||
scheduleDaily: "Daily",
|
||||
scheduleWeekly: "Weekly",
|
||||
scheduleMonthly: "Monthly",
|
||||
scheduleSaved: "Schedule saved",
|
||||
scheduleNext: "Next run: {value}",
|
||||
scheduleDisabled: "Automatic backups are disabled",
|
||||
backupRestoreStarted: "Restore started",
|
||||
confirmRestoreBackup: "Restore backup",
|
||||
loadLogs: "Load",
|
||||
panelLanguage: "Panel language",
|
||||
theme: "Theme",
|
||||
@@ -122,6 +137,7 @@ const i18n = {
|
||||
keyCreated: "Key created",
|
||||
keyDeleted: "Key deleted",
|
||||
backupCreated: "Backup created",
|
||||
qrUnavailable: "QR code is unavailable",
|
||||
serviceRestarted: "Service restarted",
|
||||
statsRepaired: "Collector restarted",
|
||||
statsCollected: "Statistics collected",
|
||||
@@ -189,6 +205,8 @@ const i18n = {
|
||||
promoHosting1: "Hosting #1",
|
||||
promoHosting2: "Hosting #2",
|
||||
promoTips: "Tips",
|
||||
qrEyebrow: "QR import",
|
||||
qrTitle: "Scan Telegram proxy",
|
||||
pageDashboardTitle: "Dashboard",
|
||||
pageDashboardKicker: "Local Admin",
|
||||
pageTrafficTitle: "Traffic",
|
||||
@@ -264,6 +282,7 @@ const i18n = {
|
||||
addKey: "Добавить ключ",
|
||||
copyLink: "Копировать ссылку",
|
||||
copySecret: "Копировать секрет",
|
||||
showQr: "QR",
|
||||
delete: "Удалить",
|
||||
enabled: "Включён",
|
||||
disabled: "Отключён",
|
||||
@@ -273,6 +292,20 @@ const i18n = {
|
||||
enableKey: "Включить ключ",
|
||||
main: "основной",
|
||||
createBackup: "Создать бекап",
|
||||
restoreBackup: "Восстановить",
|
||||
backupScheduleTitle: "Автобекапы",
|
||||
backupScheduleLoading: "Загрузка расписания...",
|
||||
backupIncludesTitle: "Что входит в бекап",
|
||||
backupIncludesText: "конфиг telemt, настройки goTelegram, ключи, отключённые ключи, сайт, шаблоны, SSL-сертификаты, бот, админка и история трафика.",
|
||||
scheduleOff: "Выкл",
|
||||
scheduleDaily: "Каждый день",
|
||||
scheduleWeekly: "Каждую неделю",
|
||||
scheduleMonthly: "Каждый месяц",
|
||||
scheduleSaved: "Расписание сохранено",
|
||||
scheduleNext: "Следующий запуск: {value}",
|
||||
scheduleDisabled: "Автобекапы отключены",
|
||||
backupRestoreStarted: "Восстановление запущено",
|
||||
confirmRestoreBackup: "Восстановить бекап",
|
||||
loadLogs: "Загрузить",
|
||||
panelLanguage: "Язык панели",
|
||||
theme: "Тема",
|
||||
@@ -322,6 +355,7 @@ const i18n = {
|
||||
keyCreated: "Ключ создан",
|
||||
keyDeleted: "Ключ удалён",
|
||||
backupCreated: "Бекап создан",
|
||||
qrUnavailable: "QR-код недоступен",
|
||||
serviceRestarted: "Сервис перезапущен",
|
||||
statsRepaired: "Сборщик перезапущен",
|
||||
statsCollected: "Статистика собрана",
|
||||
@@ -389,6 +423,8 @@ const i18n = {
|
||||
promoHosting1: "Хостинг #1",
|
||||
promoHosting2: "Хостинг #2",
|
||||
promoTips: "Чаевые",
|
||||
qrEyebrow: "QR-импорт",
|
||||
qrTitle: "Сканирование прокси Telegram",
|
||||
pageDashboardTitle: "Обзор",
|
||||
pageDashboardKicker: "Локальная админка",
|
||||
pageTrafficTitle: "Трафик",
|
||||
@@ -420,6 +456,8 @@ const state = {
|
||||
userTrafficView: "chart",
|
||||
userTraffic: null,
|
||||
userTrafficLoading: false,
|
||||
backupSchedule: null,
|
||||
qrLink: "",
|
||||
pendingUsers: new Set(),
|
||||
};
|
||||
|
||||
@@ -514,6 +552,7 @@ function applyI18n() {
|
||||
$("#visualText").textContent = t("visualText");
|
||||
updateTrafficControls();
|
||||
updateUserTrafficControls();
|
||||
renderBackupSchedule();
|
||||
updatePageTitle();
|
||||
}
|
||||
|
||||
@@ -1091,7 +1130,12 @@ function renderUsers() {
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="${escapeAttr(t("tableSecret"))}"><code title="${escapeAttr(user.secret)}">${escapeHtml(user.secret)}</code></td>
|
||||
<td data-label="${escapeAttr(t("tableLink"))}"><button class="soft" data-copy="${escapeAttr(user.link)}" ${user.enabled ? "" : "disabled"}>${escapeHtml(t("copyLink"))}</button></td>
|
||||
<td data-label="${escapeAttr(t("tableLink"))}">
|
||||
<div class="mini-actions">
|
||||
<button class="soft" data-copy="${escapeAttr(user.link)}" ${user.enabled ? "" : "disabled"}>${escapeHtml(t("copyLink"))}</button>
|
||||
<button class="soft" data-user-qr="${escapeAttr(user.name)}">${escapeHtml(t("showQr"))}</button>
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="${escapeAttr(t("tableTraffic"))}">
|
||||
<div class="traffic-cell">
|
||||
<strong>${escapeHtml(trafficTotal)}</strong>
|
||||
@@ -1109,6 +1153,7 @@ function renderUsers() {
|
||||
|
||||
function renderBackups(backups) {
|
||||
const box = $("#backupsList");
|
||||
renderBackupSchedule();
|
||||
if (!backups.length) {
|
||||
box.innerHTML = `<div class="empty">${escapeHtml(t("noBackups"))}</div>`;
|
||||
return;
|
||||
@@ -1119,11 +1164,26 @@ function renderBackups(backups) {
|
||||
<strong>${escapeHtml(item.name)}</strong>
|
||||
<span>${escapeHtml(item.path)} · ${escapeHtml(fmtDate(item.mtime))}</span>
|
||||
</div>
|
||||
<div>${escapeHtml(fmtBytes(item.size))}${item.encrypted ? ` · ${escapeHtml(t("encrypted"))}` : ""}</div>
|
||||
<div class="backup-actions">
|
||||
<span>${escapeHtml(fmtBytes(item.size))}${item.encrypted ? ` · ${escapeHtml(t("encrypted"))}` : ""}</span>
|
||||
<button class="soft" data-restore-backup="${escapeAttr(item.name)}">${escapeHtml(t("restoreBackup"))}</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join("");
|
||||
}
|
||||
|
||||
function renderBackupSchedule() {
|
||||
const schedule = state.backupSchedule || state.overview?.backup_schedule || { frequency: "off" };
|
||||
const frequency = schedule.frequency || "off";
|
||||
$$("[data-backup-schedule]").forEach((btn) => {
|
||||
btn.classList.toggle("active", btn.dataset.backupSchedule === frequency);
|
||||
});
|
||||
const next = schedule.next && schedule.next !== "n/a" ? schedule.next : "";
|
||||
$("#backupScheduleMeta").textContent = frequency === "off"
|
||||
? t("scheduleDisabled")
|
||||
: t("scheduleNext").replace("{value}", next || (schedule.calendar || "--"));
|
||||
}
|
||||
|
||||
function renderEvents() {
|
||||
const box = $("#events");
|
||||
if (!state.events.length) {
|
||||
@@ -1162,6 +1222,7 @@ async function refreshAll() {
|
||||
btn.disabled = true;
|
||||
try {
|
||||
state.overview = await api("/api/overview");
|
||||
state.backupSchedule = state.overview.backup_schedule || state.backupSchedule;
|
||||
updateLanguageFromOverview(state.overview);
|
||||
state.users = await api("/api/users");
|
||||
if (!state.stats) {
|
||||
@@ -1324,6 +1385,53 @@ async function createBackup() {
|
||||
}
|
||||
}
|
||||
|
||||
async function setBackupSchedule(frequency) {
|
||||
$$("[data-backup-schedule]").forEach((btn) => { btn.disabled = true; });
|
||||
try {
|
||||
const data = await api("/api/backups/schedule", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ frequency }),
|
||||
});
|
||||
state.backupSchedule = data.schedule || data;
|
||||
renderBackupSchedule();
|
||||
addEvent(t("scheduleSaved"), frequency);
|
||||
toast(t("scheduleSaved"));
|
||||
} catch (err) {
|
||||
toast(err.message);
|
||||
} finally {
|
||||
$$("[data-backup-schedule]").forEach((btn) => { btn.disabled = false; });
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreBackup(name) {
|
||||
const data = await api("/api/backups/restore", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
addEvent(t("backupRestoreStarted"), data.name || name);
|
||||
toast(t("backupRestoreStarted"));
|
||||
setTimeout(() => refreshAll().catch((err) => toast(err.message)), 4000);
|
||||
}
|
||||
|
||||
function showUserQr(name) {
|
||||
const user = state.users.find((item) => item.name === name);
|
||||
if (!user) {
|
||||
toast(t("qrUnavailable"));
|
||||
return;
|
||||
}
|
||||
state.qrLink = user.link || "";
|
||||
$("#qrTitle").textContent = `${t("qrTitle")} · ${user.name}`;
|
||||
$("#qrMeta").textContent = user.link || "";
|
||||
const img = $("#qrImage");
|
||||
img.alt = `${user.name} Telegram proxy QR`;
|
||||
img.onerror = () => {
|
||||
img.removeAttribute("src");
|
||||
toast(t("qrUnavailable"));
|
||||
};
|
||||
img.src = `/api/users/${encodeURIComponent(user.name)}/qr?ts=${Date.now()}`;
|
||||
$("#qrModal").hidden = false;
|
||||
}
|
||||
|
||||
async function loadLogs() {
|
||||
const service = $("#logService").value;
|
||||
const btn = $("#loadLogsBtn");
|
||||
@@ -1436,11 +1544,18 @@ document.addEventListener("click", async (eventObj) => {
|
||||
} else if (button.dataset.userTraffic) {
|
||||
state.userTrafficUser = button.dataset.userTraffic;
|
||||
refreshUserTraffic({ showLoading: true }).catch((err) => toast(err.message));
|
||||
} else if (button.dataset.userQr) {
|
||||
showUserQr(button.dataset.userQr);
|
||||
} else if (button.dataset.userTrafficRange) {
|
||||
changeUserTrafficRange(button.dataset.userTrafficRange);
|
||||
} else if (button.dataset.userTrafficView) {
|
||||
state.userTrafficView = button.dataset.userTrafficView === "table" ? "table" : "chart";
|
||||
renderUserTraffic();
|
||||
} else if (button.dataset.backupSchedule) {
|
||||
setBackupSchedule(button.dataset.backupSchedule);
|
||||
} else if (button.dataset.restoreBackup) {
|
||||
const name = button.dataset.restoreBackup;
|
||||
if (confirm(`${t("confirmRestoreBackup")} ${name}?`)) restoreBackup(name).catch((err) => toast(err.message));
|
||||
} else if (button.dataset.copy) {
|
||||
await copyText(button.dataset.copy);
|
||||
} else if (button.dataset.delete) {
|
||||
@@ -1480,6 +1595,12 @@ $("#languageSelect").addEventListener("change", (eventObj) => setLanguage(eventO
|
||||
$("#promoClose").addEventListener("click", () => {
|
||||
$("#promoModal").hidden = true;
|
||||
});
|
||||
$("#qrClose").addEventListener("click", () => {
|
||||
$("#qrModal").hidden = true;
|
||||
});
|
||||
$("#qrCopyBtn").addEventListener("click", () => {
|
||||
if (state.qrLink) copyText(state.qrLink);
|
||||
});
|
||||
$("#createBackupBtn").addEventListener("click", createBackup);
|
||||
$("#loadLogsBtn").addEventListener("click", loadLogs);
|
||||
$("#repairStatsBtn").addEventListener("click", repairStats);
|
||||
|
||||
Reference in New Issue
Block a user