From e54778c08c69d0e052ddc10c216dcd38f140af88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B8?= =?UTF-8?q?=D1=82=D0=B2=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Sat, 25 Apr 2026 09:44:53 +0300 Subject: [PATCH] v2.5.0: add key disable switches and pro UI polish --- DOCS_AI.md | 20 +-- DOCS_HUMAN.md | 18 +-- admin-web/server.py | 114 +++++++++++++--- admin-web/static/app.js | 80 ++++++++++- admin-web/static/index.html | 55 ++++++-- admin-web/static/styles.css | 218 ++++++++++++++++++++++++++++++ gotelegram-bot/README.md | 6 +- gotelegram-bot/bot.py | 171 +++++++++++++++++++---- gotelegram-bot/config.example.env | 2 +- gotelegram-bot/i18n.py | 6 +- gotelegram-bot/lang/en.json | 4 +- gotelegram-bot/lang/ru.json | 4 +- install.sh | 41 ++++-- lib/backup.sh | 16 ++- lib/common.sh | 24 ++-- lib/lang/en.sh | 20 +-- lib/lang/ru.sh | 20 +-- 17 files changed, 683 insertions(+), 136 deletions(-) diff --git a/DOCS_AI.md b/DOCS_AI.md index 152c540..b31f5a7 100644 --- a/DOCS_AI.md +++ b/DOCS_AI.md @@ -1,4 +1,4 @@ -# GoTelegram Pro — техническая документация для ИИ-агентов +# goTelegram Pro — техническая документация для ИИ-агентов **Версия:** 2.5.0 **Репозиторий:** `anten-ka/gotelegram_pro` @@ -11,7 +11,7 @@ ## 1. Общая картина -GoTelegram Pro — это менеджер MTProxy для Telegram, собранный вокруг Rust-ядра **telemt** (порт mtproto-proxy с поддержкой fake-TLS, v3.3.x). Проект даёт три вещи: +goTelegram Pro — это менеджер MTProxy для Telegram, собранный вокруг Rust-ядра **telemt** (порт mtproto-proxy с поддержкой fake-TLS, v3.3.x). Проект даёт три вещи: 1. **CLI-меню на bash** (`install.sh` + `lib/*.sh`) — установка, настройка, обновление, бекап, перезапуск, смена режима, управление сайтом-маскировкой, выбор шаблона. Единая точка входа `gotelegram` (symlink → `/opt/gotelegram/install.sh`). 2. **Сайт-маскировку** — поднимает рядом nginx с настоящим сайтом на настоящем домене (Let's Encrypt) из каталога 1801 HTML-шаблонов (html5up / startbootstrap / ThemeWagon / dawidolko). В stealth-режиме telemt слушает 443, маскировочный трафик проксирует на локальный nginx (127.0.0.1:8443) через `dns_overrides`. @@ -99,7 +99,7 @@ gotelegram-bot/locales/*.json 99 ключей i18n для бота с per-user | --- | --- | | Репо-скрипты | `/opt/gotelegram/` | | Symlink запуска | `/usr/local/bin/gotelegram` → `/opt/gotelegram/install.sh` | -| Конфиг GoTelegram (JSON) | `/opt/gotelegram/config.json` | +| Конфиг goTelegram Pro (JSON) | `/opt/gotelegram/config.json` | | Конфиг telemt | `/etc/telemt/config.toml` | | Бинарник telemt | `/usr/local/bin/telemt` | | Systemd юнит telemt | `/etc/systemd/system/telemt.service` | @@ -109,7 +109,7 @@ gotelegram-bot/locales/*.json 99 ключей i18n для бота с per-user | nginx конфиг | `/etc/nginx/sites-available/gotelegram` | | nginx enabled | `/etc/nginx/sites-enabled/gotelegram` | | Бекапы | `/opt/gotelegram/backups/` | -| Лог GoTelegram | `/var/log/gotelegram.log` | +| Лог goTelegram Pro | `/var/log/gotelegram.log` | | Логи telemt | `journalctl -u telemt` | | Логи бота | `journalctl -u gotelegram-bot` | @@ -196,7 +196,7 @@ dns_overrides = ["anten-ka.com:8443:127.0.0.1"] Генерируется функцией `install_telemt_service()` в `lib/telemt.sh`: ```ini [Unit] -Description=GoTelegram MTProxy (telemt engine) +Description=goTelegram Pro MTProxy (telemt engine) After=network-online.target Wants=network-online.target @@ -448,12 +448,14 @@ switch_language ru|en - токена нет: после SSH tunnel открывается `http://127.0.0.1:1984/`; - write-запросы дополнительно требуют `X-GoTelegram-Admin: 1`, фронтенд добавляет его автоматически. - язык панели читается из `config.json.language`, затем из `/opt/gotelegram/.language`, fallback `en`; `POST /api/settings/language` сохраняет RU/EN в общий конфиг, marker file и bot `.env`; `gotelegram-bot/i18n.py` использует тот же источник как default до per-user override; -- UI построен вкладками (`dashboard`, `traffic`, `keys`, `backups`, `logs`, `settings`), есть light/dark theme в `localStorage`; +- UI построен вкладками (`dashboard`, `traffic`, `keys`, `backups`, `logs`, `settings`), есть иконки меню, графический overview-блок, light/dark theme в `localStorage` и promo-modal раз в 24 часа через `localStorage`; - `/api/overview` отдаёт `stats_status`, `admin_bind` и `site_status`; `/api/site/check` проверяет `https://config.domain/` и считает OK только HTTP 200; `/api/stats/collect` делает разовый сбор, `/api/stats/repair` устанавливает/перезапускает `gotelegram-stats`. -Функции: overview, проверка сайта на HTTP 200, service status/restart, чтение/запись `[access.users]`, генерация proxy links, traffic history из `/opt/gotelegram/stats_history.csv`, current stats из `/run/gotelegram/stats_current.json`, список/создание backup, структурированные journal logs (`service`, `ok`, `exit_code`, `line_count`, `text`). +Функции: overview, проверка сайта на HTTP 200, service status/restart, чтение/запись `[access.users]`, enable/disable ключей через `/api/users//enabled`, генерация proxy links, traffic history из `/opt/gotelegram/stats_history.csv`, current stats из `/run/gotelegram/stats_current.json`, список/создание backup, структурированные journal logs (`service`, `ok`, `exit_code`, `line_count`, `text`). -`install_admin_web` вызывается при установке Telegram-бота. `auto_install_admin_web_if_possible` подхватывает админку после bootstrap/update, если Python уже установлен и файлы отличаются. При установке админки скрипт пытается установить/перезапустить `gotelegram-stats`; если это не удалось, оператор может нажать Repair stats в Traffic. Backup v1.3 сохраняет `admin_web/server.py` и `admin_web/static/`, restore возвращает их, удаляет legacy `admin_web/token` и пробует перезапустить `gotelegram-admin`. +Отключённые ключи хранятся в `/opt/gotelegram/disabled_users.json`: active keys остаются в `/etc/telemt/config.toml` под `[access.users]`, disabled keys удаляются из active block и могут быть возвращены обратно без потери secret. `main` защищён от удаления и отключения. + +`install_admin_web` вызывается при установке Telegram-бота. `auto_install_admin_web_if_possible` подхватывает админку после bootstrap/update, если Python уже установлен и файлы отличаются. При установке админки скрипт пытается установить/перезапустить `gotelegram-stats`; если это не удалось, оператор может нажать Repair stats в Traffic. Backup v1.4 сохраняет `admin_web/server.py`, `admin_web/static/` и `disabled_users.json`, restore возвращает их, удаляет legacy `admin_web/token` и пробует перезапустить `gotelegram-admin`. ### 13.2 Upgrade migration (v2.5.0) @@ -629,7 +631,7 @@ with socket.create_connection(("95.163.176.222", 443), timeout=5) as s: ## 17. Changelog -- **2.5.0 (2026-04-24)** — крупный maintenance pass в ветке `codex`: единая версия `2.5.0` в runtime и документации; удалён дефолтный PAT из `bootstrap.sh` (токен теперь только через `GOTELEGRAM_PAT`); `generate_telemt_toml` добавляет `[server.api]` на `127.0.0.1:9091` и metrics на `127.0.0.1:9090`, что нужно для управления пользователями и статистики; Telegram-бот получил меню `🔑 Keys` для `[access.users]` (добавить/удалить/показать ссылку/runtime info); добавлена локальная web-админка `gotelegram-admin` на `127.0.0.1:1984` с SSH-tunnel инструкцией в боте без отдельного web-admin токена, вкладочной UI-навигацией, i18n от языка установки, ручным переключателем RU/EN, site check на HTTP 200, structured journal logs, light/dark theme, адаптивом и stats repair endpoint; исправлено чтение traffic CSV в боте (header больше не ломает parsing); бот сам делает `stats_collect` перед показом статистики; `iptables` добавлен в optional deps и stats collector пытается установить его; CLI-смена шаблона теперь обновляет `config.json.template_id`, чтобы бот не показывал первый установленный шаблон; backup/restore версии `1.3` сохраняет bot `.env`, bot lang files, web-admin server/static, custom templates, templates catalog, stats history и полноценную структуру Let's Encrypt (`live/archive/renewal`) для переезда на новый сервер; добавлен безопасный детект 3x-ui/Xray на 443 и генерируется `/opt/gotelegram/shared-443-3xui.md` с объяснением shared-443 ограничений. +- **2.5.0 (2026-04-24)** — крупный maintenance pass в ветке `codex`: единая версия `2.5.0` в runtime и документации; удалён дефолтный PAT из `bootstrap.sh` (токен теперь только через `GOTELEGRAM_PAT`); `generate_telemt_toml` добавляет `[server.api]` на `127.0.0.1:9091` и metrics на `127.0.0.1:9090`, что нужно для управления пользователями и статистики; Telegram-бот получил меню `🔑 Keys` для `[access.users]` (добавить/отключить/включить/удалить/показать ссылку/runtime info); добавлена локальная web-админка goTelegram Pro `gotelegram-admin` на `127.0.0.1:1984` с SSH-tunnel инструкцией в боте без отдельного web-admin токена, вкладочной UI-навигацией, иконками, графическим overview, promo-modal раз в 24 часа, i18n от языка установки, ручным переключателем RU/EN, site check на HTTP 200, structured journal logs, light/dark theme, адаптивом и stats repair endpoint; исправлено чтение traffic CSV в боте (header больше не ломает parsing); бот сам делает `stats_collect` перед показом статистики; `iptables` добавлен в optional deps и stats collector пытается установить его; CLI-смена шаблона теперь обновляет `config.json.template_id`, чтобы бот не показывал первый установленный шаблон; backup/restore версии `1.4` сохраняет bot `.env`, bot lang files, disabled user keys, web-admin server/static, custom templates, templates catalog, stats history и полноценную структуру Let's Encrypt (`live/archive/renewal`) для переезда на новый сервер; добавлен безопасный детект 3x-ui/Xray на 443 и генерируется `/opt/gotelegram/shared-443-3xui.md` с объяснением shared-443 ограничений. - **2.4.6 (2026-04-10)** — universal `apt_lock_wait` helper: ожидание dpkg/apt lock при unattended-upgrades, исправляет установку nginx/certbot/python на свежих VPS. - **2.4.3 (2026-04-10)** — iter3-фикс: `bot_action_dispatch` оборачивается во `flock -w 30` на `/var/lock/gotelegram-bot-action.lock`. Обнаружена гонка: параллельные `change-lite-domain` получали `"no secret in config"`, потому что один процесс читал `config.json`, пока другой делал `jq ... > tmp && mv`. `util-linux` (содержит `flock`) добавлен в `critical` deps, `check_deps_present` и маппинги `apt_pkg_for_cmd`/`dnf_pkg_for_cmd`. - **2.4.2 (2026-04-10)** — реализация non-interactive `bot_action_*` в install.sh (change-template + change-lite-domain с JSON-ответом). bot.py подключает `run_bot_action()` и делает реальную работу вместо stub'ов. Критфиксы: (a) `safe_edit_message` принимает `disable_web_page_preview` (иначе TypeError в success-пути cb_pro_confirm); (b) чтение/запись `config['template_id']` вместо `config['template']` (save_gotelegram_config всегда писал `template_id`, бот смотрел не туда); (c) `bot_update_config_field` использует shell `date -Iseconds` вместо `jq now|todate` (jq 1.5 совместимость для Debian 10); (d) `asyncio.Lock _BOT_ACTION_LOCK` сериализует callback'и в процессе бота; (e) валидация `_TPL_ID_RE`/`_DOMAIN_RE` до subprocess. Полный аудит и автоустановка зависимостей: `ensure_deps` разделяет critical/optional, дедуплицирует пакеты, re-verify после install; `apt_pkg_for_cmd` и `dnf_pkg_for_cmd` мапят команды на пакеты (dig→dnsutils/bind-utils, xxd→xxd/vim-common, flock→util-linux). `check_deps_present` — быстрый чек без `apt-get update`. diff --git a/DOCS_HUMAN.md b/DOCS_HUMAN.md index ff50d27..181619d 100644 --- a/DOCS_HUMAN.md +++ b/DOCS_HUMAN.md @@ -1,4 +1,4 @@ -# GoTelegram Pro — руководство пользователя +# goTelegram Pro — руководство пользователя **Версия:** 2.5.0 **Репозиторий:** `anten-ka/gotelegram_pro` @@ -8,7 +8,7 @@ ## 1. Что это такое -GoTelegram Pro — это готовый менеджер прокси-сервера MTProxy для Telegram. Он делает три вещи, которые иначе пришлось бы собирать вручную: +goTelegram Pro — это готовый менеджер прокси-сервера MTProxy для Telegram. Он делает три вещи, которые иначе пришлось бы собирать вручную: 1. Ставит и настраивает ядро **telemt** (это современный Rust-порт mtproto-proxy с fake-TLS маскировкой). 2. Запускает рядом обычный HTTPS-сайт на настоящем домене, так что провайдеру со стороны всё выглядит как посещение безобидного лендинга — а на самом деле в том же соединении ходит Telegram-трафик. Это называется «stealth» или «Pro-режим». @@ -125,7 +125,7 @@ CLI и бот переведены на русский и английский. ## 7. Бекап и восстановление -Пункт 8 делает один файл `.tar.gz` со всем, что нужно: `/etc/telemt/config.toml`, `/opt/gotelegram/config.json`, данные nginx-сайта, сертификаты, состояние Telegram-бота и локальной web-админки. По умолчанию в `/opt/gotelegram/backups/`. +Пункт 8 делает один файл `.tar.gz` со всем, что нужно: `/etc/telemt/config.toml`, `/opt/gotelegram/config.json`, `/opt/gotelegram/disabled_users.json`, данные nginx-сайта, сертификаты, состояние Telegram-бота и локальной web-админки. По умолчанию в `/opt/gotelegram/backups/`. Пункт 9 принимает такой архив и восстанавливает всё обратно (конфиг, сервис, ссылка — всё то же, что было). @@ -145,7 +145,9 @@ CLI и бот переведены на русский и английский. - есть светлая/тёмная тема, вкладки и адаптивная вёрстка под desktop/mobile; - Telegram-бот показывает инструкцию для Termius и обычную команду `ssh -L 1984:127.0.0.1:1984 root@SERVER`. -В админке есть dashboard, проверка сайта `https://домен/` на HTTP 200, статус сервисов, управление `[access.users]`, генерация ссылок, SVG-график traffic history, кнопка восстановления сборщика статистики, список бекапов и просмотр логов с количеством строк и статусом `journalctl`. +В админке есть dashboard, проверка сайта `https://домен/` на HTTP 200, статус сервисов, управление ключами `[access.users]` с добавлением, удалением и отключением через switch, генерация ссылок, SVG-график traffic history, кнопка восстановления сборщика статистики, список бекапов и просмотр логов с количеством строк и статусом `journalctl`. + +Отключённые ключи убираются из активного telemt-конфига и сохраняются в `/opt/gotelegram/disabled_users.json`, поэтому их можно включить обратно без потери secret. Основной ключ `main` защищён от удаления и отключения. --- @@ -154,7 +156,7 @@ CLI и бот переведены на русский и английский. Два типа обновлений: - **Обновление ядра telemt** (пункт 10) — тянет свежий бинарник с GitHub Releases `telemt/telemt`, сохраняет старый в `.bak`, перезапускает сервис. Конфиг остаётся как был. -- **Обновление самого GoTelegram** (пункт 1) — переустанавливает скрипты и lib/. Файл `config.json` не трогается, ключ и домен не меняются. +- **Обновление самого goTelegram Pro** (пункт 1) — переустанавливает скрипты и lib/. Файл `config.json` не трогается, ключ и домен не меняются. Bootstrap.sh умеет сам обновлять всё, если запустить его повторно. @@ -214,7 +216,7 @@ A: Сам MTProxy — да, это публичная технология из | Скрипты (`install.sh`, `lib/`) | `/opt/gotelegram/` | | Симлинк запуска | `/usr/local/bin/gotelegram` | | Конфиг telemt | `/etc/telemt/config.toml` | -| Конфиг GoTelegram (JSON) | `/opt/gotelegram/config.json` | +| Конфиг goTelegram Pro (JSON) | `/opt/gotelegram/config.json` | | Бинарник telemt | `/usr/local/bin/telemt` | | Systemd юнит | `/etc/systemd/system/telemt.service` | | Бот | `/opt/gotelegram-bot/` | @@ -222,7 +224,7 @@ A: Сам MTProxy — да, это публичная технология из | Сайт (Pro-режим) | `/var/www/gotelegram-site/` | | nginx site | `/etc/nginx/sites-available/gotelegram` | | Бекапы | `/opt/gotelegram/backups/` | -| Лог GoTelegram | `/var/log/gotelegram.log` | +| Лог goTelegram Pro | `/var/log/gotelegram.log` | | Логи telemt | `journalctl -u telemt` | | Логи бота | `journalctl -u gotelegram-bot` | @@ -238,7 +240,7 @@ A: Сам MTProxy — да, это публичная технология из ## Changelog (коротко) -- **2.5.0** — единая версия по коду и документации; удалён дефолтный PAT из `bootstrap.sh`; исправлена статистика в боте (CSV header больше не ломает чтение истории, бот сам обновляет snapshot); CLI-смена шаблона теперь обновляет `config.json.template_id`, поэтому бот показывает текущий шаблон; telemt TOML включает локальный API `127.0.0.1:9091` и metrics `127.0.0.1:9090`; добавлено меню Telegram-бота для отдельных ключей пользователей (`[access.users]`): список, добавление, удаление, ссылка и runtime/API-информация; добавлена локальная web-админка на `127.0.0.1:1984` под SSH tunnel без отдельного токена, с вкладками, i18n от языка установки, ручным переключателем RU/EN, проверкой сайта на HTTP 200, тёмной темой, адаптивом и repair-кнопкой для статистики; backup/restore сохраняет bot `.env`, языки бота, web-admin server/static, custom templates, stats history и структуру Let's Encrypt для переезда на новый VPS; добавлен безопасный детект 3x-ui/Xray на 443 с предупреждением и заметкой по shared-443. +- **2.5.0** — единая версия по коду и документации; удалён дефолтный PAT из `bootstrap.sh`; исправлена статистика в боте (CSV header больше не ломает чтение истории, бот сам обновляет snapshot); CLI-смена шаблона теперь обновляет `config.json.template_id`, поэтому бот показывает текущий шаблон; telemt TOML включает локальный API `127.0.0.1:9091` и metrics на `127.0.0.1:9090`; добавлено меню Telegram-бота для отдельных ключей пользователей (`[access.users]`): список, добавление, отключение/включение, удаление, ссылка и runtime/API-информация; добавлена локальная web-админка goTelegram Pro на `127.0.0.1:1984` под SSH tunnel без отдельного токена, с вкладками, иконками, promo-разделом раз в 24 часа, i18n от языка установки, ручным переключателем RU/EN, проверкой сайта на HTTP 200, тёмной темой, адаптивом и repair-кнопкой для статистики; backup/restore сохраняет bot `.env`, языки бота, отключённые ключи, web-admin server/static, custom templates, stats history и структуру Let's Encrypt для переезда на новый VPS; добавлен безопасный детект 3x-ui/Xray на 443 с предупреждением и заметкой по shared-443. - **2.4.6** — ожидание apt/dpkg lock на свежих Ubuntu/Debian, чтобы установка nginx/certbot/Python не падала во время unattended-upgrades. - **2.4.3** — фикс гонки в `bot_action_dispatch`: параллельные вызовы `change-lite-domain`/`change-template` (например, два пользователя бота одновременно) могли получить ошибку «no secret in config», если один процесс читал `config.json` в момент, когда другой его перезаписывал через `jq`. Теперь диспетчер оборачивается в `flock(1)` с таймаутом 30 с; `util-linux` (содержит `flock`) добавлен в критические зависимости. - **2.4.2** — смена шаблона и домена маскировки **прямо из Telegram-бота** без SSH. Раньше эти пункты меню показывали сообщение «сделай через CLI», теперь бот вызывает `install.sh --action=change-template --json` / `--action=change-lite-domain --json` и разбирает ответ. Плюс: безопасный `safe_edit_message` принимает `disable_web_page_preview`; поле статуса шаблона наконец-то отображается (раньше читалось не из того ключа JSON); полный аудит и автоустановка системных зависимостей при первом запуске (`curl jq openssl git xxd tar dig flock` + опциональные `qrencode bc`); `asyncio.Lock` в боте сериализует параллельные callback'и; валидация tpl\_id (`[A-Za-z0-9_-]{1,64}`) и домена до subprocess. diff --git a/admin-web/server.py b/admin-web/server.py index 94bb481..5ccac3c 100644 --- a/admin-web/server.py +++ b/admin-web/server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -GoTelegram local web admin. +goTelegram Pro local web admin. The service is intentionally bound to 127.0.0.1:1984. Operators reach it through an SSH tunnel; it must never be exposed directly on the public network. @@ -37,6 +37,7 @@ CURRENT_STATS = Path(os.getenv("GOTELEGRAM_STATS_CURRENT", "/run/gotelegram/stat BACKUP_DIR = Path(os.getenv("GOTELEGRAM_BACKUP_DIR", "/opt/gotelegram/backups")) INSTALL_DIR = Path(os.getenv("GOTELEGRAM_DIR", "/opt/gotelegram")) BOT_DIR = Path(os.getenv("GOTELEGRAM_BOT_DIR", "/opt/gotelegram-bot")) +DISABLED_USERS_FILE = Path(os.getenv("GOTELEGRAM_DISABLED_USERS", "/opt/gotelegram/disabled_users.json")) HOST = os.getenv("GOTELEGRAM_ADMIN_HOST", "127.0.0.1") PORT = int(os.getenv("GOTELEGRAM_ADMIN_PORT", "1984")) @@ -152,6 +153,44 @@ def read_telemt_users() -> dict[str, str]: return users +def read_disabled_users() -> dict[str, str]: + raw = load_json(DISABLED_USERS_FILE, {}) or {} + if not isinstance(raw, dict): + return {} + users = raw.get("users") if isinstance(raw.get("users"), dict) else raw + if not isinstance(users, dict): + return {} + clean: dict[str, str] = {} + for name, secret in users.items(): + if name in {"version", "updated_at"}: + continue + name_s = str(name).strip() + secret_s = str(secret or "").strip() + if USER_RE.match(name_s) and secret_s: + clean[name_s] = secret_s + return clean + + +def write_disabled_users(users: dict[str, str]) -> None: + payload = { + "version": 1, + "updated_at": utc_now(), + "users": {name: users[name] for name in sorted(users)}, + } + save_json(DISABLED_USERS_FILE, payload) + + +def read_user_records() -> dict[str, dict[str, Any]]: + active = read_telemt_users() + disabled = read_disabled_users() + records: dict[str, dict[str, Any]] = {} + for name, secret in disabled.items(): + records[name] = {"secret": secret, "enabled": False} + for name, secret in active.items(): + records[name] = {"secret": secret, "enabled": True} + return records + + def _ordered_user_lines(users: dict[str, str]) -> list[str]: names = [] if "main" in users: @@ -462,14 +501,15 @@ def read_log_payload(service: str) -> dict[str, Any]: } -def user_payload(name: str, secret: str, include_runtime: bool = False) -> dict[str, Any]: +def user_payload(name: str, secret: str, enabled: bool = True, include_runtime: bool = False) -> dict[str, Any]: item: dict[str, Any] = { "name": name, "secret": secret, "link": proxy_link(secret), "main": name == "main", + "enabled": bool(enabled), } - if include_runtime: + if include_runtime and enabled: item["runtime"] = telemt_api(f"/v1/users/{urllib.parse.quote(name, safe='')}") return item @@ -477,7 +517,7 @@ def user_payload(name: str, secret: str, include_runtime: bool = False) -> dict[ def overview_payload() -> dict[str, Any]: config = load_json(GOTELEGRAM_CONFIG, {}) or {} language = read_language(config) - users = read_telemt_users() + users = read_user_records() current = load_json(CURRENT_STATS, {}) or {} history = load_stats_history() summary = telemt_api("/v1/stats/summary") @@ -506,7 +546,7 @@ def overview_payload() -> dict[str, Any]: class AdminHandler(BaseHTTPRequestHandler): - server_version = "GoTelegramAdmin/2.5.0" + server_version = "goTelegramProAdmin/2.5.0" def log_message(self, fmt: str, *args: Any) -> None: print("%s - %s" % (self.address_string(), fmt % args)) @@ -542,15 +582,20 @@ class AdminHandler(BaseHTTPRequestHandler): if path == "/api/overview": self.send_json({"ok": True, "data": overview_payload()}) elif path == "/api/users": - users = read_telemt_users() - self.send_json({"ok": True, "data": [user_payload(k, v) for k, v in sorted(users.items())]}) + users = read_user_records() + items = [] + for name in sorted(users, key=lambda item: (item != "main", item)): + record = users[name] + items.append(user_payload(name, record["secret"], record["enabled"])) + self.send_json({"ok": True, "data": items}) elif path.startswith("/api/users/"): name = urllib.parse.unquote(path[len("/api/users/"):]) - users = read_telemt_users() + users = read_user_records() if name not in users: self.send_error_json(404, "user not found") return - self.send_json({"ok": True, "data": user_payload(name, users[name], include_runtime=True)}) + record = users[name] + self.send_json({"ok": True, "data": user_payload(name, record["secret"], record["enabled"], include_runtime=True)}) elif path == "/api/backups": self.send_json({"ok": True, "data": list_backups()}) elif path == "/api/stats": @@ -586,10 +631,11 @@ class AdminHandler(BaseHTTPRequestHandler): if not USER_RE.match(name): self.send_error_json(400, "invalid user name") return - users = read_telemt_users() - if name in users: + records = read_user_records() + if name in records: self.send_error_json(409, "user already exists") return + users = read_telemt_users() seed = f"{name}:{time.time()}:{secrets.token_hex(32)}".encode() secret = hashlib.sha256(seed).hexdigest()[:32] users[name] = secret @@ -599,7 +645,37 @@ class AdminHandler(BaseHTTPRequestHandler): self.send_error_json(500, f"failed to save config: {exc}") return restarted = restart_service("telemt") - self.send_json({"ok": True, "data": user_payload(name, secret), "restarted": restarted}) + self.send_json({"ok": True, "data": user_payload(name, secret, True), "restarted": restarted}) + elif path.startswith("/api/users/") and path.endswith("/enabled"): + name = urllib.parse.unquote(path[len("/api/users/"):-len("/enabled")]) + if name == "main": + self.send_error_json(400, "main user cannot be disabled") + return + enabled = bool(body.get("enabled")) + active = read_telemt_users() + disabled = read_disabled_users() + records = read_user_records() + if name not in records: + self.send_error_json(404, "user not found") + return + if enabled: + secret = disabled.pop(name, records[name]["secret"]) + active[name] = secret + else: + secret = active.pop(name, records[name]["secret"]) + disabled[name] = secret + try: + if enabled: + write_telemt_users(active) + write_disabled_users(disabled) + else: + write_disabled_users(disabled) + write_telemt_users(active) + except Exception as exc: + self.send_error_json(500, f"failed to save config: {exc}") + return + restarted = restart_service("telemt") + self.send_json({"ok": True, "data": user_payload(name, secret, enabled), "restarted": restarted}) elif path == "/api/backups": ok, result = create_backup() self.send_json({"ok": ok, "data": {"path": result, "backups": list_backups()}}, 200 if ok else 500) @@ -640,13 +716,17 @@ class AdminHandler(BaseHTTPRequestHandler): if name == "main": self.send_error_json(400, "main user cannot be deleted") return - users = read_telemt_users() - if name not in users: + active = read_telemt_users() + disabled = read_disabled_users() + records = read_user_records() + if name not in records: self.send_error_json(404, "user not found") return - users.pop(name, None) + active.pop(name, None) + disabled.pop(name, None) try: - write_telemt_users(users) + write_telemt_users(active) + write_disabled_users(disabled) except Exception as exc: self.send_error_json(500, f"failed to save config: {exc}") return @@ -702,7 +782,7 @@ def main() -> None: if not STATIC_DIR.exists(): raise SystemExit(f"static dir not found: {STATIC_DIR}") httpd = ThreadingHTTPServer((HOST, PORT), AdminHandler) - print(f"GoTelegram admin listening on http://{HOST}:{PORT}") + print(f"goTelegram Pro admin listening on http://{HOST}:{PORT}") httpd.serve_forever() diff --git a/admin-web/static/app.js b/admin-web/static/app.js index 87a05c4..dad2532 100644 --- a/admin-web/static/app.js +++ b/admin-web/static/app.js @@ -43,6 +43,7 @@ const i18n = { collectStats: "Collect", repairStats: "Repair stats", tableTime: "Time", + tableStatus: "Status", tableProxyDelta: "Proxy delta", tableSiteDelta: "Site delta", tableProxyTotal: "Proxy total", @@ -56,6 +57,10 @@ const i18n = { copyLink: "Copy link", copySecret: "Copy secret", delete: "Delete", + enabled: "Enabled", + disabled: "Disabled", + disableKey: "Disable key", + enableKey: "Enable key", main: "main", createBackup: "Create backup", loadLogs: "Load", @@ -121,6 +126,15 @@ const i18n = { logsLines: "lines", logsNoData: "No log lines", languageSaved: "Language saved", + keyEnabled: "Key enabled", + keyDisabled: "Key disabled", + visualTitle: "443 shared edge", + visualText: "Website, MTProxy and local admin status in one operational view.", + promoEyebrow: "Promo", + promoTitle: "Support goTelegram Pro", + promoHosting1: "Hosting #1", + promoHosting2: "Hosting #2", + promoTips: "Tips", pageDashboardTitle: "Dashboard", pageDashboardKicker: "Local Admin", pageTrafficTitle: "Traffic", @@ -175,6 +189,7 @@ const i18n = { collectStats: "Собрать", repairStats: "Починить статистику", tableTime: "Время", + tableStatus: "Статус", tableProxyDelta: "Proxy delta", tableSiteDelta: "Site delta", tableProxyTotal: "Proxy всего", @@ -188,6 +203,10 @@ const i18n = { copyLink: "Копировать ссылку", copySecret: "Копировать secret", delete: "Удалить", + enabled: "Включён", + disabled: "Отключён", + disableKey: "Отключить ключ", + enableKey: "Включить ключ", main: "основной", createBackup: "Создать бекап", loadLogs: "Загрузить", @@ -253,6 +272,15 @@ const i18n = { logsLines: "строк", logsNoData: "Строк логов нет", languageSaved: "Язык сохранён", + keyEnabled: "Ключ включён", + keyDisabled: "Ключ отключён", + visualTitle: "Единый 443 edge", + visualText: "Сайт, MTProxy и локальная админка в одном рабочем обзоре.", + promoEyebrow: "Промо", + promoTitle: "Поддержать goTelegram Pro", + promoHosting1: "Хостинг #1", + promoHosting2: "Хостинг #2", + promoTips: "Чаевые", pageDashboardTitle: "Обзор", pageDashboardKicker: "Локальная админка", pageTrafficTitle: "Трафик", @@ -356,6 +384,8 @@ function applyI18n() { $("#languageSelect").value = state.lang; $("#settingsLanguage").textContent = state.lang === "ru" ? "Русский" : "English"; $("#settingsTheme").textContent = state.theme === "dark" ? t("darkTheme") : t("lightTheme"); + $("#visualTitle").textContent = t("visualTitle"); + $("#visualText").textContent = t("visualText"); updatePageTitle(); } @@ -607,16 +637,25 @@ function renderHistoryTable(rows) { function renderUsers() { const tbody = $("#usersTable"); if (!state.users.length) { - tbody.innerHTML = `${escapeHtml(t("noKeys"))}`; + tbody.innerHTML = `${escapeHtml(t("noKeys"))}`; return; } tbody.innerHTML = state.users.map((user) => ` - + ${escapeHtml(user.name)}${user.main ? ` ${escapeHtml(t("main"))}` : ""} + +
+ + ${escapeHtml(user.enabled ? t("enabled") : t("disabled"))} +
+ ${escapeHtml(user.secret)} - + @@ -708,6 +747,17 @@ async function deleteUser(name) { await refreshAll(); } +async function setUserEnabled(name, enabled) { + const data = await api(`/api/users/${encodeURIComponent(name)}/enabled`, { + method: "POST", + body: JSON.stringify({ enabled }), + }); + const message = data.enabled ? t("keyEnabled") : t("keyDisabled"); + addEvent(message, name); + toast(message); + await refreshAll(); +} + async function createBackup() { const btn = $("#createBackupBtn"); btn.disabled = true; @@ -802,6 +852,15 @@ async function copyText(value) { } } +function maybeShowPromo() { + const key = "gotelegram-promo-last"; + const now = Math.floor(Date.now() / 1000); + const last = Number(localStorage.getItem(key) || 0); + if (now - last < 86400) return; + localStorage.setItem(key, String(now)); + $("#promoModal").hidden = false; +} + document.addEventListener("click", async (eventObj) => { const nav = eventObj.target.closest("[data-nav]"); if (nav) { @@ -827,6 +886,17 @@ document.addEventListener("click", async (eventObj) => { } }); +document.addEventListener("change", (eventObj) => { + const input = eventObj.target.closest("[data-toggle-user]"); + if (!input) return; + input.disabled = true; + setUserEnabled(input.dataset.toggleUser, input.checked).catch((err) => { + input.checked = !input.checked; + input.disabled = false; + toast(err.message); + }); +}); + $("#addUserForm").addEventListener("submit", (eventObj) => { eventObj.preventDefault(); const input = $("#userName"); @@ -841,6 +911,9 @@ $("#addUserForm").addEventListener("submit", (eventObj) => { $("#refreshBtn").addEventListener("click", refreshAll); $("#languageSelect").addEventListener("change", (eventObj) => setLanguage(eventObj.target.value)); +$("#promoClose").addEventListener("click", () => { + $("#promoModal").hidden = true; +}); $("#createBackupBtn").addEventListener("click", createBackup); $("#loadLogsBtn").addEventListener("click", loadLogs); $("#repairStatsBtn").addEventListener("click", repairStats); @@ -852,3 +925,4 @@ setTheme(state.theme); renderEvents(); refreshAll(); loadLogs(); +maybeShowPromo(); diff --git a/admin-web/static/index.html b/admin-web/static/index.html index 7aa7311..9178b36 100644 --- a/admin-web/static/index.html +++ b/admin-web/static/index.html @@ -3,7 +3,7 @@ - GoTelegram Admin + goTelegram Pro Admin - +
@@ -19,18 +19,18 @@
GT
- GoTelegram + goTelegram Pro Local Admin