front: автоматическая генерация скриншотов скриптом

This commit is contained in:
2025-12-01 16:17:59 +03:00
parent 3bc726535e
commit 92120362a3
22 changed files with 255 additions and 4 deletions

2
.gitignore vendored
View File

@@ -7,7 +7,7 @@ dh.pem
/web-action /web-action
# эти файлы после генерации должны быть перемещены в `/static` # эти файлы после генерации должны быть перемещены в `/static`
front-generator/main-*.html front-generator/out
# логи сервера в релизной версии # логи сервера в релизной версии
http_server_*.log http_server_*.log

View File

@@ -2,6 +2,14 @@
Сервис, запускаемый на терминале как веб-морда. Сервис, запускаемый на терминале как веб-морда.
# Превью (NEW)
По многочисленным просьбам работающих, была создана новая фича - автоматическая генерация скриншотов для всех типов веб-сервера.
Вот готовые страницы со скриншотами:
* [SCPC](preview/scpc.md)
* [ШПС](preview/shps.md)
* [TDMA](preview/tdma.md)
# Зависимости # Зависимости
По идее только `boost` и `ssl` По идее только `boost` и `ssl`

View File

@@ -0,0 +1,9 @@
# Terminal web server - front generator
Эта часть является подпроектом для веб-сервера и позволяет "на коленке" портировать/изменять веб-сервер для абонентских модемов, и терминалов.
Скрипт [render.py](render.py) нужен для генерации фронтенда, скрипт [generate-screens.py](generate-screens.py) для генерации скриншотов.

View File

@@ -0,0 +1,148 @@
import subprocess
import pathlib
import time
import socket
import os
import json
from selenium import webdriver
from selenium.webdriver.common.by import By
BASE = pathlib.Path(__file__).parent
CONFIG_DIR = BASE / "config"
MODEM_TYPES = {}
for _fname in os.listdir(CONFIG_DIR):
if not _fname.endswith(".json"):
continue
path = os.path.join(CONFIG_DIR, _fname)
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
print(f"[WARN] Failed to read {path}: {e}")
continue
modem_type = data.get("modem_type")
if modem_type is None:
print(f"[WARN] {_fname} ignored: no 'modem_type' field")
continue
MODEM_TYPES[modem_type] = data
BASE_URL = "http://localhost:8080/"
SCREEN_WIDTH = 1200
LOGIN = {
"username": "admin",
"password": "admin",
}
def wait_port(port=8080, host="127.0.0.1"):
for _ in range(100):
try:
socket.create_connection((host, port), timeout=1)
return True
except:
time.sleep(0.1)
raise RuntimeError("TCP port 8080 did not open in time")
def run_server(modem, build):
binary = BASE / f"../cmake-build-{build}-{modem}/terminal-web-server"
proc = subprocess.Popen([
binary,
"nossl",
"0.0.0.0",
"8080",
"../static"
])
return proc
def create_driver():
opts = webdriver.FirefoxOptions()
serv = webdriver.FirefoxService(executable_path='/snap/bin/geckodriver')
driver = webdriver.Firefox(options=opts, service=serv)
driver.set_window_size(SCREEN_WIDTH, 1000)
driver.set_page_load_timeout(15)
return driver
def login(driver):
driver.get("http://localhost:8080/#")
# Проверяем, редиректнуло ли на /login
if "/login" in driver.current_url:
# Ищем поля
user = driver.find_element(By.NAME, "username")
pwd = driver.find_element(By.NAME, "password")
user.clear()
pwd.clear()
user.send_keys(LOGIN["username"])
pwd.send_keys(LOGIN["password"])
# submit
btn = driver.find_element(By.ID, "submit")
btn.click()
time.sleep(1)
# Проверка успешного входа
if "/login" in driver.current_url:
raise RuntimeError("Login failed: still on /login after submit")
def make_screenshots_or_checks(modem):
out_dir = BASE / "out" / modem
out_dir.mkdir(parents=True, exist_ok=True)
driver = create_driver()
try:
driver.get("http://localhost:8080/login")
driver.save_screenshot(str(out_dir / f"login.png"))
login(driver)
time.sleep(2)
for tab in MODEM_TYPES[modem]["tabs"]:
tab_name = tab["name"]
driver.find_element(By.CSS_SELECTOR, f'a[href="#{tab_name}"]').click()
# Проверка, что body загрузилось
time.sleep(1)
page_height = driver.execute_script("return document.body.scrollHeight")
driver.set_window_size(SCREEN_WIDTH, page_height + 200)
time.sleep(1)
driver.find_element(By.TAG_NAME, "body") # гарантирует что DOM есть
driver.save_screenshot(str(out_dir / f"{tab_name}.png"))
finally:
driver.quit()
def main():
for mt in MODEM_TYPES:
build = "debug"
print(f"\n=== {mt} ({build}) ===")
proc = run_server(mt, build)
try:
wait_port()
make_screenshots_or_checks(mt)
finally:
proc.terminate()
proc.wait()
print("Done.")
print("\nAll configurations processed.")
if __name__ == "__main__":
main()

View File

@@ -123,16 +123,27 @@ def render_modem(modem):
loader = FileSystemLoader('template') loader = FileSystemLoader('template')
env = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True) env = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
template = env.get_template('main.html') template = env.get_template('main.html')
context = build_modem_env(modem) context = build_modem_env(modem)
with open(f"main-{modem}.html", "w") as f: with open(f"out/main-{modem}.html", "w") as f:
f.write(template.render(context))
def render_modem_preview(modem):
loader = FileSystemLoader('template')
env = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True)
template = env.get_template('modem-preview.md')
context = build_modem_env(modem)
with open(f"out/{modem}.md", "w") as f:
f.write(template.render(context)) f.write(template.render(context))
if __name__ == '__main__': if __name__ == '__main__':
os.makedirs('out', exist_ok=True)
for mt in MODEM_TYPES: for mt in MODEM_TYPES:
print(f'Generating {mt} modem...') print(f'Generating {mt} modem...')
render_modem(mt) render_modem(mt)
os.system(f'cp -u main-{mt}.html ../static') render_modem_preview(mt)
os.system(f'cp -u out/main-{mt}.html ../static')
os.system(f'cp -u out/{mt}.md ../preview')

View File

@@ -0,0 +1,5 @@
jinja2
selenium
selenium-firefox
requests
brotli

View File

@@ -0,0 +1,13 @@
# {{ modem_name }}
## Страница входа
На эту страницу пользователь попадет при попытке зайти в веб-интерфейс модема
![login.png](images/login.png)
{% for tab in header_tabs %}
## Страница `{{ tab.desc }}`
![internet.jpg](images/{{ modem }}/{{ tab.name }}.png)
{% endfor %}

BIN
preview/images/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 KiB

BIN
preview/images/scpc/qos.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

20
preview/scpc.md Normal file
View File

@@ -0,0 +1,20 @@
# RSCM-201
## Страница входа
На эту страницу пользователь попадет при попытке зайти в веб-интерфейс модема
![login.png](images/login.png)
## Страница `Мониторинг`
![internet.jpg](images/scpc/monitoring.png)
## Страница `Настройки`
![internet.jpg](images/scpc/setup.png)
## Страница `QoS`
![internet.jpg](images/scpc/qos.png)
## Страница `Администрирование`
![internet.jpg](images/scpc/admin.png)

17
preview/shps.md Normal file
View File

@@ -0,0 +1,17 @@
# ШПС Модем
## Страница входа
На эту страницу пользователь попадет при попытке зайти в веб-интерфейс модема
![login.png](images/login.png)
## Страница `Мониторинг`
![internet.jpg](images/shps/monitoring.png)
## Страница `Настройки`
![internet.jpg](images/shps/setup.png)
## Страница `Администрирование`
![internet.jpg](images/shps/admin.png)

20
preview/tdma.md Normal file
View File

@@ -0,0 +1,20 @@
# VSAT Модем
## Страница входа
На эту страницу пользователь попадет при попытке зайти в веб-интерфейс модема
![login.png](images/login.png)
## Страница `Мониторинг`
![internet.jpg](images/tdma/monitoring.png)
## Страница `Настройки`
![internet.jpg](images/tdma/setup.png)
## Страница `Администрирование`
![internet.jpg](images/tdma/admin.png)
## Страница `Журнал`
![internet.jpg](images/tdma/logs.png)