From eb8642ebaa4a527e0a7526ab1e42dc8330b73ba3 Mon Sep 17 00:00:00 2001 From: Sergey
{fmt_label(ip)} — {dur}")
+ lines.append("")
+ if up:
+ lines.append(f"✅ Доступны ({len(up)}):")
+ for ip in up:
+ rtt = hosts_state[ip].get("rtt")
+ lines.append(f" • {fmt_label(ip)}" + (f" {rtt:.1f} ms" if rtt else ""))
+
+ lines += ["", f"⏱ {now.strftime('%Y-%m-%d %H:%M:%S')}"]
+ return "\n".join(lines)
+
+
+# ──────────────────────────────────────────────
+# Команды ботов
+# ──────────────────────────────────────────────
+async def process_telegram_commands() -> None:
+ global tg_last_update_id, _tg_bot_username
+ if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
+ return
+ async with httpx.AsyncClient(timeout=15) as client:
+ try:
+ resp = await client.get(
+ f"{TELEGRAM_API_URL}/bot{TELEGRAM_BOT_TOKEN}/getUpdates",
+ params={"timeout": 1, "offset": tg_last_update_id + 1},
+ )
+ data = resp.json()
+ for update in data.get("result", []):
+ tg_last_update_id = update["update_id"]
+ text = update.get("message", {}).get("text", "").strip().lower().split("@")[0]
+ if text == "/status":
+ await send_telegram(build_status_report("📊 Текущий статус"))
+ elif text == "/help":
+ await send_telegram(
+ "🤖 Доступные команды:\n"
+ "/status — статус всех хостов\n"
+ "/help — это сообщение"
+ )
+ except Exception as exc:
+ log.debug("TG polling: %s", exc)
+
+
+async def process_max_commands() -> None:
+ global max_last_marker
+ if not MAX_BOT_TOKEN or not MAX_CHAT_ID:
+ return
+ params = {"access_token": MAX_BOT_TOKEN}
+ if max_last_marker:
+ params["marker"] = max_last_marker
+ async with httpx.AsyncClient(timeout=15) as client:
+ try:
+ resp = await client.get(f"{MAX_API_URL}/updates", params=params)
+ data = resp.json()
+ max_last_marker = data.get("marker", max_last_marker)
+ for update in data.get("updates", []):
+ if update.get("update_type") != "message_created":
+ continue
+ text = update.get("message", {}).get("body", {}).get("text", "").strip().lower()
+ if text == "/status":
+ await send_max(build_status_report("📊 Текущий статус"))
+ elif text == "/help":
+ await send_max(
+ "🤖 Доступные команды:\n"
+ "/status — статус всех хостов\n"
+ "/help — это сообщение"
+ )
+ except Exception as exc:
+ log.debug("MAX polling: %s", exc)
+
+
+async def process_bot_commands() -> None:
+ await asyncio.gather(process_telegram_commands(), process_max_commands())
+
+
+# ──────────────────────────────────────────────
+# Ежедневный отчёт
+# ──────────────────────────────────────────────
+def check_daily_report() -> None:
+ global daily_report_sent_date
+ now = datetime.now()
+ today = now.strftime("%Y-%m-%d")
+ if now.strftime("%H:%M") == DAILY_REPORT_TIME and daily_report_sent_date != today:
+ daily_report_sent_date = today
+ notify(build_status_report(f"📅 Ежедневный отчёт — {today}"))
+ log.info("Ежедневный отчёт отправлен")
+
+
+# ──────────────────────────────────────────────
+# Веб-дашборд
+# ──────────────────────────────────────────────
+class DashboardHandler(BaseHTTPRequestHandler):
+ def log_message(self, *args):
+ pass
+
+ def do_GET(self):
+ if self.path not in ("/", "/status"):
+ self.send_response(404)
+ self.end_headers()
+ return
+
+ now = datetime.now()
+ rows = ""
+ for ip, state in hosts_state.items():
+ comment = state.get("comment", "")
+ status = state.get("status", "unknown")
+ rtt = state.get("rtt")
+ ds = state.get("down_since")
+ ls = state.get("last_seen")
+
+ if status == "up":
+ badge = '● UP'
+ rtt_cell = f"{rtt:.1f} ms" if rtt else "—"
+ dur_cell = "—"
+ elif status == "down":
+ badge = '● DOWN'
+ rtt_cell = "—"
+ dur_cell = fmt_duration(now - ds) if ds else "—"
+ else:
+ badge = '● ?'
+ rtt_cell = dur_cell = "—"
+
+ rows += f"""{ip}| IP | Имя | Статус | RTT | Даунтайм | Последний ответ | +
|---|
{ip} ({comment})" if comment else f" • {ip}"
for ip, comment in ip_list
) or " (список пуст)"
notify(
- f"🟢 MikroTik Monitor запущен\n"
- f"\n"
- f"🔧 Роутер: {identity} ({MIKROTIK_HOST})\n"
- f"📋 Address-list: {ADDRESS_LIST_NAME}\n"
- f"\n"
- f"Мониторинг адресов:\n{ip_lines}\n"
- f"\n"
+ f"🟢 MikroTik Monitor запущен\n\n"
+ f"🔧 Роутер: {router_identity} ({MIKROTIK_HOST})\n"
+ f"📋 Address-list: {ADDRESS_LIST_NAME}\n\n"
+ f"Мониторинг адресов:\n{ip_lines}\n\n"
f"⏱ Интервал: {CHECK_INTERVAL} сек"
)
startup_notified = True
if not ip_list:
- log.warning("Address-list '%s' пуст или не найден!", ADDRESS_LIST_NAME)
+ log.warning("Address-list '%s' пуст!", ADDRESS_LIST_NAME)
else:
- log.info("Адресов для проверки: %d → %s", len(ip_list), ip_list)
+ log.info("Адресов: %d", len(ip_list))
- # Добавляем новые хосты в state (без сброса существующих)
+ now = datetime.now()
for ip, comment in ip_list:
- if ip not in failure_count:
- failure_count[ip] = 0
- host_down[ip] = False
+ if ip not in hosts_state:
+ hosts_state[ip] = {
+ "comment": comment, "status": "unknown",
+ "rtt": None, "down_since": None,
+ "last_seen": None, "last_notify": None,
+ }
+ else:
+ hosts_state[ip]["comment"] = comment
+
+ # Параллельный пинг
+ ping_results = asyncio.run(ping_all_async(api, ip_list))
+ last_check_time = datetime.now()
+ now = last_check_time
- # Пингуем каждый адрес
for ip, comment in ip_list:
- label = f"{ip} ({comment})" if comment else ip
- now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- rtt = mikrotik_ping(api, ip)
+ rtt = ping_results.get(ip)
+ state = hosts_state[ip]
+ label = fmt_label(ip)
+ ts = now.strftime("%Y-%m-%d %H:%M:%S")
if rtt is not None:
- log.info("✅ %-28s avg-rtt=%.1f ms", label, rtt)
- if host_down[ip]:
- host_down[ip] = False
- failure_count[ip] = 0
+ log.info("✅ %-34s %.1f ms", label, rtt)
+ state["rtt"] = rtt
+ state["last_seen"] = now
+
+ if state["status"] == "down":
+ ds = state.get("down_since")
+ dur = fmt_duration(now - ds) if ds else "?"
+ state.update({"status": "up", "down_since": None, "last_notify": None})
notify(
f"✅ Хост восстановился\n"
- f"IP: {ip}" + (f" ({comment})" if comment else "") + f"\n"
+ f"IP: {ip}" + (f" ({comment})" if comment else "") + "\n"
f"RTT: {rtt:.1f} ms\n"
- f"Время: {now}"
+ f"Даунтайм: {dur}\n"
+ f"Время: {ts}"
)
else:
- failure_count[ip] = 0
+ state["status"] = "up"
+
else:
- failure_count[ip] += 1
- log.warning(
- "❌ %-28s не отвечает (%d/%d)",
- label, failure_count[ip], FAILURE_THRESHOLD,
- )
- if failure_count[ip] >= FAILURE_THRESHOLD and not host_down[ip]:
- host_down[ip] = True
+ log.warning("❌ %-34s нет ответа", label)
+ state["rtt"] = None
+
+ if state["status"] != "down":
+ state.update({"status": "down", "down_since": now, "last_notify": now})
notify(
f"🔴 Хост недоступен!\n"
- f"IP: {ip}" + (f" ({comment})" if comment else "") + f"\n"
+ f"IP: {ip}" + (f" ({comment})" if comment else "") + "\n"
f"Список: {ADDRESS_LIST_NAME}\n"
- f"Не отвечает {failure_count[ip]} проверки подряд\n"
- f"Время: {now}"
+ f"Время: {ts}"
)
+ elif REPEAT_NOTIFY_MIN > 0 and state.get("last_notify"):
+ mins = (now - state["last_notify"]).total_seconds() / 60
+ if mins >= REPEAT_NOTIFY_MIN:
+ ds = state.get("down_since")
+ dur = fmt_duration(now - ds) if ds else "?"
+ state["last_notify"] = now
+ notify(
+ f"🔴 Хост всё ещё недоступен!\n"
+ f"IP: {ip}" + (f" ({comment})" if comment else "") + "\n"
+ f"Недоступен уже: {dur}\n"
+ f"Время: {ts}"
+ )
+
+ asyncio.run(process_bot_commands())
+ check_daily_report()
except Exception as exc:
log.error("Ошибка: %s", exc)
- notify(f"⚠️ Ошибка подключения к MikroTik\n{exc}")
+ notify(
+ f"⚠️ Ошибка подключения к MikroTik\n"
+ f"🔧 Роутер: {router_identity} ({MIKROTIK_HOST})\n"
+ f"{exc}"
+ )
finally:
if pool:
try: