version 1.2.2: исправлен парсинг статистики трафика
- app.py: get_traffic_stats() — позиционный парсинг 8 колонок mita get users, без сканирования суффиксов - bot.py: cmd_users, cmd_dashboard — единый позиционный парсинг вместо поиска единиц - README: обновлена дорожная карта
This commit is contained in:
@@ -73,7 +73,7 @@ curl -Ls https://raw.githubusercontent.com/grendervilll/mitactl/main/bootstrap.s
|
|||||||
- [x] Веб-панель с секретным путём (Flask + Gunicorn, path-based security)
|
- [x] Веб-панель с секретным путём (Flask + Gunicorn, path-based security)
|
||||||
- [x] Тёмная тема, адаптивный UI: Dashboard, Users, SSL, Security
|
- [x] Тёмная тема, адаптивный UI: Dashboard, Users, SSL, Security
|
||||||
- [x] CRUD пользователей (создание, удаление, список) — авто/ручное/bulk
|
- [x] CRUD пользователей (создание, удаление, список) — авто/ручное/bulk
|
||||||
- [x] Статистика трафика: день, 7 дней, 30 дней
|
- [x] Статистика трафика: день, 7 дней, 30 дней (позиционный парсинг mita get users)
|
||||||
- [x] Онлайн-статус пользователей
|
- [x] Онлайн-статус пользователей
|
||||||
- [x] Генерация клиентских конфигов (mieru JSON и sing-box JSON)
|
- [x] Генерация клиентских конфигов (mieru JSON и sing-box JSON)
|
||||||
- [x] Скачивание конфигов и отображение QR-кодов
|
- [x] Скачивание конфигов и отображение QR-кодов
|
||||||
|
|||||||
@@ -175,35 +175,41 @@ def get_warp_ip():
|
|||||||
return "недоступен"
|
return "недоступен"
|
||||||
|
|
||||||
def get_traffic_stats():
|
def get_traffic_stats():
|
||||||
"""Возвращает трафик и статус активности по всем пользователям."""
|
"""Возвращает трафик и статус активности по всем пользователям.
|
||||||
|
Данные берутся напрямую из mita get users."""
|
||||||
raw = mita_cmd("get", "users")
|
raw = mita_cmd("get", "users")
|
||||||
week_total = month_total = 0.0
|
week_total = month_total = 0.0
|
||||||
users_stats = []
|
users_stats = []
|
||||||
today = datetime.now().date()
|
today = datetime.now().date()
|
||||||
|
|
||||||
# mita get users output format:
|
# mita get users output format (8 колонок):
|
||||||
# User LastActive 1DayDown 1DayUp 7DaysDown 7DaysUp 30DaysDown 30DaysUp
|
# User LastActive 1DayDown 1DayUp 7DaysDown 7DaysUp 30DaysDown 30DaysUp
|
||||||
lines = raw.splitlines()
|
for line in raw.splitlines():
|
||||||
data_lines = []
|
|
||||||
for line in lines:
|
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line or line.upper().startswith("USER"):
|
if not line or line.upper().startswith("USER"):
|
||||||
continue
|
continue
|
||||||
data_lines.append(line)
|
parts = line.split()
|
||||||
|
if len(parts) < 8:
|
||||||
for line in data_lines:
|
|
||||||
parts = re.split(r"\s{2,}", line.strip())
|
|
||||||
if len(parts) < 3:
|
|
||||||
parts = line.split()
|
|
||||||
if len(parts) < 3:
|
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
name = parts[0]
|
name = parts[0]
|
||||||
|
d1_down = _parse_traffic(parts[2])
|
||||||
|
d1_up = _parse_traffic(parts[3])
|
||||||
|
d7_down = _parse_traffic(parts[4])
|
||||||
|
d7_up = _parse_traffic(parts[5])
|
||||||
|
d30_down = _parse_traffic(parts[6])
|
||||||
|
d30_up = _parse_traffic(parts[7])
|
||||||
|
|
||||||
|
d1_bytes = d1_down + d1_up
|
||||||
|
d7_bytes = d7_down + d7_up
|
||||||
|
d30_bytes = d30_down + d30_up
|
||||||
|
|
||||||
|
month_total += d30_bytes
|
||||||
|
week_total += d7_bytes
|
||||||
|
|
||||||
# Parse LAST ACTIVE field (parts[1])
|
|
||||||
last_active_raw = parts[1].strip() if len(parts) > 1 else ""
|
|
||||||
online = False
|
online = False
|
||||||
last_active_display = "никогда"
|
last_active_display = "никогда"
|
||||||
|
last_active_raw = parts[1]
|
||||||
if last_active_raw and last_active_raw.lower() not in ("never", "-", "n/a", "никогда"):
|
if last_active_raw and last_active_raw.lower() not in ("never", "-", "n/a", "никогда"):
|
||||||
try:
|
try:
|
||||||
la_date = datetime.strptime(last_active_raw[:10], "%Y-%m-%d").date()
|
la_date = datetime.strptime(last_active_raw[:10], "%Y-%m-%d").date()
|
||||||
@@ -222,38 +228,13 @@ def get_traffic_stats():
|
|||||||
except Exception:
|
except Exception:
|
||||||
last_active_display = last_active_raw
|
last_active_display = last_active_raw
|
||||||
|
|
||||||
# Find traffic columns — contain units like MiB, GiB, KiB, B
|
|
||||||
# mita output: 1DayDown 1DayUp 7DaysDown 7DaysUp 30DaysDown 30DaysUp
|
|
||||||
traffic_parts = [p for p in parts[1:] if any(u in p for u in ["TiB","GiB","MiB","KiB","iB","B"])]
|
|
||||||
d1_down = d1_up = d7_down = d7_up = d30_down = d30_up = 0.0
|
|
||||||
if len(traffic_parts) >= 6:
|
|
||||||
d1_down = _parse_traffic(traffic_parts[0])
|
|
||||||
d1_up = _parse_traffic(traffic_parts[1])
|
|
||||||
d7_down = _parse_traffic(traffic_parts[2])
|
|
||||||
d7_up = _parse_traffic(traffic_parts[3])
|
|
||||||
d30_down = _parse_traffic(traffic_parts[4])
|
|
||||||
d30_up = _parse_traffic(traffic_parts[5])
|
|
||||||
elif len(traffic_parts) >= 4:
|
|
||||||
d1_down = _parse_traffic(traffic_parts[0])
|
|
||||||
d1_up = _parse_traffic(traffic_parts[1])
|
|
||||||
d30_down = _parse_traffic(traffic_parts[2])
|
|
||||||
d30_up = _parse_traffic(traffic_parts[3])
|
|
||||||
elif len(traffic_parts) >= 2:
|
|
||||||
d30_down = _parse_traffic(traffic_parts[0])
|
|
||||||
d30_up = _parse_traffic(traffic_parts[1])
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
d7_bytes = d7_down + d7_up if (d7_down + d7_up) > 0 else (d30_down + d30_up) / 30 * 7
|
|
||||||
month_total += d30_down + d30_up
|
|
||||||
week_total += d7_bytes
|
|
||||||
users_stats.append({
|
users_stats.append({
|
||||||
"name": name,
|
"name": name,
|
||||||
"online": online,
|
"online": online,
|
||||||
"last_active": last_active_display,
|
"last_active": last_active_display,
|
||||||
"day_mb": round((d1_down + d1_up) / 1024 / 1024, 2),
|
"day_mb": round(d1_bytes / 1024 / 1024, 2),
|
||||||
"week_mb": round(d7_bytes / 1024 / 1024, 2),
|
"week_mb": round(d7_bytes / 1024 / 1024, 2),
|
||||||
"month_mb": round((d30_down + d30_up) / 1024 / 1024, 2),
|
"month_mb": round(d30_bytes / 1024 / 1024, 2),
|
||||||
})
|
})
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -149,26 +149,25 @@ async def cmd_users(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
|
|
||||||
stats_map = {}
|
stats_map = {}
|
||||||
for line in data_lines:
|
for line in data_lines:
|
||||||
parts = re.split(r"\s{2,}", line.strip())
|
parts = line.split()
|
||||||
if len(parts) < 3:
|
if len(parts) < 8:
|
||||||
parts = line.split()
|
|
||||||
if len(parts) < 3:
|
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
name = parts[0]
|
name = parts[0]
|
||||||
traffic_parts = [p for p in parts[1:] if any(u in p for u in ["TiB","GiB","MiB","KiB","iB","B"])]
|
|
||||||
d1 = d30 = 0.0
|
d1_down = _parse_traffic(parts[2])
|
||||||
if len(traffic_parts) >= 6:
|
d1_up = _parse_traffic(parts[3])
|
||||||
d1 = _parse_traffic(traffic_parts[0]) + _parse_traffic(traffic_parts[1])
|
d7_down = _parse_traffic(parts[4])
|
||||||
d30= _parse_traffic(traffic_parts[4]) + _parse_traffic(traffic_parts[5])
|
d7_up = _parse_traffic(parts[5])
|
||||||
elif len(traffic_parts) >= 4:
|
d30_down = _parse_traffic(parts[6])
|
||||||
d1 = _parse_traffic(traffic_parts[0]) + _parse_traffic(traffic_parts[1])
|
d30_up = _parse_traffic(parts[7])
|
||||||
d30= _parse_traffic(traffic_parts[2]) + _parse_traffic(traffic_parts[3])
|
|
||||||
elif len(traffic_parts) >= 2:
|
d1_bytes = d1_down + d1_up
|
||||||
d30= _parse_traffic(traffic_parts[0]) + _parse_traffic(traffic_parts[1])
|
d7_bytes = d7_down + d7_up
|
||||||
|
d30_bytes = d30_down + d30_up
|
||||||
|
|
||||||
online = False
|
online = False
|
||||||
la_raw = parts[1].strip() if len(parts) > 1 else ""
|
la_raw = parts[1]
|
||||||
if la_raw and la_raw.lower() not in ("never","-","n/a","никогда"):
|
if la_raw and la_raw.lower() not in ("never","-","n/a","никогда"):
|
||||||
try:
|
try:
|
||||||
la_date = datetime.strptime(la_raw[:10], "%Y-%m-%d").date()
|
la_date = datetime.strptime(la_raw[:10], "%Y-%m-%d").date()
|
||||||
@@ -177,8 +176,10 @@ async def cmd_users(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
stats_map[name] = {"online": online, "day_mb": round(d1/1024/1024, 2),
|
stats_map[name] = {"online": online,
|
||||||
"month_mb": round(d30/1024/1024, 2)}
|
"day_mb": round(d1_bytes/1024/1024, 2),
|
||||||
|
"week_mb": round(d7_bytes/1024/1024, 2),
|
||||||
|
"month_mb": round(d30_bytes/1024/1024, 2)}
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -346,18 +347,16 @@ async def cmd_dashboard(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
traffic = None
|
traffic = None
|
||||||
try:
|
try:
|
||||||
raw = mita_cmd("get", "users")
|
raw = mita_cmd("get", "users")
|
||||||
d_lines = [l.strip() for l in raw.splitlines() if l.strip() and not l.upper().startswith("USER")]
|
|
||||||
week_total = month_total = 0.0
|
week_total = month_total = 0.0
|
||||||
for line in d_lines:
|
for line in raw.splitlines():
|
||||||
parts = re.split(r"\s{2,}", line.strip())
|
line = line.strip()
|
||||||
if len(parts) < 3:
|
if not line or line.upper().startswith("USER"):
|
||||||
parts = line.split()
|
|
||||||
if len(parts) < 3:
|
|
||||||
continue
|
continue
|
||||||
tp = [p for p in parts[1:] if any(u in p for u in ["TiB","GiB","MiB","KiB","iB","B"])]
|
parts = line.split()
|
||||||
if len(tp) >= 6:
|
if len(parts) < 8:
|
||||||
week_total += _parse_traffic(tp[2]) + _parse_traffic(tp[3])
|
continue
|
||||||
month_total += _parse_traffic(tp[4]) + _parse_traffic(tp[5])
|
week_total += _parse_traffic(parts[4]) + _parse_traffic(parts[5])
|
||||||
|
month_total += _parse_traffic(parts[6]) + _parse_traffic(parts[7])
|
||||||
traffic = (round(week_total/1024**3, 2), round(month_total/1024**3, 2))
|
traffic = (round(week_total/1024**3, 2), round(month_total/1024**3, 2))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user