feat: Implement semi-automatic GAS Pravosudie sudrf scraper and web UI integration
This commit is contained in:
parent
23f44e7679
commit
48b23f5980
4 changed files with 476 additions and 2 deletions
145
scraper_gis/sudrf_pdf_compiler.py
Normal file
145
scraper_gis/sudrf_pdf_compiler.py
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from pypdf import PdfWriter, PdfReader
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
from reportlab.lib.pagesizes import A4
|
||||||
|
from reportlab.lib import colors
|
||||||
|
from PIL import Image
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [%(levelname)s] %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler("/home/matrixhasyou/swarm-services/domovoy-bot/scraper_gis/logs/sudrf_compiler.log", encoding="utf-8"),
|
||||||
|
logging.StreamHandler(sys.stdout)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
log = logging.getLogger("SUD-Compiler")
|
||||||
|
|
||||||
|
# Add libs path
|
||||||
|
sys.path.append("/home/matrixhasyou/swarm-services/libs")
|
||||||
|
try:
|
||||||
|
from swarmlib.gdrive import GDriveManager
|
||||||
|
except ImportError as e:
|
||||||
|
log.error(f"Cannot import swarmlib: {e}")
|
||||||
|
GDriveManager = None
|
||||||
|
|
||||||
|
EXPORT_DIR = Path("/home/matrixhasyou/swarm-services/domovoy-bot/scraper_gis/exports/sudrf")
|
||||||
|
OUTPUT_DIR = Path("/home/matrixhasyou/swarm-services/domovoy-bot/exports/compiled_sudrf")
|
||||||
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
BOT_TOKEN = "8725618164:AAH1tGalq-pw1l0t4P5c0sdLkCVJdh7IE7M"
|
||||||
|
CHAT_ID = "197957361"
|
||||||
|
|
||||||
|
def send_tg(msg):
|
||||||
|
subprocess.run(["curl", "-s", "-X", "POST", f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
|
||||||
|
"-d", f"chat_id={CHAT_ID}&text={msg}&parse_mode=markdown"])
|
||||||
|
|
||||||
|
def tag_image_to_pdf(image_path, output_pdf_path, label):
|
||||||
|
try:
|
||||||
|
c = canvas.Canvas(output_pdf_path, pagesize=A4)
|
||||||
|
width, height = A4
|
||||||
|
c.setFillColor(colors.black)
|
||||||
|
c.rect(0, height - 50, width, 50, fill=1)
|
||||||
|
c.setFillColor(colors.white)
|
||||||
|
c.setFont("Helvetica-Bold", 10)
|
||||||
|
c.drawString(20, height - 30, f"SUD: {label}".upper())
|
||||||
|
|
||||||
|
img = Image.open(image_path)
|
||||||
|
img_width, img_height = img.size
|
||||||
|
aspect = img_height / float(img_width)
|
||||||
|
|
||||||
|
display_width = width - 40
|
||||||
|
display_height = display_width * aspect
|
||||||
|
if display_height > (height - 70):
|
||||||
|
display_height = height - 70
|
||||||
|
display_width = display_height / aspect
|
||||||
|
|
||||||
|
c.drawImage(image_path, 20, height - 60 - display_height, width=display_width, height=display_height)
|
||||||
|
c.showPage()
|
||||||
|
c.save()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to tag image {image_path}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def compile_appeals():
|
||||||
|
send_tg("🧬 **ГАС Правосудие: Запуск PDF-компилятора**\nОбрабатываю выгруженные материалы...")
|
||||||
|
|
||||||
|
if not EXPORT_DIR.exists():
|
||||||
|
log.error(f"Export dir not found: {EXPORT_DIR}")
|
||||||
|
send_tg("❌ **Компиляция прервана**: папка с выгрузками не найдена.")
|
||||||
|
return
|
||||||
|
|
||||||
|
gdrive = None
|
||||||
|
if GDriveManager:
|
||||||
|
try:
|
||||||
|
gdrive = GDriveManager()
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to initialize GDriveManager: {e}")
|
||||||
|
|
||||||
|
folders = [d for d in EXPORT_DIR.iterdir() if d.is_dir() and d.name.startswith("appeal_")]
|
||||||
|
processed = 0
|
||||||
|
|
||||||
|
for folder in folders:
|
||||||
|
appeal_id = folder.name.replace("appeal_", "")
|
||||||
|
log.info(f"Compiling appeal: {appeal_id}")
|
||||||
|
|
||||||
|
writer = PdfWriter()
|
||||||
|
|
||||||
|
# 1. Convert detail screenshot (evidence.png) to PDF first
|
||||||
|
evidence_img = folder / "evidence.png"
|
||||||
|
evidence_pdf = folder / "evidence.pdf"
|
||||||
|
if evidence_img.exists():
|
||||||
|
if tag_image_to_pdf(str(evidence_img), str(evidence_pdf), f"APPEAL {appeal_id}"):
|
||||||
|
writer.append(str(evidence_pdf))
|
||||||
|
|
||||||
|
# 2. Append all other PDF files inside the folder
|
||||||
|
files = sorted(list(folder.iterdir()))
|
||||||
|
for f in files:
|
||||||
|
if f.suffix.lower() == ".pdf" and f.name != "evidence.pdf":
|
||||||
|
try:
|
||||||
|
writer.append(str(f))
|
||||||
|
log.info(f"Appended PDF: {f.name}")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to append PDF {f.name}: {e}")
|
||||||
|
elif f.suffix.lower() in [".png", ".jpg", ".jpeg"] and f.name != "evidence.png":
|
||||||
|
# Convert image attachments to PDF and append
|
||||||
|
img_pdf = folder / f"{f.stem}_attachment.pdf"
|
||||||
|
if tag_image_to_pdf(str(f), str(img_pdf), f"ATTACHMENT {f.name}"):
|
||||||
|
try:
|
||||||
|
writer.append(str(img_pdf))
|
||||||
|
log.info(f"Appended converted image: {f.name}")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to append converted image {f.name}: {e}")
|
||||||
|
|
||||||
|
output_pdf = OUTPUT_DIR / f"SUD_APPEAL_{appeal_id}.pdf"
|
||||||
|
try:
|
||||||
|
with open(output_pdf, "wb") as out_f:
|
||||||
|
writer.write(out_f)
|
||||||
|
processed += 1
|
||||||
|
log.info(f"Successfully compiled: {output_pdf.name}")
|
||||||
|
|
||||||
|
# Upload to GDrive
|
||||||
|
drive_status = "не настроен"
|
||||||
|
if gdrive:
|
||||||
|
try:
|
||||||
|
drive_status = gdrive.upload_or_update(str(output_pdf), output_pdf.name)
|
||||||
|
except Exception as e:
|
||||||
|
drive_status = f"ошибка GDrive: {e}"
|
||||||
|
|
||||||
|
send_tg(f"📄 **Обращение {appeal_id} собрано в PDF**\n☁️ GDrive: `{drive_status}`")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to write compiled PDF for {appeal_id}: {e}")
|
||||||
|
send_tg(f"❌ **Ошибка сборки обращения {appeal_id}**: {e}")
|
||||||
|
|
||||||
|
send_tg(f"🏁 **Компиляция судебных дел завершена!**\nСобрано обращений: {processed}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
compile_appeals()
|
||||||
230
scraper_gis/sudrf_reaper.py
Normal file
230
scraper_gis/sudrf_reaper.py
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from playwright.async_api import async_playwright
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [%(levelname)s] %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler("/app/logs/sudrf.log", encoding="utf-8"),
|
||||||
|
logging.StreamHandler(sys.stdout)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
logger = logging.getLogger("SUD-Reaper")
|
||||||
|
|
||||||
|
USER_DATA_DIR = "/app/browser_context_sudrf"
|
||||||
|
EXPORT_DIR = "/app/exports/sudrf"
|
||||||
|
os.makedirs(EXPORT_DIR, exist_ok=True)
|
||||||
|
os.makedirs("/app/logs", exist_ok=True)
|
||||||
|
|
||||||
|
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "8725618164:AAH1tGalq-pw1l0t4P5c0sdLkCVJdh7IE7M")
|
||||||
|
CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "197957361")
|
||||||
|
|
||||||
|
def send_tg(msg):
|
||||||
|
import subprocess
|
||||||
|
subprocess.run(["curl", "-s", "-X", "POST", f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
|
||||||
|
"-d", f"chat_id={CHAT_ID}&text={msg}&parse_mode=markdown"])
|
||||||
|
|
||||||
|
async def human_delay(min_sec=3, max_sec=6):
|
||||||
|
await asyncio.sleep(random.uniform(min_sec, max_sec))
|
||||||
|
|
||||||
|
async def run_mission():
|
||||||
|
logger.info("Starting Playwright in VNC display...")
|
||||||
|
send_tg("🕵️♂️ **ГАС Правосудие: Запуск браузера**\nОжидаю авторизацию через Госуслуги во VNC: http://192.168.10.116:6080/vnc.html")
|
||||||
|
|
||||||
|
async with async_playwright() as p:
|
||||||
|
# Launch persistent browser context
|
||||||
|
context = await p.chromium.launch_persistent_context(
|
||||||
|
user_data_dir=USER_DATA_DIR,
|
||||||
|
headless=False,
|
||||||
|
args=["--no-sandbox", "--disable-setuid-sandbox", "--start-maximized"]
|
||||||
|
)
|
||||||
|
|
||||||
|
page = context.pages[0] if context.pages else await context.new_page()
|
||||||
|
await page.set_viewport_size({"width": 1280, "height": 1024})
|
||||||
|
|
||||||
|
# Navigate to portal
|
||||||
|
logger.info("Navigating to sudrf...")
|
||||||
|
await page.goto("https://ej.sudrf.ru/")
|
||||||
|
await human_delay(4, 7)
|
||||||
|
|
||||||
|
# Phase 0: Wait for login
|
||||||
|
logger.info("Waiting for user authorization...")
|
||||||
|
logged_in = False
|
||||||
|
for i in range(120): # 10 minutes total
|
||||||
|
content = ""
|
||||||
|
try:
|
||||||
|
content = await page.content()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error reading page content: {e}")
|
||||||
|
|
||||||
|
# Check for common logout or profile indicators
|
||||||
|
if any(indicator in content for indicator in ["Выйти", "Выход", "Личный кабинет", "История обращений"]):
|
||||||
|
logged_in = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if i % 6 == 0:
|
||||||
|
logger.info(f"Still waiting... i={i}")
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
if not logged_in:
|
||||||
|
logger.error("Login timeout exceeded.")
|
||||||
|
send_tg("❌ **ГАС Правосудие: Превышено время ожидания входа**.")
|
||||||
|
await context.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("Login detected. Navigating to appeals history...")
|
||||||
|
send_tg("✅ **Авторизация успешна!** Начинаю сбор обращений...")
|
||||||
|
|
||||||
|
await page.goto("https://ej.sudrf.ru/#/appeals")
|
||||||
|
await asyncio.sleep(10) # wait for page to render appeals list
|
||||||
|
|
||||||
|
# Take list page screenshot
|
||||||
|
list_screenshot = os.path.join(EXPORT_DIR, "appeals_list.png")
|
||||||
|
await page.screenshot(path=list_screenshot, full_page=True)
|
||||||
|
|
||||||
|
# Dump HTML for structural analysis
|
||||||
|
list_html = os.path.join(EXPORT_DIR, "appeals_list.html")
|
||||||
|
with open(list_html, "w", encoding="utf-8") as f:
|
||||||
|
f.write(await page.content())
|
||||||
|
|
||||||
|
logger.info(f"Appeals list screenshot saved to {list_screenshot}")
|
||||||
|
|
||||||
|
# Extract appeal links
|
||||||
|
links = []
|
||||||
|
all_links = await page.query_selector_all("a")
|
||||||
|
for link in all_links:
|
||||||
|
href = await link.get_attribute("href")
|
||||||
|
text = await link.inner_text()
|
||||||
|
if href and ("appeal" in href or "card" in href or "request" in href):
|
||||||
|
links.append({"href": href, "text": text.strip()})
|
||||||
|
|
||||||
|
logger.info(f"Found {len(links)} matching links on page: {links}")
|
||||||
|
|
||||||
|
# Process each appeal
|
||||||
|
# We can extract rows from the table to get exact dates, numbers and link objects
|
||||||
|
# For sudrf, appeals table often has tr tags with status and numbers
|
||||||
|
rows = await page.query_selector_all("tr, .appeals-item")
|
||||||
|
logger.info(f"Found {len(rows)} rows/items in appeals list.")
|
||||||
|
|
||||||
|
# Dump structured report of links to a json file
|
||||||
|
import json
|
||||||
|
with open(os.path.join(EXPORT_DIR, "found_links.json"), "w", encoding="utf-8") as f:
|
||||||
|
json.dump({"links": links, "rows_count": len(rows)}, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
# Try to parse the table rows if they contain appeal details
|
||||||
|
parsed_appeals = []
|
||||||
|
for index, row in enumerate(rows[:20]): # parse top 20 appeals first for safety
|
||||||
|
cells = await row.query_selector_all("td")
|
||||||
|
if not cells: continue
|
||||||
|
|
||||||
|
row_data = []
|
||||||
|
for cell in cells:
|
||||||
|
row_data.append((await cell.inner_text()).strip())
|
||||||
|
|
||||||
|
# Look for link inside the row
|
||||||
|
row_link = await row.query_selector("a")
|
||||||
|
href = await row_link.get_attribute("href") if row_link else None
|
||||||
|
|
||||||
|
parsed_appeals.append({
|
||||||
|
"row_index": index,
|
||||||
|
"data": row_data,
|
||||||
|
"href": href
|
||||||
|
})
|
||||||
|
|
||||||
|
with open(os.path.join(EXPORT_DIR, "parsed_table.json"), "w", encoding="utf-8") as f:
|
||||||
|
json.dump(parsed_appeals, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
# Loop through found details pages and download documents
|
||||||
|
count = 0
|
||||||
|
processed_hrefs = set()
|
||||||
|
|
||||||
|
# Collect actual detail pages
|
||||||
|
detail_hrefs = []
|
||||||
|
for appeal in parsed_appeals:
|
||||||
|
href = appeal.get("href")
|
||||||
|
if href and href not in processed_hrefs:
|
||||||
|
detail_hrefs.append(href)
|
||||||
|
processed_hrefs.add(href)
|
||||||
|
|
||||||
|
# Also check all links for hrefs like #/appeals/view/ or similar
|
||||||
|
for link in links:
|
||||||
|
href = link["href"]
|
||||||
|
if href and href not in processed_hrefs:
|
||||||
|
detail_hrefs.append(href)
|
||||||
|
processed_hrefs.add(href)
|
||||||
|
|
||||||
|
logger.info(f"Unique detail pages to process: {detail_hrefs}")
|
||||||
|
|
||||||
|
for href in detail_hrefs[:10]: # Safe limit to top 10 appeals in recon mode
|
||||||
|
try:
|
||||||
|
full_url = href
|
||||||
|
if href.startswith("#"):
|
||||||
|
full_url = f"https://ej.sudrf.ru/{href}"
|
||||||
|
elif href.startswith("/"):
|
||||||
|
full_url = f"https://ej.sudrf.ru{href}"
|
||||||
|
|
||||||
|
logger.info(f"Navigating to detail page: {full_url}")
|
||||||
|
await page.goto(full_url)
|
||||||
|
await asyncio.sleep(8) # Wait for page load
|
||||||
|
|
||||||
|
safe_id = "".join([c for c in href if c.isalnum() or c in ("-", "_")]).strip()
|
||||||
|
appeal_dir = os.path.join(EXPORT_DIR, f"appeal_{safe_id}")
|
||||||
|
os.makedirs(appeal_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Take detail screenshot
|
||||||
|
detail_screenshot = os.path.join(appeal_dir, "evidence.png")
|
||||||
|
await page.screenshot(path=detail_screenshot, full_page=True)
|
||||||
|
|
||||||
|
# Dump detail HTML
|
||||||
|
detail_html = os.path.join(appeal_dir, "page.html")
|
||||||
|
with open(detail_html, "w", encoding="utf-8") as f:
|
||||||
|
f.write(await page.content())
|
||||||
|
|
||||||
|
# Look for download links
|
||||||
|
download_selectors = [
|
||||||
|
"a[href*='download']",
|
||||||
|
"a[href*='file']",
|
||||||
|
"button:has-text('Скачать')",
|
||||||
|
"a:has-text('Скачать')"
|
||||||
|
]
|
||||||
|
|
||||||
|
files_downloaded = []
|
||||||
|
for selector in download_selectors:
|
||||||
|
elements = await page.locator(selector).all()
|
||||||
|
for el in elements:
|
||||||
|
if not await el.is_visible(): continue
|
||||||
|
try:
|
||||||
|
async with page.expect_download(timeout=15000) as di:
|
||||||
|
await el.click(force=True)
|
||||||
|
download = await di.value
|
||||||
|
fname = download.suggested_filename
|
||||||
|
dest = os.path.join(appeal_dir, fname)
|
||||||
|
await download.save_as(dest)
|
||||||
|
files_downloaded.append(fname)
|
||||||
|
logger.info(f"Downloaded: {fname}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed download click: {e}")
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
send_tg(f"📥 **Обращение {count} собрано**\nПапка: `appeal_{safe_id}`\nФайлов скачано: {len(files_downloaded)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing appeal details {href}: {e}")
|
||||||
|
|
||||||
|
send_tg(f"🏁 **Миссия ГАС Правосудие завершена!** Собрано обращений: {count}. Запускаю сборку в PDF.")
|
||||||
|
|
||||||
|
# Trigger PDF compilation script
|
||||||
|
import subprocess
|
||||||
|
subprocess.Popen(["python3", "/app/sudrf_pdf_compiler.py"], start_new_session=True)
|
||||||
|
|
||||||
|
await context.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(run_mission())
|
||||||
22
web/app.py
22
web/app.py
|
|
@ -884,6 +884,28 @@ async def api_gis_status(username: str = Depends(get_current_admin)):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JSONResponse(status_code=500, content={"message": str(e)})
|
return JSONResponse(status_code=500, content={"message": str(e)})
|
||||||
|
|
||||||
|
@app.post("/api/infra/run_sudrf_parser")
|
||||||
|
async def api_run_sudrf_parser(username: str = Depends(get_current_admin)):
|
||||||
|
try:
|
||||||
|
subprocess.run(["docker", "exec", "lkm37-gis-scraper", "pkill", "-f", "sudrf_reaper.py"])
|
||||||
|
subprocess.Popen(["docker", "exec", "-d", "lkm37-gis-scraper", "python3", "/app/sudrf_reaper.py"])
|
||||||
|
return JSONResponse(content={"message": "Сессия ГАС Правосудие успешно запущена. Перейдите по ссылке VNC и авторизуйтесь."})
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse(status_code=500, content={"message": str(e)})
|
||||||
|
|
||||||
|
@app.get("/api/infra/sudrf_status")
|
||||||
|
async def api_sudrf_status(username: str = Depends(get_current_admin)):
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(["docker", "exec", "lkm37-gis-scraper", "ps", "aux"], capture_output=True, text=True)
|
||||||
|
is_alive = "sudrf_reaper.py" in proc.stdout
|
||||||
|
log_proc = subprocess.run(["docker", "exec", "lkm37-gis-scraper", "tail", "-n", "30", "/app/logs/sudrf.log"], capture_output=True, text=True)
|
||||||
|
return JSONResponse(content={
|
||||||
|
"alive": is_alive,
|
||||||
|
"log": log_proc.stdout or "Log is empty or file not found."
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse(status_code=500, content={"message": str(e)})
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card bg-dark border-danger">
|
||||||
|
<div class="card-header text-danger">ГАС ПРАВОСУДИЕ (СУДЕБНЫЙ АРХИВ)</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<button id="runSudrfParserBtn" class="btn btn-outline-danger flex-grow-1 me-2">
|
||||||
|
<i class="bi bi-play-circle"></i> ЗАПУСТИТЬ ЖАТВУ СУДОВ
|
||||||
|
</button>
|
||||||
|
<div id="sudrfStatusBadge" class="badge bg-secondary p-2">СТАТУС: НЕИЗВЕСТНО</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<a href="http://192.168.10.116:6080/vnc.html" target="_blank" class="btn btn-outline-info w-100">
|
||||||
|
<i class="bi bi-window"></i> ОТКРЫТЬ VNC БРАУЗЕР (ДЛЯ ЕСИА)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2 d-flex justify-content-between">
|
||||||
|
<span class="small text-muted">ПОСЛЕДНИЕ ЛОГИ:</span>
|
||||||
|
<button id="refreshSudrfStatusBtn" class="btn btn-sm btn-link p-0 text-danger">ОБНОВИТЬ СТАТУС</button>
|
||||||
|
</div>
|
||||||
|
<pre id="sudrfLogs" class="bg-black text-danger p-2 small border border-secondary" style="height: 200px; overflow-y: scroll; font-size: 0.7rem;"></pre>
|
||||||
|
<div class="small opacity-50 mt-2">Выполняет: Открытие сессии -> Ввод 2FA пользователем -> Парсинг ej.sudrf.ru -> Сборка PDF -> Google Drive</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card bg-dark border-warning">
|
<div class="card bg-dark border-warning">
|
||||||
<div class="card-header text-warning">ДЕЙСТВИЯ</div>
|
<div class="card-header text-warning">ДЕЙСТВИЯ</div>
|
||||||
|
|
@ -155,9 +179,62 @@ async function refreshGisStatus() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.getElementById('runSudrfParserBtn').addEventListener('click', async function() {
|
||||||
|
const btn = this;
|
||||||
|
if (!confirm('Запустить сборку судебного архива ГАС Правосудие? Потребуется войти через Госуслуги во VNC.')) return;
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> СЕССИЯ СУДОВ...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/infra/run_sudrf_parser', { method: 'POST' });
|
||||||
|
const result = await response.json();
|
||||||
|
alert(result.message);
|
||||||
|
} catch (e) {
|
||||||
|
alert('Ошибка: ' + e);
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = '<i class="bi bi-play-circle"></i> ЗАПУСТИТЬ ЖАТВУ СУДОВ';
|
||||||
|
refreshSudrfStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function refreshSudrfStatus() {
|
||||||
|
const badge = document.getElementById('sudrfStatusBadge');
|
||||||
|
const logs = document.getElementById('sudrfLogs');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/infra/sudrf_status');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.alive) {
|
||||||
|
badge.className = 'badge bg-success p-2';
|
||||||
|
badge.innerText = 'СТАТУС: РАБОТАЕТ';
|
||||||
|
} else {
|
||||||
|
badge.className = 'badge bg-danger p-2';
|
||||||
|
badge.innerText = 'СТАТУС: ОСТАНОВЛЕН';
|
||||||
|
}
|
||||||
|
|
||||||
|
logs.innerText = data.log;
|
||||||
|
logs.scrollTop = logs.scrollHeight;
|
||||||
|
} catch (e) {
|
||||||
|
badge.className = 'badge bg-warning p-2';
|
||||||
|
badge.innerText = 'ОШИБКА API';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('refreshGisStatusBtn').addEventListener('click', refreshGisStatus);
|
document.getElementById('refreshGisStatusBtn').addEventListener('click', refreshGisStatus);
|
||||||
window.addEventListener('load', refreshGisStatus);
|
document.getElementById('refreshSudrfStatusBtn').addEventListener('click', refreshSudrfStatus);
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
refreshGisStatus();
|
||||||
|
refreshSudrfStatus();
|
||||||
|
});
|
||||||
|
|
||||||
// Авто-обновление раз в 30 секунд
|
// Авто-обновление раз в 30 секунд
|
||||||
setInterval(refreshGisStatus, 30000);
|
setInterval(() => {
|
||||||
|
refreshGisStatus();
|
||||||
|
refreshSudrfStatus();
|
||||||
|
}, 30000);
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue