# -*- coding: utf-8 -*- """生成 Web 终端外观的离线预览页(用真实 app.css,不手抄样式)。 用途:终端样式(滚动条 / 自适应高度 / 栏内按钮)改了之后,想确认效果但 不方便在浏览器里连真机时用。它把 viewWebShell() 里的那段终端 DOM 原样抠出来, 填上假的回显内容,套上真的 web/app.css,产出一个能直接打开的 HTML。 刻意不手抄样式和 DOM:手抄的预览页改完样式就过期了,还会给出「看起来对了」 的假信心。这里两样都从源码里取,所以预览和线上永远一致。 用法: python3 devtools/preview-webshell.py # 写到 _preview-webshell.html python3 devtools/preview-webshell.py 输出路径 """ import io import os import re import sys HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) # 造一段"长回显":既要够长能把容器撑到滚动,又要像真的 shell 输出。 FAKE_SB = [ "root@debian-primaryrouter:~# ", "Linux debian-primaryrouter 6.1.0-18-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.90-1 x86_64", "", "The programs included with the Debian GNU/Linux system are free software;", "the exact distribution terms for each program are described in the", "individual files in /usr/share/doc/*/copyright.", "", "root@debian-primaryrouter:~# uname -a", "Linux debian-primaryrouter 6.1.0-18-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.90-1 x86_64 GNU/Linux", "root@debian-primaryrouter:~# free -h", " total used free shared buff/cache available", "Mem: 3.8Gi 401Mi 1.9Gi 1.0Mi 1.7Gi 3.3Gi", "Swap: 2.0Gi 0B 2.0Gi", "root@debian-primaryrouter:~# ls -la /etc/drouter/", "total 84", "drwxr-xr-x 3 root root 4096 Sep 30 21:04 .", "drwxr-xr-x 92 root root 4096 Sep 30 20:11 ..", "-rw-r--r-- 1 root root 1182 Sep 30 21:04 dcfg.json", "-rw-r--r-- 1 root root 412 Sep 30 20:11 nft_v4.conf", "-rw-r--r-- 1 root root 186 Sep 30 20:11 nft_v6.conf", "-rw------- 1 root root 64 Sep 30 21:02 web-port", "root@debian-primaryrouter:~# systemctl status drouter-shelld --no-pager", "\u25cf drouter-shelld.service - drouter Web 终端 PTY 会话守护", " Loaded: loaded (/etc/systemd/system/drouter-shelld.service; enabled)", " Active: active (running) since Wed 2026-10-01 01:24:00 CST", " Main PID: 301298 (python3)", " Tasks: 5 (limit: 4608)", " Memory: 13.8M", " CPU: 143ms", "root@debian-primaryrouter:~# nft list ruleset", "table inet drouter_fw {", " chain input {", " type filter hook input priority filter; policy drop;", " ct state established,related accept", " iif lo accept", " tcp dport 8443 accept", " }", "}", ] def load(rel): with io.open(os.path.join(ROOT, rel), encoding='utf-8') as f: return f.read() def term_markup(app_js): """从 viewWebShell() 里抠出 .term-wrap 那段 DOM,原样使用。""" start = app_js.index('