docs: свежие скриншоты дашборда — находки с дедупом, вкладка free-API панелей (131/175), реестр 3815; tools/shots.py с проверкой на секреты в кадре
10
README.md
@@ -58,7 +58,7 @@ python -m omni serve --port 8077
|
|||||||
|
|
||||||
## Скриншоты панели
|
## Скриншоты панели
|
||||||
|
|
||||||
Снято с живого инстанса (`python -m omni serve --port 8077`), база на момент съёмки: **46 423 записи · 3 815 источников · 6 746 находок**. Пересъёмка одной командой: `python tools/shots_ui.py` (нужен playwright, ждёт исчезновения скелетонов и только потом стреляет).
|
Снято с живого инстанса (`python -m omni serve --port 8077`), база на момент съёмки: **46 423 записи · 3 815 источников · 6 746 находок**. Пересъёмка одной командой: `python tools/shots.py` (нужен playwright, ждёт исчезновения скелетонов и только потом стреляет).
|
||||||
|
|
||||||
### Лента
|
### Лента
|
||||||
|
|
||||||
@@ -68,10 +68,16 @@ python -m omni serve --port 8077
|
|||||||
|
|
||||||
### Находки
|
### Находки
|
||||||
|
|
||||||
Промо, триалы, сливы и панели с бесплатными API: 6 746 находок, 1 368 уникальных, фильтр по тегам (`ключи / промо / триалы / сливы / кредиты / инвайты / lifetime deal`).
|
Промо, триалы, сливы и панели с бесплатными API: 6 746 находок, 5 182 уникальных — перепечатки одного поста из разных каналов схлопнуты по подписи, в карточке видно сколько копий склеено. Фильтр по тегам (`ключи / промо / триалы / сливы / кредиты / инвайты / lifetime deal`).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Бесплатные API-панели
|
||||||
|
|
||||||
|
Отдельная вкладка в «Находках»: new-api шлюзы, у которых открыт бесплатный доступ к моделям. 175 панелей в реестре, 131 с бесплатными моделями, проверка идёт через публичный `/api/pricing` — учётные данные не запрашиваются.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Источники
|
### Источники
|
||||||
|
|
||||||
Реестр на 3 815 источников: тумблер вкл/выкл, результат проверки, число записей в базе, дата последней проверки, добавление своего фида и health-аудит по категории.
|
Реестр на 3 815 источников: тумблер вкл/выкл, результат проверки, число записей в базе, дата последней проверки, добавление своего фида и health-аудит по категории.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 533 KiB After Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 389 KiB After Width: | Height: | Size: 199 KiB |
BIN
docs/screenshots/panels.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 286 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 216 KiB |
56
tools/shots.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
"""Пересъёмка скриншотов дашборда для README.
|
||||||
|
|
||||||
|
Запуск: python tools/shots.py
|
||||||
|
Пишет docs/screenshots/*.png с живого http://127.0.0.1:8078.
|
||||||
|
Перед сохранением проверяет кадр на токеноподобные строки —
|
||||||
|
скриншот с секретом в реестр не попадёт.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from camoufox.sync_api import Camoufox
|
||||||
|
|
||||||
|
BASE = "http://127.0.0.1:8078"
|
||||||
|
SHOTS = [
|
||||||
|
("/#/", "overview.png"),
|
||||||
|
("/#/feed", "feed.png"),
|
||||||
|
("/#/abuse", "abuse.png"),
|
||||||
|
("/#/abuse?tab=panels", "panels.png"),
|
||||||
|
("/#/sources", "sources.png"),
|
||||||
|
("/#/tg", "tg.png"),
|
||||||
|
("/#/watch", "watch.png"),
|
||||||
|
("/#/digest", "digest.png"),
|
||||||
|
("/#/runs", "runs.png"),
|
||||||
|
]
|
||||||
|
SECRET = re.compile(r"\b(sk-|pk-|ek_live_|ghp_|xoxb-)[A-Za-z0-9_\-]{12,}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
bad = []
|
||||||
|
with Camoufox(headless=True, geoip=False, humanize=True) as b:
|
||||||
|
for route, name in SHOTS:
|
||||||
|
p = b.new_page(viewport={"width": 1500, "height": 1350})
|
||||||
|
p.goto(BASE + route, wait_until="domcontentloaded", timeout=90000)
|
||||||
|
n = 0
|
||||||
|
for _ in range(12):
|
||||||
|
p.wait_for_timeout(2500)
|
||||||
|
n = p.evaluate("document.querySelectorAll('.item,tbody tr').length")
|
||||||
|
head = p.evaluate("document.body.innerText.slice(0,400)")
|
||||||
|
if n > 3 and "Загрузка" not in head:
|
||||||
|
break
|
||||||
|
txt = p.evaluate("document.body.innerText")
|
||||||
|
if SECRET.search(txt):
|
||||||
|
bad.append(name)
|
||||||
|
print("SKIP (секрет в кадре):", name)
|
||||||
|
p.close()
|
||||||
|
continue
|
||||||
|
p.screenshot(path="docs/screenshots/" + name)
|
||||||
|
print("%-12s %-22s items/rows %s" % (name, route, n))
|
||||||
|
p.close()
|
||||||
|
if bad:
|
||||||
|
print("не сняты из-за секретов:", bad)
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||