version 1.2.3: SSH-туннель в веб-панели, авто-ID бота, фиксы
- templates/index.html: вкладка SSL — замена самоподписного сертификата на SSH-туннель, авто-ID при установке бота - app.py: API /api/panel/access (get/set ip/ssh), /api/bot/detect-id, правка парсинга трафика - bot.py: позиционный парсинг mita get users (8 колонок) - install.sh, mita-ctl.sh: автоопределение Telegram ID через getUpdates API, исправлен unbound variable BOLD, SCRIPT_DIR - install-panel.sh: копирование bot.py и install-bot.sh в /opt/mita-panel/ - README: обновлена дорожная карта
This commit is contained in:
@@ -78,8 +78,9 @@ curl -Ls https://raw.githubusercontent.com/grendervilll/mitactl/main/bootstrap.s
|
||||
- [x] Генерация клиентских конфигов (mieru JSON и sing-box JSON)
|
||||
- [x] Скачивание конфигов и отображение QR-кодов
|
||||
- [x] Управление WARP на пользователя (toggle + правила: домены, IP, geosite, geoip, URL источники)
|
||||
- [x] SSL-сертификаты: самоподписной (10 лет) создаётся автоматически при установке, Let's Encrypt через UI
|
||||
- [x] Кнопка перезапуска панели на вкладке SSL
|
||||
- [x] SSL-сертификаты: самоподписной (10 лет) авто при установке, LE через UI, кнопка перезапуска
|
||||
- [x] Переключение режима доступа: IP / SSH-туннель через вкладку SSL в веб-панели
|
||||
- [x] Автоопределение Telegram ID через API бота при установке (веб-панель, CLI, install.sh)
|
||||
- [x] Интеграция fail2ban: rate-limiting панели + fail2ban jail (пресеты)
|
||||
- [x] Управление файрволом (UFW/iptables) через CLI и панель
|
||||
- [x] Режим SSH-туннеля: панель только на 127.0.0.1
|
||||
|
||||
@@ -677,6 +677,99 @@ def api_panel_restart():
|
||||
return jsonify({"ok": False, "error": r.stderr.strip()}), 500
|
||||
return jsonify({"ok": True})
|
||||
|
||||
@app.route(f"{BASE}/api/panel/access")
|
||||
@login_required
|
||||
def api_panel_access_get():
|
||||
pc = load_panel_config()
|
||||
panel_port = os.environ.get("PANEL_PORT", "8080")
|
||||
secret = os.environ.get("SECRET_PATH", "")
|
||||
mode = pc.get("access_mode", "ip")
|
||||
ssh_port = pc.get("ssh_port", 22)
|
||||
return jsonify({
|
||||
"mode": mode,
|
||||
"panel_port": panel_port,
|
||||
"secret": secret,
|
||||
"ssh_port": ssh_port,
|
||||
"server_ip": get_server_ip(),
|
||||
})
|
||||
|
||||
@app.route(f"{BASE}/api/panel/access", methods=["POST"])
|
||||
@login_required
|
||||
def api_panel_access_set():
|
||||
data = request.get_json(silent=True) or {}
|
||||
mode = data.get("mode", "").strip()
|
||||
|
||||
if mode not in ("ip", "ssh"):
|
||||
return jsonify({"ok": False, "error": "mode должен быть ip или ssh"}), 400
|
||||
|
||||
pc = load_panel_config()
|
||||
panel_port = os.environ.get("PANEL_PORT", "8080")
|
||||
secret = os.environ.get("SECRET_PATH", "")
|
||||
|
||||
if mode == "ssh":
|
||||
bind = "127.0.0.1"
|
||||
# Закрыть порт в firewall
|
||||
_fw_close_port(panel_port)
|
||||
else:
|
||||
bind = "0.0.0.0"
|
||||
_fw_open_port(panel_port)
|
||||
|
||||
# Обновить start.sh с новым bind
|
||||
start_sh = "/opt/mita-panel/start.sh"
|
||||
try:
|
||||
Path(start_sh).write_text(f"""#!/bin/bash
|
||||
set -a; source /etc/mita/panel.env; set +a
|
||||
SSL_ARGS=""
|
||||
if [[ -n "$SSL_CERT" && -n "$SSL_KEY" && -f "$SSL_CERT" && -f "$SSL_KEY" ]]; then
|
||||
SSL_ARGS="--certfile=$SSL_CERT --keyfile=$SSL_KEY"
|
||||
fi
|
||||
exec /opt/mita-panel/venv/bin/gunicorn \\
|
||||
--bind {bind}:$PANEL_PORT \\
|
||||
--workers 2 --timeout 120 \\
|
||||
--access-logfile /var/log/mita-panel-access.log \\
|
||||
--error-logfile /var/log/mita-panel.log \\
|
||||
$SSL_ARGS app:app
|
||||
""")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Сохранить режим в panel.json
|
||||
pc["access_mode"] = mode
|
||||
Path(PANEL_CONFIG).write_text(json.dumps(pc, indent=2))
|
||||
|
||||
subprocess.run(["systemctl","restart","mita-panel"], capture_output=True, timeout=10)
|
||||
|
||||
ssh_port = pc.get("ssh_port", 22)
|
||||
return jsonify({
|
||||
"ok": True,
|
||||
"mode": mode,
|
||||
"panel_port": panel_port,
|
||||
"secret": secret,
|
||||
"server_ip": get_server_ip(),
|
||||
"ssh_port": ssh_port,
|
||||
})
|
||||
|
||||
def _fw_close_port(port):
|
||||
try:
|
||||
if subprocess.run(["which","ufw"], capture_output=True).returncode == 0:
|
||||
subprocess.run(["ufw","delete","allow",f"{port}/tcp"], capture_output=True)
|
||||
elif subprocess.run(["which","iptables"], capture_output=True).returncode == 0:
|
||||
subprocess.run(["iptables","-D","INPUT","-p","tcp","--dport",str(port),"-j","ACCEPT"], capture_output=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _fw_open_port(port):
|
||||
try:
|
||||
if subprocess.run(["which","ufw"], capture_output=True).returncode == 0 \
|
||||
and subprocess.run(["ufw","status"], capture_output=True, text=True).stdout.find("active") != -1:
|
||||
subprocess.run(["ufw","allow",f"{port}/tcp","comment","mita-panel"], capture_output=True)
|
||||
elif subprocess.run(["which","iptables"], capture_output=True).returncode == 0:
|
||||
r = subprocess.run(["iptables","-C","INPUT","-p","tcp","--dport",str(port),"-j","ACCEPT"], capture_output=True)
|
||||
if r.returncode != 0:
|
||||
subprocess.run(["iptables","-I","INPUT","1","-p","tcp","--dport",str(port),"-j","ACCEPT"], capture_output=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _update_env(key, value):
|
||||
env_file = "/etc/mita/panel.env"
|
||||
lines = []
|
||||
@@ -1132,6 +1225,43 @@ def api_bot_config():
|
||||
"admin_ids": cfg.get("admin_ids", []),
|
||||
})
|
||||
|
||||
@app.route(f"{BASE}/api/bot/detect-id", methods=["POST"])
|
||||
@login_required
|
||||
def api_bot_detect_id():
|
||||
data = request.get_json(silent=True) or {}
|
||||
token = data.get("token","").strip()
|
||||
if not token:
|
||||
return jsonify({"ok": False, "error": "Токен обязателен"}), 400
|
||||
|
||||
import urllib.request
|
||||
url = f"https://api.telegram.org/bot{token}/getUpdates"
|
||||
# Очищаем pending updates перед опросом
|
||||
try:
|
||||
req = urllib.request.Request(url + "?offset=-1", headers={"User-Agent": "mita-panel"})
|
||||
urllib.request.urlopen(req, timeout=5).read()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
admin_id = ""
|
||||
for _ in range(20):
|
||||
time.sleep(3)
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "mita-panel"})
|
||||
with urllib.request.urlopen(req, timeout=5) as resp:
|
||||
data_raw = json.loads(resp.read())
|
||||
ids = [r["message"]["from"]["id"]
|
||||
for r in data_raw.get("result", [])
|
||||
if "message" in r and "from" in r["message"]]
|
||||
if ids:
|
||||
admin_id = str(ids[-1])
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if admin_id:
|
||||
return jsonify({"ok": True, "admin_id": admin_id})
|
||||
return jsonify({"ok": False, "error": "Не получено ни одного сообщения. Отправьте любое сообщение боту и попробуйте снова."}), 404
|
||||
|
||||
# ── API: скачать конфиг как файл ─────────────────────────────────────────────
|
||||
@app.route(f"{BASE}/api/users/config/download")
|
||||
@login_required
|
||||
|
||||
@@ -84,6 +84,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cp "$SCRIPT_DIR/app.py" "$PANEL_DIR/"
|
||||
cp -r "$SCRIPT_DIR/templates" "$PANEL_DIR/"
|
||||
[[ -d "$SCRIPT_DIR/static" ]] && cp -r "$SCRIPT_DIR/static" "$PANEL_DIR/"
|
||||
# Копируем установщик бота рядом — чтобы работала установка через веб-панель и mita-ctl
|
||||
[[ -f "$SCRIPT_DIR/install-bot.sh" ]] && cp "$SCRIPT_DIR/install-bot.sh" "$PANEL_DIR/"
|
||||
[[ -f "$SCRIPT_DIR/bot.py" ]] && cp "$SCRIPT_DIR/bot.py" "$PANEL_DIR/"
|
||||
ok "Файлы скопированы в $PANEL_DIR"
|
||||
|
||||
# ── генерация секретов ─────────────────────────────────────────────
|
||||
|
||||
+33
-9
@@ -10,7 +10,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# ========================= ЦВЕТА ДЛЯ ВЫВОДА =========================
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
@@ -782,10 +782,10 @@ else
|
||||
echo -e " В ответ вы получите ${BOLD}токен${NC} — длинную строку вида:"
|
||||
echo -e " ${YELLOW}1234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw${NC}"
|
||||
echo ""
|
||||
echo -e " ${BOLD}2.${NC} Найдите ${GREEN}@userinfobot${NC} и нажмите /start"
|
||||
echo -e " Он покажет ваш ${BOLD}Telegram ID${NC} — число (например 123456789)"
|
||||
echo -e " ${BOLD}2.${NC} Откройте своего бота в Telegram и отправьте любое сообщение"
|
||||
echo -e " (например «привет»). ID определится автоматически."
|
||||
echo ""
|
||||
echo -e " ${BOLD}3.${NC} Когда токен и ID готовы — нажмите Enter для продолжения"
|
||||
echo -e " ${BOLD}3.${NC} Когда токен готов — нажмите Enter для продолжения"
|
||||
|
||||
while true; do
|
||||
echo ""
|
||||
@@ -797,15 +797,39 @@ else
|
||||
break
|
||||
done
|
||||
|
||||
echo ""
|
||||
info "Определение вашего Telegram ID..."
|
||||
echo -e " Отправьте ${YELLOW}любое сообщение${NC} своему боту в Telegram прямо сейчас."
|
||||
echo -e " Ожидание сообщения (макс. 60 сек)..."
|
||||
|
||||
BOT_ADMIN=""
|
||||
for i in $(seq 1 30); do
|
||||
BOT_ADMIN=$(curl -s --max-time 5 "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates" 2>/dev/null | \
|
||||
python3 -c "
|
||||
import sys,json
|
||||
try:
|
||||
d=json.load(sys.stdin)
|
||||
ids=[r['message']['from']['id'] for r in d.get('result',[]) if 'message' in r and 'from' in r['message']]
|
||||
print(ids[-1] if ids else '')
|
||||
except: pass
|
||||
" 2>/dev/null)
|
||||
[[ -n "$BOT_ADMIN" ]] && break
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [[ -z "$BOT_ADMIN" || ! "$BOT_ADMIN" =~ ^[0-9]+$ ]]; then
|
||||
warn "Не удалось определить ID автоматически."
|
||||
while true; do
|
||||
echo ""
|
||||
read -r -p " Впишите ваш Telegram ID (число): " BOT_ADMIN
|
||||
if [[ -z "$BOT_ADMIN" || ! "$BOT_ADMIN" =~ ^[0-9]+$ ]]; then
|
||||
warn "Telegram ID должен быть числом (узнайте у @userinfobot)"
|
||||
continue
|
||||
fi
|
||||
read -r -p " Впишите ваш Telegram ID вручную (число): " BOT_ADMIN
|
||||
if [[ -n "$BOT_ADMIN" && "$BOT_ADMIN" =~ ^[0-9]+$ ]]; then
|
||||
break
|
||||
fi
|
||||
warn "Telegram ID должен быть числом"
|
||||
done
|
||||
else
|
||||
ok "Telegram ID определён: ${GREEN}${BOT_ADMIN}${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
info "Запуск установки Telegram-бота..."
|
||||
|
||||
+27
-2
@@ -1151,7 +1151,7 @@ _bot_install() {
|
||||
section "Установка Telegram-бота"
|
||||
|
||||
local installer=""
|
||||
for candidate in /opt/mita-panel/install-bot.sh "$SCRIPT_DIR/install-bot.sh"; do
|
||||
for candidate in /opt/mita-panel/install-bot.sh; do
|
||||
[[ -f "$candidate" ]] && { installer="$candidate"; break; }
|
||||
done
|
||||
|
||||
@@ -1164,7 +1164,32 @@ _bot_install() {
|
||||
read -r -p "Токен бота (от @BotFather): " token
|
||||
[[ -z "$token" ]] && { error "Токен обязателен"; return; }
|
||||
|
||||
read -r -p "Ваш Telegram ID (число, @userinfobot): " admin_id
|
||||
echo ""
|
||||
info "Определение вашего Telegram ID..."
|
||||
echo -e " Отправьте ${YELLOW}любое сообщение${NC} своему боту в Telegram прямо сейчас."
|
||||
echo -e " Ожидание сообщения (макс. 60 сек)..."
|
||||
|
||||
admin_id=""
|
||||
for i in $(seq 1 30); do
|
||||
admin_id=$(curl -s --max-time 5 "https://api.telegram.org/bot${token}/getUpdates" 2>/dev/null | \
|
||||
python3 -c "
|
||||
import sys,json
|
||||
try:
|
||||
d=json.load(sys.stdin)
|
||||
ids=[r['message']['from']['id'] for r in d.get('result',[]) if 'message' in r and 'from' in r['message']]
|
||||
print(ids[-1] if ids else '')
|
||||
except: pass
|
||||
" 2>/dev/null)
|
||||
[[ -n "$admin_id" ]] && break
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [[ -z "$admin_id" || ! "$admin_id" =~ ^[0-9]+$ ]]; then
|
||||
warn "Не удалось определить ID автоматически."
|
||||
read -r -p "Впишите ваш Telegram ID вручную (число): " admin_id
|
||||
else
|
||||
ok "Telegram ID определён: ${GREEN}${admin_id}${NC}"
|
||||
fi
|
||||
[[ -z "$admin_id" || ! "$admin_id" =~ ^[0-9]+$ ]] && { error "ID должен быть числом"; return; }
|
||||
|
||||
BOT_TOKEN_NONINTERACTIVE="$token" BOT_ADMIN_NONINTERACTIVE="$admin_id" bash "$installer"
|
||||
|
||||
+83
-26
@@ -53,8 +53,7 @@
|
||||
<div class="stat-label">Аптайм</div>
|
||||
<div class="stat-value" style="font-size:1.1rem" id="s-uptime">—</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Быстрые действия</div>
|
||||
@@ -144,17 +143,19 @@
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<div class="card-title">Самоподписной сертификат</div>
|
||||
<div class="card-title">SSH-туннель</div>
|
||||
<p style="color:var(--muted);font-size:.875rem;margin-bottom:16px;line-height:1.6">
|
||||
Генерируется мгновенно, действует <strong style="color:var(--text)">10 лет</strong>.
|
||||
Браузер покажет предупреждение — его можно принять.
|
||||
Подходит для личного использования.
|
||||
Панель будет слушать только <strong style="color:var(--text)">127.0.0.1</strong> — недоступна снаружи.
|
||||
Подключение через зашифрованный SSH-туннель с вашего компьютера.
|
||||
</p>
|
||||
<button class="btn btn-secondary" onclick="genSelfSigned()">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>
|
||||
Создать самоподписной
|
||||
<div id="ssh-tunnel-info" style="font-size:.82rem;color:var(--muted);margin-bottom:14px;line-height:1.7">
|
||||
Загрузка...
|
||||
</div>
|
||||
<button class="btn btn-warn" onclick="switchToSSHTunnel()">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"/></svg>
|
||||
Переключить на SSH-туннель
|
||||
</button>
|
||||
<div id="selfsigned-result" style="margin-top:12px"></div>
|
||||
<div id="ssh-result" style="margin-top:12px"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title">Let's Encrypt (бесплатный)</div>
|
||||
@@ -264,17 +265,21 @@
|
||||
<div class="card" id="bot-install-card" style="display:none">
|
||||
<div class="card-title">Установка бота</div>
|
||||
<p style="color:var(--muted);font-size:.875rem;margin-bottom:18px;line-height:1.6">
|
||||
Создайте бота через <strong style="color:var(--accent)">@BotFather</strong> в Telegram,
|
||||
скопируйте токен и узнайте ваш Telegram ID через <strong style="color:var(--accent)">@userinfobot</strong>.
|
||||
Создайте бота через <strong style="color:var(--accent)">@BotFather</strong> в Telegram и скопируйте токен.
|
||||
Затем нажмите «Определить ID» и отправьте любое сообщение своему боту.
|
||||
</p>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Токен бота (от @BotFather)</label>
|
||||
<input type="text" id="bot-token" placeholder="123456:ABC-DEF1234gh...">
|
||||
</div>
|
||||
<div class="form-group" style="max-width:200px">
|
||||
<div class="form-group" style="max-width:240px">
|
||||
<label>Ваш Telegram ID</label>
|
||||
<input type="text" id="bot-admin-id" placeholder="123456789">
|
||||
<div style="display:flex;gap:8px">
|
||||
<input type="text" id="bot-admin-id" placeholder="123456789" style="flex:1">
|
||||
<button class="btn btn-ok btn-sm" onclick="detectBotAdminId()" style="white-space:nowrap">🔍 Определить ID</button>
|
||||
</div>
|
||||
<div id="bot-detect-status" style="font-size:.75rem;color:var(--muted);margin-top:4px"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="installBot()">
|
||||
@@ -856,20 +861,43 @@ async function loadSSL(){
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
loadPanelAccess();
|
||||
}
|
||||
|
||||
async function genSelfSigned(){
|
||||
const btn=event.target;
|
||||
btn.disabled=true;btn.innerHTML='<span class="spinner"></span> Генерация...';
|
||||
const el=document.getElementById('selfsigned-result');
|
||||
const d=await api('/api/ssl/selfsigned','POST');
|
||||
btn.disabled=false;btn.textContent='Создать самоподписной';
|
||||
if(!d)return;
|
||||
if(d.ok){
|
||||
el.innerHTML=`<div class="alert alert-ok">✓ Сертификат создан (10 лет)<br><small>${escH(d.expires||'')}</small><br><small style="opacity:.7">${escH(d.note||'')}</small></div>`;
|
||||
loadSSL();
|
||||
}else{
|
||||
el.innerHTML=`<div class="alert alert-err">Ошибка: ${escH(d.error||'')}</div>`;
|
||||
async function loadPanelAccess() {
|
||||
const d = await api('/api/panel/access');
|
||||
if (!d) return;
|
||||
const el = document.getElementById('ssh-tunnel-info');
|
||||
if (!el) return;
|
||||
const icon = d.mode === 'ssh' ? '🔒' : '🌐';
|
||||
const label = d.mode === 'ssh' ? 'SSH-туннель (только 127.0.0.1)' : 'Доступ по IP (0.0.0.0)';
|
||||
const sshCmd = `ssh -L ${d.panel_port}:127.0.0.1:${d.panel_port} -p ${d.ssh_port} root@${d.server_ip} -N`;
|
||||
el.innerHTML = `
|
||||
<div style="margin-bottom:8px">${icon} <strong>${label}</strong></div>
|
||||
<div>Сервер: <code style="color:var(--accent)">${escH(d.server_ip)}</code></div>
|
||||
<div>Порт: <code style="color:var(--accent)">${d.panel_port}</code></div>
|
||||
<div style="margin-top:8px;font-size:.8rem;color:var(--ok)">Команда для туннеля:</div>
|
||||
<div class="code-block" id="ssh-cmd-block">${escH(sshCmd)}</div>
|
||||
<button class="btn btn-secondary btn-sm" style="margin-top:4px" onclick="copyText(document.getElementById('ssh-cmd-block').textContent)">Копировать</button>`;
|
||||
}
|
||||
|
||||
async function switchToSSHTunnel() {
|
||||
const btn = document.querySelector('#page-ssl .btn-warn');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner"></span> Переключение...';
|
||||
|
||||
const el = document.getElementById('ssh-result');
|
||||
const d = await api('/api/panel/access', 'POST', {mode: 'ssh'});
|
||||
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"/></svg> Переключить на SSH-туннель';
|
||||
|
||||
if (d?.ok) {
|
||||
el.innerHTML = '<div class="alert alert-ok">✓ Панель переключена на SSH-туннель. Используйте команду ниже для подключения.</div>';
|
||||
showToast('SSH-туннель активирован');
|
||||
loadPanelAccess();
|
||||
} else {
|
||||
el.innerHTML = `<div class="alert alert-err">Ошибка: ${escH(d?.error||'')}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,6 +970,35 @@ async function loadBotPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function detectBotAdminId() {
|
||||
const token = document.getElementById('bot-token').value.trim();
|
||||
if (!token) { showToast('Введите токен бота', true); return; }
|
||||
|
||||
const el = document.getElementById('bot-detect-status');
|
||||
const btn = document.querySelector('#bot-install-card .btn-ok');
|
||||
|
||||
el.textContent = 'Отправьте любое сообщение боту в Telegram. Ожидание (60 сек)...';
|
||||
el.style.color = 'var(--warn)';
|
||||
if (btn) { btn.disabled = true; btn.textContent = '⏳ Ожидание...'; }
|
||||
|
||||
try {
|
||||
const d = await api('/api/bot/detect-id', 'POST', {token});
|
||||
if (d?.ok) {
|
||||
document.getElementById('bot-admin-id').value = d.admin_id;
|
||||
el.textContent = '✓ ID определён: ' + d.admin_id;
|
||||
el.style.color = 'var(--ok)';
|
||||
showToast('Telegram ID определён');
|
||||
} else {
|
||||
el.textContent = '⚠ ' + (d?.error || 'Не удалось');
|
||||
el.style.color = 'var(--danger)';
|
||||
}
|
||||
} catch(e) {
|
||||
el.textContent = '⚠ Ошибка: ' + e.message;
|
||||
el.style.color = 'var(--danger)';
|
||||
}
|
||||
if (btn) { btn.disabled = false; btn.textContent = '🔍 Определить ID'; }
|
||||
}
|
||||
|
||||
async function installBot() {
|
||||
const token = document.getElementById('bot-token').value.trim();
|
||||
const adminId = document.getElementById('bot-admin-id').value.trim();
|
||||
|
||||
Reference in New Issue
Block a user